Files
BlueArchiveToolkit/internal/api/release_attestation_test.go
nyaKazuha 99355effe4
bat-rust / Build and test Rust (push) Canceled after 0s
bat-rust / Build and test Go API (push) Canceled after 0s
fix(release): 完成分发证明代际绑定与质量门禁收口
2026-09-15 21:13:52 +08:00

286 lines
9.2 KiB
Go

package api
import (
"context"
"encoding/json"
"testing"
"time"
"bat-api/internal/backendrpc"
)
func testAttestation(root, releaseID, integrity string, ready bool) *backendrpc.DistributionAttestation {
verifiedAt := uint64(time.Now().Unix())
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,
VerifiedAt: &verifiedAt,
MaxAgeSeconds: 7260,
}
}
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: "generation",
mutate: func(page *backendrpc.ResourceManifestPage) { page.Generation = 5 },
},
{
name: "publication identity",
mutate: func(page *backendrpc.ResourceManifestPage) { page.PublicationIdentity = "publication-b" },
},
{
name: "mapping identity",
mutate: func(page *backendrpc.ResourceManifestPage) { page.MappingIdentity = "mapping-b" },
},
{
name: "manifest version",
mutate: func(page *backendrpc.ResourceManifestPage) { page.ManifestVersion = 2 },
},
{
name: "total",
mutate: func(page *backendrpc.ResourceManifestPage) { page.TotalEntries = 4 },
},
{
name: "limit",
mutate: func(page *backendrpc.ResourceManifestPage) { page.Limit = 1 },
},
}
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 ||
backend.params[1].ExpectedVerificationGeneration != attestation.VerificationGeneration {
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())
}
})
}
}
func TestExpiredReadyAttestationNeverReadiesIndex(t *testing.T) {
root := fixtureRoot(t)
backend := fixtureRPCBackend(t, root)
attestation := testAttestation(root, "official-fixture", "verified", true)
expired := uint64(time.Now().Unix()) - attestation.MaxAgeSeconds - 1
attestation.VerifiedAt = &expired
backend.attestation = attestation
result, err := DiscoverAndIndex(context.Background(), backend, "")
if err != nil {
t.Fatal(err)
}
if result.Index == nil || result.Index.Summary().Ready {
t.Fatalf("expired attestation unexpectedly ready: %+v", result.Index.Summary())
}
if len(backend.manifestParams) != 0 {
t.Fatalf("manifest should not be fetched for expired attestation: %+v", backend.manifestParams)
}
}