diff --git a/workspace-server/internal/memory/pgplugin/store.go b/workspace-server/internal/memory/pgplugin/store.go index 3bb6ad2a..c00c0112 100644 --- a/workspace-server/internal/memory/pgplugin/store.go +++ b/workspace-server/internal/memory/pgplugin/store.go @@ -80,6 +80,7 @@ func (s *Store) PatchNamespace(ctx context.Context, name string, body contract.N } parts = append(parts, fmt.Sprintf("metadata = $%d", idx)) args = append(args, metadata) + idx++ // advance so subsequent fields (if any) get correct positional index } query := fmt.Sprintf(` UPDATE memory_namespaces SET %s diff --git a/workspace-server/internal/memory/pgplugin/store_test.go b/workspace-server/internal/memory/pgplugin/store_test.go index 129b55a2..0e3160d4 100644 --- a/workspace-server/internal/memory/pgplugin/store_test.go +++ b/workspace-server/internal/memory/pgplugin/store_test.go @@ -302,3 +302,30 @@ func TestStore_PatchNamespace_NotFound_SqlNoRows(t *testing.T) { t.Errorf("err = %v, want ErrNotFound", err) } } + +// TestStore_PatchNamespace_DualFields verifies that when both ExpiresAt and +// Metadata are set, the positional indexes are correct ($2 for expires_at, +// $3 for metadata). Prior to ad7acd30 this was broken: the idx++ after the +// metadata branch was removed as a golangci-lint false-positive, causing +// metadata to be written as $2 (same slot as expires_at) and expires_at to +// be omitted from args entirely. +func TestStore_PatchNamespace_DualFields(t *testing.T) { + db, mock := setupMockDB(t) + store := NewStore(db) + exp := time.Now().Add(time.Hour).UTC() + // sqlmock matches by query string; we verify the query uses $2 and $3. + mock.ExpectQuery("UPDATE memory_namespaces SET expires_at = \\$2, metadata = \\$3 WHERE name = \\$1"). + WithArgs("workspace:abc", sqlmock.AnyArg(), sqlmock.AnyArg()). + WillReturnRows(sqlmock.NewRows([]string{"name", "kind", "expires_at", "metadata", "created_at"}). + AddRow("workspace:abc", "workspace", exp, []byte(`{}`), time.Now())) + got, err := store.PatchNamespace(context.Background(), "workspace:abc", contract.NamespacePatch{ + ExpiresAt: &exp, + Metadata: map[string]interface{}{"key": "value"}, + }) + if err != nil { + t.Fatalf("err = %v, want nil", err) + } + if got.Name != "workspace:abc" { + t.Errorf("got.Name = %q, want workspace:abc", got.Name) + } +}