package bundle import ( "context" "encoding/json" "strings" "testing" "gitea.maximumdirect.net/eric/distributor/internal/storage" "gitea.maximumdirect.net/eric/distributor/internal/storage/fake" ) func TestValidateValidBundle(t *testing.T) { backend := validFakeBundle(t) sourceBundle, err := Validate(context.Background(), backend, "") if err != nil { t.Fatalf("Validate() error = %v", err) } if sourceBundle.RootRelativePath != "" { t.Fatalf("root = %q, want empty", sourceBundle.RootRelativePath) } if sourceBundle.Manifest.ID != "weather.daily.brentwood.2026-05-30" { t.Fatalf("id = %q", sourceBundle.Manifest.ID) } } func TestValidateRejectsMissingFile(t *testing.T) { backend := validFakeBundle(t) deleteFakeFile(t, backend, "summary.txt") _, err := Validate(context.Background(), backend, "") assertErrorContains(t, err, "stat") } func TestValidateRejectsSizeMismatch(t *testing.T) { backend := validFakeBundle(t) manifest := validFixtureManifest(t) manifest.Files[1].Size = 9 manifest.Digest = BundleDigest(manifest.Files) writeManifest(t, backend, manifest) _, err := Validate(context.Background(), backend, "") assertErrorContains(t, err, "size mismatch") } func TestValidateRejectsPerFileDigestMismatch(t *testing.T) { backend := validFakeBundle(t) writeFakeFile(t, backend, "report.md", "# Report\nCloud.\n") _, err := Validate(context.Background(), backend, "") assertErrorContains(t, err, "sha256 mismatch") } func TestValidateRejectsBundleDigestMismatch(t *testing.T) { backend := validFakeBundle(t) manifest := strings.Replace(string(readFixture(t, "testdata/valid_bundle/manifest.json")), `"digest": "sha256:099b205780d2b050024868399961b05731729a548d5d6329c7b06a6740dd75fe"`, `"digest": "sha256:0000000000000000000000000000000000000000000000000000000000000000"`, 1) writeFakeFile(t, backend, "manifest.json", manifest) _, err := Validate(context.Background(), backend, "") assertErrorContains(t, err, "digest mismatch") } func TestValidateRejectsSymlinkFile(t *testing.T) { backend := validFakeBundle(t) if err := backend.AddSymlink("summary.txt"); err != nil { t.Fatalf("AddSymlink() error = %v", err) } _, err := Validate(context.Background(), backend, "") assertErrorContains(t, err, "regular file") } func TestValidateRejectsUnsafeManifestPaths(t *testing.T) { tests := []string{ "", ".", "./report.md", "../report.md", "/report.md", "nested/../report.md", "nested/./report.md", "nested//report.md", `nested\report.md`, ManifestName, storage.StateFileName, "nested/" + ManifestName, "nested/" + storage.StateFileName, } for _, path := range tests { t.Run(path, func(t *testing.T) { backend := validFakeBundle(t) manifest := validFixtureManifest(t) manifest.Files[0].Path = path manifest.Digest = BundleDigest(manifest.Files) writeManifest(t, backend, manifest) _, err := Validate(context.Background(), backend, "") if err == nil { t.Fatal("Validate() error = nil, want unsafe path error") } }) } } func validFakeBundle(t *testing.T) *fake.Backend { t.Helper() backend := fake.New() writeFakeFile(t, backend, "manifest.json", string(readFixture(t, "testdata/valid_bundle/manifest.json"))) writeFakeFile(t, backend, "report.md", string(readFixture(t, "testdata/valid_bundle/report.md"))) writeFakeFile(t, backend, "summary.txt", string(readFixture(t, "testdata/valid_bundle/summary.txt"))) return backend } func writeFakeFile(t *testing.T, backend *fake.Backend, path, data string) { t.Helper() _, err := backend.WriteFile(context.Background(), path, []byte(data), storage.WriteOptions{Overwrite: true}) if err != nil { t.Fatalf("WriteFile(%q) error = %v", path, err) } } func writeManifest(t *testing.T, backend *fake.Backend, manifest Manifest) { t.Helper() data, err := json.MarshalIndent(manifest, "", " ") if err != nil { t.Fatalf("MarshalIndent() error = %v", err) } writeFakeFile(t, backend, ManifestName, string(append(data, '\n'))) } func deleteFakeFile(t *testing.T, backend *fake.Backend, path string) { t.Helper() err := backend.DeleteManagedBundle(context.Background(), "", []string{path}, storage.DeleteOptions{IgnoreMissing: true}) if err != nil { t.Fatalf("DeleteManagedBundle(%q) error = %v", path, err) } }