github.com/weaviate/weaviate@v1.24.6/test/acceptance/schema/transactions_test.go (about)

     1  //                           _       _
     2  // __      _____  __ ___   ___  __ _| |_ ___
     3  // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
     4  //  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
     5  //   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
     6  //
     7  //  Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
     8  //
     9  //  CONTACT: hello@weaviate.io
    10  //
    11  
    12  package test
    13  
    14  import (
    15  	"bytes"
    16  	"fmt"
    17  	"io"
    18  	"net/http"
    19  	"testing"
    20  
    21  	"github.com/stretchr/testify/assert"
    22  	"github.com/stretchr/testify/require"
    23  )
    24  
    25  // This test makes sure that a malformed tx payload (possibly from a bad actor)
    26  // can't crash - or possibly worse - deadlock Weaviate
    27  //
    28  // See https://github.com/weaviate/weaviate/issues/3401 for details
    29  func TestCrashServerThroughInvalidTxPayloads(t *testing.T) {
    30  	tests := []struct {
    31  		name           string
    32  		txId           string
    33  		payload        string
    34  		errMustContain string
    35  	}{
    36  		{
    37  			name:           "add_class: empty class payload",
    38  			txId:           "1",
    39  			payload:        `{"id":"1", "type": "add_class", "payload":{}}`,
    40  			errMustContain: "class is nil",
    41  		},
    42  		{
    43  			name:           "add_class: class set, but empty, no state",
    44  			txId:           "2",
    45  			payload:        `{"id":"2", "type": "add_class", "payload":{"class":{}}}`,
    46  			errMustContain: "state is nil",
    47  		},
    48  		{
    49  			name:           "add_class: class set and valid, no state",
    50  			txId:           "3",
    51  			payload:        `{"id":"3", "type": "add_class", "payload":{"class":{"vectorIndexType":"hnsw","class":"Foo"}}}`,
    52  			errMustContain: "state is nil",
    53  		},
    54  		{
    55  			name:           "add_class: class set, but empty, with state",
    56  			txId:           "4",
    57  			payload:        `{"id":"4", "type": "add_class", "payload":{"state":{},"class":{}}}`,
    58  			errMustContain: "unsupported vector index",
    59  		},
    60  		{
    61  			name:           "update_class: empty class payload",
    62  			txId:           "1",
    63  			payload:        `{"id":"1", "type": "update_class", "payload":{}}`,
    64  			errMustContain: "class is nil",
    65  		},
    66  		{
    67  			name:    "update_class: class set and valid, no state",
    68  			txId:    "3",
    69  			payload: `{"id":"3", "type": "update_class", "payload":{"class":{"vectorIndexType":"hnsw","class":"FoobarBazzzar"}}}`,
    70  		},
    71  		{
    72  			name:           "update_class: class set, but empty, with state",
    73  			txId:           "4",
    74  			payload:        `{"id":"4", "type": "update_class", "payload":{"state":{},"class":{}}}`,
    75  			errMustContain: "unsupported vector index",
    76  		},
    77  		{
    78  			name:           "add_tenants: empty payload",
    79  			txId:           "1",
    80  			payload:        `{"id":"1", "type": "add_tenants", "payload":{}}`,
    81  			errMustContain: "not found",
    82  		},
    83  		{
    84  			name:           "delete_tenants: malformed payload",
    85  			txId:           "1",
    86  			payload:        `{"id":"1", "type": "delete_tenants", "payload":7}`,
    87  			errMustContain: "invalid",
    88  		},
    89  		{
    90  			name:           "delete_class: malformed payload",
    91  			txId:           "2",
    92  			payload:        `{"id":"2", "type": "delete_class", "payload":7}`,
    93  			errMustContain: "invalid",
    94  		},
    95  		{
    96  			name:           "add_property: missing prop",
    97  			txId:           "1",
    98  			payload:        `{"id":"1", "type": "add_property", "payload":{"class":"Foo"}}`,
    99  			errMustContain: "property is nil",
   100  		},
   101  	}
   102  
   103  	for _, test := range tests {
   104  		t.Run(test.name, func(t *testing.T) {
   105  			client := http.Client{}
   106  
   107  			// open tx
   108  			payload := []byte(test.payload)
   109  			req, err := http.NewRequest(http.MethodPost, "http://localhost:7101/schema/transactions/", bytes.NewReader(payload))
   110  			require.NoError(t, err)
   111  
   112  			req.Header.Add("Content-Type", "application/json")
   113  
   114  			res, err := client.Do(req)
   115  			require.NoError(t, err)
   116  			defer res.Body.Close()
   117  
   118  			// try to commit tx
   119  			req, err = http.NewRequest(http.MethodPut,
   120  				fmt.Sprintf("http://localhost:7101/schema/transactions/%s/commit", test.txId), nil)
   121  			require.NoError(t, err)
   122  
   123  			res, err = client.Do(req)
   124  			require.NoError(t, err)
   125  			defer res.Body.Close()
   126  
   127  			assert.Greater(t, res.StatusCode, 399)
   128  			resBytes, _ := io.ReadAll(res.Body)
   129  			assert.Contains(t, string(resBytes), test.errMustContain)
   130  
   131  			// clean up tx (so next test doesn't have concurrent tx error)
   132  			req, err = http.NewRequest(http.MethodDelete,
   133  				fmt.Sprintf("http://localhost:7101/schema/transactions/%s", test.txId), nil)
   134  			require.NoError(t, err)
   135  
   136  			res, err = client.Do(req)
   137  			require.NoError(t, err)
   138  			defer res.Body.Close()
   139  		})
   140  	}
   141  }