package api import ( "context" "encoding/json" "testing" "bat-api/internal/backendrpc" ) func testAttestation(root, releaseID, integrity string, ready bool) *backendrpc.DistributionAttestation { return &backendrpc.DistributionAttestation{ Available: true, Channel: "official", ReleaseID: releaseID, ResourceRoot: root, PublicationIdentity: "publication-" + releaseID, MappingIdentity: "mapping-" + releaseID, ManifestIdentity: "manifest-" + releaseID, EntryCount: 3, IntegrityStatus: integrity, Status: integrity, StatusCode: "distribution." + integrity, Ready: ready, VerificationGeneration: 4, } } func testManifestPage(attestation *backendrpc.DistributionAttestation, offset int, entries int) *backendrpc.ResourceManifestPage { pageEntries := make([]backendrpc.ResourceManifestEntry, entries) for index := range pageEntries { size := uint64(index + 1) pageEntries[index] = backendrpc.ResourceManifestEntry{ URL: "https://example.invalid/" + string(rune('a'+offset+index)), Destination: "resource-" + string(rune('a'+offset+index)), Bytes: &size, BLAKE3: "blake3", } } return &backendrpc.ResourceManifestPage{ Available: true, Channel: "official", ReleaseID: attestation.ReleaseID, ResourceRoot: attestation.ResourceRoot, ManifestVersion: 1, PublicationIdentity: attestation.PublicationIdentity, MappingIdentity: attestation.MappingIdentity, ManifestIdentity: attestation.ManifestIdentity, Generation: attestation.VerificationGeneration, TotalEntries: attestation.EntryCount, Offset: offset, Limit: 2, Entries: pageEntries, } } type pagedManifestBackend struct { *fakeBackend pages []*backendrpc.ResourceManifestPage params []backendrpc.ResourceManifestParams } func (b *pagedManifestBackend) ResourceManifest(_ context.Context, params backendrpc.ResourceManifestParams) (*backendrpc.ResourceManifestPage, error) { b.params = append(b.params, params) pageIndex := len(b.params) - 1 page := *b.pages[pageIndex] return &page, nil } func TestFetchAllManifestEntriesRejectsMixedPages(t *testing.T) { attestation := testAttestation("/srv/official/current", "official-a", "verified", true) tests := []struct { name string mutate func(*backendrpc.ResourceManifestPage) }{ { name: "release", mutate: func(page *backendrpc.ResourceManifestPage) { page.ReleaseID = "official-b" }, }, { name: "root", mutate: func(page *backendrpc.ResourceManifestPage) { page.ResourceRoot = "/srv/official/current-b" }, }, { name: "manifest identity", mutate: func(page *backendrpc.ResourceManifestPage) { page.ManifestIdentity = "manifest-b" }, }, { name: "total", mutate: func(page *backendrpc.ResourceManifestPage) { page.TotalEntries = 4 }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { first := testManifestPage(attestation, 0, 2) second := testManifestPage(attestation, 2, 1) test.mutate(second) backend := &pagedManifestBackend{ fakeBackend: &fakeBackend{}, pages: []*backendrpc.ResourceManifestPage{first, second}, } if _, _, _, err := fetchAllManifestEntriesWithPageSize( context.Background(), backend, attestation, 2, ); err == nil { t.Fatal("expected mixed-page validation error") } }) } } func TestFetchAllManifestEntriesAcceptsMatchingGeneration(t *testing.T) { attestation := testAttestation("/srv/official/current", "official-a", "verified", true) backend := &pagedManifestBackend{ fakeBackend: &fakeBackend{}, pages: []*backendrpc.ResourceManifestPage{ testManifestPage(attestation, 0, 2), testManifestPage(attestation, 2, 1), }, } entries, version, root, err := fetchAllManifestEntriesWithPageSize( context.Background(), backend, attestation, 2, ) if err != nil { t.Fatal(err) } if len(entries) != 3 || version != 1 || root != attestation.ResourceRoot { t.Fatalf("entries=%d version=%d root=%q", len(entries), version, root) } if len(backend.params) != 2 || backend.params[1].ReleaseID != attestation.ReleaseID || backend.params[1].ExpectedManifestIdentity != attestation.ManifestIdentity { t.Fatalf("params=%+v", backend.params) } } func TestDiscoverRejectsAttestationThenCatalogCurrentSwitch(t *testing.T) { root := fixtureRoot(t) backend := fixtureRPCBackend(t, root) backend.attestation = testAttestation(root, "official-a", "verified", true) catalog, err := json.Marshal(map[string]any{ "available": true, "version": map[string]any{ "id": "official-b", "resource_root": root, }, }) if err != nil { t.Fatal(err) } backend.catalog = catalog result, err := DiscoverAndIndex(context.Background(), backend, "") if err != nil { t.Fatal(err) } if result.Index == nil || result.Index.Summary().Ready || result.Index.Summary().EntryCount != 0 { t.Fatalf("summary=%+v", result.Index.Summary()) } if len(backend.manifestParams) != 0 { t.Fatalf("manifest should not be fetched after current switch: %+v", backend.manifestParams) } } func TestHealthyAttestationThenCurrentSwitchClearsSnapshot(t *testing.T) { root := copyFixtureRoot(t) backend := fixtureRPCBackend(t, root) backend.attestation = testAttestation(root, "official-a", "verified", true) backend.attestation.EntryCount = 2 backend.catalog = mustCatalogForTest(t, root, "official-a") backend.manifest.ReleaseID = "official-a" backend.manifest.PublicationIdentity = "publication-official-a" backend.manifest.MappingIdentity = "mapping-official-a" backend.manifest.ManifestIdentity = "manifest-official-a" backend.manifest.Generation = 4 cfg := DefaultConfig() cfg.RefreshInterval = 0 if err := cfg.Normalize(); err != nil { t.Fatal(err) } server := NewServer(cfg, backend, nil) if err := server.Refresh(context.Background()); err != nil { t.Fatal(err) } if !server.index().Summary().Ready { t.Fatal("initial snapshot should be ready") } backend.attestation = testAttestation(root, "official-b", "verified", true) backend.catalog = mustCatalogForTest(t, root, "official-b") if err := server.Refresh(context.Background()); err != nil { t.Fatal(err) } if summary := server.index().Summary(); summary.Ready || summary.ResourceRoot != "" { t.Fatalf("mixed snapshot was retained: %+v", summary) } } func mustCatalogForTest(t *testing.T, root, releaseID string) json.RawMessage { t.Helper() raw, err := json.Marshal(map[string]any{ "available": true, "version": map[string]any{ "id": releaseID, "resource_root": root, }, }) if err != nil { t.Fatal(err) } return raw } func TestStaleOrInvalidMatchingAttestationNeverReadiesIndex(t *testing.T) { for _, integrity := range []string{"stale", "invalid"} { t.Run(integrity, func(t *testing.T) { root := fixtureRoot(t) backend := fixtureRPCBackend(t, root) backend.attestation = testAttestation(root, "official-a", integrity, false) backend.attestation.EntryCount = 2 backend.catalog = mustCatalogForTest(t, root, "official-a") backend.manifest.ReleaseID = "official-a" backend.manifest.PublicationIdentity = "publication-official-a" backend.manifest.MappingIdentity = "mapping-official-a" backend.manifest.ManifestIdentity = "manifest-official-a" backend.manifest.Generation = 4 result, err := DiscoverAndIndex(context.Background(), backend, "") if err != nil { t.Fatal(err) } if result.Index == nil || result.Index.Summary().Ready || result.Index.Summary().Distribution.Ready { t.Fatalf("summary=%+v", result.Index.Summary()) } }) } }