-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathintegration_features_test.go
More file actions
358 lines (315 loc) · 11.1 KB
/
Copy pathintegration_features_test.go
File metadata and controls
358 lines (315 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
//go:build integration
//nolint:noctx
package yaad_test
import (
"context"
"fmt"
"strings"
"testing"
"time"
"github.com/GrayCodeAI/yaad/engine"
"github.com/GrayCodeAI/yaad/ingest"
intentpkg "github.com/GrayCodeAI/yaad/intent"
"github.com/GrayCodeAI/yaad/internal/bench"
"github.com/GrayCodeAI/yaad/skill"
"github.com/GrayCodeAI/yaad/storage"
)
// This file is part of the yaad_test integration suite. It holds the
// skills, benchmark, profile, conflict, temporal, dedup, compaction,
// mental-model, intent/phase-6, and privacy tests moved verbatim out of
// integration_test.go for readability; behavior is unchanged.
func TestPhase5Skills(t *testing.T) {
eng, cleanup := setup(t)
defer cleanup()
sk := &skill.Skill{
Name: "deploy",
Description: "Deploy the application",
Steps: []skill.Step{
{Order: 1, Description: "Run tests", Command: "pnpm test"},
{Order: 2, Description: "Build", Command: "pnpm build"},
{Order: 3, Description: "Deploy", Command: "fly deploy"},
},
}
node, err := skill.Store(context.Background(), eng, sk, "")
if err != nil {
t.Fatal(err)
}
if node.ID == "" {
t.Error("skill store: empty node ID")
}
// List skills
skills, err := skill.ListSkills(context.Background(), eng.Store(), "")
if err != nil {
t.Fatal(err)
}
if len(skills) == 0 {
t.Error("skill list: expected skills > 0")
}
// Replay
replay := skill.Replay(sk)
if !strings.Contains(replay, "deploy") {
t.Error("skill replay missing content")
}
}
func TestPhase5Benchmark(t *testing.T) {
eng, cleanup := setup(t)
defer cleanup()
// Seed some memories
eng.Remember(context.Background(), engine.RememberInput{Type: "convention", Content: "Use jose not jsonwebtoken for Edge compatibility", Scope: "project"})
eng.Remember(context.Background(), engine.RememberInput{Type: "decision", Content: "Chose NATS over Redis Streams for event bus", Scope: "project"})
eng.Remember(context.Background(), engine.RememberInput{Type: "bug", Content: "Token refresh race condition in auth middleware", Scope: "project"})
result := bench.Run(context.Background(), eng, bench.DefaultQAs(), 2, 10)
if result.Total == 0 {
t.Error("benchmark: no questions evaluated")
}
// R@5 should be > 0 with seeded data
if result.HitAtK[5] == 0 {
t.Log("benchmark: R@5=0 (may be ok with small dataset)")
}
t.Logf("Benchmark:\n%s", result.String())
}
func TestUserProfile(t *testing.T) {
eng, cleanup := setup(t)
defer cleanup()
eng.Remember(context.Background(), engine.RememberInput{Type: "convention", Content: "Use jose for JWT auth", Scope: "project"})
eng.Remember(context.Background(), engine.RememberInput{Type: "decision", Content: "Chose NATS for event bus", Scope: "project"})
eng.Remember(context.Background(), engine.RememberInput{Type: "task", Content: "Add rate limiting", Scope: "project"})
eng.Remember(context.Background(), engine.RememberInput{Type: "preference", Content: "Prefers functional style", Scope: "project"})
p, err := eng.Profile(context.Background(), "")
if err != nil {
t.Fatal(err)
}
if len(p.Static) == 0 {
t.Error("profile: no static facts")
}
if p.Summary == "" {
t.Error("profile: empty summary")
}
formatted := p.Format()
if !strings.Contains(formatted, "User Profile") {
t.Error("profile: formatted output missing header")
}
t.Logf("Profile:\n%s", formatted)
}
func TestConflictResolver(t *testing.T) {
eng, cleanup := setup(t)
defer cleanup()
// Store original convention
old, _ := eng.Remember(context.Background(), engine.RememberInput{
Type: "convention", Content: "Use jsonwebtoken library for JWT", Scope: "project",
})
// Store contradicting convention (should supersede)
newNode, _ := eng.Remember(context.Background(), engine.RememberInput{
Type: "convention", Content: "Use jose instead of jsonwebtoken for Edge compatibility", Scope: "project",
})
// Verify old node confidence was lowered
oldUpdated, _ := eng.Store().GetNode(context.Background(), old.ID)
if oldUpdated.Confidence >= 1.0 {
t.Errorf("conflict: old node confidence should be lowered, got %.2f", oldUpdated.Confidence)
}
// Verify supersedes edge exists
edges, _ := eng.Store().GetEdgesFrom(context.Background(), newNode.ID)
hasSupersedes := false
for _, e := range edges {
if e.Type == "supersedes" && e.ToID == old.ID {
hasSupersedes = true
}
}
if !hasSupersedes {
t.Error("conflict: supersedes edge not created")
}
}
func TestTemporalBackbone(t *testing.T) {
eng, cleanup := setup(t)
defer cleanup()
n1, _ := eng.Remember(context.Background(), engine.RememberInput{Type: "convention", Content: "First convention", Scope: "project", Project: "test"})
n2, _ := eng.Remember(context.Background(), engine.RememberInput{Type: "decision", Content: "Second decision", Scope: "project", Project: "test"})
n3, _ := eng.Remember(context.Background(), engine.RememberInput{Type: "bug", Content: "Third bug report", Scope: "project", Project: "test"})
// Verify temporal chain: n1 → n2 → n3
edges1, _ := eng.Store().GetEdgesFrom(context.Background(), n1.ID)
hasLink12 := false
for _, e := range edges1 {
if e.Type == "learned_in" && e.ToID == n2.ID {
hasLink12 = true
}
}
edges2, _ := eng.Store().GetEdgesFrom(context.Background(), n2.ID)
hasLink23 := false
for _, e := range edges2 {
if e.Type == "learned_in" && e.ToID == n3.ID {
hasLink23 = true
}
}
if !hasLink12 {
t.Error("temporal: n1→n2 learned_in edge missing")
}
if !hasLink23 {
t.Error("temporal: n2→n3 learned_in edge missing")
}
}
func TestDedupRollingWindow(t *testing.T) {
eng, cleanup := setup(t)
defer cleanup()
n1, _ := eng.Remember(context.Background(), engine.RememberInput{
Type: "convention", Content: "Use jose for JWT auth", Scope: "project",
})
// Same content again — should return same node (dedup)
n2, _ := eng.Remember(context.Background(), engine.RememberInput{
Type: "convention", Content: "Use jose for JWT auth", Scope: "project",
})
if n1.ID != n2.ID {
t.Errorf("dedup: expected same node ID, got %s and %s", n1.ID[:8], n2.ID[:8])
}
}
func TestCompaction(t *testing.T) {
eng, cleanup := setup(t)
defer cleanup()
// Store 5 low-confidence nodes
for i := 0; i < 5; i++ {
n, _ := eng.Remember(context.Background(), engine.RememberInput{
Type: "decision", Content: fmt.Sprintf("Old decision %d about something", i), Scope: "project",
})
node, _ := eng.Store().GetNode(context.Background(), n.ID)
node.Confidence = 0.2
node.AccessCount = 0
eng.Store().UpdateNode(context.Background(), node)
}
// Run compaction
compacted, err := eng.Compact(context.Background(), "")
if err != nil {
t.Fatal(err)
}
if compacted == 0 {
t.Error("compaction: expected nodes to be compacted")
}
t.Logf("Compacted %d nodes", compacted)
}
func TestMentalModel(t *testing.T) {
eng, cleanup := setup(t)
defer cleanup()
eng.Remember(context.Background(), engine.RememberInput{Type: "convention", Content: "Use jose for JWT", Scope: "project"})
eng.Remember(context.Background(), engine.RememberInput{Type: "decision", Content: "Chose NATS for events", Scope: "project"})
eng.Remember(context.Background(), engine.RememberInput{Type: "task", Content: "Add rate limiting", Scope: "project"})
model, err := eng.MentalModel(context.Background(), "")
if err != nil {
t.Fatal(err)
}
if model.Summary == "" {
t.Error("mental model: empty summary")
}
if len(model.Conventions) == 0 {
t.Error("mental model: no conventions")
}
formatted := model.Format()
if formatted == "" {
t.Error("mental model: empty formatted output")
}
t.Logf("Mental model:\n%s", formatted)
}
func TestPhase6IntentClassifier(t *testing.T) {
cases := []struct {
query string
expected intentpkg.Intent
}{
{"why did we choose NATS over Redis?", intentpkg.IntentWhy},
{"when did we fix the auth bug?", intentpkg.IntentWhen},
{"how to deploy the application?", intentpkg.IntentHow},
{"what is the auth subsystem?", intentpkg.IntentWhat},
{"which library should I use for JWT?", intentpkg.IntentWho},
{"recall auth middleware", intentpkg.IntentGeneral},
}
for _, c := range cases {
got := intentpkg.Classify(c.query)
if got != c.expected {
t.Errorf("Classify(%q) = %s, want %s", c.query, got, c.expected)
}
}
}
func TestPhase6IntentAwareRetrieval(t *testing.T) {
eng, cleanup := setup(t)
defer cleanup()
// Seed memories
decision, _ := eng.Remember(context.Background(), engine.RememberInput{Type: "decision", Content: "Chose NATS over Redis Streams for event bus", Scope: "project"})
convention, _ := eng.Remember(context.Background(), engine.RememberInput{Type: "convention", Content: "Use NATS client v2 for all event publishing", Scope: "project"})
// Link: decision led_to convention
eng.Graph().AddEdge(context.Background(), &storage.Edge{
ID: "e-test", FromID: decision.ID, ToID: convention.ID, Type: "led_to", Weight: 1.0,
})
// Why query should find the decision via causal traversal
result, err := eng.Recall(context.Background(), engine.RecallOpts{Query: "why NATS", Depth: 2, Limit: 10})
if err != nil {
t.Fatal(err)
}
if len(result.Nodes) == 0 {
t.Error("intent-aware recall returned no nodes")
}
// Should find both decision and convention via causal chain
found := map[string]bool{}
for _, n := range result.Nodes {
found[n.Type] = true
}
t.Logf("Why query found types: %v", found)
}
func TestPhase6DualStream(t *testing.T) {
eng, cleanup := setup(t)
defer cleanup()
ds := ingest.New(eng)
defer ds.Stop()
// Fast path should return immediately
node, err := ds.Remember(context.Background(), engine.RememberInput{
Type: "convention", Content: "Use jose not jsonwebtoken", Scope: "project",
})
if err != nil {
t.Fatal(err)
}
if node.ID == "" {
t.Error("dual stream: empty node ID")
}
// Second remember should create temporal backbone edge
node2, err := ds.Remember(context.Background(), engine.RememberInput{
Type: "decision", Content: "Chose RS256 for JWT", Scope: "project",
})
if err != nil {
t.Fatal(err)
}
if node2.ID == "" {
t.Error("dual stream: second node empty ID")
}
// Give slow path time to run and release DB lock
// Retry up to 500ms (slow path runs async)
var hasTemporalEdge bool
for i := 0; i < 10; i++ {
time.Sleep(50 * time.Millisecond)
edges, _ := eng.Store().GetEdgesFrom(context.Background(), node.ID)
for _, e := range edges {
if e.ToID == node2.ID && e.Type == "learned_in" {
hasTemporalEdge = true
}
}
if hasTemporalEdge {
break
}
}
if !hasTemporalEdge {
t.Error("dual stream: temporal backbone edge not created within 500ms")
}
}
func TestPrivacyFilter(t *testing.T) {
eng, cleanup := setup(t)
defer cleanup()
// Store content with secrets — should be stripped
node, _ := eng.Remember(context.Background(), engine.RememberInput{
Type: "convention",
Content: "Use API key sk-1234567890abcdefghijklmnop for auth and AKIA1234567890ABCDEF for AWS",
Scope: "project",
})
if strings.Contains(node.Content, "sk-1234567890") {
t.Error("privacy: API key not stripped")
}
if strings.Contains(node.Content, "AKIA1234567890") {
t.Error("privacy: AWS key not stripped")
}
if !strings.Contains(node.Content, "[REDACTED]") {
t.Error("privacy: expected [REDACTED] placeholder")
}
}