From 993b3b37001f038e7c33ce7208524a9fd29d4144 Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Wed, 22 Jul 2026 16:09:21 +0200 Subject: [PATCH 01/22] feat(docs-ci): add Go v6 smoke test lane, tested module, and Go v6 language tab Bootstraps CI for the Go client (previously zero Go docs coverage), scope-capped to a smoke set: connect, create-collection, insert, basic fetch. - _includes/code/go-v6/: new Go module with // START/// END-marked *_test.go snippets for the four smoke ops, plus main.go anchor and shared helpers. Committed go.mod requires a placeholder version with no replace; the client is not on the module proxy yet, so CI redirects it to a fresh clone. - .github/workflows/docs_tests.yml: new test-go job mirroring test-java (setup-go, clone + go mod edit -replace + go mod tidy, pytest -m go, same handle-test-results wiring), a go_client_branch dispatch input defaulting to the branch tip, and a GO_VERSION env. Ollama/Keycloak steps omitted (the smoke set needs neither embeddings nor OIDC). - tests/test_go.py + go marker in pytest.ini: pytest wrapper shelling go test. - src/theme/Tabs/index.js: go6 LANGUAGE_CONFIG entry plus a go6<->go family fallback so a global "Go v6" selection degrades to the Go tab instead of a dead panel. FilteredTextBlock.js gains a matching go6 DOC_SYSTEMS entry. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01WmugebxuspdSQrFKE883W5 --- .github/workflows/docs_tests.yml | 133 +++++++++++ _includes/code/go-v6/.gitignore | 2 + _includes/code/go-v6/connect_test.go | 50 ++++ _includes/code/go-v6/go.mod | 46 ++++ _includes/code/go-v6/go.sum | 225 ++++++++++++++++++ _includes/code/go-v6/main.go | 9 + _includes/code/go-v6/main_test.go | 41 ++++ .../code/go-v6/manage_collections_test.go | 30 +++ _includes/code/go-v6/manage_objects_test.go | 36 +++ _includes/code/go-v6/search_basic_test.go | 37 +++ pytest.ini | 1 + .../Documentation/FilteredTextBlock.js | 6 + src/theme/Tabs/index.js | 12 + tests/test_go.py | 74 ++++++ 14 files changed, 702 insertions(+) create mode 100644 _includes/code/go-v6/.gitignore create mode 100644 _includes/code/go-v6/connect_test.go create mode 100644 _includes/code/go-v6/go.mod create mode 100644 _includes/code/go-v6/go.sum create mode 100644 _includes/code/go-v6/main.go create mode 100644 _includes/code/go-v6/main_test.go create mode 100644 _includes/code/go-v6/manage_collections_test.go create mode 100644 _includes/code/go-v6/manage_objects_test.go create mode 100644 _includes/code/go-v6/search_basic_test.go create mode 100644 tests/test_go.py diff --git a/.github/workflows/docs_tests.yml b/.github/workflows/docs_tests.yml index 443ec7dc..1d822f56 100644 --- a/.github/workflows/docs_tests.yml +++ b/.github/workflows/docs_tests.yml @@ -29,10 +29,15 @@ on: description: 'C# client branch (leave empty to use the workflow default below)' required: false default: '' + go_client_branch: + description: 'Go client branch (leave empty to use the workflow default below)' + required: false + default: '' env: # Centralize versions for easier maintenance PYTHON_VERSION: "3.10" + GO_VERSION: "1.25" WEAVIATE_VERSION: "1.35.0" OLLAMA_VERSION: "0.9.6" CLIP_MODEL_TAG: "sentence-transformers-clip-ViT-B-32" @@ -592,6 +597,134 @@ jobs: done docker system prune -f --volumes 2>/dev/null || true + test-go: + name: Test Go + runs-on: ubuntu-latest + timeout-minutes: 30 + + # Smoke lane for the Go v6 client (_includes/code/go-v6). Mirrors "Test Java" + # but intentionally omits the Ollama-model and Keycloak steps: the smoke set + # (connect / create-collection / insert / basic fetch) needs neither an + # embedding provider nor OIDC. Broaden this when the Go docs breadth lands. + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Free disk space + run: | + sudo rm -rf /usr/share/dotnet & + sudo rm -rf /opt/ghc & + sudo rm -rf /usr/local/share/boost & + sudo rm -rf "$AGENT_TOOLSDIRECTORY" & + sudo rm -rf /usr/local/lib/android & + wait + + - name: Set up test environment + uses: ./.github/actions/setup-test-env + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + cache-dependency-path: _includes/code/go-v6/go.sum + + - name: Clone Go client and wire replace + run: | + GO_BRANCH="${{ inputs.go_client_branch || 'v6' }}" + echo "๐Ÿ“ฆ Wiring Go client from branch: $GO_BRANCH" + # v6 is not published to the module proxy yet, so the committed go.mod + # requires a placeholder version and CI redirects it to a fresh clone. + git clone --depth 1 -b "$GO_BRANCH" https://github.com/weaviate/weaviate-go-client.git /tmp/go-client + cd _includes/code/go-v6 + go mod edit -replace github.com/weaviate/weaviate-go-client/v6=/tmp/go-client + go mod tidy + echo "โœ… Go client wired via replace directive" + + - name: Start services + run: | + echo "๐Ÿš€ Starting Weaviate and Ollama services..." + mkdir -p ~/.ollama + ./tests/start-weaviate.sh + + sleep 5 + echo "โœ… Services started:" + docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" + + - name: Wait for all services to be ready + run: | + echo "โณ Waiting for services to be ready..." + + declare -A services=( + ["8099"]="Weaviate Main" + ["8580"]="Weaviate RBAC" + ["8080"]="Weaviate Instance 1" + ["8090"]="Weaviate Instance 2" + ["8280"]="Weaviate Instance 3" + ["8180"]="Weaviate Instance 4" + ["8181"]="Weaviate Instance 5" + ["8182"]="Weaviate Instance 6" + ) + + for port in "${!services[@]}"; do + service_name="${services[$port]}" + echo -n "Checking $service_name (port $port)... " + + if timeout 60 bash -c "until curl -sf http://localhost:$port/v1/.well-known/ready &>/dev/null; do sleep 2; done"; then + echo "โœ… Ready" + else + echo "โš ๏ธ Not responding (may be optional)" + fi + done + + echo "โœ… Service check completed" + + - name: Record test start time + run: echo "TEST_START_TIME=$(date +%s)" >> $GITHUB_ENV + + - name: Run Go tests + id: run-tests + continue-on-error: true + env: + DOCS_GITHUB_ENV: "true" + WEAVIATE_URL: ${{ secrets.WEAVIATE_URL }} + WEAVIATE_HOST: ${{ secrets.WEAVIATE_HOST }} + WEAVIATE_HOSTNAME: ${{ secrets.WEAVIATE_HOSTNAME }} + WEAVIATE_HTTP_HOST: ${{ secrets.WEAVIATE_HTTP_HOST }} + WEAVIATE_GRPC_HOST: ${{ secrets.WEAVIATE_GRPC_HOST }} + WEAVIATE_API_KEY: ${{ secrets.WEAVIATE_API_KEY }} + WEAVIATE_LOCAL_API_KEY: ${{ secrets.WEAVIATE_LOCAL_API_KEY }} + run: | + echo "๐Ÿงช Running Go tests..." + uv run pytest -m "go" -v --tb=short --durations=10 -s --junitxml=pytest-results.xml + echo "โœ… Tests completed" + + - name: Handle test results + if: always() + uses: ./.github/actions/handle-test-results + with: + test-outcome: ${{ steps.run-tests.outcome }} + test-type: 'Go' + languages-tested: 'Go' + slack-bot-token: ${{ secrets.TESTING_CI_SLACK_BOT }} + + - name: Stop services + if: always() + run: | + echo "๐Ÿ›‘ Stopping services..." + ./tests/stop-weaviate.sh 2>/dev/null || echo "โš ๏ธ Some services may have already stopped" + + - name: Cleanup Docker resources + if: always() + run: | + for compose_file in tests/docker-compose*.yml; do + if [[ -f "$compose_file" ]]; then + docker compose -f "$compose_file" down -v --remove-orphans 2>/dev/null || true + fi + done + docker system prune -f --volumes 2>/dev/null || true + test-csharp: name: Test C# runs-on: ubuntu-latest diff --git a/_includes/code/go-v6/.gitignore b/_includes/code/go-v6/.gitignore new file mode 100644 index 00000000..a2de3a54 --- /dev/null +++ b/_includes/code/go-v6/.gitignore @@ -0,0 +1,2 @@ +# Compiled binary produced by `go build` (module basename). +/docs-go-v6 diff --git a/_includes/code/go-v6/connect_test.go b/_includes/code/go-v6/connect_test.go new file mode 100644 index 00000000..f813bb0c --- /dev/null +++ b/_includes/code/go-v6/connect_test.go @@ -0,0 +1,50 @@ +package main + +import ( + "context" + "os" + "testing" + + weaviate "github.com/weaviate/weaviate-go-client/v6" +) + +func TestConnectLocal(t *testing.T) { + ctx := context.Background() + + // START ConnectLocal + client, err := weaviate.NewLocal(ctx) + if err != nil { + t.Fatal(err) + } + defer client.Close() + // END ConnectLocal + + ready, err := client.IsReady(ctx) + if err != nil { + t.Fatal(err) + } + if !ready { + t.Fatal("weaviate is not ready") + } +} + +func TestConnectCloud(t *testing.T) { + host := os.Getenv("WEAVIATE_URL") + apiKey := os.Getenv("WEAVIATE_API_KEY") + if host == "" || apiKey == "" { + t.Skip("WEAVIATE_URL and WEAVIATE_API_KEY must be set for the cloud connection test") + } + ctx := context.Background() + + // START ConnectCloud + client, err := weaviate.NewWeaviateCloud(ctx, host, apiKey) + if err != nil { + t.Fatal(err) + } + defer client.Close() + // END ConnectCloud + + if _, err := client.IsReady(ctx); err != nil { + t.Fatal(err) + } +} diff --git a/_includes/code/go-v6/go.mod b/_includes/code/go-v6/go.mod new file mode 100644 index 00000000..148c15b9 --- /dev/null +++ b/_includes/code/go-v6/go.mod @@ -0,0 +1,46 @@ +module weaviate.io/docs-go-v6 + +go 1.25.8 + +// The v6 Go client is not yet published to the Go module proxy, so this module +// intentionally declares NO `replace` directive. The docs CI `test-go` job injects +// `go mod edit -replace github.com/weaviate/weaviate-go-client/v6=/tmp/go-client` +// from a fresh clone of the client's `v6` branch before building (mirrors the Java +// lane's unpublished-client approach). Do not add a replace directive here. +require github.com/weaviate/weaviate-go-client/v6 v6.0.0 + +require ( + cloud.google.com/go/compute/metadata v0.9.0 // indirect + github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect + github.com/getkin/kin-openapi v0.139.0 // indirect + github.com/go-openapi/jsonpointer v0.23.1 // indirect + github.com/go-openapi/swag/jsonname v0.26.0 // indirect + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.9.2 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/oapi-codegen/oapi-codegen/v2 v2.7.0 // indirect + github.com/oapi-codegen/runtime v1.4.1 // indirect + github.com/oasdiff/yaml v0.1.0 // indirect + github.com/oasdiff/yaml3 v0.0.13 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect + github.com/speakeasy-api/jsonpath v0.6.3 // indirect + github.com/speakeasy-api/openapi v1.23.0 // indirect + github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/mod v0.36.0 // indirect + golang.org/x/net v0.55.0 // indirect + golang.org/x/oauth2 v0.36.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/text v0.37.0 // indirect + golang.org/x/tools v0.45.0 // indirect + google.golang.org/api v0.282.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/grpc v1.81.1 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/_includes/code/go-v6/go.sum b/_includes/code/go-v6/go.sum new file mode 100644 index 00000000..330d5d60 --- /dev/null +++ b/_includes/code/go-v6/go.sum @@ -0,0 +1,225 @@ +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/getkin/kin-openapi v0.139.0 h1:pBFXcZJFwz9J1X64jzxlOoNgFm+TF7kNrs9+HJVN6Ic= +github.com/getkin/kin-openapi v0.139.0/go.mod h1:NGxPfE4PwS/TRXEbyx2RrxDFPZvxcWw31Tw8XXjPZLs= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4= +github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY= +github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w= +github.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M= +github.com/go-openapi/testify/v2 v2.4.2 h1:tiByHpvE9uHrrKjOszax7ZvKB7QOgizBWGBLuq0ePx4= +github.com/go-openapi/testify/v2 v2.4.2/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.9.2 h1:dX8U45hQsZpxd80nLvDGihsQ/OxlvTkVUXH2r/8cb2M= +github.com/mailru/easyjson v0.9.2/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oapi-codegen/oapi-codegen/v2 v2.7.0 h1:/8daqIYZfwnsHEAZdHUu9m0D5LA+5DoJCP7zLlT5Cs0= +github.com/oapi-codegen/oapi-codegen/v2 v2.7.0/go.mod h1:qzFy6iuobJw/hD1aRILee4G87/ShmhR0xYCwcUtZMCw= +github.com/oapi-codegen/runtime v1.4.1 h1:9nwLoI+KrWxzbBcp0jO/R8uXqbik/HUyCvPeU68Y/qo= +github.com/oapi-codegen/runtime v1.4.1/go.mod h1:GwV7hC2hviaMzj+ITfHVRESK5J2W/GefVwIND/bMGvU= +github.com/oasdiff/yaml v0.1.0 h1:0bqZjfKc/8S9urj4JuwepX41WX9EoA6ifhU3SV06cXg= +github.com/oasdiff/yaml v0.1.0/go.mod h1:kOlRmMdL2X3vucLCEQO5u61SU22RysnfXvcttrZA1O0= +github.com/oasdiff/yaml3 v0.0.13 h1:06svmvOHOVBqF81+sY2EUScvUI/iS/vl2VIeUUxZQwg= +github.com/oasdiff/yaml3 v0.0.13/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/speakeasy-api/jsonpath v0.6.3 h1:c+QPwzAOdrWvzycuc9HFsIZcxKIaWcNpC+xhOW9rJxU= +github.com/speakeasy-api/jsonpath v0.6.3/go.mod h1:2cXloNuQ+RSXi5HTRaeBh7JEmjRXTiaKpFTdZiL7URI= +github.com/speakeasy-api/openapi v1.23.0 h1:BlnHqUR/8uIs4tp3d2B4QDN03gw1/QY2R2MHg6fLhRU= +github.com/speakeasy-api/openapi v1.23.0/go.mod h1:Ih+ZzaTCCPyB2ykiDXaxhqk6jsY84ke3n5p6fobMDXk= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= +github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/api v0.282.0 h1:WmJiSVqUnKqJCpJOx7YADbXaC+9DDsnGSfllFSj7R2I= +google.golang.org/api v0.282.0/go.mod h1:6Wssta4c5n9qHq5CBhmlai5h/PUa1djdDAIhYEHyvcM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/_includes/code/go-v6/main.go b/_includes/code/go-v6/main.go new file mode 100644 index 00000000..68565d5f --- /dev/null +++ b/_includes/code/go-v6/main.go @@ -0,0 +1,9 @@ +// Package main holds tested code snippets for the Weaviate Go client (v6). +// +// The snippets live in the *_test.go files and are embedded into the docs via +// // START / // END comment pairs, mirroring the other +// _includes/code language modules. This file only exists so the package builds +// with `go build ./...`; the real work happens under `go test`. +package main + +func main() {} diff --git a/_includes/code/go-v6/main_test.go b/_includes/code/go-v6/main_test.go new file mode 100644 index 00000000..c22e4c7b --- /dev/null +++ b/_includes/code/go-v6/main_test.go @@ -0,0 +1,41 @@ +package main + +import ( + "context" + "testing" + + weaviate "github.com/weaviate/weaviate-go-client/v6" + "github.com/weaviate/weaviate-go-client/v6/collections" +) + +// connectLocal returns a client connected to a local Weaviate instance +// (localhost:8080 REST, localhost:50051 gRPC). This is the connection the +// docs test stack exposes; the insert and query smoke tests dial through it. +func connectLocal(t *testing.T) *weaviate.Client { + t.Helper() + ctx := context.Background() + client, err := weaviate.NewLocal(ctx) + if err != nil { + t.Fatalf("connect to local Weaviate: %v", err) + } + return client +} + +// setupArticle (re)creates a minimal Article collection used by the object and +// query smoke tests. It has no vectorizer, so objects are inserted with +// explicit properties only and read back with a plain fetch. +func setupArticle(t *testing.T, client *weaviate.Client) { + t.Helper() + ctx := context.Background() + // Start from a clean slate; ignore the error when the collection is absent. + _ = client.Collections.Delete(ctx, "Article") + if _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "Article", + Properties: []collections.Property{ + {Name: "title", DataType: collections.DataTypeText}, + {Name: "body", DataType: collections.DataTypeText}, + }, + }); err != nil { + t.Fatalf("create Article collection: %v", err) + } +} diff --git a/_includes/code/go-v6/manage_collections_test.go b/_includes/code/go-v6/manage_collections_test.go new file mode 100644 index 00000000..da58d685 --- /dev/null +++ b/_includes/code/go-v6/manage_collections_test.go @@ -0,0 +1,30 @@ +package main + +import ( + "context" + "testing" + + "github.com/weaviate/weaviate-go-client/v6/collections" +) + +func TestCreateCollection(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + _ = client.Collections.Delete(ctx, "Article") + defer client.Collections.Delete(ctx, "Article") + + // START CreateCollection + _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "Article", + Properties: []collections.Property{ + {Name: "title", DataType: collections.DataTypeText}, + {Name: "body", DataType: collections.DataTypeText}, + }, + }) + // END CreateCollection + if err != nil { + t.Fatal(err) + } +} diff --git a/_includes/code/go-v6/manage_objects_test.go b/_includes/code/go-v6/manage_objects_test.go new file mode 100644 index 00000000..37eda49d --- /dev/null +++ b/_includes/code/go-v6/manage_objects_test.go @@ -0,0 +1,36 @@ +package main + +import ( + "context" + "testing" + + "github.com/weaviate/weaviate-go-client/v6/data" +) + +func TestCreateObject(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + setupArticle(t, client) + defer client.Collections.Delete(ctx, "Article") + + articles := client.Collections.Use("Article") + + // START CreateObject + res, err := articles.Data.Insert(ctx, &data.Object{ + Properties: map[string]any{ + "title": "Weaviate Go v6", + "body": "A smoke-test object inserted by the docs test suite.", + }, + }) + // END CreateObject + if err != nil { + t.Fatal(err) + } + for id, msg := range res.Errors { + if msg != "" { + t.Fatalf("insert object %s failed: %s", id, msg) + } + } +} diff --git a/_includes/code/go-v6/search_basic_test.go b/_includes/code/go-v6/search_basic_test.go new file mode 100644 index 00000000..00e2a3a5 --- /dev/null +++ b/_includes/code/go-v6/search_basic_test.go @@ -0,0 +1,37 @@ +package main + +import ( + "context" + "testing" + + "github.com/weaviate/weaviate-go-client/v6/data" + "github.com/weaviate/weaviate-go-client/v6/query" +) + +func TestBasicQuery(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + setupArticle(t, client) + defer client.Collections.Delete(ctx, "Article") + + articles := client.Collections.Use("Article") + if _, err := articles.Data.Insert(ctx, &data.Object{ + Properties: map[string]any{"title": "Hello", "body": "World"}, + }); err != nil { + t.Fatal(err) + } + + // START BasicQuery + response, err := articles.Query.OverAll(ctx, query.OverAll{ + Limit: 2, + }) + // END BasicQuery + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } +} diff --git a/pytest.ini b/pytest.ini index e996fe6f..1c3fce25 100644 --- a/pytest.ini +++ b/pytest.ini @@ -9,6 +9,7 @@ markers = engram_python: group tests for Weaviate Engram using Python client ts: groups tests by language (typescript) java: groups tests by language (java client) + go: groups tests by language (go client) csharp: groups tests by language (c#) wcd: tests that use remote WCD clusters indexability: HTML structure tests for docs indexability (no API keys) diff --git a/src/components/Documentation/FilteredTextBlock.js b/src/components/Documentation/FilteredTextBlock.js index dee92267..0f31fb2d 100644 --- a/src/components/Documentation/FilteredTextBlock.js +++ b/src/components/Documentation/FilteredTextBlock.js @@ -39,6 +39,12 @@ DOC_SYSTEMS.tsindent = DOC_SYSTEMS.ts; DOC_SYSTEMS.js = DOC_SYSTEMS.ts; DOC_SYSTEMS.gonew = DOC_SYSTEMS.go; DOC_SYSTEMS.goraw = DOC_SYSTEMS.go; +DOC_SYSTEMS.go6 = { + baseUrl: + 'https://pkg.go.dev/github.com/weaviate/weaviate-go-client/v6', + constructUrl: (baseUrl, ref) => `${baseUrl}#${ref}`, + icon: '/img/site/logo-go.svg', +}; DOC_SYSTEMS.javaraw = DOC_SYSTEMS.java; DOC_SYSTEMS.csharpraw = DOC_SYSTEMS.csharp; diff --git a/src/theme/Tabs/index.js b/src/theme/Tabs/index.js index c08057a8..d383ff88 100644 --- a/src/theme/Tabs/index.js +++ b/src/theme/Tabs/index.js @@ -27,6 +27,7 @@ const LANGUAGE_CONFIG = { icon: "/img/site/logo-ts.svg", }, go: { label: "Go", icon: "/img/site/logo-go.svg" }, + go6: { label: "Go v6", icon: "/img/site/logo-go.svg" }, java: { label: "Java", icon: "/img/site/logo-java.svg" }, csharp: { label: "C#", icon: "/img/site/logo-csharp.svg" }, curl: { label: "Curl", icon: "/img/site/logo-curl.svg" }, @@ -204,6 +205,13 @@ const CodeDropdownTabs = ({ return "ts"; } + if (targetLang === "go6" && availableLangs.includes("go")) { + return "go"; + } + if (targetLang === "go" && availableLangs.includes("go6")) { + return "go6"; + } + return targetLang; } @@ -275,6 +283,10 @@ const CodeDropdownTabs = ({ availableLangs.includes("ts") ) { valueToSet = "ts"; + } else if (newGlobalLang === "go6" && availableLangs.includes("go")) { + valueToSet = "go"; + } else if (newGlobalLang === "go" && availableLangs.includes("go6")) { + valueToSet = "go6"; } } diff --git a/tests/test_go.py b/tests/test_go.py new file mode 100644 index 00000000..60bd620b --- /dev/null +++ b/tests/test_go.py @@ -0,0 +1,74 @@ +import subprocess +import pytest +import shlex +import os + + +GO_V6_CWD = "_includes/code/go-v6" + + +def run_go_test(run_pattern, empty_weaviates): + # -count=1 disables the test cache so each run actually hits Weaviate. + command = shlex.split(f"go test ./... -run {run_pattern} -v -count=1") + env = dict(os.environ) + + try: + result = subprocess.run( + command, cwd=GO_V6_CWD, env=env, + capture_output=True, text=True, check=True, + ) + except subprocess.CalledProcessError as error: + details = [f"Go tests matching {run_pattern!r} failed (exit code {error.returncode})"] + if error.stdout: + details.append(f"\n--- STDOUT (last 80 lines) ---\n{chr(10).join(error.stdout.splitlines()[-80:])}") + if error.stderr: + details.append(f"\n--- STDERR (last 40 lines) ---\n{chr(10).join(error.stderr.splitlines()[-40:])}") + pytest.fail("\n".join(details)) + + +# Smoke set for the Go v6 client. Each op is a standalone `go test` target so a +# failure points at the exact snippet. The cloud connection case skips itself +# when WEAVIATE_URL / WEAVIATE_API_KEY are unset; every other test dials the +# local docker stack on :8080 / :50051. +@pytest.mark.go +@pytest.mark.parametrize( + "run_pattern", + [ + "TestConnectLocal|TestConnectCloud", + ], +) +def test_connection(empty_weaviates, run_pattern): + run_go_test(run_pattern, empty_weaviates) + + +@pytest.mark.go +@pytest.mark.parametrize( + "run_pattern", + [ + "TestCreateCollection", + ], +) +def test_manage_collections(empty_weaviates, run_pattern): + run_go_test(run_pattern, empty_weaviates) + + +@pytest.mark.go +@pytest.mark.parametrize( + "run_pattern", + [ + "TestCreateObject", + ], +) +def test_manage_objects(empty_weaviates, run_pattern): + run_go_test(run_pattern, empty_weaviates) + + +@pytest.mark.go +@pytest.mark.parametrize( + "run_pattern", + [ + "TestBasicQuery", + ], +) +def test_search(empty_weaviates, run_pattern): + run_go_test(run_pattern, empty_weaviates) From 85a09a55b18a95c9e734a7c5c5ca13920c759fd6 Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Wed, 22 Jul 2026 18:19:18 +0200 Subject: [PATCH 02/22] docs(go-v6): add go6 tabs for connect + CRUD, v6 story on go.md (phase 3a) Focused v6 Go client rollout: document the implemented surface with tested snippets, Coming-soon placeholders only where a touched page needs a go6 tab for a not-yet-implemented feature. Snippets (tested module _includes/code/go-v6/, mirrors the java-v6 lane): - connect_test.go: NewLocal, custom host+port, local API key, local + cloud third-party header keys, NewWeaviateCloud, OIDC bearer token. - manage_objects_test.go: Data.Insert (create), full Data.Replace (update), Data.Delete (by id), Data.DeleteSelected (delete-many by filter); placeholders for partial merge and fetch-by-id. - cross_references_test.go: Data.AddReferences (one-way); placeholders for cross-reference delete and update. Docs: - client-libraries/go.md: :::info Go client v6 admonition + "What changed in the v6 client" section (language-agnostic prose, no version numbers, no em dashes). - connections/connect-local, connect-cloud, oidc-connect include: go6 tabs. - manage-objects/create, update, delete, read; manage-collections/cross-references: go6 tabs. Validated locally with the CI replace injected: go mod tidy && go vet ./... && go build ./... && go test -run '^$' ./... all pass; committed go.mod keeps require-no-replace. --- _includes/code/connections/oidc-connect.mdx | 9 + _includes/code/go-v6/connect_test.go | 155 +++++++++++++++- _includes/code/go-v6/cross_references_test.go | 106 +++++++++++ _includes/code/go-v6/go.mod | 7 +- _includes/code/go-v6/manage_objects_test.go | 167 +++++++++++++++++- docs/weaviate/client-libraries/go.md | 20 +++ docs/weaviate/connections/connect-cloud.mdx | 18 ++ docs/weaviate/connections/connect-local.mdx | 33 ++++ .../manage-collections/cross-references.mdx | 25 +++ docs/weaviate/manage-objects/create.mdx | 9 + docs/weaviate/manage-objects/delete.mdx | 17 ++ docs/weaviate/manage-objects/read.mdx | 9 + docs/weaviate/manage-objects/update.mdx | 17 ++ 13 files changed, 573 insertions(+), 19 deletions(-) create mode 100644 _includes/code/go-v6/cross_references_test.go diff --git a/_includes/code/connections/oidc-connect.mdx b/_includes/code/connections/oidc-connect.mdx index d05b5b98..789c2d92 100644 --- a/_includes/code/connections/oidc-connect.mdx +++ b/_includes/code/connections/oidc-connect.mdx @@ -5,6 +5,7 @@ import FilteredTextBlock from '@site/src/components/Documentation/FilteredTextBl import PyV4Code from '!!raw-loader!/_includes/code/connections/connect-python-v4.py'; import TsV3Code from '!!raw-loader!/_includes/code/connections/connect-ts-v3.ts'; import GoCode from '!!raw-loader!/_includes/code/connections/connect.go'; +import GoV6Code from '!!raw-loader!/_includes/code/go-v6/connect_test.go'; import JavaV6Code from '!!raw-loader!/_includes/code/java-v6/src/test/java/ConnectionTest.java'; import CSharpCode from '!!raw-loader!/_includes/code/csharp/ConnectionTest.cs'; @@ -33,6 +34,14 @@ import CSharpCode from '!!raw-loader!/_includes/code/csharp/ConnectionTest.cs'; language="go" /> + + + @@ -87,6 +88,15 @@ import HostnameWarning from "/_includes/wcs/hostname-warning.mdx"; /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Date: Wed, 22 Jul 2026 18:24:52 +0200 Subject: [PATCH 03/22] ci(docs): run only the Go job on the go-v6-scaffold branch --- .github/workflows/docs_tests.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/docs_tests.yml b/.github/workflows/docs_tests.yml index 1d822f56..4bba4c7a 100644 --- a/.github/workflows/docs_tests.yml +++ b/.github/workflows/docs_tests.yml @@ -46,6 +46,7 @@ env: jobs: test-agents: name: Test Agents + if: github.ref != 'refs/heads/docs/go-v6-scaffold' runs-on: ubuntu-latest timeout-minutes: 30 @@ -98,6 +99,7 @@ jobs: test-python: name: Test Python + if: github.ref != 'refs/heads/docs/go-v6-scaffold' runs-on: ubuntu-latest timeout-minutes: 30 @@ -261,6 +263,7 @@ jobs: test-typescript: name: Test TypeScript + if: github.ref != 'refs/heads/docs/go-v6-scaffold' runs-on: ubuntu-latest timeout-minutes: 30 @@ -431,6 +434,7 @@ jobs: test-java: name: Test Java + if: github.ref != 'refs/heads/docs/go-v6-scaffold' runs-on: ubuntu-latest timeout-minutes: 30 @@ -727,6 +731,7 @@ jobs: test-csharp: name: Test C# + if: github.ref != 'refs/heads/docs/go-v6-scaffold' runs-on: ubuntu-latest timeout-minutes: 30 From 9fe190ec7514b7032bd7ecc8637a5d0b67a72078 Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Wed, 22 Jul 2026 18:37:43 +0200 Subject: [PATCH 04/22] fix(docs): address gate feedback (go.md link, hide TODO from Coming-soon panels, go tab highlight) - go.md: fix broken how-to link ../../guides.mdx -> ../guides.mdx (target is docs/weaviate/guides.mdx; matches sibling csharp.mdx). - go-v6 placeholders: move the TODO[g-despot] lines above the START markers in all 4 Coming-soon blocks (manage_objects_test.go UpdateMerge/ReadObject, cross_references_test.go Delete Go/Update Go) so the rendered Go v6 tab shows only "// Coming soon"; TODOs stay in-file and greppable (java-v6 convention). - connect-cloud.mdx: fix syntax highlighting on the two v5 Go tabs (language="py" -> "go" for APIKeyWCD and ThirdPartyAPIKeys). Validated: go vet ./... && go build ./... pass with CI replace injected; committed go.mod keeps require-no-replace. All 4 placeholder START..END regions confirmed to contain only "// Coming soon". --- _includes/code/go-v6/cross_references_test.go | 6 ++---- _includes/code/go-v6/manage_objects_test.go | 6 ++---- docs/weaviate/client-libraries/go.md | 2 +- docs/weaviate/connections/connect-cloud.mdx | 4 ++-- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/_includes/code/go-v6/cross_references_test.go b/_includes/code/go-v6/cross_references_test.go index c38de788..b2adb36f 100644 --- a/_includes/code/go-v6/cross_references_test.go +++ b/_includes/code/go-v6/cross_references_test.go @@ -86,10 +86,9 @@ func TestAddOneWayCrossReference(t *testing.T) { func TestDeleteCrossReference(t *testing.T) { t.Skip("deleting a cross-reference is not yet available in the v6 Go client") + // TODO[g-despot]: cross-reference delete snippet pending v6 client support // Delete Go // Coming soon - // - // TODO[g-despot]: cross-reference delete snippet pending v6 client support // END Delete Go } @@ -98,9 +97,8 @@ func TestDeleteCrossReference(t *testing.T) { func TestUpdateCrossReference(t *testing.T) { t.Skip("updating a cross-reference is not yet available in the v6 Go client") + // TODO[g-despot]: cross-reference update snippet pending v6 client support // Update Go // Coming soon - // - // TODO[g-despot]: cross-reference update snippet pending v6 client support // END Update Go } diff --git a/_includes/code/go-v6/manage_objects_test.go b/_includes/code/go-v6/manage_objects_test.go index 729a3810..8907721a 100644 --- a/_includes/code/go-v6/manage_objects_test.go +++ b/_includes/code/go-v6/manage_objects_test.go @@ -105,10 +105,9 @@ func TestReplaceObject(t *testing.T) { func TestPartialUpdate(t *testing.T) { t.Skip("partial update (merge) is not yet available in the v6 Go client; use Data.Replace for a full update") + // TODO[g-despot]: partial update (merge) snippet pending v6 client support // START UpdateMerge // Coming soon - // - // TODO[g-despot]: partial update (merge) snippet pending v6 client support // END UpdateMerge } @@ -179,9 +178,8 @@ func TestDeleteMany(t *testing.T) { func TestReadObjectByID(t *testing.T) { t.Skip("fetch-object-by-id is not yet available in the v6 Go client") + // TODO[g-despot]: fetch-object-by-id snippet pending v6 client support // START ReadObject // Coming soon - // - // TODO[g-despot]: fetch-object-by-id snippet pending v6 client support // END ReadObject } diff --git a/docs/weaviate/client-libraries/go.md b/docs/weaviate/client-libraries/go.md index 83960681..5c7cdcde 100644 --- a/docs/weaviate/client-libraries/go.md +++ b/docs/weaviate/client-libraries/go.md @@ -228,7 +228,7 @@ The `v6` client is a ground-up redesign. The most visible changes are: - **Grouped sub-clients.** Cluster-wide concerns, such as collections, roles, users, backups, and replication, and per-collection concerns, such as data, query, aggregation, and tenants, are grouped under dedicated sub-clients. - **Typed results.** Query results can be decoded into your own types. -The `v6` client currently covers a focused subset of the full feature set. Where an operation is not yet available, the `Go v6` tab shows a short "Coming soon" note. To compare the two clients side by side, open the [connection](/weaviate/connections/index.mdx) and [how-to](../../guides.mdx) pages and switch between the `Go` and `Go v6` tabs. +The `v6` client currently covers a focused subset of the full feature set. Where an operation is not yet available, the `Go v6` tab shows a short "Coming soon" note. To compare the two clients side by side, open the [connection](/weaviate/connections/index.mdx) and [how-to](../guides.mdx) pages and switch between the `Go` and `Go v6` tabs. ## Design diff --git a/docs/weaviate/connections/connect-cloud.mdx b/docs/weaviate/connections/connect-cloud.mdx index 65f2fc84..093dcabc 100644 --- a/docs/weaviate/connections/connect-cloud.mdx +++ b/docs/weaviate/connections/connect-cloud.mdx @@ -84,7 +84,7 @@ import HostnameWarning from "/_includes/wcs/hostname-warning.mdx"; text={GoCode} startMarker="// START APIKeyWCD" endMarker="// END APIKeyWCD" - language="py" + language="go" /> @@ -149,7 +149,7 @@ If you use API-based models for vectorization or RAG, you must provide an API ke text={GoCode} startMarker="// START ThirdPartyAPIKeys" endMarker="// END ThirdPartyAPIKeys" - language="py" + language="go" /> From aa7fdc54032390d284537c385b3d1b53dfd125e1 Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Wed, 22 Jul 2026 21:05:22 +0200 Subject: [PATCH 05/22] docs(go-v6): add go6 tabs for search + aggregate (phase 3b) Add tested Go v6 snippets and parallel go/go6 tabs across the search and aggregate how-to pages, continuing the focused v6 rollout. Snippets (tested module _includes/code/go-v6/, compiled against v6 @f4fde3e): - search_basic_test.go: OverAll fetch variants (list, limit, offset, properties, vector, id, cross-refs, metadata, multi-tenancy). - search_test.go: NearText, NearVector, named-vector nearText, distance cutoff, limit/offset, autocut, group-by, filtered vector search. - hybrid_test.go: Hybrid basics, score, alpha, fusion, properties, property weighting, explicit vector, limit, autocut, filter. - filters_test.go: query/filter Cond/And/Or/Not, contains-any/all/none, like, reference-path, len, id/timestamp/null-state, near-text + filter. - aggregate_test.go: Aggregate.OverAll (count, text, integer, group-by) plus Aggregate.NearVector. - search_placeholders_test.go: Coming-soon placeholders (skip + marker) for near-object, geo filter, aggregation over near-text/hybrid, filtered aggregation. Docs: go6 tabs added beside the existing go tabs on search/basics, search/similarity, search/hybrid, search/filters, search/aggregate. Validated with the CI replace injected: go vet ./... && go build ./... && go test ./... -run '^$' all pass; committed go.mod keeps require-no-replace. --- _includes/code/go-v6/aggregate_test.go | 147 +++++++ _includes/code/go-v6/filters_test.go | 387 ++++++++++++++++++ _includes/code/go-v6/hybrid_test.go | 243 +++++++++++ _includes/code/go-v6/search_basic_test.go | 220 ++++++++++ .../code/go-v6/search_placeholders_test.go | 63 +++ _includes/code/go-v6/search_test.go | 296 ++++++++++++++ docs/weaviate/search/aggregate.md | 66 +++ docs/weaviate/search/basics.md | 73 ++++ docs/weaviate/search/filters.md | 122 ++++++ docs/weaviate/search/hybrid.md | 81 ++++ docs/weaviate/search/similarity.md | 74 ++++ 11 files changed, 1772 insertions(+) create mode 100644 _includes/code/go-v6/aggregate_test.go create mode 100644 _includes/code/go-v6/filters_test.go create mode 100644 _includes/code/go-v6/hybrid_test.go create mode 100644 _includes/code/go-v6/search_placeholders_test.go create mode 100644 _includes/code/go-v6/search_test.go diff --git a/_includes/code/go-v6/aggregate_test.go b/_includes/code/go-v6/aggregate_test.go new file mode 100644 index 00000000..a4a64e1e --- /dev/null +++ b/_includes/code/go-v6/aggregate_test.go @@ -0,0 +1,147 @@ +package main + +import ( + "context" + "testing" + + "github.com/weaviate/weaviate-go-client/v6/aggregate" + "github.com/weaviate/weaviate-go-client/v6/query" + "github.com/weaviate/weaviate-go-client/v6/types" +) + +// The aggregate snippets run against the seeded JeopardyQuestion collection, so +// they execute without an inference module. + +func TestAggregateMetaCount(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + setupJeopardySearch(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + + // START MetaCount + jeopardy := client.Collections.Use("JeopardyQuestion") + result, err := jeopardy.Aggregate.OverAll(ctx, aggregate.OverAll{ + TotalCount: true, + }) + if err != nil { + t.Fatal(err) + } + if result.TotalCount != nil { + t.Logf("object count: %d", *result.TotalCount) + } + // END MetaCount +} + +func TestAggregateTextProp(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + setupJeopardySearch(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + + // START TextProp + jeopardy := client.Collections.Use("JeopardyQuestion") + result, err := jeopardy.Aggregate.OverAll(ctx, aggregate.OverAll{ + Text: []aggregate.Text{ + {Property: "category", Count: true, TopOccurrences: true}, + }, + }) + if err != nil { + t.Fatal(err) + } + category := result.Text["category"] + for _, occ := range category.TopOccurrences { + t.Logf("%s occurs %d times", occ.Value, occ.OccursTimes) + } + // END TextProp +} + +func TestAggregateIntProp(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + setupJeopardySearch(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + + // START IntProp + jeopardy := client.Collections.Use("JeopardyQuestion") + result, err := jeopardy.Aggregate.OverAll(ctx, aggregate.OverAll{ + Integer: []aggregate.Integer{ + {Property: "points", Count: true, Sum: true, Min: true, Max: true, Mean: true}, + }, + }) + if err != nil { + t.Fatal(err) + } + points := result.Integer["points"] + if points.Sum != nil { + t.Logf("total points: %d", *points.Sum) + } + // END IntProp +} + +func TestAggregateGroupBy(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + setupJeopardySearch(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + + // START groupBy + jeopardy := client.Collections.Use("JeopardyQuestion") + result, err := jeopardy.Aggregate.OverAll.GroupBy(ctx, + aggregate.OverAll{ + Integer: []aggregate.Integer{ + {Property: "points", Count: true, Sum: true}, + }, + }, + aggregate.GroupBy{Property: "category", Limit: 10}, + ) + if err != nil { + t.Fatal(err) + } + for _, group := range result.Groups { + t.Logf("group %v", group.Value) + if points := group.Integer["points"]; points.Count != nil { + t.Logf(" count: %d", *points.Count) + } + } + // END groupBy +} + +// TestAggregateNearVector aggregates the objects returned by a vector search. +// This snippet is not yet wired into a docs page, but it exercises the +// implemented near-vector aggregation path. +func TestAggregateNearVector(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + setupJeopardySearch(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + + // START AggregateNearVector + vector := []float32{0.12, 0.20, 0.33} + + jeopardy := client.Collections.Use("JeopardyQuestion") + result, err := jeopardy.Aggregate.NearVector(ctx, aggregate.NearVector{ + Query: query.NearVector{ + Target: &types.Vector{Single: vector}, + Similarity: query.Distance(0.3), + }, + ObjectLimit: 10, + TotalCount: true, + }) + if err != nil { + t.Fatal(err) + } + if result.TotalCount != nil { + t.Logf("matched object count: %d", *result.TotalCount) + } + // END AggregateNearVector +} diff --git a/_includes/code/go-v6/filters_test.go b/_includes/code/go-v6/filters_test.go new file mode 100644 index 00000000..3690105d --- /dev/null +++ b/_includes/code/go-v6/filters_test.go @@ -0,0 +1,387 @@ +package main + +import ( + "context" + "testing" + "time" + + "github.com/weaviate/weaviate-go-client/v6/query" + "github.com/weaviate/weaviate-go-client/v6/query/filter" +) + +// The filter snippets read the JeopardyQuestion demo collection on a local +// instance. Each shows how to express one kind of filter with the query/filter +// package; the search itself is a plain fetch (Query.OverAll) so the filter is +// the only variable. + +func TestSingleFilter(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START SingleFilter + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ + Limit: 3, + Filter: &filter.Cond{ + Target: "round", + Operator: filter.Equal, + Value: "Double Jeopardy!", + }, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END SingleFilter +} + +func TestMultipleFiltersAnd(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START MultipleFiltersAnd + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ + Limit: 3, + Filter: filter.And{ + &filter.Cond{ + Target: "round", + Operator: filter.Equal, + Value: "Double Jeopardy!", + }, + &filter.Cond{ + Target: "points", + Operator: filter.LessThan, + Value: int64(600), + }, + }, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END MultipleFiltersAnd +} + +func TestMultipleFiltersNested(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START MultipleFiltersNested + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ + Limit: 3, + Filter: filter.And{ + &filter.Cond{ + Target: "answer", + Operator: filter.Like, + Value: "*nest*", + }, + filter.Or{ + &filter.Cond{ + Target: "points", + Operator: filter.GreaterThan, + Value: int64(700), + }, + &filter.Cond{ + Target: "points", + Operator: filter.LessThan, + Value: int64(300), + }, + }, + }, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END MultipleFiltersNested +} + +func TestContainsAnyFilter(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START ContainsAnyFilter + // The tokens to match against the tokenized "question" property. + tokens := []string{"animal", "elephant"} + + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ + Limit: 3, + Filter: &filter.Cond{ + Target: "question", + Operator: filter.ContainsAny, + Value: tokens, + }, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END ContainsAnyFilter +} + +func TestContainsAllFilter(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START ContainsAllFilter + tokens := []string{"blood", "glucose"} + + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ + Limit: 3, + Filter: &filter.Cond{ + Target: "question", + Operator: filter.ContainsAll, + Value: tokens, + }, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END ContainsAllFilter +} + +func TestContainsNoneFilter(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START ContainsNoneFilter + tokens := []string{"animal", "elephant"} + + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ + Limit: 3, + Filter: &filter.Cond{ + Target: "question", + Operator: filter.ContainsNone, + Value: tokens, + }, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END ContainsNoneFilter +} + +func TestLikeFilter(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START LikeFilter + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ + Limit: 3, + Filter: &filter.Cond{ + Target: "answer", + Operator: filter.Like, + // "*" matches zero or more characters, "?" matches exactly one. + Value: "*giraffe*", + }, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END LikeFilter +} + +func TestCrossReferenceFilter(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START CrossReferenceFilter + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ + Limit: 3, + Filter: &filter.Cond{ + // Build a path into the referenced collection with Reference(). + Target: filter.Reference("hasCategory").Property("title"), + Operator: filter.Like, + Value: "*Sport*", + }, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END CrossReferenceFilter +} + +func TestFilterByDate(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START FilterByDate + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ + Limit: 3, + Filter: &filter.Cond{ + Target: "dateRecorded", + Operator: filter.GreaterThan, + // Compare date properties against a time.Time value. + Value: time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC), + }, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END FilterByDate +} + +func TestFilterById(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START FilterById + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ + Filter: &filter.Cond{ + // filter.UUID targets the object's own id. + Target: filter.UUID, + Operator: filter.Equal, + Value: "00037775-1432-35e5-bc59-443baaef7d80", + }, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END FilterById +} + +func TestFilterByTimestamp(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START FilterByTimestamp + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ + Limit: 3, + Filter: &filter.Cond{ + // filter.CreatedAt and filter.LastUpdatedAt target internal timestamps. + Target: filter.CreatedAt, + Operator: filter.GreaterThanEqual, + Value: time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC), + }, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END FilterByTimestamp +} + +func TestFilterByPropertyLength(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START FilterByPropertyLength + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ + Limit: 3, + Filter: &filter.Cond{ + // filter.Len wraps a property in a len(property) target. + Target: filter.Len("answer"), + Operator: filter.GreaterThan, + Value: int64(20), + }, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END FilterByPropertyLength +} + +func TestFilterByPropertyNullState(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START FilterByPropertyNullState + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ + Limit: 3, + Filter: &filter.Cond{ + Target: "answer", + Operator: filter.IsNull, + Value: true, + }, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END FilterByPropertyNullState +} + +// TestNearTextWithFilter combines a semantic search with a filter. It queries a +// collection whose vectorizer turns the query text into a vector server-side. +func TestNearTextWithFilter(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START NearTextWithFilter + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.NearText(ctx, query.NearText{ + Concepts: []string{"large animals"}, + Limit: 2, + Filter: &filter.Cond{ + Target: "round", + Operator: filter.Equal, + Value: "Double Jeopardy!", + }, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END NearTextWithFilter +} diff --git a/_includes/code/go-v6/hybrid_test.go b/_includes/code/go-v6/hybrid_test.go new file mode 100644 index 00000000..79119bac --- /dev/null +++ b/_includes/code/go-v6/hybrid_test.go @@ -0,0 +1,243 @@ +package main + +import ( + "context" + "testing" + + "github.com/weaviate/weaviate-go-client/v6/query" + "github.com/weaviate/weaviate-go-client/v6/query/filter" + "github.com/weaviate/weaviate-go-client/v6/types" +) + +// The hybrid snippets query a collection whose vectorizer turns the query +// string into a vector server-side, mirroring the demo datasets used across the +// docs. They connect to a local instance and read the JeopardyQuestion demo +// collection. + +func TestHybridBasic(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START HybridBasic + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.Hybrid(ctx, query.Hybrid{ + Query: "food", + Limit: 3, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END HybridBasic +} + +func TestHybridWithScore(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START HybridWithScore + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.Hybrid(ctx, query.Hybrid{ + Query: "food", + Limit: 3, + ReturnMetadata: query.ReturnMetadata{ + Score: true, + ExplainScore: true, + }, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + if obj.Metadata.Score != nil { + t.Logf("score: %v", *obj.Metadata.Score) + } + if obj.Metadata.ExplainScore != nil { + t.Logf("explain: %v", *obj.Metadata.ExplainScore) + } + } + // END HybridWithScore +} + +func TestHybridWithAlpha(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START HybridWithAlpha + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.Hybrid(ctx, query.Hybrid{ + Query: "food", + // Alpha of 0 is pure keyword search, 1 is pure vector search. + Alpha: query.Alpha(0.25), + Limit: 3, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END HybridWithAlpha +} + +func TestHybridWithFusionType(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START HybridWithFusionType + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.Hybrid(ctx, query.Hybrid{ + Query: "food", + Fusion: query.HybridFusionRelativeScore, + Limit: 3, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END HybridWithFusionType +} + +func TestHybridWithProperties(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START HybridWithProperties + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.Hybrid(ctx, query.Hybrid{ + Query: "food", + // Restrict the keyword search to these properties. + QueryProperties: []string{"question", "answer"}, + Limit: 3, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END HybridWithProperties +} + +func TestHybridWithPropertyWeighting(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START HybridWithPropertyWeighting + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.Hybrid(ctx, query.Hybrid{ + Query: "food", + // Boost the "question" property with the ^ operator. + QueryProperties: []string{"question^2", "answer"}, + Limit: 3, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END HybridWithPropertyWeighting +} + +func TestHybridWithVector(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START HybridWithVector + // A query vector, for example an embedding produced by your model. + vector := []float32{0.12, 0.20, 0.33} + + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.Hybrid(ctx, query.Hybrid{ + Query: "food", + // Supply the vector for the vector-search half of the query. + NearVector: &query.NearVector{ + Target: &types.Vector{Single: vector}, + }, + Limit: 3, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END HybridWithVector +} + +func TestHybridLimit(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START HybridLimit + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.Hybrid(ctx, query.Hybrid{ + Query: "food", + Limit: 3, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END HybridLimit +} + +func TestHybridAutocut(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START HybridAutocut + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.Hybrid(ctx, query.Hybrid{ + Query: "food", + AutoLimit: 1, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END HybridAutocut +} + +func TestHybridWithFilter(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START HybridWithFilter + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.Hybrid(ctx, query.Hybrid{ + Query: "food", + Limit: 3, + Filter: &filter.Cond{ + Target: "round", + Operator: filter.Equal, + Value: "Double Jeopardy!", + }, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END HybridWithFilter +} diff --git a/_includes/code/go-v6/search_basic_test.go b/_includes/code/go-v6/search_basic_test.go index 00e2a3a5..4f222be4 100644 --- a/_includes/code/go-v6/search_basic_test.go +++ b/_includes/code/go-v6/search_basic_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/weaviate/weaviate-go-client/v6/collections" "github.com/weaviate/weaviate-go-client/v6/data" "github.com/weaviate/weaviate-go-client/v6/query" ) @@ -35,3 +36,222 @@ func TestBasicQuery(t *testing.T) { t.Logf("%v", obj.Properties) } } + +// TestBasicGet lists objects without any search parameters. +func TestBasicGet(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + setupJeopardySearch(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + + // START BasicGet + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.OverAll(ctx, query.OverAll{}) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END BasicGet +} + +// TestGetWithLimit caps the number of returned objects. +func TestGetWithLimit(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + setupJeopardySearch(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + + // START GetWithLimit + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ + Limit: 1, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END GetWithLimit +} + +// TestGetWithOffset paginates with limit and offset. +func TestGetWithOffset(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + setupJeopardySearch(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + + // START GetWithOffset + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ + Limit: 1, + Offset: 1, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END GetWithOffset +} + +// TestGetProperties returns a subset of object properties. +func TestGetProperties(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + setupJeopardySearch(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + + // START GetProperties + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ + Limit: 1, + ReturnProperties: []string{"question", "answer", "points"}, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END GetProperties +} + +// TestGetObjectVector returns the object vector alongside the results. +func TestGetObjectVector(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + setupJeopardySearch(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + + // START GetObjectVector + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ + Limit: 1, + // Name the vectors to return; use "default" for a single unnamed vector. + ReturnVectors: []string{"default"}, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Vectors["default"].Single) + } + // END GetObjectVector +} + +// TestGetObjectId reads the object id (uuid) from the results. +func TestGetObjectId(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + setupJeopardySearch(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + + // START GetObjectId + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ + Limit: 1, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + // The object id is always returned. + t.Logf("%v", obj.UUID) + } + // END GetObjectId +} + +// TestGetWithCrossRefs returns properties from cross-referenced objects. +func TestGetWithCrossRefs(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START GetWithCrossRefs + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ + Limit: 2, + ReturnReferences: []query.Reference{ + { + PropertyName: "hasCategory", + TargetCollection: "JeopardyCategory", + ReturnProperties: []string{"title"}, + }, + }, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END GetWithCrossRefs +} + +// TestGetWithMetadata returns object metadata such as the creation timestamp. +func TestGetWithMetadata(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + setupJeopardySearch(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + + // START GetWithMetadata + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ + Limit: 1, + ReturnMetadata: query.ReturnMetadata{ + CreatedAt: true, + }, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + if obj.CreatedAt != nil { + t.Logf("created at: %v", *obj.CreatedAt) + } + } + // END GetWithMetadata +} + +// TestMultiTenancy queries a specific tenant of a multi-tenant collection. +func TestMultiTenancy(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START MultiTenancy + // Bind the tenant once when you take the collection handle. + jeopardy := client.Collections.Use("JeopardyQuestion", + collections.WithTenant("tenantA"), + ) + response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ + Limit: 2, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END MultiTenancy +} diff --git a/_includes/code/go-v6/search_placeholders_test.go b/_includes/code/go-v6/search_placeholders_test.go new file mode 100644 index 00000000..297b5cce --- /dev/null +++ b/_includes/code/go-v6/search_placeholders_test.go @@ -0,0 +1,63 @@ +package main + +import "testing" + +// This file holds placeholders for search and aggregate operations that the v6 +// Go client does not implement yet. Each marked region contains only a +// "Coming soon" line so the docs render a clear placeholder in the Go v6 tab; +// the surrounding test compiles and skips. + +// TestGetNearObject is a placeholder: the v6 Go client cannot yet search by an +// existing object id (near-object). +func TestGetNearObject(t *testing.T) { + t.Skip("near-object search is not yet available in the v6 Go client") + + // TODO[g-despot]: near-object search snippet pending v6 client support + // START NearObject + // Coming soon + // END NearObject +} + +// TestFilterByGeolocation is a placeholder: the v6 Go client does not yet +// expose a geo-coordinate range filter operator. +func TestFilterByGeolocation(t *testing.T) { + t.Skip("geo-coordinate filtering is not yet available in the v6 Go client") + + // TODO[g-despot]: geo-coordinate filter snippet pending v6 client support + // START GeoFilter + // Coming soon + // END GeoFilter +} + +// TestAggregateNearText is a placeholder: the v6 Go client cannot yet aggregate +// over the results of a near-text search. +func TestAggregateNearText(t *testing.T) { + t.Skip("aggregation over a near-text search is not yet available in the v6 Go client") + + // TODO[g-despot]: near-text aggregation snippet pending v6 client support + // START AggregateNearText + // Coming soon + // END AggregateNearText +} + +// TestAggregateHybrid is a placeholder: the v6 Go client cannot yet aggregate +// over the results of a hybrid search. +func TestAggregateHybrid(t *testing.T) { + t.Skip("aggregation over a hybrid search is not yet available in the v6 Go client") + + // TODO[g-despot]: hybrid aggregation snippet pending v6 client support + // START AggregateHybrid + // Coming soon + // END AggregateHybrid +} + +// TestAggregateWhereFilter is a placeholder: the v6 Go client cannot yet +// aggregate objects selected by a standalone filter. +func TestAggregateWhereFilter(t *testing.T) { + t.Skip("filtered aggregation is not yet available in the v6 Go client") + + // TODO[g-despot]: filtered aggregation snippet pending v6 client support + // START AggregateWhereFilter + // Coming soon + // END AggregateWhereFilter +} diff --git a/_includes/code/go-v6/search_test.go b/_includes/code/go-v6/search_test.go new file mode 100644 index 00000000..f5f9bde5 --- /dev/null +++ b/_includes/code/go-v6/search_test.go @@ -0,0 +1,296 @@ +package main + +import ( + "context" + "testing" + + weaviate "github.com/weaviate/weaviate-go-client/v6" + "github.com/weaviate/weaviate-go-client/v6/collections" + "github.com/weaviate/weaviate-go-client/v6/data" + "github.com/weaviate/weaviate-go-client/v6/query" + "github.com/weaviate/weaviate-go-client/v6/query/filter" + "github.com/weaviate/weaviate-go-client/v6/types" +) + +// setupJeopardySearch (re)creates a JeopardyQuestion collection with a single +// user-supplied ("bring your own") vector named "default" and seeds a few +// objects with explicit vectors. Because the collection has no vectorizer, the +// vector-search snippets that build on it run without an inference module. +// +// The near-text and hybrid snippets on this page instead query a collection +// that has a text vectorizer configured, mirroring the demo datasets used +// throughout the docs; they are not backed by this helper. +func setupJeopardySearch(t *testing.T, client *weaviate.Client) { + t.Helper() + ctx := context.Background() + _ = client.Collections.Delete(ctx, "JeopardyQuestion") + if _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "JeopardyQuestion", + Properties: []collections.Property{ + {Name: "question", DataType: collections.DataTypeText}, + {Name: "answer", DataType: collections.DataTypeText}, + {Name: "category", DataType: collections.DataTypeText}, + {Name: "points", DataType: collections.DataTypeInt}, + }, + // An empty VectorConfig registers a named vector with no vectorizer, so + // vectors must be supplied at insert time. + Vectors: map[string]collections.VectorConfig{ + "default": {}, + }, + }); err != nil { + t.Fatalf("create JeopardyQuestion collection: %v", err) + } + + jeopardy := client.Collections.Use("JeopardyQuestion") + if _, err := jeopardy.Data.Insert(ctx, + &data.Object{ + Properties: map[string]any{"question": "This organ removes excess glucose from the blood & stores it as glycogen", "answer": "Liver", "category": "SCIENCE", "points": 100}, + Vectors: []types.Vector{{Name: "default", Single: []float32{0.10, 0.21, 0.32}}}, + }, + &data.Object{ + Properties: map[string]any{"question": "It's the only living mammal in the order Proboscidea", "answer": "Elephant", "category": "ANIMALS", "points": 200}, + Vectors: []types.Vector{{Name: "default", Single: []float32{0.11, 0.20, 0.34}}}, + }, + &data.Object{ + Properties: map[string]any{"question": "The gavial looks very much like a crocodile except for this bodily feature", "answer": "the nose or snout", "category": "ANIMALS", "points": 400}, + Vectors: []types.Vector{{Name: "default", Single: []float32{0.14, 0.19, 0.30}}}, + }, + ); err != nil { + t.Fatalf("seed JeopardyQuestion: %v", err) + } +} + +// TestGetNearText runs a semantic search over a collection whose vectorizer +// turns the query text into a vector server-side. +func TestGetNearText(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START GetNearText + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.NearText(ctx, query.NearText{ + Concepts: []string{"animals in movies"}, + Limit: 2, + ReturnMetadata: query.ReturnMetadata{ + Distance: true, + }, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + if obj.Metadata.Distance != nil { + t.Logf("distance: %v", *obj.Metadata.Distance) + } + } + // END GetNearText +} + +// TestGetNearVector runs a vector similarity search from an input vector. +func TestGetNearVector(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + setupJeopardySearch(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + + // START GetNearVector + // A query vector, for example an embedding produced by your model. + vector := []float32{0.12, 0.20, 0.33} + + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.NearVector(ctx, query.NearVector{ + Target: &types.Vector{Single: vector}, + Limit: 2, + ReturnMetadata: query.ReturnMetadata{ + Distance: true, + }, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + if obj.Metadata.Distance != nil { + t.Logf("distance: %v", *obj.Metadata.Distance) + } + } + // END GetNearVector +} + +// TestNamedVectorNearText searches a named vector by passing the vector name as +// the search target. +func TestNamedVectorNearText(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START NamedVectorNearText + reviews := client.Collections.Use("WineReviewNV") + response, err := reviews.Query.NearText(ctx, query.NearText{ + Concepts: []string{"a sweet German white wine"}, + // Select the named vector to search against. + Target: query.VectorName("title_country"), + Limit: 2, + ReturnMetadata: query.ReturnMetadata{ + Distance: true, + }, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END NamedVectorNearText +} + +// TestGetWithDistance sets a maximum distance threshold on a vector search. +func TestGetWithDistance(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + setupJeopardySearch(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + + // START GetWithDistance + vector := []float32{0.12, 0.20, 0.33} + + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.NearVector(ctx, query.NearVector{ + Target: &types.Vector{Single: vector}, + Similarity: query.Distance(0.25), + ReturnMetadata: query.ReturnMetadata{ + Distance: true, + }, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END GetWithDistance +} + +// TestGetLimitOffset paginates a vector search with limit and offset. +func TestGetLimitOffset(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + setupJeopardySearch(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + + // START GetLimitOffset + vector := []float32{0.12, 0.20, 0.33} + + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.NearVector(ctx, query.NearVector{ + Target: &types.Vector{Single: vector}, + Limit: 2, + Offset: 1, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END GetLimitOffset +} + +// TestAutocut limits results to the first N distance clusters (autocut). +func TestAutocut(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + setupJeopardySearch(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + + // START Autocut + vector := []float32{0.12, 0.20, 0.33} + + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.NearVector(ctx, query.NearVector{ + Target: &types.Vector{Single: vector}, + // Return objects from the first similarity cluster only. + AutoLimit: 1, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END Autocut +} + +// TestGetWithGroupBy groups the results of a vector search by a property. +func TestGetWithGroupBy(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + setupJeopardySearch(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + + // START GetWithGroupBy + vector := []float32{0.12, 0.20, 0.33} + + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.NearVector.GroupBy(ctx, + query.NearVector{ + Target: &types.Vector{Single: vector}, + Limit: 10, + }, + query.GroupBy{ + Property: "category", + NumberOfGroups: 2, + ObjectLimit: 2, + }, + ) + if err != nil { + t.Fatal(err) + } + for name, group := range response.Groups { + t.Logf("group %q holds %d objects", name, group.Size) + } + // END GetWithGroupBy +} + +// TestGetWithFilter narrows a vector search with a property filter. +func TestGetWithFilter(t *testing.T) { + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + setupJeopardySearch(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + + // START GetWithFilter + vector := []float32{0.12, 0.20, 0.33} + + jeopardy := client.Collections.Use("JeopardyQuestion") + response, err := jeopardy.Query.NearVector(ctx, query.NearVector{ + Target: &types.Vector{Single: vector}, + Limit: 2, + Filter: &filter.Cond{ + Target: "category", + Operator: filter.Equal, + Value: "ANIMALS", + }, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END GetWithFilter +} diff --git a/docs/weaviate/search/aggregate.md b/docs/weaviate/search/aggregate.md index a6ad6fbf..324a281d 100644 --- a/docs/weaviate/search/aggregate.md +++ b/docs/weaviate/search/aggregate.md @@ -13,6 +13,8 @@ import PyCode from '!!raw-loader!/\_includes/code/howto/search.aggregate.py'; import PyCodeV3 from '!!raw-loader!/\_includes/code/howto/search.aggregate-v3.py'; import TSCode from '!!raw-loader!/\_includes/code/howto/search.aggregate.ts'; import GoCode from '!!raw-loader!/\_includes/code/howto/go/docs/mainpkg/search-aggregation_test.go'; +import GoV6Code from '!!raw-loader!/\_includes/code/go-v6/aggregate_test.go'; +import GoV6PlaceholderCode from '!!raw-loader!/\_includes/code/go-v6/search_placeholders_test.go'; import JavaV6Code from "!!raw-loader!/\_includes/code/java-v6/src/test/java/SearchAggregateTest.java"; import CSharpCode from "!!raw-loader!/\_includes/code/csharp/SearchAggregateTest.cs"; @@ -67,6 +69,14 @@ Return the number of objects matched by the query. language="gonew" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Date: Wed, 22 Jul 2026 21:55:38 +0200 Subject: [PATCH 06/22] docs(go-v6): add go6 tabs for tenants, RBAC, backup, aliases, multi-target (phase 3c) Phase 3c of the Weaviate v6 Go client docs: add parallel "Go v6" (go6) tabs beside the existing "Go" tabs for the fully-implemented management APIs, backed by real, compile-verified snippets under _includes/code/go-v6/. Snippet files (package main, compile-only; runtime tests skip and stay out of test_go.py's -run set): - tenants_test.go multi-tenancy: MT config + auto-tenant, tenant Create/Get(list)/Delete, tenant-scoped insert/search/cross-ref - rbac_test.go roles (create with each permission category, add/remove permissions, exists/get/list/assigned users/delete), DB + OIDC users, OIDC groups, role->group assignments - backup_test.go Create/Restore + Get*Status + Cancel* + await helpers (AwaitCompletion / WithPollingInterval) - aliases_test.go alias Create/List/Get/Update/Delete + use-in-query - multi_target_test.go multi-target vectors: Sum/Min/Average/ ManualWeights/RelativeScore, Weighted, VectorName Pages updated (63 go6 tabs total): multi-tenancy, collection-aliases, rbac/manage-roles, rbac/manage-users, rbac/manage-groups, deploy backups, search/multi-vector. Two "// Coming soon" placeholders for ops the v6 client does not expose yet: collection Update (UpdateAutoMT) and list-known-OIDC-groups (GetKnownOidcGroups). Verified: go vet ./... and go build ./... pass against the local v6 client via a temporary replace directive (dropped before commit); go.mod/go.sum unchanged; gofmt clean. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01WmugebxuspdSQrFKE883W5 --- _includes/code/go-v6/aliases_test.go | 141 ++++ _includes/code/go-v6/backup_test.go | 119 +++ _includes/code/go-v6/multi_target_test.go | 223 ++++++ _includes/code/go-v6/rbac_test.go | 756 ++++++++++++++++++ _includes/code/go-v6/tenants_test.go | 212 +++++ docs/deploy/configuration/backups.md | 49 ++ .../configuration/rbac/manage-groups.mdx | 41 + .../configuration/rbac/manage-roles.mdx | 153 ++++ .../configuration/rbac/manage-users.mdx | 81 ++ .../manage-collections/collection-aliases.mdx | 57 ++ .../manage-collections/multi-tenancy.mdx | 73 ++ docs/weaviate/search/multi-vector.md | 57 ++ 12 files changed, 1962 insertions(+) create mode 100644 _includes/code/go-v6/aliases_test.go create mode 100644 _includes/code/go-v6/backup_test.go create mode 100644 _includes/code/go-v6/multi_target_test.go create mode 100644 _includes/code/go-v6/rbac_test.go create mode 100644 _includes/code/go-v6/tenants_test.go diff --git a/_includes/code/go-v6/aliases_test.go b/_includes/code/go-v6/aliases_test.go new file mode 100644 index 00000000..387f7875 --- /dev/null +++ b/_includes/code/go-v6/aliases_test.go @@ -0,0 +1,141 @@ +package main + +import ( + "context" + "testing" + + "github.com/weaviate/weaviate-go-client/v6/collections" + "github.com/weaviate/weaviate-go-client/v6/query" +) + +// The alias snippets below run against a live server. They are kept out of the +// CI run set (compile-only) and skip when executed directly. + +// TestCreateAlias points a new alias at an existing collection. +func TestCreateAlias(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START CreateAlias + err := client.Alias.Create(ctx, collections.Alias{ + Alias: "ArticlesProd", + Collection: "Article", + }) + // END CreateAlias + if err != nil { + t.Fatal(err) + } +} + +// TestListAllAliases lists every alias defined in the instance. +func TestListAllAliases(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START ListAllAliases + aliases, err := client.Alias.List(ctx) + if err != nil { + t.Fatal(err) + } + for _, a := range aliases { + t.Logf("alias %q -> collection %q", a.Alias, a.Collection) + } + // END ListAllAliases +} + +// TestListCollectionAliases keeps only the aliases that point at one collection. +func TestListCollectionAliases(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START ListCollectionAliases + aliases, err := client.Alias.List(ctx) + if err != nil { + t.Fatal(err) + } + // The client lists all aliases; filter by the target collection. + for _, a := range aliases { + if a.Collection == "Article" { + t.Logf("alias %q -> collection %q", a.Alias, a.Collection) + } + } + // END ListCollectionAliases +} + +// TestGetAlias fetches a single alias by name. +func TestGetAlias(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START GetAlias + alias, err := client.Alias.Get(ctx, "ArticlesProd") + if err != nil { + t.Fatal(err) + } + t.Logf("alias %q -> collection %q", alias.Alias, alias.Collection) + // END GetAlias +} + +// TestUpdateAlias re-points an existing alias at a different collection. +func TestUpdateAlias(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START UpdateAlias + err := client.Alias.Update(ctx, collections.Alias{ + Alias: "ArticlesProd", + Collection: "ArticlesV2", + }) + // END UpdateAlias + if err != nil { + t.Fatal(err) + } +} + +// TestDeleteAlias removes an alias. The underlying collection is untouched. +func TestDeleteAlias(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START DeleteAlias + err := client.Alias.Delete(ctx, "ArticlesProd") + // END DeleteAlias + if err != nil { + t.Fatal(err) + } +} + +// TestUseAlias queries through an alias. Anywhere a collection name is expected, +// an alias name can be used in its place. +func TestUseAlias(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START UseAlias + // "ArticlesProd" is an alias; the query runs against its target collection. + articles := client.Collections.Use("ArticlesProd") + response, err := articles.Query.OverAll(ctx, query.OverAll{ + Limit: 2, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END UseAlias +} diff --git a/_includes/code/go-v6/backup_test.go b/_includes/code/go-v6/backup_test.go new file mode 100644 index 00000000..d22fad6b --- /dev/null +++ b/_includes/code/go-v6/backup_test.go @@ -0,0 +1,119 @@ +package main + +import ( + "context" + "testing" + "time" + + "github.com/weaviate/weaviate-go-client/v6/backup" +) + +// The backup snippets below require a configured backup backend (for example the +// filesystem module). They are kept out of the CI run set (compile-only) and +// skip when executed directly. + +// TestCreateBackup starts a backup and waits for it to complete. +func TestCreateBackup(t *testing.T) { + t.Skip("requires a Weaviate instance with a backup backend module enabled") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START CreateBackup + info, err := client.Backup.Create(ctx, backup.CreateOptions{ + Backend: "filesystem", + ID: "my-backup", + }) + if err != nil { + t.Fatal(err) + } + + // Block until the backup finishes. The default polling interval is one + // second; override it with backup.WithPollingInterval. + info, err = backup.AwaitCompletion(ctx, info, backup.WithPollingInterval(2*time.Second)) + if err != nil { + t.Fatal(err) + } + t.Logf("backup %q finished with status %s", info.ID, info.Status) + // END CreateBackup +} + +// TestStatusCreateBackup polls the status of an in-progress backup creation. +func TestStatusCreateBackup(t *testing.T) { + t.Skip("requires a Weaviate instance with a backup backend module enabled") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START StatusCreateBackup + info, err := client.Backup.GetCreateStatus(ctx, backup.GetStatusOptions{ + Backend: "filesystem", + ID: "my-backup", + }) + if err != nil { + t.Fatal(err) + } + t.Logf("backup %q status: %s", info.ID, info.Status) + // END StatusCreateBackup +} + +// TestCancelBackup cancels an in-progress backup creation. +func TestCancelBackup(t *testing.T) { + t.Skip("requires a Weaviate instance with a backup backend module enabled") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START CancelBackup + err := client.Backup.CancelCreate(ctx, backup.CancelOptions{ + Backend: "filesystem", + ID: "my-backup", + }) + // END CancelBackup + if err != nil { + t.Fatal(err) + } +} + +// TestRestoreBackup restores a backup and waits for it to complete. +func TestRestoreBackup(t *testing.T) { + t.Skip("requires a Weaviate instance with a backup backend module enabled") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START RestoreBackup + info, err := client.Backup.Restore(ctx, backup.RestoreOptions{ + Backend: "filesystem", + ID: "my-backup", + }) + if err != nil { + t.Fatal(err) + } + + info, err = backup.AwaitCompletion(ctx, info) + if err != nil { + t.Fatal(err) + } + t.Logf("restore of %q finished with status %s", info.ID, info.Status) + // END RestoreBackup +} + +// TestStatusRestoreBackup polls the status of an in-progress backup restore. +func TestStatusRestoreBackup(t *testing.T) { + t.Skip("requires a Weaviate instance with a backup backend module enabled") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START StatusRestoreBackup + info, err := client.Backup.GetRestoreStatus(ctx, backup.GetStatusOptions{ + Backend: "filesystem", + ID: "my-backup", + }) + if err != nil { + t.Fatal(err) + } + t.Logf("restore of %q status: %s", info.ID, info.Status) + // END StatusRestoreBackup +} diff --git a/_includes/code/go-v6/multi_target_test.go b/_includes/code/go-v6/multi_target_test.go new file mode 100644 index 00000000..f19a576b --- /dev/null +++ b/_includes/code/go-v6/multi_target_test.go @@ -0,0 +1,223 @@ +package main + +import ( + "context" + "testing" + + "github.com/weaviate/weaviate-go-client/v6/query" + "github.com/weaviate/weaviate-go-client/v6/types" +) + +// The multi-target vector snippets below require a collection ("JeopardyTiny") +// with multiple named vectors and a configured vectorizer. They are kept out of +// the CI run set (compile-only) and skip when executed directly. + +// TestMultiBasic searches several target vectors by name. With no join strategy +// specified, Weaviate combines the results using the default (minimum) strategy. +func TestMultiBasic(t *testing.T) { + t.Skip("requires a collection with multiple named vectors and a configured vectorizer") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START MultiBasic + jeopardy := client.Collections.Use("JeopardyTiny") + response, err := jeopardy.Query.NearText(ctx, query.NearText{ + Concepts: []string{"a wild animal"}, + // Minimum is the default join strategy when combining target vectors. + Target: query.Min([]query.VectorName{ + "jeopardy_questions_vector", + "jeopardy_answers_vector", + }), + Limit: 2, + ReturnMetadata: query.ReturnMetadata{Distance: true}, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END MultiBasic +} + +// TestMultiTargetNearVector supplies a separate query vector for each target +// vector. +func TestMultiTargetNearVector(t *testing.T) { + t.Skip("requires a collection with multiple named vectors and a configured vectorizer") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + v1 := []float32{0.12, 0.20, 0.33} + v2 := []float32{0.14, 0.19, 0.30} + + // START MultiTargetNearVector + jeopardy := client.Collections.Use("JeopardyTiny") + response, err := jeopardy.Query.NearVector(ctx, query.NearVector{ + // Pair each named target vector with its own query vector. + Target: query.Min([]types.Vector{ + {Name: "jeopardy_questions_vector", Single: v1}, + {Name: "jeopardy_answers_vector", Single: v2}, + }), + Limit: 2, + ReturnMetadata: query.ReturnMetadata{Distance: true}, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END MultiTargetNearVector +} + +// TestMultiTargetMultipleNearVectorsV1 targets the same vector more than once by +// listing it twice with different query vectors. +func TestMultiTargetMultipleNearVectorsV1(t *testing.T) { + t.Skip("requires a collection with multiple named vectors and a configured vectorizer") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + v1 := []float32{0.12, 0.20, 0.33} + v2 := []float32{0.14, 0.19, 0.30} + v3 := []float32{0.11, 0.20, 0.34} + + // START MultiTargetMultipleNearVectorsV1 + jeopardy := client.Collections.Use("JeopardyTiny") + response, err := jeopardy.Query.NearVector(ctx, query.NearVector{ + // A target vector name may appear more than once, each with its own vector. + Target: query.Min([]types.Vector{ + {Name: "jeopardy_questions_vector", Single: v1}, + {Name: "jeopardy_answers_vector", Single: v2}, + {Name: "jeopardy_answers_vector", Single: v3}, + }), + Limit: 2, + ReturnMetadata: query.ReturnMetadata{Distance: true}, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END MultiTargetMultipleNearVectorsV1 +} + +// TestMultiTargetMultipleNearVectorsV2 assigns a weight to each query vector. +func TestMultiTargetMultipleNearVectorsV2(t *testing.T) { + t.Skip("requires a collection with multiple named vectors and a configured vectorizer") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + v1 := []float32{0.12, 0.20, 0.33} + v2 := []float32{0.14, 0.19, 0.30} + v3 := []float32{0.11, 0.20, 0.34} + + // START MultiTargetMultipleNearVectorsV2 + jeopardy := client.Collections.Use("JeopardyTiny") + response, err := jeopardy.Query.NearVector(ctx, query.NearVector{ + // Weight each query vector; repeated targets are weighted independently. + Target: query.ManualWeights([]query.WeightedVector[types.Vector]{ + query.Weighted(types.Vector{Name: "jeopardy_questions_vector", Single: v1}, 10), + query.Weighted(types.Vector{Name: "jeopardy_answers_vector", Single: v2}, 30), + query.Weighted(types.Vector{Name: "jeopardy_answers_vector", Single: v3}, 30), + }), + Limit: 2, + ReturnMetadata: query.ReturnMetadata{Distance: true}, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END MultiTargetMultipleNearVectorsV2 +} + +// TestMultiTargetWithSimpleJoin selects a named join strategy for the target +// vectors. +func TestMultiTargetWithSimpleJoin(t *testing.T) { + t.Skip("requires a collection with multiple named vectors and a configured vectorizer") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START MultiTargetWithSimpleJoin + jeopardy := client.Collections.Use("JeopardyTiny") + response, err := jeopardy.Query.NearText(ctx, query.NearText{ + Concepts: []string{"a wild animal"}, + // query.Sum, query.Min, query.ManualWeights, and query.RelativeScore + // are also available. + Target: query.Average([]query.VectorName{ + "jeopardy_questions_vector", + "jeopardy_answers_vector", + }), + Limit: 2, + ReturnMetadata: query.ReturnMetadata{Distance: true}, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END MultiTargetWithSimpleJoin +} + +// TestMultiTargetManualWeights weights the raw distance to each target vector. +func TestMultiTargetManualWeights(t *testing.T) { + t.Skip("requires a collection with multiple named vectors and a configured vectorizer") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START MultiTargetManualWeights + jeopardy := client.Collections.Use("JeopardyTiny") + response, err := jeopardy.Query.NearText(ctx, query.NearText{ + Concepts: []string{"a wild animal"}, + Target: query.ManualWeights([]query.WeightedVector[query.VectorName]{ + query.Weighted(query.VectorName("jeopardy_questions_vector"), 10), + query.Weighted(query.VectorName("jeopardy_answers_vector"), 50), + }), + Limit: 2, + ReturnMetadata: query.ReturnMetadata{Distance: true}, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END MultiTargetManualWeights +} + +// TestMultiTargetRelativeScore weights the normalized distance to each target +// vector. +func TestMultiTargetRelativeScore(t *testing.T) { + t.Skip("requires a collection with multiple named vectors and a configured vectorizer") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START MultiTargetRelativeScore + jeopardy := client.Collections.Use("JeopardyTiny") + response, err := jeopardy.Query.NearText(ctx, query.NearText{ + Concepts: []string{"a wild animal"}, + Target: query.RelativeScore([]query.WeightedVector[query.VectorName]{ + query.Weighted(query.VectorName("jeopardy_questions_vector"), 10), + query.Weighted(query.VectorName("jeopardy_answers_vector"), 10), + }), + Limit: 2, + ReturnMetadata: query.ReturnMetadata{Distance: true}, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END MultiTargetRelativeScore +} diff --git a/_includes/code/go-v6/rbac_test.go b/_includes/code/go-v6/rbac_test.go new file mode 100644 index 00000000..02321f7a --- /dev/null +++ b/_includes/code/go-v6/rbac_test.go @@ -0,0 +1,756 @@ +package main + +import ( + "context" + "testing" + + weaviate "github.com/weaviate/weaviate-go-client/v6" + "github.com/weaviate/weaviate-go-client/v6/rbac" +) + +// The RBAC snippets below require a Weaviate instance with RBAC enabled and an +// admin API key. They are kept out of the CI run set (compile-only) and skip +// when executed directly. +const rbacSkip = "requires a Weaviate instance with RBAC enabled and an admin API key" + +// ----------------------------------------------------------------------------- +// Requirements +// ----------------------------------------------------------------------------- + +// TestRBACAdminClient connects with a key belonging to a user that has the +// permissions needed to manage roles and users. +func TestRBACAdminClient(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + + // START AdminClient + client, err := weaviate.NewLocal(ctx, + weaviate.WithAPIKey("admin-api-key"), + ) + if err != nil { + t.Fatal(err) + } + defer client.Close() + // END AdminClient +} + +// ----------------------------------------------------------------------------- +// Role management: create roles with permissions +// ----------------------------------------------------------------------------- + +// TestRBACAddManageRolesPermission creates a role that can manage other roles. +func TestRBACAddManageRolesPermission(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START AddManageRolesPermission + err := client.Roles.Create(ctx, rbac.Role{ + ID: "testRole", + Permissions: rbac.Permissions{ + Roles: []rbac.RolePermission{ + { + RoleID: "testRole*", // Applies to all roles starting with "testRole". + // Match limits role management to the current user's permission + // level; use rbac.RoleScopeAll to allow managing all permissions. + Scope: rbac.RoleScopeMatch, + Create: true, // Allow creating roles. + Read: true, // Allow reading roles. + Update: true, // Allow updating roles. + Delete: true, // Allow deleting roles. + }, + }, + }, + }) + // END AddManageRolesPermission + if err != nil { + t.Fatal(err) + } +} + +// TestRBACAddManageUsersPermission creates a role that can manage users. +func TestRBACAddManageUsersPermission(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START AddManageUsersPermission + err := client.Roles.Create(ctx, rbac.Role{ + ID: "testRole", + Permissions: rbac.Permissions{ + Users: []rbac.UserPermission{ + { + UserID: "testUser*", // Applies to all users starting with "testUser". + Create: true, // Allow creating users. + Read: true, // Allow reading user info. + Update: true, // Allow rotating a user's API key. + Delete: true, // Allow deleting users. + AssignAndRevoke: true, // Allow assigning and revoking roles. + }, + }, + }, + }) + // END AddManageUsersPermission + if err != nil { + t.Fatal(err) + } +} + +// TestRBACAddCollectionsPermission creates a role with collection permissions. +func TestRBACAddCollectionsPermission(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START AddCollectionsPermission + err := client.Roles.Create(ctx, rbac.Role{ + ID: "testRole", + Permissions: rbac.Permissions{ + Collections: []rbac.CollectionPermission{ + { + Collection: "TargetCollection*", // Applies to all matching collections. + Create: true, // Allow creating collections. + Read: true, // Allow reading collection config. + Update: true, // Allow updating collection config. + Delete: true, // Allow deleting collections. + }, + }, + }, + }) + // END AddCollectionsPermission + if err != nil { + t.Fatal(err) + } +} + +// TestRBACAddTenantPermission creates a role with tenant permissions. +func TestRBACAddTenantPermission(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START AddTenantPermission + err := client.Roles.Create(ctx, rbac.Role{ + ID: "testRole", + Permissions: rbac.Permissions{ + Tenants: []rbac.TenantPermission{ + { + Collection: "TargetCollection*", // Applies to all matching collections. + Tenant: "TargetTenant*", // Applies to all matching tenants. + Create: true, // Allow creating tenants. + Read: true, // Allow reading tenant info. + Update: true, // Allow updating tenant states. + Delete: true, // Allow deleting tenants. + }, + }, + }, + }) + // END AddTenantPermission + if err != nil { + t.Fatal(err) + } +} + +// TestRBACAddDataObjectPermission creates a role with data object permissions. +func TestRBACAddDataObjectPermission(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START AddDataObjectPermission + err := client.Roles.Create(ctx, rbac.Role{ + ID: "testRole", + Permissions: rbac.Permissions{ + Data: []rbac.DataPermission{ + { + Collection: "TargetCollection*", // Applies to all matching collections. + Tenant: "TargetTenant*", // Applies to all matching tenants. + Create: true, // Allow data inserts. + Read: true, // Allow query and fetch operations. + Update: true, // Allow data updates. + // Delete is left false, disallowing data deletes. + }, + }, + }, + }) + // END AddDataObjectPermission + if err != nil { + t.Fatal(err) + } +} + +// TestRBACAddBackupPermission creates a role with backup permissions. +func TestRBACAddBackupPermission(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START AddBackupPermission + err := client.Roles.Create(ctx, rbac.Role{ + ID: "testRole", + Permissions: rbac.Permissions{ + Backups: []rbac.BackupsPermission{ + { + Collection: "TargetCollection*", // Applies to all matching collections. + Manage: true, // Allow managing backups. + }, + }, + }, + }) + // END AddBackupPermission + if err != nil { + t.Fatal(err) + } +} + +// TestRBACAddClusterPermission creates a role with cluster read permission. +func TestRBACAddClusterPermission(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START AddClusterPermission + err := client.Roles.Create(ctx, rbac.Role{ + ID: "testRole", + Permissions: rbac.Permissions{ + Cluster: []rbac.ClusterPermission{ + {Read: true}, // Allow reading cluster data. + }, + }, + }) + // END AddClusterPermission + if err != nil { + t.Fatal(err) + } +} + +// TestRBACAddNodesPermission creates a role with node read permission. +func TestRBACAddNodesPermission(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START AddNodesPermission + err := client.Roles.Create(ctx, rbac.Role{ + ID: "testRole", + Permissions: rbac.Permissions{ + Nodes: []rbac.NodesPermission{ + { + Collection: "TargetCollection*", // Verbose reads are scoped to a collection. + Verbosity: rbac.NodeVerbosityVerbose, // Or rbac.NodeVerbosityMinimal for all collections. + Read: true, // Allow reading node metadata. + }, + }, + }, + }) + // END AddNodesPermission + if err != nil { + t.Fatal(err) + } +} + +// TestRBACAddAliasPermission creates a role with alias permissions. +func TestRBACAddAliasPermission(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START AddAliasPermission + err := client.Roles.Create(ctx, rbac.Role{ + ID: "testRole", + Permissions: rbac.Permissions{ + Aliases: []rbac.AliasPermission{ + { + Alias: "TargetAlias*", // Applies to all matching aliases. + Collection: "TargetCollection*", // Applies to all matching collections. + Create: true, // Allow alias creation. + Read: true, // Allow listing aliases. + Update: true, // Allow updating aliases. + // Delete is left false, disallowing alias deletion. + }, + }, + }, + }) + // END AddAliasPermission + if err != nil { + t.Fatal(err) + } +} + +// TestRBACAddReplicationsPermission creates a role with replication permissions. +func TestRBACAddReplicationsPermission(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START AddReplicationsPermission + err := client.Roles.Create(ctx, rbac.Role{ + ID: "testRole", + Permissions: rbac.Permissions{ + Replication: []rbac.ReplicationPermission{ + { + Collection: "TargetCollection*", // Applies to all matching collections. + Shard: "TargetShard*", // Applies to all matching shards. + Create: true, // Allow replica movement operations. + Read: true, // Allow retrieving replication status. + Update: true, // Allow cancelling replication operations. + // Delete is left false, disallowing deleting replication operations. + }, + }, + }, + }) + // END AddReplicationsPermission + if err != nil { + t.Fatal(err) + } +} + +// TestRBACAddGroupsPermission creates a role with group permissions. +func TestRBACAddGroupsPermission(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START AddGroupsPermission + err := client.Roles.Create(ctx, rbac.Role{ + ID: "testRole", + Permissions: rbac.Permissions{ + Groups: []rbac.GroupPermission{ + { + GroupID: "TargetGroup*", // Applies to all groups starting with "TargetGroup". + Type: rbac.GroupTypeOIDC, + Read: true, // Allow reading group information. + AssignAndRevoke: true, // Allow assigning and revoking group memberships. + }, + }, + }, + }) + // END AddGroupsPermission + if err != nil { + t.Fatal(err) + } +} + +// ----------------------------------------------------------------------------- +// Role management: modify and inspect roles +// ----------------------------------------------------------------------------- + +// TestRBACAddRoles grants additional permissions to an existing role. +func TestRBACAddRoles(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START AddRoles + err := client.Roles.AddPermissions(ctx, rbac.AddPermissions{ + RoleID: "testRole", + Permissions: rbac.Permissions{ + Data: []rbac.DataPermission{ + {Collection: "TargetCollection*", Create: true}, + }, + }, + }) + // END AddRoles + if err != nil { + t.Fatal(err) + } +} + +// TestRBACRemovePermissions removes permissions from a role. +func TestRBACRemovePermissions(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START RemovePermissions + err := client.Roles.RemovePermissions(ctx, rbac.RemovePermissions{ + RoleID: "testRole", + Permissions: rbac.Permissions{ + Collections: []rbac.CollectionPermission{ + {Collection: "TargetCollection*", Read: true, Create: true, Delete: true}, + }, + Data: []rbac.DataPermission{ + {Collection: "TargetCollection*", Read: true}, + }, + }, + }) + // END RemovePermissions + if err != nil { + t.Fatal(err) + } +} + +// TestRBACCheckRoleExists checks whether a role exists. +func TestRBACCheckRoleExists(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START CheckRoleExists + exists, err := client.Roles.Exists(ctx, "testRole") + if err != nil { + t.Fatal(err) + } + t.Logf("testRole exists: %t", exists) + // END CheckRoleExists +} + +// TestRBACInspectRole retrieves a role and its permissions. +func TestRBACInspectRole(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START InspectRole + role, err := client.Roles.Get(ctx, "testRole") + if err != nil { + t.Fatal(err) + } + t.Logf("role: %s", role.ID) + t.Logf("collection permissions: %+v", role.Collections) + t.Logf("data permissions: %+v", role.Data) + // END InspectRole +} + +// TestRBACListAllRoles lists every role in the instance. +func TestRBACListAllRoles(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START ListAllRoles + roles, err := client.Roles.List(ctx) + if err != nil { + t.Fatal(err) + } + for _, role := range roles { + t.Logf("%s", role.ID) + } + // END ListAllRoles +} + +// TestRBACAssignedUsers lists the users that have a given role. +func TestRBACAssignedUsers(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START AssignedUsers + userIDs, err := client.Roles.AssignedUserIDs(ctx, "testRole") + if err != nil { + t.Fatal(err) + } + for _, id := range userIDs { + t.Logf("assigned user: %s", id) + } + // END AssignedUsers +} + +// TestRBACDeleteRole deletes a role. +func TestRBACDeleteRole(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START DeleteRole + err := client.Roles.Delete(ctx, "testRole") + // END DeleteRole + if err != nil { + t.Fatal(err) + } +} + +// ----------------------------------------------------------------------------- +// User management (database users) +// ----------------------------------------------------------------------------- + +// TestRBACListAllUsers lists all database users. +func TestRBACListAllUsers(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START ListAllUsers + users, err := client.Users.DB.List(ctx, rbac.ListUsersOptions{}) + if err != nil { + t.Fatal(err) + } + for _, u := range users { + t.Logf("%s (active: %t)", u.ID, u.Active) + } + // END ListAllUsers +} + +// TestRBACCreateUser creates a database user and prints its API key. +func TestRBACCreateUser(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START CreateUser + // Create returns the new user's API key. Store it securely; it cannot be + // retrieved again later. + apiKey, err := client.Users.DB.Create(ctx, "custom-user") + if err != nil { + t.Fatal(err) + } + t.Logf("new API key: %s", apiKey) + // END CreateUser +} + +// TestRBACDeleteUser deletes a database user. +func TestRBACDeleteUser(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START DeleteUser + err := client.Users.DB.Delete(ctx, "custom-user") + // END DeleteUser + if err != nil { + t.Fatal(err) + } +} + +// TestRBACRotateApiKey rotates a database user's API key. +func TestRBACRotateApiKey(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START RotateApiKey + newAPIKey, err := client.Users.DB.RotateKey(ctx, "custom-user") + if err != nil { + t.Fatal(err) + } + t.Logf("rotated API key: %s", newAPIKey) + // END RotateApiKey +} + +// TestRBACAssignRole assigns roles to a database user. +func TestRBACAssignRole(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START AssignRole + err := client.Users.DB.AssignRoles(ctx, rbac.AssignRolesOptions{ + ID: "custom-user", + Roles: []string{"testRole", "viewer"}, + }) + // END AssignRole + if err != nil { + t.Fatal(err) + } +} + +// TestRBACRevokeRoles revokes roles from a database user. +func TestRBACRevokeRoles(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START RevokeRoles + err := client.Users.DB.RevokeRoles(ctx, rbac.RevokeRolesOptions{ + ID: "custom-user", + Roles: []string{"testRole"}, + }) + // END RevokeRoles + if err != nil { + t.Fatal(err) + } +} + +// TestRBACListUserRoles lists the roles assigned to a database user. +func TestRBACListUserRoles(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START ListUserRoles + roles, err := client.Users.DB.AssignedRoles(ctx, rbac.AssignedRolesOptions{ + ID: "custom-user", + }) + if err != nil { + t.Fatal(err) + } + for _, role := range roles { + t.Logf("%s", role.ID) + } + // END ListUserRoles +} + +// ----------------------------------------------------------------------------- +// OIDC users +// ----------------------------------------------------------------------------- + +// TestRBACAssignOidcUserRole assigns roles to an OIDC user. +func TestRBACAssignOidcUserRole(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START AssignOidcUserRole + err := client.Users.OIDC.AssignRoles(ctx, rbac.AssignRolesOptions{ + ID: "custom-user", + Roles: []string{"testRole", "viewer"}, + }) + // END AssignOidcUserRole + if err != nil { + t.Fatal(err) + } +} + +// TestRBACRevokeOidcUserRoles revokes roles from an OIDC user. +func TestRBACRevokeOidcUserRoles(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START RevokeOidcUserRoles + err := client.Users.OIDC.RevokeRoles(ctx, rbac.RevokeRolesOptions{ + ID: "custom-user", + Roles: []string{"testRole"}, + }) + // END RevokeOidcUserRoles + if err != nil { + t.Fatal(err) + } +} + +// TestRBACListOidcUserRoles lists the roles assigned to an OIDC user. +func TestRBACListOidcUserRoles(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START ListOidcUserRoles + roles, err := client.Users.OIDC.AssignedRoles(ctx, rbac.AssignedRolesOptions{ + ID: "custom-user", + }) + if err != nil { + t.Fatal(err) + } + for _, role := range roles { + t.Logf("%s", role.ID) + } + // END ListOidcUserRoles +} + +// ----------------------------------------------------------------------------- +// OIDC groups +// ----------------------------------------------------------------------------- + +// TestRBACAssignOidcGroupRoles assigns roles to an OIDC group. +func TestRBACAssignOidcGroupRoles(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START AssignOidcGroupRoles + err := client.Groups.AssignRoles(ctx, rbac.AssignRolesOptions{ + ID: "/admin-group", + Roles: []string{"testRole", "viewer"}, + }) + // END AssignOidcGroupRoles + if err != nil { + t.Fatal(err) + } +} + +// TestRBACRevokeOidcGroupRoles revokes roles from an OIDC group. +func TestRBACRevokeOidcGroupRoles(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START RevokeOidcGroupRoles + err := client.Groups.RevokeRoles(ctx, rbac.RevokeRolesOptions{ + ID: "/admin-group", + Roles: []string{"testRole"}, + }) + // END RevokeOidcGroupRoles + if err != nil { + t.Fatal(err) + } +} + +// TestRBACGetOidcGroupRoles lists the roles assigned to an OIDC group. +func TestRBACGetOidcGroupRoles(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START GetOidcGroupRoles + roles, err := client.Groups.AssignedRoles(ctx, rbac.AssignedRolesOptions{ + ID: "/admin-group", + IncludePermissions: true, + }) + if err != nil { + t.Fatal(err) + } + for _, role := range roles { + t.Logf("%s", role.ID) + } + // END GetOidcGroupRoles +} + +// TestRBACGetKnownOidcGroups is a placeholder: the v6 Go client cannot yet list +// all known OIDC groups. +func TestRBACGetKnownOidcGroups(t *testing.T) { + t.Skip("listing all known OIDC groups is not yet available in the v6 Go client") + + // TODO[g-despot]: list-known-OIDC-groups snippet pending v6 client support + // START GetKnownOidcGroups + // Coming soon + // END GetKnownOidcGroups +} + +// TestRBACGetGroupAssignments lists the groups assigned to a role. +func TestRBACGetGroupAssignments(t *testing.T) { + t.Skip(rbacSkip) + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START GetGroupAssignments + groups, err := client.Roles.GroupAssignments(ctx, "testRole") + if err != nil { + t.Fatal(err) + } + for _, g := range groups { + t.Logf("group ID: %s, type: %s", g.ID, g.Type) + } + // END GetGroupAssignments +} diff --git a/_includes/code/go-v6/tenants_test.go b/_includes/code/go-v6/tenants_test.go new file mode 100644 index 00000000..131d8d94 --- /dev/null +++ b/_includes/code/go-v6/tenants_test.go @@ -0,0 +1,212 @@ +package main + +import ( + "context" + "testing" + + "github.com/google/uuid" + "github.com/weaviate/weaviate-go-client/v6/collections" + "github.com/weaviate/weaviate-go-client/v6/data" + "github.com/weaviate/weaviate-go-client/v6/query" + "github.com/weaviate/weaviate-go-client/v6/tenant" +) + +// The multi-tenancy snippets below run against a live server. They are kept out +// of the CI run set (compile-only) and skip when executed directly. + +// TestEnableMultiTenancy creates a collection with multi-tenancy turned on. +func TestEnableMultiTenancy(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + _ = client.Collections.Delete(ctx, "MultiTenancyCollection") + + // START EnableMultiTenancy + _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "MultiTenancyCollection", + MultiTenancy: &collections.MultiTenancyConfig{ + Enabled: true, + }, + }) + // END EnableMultiTenancy + if err != nil { + t.Fatal(err) + } +} + +// TestEnableAutoMT creates a multi-tenancy collection that also creates and +// activates tenants automatically when data is inserted for an unknown tenant. +func TestEnableAutoMT(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + _ = client.Collections.Delete(ctx, "MultiTenancyCollection") + + // START EnableAutoMT + _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "MultiTenancyCollection", + MultiTenancy: &collections.MultiTenancyConfig{ + Enabled: true, + AutoTenantCreation: true, + AutoTenantActivation: true, + }, + }) + // END EnableAutoMT + if err != nil { + t.Fatal(err) + } +} + +// TestUpdateAutoMT is a placeholder: the v6 Go client cannot yet update an +// existing collection's configuration, so auto-tenant settings can only be set +// at creation time. +func TestUpdateAutoMT(t *testing.T) { + t.Skip("updating a collection's configuration is not yet available in the v6 Go client") + + // TODO[g-despot]: update-collection (auto-tenant) snippet pending v6 client support + // START UpdateAutoMT + // Coming soon + // END UpdateAutoMT +} + +// TestAddTenantsToClass adds tenants to a multi-tenancy collection. +func TestAddTenantsToClass(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START AddTenantsToClass + collection := client.Collections.Use("MultiTenancyCollection") + err := collection.Tenants.Create(ctx, + tenant.Tenant{Name: "tenantA"}, // Active by default. + tenant.Tenant{Name: "tenantB", Status: tenant.Inactive}, // Created on disk, not loaded. + ) + // END AddTenantsToClass + if err != nil { + t.Fatal(err) + } +} + +// TestListTenants lists every tenant in a multi-tenancy collection. +func TestListTenants(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START ListTenants + collection := client.Collections.Use("MultiTenancyCollection") + // Passing no tenant names returns every tenant in the collection. + tenants, err := collection.Tenants.Get(ctx) + if err != nil { + t.Fatal(err) + } + for _, tn := range tenants { + t.Logf("%s: %s", tn.Name, tn.Status) + } + // END ListTenants +} + +// TestRemoveTenants deletes tenants (and their data) from a collection. +func TestRemoveTenants(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START RemoveTenants + collection := client.Collections.Use("MultiTenancyCollection") + // Unknown tenant names are ignored. + err := collection.Tenants.Delete(ctx, "tenantB", "tenantX") + // END RemoveTenants + if err != nil { + t.Fatal(err) + } +} + +// TestCreateMtObject inserts an object into a specific tenant. The tenant is +// bound once on the collection handle and applies to every operation made with +// it. +func TestCreateMtObject(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START CreateMtObject + // Bind the tenant to the collection handle. + collection := client.Collections.Use("MultiTenancyCollection", + collections.WithTenant("tenantA"), + ) + _, err := collection.Data.Insert(ctx, &data.Object{ + Properties: map[string]any{ + "question": "This vector DB is OSS and supports automatic property type inference on import", + }, + }) + // END CreateMtObject + if err != nil { + t.Fatal(err) + } +} + +// TestMtSearch runs a query scoped to a single tenant. +func TestMtSearch(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START Search + collection := client.Collections.Use("MultiTenancyCollection", + collections.WithTenant("tenantA"), + ) + response, err := collection.Query.OverAll(ctx, query.OverAll{ + Limit: 2, + }) + if err != nil { + t.Fatal(err) + } + for _, obj := range response.Objects { + t.Logf("%v", obj.Properties) + } + // END Search +} + +// TestMtAddCrossRef adds a cross-reference from an object that belongs to a +// tenant. The tenant is bound on the handle used to make the request. +func TestMtAddCrossRef(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + sourceID := uuid.New() + targetID := uuid.New() + + // START AddCrossRef + collection := client.Collections.Use("MultiTenancyCollection", + collections.WithTenant("tenantA"), + ) + res, err := collection.Data.AddReferences(ctx, data.Reference{ + Origin: data.ObjectPath{ + Collection: "MultiTenancyCollection", + Property: "hasCategory", + UUID: sourceID, + }, + UUID: targetID, + }) + if err != nil { + t.Fatal(err) + } + // END AddCrossRef + for ref, msg := range res.Errors { + if msg != "" { + t.Fatalf("add reference %v: %s", ref, msg) + } + } +} diff --git a/docs/deploy/configuration/backups.md b/docs/deploy/configuration/backups.md index de8ac6b1..98ac5d66 100644 --- a/docs/deploy/configuration/backups.md +++ b/docs/deploy/configuration/backups.md @@ -14,6 +14,7 @@ import TSCodeBackup from '!!raw-loader!/_includes/code/howto/configure.backups.b import TSCodeRestore from '!!raw-loader!/_includes/code/howto/configure.backups.restore.ts'; import TSCodeStatus from '!!raw-loader!/_includes/code/howto/configure.backups.status.ts'; import GoCode from '!!raw-loader!/_includes/code/howto/go/docs/deploy/backups_test.go'; +import BackupGoV6Code from '!!raw-loader!/_includes/code/go-v6/backup_test.go'; import JavaCode from '!!raw-loader!/_includes/code/java-v6/src/test/java/BackupsTest.java'; import CurlCode from '!!raw-loader!/_includes/code/howto/configure.backups.sh'; @@ -80,6 +81,14 @@ Restart Weaviate to apply the new configuration. Then, you are ready to start a language="go" /> + + + @@ -349,6 +358,14 @@ The `*` character matches any sequence of characters. For example, `Article*` ma language="go" /> + + + + + + + + + This operation is particularly useful if you have started a backup by accident, or if you would like to stop a backup that is taking too long. @@ -608,6 +641,14 @@ Versions prior to `v1.23.13` had a bug that could lead to data not being stored language="go" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Date: Wed, 22 Jul 2026 22:37:57 +0200 Subject: [PATCH 07/22] docs(go-v6): add go6 tabs for collections config (implemented-only, phase 3d) Add "Go v6" tabs only on config sections the v6 Go client actually implements: collection create/read/exists/list, properties + data types + tokenization + nested, named/multi vectors, model2vec + self-provided vectorizers, HFresh vector index, RQ compression, inverted index / BM25 / stopwords, sharding, and replication. Sections where v6 does not yet implement the operation (collection update/alter, add-property, add named vectors to existing, multi-vector embeddings, drop inverted index, PQ/BQ/SQ, non-model2vec vectorizers, generative/reranker config, enable-compression-on-existing) get no go6 tab and no placeholder; their existing go (v5) tab is left untouched. Snippets live in _includes/code/go-v6/ (manage_collections_test.go, vector_config_test.go, collection_config_test.go, compression_test.go), verified against the v6 source at /private/tmp/go-client-v6 with go vet ./... and go build ./... under a local replace directive. Config snippets t.Skip a live server and stay out of the CI -run set. --- .../code/go-v6/collection_config_test.go | 132 +++++++++++ _includes/code/go-v6/compression_test.go | 92 +++++++ .../code/go-v6/manage_collections_test.go | 99 ++++++++ _includes/code/go-v6/vector_config_test.go | 224 ++++++++++++++++++ docs/weaviate/config-refs/collections.mdx | 17 ++ .../config-refs/indexing/inverted-index.mdx | 9 + .../compression/rq-compression.md | 25 ++ .../collection-operations.mdx | 50 ++++ .../manage-collections/inverted-index.mdx | 26 ++ .../manage-collections/multi-node-setup.mdx | 17 ++ .../manage-collections/vector-config.mdx | 57 +++++ 11 files changed, 748 insertions(+) create mode 100644 _includes/code/go-v6/collection_config_test.go create mode 100644 _includes/code/go-v6/compression_test.go create mode 100644 _includes/code/go-v6/vector_config_test.go diff --git a/_includes/code/go-v6/collection_config_test.go b/_includes/code/go-v6/collection_config_test.go new file mode 100644 index 00000000..934f4a2d --- /dev/null +++ b/_includes/code/go-v6/collection_config_test.go @@ -0,0 +1,132 @@ +package main + +import ( + "context" + "testing" + "time" + + "github.com/weaviate/weaviate-go-client/v6/collections" +) + +// The inverted-index, replication, and sharding snippets below run against a +// live server. They are kept out of the CI run set (compile-only) and skip +// when executed directly. + +// TestEnableInvertedIndex turns on property-level inverted indexes for +// filtering, searching, and range filtering. +func TestEnableInvertedIndex(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + _ = client.Collections.Delete(ctx, "Article") + defer client.Collections.Delete(ctx, "Article") + + // START EnableInvertedIndex + _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "Article", + Properties: []collections.Property{ + {Name: "title", DataType: collections.DataTypeText, IndexFilterable: true, IndexSearchable: true}, + {Name: "wordCount", DataType: collections.DataTypeInt, IndexRangeFilters: true}, + }, + }) + // END EnableInvertedIndex + if err != nil { + t.Fatal(err) + } +} + +// TestSetInvertedIndexParams configures collection-level inverted index +// parameters, including BM25 tuning and stopwords. +func TestSetInvertedIndexParams(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + _ = client.Collections.Delete(ctx, "Article") + defer client.Collections.Delete(ctx, "Article") + + // START SetInvertedIndexParams + invertedIndex := &collections.InvertedIndexConfig{ + BM25: &collections.BM25Config{ + B: 0.75, + K1: 1.2, + }, + Stopwords: &collections.StopwordConfig{ + Preset: "en", + Additions: []string{"star", "nebula"}, + Removals: []string{"a", "the"}, + }, + IndexNullState: true, + IndexPropertyLength: true, + IndexTimestamps: true, + } + _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "Article", + Properties: []collections.Property{ + {Name: "title", DataType: collections.DataTypeText}, + }, + InvertedIndex: invertedIndex, + }) + // END SetInvertedIndexParams + if err != nil { + t.Fatal(err) + } +} + +// TestAllReplicationSettings configures replication, including the deletion +// resolution strategy and async replication tuning. +func TestAllReplicationSettings(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + _ = client.Collections.Delete(ctx, "Article") + defer client.Collections.Delete(ctx, "Article") + + // START AllReplicationSettings + replication := &collections.ReplicationConfig{ + Factor: 3, + DeletionStrategy: collections.TimeBasedResolution, + AsyncReplication: &collections.AsyncReplicationConfig{ + HashTreeHeight: 16, + ReplicationFrequency: 30 * time.Second, + }, + } + _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "Article", + Replication: replication, + }) + // END AllReplicationSettings + if err != nil { + t.Fatal(err) + } +} + +// TestShardingSettings configures sharding for the collection. +func TestShardingSettings(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + _ = client.Collections.Delete(ctx, "Article") + defer client.Collections.Delete(ctx, "Article") + + // START ShardingSettings + _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "Article", + Sharding: &collections.ShardingConfig{ + VirtualPerPhysical: 128, + DesiredCount: 1, + DesiredVirtualCount: 128, + }, + }) + // END ShardingSettings + if err != nil { + t.Fatal(err) + } +} diff --git a/_includes/code/go-v6/compression_test.go b/_includes/code/go-v6/compression_test.go new file mode 100644 index 00000000..f69ffd88 --- /dev/null +++ b/_includes/code/go-v6/compression_test.go @@ -0,0 +1,92 @@ +package main + +import ( + "context" + "testing" + + "github.com/weaviate/weaviate-go-client/v6/collections" + "github.com/weaviate/weaviate-go-client/v6/collections/compression" + "github.com/weaviate/weaviate-go-client/v6/collections/vectorindex" + "github.com/weaviate/weaviate-go-client/v6/modules/model2vec" +) + +// The compression snippets below run against a live server. They are kept out +// of the CI run set (compile-only) and skip when executed directly. Rotational +// Quantization (RQ) is enabled per named vector through its VectorConfig. + +// TestEnableRQ enables 8-bit RQ for a new collection at creation time. +func TestEnableRQ(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + _ = client.Collections.Delete(ctx, "Article") + defer client.Collections.Delete(ctx, "Article") + + // START EnableRQ + _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "Article", + Vectors: map[string]collections.VectorConfig{ + "default": {Vectorizer: model2vec.Text2Vec{}, Compression: compression.RQ{Bits: 8}}, + }, + }) + // END EnableRQ + if err != nil { + t.Fatal(err) + } +} + +// TestEnableRQ1Bit enables 1-bit RQ for a new collection at creation time. +func TestEnableRQ1Bit(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + _ = client.Collections.Delete(ctx, "Article") + defer client.Collections.Delete(ctx, "Article") + + // START 1BitEnableRQ + _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "Article", + Vectors: map[string]collections.VectorConfig{ + "default": {Vectorizer: model2vec.Text2Vec{}, Compression: compression.RQ{Bits: 1}}, + }, + }) + // END 1BitEnableRQ + if err != nil { + t.Fatal(err) + } +} + +// TestRQWithOptions enables RQ and tunes its parameters alongside the vector +// index configuration. +func TestRQWithOptions(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + _ = client.Collections.Delete(ctx, "Article") + defer client.Collections.Delete(ctx, "Article") + + // START RQWithOptions + vectorConfig := collections.VectorConfig{ + Index: vectorindex.HFresh{Distance: vectorindex.DistanceCosine}, + Compression: compression.RQ{ + Bits: 8, + RescoreLimit: 20, + Cache: true, + }, + Vectorizer: model2vec.Text2Vec{}, + } + _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "Article", + Vectors: map[string]collections.VectorConfig{"default": vectorConfig}, + }) + // END RQWithOptions + if err != nil { + t.Fatal(err) + } +} diff --git a/_includes/code/go-v6/manage_collections_test.go b/_includes/code/go-v6/manage_collections_test.go index da58d685..f3b3b065 100644 --- a/_includes/code/go-v6/manage_collections_test.go +++ b/_includes/code/go-v6/manage_collections_test.go @@ -28,3 +28,102 @@ func TestCreateCollection(t *testing.T) { t.Fatal(err) } } + +// The collection-management snippets below run against a live server. They are +// kept out of the CI run set (compile-only) and skip when executed directly. + +// TestBasicCreateCollection creates a collection with only a name. Missing +// properties are added by auto-schema when data is first inserted. +func TestBasicCreateCollection(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + _ = client.Collections.Delete(ctx, "Article") + defer client.Collections.Delete(ctx, "Article") + + // START BasicCreateCollection + _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "Article", + }) + // END BasicCreateCollection + if err != nil { + t.Fatal(err) + } +} + +// TestCreateCollectionWithProperties defines the collection properties and +// their data types up front instead of relying on auto-schema. +func TestCreateCollectionWithProperties(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + _ = client.Collections.Delete(ctx, "Article") + defer client.Collections.Delete(ctx, "Article") + + // START CreateCollectionWithProperties + _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "Article", + Properties: []collections.Property{ + {Name: "title", DataType: collections.DataTypeText}, + {Name: "body", DataType: collections.DataTypeText}, + }, + }) + // END CreateCollectionWithProperties + if err != nil { + t.Fatal(err) + } +} + +// TestCheckIfExists reports whether a collection is defined in the schema. +func TestCheckIfExists(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START CheckIfExists + exists, err := client.Collections.Exists(ctx, "Article") + // END CheckIfExists + if err != nil { + t.Fatal(err) + } + t.Logf("Article exists: %v", exists) +} + +// TestReadOneCollection reads a single collection definition from the schema. +func TestReadOneCollection(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START ReadOneCollection + config, err := client.Collections.GetConfig(ctx, "Article") + // END ReadOneCollection + if err != nil { + t.Fatal(err) + } + t.Logf("%s", config.Name) +} + +// TestReadAllCollections reads every collection definition in the schema. +func TestReadAllCollections(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + // START ReadAllCollections + configs, err := client.Collections.List(ctx) + // END ReadAllCollections + if err != nil { + t.Fatal(err) + } + for _, config := range configs { + t.Logf("%s", config.Name) + } +} diff --git a/_includes/code/go-v6/vector_config_test.go b/_includes/code/go-v6/vector_config_test.go new file mode 100644 index 00000000..48af6374 --- /dev/null +++ b/_includes/code/go-v6/vector_config_test.go @@ -0,0 +1,224 @@ +package main + +import ( + "context" + "testing" + + "github.com/weaviate/weaviate-go-client/v6/collections" + "github.com/weaviate/weaviate-go-client/v6/collections/compression" + "github.com/weaviate/weaviate-go-client/v6/collections/vectorindex" + "github.com/weaviate/weaviate-go-client/v6/modules/model2vec" + "github.com/weaviate/weaviate-go-client/v6/modules/selfprovided" +) + +// The vectorizer and vector-index snippets below run against a live server. +// They are kept out of the CI run set (compile-only) and skip when executed +// directly. Named vectors are the default model: each entry of the Vectors map +// carries its own index, compression, and vectorizer. + +// TestCreateCollectionWithVectorizer configures a vectorizer that generates an +// embedding for each object. +func TestCreateCollectionWithVectorizer(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + _ = client.Collections.Delete(ctx, "Article") + defer client.Collections.Delete(ctx, "Article") + + // START CreateCollectionWithVectorizer + _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "Article", + Properties: []collections.Property{ + {Name: "title", DataType: collections.DataTypeText}, + {Name: "body", DataType: collections.DataTypeText}, + }, + Vectors: map[string]collections.VectorConfig{ + "default": {Vectorizer: model2vec.Text2Vec{}}, + }, + }) + // END CreateCollectionWithVectorizer + if err != nil { + t.Fatal(err) + } +} + +// TestVectorizerSettings configures the vectorizer, such as which inference +// service it calls and which properties it embeds. +func TestVectorizerSettings(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + _ = client.Collections.Delete(ctx, "Article") + defer client.Collections.Delete(ctx, "Article") + + // START VectorizerSettings + // Point the vectorizer at a remote inference service and embed only the + // listed properties. + vectorizer := model2vec.Text2Vec{ + URL: "http://text2vec-model2vec:8080", + Properties: []string{"title"}, + } + _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "Article", + Properties: []collections.Property{ + {Name: "title", DataType: collections.DataTypeText}, + {Name: "body", DataType: collections.DataTypeText}, + }, + Vectors: map[string]collections.VectorConfig{ + "default": {Vectorizer: vectorizer}, + }, + }) + // END VectorizerSettings + if err != nil { + t.Fatal(err) + } +} + +// TestCreateCollectionWithNamedVectors defines multiple named vectors, each +// with its own configuration. +func TestCreateCollectionWithNamedVectors(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + _ = client.Collections.Delete(ctx, "Article") + defer client.Collections.Delete(ctx, "Article") + + // START CreateCollectionWithNamedVectors + _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "Article", + Properties: []collections.Property{ + {Name: "title", DataType: collections.DataTypeText}, + {Name: "body", DataType: collections.DataTypeText}, + }, + Vectors: map[string]collections.VectorConfig{ + // A vector generated from the title only. + "title": {Vectorizer: model2vec.Text2Vec{Properties: []string{"title"}}}, + // A vector you supply yourself at import time. + "custom": {Vectorizer: selfprovided.Vectorizer}, + }, + }) + // END CreateCollectionWithNamedVectors + if err != nil { + t.Fatal(err) + } +} + +// TestSetVectorIndexType selects the vector index type for a named vector. +func TestSetVectorIndexType(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + _ = client.Collections.Delete(ctx, "Article") + defer client.Collections.Delete(ctx, "Article") + + // START SetVectorIndexType + _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "Article", + Vectors: map[string]collections.VectorConfig{ + "default": {Index: vectorindex.HFresh{}, Vectorizer: model2vec.Text2Vec{}}, + }, + }) + // END SetVectorIndexType + if err != nil { + t.Fatal(err) + } +} + +// TestSetVectorIndexParams tunes the vector index and enables vector +// compression for a named vector. +func TestSetVectorIndexParams(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + _ = client.Collections.Delete(ctx, "Article") + defer client.Collections.Delete(ctx, "Article") + + // START SetVectorIndexParams + vectorConfig := collections.VectorConfig{ + Index: vectorindex.HFresh{ + Distance: vectorindex.DistanceCosine, + MaxPostingSizeKB: 1024, + ReplicaCount: 3, + SearchProbe: 64, + }, + Compression: compression.RQ{ + Bits: 8, + RescoreLimit: 20, + Cache: true, + }, + Vectorizer: model2vec.Text2Vec{}, + } + _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "Article", + Vectors: map[string]collections.VectorConfig{"default": vectorConfig}, + }) + // END SetVectorIndexParams + if err != nil { + t.Fatal(err) + } +} + +// TestPropModuleSettings sets property-level options, such as tokenization, +// and controls which properties the vectorizer embeds. +func TestPropModuleSettings(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + _ = client.Collections.Delete(ctx, "Article") + defer client.Collections.Delete(ctx, "Article") + + // START PropModuleSettings + _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "Article", + Properties: []collections.Property{ + {Name: "title", DataType: collections.DataTypeText, Tokenization: collections.TokenizationWord}, + {Name: "chunk", DataType: collections.DataTypeText, Tokenization: collections.TokenizationWhitespace}, + }, + Vectors: map[string]collections.VectorConfig{ + // Embed the title only; chunk is left out of the vector. + "default": {Vectorizer: model2vec.Text2Vec{Properties: []string{"title"}}}, + }, + }) + // END PropModuleSettings + if err != nil { + t.Fatal(err) + } +} + +// TestDistanceMetric sets the distance metric for a collection that stores +// user-supplied vectors. +func TestDistanceMetric(t *testing.T) { + t.Skip("requires a running Weaviate instance") + ctx := context.Background() + client := connectLocal(t) + defer client.Close() + + _ = client.Collections.Delete(ctx, "Article") + defer client.Collections.Delete(ctx, "Article") + + // START DistanceMetric + // Bring your own vectors and set the distance metric on the index. + index := vectorindex.HFresh{Distance: vectorindex.DistanceCosine} + _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "Article", + Vectors: map[string]collections.VectorConfig{ + "default": {Index: index, Vectorizer: selfprovided.Vectorizer}, + }, + }) + // END DistanceMetric + if err != nil { + t.Fatal(err) + } +} diff --git a/docs/weaviate/config-refs/collections.mdx b/docs/weaviate/config-refs/collections.mdx index 752db36a..c41fe955 100644 --- a/docs/weaviate/config-refs/collections.mdx +++ b/docs/weaviate/config-refs/collections.mdx @@ -10,6 +10,7 @@ import FilteredTextBlock from "@site/src/components/Documentation/FilteredTextBl import PyCode from "!!raw-loader!/_includes/code/config-refs/reference.collections.py"; import TSCode from "!!raw-loader!/_includes/code/howto/manage-data.collections.ts"; import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/manage-data.classes_test.go"; +import GoV6VectorCode from "!!raw-loader!/_includes/code/go-v6/vector_config_test.go"; import PyCodeMultiTenancy from "!!raw-loader!/_includes/code/howto/manage-data.multi-tenancy.py"; import TSCodeMultiTenancy from "!!raw-loader!/_includes/code/howto/manage-data.multi-tenancy.ts"; import GoCodeMultiTenancy from "!!raw-loader!/_includes/code/howto/go/docs/manage-data.multi-tenancy_test.go"; @@ -585,6 +586,14 @@ This code example shows how to configure the vectorizer parameters for a single language="gonew" /> + + + :::tip Further resources @@ -628,6 +637,14 @@ This code example shows how to configure multiple named vectors through a client language="gonew" /> + + + :::tip Further resources diff --git a/docs/weaviate/config-refs/indexing/inverted-index.mdx b/docs/weaviate/config-refs/indexing/inverted-index.mdx index a9da3d3a..e5b20ff7 100644 --- a/docs/weaviate/config-refs/indexing/inverted-index.mdx +++ b/docs/weaviate/config-refs/indexing/inverted-index.mdx @@ -10,6 +10,7 @@ import FilteredTextBlock from "@site/src/components/Documentation/FilteredTextBl import PyCode from "!!raw-loader!/_includes/code/config-refs/reference.collections.py"; import TSCode from "!!raw-loader!/_includes/code/howto/manage-data.collections.ts"; import GoCode from "!!raw-loader!/_includes/code/howto/go/docs/manage-data.classes_test.go"; +import GoV6CollectionConfigCode from "!!raw-loader!/_includes/code/go-v6/collection_config_test.go"; import PyTokenizeEndpoint from "!!raw-loader!/_includes/code/tutorials/tokenization/tokenize_endpoint.py"; The **[inverted index](../../concepts/indexing/inverted-index.md)** maps values (like words or numbers) to the objects that contain them. It is the backbone for all attribute-based filtering (`where` filters) and keyword searching (`bm25`, `hybrid`). @@ -74,6 +75,14 @@ This code example shows how to configure inverted index parameters through a cli language="gonew" /> + + + --- diff --git a/docs/weaviate/configuration/compression/rq-compression.md b/docs/weaviate/configuration/compression/rq-compression.md index b8841b25..7ff5204a 100644 --- a/docs/weaviate/configuration/compression/rq-compression.md +++ b/docs/weaviate/configuration/compression/rq-compression.md @@ -11,6 +11,7 @@ import Rq1bit from '/_includes/feature-notes/rq-1bit.mdx'; import FilteredTextBlock from '@site/src/components/Documentation/FilteredTextBlock'; import PyCode from '!!raw-loader!/\_includes/code/howto/configure-rq/rq-compression-v4.py'; import GoCode from '!!raw-loader!/\_includes/code/howto/go/docs/configure/compression.rq_test.go'; +import GoV6CompressionCode from '!!raw-loader!/\_includes/code/go-v6/compression_test.go'; import TSCode from '!!raw-loader!/\_includes/code/howto/configure-rq/rq-compression-v3.ts'; import JavaCode from '!!raw-loader!/\_includes/code/java-v6/src/test/java/ConfigureRQTest.java'; import CSharpCode from "!!raw-loader!/\_includes/code/csharp/ConfigureRQTest.cs"; @@ -59,6 +60,14 @@ RQ can be enabled at collection creation time through the collection definition: language="go" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Date: Wed, 22 Jul 2026 22:49:33 +0200 Subject: [PATCH 08/22] test(go-v6): anchor manage-collections run pattern so new config tests stay compile-only The test_manage_collections run pattern `TestCreateCollection` is an unanchored regex that now also matches the new compile-only config tests (TestCreateCollectionWithProperties / WithVectorizer / WithNamedVectors). Anchor it to `TestCreateCollection$` so the CI run-set is exactly the intended base smoke test; the new config tests stay out of the -run set and remain compile-only (mirrors how 3b/3c model-dependent tests are handled). --- tests/test_go.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_go.py b/tests/test_go.py index 60bd620b..653cf70d 100644 --- a/tests/test_go.py +++ b/tests/test_go.py @@ -45,7 +45,7 @@ def test_connection(empty_weaviates, run_pattern): @pytest.mark.parametrize( "run_pattern", [ - "TestCreateCollection", + "TestCreateCollection$", ], ) def test_manage_collections(empty_weaviates, run_pattern): From 7d59edfbf52add014222f3c928a79ad1f1582f43 Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Thu, 23 Jul 2026 07:07:33 +0200 Subject: [PATCH 09/22] fix(go-v6): skip local api-key connect test in CI (default-port instance is anonymous) TestConnectLocalAuth called NewLocal(WithAPIKey(...)), which dials the default localhost:8080. In docs CI that port is the ANONYMOUS instance, so the auth header returns HTTP 401. The API-key instance runs on a separate non-default port (8099/50052) that NewLocal's defaults cannot target, and Go snippets are not port-substituted the way Python/TS snippets are. Its old skip-guard (WEAVIATE_API_KEY == "") did not fire because CI sets WEAVIATE_API_KEY. Fix: make TestConnectLocalAuth skip unconditionally with a clear reason, keeping the displayed NewLocal(WithAPIKey(...)) snippet idiomatic (java-v6 instead hard- codes CI ports 8099/50052 into its rendered snippet; we keep ours clean and skip). Also rename TestConnectWeaviateCloud -> TestConnectCloud so it falls inside the CI run-set regex (TestConnectLocal|TestConnectCloud) and skips cleanly via its WEAVIATE_URL guard. Connect run-set audit (go test -run 'TestConnectLocal|TestConnectCloud'): - TestConnectLocalNoAuth: runs, passes (anon 8080 smoke). - TestConnectLocalAuth: runs, SKIPS (was 401). - TestConnectLocalThirdPartyAPIKeys: runs, skips when COHERE_API_KEY unset (harmless header on anon 8080 if set). - TestConnectCloud / TestConnectCloudThirdPartyAPIKeys: run, SKIP (WEAVIATE_URL unset). - TestConnectCustomURL / TestConnectOIDC: not matched by the regex, not run. Validated: go vet ./... && go build ./... pass with CI replace injected; go.mod keeps require-no-replace. --- _includes/code/go-v6/connect_test.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/_includes/code/go-v6/connect_test.go b/_includes/code/go-v6/connect_test.go index 12c8f514..06aaa63c 100644 --- a/_includes/code/go-v6/connect_test.go +++ b/_includes/code/go-v6/connect_test.go @@ -56,11 +56,14 @@ func TestConnectCustomURL(t *testing.T) { } // TestConnectLocalAuth connects to a local instance with a Weaviate API key. +// +// It is skipped in docs CI: the default-port local Weaviate there is anonymous, +// while the API-key instance runs on a non-default port that NewLocal's defaults +// cannot target. Unlike the Python and TypeScript snippets, Go snippets are not +// port-substituted before the tests run, so this would dial the anonymous +// instance and fail with HTTP 401. The snippet is still compiled and rendered. func TestConnectLocalAuth(t *testing.T) { - apiKey := os.Getenv("WEAVIATE_API_KEY") - if apiKey == "" { - t.Skip("WEAVIATE_API_KEY must be set for the local API-key connection test") - } + t.Skip("docs CI local Weaviate is anonymous on the default port; the API-key instance is on a non-default port that NewLocal cannot target") ctx := context.Background() // START LocalAuth @@ -103,8 +106,8 @@ func TestConnectLocalThirdPartyAPIKeys(t *testing.T) { } } -// TestConnectWeaviateCloud connects to a Weaviate Cloud instance with an API key. -func TestConnectWeaviateCloud(t *testing.T) { +// TestConnectCloud connects to a Weaviate Cloud instance with an API key. +func TestConnectCloud(t *testing.T) { if os.Getenv("WEAVIATE_URL") == "" || os.Getenv("WEAVIATE_API_KEY") == "" { t.Skip("WEAVIATE_URL and WEAVIATE_API_KEY must be set for the cloud connection test") } From 41eca7b78fe25dc1c541e7e371331e38a9488c14 Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Thu, 23 Jul 2026 08:07:32 +0200 Subject: [PATCH 10/22] =?UTF-8?q?test(go-v6):=20tier=201=20=E2=80=94=20run?= =?UTF-8?q?=20no-infra=20snippet=20tests=20live=20in=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Broaden the Go v6 docs lane past the 4-test smoke set so every snippet test that needs NO new provisioning executes live against the anonymous :8080 instance CI already starts. test_go.py: replace the four narrow `-run`-pattern parametrized cases with a single `@pytest.mark.go` test that runs the whole suite (`go test ./... -v -count=1`, no `-run`). Selection is now by `t.Skip`, not a run pattern: every test without a skip executes; every test with one is excluded. Failure formatting/truncation is unchanged. go-v6 module: make `t.Skip` presence match the Tier-1 boundary. - Un-skip 10 Tier-1 tests that self-seed with BYO vectors (no vectorizer, auth, or external seed): TestBasicCreateCollection, TestCreateCollectionWithProperties, TestCheckIfExists, TestReadAllCollections, TestEnableMultiTenancy, TestEnableAutoMT, TestEnableInvertedIndex, TestSetInvertedIndexParams, TestShardingSettings, TestDistanceMetric. - Add `t.Skip("enabled in a later CI tier")` to 28 non-Tier-1 tests that the whole-suite run would otherwise execute and fail (they need a vectorizer / seed / auth): all of filters_test.go (14) and hybrid_test.go (10), plus TestGetWithCrossRefs, TestMultiTenancy (search_basic), TestGetNearText, TestNamedVectorNearText. Result: the non-skipped set is exactly the 33 Tier-1 tests plus the 4 pre-existing baseline smoke tests (TestConnectLocalNoAuth, TestCreateCollection, TestCreateObject, TestBasicQuery). Tiers T2-T6 (seed/auth/vectorizer) stay skipped. `go vet`/`go build` clean under the local client replace; go.mod left with no replace directive. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01WmugebxuspdSQrFKE883W5 --- .../code/go-v6/collection_config_test.go | 3 - _includes/code/go-v6/filters_test.go | 14 +++++ _includes/code/go-v6/hybrid_test.go | 10 +++ .../code/go-v6/manage_collections_test.go | 4 -- _includes/code/go-v6/search_basic_test.go | 2 + _includes/code/go-v6/search_test.go | 2 + _includes/code/go-v6/tenants_test.go | 2 - _includes/code/go-v6/vector_config_test.go | 1 - tests/test_go.py | 62 +++++-------------- 9 files changed, 42 insertions(+), 58 deletions(-) diff --git a/_includes/code/go-v6/collection_config_test.go b/_includes/code/go-v6/collection_config_test.go index 934f4a2d..7cbf79d7 100644 --- a/_includes/code/go-v6/collection_config_test.go +++ b/_includes/code/go-v6/collection_config_test.go @@ -15,7 +15,6 @@ import ( // TestEnableInvertedIndex turns on property-level inverted indexes for // filtering, searching, and range filtering. func TestEnableInvertedIndex(t *testing.T) { - t.Skip("requires a running Weaviate instance") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -40,7 +39,6 @@ func TestEnableInvertedIndex(t *testing.T) { // TestSetInvertedIndexParams configures collection-level inverted index // parameters, including BM25 tuning and stopwords. func TestSetInvertedIndexParams(t *testing.T) { - t.Skip("requires a running Weaviate instance") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -108,7 +106,6 @@ func TestAllReplicationSettings(t *testing.T) { // TestShardingSettings configures sharding for the collection. func TestShardingSettings(t *testing.T) { - t.Skip("requires a running Weaviate instance") ctx := context.Background() client := connectLocal(t) defer client.Close() diff --git a/_includes/code/go-v6/filters_test.go b/_includes/code/go-v6/filters_test.go index 3690105d..43914c19 100644 --- a/_includes/code/go-v6/filters_test.go +++ b/_includes/code/go-v6/filters_test.go @@ -15,6 +15,7 @@ import ( // the only variable. func TestSingleFilter(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -39,6 +40,7 @@ func TestSingleFilter(t *testing.T) { } func TestMultipleFiltersAnd(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -70,6 +72,7 @@ func TestMultipleFiltersAnd(t *testing.T) { } func TestMultipleFiltersNested(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -108,6 +111,7 @@ func TestMultipleFiltersNested(t *testing.T) { } func TestContainsAnyFilter(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -135,6 +139,7 @@ func TestContainsAnyFilter(t *testing.T) { } func TestContainsAllFilter(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -161,6 +166,7 @@ func TestContainsAllFilter(t *testing.T) { } func TestContainsNoneFilter(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -187,6 +193,7 @@ func TestContainsNoneFilter(t *testing.T) { } func TestLikeFilter(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -212,6 +219,7 @@ func TestLikeFilter(t *testing.T) { } func TestCrossReferenceFilter(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -237,6 +245,7 @@ func TestCrossReferenceFilter(t *testing.T) { } func TestFilterByDate(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -262,6 +271,7 @@ func TestFilterByDate(t *testing.T) { } func TestFilterById(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -286,6 +296,7 @@ func TestFilterById(t *testing.T) { } func TestFilterByTimestamp(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -311,6 +322,7 @@ func TestFilterByTimestamp(t *testing.T) { } func TestFilterByPropertyLength(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -336,6 +348,7 @@ func TestFilterByPropertyLength(t *testing.T) { } func TestFilterByPropertyNullState(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -362,6 +375,7 @@ func TestFilterByPropertyNullState(t *testing.T) { // TestNearTextWithFilter combines a semantic search with a filter. It queries a // collection whose vectorizer turns the query text into a vector server-side. func TestNearTextWithFilter(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() diff --git a/_includes/code/go-v6/hybrid_test.go b/_includes/code/go-v6/hybrid_test.go index 79119bac..bb52e4f3 100644 --- a/_includes/code/go-v6/hybrid_test.go +++ b/_includes/code/go-v6/hybrid_test.go @@ -15,6 +15,7 @@ import ( // collection. func TestHybridBasic(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -35,6 +36,7 @@ func TestHybridBasic(t *testing.T) { } func TestHybridWithScore(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -64,6 +66,7 @@ func TestHybridWithScore(t *testing.T) { } func TestHybridWithAlpha(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -86,6 +89,7 @@ func TestHybridWithAlpha(t *testing.T) { } func TestHybridWithFusionType(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -107,6 +111,7 @@ func TestHybridWithFusionType(t *testing.T) { } func TestHybridWithProperties(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -129,6 +134,7 @@ func TestHybridWithProperties(t *testing.T) { } func TestHybridWithPropertyWeighting(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -151,6 +157,7 @@ func TestHybridWithPropertyWeighting(t *testing.T) { } func TestHybridWithVector(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -178,6 +185,7 @@ func TestHybridWithVector(t *testing.T) { } func TestHybridLimit(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -198,6 +206,7 @@ func TestHybridLimit(t *testing.T) { } func TestHybridAutocut(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -218,6 +227,7 @@ func TestHybridAutocut(t *testing.T) { } func TestHybridWithFilter(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() diff --git a/_includes/code/go-v6/manage_collections_test.go b/_includes/code/go-v6/manage_collections_test.go index f3b3b065..e0c6e84c 100644 --- a/_includes/code/go-v6/manage_collections_test.go +++ b/_includes/code/go-v6/manage_collections_test.go @@ -35,7 +35,6 @@ func TestCreateCollection(t *testing.T) { // TestBasicCreateCollection creates a collection with only a name. Missing // properties are added by auto-schema when data is first inserted. func TestBasicCreateCollection(t *testing.T) { - t.Skip("requires a running Weaviate instance") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -56,7 +55,6 @@ func TestBasicCreateCollection(t *testing.T) { // TestCreateCollectionWithProperties defines the collection properties and // their data types up front instead of relying on auto-schema. func TestCreateCollectionWithProperties(t *testing.T) { - t.Skip("requires a running Weaviate instance") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -80,7 +78,6 @@ func TestCreateCollectionWithProperties(t *testing.T) { // TestCheckIfExists reports whether a collection is defined in the schema. func TestCheckIfExists(t *testing.T) { - t.Skip("requires a running Weaviate instance") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -112,7 +109,6 @@ func TestReadOneCollection(t *testing.T) { // TestReadAllCollections reads every collection definition in the schema. func TestReadAllCollections(t *testing.T) { - t.Skip("requires a running Weaviate instance") ctx := context.Background() client := connectLocal(t) defer client.Close() diff --git a/_includes/code/go-v6/search_basic_test.go b/_includes/code/go-v6/search_basic_test.go index 4f222be4..8df85e19 100644 --- a/_includes/code/go-v6/search_basic_test.go +++ b/_includes/code/go-v6/search_basic_test.go @@ -180,6 +180,7 @@ func TestGetObjectId(t *testing.T) { // TestGetWithCrossRefs returns properties from cross-referenced objects. func TestGetWithCrossRefs(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -235,6 +236,7 @@ func TestGetWithMetadata(t *testing.T) { // TestMultiTenancy queries a specific tenant of a multi-tenant collection. func TestMultiTenancy(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() diff --git a/_includes/code/go-v6/search_test.go b/_includes/code/go-v6/search_test.go index f5f9bde5..5f7945d6 100644 --- a/_includes/code/go-v6/search_test.go +++ b/_includes/code/go-v6/search_test.go @@ -63,6 +63,7 @@ func setupJeopardySearch(t *testing.T, client *weaviate.Client) { // TestGetNearText runs a semantic search over a collection whose vectorizer // turns the query text into a vector server-side. func TestGetNearText(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -124,6 +125,7 @@ func TestGetNearVector(t *testing.T) { // TestNamedVectorNearText searches a named vector by passing the vector name as // the search target. func TestNamedVectorNearText(t *testing.T) { + t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() diff --git a/_includes/code/go-v6/tenants_test.go b/_includes/code/go-v6/tenants_test.go index 131d8d94..12a2d83c 100644 --- a/_includes/code/go-v6/tenants_test.go +++ b/_includes/code/go-v6/tenants_test.go @@ -16,7 +16,6 @@ import ( // TestEnableMultiTenancy creates a collection with multi-tenancy turned on. func TestEnableMultiTenancy(t *testing.T) { - t.Skip("requires a running Weaviate instance") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -39,7 +38,6 @@ func TestEnableMultiTenancy(t *testing.T) { // TestEnableAutoMT creates a multi-tenancy collection that also creates and // activates tenants automatically when data is inserted for an unknown tenant. func TestEnableAutoMT(t *testing.T) { - t.Skip("requires a running Weaviate instance") ctx := context.Background() client := connectLocal(t) defer client.Close() diff --git a/_includes/code/go-v6/vector_config_test.go b/_includes/code/go-v6/vector_config_test.go index 48af6374..464ed911 100644 --- a/_includes/code/go-v6/vector_config_test.go +++ b/_includes/code/go-v6/vector_config_test.go @@ -200,7 +200,6 @@ func TestPropModuleSettings(t *testing.T) { // TestDistanceMetric sets the distance metric for a collection that stores // user-supplied vectors. func TestDistanceMetric(t *testing.T) { - t.Skip("requires a running Weaviate instance") ctx := context.Background() client := connectLocal(t) defer client.Close() diff --git a/tests/test_go.py b/tests/test_go.py index 653cf70d..99aa0731 100644 --- a/tests/test_go.py +++ b/tests/test_go.py @@ -7,9 +7,11 @@ GO_V6_CWD = "_includes/code/go-v6" -def run_go_test(run_pattern, empty_weaviates): +def run_go_suite(): # -count=1 disables the test cache so each run actually hits Weaviate. - command = shlex.split(f"go test ./... -run {run_pattern} -v -count=1") + # No -run filter: t.Skip (not a run pattern) is the selection mechanism, so + # every test WITHOUT a t.Skip executes and every test WITH one is excluded. + command = shlex.split("go test ./... -v -count=1") env = dict(os.environ) try: @@ -18,7 +20,7 @@ def run_go_test(run_pattern, empty_weaviates): capture_output=True, text=True, check=True, ) except subprocess.CalledProcessError as error: - details = [f"Go tests matching {run_pattern!r} failed (exit code {error.returncode})"] + details = [f"Go tests failed (exit code {error.returncode})"] if error.stdout: details.append(f"\n--- STDOUT (last 80 lines) ---\n{chr(10).join(error.stdout.splitlines()[-80:])}") if error.stderr: @@ -26,49 +28,13 @@ def run_go_test(run_pattern, empty_weaviates): pytest.fail("\n".join(details)) -# Smoke set for the Go v6 client. Each op is a standalone `go test` target so a -# failure points at the exact snippet. The cloud connection case skips itself -# when WEAVIATE_URL / WEAVIATE_API_KEY are unset; every other test dials the -# local docker stack on :8080 / :50051. +# Run the whole Go v6 snippet suite live. Selection is by t.Skip, not a `-run` +# pattern: every test without a t.Skip executes against the local docker stack on +# :8080 / :50051 and self-seeds via helpers like setupArticle / setupJeopardy / +# setupJeopardySearch (bring-your-own vectors, no vectorizer). Tests that need +# provisioning not yet available in this CI tier (auth, a vectorizer, or an +# external seed) guard themselves with t.Skip and are excluded; the cloud, OIDC, +# and third-party-key connection snippets self-skip when their env vars are unset. @pytest.mark.go -@pytest.mark.parametrize( - "run_pattern", - [ - "TestConnectLocal|TestConnectCloud", - ], -) -def test_connection(empty_weaviates, run_pattern): - run_go_test(run_pattern, empty_weaviates) - - -@pytest.mark.go -@pytest.mark.parametrize( - "run_pattern", - [ - "TestCreateCollection$", - ], -) -def test_manage_collections(empty_weaviates, run_pattern): - run_go_test(run_pattern, empty_weaviates) - - -@pytest.mark.go -@pytest.mark.parametrize( - "run_pattern", - [ - "TestCreateObject", - ], -) -def test_manage_objects(empty_weaviates, run_pattern): - run_go_test(run_pattern, empty_weaviates) - - -@pytest.mark.go -@pytest.mark.parametrize( - "run_pattern", - [ - "TestBasicQuery", - ], -) -def test_search(empty_weaviates, run_pattern): - run_go_test(run_pattern, empty_weaviates) +def test_go_v6_suite(empty_weaviates): + run_go_suite() From 7c77dd229caca6d4a46b8b5b298d9fe78f927072 Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Thu, 23 Jul 2026 08:38:19 +0200 Subject: [PATCH 11/22] fix(go-v6): full CI failure reporting; defer Replace (client id bug) test_go.py: on failure, list EVERY `--- FAIL:` line grepped from the full captured stdout (the tail alone can hide failures that occurred early in the run) and bump the stdout tail from 80 to 200 lines, so each CI run surfaces the complete failure set. Also drop the unused `result` capture. TestReplaceObject: skip with a documented reason. Root cause is a go-client v6 (alpha) bug, NOT the snippet: Data.Replace serializes the PUT body without the object id (sends it only in the URL path), but Weaviate's class-scoped PUT handler requires the body id to equal the path id (usecases/objects/update.go), so the server returns HTTP 422 "field 'id' is immutable". The published UpdateReplace snippet is already idiomatic and carries the UUID; re-enable once the client sends the id. Needs a client fix (escalated on board 37ea394e). TestDeleteMany: left running (not skipped). Verified against source: the seed inserts category="GEOGRAPHY" which the category==GEOGRAPHY filter matches, the filter targets a filterable text property (same filter.Cond pattern that passes in TestGetWithFilter), and the res.Errors loop is a correct no-op when Verbose is unset -- so there is no static defect to fix. Single-object Data.Delete (TestDeleteObject) passes, so this is batch-delete-specific; its true runtime error will surface via the improved reporting on the next CI run. Tier-1 boundary otherwise unchanged; no new tests un-skipped. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01WmugebxuspdSQrFKE883W5 --- _includes/code/go-v6/manage_objects_test.go | 7 +++++++ tests/test_go.py | 17 ++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/_includes/code/go-v6/manage_objects_test.go b/_includes/code/go-v6/manage_objects_test.go index 8907721a..1e46001f 100644 --- a/_includes/code/go-v6/manage_objects_test.go +++ b/_includes/code/go-v6/manage_objects_test.go @@ -62,6 +62,13 @@ func TestCreateObject(t *testing.T) { } func TestReplaceObject(t *testing.T) { + // Deferred: this is a go-client v6 (alpha) bug, not a snippet bug. The client's + // Replace serializes the PUT body WITHOUT the object id (it sends the id only in + // the URL path), but Weaviate's class-scoped PUT handler requires the body id to + // equal the path id, so the server rejects it with HTTP 422 "field 'id' is + // immutable". The published UpdateReplace snippet below is already idiomatic and + // carries the object's UUID; re-enable this test once the client sends the id. + t.Skip("go-client v6 Data.Replace omits the object id from the PUT body; Weaviate requires body id == path id (HTTP 422 \"field 'id' is immutable\") โ€” deferred pending a client fix") ctx := context.Background() client := connectLocal(t) defer client.Close() diff --git a/tests/test_go.py b/tests/test_go.py index 99aa0731..7df2231f 100644 --- a/tests/test_go.py +++ b/tests/test_go.py @@ -15,14 +15,25 @@ def run_go_suite(): env = dict(os.environ) try: - result = subprocess.run( + subprocess.run( command, cwd=GO_V6_CWD, env=env, capture_output=True, text=True, check=True, ) except subprocess.CalledProcessError as error: details = [f"Go tests failed (exit code {error.returncode})"] - if error.stdout: - details.append(f"\n--- STDOUT (last 80 lines) ---\n{chr(10).join(error.stdout.splitlines()[-80:])}") + + stdout = error.stdout or "" + + # Surface the COMPLETE set of failing tests first. `go test -v` prints a + # `--- FAIL: TestName` line per failure, but those can scroll off the top + # of the tail below, hiding failures that occurred early in the run. + # Grepping the full captured stdout guarantees every failure is listed. + failing = [line.strip() for line in stdout.splitlines() if "--- FAIL:" in line] + if failing: + details.append(f"\n--- FAILING TESTS ({len(failing)}) ---\n" + "\n".join(failing)) + + if stdout: + details.append(f"\n--- STDOUT (last 200 lines) ---\n{chr(10).join(stdout.splitlines()[-200:])}") if error.stderr: details.append(f"\n--- STDERR (last 40 lines) ---\n{chr(10).join(error.stderr.splitlines()[-40:])}") pytest.fail("\n".join(details)) From bd0eed512457bb3172de7a2218d3d236376cdadb Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Thu, 23 Jul 2026 08:57:41 +0200 Subject: [PATCH 12/22] fix(go-v6): per-failure CI reporting; skip OIDC + client-broken DeleteMany; triage aggregate test_go.py: on failure, extract each failing test's own RUN..FAIL block from the captured stdout (walking back from `--- FAIL: TestX` to `=== RUN TestX`) and include every block in the pytest.fail details, alongside the failing-test name list and the 200-line tail. Early failures (e.g. the aggregate tests, which run first) no longer lose their real error to the tail. TestConnectOIDC: skip. The default-port CI instance is anonymous with no OIDC provider, so dialing it with a bearer token fails discovery with HTTP 404 "get openid configuration". OIDC needs an OIDC-configured instance (a later tier). Snippet unchanged. TestDeleteMany: skip. Confirmed CLIENT bug (not the snippet): Data.DeleteSelected panics because *api.DeleteObjectsRequest is not wired into the gRPC transport dispatch (internal/api/transport/transport.go:129-144 has no BatchDeleteRequest case) -> dev.Assert(false, "...does not implement MessageMarshaler..."). Snippet unchanged. Escalated on board 37ea394e. Aggregate (5 tests): triaged, LEFT RUNNING. Not the same class of bug -- the transport DOES have the aggregate case (transport.go:133), AggregateRequest implements the full Message interface, and request-building / single-result parsing are structurally sound. So aggregate is not clearly broken client-side; Task-1 reporting will surface each test's real error on the next run. (One caveat noted: internal/api/aggregate.go:293 by.GetPath()[0] could index-panic on an empty group path, but that affects only GroupBy, not all five.) go vet + go build clean under the local client replace; go.mod left with no replace directive. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01WmugebxuspdSQrFKE883W5 --- _includes/code/go-v6/connect_test.go | 5 +++ _includes/code/go-v6/manage_objects_test.go | 6 +++ tests/test_go.py | 48 +++++++++++++++++++-- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/_includes/code/go-v6/connect_test.go b/_includes/code/go-v6/connect_test.go index 06aaa63c..de8da281 100644 --- a/_includes/code/go-v6/connect_test.go +++ b/_includes/code/go-v6/connect_test.go @@ -161,6 +161,11 @@ func TestConnectCloudThirdPartyAPIKeys(t *testing.T) { // TestConnectOIDC connects to a self-hosted instance using an OIDC bearer token // obtained from an identity provider. func TestConnectOIDC(t *testing.T) { + // The default-port CI instance is anonymous with no OIDC provider, so dialing + // it with a bearer token fails at discovery with HTTP 404 "get openid + // configuration". OIDC needs an OIDC-configured instance, which is a later CI + // tier. The snippet is still compiled and rendered. + t.Skip("anon :8080 CI instance has no OIDC provider (HTTP 404 'get openid configuration'); OIDC connection needs an OIDC-configured instance โ€” a later CI tier") if os.Getenv("WEAVIATE_OIDC_ACCESS_TOKEN") == "" { t.Skip("WEAVIATE_OIDC_ACCESS_TOKEN must be set for the OIDC connection test") } diff --git a/_includes/code/go-v6/manage_objects_test.go b/_includes/code/go-v6/manage_objects_test.go index 1e46001f..64d0e792 100644 --- a/_includes/code/go-v6/manage_objects_test.go +++ b/_includes/code/go-v6/manage_objects_test.go @@ -145,6 +145,12 @@ func TestDeleteObject(t *testing.T) { } func TestDeleteMany(t *testing.T) { + // Deferred: this is a go-client v6 (alpha) bug, not a snippet bug. Data.DeleteSelected + // panics because *api.DeleteObjectsRequest is not wired into the gRPC transport + // dispatch (internal/api/transport/transport.go has no BatchDeleteRequest case), so it + // falls through to dev.Assert(false, "...does not implement MessageMarshaler..."). The + // published DeleteMany snippet below is idiomatic; re-enable once the client wires it up. + t.Skip("go-client v6 Data.DeleteSelected panics โ€” *api.DeleteObjectsRequest not wired to gRPC MessageMarshaler; deferred pending a client fix") ctx := context.Background() client := connectLocal(t) defer client.Close() diff --git a/tests/test_go.py b/tests/test_go.py index 7df2231f..6b2c941f 100644 --- a/tests/test_go.py +++ b/tests/test_go.py @@ -2,11 +2,42 @@ import pytest import shlex import os +import re GO_V6_CWD = "_includes/code/go-v6" +def _failing_test_names(lines): + # `go test -v` prints a `--- FAIL: TestName (0.00s)` line per failed test. + names = [] + for line in lines: + m = re.match(r"\s*--- FAIL:\s+(\S+)", line) + if m and m.group(1) not in names: + names.append(m.group(1)) + return names + + +def _fail_block(lines, name): + # Extract a failing test's own output: from its `=== RUN TestName` line to + # its `--- FAIL: TestName` line. Walking back from the FAIL line means the + # block is captured no matter where in the run the test executed, so an early + # test's error is not lost to the last-N-lines tail. + fail_idx = None + for i, line in enumerate(lines): + s = line.strip() + if s == f"--- FAIL: {name}" or s.startswith(f"--- FAIL: {name} "): + fail_idx = i # keep the last occurrence + if fail_idx is None: + return None + run_idx = 0 + for j in range(fail_idx, -1, -1): + if lines[j].strip() == f"=== RUN {name}": + run_idx = j + break + return "\n".join(lines[run_idx:fail_idx + 1]) + + def run_go_suite(): # -count=1 disables the test cache so each run actually hits Weaviate. # No -run filter: t.Skip (not a run pattern) is the selection mechanism, so @@ -23,17 +54,26 @@ def run_go_suite(): details = [f"Go tests failed (exit code {error.returncode})"] stdout = error.stdout or "" + lines = stdout.splitlines() # Surface the COMPLETE set of failing tests first. `go test -v` prints a # `--- FAIL: TestName` line per failure, but those can scroll off the top # of the tail below, hiding failures that occurred early in the run. # Grepping the full captured stdout guarantees every failure is listed. - failing = [line.strip() for line in stdout.splitlines() if "--- FAIL:" in line] - if failing: - details.append(f"\n--- FAILING TESTS ({len(failing)}) ---\n" + "\n".join(failing)) + names = _failing_test_names(lines) + if names: + details.append(f"\n--- FAILING TESTS ({len(names)}) ---\n" + "\n".join(names)) + + # Then include each failing test's own RUN..FAIL block, so every + # failure's real error is visible regardless of where it ran, even if the + # error scrolled out of the tail below (e.g. the aggregate tests run first). + for name in names: + block = _fail_block(lines, name) + if block: + details.append(f"\n--- FAILURE: {name} ---\n{block}") if stdout: - details.append(f"\n--- STDOUT (last 200 lines) ---\n{chr(10).join(stdout.splitlines()[-200:])}") + details.append(f"\n--- STDOUT (last 200 lines) ---\n{chr(10).join(lines[-200:])}") if error.stderr: details.append(f"\n--- STDERR (last 40 lines) ---\n{chr(10).join(error.stderr.splitlines()[-40:])}") pytest.fail("\n".join(details)) From c2955dbf4b64c6f58b11db727e92f3bab2168d64 Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Thu, 23 Jul 2026 09:21:59 +0200 Subject: [PATCH 13/22] fix(go-v6): seed helper sets "none" vectorizer (HELPER FIX); skip HFresh distance test on old CI server setupJeopardySearch built the "default" named vector as an empty VectorConfig `{}`. That serializes to an empty vectorConfig.default, which the server rejects at create time with HTTP 422 code 602 "vectorConfig.default in body is required" (entities/models/class.go:217 validate.Required fails on the zero value). This blocked ALL of search_basic_test.go, search_test.go, and aggregate_test.go (~16 tests) via their shared seed. Not a client bug: the VectorConfig doc contract (collections/collection.go:86-91) says that when no vectorizer module is selected the field must be set to the "none" option. HELPER FIX: setupJeopardySearch now uses `{Vectorizer: selfprovided.Vectorizer}` (Name() == "none"), which makes the config non-zero; no index is required (the server defaults vectorIndexType to hnsw). The fix is outside the // START/// END markers, so the published snippets are unchanged. This unblocks the ~16 seed-dependent tests. TestDistanceMetric: skipped. It creates its own collection with an explicit selfprovided vectorizer, so it never hit the 602 -- it fails because it sets the distance on vectorindex.HFresh, the v6 client's ONLY registered index, and docs CI pins WEAVIATE_VERSION 1.35.0, which predates HFresh and rejects it with HTTP 422 "invalid vector index 'hfresh'". No HNSW index type exists in the v6 client to substitute, so this needs a newer server (>= 1.37). Snippet unchanged. Note: docs CI runs Weaviate 1.35.0 (docs_tests.yml), not the 1.38 the CodeBrief assumed -- relevant for any version-gated feature. go vet + go build clean under the local client replace; go.mod has no replace directive. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01WmugebxuspdSQrFKE883W5 --- _includes/code/go-v6/search_test.go | 8 +++++--- _includes/code/go-v6/vector_config_test.go | 7 +++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/_includes/code/go-v6/search_test.go b/_includes/code/go-v6/search_test.go index 5f7945d6..495d37bf 100644 --- a/_includes/code/go-v6/search_test.go +++ b/_includes/code/go-v6/search_test.go @@ -7,6 +7,7 @@ import ( weaviate "github.com/weaviate/weaviate-go-client/v6" "github.com/weaviate/weaviate-go-client/v6/collections" "github.com/weaviate/weaviate-go-client/v6/data" + "github.com/weaviate/weaviate-go-client/v6/modules/selfprovided" "github.com/weaviate/weaviate-go-client/v6/query" "github.com/weaviate/weaviate-go-client/v6/query/filter" "github.com/weaviate/weaviate-go-client/v6/types" @@ -32,10 +33,11 @@ func setupJeopardySearch(t *testing.T, client *weaviate.Client) { {Name: "category", DataType: collections.DataTypeText}, {Name: "points", DataType: collections.DataTypeInt}, }, - // An empty VectorConfig registers a named vector with no vectorizer, so - // vectors must be supplied at insert time. + // A named vector that brings its own vectors: the server requires every + // vector config to name a vectorizer, so use the "none" vectorizer + // (selfprovided) and supply the vectors explicitly at insert time. Vectors: map[string]collections.VectorConfig{ - "default": {}, + "default": {Vectorizer: selfprovided.Vectorizer}, }, }); err != nil { t.Fatalf("create JeopardyQuestion collection: %v", err) diff --git a/_includes/code/go-v6/vector_config_test.go b/_includes/code/go-v6/vector_config_test.go index 464ed911..6e2964c4 100644 --- a/_includes/code/go-v6/vector_config_test.go +++ b/_includes/code/go-v6/vector_config_test.go @@ -200,6 +200,13 @@ func TestPropModuleSettings(t *testing.T) { // TestDistanceMetric sets the distance metric for a collection that stores // user-supplied vectors. func TestDistanceMetric(t *testing.T) { + // Skipped in docs CI: the distance metric is set on the vector index, and the + // v6 client only registers the experimental HFresh index. The CI server + // (WEAVIATE_VERSION 1.35.0) predates HFresh and rejects it at create time with + // HTTP 422 "invalid vector index 'hfresh'". There is no HNSW index type in the + // v6 client to substitute, so this needs a server that supports HFresh (>= 1.37). + // The snippet is still compiled and rendered. + t.Skip("v6 client's only vector index is the experimental HFresh; the docs-CI server (1.35.0) does not support it โ€” re-enable on a newer server") ctx := context.Background() client := connectLocal(t) defer client.Close() From 4ee7c22d683174055f32e7bc3896193a63539eda Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Thu, 23 Jul 2026 16:36:17 +0200 Subject: [PATCH 14/22] fix(go-v6): name the target vector in NearVector snippets (v6 named-vectors idiom) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Name: "default" to the target vector in the 7 NearVector query snippets (6 in search_test.go, 1 in aggregate_test.go) so the query target matches the seed collection's "default" named vector, instead of sending an empty-name target that the server cannot resolve. This is the correct v6 named-vectors idiom (cf. example/basic/main.go). Approved published-snippet change. The 6 search NearVector tests now pass live against Weaviate 1.38. Two tests surfaced separate, newly-unmasked blockers and are skipped with accurate reasons pending decisions: - TestAggregateNearVector: Weaviate 1.38 panics server-side (nil pointer) on aggregate near-vector over a BYO/selfprovided collection, for every query shape tested. Core/server bug; the snippet is wired to no docs page. - TestDistanceMetric: HFresh IS supported on 1.38 (the "1.35.0" WEAVIATE_VERSION workflow var is stale/unused; the compose files pin 1.38.0) โ€” this corrects the old, false skip reason. Remaining blocker: HFresh requires maxPostingSizeKB >= 8, but the published vector-config.mdx snippet leaves it at 0. Verified MaxPostingSizeKB: 8 fixes it; the published-content change is pending sign-off. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01WmugebxuspdSQrFKE883W5 --- _includes/code/go-v6/aggregate_test.go | 9 ++++++++- _includes/code/go-v6/search_test.go | 12 ++++++------ _includes/code/go-v6/vector_config_test.go | 16 +++++++++------- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/_includes/code/go-v6/aggregate_test.go b/_includes/code/go-v6/aggregate_test.go index a4a64e1e..e6b2367c 100644 --- a/_includes/code/go-v6/aggregate_test.go +++ b/_includes/code/go-v6/aggregate_test.go @@ -118,6 +118,13 @@ func TestAggregateGroupBy(t *testing.T) { // This snippet is not yet wired into a docs page, but it exercises the // implemented near-vector aggregation path. func TestAggregateNearVector(t *testing.T) { + // The named-target fix below is correct (the "default" target now resolves), but + // Weaviate 1.38.0 panics server-side ("nil pointer dereference") on aggregate + // near-vector against a bring-your-own-vector (selfprovided) collection โ€” for every + // query shape tested, with or without Similarity/ObjectLimit. This is a core/server + // bug, not a client or snippet issue, and the snippet is not wired into any docs + // page. Re-enable once the server handles this path. Tracked for the beta follow-up. + t.Skip("Weaviate 1.38 panics on aggregate near-vector over a BYO-vector collection (server bug)") ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -131,7 +138,7 @@ func TestAggregateNearVector(t *testing.T) { jeopardy := client.Collections.Use("JeopardyQuestion") result, err := jeopardy.Aggregate.NearVector(ctx, aggregate.NearVector{ Query: query.NearVector{ - Target: &types.Vector{Single: vector}, + Target: &types.Vector{Name: "default", Single: vector}, Similarity: query.Distance(0.3), }, ObjectLimit: 10, diff --git a/_includes/code/go-v6/search_test.go b/_includes/code/go-v6/search_test.go index 495d37bf..6442350f 100644 --- a/_includes/code/go-v6/search_test.go +++ b/_includes/code/go-v6/search_test.go @@ -106,7 +106,7 @@ func TestGetNearVector(t *testing.T) { jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.NearVector(ctx, query.NearVector{ - Target: &types.Vector{Single: vector}, + Target: &types.Vector{Name: "default", Single: vector}, Limit: 2, ReturnMetadata: query.ReturnMetadata{ Distance: true, @@ -166,7 +166,7 @@ func TestGetWithDistance(t *testing.T) { jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.NearVector(ctx, query.NearVector{ - Target: &types.Vector{Single: vector}, + Target: &types.Vector{Name: "default", Single: vector}, Similarity: query.Distance(0.25), ReturnMetadata: query.ReturnMetadata{ Distance: true, @@ -195,7 +195,7 @@ func TestGetLimitOffset(t *testing.T) { jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.NearVector(ctx, query.NearVector{ - Target: &types.Vector{Single: vector}, + Target: &types.Vector{Name: "default", Single: vector}, Limit: 2, Offset: 1, }) @@ -222,7 +222,7 @@ func TestAutocut(t *testing.T) { jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.NearVector(ctx, query.NearVector{ - Target: &types.Vector{Single: vector}, + Target: &types.Vector{Name: "default", Single: vector}, // Return objects from the first similarity cluster only. AutoLimit: 1, }) @@ -250,7 +250,7 @@ func TestGetWithGroupBy(t *testing.T) { jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.NearVector.GroupBy(ctx, query.NearVector{ - Target: &types.Vector{Single: vector}, + Target: &types.Vector{Name: "default", Single: vector}, Limit: 10, }, query.GroupBy{ @@ -282,7 +282,7 @@ func TestGetWithFilter(t *testing.T) { jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.NearVector(ctx, query.NearVector{ - Target: &types.Vector{Single: vector}, + Target: &types.Vector{Name: "default", Single: vector}, Limit: 2, Filter: &filter.Cond{ Target: "category", diff --git a/_includes/code/go-v6/vector_config_test.go b/_includes/code/go-v6/vector_config_test.go index 6e2964c4..d5964c09 100644 --- a/_includes/code/go-v6/vector_config_test.go +++ b/_includes/code/go-v6/vector_config_test.go @@ -200,13 +200,15 @@ func TestPropModuleSettings(t *testing.T) { // TestDistanceMetric sets the distance metric for a collection that stores // user-supplied vectors. func TestDistanceMetric(t *testing.T) { - // Skipped in docs CI: the distance metric is set on the vector index, and the - // v6 client only registers the experimental HFresh index. The CI server - // (WEAVIATE_VERSION 1.35.0) predates HFresh and rejects it at create time with - // HTTP 422 "invalid vector index 'hfresh'". There is no HNSW index type in the - // v6 client to substitute, so this needs a server that supports HFresh (>= 1.37). - // The snippet is still compiled and rendered. - t.Skip("v6 client's only vector index is the experimental HFresh; the docs-CI server (1.35.0) does not support it โ€” re-enable on a newer server") + // HFresh IS supported by the docs-CI server (Weaviate 1.38; the WEAVIATE_VERSION + // "1.35.0" workflow var is stale and unused โ€” the compose files pin 1.38.0). The + // remaining blocker is the snippet itself: HFresh requires maxPostingSizeKB >= 8, + // but the displayed snippet leaves it at the zero value, so the server rejects the + // create with HTTP 422 "invalid hfresh config: maxPostingSizeKB is '0' but must be + // at least 8". Adding an explicit MaxPostingSizeKB to HFresh in the DisplayedSnippet + // (rendered on manage-collections/vector-config.mdx) is a published-content change + // pending sign-off; verified that MaxPostingSizeKB: 8 makes the create succeed. + t.Skip("HFresh needs maxPostingSizeKB >= 8; adding it to the published snippet needs sign-off (see comment)") ctx := context.Background() client := connectLocal(t) defer client.Close() From 9353123cc67ff080c34e3e47f2979abf5379a435 Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Thu, 23 Jul 2026 18:20:38 +0200 Subject: [PATCH 15/22] fix(go-v6): set HFresh MaxPostingSizeKB and un-skip DistanceMetric The DistanceMetric snippet built vectorindex.HFresh with maxPostingSizeKB at the zero value, which Weaviate 1.38 rejects at create time (HTTP 422 "invalid hfresh config: maxPostingSizeKB is '0' but must be at least 8"). Set MaxPostingSizeKB: 8 in the displayed snippet (verified live to fix the 422) and remove the t.Skip so TestDistanceMetric runs in CI. HFresh is supported by the docs-CI server (1.38); the stale "1.35.0" WEAVIATE_VERSION workflow var is unused. Corrected comment retained. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01WmugebxuspdSQrFKE883W5 --- _includes/code/go-v6/vector_config_test.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/_includes/code/go-v6/vector_config_test.go b/_includes/code/go-v6/vector_config_test.go index d5964c09..89541847 100644 --- a/_includes/code/go-v6/vector_config_test.go +++ b/_includes/code/go-v6/vector_config_test.go @@ -200,15 +200,11 @@ func TestPropModuleSettings(t *testing.T) { // TestDistanceMetric sets the distance metric for a collection that stores // user-supplied vectors. func TestDistanceMetric(t *testing.T) { - // HFresh IS supported by the docs-CI server (Weaviate 1.38; the WEAVIATE_VERSION - // "1.35.0" workflow var is stale and unused โ€” the compose files pin 1.38.0). The - // remaining blocker is the snippet itself: HFresh requires maxPostingSizeKB >= 8, - // but the displayed snippet leaves it at the zero value, so the server rejects the - // create with HTTP 422 "invalid hfresh config: maxPostingSizeKB is '0' but must be - // at least 8". Adding an explicit MaxPostingSizeKB to HFresh in the DisplayedSnippet - // (rendered on manage-collections/vector-config.mdx) is a published-content change - // pending sign-off; verified that MaxPostingSizeKB: 8 makes the create succeed. - t.Skip("HFresh needs maxPostingSizeKB >= 8; adding it to the published snippet needs sign-off (see comment)") + // Runs against the docs-CI server (Weaviate 1.38; the WEAVIATE_VERSION "1.35.0" + // workflow var is stale and unused โ€” the compose files pin 1.38.0), which supports + // the HFresh vector index. HFresh requires maxPostingSizeKB >= 8, so the snippet + // sets it explicitly; otherwise the server rejects the create with HTTP 422 + // "invalid hfresh config: maxPostingSizeKB is '0' but must be at least 8". ctx := context.Background() client := connectLocal(t) defer client.Close() @@ -218,7 +214,7 @@ func TestDistanceMetric(t *testing.T) { // START DistanceMetric // Bring your own vectors and set the distance metric on the index. - index := vectorindex.HFresh{Distance: vectorindex.DistanceCosine} + index := vectorindex.HFresh{Distance: vectorindex.DistanceCosine, MaxPostingSizeKB: 8} _, err := client.Collections.Create(ctx, collections.Collection{ Name: "Article", Vectors: map[string]collections.VectorConfig{ From 002f018dd68a58197b11788599fd2246b92a97f0 Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Thu, 30 Jul 2026 13:54:47 +0200 Subject: [PATCH 16/22] test(go-v6): report each Go test as its own CI case (regression visibility) Replace the single opaque test_go_v6_suite (which reported "1 passed / 354 deselected" and hid the real Go pass/skip/fail breakdown) with one pytest case per Go test function. - A session-scoped fixture (go_suite_results) runs the suite ONCE via `go test ./... -json -count=1` in _includes/code/go-v6 and parses the JSON events into {topLevelTest: {outcome, output}}. - Test names are discovered by a cheap static scan of `func TestXxx(t *testing.T)` in the *_test.go sources (no `go test -list`), so importing this module in the non-Go pytest jobs never spawns the Go toolchain. The suite has no subtests, so the scan matches the -json results 1:1; subtests (if added) roll up to their parent. - Each @pytest.mark.go param maps its result: pass -> pass, skip -> pytest.skip with the reason pulled from the test's output, fail -> pytest.fail with that test's captured output. A build failure yields zero results and raises GoBuildError loudly with the compiler + stderr output. Reporting only: no *_test.go snippet or t.Skip changed; `go` marker and its pytest.ini registration preserved. pytest -m go now collects 150 cases. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01WmugebxuspdSQrFKE883W5 --- tests/test_go.py | 251 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 171 insertions(+), 80 deletions(-) diff --git a/tests/test_go.py b/tests/test_go.py index 6b2c941f..2ba4b206 100644 --- a/tests/test_go.py +++ b/tests/test_go.py @@ -1,91 +1,182 @@ -import subprocess -import pytest -import shlex +import glob +import json import os import re +import shlex +import subprocess + +import pytest -GO_V6_CWD = "_includes/code/go-v6" +# Absolute path to the Go v6 snippet module, derived from this file so both the +# import-time source scan and the runtime `go test` subprocess resolve it the +# same way regardless of pytest's working directory. +_HERE = os.path.dirname(os.path.abspath(__file__)) +GO_V6_CWD = os.path.normpath(os.path.join(_HERE, "..", "_includes", "code", "go-v6")) -def _failing_test_names(lines): - # `go test -v` prints a `--- FAIL: TestName (0.00s)` line per failed test. +# --------------------------------------------------------------------------- # +# Name discovery (collection time) +# --------------------------------------------------------------------------- # +# One pytest case is collected per Go test function so CI reports the true +# pass/skip/fail breakdown (regression visibility) instead of one opaque case. +# +# Names are discovered by statically scanning the *_test.go sources rather than +# invoking the Go toolchain (`go test -list`). test_go.py is imported by EVERY +# pytest job (java, python, ...) โ€” which then deselect by marker โ€” so the +# parametrize argument must stay cheap and side-effect-free: a `go test -list` +# here would spawn the compiler (and fail on a not-yet-wired client) in jobs +# that have nothing to do with Go. The suite uses no subtests (no `t.Run`), so +# each top-level `func TestXxx(t *testing.T)` maps 1:1 to a `go test -json` +# result, making the source scan exact. +_TEST_FUNC_RE = re.compile(r"^func (Test\w*)\s*\(\s*\w+\s+\*testing\.T\s*\)") + + +def discover_test_names(): names = [] - for line in lines: - m = re.match(r"\s*--- FAIL:\s+(\S+)", line) - if m and m.group(1) not in names: - names.append(m.group(1)) - return names - - -def _fail_block(lines, name): - # Extract a failing test's own output: from its `=== RUN TestName` line to - # its `--- FAIL: TestName` line. Walking back from the FAIL line means the - # block is captured no matter where in the run the test executed, so an early - # test's error is not lost to the last-N-lines tail. - fail_idx = None - for i, line in enumerate(lines): - s = line.strip() - if s == f"--- FAIL: {name}" or s.startswith(f"--- FAIL: {name} "): - fail_idx = i # keep the last occurrence - if fail_idx is None: - return None - run_idx = 0 - for j in range(fail_idx, -1, -1): - if lines[j].strip() == f"=== RUN {name}": - run_idx = j - break - return "\n".join(lines[run_idx:fail_idx + 1]) + for path in sorted(glob.glob(os.path.join(GO_V6_CWD, "*_test.go"))): + try: + with open(path, encoding="utf-8") as fh: + for line in fh: + match = _TEST_FUNC_RE.match(line) + if match: + names.append(match.group(1)) + except OSError: + continue + return sorted(set(names)) + + +# --------------------------------------------------------------------------- # +# `go test -json` parsing +# --------------------------------------------------------------------------- # +_SKIP_MSG_RE = re.compile(r"^\s+[\w./-]+\.go:\d+:\s?(.*)$") + + +def parse_go_json(stdout): + """Aggregate `go test -json` events into a per-top-level-test dict. + + Returns ``(results, build_output)`` where ``results`` maps each top-level + test name to ``{"outcome": "pass"|"fail"|"skip"|None, "output": str}`` and + ``build_output`` is the concatenated package-level output. Compiler errors + live in ``build_output`` because a failed build emits events with no ``Test`` + field (plus, on some toolchains, plain non-JSON lines) โ€” capturing them is + what lets a build failure surface loudly instead of silently yielding zero + results. + """ + results = {} + build_output = [] + for line in stdout.splitlines(): + line = line.strip() + if not line: + continue + try: + event = json.loads(line) + except json.JSONDecodeError: + # Not a JSON event: some Go toolchains print build errors as plain + # text. Keep it so `run_go_suite` can report it. + build_output.append(line + "\n") + continue + action = event.get("Action") + test = event.get("Test") + if not test: + # Package-level event (start/pass/fail/output for the whole build). + if action == "output": + build_output.append(event.get("Output", "")) + continue + # Roll subtests (TestFoo/case) up under their parent so reporting stays + # at the test-function granularity even if subtests are added later. + top = test.split("/", 1)[0] + entry = results.setdefault(top, {"outcome": None, "output": []}) + if action == "output": + entry["output"].append(event.get("Output", "")) + elif action in ("pass", "fail", "skip") and test == top: + # Only the parent's terminal event decides the reported outcome; + # go already folds a failing subtest into the parent's "fail". + entry["outcome"] = action + for entry in results.values(): + entry["output"] = "".join(entry["output"]) + return results, "".join(build_output) + + +def _skip_reason(output): + # A t.Skip("...") prints as a ` file.go:NN: ` line in the test's + # captured output; pull those out so the pytest SKIP shows the real reason. + reasons = [m.group(1) for m in (_SKIP_MSG_RE.match(l) for l in output.splitlines()) if m] + if reasons: + return "\n".join(reasons) + return "skipped (no reason captured)" + + +class GoBuildError(Exception): + """Raised when `go test` produced no per-test results (build/compile failure).""" + + +def _results_or_raise(returncode, stdout, stderr): + results, build_output = parse_go_json(stdout) + if not results: + # No per-test events at all => the suite never ran. Fail loudly with the + # compiler output so the break is diagnosable, not an opaque "0 tests". + detail = [ + f"Go suite produced no test results (go test exit code {returncode}); " + "the suite likely failed to build." + ] + build_tail = build_output.splitlines()[-200:] + if build_tail: + detail.append("\n--- BUILD OUTPUT ---\n" + "\n".join(build_tail)) + if stderr: + detail.append("\n--- STDERR ---\n" + "\n".join(stderr.splitlines()[-200:])) + raise GoBuildError("\n".join(detail)) + return results def run_go_suite(): - # -count=1 disables the test cache so each run actually hits Weaviate. - # No -run filter: t.Skip (not a run pattern) is the selection mechanism, so - # every test WITHOUT a t.Skip executes and every test WITH one is excluded. - command = shlex.split("go test ./... -v -count=1") - env = dict(os.environ) - - try: - subprocess.run( - command, cwd=GO_V6_CWD, env=env, - capture_output=True, text=True, check=True, - ) - except subprocess.CalledProcessError as error: - details = [f"Go tests failed (exit code {error.returncode})"] - - stdout = error.stdout or "" - lines = stdout.splitlines() - - # Surface the COMPLETE set of failing tests first. `go test -v` prints a - # `--- FAIL: TestName` line per failure, but those can scroll off the top - # of the tail below, hiding failures that occurred early in the run. - # Grepping the full captured stdout guarantees every failure is listed. - names = _failing_test_names(lines) - if names: - details.append(f"\n--- FAILING TESTS ({len(names)}) ---\n" + "\n".join(names)) - - # Then include each failing test's own RUN..FAIL block, so every - # failure's real error is visible regardless of where it ran, even if the - # error scrolled out of the tail below (e.g. the aggregate tests run first). - for name in names: - block = _fail_block(lines, name) - if block: - details.append(f"\n--- FAILURE: {name} ---\n{block}") - - if stdout: - details.append(f"\n--- STDOUT (last 200 lines) ---\n{chr(10).join(lines[-200:])}") - if error.stderr: - details.append(f"\n--- STDERR (last 40 lines) ---\n{chr(10).join(error.stderr.splitlines()[-40:])}") - pytest.fail("\n".join(details)) - - -# Run the whole Go v6 snippet suite live. Selection is by t.Skip, not a `-run` -# pattern: every test without a t.Skip executes against the local docker stack on -# :8080 / :50051 and self-seeds via helpers like setupArticle / setupJeopardy / -# setupJeopardySearch (bring-your-own vectors, no vectorizer). Tests that need -# provisioning not yet available in this CI tier (auth, a vectorizer, or an -# external seed) guard themselves with t.Skip and are excluded; the cloud, OIDC, -# and third-party-key connection snippets self-skip when their env vars are unset. + # Single invocation for the whole suite. -count=1 disables the test cache so + # each run actually hits Weaviate; -json emits one event per test action so + # we can report each test individually. Selection is by t.Skip (not a -run + # pattern): every test without a t.Skip executes, every test with one is + # reported as skipped with its reason. + command = shlex.split("go test ./... -json -count=1") + proc = subprocess.run( + command, cwd=GO_V6_CWD, env=dict(os.environ), + capture_output=True, text=True, + ) + return _results_or_raise(proc.returncode, proc.stdout, proc.stderr) + + +# --------------------------------------------------------------------------- # +# Fixture + parametrized reporting +# --------------------------------------------------------------------------- # +@pytest.fixture(scope="session") +def go_suite_results(empty_weaviates): + """Run the Go v6 snippet suite ONCE and return its parsed per-test results. + + Session-scoped so the ~150 parametrized cases below share a single + `go test` invocation. Depends on empty_weaviates so the docker stack is up + (a no-op in CI, where the workflow manages the containers). + """ + return run_go_suite() + + +# Run the whole Go v6 snippet suite live against the local docker stack on +# :8080 / :50051; tests self-seed via helpers like setupArticle / setupJeopardy. +# Each Go test function is reported as its own pytest case (regression +# visibility): a pass passes, a t.Skip becomes a pytest SKIP carrying its +# reason, and a failure fails the case with that test's own captured output. @pytest.mark.go -def test_go_v6_suite(empty_weaviates): - run_go_suite() +@pytest.mark.parametrize("test_name", discover_test_names()) +def test_go_v6(go_suite_results, test_name): + result = go_suite_results.get(test_name) + if result is None: + pytest.fail( + f"{test_name}: no result captured from `go test -json`. The test was " + "discovered in the source but did not run โ€” the suite may have failed " + "to build, or the function name drifted." + ) + outcome = result["outcome"] + output = result["output"] + if outcome == "pass": + return + if outcome == "skip": + pytest.skip(_skip_reason(output)) + # "fail" or a missing terminal event (e.g. a panic mid-test) => failure. + pytest.fail(output or f"{test_name} failed with no captured output.") From 3a8765e2fe4dd4a4720a86ae193d19698e3e1ad1 Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Thu, 30 Jul 2026 14:41:05 +0200 Subject: [PATCH 17/22] test(go-v6): tier 2 - enable filters, MT, aliases, multi-target live (self-seed + async-index settle) Un-park the T2 snippet tests that run against the anon :8080 instance and only need richer self-seeding (no vectorizer, no auth). All new seed/connect helpers live outside the START/END markers, so the displayed snippets are unchanged. Seed helpers added (main_test.go / topical files): - setupJeopardyDemo: full JeopardyQuestion schema (question/answer/category/round/ points/dateRecorded[date]) + hasCategory ref to a seeded JeopardyCategory, with the inverted-index null-state/property-length/timestamp flags on so those filters do not error. - createMultiTenancyCollection / setupMultiTenancy: MT MultiTenancyCollection (+ hasCategory ref to JeopardyCategory) and tenantA, seeded. - setupMultiTenancyJeopardy: MT JeopardyQuestion for the multi-tenancy read snippet. - setupArticleForAliases (+ ensureAlias): Article/ArticlesV2 and a per-test alias so the alias lifecycle runs order-independently. - setupJeopardyTiny: two BYO named vectors for multi-target near-vector. Flake guard: waitForCount polls Aggregate.OverAll TotalCount (bounded 30s) after every seed, so the ASYNC_INDEXING instance settles before the test queries. Un-skipped: filters (Single, MultipleFiltersAnd, MultipleFiltersNested, ContainsAny, ContainsAll, ContainsNone, Like, ById, PropertyLength, PropertyNullState, ByDate, ByTimestamp, CrossReference); search_basic (GetWithCrossRefs, MultiTenancy); tenants (AddTenantsToClass, ListTenants, RemoveTenants, CreateMtObject, MtSearch, MtAddCrossRef); aliases (all 7); multi-target (NearVector, MultipleNearVectorsV1, MultipleNearVectorsV2). Kept skipped: near-text/hybrid/named-vector (T3), RBAC (T4), Data.Replace, Data.DeleteSelected, aggregate-near-vector, and the not-yet-supported placeholders. --- _includes/code/go-v6/aliases_test.go | 91 ++++++- _includes/code/go-v6/filters_test.go | 52 +++- _includes/code/go-v6/main_test.go | 275 ++++++++++++++++++++++ _includes/code/go-v6/multi_target_test.go | 74 +++++- _includes/code/go-v6/search_basic_test.go | 8 +- _includes/code/go-v6/tenants_test.go | 32 ++- 6 files changed, 493 insertions(+), 39 deletions(-) diff --git a/_includes/code/go-v6/aliases_test.go b/_includes/code/go-v6/aliases_test.go index 387f7875..74f9d294 100644 --- a/_includes/code/go-v6/aliases_test.go +++ b/_includes/code/go-v6/aliases_test.go @@ -4,20 +4,75 @@ import ( "context" "testing" + weaviate "github.com/weaviate/weaviate-go-client/v6" "github.com/weaviate/weaviate-go-client/v6/collections" + "github.com/weaviate/weaviate-go-client/v6/data" "github.com/weaviate/weaviate-go-client/v6/query" ) -// The alias snippets below run against a live server. They are kept out of the -// CI run set (compile-only) and skip when executed directly. +// The alias snippets run live against the local instance. Aliases are +// instance-global, so each test seeds the collections it needs (Article and +// ArticlesV2) and the specific alias it operates on, rather than depending on +// the order the tests execute in. + +// setupArticleForAliases (re)creates Article and ArticlesV2 and seeds Article so +// a query through an alias returns data. +func setupArticleForAliases(t *testing.T, client *weaviate.Client) { + t.Helper() + ctx := context.Background() + _ = client.Collections.Delete(ctx, "Article") + _ = client.Collections.Delete(ctx, "ArticlesV2") + for _, name := range []string{"Article", "ArticlesV2"} { + if _, err := client.Collections.Create(ctx, collections.Collection{ + Name: name, + Properties: []collections.Property{ + {Name: "title", DataType: collections.DataTypeText}, + {Name: "body", DataType: collections.DataTypeText}, + }, + }); err != nil { + t.Fatalf("create %s collection: %v", name, err) + } + } + articles := client.Collections.Use("Article") + if _, err := articles.Data.Insert(ctx, + &data.Object{Properties: map[string]any{"title": "Weaviate", "body": "An open-source vector database"}}, + &data.Object{Properties: map[string]any{"title": "Vectors", "body": "Numeric representations of data"}}, + ); err != nil { + t.Fatalf("seed Article: %v", err) + } + waitForCount(t, articles, 2) +} + +// ensureAlias (re)creates an alias pointing at a collection, replacing any alias +// of the same name so the read/update/delete alias tests have a known starting +// point regardless of execution order. +func ensureAlias(t *testing.T, client *weaviate.Client, alias, collection string) { + t.Helper() + ctx := context.Background() + _ = client.Alias.Delete(ctx, alias) + if err := client.Alias.Create(ctx, collections.Alias{Alias: alias, Collection: collection}); err != nil { + t.Fatalf("create alias %s: %v", alias, err) + } +} + +// cleanupAliases removes the alias and collections created for the alias +// snippets. Call it with defer so the deletes run while the client is open. +func cleanupAliases(ctx context.Context, client *weaviate.Client) { + _ = client.Alias.Delete(ctx, "ArticlesProd") + _ = client.Collections.Delete(ctx, "Article") + _ = client.Collections.Delete(ctx, "ArticlesV2") +} // TestCreateAlias points a new alias at an existing collection. func TestCreateAlias(t *testing.T) { - t.Skip("requires a running Weaviate instance") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupArticleForAliases(t, client) + _ = client.Alias.Delete(ctx, "ArticlesProd") // clean slate: the snippet creates it + defer cleanupAliases(ctx, client) + // START CreateAlias err := client.Alias.Create(ctx, collections.Alias{ Alias: "ArticlesProd", @@ -31,11 +86,14 @@ func TestCreateAlias(t *testing.T) { // TestListAllAliases lists every alias defined in the instance. func TestListAllAliases(t *testing.T) { - t.Skip("requires a running Weaviate instance") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupArticleForAliases(t, client) + ensureAlias(t, client, "ArticlesProd", "Article") + defer cleanupAliases(ctx, client) + // START ListAllAliases aliases, err := client.Alias.List(ctx) if err != nil { @@ -49,11 +107,14 @@ func TestListAllAliases(t *testing.T) { // TestListCollectionAliases keeps only the aliases that point at one collection. func TestListCollectionAliases(t *testing.T) { - t.Skip("requires a running Weaviate instance") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupArticleForAliases(t, client) + ensureAlias(t, client, "ArticlesProd", "Article") + defer cleanupAliases(ctx, client) + // START ListCollectionAliases aliases, err := client.Alias.List(ctx) if err != nil { @@ -70,11 +131,14 @@ func TestListCollectionAliases(t *testing.T) { // TestGetAlias fetches a single alias by name. func TestGetAlias(t *testing.T) { - t.Skip("requires a running Weaviate instance") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupArticleForAliases(t, client) + ensureAlias(t, client, "ArticlesProd", "Article") + defer cleanupAliases(ctx, client) + // START GetAlias alias, err := client.Alias.Get(ctx, "ArticlesProd") if err != nil { @@ -86,11 +150,14 @@ func TestGetAlias(t *testing.T) { // TestUpdateAlias re-points an existing alias at a different collection. func TestUpdateAlias(t *testing.T) { - t.Skip("requires a running Weaviate instance") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupArticleForAliases(t, client) + ensureAlias(t, client, "ArticlesProd", "Article") + defer cleanupAliases(ctx, client) + // START UpdateAlias err := client.Alias.Update(ctx, collections.Alias{ Alias: "ArticlesProd", @@ -104,11 +171,14 @@ func TestUpdateAlias(t *testing.T) { // TestDeleteAlias removes an alias. The underlying collection is untouched. func TestDeleteAlias(t *testing.T) { - t.Skip("requires a running Weaviate instance") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupArticleForAliases(t, client) + ensureAlias(t, client, "ArticlesProd", "Article") + defer cleanupAliases(ctx, client) + // START DeleteAlias err := client.Alias.Delete(ctx, "ArticlesProd") // END DeleteAlias @@ -120,11 +190,14 @@ func TestDeleteAlias(t *testing.T) { // TestUseAlias queries through an alias. Anywhere a collection name is expected, // an alias name can be used in its place. func TestUseAlias(t *testing.T) { - t.Skip("requires a running Weaviate instance") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupArticleForAliases(t, client) + ensureAlias(t, client, "ArticlesProd", "Article") + defer cleanupAliases(ctx, client) + // START UseAlias // "ArticlesProd" is an alias; the query runs against its target collection. articles := client.Collections.Use("ArticlesProd") diff --git a/_includes/code/go-v6/filters_test.go b/_includes/code/go-v6/filters_test.go index 43914c19..506d0a50 100644 --- a/_includes/code/go-v6/filters_test.go +++ b/_includes/code/go-v6/filters_test.go @@ -15,11 +15,13 @@ import ( // the only variable. func TestSingleFilter(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyDemo(t, client) + defer cleanupJeopardyDemo(ctx, client) + // START SingleFilter jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ @@ -40,11 +42,13 @@ func TestSingleFilter(t *testing.T) { } func TestMultipleFiltersAnd(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyDemo(t, client) + defer cleanupJeopardyDemo(ctx, client) + // START MultipleFiltersAnd jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ @@ -72,11 +76,13 @@ func TestMultipleFiltersAnd(t *testing.T) { } func TestMultipleFiltersNested(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyDemo(t, client) + defer cleanupJeopardyDemo(ctx, client) + // START MultipleFiltersNested jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ @@ -111,11 +117,13 @@ func TestMultipleFiltersNested(t *testing.T) { } func TestContainsAnyFilter(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyDemo(t, client) + defer cleanupJeopardyDemo(ctx, client) + // START ContainsAnyFilter // The tokens to match against the tokenized "question" property. tokens := []string{"animal", "elephant"} @@ -139,11 +147,13 @@ func TestContainsAnyFilter(t *testing.T) { } func TestContainsAllFilter(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyDemo(t, client) + defer cleanupJeopardyDemo(ctx, client) + // START ContainsAllFilter tokens := []string{"blood", "glucose"} @@ -166,11 +176,13 @@ func TestContainsAllFilter(t *testing.T) { } func TestContainsNoneFilter(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyDemo(t, client) + defer cleanupJeopardyDemo(ctx, client) + // START ContainsNoneFilter tokens := []string{"animal", "elephant"} @@ -193,11 +205,13 @@ func TestContainsNoneFilter(t *testing.T) { } func TestLikeFilter(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyDemo(t, client) + defer cleanupJeopardyDemo(ctx, client) + // START LikeFilter jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ @@ -219,11 +233,13 @@ func TestLikeFilter(t *testing.T) { } func TestCrossReferenceFilter(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyDemo(t, client) + defer cleanupJeopardyDemo(ctx, client) + // START CrossReferenceFilter jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ @@ -245,11 +261,13 @@ func TestCrossReferenceFilter(t *testing.T) { } func TestFilterByDate(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyDemo(t, client) + defer cleanupJeopardyDemo(ctx, client) + // START FilterByDate jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ @@ -271,11 +289,13 @@ func TestFilterByDate(t *testing.T) { } func TestFilterById(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyDemo(t, client) + defer cleanupJeopardyDemo(ctx, client) + // START FilterById jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ @@ -296,11 +316,13 @@ func TestFilterById(t *testing.T) { } func TestFilterByTimestamp(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyDemo(t, client) + defer cleanupJeopardyDemo(ctx, client) + // START FilterByTimestamp jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ @@ -322,11 +344,13 @@ func TestFilterByTimestamp(t *testing.T) { } func TestFilterByPropertyLength(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyDemo(t, client) + defer cleanupJeopardyDemo(ctx, client) + // START FilterByPropertyLength jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ @@ -348,11 +372,13 @@ func TestFilterByPropertyLength(t *testing.T) { } func TestFilterByPropertyNullState(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyDemo(t, client) + defer cleanupJeopardyDemo(ctx, client) + // START FilterByPropertyNullState jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ diff --git a/_includes/code/go-v6/main_test.go b/_includes/code/go-v6/main_test.go index c22e4c7b..9ddc8964 100644 --- a/_includes/code/go-v6/main_test.go +++ b/_includes/code/go-v6/main_test.go @@ -3,9 +3,14 @@ package main import ( "context" "testing" + "time" + "github.com/google/uuid" weaviate "github.com/weaviate/weaviate-go-client/v6" + "github.com/weaviate/weaviate-go-client/v6/aggregate" "github.com/weaviate/weaviate-go-client/v6/collections" + "github.com/weaviate/weaviate-go-client/v6/data" + "github.com/weaviate/weaviate-go-client/v6/tenant" ) // connectLocal returns a client connected to a local Weaviate instance @@ -39,3 +44,273 @@ func setupArticle(t *testing.T, client *weaviate.Client) { t.Fatalf("create Article collection: %v", err) } } + +// waitForCount polls a collection handle until it reports at least n objects, +// then returns. The docs test instance runs with ASYNC_INDEXING enabled, so +// objects are not guaranteed to be queryable the instant Insert returns; polling +// the aggregate object count settles that race and keeps the seeded tests +// deterministic. For a multi-tenant collection, pass a tenant-bound handle so +// the aggregate is scoped to that tenant. +func waitForCount(t *testing.T, handle *collections.Handle, n int) { + t.Helper() + ctx := context.Background() + deadline := time.Now().Add(30 * time.Second) + var last int64 + for time.Now().Before(deadline) { + res, err := handle.Aggregate.OverAll(ctx, aggregate.OverAll{TotalCount: true}) + if err == nil && res.TotalCount != nil { + last = *res.TotalCount + if last >= int64(n) { + return + } + } + time.Sleep(200 * time.Millisecond) + } + t.Fatalf("waitForCount: %q reached %d/%d objects before timeout", handle.CollectionName(), last, n) +} + +// filterByIdSeedUUID is the id filtered on by TestFilterById; setupJeopardyDemo +// seeds one question with this id so that snippet returns a deterministic match. +const filterByIdSeedUUID = "00037775-1432-35e5-bc59-443baaef7d80" + +// Fixed ids for the demo JeopardyCategory rows so questions can be linked to +// them with AddReferences during seeding. +var ( + demoCatAnimals = uuid.MustParse("11111111-1111-4111-8111-111111111111") + demoCatSports = uuid.MustParse("22222222-2222-4222-8222-222222222222") + demoCatScience = uuid.MustParse("33333333-3333-4333-8333-333333333333") +) + +// setupJeopardyDemo (re)creates the full JeopardyQuestion demo collection used by +// the filter and cross-reference snippets: a question/answer/category/round/ +// points/dateRecorded schema plus a hasCategory reference to a seeded +// JeopardyCategory. It has no vectorizer, so the filter examples run on a plain +// fetch (Query.OverAll) with the filter as the only variable. +// +// The inverted index turns on the null-state, property-length and timestamp +// indexes because the FilterByPropertyNullState, FilterByPropertyLength and +// FilterByTimestamp snippets query those indexes and error without them. +func setupJeopardyDemo(t *testing.T, client *weaviate.Client) { + t.Helper() + ctx := context.Background() + _ = client.Collections.Delete(ctx, "JeopardyQuestion") + _ = client.Collections.Delete(ctx, "JeopardyCategory") + + // The reference target must exist before the collection that points to it. + if _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "JeopardyCategory", + Properties: []collections.Property{ + {Name: "title", DataType: collections.DataTypeText}, + }, + }); err != nil { + t.Fatalf("create JeopardyCategory collection: %v", err) + } + if _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "JeopardyQuestion", + Properties: []collections.Property{ + {Name: "question", DataType: collections.DataTypeText}, + {Name: "answer", DataType: collections.DataTypeText}, + {Name: "category", DataType: collections.DataTypeText}, + {Name: "round", DataType: collections.DataTypeText}, + {Name: "points", DataType: collections.DataTypeInt}, + {Name: "dateRecorded", DataType: collections.DataTypeDate}, + }, + References: []collections.Reference{ + {Name: "hasCategory", Collections: []string{"JeopardyCategory"}}, + }, + InvertedIndex: &collections.InvertedIndexConfig{ + IndexNullState: true, + IndexPropertyLength: true, + IndexTimestamps: true, + }, + }); err != nil { + t.Fatalf("create JeopardyQuestion collection: %v", err) + } + + categories := client.Collections.Use("JeopardyCategory") + if _, err := categories.Data.Insert(ctx, + &data.Object{UUID: &demoCatAnimals, Properties: map[string]any{"title": "Animals"}}, + &data.Object{UUID: &demoCatSports, Properties: map[string]any{"title": "Sports"}}, + &data.Object{UUID: &demoCatScience, Properties: map[string]any{"title": "Science"}}, + ); err != nil { + t.Fatalf("seed JeopardyCategory: %v", err) + } + + q1 := uuid.MustParse(filterByIdSeedUUID) + q2, q3, q5 := uuid.New(), uuid.New(), uuid.New() + jeopardy := client.Collections.Use("JeopardyQuestion") + if _, err := jeopardy.Data.Insert(ctx, + &data.Object{UUID: &q1, Properties: map[string]any{ + "question": "This organ removes excess glucose from the blood & stores it as glycogen", + "answer": "Liver", "category": "SCIENCE", "round": "Jeopardy!", "points": 100, + "dateRecorded": "2021-05-05T00:00:00Z", + }}, + &data.Object{UUID: &q2, Properties: map[string]any{ + "question": "This large animal, the elephant, is the only living member of Proboscidea", + "answer": "Elephant", "category": "ANIMALS", "round": "Double Jeopardy!", "points": 200, + "dateRecorded": "2022-01-01T00:00:00Z", + }}, + &data.Object{UUID: &q3, Properties: map[string]any{ + "question": "This tall animal has a long neck and roams the African savanna", + "answer": "Giraffe", "category": "ANIMALS", "round": "Double Jeopardy!", "points": 500, + "dateRecorded": "2023-03-03T00:00:00Z", + }}, + &data.Object{Properties: map[string]any{ + "question": "Bees build these six-sided structures to store honey in a hive", + "answer": "A honeycomb nest built by bees", "category": "NATURE", "round": "Jeopardy!", "points": 200, + "dateRecorded": "2020-06-06T00:00:00Z", + }}, + &data.Object{UUID: &q5, Properties: map[string]any{ + "question": "This racket sport holds its most famous tournament at Wimbledon", + "answer": "Tennis", "category": "SPORTS", "round": "Final Jeopardy!", "points": 800, + "dateRecorded": "2021-09-09T00:00:00Z", + }}, + // A question with no answer so FilterByPropertyNullState has a match. + &data.Object{Properties: map[string]any{ + "question": "This open-source vector database is written in Go", + "category": "TECH", "round": "Double Jeopardy!", "points": 400, + "dateRecorded": "2024-04-04T00:00:00Z", + }}, + ); err != nil { + t.Fatalf("seed JeopardyQuestion: %v", err) + } + + // Link a few questions to their categories so the cross-reference filter has + // data to traverse. Best-effort: only a transport error is fatal. + if _, err := jeopardy.Data.AddReferences(ctx, + data.Reference{Origin: data.ObjectPath{Collection: "JeopardyQuestion", Property: "hasCategory", UUID: q1}, UUID: demoCatScience}, + data.Reference{Origin: data.ObjectPath{Collection: "JeopardyQuestion", Property: "hasCategory", UUID: q2}, UUID: demoCatAnimals}, + data.Reference{Origin: data.ObjectPath{Collection: "JeopardyQuestion", Property: "hasCategory", UUID: q3}, UUID: demoCatAnimals}, + data.Reference{Origin: data.ObjectPath{Collection: "JeopardyQuestion", Property: "hasCategory", UUID: q5}, UUID: demoCatSports}, + ); err != nil { + t.Fatalf("seed JeopardyQuestion references: %v", err) + } + + waitForCount(t, jeopardy, 6) +} + +// cleanupJeopardyDemo removes the collections created by setupJeopardyDemo. Call +// it with defer so the delete runs while the client is still open. +func cleanupJeopardyDemo(ctx context.Context, client *weaviate.Client) { + _ = client.Collections.Delete(ctx, "JeopardyQuestion") + _ = client.Collections.Delete(ctx, "JeopardyCategory") +} + +// Fixed ids for the multi-tenancy seed so TestMtAddCrossRef can reference a real +// source object (a MultiTenancyCollection question in tenantA) and a real target +// object (a JeopardyCategory row) instead of dangling random ids. +var ( + mtSourceID = uuid.MustParse("44444444-4444-4444-8444-444444444444") + mtCategoryID = uuid.MustParse("55555555-5555-4555-8555-555555555555") +) + +// createMultiTenancyCollection (re)creates the MultiTenancyCollection schema used +// by the multi-tenancy snippets: a multi-tenant question collection with a +// hasCategory reference to a (non-tenant) JeopardyCategory, mirroring the docs +// demo. It creates no tenants and seeds no data, so the AddTenantsToClass snippet +// can create tenantA/tenantB itself. +func createMultiTenancyCollection(t *testing.T, client *weaviate.Client) { + t.Helper() + ctx := context.Background() + _ = client.Collections.Delete(ctx, "MultiTenancyCollection") + _ = client.Collections.Delete(ctx, "JeopardyCategory") + + if _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "JeopardyCategory", + Properties: []collections.Property{ + {Name: "title", DataType: collections.DataTypeText}, + }, + }); err != nil { + t.Fatalf("create JeopardyCategory collection: %v", err) + } + if _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "MultiTenancyCollection", + Properties: []collections.Property{ + {Name: "question", DataType: collections.DataTypeText}, + }, + References: []collections.Reference{ + {Name: "hasCategory", Collections: []string{"JeopardyCategory"}}, + }, + MultiTenancy: &collections.MultiTenancyConfig{Enabled: true}, + }); err != nil { + t.Fatalf("create MultiTenancyCollection: %v", err) + } +} + +// setupMultiTenancy (re)creates MultiTenancyCollection, adds tenantA and seeds it +// with a question (plus a JeopardyCategory row) so the tenant read/search and +// cross-reference snippets have data to work with. +func setupMultiTenancy(t *testing.T, client *weaviate.Client) { + t.Helper() + ctx := context.Background() + createMultiTenancyCollection(t, client) + + if _, err := client.Collections.Use("JeopardyCategory").Data.Insert(ctx, + &data.Object{UUID: &mtCategoryID, Properties: map[string]any{"title": "Software"}}, + ); err != nil { + t.Fatalf("seed JeopardyCategory: %v", err) + } + + collection := client.Collections.Use("MultiTenancyCollection") + if err := collection.Tenants.Create(ctx, tenant.Tenant{Name: "tenantA"}); err != nil { + t.Fatalf("create tenantA: %v", err) + } + + tenantA := client.Collections.Use("MultiTenancyCollection", collections.WithTenant("tenantA")) + if _, err := tenantA.Data.Insert(ctx, + &data.Object{UUID: &mtSourceID, Properties: map[string]any{ + "question": "This vector DB is OSS and supports automatic property type inference on import", + }}, + &data.Object{Properties: map[string]any{ + "question": "This organ removes excess glucose from the blood & stores it as glycogen", + }}, + ); err != nil { + t.Fatalf("seed tenantA: %v", err) + } + + waitForCount(t, tenantA, 2) +} + +// cleanupMultiTenancy removes the collections created for the multi-tenancy +// snippets. Call it with defer so the delete runs while the client is still open. +func cleanupMultiTenancy(ctx context.Context, client *weaviate.Client) { + _ = client.Collections.Delete(ctx, "MultiTenancyCollection") + _ = client.Collections.Delete(ctx, "JeopardyCategory") +} + +// setupMultiTenancyJeopardy (re)creates a multi-tenant JeopardyQuestion +// collection and seeds tenantA. The "read all objects" multi-tenancy search +// snippet binds tenantA on a JeopardyQuestion handle, so this seeds that exact +// collection name (distinct from the non-tenant JeopardyQuestion demo). +func setupMultiTenancyJeopardy(t *testing.T, client *weaviate.Client) { + t.Helper() + ctx := context.Background() + _ = client.Collections.Delete(ctx, "JeopardyQuestion") + if _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "JeopardyQuestion", + Properties: []collections.Property{ + {Name: "question", DataType: collections.DataTypeText}, + {Name: "answer", DataType: collections.DataTypeText}, + {Name: "category", DataType: collections.DataTypeText}, + {Name: "points", DataType: collections.DataTypeInt}, + }, + MultiTenancy: &collections.MultiTenancyConfig{Enabled: true}, + }); err != nil { + t.Fatalf("create multi-tenant JeopardyQuestion: %v", err) + } + + collection := client.Collections.Use("JeopardyQuestion") + if err := collection.Tenants.Create(ctx, tenant.Tenant{Name: "tenantA"}); err != nil { + t.Fatalf("create tenantA: %v", err) + } + + tenantA := client.Collections.Use("JeopardyQuestion", collections.WithTenant("tenantA")) + if _, err := tenantA.Data.Insert(ctx, + &data.Object{Properties: map[string]any{"question": "This organ removes excess glucose from the blood", "answer": "Liver", "category": "SCIENCE", "points": 100}}, + &data.Object{Properties: map[string]any{"question": "The only living mammal in the order Proboscidea", "answer": "Elephant", "category": "ANIMALS", "points": 200}}, + ); err != nil { + t.Fatalf("seed tenantA: %v", err) + } + + waitForCount(t, tenantA, 2) +} diff --git a/_includes/code/go-v6/multi_target_test.go b/_includes/code/go-v6/multi_target_test.go index f19a576b..4a24cfc8 100644 --- a/_includes/code/go-v6/multi_target_test.go +++ b/_includes/code/go-v6/multi_target_test.go @@ -4,13 +4,69 @@ import ( "context" "testing" + weaviate "github.com/weaviate/weaviate-go-client/v6" + "github.com/weaviate/weaviate-go-client/v6/collections" + "github.com/weaviate/weaviate-go-client/v6/data" + "github.com/weaviate/weaviate-go-client/v6/modules/selfprovided" "github.com/weaviate/weaviate-go-client/v6/query" "github.com/weaviate/weaviate-go-client/v6/types" ) -// The multi-target vector snippets below require a collection ("JeopardyTiny") -// with multiple named vectors and a configured vectorizer. They are kept out of -// the CI run set (compile-only) and skip when executed directly. +// The multi-target vector snippets query a "JeopardyTiny" collection with two +// named vectors. The bring-your-own-vector (NearVector) variants run live via +// setupJeopardyTiny; the near-text variants stay skipped until a vectorizer is +// available in this CI lane. + +// setupJeopardyTiny (re)creates JeopardyTiny with two bring-your-own named +// vectors and seeds objects that carry both. The vector-config keys match the +// target names used by the multi-target near-vector snippets. +func setupJeopardyTiny(t *testing.T, client *weaviate.Client) { + t.Helper() + ctx := context.Background() + _ = client.Collections.Delete(ctx, "JeopardyTiny") + if _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "JeopardyTiny", + Properties: []collections.Property{ + {Name: "question", DataType: collections.DataTypeText}, + {Name: "answer", DataType: collections.DataTypeText}, + }, + Vectors: map[string]collections.VectorConfig{ + "jeopardy_questions_vector": {Vectorizer: selfprovided.Vectorizer}, + "jeopardy_answers_vector": {Vectorizer: selfprovided.Vectorizer}, + }, + }); err != nil { + t.Fatalf("create JeopardyTiny collection: %v", err) + } + + jeopardy := client.Collections.Use("JeopardyTiny") + if _, err := jeopardy.Data.Insert(ctx, + &data.Object{ + Properties: map[string]any{"question": "This organ removes excess glucose from the blood", "answer": "Liver"}, + Vectors: []types.Vector{ + {Name: "jeopardy_questions_vector", Single: []float32{0.10, 0.21, 0.32}}, + {Name: "jeopardy_answers_vector", Single: []float32{0.20, 0.11, 0.30}}, + }, + }, + &data.Object{ + Properties: map[string]any{"question": "The only living mammal in the order Proboscidea", "answer": "Elephant"}, + Vectors: []types.Vector{ + {Name: "jeopardy_questions_vector", Single: []float32{0.11, 0.20, 0.34}}, + {Name: "jeopardy_answers_vector", Single: []float32{0.14, 0.19, 0.30}}, + }, + }, + &data.Object{ + Properties: map[string]any{"question": "This tall animal has a long neck and roams the savanna", "answer": "Giraffe"}, + Vectors: []types.Vector{ + {Name: "jeopardy_questions_vector", Single: []float32{0.14, 0.19, 0.30}}, + {Name: "jeopardy_answers_vector", Single: []float32{0.11, 0.20, 0.34}}, + }, + }, + ); err != nil { + t.Fatalf("seed JeopardyTiny: %v", err) + } + + waitForCount(t, jeopardy, 3) +} // TestMultiBasic searches several target vectors by name. With no join strategy // specified, Weaviate combines the results using the default (minimum) strategy. @@ -44,11 +100,13 @@ func TestMultiBasic(t *testing.T) { // TestMultiTargetNearVector supplies a separate query vector for each target // vector. func TestMultiTargetNearVector(t *testing.T) { - t.Skip("requires a collection with multiple named vectors and a configured vectorizer") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyTiny(t, client) + defer client.Collections.Delete(ctx, "JeopardyTiny") + v1 := []float32{0.12, 0.20, 0.33} v2 := []float32{0.14, 0.19, 0.30} @@ -75,11 +133,13 @@ func TestMultiTargetNearVector(t *testing.T) { // TestMultiTargetMultipleNearVectorsV1 targets the same vector more than once by // listing it twice with different query vectors. func TestMultiTargetMultipleNearVectorsV1(t *testing.T) { - t.Skip("requires a collection with multiple named vectors and a configured vectorizer") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyTiny(t, client) + defer client.Collections.Delete(ctx, "JeopardyTiny") + v1 := []float32{0.12, 0.20, 0.33} v2 := []float32{0.14, 0.19, 0.30} v3 := []float32{0.11, 0.20, 0.34} @@ -107,11 +167,13 @@ func TestMultiTargetMultipleNearVectorsV1(t *testing.T) { // TestMultiTargetMultipleNearVectorsV2 assigns a weight to each query vector. func TestMultiTargetMultipleNearVectorsV2(t *testing.T) { - t.Skip("requires a collection with multiple named vectors and a configured vectorizer") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyTiny(t, client) + defer client.Collections.Delete(ctx, "JeopardyTiny") + v1 := []float32{0.12, 0.20, 0.33} v2 := []float32{0.14, 0.19, 0.30} v3 := []float32{0.11, 0.20, 0.34} diff --git a/_includes/code/go-v6/search_basic_test.go b/_includes/code/go-v6/search_basic_test.go index 8df85e19..b1636220 100644 --- a/_includes/code/go-v6/search_basic_test.go +++ b/_includes/code/go-v6/search_basic_test.go @@ -180,11 +180,13 @@ func TestGetObjectId(t *testing.T) { // TestGetWithCrossRefs returns properties from cross-referenced objects. func TestGetWithCrossRefs(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyDemo(t, client) + defer cleanupJeopardyDemo(ctx, client) + // START GetWithCrossRefs jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.OverAll(ctx, query.OverAll{ @@ -236,11 +238,13 @@ func TestGetWithMetadata(t *testing.T) { // TestMultiTenancy queries a specific tenant of a multi-tenant collection. func TestMultiTenancy(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupMultiTenancyJeopardy(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + // START MultiTenancy // Bind the tenant once when you take the collection handle. jeopardy := client.Collections.Use("JeopardyQuestion", diff --git a/_includes/code/go-v6/tenants_test.go b/_includes/code/go-v6/tenants_test.go index 12a2d83c..d669c091 100644 --- a/_includes/code/go-v6/tenants_test.go +++ b/_includes/code/go-v6/tenants_test.go @@ -4,7 +4,6 @@ import ( "context" "testing" - "github.com/google/uuid" "github.com/weaviate/weaviate-go-client/v6/collections" "github.com/weaviate/weaviate-go-client/v6/data" "github.com/weaviate/weaviate-go-client/v6/query" @@ -73,11 +72,14 @@ func TestUpdateAutoMT(t *testing.T) { // TestAddTenantsToClass adds tenants to a multi-tenancy collection. func TestAddTenantsToClass(t *testing.T) { - t.Skip("requires a running Weaviate instance") ctx := context.Background() client := connectLocal(t) defer client.Close() + // A fresh collection with no tenants; the snippet creates them. + createMultiTenancyCollection(t, client) + defer cleanupMultiTenancy(ctx, client) + // START AddTenantsToClass collection := client.Collections.Use("MultiTenancyCollection") err := collection.Tenants.Create(ctx, @@ -92,11 +94,13 @@ func TestAddTenantsToClass(t *testing.T) { // TestListTenants lists every tenant in a multi-tenancy collection. func TestListTenants(t *testing.T) { - t.Skip("requires a running Weaviate instance") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupMultiTenancy(t, client) + defer cleanupMultiTenancy(ctx, client) + // START ListTenants collection := client.Collections.Use("MultiTenancyCollection") // Passing no tenant names returns every tenant in the collection. @@ -112,11 +116,13 @@ func TestListTenants(t *testing.T) { // TestRemoveTenants deletes tenants (and their data) from a collection. func TestRemoveTenants(t *testing.T) { - t.Skip("requires a running Weaviate instance") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupMultiTenancy(t, client) + defer cleanupMultiTenancy(ctx, client) + // START RemoveTenants collection := client.Collections.Use("MultiTenancyCollection") // Unknown tenant names are ignored. @@ -131,11 +137,13 @@ func TestRemoveTenants(t *testing.T) { // bound once on the collection handle and applies to every operation made with // it. func TestCreateMtObject(t *testing.T) { - t.Skip("requires a running Weaviate instance") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupMultiTenancy(t, client) + defer cleanupMultiTenancy(ctx, client) + // START CreateMtObject // Bind the tenant to the collection handle. collection := client.Collections.Use("MultiTenancyCollection", @@ -154,11 +162,13 @@ func TestCreateMtObject(t *testing.T) { // TestMtSearch runs a query scoped to a single tenant. func TestMtSearch(t *testing.T) { - t.Skip("requires a running Weaviate instance") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupMultiTenancy(t, client) + defer cleanupMultiTenancy(ctx, client) + // START Search collection := client.Collections.Use("MultiTenancyCollection", collections.WithTenant("tenantA"), @@ -178,13 +188,17 @@ func TestMtSearch(t *testing.T) { // TestMtAddCrossRef adds a cross-reference from an object that belongs to a // tenant. The tenant is bound on the handle used to make the request. func TestMtAddCrossRef(t *testing.T) { - t.Skip("requires a running Weaviate instance") ctx := context.Background() client := connectLocal(t) defer client.Close() - sourceID := uuid.New() - targetID := uuid.New() + setupMultiTenancy(t, client) + defer cleanupMultiTenancy(ctx, client) + + // Reference a seeded MultiTenancyCollection question (in tenantA) as the + // source and a seeded JeopardyCategory row as the target. + sourceID := mtSourceID + targetID := mtCategoryID // START AddCrossRef collection := client.Collections.Use("MultiTenancyCollection", From 68657ee7dcffd5d27aa3f510bd2682b1f71d6a0c Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Thu, 30 Jul 2026 15:21:32 +0200 Subject: [PATCH 18/22] fix(go-v6): T2 seed UUID (leading-zero id_as_bytes) + skip two client-bug ops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tier-2 CI reported 18 Go failures. Root causes and dispositions: - TestListTenants PANICKED (Tenants.Get -> *api.GetTenantsRequest is not wired into the gRPC transport MessageMarshaler switch; hits dev.Assert(false)). A panicking test aborts the whole `go test` binary, so every test scheduled after it produced no result โ€” the 7 vector_config tests (all t.Skip) and 4 MT tests. That is why vector_config "regressed": the binary never reached them, not cross-test contamination. Skipped TestListTenants (client bug, escalate); this un-blocks all 11 "did not run" tests. - "invalid UUID (got 15 bytes)" on filters/search (5 tests): Weaviate's gRPC search reply encodes the object id in `id_as_bytes`, which drops leading zero bytes. filterByIdSeedUUID began 0x00 ("00037775-โ€ฆ") -> 15 bytes -> client uuid.FromBytes rejects the whole reply, failing every query over the collection (empirically confirmed live: 0x00 -> 15 bytes, 0x0000 -> 14). Gave filterByIdSeedUUID (and the matching FilterById snippet) and every setupJeopardyDemo object a fixed, non-leading-zero id โ€” deterministic and leading-zero-safe. - TestMultiTargetMultipleNearVectorsV2: the Go client de-duplicates TargetVectors but sends one weight per query vector, so a repeated weighted target yields len(weights)=3 vs len(targets)=2; Weaviate 1.38 rejects it. The Python/Java clients duplicate the target name to match the weights; the snippet is idiomatic. Skipped (client bug, escalate). All seed helpers already delete-before-create, so the sequential run is order-independent. Validated: go vet ./... and go build ./... clean against a reconstructed origin/v6 client; multi-target V1 passes, the 4 MT ops (insert, tenant query, tenant AddReferences, delete-unknown-tenant) pass live, and the two skips register. Reproduced against local Weaviate 1.38. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01WmugebxuspdSQrFKE883W5 --- _includes/code/go-v6/filters_test.go | 2 +- _includes/code/go-v6/main_test.go | 23 +++++++++++++++++++---- _includes/code/go-v6/multi_target_test.go | 2 ++ _includes/code/go-v6/tenants_test.go | 2 ++ 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/_includes/code/go-v6/filters_test.go b/_includes/code/go-v6/filters_test.go index 506d0a50..dc02ed96 100644 --- a/_includes/code/go-v6/filters_test.go +++ b/_includes/code/go-v6/filters_test.go @@ -303,7 +303,7 @@ func TestFilterById(t *testing.T) { // filter.UUID targets the object's own id. Target: filter.UUID, Operator: filter.Equal, - Value: "00037775-1432-35e5-bc59-443baaef7d80", + Value: "a1b2c3d4-e5f6-4a5b-8c9d-1a2b3c4d5e6f", }, }) if err != nil { diff --git a/_includes/code/go-v6/main_test.go b/_includes/code/go-v6/main_test.go index 9ddc8964..b2a8422c 100644 --- a/_includes/code/go-v6/main_test.go +++ b/_includes/code/go-v6/main_test.go @@ -71,7 +71,14 @@ func waitForCount(t *testing.T, handle *collections.Handle, n int) { // filterByIdSeedUUID is the id filtered on by TestFilterById; setupJeopardyDemo // seeds one question with this id so that snippet returns a deterministic match. -const filterByIdSeedUUID = "00037775-1432-35e5-bc59-443baaef7d80" +// +// The first byte must be non-zero. Weaviate's gRPC search reply carries the +// object id in the `id_as_bytes` field, which drops leading zero bytes: an id +// beginning 0x00 comes back as 15 bytes and the client's uuid.FromBytes rejects +// the whole reply with "invalid UUID (got 15 bytes)", failing every query over +// the collection. The previous value ("00037775-โ€ฆ") began 0x00 and did exactly +// that. Keep every seeded id's first byte non-zero. +const filterByIdSeedUUID = "a1b2c3d4-e5f6-4a5b-8c9d-1a2b3c4d5e6f" // Fixed ids for the demo JeopardyCategory rows so questions can be linked to // them with AddReferences during seeding. @@ -136,8 +143,16 @@ func setupJeopardyDemo(t *testing.T, client *weaviate.Client) { t.Fatalf("seed JeopardyCategory: %v", err) } + // Every question gets a fixed, non-leading-zero id so the run is deterministic + // and no seeded object trips the id_as_bytes leading-zero bug (see + // filterByIdSeedUUID). Random or server-assigned ids carry a ~1/256 chance of a + // leading 0x00 byte, which would flake the whole collection's queries. q1 := uuid.MustParse(filterByIdSeedUUID) - q2, q3, q5 := uuid.New(), uuid.New(), uuid.New() + q2 := uuid.MustParse("b2c3d4e5-f6a7-4b5c-8d9e-2a3b4c5d6e7f") + q3 := uuid.MustParse("c3d4e5f6-a7b8-4c5d-8e9f-3a4b5c6d7e80") + q4 := uuid.MustParse("d4e5f6a7-b8c9-4d5e-8f90-4b5c6d7e8f90") + q5 := uuid.MustParse("e5f6a7b8-c9d0-4e5f-8a01-5c6d7e8f9012") + q6 := uuid.MustParse("f6a7b8c9-d0e1-4f5a-8b02-6d7e8f901234") jeopardy := client.Collections.Use("JeopardyQuestion") if _, err := jeopardy.Data.Insert(ctx, &data.Object{UUID: &q1, Properties: map[string]any{ @@ -155,7 +170,7 @@ func setupJeopardyDemo(t *testing.T, client *weaviate.Client) { "answer": "Giraffe", "category": "ANIMALS", "round": "Double Jeopardy!", "points": 500, "dateRecorded": "2023-03-03T00:00:00Z", }}, - &data.Object{Properties: map[string]any{ + &data.Object{UUID: &q4, Properties: map[string]any{ "question": "Bees build these six-sided structures to store honey in a hive", "answer": "A honeycomb nest built by bees", "category": "NATURE", "round": "Jeopardy!", "points": 200, "dateRecorded": "2020-06-06T00:00:00Z", @@ -166,7 +181,7 @@ func setupJeopardyDemo(t *testing.T, client *weaviate.Client) { "dateRecorded": "2021-09-09T00:00:00Z", }}, // A question with no answer so FilterByPropertyNullState has a match. - &data.Object{Properties: map[string]any{ + &data.Object{UUID: &q6, Properties: map[string]any{ "question": "This open-source vector database is written in Go", "category": "TECH", "round": "Double Jeopardy!", "points": 400, "dateRecorded": "2024-04-04T00:00:00Z", diff --git a/_includes/code/go-v6/multi_target_test.go b/_includes/code/go-v6/multi_target_test.go index 4a24cfc8..475bad69 100644 --- a/_includes/code/go-v6/multi_target_test.go +++ b/_includes/code/go-v6/multi_target_test.go @@ -167,6 +167,8 @@ func TestMultiTargetMultipleNearVectorsV1(t *testing.T) { // TestMultiTargetMultipleNearVectorsV2 assigns a weight to each query vector. func TestMultiTargetMultipleNearVectorsV2(t *testing.T) { + t.Skip("go-client v6 mis-marshals a repeated target with per-vector weights: marshalNearVector (internal/api/search.go) de-duplicates TargetVectors but appends one WeightsForTarget per vector, so a target named twice yields len(weights)=3 vs len(targets)=2. Weaviate 1.38 rejects it (parse_search_request.go extractWeights requires equal, positional counts): \"number of weights (3) does not match number of targets (2)\". The Python/Java clients duplicate the target name to match the weights; the Go client should too. Snippet is idiomatic (matches the other clients); deferred pending a client fix") + ctx := context.Background() client := connectLocal(t) defer client.Close() diff --git a/_includes/code/go-v6/tenants_test.go b/_includes/code/go-v6/tenants_test.go index d669c091..17f45f87 100644 --- a/_includes/code/go-v6/tenants_test.go +++ b/_includes/code/go-v6/tenants_test.go @@ -94,6 +94,8 @@ func TestAddTenantsToClass(t *testing.T) { // TestListTenants lists every tenant in a multi-tenancy collection. func TestListTenants(t *testing.T) { + t.Skip("go-client v6 Tenants.Get panics โ€” *api.GetTenantsRequest is not wired into the gRPC transport MessageMarshaler switch (internal/api/transport/transport.go), so Tenants.Get(ctx) hits the default dev.Assert(false) and panics, aborting the whole test binary; deferred pending a client fix") + ctx := context.Background() client := connectLocal(t) defer client.Close() From f6ccd6ed9b7961bc501a0a297ab8aed742a329b8 Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Thu, 30 Jul 2026 15:58:31 +0200 Subject: [PATCH 19/22] test(go-v6): tier 3 - enable near-text/hybrid/named-vector live (contextionary seed) Enable the vectorized search tests against the anon :8080 instance's text2vec-contextionary module (no new Docker image, no API key). A test-only contextionaryVectorizer{} that implements modules.Module (Name() only) is passed as the seed VectorConfig.Vectorizer; the client reflect-encodes it to {"text2vec-contextionary": {}}, so the collections get server-side vectors and near-text/hybrid can vectorize their query text. No client registration needed (that is only for the decode/read path). Seed helpers (outside START/END markers, delete-before-create + waitForCount): - setupJeopardyVectorized JeopardyQuestion, contextionary "default" vector - setupWineReviewNV WineReviewNV, named "title_country" vector - setupJeopardyTinyVectorized JeopardyTiny, two contextionary named vectors Un-skipped: TestGetNearText, TestNamedVectorNearText, TestNearTextWithFilter, all hybrid tests, and the multi-target near-text joins (MultiBasic, MultiTargetWithSimpleJoin, MultiTargetManualWeights, MultiTargetRelativeScore). TestHybridWithVector's NearVector target now names the "default" vector. Confirmed client/server bugs, OIDC, RBAC/T4, model2vec and placeholders stay skipped. --- _includes/code/go-v6/filters_test.go | 4 +- _includes/code/go-v6/hybrid_test.go | 42 ++++-- _includes/code/go-v6/main_test.go | 163 ++++++++++++++++++++++ _includes/code/go-v6/multi_target_test.go | 16 ++- _includes/code/go-v6/search_test.go | 8 +- 5 files changed, 215 insertions(+), 18 deletions(-) diff --git a/_includes/code/go-v6/filters_test.go b/_includes/code/go-v6/filters_test.go index dc02ed96..27e3bf6a 100644 --- a/_includes/code/go-v6/filters_test.go +++ b/_includes/code/go-v6/filters_test.go @@ -401,11 +401,13 @@ func TestFilterByPropertyNullState(t *testing.T) { // TestNearTextWithFilter combines a semantic search with a filter. It queries a // collection whose vectorizer turns the query text into a vector server-side. func TestNearTextWithFilter(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyVectorized(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + // START NearTextWithFilter jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.NearText(ctx, query.NearText{ diff --git a/_includes/code/go-v6/hybrid_test.go b/_includes/code/go-v6/hybrid_test.go index bb52e4f3..4a9b163c 100644 --- a/_includes/code/go-v6/hybrid_test.go +++ b/_includes/code/go-v6/hybrid_test.go @@ -15,11 +15,13 @@ import ( // collection. func TestHybridBasic(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyVectorized(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + // START HybridBasic jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.Hybrid(ctx, query.Hybrid{ @@ -36,11 +38,13 @@ func TestHybridBasic(t *testing.T) { } func TestHybridWithScore(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyVectorized(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + // START HybridWithScore jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.Hybrid(ctx, query.Hybrid{ @@ -66,11 +70,13 @@ func TestHybridWithScore(t *testing.T) { } func TestHybridWithAlpha(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyVectorized(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + // START HybridWithAlpha jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.Hybrid(ctx, query.Hybrid{ @@ -89,11 +95,13 @@ func TestHybridWithAlpha(t *testing.T) { } func TestHybridWithFusionType(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyVectorized(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + // START HybridWithFusionType jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.Hybrid(ctx, query.Hybrid{ @@ -111,11 +119,13 @@ func TestHybridWithFusionType(t *testing.T) { } func TestHybridWithProperties(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyVectorized(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + // START HybridWithProperties jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.Hybrid(ctx, query.Hybrid{ @@ -134,11 +144,13 @@ func TestHybridWithProperties(t *testing.T) { } func TestHybridWithPropertyWeighting(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyVectorized(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + // START HybridWithPropertyWeighting jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.Hybrid(ctx, query.Hybrid{ @@ -157,11 +169,13 @@ func TestHybridWithPropertyWeighting(t *testing.T) { } func TestHybridWithVector(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyVectorized(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + // START HybridWithVector // A query vector, for example an embedding produced by your model. vector := []float32{0.12, 0.20, 0.33} @@ -171,7 +185,7 @@ func TestHybridWithVector(t *testing.T) { Query: "food", // Supply the vector for the vector-search half of the query. NearVector: &query.NearVector{ - Target: &types.Vector{Single: vector}, + Target: &types.Vector{Name: "default", Single: vector}, }, Limit: 3, }) @@ -185,11 +199,13 @@ func TestHybridWithVector(t *testing.T) { } func TestHybridLimit(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyVectorized(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + // START HybridLimit jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.Hybrid(ctx, query.Hybrid{ @@ -206,11 +222,13 @@ func TestHybridLimit(t *testing.T) { } func TestHybridAutocut(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyVectorized(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + // START HybridAutocut jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.Hybrid(ctx, query.Hybrid{ @@ -227,11 +245,13 @@ func TestHybridAutocut(t *testing.T) { } func TestHybridWithFilter(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyVectorized(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + // START HybridWithFilter jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.Hybrid(ctx, query.Hybrid{ diff --git a/_includes/code/go-v6/main_test.go b/_includes/code/go-v6/main_test.go index b2a8422c..6f8b1e04 100644 --- a/_includes/code/go-v6/main_test.go +++ b/_includes/code/go-v6/main_test.go @@ -329,3 +329,166 @@ func setupMultiTenancyJeopardy(t *testing.T, client *weaviate.Client) { waitForCount(t, tenantA, 2) } + +// contextionaryVectorizer is a test-only Vectorizer that selects the +// text2vec-contextionary module, which is enabled on the docs test instance +// (deterministic, no model download, no API key). The v6 client encodes any +// modules.Module by reflection from its Name, so a struct that only reports the +// module name is enough to request server-side vectorization; the module does +// not need to be registered with the client because these seeds only create +// collections (the write path) and never decode the module config back. +// +// It carries no configuration, so it serializes to +// {"text2vec-contextionary": {}} and the module vectorizes every text property +// of the collection by default. The near-text and hybrid snippets rely on this +// to turn their query text into a vector server-side. +type contextionaryVectorizer struct{} + +func (contextionaryVectorizer) Name() string { return "text2vec-contextionary" } + +// setupJeopardyVectorized (re)creates a JeopardyQuestion collection whose +// "default" vector is produced server-side by text2vec-contextionary, then +// seeds it. Because the server vectorizes the objects on import (and the query +// text at search time), this unblocks the near-text and hybrid snippets, which +// do not supply their own vectors. Every object gets a fixed, non-leading-zero +// id so the run stays deterministic and no seeded id trips the id_as_bytes +// leading-zero bug (see filterByIdSeedUUID). +func setupJeopardyVectorized(t *testing.T, client *weaviate.Client) { + t.Helper() + ctx := context.Background() + _ = client.Collections.Delete(ctx, "JeopardyQuestion") + if _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "JeopardyQuestion", + Properties: []collections.Property{ + {Name: "question", DataType: collections.DataTypeText}, + {Name: "answer", DataType: collections.DataTypeText}, + {Name: "category", DataType: collections.DataTypeText}, + {Name: "round", DataType: collections.DataTypeText}, + {Name: "points", DataType: collections.DataTypeInt}, + }, + // The "default" vector is filled in by the server via + // text2vec-contextionary, so objects are inserted without vectors and + // near-text/hybrid queries can vectorize their query text server-side. + Vectors: map[string]collections.VectorConfig{ + "default": {Vectorizer: contextionaryVectorizer{}}, + }, + }); err != nil { + t.Fatalf("create vectorized JeopardyQuestion collection: %v", err) + } + + q1 := uuid.MustParse("a1b2c3d4-e5f6-4a5b-8c9d-1a2b3c4d5e6f") + q2 := uuid.MustParse("b2c3d4e5-f6a7-4b5c-8d9e-2a3b4c5d6e7f") + q3 := uuid.MustParse("c3d4e5f6-a7b8-4c5d-8e9f-3a4b5c6d7e80") + q4 := uuid.MustParse("d4e5f6a7-b8c9-4d5e-8f90-4b5c6d7e8f90") + q5 := uuid.MustParse("e5f6a7b8-c9d0-4e5f-8a01-5c6d7e8f9012") + q6 := uuid.MustParse("f6a7b8c9-d0e1-4f5a-8b02-6d7e8f901234") + jeopardy := client.Collections.Use("JeopardyQuestion") + if _, err := jeopardy.Data.Insert(ctx, + &data.Object{UUID: &q1, Properties: map[string]any{ + "question": "This organ removes excess glucose from the blood and stores it as glycogen", + "answer": "Liver", "category": "SCIENCE", "round": "Jeopardy!", "points": 100, + }}, + &data.Object{UUID: &q2, Properties: map[string]any{ + "question": "This large animal is the only living mammal in the order Proboscidea", + "answer": "Elephant", "category": "ANIMALS", "round": "Double Jeopardy!", "points": 200, + }}, + &data.Object{UUID: &q3, Properties: map[string]any{ + "question": "This tall animal has a long neck and roams the African savanna", + "answer": "Giraffe", "category": "ANIMALS", "round": "Double Jeopardy!", "points": 500, + }}, + &data.Object{UUID: &q4, Properties: map[string]any{ + "question": "Bees make this sweet food and store it in a hive", + "answer": "Honey", "category": "NATURE", "round": "Jeopardy!", "points": 200, + }}, + &data.Object{UUID: &q5, Properties: map[string]any{ + "question": "This racket sport holds its most famous tournament at Wimbledon", + "answer": "Tennis", "category": "SPORTS", "round": "Final Jeopardy!", "points": 800, + }}, + &data.Object{UUID: &q6, Properties: map[string]any{ + "question": "This yellow fruit is a favorite food of monkeys", + "answer": "Banana", "category": "FOOD", "round": "Double Jeopardy!", "points": 300, + }}, + ); err != nil { + t.Fatalf("seed vectorized JeopardyQuestion: %v", err) + } + + waitForCount(t, jeopardy, 6) +} + +// setupWineReviewNV (re)creates a WineReviewNV collection with a single named +// vector, "title_country", produced server-side by text2vec-contextionary from +// the collection's text properties (title and country). It seeds a few reviews +// so the named-vector near-text snippet has a target vector to search. +func setupWineReviewNV(t *testing.T, client *weaviate.Client) { + t.Helper() + ctx := context.Background() + _ = client.Collections.Delete(ctx, "WineReviewNV") + if _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "WineReviewNV", + Properties: []collections.Property{ + {Name: "title", DataType: collections.DataTypeText}, + {Name: "country", DataType: collections.DataTypeText}, + }, + Vectors: map[string]collections.VectorConfig{ + "title_country": {Vectorizer: contextionaryVectorizer{}}, + }, + }); err != nil { + t.Fatalf("create WineReviewNV collection: %v", err) + } + + w1 := uuid.MustParse("1a2b3c4d-5e6f-4a5b-8c9d-1a2b3c4d5e6f") + w2 := uuid.MustParse("2b3c4d5e-6f7a-4b5c-8d9e-2b3c4d5e6f7a") + w3 := uuid.MustParse("3c4d5e6f-7a8b-4c5d-8e9f-3c4d5e6f7a8b") + w4 := uuid.MustParse("4d5e6f7a-8b9c-4d5e-8f90-4d5e6f7a8b9c") + reviews := client.Collections.Use("WineReviewNV") + if _, err := reviews.Data.Insert(ctx, + &data.Object{UUID: &w1, Properties: map[string]any{"title": "A sweet white Riesling wine", "country": "Germany"}}, + &data.Object{UUID: &w2, Properties: map[string]any{"title": "A dry white Chardonnay wine", "country": "France"}}, + &data.Object{UUID: &w3, Properties: map[string]any{"title": "A bold red Cabernet Sauvignon wine", "country": "United States"}}, + &data.Object{UUID: &w4, Properties: map[string]any{"title": "A crisp white Sauvignon Blanc wine", "country": "New Zealand"}}, + ); err != nil { + t.Fatalf("seed WineReviewNV: %v", err) + } + + waitForCount(t, reviews, 4) +} + +// setupJeopardyTinyVectorized (re)creates JeopardyTiny with two named vectors, +// "jeopardy_questions_vector" and "jeopardy_answers_vector", both produced +// server-side by text2vec-contextionary. It is the vectorized companion to +// setupJeopardyTiny (which brings its own vectors): the multi-target near-text +// snippets need server-side vectors so the query text can be vectorized against +// each named target, whereas the multi-target near-vector snippets supply their +// own vectors and use setupJeopardyTiny. +func setupJeopardyTinyVectorized(t *testing.T, client *weaviate.Client) { + t.Helper() + ctx := context.Background() + _ = client.Collections.Delete(ctx, "JeopardyTiny") + if _, err := client.Collections.Create(ctx, collections.Collection{ + Name: "JeopardyTiny", + Properties: []collections.Property{ + {Name: "question", DataType: collections.DataTypeText}, + {Name: "answer", DataType: collections.DataTypeText}, + }, + Vectors: map[string]collections.VectorConfig{ + "jeopardy_questions_vector": {Vectorizer: contextionaryVectorizer{}}, + "jeopardy_answers_vector": {Vectorizer: contextionaryVectorizer{}}, + }, + }); err != nil { + t.Fatalf("create vectorized JeopardyTiny collection: %v", err) + } + + t1 := uuid.MustParse("5e6f7a8b-9c0d-4e5f-8a01-5e6f7a8b9c0d") + t2 := uuid.MustParse("6f7a8b9c-0d1e-4f5a-8b02-6f7a8b9c0d1e") + t3 := uuid.MustParse("7a8b9c0d-1e2f-4a5b-8c03-7a8b9c0d1e2f") + jeopardy := client.Collections.Use("JeopardyTiny") + if _, err := jeopardy.Data.Insert(ctx, + &data.Object{UUID: &t1, Properties: map[string]any{"question": "This organ removes excess glucose from the blood", "answer": "Liver"}}, + &data.Object{UUID: &t2, Properties: map[string]any{"question": "This large animal is the only living mammal in the order Proboscidea", "answer": "Elephant"}}, + &data.Object{UUID: &t3, Properties: map[string]any{"question": "This tall animal has a long neck and roams the savanna", "answer": "Giraffe"}}, + ); err != nil { + t.Fatalf("seed vectorized JeopardyTiny: %v", err) + } + + waitForCount(t, jeopardy, 3) +} diff --git a/_includes/code/go-v6/multi_target_test.go b/_includes/code/go-v6/multi_target_test.go index 475bad69..ff27fd18 100644 --- a/_includes/code/go-v6/multi_target_test.go +++ b/_includes/code/go-v6/multi_target_test.go @@ -71,11 +71,13 @@ func setupJeopardyTiny(t *testing.T, client *weaviate.Client) { // TestMultiBasic searches several target vectors by name. With no join strategy // specified, Weaviate combines the results using the default (minimum) strategy. func TestMultiBasic(t *testing.T) { - t.Skip("requires a collection with multiple named vectors and a configured vectorizer") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyTinyVectorized(t, client) + defer client.Collections.Delete(ctx, "JeopardyTiny") + // START MultiBasic jeopardy := client.Collections.Use("JeopardyTiny") response, err := jeopardy.Query.NearText(ctx, query.NearText{ @@ -204,11 +206,13 @@ func TestMultiTargetMultipleNearVectorsV2(t *testing.T) { // TestMultiTargetWithSimpleJoin selects a named join strategy for the target // vectors. func TestMultiTargetWithSimpleJoin(t *testing.T) { - t.Skip("requires a collection with multiple named vectors and a configured vectorizer") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyTinyVectorized(t, client) + defer client.Collections.Delete(ctx, "JeopardyTiny") + // START MultiTargetWithSimpleJoin jeopardy := client.Collections.Use("JeopardyTiny") response, err := jeopardy.Query.NearText(ctx, query.NearText{ @@ -233,11 +237,13 @@ func TestMultiTargetWithSimpleJoin(t *testing.T) { // TestMultiTargetManualWeights weights the raw distance to each target vector. func TestMultiTargetManualWeights(t *testing.T) { - t.Skip("requires a collection with multiple named vectors and a configured vectorizer") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyTinyVectorized(t, client) + defer client.Collections.Delete(ctx, "JeopardyTiny") + // START MultiTargetManualWeights jeopardy := client.Collections.Use("JeopardyTiny") response, err := jeopardy.Query.NearText(ctx, query.NearText{ @@ -261,11 +267,13 @@ func TestMultiTargetManualWeights(t *testing.T) { // TestMultiTargetRelativeScore weights the normalized distance to each target // vector. func TestMultiTargetRelativeScore(t *testing.T) { - t.Skip("requires a collection with multiple named vectors and a configured vectorizer") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyTinyVectorized(t, client) + defer client.Collections.Delete(ctx, "JeopardyTiny") + // START MultiTargetRelativeScore jeopardy := client.Collections.Use("JeopardyTiny") response, err := jeopardy.Query.NearText(ctx, query.NearText{ diff --git a/_includes/code/go-v6/search_test.go b/_includes/code/go-v6/search_test.go index 6442350f..01e68a08 100644 --- a/_includes/code/go-v6/search_test.go +++ b/_includes/code/go-v6/search_test.go @@ -65,11 +65,13 @@ func setupJeopardySearch(t *testing.T, client *weaviate.Client) { // TestGetNearText runs a semantic search over a collection whose vectorizer // turns the query text into a vector server-side. func TestGetNearText(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupJeopardyVectorized(t, client) + defer client.Collections.Delete(ctx, "JeopardyQuestion") + // START GetNearText jeopardy := client.Collections.Use("JeopardyQuestion") response, err := jeopardy.Query.NearText(ctx, query.NearText{ @@ -127,11 +129,13 @@ func TestGetNearVector(t *testing.T) { // TestNamedVectorNearText searches a named vector by passing the vector name as // the search target. func TestNamedVectorNearText(t *testing.T) { - t.Skip("enabled in a later CI tier") ctx := context.Background() client := connectLocal(t) defer client.Close() + setupWineReviewNV(t, client) + defer client.Collections.Delete(ctx, "WineReviewNV") + // START NamedVectorNearText reviews := client.Collections.Use("WineReviewNV") response, err := reviews.Query.NearText(ctx, query.NearText{ From 91373702354a68c37a0fa97d4d1e4bd71aab311e Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Thu, 30 Jul 2026 16:30:09 +0200 Subject: [PATCH 20/22] fix(go-v6): T3 - skip TestHybridWithVector (hybrid+NearVector server panic, not a dim mismatch) The T3 run failed only on TestHybridWithVector. The coordinator hypothesized a 3-dim-vs-300-dim mismatch against the contextionary "default" vector. Verified live against a MATCHING-dimension BYO-vector collection (Weaviate 1.38.0, localhost gRPC): the same rpc code=Unknown "panic occurred: nil pointer dereference" still happens. Controls on the same collection pass (plain NearVector returns objects, plain Hybrid returns objects), so the defect is specific to Hybrid with a provided NearVector, independent of dimensions. The v6 client marshals proto.Hybrid.NearVector via marshalNearVector (internal/api/search.go:675) into VectorForTargets/Targets, a shape the 1.38 hybrid path mishandles into a nil-pointer panic. This is a real client/server bug, not a docs/snippet bug, so the test is t.Skip'd with the reason (not hacked). The displayed snippet keeps the idiomatic Name:"default" target. Escalation candidates: (1) hybrid + provided NearVector to a server nil-pointer panic; (2) a dimension mismatch should be a clean 4xx, not a server panic. --- _includes/code/go-v6/hybrid_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/_includes/code/go-v6/hybrid_test.go b/_includes/code/go-v6/hybrid_test.go index 4a9b163c..4002bb49 100644 --- a/_includes/code/go-v6/hybrid_test.go +++ b/_includes/code/go-v6/hybrid_test.go @@ -169,13 +169,11 @@ func TestHybridWithPropertyWeighting(t *testing.T) { } func TestHybridWithVector(t *testing.T) { + t.Skip("hybrid search with a provided NearVector panics the server (Weaviate 1.38.0): rpc code=Unknown \"panic occurred: nil pointer dereference\". Reproduced live against a MATCHING-dimension bring-your-own-vector collection, so it is not a dimension mismatch; plain NearVector and plain Hybrid over the same collection both succeed, so the defect is specific to Hybrid.NearVector. The v6 client marshals proto.Hybrid.NearVector via marshalNearVector (internal/api/search.go:675) into VectorForTargets/Targets, a shape the 1.38 hybrid path mishandles. Snippet is idiomatic; deferred pending a client/server fix") ctx := context.Background() client := connectLocal(t) defer client.Close() - setupJeopardyVectorized(t, client) - defer client.Collections.Delete(ctx, "JeopardyQuestion") - // START HybridWithVector // A query vector, for example an embedding produced by your model. vector := []float32{0.12, 0.20, 0.33} From 2d7a06b0cca4ced00f7840d2fe7c61624a3edd20 Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Thu, 30 Jul 2026 17:08:53 +0200 Subject: [PATCH 21/22] =?UTF-8?q?test(go-v6):=20tier=204=20=E2=80=94=20ena?= =?UTF-8?q?ble=20RBAC=20tests=20live=20(:8580=20admin=20connect=20+=20clea?= =?UTF-8?q?nup)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Broaden the Go v6 docs CI lane to execute the RBAC snippets against the RBAC-enabled instance the docs stack already runs (service weaviate_rbac, http :8580 / grpc :50551, root user root-user / key root-user-key). - main_test.go: add connectRBACAdmin(t) plus getenvOr/getenvPortOr helpers (env-driven, localhost:8580/:50551 fallbacks). It authenticates with a Bearer Authorization header rather than WithAPIKey to sidestep a v6-client bug: WithAPIKey attaches a gRPC per-RPC oauth credential whose RequireTransportSecurity() is true, so over the plaintext-http RBAC instance grpc.NewClient() refuses to build the channel and NewClient fails to connect at all (origin/v6 @8a70091, internal/transports/grpc.go). RBAC endpoints are REST, so the header authenticates every one as root-user. - rbac_test.go: un-skip the 32 role/permission/db-user/OIDC/group snippets, swap connectLocal -> connectRBACAdmin, and add out-of-marker isolation: roles and users are cluster-global, so each test delete-before-creates and defer-deletes the role/user it touches and seeds any prerequisite (seedRole/seedDBUser, plus pre-assigning a role for the revoke snippets). Snippets between // START/// END are byte-for-byte unchanged. - Keep skipped: TestRBACAdminClient (connect is inside the marker, same class as TestConnectLocalAuth) and TestRBACGetKnownOidcGroups (placeholder). Validated live against a local RBAC+OIDC+Keycloak stack: 32 pass / 2 skip / 0 fail, order-independent (passes shuffled and per-test in isolation). go vet and go build clean under the CI replace directive. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01WmugebxuspdSQrFKE883W5 --- _includes/code/go-v6/main_test.go | 64 +++++++ _includes/code/go-v6/rbac_test.go | 271 ++++++++++++++++++++++-------- 2 files changed, 267 insertions(+), 68 deletions(-) diff --git a/_includes/code/go-v6/main_test.go b/_includes/code/go-v6/main_test.go index 6f8b1e04..0e6086cc 100644 --- a/_includes/code/go-v6/main_test.go +++ b/_includes/code/go-v6/main_test.go @@ -2,6 +2,9 @@ package main import ( "context" + "net/http" + "os" + "strconv" "testing" "time" @@ -26,6 +29,67 @@ func connectLocal(t *testing.T) *weaviate.Client { return client } +// getenvOr returns the value of environment variable key, or fallback when the +// variable is unset or empty. +func getenvOr(key, fallback string) string { + if v := os.Getenv(key); v != "" { + return v + } + return fallback +} + +// getenvPortOr returns the integer value of environment variable key, or fallback +// when the variable is unset, empty, or not a valid integer. +func getenvPortOr(key string, fallback int) int { + if v := os.Getenv(key); v != "" { + if p, err := strconv.Atoi(v); err == nil { + return p + } + } + return fallback +} + +// connectRBACAdmin returns a client connected as the RBAC root user to the +// RBAC-enabled Weaviate instance the docs CI stack runs (service weaviate_rbac in +// tests/docker-compose-rbac.yml: HTTP :8580 / gRPC :50551, root user "root-user" +// with API key "root-user-key", RBAC + OIDC + DB-users enabled). Host, ports and +// key are overridable by environment variable so the same helper works on a +// laptop and in CI. The RBAC snippets dial through this instead of connectLocal +// (which reaches the anonymous default-port instance that does not enforce RBAC), +// so the role, database-user and OIDC snippets run against an instance that +// actually authorizes them. This helper sits outside every snippet marker, so it +// never appears in a rendered snippet. +// +// The root key is sent as a Bearer Authorization header rather than via +// weaviate.WithAPIKey(...). WithAPIKey attaches the key as a gRPC per-RPC +// credential (oauth.TokenSource) whose RequireTransportSecurity() reports true, so +// against this plaintext-http instance grpc.NewClient() refuses to build the +// channel ("credentials require transport level security") and NewClient fails to +// connect at all โ€” a v6-client bug (internal/transports/grpc.go attaches the +// per-RPC credential even when it selected insecure transport creds; still present +// on origin/v6 @8a70091). Every RBAC endpoint is REST, so an Authorization header +// authenticates all of them as root-user while sidestepping the broken gRPC path. +// The rendered AdminClient snippet still shows weaviate.WithAPIKey, which is the +// correct pattern for a TLS deployment where the bug does not bite. See the tier-4 +// escalation notes. +func connectRBACAdmin(t *testing.T) *weaviate.Client { + t.Helper() + ctx := context.Background() + apiKey := getenvOr("WEAVIATE_RBAC_API_KEY", "root-user-key") + client, err := weaviate.NewClient(ctx, + weaviate.WithScheme("http"), + weaviate.WithHTTPHost(getenvOr("WEAVIATE_RBAC_HTTP_HOST", "localhost")), + weaviate.WithHTTPPort(getenvPortOr("WEAVIATE_RBAC_HTTP_PORT", 8580)), + weaviate.WithGRPCHost(getenvOr("WEAVIATE_RBAC_GRPC_HOST", "localhost")), + weaviate.WithGRPCPort(getenvPortOr("WEAVIATE_RBAC_GRPC_PORT", 50551)), + weaviate.WithHeader(http.Header{"Authorization": []string{"Bearer " + apiKey}}), + ) + if err != nil { + t.Fatalf("connect to RBAC Weaviate: %v", err) + } + return client +} + // setupArticle (re)creates a minimal Article collection used by the object and // query smoke tests. It has no vectorizer, so objects are inserted with // explicit properties only and read back with a plain fetch. diff --git a/_includes/code/go-v6/rbac_test.go b/_includes/code/go-v6/rbac_test.go index 02321f7a..fbf2a51b 100644 --- a/_includes/code/go-v6/rbac_test.go +++ b/_includes/code/go-v6/rbac_test.go @@ -8,10 +8,75 @@ import ( "github.com/weaviate/weaviate-go-client/v6/rbac" ) -// The RBAC snippets below require a Weaviate instance with RBAC enabled and an -// admin API key. They are kept out of the CI run set (compile-only) and skip -// when executed directly. -const rbacSkip = "requires a Weaviate instance with RBAC enabled and an admin API key" +// The RBAC snippets run against the RBAC-enabled instance (connectRBACAdmin, +// service weaviate_rbac on :8580). TestRBACAdminClient stays skipped: its connect +// is INSIDE the snippet marker (weaviate.NewLocal + WithAPIKey), so it cannot be +// redirected to the non-default RBAC port without leaking the port into the +// rendered snippet โ€” the same class as TestConnectLocalAuth. +const rbacSkip = "connect is inside the snippet marker (NewLocal+WithAPIKey); cannot redirect to the non-default RBAC port without leaking it into the rendered snippet" + +// ----------------------------------------------------------------------------- +// Test-only isolation helpers. They live outside every snippet marker, so the +// rendered snippets are unaffected. +// +// Roles, database users and OIDC role assignments are cluster-global, not scoped +// to a collection, and these tests run sequentially against one shared RBAC +// instance. Each test therefore deletes the role/user it touches BEFORE it runs +// (clearing anything a previous failed run leaked) and AFTER (via defer, while the +// client is still open), and seeds any prerequisite a snippet assumes already +// exists. Every RBAC operation is a REST call, so none of them hit the gRPC +// transport-switch panic path (unlike Tenants.Get / batch-delete). +// ----------------------------------------------------------------------------- + +// deleteRoleIfExists best-effort removes a role, ignoring a missing role. Safe to +// call for delete-before-create isolation and as deferred cleanup. +func deleteRoleIfExists(client *weaviate.Client, roleID string) { + ctx := context.Background() + if exists, err := client.Roles.Exists(ctx, roleID); err == nil && exists { + _ = client.Roles.Delete(ctx, roleID) + } +} + +// seedRole (re)creates roleID with a known permission set so snippets that expect +// the role to already exist (add/remove permissions, inspect, assign, delete) have +// something to act on. The set is a superset of the permissions +// TestRBACRemovePermissions removes, plus a cluster-read permission it does not +// remove, so the role never ends up empty. +func seedRole(t *testing.T, client *weaviate.Client, roleID string) { + t.Helper() + ctx := context.Background() + deleteRoleIfExists(client, roleID) + if err := client.Roles.Create(ctx, rbac.Role{ + ID: roleID, + Permissions: rbac.Permissions{ + Collections: []rbac.CollectionPermission{ + {Collection: "TargetCollection*", Create: true, Read: true, Update: true, Delete: true}, + }, + Data: []rbac.DataPermission{ + {Collection: "TargetCollection*", Read: true}, + }, + Cluster: []rbac.ClusterPermission{{Read: true}}, + }, + }); err != nil { + t.Fatalf("seed role %q: %v", roleID, err) + } +} + +// deleteDBUserIfExists best-effort removes a database user, ignoring a missing +// user. Safe for delete-before-create isolation and deferred cleanup. +func deleteDBUserIfExists(client *weaviate.Client, userID string) { + _ = client.Users.DB.Delete(context.Background(), userID) +} + +// seedDBUser (re)creates a database user so snippets that expect the user to +// already exist (delete, rotate key, assign/revoke/list roles) have a target. +func seedDBUser(t *testing.T, client *weaviate.Client, userID string) { + t.Helper() + deleteDBUserIfExists(client, userID) + if _, err := client.Users.DB.Create(context.Background(), userID); err != nil { + t.Fatalf("seed database user %q: %v", userID, err) + } +} // ----------------------------------------------------------------------------- // Requirements @@ -40,10 +105,11 @@ func TestRBACAdminClient(t *testing.T) { // TestRBACAddManageRolesPermission creates a role that can manage other roles. func TestRBACAddManageRolesPermission(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + deleteRoleIfExists(client, "testRole") + defer deleteRoleIfExists(client, "testRole") // START AddManageRolesPermission err := client.Roles.Create(ctx, rbac.Role{ @@ -71,10 +137,11 @@ func TestRBACAddManageRolesPermission(t *testing.T) { // TestRBACAddManageUsersPermission creates a role that can manage users. func TestRBACAddManageUsersPermission(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + deleteRoleIfExists(client, "testRole") + defer deleteRoleIfExists(client, "testRole") // START AddManageUsersPermission err := client.Roles.Create(ctx, rbac.Role{ @@ -100,10 +167,11 @@ func TestRBACAddManageUsersPermission(t *testing.T) { // TestRBACAddCollectionsPermission creates a role with collection permissions. func TestRBACAddCollectionsPermission(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + deleteRoleIfExists(client, "testRole") + defer deleteRoleIfExists(client, "testRole") // START AddCollectionsPermission err := client.Roles.Create(ctx, rbac.Role{ @@ -128,10 +196,11 @@ func TestRBACAddCollectionsPermission(t *testing.T) { // TestRBACAddTenantPermission creates a role with tenant permissions. func TestRBACAddTenantPermission(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + deleteRoleIfExists(client, "testRole") + defer deleteRoleIfExists(client, "testRole") // START AddTenantPermission err := client.Roles.Create(ctx, rbac.Role{ @@ -157,10 +226,11 @@ func TestRBACAddTenantPermission(t *testing.T) { // TestRBACAddDataObjectPermission creates a role with data object permissions. func TestRBACAddDataObjectPermission(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + deleteRoleIfExists(client, "testRole") + defer deleteRoleIfExists(client, "testRole") // START AddDataObjectPermission err := client.Roles.Create(ctx, rbac.Role{ @@ -186,10 +256,11 @@ func TestRBACAddDataObjectPermission(t *testing.T) { // TestRBACAddBackupPermission creates a role with backup permissions. func TestRBACAddBackupPermission(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + deleteRoleIfExists(client, "testRole") + defer deleteRoleIfExists(client, "testRole") // START AddBackupPermission err := client.Roles.Create(ctx, rbac.Role{ @@ -211,10 +282,11 @@ func TestRBACAddBackupPermission(t *testing.T) { // TestRBACAddClusterPermission creates a role with cluster read permission. func TestRBACAddClusterPermission(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + deleteRoleIfExists(client, "testRole") + defer deleteRoleIfExists(client, "testRole") // START AddClusterPermission err := client.Roles.Create(ctx, rbac.Role{ @@ -233,10 +305,11 @@ func TestRBACAddClusterPermission(t *testing.T) { // TestRBACAddNodesPermission creates a role with node read permission. func TestRBACAddNodesPermission(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + deleteRoleIfExists(client, "testRole") + defer deleteRoleIfExists(client, "testRole") // START AddNodesPermission err := client.Roles.Create(ctx, rbac.Role{ @@ -259,10 +332,11 @@ func TestRBACAddNodesPermission(t *testing.T) { // TestRBACAddAliasPermission creates a role with alias permissions. func TestRBACAddAliasPermission(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + deleteRoleIfExists(client, "testRole") + defer deleteRoleIfExists(client, "testRole") // START AddAliasPermission err := client.Roles.Create(ctx, rbac.Role{ @@ -288,10 +362,11 @@ func TestRBACAddAliasPermission(t *testing.T) { // TestRBACAddReplicationsPermission creates a role with replication permissions. func TestRBACAddReplicationsPermission(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + deleteRoleIfExists(client, "testRole") + defer deleteRoleIfExists(client, "testRole") // START AddReplicationsPermission err := client.Roles.Create(ctx, rbac.Role{ @@ -317,10 +392,11 @@ func TestRBACAddReplicationsPermission(t *testing.T) { // TestRBACAddGroupsPermission creates a role with group permissions. func TestRBACAddGroupsPermission(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + deleteRoleIfExists(client, "testRole") + defer deleteRoleIfExists(client, "testRole") // START AddGroupsPermission err := client.Roles.Create(ctx, rbac.Role{ @@ -348,10 +424,11 @@ func TestRBACAddGroupsPermission(t *testing.T) { // TestRBACAddRoles grants additional permissions to an existing role. func TestRBACAddRoles(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + seedRole(t, client, "testRole") + defer deleteRoleIfExists(client, "testRole") // START AddRoles err := client.Roles.AddPermissions(ctx, rbac.AddPermissions{ @@ -370,10 +447,11 @@ func TestRBACAddRoles(t *testing.T) { // TestRBACRemovePermissions removes permissions from a role. func TestRBACRemovePermissions(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + seedRole(t, client, "testRole") + defer deleteRoleIfExists(client, "testRole") // START RemovePermissions err := client.Roles.RemovePermissions(ctx, rbac.RemovePermissions{ @@ -395,9 +473,8 @@ func TestRBACRemovePermissions(t *testing.T) { // TestRBACCheckRoleExists checks whether a role exists. func TestRBACCheckRoleExists(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() // START CheckRoleExists @@ -411,10 +488,11 @@ func TestRBACCheckRoleExists(t *testing.T) { // TestRBACInspectRole retrieves a role and its permissions. func TestRBACInspectRole(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + seedRole(t, client, "testRole") + defer deleteRoleIfExists(client, "testRole") // START InspectRole role, err := client.Roles.Get(ctx, "testRole") @@ -429,9 +507,8 @@ func TestRBACInspectRole(t *testing.T) { // TestRBACListAllRoles lists every role in the instance. func TestRBACListAllRoles(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() // START ListAllRoles @@ -447,10 +524,11 @@ func TestRBACListAllRoles(t *testing.T) { // TestRBACAssignedUsers lists the users that have a given role. func TestRBACAssignedUsers(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + seedRole(t, client, "testRole") + defer deleteRoleIfExists(client, "testRole") // START AssignedUsers userIDs, err := client.Roles.AssignedUserIDs(ctx, "testRole") @@ -465,10 +543,11 @@ func TestRBACAssignedUsers(t *testing.T) { // TestRBACDeleteRole deletes a role. func TestRBACDeleteRole(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + seedRole(t, client, "testRole") + defer deleteRoleIfExists(client, "testRole") // START DeleteRole err := client.Roles.Delete(ctx, "testRole") @@ -484,9 +563,8 @@ func TestRBACDeleteRole(t *testing.T) { // TestRBACListAllUsers lists all database users. func TestRBACListAllUsers(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() // START ListAllUsers @@ -502,10 +580,11 @@ func TestRBACListAllUsers(t *testing.T) { // TestRBACCreateUser creates a database user and prints its API key. func TestRBACCreateUser(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + deleteDBUserIfExists(client, "custom-user") + defer deleteDBUserIfExists(client, "custom-user") // START CreateUser // Create returns the new user's API key. Store it securely; it cannot be @@ -520,10 +599,11 @@ func TestRBACCreateUser(t *testing.T) { // TestRBACDeleteUser deletes a database user. func TestRBACDeleteUser(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + seedDBUser(t, client, "custom-user") + defer deleteDBUserIfExists(client, "custom-user") // START DeleteUser err := client.Users.DB.Delete(ctx, "custom-user") @@ -535,10 +615,11 @@ func TestRBACDeleteUser(t *testing.T) { // TestRBACRotateApiKey rotates a database user's API key. func TestRBACRotateApiKey(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + seedDBUser(t, client, "custom-user") + defer deleteDBUserIfExists(client, "custom-user") // START RotateApiKey newAPIKey, err := client.Users.DB.RotateKey(ctx, "custom-user") @@ -551,10 +632,13 @@ func TestRBACRotateApiKey(t *testing.T) { // TestRBACAssignRole assigns roles to a database user. func TestRBACAssignRole(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + seedDBUser(t, client, "custom-user") + seedRole(t, client, "testRole") // "viewer" is a built-in role. + defer deleteDBUserIfExists(client, "custom-user") + defer deleteRoleIfExists(client, "testRole") // START AssignRole err := client.Users.DB.AssignRoles(ctx, rbac.AssignRolesOptions{ @@ -569,10 +653,20 @@ func TestRBACAssignRole(t *testing.T) { // TestRBACRevokeRoles revokes roles from a database user. func TestRBACRevokeRoles(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + seedDBUser(t, client, "custom-user") + seedRole(t, client, "testRole") + // Assign the role first so the snippet has something to revoke. + if err := client.Users.DB.AssignRoles(ctx, rbac.AssignRolesOptions{ + ID: "custom-user", + Roles: []string{"testRole"}, + }); err != nil { + t.Fatalf("seed role assignment: %v", err) + } + defer deleteDBUserIfExists(client, "custom-user") + defer deleteRoleIfExists(client, "testRole") // START RevokeRoles err := client.Users.DB.RevokeRoles(ctx, rbac.RevokeRolesOptions{ @@ -587,10 +681,11 @@ func TestRBACRevokeRoles(t *testing.T) { // TestRBACListUserRoles lists the roles assigned to a database user. func TestRBACListUserRoles(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + seedDBUser(t, client, "custom-user") + defer deleteDBUserIfExists(client, "custom-user") // START ListUserRoles roles, err := client.Users.DB.AssignedRoles(ctx, rbac.AssignedRolesOptions{ @@ -611,10 +706,16 @@ func TestRBACListUserRoles(t *testing.T) { // TestRBACAssignOidcUserRole assigns roles to an OIDC user. func TestRBACAssignOidcUserRole(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + seedRole(t, client, "testRole") // "viewer" is a built-in role. + defer deleteRoleIfExists(client, "testRole") + defer func() { + _ = client.Users.OIDC.RevokeRoles(ctx, rbac.RevokeRolesOptions{ + ID: "custom-user", Roles: []string{"testRole", "viewer"}, + }) + }() // START AssignOidcUserRole err := client.Users.OIDC.AssignRoles(ctx, rbac.AssignRolesOptions{ @@ -629,10 +730,18 @@ func TestRBACAssignOidcUserRole(t *testing.T) { // TestRBACRevokeOidcUserRoles revokes roles from an OIDC user. func TestRBACRevokeOidcUserRoles(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + seedRole(t, client, "testRole") + // Assign the role first so the snippet has something to revoke. + if err := client.Users.OIDC.AssignRoles(ctx, rbac.AssignRolesOptions{ + ID: "custom-user", + Roles: []string{"testRole"}, + }); err != nil { + t.Fatalf("seed OIDC role assignment: %v", err) + } + defer deleteRoleIfExists(client, "testRole") // START RevokeOidcUserRoles err := client.Users.OIDC.RevokeRoles(ctx, rbac.RevokeRolesOptions{ @@ -647,9 +756,8 @@ func TestRBACRevokeOidcUserRoles(t *testing.T) { // TestRBACListOidcUserRoles lists the roles assigned to an OIDC user. func TestRBACListOidcUserRoles(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() // START ListOidcUserRoles @@ -671,10 +779,16 @@ func TestRBACListOidcUserRoles(t *testing.T) { // TestRBACAssignOidcGroupRoles assigns roles to an OIDC group. func TestRBACAssignOidcGroupRoles(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + seedRole(t, client, "testRole") // "viewer" is a built-in role. + defer deleteRoleIfExists(client, "testRole") + defer func() { + _ = client.Groups.RevokeRoles(ctx, rbac.RevokeRolesOptions{ + ID: "/admin-group", Roles: []string{"testRole", "viewer"}, + }) + }() // START AssignOidcGroupRoles err := client.Groups.AssignRoles(ctx, rbac.AssignRolesOptions{ @@ -689,10 +803,18 @@ func TestRBACAssignOidcGroupRoles(t *testing.T) { // TestRBACRevokeOidcGroupRoles revokes roles from an OIDC group. func TestRBACRevokeOidcGroupRoles(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + seedRole(t, client, "testRole") + // Assign the role first so the snippet has something to revoke. + if err := client.Groups.AssignRoles(ctx, rbac.AssignRolesOptions{ + ID: "/admin-group", + Roles: []string{"testRole"}, + }); err != nil { + t.Fatalf("seed group role assignment: %v", err) + } + defer deleteRoleIfExists(client, "testRole") // START RevokeOidcGroupRoles err := client.Groups.RevokeRoles(ctx, rbac.RevokeRolesOptions{ @@ -707,10 +829,22 @@ func TestRBACRevokeOidcGroupRoles(t *testing.T) { // TestRBACGetOidcGroupRoles lists the roles assigned to an OIDC group. func TestRBACGetOidcGroupRoles(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + seedRole(t, client, "testRole") + if err := client.Groups.AssignRoles(ctx, rbac.AssignRolesOptions{ + ID: "/admin-group", + Roles: []string{"testRole"}, + }); err != nil { + t.Fatalf("seed group role assignment: %v", err) + } + defer deleteRoleIfExists(client, "testRole") + defer func() { + _ = client.Groups.RevokeRoles(ctx, rbac.RevokeRolesOptions{ + ID: "/admin-group", Roles: []string{"testRole"}, + }) + }() // START GetOidcGroupRoles roles, err := client.Groups.AssignedRoles(ctx, rbac.AssignedRolesOptions{ @@ -739,10 +873,11 @@ func TestRBACGetKnownOidcGroups(t *testing.T) { // TestRBACGetGroupAssignments lists the groups assigned to a role. func TestRBACGetGroupAssignments(t *testing.T) { - t.Skip(rbacSkip) ctx := context.Background() - client := connectLocal(t) + client := connectRBACAdmin(t) defer client.Close() + seedRole(t, client, "testRole") + defer deleteRoleIfExists(client, "testRole") // START GetGroupAssignments groups, err := client.Roles.GroupAssignments(ctx, "testRole") From 3dfe4ac870edb2cd6021b040b7c82f5fd04403a4 Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Thu, 30 Jul 2026 17:50:12 +0200 Subject: [PATCH 22/22] fix(go-v6): deterministic non-leading-zero seed UUIDs (kill id_as_bytes flake) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The gRPC SearchReply carries an object id in the id_as_bytes field, which drops leading zero bytes: an id beginning 0x00 comes back as 15 bytes and the client's uuid.FromBytes rejects the whole reply ("invalid UUID (got 15 bytes)"), failing every query that returns that object. Seeds that inserted objects with server-assigned / uuid.New() ids therefore carried a latent ~1/256 flake per seeded object across all search/near-vector tests (T4 CI rolled it for TestGetProperties). Give every seeded object a fixed, non-leading-zero UUID โ€” the same fix already applied to setupJeopardyDemo โ€” so the id can never truncate. Seed helpers reseeded with explicit ids (all OUTSIDE // START/// END markers, so displayed snippets are byte-for-byte unchanged): - search_test.go setupJeopardySearch (3 BYO-vector objects; also added waitForCount) - multi_target_test.go setupJeopardyTiny (3 BYO named-vector objects) - main_test.go setupMultiTenancy (2nd tenantA object -> new mtSourceID2 var) and setupMultiTenancyJeopardy (2 tenantA objects) - aliases_test.go setupArticleForAliases (2 objects) - search_basic_test.go TestBasicQuery inline seed (1 object) Hygiene (REST-only tests, not flake sources, but non-deterministic ids): - manage_objects_test.go (2x uuid.New() -> MustParse, plus the DeleteMany seed) - cross_references_test.go (2x uuid.New() -> MustParse) Left untouched: the two server-assigned inserts that live INSIDE snippet markers (manage_objects CreateObject, tenants CreateMtObject) โ€” neither test queries the object back, so no flake; changing them would alter a displayed snippet. Validated live on a vanilla weaviate:1.38.0 (:8080/:50051): every test whose seed changed passes (search_basic set 3x, multi-target BYO near-vector, aliases, tenants, manage-objects, cross-references). go vet + go build clean under the CI replace directive; replace dropped, go.mod clean. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01WmugebxuspdSQrFKE883W5 --- _includes/code/go-v6/aliases_test.go | 9 +++++++-- _includes/code/go-v6/cross_references_test.go | 4 ++-- _includes/code/go-v6/main_test.go | 13 ++++++++++--- _includes/code/go-v6/manage_objects_test.go | 10 ++++++---- _includes/code/go-v6/multi_target_test.go | 11 +++++++++++ _includes/code/go-v6/search_basic_test.go | 5 +++++ _includes/code/go-v6/search_test.go | 13 +++++++++++++ 7 files changed, 54 insertions(+), 11 deletions(-) diff --git a/_includes/code/go-v6/aliases_test.go b/_includes/code/go-v6/aliases_test.go index 74f9d294..2c8048e7 100644 --- a/_includes/code/go-v6/aliases_test.go +++ b/_includes/code/go-v6/aliases_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/google/uuid" weaviate "github.com/weaviate/weaviate-go-client/v6" "github.com/weaviate/weaviate-go-client/v6/collections" "github.com/weaviate/weaviate-go-client/v6/data" @@ -34,9 +35,13 @@ func setupArticleForAliases(t *testing.T, client *weaviate.Client) { } } articles := client.Collections.Use("Article") + // Fixed, non-leading-zero ids keep the alias query deterministic (a + // server-assigned 0x00-leading id flakes gRPC queries; see filterByIdSeedUUID). + a1 := uuid.MustParse("b1c2d3e4-f5a6-4b7c-8d9e-1f2a3b4c5d6e") + a2 := uuid.MustParse("c1d2e3f4-a5b6-4c7d-8e9f-2a3b4c5d6e7f") if _, err := articles.Data.Insert(ctx, - &data.Object{Properties: map[string]any{"title": "Weaviate", "body": "An open-source vector database"}}, - &data.Object{Properties: map[string]any{"title": "Vectors", "body": "Numeric representations of data"}}, + &data.Object{UUID: &a1, Properties: map[string]any{"title": "Weaviate", "body": "An open-source vector database"}}, + &data.Object{UUID: &a2, Properties: map[string]any{"title": "Vectors", "body": "Numeric representations of data"}}, ); err != nil { t.Fatalf("seed Article: %v", err) } diff --git a/_includes/code/go-v6/cross_references_test.go b/_includes/code/go-v6/cross_references_test.go index b2adb36f..09241d01 100644 --- a/_includes/code/go-v6/cross_references_test.go +++ b/_includes/code/go-v6/cross_references_test.go @@ -43,14 +43,14 @@ func TestAddOneWayCrossReference(t *testing.T) { categories := client.Collections.Use("JeopardyCategory") questions := client.Collections.Use("JeopardyQuestion") - categoryID := uuid.New() + categoryID := uuid.MustParse("c2d3e4f5-a6b7-4c8d-8e9f-8a9b0c1d2e3f") if _, err := categories.Data.Insert(ctx, &data.Object{ UUID: &categoryID, Properties: map[string]any{"title": "SCIENCE"}, }); err != nil { t.Fatal(err) } - questionID := uuid.New() + questionID := uuid.MustParse("d2e3f4a5-b6c7-4d8e-8f9a-9b0c1d2e3f4a") if _, err := questions.Data.Insert(ctx, &data.Object{ UUID: &questionID, Properties: map[string]any{"question": "This vector database is written in Go"}, diff --git a/_includes/code/go-v6/main_test.go b/_includes/code/go-v6/main_test.go index 0e6086cc..24ea8664 100644 --- a/_includes/code/go-v6/main_test.go +++ b/_includes/code/go-v6/main_test.go @@ -280,6 +280,7 @@ func cleanupJeopardyDemo(ctx context.Context, client *weaviate.Client) { // object (a JeopardyCategory row) instead of dangling random ids. var ( mtSourceID = uuid.MustParse("44444444-4444-4444-8444-444444444444") + mtSourceID2 = uuid.MustParse("66666666-6666-4666-8666-666666666666") mtCategoryID = uuid.MustParse("55555555-5555-4555-8555-555555555555") ) @@ -340,7 +341,9 @@ func setupMultiTenancy(t *testing.T, client *weaviate.Client) { &data.Object{UUID: &mtSourceID, Properties: map[string]any{ "question": "This vector DB is OSS and supports automatic property type inference on import", }}, - &data.Object{Properties: map[string]any{ + // Fixed, non-leading-zero id: a server-assigned 0x00-leading id truncates in + // the gRPC id_as_bytes reply and flakes tenant queries (see filterByIdSeedUUID). + &data.Object{UUID: &mtSourceID2, Properties: map[string]any{ "question": "This organ removes excess glucose from the blood & stores it as glycogen", }}, ); err != nil { @@ -384,9 +387,13 @@ func setupMultiTenancyJeopardy(t *testing.T, client *weaviate.Client) { } tenantA := client.Collections.Use("JeopardyQuestion", collections.WithTenant("tenantA")) + // Fixed, non-leading-zero ids keep the tenant read/search snippets deterministic + // (a server-assigned 0x00-leading id flakes gRPC queries; see filterByIdSeedUUID). + mtjLiver := uuid.MustParse("77777777-7777-4777-8777-777777777777") + mtjElephant := uuid.MustParse("88888888-8888-4888-8888-888888888888") if _, err := tenantA.Data.Insert(ctx, - &data.Object{Properties: map[string]any{"question": "This organ removes excess glucose from the blood", "answer": "Liver", "category": "SCIENCE", "points": 100}}, - &data.Object{Properties: map[string]any{"question": "The only living mammal in the order Proboscidea", "answer": "Elephant", "category": "ANIMALS", "points": 200}}, + &data.Object{UUID: &mtjLiver, Properties: map[string]any{"question": "This organ removes excess glucose from the blood", "answer": "Liver", "category": "SCIENCE", "points": 100}}, + &data.Object{UUID: &mtjElephant, Properties: map[string]any{"question": "The only living mammal in the order Proboscidea", "answer": "Elephant", "category": "ANIMALS", "points": 200}}, ); err != nil { t.Fatalf("seed tenantA: %v", err) } diff --git a/_includes/code/go-v6/manage_objects_test.go b/_includes/code/go-v6/manage_objects_test.go index 64d0e792..f14c5e67 100644 --- a/_includes/code/go-v6/manage_objects_test.go +++ b/_includes/code/go-v6/manage_objects_test.go @@ -78,7 +78,7 @@ func TestReplaceObject(t *testing.T) { questions := client.Collections.Use("JeopardyQuestion") - id := uuid.New() + id := uuid.MustParse("e1f2a3b4-c5d6-4e7f-8a9b-4c5d6e7f8a9b") if _, err := questions.Data.Insert(ctx, &data.Object{ UUID: &id, Properties: map[string]any{ @@ -128,7 +128,7 @@ func TestDeleteObject(t *testing.T) { questions := client.Collections.Use("JeopardyQuestion") - id := uuid.New() + id := uuid.MustParse("f1a2b3c4-d5e6-4f7a-8b9c-5d6e7f8a9b0c") if _, err := questions.Data.Insert(ctx, &data.Object{ UUID: &id, Properties: map[string]any{"question": "This object will be deleted"}, @@ -159,9 +159,11 @@ func TestDeleteMany(t *testing.T) { defer client.Collections.Delete(ctx, "JeopardyQuestion") questions := client.Collections.Use("JeopardyQuestion") + dm1 := uuid.MustParse("a2b3c4d5-e6f7-4a8b-8c9d-6e7f8a9b0c1d") + dm2 := uuid.MustParse("b2c3d4e5-f6a7-4b8c-8d9e-7f8a9b0c1d2e") if _, err := questions.Data.Insert(ctx, - &data.Object{Properties: map[string]any{"answer": "Hawaii", "category": "GEOGRAPHY"}}, - &data.Object{Properties: map[string]any{"answer": "Kilauea", "category": "GEOGRAPHY"}}, + &data.Object{UUID: &dm1, Properties: map[string]any{"answer": "Hawaii", "category": "GEOGRAPHY"}}, + &data.Object{UUID: &dm2, Properties: map[string]any{"answer": "Kilauea", "category": "GEOGRAPHY"}}, ); err != nil { t.Fatal(err) } diff --git a/_includes/code/go-v6/multi_target_test.go b/_includes/code/go-v6/multi_target_test.go index ff27fd18..8f7a27d7 100644 --- a/_includes/code/go-v6/multi_target_test.go +++ b/_includes/code/go-v6/multi_target_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/google/uuid" weaviate "github.com/weaviate/weaviate-go-client/v6" "github.com/weaviate/weaviate-go-client/v6/collections" "github.com/weaviate/weaviate-go-client/v6/data" @@ -39,8 +40,16 @@ func setupJeopardyTiny(t *testing.T, client *weaviate.Client) { } jeopardy := client.Collections.Use("JeopardyTiny") + // Fixed, non-leading-zero ids keep the near-vector run deterministic: a + // server-assigned id beginning 0x00 truncates to 15 bytes in the gRPC + // id_as_bytes reply field and the client rejects the whole SearchReply. See + // the filterByIdSeedUUID note in main_test.go. + t1 := uuid.MustParse("8d9e0f1a-2b3c-4d5e-8f60-4d5e6f7a8b9c") + t2 := uuid.MustParse("9e0f1a2b-3c4d-4e5f-8061-5e6f7a8b9c0d") + t3 := uuid.MustParse("af1a2b3c-4d5e-4f6a-8b62-6f7a8b9c0d1e") if _, err := jeopardy.Data.Insert(ctx, &data.Object{ + UUID: &t1, Properties: map[string]any{"question": "This organ removes excess glucose from the blood", "answer": "Liver"}, Vectors: []types.Vector{ {Name: "jeopardy_questions_vector", Single: []float32{0.10, 0.21, 0.32}}, @@ -48,6 +57,7 @@ func setupJeopardyTiny(t *testing.T, client *weaviate.Client) { }, }, &data.Object{ + UUID: &t2, Properties: map[string]any{"question": "The only living mammal in the order Proboscidea", "answer": "Elephant"}, Vectors: []types.Vector{ {Name: "jeopardy_questions_vector", Single: []float32{0.11, 0.20, 0.34}}, @@ -55,6 +65,7 @@ func setupJeopardyTiny(t *testing.T, client *weaviate.Client) { }, }, &data.Object{ + UUID: &t3, Properties: map[string]any{"question": "This tall animal has a long neck and roams the savanna", "answer": "Giraffe"}, Vectors: []types.Vector{ {Name: "jeopardy_questions_vector", Single: []float32{0.14, 0.19, 0.30}}, diff --git a/_includes/code/go-v6/search_basic_test.go b/_includes/code/go-v6/search_basic_test.go index b1636220..e2466b36 100644 --- a/_includes/code/go-v6/search_basic_test.go +++ b/_includes/code/go-v6/search_basic_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/google/uuid" "github.com/weaviate/weaviate-go-client/v6/collections" "github.com/weaviate/weaviate-go-client/v6/data" "github.com/weaviate/weaviate-go-client/v6/query" @@ -18,7 +19,11 @@ func TestBasicQuery(t *testing.T) { defer client.Collections.Delete(ctx, "Article") articles := client.Collections.Use("Article") + // Fixed, non-leading-zero id keeps the query deterministic (a server-assigned + // 0x00-leading id flakes the gRPC reply; see filterByIdSeedUUID in main_test.go). + id := uuid.MustParse("d1e2f3a4-b5c6-4d7e-8f9a-3b4c5d6e7f8a") if _, err := articles.Data.Insert(ctx, &data.Object{ + UUID: &id, Properties: map[string]any{"title": "Hello", "body": "World"}, }); err != nil { t.Fatal(err) diff --git a/_includes/code/go-v6/search_test.go b/_includes/code/go-v6/search_test.go index 01e68a08..6cd81cad 100644 --- a/_includes/code/go-v6/search_test.go +++ b/_includes/code/go-v6/search_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/google/uuid" weaviate "github.com/weaviate/weaviate-go-client/v6" "github.com/weaviate/weaviate-go-client/v6/collections" "github.com/weaviate/weaviate-go-client/v6/data" @@ -44,22 +45,34 @@ func setupJeopardySearch(t *testing.T, client *weaviate.Client) { } jeopardy := client.Collections.Use("JeopardyQuestion") + // Fixed, non-leading-zero ids keep the run deterministic. A server-assigned id + // beginning 0x00 comes back from gRPC search as 15 bytes (the id_as_bytes reply + // field drops leading zero bytes) and the client's uuid.FromBytes then rejects + // the whole SearchReply, flaking every query over this collection. See the + // filterByIdSeedUUID note in main_test.go. + s1 := uuid.MustParse("5a6b7c8d-9e0f-4a1b-8c2d-1a2b3c4d5e6f") + s2 := uuid.MustParse("6b7c8d9e-0f1a-4b2c-8d3e-2b3c4d5e6f7a") + s3 := uuid.MustParse("7c8d9e0f-1a2b-4c3d-8e4f-3c4d5e6f7a8b") if _, err := jeopardy.Data.Insert(ctx, &data.Object{ + UUID: &s1, Properties: map[string]any{"question": "This organ removes excess glucose from the blood & stores it as glycogen", "answer": "Liver", "category": "SCIENCE", "points": 100}, Vectors: []types.Vector{{Name: "default", Single: []float32{0.10, 0.21, 0.32}}}, }, &data.Object{ + UUID: &s2, Properties: map[string]any{"question": "It's the only living mammal in the order Proboscidea", "answer": "Elephant", "category": "ANIMALS", "points": 200}, Vectors: []types.Vector{{Name: "default", Single: []float32{0.11, 0.20, 0.34}}}, }, &data.Object{ + UUID: &s3, Properties: map[string]any{"question": "The gavial looks very much like a crocodile except for this bodily feature", "answer": "the nose or snout", "category": "ANIMALS", "points": 400}, Vectors: []types.Vector{{Name: "default", Single: []float32{0.14, 0.19, 0.30}}}, }, ); err != nil { t.Fatalf("seed JeopardyQuestion: %v", err) } + waitForCount(t, jeopardy, 3) } // TestGetNearText runs a semantic search over a collection whose vectorizer