carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package release
2
3import (
4 "context"
5 "encoding/json"
6 "errors"
7 "os"
8 "strings"
9 "testing"
10 "time"
11)
12
13func TestTagWriteGetList(t *testing.T) {
14 s := NewDirStore(t.TempDir())
15 ctx := context.Background()
16 if _, err := GetTag(ctx, s, "acct1", "km", "0.5.0"); !errors.Is(err, os.ErrNotExist) {
17 t.Fatalf("missing tag: got %v; want os.ErrNotExist", err)
18 }
19 tags := []Tag{
20 {Schema: 1, Version: "0.5.0", Release: "0.5.0-3", TaggedAt: time.Now().UTC(), Actor: "paul@example.com", Changelog: "## 0.5.0-1 — fixing modals\n"},
21 {Schema: 1, Version: "0.10.0", Release: "0.10.0-1", TaggedAt: time.Now().UTC()},
22 {Schema: 1, Version: "0.6.0", Release: "0.6.0-2", TaggedAt: time.Now().UTC()},
23 }
24 for i := range tags {
25 if err := writeTag(ctx, s, "acct1", "km", &tags[i]); err != nil {
26 t.Fatal(err)
27 }
28 }
29 got, err := GetTag(ctx, s, "acct1", "km", "0.5.0")
30 if err != nil {
31 t.Fatal(err)
32 }
33 if got.Release != "0.5.0-3" || got.Changelog == "" {
34 t.Fatalf("round trip lost fields: %+v", got)
35 }
36 list, err := ListTags(ctx, s, "acct1", "km")
37 if err != nil {
38 t.Fatal(err)
39 }
40 // Semver-descending, which is NOT string order (0.10.0 > 0.6.0).
41 if len(list) != 3 || list[0].Version != "0.10.0" || list[1].Version != "0.6.0" || list[2].Version != "0.5.0" {
42 t.Fatalf("wrong order: %+v", list)
43 }
44}
45
46func TestTagImmutable(t *testing.T) {
47 s := NewDirStore(t.TempDir())
48 ctx := context.Background()
49 a := Tag{Schema: 1, Version: "0.5.0", Release: "0.5.0-3", TaggedAt: time.Unix(100, 0).UTC()}
50 if err := writeTag(ctx, s, "acct1", "km", &a); err != nil {
51 t.Fatal(err)
52 }
53 // Same bytes: idempotent. Different bytes: refused.
54 if err := writeTag(ctx, s, "acct1", "km", &a); err != nil {
55 t.Fatalf("identical rewrite refused: %v", err)
56 }
57 b := a
58 b.Release = "0.5.0-4"
59 if err := writeTag(ctx, s, "acct1", "km", &b); err == nil {
60 t.Fatal("conflicting rewrite accepted")
61 }
62}
63
64func TestTagListErrors(t *testing.T) {
65 s := NewDirStore(t.TempDir())
66 ctx := context.Background()
67 // Write a valid tag and a garbage tag (malformed JSON).
68 good := Tag{Schema: 1, Version: "0.5.0", Release: "0.5.0-1", TaggedAt: time.Now().UTC()}
69 if err := writeTag(ctx, s, "acct1", "km", &good); err != nil {
70 t.Fatal(err)
71 }
72 // Write garbage bytes directly to a valid tag key.
73 if _, err := s.Put(ctx, TagKey("acct1", "km", "0.7.0"), []byte("not json"), ""); err != nil {
74 t.Fatal(err)
75 }
76 // ListTags should return an error naming the key, not silently skip it.
77 _, err := ListTags(ctx, s, "acct1", "km")
78 if err == nil {
79 t.Fatal("ListTags accepted malformed JSON")
80 }
81 if !strings.Contains(err.Error(), "0.7.0") {
82 t.Fatalf("error does not name the key: %v", err)
83 }
84}
85
86func shipIteration(t *testing.T, s Store, ver, label, notes string) {
87 t.Helper()
88 m := Manifest{Schema: 1, App: "km", Version: ver, Kind: "binary",
89 Commit: "c-" + ver, Label: label, Notes: notes,
90 Artifacts: map[string]Artifact{"linux-arm64": {File: "linux-arm64", SHA256: "0000000000000000000000000000000000000000000000000000000000000000", Size: 1}}}
91 raw, err := json.Marshal(m)
92 if err != nil {
93 t.Fatal(err)
94 }
95 if err := putImmutable(context.Background(), s, ManifestKey("acct1", "km", ver), raw); err != nil {
96 t.Fatal(err)
97 }
98}
99
100func TestEnsureTagCompilesChangelog(t *testing.T) {
101 s := NewDirStore(t.TempDir())
102 ctx := context.Background()
103 now := time.Unix(2000, 0).UTC()
104 shipIteration(t, s, "0.5.0-1", "fixing modals", "- focus trap fixed")
105 shipIteration(t, s, "0.5.0-2", "", "")
106 shipIteration(t, s, "0.5.0-3", "polish", "- rounded corners")
107 tag, created, err := EnsureTag(ctx, s, "acct1", "km", "0.5.0-3", "paul@example.com", now)
108 if err != nil || !created || tag == nil {
109 t.Fatalf("EnsureTag: %+v, %v, %v", tag, created, err)
110 }
111 if tag.Version != "0.5.0" || tag.Release != "0.5.0-3" || tag.Commit != "c-0.5.0-3" || tag.Actor != "paul@example.com" {
112 t.Fatalf("tag fields: %+v", tag)
113 }
114 want := "## 0.5.0-1 — fixing modals\n\n- focus trap fixed\n\n## 0.5.0-2\n\n## 0.5.0-3 — polish\n\n- rounded corners\n"
115 if tag.Changelog != want {
116 t.Fatalf("changelog:\n%q\nwant:\n%q", tag.Changelog, want)
117 }
118 // Auto-advance: the target moved to the next patch.
119 vt, _, err := GetTarget(ctx, s, "acct1", "km")
120 if err != nil || vt == nil || vt.Target != "0.5.1" {
121 t.Fatalf("target after cut: %+v, %v; want 0.5.1", vt, err)
122 }
123 // Idempotent: a second call finds the tag, creates nothing.
124 tag2, created2, err := EnsureTag(ctx, s, "acct1", "km", "0.5.0-3", "someone@else.com", now.Add(time.Hour))
125 if err != nil || created2 || tag2.Actor != "paul@example.com" {
126 t.Fatalf("repeat: %+v, %v, %v", tag2, created2, err)
127 }
128}
129
130func TestEnsureTagSkipsNonIterations(t *testing.T) {
131 s := NewDirStore(t.TempDir())
132 tag, created, err := EnsureTag(context.Background(), s, "acct1", "km", "de2766f8", "x", time.Unix(0, 0).UTC())
133 if tag != nil || created || err != nil {
134 t.Fatalf("sha version: %+v, %v, %v; want nil, false, nil", tag, created, err)
135 }
136}
137
138func TestEnsureTagDoesNotLowerRaisedTarget(t *testing.T) {
139 s := NewDirStore(t.TempDir())
140 ctx := context.Background()
141 now := time.Unix(2000, 0).UTC()
142 shipIteration(t, s, "0.5.0-1", "", "")
143 // Operator already aimed at 0.6.0; cutting 0.5.0 must not drag it back.
144 if err := SetTarget(ctx, s, "acct1", "km", "0.6.0", "paul@example.com", now); err != nil {
145 t.Fatal(err)
146 }
147 if _, _, err := EnsureTag(ctx, s, "acct1", "km", "0.5.0-1", "x", now); err != nil {
148 t.Fatal(err)
149 }
150 vt, _, _ := GetTarget(ctx, s, "acct1", "km")
151 if vt == nil || vt.Target != "0.6.0" {
152 t.Fatalf("target was moved: %+v; want 0.6.0 kept", vt)
153 }
154}
155
156func TestEnsureTagBlockedByHigherTag(t *testing.T) {
157 s := NewDirStore(t.TempDir())
158 ctx := context.Background()
159 now := time.Unix(2000, 0).UTC()
160 shipIteration(t, s, "0.5.0-1", "first", "- first iteration")
161 // Operator or a fleet already past has a 0.6.0 tag; next patch would collide.
162 higherTag := &Tag{Schema: 1, Version: "0.6.0", Release: "0.6.0-1", TaggedAt: now, Actor: "admin"}
163 if err := writeTag(ctx, s, "acct1", "km", higherTag); err != nil {
164 t.Fatal(err)
165 }
166 // Cutting 0.5.0 from -1 must succeed, but advance is blocked by the collision.
167 tag, created, err := EnsureTag(ctx, s, "acct1", "km", "0.5.0-1", "paul@example.com", now)
168 if err != nil || !created || tag == nil {
169 t.Fatalf("EnsureTag should succeed: %+v, %v, %v", tag, created, err)
170 }
171 if tag.Version != "0.5.0" {
172 t.Fatalf("tag version: %s; want 0.5.0", tag.Version)
173 }
174 // Target must not have been set (advance blocked by 0.6.0).
175 vt, _, _ := GetTarget(ctx, s, "acct1", "km")
176 if vt != nil {
177 t.Fatalf("target was set: %+v; want nil (blocked by higher tag 0.6.0)", vt)
178 }
179}
180
181func TestEnsureTagMidRangeGap(t *testing.T) {
182 s := NewDirStore(t.TempDir())
183 ctx := context.Background()
184 now := time.Unix(2000, 0).UTC()
185 shipIteration(t, s, "0.5.0-1", "first", "- first")
186 // Skip 0.5.0-2 intentionally.
187 shipIteration(t, s, "0.5.0-3", "third", "- third")
188 tag, created, err := EnsureTag(ctx, s, "acct1", "km", "0.5.0-3", "paul@example.com", now)
189 if err != nil || !created || tag == nil {
190 t.Fatalf("EnsureTag: %+v, %v, %v", tag, created, err)
191 }
192 // Changelog should have sections for -1 and -3 only (gap at -2 skipped).
193 want := "## 0.5.0-1 — first\n\n- first\n\n## 0.5.0-3 — third\n\n- third\n"
194 if tag.Changelog != want {
195 t.Fatalf("changelog with gap:\n%q\nwant:\n%q", tag.Changelog, want)
196 }
197 // Target should auto-advance to 0.5.1 (no collision).
198 vt, _, _ := GetTarget(ctx, s, "acct1", "km")
199 if vt == nil || vt.Target != "0.5.1" {
200 t.Fatalf("target: %+v; want 0.5.1", vt)
201 }
202}
203