arche / internal/store/store_test.go

commit 154431fd
  1package store_test
  2
  3import (
  4	"bytes"
  5	"path/filepath"
  6	"testing"
  7
  8	"arche/internal/object"
  9	"arche/internal/store"
 10
 11	_ "github.com/mattn/go-sqlite3"
 12)
 13
 14func openTestStore(t *testing.T) *store.SQLiteStore {
 15	t.Helper()
 16	dir := t.TempDir()
 17	packDir := filepath.Join(dir, "packs")
 18	s, err := store.OpenSQLiteStore(filepath.Join(dir, "store.db"), packDir, 0, 0, "zstd")
 19	if err != nil {
 20		t.Fatalf("OpenSQLiteStore: %v", err)
 21	}
 22	t.Cleanup(func() { s.Close() })
 23	return s
 24}
 25
 26func TestStore_WriteReadObject(t *testing.T) {
 27	s := openTestStore(t)
 28
 29	data := []byte("hello world")
 30	id := object.HashBlob(&object.Blob{Content: data})
 31	var buf bytes.Buffer
 32	object.EncodeBlob(&buf, &object.Blob{Content: data})
 33	raw := buf.Bytes()
 34
 35	tx, err := s.Begin()
 36	if err != nil {
 37		t.Fatalf("Begin: %v", err)
 38	}
 39	if err := s.WriteObject(tx, id, "blob", raw); err != nil {
 40		t.Fatalf("WriteObject: %v", err)
 41	}
 42	if err := s.Commit(tx); err != nil {
 43		t.Fatalf("Commit: %v", err)
 44	}
 45
 46	kind, got, err := s.ReadObject(id)
 47	if err != nil {
 48		t.Fatalf("ReadObject: %v", err)
 49	}
 50	if kind != "blob" {
 51		t.Errorf("kind: want blob got %q", kind)
 52	}
 53	if string(got) != string(raw) {
 54		t.Errorf("data mismatch")
 55	}
 56}
 57
 58func TestStore_HasObject(t *testing.T) {
 59	s := openTestStore(t)
 60
 61	data := []byte("content")
 62	id := object.HashBlob(&object.Blob{Content: data})
 63	var rawBuf bytes.Buffer
 64	object.EncodeBlob(&rawBuf, &object.Blob{Content: data})
 65	raw := rawBuf.Bytes()
 66
 67	ok, err := s.HasObject(id)
 68	if err != nil || ok {
 69		t.Errorf("should not exist before write; has=%v err=%v", ok, err)
 70	}
 71
 72	tx, _ := s.Begin()
 73	s.WriteObject(tx, id, "blob", raw) //nolint:errcheck
 74	s.Commit(tx)                       //nolint:errcheck
 75
 76	ok, err = s.HasObject(id)
 77	if err != nil || !ok {
 78		t.Errorf("should exist after write; has=%v err=%v", ok, err)
 79	}
 80}
 81
 82func TestStore_WriteObject_Idempotent(t *testing.T) {
 83	s := openTestStore(t)
 84	var rawBuf2 bytes.Buffer
 85	object.EncodeBlob(&rawBuf2, &object.Blob{Content: []byte("dup")})
 86	raw := rawBuf2.Bytes()
 87	id := object.HashBlob(&object.Blob{Content: []byte("dup")})
 88
 89	for range 3 {
 90		tx, _ := s.Begin()
 91		if err := s.WriteObject(tx, id, "blob", raw); err != nil {
 92			t.Fatalf("WriteObject iteration: %v", err)
 93		}
 94		s.Commit(tx) //nolint:errcheck
 95	}
 96
 97	_, got, err := s.ReadObject(id)
 98	if err != nil {
 99		t.Fatalf("ReadObject: %v", err)
100	}
101	if !bytes.Equal(got, raw) {
102		t.Error("data corrupted after duplicate writes")
103	}
104}
105
106func TestStore_Bookmarks(t *testing.T) {
107	s := openTestStore(t)
108
109	var bmBuf bytes.Buffer
110	object.EncodeBlob(&bmBuf, &object.Blob{Content: []byte("bookmark-obj")})
111	id := object.HashBlob(&object.Blob{Content: []byte("bookmark-obj")})
112	txObj, _ := s.Begin()
113	s.WriteObject(txObj, id, "blob", bmBuf.Bytes()) //nolint:errcheck
114	s.Commit(txObj)                                 //nolint:errcheck
115
116	tx, _ := s.Begin()
117	err := s.SetBookmark(tx, store.Bookmark{Name: "main", CommitID: id})
118	if err != nil {
119		t.Fatalf("SetBookmark: %v", err)
120	}
121	s.Commit(tx) //nolint:errcheck
122
123	got, err := s.GetBookmark("main")
124	if err != nil || got == nil {
125		t.Fatalf("GetBookmark: %v", err)
126	}
127	if got.CommitID != id {
128		t.Errorf("commit ID mismatch")
129	}
130
131	bms, err := s.ListBookmarks()
132	if err != nil || len(bms) != 1 {
133		t.Errorf("ListBookmarks: want 1, got %d err=%v", len(bms), err)
134	}
135
136	tx2, _ := s.Begin()
137	s.DeleteBookmark(tx2, "main") //nolint:errcheck
138	s.Commit(tx2)                 //nolint:errcheck
139
140	bms, _ = s.ListBookmarks()
141	if len(bms) != 0 {
142		t.Errorf("after delete want 0 bookmarks, got %d", len(bms))
143	}
144}
145
146func TestStore_Phase(t *testing.T) {
147	s := openTestStore(t)
148
149	var id [32]byte
150	id[0] = 0x01
151
152	var phaseBuf bytes.Buffer
153	object.EncodeBlob(&phaseBuf, &object.Blob{Content: []byte("x")})
154	tx, _ := s.Begin()
155	s.WriteObject(tx, id, "blob", phaseBuf.Bytes()) //nolint:errcheck
156	s.Commit(tx)                                    //nolint:errcheck
157
158	phase, _ := s.GetPhase(id)
159	_ = phase
160
161	tx2, _ := s.Begin()
162	if err := s.SetPhase(tx2, id, object.PhasePublic); err != nil {
163		t.Fatalf("SetPhase: %v", err)
164	}
165	s.Commit(tx2) //nolint:errcheck
166
167	phase, err := s.GetPhase(id)
168	if err != nil {
169		t.Fatalf("GetPhase: %v", err)
170	}
171	if phase != object.PhasePublic {
172		t.Errorf("phase: want Public got %v", phase)
173	}
174}
175
176func TestStore_OperationLog(t *testing.T) {
177	s := openTestStore(t)
178
179	tx, _ := s.Begin()
180	_, err := s.InsertOperation(tx, store.Operation{
181		Kind:      "snap",
182		Timestamp: 1000,
183		Before:    `{"head":"old"}`,
184		After:     `{"head":"new"}`,
185	})
186	if err != nil {
187		t.Fatalf("InsertOperation: %v", err)
188	}
189	s.Commit(tx) //nolint:errcheck
190
191	ops, err := s.ListOperations(10)
192	if err != nil || len(ops) == 0 {
193		t.Errorf("ListOperations: want >=1, got %d err=%v", len(ops), err)
194	}
195
196	last, err := s.GetLastOperation()
197	if err != nil || last == nil {
198		t.Fatalf("GetLastOperation: %v", err)
199	}
200	if last.Kind != "snap" {
201		t.Errorf("last op kind: want snap got %q", last.Kind)
202	}
203}
204
205func TestStore_WCache(t *testing.T) {
206	s := openTestStore(t)
207
208	e := store.WCacheEntry{
209		Path:    "src/main.go",
210		Inode:   42,
211		MtimeNs: 1234567890,
212		Size:    512,
213	}
214
215	tx, _ := s.Begin()
216	if err := s.SetWCacheEntry(tx, e); err != nil {
217		t.Fatalf("SetWCacheEntry: %v", err)
218	}
219	s.Commit(tx) //nolint:errcheck
220
221	got, err := s.GetWCacheEntry("src/main.go")
222	if err != nil || got == nil {
223		t.Fatalf("GetWCacheEntry: %v", err)
224	}
225	if got.Inode != 42 || got.Size != 512 || got.MtimeNs != 1234567890 {
226		t.Errorf("entry mismatch: %+v", got)
227	}
228
229	entries, err := s.ListWCacheEntries()
230	if err != nil || len(entries) != 1 {
231		t.Errorf("ListWCacheEntries: want 1, got %d err=%v", len(entries), err)
232	}
233
234	tx2, _ := s.Begin()
235	s.DeleteWCacheEntry(tx2, "src/main.go") //nolint:errcheck
236	s.Commit(tx2)                           //nolint:errcheck
237
238	entries, _ = s.ListWCacheEntries()
239	if len(entries) != 0 {
240		t.Errorf("after delete want 0 entries, got %d", len(entries))
241	}
242}
243
244func TestStore_ChangeCommit(t *testing.T) {
245	s := openTestStore(t)
246
247	var ccBuf bytes.Buffer
248	object.EncodeBlob(&ccBuf, &object.Blob{Content: []byte("change-obj")})
249	commitID := object.HashBlob(&object.Blob{Content: []byte("change-obj")})
250	txObj, _ := s.Begin()
251	s.WriteObject(txObj, commitID, "blob", ccBuf.Bytes()) //nolint:errcheck
252	s.Commit(txObj)                                       //nolint:errcheck
253
254	tx, _ := s.Begin()
255	changeID, err := s.AllocChangeID(tx)
256	if err != nil {
257		t.Fatalf("AllocChangeID: %v", err)
258	}
259	if err := s.SetChangeCommit(tx, changeID, commitID); err != nil {
260		t.Fatalf("SetChangeCommit: %v", err)
261	}
262	s.Commit(tx) //nolint:errcheck
263
264	got, err := s.GetChangeCommit(changeID)
265	if err != nil {
266		t.Fatalf("GetChangeCommit: %v", err)
267	}
268	if got != commitID {
269		t.Errorf("commit ID mismatch")
270	}
271}