arche / internal/repo/repo_test.go

commit 154431fd
  1package repo_test
  2
  3import (
  4	"os"
  5	"path/filepath"
  6	"testing"
  7
  8	"arche/internal/diff"
  9	"arche/internal/object"
 10	"arche/internal/repo"
 11	"arche/internal/store"
 12	"arche/internal/wc"
 13
 14	_ "github.com/mattn/go-sqlite3"
 15)
 16
 17func initTestRepo(t *testing.T) *repo.Repo {
 18	t.Helper()
 19	dir := t.TempDir()
 20	r, err := repo.Init(dir)
 21	if err != nil {
 22		t.Fatalf("repo.Init: %v", err)
 23	}
 24	t.Cleanup(func() { r.Close() })
 25	return r
 26}
 27
 28func writeFile(t *testing.T, r *repo.Repo, name, content string) string {
 29	t.Helper()
 30	abs := filepath.Join(r.Root, name)
 31	if err := os.MkdirAll(filepath.Dir(abs), 0o755); err != nil {
 32		t.Fatalf("MkdirAll: %v", err)
 33	}
 34	if err := os.WriteFile(abs, []byte(content), 0o644); err != nil {
 35		t.Fatalf("WriteFile %s: %v", name, err)
 36	}
 37	return name
 38}
 39
 40func TestInit_HeadSet(t *testing.T) {
 41	r := initTestRepo(t)
 42	head, err := r.Head()
 43	if err != nil {
 44		t.Fatalf("Head: %v", err)
 45	}
 46	if len(head) == 0 {
 47		t.Error("HEAD is empty after init")
 48	}
 49	if head[:3] != "ch:" {
 50		t.Errorf("HEAD should start with ch:, got %q", head)
 51	}
 52}
 53
 54func TestInit_InitialCommitExists(t *testing.T) {
 55	r := initTestRepo(t)
 56	c, id, err := r.HeadCommit()
 57	if err != nil {
 58		t.Fatalf("HeadCommit: %v", err)
 59	}
 60	if id == object.ZeroID {
 61		t.Error("initial commit has zero ID")
 62	}
 63	if c.Phase != object.PhaseDraft {
 64		t.Errorf("initial commit phase: got %v, want draft", c.Phase)
 65	}
 66}
 67
 68func TestInit_NotARepo(t *testing.T) {
 69	dir := t.TempDir()
 70	_, err := repo.Open(filepath.Join(dir, "nonexistent"))
 71	if err == nil {
 72		t.Error("expected error opening non-repo dir, got nil")
 73	}
 74}
 75
 76func TestSnap_EmptyRepo(t *testing.T) {
 77	r := initTestRepo(t)
 78	w := wc.New(r)
 79	_, id, err := w.Snap("first snap")
 80	if err != nil {
 81		t.Fatalf("Snap: %v", err)
 82	}
 83	if id == object.ZeroID {
 84		t.Error("snapped commit has zero ID")
 85	}
 86	head, err := r.Head()
 87	if err != nil {
 88		t.Fatalf("Head: %v", err)
 89	}
 90	if head[:3] != "ch:" {
 91		t.Errorf("HEAD after snap: %q", head)
 92	}
 93}
 94
 95func TestSnap_WithFiles(t *testing.T) {
 96	r := initTestRepo(t)
 97	writeFile(t, r, "hello.txt", "Hello, Arche!\n")
 98	writeFile(t, r, "src/main.go", "package main\n")
 99	w := wc.New(r)
100	snapped, snapID, err := w.Snap("add files")
101	if err != nil {
102		t.Fatalf("Snap: %v", err)
103	}
104	if snapped.Message != "add files" {
105		t.Errorf("Message: got %q, want %q", snapped.Message, "add files")
106	}
107	tree, err := r.ReadTree(snapped.TreeID)
108	if err != nil {
109		t.Fatalf("ReadTree: %v", err)
110	}
111	if len(tree.Entries) == 0 {
112		t.Error("snapped tree is empty")
113	}
114	_, snapID2, err := w.Snap("no change snap")
115	if err != nil {
116		t.Fatalf("second Snap: %v", err)
117	}
118	if snapID2 == snapID {
119		t.Error("two snaps should produce distinct draft commit IDs")
120	}
121}
122
123func TestSnap_FilesStoredInTree(t *testing.T) {
124	r := initTestRepo(t)
125	writeFile(t, r, "README.md", "# Project\n")
126	writeFile(t, r, "lib/util.go", "package lib\n")
127	w := wc.New(r)
128	snapped, _, err := w.Snap("files snap")
129	if err != nil {
130		t.Fatalf("Snap: %v", err)
131	}
132	tree, err := r.ReadTree(snapped.TreeID)
133	if err != nil {
134		t.Fatalf("ReadTree: %v", err)
135	}
136	names := map[string]bool{}
137	for _, e := range tree.Entries {
138		names[e.Name] = true
139	}
140	if !names["README.md"] && !names["lib"] {
141		t.Errorf("tree entries: %v", names)
142	}
143}
144
145func TestStatus_Added(t *testing.T) {
146	r := initTestRepo(t)
147	w := wc.New(r)
148	if _, _, err := w.Snap("base"); err != nil {
149		t.Fatalf("initial snap: %v", err)
150	}
151	writeFile(t, r, "new.txt", "new file")
152	changes, err := w.Status()
153	if err != nil {
154		t.Fatalf("Status: %v", err)
155	}
156	var found bool
157	for _, c := range changes {
158		if c.Path == "new.txt" && c.Status == 'A' {
159			found = true
160		}
161	}
162	if !found {
163		t.Errorf("expected new.txt as Added, got: %v", changes)
164	}
165}
166
167func TestStatus_Clean(t *testing.T) {
168	r := initTestRepo(t)
169	writeFile(t, r, "file.txt", "content")
170	w := wc.New(r)
171	if _, _, err := w.Snap("snap"); err != nil {
172		t.Fatalf("Snap: %v", err)
173	}
174	_, err := w.Status()
175	if err != nil {
176		t.Fatalf("Status: %v", err)
177	}
178}
179
180func TestBookmarks(t *testing.T) {
181	r := initTestRepo(t)
182	_, commitID, err := r.HeadCommit()
183	if err != nil {
184		t.Fatalf("HeadCommit: %v", err)
185	}
186	tx, err := r.Store.Begin()
187	if err != nil {
188		t.Fatalf("Begin: %v", err)
189	}
190	err = r.Store.SetBookmark(tx, store.Bookmark{Name: "main", CommitID: commitID})
191	if err != nil {
192		r.Store.Rollback(tx)
193		t.Fatalf("SetBookmark: %v", err)
194	}
195	if err := r.Store.Commit(tx); err != nil {
196		t.Fatalf("Commit: %v", err)
197	}
198	bm, err := r.Store.GetBookmark("main")
199	if err != nil {
200		t.Fatalf("GetBookmark: %v", err)
201	}
202	if bm == nil {
203		t.Fatal("bookmark not found")
204	}
205	if bm.CommitID != commitID {
206		t.Errorf("CommitID: got %x, want %x", bm.CommitID[:6], commitID[:6])
207	}
208	bms, err := r.Store.ListBookmarks()
209	if err != nil {
210		t.Fatalf("ListBookmarks: %v", err)
211	}
212	if len(bms) != 1 || bms[0].Name != "main" {
213		t.Errorf("unexpected bookmarks: %v", bms)
214	}
215	tx2, _ := r.Store.Begin()
216	r.Store.DeleteBookmark(tx2, "main")
217	r.Store.Commit(tx2)
218	bm2, _ := r.Store.GetBookmark("main")
219	if bm2 != nil {
220		t.Error("bookmark should be deleted")
221	}
222}
223
224func TestPhaseDefaultDraft(t *testing.T) {
225	r := initTestRepo(t)
226	_, commitID, err := r.HeadCommit()
227	if err != nil {
228		t.Fatalf("HeadCommit: %v", err)
229	}
230	phase, err := r.Store.GetPhase(commitID)
231	if err != nil {
232		t.Fatalf("GetPhase: %v", err)
233	}
234	if phase != object.PhaseDraft {
235		t.Errorf("default phase: got %v, want draft", phase)
236	}
237}
238
239func TestSetPhase(t *testing.T) {
240	r := initTestRepo(t)
241	_, commitID, err := r.HeadCommit()
242	if err != nil {
243		t.Fatalf("HeadCommit: %v", err)
244	}
245	tx, _ := r.Store.Begin()
246	if err := r.Store.SetPhase(tx, commitID, object.PhasePublic); err != nil {
247		r.Store.Rollback(tx)
248		t.Fatalf("SetPhase: %v", err)
249	}
250	r.Store.Commit(tx)
251	phase, err := r.Store.GetPhase(commitID)
252	if err != nil {
253		t.Fatalf("GetPhase: %v", err)
254	}
255	if phase != object.PhasePublic {
256		t.Errorf("phase: got %v, want public", phase)
257	}
258}
259
260func TestOperationLog(t *testing.T) {
261	r := initTestRepo(t)
262	ops, err := r.Store.ListOperations(10)
263	if err != nil {
264		t.Fatalf("ListOperations: %v", err)
265	}
266	if len(ops) == 0 {
267		t.Error("no operations after init")
268	}
269	writeFile(t, r, "a.txt", "a")
270	w := wc.New(r)
271	if _, _, err := w.Snap("snap"); err != nil {
272		t.Fatalf("Snap: %v", err)
273	}
274	ops2, err := r.Store.ListOperations(10)
275	if err != nil {
276		t.Fatalf("ListOperations: %v", err)
277	}
278	if len(ops2) <= len(ops) {
279		t.Errorf("snap should add operation; before=%d after=%d", len(ops), len(ops2))
280	}
281}
282
283func TestTreeDiff_NoChanges(t *testing.T) {
284	r := initTestRepo(t)
285	writeFile(t, r, "file.txt", "content\n")
286	w := wc.New(r)
287	snapped, _, err := w.Snap("snap")
288	if err != nil {
289		t.Fatalf("Snap: %v", err)
290	}
291	diffs, err := diff.TreeDiff(r, snapped.TreeID, snapped.TreeID)
292	if err != nil {
293		t.Fatalf("TreeDiff: %v", err)
294	}
295	if len(diffs) != 0 {
296		t.Errorf("same tree should produce no diffs, got %v", diffs)
297	}
298}
299
300func TestTreeDiff_AddedFile(t *testing.T) {
301	r := initTestRepo(t)
302	w := wc.New(r)
303	snap1, _, err := w.Snap("empty")
304	if err != nil {
305		t.Fatalf("first Snap: %v", err)
306	}
307	writeFile(t, r, "new.go", "package main\n")
308	snap2, _, err := w.Snap("add file")
309	if err != nil {
310		t.Fatalf("second Snap: %v", err)
311	}
312	diffs, err := diff.TreeDiff(r, snap1.TreeID, snap2.TreeID)
313	if err != nil {
314		t.Fatalf("TreeDiff: %v", err)
315	}
316	var found bool
317	for _, d := range diffs {
318		if d.Path == "new.go" && d.Status == 'A' {
319			found = true
320		}
321	}
322	if !found {
323		t.Errorf("expected new.go as Added, got: %v", diffs)
324	}
325}
326
327func TestCommitDiff_RootCommit(t *testing.T) {
328	r := initTestRepo(t)
329	writeFile(t, r, "main.go", "package main\n")
330	w := wc.New(r)
331	_, snapID, err := w.Snap("first commit")
332	if err != nil {
333		t.Fatalf("Snap: %v", err)
334	}
335	diffs, err := diff.CommitDiff(r, snapID)
336	if err != nil {
337		t.Fatalf("CommitDiff: %v", err)
338	}
339	for _, d := range diffs {
340		if d.Status != 'A' {
341			t.Errorf("root commit diff should only have Added files, got status=%c path=%s", d.Status, d.Path)
342		}
343	}
344}