package bundle import ( "os" "strings" "testing" "gitea.maximumdirect.net/eric/distributor/internal/storage" ) func TestParseManifestValid(t *testing.T) { data := readFixture(t, "testdata/valid_bundle/manifest.json") manifest, err := ParseManifest(data) if err != nil { t.Fatalf("ParseManifest() error = %v", err) } if manifest.SchemaVersion != 1 { t.Fatalf("schema version = %d, want 1", manifest.SchemaVersion) } if manifest.ID != "weather.daily.brentwood.2026-05-30" { t.Fatalf("id = %q", manifest.ID) } if got, want := len(manifest.Files), 2; got != want { t.Fatalf("file count = %d, want %d", got, want) } } func TestParseManifestRejectsInvalidJSON(t *testing.T) { _, err := ParseManifest([]byte(`{"schema_version":`)) assertErrorContains(t, err, "parse manifest") } func TestParseManifestRejectsMissingRequiredFields(t *testing.T) { tests := map[string]string{ "schema_version": `{"id":"id","digest":"sha256:0000000000000000000000000000000000000000000000000000000000000000","created":"2026-05-30T11:10:00Z","files":[{"path":"report.md","sha256":"sha256:0000000000000000000000000000000000000000000000000000000000000000","size":0}]}`, "id": `{"schema_version":1,"digest":"sha256:0000000000000000000000000000000000000000000000000000000000000000","created":"2026-05-30T11:10:00Z","files":[{"path":"report.md","sha256":"sha256:0000000000000000000000000000000000000000000000000000000000000000","size":0}]}`, "digest": `{"schema_version":1,"id":"id","created":"2026-05-30T11:10:00Z","files":[{"path":"report.md","sha256":"sha256:0000000000000000000000000000000000000000000000000000000000000000","size":0}]}`, "created": `{"schema_version":1,"id":"id","digest":"sha256:0000000000000000000000000000000000000000000000000000000000000000","files":[{"path":"report.md","sha256":"sha256:0000000000000000000000000000000000000000000000000000000000000000","size":0}]}`, "files": `{"schema_version":1,"id":"id","digest":"sha256:0000000000000000000000000000000000000000000000000000000000000000","created":"2026-05-30T11:10:00Z"}`, "file path": `{"schema_version":1,"id":"id","digest":"sha256:0000000000000000000000000000000000000000000000000000000000000000","created":"2026-05-30T11:10:00Z","files":[{"sha256":"sha256:0000000000000000000000000000000000000000000000000000000000000000","size":0}]}`, "file digest": `{"schema_version":1,"id":"id","digest":"sha256:0000000000000000000000000000000000000000000000000000000000000000","created":"2026-05-30T11:10:00Z","files":[{"path":"report.md","size":0}]}`, "file size": `{"schema_version":1,"id":"id","digest":"sha256:0000000000000000000000000000000000000000000000000000000000000000","created":"2026-05-30T11:10:00Z","files":[{"path":"report.md","sha256":"sha256:0000000000000000000000000000000000000000000000000000000000000000"}]}`, } for name, body := range tests { t.Run(name, func(t *testing.T) { _, err := ParseManifest([]byte(body)) assertErrorContains(t, err, "required") }) } } func TestParseManifestRejectsInvalidSchemaVersion(t *testing.T) { data := replaceFixture(t, `"schema_version": 1`, `"schema_version": 2`) _, err := ParseManifest(data) assertErrorContains(t, err, "schema_version must be 1") } func TestParseManifestRejectsInvalidTimestamp(t *testing.T) { data := replaceFixture(t, `"created": "2026-05-30T11:10:00Z"`, `"created": "May 30"`) _, err := ParseManifest(data) assertErrorContains(t, err, "RFC3339") } func TestParseManifestRejectsInvalidDigestFormat(t *testing.T) { data := replaceFixture(t, `"digest": "sha256:099b205780d2b050024868399961b05731729a548d5d6329c7b06a6740dd75fe"`, `"digest": "SHA256:099b205780d2b050024868399961b05731729a548d5d6329c7b06a6740dd75fe"`) _, err := ParseManifest(data) assertErrorContains(t, err, "lowercase") } func TestParseManifestRejectsUnsafeFilePaths(t *testing.T) { tests := []string{ `"path": "../report.md"`, `"path": "/report.md"`, `"path": "nested/../report.md"`, `"path": "manifest.json"`, `"path": "` + storage.StateFileName + `"`, } for _, replacement := range tests { t.Run(replacement, func(t *testing.T) { data := replaceFixture(t, `"path": "report.md"`, replacement) _, err := ParseManifest(data) if err == nil { t.Fatal("ParseManifest() error = nil, want error") } }) } } func TestParseManifestRejectsDuplicatePaths(t *testing.T) { data := replaceFixture(t, `"path": "summary.txt"`, `"path": "report.md"`) _, err := ParseManifest(data) assertErrorContains(t, err, "duplicates") } func readFixture(t *testing.T, path string) []byte { t.Helper() data, err := os.ReadFile(path) if err != nil { t.Fatalf("read fixture %s: %v", path, err) } return data } func replaceFixture(t *testing.T, old, replacement string) []byte { t.Helper() body := string(readFixture(t, "testdata/valid_bundle/manifest.json")) if !strings.Contains(body, old) { t.Fatalf("fixture does not contain %q", old) } return []byte(strings.Replace(body, old, replacement, 1)) } func assertErrorContains(t *testing.T, err error, want string) { t.Helper() if err == nil { t.Fatalf("error = nil, want substring %q", want) } if !strings.Contains(err.Error(), want) { t.Fatalf("error = %q, want substring %q", err.Error(), want) } }