mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-09-18 07:24:55 +08:00
286 lines
9.2 KiB
Go
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)
|
|
}
|
|
}
|