1package snapshot
2
3import (
4 "context"
5 "crypto/sha256"
6 "testing"
7
8 "github.com/bnema/vev/internal/domain"
9
10 "github.com/bnema/vev/internal/ports"
11 codec "github.com/bnema/vev/internal/usecase/snapshot"
12)
13
14func TestRepositoryPublishesAndLoadsCompleteGeneration(t *testing.T) {
15 t.Parallel()
16 dir := privateDir(t)
17 repo := NewRepository(dir)
18 pub := repositoryPublication(t, "named", 1, []byte("state"))
19 if err := repo.Publish(context.Background(), pub); err != nil {
20 t.Fatalf("Publish: %v", err)
21 }
22 got, err := loadPublication(context.Background(), repo, pub)
23 if err != nil {
24 t.Fatalf("Load: %v", err)
25 }
26 if got.Name != "named" || got.Generation != 1 || len(got.Objects) != 2 {
27 t.Fatalf("Load = %#v", got)
28 }
29}
30
31func loadPublication(ctx context.Context, repo *Repository, publication ports.SnapshotPublication) (ports.SnapshotGeneration, error) {
32 return repo.LoadCheckpoint(ctx, publication.IncarnationID, publication.Name, ports.CheckpointRef{
33 Generation: publication.Generation,
34 ManifestDigest: codec.ManifestDigest(publication.Manifest),
35 })
36}
37
38func repositoryPublicationAfter(t *testing.T, repo *Repository, name string, generation uint64, payload []byte) ports.SnapshotPublication {
39 t.Helper()
40 return publicationWithCurrentParent(t, repo, repositoryPublication(t, name, generation, payload))
41}
42
43// publicationWithCurrentParent requires repositories to contain HEAD for every
44// publication after generation one. Callers such as prepareHeadStage and
45// seedCompletePublication must seed or publish the parent generation first.
46func publicationWithCurrentParent(t *testing.T, repo *Repository, publication ports.SnapshotPublication) ports.SnapshotPublication {
47 t.Helper()
48 if publication.Generation == 1 {
49 return publication
50 }
51 currentGeneration, digest, err := repo.readHead(publication.IncarnationID)
52 if err != nil {
53 t.Fatalf("read parent HEAD: %v", err)
54 }
55 parent := &domain.CheckpointRef{Generation: currentGeneration, ManifestDigest: digest}
56 manifest, err := codec.UnmarshalManifest(publication.Manifest)
57 if err != nil {
58 t.Fatal(err)
59 }
60 manifest.ParentCheckpoint = parent
61 publication.Manifest, err = codec.MarshalManifest(manifest)
62 if err != nil {
63 t.Fatal(err)
64 }
65 publication.ParentCheckpoint = parent
66 return publication
67}
68
69func repositoryPublication(t *testing.T, name string, generation uint64, payload []byte) ports.SnapshotPublication {
70 t.Helper()
71 id := testIncarnationID(name)
72 tail, err := codec.MarshalObject(codec.HistoryTail, canonicalHistoryBlob(t, string(payload)))
73 if err != nil {
74 t.Fatal(err)
75 }
76 transcript, err := codec.MarshalObject(codec.RecoveryTranscript, canonicalHistoryBlob(t, "transcript"))
77 if err != nil {
78 t.Fatal(err)
79 }
80 manifest, err := codec.MarshalManifest(codec.Manifest{Generation: generation, IncarnationID: id, Name: name, Tabs: []codec.ManifestTab{{Cols: 1, Rows: 1, Panes: []codec.ManifestPane{{ID: "p", Tail: codec.ObjectRef{Kind: codec.HistoryTail, Digest: tail.Digest, Size: uint32(len(tail.Data))}, Transcript: codec.ObjectRef{Kind: codec.RecoveryTranscript, Digest: transcript.Digest, Size: uint32(len(transcript.Data))}}}}}})
81 if err != nil {
82 t.Fatal(err)
83 }
84 return ports.SnapshotPublication{IncarnationID: id, Name: name, Generation: generation, Manifest: manifest, Objects: []ports.SnapshotObject{tail, transcript}}
85}
86
87func testIncarnationID(name string) domain.IncarnationID {
88 digest := sha256.Sum256([]byte("test incarnation: " + name))
89 var id domain.IncarnationID
90 copy(id[:], digest[:])
91 return id
92}