mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-07-13 03:05:12 +00:00
dd65fbcba0
Three fixes required to run the integration suite after Phase 4: 1. v3_vault_store_test.go makeVaultDoc: AgentID is *string (nullable), not string — was a pre-existing type error from v3 commit8f56ddaathat blocked the test binary build. Pass &agentID. 2. vault_classify_test.go imports: remove unused `store/pg` import — pre-existing from v3 commit4b7a2e6e. 3. TestVaultClassify_DeleteDocLinksByTypes_MultipleTypes: fix off-by-one in target-doc loop. Pre-existing runtime panic from v3 commit — loop created 7 targets but linkTypes slice has 8 entries, accessing docs[8] on a len-8 slice. Changed loop to `range 8`. 4. TestVaultClassify_DeleteDocLinksByTypes_NonExistentDoc: swap "fake-doc-id-12345" placeholder for a well-formed but unused UUID. Post-Phase-4, parseUUID rejects bare strings at the caller boundary; the "non-existent = no-op" contract requires a syntactically valid UUID. Per Q7 documented contract change. Phase 4 Step 4g integration gate.
581 lines
17 KiB
Go
581 lines
17 KiB
Go
//go:build integration
|
|
|
|
package integration
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/store"
|
|
)
|
|
|
|
func TestVaultClassify_DeleteDocLinksByTypes_SingleType(t *testing.T) {
|
|
db := testDB(t)
|
|
tenantID, agentID := seedTenantAgent(t, db)
|
|
ctx := tenantCtx(tenantID)
|
|
vs := newVaultStore(db)
|
|
|
|
tid := tenantID.String()
|
|
aid := agentID.String()
|
|
|
|
// Create 2 documents
|
|
docA := makeVaultDoc(tid, aid, "classify/source.md", "Source Doc")
|
|
docB := makeVaultDoc(tid, aid, "classify/target.md", "Target Doc")
|
|
if err := vs.UpsertDocument(ctx, docA); err != nil {
|
|
t.Fatalf("UpsertDocument A: %v", err)
|
|
}
|
|
if err := vs.UpsertDocument(ctx, docB); err != nil {
|
|
t.Fatalf("UpsertDocument B: %v", err)
|
|
}
|
|
|
|
// Create 3 links with different types
|
|
links := []struct {
|
|
linkType string
|
|
context string
|
|
}{
|
|
{"reference", "cited in source"},
|
|
{"wikilink", "mentioned in source"},
|
|
{"depends_on", "dependency of source"},
|
|
}
|
|
|
|
for _, l := range links {
|
|
link := &store.VaultLink{
|
|
FromDocID: docA.ID,
|
|
ToDocID: docB.ID,
|
|
LinkType: l.linkType,
|
|
Context: l.context,
|
|
}
|
|
if err := vs.CreateLink(ctx, link); err != nil {
|
|
t.Fatalf("CreateLink %s: %v", l.linkType, err)
|
|
}
|
|
}
|
|
|
|
// Verify: 3 links exist
|
|
outLinks, err := vs.GetOutLinks(ctx, tid, docA.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetOutLinks before delete: %v", err)
|
|
}
|
|
if len(outLinks) != 3 {
|
|
t.Errorf("expected 3 links before delete, got %d", len(outLinks))
|
|
}
|
|
|
|
// Delete links with type "reference" only
|
|
if err := vs.DeleteDocLinksByTypes(ctx, tid, docA.ID, []string{"reference"}); err != nil {
|
|
t.Fatalf("DeleteDocLinksByTypes: %v", err)
|
|
}
|
|
|
|
// Verify: only "reference" deleted, "wikilink" and "depends_on" remain
|
|
outLinksAfter, err := vs.GetOutLinks(ctx, tid, docA.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetOutLinks after delete: %v", err)
|
|
}
|
|
if len(outLinksAfter) != 2 {
|
|
t.Errorf("expected 2 links after deleting 1, got %d", len(outLinksAfter))
|
|
}
|
|
|
|
// Verify remaining links are correct types
|
|
typeMap := make(map[string]bool)
|
|
for _, l := range outLinksAfter {
|
|
typeMap[l.LinkType] = true
|
|
}
|
|
if !typeMap["wikilink"] || !typeMap["depends_on"] {
|
|
t.Errorf("expected wikilink and depends_on, got types: %v", typeMap)
|
|
}
|
|
if typeMap["reference"] {
|
|
t.Errorf("reference should have been deleted")
|
|
}
|
|
}
|
|
|
|
func TestVaultClassify_DeleteDocLinksByTypes_MultipleTypes(t *testing.T) {
|
|
db := testDB(t)
|
|
tenantID, agentID := seedTenantAgent(t, db)
|
|
ctx := tenantCtx(tenantID)
|
|
vs := newVaultStore(db)
|
|
|
|
tid := tenantID.String()
|
|
aid := agentID.String()
|
|
|
|
// Create source doc and 8 target docs (one per link type: 6 classify + semantic + wikilink).
|
|
docSource := makeVaultDoc(tid, aid, "source.md", "Source")
|
|
docs := make([]*store.VaultDocument, 0)
|
|
docs = append(docs, docSource)
|
|
|
|
for i := range 8 {
|
|
doc := makeVaultDoc(tid, aid, "classify/target-"+string(rune('a'+i))+".md", "Target "+string(rune('A'+i)))
|
|
if err := vs.UpsertDocument(ctx, doc); err != nil {
|
|
t.Fatalf("UpsertDocument target %d: %v", i, err)
|
|
}
|
|
docs = append(docs, doc)
|
|
}
|
|
docSource = docs[0]
|
|
|
|
if err := vs.UpsertDocument(ctx, docSource); err != nil {
|
|
t.Fatalf("UpsertDocument source: %v", err)
|
|
}
|
|
|
|
// Create links with 6 classify types + semantic + wikilink (8 total)
|
|
linkTypes := []string{
|
|
"reference", // classify type 1
|
|
"depends_on", // classify type 2
|
|
"extends", // classify type 3
|
|
"related", // classify type 4
|
|
"supersedes", // classify type 5
|
|
"contradicts", // classify type 6
|
|
"semantic", // legacy auto-classified type
|
|
"wikilink", // manual link type
|
|
}
|
|
|
|
for i, lt := range linkTypes {
|
|
link := &store.VaultLink{
|
|
FromDocID: docSource.ID,
|
|
ToDocID: docs[i+1].ID,
|
|
LinkType: lt,
|
|
Context: "test context for " + lt,
|
|
}
|
|
if err := vs.CreateLink(ctx, link); err != nil {
|
|
t.Fatalf("CreateLink %s: %v", lt, err)
|
|
}
|
|
}
|
|
|
|
// Verify: 8 links created
|
|
outLinks, err := vs.GetOutLinks(ctx, tid, docSource.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetOutLinks before delete: %v", err)
|
|
}
|
|
if len(outLinks) != 8 {
|
|
t.Errorf("expected 8 links before delete, got %d", len(outLinks))
|
|
}
|
|
|
|
// Delete all classify types + semantic (7 types), keep wikilink
|
|
typesToDelete := []string{
|
|
"reference", "depends_on", "extends",
|
|
"related", "supersedes", "contradicts",
|
|
"semantic",
|
|
}
|
|
if err := vs.DeleteDocLinksByTypes(ctx, tid, docSource.ID, typesToDelete); err != nil {
|
|
t.Fatalf("DeleteDocLinksByTypes: %v", err)
|
|
}
|
|
|
|
// Verify: only wikilink survives
|
|
outLinksAfter, err := vs.GetOutLinks(ctx, tid, docSource.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetOutLinks after delete: %v", err)
|
|
}
|
|
if len(outLinksAfter) != 1 {
|
|
t.Errorf("expected 1 link after deleting 7, got %d", len(outLinksAfter))
|
|
}
|
|
if outLinksAfter[0].LinkType != "wikilink" {
|
|
t.Errorf("expected wikilink, got %s", outLinksAfter[0].LinkType)
|
|
}
|
|
}
|
|
|
|
func TestVaultClassify_DeleteDocLinksByTypes_NoMatches(t *testing.T) {
|
|
db := testDB(t)
|
|
tenantID, agentID := seedTenantAgent(t, db)
|
|
ctx := tenantCtx(tenantID)
|
|
vs := newVaultStore(db)
|
|
|
|
tid := tenantID.String()
|
|
aid := agentID.String()
|
|
|
|
// Create docs
|
|
docA := makeVaultDoc(tid, aid, "nomatch/a.md", "Doc A")
|
|
docB := makeVaultDoc(tid, aid, "nomatch/b.md", "Doc B")
|
|
if err := vs.UpsertDocument(ctx, docA); err != nil {
|
|
t.Fatalf("UpsertDocument A: %v", err)
|
|
}
|
|
if err := vs.UpsertDocument(ctx, docB); err != nil {
|
|
t.Fatalf("UpsertDocument B: %v", err)
|
|
}
|
|
|
|
// Create link with type "wikilink"
|
|
link := &store.VaultLink{
|
|
FromDocID: docA.ID,
|
|
ToDocID: docB.ID,
|
|
LinkType: "wikilink",
|
|
Context: "manual link",
|
|
}
|
|
if err := vs.CreateLink(ctx, link); err != nil {
|
|
t.Fatalf("CreateLink: %v", err)
|
|
}
|
|
|
|
// Delete non-existent type — should succeed but delete nothing
|
|
if err := vs.DeleteDocLinksByTypes(ctx, tid, docA.ID, []string{"reference"}); err != nil {
|
|
t.Fatalf("DeleteDocLinksByTypes non-existent type: %v", err)
|
|
}
|
|
|
|
// Verify wikilink still exists
|
|
outLinks, err := vs.GetOutLinks(ctx, tid, docA.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetOutLinks after no-op delete: %v", err)
|
|
}
|
|
if len(outLinks) != 1 {
|
|
t.Errorf("expected 1 link (unchanged), got %d", len(outLinks))
|
|
}
|
|
if outLinks[0].LinkType != "wikilink" {
|
|
t.Errorf("expected wikilink, got %s", outLinks[0].LinkType)
|
|
}
|
|
}
|
|
|
|
func TestVaultClassify_DeleteDocLinksByTypes_EmptyTypeList(t *testing.T) {
|
|
db := testDB(t)
|
|
tenantID, agentID := seedTenantAgent(t, db)
|
|
ctx := tenantCtx(tenantID)
|
|
vs := newVaultStore(db)
|
|
|
|
tid := tenantID.String()
|
|
aid := agentID.String()
|
|
|
|
// Create docs and link
|
|
docA := makeVaultDoc(tid, aid, "empty/a.md", "Doc A")
|
|
docB := makeVaultDoc(tid, aid, "empty/b.md", "Doc B")
|
|
if err := vs.UpsertDocument(ctx, docA); err != nil {
|
|
t.Fatalf("UpsertDocument A: %v", err)
|
|
}
|
|
if err := vs.UpsertDocument(ctx, docB); err != nil {
|
|
t.Fatalf("UpsertDocument B: %v", err)
|
|
}
|
|
|
|
link := &store.VaultLink{
|
|
FromDocID: docA.ID,
|
|
ToDocID: docB.ID,
|
|
LinkType: "reference",
|
|
Context: "test",
|
|
}
|
|
if err := vs.CreateLink(ctx, link); err != nil {
|
|
t.Fatalf("CreateLink: %v", err)
|
|
}
|
|
|
|
// Delete with empty type list — should succeed but delete nothing
|
|
if err := vs.DeleteDocLinksByTypes(ctx, tid, docA.ID, []string{}); err != nil {
|
|
t.Fatalf("DeleteDocLinksByTypes empty list: %v", err)
|
|
}
|
|
|
|
// Verify link still exists
|
|
outLinks, err := vs.GetOutLinks(ctx, tid, docA.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetOutLinks after empty delete: %v", err)
|
|
}
|
|
if len(outLinks) != 1 {
|
|
t.Errorf("expected 1 link (unchanged), got %d", len(outLinks))
|
|
}
|
|
}
|
|
|
|
func TestVaultClassify_DeleteDocLinksByTypes_MultipleSourceDocs(t *testing.T) {
|
|
db := testDB(t)
|
|
tenantID, agentID := seedTenantAgent(t, db)
|
|
ctx := tenantCtx(tenantID)
|
|
vs := newVaultStore(db)
|
|
|
|
tid := tenantID.String()
|
|
aid := agentID.String()
|
|
|
|
// Create 2 source docs and 1 target doc
|
|
docSource1 := makeVaultDoc(tid, aid, "multi/source1.md", "Source 1")
|
|
docSource2 := makeVaultDoc(tid, aid, "multi/source2.md", "Source 2")
|
|
docTarget := makeVaultDoc(tid, aid, "multi/target.md", "Target")
|
|
|
|
for _, doc := range []*store.VaultDocument{docSource1, docSource2, docTarget} {
|
|
if err := vs.UpsertDocument(ctx, doc); err != nil {
|
|
t.Fatalf("UpsertDocument: %v", err)
|
|
}
|
|
}
|
|
|
|
// Create links from both sources to target
|
|
linkTypes := []string{"reference", "semantic", "wikilink"}
|
|
for _, lt := range linkTypes {
|
|
link1 := &store.VaultLink{
|
|
FromDocID: docSource1.ID,
|
|
ToDocID: docTarget.ID,
|
|
LinkType: lt,
|
|
Context: "from source1",
|
|
}
|
|
link2 := &store.VaultLink{
|
|
FromDocID: docSource2.ID,
|
|
ToDocID: docTarget.ID,
|
|
LinkType: lt,
|
|
Context: "from source2",
|
|
}
|
|
if err := vs.CreateLink(ctx, link1); err != nil {
|
|
t.Fatalf("CreateLink source1: %v", err)
|
|
}
|
|
if err := vs.CreateLink(ctx, link2); err != nil {
|
|
t.Fatalf("CreateLink source2: %v", err)
|
|
}
|
|
}
|
|
|
|
// Verify: both sources have 3 links each
|
|
outLinks1, err := vs.GetOutLinks(ctx, tid, docSource1.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetOutLinks source1 before: %v", err)
|
|
}
|
|
outLinks2, err := vs.GetOutLinks(ctx, tid, docSource2.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetOutLinks source2 before: %v", err)
|
|
}
|
|
if len(outLinks1) != 3 || len(outLinks2) != 3 {
|
|
t.Errorf("expected 3 links each source before delete, got %d and %d", len(outLinks1), len(outLinks2))
|
|
}
|
|
|
|
// Delete "reference" + "semantic" from source1 only
|
|
if err := vs.DeleteDocLinksByTypes(ctx, tid, docSource1.ID, []string{"reference", "semantic"}); err != nil {
|
|
t.Fatalf("DeleteDocLinksByTypes source1: %v", err)
|
|
}
|
|
|
|
// Verify: source1 has only "wikilink", source2 unchanged
|
|
outLinks1After, err := vs.GetOutLinks(ctx, tid, docSource1.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetOutLinks source1 after: %v", err)
|
|
}
|
|
outLinks2After, err := vs.GetOutLinks(ctx, tid, docSource2.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetOutLinks source2 after: %v", err)
|
|
}
|
|
|
|
if len(outLinks1After) != 1 {
|
|
t.Errorf("expected 1 link in source1 after delete, got %d", len(outLinks1After))
|
|
}
|
|
if outLinks1After[0].LinkType != "wikilink" {
|
|
t.Errorf("expected wikilink in source1, got %s", outLinks1After[0].LinkType)
|
|
}
|
|
|
|
if len(outLinks2After) != 3 {
|
|
t.Errorf("expected 3 links in source2 (unchanged), got %d", len(outLinks2After))
|
|
}
|
|
}
|
|
|
|
func TestVaultClassify_DeleteDocLinksByTypes_TenantIsolation(t *testing.T) {
|
|
db := testDB(t)
|
|
tenantA, tenantB, agentA, agentB := seedTwoTenants(t, db)
|
|
ctxA := tenantCtx(tenantA)
|
|
ctxB := tenantCtx(tenantB)
|
|
vs := newVaultStore(db)
|
|
|
|
tidA := tenantA.String()
|
|
tidB := tenantB.String()
|
|
aidA := agentA.String()
|
|
aidB := agentB.String()
|
|
|
|
// Create docs in tenant A
|
|
docA1 := makeVaultDoc(tidA, aidA, "iso/a1.md", "Tenant A Doc 1")
|
|
docA2 := makeVaultDoc(tidA, aidA, "iso/a2.md", "Tenant A Doc 2")
|
|
if err := vs.UpsertDocument(ctxA, docA1); err != nil {
|
|
t.Fatalf("UpsertDocument tenantA doc1: %v", err)
|
|
}
|
|
if err := vs.UpsertDocument(ctxA, docA2); err != nil {
|
|
t.Fatalf("UpsertDocument tenantA doc2: %v", err)
|
|
}
|
|
|
|
// Create docs in tenant B
|
|
docB1 := makeVaultDoc(tidB, aidB, "iso/b1.md", "Tenant B Doc 1")
|
|
docB2 := makeVaultDoc(tidB, aidB, "iso/b2.md", "Tenant B Doc 2")
|
|
if err := vs.UpsertDocument(ctxB, docB1); err != nil {
|
|
t.Fatalf("UpsertDocument tenantB doc1: %v", err)
|
|
}
|
|
if err := vs.UpsertDocument(ctxB, docB2); err != nil {
|
|
t.Fatalf("UpsertDocument tenantB doc2: %v", err)
|
|
}
|
|
|
|
// Create links in both tenants
|
|
linkA := &store.VaultLink{
|
|
FromDocID: docA1.ID,
|
|
ToDocID: docA2.ID,
|
|
LinkType: "reference",
|
|
Context: "tenant A link",
|
|
}
|
|
linkB := &store.VaultLink{
|
|
FromDocID: docB1.ID,
|
|
ToDocID: docB2.ID,
|
|
LinkType: "reference",
|
|
Context: "tenant B link",
|
|
}
|
|
|
|
if err := vs.CreateLink(ctxA, linkA); err != nil {
|
|
t.Fatalf("CreateLink tenantA: %v", err)
|
|
}
|
|
if err := vs.CreateLink(ctxB, linkB); err != nil {
|
|
t.Fatalf("CreateLink tenantB: %v", err)
|
|
}
|
|
|
|
// Verify both have links
|
|
outA, err := vs.GetOutLinks(ctxA, tidA, docA1.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetOutLinks tenantA before: %v", err)
|
|
}
|
|
outB, err := vs.GetOutLinks(ctxB, tidB, docB1.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetOutLinks tenantB before: %v", err)
|
|
}
|
|
if len(outA) != 1 || len(outB) != 1 {
|
|
t.Errorf("expected 1 link each before delete, got %d and %d", len(outA), len(outB))
|
|
}
|
|
|
|
// Delete from tenant A only — should NOT affect tenant B
|
|
if err := vs.DeleteDocLinksByTypes(ctxA, tidA, docA1.ID, []string{"reference"}); err != nil {
|
|
t.Fatalf("DeleteDocLinksByTypes tenantA: %v", err)
|
|
}
|
|
|
|
// Verify: tenantA link deleted, tenantB link unchanged
|
|
outAAfter, err := vs.GetOutLinks(ctxA, tidA, docA1.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetOutLinks tenantA after: %v", err)
|
|
}
|
|
outBAfter, err := vs.GetOutLinks(ctxB, tidB, docB1.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetOutLinks tenantB after: %v", err)
|
|
}
|
|
|
|
if len(outAAfter) != 0 {
|
|
t.Errorf("expected 0 links in tenantA after delete, got %d", len(outAAfter))
|
|
}
|
|
if len(outBAfter) != 1 {
|
|
t.Errorf("expected 1 link in tenantB (unchanged), got %d", len(outBAfter))
|
|
}
|
|
}
|
|
|
|
func TestVaultClassify_DeleteDocLinksByTypes_NonExistentDoc(t *testing.T) {
|
|
db := testDB(t)
|
|
tenantID, _ := seedTenantAgent(t, db)
|
|
ctx := tenantCtx(tenantID)
|
|
vs := newVaultStore(db)
|
|
|
|
tid := tenantID.String()
|
|
|
|
// Use a well-formed but non-existent UUID — post-Phase-4 parseUUID rejects
|
|
// bare strings like "fake-doc-id-12345" at the caller boundary, so the
|
|
// "non-existent doc = no-op" contract requires a syntactically valid UUID
|
|
// that just happens to not exist in the DB.
|
|
fakeDocID := "00000000-0000-4000-8000-000000000dea"
|
|
if err := vs.DeleteDocLinksByTypes(ctx, tid, fakeDocID, []string{"reference"}); err != nil {
|
|
t.Fatalf("DeleteDocLinksByTypes on non-existent doc: %v", err)
|
|
}
|
|
|
|
// GetOutLinks for non-existent doc should return empty or error gracefully
|
|
outLinks, err := vs.GetOutLinks(ctx, tid, fakeDocID)
|
|
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
|
// Some implementations may error on non-existent doc, others return empty list
|
|
// Both are acceptable for a non-existent doc
|
|
}
|
|
if outLinks != nil && len(outLinks) != 0 {
|
|
t.Errorf("expected empty or error for non-existent doc, got %d links", len(outLinks))
|
|
}
|
|
}
|
|
|
|
func TestVaultClassify_DeleteAllClassifyTypesAndSemantic(t *testing.T) {
|
|
db := testDB(t)
|
|
tenantID, agentID := seedTenantAgent(t, db)
|
|
ctx := tenantCtx(tenantID)
|
|
vs := newVaultStore(db)
|
|
|
|
tid := tenantID.String()
|
|
aid := agentID.String()
|
|
|
|
// Create documents
|
|
docSource := makeVaultDoc(tid, aid, "classify/source.md", "Source Doc")
|
|
docTarget1 := makeVaultDoc(tid, aid, "classify/target1.md", "Target 1")
|
|
docTarget2 := makeVaultDoc(tid, aid, "classify/target2.md", "Target 2")
|
|
|
|
for _, doc := range []*store.VaultDocument{docSource, docTarget1, docTarget2} {
|
|
if err := vs.UpsertDocument(ctx, doc); err != nil {
|
|
t.Fatalf("UpsertDocument: %v", err)
|
|
}
|
|
}
|
|
|
|
// Create links with mix of classify types, semantic, and manual types
|
|
linkConfigs := []struct {
|
|
toDocID string
|
|
linkType string
|
|
}{
|
|
{docTarget1.ID, "reference"},
|
|
{docTarget1.ID, "semantic"},
|
|
{docTarget2.ID, "depends_on"},
|
|
{docTarget2.ID, "wikilink"},
|
|
}
|
|
|
|
for _, cfg := range linkConfigs {
|
|
link := &store.VaultLink{
|
|
FromDocID: docSource.ID,
|
|
ToDocID: cfg.toDocID,
|
|
LinkType: cfg.linkType,
|
|
Context: "test context",
|
|
}
|
|
if err := vs.CreateLink(ctx, link); err != nil {
|
|
t.Fatalf("CreateLink %s: %v", cfg.linkType, err)
|
|
}
|
|
}
|
|
|
|
// Verify: 4 links created
|
|
outLinks, err := vs.GetOutLinks(ctx, tid, docSource.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetOutLinks before delete: %v", err)
|
|
}
|
|
if len(outLinks) != 4 {
|
|
t.Errorf("expected 4 links before delete, got %d", len(outLinks))
|
|
}
|
|
|
|
// Delete all classify types (reference, depends_on) + semantic, keep wikilink
|
|
if err := vs.DeleteDocLinksByTypes(ctx, tid, docSource.ID, []string{
|
|
"reference", "depends_on", "extends", "related", "supersedes", "contradicts", "semantic",
|
|
}); err != nil {
|
|
t.Fatalf("DeleteDocLinksByTypes: %v", err)
|
|
}
|
|
|
|
// Verify: only wikilink remains
|
|
outLinksAfter, err := vs.GetOutLinks(ctx, tid, docSource.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetOutLinks after delete: %v", err)
|
|
}
|
|
if len(outLinksAfter) != 1 {
|
|
t.Errorf("expected 1 link after cleanup, got %d", len(outLinksAfter))
|
|
}
|
|
if outLinksAfter[0].LinkType != "wikilink" {
|
|
t.Errorf("expected wikilink survivor, got %s", outLinksAfter[0].LinkType)
|
|
}
|
|
}
|
|
|
|
func TestVaultClassify_DeleteDocLinksByTypes_CasePreservation(t *testing.T) {
|
|
db := testDB(t)
|
|
tenantID, agentID := seedTenantAgent(t, db)
|
|
ctx := tenantCtx(tenantID)
|
|
vs := newVaultStore(db)
|
|
|
|
tid := tenantID.String()
|
|
aid := agentID.String()
|
|
|
|
// Create documents
|
|
docA := makeVaultDoc(tid, aid, "case/a.md", "Doc A")
|
|
docB := makeVaultDoc(tid, aid, "case/b.md", "Doc B")
|
|
if err := vs.UpsertDocument(ctx, docA); err != nil {
|
|
t.Fatalf("UpsertDocument A: %v", err)
|
|
}
|
|
if err := vs.UpsertDocument(ctx, docB); err != nil {
|
|
t.Fatalf("UpsertDocument B: %v", err)
|
|
}
|
|
|
|
// Create links with different case sensitivity
|
|
link := &store.VaultLink{
|
|
FromDocID: docA.ID,
|
|
ToDocID: docB.ID,
|
|
LinkType: "depends_on", // lowercase with underscore
|
|
Context: "test",
|
|
}
|
|
if err := vs.CreateLink(ctx, link); err != nil {
|
|
t.Fatalf("CreateLink: %v", err)
|
|
}
|
|
|
|
// Try deleting with exact case match
|
|
if err := vs.DeleteDocLinksByTypes(ctx, tid, docA.ID, []string{"depends_on"}); err != nil {
|
|
t.Fatalf("DeleteDocLinksByTypes: %v", err)
|
|
}
|
|
|
|
// Verify link is deleted
|
|
outLinks, err := vs.GetOutLinks(ctx, tid, docA.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetOutLinks: %v", err)
|
|
}
|
|
if len(outLinks) != 0 {
|
|
t.Errorf("expected 0 links after exact case match delete, got %d", len(outLinks))
|
|
}
|
|
}
|