diff --git a/terraform/provider/litellm/resource_vector_store_crud.go b/terraform/provider/litellm/resource_vector_store_crud.go index b05017f7125..b8718646174 100644 --- a/terraform/provider/litellm/resource_vector_store_crud.go +++ b/terraform/provider/litellm/resource_vector_store_crud.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" + "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -29,7 +30,10 @@ func resourceLiteLLMVectorStoreCreate(d *schema.ResourceData, m interface{}) err paramsMap[k] = v } + vectorStoreID := uuid.New().String() + vectorStoreRequest := VectorStoreRequest{ + VectorStoreID: vectorStoreID, CustomLLMProvider: customLLMProvider, VectorStoreName: vectorStoreName, VectorStoreDescription: vectorStoreDescription, @@ -49,9 +53,7 @@ func resourceLiteLLMVectorStoreCreate(d *schema.ResourceData, m interface{}) err return fmt.Errorf("failed to create vector store: %w", err) } - // Set the resource ID to the vector store name for now - // We'll update this after reading the response to get the actual ID - d.SetId(vectorStoreName) + d.SetId(vectorStoreID) return resourceLiteLLMVectorStoreRead(d, m) } @@ -76,8 +78,10 @@ func resourceLiteLLMVectorStoreRead(d *schema.ResourceData, m interface{}) error return nil } - var vectorStoreResp VectorStoreResponse - err = handleVectorStoreAPIResponse(resp, &vectorStoreResp, client) + var infoResp struct { + VectorStore VectorStoreResponse `json:"vector_store"` + } + err = handleVectorStoreAPIResponse(resp, &infoResp, client) if err != nil { if err.Error() == "vector_store_not_found" { d.SetId("") @@ -85,6 +89,7 @@ func resourceLiteLLMVectorStoreRead(d *schema.ResourceData, m interface{}) error } return fmt.Errorf("failed to read vector store: %w", err) } + vectorStoreResp := infoResp.VectorStore // Update the resource ID to the actual vector store ID from the response if vectorStoreResp.VectorStoreID != "" { diff --git a/terraform/provider/litellm/resource_vector_store_crud_test.go b/terraform/provider/litellm/resource_vector_store_crud_test.go index 485ec54346d..424895b2a03 100644 --- a/terraform/provider/litellm/resource_vector_store_crud_test.go +++ b/terraform/provider/litellm/resource_vector_store_crud_test.go @@ -19,7 +19,7 @@ func TestVectorStoreReadDoesNotPersistServerLitellmParams(t *testing.T) { "api_base": "https://upstream.example.com", }, } - body, _ := json.Marshal(resp) + body, _ := json.Marshal(map[string]interface{}{"vector_store": resp}) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -53,3 +53,61 @@ func TestVectorStoreReadDoesNotPersistServerLitellmParams(t *testing.T) { t.Fatalf("read did not populate non-sensitive fields") } } + +func TestVectorStoreCreateSendsGeneratedIDAndUsesItAsResourceID(t *testing.T) { + var createBody map[string]interface{} + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + if r.URL.Path == "/vector_store/new" { + json.NewDecoder(r.Body).Decode(&createBody) + id, _ := createBody["vector_store_id"].(string) + if id == "" { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(`{"detail":"vector_store_id and custom_llm_provider are required"}`)) + return + } + json.NewEncoder(w).Encode(map[string]interface{}{ + "status": "success", + "vector_store": VectorStoreResponse{VectorStoreID: id, VectorStoreName: "kb", CustomLLMProvider: "openai"}, + }) + return + } + + var info VectorStoreInfoRequest + json.NewDecoder(r.Body).Decode(&info) + json.NewEncoder(w).Encode(map[string]interface{}{ + "vector_store": VectorStoreResponse{ + VectorStoreID: info.VectorStoreID, + VectorStoreName: "kb", + CustomLLMProvider: "openai", + CreatedAt: "2026-01-01T00:00:00Z", + }, + }) + })) + defer srv.Close() + + d := schema.TestResourceDataRaw(t, resourceLiteLLMVectorStore().Schema, map[string]interface{}{ + "vector_store_name": "kb", + "custom_llm_provider": "openai", + }) + + if err := resourceLiteLLMVectorStoreCreate(d, NewClient(srv.URL, "test-key", true)); err != nil { + t.Fatalf("create failed: %v", err) + } + + sentID, _ := createBody["vector_store_id"].(string) + if sentID == "" { + t.Fatal("create payload omitted vector_store_id, which the proxy rejects") + } + if d.Id() != sentID { + t.Errorf("resource id = %q, want the created store id %q", d.Id(), sentID) + } + if d.Get("vector_store_id").(string) != sentID { + t.Errorf("vector_store_id = %q, want %q", d.Get("vector_store_id").(string), sentID) + } + if d.Get("created_at").(string) != "2026-01-01T00:00:00Z" { + t.Errorf("create did not refresh computed fields from the API: %v", d.Get("created_at")) + } +} diff --git a/tests/unit/interactions/test_openapi_compliance.py b/tests/unit/interactions/test_openapi_compliance.py index d3f1183cea6..462a9581a7f 100644 --- a/tests/unit/interactions/test_openapi_compliance.py +++ b/tests/unit/interactions/test_openapi_compliance.py @@ -3,12 +3,14 @@ OpenAPI compliance tests for Google Interactions API. Validates that our SDK requests/responses match the OpenAPI spec at: https://ai.google.dev/static/api/interactions.openapi.json +Schema names verified against that spec on 2026-09-28. Run with: pytest tests/unit/interactions/test_openapi_compliance.py -v """ import json import os +import re from typing import Any, Dict from unittest.mock import MagicMock, patch @@ -60,12 +62,11 @@ class TestRequestCompliance: """Tests that our request bodies match the OpenAPI spec.""" def test_create_model_interaction_request_schema(self, spec_dict): - """Verify CreateModelInteractionParams schema fields.""" - schema = spec_dict["components"]["schemas"]["CreateModelInteractionParams"] + """Verify ModelInteraction schema fields.""" + schema = spec_dict["components"]["schemas"]["ModelInteraction"] - # Required fields per spec assert "model" in schema["required"] - assert "input" in schema["required"] + assert "input" in schema["properties"] # Check our supported optional fields exist in spec our_optional_fields = [ @@ -88,7 +89,7 @@ class TestRequestCompliance: def test_input_types_match_spec(self, spec_dict): """Verify input field supports string, Content, Content[], Turn[].""" - schema = spec_dict["components"]["schemas"]["CreateModelInteractionParams"] + schema = spec_dict["components"]["schemas"]["ModelInteraction"] input_schema = schema["properties"]["input"] # The input property may be inline oneOf or a $ref to InteractionsInput @@ -313,7 +314,7 @@ class TestEndpointCompliance: get_path = None for path, methods in paths.items(): - if "{id}" in path and "interactions" in path and "get" in methods: + if re.fullmatch(r".*/interactions/\{[^/{}]+\}", path) and "get" in methods: get_path = path break @@ -326,7 +327,7 @@ class TestEndpointCompliance: delete_path = None for path, methods in paths.items(): - if "{id}" in path and "interactions" in path and "delete" in methods: + if re.fullmatch(r".*/interactions/\{[^/{}]+\}", path) and "delete" in methods: delete_path = path break