1package snapshot
2
3import (
4 "context"
5 "crypto/sha256"
6 "os"
7 "path/filepath"
8 "testing"
9
10 "github.com/bnema/vev/internal/domain"
11)
12
13func TestRepositoryPublishAcceptsGenerationOneWithoutParent(t *testing.T) {
14 repo := NewRepository(privateDir(t))
15 publication := repositoryPublication(t, "named", 1, []byte("one"))
16
17 if err := repo.Publish(context.Background(), publication); err != nil {
18 t.Fatalf("Publish generation 1 without parent: %v", err)
19 }
20}
21
22func TestRepositoryPublishRejectsInvalidLaterParentWithoutMutation(t *testing.T) {
23 tests := []struct {
24 name string
25 parent func(domain.CheckpointRef) *domain.CheckpointRef
26 }{
27 {name: "nil", parent: func(domain.CheckpointRef) *domain.CheckpointRef { return nil }},
28 {name: "stale generation", parent: func(current domain.CheckpointRef) *domain.CheckpointRef {
29 current.Generation--
30 return ¤t
31 }},
32 {name: "mismatched digest", parent: func(current domain.CheckpointRef) *domain.CheckpointRef {
33 current.ManifestDigest[0]++
34 return ¤t
35 }},
36 }
37
38 for _, test := range tests {
39 t.Run(test.name, func(t *testing.T) {
40 repo := NewRepository(privateDir(t))
41 first := repositoryPublication(t, "named", 1, []byte("one"))
42 if err := repo.Publish(context.Background(), first); err != nil {
43 t.Fatalf("Publish generation 1: %v", err)
44 }
45 firstRef := domain.CheckpointRef{Generation: 1, ManifestDigest: sha256.Sum256(first.Manifest)}
46 second := incarnationPublication(t, first.IncarnationID, first.Name, 2, &firstRef)
47 if err := repo.Publish(context.Background(), second); err != nil {
48 t.Fatalf("Publish generation 2: %v", err)
49 }
50 current := domain.CheckpointRef{Generation: 2, ManifestDigest: sha256.Sum256(second.Manifest)}
51 third := incarnationPublication(t, first.IncarnationID, first.Name, 3, test.parent(current))
52 before := snapshotRepositoryFiles(t, repo.dir)
53
54 if err := repo.Publish(context.Background(), third); err == nil {
55 t.Fatal("Publish accepted invalid parent")
56 }
57 after := snapshotRepositoryFiles(t, repo.dir)
58 if len(after) != len(before) {
59 t.Fatalf("repository file count changed: before=%d after=%d", len(before), len(after))
60 }
61 for path, data := range before {
62 if string(after[path]) != string(data) {
63 t.Fatalf("repository file %q mutated", path)
64 }
65 }
66 })
67 }
68}
69
70func TestRepositoryPublishAcceptsExactCurrentParent(t *testing.T) {
71 repo := NewRepository(privateDir(t))
72 first := repositoryPublication(t, "named", 1, []byte("one"))
73 if err := repo.Publish(context.Background(), first); err != nil {
74 t.Fatalf("Publish generation 1: %v", err)
75 }
76 parent := &domain.CheckpointRef{Generation: 1, ManifestDigest: sha256.Sum256(first.Manifest)}
77 second := incarnationPublication(t, first.IncarnationID, first.Name, 2, parent)
78
79 if err := repo.Publish(context.Background(), second); err != nil {
80 t.Fatalf("Publish generation 2 with exact parent: %v", err)
81 }
82}
83
84func snapshotRepositoryFiles(t *testing.T, root string) map[string][]byte {
85 t.Helper()
86 files := make(map[string][]byte)
87 err := filepath.WalkDir(root, func(path string, entry os.DirEntry, err error) error {
88 if err != nil {
89 return err
90 }
91 if entry.IsDir() {
92 return nil
93 }
94 data, err := os.ReadFile(path)
95 if err != nil {
96 return err
97 }
98 relative, err := filepath.Rel(root, path)
99 if err != nil {
100 return err
101 }
102 files[relative] = data
103 return nil
104 })
105 if err != nil {
106 t.Fatalf("snapshot repository files: %v", err)
107 }
108 return files
109}