mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-07-18 00:16:41 +00:00
- 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).
170 lines
5.0 KiB
Go
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")
|
|
}
|
|
}
|