Step 8: Polish — lint, clock abstraction, e2e test.
- golangci-lint config with errcheck, govet, staticcheck, errorlint - Fix all lint issues (unchecked error returns in cleanup paths, De Morgan) - Inject jonboulle/clockwork into Garden for deterministic timestamps - Add manifest.NewWithTime() for clock-aware initialization - E2e lifecycle test: init → add → checkpoint → modify → status → restore → verify - Update CLAUDE.md, PROJECT_PLAN.md, PROGRESS.md Phase 1 (local) is now complete. All 9 CLI commands implemented and tested. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
116
garden/e2e_test.go
Normal file
116
garden/e2e_test.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package garden
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jonboulle/clockwork"
|
||||
)
|
||||
|
||||
// TestE2E exercises the full lifecycle: init → add → checkpoint → modify →
|
||||
// status → restore → verify.
|
||||
func TestE2E(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
repoDir := filepath.Join(root, "repo")
|
||||
|
||||
// Use a fake clock so timestamps are deterministic.
|
||||
fakeClock := clockwork.NewFakeClockAt(time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
|
||||
// 1. Init
|
||||
g, err := Init(repoDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Init: %v", err)
|
||||
}
|
||||
g.SetClock(fakeClock)
|
||||
|
||||
// 2. Create and add files.
|
||||
bashrc := filepath.Join(root, "bashrc")
|
||||
gitconfig := filepath.Join(root, "gitconfig")
|
||||
if err := os.WriteFile(bashrc, []byte("export PS1='$ '\n"), 0o644); err != nil {
|
||||
t.Fatalf("writing bashrc: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(gitconfig, []byte("[user]\n\tname = test\n"), 0o644); err != nil {
|
||||
t.Fatalf("writing gitconfig: %v", err)
|
||||
}
|
||||
|
||||
if err := g.Add([]string{bashrc, gitconfig}); err != nil {
|
||||
t.Fatalf("Add: %v", err)
|
||||
}
|
||||
|
||||
if len(g.manifest.Files) != 2 {
|
||||
t.Fatalf("expected 2 entries, got %d", len(g.manifest.Files))
|
||||
}
|
||||
|
||||
// 3. Checkpoint.
|
||||
fakeClock.Advance(time.Hour)
|
||||
if err := g.Checkpoint("initial checkpoint"); err != nil {
|
||||
t.Fatalf("Checkpoint: %v", err)
|
||||
}
|
||||
|
||||
if g.manifest.Message != "initial checkpoint" {
|
||||
t.Errorf("expected message 'initial checkpoint', got %q", g.manifest.Message)
|
||||
}
|
||||
|
||||
// 4. Modify a file.
|
||||
if err := os.WriteFile(bashrc, []byte("export PS1='> '\n"), 0o644); err != nil {
|
||||
t.Fatalf("modifying bashrc: %v", err)
|
||||
}
|
||||
|
||||
// 5. Status — should detect modification.
|
||||
statuses, err := g.Status()
|
||||
if err != nil {
|
||||
t.Fatalf("Status: %v", err)
|
||||
}
|
||||
|
||||
stateMap := make(map[string]string)
|
||||
for _, s := range statuses {
|
||||
stateMap[s.Path] = s.State
|
||||
}
|
||||
|
||||
bashrcPath := toTildePath(bashrc)
|
||||
gitconfigPath := toTildePath(gitconfig)
|
||||
|
||||
if stateMap[bashrcPath] != "modified" {
|
||||
t.Errorf("bashrc should be modified, got %s", stateMap[bashrcPath])
|
||||
}
|
||||
if stateMap[gitconfigPath] != "ok" {
|
||||
t.Errorf("gitconfig should be ok, got %s", stateMap[gitconfigPath])
|
||||
}
|
||||
|
||||
// 6. Restore — force to overwrite modified file.
|
||||
if err := g.Restore([]string{bashrc}, true, nil); err != nil {
|
||||
t.Fatalf("Restore: %v", err)
|
||||
}
|
||||
|
||||
got, err := os.ReadFile(bashrc)
|
||||
if err != nil {
|
||||
t.Fatalf("reading restored bashrc: %v", err)
|
||||
}
|
||||
if string(got) != "export PS1='$ '\n" {
|
||||
t.Errorf("bashrc not restored correctly, got %q", got)
|
||||
}
|
||||
|
||||
// 7. Status after restore — should be ok.
|
||||
statuses, err = g.Status()
|
||||
if err != nil {
|
||||
t.Fatalf("Status after restore: %v", err)
|
||||
}
|
||||
for _, s := range statuses {
|
||||
if s.State != "ok" {
|
||||
t.Errorf("after restore, %s should be ok, got %s", s.Path, s.State)
|
||||
}
|
||||
}
|
||||
|
||||
// 8. Verify — all blobs should be intact.
|
||||
results, err := g.Verify()
|
||||
if err != nil {
|
||||
t.Fatalf("Verify: %v", err)
|
||||
}
|
||||
for _, r := range results {
|
||||
if !r.OK {
|
||||
t.Errorf("verify failed for %s: %s", r.Path, r.Detail)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user