package cli import ( "crypto/sha256" "encoding/hex" "os" "path/filepath" "strings" "testing" ) func TestBuildReceiptRoundTripBindsFilesAndBuildInputs(t *testing.T) { outputDir := t.TempDir() repositoryDir := t.TempDir() filename := "example-v1.2.3-linux-amd64" artifactPath := filepath.Join(outputDir, filename) contents := []byte("built application") if err := os.WriteFile(artifactPath, contents, 0o755); err != nil { t.Fatal(err) } digest := sha256.Sum256(contents) receipt := BuildReceipt{ RepositoryDir: repositoryDir, CandidateCommit: strings.Repeat("a", 40), Version: "v1.2.3", MainPackage: "./cmd/example", BinaryName: "example", Targets: []ReceiptTarget{{OS: "linux", Arch: "amd64"}}, Compiler: "go version go1.25.0 linux/amd64", Artifacts: []ReceiptArtifact{{ Path: artifactPath, Filename: filename, Target: ReceiptTarget{OS: "linux", Arch: "amd64"}, SHA256: hex.EncodeToString(digest[:]), }}, } path, err := WriteBuildReceipt(outputDir, receipt) if err != nil { t.Fatal(err) } loaded, err := LoadBuildReceipt(path) if err != nil { t.Fatal(err) } settings := BuildSettings{ MainPackage: "./cmd/example", BinaryName: "example", Targets: []string{"linux/amd64", "linux/amd64"}, } if !loaded.MatchesBuild(repositoryDir, strings.Repeat("a", 40), "v1.2.3", settings) { t.Fatalf("receipt does not match normalized build settings: %#v", loaded) } settings.BinaryName = "other" if loaded.MatchesBuild(repositoryDir, strings.Repeat("a", 40), "v1.2.3", settings) { t.Fatal("receipt matched changed build settings") } if err := os.WriteFile(artifactPath, []byte("changed artifact"), 0o755); err != nil { t.Fatal(err) } if _, err := LoadBuildReceipt(path); err == nil || !strings.Contains(err.Error(), "hash changed") { t.Fatalf("LoadBuildReceipt() error = %v, want changed-file hash error", err) } } func TestLoadBuildReceiptRejectsUnknownFieldsAndMultipleValues(t *testing.T) { for _, test := range []struct { name string body string want string }{ {name: "unknown field", body: `{"unexpected":true}`, want: "unknown field"}, {name: "multiple values", body: `{} {}`, want: "multiple JSON values"}, } { t.Run(test.name, func(t *testing.T) { path := filepath.Join(t.TempDir(), BuildReceiptName) if err := os.WriteFile(path, []byte(test.body), 0o600); err != nil { t.Fatal(err) } if _, err := LoadBuildReceipt(path); err == nil || !strings.Contains(err.Error(), test.want) { t.Fatalf("LoadBuildReceipt() error = %v, want %q", err, test.want) } }) } }