Spaces:
Running
Running
// _ _ | |
// __ _____ __ ___ ___ __ _| |_ ___ | |
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ | |
// \ V V / __/ (_| |\ V /| | (_| | || __/ | |
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| | |
// | |
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. | |
// | |
// CONTACT: [email protected] | |
// | |
package test | |
import ( | |
"bytes" | |
"fmt" | |
"io" | |
"net/http" | |
"testing" | |
"github.com/stretchr/testify/assert" | |
"github.com/stretchr/testify/require" | |
) | |
// This test makes sure that a malformed tx payload (possibly from a bad actor) | |
// can't crash - or possibly worse - deadlock Weaviate | |
// | |
// See https://github.com/weaviate/weaviate/issues/3401 for details | |
func TestCrashServerThroughInvalidTxPayloads(t *testing.T) { | |
tests := []struct { | |
name string | |
txId string | |
payload string | |
errMustContain string | |
}{ | |
{ | |
name: "add_class: empty class payload", | |
txId: "1", | |
payload: `{"id":"1", "type": "add_class", "payload":{}}`, | |
errMustContain: "class is nil", | |
}, | |
{ | |
name: "add_class: class set, but empty, no state", | |
txId: "2", | |
payload: `{"id":"2", "type": "add_class", "payload":{"class":{}}}`, | |
errMustContain: "state is nil", | |
}, | |
{ | |
name: "add_class: class set and valid, no state", | |
txId: "3", | |
payload: `{"id":"3", "type": "add_class", "payload":{"class":{"vectorIndexType":"hnsw","class":"Foo"}}}`, | |
errMustContain: "state is nil", | |
}, | |
{ | |
name: "add_class: class set, but empty, with state", | |
txId: "4", | |
payload: `{"id":"4", "type": "add_class", "payload":{"state":{},"class":{}}}`, | |
errMustContain: "unsupported vector index", | |
}, | |
{ | |
name: "update_class: empty class payload", | |
txId: "1", | |
payload: `{"id":"1", "type": "update_class", "payload":{}}`, | |
errMustContain: "class is nil", | |
}, | |
{ | |
name: "update_class: class set and valid, no state", | |
txId: "3", | |
payload: `{"id":"3", "type": "update_class", "payload":{"class":{"vectorIndexType":"hnsw","class":"FoobarBazzzar"}}}`, | |
}, | |
{ | |
name: "update_class: class set, but empty, with state", | |
txId: "4", | |
payload: `{"id":"4", "type": "update_class", "payload":{"state":{},"class":{}}}`, | |
errMustContain: "unsupported vector index", | |
}, | |
{ | |
name: "add_tenants: empty payload", | |
txId: "1", | |
payload: `{"id":"1", "type": "add_tenants", "payload":{}}`, | |
errMustContain: "not found", | |
}, | |
{ | |
name: "delete_tenants: malformed payload", | |
txId: "1", | |
payload: `{"id":"1", "type": "delete_tenants", "payload":7}`, | |
errMustContain: "invalid", | |
}, | |
{ | |
name: "delete_class: malformed payload", | |
txId: "2", | |
payload: `{"id":"2", "type": "delete_class", "payload":7}`, | |
errMustContain: "invalid", | |
}, | |
{ | |
name: "add_property: missing prop", | |
txId: "1", | |
payload: `{"id":"1", "type": "add_property", "payload":{"class":"Foo"}}`, | |
errMustContain: "property is nil", | |
}, | |
} | |
for _, test := range tests { | |
t.Run(test.name, func(t *testing.T) { | |
client := http.Client{} | |
// open tx | |
payload := []byte(test.payload) | |
req, err := http.NewRequest(http.MethodPost, "http://localhost:7101/schema/transactions/", bytes.NewReader(payload)) | |
require.NoError(t, err) | |
req.Header.Add("Content-Type", "application/json") | |
res, err := client.Do(req) | |
require.NoError(t, err) | |
defer res.Body.Close() | |
// try to commit tx | |
req, err = http.NewRequest(http.MethodPut, | |
fmt.Sprintf("http://localhost:7101/schema/transactions/%s/commit", test.txId), nil) | |
require.NoError(t, err) | |
res, err = client.Do(req) | |
require.NoError(t, err) | |
defer res.Body.Close() | |
assert.Greater(t, res.StatusCode, 399) | |
resBytes, _ := io.ReadAll(res.Body) | |
assert.Contains(t, string(resBytes), test.errMustContain) | |
// clean up tx (so next test doesn't have concurrent tx error) | |
req, err = http.NewRequest(http.MethodDelete, | |
fmt.Sprintf("http://localhost:7101/schema/transactions/%s", test.txId), nil) | |
require.NoError(t, err) | |
res, err = client.Do(req) | |
require.NoError(t, err) | |
defer res.Body.Close() | |
}) | |
} | |
} | |