Files
ARTEX/db/intent_delete_test.go
BaaRai e233c02314 Translate all Chinese text to English
Comments, doc comments, UI labels, log and error messages, LLM prompt
strings, markdown docs, test data and assertions. Identifiers, enum
keys, routes, placeholders and code structure are unchanged.

Notes:
- docs/漏洞流量证据.md renamed to docs/vulnerability-traffic-evidence.md
- tests that exercised CJK specific handling (GBK zip names, Chinese
  full text search) now use ASCII equivalents; go build passes and the
  test suite matches the pre translation baseline (remaining failures
  need a PostgreSQL DSN and fail identically on the original)
- frontend mention syntax now uses English kinds (finding, asset, ...)
  matching the backend, chat mentions tests pass
2026-10-08 23:14:34 +02:00

175 lines
5.6 KiB
Go

package db
import "testing"
// mustIntent / mustNode / mustLink are terse builders for the delete-cascade tests.
func mustIntent(t *testing.T, es *ExplorationStore, summary string) int64 {
t.Helper()
id, err := es.AddIntent(map[string]any{"summary": summary}, 1, nil, "planner")
if err != nil {
t.Fatal(err)
}
return id
}
func mustNode(t *testing.T, es *ExplorationStore, kind, summary string) int64 {
t.Helper()
id, err := es.AddNode(kind, map[string]any{"summary": summary}, 1, "confirmed", "worker", nil)
if err != nil {
t.Fatal(err)
}
return id
}
func mustLink(t *testing.T, es *ExplorationStore, from int64, rel string, to int64) {
t.Helper()
if err := es.Link(from, rel, to); err != nil {
t.Fatal(err)
}
}
func gone(t *testing.T, es *ExplorationStore, id int64) bool {
t.Helper()
n, err := es.GetNode(id)
if err != nil {
t.Fatal(err)
}
return n == nil
}
// TestSoftDeleteIntent: soft deletion sets deleted + delete_reason and keeps the node.
func TestSoftDeleteIntent(t *testing.T) {
d, err := Open(testDSN(t))
if err != nil {
t.Skipf("postgres unavailable (%v) — skipping", err)
}
defer d.Close()
expID, err := d.CreateExploration("soft delete", "soft delete")
if err != nil {
t.Fatal(err)
}
defer d.Exec(`DELETE FROM explorations WHERE id=$1`, expID)
es := d.Exploration(expID)
intent := mustIntent(t, es, "intent to delete")
if err := es.SetIntentState(intent, "paused"); err != nil {
t.Fatal(err)
}
summary, err := es.SoftDeleteIntent(intent, "wrong direction call")
if err != nil {
t.Fatal(err)
}
if summary != "intent to delete" {
t.Fatalf("summary=%q, want intent to delete", summary)
}
n, err := es.GetNode(intent)
if err != nil || n == nil {
t.Fatalf("intent removed by soft delete: n=%+v err=%v", n, err)
}
if n.State != StateIntentDeleted || n.DeleteReason != "wrong direction call" {
t.Fatalf("state=%q delete_reason=%q, want deleted/wrong direction call", n.State, n.DeleteReason)
}
// Open intents may also be soft-deleted.
openIntent := mustIntent(t, es, "open intent")
if _, err := es.SoftDeleteIntent(openIntent, "direction no longer needed"); err != nil {
t.Fatalf("soft delete open intent: %v", err)
}
if n, err := es.GetNode(openIntent); err != nil || n == nil || n.State != StateIntentDeleted {
t.Fatalf("open intent not soft-deleted: n=%+v err=%v", n, err)
}
// Already-deleted and other states cannot be soft-deleted again.
if _, err := es.SoftDeleteIntent(intent, "delete again"); err == nil {
t.Fatal("soft-deleting an already-deleted intent unexpectedly succeeded")
}
}
// TestHardDeleteCascadesExclusiveDescendants: hard deletion cascades along the
// chain, removing exclusively-owned descendants down to the leaves.
func TestHardDeleteCascadesExclusiveDescendants(t *testing.T) {
d, err := Open(testDSN(t))
if err != nil {
t.Skipf("postgres unavailable (%v) — skipping", err)
}
defer d.Close()
expID, err := d.CreateExploration("hard cascade", "cascade delete")
if err != nil {
t.Fatal(err)
}
defer d.Exec(`DELETE FROM explorations WHERE id=$1`, expID)
es := d.Exploration(expID)
// intent1 --yields--> fact1 --derived_from--> intent2 --yields--> fact2 (leaf)
intent1 := mustIntent(t, es, "root intent")
fact1 := mustNode(t, es, KindFact, "fact1")
mustLink(t, es, intent1, RelYields, fact1)
intent2 := mustIntent(t, es, "derived intent")
mustLink(t, es, fact1, RelDerivedFrom, intent2)
fact2 := mustNode(t, es, KindFact, "fact2")
mustLink(t, es, intent2, RelYields, fact2)
cleanup, err := es.CancelIntent(intent1)
if err != nil {
t.Fatal(err)
}
if cleanup.Intents != 2 || cleanup.Facts != 2 {
t.Fatalf("cleanup=%+v, want 2 intents / 2 facts", cleanup)
}
for _, id := range []int64{intent1, fact1, intent2, fact2} {
if !gone(t, es, id) {
t.Fatalf("node %d survived cascade", id)
}
}
}
// TestHardDeletePreservesSharedAndGoal: hard deletion keeps shared descendants
// (those with other parents) and goals.
func TestHardDeletePreservesSharedAndGoal(t *testing.T) {
d, err := Open(testDSN(t))
if err != nil {
t.Skipf("postgres unavailable (%v) — skipping", err)
}
defer d.Close()
expID, err := d.CreateExploration("hard preserve", "keep shared/goal")
if err != nil {
t.Fatal(err)
}
defer d.Exec(`DELETE FROM explorations WHERE id=$1`, expID)
es := d.Exploration(expID)
goal, err := es.AddGoal(map[string]any{"text": "take the backend"}, "human")
if err != nil {
t.Fatal(err)
}
// intent1 exclusively owns finding (proves goal); intent1 and intentX share
// fact1 (fact1 derives intent2).
intent1 := mustIntent(t, es, "intent to delete")
intentX := mustIntent(t, es, "bypass intent")
finding := mustNode(t, es, KindFinding, "vulnerability")
mustLink(t, es, intent1, RelYields, finding)
mustLink(t, es, finding, RelProves, goal)
shared := mustNode(t, es, KindFact, "shared fact")
mustLink(t, es, intent1, RelYields, shared)
mustLink(t, es, intentX, RelYields, shared)
intent2 := mustIntent(t, es, "derived from the shared fact")
mustLink(t, es, shared, RelDerivedFrom, intent2)
cleanup, err := es.CancelIntent(intent1)
if err != nil {
t.Fatal(err)
}
// Only intent1 and its exclusively-owned finding are deleted; shared (with
// intentX as a parent), its downstream intent2, and the goal are all kept.
if cleanup.Intents != 1 || cleanup.Findings != 1 || cleanup.Facts != 0 {
t.Fatalf("cleanup=%+v, want 1 intent / 1 finding / 0 fact", cleanup)
}
if !gone(t, es, intent1) || !gone(t, es, finding) {
t.Fatal("intent1/finding should be removed")
}
for _, id := range []int64{goal, intentX, shared, intent2} {
if gone(t, es, id) {
t.Fatalf("node %d was wrongly cascaded", id)
}
}
}