elixir: add model and workspace test suites (22 tests)
Swadhin Biswas committed 7 months ago
fcd1d5c
1 parent a407de2
commit fcd1d5c
3 files changed
+273 -0
Showing 3 changed files+273-0
▼ELIXIR/test/models_test.exs
+88-0
| @@ -0,0 +1,88 @@ | ||
1 | +defmodule Contexa.ModelsTest do | |
2 | + use ExUnit.Case, async: true | |
3 | + | |
4 | + alias Contexa.Models | |
5 | + alias Contexa.Models.{OTARecord, CommitRecord, BranchMetadata} | |
6 | + | |
7 | + test "generate_id returns 8 hex chars" do | |
8 | + id = Models.generate_id() | |
9 | + assert String.length(id) == 8 | |
10 | + assert Regex.match?(~r/^[0-9a-f]+$/, id) | |
11 | + end | |
12 | + | |
13 | + test "timestamp returns ISO 8601" do | |
14 | + ts = Models.timestamp() | |
15 | + assert Regex.match?(~r/^\d{4}-\d{2}-\d{2}T/, ts) | |
16 | + end | |
17 | + | |
18 | + test "OTA to_markdown renders correctly" do | |
19 | + rec = %OTARecord{step: 1, timestamp: "2026-01-01T00:00:00Z", | |
20 | + observation: "saw something", thought: "thinking", action: "did something"} | |
21 | + md = Models.ota_to_markdown(rec) | |
22 | + assert md =~ "### Step 1" | |
23 | + assert md =~ "saw something" | |
24 | + assert md =~ "thinking" | |
25 | + assert md =~ "did something" | |
26 | + end | |
27 | + | |
28 | + test "Commit to_markdown renders correctly" do | |
29 | + rec = %CommitRecord{commit_id: "abc12345", branch_name: "main", | |
30 | + branch_purpose: "purpose", previous_progress_summary: "prev", | |
31 | + this_commit_contribution: "contrib", timestamp: "2026-01-01T00:00:00Z"} | |
32 | + md = Models.commit_to_markdown(rec) | |
33 | + assert md =~ "## Commit `abc12345`" | |
34 | + assert md =~ "purpose" | |
35 | + assert md =~ "contrib" | |
36 | + end | |
37 | + | |
38 | + test "BranchMetadata YAML round-trip" do | |
39 | + meta = Models.new_branch_metadata("test-branch", "test purpose", "main") | |
40 | + yaml = Models.metadata_to_yaml(meta) | |
41 | + parsed = Models.metadata_from_yaml(yaml) | |
42 | + assert parsed.name == "test-branch" | |
43 | + assert parsed.purpose == "test purpose" | |
44 | + assert parsed.created_from == "main" | |
45 | + assert parsed.status == "active" | |
46 | + assert parsed.merged_into == nil | |
47 | + end | |
48 | + | |
49 | + test "parse_commits extracts records" do | |
50 | + text = """ | |
51 | + # Header | |
52 | + | |
53 | + ## Commit `abc12345` | |
54 | + **Timestamp:** 2026-01-01T00:00:00Z | |
55 | + | |
56 | + **Branch Purpose:** test | |
57 | + | |
58 | + **Previous Progress Summary:** prev | |
59 | + | |
60 | + **This Commit's Contribution:** contrib | |
61 | + | |
62 | + --- | |
63 | + """ | |
64 | + commits = Models.parse_commits(text) | |
65 | + assert length(commits) == 1 | |
66 | + assert hd(commits).commit_id == "abc12345" | |
67 | + assert hd(commits).this_commit_contribution == "contrib" | |
68 | + end | |
69 | + | |
70 | + test "parse_ota extracts records" do | |
71 | + text = """ | |
72 | + # Header | |
73 | + | |
74 | + ### Step 1 — 2026-01-01T00:00:00Z | |
75 | + **Observation:** obs1 | |
76 | + | |
77 | + **Thought:** thought1 | |
78 | + | |
79 | + **Action:** action1 | |
80 | + | |
81 | + --- | |
82 | + """ | |
83 | + records = Models.parse_ota(text) | |
84 | + assert length(records) == 1 | |
85 | + assert hd(records).step == 1 | |
86 | + assert hd(records).observation == "obs1" | |
87 | + end | |
88 | +end | |
0 | 89 | |
▼ELIXIR/test/test_helper.exs
+1-0
| @@ -0,0 +1 @@ | ||
1 | +ExUnit.start() | |
0 | 2 | |
▼ELIXIR/test/workspace_test.exs
+184-0
| @@ -0,0 +1,184 @@ | ||
1 | +defmodule Contexa.WorkspaceTest do | |
2 | + use ExUnit.Case, async: true | |
3 | + | |
4 | + alias Contexa.Workspace | |
5 | + alias Contexa.Models | |
6 | + | |
7 | + defp make_tmp do | |
8 | + dir = Path.join(System.tmp_dir!(), "contexa_ex_test_#{System.unique_integer([:positive])}") | |
9 | + File.rm_rf!(dir) | |
10 | + File.mkdir_p!(dir) | |
11 | + dir | |
12 | + end | |
13 | + | |
14 | + defp cleanup(dir) do | |
15 | + File.rm_rf!(dir) | |
16 | + end | |
17 | + | |
18 | + test "init creates workspace" do | |
19 | + dir = make_tmp() | |
20 | + ws = Workspace.new(dir) |> Workspace.init("Test roadmap") | |
21 | + assert ws.current_branch == "main" | |
22 | + assert File.dir?(Path.join(dir, ".GCC")) | |
23 | + cleanup(dir) | |
24 | + end | |
25 | + | |
26 | + test "init fails if already exists" do | |
27 | + dir = make_tmp() | |
28 | + ws = Workspace.new(dir) |> Workspace.init() | |
29 | + assert_raise RuntimeError, fn -> | |
30 | + Workspace.init(ws) | |
31 | + end | |
32 | + cleanup(dir) | |
33 | + end | |
34 | + | |
35 | + test "load attaches to existing workspace" do | |
36 | + dir = make_tmp() | |
37 | + Workspace.new(dir) |> Workspace.init() | |
38 | + ws = Workspace.new(dir) |> Workspace.load() | |
39 | + assert ws.current_branch == "main" | |
40 | + cleanup(dir) | |
41 | + end | |
42 | + | |
43 | + test "load fails if no workspace" do | |
44 | + dir = make_tmp() | |
45 | + assert_raise RuntimeError, fn -> | |
46 | + Workspace.new(dir) |> Workspace.load() | |
47 | + end | |
48 | + cleanup(dir) | |
49 | + end | |
50 | + | |
51 | + test "log_ota appends records with incrementing steps" do | |
52 | + dir = make_tmp() | |
53 | + ws = Workspace.new(dir) |> Workspace.init() | |
54 | + {ws, r1} = Workspace.log_ota(ws, "obs1", "thought1", "act1") | |
55 | + assert r1.step == 1 | |
56 | + {_ws, r2} = Workspace.log_ota(ws, "obs2", "thought2", "act2") | |
57 | + assert r2.step == 2 | |
58 | + cleanup(dir) | |
59 | + end | |
60 | + | |
61 | + test "commit creates record with auto previous_summary" do | |
62 | + dir = make_tmp() | |
63 | + ws = Workspace.new(dir) |> Workspace.init() | |
64 | + {ws, c1} = Workspace.commit(ws, "First contribution") | |
65 | + assert String.length(c1.commit_id) == 8 | |
66 | + {_ws, c2} = Workspace.commit(ws, "Second contribution") | |
67 | + assert c2.previous_progress_summary == "First contribution" | |
68 | + cleanup(dir) | |
69 | + end | |
70 | + | |
71 | + test "branch creates isolated workspace" do | |
72 | + dir = make_tmp() | |
73 | + ws = Workspace.new(dir) |> Workspace.init() | |
74 | + {ws, _} = Workspace.log_ota(ws, "obs", "thought", "action") | |
75 | + ws = Workspace.branch(ws, "feature", "Test feature") | |
76 | + assert ws.current_branch == "feature" | |
77 | + ctx = Workspace.context(ws, "feature", 10) | |
78 | + assert length(ctx.ota_records) == 0 | |
79 | + assert length(ctx.commits) == 0 | |
80 | + cleanup(dir) | |
81 | + end | |
82 | + | |
83 | + test "branch fails if duplicate name" do | |
84 | + dir = make_tmp() | |
85 | + ws = Workspace.new(dir) |> Workspace.init() | |
86 | + ws = Workspace.branch(ws, "dup", "purpose") | |
87 | + ws = Workspace.switch_branch(ws, "main") | |
88 | + assert_raise RuntimeError, fn -> | |
89 | + Workspace.branch(ws, "dup", "purpose") | |
90 | + end | |
91 | + cleanup(dir) | |
92 | + end | |
93 | + | |
94 | + test "switch_branch changes current branch" do | |
95 | + dir = make_tmp() | |
96 | + ws = Workspace.new(dir) |> Workspace.init() | |
97 | + ws = Workspace.branch(ws, "feature", "purpose") | |
98 | + ws = Workspace.switch_branch(ws, "main") | |
99 | + assert ws.current_branch == "main" | |
100 | + cleanup(dir) | |
101 | + end | |
102 | + | |
103 | + test "switch_branch fails on non-existent branch" do | |
104 | + dir = make_tmp() | |
105 | + ws = Workspace.new(dir) |> Workspace.init() | |
106 | + assert_raise RuntimeError, fn -> | |
107 | + Workspace.switch_branch(ws, "nope") | |
108 | + end | |
109 | + cleanup(dir) | |
110 | + end | |
111 | + | |
112 | + test "list_branches returns all branches" do | |
113 | + dir = make_tmp() | |
114 | + ws = Workspace.new(dir) |> Workspace.init() | |
115 | + ws = Workspace.branch(ws, "alpha", "purpose a") | |
116 | + ws = Workspace.switch_branch(ws, "main") | |
117 | + ws = Workspace.branch(ws, "beta", "purpose b") | |
118 | + branches = Workspace.list_branches(ws) | |
119 | + assert length(branches) >= 3 | |
120 | + cleanup(dir) | |
121 | + end | |
122 | + | |
123 | + test "merge integrates branch into target" do | |
124 | + dir = make_tmp() | |
125 | + ws = Workspace.new(dir) |> Workspace.init() | |
126 | + ws = Workspace.branch(ws, "feature", "Add feature X") | |
127 | + {ws, _} = Workspace.log_ota(ws, "obs", "thought", "action") | |
128 | + {ws, _} = Workspace.commit(ws, "Implemented feature X") | |
129 | + {ws, merge_rec} = Workspace.merge(ws, "feature", nil, "main") | |
130 | + assert ws.current_branch == "main" | |
131 | + assert String.length(merge_rec.commit_id) == 8 | |
132 | + ctx = Workspace.context(ws, "main", 10) | |
133 | + assert length(ctx.commits) >= 1 | |
134 | + cleanup(dir) | |
135 | + end | |
136 | + | |
137 | + test "merge fails on non-existent source" do | |
138 | + dir = make_tmp() | |
139 | + ws = Workspace.new(dir) |> Workspace.init() | |
140 | + assert_raise RuntimeError, fn -> | |
141 | + Workspace.merge(ws, "nope") | |
142 | + end | |
143 | + cleanup(dir) | |
144 | + end | |
145 | + | |
146 | + test "context returns correct data" do | |
147 | + dir = make_tmp() | |
148 | + ws = Workspace.new(dir) |> Workspace.init("My roadmap") | |
149 | + {ws, _} = Workspace.log_ota(ws, "obs1", "t1", "a1") | |
150 | + {ws, _} = Workspace.log_ota(ws, "obs2", "t2", "a2") | |
151 | + {ws, _} = Workspace.commit(ws, "Commit 1") | |
152 | + {ws, _} = Workspace.commit(ws, "Commit 2") | |
153 | + {ws, _} = Workspace.commit(ws, "Commit 3") | |
154 | + ctx = Workspace.context(ws, "main", 2) | |
155 | + assert length(ctx.commits) == 2 | |
156 | + assert length(ctx.ota_records) == 2 | |
157 | + assert ctx.main_roadmap =~ "My roadmap" | |
158 | + assert ctx.metadata != nil | |
159 | + assert ctx.metadata.name == "main" | |
160 | + cleanup(dir) | |
161 | + end | |
162 | + | |
163 | + test "context k larger than available commits" do | |
164 | + dir = make_tmp() | |
165 | + ws = Workspace.new(dir) |> Workspace.init() | |
166 | + {ws, _} = Workspace.commit(ws, "Only commit") | |
167 | + ctx = Workspace.context(ws, "main", 100) | |
168 | + assert length(ctx.commits) == 1 | |
169 | + cleanup(dir) | |
170 | + end | |
171 | + | |
172 | + test "context_summary renders markdown" do | |
173 | + dir = make_tmp() | |
174 | + ws = Workspace.new(dir) |> Workspace.init("Roadmap text") | |
175 | + {ws, _} = Workspace.log_ota(ws, "obs", "thought", "action") | |
176 | + {ws, _} = Workspace.commit(ws, "My contribution") | |
177 | + ctx = Workspace.context(ws, "main", 1) | |
178 | + summary = Models.context_summary(ctx) | |
179 | + assert summary =~ "CONTEXT" | |
180 | + assert summary =~ "Roadmap" | |
181 | + assert summary =~ "My contribution" | |
182 | + cleanup(dir) | |
183 | + end | |
184 | +end | |
0 | 185 | |