From d04c97c00047120bcb47e27020e76ec630c3e1ce Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Wed, 10 Jun 2026 14:34:43 +0000 Subject: [PATCH 1/6] fix(e2e): shared ConfigVolumeName helpers prevent KI-013 drift (SEV-2499) Extracts shell SSOT helpers for container/volume names in tests/e2e/_lib.sh: e2e_container_name, e2e_config_volume_name, e2e_session_volume_name, e2e_workspace_volume_name These bash helpers are cross-referenced to their Go equivalents in provisioner.go so the naming contract is explicit in both languages. Updates test_local_provision_lifecycle_e2e.sh to use the helpers so the test seed and the provisioner can never drift again. Refs #2499 Co-Authored-By: Claude Opus 4.8 --- tests/e2e/_lib.sh | 41 +++++++++++++++++++ .../e2e/test_local_provision_lifecycle_e2e.sh | 14 +++---- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/tests/e2e/_lib.sh b/tests/e2e/_lib.sh index f287be514..be21d129c 100755 --- a/tests/e2e/_lib.sh +++ b/tests/e2e/_lib.sh @@ -106,6 +106,47 @@ except Exception: -H "X-Confirm-Name: $name" ${curl_args[@]+"${curl_args[@]}"} > /dev/null || true } +# --------------------------------------------------------------------------- +# Docker container / volume naming helpers (KI-013 / SEV-2499). +# +# KI-013 changed workspace container and volume names from truncated 12-char +# IDs to full UUIDs. These helpers are the bash SSOT for that naming scheme. +# They MUST be kept in sync with the Go equivalents in: +# workspace-server/internal/provisioner/provisioner.go +# +# ContainerName(workspaceID) -> ws- +# ConfigVolumeName(workspaceID) -> ws--configs +# ClaudeSessionVolumeName(wsID) -> ws--claude-sessions +# buildWorkspaceMount(wsID) -> ws--workspace +# +# The drift-guard script .gitea/scripts/lint-e2e-ki013-container-names.sh +# fails CI if any e2e script uses bash substring truncation in a ws-* context. +# --------------------------------------------------------------------------- + +# e2e_container_name returns the Docker container name for a workspace. +# Keep in sync with provisioner.ContainerName. +e2e_container_name() { + echo "ws-${1}" +} + +# e2e_config_volume_name returns the Docker named volume for a workspace's +# /configs directory. Keep in sync with provisioner.ConfigVolumeName. +e2e_config_volume_name() { + echo "ws-${1}-configs" +} + +# e2e_session_volume_name returns the Docker named volume for a workspace's +# Claude Code session directory. Keep in sync with provisioner.ClaudeSessionVolumeName. +e2e_session_volume_name() { + echo "ws-${1}-claude-sessions" +} + +# e2e_workspace_volume_name returns the Docker named volume for a workspace's +# /workspace directory. Keep in sync with buildWorkspaceMount in provisioner.go. +e2e_workspace_volume_name() { + echo "ws-${1}-workspace" +} + e2e_cleanup_all_workspaces() { # GET /workspaces (list) is AdminAuth-gated (router.go:165). Send the platform # admin bearer if one is set so the list doesn't 401 → empty → no cleanup. diff --git a/tests/e2e/test_local_provision_lifecycle_e2e.sh b/tests/e2e/test_local_provision_lifecycle_e2e.sh index 1fa325799..772f215f4 100755 --- a/tests/e2e/test_local_provision_lifecycle_e2e.sh +++ b/tests/e2e/test_local_provision_lifecycle_e2e.sh @@ -191,7 +191,7 @@ except Exception: } container_running() { # container_running -> echoes name if running - docker ps --filter "name=ws-${1}" --filter "status=running" --format '{{.Names}}' 2>/dev/null | head -1 + docker ps --filter "name=$(e2e_container_name "$1")" --filter "status=running" --format '{{.Names}}' 2>/dev/null | head -1 } diagnose_provision() { @@ -224,11 +224,11 @@ cleanup() { # SCOPED teardown — only the workspace this test created. Never a blanket # sweep (other dev workspaces may be live on this shared daemon). e2e_delete_workspace "$WSID" "" >/dev/null 2>&1 || true - docker rm -f "ws-${WSID}" >/dev/null 2>&1 || true + docker rm -f "$(e2e_container_name "$WSID")" >/dev/null 2>&1 || true docker volume rm -f \ - "ws-${WSID}-configs" "ws-${WSID}-claude-sessions" \ - "ws-${WSID}-workspace" >/dev/null 2>&1 || true - echo "cleaned workspace $WSID + ws-${WSID} container/volumes" + "$(e2e_config_volume_name "$WSID")" "$(e2e_session_volume_name "$WSID")" \ + "$(e2e_workspace_volume_name "$WSID")" >/dev/null 2>&1 || true + echo "cleaned workspace $WSID + $(e2e_container_name "$WSID") container/volumes" fi # Restore the cache tag to whatever it pointed at before we retagged it, so a # stub run doesn't leave the real claude-code tag aliased to the stub. @@ -347,7 +347,7 @@ if [ -z "$WSID" ]; then exit 1 fi pass "workspace created: $WSID" -CONFIG_VOL="ws-${WSID}-configs" +CONFIG_VOL="$(e2e_config_volume_name "$WSID")" # Mint a workspace bearer for the WorkspaceAuth-gated secret + /restart calls. WTOKEN=$(e2e_mint_workspace_token "$WSID" || true) @@ -453,7 +453,7 @@ done check "workspace reached online (status=$STATUS)" "online" "$STATUS" if [ "$FAIL" -gt 0 ]; then diagnose_provision "$WSID"; echo "=== Results: $PASS passed, $FAIL failed ==="; exit 1; fi RUN=$(container_running "$WSID") -if [ -n "$RUN" ]; then pass "container running: $RUN"; else fail "no running ws-${WSID} container" "docker ps shows none"; fi +if [ -n "$RUN" ]; then pass "container running: $RUN"; else fail "no running $(e2e_container_name "$WSID") container" "docker ps shows none"; fi echo "" # ---------------------------------------------------------------------------- -- 2.52.0 From 5b722287cb4912150b1584c7898de854c2a36c4b Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Wed, 10 Jun 2026 15:10:43 +0000 Subject: [PATCH 2/6] test(handlers): integration test for memory-write FK outage (#2517) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds real-Postgres integration tests for the #2517 fleet-wide regression where the HTTP Commit path skipped namespace upsert, causing every memory write to fail with memory_records_namespace_fkey. Tests: - TestIntegration_MemoriesCommit_NoNamespace_UpsertsAndWrites: Asserts that Commit returns 201 and both the namespace row and memory record exist in the DB when the namespace was never seeded. - TestIntegration_MemoriesCommit_NamespaceAlreadyExists_Idempotent: Asserts the warm path (namespace already exists) stays harmless. Uses pgplugin.Store directly (via a small adapter for the ForgetMemory signature mismatch) so the test exercises the actual SQL, not stubs. Local verification: - go build ./... & go vet ./... → green - golangci-lint run ./... → 0 issues Refs #2517 Co-Authored-By: Claude Opus 4.8 --- .../handlers/memories_integration_test.go | 219 ++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 workspace-server/internal/handlers/memories_integration_test.go diff --git a/workspace-server/internal/handlers/memories_integration_test.go b/workspace-server/internal/handlers/memories_integration_test.go new file mode 100644 index 000000000..86fd75d6c --- /dev/null +++ b/workspace-server/internal/handlers/memories_integration_test.go @@ -0,0 +1,219 @@ +//go:build integration +// +build integration + +// memories_integration_test.go — REAL Postgres integration tests for the +// #2517 memory-write FK outage (fleet-wide 2026-06-10). +// +// Run with: +// +// docker run --rm -d --name pg-mem-integ \ +// -e POSTGRES_PASSWORD=test -e POSTGRES_DB=molecule \ +// -p 55432:5432 postgres:15-alpine +// sleep 4 +// psql ... < workspace-server/cmd/memory-plugin-postgres/migrations/001_memory_v2.up.sql +// cd workspace-server +// INTEGRATION_DB_URL="postgres://postgres:test@localhost:55432/molecule?sslmode=disable" \ +// go test -tags=integration ./internal/handlers/ -run "^TestIntegration_Memories" +// +// CI: Handlers Postgres Integration workflow (handlers-postgres-integration.yml) +// already starts postgres and applies migrations. The test applies the +// memory plugin schema inline if the tables are missing. + +package handlers + +import ( + "bytes" + "context" + "database/sql" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "git.moleculesai.app/molecule-ai/molecule-core/workspace-server/internal/memory/contract" + "git.moleculesai.app/molecule-ai/molecule-core/workspace-server/internal/memory/namespace" + "git.moleculesai.app/molecule-ai/molecule-core/workspace-server/internal/memory/pgplugin" + "github.com/gin-gonic/gin" + _ "github.com/lib/pq" +) + +// pgpluginAdapter wraps *pgplugin.Store to satisfy memoryPluginAPI. +// The store's ForgetMemory takes (ctx, id, namespace) while the interface +// takes (ctx, id, contract.ForgetRequest); this adapter bridges the gap. +type pgpluginAdapter struct { + store *pgplugin.Store +} + +func (a *pgpluginAdapter) UpsertNamespace(ctx context.Context, name string, body contract.NamespaceUpsert) (*contract.Namespace, error) { + return a.store.UpsertNamespace(ctx, name, body) +} + +func (a *pgpluginAdapter) CommitMemory(ctx context.Context, namespace string, body contract.MemoryWrite) (*contract.MemoryWriteResponse, error) { + return a.store.CommitMemory(ctx, namespace, body) +} + +func (a *pgpluginAdapter) Search(ctx context.Context, body contract.SearchRequest) (*contract.SearchResponse, error) { + return a.store.Search(ctx, body) +} + +func (a *pgpluginAdapter) ForgetMemory(ctx context.Context, id string, _ contract.ForgetRequest) error { + // The integration test only exercises Commit, so the exact namespace + // extraction from ForgetRequest is not load-bearing here. + return a.store.ForgetMemory(ctx, id, "") +} + +// memoryIntegrationDB returns a real postgres connection for memory tests. +// It applies the memory plugin schema if missing and cleans up tables on +// t.Cleanup so tests are hermetic. +func memoryIntegrationDB(t *testing.T) *sql.DB { + t.Helper() + url := requireIntegrationDBURL(t) + conn, err := sql.Open("postgres", url) + if err != nil { + t.Fatalf("open: %v", err) + } + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + if err := conn.PingContext(ctx); err != nil { + t.Fatalf("ping: %v", err) + } + + // Apply memory plugin schema if tables are missing (the CI workflow + // only applies workspace-server/migrations/*.sql, not the plugin's + // own migrations under cmd/memory-plugin-postgres/migrations/). + if _, err := conn.ExecContext(ctx, ` + CREATE TABLE IF NOT EXISTS memory_namespaces ( + name TEXT PRIMARY KEY, + kind TEXT NOT NULL CHECK (kind IN ('workspace','team','org','custom')), + expires_at TIMESTAMPTZ, + metadata JSONB, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() + ); + CREATE TABLE IF NOT EXISTS memory_records ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + namespace TEXT NOT NULL REFERENCES memory_namespaces(name) ON DELETE CASCADE, + content TEXT NOT NULL, + kind TEXT NOT NULL CHECK (kind IN ('fact','summary','checkpoint')), + source TEXT NOT NULL CHECK (source IN ('agent','runtime','user')), + expires_at TIMESTAMPTZ, + propagation JSONB, + pin BOOLEAN NOT NULL DEFAULT false, + embedding vector(1536), + content_tsv tsvector GENERATED ALWAYS AS (to_tsvector('english', content)) STORED, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() + ); + `); err != nil { + // vector extension may not be available in lightweight test postgres. + // If the tables already exist from a previous test, we're fine. + if !strings.Contains(err.Error(), "already exists") { + t.Logf("memory schema apply (may be pre-existing): %v", err) + } + } + + // Clean slate: delete all memory rows so tests are hermetic. + ctx2, cancel2 := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel2() + if _, err := conn.ExecContext(ctx2, `DELETE FROM memory_records`); err != nil { + t.Fatalf("cleanup memory_records: %v", err) + } + if _, err := conn.ExecContext(ctx2, `DELETE FROM memory_namespaces`); err != nil { + t.Fatalf("cleanup memory_namespaces: %v", err) + } + + t.Cleanup(func() { conn.Close() }) + return conn +} + +// TestIntegration_MemoriesCommit_NoNamespace_UpsertsAndWrites pins the +// #2517 fleet-wide regression: the HTTP Commit path skipped namespace +// upsert, so any workspace whose memory_namespaces row was never seeded +// failed every write with memory_records_namespace_fkey. +// +// This test uses a REAL postgres (no stubs) and asserts the observable +// row state: after Commit returns 201, both the namespace row and the +// memory record exist in the DB. +func TestIntegration_MemoriesCommit_NoNamespace_UpsertsAndWrites(t *testing.T) { + conn := memoryIntegrationDB(t) + gin.SetMode(gin.TestMode) + + adapter := &pgpluginAdapter{store: pgplugin.NewStore(conn)} + resolver := namespace.New(conn) + handler := NewMemoriesHandler().withMemoryV2APIs(adapter, resolver) + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Params = gin.Params{{Key: "id", Value: "ws-fk-integ"}} + c.Request = httptest.NewRequest("POST", "/", bytes.NewBufferString(`{"content":"integration test memory","scope":"LOCAL"}`)) + c.Request.Header.Set("Content-Type", "application/json") + + handler.Commit(c) + + if w.Code != http.StatusCreated { + t.Fatalf("expected 201, got %d: %s", w.Code, w.Body.String()) + } + + // Verify the namespace row was auto-created. + var nsCount int + err := conn.QueryRowContext(context.Background(), + `SELECT count(*) FROM memory_namespaces WHERE name = $1`, "workspace:ws-fk-integ").Scan(&nsCount) + if err != nil { + t.Fatalf("select namespace: %v", err) + } + if nsCount != 1 { + t.Errorf("namespace row missing — upsert did not run before commit (count=%d)", nsCount) + } + + // Verify the memory record landed. + var memCount int + err = conn.QueryRowContext(context.Background(), + `SELECT count(*) FROM memory_records WHERE namespace = $1 AND content = $2`, + "workspace:ws-fk-integ", "integration test memory").Scan(&memCount) + if err != nil { + t.Fatalf("select memory record: %v", err) + } + if memCount != 1 { + t.Errorf("memory record missing — write did not land (count=%d)", memCount) + } +} + +// TestIntegration_MemoriesCommit_NamespaceAlreadyExists_Idempotent pins +// that the upsert is harmless when the namespace already exists (warm +// path — no duplicate rows, no error). +func TestIntegration_MemoriesCommit_NamespaceAlreadyExists_Idempotent(t *testing.T) { + conn := memoryIntegrationDB(t) + gin.SetMode(gin.TestMode) + + store := pgplugin.NewStore(conn) + adapter := &pgpluginAdapter{store: store} + resolver := namespace.New(conn) + handler := NewMemoriesHandler().withMemoryV2APIs(adapter, resolver) + + // Pre-seed the namespace. + if _, err := store.UpsertNamespace(context.Background(), "workspace:ws-warm", contract.NamespaceUpsert{Kind: contract.NamespaceKindWorkspace}); err != nil { + t.Fatalf("pre-seed namespace: %v", err) + } + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Params = gin.Params{{Key: "id", Value: "ws-warm"}} + c.Request = httptest.NewRequest("POST", "/", bytes.NewBufferString(`{"content":"warm path memory","scope":"LOCAL"}`)) + c.Request.Header.Set("Content-Type", "application/json") + + handler.Commit(c) + + if w.Code != http.StatusCreated { + t.Fatalf("expected 201, got %d: %s", w.Code, w.Body.String()) + } + + // Must still be exactly one namespace row. + var nsCount int + err := conn.QueryRowContext(context.Background(), + `SELECT count(*) FROM memory_namespaces WHERE name = $1`, "workspace:ws-warm").Scan(&nsCount) + if err != nil { + t.Fatalf("select namespace: %v", err) + } + if nsCount != 1 { + t.Errorf("duplicate namespace rows created (count=%d)", nsCount) + } +} -- 2.52.0 From 549b933ca597986f3723078dbdfd1869508906d8 Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Thu, 11 Jun 2026 05:47:47 +0000 Subject: [PATCH 3/6] fix(ci): use pgvector image + create extension in memory integration test (#2540) The Handlers Postgres Integration job was failing with: pq: type "vector" does not exist because postgres:15-alpine does not ship the pgvector extension. - Switch workflow postgres image to pgvector/pgvector:pg15-alpine - Add CREATE EXTENSION IF NOT EXISTS vector in test setup - Make cleanup robust when tables don't exist (skip instead of fatal) Co-Authored-By: Claude --- .../handlers-postgres-integration.yml | 2 +- .../handlers/memories_integration_test.go | 23 ++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/.gitea/workflows/handlers-postgres-integration.yml b/.gitea/workflows/handlers-postgres-integration.yml index cdbeb9e89..23db8876f 100644 --- a/.gitea/workflows/handlers-postgres-integration.yml +++ b/.gitea/workflows/handlers-postgres-integration.yml @@ -180,7 +180,7 @@ jobs: --health-retries 10 \ -e POSTGRES_PASSWORD=test \ -e POSTGRES_DB=molecule \ - postgres:15-alpine >/dev/null + pgvector/pgvector:pg15-alpine >/dev/null # Read back the bridge IP. Always present immediately after # `docker run -d` for bridge networks. diff --git a/workspace-server/internal/handlers/memories_integration_test.go b/workspace-server/internal/handlers/memories_integration_test.go index 86fd75d6c..67ccdf515 100644 --- a/workspace-server/internal/handlers/memories_integration_test.go +++ b/workspace-server/internal/handlers/memories_integration_test.go @@ -8,7 +8,7 @@ // // docker run --rm -d --name pg-mem-integ \ // -e POSTGRES_PASSWORD=test -e POSTGRES_DB=molecule \ -// -p 55432:5432 postgres:15-alpine +// -p 55432:5432 pgvector/pgvector:pg15-alpine // sleep 4 // psql ... < workspace-server/cmd/memory-plugin-postgres/migrations/001_memory_v2.up.sql // cd workspace-server @@ -82,6 +82,13 @@ func memoryIntegrationDB(t *testing.T) *sql.DB { // Apply memory plugin schema if tables are missing (the CI workflow // only applies workspace-server/migrations/*.sql, not the plugin's // own migrations under cmd/memory-plugin-postgres/migrations/). + // + // We create the pgvector extension first so the vector(1536) column + // type resolves. If the extension is unavailable, the test skips + // rather than failing with an opaque "relation does not exist". + if _, err := conn.ExecContext(ctx, `CREATE EXTENSION IF NOT EXISTS vector;`); err != nil { + t.Skipf("pgvector extension unavailable — memory integration tests require pgvector: %v", err) + } if _, err := conn.ExecContext(ctx, ` CREATE TABLE IF NOT EXISTS memory_namespaces ( name TEXT PRIMARY KEY, @@ -104,21 +111,21 @@ func memoryIntegrationDB(t *testing.T) *sql.DB { created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); `); err != nil { - // vector extension may not be available in lightweight test postgres. - // If the tables already exist from a previous test, we're fine. - if !strings.Contains(err.Error(), "already exists") { - t.Logf("memory schema apply (may be pre-existing): %v", err) - } + t.Fatalf("memory schema apply failed: %v", err) } // Clean slate: delete all memory rows so tests are hermetic. ctx2, cancel2 := context.WithTimeout(context.Background(), 10*time.Second) defer cancel2() if _, err := conn.ExecContext(ctx2, `DELETE FROM memory_records`); err != nil { - t.Fatalf("cleanup memory_records: %v", err) + if !strings.Contains(err.Error(), "does not exist") { + t.Fatalf("cleanup memory_records: %v", err) + } } if _, err := conn.ExecContext(ctx2, `DELETE FROM memory_namespaces`); err != nil { - t.Fatalf("cleanup memory_namespaces: %v", err) + if !strings.Contains(err.Error(), "does not exist") { + t.Fatalf("cleanup memory_namespaces: %v", err) + } } t.Cleanup(func() { conn.Close() }) -- 2.52.0 From e8c2d853fd48f594dab0a791bafc05f4e186171f Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Thu, 11 Jun 2026 05:52:09 +0000 Subject: [PATCH 4/6] =?UTF-8?q?fixup!=20correct=20pgvector=20image=20tag?= =?UTF-8?q?=20=E2=80=94=20pg15-alpine=20does=20not=20exist,=20use=20pg15?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/handlers-postgres-integration.yml | 2 +- workspace-server/internal/handlers/memories_integration_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/handlers-postgres-integration.yml b/.gitea/workflows/handlers-postgres-integration.yml index 23db8876f..a8ae7f88c 100644 --- a/.gitea/workflows/handlers-postgres-integration.yml +++ b/.gitea/workflows/handlers-postgres-integration.yml @@ -180,7 +180,7 @@ jobs: --health-retries 10 \ -e POSTGRES_PASSWORD=test \ -e POSTGRES_DB=molecule \ - pgvector/pgvector:pg15-alpine >/dev/null + pgvector/pgvector:pg15 >/dev/null # Read back the bridge IP. Always present immediately after # `docker run -d` for bridge networks. diff --git a/workspace-server/internal/handlers/memories_integration_test.go b/workspace-server/internal/handlers/memories_integration_test.go index 67ccdf515..6e061e57c 100644 --- a/workspace-server/internal/handlers/memories_integration_test.go +++ b/workspace-server/internal/handlers/memories_integration_test.go @@ -8,7 +8,7 @@ // // docker run --rm -d --name pg-mem-integ \ // -e POSTGRES_PASSWORD=test -e POSTGRES_DB=molecule \ -// -p 55432:5432 pgvector/pgvector:pg15-alpine +// -p 55432:5432 pgvector/pgvector:pg15 // sleep 4 // psql ... < workspace-server/cmd/memory-plugin-postgres/migrations/001_memory_v2.up.sql // cd workspace-server -- 2.52.0 From 7b1cfbb98c3945b1a3bce63121134d2d7f61951d Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Thu, 11 Jun 2026 05:58:44 +0000 Subject: [PATCH 5/6] =?UTF-8?q?fixup!=20seed=20real=20workspace=20UUIDs=20?= =?UTF-8?q?=E2=80=94=20namespace=20resolver=20needs=20valid=20workspaces?= =?UTF-8?q?=20row=20(#2540)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The memory handler calls resolver.WritableNamespaces which walks the workspaces table via recursive CTE. Passing fake string IDs like 'ws-fk-integ' caused: pq: invalid input syntax for type uuid: "ws-fk-integ" Add seedWorkspace helper (matches pattern in sibling integration tests) and use gen_random_uuid() workspace rows so the resolver can walk the chain and derive namespace names correctly. Co-Authored-By: Claude --- .../handlers/memories_integration_test.go | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/workspace-server/internal/handlers/memories_integration_test.go b/workspace-server/internal/handlers/memories_integration_test.go index 6e061e57c..ccb1c3b04 100644 --- a/workspace-server/internal/handlers/memories_integration_test.go +++ b/workspace-server/internal/handlers/memories_integration_test.go @@ -63,6 +63,22 @@ func (a *pgpluginAdapter) ForgetMemory(ctx context.Context, id string, _ contrac return a.store.ForgetMemory(ctx, id, "") } +// seedWorkspace inserts a workspaces row and returns its UUID. The +// namespace resolver (and the handler's parent-id check for GLOBAL) +// both need a real row with a valid UUID. +func seedWorkspace(t *testing.T, conn *sql.DB, name string) string { + t.Helper() + var id string + if err := conn.QueryRowContext(context.Background(), ` + INSERT INTO workspaces (id, name, status) + VALUES (gen_random_uuid(), $1, 'online') + RETURNING id + `, name).Scan(&id); err != nil { + t.Fatalf("seedWorkspace %q: %v", name, err) + } + return id +} + // memoryIntegrationDB returns a real postgres connection for memory tests. // It applies the memory plugin schema if missing and cleans up tables on // t.Cleanup so tests are hermetic. @@ -144,13 +160,14 @@ func TestIntegration_MemoriesCommit_NoNamespace_UpsertsAndWrites(t *testing.T) { conn := memoryIntegrationDB(t) gin.SetMode(gin.TestMode) + wsID := seedWorkspace(t, conn, "fk-integ-ws") adapter := &pgpluginAdapter{store: pgplugin.NewStore(conn)} resolver := namespace.New(conn) handler := NewMemoriesHandler().withMemoryV2APIs(adapter, resolver) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) - c.Params = gin.Params{{Key: "id", Value: "ws-fk-integ"}} + c.Params = gin.Params{{Key: "id", Value: wsID}} c.Request = httptest.NewRequest("POST", "/", bytes.NewBufferString(`{"content":"integration test memory","scope":"LOCAL"}`)) c.Request.Header.Set("Content-Type", "application/json") @@ -163,7 +180,7 @@ func TestIntegration_MemoriesCommit_NoNamespace_UpsertsAndWrites(t *testing.T) { // Verify the namespace row was auto-created. var nsCount int err := conn.QueryRowContext(context.Background(), - `SELECT count(*) FROM memory_namespaces WHERE name = $1`, "workspace:ws-fk-integ").Scan(&nsCount) + `SELECT count(*) FROM memory_namespaces WHERE name = $1`, "workspace:"+wsID).Scan(&nsCount) if err != nil { t.Fatalf("select namespace: %v", err) } @@ -175,7 +192,7 @@ func TestIntegration_MemoriesCommit_NoNamespace_UpsertsAndWrites(t *testing.T) { var memCount int err = conn.QueryRowContext(context.Background(), `SELECT count(*) FROM memory_records WHERE namespace = $1 AND content = $2`, - "workspace:ws-fk-integ", "integration test memory").Scan(&memCount) + "workspace:"+wsID, "integration test memory").Scan(&memCount) if err != nil { t.Fatalf("select memory record: %v", err) } @@ -191,19 +208,21 @@ func TestIntegration_MemoriesCommit_NamespaceAlreadyExists_Idempotent(t *testing conn := memoryIntegrationDB(t) gin.SetMode(gin.TestMode) + wsID := seedWorkspace(t, conn, "warm-ws") store := pgplugin.NewStore(conn) adapter := &pgpluginAdapter{store: store} resolver := namespace.New(conn) handler := NewMemoriesHandler().withMemoryV2APIs(adapter, resolver) + nsName := "workspace:" + wsID // Pre-seed the namespace. - if _, err := store.UpsertNamespace(context.Background(), "workspace:ws-warm", contract.NamespaceUpsert{Kind: contract.NamespaceKindWorkspace}); err != nil { + if _, err := store.UpsertNamespace(context.Background(), nsName, contract.NamespaceUpsert{Kind: contract.NamespaceKindWorkspace}); err != nil { t.Fatalf("pre-seed namespace: %v", err) } w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) - c.Params = gin.Params{{Key: "id", Value: "ws-warm"}} + c.Params = gin.Params{{Key: "id", Value: wsID}} c.Request = httptest.NewRequest("POST", "/", bytes.NewBufferString(`{"content":"warm path memory","scope":"LOCAL"}`)) c.Request.Header.Set("Content-Type", "application/json") @@ -216,7 +235,7 @@ func TestIntegration_MemoriesCommit_NamespaceAlreadyExists_Idempotent(t *testing // Must still be exactly one namespace row. var nsCount int err := conn.QueryRowContext(context.Background(), - `SELECT count(*) FROM memory_namespaces WHERE name = $1`, "workspace:ws-warm").Scan(&nsCount) + `SELECT count(*) FROM memory_namespaces WHERE name = $1`, nsName).Scan(&nsCount) if err != nil { t.Fatalf("select namespace: %v", err) } -- 2.52.0 From 6b237e8b32ea9a99de9f5d08b23aadeb7a3a6cf5 Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Thu, 11 Jun 2026 06:00:46 +0000 Subject: [PATCH 6/6] =?UTF-8?q?fixup!=20remove=20duplicate=20seedWorkspace?= =?UTF-8?q?=20=E2=80=94=20already=20defined=20in=20activity=5Fdelegation?= =?UTF-8?q?=5Fa2a=5Fintegration=5Ftest.go?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handlers/memories_integration_test.go | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/workspace-server/internal/handlers/memories_integration_test.go b/workspace-server/internal/handlers/memories_integration_test.go index ccb1c3b04..1731519e7 100644 --- a/workspace-server/internal/handlers/memories_integration_test.go +++ b/workspace-server/internal/handlers/memories_integration_test.go @@ -63,22 +63,6 @@ func (a *pgpluginAdapter) ForgetMemory(ctx context.Context, id string, _ contrac return a.store.ForgetMemory(ctx, id, "") } -// seedWorkspace inserts a workspaces row and returns its UUID. The -// namespace resolver (and the handler's parent-id check for GLOBAL) -// both need a real row with a valid UUID. -func seedWorkspace(t *testing.T, conn *sql.DB, name string) string { - t.Helper() - var id string - if err := conn.QueryRowContext(context.Background(), ` - INSERT INTO workspaces (id, name, status) - VALUES (gen_random_uuid(), $1, 'online') - RETURNING id - `, name).Scan(&id); err != nil { - t.Fatalf("seedWorkspace %q: %v", name, err) - } - return id -} - // memoryIntegrationDB returns a real postgres connection for memory tests. // It applies the memory plugin schema if missing and cleans up tables on // t.Cleanup so tests are hermetic. -- 2.52.0