Files
goclaw/tests/integration/sqlite_vault_shared_docs_test.go
T
viettranx 7cfcbbf9db fix(vault): include shared docs in agent read paths (#917)
- Patch vault_search, ListDocuments, CountDocuments, ListTreeEntries to
  include shared docs (agent_id IS NULL) for agents in the tenant
- Keep DELETE/GetDocument/GetByBasename strict (auth-intent)
- Add CHECK invariant vault_documents_scope_consistency (PG migration
  000055 NOT VALID, SQLite triggers v24) to prevent future drift
- Update docs and changelog

Affects PostgreSQL and SQLite (desktop edition).
2026-04-16 14:17:48 +07:00

170 lines
5.0 KiB
Go

//go:build sqliteonly
package integration
import (
"context"
"os"
"path/filepath"
"testing"
"github.com/google/uuid"
"github.com/nextlevelbuilder/goclaw/internal/store"
"github.com/nextlevelbuilder/goclaw/internal/store/sqlitestore"
)
// newSQLiteTestStores creates a SQLite-backed store for integration tests.
func newSQLiteTestStores(t *testing.T) *store.Stores {
t.Helper()
tmpDir := t.TempDir()
dbPath := filepath.Join(tmpDir, "test.db")
skillDir := filepath.Join(tmpDir, "skills")
os.MkdirAll(skillDir, 0o755)
stores, err := sqlitestore.NewSQLiteStores(store.StoreConfig{
SQLitePath: dbPath,
SkillsStorageDir: skillDir,
EncryptionKey: "test-key-32-bytes-long-for-aes!!",
})
if err != nil {
t.Fatalf("NewSQLiteStores: %v", err)
}
t.Cleanup(func() { stores.DB.Close() })
return stores
}
// TestSQLiteVaultStore_Search_SharedDocsVisible verifies that Search returns
// shared (agent_id=NULL) documents alongside personal (agent_id=X) documents
// when AgentID is specified.
func TestSQLiteVaultStore_Search_SharedDocsVisible(t *testing.T) {
stores := newSQLiteTestStores(t)
tenantID := uuid.Must(uuid.NewV7())
agentID := uuid.Must(uuid.NewV7())
agentIDStr := agentID.String()
// Seed tenant + agent.
stores.DB.Exec(`INSERT INTO tenants (id, name, slug, status) VALUES (?, 'T', 'srch', 'active')`, tenantID.String())
stores.DB.Exec(
`INSERT INTO agents (id, agent_key, display_name, status, tenant_id, owner_id, model, provider)
VALUES (?, 'srch-agt', 'A', 'active', ?, 'owner', 'gpt-4o', 'openai')`,
agentIDStr, tenantID.String())
ctx := store.WithTenantID(context.Background(), tenantID)
// Personal doc (agent-owned).
personalDoc := &store.VaultDocument{
TenantID: tenantID.String(),
AgentID: &agentIDStr,
Scope: "personal",
Path: "agents/a1/srch-personal.md",
Title: "Personal Search Doc",
DocType: "note",
ContentHash: "ph1",
}
if err := stores.Vault.UpsertDocument(ctx, personalDoc); err != nil {
t.Fatalf("UpsertDocument personal: %v", err)
}
// Shared doc (agent_id=NULL, scope=shared).
sharedDoc := &store.VaultDocument{
TenantID: tenantID.String(),
AgentID: nil,
Scope: "shared",
Path: "shared/srch-shared.md",
Title: "Shared Search Doc",
DocType: "note",
ContentHash: "sh1",
}
if err := stores.Vault.UpsertDocument(ctx, sharedDoc); err != nil {
t.Fatalf("UpsertDocument shared: %v", err)
}
results, err := stores.Vault.Search(ctx, store.VaultSearchOptions{
Query: "Search Doc",
AgentID: agentIDStr,
TenantID: tenantID.String(),
})
if err != nil {
t.Fatalf("Search: %v", err)
}
found := map[string]bool{}
for _, r := range results {
found[r.Document.Path] = true
}
if !found["agents/a1/srch-personal.md"] {
t.Errorf("Search: personal doc not found in results %v", results)
}
if !found["shared/srch-shared.md"] {
t.Errorf("Search: shared doc (agent_id=NULL) not found in results — BUG: shared docs excluded")
}
}
// TestSQLiteVaultStore_List_IncludesShared verifies that ListDocuments returns
// shared (agent_id=NULL) documents alongside personal (agent_id=X) documents
// when agentID is specified.
func TestSQLiteVaultStore_List_IncludesShared(t *testing.T) {
stores := newSQLiteTestStores(t)
tenantID := uuid.Must(uuid.NewV7())
agentID := uuid.Must(uuid.NewV7())
agentIDStr := agentID.String()
// Seed tenant + agent.
stores.DB.Exec(`INSERT INTO tenants (id, name, slug, status) VALUES (?, 'T', 'list', 'active')`, tenantID.String())
stores.DB.Exec(
`INSERT INTO agents (id, agent_key, display_name, status, tenant_id, owner_id, model, provider)
VALUES (?, 'list-agt', 'A', 'active', ?, 'owner', 'gpt-4o', 'openai')`,
agentIDStr, tenantID.String())
ctx := store.WithTenantID(context.Background(), tenantID)
// Personal doc.
personalDoc := &store.VaultDocument{
TenantID: tenantID.String(),
AgentID: &agentIDStr,
Scope: "personal",
Path: "agents/a1/list-personal.md",
Title: "Personal List Doc",
DocType: "note",
ContentHash: "lph1",
}
if err := stores.Vault.UpsertDocument(ctx, personalDoc); err != nil {
t.Fatalf("UpsertDocument personal: %v", err)
}
// Shared doc.
sharedDoc := &store.VaultDocument{
TenantID: tenantID.String(),
AgentID: nil,
Scope: "shared",
Path: "shared/list-shared.md",
Title: "Shared List Doc",
DocType: "note",
ContentHash: "lsh1",
}
if err := stores.Vault.UpsertDocument(ctx, sharedDoc); err != nil {
t.Fatalf("UpsertDocument shared: %v", err)
}
docs, err := stores.Vault.ListDocuments(ctx, tenantID.String(), agentIDStr, store.VaultListOptions{Limit: 50})
if err != nil {
t.Fatalf("ListDocuments: %v", err)
}
found := map[string]bool{}
for _, d := range docs {
found[d.Path] = true
}
if !found["agents/a1/list-personal.md"] {
t.Errorf("ListDocuments: personal doc not found in results %v", docs)
}
if !found["shared/list-shared.md"] {
t.Errorf("ListDocuments: shared doc (agent_id=NULL) not found in results — BUG: shared docs excluded")
}
}