| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "fmt" |
| 6 | "os" |
| 7 | "path/filepath" |
| 8 | "strings" |
| 9 | "testing" |
| 10 | "time" |
| 11 | |
| 12 | "github.com/carlosframework/platform/internal/release" |
| 13 | ) |
| 14 | |
| 15 | // manifestCountingStore is the production store shape in miniature — a bare |
| 16 | // DirStore, ListMod and all (struct embedding keeps the method) — counting |
| 17 | // manifest reads specifically, so the tag reads ListTags does don't muddy |
| 18 | // the assertion. |
| 19 | type manifestCountingStore struct { |
| 20 | *release.DirStore |
| 21 | manifestGets int |
| 22 | } |
| 23 | |
| 24 | func (c *manifestCountingStore) Get(ctx context.Context, key string) ([]byte, string, error) { |
| 25 | if strings.HasSuffix(key, "/manifest.json") { |
| 26 | c.manifestGets++ |
| 27 | } |
| 28 | return c.DirStore.Get(ctx, key) |
| 29 | } |
| 30 | |
| 31 | // TestLocalReleasesBoundsManifestReads: `carlos releases` direct mode reads |
| 32 | // only the manifests it will print, chosen by manifest mod time exactly as |
| 33 | // the console does (release.VersionCandidates is shared) — version names |
| 34 | // here are numbered AGAINST recency so a name-ranked selection fails, and a |
| 35 | // fetch-everything regression fails the read count. |
| 36 | func TestLocalReleasesBoundsManifestReads(t *testing.T) { |
| 37 | dir := t.TempDir() |
| 38 | store := &manifestCountingStore{DirStore: release.NewDirStore(dir)} |
| 39 | ctx := context.Background() |
| 40 | base := time.Date(2026, 8, 1, 10, 0, 0, 0, time.UTC) |
| 41 | const shipped = releasesCap + 5 |
| 42 | for i := 1; i <= shipped; i++ { |
| 43 | v := fmt.Sprintf("v%02d", i) |
| 44 | mod := base.Add(time.Duration(shipped-i) * time.Minute) // v01 newest |
| 45 | m := release.Manifest{ |
| 46 | Schema: 1, App: "kass", Version: v, Kind: "binary", BuiltAt: mod.Format(time.RFC3339), |
| 47 | Artifacts: map[string]release.Artifact{"linux-amd64": {File: "kass"}}, |
| 48 | } |
| 49 | if err := release.Ship(ctx, store, release.OpsAccount, m, map[string][]byte{"linux-amd64": []byte("bin " + v)}); err != nil { |
| 50 | t.Fatal(err) |
| 51 | } |
| 52 | key := release.ManifestKey(release.OpsAccount, "kass", v) |
| 53 | if err := os.Chtimes(filepath.Join(dir, filepath.FromSlash(key)), mod, mod); err != nil { |
| 54 | t.Fatal(err) |
| 55 | } |
| 56 | } |
| 57 | store.manifestGets = 0 // Ship reads during setup don't count |
| 58 | rows, err := localReleases(ctx, store, release.OpsAccount, "kass") |
| 59 | if err != nil { |
| 60 | t.Fatal(err) |
| 61 | } |
| 62 | if len(rows) != releasesCap { |
| 63 | t.Fatalf("len = %d, want releasesCap %d", len(rows), releasesCap) |
| 64 | } |
| 65 | for i := 0; i < releasesCap; i++ { |
| 66 | want := fmt.Sprintf("v%02d", i+1) |
| 67 | if rows[i].Version != want { |
| 68 | t.Fatalf("rows[%d] = %s, want %s (rank by mod time, not name)", i, rows[i].Version, want) |
| 69 | } |
| 70 | } |
| 71 | if rows[0].Kind != "binary" || rows[0].Err != "" { |
| 72 | t.Fatalf("rows[0] = %+v, want a rendered release row", rows[0]) |
| 73 | } |
| 74 | if store.manifestGets != releasesCap { |
| 75 | t.Fatalf("read %d manifests for %d shipped versions, want %d", store.manifestGets, shipped, releasesCap) |
| 76 | } |
| 77 | } |
| 78 | |