package artifact import ( "crypto/sha256" "encoding/hex" "os" "path/filepath" "strings" "testing" ) func TestBuildManifestSortsNamesAndOmitsEmptySourceOnlyManifest(t *testing.T) { manifest, err := BuildManifest([]ExpectedAsset{ assetFor("zeta.bin", []byte("zeta")), assetFor("alpha.bin", []byte("alpha")), }) if err != nil { t.Fatal(err) } want := assetDigest([]byte("alpha")) + " alpha.bin\n" + assetDigest([]byte("zeta")) + " zeta.bin\n" if string(manifest.Content) != want || manifest.Filename != ManifestFilename || manifest.SHA256 != assetDigest([]byte(want)) || manifest.Size != int64(len(want)) { t.Fatalf("manifest = %#v, want content %q", manifest, want) } empty, err := BuildManifest(nil) if err != nil || empty.Filename != "" || len(empty.Content) != 0 { t.Fatalf("empty manifest = %#v, error = %v", empty, err) } } func TestBuildManifestRejectsInvalidOrDuplicateDeclarations(t *testing.T) { for _, test := range []struct { name string assets []ExpectedAsset }{ {name: "invalid filename", assets: []ExpectedAsset{assetFor("../app.bin", []byte("app"))}}, {name: "manifest filename", assets: []ExpectedAsset{assetFor(ManifestFilename, []byte("app"))}}, {name: "invalid digest", assets: []ExpectedAsset{{Filename: "app.bin", SHA256: "not-a-digest"}}}, {name: "duplicate filename", assets: []ExpectedAsset{assetFor("app.bin", []byte("one")), assetFor("app.bin", []byte("two"))}}, } { t.Run(test.name, func(t *testing.T) { if _, err := BuildManifest(test.assets); err == nil { t.Fatal("BuildManifest() succeeded for invalid declarations") } }) } } func TestVerifyLocalAssetsRejectsSameSizeTampering(t *testing.T) { path := filepath.Join(t.TempDir(), "app.bin") original := []byte("alpha") if err := os.WriteFile(path, original, 0o600); err != nil { t.Fatal(err) } asset := assetFor(filepath.Base(path), original) asset.Content = nil asset.Path = path if err := VerifyLocalAssets([]ExpectedAsset{asset}); err != nil { t.Fatal(err) } tampered := []byte("bravo") if err := os.WriteFile(path, tampered, 0o600); err != nil { t.Fatal(err) } if err := VerifyLocalAssets([]ExpectedAsset{asset}); err == nil || !strings.Contains(err.Error(), "does not match declared asset") { t.Fatalf("VerifyLocalAssets() error = %v, want content mismatch", err) } } func TestValidateExpectedAssetsRejectsDuplicateAndMismatchedInlineContent(t *testing.T) { asset := assetFor("app.bin", []byte("alpha")) if err := ValidateExpectedAssets([]ExpectedAsset{asset, asset}); err == nil || !strings.Contains(err.Error(), "duplicate") { t.Fatalf("ValidateExpectedAssets() error = %v, want duplicate", err) } asset.Content = []byte("bravo") if err := ValidateExpectedAssets([]ExpectedAsset{asset}); err == nil || !strings.Contains(err.Error(), "does not match identity") { t.Fatalf("ValidateExpectedAssets() error = %v, want content mismatch", err) } } func assetFor(filename string, content []byte) ExpectedAsset { return ExpectedAsset{ Filename: filename, SHA256: assetDigest(content), Size: int64(len(content)), Content: append([]byte(nil), content...), } } func assetDigest(content []byte) string { sum := sha256.Sum256(content) return hex.EncodeToString(sum[:]) }