1package wc_test
2
3import (
4 "os"
5 "path/filepath"
6 "testing"
7
8 "arche/internal/repo"
9 "arche/internal/wc"
10
11 _ "github.com/mattn/go-sqlite3"
12)
13
14func TestMaterialize_RestoresFiles(t *testing.T) {
15 r := initRepo(t)
16 w := wc.New(r)
17
18 writeFile(t, r, "keep.txt", "original")
19 writeFile(t, r, "also.txt", "also original")
20
21 snapped, _, err := w.Snap("baseline")
22 if err != nil {
23 t.Fatalf("Snap: %v", err)
24 }
25
26 baselineTree := snapped.TreeID
27 headCID, err := r.HeadChangeID()
28 if err != nil {
29 t.Fatalf("HeadChangeID: %v", err)
30 }
31
32 writeFile(t, r, "keep.txt", "MODIFIED")
33 writeFile(t, r, "extra.txt", "should vanish")
34
35 if err := w.Materialize(baselineTree, headCID); err != nil {
36 t.Fatalf("Materialize: %v", err)
37 }
38
39 got, exists := readFile(t, r, "keep.txt")
40 if !exists || got != "original" {
41 t.Errorf("keep.txt: want %q got (%q, exists=%v)", "original", got, exists)
42 }
43
44 got2, exists2 := readFile(t, r, "also.txt")
45 if !exists2 || got2 != "also original" {
46 t.Errorf("also.txt: want %q got (%q, exists=%v)", "also original", got2, exists2)
47 }
48
49 _, extraExists := readFile(t, r, "extra.txt")
50 if extraExists {
51 t.Error("extra.txt should have been removed by Materialize")
52 }
53}
54
55func TestMaterialize_AddsNewFiles(t *testing.T) {
56 r := initRepo(t)
57 w := wc.New(r)
58
59 writeFile(t, r, "new.txt", "new content")
60 snapped, _, err := w.Snap("add new.txt")
61 if err != nil {
62 t.Fatalf("Snap: %v", err)
63 }
64
65 targetTree := snapped.TreeID
66 headCID, _ := r.HeadChangeID()
67
68 if err := os.Remove(filepath.Join(r.Root, "new.txt")); err != nil {
69 t.Fatalf("Remove: %v", err)
70 }
71
72 _, existsBefore := readFile(t, r, "new.txt")
73 if existsBefore {
74 t.Fatal("pre-condition: new.txt should not exist")
75 }
76
77 if err := w.Materialize(targetTree, headCID); err != nil {
78 t.Fatalf("Materialize: %v", err)
79 }
80
81 got, exists := readFile(t, r, "new.txt")
82 if !exists || got != "new content" {
83 t.Errorf("new.txt: want %q got (%q, exists=%v)", "new content", got, exists)
84 }
85}
86
87func TestMaterialize_RecordsOperation(t *testing.T) {
88 r := initRepo(t)
89 w := wc.New(r)
90
91 writeFile(t, r, "f.txt", "data")
92 snapped, _, err := w.Snap("snap")
93 if err != nil {
94 t.Fatalf("Snap: %v", err)
95 }
96
97 headCID, _ := r.HeadChangeID()
98 opsBefore, _ := r.Store.ListOperations(50)
99
100 if err := w.Materialize(snapped.TreeID, headCID); err != nil {
101 t.Fatalf("Materialize: %v", err)
102 }
103
104 opsAfter, _ := r.Store.ListOperations(50)
105 if len(opsAfter) <= len(opsBefore) {
106 t.Errorf("expected a new operation after Materialize; before=%d after=%d",
107 len(opsBefore), len(opsAfter))
108 }
109
110 last, _ := r.Store.GetLastOperation()
111 if last == nil || last.Kind != "co" {
112 t.Errorf("last operation kind: want co, got %v", last)
113 }
114}
115
116func TestStatus_Modified(t *testing.T) {
117 r := initRepo(t)
118 w := wc.New(r)
119
120 writeFile(t, r, "mod.txt", "before")
121
122 if _, _, err := w.Snap("base"); err != nil {
123 t.Fatalf("Snap: %v", err)
124 }
125
126 writeFile(t, r, "mod.txt", "after")
127
128 statuses, err := w.Status()
129 if err != nil {
130 t.Fatalf("Status: %v", err)
131 }
132
133 var found bool
134 for _, s := range statuses {
135 if s.Path == "mod.txt" && s.Status == 'M' {
136 found = true
137 }
138 }
139 if !found {
140 t.Errorf("mod.txt should be Modified, got statuses: %v", statuses)
141 }
142}
143
144func TestStatus_Deleted(t *testing.T) {
145 r := initRepo(t)
146 w := wc.New(r)
147
148 writeFile(t, r, "del.txt", "bye")
149
150 if _, _, err := w.Snap("base"); err != nil {
151 t.Fatalf("Snap: %v", err)
152 }
153
154 if err := os.Remove(filepath.Join(r.Root, "del.txt")); err != nil {
155 t.Fatalf("Remove: %v", err)
156 }
157
158 statuses, err := w.Status()
159 if err != nil {
160 t.Fatalf("Status: %v", err)
161 }
162
163 var found bool
164 for _, s := range statuses {
165 if s.Path == "del.txt" && s.Status == 'D' {
166 found = true
167 }
168 }
169 if !found {
170 t.Errorf("del.txt should be Deleted, got statuses: %v", statuses)
171 }
172}
173
174func initRepo(t *testing.T) *repo.Repo {
175 t.Helper()
176 dir := t.TempDir()
177 r, err := repo.Init(dir)
178 if err != nil {
179 t.Fatalf("repo.Init: %v", err)
180 }
181 t.Cleanup(func() { r.Close() })
182 return r
183}
184
185func writeFile(t *testing.T, r *repo.Repo, name, content string) {
186 t.Helper()
187 abs := filepath.Join(r.Root, name)
188 if err := os.MkdirAll(filepath.Dir(abs), 0o755); err != nil {
189 t.Fatalf("MkdirAll: %v", err)
190 }
191 if err := os.WriteFile(abs, []byte(content), 0o644); err != nil {
192 t.Fatalf("WriteFile %s: %v", name, err)
193 }
194}
195
196func readFile(t *testing.T, r *repo.Repo, name string) (string, bool) {
197 t.Helper()
198 data, err := os.ReadFile(filepath.Join(r.Root, name))
199 if os.IsNotExist(err) {
200 return "", false
201 }
202 if err != nil {
203 t.Fatalf("readFile %s: %v", name, err)
204 }
205 return string(data), true
206}