go: add 20 tests covering init, ota, commit, branch, merge, context

Swadhin Biswas committed 7 months ago
fee4079
1 parent 6011ef8
commit fee4079
1 file changed
+375 -0
Showing 1 changed file+375-0
▼GO/cortexa/workspace_test.go
+375-0
1
+package cortexa
2
+
3
+import (
4
+ "os"
5
+ "path/filepath"
6
+ "strings"
7
+ "testing"
8
+)
9
+
10
+// helper: creates a temp dir, initialises a Workspace, returns ws + cleanup func.
11
+func setupWorkspace(t *testing.T, roadmap string) (*Workspace, func()) {
12
+ t.Helper()
13
+ dir := t.TempDir()
14
+ ws := New(dir)
15
+ if err := ws.Init(roadmap); err != nil {
16
+ t.Fatalf("Init: %v", err)
17
+ }
18
+ return ws, func() {} // t.TempDir handles cleanup
19
+}
20
+
21
+// ------------------------------------------------------------------ //
22
+// Init //
23
+// ------------------------------------------------------------------ //
24
+
25
+func TestInit_CreatesGCCStructure(t *testing.T) {
26
+ ws, _ := setupWorkspace(t, "Build an AI agent")
27
+
28
+ // .GCC/main.md exists
29
+ data, err := os.ReadFile(ws.mainMd())
30
+ if err != nil {
31
+ t.Fatalf("main.md missing: %v", err)
32
+ }
33
+ if !strings.Contains(string(data), "Build an AI agent") {
34
+ t.Errorf("main.md missing roadmap content")
35
+ }
36
+
37
+ // branches/main/ directory created
38
+ info, err := os.Stat(ws.branchDir(mainBranch))
39
+ if err != nil || !info.IsDir() {
40
+ t.Errorf("expected branches/main directory")
41
+ }
42
+
43
+ // log.md, commit.md, metadata.yaml present
44
+ for _, f := range []string{ws.logPath(mainBranch), ws.commitPath(mainBranch), ws.metaPath(mainBranch)} {
45
+ if _, err := os.Stat(f); err != nil {
46
+ t.Errorf("expected %s to exist", filepath.Base(f))
47
+ }
48
+ }
49
+}
50
+
51
+func TestInit_DoubleInit_Fails(t *testing.T) {
52
+ ws, _ := setupWorkspace(t, "test")
53
+ if err := ws.Init("again"); err == nil {
54
+ t.Errorf("expected error on double init")
55
+ }
56
+}
57
+
58
+// ------------------------------------------------------------------ //
59
+// LogOTA //
60
+// ------------------------------------------------------------------ //
61
+
62
+func TestLogOTA_IncrementsStep(t *testing.T) {
63
+ ws, _ := setupWorkspace(t, "test")
64
+
65
+ r1, err := ws.LogOTA("obs1", "thought1", "act1")
66
+ if err != nil {
67
+ t.Fatalf("LogOTA: %v", err)
68
+ }
69
+ if r1.Step != 1 {
70
+ t.Errorf("expected step 1, got %d", r1.Step)
71
+ }
72
+
73
+ r2, err := ws.LogOTA("obs2", "thought2", "act2")
74
+ if err != nil {
75
+ t.Fatalf("LogOTA: %v", err)
76
+ }
77
+ if r2.Step != 2 {
78
+ t.Errorf("expected step 2, got %d", r2.Step)
79
+ }
80
+}
81
+
82
+func TestLogOTA_AppendsToLogMd(t *testing.T) {
83
+ ws, _ := setupWorkspace(t, "test")
84
+ ws.LogOTA("observation text", "thinking hard", "do something")
85
+
86
+ data := ws.read(ws.logPath(mainBranch))
87
+ if !strings.Contains(data, "observation text") {
88
+ t.Errorf("log.md missing observation")
89
+ }
90
+ if !strings.Contains(data, "thinking hard") {
91
+ t.Errorf("log.md missing thought")
92
+ }
93
+}
94
+
95
+// ------------------------------------------------------------------ //
96
+// Commit //
97
+// ------------------------------------------------------------------ //
98
+
99
+func TestCommit_WritesToCommitMd(t *testing.T) {
100
+ ws, _ := setupWorkspace(t, "test")
101
+
102
+ c, err := ws.Commit("Finished setup phase", nil, nil)
103
+ if err != nil {
104
+ t.Fatalf("Commit: %v", err)
105
+ }
106
+ if c.CommitID == "" {
107
+ t.Error("expected non-empty commit ID")
108
+ }
109
+ if c.BranchName != mainBranch {
110
+ t.Errorf("expected branch %s, got %s", mainBranch, c.BranchName)
111
+ }
112
+
113
+ data := ws.read(ws.commitPath(mainBranch))
114
+ if !strings.Contains(data, "Finished setup phase") {
115
+ t.Error("commit.md missing contribution text")
116
+ }
117
+}
118
+
119
+func TestCommit_AutoPreviousSummary(t *testing.T) {
120
+ ws, _ := setupWorkspace(t, "test")
121
+
122
+ // First commit — no prior, should default
123
+ c1, _ := ws.Commit("First milestone", nil, nil)
124
+ if c1.PreviousProgressSummary != "Initial state — no prior commits." {
125
+ t.Errorf("unexpected first prev summary: %q", c1.PreviousProgressSummary)
126
+ }
127
+
128
+ // Second commit — auto-uses first's contribution
129
+ c2, _ := ws.Commit("Second milestone", nil, nil)
130
+ if c2.PreviousProgressSummary != "First milestone" {
131
+ t.Errorf("expected auto previous summary 'First milestone', got %q", c2.PreviousProgressSummary)
132
+ }
133
+}
134
+
135
+func TestCommit_UpdateRoadmap(t *testing.T) {
136
+ ws, _ := setupWorkspace(t, "test")
137
+
138
+ update := "We pivoted to approach B"
139
+ ws.Commit("Pivot done", nil, &update)
140
+
141
+ data := ws.read(ws.mainMd())
142
+ if !strings.Contains(data, "We pivoted to approach B") {
143
+ t.Error("main.md should contain roadmap update")
144
+ }
145
+}
146
+
147
+// ------------------------------------------------------------------ //
148
+// Branch //
149
+// ------------------------------------------------------------------ //
150
+
151
+func TestBranch_CreatesIsolatedWorkspace(t *testing.T) {
152
+ ws, _ := setupWorkspace(t, "test")
153
+
154
+ if err := ws.Branch("experiment", "Try alternative approach"); err != nil {
155
+ t.Fatalf("Branch: %v", err)
156
+ }
157
+ if ws.CurrentBranch() != "experiment" {
158
+ t.Errorf("expected current branch 'experiment', got %q", ws.CurrentBranch())
159
+ }
160
+
161
+ // Branch should have its own log.md
162
+ info, err := os.Stat(ws.logPath("experiment"))
163
+ if err != nil {
164
+ t.Fatalf("experiment/log.md should exist: %v", err)
165
+ }
166
+ if info.Size() == 0 {
167
+ t.Error("log.md should not be empty (should have header)")
168
+ }
169
+
170
+ // metadata.yaml should record purpose
171
+ meta, err := ws.parseMeta("experiment")
172
+ if err != nil {
173
+ t.Fatalf("parseMeta: %v", err)
174
+ }
175
+ if meta == nil || meta.Purpose != "Try alternative approach" {
176
+ t.Error("metadata should record purpose")
177
+ }
178
+}
179
+
180
+func TestBranch_DuplicateName_Fails(t *testing.T) {
181
+ ws, _ := setupWorkspace(t, "test")
182
+ ws.Branch("exp", "first")
183
+ if err := ws.Branch("exp", "second"); err == nil {
184
+ t.Error("expected error creating duplicate branch")
185
+ }
186
+}
187
+
188
+// ------------------------------------------------------------------ //
189
+// Merge //
190
+// ------------------------------------------------------------------ //
191
+
192
+func TestMerge_IntegratesBranchIntoTarget(t *testing.T) {
193
+ ws, _ := setupWorkspace(t, "test")
194
+
195
+ // Work on main, then branch
196
+ ws.LogOTA("main obs", "main thought", "main action")
197
+ ws.Commit("Main setup done", nil, nil)
198
+
199
+ ws.Branch("feature", "Add new capability")
200
+ ws.LogOTA("feature obs", "feature thought", "feature action")
201
+ ws.Commit("Feature implemented", nil, nil)
202
+
203
+ // Merge back to main
204
+ mergeCommit, err := ws.Merge("feature", nil, mainBranch)
205
+ if err != nil {
206
+ t.Fatalf("Merge: %v", err)
207
+ }
208
+
209
+ // Check merge commit
210
+ if !strings.Contains(mergeCommit.ThisCommitContribution, "feature") {
211
+ t.Error("merge commit should reference the merged branch")
212
+ }
213
+ if ws.CurrentBranch() != mainBranch {
214
+ t.Errorf("after merge, should be on %s, got %s", mainBranch, ws.CurrentBranch())
215
+ }
216
+
217
+ // OTA from feature should appear in main's log
218
+ data := ws.read(ws.logPath(mainBranch))
219
+ if !strings.Contains(data, "feature obs") {
220
+ t.Error("main log should contain merged OTA from feature branch")
221
+ }
222
+
223
+ // Feature branch metadata should say "merged"
224
+ meta, _ := ws.parseMeta("feature")
225
+ if meta == nil || meta.Status != "merged" {
226
+ t.Error("feature branch metadata should be status=merged")
227
+ }
228
+}
229
+
230
+func TestMerge_NonexistentBranch_Fails(t *testing.T) {
231
+ ws, _ := setupWorkspace(t, "test")
232
+ _, err := ws.Merge("nope", nil, mainBranch)
233
+ if err == nil {
234
+ t.Error("expected error merging nonexistent branch")
235
+ }
236
+}
237
+
238
+func TestMerge_NonexistentTarget_Fails(t *testing.T) {
239
+ ws, _ := setupWorkspace(t, "test")
240
+ ws.Branch("feature", "test")
241
+ _, err := ws.Merge("feature", nil, "nonexistent")
242
+ if err == nil {
243
+ t.Error("expected error merging into nonexistent target")
244
+ }
245
+}
246
+
247
+// ------------------------------------------------------------------ //
248
+// Context //
249
+// ------------------------------------------------------------------ //
250
+
251
+func TestContext_ReturnsRoadmapAndCommits(t *testing.T) {
252
+ ws, _ := setupWorkspace(t, "Build a multi-agent system")
253
+ ws.LogOTA("o", "t", "a")
254
+ ws.Commit("Phase 1 complete", nil, nil)
255
+ ws.Commit("Phase 2 complete", nil, nil)
256
+
257
+ // K=1 should return only last commit
258
+ ctx, err := ws.Context(nil, 1)
259
+ if err != nil {
260
+ t.Fatalf("Context: %v", err)
261
+ }
262
+ if !strings.Contains(ctx.MainRoadmap, "multi-agent system") {
263
+ t.Error("context should include roadmap")
264
+ }
265
+ if len(ctx.Commits) != 1 {
266
+ t.Errorf("K=1 should return 1 commit, got %d", len(ctx.Commits))
267
+ }
268
+ if ctx.Commits[0].ThisCommitContribution != "Phase 2 complete" {
269
+ t.Error("K=1 should return the most recent commit")
270
+ }
271
+
272
+ // K=5 (more than available) should return all
273
+ ctx2, _ := ws.Context(nil, 5)
274
+ if len(ctx2.Commits) != 2 {
275
+ t.Errorf("K=5 should return all 2 commits, got %d", len(ctx2.Commits))
276
+ }
277
+}
278
+
279
+func TestContext_NonexistentBranch_Fails(t *testing.T) {
280
+ ws, _ := setupWorkspace(t, "test")
281
+ nope := "nonexistent"
282
+ _, err := ws.Context(&nope, 1)
283
+ if err == nil {
284
+ t.Error("expected error for nonexistent branch")
285
+ }
286
+}
287
+
288
+func TestContext_Summary(t *testing.T) {
289
+ ws, _ := setupWorkspace(t, "Project X")
290
+ ws.LogOTA("obs", "thought", "action")
291
+ ws.Commit("Done step 1", nil, nil)
292
+
293
+ ctx, _ := ws.Context(nil, 1)
294
+ summary := ctx.Summary()
295
+ if !strings.Contains(summary, "CONTEXT") {
296
+ t.Error("summary should contain CONTEXT header")
297
+ }
298
+ if !strings.Contains(summary, mainBranch) {
299
+ t.Error("summary should mention branch name")
300
+ }
301
+}
302
+
303
+// ------------------------------------------------------------------ //
304
+// Helpers //
305
+// ------------------------------------------------------------------ //
306
+
307
+func TestSwitchBranch(t *testing.T) {
308
+ ws, _ := setupWorkspace(t, "test")
309
+ ws.Branch("alt", "alternative path")
310
+ ws.SwitchBranch(mainBranch)
311
+ if ws.CurrentBranch() != mainBranch {
312
+ t.Errorf("expected main, got %s", ws.CurrentBranch())
313
+ }
314
+}
315
+
316
+func TestSwitchBranch_Nonexistent_Fails(t *testing.T) {
317
+ ws, _ := setupWorkspace(t, "test")
318
+ if err := ws.SwitchBranch("nope"); err == nil {
319
+ t.Error("expected error switching to nonexistent branch")
320
+ }
321
+}
322
+
323
+func TestListBranches(t *testing.T) {
324
+ ws, _ := setupWorkspace(t, "test")
325
+ ws.Branch("alpha", "a")
326
+ ws.Branch("beta", "b")
327
+
328
+ branches, err := ws.ListBranches()
329
+ if err != nil {
330
+ t.Fatalf("ListBranches: %v", err)
331
+ }
332
+ // Should have main, alpha, beta
333
+ if len(branches) != 3 {
334
+ t.Errorf("expected 3 branches, got %d: %v", len(branches), branches)
335
+ }
336
+}
337
+
338
+// ------------------------------------------------------------------ //
339
+// Models //
340
+// ------------------------------------------------------------------ //
341
+
342
+func TestOTARecord_ToMarkdown(t *testing.T) {
343
+ r := OTARecord{
344
+ Step: 1,
345
+ Timestamp: "2025-01-01T00:00:00Z",
346
+ Observation: "saw an error",
347
+ Thought: "need to fix it",
348
+ Action: "patched the code",
349
+ }
350
+ md := r.ToMarkdown()
351
+ if !strings.Contains(md, "Step 1") {
352
+ t.Error("markdown should contain step number")
353
+ }
354
+ if !strings.Contains(md, "saw an error") {
355
+ t.Error("markdown should contain observation")
356
+ }
357
+}
358
+
359
+func TestCommitRecord_ToMarkdown(t *testing.T) {
360
+ c := CommitRecord{
361
+ CommitID: "abc123",
362
+ BranchName: "main",
363
+ BranchPurpose: "primary",
364
+ PreviousProgressSummary: "none",
365
+ ThisCommitContribution: "added feature X",
366
+ Timestamp: "2025-01-01T00:00:00Z",
367
+ }
368
+ md := c.ToMarkdown()
369
+ if !strings.Contains(md, "abc123") {
370
+ t.Error("markdown should contain commit ID")
371
+ }
372
+ if !strings.Contains(md, "added feature X") {
373
+ t.Error("markdown should contain contribution")
374
+ }
375
+}
0
376