2026-10-05 16:49:54 +00:00
package main
import (
"bytes"
2026-10-05 17:47:50 +00:00
"context"
2026-10-05 20:39:33 +00:00
"crypto/sha256"
"encoding/hex"
2026-10-05 17:47:50 +00:00
"encoding/json"
2026-10-05 16:49:54 +00:00
"fmt"
2026-10-05 20:39:33 +00:00
"io"
"net/http"
"net/http/httptest"
2026-10-05 17:47:50 +00:00
"os"
"os/exec"
"path/filepath"
2026-10-05 19:38:50 +00:00
"runtime"
2026-10-05 17:47:50 +00:00
"strings"
2026-10-05 16:49:54 +00:00
"testing"
2026-10-07 20:27:39 +00:00
releasekit "gitea.maximumdirect.net/eric/ReleaseKit"
2026-10-05 16:49:54 +00:00
"gitea.maximumdirect.net/eric/ReleaseKit/buildinfo"
2026-10-05 17:47:50 +00:00
"gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit/internal/cli"
2026-10-05 16:49:54 +00:00
)
func TestVersionCommand ( t * testing . T ) {
var stdout , stderr bytes . Buffer
2026-10-05 17:47:50 +00:00
if status := run ( context . Background ( ) , [ ] string { "version" } , & stdout , & stderr ) ; status != 0 {
2026-10-05 16:49:54 +00:00
t . Fatalf ( "run() status = %d, want 0; stderr = %q" , status , stderr . String ( ) )
}
if want := fmt . Sprintf ( "releasekit %s\n" , buildinfo . Version ( ) ) ; stdout . String ( ) != want {
t . Fatalf ( "version output = %q, want %q" , stdout . String ( ) , want )
}
if stderr . Len ( ) != 0 {
t . Fatalf ( "version command wrote to stderr: %q" , stderr . String ( ) )
}
}
func TestUnknownCommand ( t * testing . T ) {
var stdout , stderr bytes . Buffer
2026-10-05 19:04:55 +00:00
if status := run ( context . Background ( ) , [ ] string { "unknown" } , & stdout , & stderr ) ; status != 2 {
2026-10-05 16:49:54 +00:00
t . Fatalf ( "run() status = %d, want 2" , status )
}
if stdout . Len ( ) != 0 {
t . Fatalf ( "unknown command wrote to stdout: %q" , stdout . String ( ) )
}
2026-10-05 19:04:55 +00:00
if want := "unknown command \"unknown\"\n" + cli . Usage + "\n" ; stderr . String ( ) != want {
2026-10-05 16:49:54 +00:00
t . Fatalf ( "usage output = %q, want %q" , stderr . String ( ) , want )
}
}
2026-10-05 17:47:50 +00:00
func TestCheckCommandBindsConfiguredChecksToCapturedCandidate ( t * testing . T ) {
2026-10-07 22:10:34 +00:00
raceSelected := racePrerequisitesAvailable ( )
2026-10-05 17:47:50 +00:00
repository := newCLIRepository ( t )
profilePath := filepath . Join ( t . TempDir ( ) , "profile with spaces.json" )
2026-10-06 17:15:46 +00:00
orderPath := filepath . Join ( t . TempDir ( ) , "check-order" )
t . Setenv ( "RELEASEKIT_CHECK_ORDER_FILE" , orderPath )
2026-10-05 17:47:50 +00:00
executable , err := os . Executable ( )
if err != nil {
t . Fatal ( err )
}
2026-10-06 17:15:46 +00:00
check := func ( name string ) cli . CheckCommand {
return cli . CheckCommand {
Args : [ ] string { executable , "-test.run=TestCandidateCheckHelper" , "--" , name } ,
2026-10-05 17:47:50 +00:00
Env : map [ string ] string {
"RELEASEKIT_CHECK_HELPER" : "1" ,
2026-10-06 17:15:46 +00:00
"RELEASEKIT_CHECK_NAME" : name ,
2026-10-05 17:47:50 +00:00
"RELEASEKIT_VERSION" : "profile version" ,
"RELEASEKIT_CANDIDATE" : "profile commit" ,
} ,
2026-10-06 17:15:46 +00:00
}
}
settings := cli . Settings {
Product : "Example Product" ,
Remote : "origin" ,
Checks : [ ] cli . CheckCommand { check ( "first" ) , check ( "second" ) } ,
2026-10-05 17:47:50 +00:00
}
2026-10-07 22:10:34 +00:00
if raceSelected {
settings . Validation = & cli . ValidationSettings { Go : cli . GoValidationSettings { Race : true } }
}
2026-10-05 17:47:50 +00:00
contents , err := json . Marshal ( settings )
if err != nil {
t . Fatal ( err )
}
if err := os . WriteFile ( profilePath , contents , 0 o600 ) ; err != nil {
t . Fatal ( err )
}
var stdout , stderr bytes . Buffer
2026-10-07 22:10:34 +00:00
raceMarker := ""
if raceSelected {
raceMarker = filepath . Join ( t . TempDir ( ) , "race-enabled" )
t . Setenv ( "RELEASEKIT_RACE_MARKER" , raceMarker )
}
2026-10-05 17:47:50 +00:00
args := [ ] string { "check" , "--repo" , repository , "--config" , profilePath , "v1.2.3" }
if status := run ( context . Background ( ) , args , & stdout , & stderr ) ; status != 0 {
t . Fatalf ( "run() status = %d; stdout=%q stderr=%q" , status , stdout . String ( ) , stderr . String ( ) )
}
commit := cliGitOutput ( t , repository , "rev-parse" , "HEAD" )
2026-10-06 17:15:46 +00:00
if got , err := os . ReadFile ( orderPath ) ; err != nil || string ( got ) != "first\nsecond\n" {
t . Fatalf ( "configured check order = %q, error = %v" , got , err )
}
if got , want := strings . Count ( stderr . String ( ) , "v1.2.3|" + commit ) , 2 ; got != want {
t . Fatalf ( "configured checks received captured candidate %d times, want %d; stderr=%q" , got , want , stderr . String ( ) )
2026-10-05 17:47:50 +00:00
}
if want := fmt . Sprintf ( "local candidate v1.2.3 at %s is valid\n" , commit ) ; stdout . String ( ) != want {
t . Fatalf ( "stdout = %q, want %q" , stdout . String ( ) , want )
}
2026-10-07 22:10:34 +00:00
if raceSelected {
if _ , err := os . Stat ( raceMarker ) ; err != nil {
t . Fatalf ( "race-enabled profile did not run: %v" , err )
}
}
}
2026-10-07 22:18:47 +00:00
func TestCandidateRequestMapsGoValidation ( t * testing . T ) {
2026-10-07 22:10:34 +00:00
request := candidateRequest ( cli . Invocation { Repository : "repository" , Version : "v1.2.3" } , cli . Settings {
2026-10-07 22:31:58 +00:00
Product : "Example Product" ,
ExpectedPushURL : "publication-only value" ,
2026-10-07 22:10:34 +00:00
Validation : & cli . ValidationSettings {
2026-10-07 22:45:47 +00:00
GoFormat : true ,
Go : cli . GoValidationSettings { Race : true , FullVet : true , CompileTargets : [ ] string { "linux/amd64" , "darwin/arm64" } } ,
ModuleDistribution : cli . ModuleDistributionSettings { WorkspaceFiles : true } ,
2026-10-07 22:10:34 +00:00
} ,
} , nil )
2026-10-07 22:18:47 +00:00
if ! request . Validation . Go . Race || ! request . Validation . Go . FullVet {
t . Fatalf ( "candidate request validation = %#v, want Go race and full vet enabled" , request . Validation )
2026-10-07 22:10:34 +00:00
}
2026-10-07 22:39:15 +00:00
if ! request . Validation . GoFormat {
t . Fatalf ( "candidate request GoFormat = false, want true" )
}
2026-10-07 22:45:47 +00:00
if ! request . Validation . ModuleDistribution . WorkspaceFiles {
t . Fatalf ( "candidate request workspace files = false, want true" )
}
2026-10-07 22:31:58 +00:00
if request . ExpectedPushURL != "" {
t . Fatalf ( "candidate request carried publication-only expected push URL %q" , request . ExpectedPushURL )
}
2026-10-07 22:27:31 +00:00
wantTargets := [ ] releasekit . Target { { OS : "linux" , Arch : "amd64" } , { OS : "darwin" , Arch : "arm64" } }
if len ( request . Validation . Go . CompileTargets ) != len ( wantTargets ) {
t . Fatalf ( "candidate request compile targets = %#v, want %#v" , request . Validation . Go . CompileTargets , wantTargets )
}
for index := range wantTargets {
if request . Validation . Go . CompileTargets [ index ] != wantTargets [ index ] {
t . Fatalf ( "candidate request compile targets = %#v, want %#v" , request . Validation . Go . CompileTargets , wantTargets )
}
}
2026-10-05 17:47:50 +00:00
}
2026-10-07 18:36:37 +00:00
func TestCandidateCommandsPresentValidationDiagnosticsOnce ( t * testing . T ) {
repository := newCLIRepository ( t )
profile , err := json . Marshal ( cli . Settings { Product : "Example Product" } )
if err != nil {
t . Fatal ( err )
}
writeRepositoryFile ( t , repository , "releasekit.json" , string ( profile ) )
cliGitRun ( t , repository , "add" , "releasekit.json" )
cliGitRun ( t , repository , "commit" , "-m" , "configure candidate commands" )
remote := filepath . Join ( t . TempDir ( ) , "origin.git" )
cliGitRun ( t , repository , "init" , "--bare" , remote )
cliGitRun ( t , repository , "remote" , "add" , "origin" , remote )
cliGitRun ( t , repository , "push" , "origin" , "HEAD:refs/heads/main" )
goPath , err := exec . LookPath ( "go" )
if err != nil {
t . Fatal ( err )
}
goDirectory := t . TempDir ( )
shim := filepath . Join ( goDirectory , "go" )
if err := os . WriteFile ( shim , [ ] byte ( "#!/bin/sh\nif [ \"$1\" = test ]; then echo 'fixture Go suite failure' >&2; exit 1; fi\nexec \"$RELEASEKIT_REAL_GO\" \"$@\"\n" ) , 0 o755 ) ; err != nil {
t . Fatal ( err )
}
t . Setenv ( "RELEASEKIT_REAL_GO" , goPath )
t . Setenv ( "PATH" , goDirectory + string ( os . PathListSeparator ) + os . Getenv ( "PATH" ) )
for _ , command := range [ ] struct {
name string
wantStatus int
args [ ] string
} {
{ name : "check" , wantStatus : 1 , args : [ ] string { "check" , "--repo" , repository , "v1.2.3" } } ,
{ name : "publish" , wantStatus : 1 , args : [ ] string { "publish" , "--repo" , repository , "v1.2.3" } } ,
} {
t . Run ( command . name , func ( t * testing . T ) {
var stdout , stderr bytes . Buffer
if status := run ( context . Background ( ) , command . args , & stdout , & stderr ) ; status != command . wantStatus {
t . Fatalf ( "run() status = %d; stdout=%q stderr=%q" , status , stdout . String ( ) , stderr . String ( ) )
}
if got := strings . Count ( stderr . String ( ) , "go-test validation failed" ) ; got != 1 {
t . Fatalf ( "validation diagnostic appeared %d times: %q" , got , stderr . String ( ) )
}
if ! strings . Contains ( stderr . String ( ) , "fixture Go suite failure" ) {
t . Fatalf ( "stderr omitted the validation cause: %q" , stderr . String ( ) )
}
if command . name == "publish" && ! strings . Contains ( stderr . String ( ) , "state=not_attempted phase=validation" ) {
t . Fatalf ( "publication validation error lost its state: %q" , stderr . String ( ) )
}
} )
}
}
2026-10-07 20:27:39 +00:00
func TestCandidateFailurePresentationRetainsCallbackContext ( t * testing . T ) {
diagnostic := & releasekit . ValidationError {
Check : "examples" ,
Findings : [ ] releasekit . ValidationFinding { { Path : "examples/run/main.go" , Message : "unexpected output" } } ,
}
err := fmt . Errorf ( "validate candidate captured-commit: %w" , diagnostic )
text := candidateFailureText ( err )
for _ , part := range [ ] string { "captured-commit" , "examples" , "examples/run/main.go" , "unexpected output" } {
if ! strings . Contains ( text , part ) {
t . Errorf ( "CLI presentation %q omits %q" , text , part )
}
}
}
2026-10-05 19:04:55 +00:00
func TestPublishCommandPublishesConfiguredCandidate ( t * testing . T ) {
2026-10-07 22:10:34 +00:00
raceSelected := racePrerequisitesAvailable ( )
2026-10-05 19:04:55 +00:00
repository := newCLIRepository ( t )
2026-10-06 20:15:14 +00:00
writeRepositoryFile ( t , repository , "docs/releases/v1.2.3.md" , "# Example Product v1.2.3\n\n## Compatibility\n\nNo action required.\n" )
writeRepositoryFile ( t , repository , "scripts/check-release-note.sh" , ` # ! / bin / sh
set - eu
version = $ RELEASEKIT_VERSION
candidate = $ RELEASEKIT_CANDIDATE
test "$(git rev-parse HEAD)" = "$candidate"
test "$(sed -n '1p' " docs / releases / $ version . md ")" = "# Example Product $version"
grep - Fxq ' # # Compatibility ' "docs/releases/$version.md"
grep - Fxq ' No action required . ' "docs/releases/$version.md"
grep - Fxq ' module example . com / cli - candidate ' go . mod
printf ' % s % s \ n ' "$version" "$candidate" >> "$RELEASEKIT_COMPATIBILITY_LOG"
` )
cliGitRun ( t , repository , "add" , "docs/releases/v1.2.3.md" , "scripts/check-release-note.sh" )
cliGitRun ( t , repository , "commit" , "-m" , "add compatibility note and check" )
checkLog := filepath . Join ( t . TempDir ( ) , "compatibility-check.log" )
2026-10-07 22:31:58 +00:00
remote := filepath . Join ( t . TempDir ( ) , "origin.git" )
2026-10-05 19:04:55 +00:00
settings := cli . Settings {
2026-10-07 22:31:58 +00:00
Product : "Example Product" ,
Remote : "origin" ,
ExpectedPushURL : remote ,
2026-10-06 20:15:14 +00:00
Checks : [ ] cli . CheckCommand { {
Args : [ ] string { "sh" , "scripts/check-release-note.sh" } ,
Env : map [ string ] string { "RELEASEKIT_COMPATIBILITY_LOG" : checkLog } ,
} } ,
2026-10-05 19:04:55 +00:00
}
2026-10-07 22:10:34 +00:00
if raceSelected {
settings . Validation = & cli . ValidationSettings { Go : cli . GoValidationSettings { Race : true } }
}
2026-10-05 19:04:55 +00:00
contents , err := json . Marshal ( settings )
if err != nil {
t . Fatal ( err )
}
if err := os . WriteFile ( filepath . Join ( repository , "releasekit.json" ) , contents , 0 o600 ) ; err != nil {
t . Fatal ( err )
}
cliGitRun ( t , repository , "add" , "releasekit.json" )
cliGitRun ( t , repository , "commit" , "-m" , "configure source release" )
cliGitRun ( t , repository , "init" , "--bare" , remote )
cliGitRun ( t , repository , "remote" , "add" , "origin" , remote )
cliGitRun ( t , repository , "push" , "--set-upstream" , "origin" , "main" )
var stdout , stderr bytes . Buffer
2026-10-07 22:10:34 +00:00
raceMarker := ""
if raceSelected {
raceMarker = filepath . Join ( t . TempDir ( ) , "race-enabled" )
t . Setenv ( "RELEASEKIT_RACE_MARKER" , raceMarker )
}
2026-10-05 19:04:55 +00:00
args := [ ] string { "publish" , "--repo" , repository , "v1.2.3" }
if status := run ( context . Background ( ) , args , & stdout , & stderr ) ; status != 0 {
t . Fatalf ( "run() status = %d; stdout=%q stderr=%q" , status , stdout . String ( ) , stderr . String ( ) )
}
commit := cliGitOutput ( t , repository , "rev-parse" , "HEAD" )
tagObject := cliGitOutput ( t , repository , "rev-parse" , "refs/tags/v1.2.3" )
if want := fmt . Sprintf ( "published v1.2.3 at %s (tag object %s)\n" , commit , tagObject ) ; stdout . String ( ) != want {
t . Fatalf ( "stdout = %q, want %q" , stdout . String ( ) , want )
}
if ! bytes . Contains ( stderr . Bytes ( ) , [ ] byte ( "validating and publishing v1.2.3" ) ) || ! bytes . Contains ( stderr . Bytes ( ) , [ ] byte ( "example.com/cli-candidate" ) ) {
t . Fatalf ( "stderr does not show publication progress and validation: %q" , stderr . String ( ) )
}
if got := cliGitOutput ( t , remote , "rev-parse" , "refs/tags/v1.2.3" ) ; got != tagObject {
t . Fatalf ( "remote tag object = %s, want %s" , got , tagObject )
}
2026-10-06 20:15:14 +00:00
checkResult , err := os . ReadFile ( checkLog )
if err != nil {
t . Fatalf ( "read product compatibility check result: %v" , err )
}
if got , want := strings . Count ( string ( checkResult ) , "v1.2.3 " + commit ) , 1 ; got != want {
t . Fatalf ( "product compatibility check ran %d times, want %d" , got , want )
}
2026-10-07 22:10:34 +00:00
if raceSelected {
if _ , err := os . Stat ( raceMarker ) ; err != nil {
t . Fatalf ( "race-enabled profile did not run: %v" , err )
}
}
2026-10-05 19:04:55 +00:00
}
2026-10-05 19:38:50 +00:00
func TestBuildCommandWritesValidatedReceiptForConfiguredArtifacts ( t * testing . T ) {
repository := newCLIBuildRepository ( t , ` package main
import (
"fmt"
"os"
"gitea.maximumdirect.net/eric/ReleaseKit/buildinfo"
)
func main ( ) {
if len ( os . Args ) == 2 && os . Args [ 1 ] == "version" { fmt . Printf ( "fixture %s\n" , buildinfo . Version ( ) ) }
}
` )
var stdout , stderr bytes . Buffer
args := [ ] string { "build" , "--repo" , repository , "v1.2.3" }
if status := run ( context . Background ( ) , args , & stdout , & stderr ) ; status != 0 {
t . Fatalf ( "run() status = %d; stdout=%q stderr=%q" , status , stdout . String ( ) , stderr . String ( ) )
}
receiptPath := filepath . Join ( repository , "dist" , cli . BuildReceiptName )
receipt , err := cli . LoadBuildReceipt ( receiptPath )
if err != nil {
t . Fatal ( err )
}
commit := cliGitOutput ( t , repository , "rev-parse" , "HEAD" )
settings := cli . BuildSettings {
MainPackage : "./cmd/app" ,
BinaryName : "fixture" ,
Targets : [ ] string { fmt . Sprintf ( "%s/%s" , runtime . GOOS , runtime . GOARCH ) } ,
}
if ! receipt . MatchesBuild ( repository , commit , "v1.2.3" , settings ) || len ( receipt . Artifacts ) != 1 {
t . Fatalf ( "build receipt = %#v" , receipt )
}
if ! bytes . Contains ( stdout . Bytes ( ) , [ ] byte ( receipt . Artifacts [ 0 ] . Filename ) ) || ! bytes . Contains ( stdout . Bytes ( ) , [ ] byte ( receipt . Artifacts [ 0 ] . SHA256 ) ) {
t . Fatalf ( "stdout does not report receipt artifacts: %q" , stdout . String ( ) )
}
if got := cliGitOutput ( t , repository , "status" , "--porcelain" ) ; got != "" {
t . Fatalf ( "build output dirtied candidate: %s" , got )
}
}
func TestBuildCommandLeavesNoReceiptAfterSmokeFailure ( t * testing . T ) {
repository := newCLIBuildRepository ( t , ` package main
import (
"fmt"
"os"
)
func main ( ) { if len ( os . Args ) == 2 && os . Args [ 1 ] == "version" { fmt . Println ( "wrong" ) } }
` )
var stdout , stderr bytes . Buffer
args := [ ] string { "build" , "--repo" , repository , "v1.2.3" }
if status := run ( context . Background ( ) , args , & stdout , & stderr ) ; status != 1 {
t . Fatalf ( "run() status = %d; stdout=%q stderr=%q, want 1" , status , stdout . String ( ) , stderr . String ( ) )
}
if stdout . Len ( ) != 0 || ! bytes . Contains ( stderr . Bytes ( ) , [ ] byte ( "native version output" ) ) {
t . Fatalf ( "stdout=%q stderr=%q; want smoke failure without final output" , stdout . String ( ) , stderr . String ( ) )
}
if _ , err := os . Stat ( filepath . Join ( repository , "dist" , cli . BuildReceiptName ) ) ; ! os . IsNotExist ( err ) {
t . Fatalf ( "partial build wrote receipt: %v" , err )
}
filename := fmt . Sprintf ( "fixture-v1.2.3-%s-%s" , runtime . GOOS , runtime . GOARCH )
if runtime . GOOS == "windows" {
filename += ".exe"
}
if _ , err := os . Stat ( filepath . Join ( repository , "dist" , filename ) ) ; err != nil {
t . Fatalf ( "completed artifact not reported or retained: %v; stderr=%q" , err , stderr . String ( ) )
}
}
2026-10-05 17:47:50 +00:00
func TestCandidateCheckHelper ( t * testing . T ) {
if os . Getenv ( "RELEASEKIT_CHECK_HELPER" ) != "1" {
return
}
2026-10-06 17:15:46 +00:00
if orderPath := os . Getenv ( "RELEASEKIT_CHECK_ORDER_FILE" ) ; orderPath != "" {
file , err := os . OpenFile ( orderPath , os . O_CREATE | os . O_APPEND | os . O_WRONLY , 0 o600 )
if err != nil {
fmt . Fprintln ( os . Stderr , err )
os . Exit ( 9 )
}
if _ , err := fmt . Fprintln ( file , os . Getenv ( "RELEASEKIT_CHECK_NAME" ) ) ; err != nil {
_ = file . Close ( )
fmt . Fprintln ( os . Stderr , err )
os . Exit ( 9 )
}
if err := file . Close ( ) ; err != nil {
fmt . Fprintln ( os . Stderr , err )
os . Exit ( 9 )
}
}
2026-10-05 17:47:50 +00:00
fmt . Fprintf ( os . Stdout , "%s|%s" , os . Getenv ( "RELEASEKIT_VERSION" ) , os . Getenv ( "RELEASEKIT_CANDIDATE" ) )
os . Exit ( 0 )
}
func newCLIRepository ( t * testing . T ) string {
t . Helper ( )
repository := filepath . Join ( t . TempDir ( ) , "candidate repository" )
if err := os . Mkdir ( repository , 0 o700 ) ; err != nil {
t . Fatal ( err )
}
cliGitRun ( t , repository , "init" , "--initial-branch=main" )
cliGitRun ( t , repository , "config" , "user.name" , "ReleaseKit Test" )
cliGitRun ( t , repository , "config" , "user.email" , "releasekit@example.test" )
cliGitRun ( t , repository , "config" , "commit.gpgsign" , "false" )
write := func ( name , value string ) {
t . Helper ( )
path := filepath . Join ( repository , filepath . FromSlash ( name ) )
if err := os . MkdirAll ( filepath . Dir ( path ) , 0 o700 ) ; err != nil {
t . Fatal ( err )
}
if err := os . WriteFile ( path , [ ] byte ( value ) , 0 o600 ) ; err != nil {
t . Fatal ( err )
}
}
write ( "go.mod" , "module example.com/cli-candidate\n\ngo 1.25.0\n" )
write ( "candidate_test.go" , "package candidate\nimport \"testing\"\nfunc TestFixture(t *testing.T) {}\n" )
2026-10-07 22:10:34 +00:00
write ( "race_candidate_test.go" , ` //go:build race
package candidate
import (
"os"
"testing"
)
func TestRaceBuildSelection ( t * testing . T ) {
marker := os . Getenv ( "RELEASEKIT_RACE_MARKER" )
if marker == "" {
return
}
if err := os . WriteFile ( marker , [ ] byte ( "race enabled" ) , 0 o600 ) ; err != nil {
t . Fatal ( err )
}
}
` )
2026-10-05 17:47:50 +00:00
write ( "docs/releases/v1.2.3.md" , "# Example Product v1.2.3\n\nRelease note.\n" )
cliGitRun ( t , repository , "add" , "--all" )
cliGitRun ( t , repository , "commit" , "-m" , "candidate" )
return repository
}
2026-10-07 22:10:34 +00:00
func racePrerequisitesAvailable ( ) bool {
supported := runtime . GOOS == "darwin" && ( runtime . GOARCH == "amd64" || runtime . GOARCH == "arm64" ) ||
( runtime . GOOS == "freebsd" || runtime . GOOS == "linux" ) && ( runtime . GOARCH == "amd64" || runtime . GOARCH == "arm64" || runtime . GOARCH == "ppc64le" ) ||
runtime . GOOS == "windows" && runtime . GOARCH == "amd64"
if ! supported {
return false
}
cgo , err := exec . Command ( "go" , "env" , "CGO_ENABLED" ) . Output ( )
if err != nil || strings . TrimSpace ( string ( cgo ) ) != "1" {
return false
}
cc , err := exec . Command ( "go" , "env" , "CC" ) . Output ( )
if err != nil || len ( strings . Fields ( string ( cc ) ) ) == 0 {
return false
}
if _ , err := exec . LookPath ( strings . Fields ( string ( cc ) ) [ 0 ] ) ; err != nil {
return false
}
return true
}
2026-10-05 20:39:33 +00:00
func TestDeliverPageOnlyCreatesAndReusesRelease ( t * testing . T ) {
repository := newCLIRepository ( t )
state := & deliveryServerState { }
server := newDeliveryServer ( t , state )
defer server . Close ( )
2026-10-07 22:27:31 +00:00
settings := cli . Settings {
Product : "Example Product" ,
Validation : & cli . ValidationSettings { Go : cli . GoValidationSettings { CompileTargets : [ ] string { "linux/amd64" } } } ,
Delivery : & cli . DeliverySettings { BaseURL : server . URL , Owner : "owner" , Repository : "example" } ,
}
2026-10-05 20:39:33 +00:00
writeDeliveryProfile ( t , repository , settings )
state . commit = cliGitOutput ( t , repository , "rev-parse" , "HEAD" )
cliGitRun ( t , repository , "tag" , "v1.2.3" ) // Historical lightweight tags are valid delivery inputs.
t . Setenv ( "RELEASEKIT_GITEA_TOKEN" , "fixture-token" )
for attempt := 0 ; attempt < 2 ; attempt ++ {
var stdout , stderr bytes . Buffer
status := run ( context . Background ( ) , [ ] string { "deliver" , "--repo" , repository , "v1.2.3" } , & stdout , & stderr )
if status != 0 {
t . Fatalf ( "delivery attempt %d status=%d stdout=%q stderr=%q" , attempt + 1 , status , stdout . String ( ) , stderr . String ( ) )
}
if ! strings . Contains ( stdout . String ( ) , "state complete" ) || ! strings . Contains ( stdout . String ( ) , "release: " + server . URL + "/releases/10" ) {
t . Fatalf ( "delivery output = %q" , stdout . String ( ) )
}
}
if state . releaseCreates != 1 || state . assetUploads != 0 || state . assetListReads != 0 {
t . Fatalf ( "page-only calls = creates:%d asset uploads:%d asset reads:%d" , state . releaseCreates , state . assetUploads , state . assetListReads )
}
if state . noteBody != "# Example Product v1.2.3\n\nRelease note.\n" {
t . Fatalf ( "hosted note body = %q" , state . noteBody )
}
}
func TestDeliverBuildsTaggedArtifactsAndReusesAssets ( t * testing . T ) {
repository := newDeliveryBuildRepository ( t )
2026-10-06 18:35:45 +00:00
temporaryRoot := t . TempDir ( )
t . Setenv ( "TMPDIR" , temporaryRoot )
2026-10-05 20:39:33 +00:00
state := & deliveryServerState { }
server := newDeliveryServer ( t , state )
defer server . Close ( )
settings := cli . Settings {
Product : "Example Product" ,
Build : & cli . BuildSettings { MainPackage : "." , BinaryName : "helper" , Targets : [ ] string { "linux/amd64" } } ,
Delivery : & cli . DeliverySettings { BaseURL : server . URL , Owner : "owner" , Repository : "example" } ,
}
writeDeliveryProfile ( t , repository , settings )
state . commit = cliGitOutput ( t , repository , "rev-parse" , "HEAD" )
cliGitRun ( t , repository , "tag" , "v1.2.3" )
cliGitRun ( t , repository , "checkout" , "--detach" , "v1.2.3" )
t . Setenv ( "RELEASEKIT_GITEA_TOKEN" , "fixture-token" )
for attempt := 0 ; attempt < 2 ; attempt ++ {
var stdout , stderr bytes . Buffer
status := run ( context . Background ( ) , [ ] string { "deliver" , "--repo" , repository , "v1.2.3" } , & stdout , & stderr )
if status != 0 {
t . Fatalf ( "delivery attempt %d status=%d stdout=%q stderr=%q" , attempt + 1 , status , stdout . String ( ) , stderr . String ( ) )
}
2026-10-06 18:35:45 +00:00
if attempt == 1 && ( ! strings . Contains ( stdout . String ( ) , "matching helper-v1.2.3-linux-amd64" ) || ! strings . Contains ( stdout . String ( ) , "matching SHA256SUMS" ) ) {
t . Fatalf ( "second delivery did not report matching assets: %q" , stdout . String ( ) )
}
2026-10-05 20:39:33 +00:00
}
if state . releaseCreates != 1 || state . assetUploads != 2 {
t . Fatalf ( "delivery calls = creates:%d asset uploads:%d" , state . releaseCreates , state . assetUploads )
}
if _ , ok := state . assets [ "helper-v1.2.3-linux-amd64" ] ; ! ok {
t . Fatalf ( "binary was not uploaded: %#v" , state . assets )
}
if _ , ok := state . assets [ "SHA256SUMS" ] ; ! ok {
t . Fatalf ( "checksum manifest was not uploaded: %#v" , state . assets )
}
2026-10-06 18:35:45 +00:00
if entries , err := os . ReadDir ( temporaryRoot ) ; err != nil || len ( entries ) != 0 {
t . Fatalf ( "facade-owned build output remains after delivery: entries=%v error=%v" , entries , err )
}
}
2026-10-06 19:26:54 +00:00
func TestCIDeliverLoadsTaggedProfileAndSkipsUnavailablePageOnlyDependencies ( t * testing . T ) {
repository := newCLIRepository ( t )
writeRepositoryFile ( t , repository , "go.mod" , "module example.com/cli-candidate\n\ngo 1.25.0\n\nrequire example.com/unavailable/module v1.2.3\n" )
cliGitRun ( t , repository , "add" , "go.mod" )
cliGitRun ( t , repository , "commit" , "-m" , "declare unavailable dependency" )
state := & deliveryServerState { }
server := newDeliveryServer ( t , state )
defer server . Close ( )
writeDeliveryProfileAt ( t , repository , "profiles/ci-delivery.json" , cli . Settings {
Product : "Example Product" ,
Delivery : & cli . DeliverySettings { BaseURL : server . URL , Owner : "owner" , Repository : "example" } ,
} )
expectedCommit := pushTaggedCLIRepository ( t , repository )
state . commit = expectedCommit
writeDeliveryProfileAt ( t , repository , "profiles/ci-delivery.json" , cli . Settings { Product : "Later Branch Profile" } )
t . Setenv ( "RELEASEKIT_GITEA_TOKEN" , "fixture-token" )
t . Setenv ( "GOPROXY" , "off" )
t . Setenv ( "GOSUMDB" , "off" )
var stdout , stderr bytes . Buffer
args := [ ] string { "ci-deliver" , "--repo" , repository , "--config" , "profiles/ci-delivery.json" , "--commit" , expectedCommit , "v1.2.3" }
if status := run ( context . Background ( ) , args , & stdout , & stderr ) ; status != 0 {
t . Fatalf ( "run() status=%d stdout=%q stderr=%q" , status , stdout . String ( ) , stderr . String ( ) )
}
if state . releaseCreates != 1 || state . apiRequests == 0 {
t . Fatalf ( "page delivery did not reach the server: creates=%d requests=%d" , state . releaseCreates , state . apiRequests )
}
if got := cliGitOutput ( t , repository , "rev-parse" , "HEAD" ) ; got != expectedCommit {
t . Fatalf ( "prepared HEAD = %s, want %s" , got , expectedCommit )
}
}
func TestCIDeliverRejectsWrongCommitBeforeGoOrHostedRequests ( t * testing . T ) {
repository := newCLIRepository ( t )
writeRepositoryFile ( t , repository , "go.mod" , "module example.com/cli-candidate\n\ngo 1.25.0\n\nrequire example.com/unavailable/module v1.2.3\n" )
cliGitRun ( t , repository , "add" , "go.mod" )
cliGitRun ( t , repository , "commit" , "-m" , "declare unavailable dependency" )
state := & deliveryServerState { }
server := newDeliveryServer ( t , state )
defer server . Close ( )
writeDeliveryProfile ( t , repository , cli . Settings {
Product : "Example Product" ,
Build : & cli . BuildSettings { MainPackage : "." , BinaryName : "helper" , Targets : [ ] string { "linux/amd64" } } ,
Delivery : & cli . DeliverySettings { BaseURL : server . URL , Owner : "owner" , Repository : "example" } ,
} )
expectedCommit := pushTaggedCLIRepository ( t , repository )
state . commit = expectedCommit
shimDir := t . TempDir ( )
markerPath := filepath . Join ( shimDir , "go-invoked" )
goShim := "#!/bin/sh\n: > \"$RELEASEKIT_GO_MARKER\"\nexit 99\n"
if err := os . WriteFile ( filepath . Join ( shimDir , "go" ) , [ ] byte ( goShim ) , 0 o700 ) ; err != nil {
t . Fatal ( err )
}
t . Setenv ( "RELEASEKIT_GO_MARKER" , markerPath )
t . Setenv ( "PATH" , shimDir + string ( os . PathListSeparator ) + os . Getenv ( "PATH" ) )
t . Setenv ( "RELEASEKIT_GITEA_TOKEN" , "fixture-token" )
var stdout , stderr bytes . Buffer
wrongCommit := strings . Repeat ( "0" , 40 )
args := [ ] string { "ci-deliver" , "--repo" , repository , "--commit" , wrongCommit , "v1.2.3" }
if status := run ( context . Background ( ) , args , & stdout , & stderr ) ; status != 1 {
t . Fatalf ( "run() status=%d stdout=%q stderr=%q, want preparation failure" , status , stdout . String ( ) , stderr . String ( ) )
}
if ! strings . Contains ( stderr . String ( ) , "resolves to commit" ) {
t . Fatalf ( "stderr=%q, want expected-commit mismatch" , stderr . String ( ) )
}
if _ , err := os . Stat ( markerPath ) ; ! os . IsNotExist ( err ) {
t . Fatalf ( "Go was invoked before commit agreement: %v" , err )
}
if state . apiRequests != 0 {
t . Fatalf ( "hosted API requests = %d, want 0" , state . apiRequests )
}
}
func TestCIDeliverPreparesDependenciesAndBuildsConfiguredTargets ( t * testing . T ) {
repository := newDeliveryBuildRepository ( t )
state := & deliveryServerState { }
server := newDeliveryServer ( t , state )
defer server . Close ( )
writeDeliveryProfile ( t , repository , cli . Settings {
Product : "Example Product" ,
Build : & cli . BuildSettings { MainPackage : "." , BinaryName : "helper" , Targets : [ ] string { fmt . Sprintf ( "%s/%s" , runtime . GOOS , runtime . GOARCH ) } } ,
Delivery : & cli . DeliverySettings { BaseURL : server . URL , Owner : "owner" , Repository : "example" } ,
} )
expectedCommit := pushTaggedCLIRepository ( t , repository )
state . commit = expectedCommit
t . Setenv ( "RELEASEKIT_GITEA_TOKEN" , "fixture-token" )
var stdout , stderr bytes . Buffer
args := [ ] string { "ci-deliver" , "--repo" , repository , "--commit" , expectedCommit , "v1.2.3" }
if status := run ( context . Background ( ) , args , & stdout , & stderr ) ; status != 0 {
t . Fatalf ( "run() status=%d stdout=%q stderr=%q" , status , stdout . String ( ) , stderr . String ( ) )
}
if state . releaseCreates != 1 || state . assetUploads != 2 {
t . Fatalf ( "delivery counts = creates:%d uploads:%d" , state . releaseCreates , state . assetUploads )
}
if _ , ok := state . assets [ "helper-v1.2.3-" + runtime . GOOS + "-" + runtime . GOARCH ] ; ! ok {
t . Fatalf ( "binary not uploaded: %#v" , state . assets )
}
}
func TestCIDeliverValidatesCredentialsBeforePreparingDependencies ( t * testing . T ) {
repository := newDeliveryBuildRepository ( t )
state := & deliveryServerState { }
server := newDeliveryServer ( t , state )
defer server . Close ( )
writeDeliveryProfile ( t , repository , cli . Settings {
Product : "Example Product" ,
Build : & cli . BuildSettings { MainPackage : "." , BinaryName : "helper" , Targets : [ ] string { "linux/amd64" } } ,
Delivery : & cli . DeliverySettings { BaseURL : server . URL , Owner : "owner" , Repository : "example" } ,
} )
expectedCommit := pushTaggedCLIRepository ( t , repository )
state . commit = expectedCommit
shimDir := t . TempDir ( )
markerPath := filepath . Join ( shimDir , "go-invoked" )
goShim := "#!/bin/sh\n: > \"$RELEASEKIT_GO_MARKER\"\nexit 99\n"
if err := os . WriteFile ( filepath . Join ( shimDir , "go" ) , [ ] byte ( goShim ) , 0 o700 ) ; err != nil {
t . Fatal ( err )
}
t . Setenv ( "RELEASEKIT_GO_MARKER" , markerPath )
t . Setenv ( "PATH" , shimDir + string ( os . PathListSeparator ) + os . Getenv ( "PATH" ) )
t . Setenv ( "RELEASEKIT_GITEA_TOKEN" , "" )
var stdout , stderr bytes . Buffer
args := [ ] string { "ci-deliver" , "--repo" , repository , "--commit" , expectedCommit , "v1.2.3" }
if status := run ( context . Background ( ) , args , & stdout , & stderr ) ; status != 2 {
t . Fatalf ( "run() status=%d stdout=%q stderr=%q, want credential error" , status , stdout . String ( ) , stderr . String ( ) )
}
if ! strings . Contains ( stderr . String ( ) , "non-empty token" ) {
t . Fatalf ( "stderr=%q, want missing-credential error" , stderr . String ( ) )
}
if _ , err := os . Stat ( markerPath ) ; ! os . IsNotExist ( err ) {
t . Fatalf ( "Go was invoked before credential validation: %v" , err )
}
if state . apiRequests != 0 {
t . Fatalf ( "hosted API requests = %d, want 0" , state . apiRequests )
}
}
2026-10-06 18:35:45 +00:00
func TestDeliverRejectsInvalidTaggedSourceBeforeManagedBuild ( t * testing . T ) {
repository := newDeliveryBuildRepository ( t )
state := & deliveryServerState { }
server := newDeliveryServer ( t , state )
defer server . Close ( )
settings := cli . Settings {
Product : "Example Product" ,
Build : & cli . BuildSettings { MainPackage : "." , BinaryName : "helper" , Targets : [ ] string { "linux/amd64" } } ,
Delivery : & cli . DeliverySettings { BaseURL : server . URL , Owner : "owner" , Repository : "example" } ,
}
writeDeliveryProfile ( t , repository , settings )
state . commit = cliGitOutput ( t , repository , "rev-parse" , "HEAD" )
cliGitRun ( t , repository , "tag" , "v1.2.3" )
if err := os . WriteFile ( filepath . Join ( repository , "uncommitted.txt" ) , [ ] byte ( "dirty" ) , 0600 ) ; err != nil {
t . Fatal ( err )
}
t . Setenv ( "RELEASEKIT_GITEA_TOKEN" , "fixture-token" )
var stdout , stderr bytes . Buffer
status := run ( context . Background ( ) , [ ] string { "deliver" , "--repo" , repository , "v1.2.3" } , & stdout , & stderr )
if status != 1 || state . apiRequests != 0 || strings . Contains ( stderr . String ( ) , "building " ) {
t . Fatalf ( "invalid-source delivery status=%d requests=%d stdout=%q stderr=%q" , status , state . apiRequests , stdout . String ( ) , stderr . String ( ) )
}
2026-10-05 20:39:33 +00:00
}
2026-10-06 17:54:22 +00:00
func TestDeliverFailureReportsRecoveryForPartialConflictAndUnknownStates ( t * testing . T ) {
for _ , test := range [ ] struct {
name string
state string
status int
recovery [ ] string
assetObservation string
missingObservation string
} {
{
name : "partial" ,
status : 1 ,
recovery : [ ] string { "recovery: fix the reported cause" , "retry this version" } ,
assetObservation : "pending helper-v1.2.3-linux-amd64" ,
} ,
{
name : "conflict" ,
status : 1 ,
recovery : [ ] string { "investigate conflicting hosted content" , "new version" , "retain the original build artifacts" } ,
assetObservation : "conflicting helper-v1.2.3-linux-amd64" ,
missingObservation : "pending SHA256SUMS" ,
} ,
{
name : "unknown" ,
status : 3 ,
recovery : [ ] string { "resolve the remote state before retrying" } ,
assetObservation : "uncertain helper-v1.2.3-linux-amd64" ,
missingObservation : "pending SHA256SUMS" ,
} ,
} {
t . Run ( test . name , func ( t * testing . T ) {
repository := newCLIRepository ( t )
state := & deliveryServerState { }
server := newDeliveryServer ( t , state )
defer server . Close ( )
build := cli . BuildSettings { MainPackage : "." , BinaryName : "helper" , Targets : [ ] string { "linux/amd64" } }
writeDeliveryProfile ( t , repository , cli . Settings {
Product : "Example Product" ,
Build : & build ,
Delivery : & cli . DeliverySettings { BaseURL : server . URL , Owner : "owner" , Repository : "example" } ,
} )
state . commit = cliGitOutput ( t , repository , "rev-parse" , "HEAD" )
cliGitRun ( t , repository , "tag" , "v1.2.3" )
assetsDir , _ := writeDeliveryReceipt ( t , repository , build )
switch test . name {
case "partial" :
state . failAssetName = "helper-v1.2.3-linux-amd64"
case "conflict" :
state . assets [ "helper-v1.2.3-linux-amd64" ] = [ ] byte ( "different hosted bytes" )
case "unknown" :
state . ambiguousAssetName = "helper-v1.2.3-linux-amd64"
}
t . Setenv ( "RELEASEKIT_GITEA_TOKEN" , "fixture-token" )
var stdout , stderr bytes . Buffer
status := run ( context . Background ( ) , [ ] string { "deliver" , "--repo" , repository , "--assets" , assetsDir , "v1.2.3" } , & stdout , & stderr )
if status != test . status || ! strings . Contains ( stderr . String ( ) , "state=" + test . name ) {
t . Fatalf ( "status=%d output=%q want status=%d state=%s" , status , stderr . String ( ) , test . status , test . name )
}
for _ , phrase := range test . recovery {
if ! strings . Contains ( stderr . String ( ) , phrase ) {
t . Errorf ( "delivery output does not include recovery guidance %q: %s" , phrase , stderr . String ( ) )
}
}
if ! strings . Contains ( stderr . String ( ) , test . assetObservation ) {
t . Errorf ( "delivery output does not include asset observation %q: %s" , test . assetObservation , stderr . String ( ) )
}
if test . missingObservation != "" && ! strings . Contains ( stderr . String ( ) , test . missingObservation ) {
t . Errorf ( "delivery output does not include later missing asset %q: %s" , test . missingObservation , stderr . String ( ) )
}
if test . name == "unknown" && strings . Contains ( stderr . String ( ) , "pending helper-v1.2.3-linux-amd64" ) {
t . Errorf ( "uncertain asset also appears pending: %s" , stderr . String ( ) )
}
} )
}
}
2026-10-05 20:39:33 +00:00
func TestDeliverRejectsWrongHostedTagBeforeReleaseWrite ( t * testing . T ) {
repository := newCLIRepository ( t )
state := & deliveryServerState { remoteCommit : strings . Repeat ( "0" , 40 ) }
server := newDeliveryServer ( t , state )
defer server . Close ( )
writeDeliveryProfile ( t , repository , cli . Settings { Product : "Example Product" , Delivery : & cli . DeliverySettings { BaseURL : server . URL , Owner : "owner" , Repository : "example" } } )
state . commit = cliGitOutput ( t , repository , "rev-parse" , "HEAD" )
cliGitRun ( t , repository , "tag" , "v1.2.3" )
t . Setenv ( "RELEASEKIT_GITEA_TOKEN" , "fixture-token" )
var stdout , stderr bytes . Buffer
status := run ( context . Background ( ) , [ ] string { "deliver" , "--repo" , repository , "v1.2.3" } , & stdout , & stderr )
if status != 1 || state . releaseCreates != 0 || state . assetUploads != 0 {
t . Fatalf ( "wrong hosted tag status=%d creates=%d uploads=%d stdout=%q stderr=%q" , status , state . releaseCreates , state . assetUploads , stdout . String ( ) , stderr . String ( ) )
}
}
func TestDeliverRequiresCredentialBeforeRemoteRequests ( t * testing . T ) {
repository := newCLIRepository ( t )
state := & deliveryServerState { }
server := newDeliveryServer ( t , state )
defer server . Close ( )
writeDeliveryProfile ( t , repository , cli . Settings { Product : "Example Product" , Delivery : & cli . DeliverySettings { BaseURL : server . URL , Owner : "owner" , Repository : "example" , TokenEnv : "RELEASEKIT_TEST_DELIVERY_TOKEN" } } )
state . commit = cliGitOutput ( t , repository , "rev-parse" , "HEAD" )
cliGitRun ( t , repository , "tag" , "v1.2.3" )
t . Setenv ( "RELEASEKIT_TEST_DELIVERY_TOKEN" , "" )
var stdout , stderr bytes . Buffer
status := run ( context . Background ( ) , [ ] string { "deliver" , "--repo" , repository , "v1.2.3" } , & stdout , & stderr )
if status != 2 || state . apiRequests != 0 || ! strings . Contains ( stderr . String ( ) , "RELEASEKIT_TEST_DELIVERY_TOKEN" ) {
t . Fatalf ( "missing credential status=%d requests=%d stdout=%q stderr=%q" , status , state . apiRequests , stdout . String ( ) , stderr . String ( ) )
}
}
func TestDeliverRejectsCheckoutThatDiffersFromLocalTag ( t * testing . T ) {
repository := newCLIRepository ( t )
state := & deliveryServerState { }
server := newDeliveryServer ( t , state )
defer server . Close ( )
writeDeliveryProfile ( t , repository , cli . Settings { Product : "Example Product" , Delivery : & cli . DeliverySettings { BaseURL : server . URL , Owner : "owner" , Repository : "example" } } )
state . commit = cliGitOutput ( t , repository , "rev-parse" , "HEAD" )
cliGitRun ( t , repository , "tag" , "v1.2.3" )
if err := os . WriteFile ( filepath . Join ( repository , "later.txt" ) , [ ] byte ( "later commit" ) , 0 o600 ) ; err != nil {
t . Fatal ( err )
}
cliGitRun ( t , repository , "add" , "later.txt" )
cliGitRun ( t , repository , "commit" , "-m" , "advance checkout" )
t . Setenv ( "RELEASEKIT_GITEA_TOKEN" , "fixture-token" )
var stdout , stderr bytes . Buffer
status := run ( context . Background ( ) , [ ] string { "deliver" , "--repo" , repository , "v1.2.3" } , & stdout , & stderr )
if status != 1 || state . apiRequests != 0 || state . releaseCreates != 0 {
t . Fatalf ( "mismatched checkout status=%d requests=%d creates=%d stdout=%q stderr=%q" , status , state . apiRequests , state . releaseCreates , stdout . String ( ) , stderr . String ( ) )
}
if got := cliGitOutput ( t , repository , "rev-parse" , "refs/tags/v1.2.3" ) ; got != state . commit {
t . Fatalf ( "delivery moved tag from %s to %s" , state . commit , got )
}
}
func TestDeliverRejectsTamperedAssetsAndStaleReceiptBeforeRemoteWrites ( t * testing . T ) {
for _ , mode := range [ ] string { "valid receipt" , "tampered" , "stale settings" } {
t . Run ( mode , func ( t * testing . T ) {
repository := newCLIRepository ( t )
state := & deliveryServerState { }
server := newDeliveryServer ( t , state )
defer server . Close ( )
current := cli . BuildSettings { MainPackage : "." , BinaryName : "helper" , Targets : [ ] string { "linux/amd64" } }
settings := cli . Settings { Product : "Example Product" , Build : & current , Delivery : & cli . DeliverySettings { BaseURL : server . URL , Owner : "owner" , Repository : "example" } }
writeDeliveryProfile ( t , repository , settings )
state . commit = cliGitOutput ( t , repository , "rev-parse" , "HEAD" )
cliGitRun ( t , repository , "tag" , "v1.2.3" )
receiptSettings := current
if mode == "stale settings" {
receiptSettings . BinaryName = "previous"
}
assetsDir , artifactPath := writeDeliveryReceipt ( t , repository , receiptSettings )
if mode == "tampered" {
if err := os . WriteFile ( artifactPath , [ ] byte ( "tampered artifact" ) , 0 o600 ) ; err != nil {
t . Fatal ( err )
}
}
t . Setenv ( "RELEASEKIT_GITEA_TOKEN" , "fixture-token" )
var stdout , stderr bytes . Buffer
status := run ( context . Background ( ) , [ ] string { "deliver" , "--repo" , repository , "--assets" , assetsDir , "v1.2.3" } , & stdout , & stderr )
if mode == "valid receipt" {
if status != 0 || state . releaseCreates != 1 || state . assetUploads != 2 {
t . Fatalf ( "valid receipt status=%d creates=%d uploads=%d stdout=%q stderr=%q" , status , state . releaseCreates , state . assetUploads , stdout . String ( ) , stderr . String ( ) )
}
2026-10-06 18:35:45 +00:00
if _ , err := os . Stat ( artifactPath ) ; err != nil {
t . Fatalf ( "prebuilt artifact was not retained: %v" , err )
}
2026-10-05 20:39:33 +00:00
return
}
if status != 1 || state . apiRequests != 0 || state . releaseCreates != 0 || state . assetUploads != 0 {
t . Fatalf ( "%s receipt status=%d requests=%d stdout=%q stderr=%q" , mode , status , state . apiRequests , stdout . String ( ) , stderr . String ( ) )
}
} )
}
}
func writeDeliveryReceipt ( t * testing . T , repository string , settings cli . BuildSettings ) ( string , string ) {
t . Helper ( )
assetsDir := t . TempDir ( )
filename := settings . BinaryName + "-v1.2.3-linux-amd64"
artifactPath := filepath . Join ( assetsDir , filename )
content := [ ] byte ( "fixture artifact" )
if err := os . WriteFile ( artifactPath , content , 0 o600 ) ; err != nil {
t . Fatal ( err )
}
resolvedRepository , err := filepath . Abs ( repository )
if err != nil {
t . Fatal ( err )
}
digest := sha256 . Sum256 ( content )
target := cli . ReceiptTarget { OS : "linux" , Arch : "amd64" }
receipt := cli . BuildReceipt {
RepositoryDir : resolvedRepository , CandidateCommit : cliGitOutput ( t , repository , "rev-parse" , "HEAD" ) ,
2026-10-06 00:51:56 +00:00
SourceVerified : true ,
Version : "v1.2.3" , MainPackage : settings . MainPackage , BinaryName : settings . BinaryName ,
2026-10-05 20:39:33 +00:00
OutputDir : assetsDir , Targets : [ ] cli . ReceiptTarget { target } , Compiler : "go version fixture" ,
Artifacts : [ ] cli . ReceiptArtifact { { Path : artifactPath , Filename : filename , Target : target , SHA256 : hex . EncodeToString ( digest [ : ] ) } } ,
}
if _ , err := cli . WriteBuildReceipt ( assetsDir , receipt ) ; err != nil {
t . Fatal ( err )
}
return assetsDir , artifactPath
}
type deliveryServerState struct {
2026-10-06 00:54:49 +00:00
malformedCreate bool
failAssetListing bool
failAssetDownloads bool
2026-10-06 17:54:22 +00:00
failAssetName string
ambiguousAssetName string
2026-10-06 00:54:49 +00:00
commit string
product string
remoteCommit string
apiRequests int
pageExists bool
releaseCreates int
assetUploads int
assetListReads int
noteBody string
assets map [ string ] [ ] byte
2026-10-05 20:39:33 +00:00
}
func newDeliveryServer ( t * testing . T , state * deliveryServerState ) * httptest . Server {
t . Helper ( )
if state . assets == nil {
state . assets = make ( map [ string ] [ ] byte )
}
serverURL := ""
handler := http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
if strings . HasPrefix ( r . URL . Path , "/api/" ) {
state . apiRequests ++
if r . Header . Get ( "Authorization" ) != "token fixture-token" {
t . Errorf ( "authorization = %q" , r . Header . Get ( "Authorization" ) )
}
}
switch r . URL . Path {
case "/api/v1/repos/owner/example/tags/v1.2.3" :
if state . commit == "" {
t . Error ( "tag source commit was not initialized" )
}
commit := state . remoteCommit
if commit == "" {
commit = state . commit
}
_ , _ = fmt . Fprintf ( w , ` { "name":"v1.2.3","commit": { "sha":%q}} ` , commit )
case "/api/v1/repos/owner/example/releases/tags/v1.2.3" :
if ! state . pageExists {
http . NotFound ( w , r )
return
}
writeDeliveryRelease ( w , serverURL , state )
case "/api/v1/repos/owner/example/releases" :
if r . Method != http . MethodPost {
t . Errorf ( "release request method = %s" , r . Method )
http . Error ( w , "method" , 405 )
return
}
var payload struct {
Tag string ` json:"tag_name" `
Target string ` json:"target_commitish" `
Name string ` json:"name" `
Body string ` json:"body" `
}
if err := json . NewDecoder ( r . Body ) . Decode ( & payload ) ; err != nil {
t . Errorf ( "decode create payload: %v" , err )
}
if payload . Tag != "v1.2.3" || payload . Target != state . commit {
t . Errorf ( "release source payload = %#v" , payload )
}
state . pageExists , state . releaseCreates , state . noteBody = true , state . releaseCreates + 1 , payload . Body
2026-10-06 00:53:33 +00:00
if state . malformedCreate {
w . WriteHeader ( http . StatusCreated )
_ = json . NewEncoder ( w ) . Encode ( map [ string ] any { "id" : 10 , "tag_name" : "v1.2.3" , "name" : "inconsistent response" , "body" : payload . Body } )
return
}
2026-10-05 20:39:33 +00:00
writeDeliveryRelease ( w , serverURL , state )
case "/api/v1/repos/owner/example/releases/10/assets" :
if r . Method == http . MethodGet {
state . assetListReads ++
2026-10-06 00:53:33 +00:00
if state . failAssetListing {
http . Error ( w , "unavailable" , 503 )
return
}
2026-10-05 20:39:33 +00:00
list := make ( [ ] map [ string ] any , 0 , len ( state . assets ) )
for name , content := range state . assets {
list = append ( list , map [ string ] any { "id" : len ( list ) + 1 , "name" : name , "size" : len ( content ) , "browser_download_url" : serverURL + "/files/" + name } )
}
_ = json . NewEncoder ( w ) . Encode ( list )
return
}
if r . Method != http . MethodPost {
t . Errorf ( "asset request method = %s" , r . Method )
http . Error ( w , "method" , 405 )
return
}
name := r . URL . Query ( ) . Get ( "name" )
file , _ , err := r . FormFile ( "attachment" )
if err != nil {
t . Errorf ( "read asset upload %q: %v" , name , err )
http . Error ( w , "multipart" , 400 )
return
}
content , err := io . ReadAll ( file )
_ = file . Close ( )
if err != nil {
t . Errorf ( "read asset bytes: %v" , err )
}
if _ , exists := state . assets [ name ] ; exists {
t . Errorf ( "asset %q uploaded more than once" , name )
}
2026-10-06 17:54:22 +00:00
if name == state . failAssetName {
http . Error ( w , "upload failed" , http . StatusBadRequest )
return
}
if name == state . ambiguousAssetName {
http . Error ( w , "upload outcome ambiguous" , http . StatusInternalServerError )
return
}
2026-10-05 20:39:33 +00:00
state . assets [ name ] = content
state . assetUploads ++
_ = json . NewEncoder ( w ) . Encode ( map [ string ] any { "id" : state . assetUploads , "name" : name , "size" : len ( content ) } )
default :
if strings . HasPrefix ( r . URL . Path , "/files/" ) {
2026-10-06 00:54:49 +00:00
if state . failAssetDownloads {
http . Error ( w , "unavailable" , 503 )
return
}
2026-10-05 20:39:33 +00:00
name := strings . TrimPrefix ( r . URL . Path , "/files/" )
content , ok := state . assets [ name ]
if ! ok {
http . NotFound ( w , r )
return
}
_ , _ = w . Write ( content )
return
}
t . Errorf ( "unexpected delivery request %s %s" , r . Method , r . URL . String ( ) )
http . NotFound ( w , r )
}
} )
server := httptest . NewServer ( handler )
serverURL = server . URL
return server
}
func writeDeliveryRelease ( w http . ResponseWriter , serverURL string , state * deliveryServerState ) {
w . Header ( ) . Set ( "Content-Type" , "application/json" )
2026-10-05 20:56:57 +00:00
product := state . product
if product == "" {
product = "Example Product"
}
_ = json . NewEncoder ( w ) . Encode ( map [ string ] any { "id" : 10 , "html_url" : serverURL + "/releases/10" , "tag_name" : "v1.2.3" , "target_commitish" : state . commit , "name" : product + " v1.2.3" , "body" : state . noteBody , "draft" : false , "prerelease" : false } )
2026-10-05 20:39:33 +00:00
}
func writeDeliveryProfile ( t * testing . T , repository string , settings cli . Settings ) {
2026-10-06 19:26:54 +00:00
writeDeliveryProfileAt ( t , repository , "releasekit.json" , settings )
}
func writeDeliveryProfileAt ( t * testing . T , repository , name string , settings cli . Settings ) {
2026-10-05 20:39:33 +00:00
t . Helper ( )
contents , err := json . Marshal ( settings )
if err != nil {
t . Fatal ( err )
}
2026-10-06 19:26:54 +00:00
path := filepath . Join ( repository , filepath . FromSlash ( name ) )
if err := os . MkdirAll ( filepath . Dir ( path ) , 0 o700 ) ; err != nil {
2026-10-05 20:39:33 +00:00
t . Fatal ( err )
}
2026-10-06 19:26:54 +00:00
if err := os . WriteFile ( path , contents , 0 o600 ) ; err != nil {
t . Fatal ( err )
}
cliGitRun ( t , repository , "add" , name )
2026-10-05 20:39:33 +00:00
cliGitRun ( t , repository , "commit" , "-m" , "configure delivery" )
}
func newDeliveryBuildRepository ( t * testing . T ) string {
t . Helper ( )
repository := filepath . Join ( t . TempDir ( ) , "tagged application" )
if err := os . Mkdir ( repository , 0 o700 ) ; err != nil {
t . Fatal ( err )
}
cliGitRun ( t , repository , "init" , "--initial-branch=main" )
cliGitRun ( t , repository , "config" , "user.name" , "ReleaseKit Test" )
cliGitRun ( t , repository , "config" , "user.email" , "releasekit@example.test" )
files := map [ string ] string {
"go.mod" : "module example.com/tagged-app\n\ngo 1.25.0\n" ,
"main.go" : "package main\nimport \"fmt\"\nfunc main() { fmt.Println(\"helper v1.2.3\") }\n" ,
"docs/releases/v1.2.3.md" : "# Example Product v1.2.3\n\nRelease note.\n" ,
}
for name , contents := range files {
path := filepath . Join ( repository , filepath . FromSlash ( name ) )
if err := os . MkdirAll ( filepath . Dir ( path ) , 0 o700 ) ; err != nil {
t . Fatal ( err )
}
if err := os . WriteFile ( path , [ ] byte ( contents ) , 0 o600 ) ; err != nil {
t . Fatal ( err )
}
}
cliGitRun ( t , repository , "add" , "--all" )
cliGitRun ( t , repository , "commit" , "-m" , "tagged candidate" )
return repository
}
2026-10-05 19:38:50 +00:00
func newCLIBuildRepository ( t * testing . T , mainSource string ) string {
t . Helper ( )
repository := newCLIRepository ( t )
root , err := filepath . Abs ( filepath . Join ( ".." , ".." ) )
if err != nil {
t . Fatal ( err )
}
module := fmt . Sprintf ( "module example.com/cli-candidate\n\ngo 1.25.0\n\nrequire gitea.maximumdirect.net/eric/ReleaseKit v0.0.0\n\nreplace gitea.maximumdirect.net/eric/ReleaseKit => %s\n" , filepath . ToSlash ( root ) )
write := func ( name , contents string ) {
t . Helper ( )
path := filepath . Join ( repository , filepath . FromSlash ( name ) )
if err := os . MkdirAll ( filepath . Dir ( path ) , 0 o700 ) ; err != nil {
t . Fatal ( err )
}
if err := os . WriteFile ( path , [ ] byte ( contents ) , 0 o600 ) ; err != nil {
t . Fatal ( err )
}
}
write ( "go.mod" , module )
write ( "cmd/app/main.go" , mainSource )
write ( "releasekit.json" , fmt . Sprintf ( ` { "product":"Example Product","build": { "main_package":"./cmd/app","binary_name":"fixture","targets":[%q]}} ` , fmt . Sprintf ( "%s/%s" , runtime . GOOS , runtime . GOARCH ) ) )
write ( ".gitignore" , "dist/\n" )
cliGitRun ( t , repository , "add" , "--all" )
cliGitRun ( t , repository , "commit" , "-m" , "configure build" )
return repository
}
2026-10-05 17:47:50 +00:00
func cliGitRun ( t * testing . T , repository string , args ... string ) {
t . Helper ( )
commandArgs := append ( [ ] string { "git" } , args ... )
command := exec . Command ( commandArgs [ 0 ] , commandArgs [ 1 : ] ... )
command . Dir = repository
output , err := command . CombinedOutput ( )
if err != nil {
t . Fatalf ( "%v: %v\n%s" , commandArgs , err , output )
}
}
2026-10-06 19:26:54 +00:00
func writeRepositoryFile ( t * testing . T , repository , name , contents string ) {
t . Helper ( )
path := filepath . Join ( repository , filepath . FromSlash ( name ) )
if err := os . MkdirAll ( filepath . Dir ( path ) , 0 o700 ) ; err != nil {
t . Fatal ( err )
}
if err := os . WriteFile ( path , [ ] byte ( contents ) , 0 o600 ) ; err != nil {
t . Fatal ( err )
}
}
func pushTaggedCLIRepository ( t * testing . T , repository string ) string {
t . Helper ( )
cliGitRun ( t , repository , "tag" , "v1.2.3" )
commit := cliGitOutput ( t , repository , "rev-parse" , "v1.2.3^{commit}" )
remote := filepath . Join ( t . TempDir ( ) , "origin.git" )
cliGitRun ( t , repository , "init" , "--bare" , remote )
cliGitRun ( t , repository , "remote" , "add" , "origin" , remote )
cliGitRun ( t , repository , "push" , "origin" , "refs/tags/v1.2.3" )
return commit
}
2026-10-05 17:47:50 +00:00
func cliGitOutput ( t * testing . T , repository string , args ... string ) string {
t . Helper ( )
commandArgs := append ( [ ] string { "git" } , args ... )
command := exec . Command ( commandArgs [ 0 ] , commandArgs [ 1 : ] ... )
command . Dir = repository
output , err := command . Output ( )
if err != nil {
t . Fatalf ( "%v: %v" , commandArgs , err )
}
return strings . TrimSpace ( string ( output ) )
}
2026-10-06 00:51:56 +00:00
func TestDeliverRejectsReceiptBuiltFromDirtySourceAfterCleanup ( t * testing . T ) {
repository := newDeliveryBuildRepository ( t )
state := & deliveryServerState { }
server := newDeliveryServer ( t , state )
defer server . Close ( )
settings := cli . Settings { Product : "Example Product" , Build : & cli . BuildSettings { MainPackage : "." , BinaryName : "helper" , Targets : [ ] string { "linux/amd64" } } , Delivery : & cli . DeliverySettings { BaseURL : server . URL , Owner : "owner" , Repository : "example" } }
writeDeliveryProfile ( t , repository , settings )
state . commit = cliGitOutput ( t , repository , "rev-parse" , "HEAD" )
cliGitRun ( t , repository , "tag" , "v1.2.3" )
mainPath := filepath . Join ( repository , "main.go" )
original , err := os . ReadFile ( mainPath )
if err != nil {
t . Fatal ( err )
}
if err := os . WriteFile ( mainPath , append ( original , [ ] byte ( "\n// uncommitted build\n" ) ... ) , 0600 ) ; err != nil {
t . Fatal ( err )
}
assets := t . TempDir ( )
var stdout , stderr bytes . Buffer
if status := run ( context . Background ( ) , [ ] string { "build" , "--repo" , repository , "--output" , assets , "v1.2.3" } , & stdout , & stderr ) ; status != 0 {
t . Fatalf ( "local build status=%d %s" , status , stderr . String ( ) )
}
receipt , err := cli . LoadBuildReceipt ( filepath . Join ( assets , cli . BuildReceiptName ) )
if err != nil {
t . Fatal ( err )
}
if receipt . SourceVerified {
t . Fatal ( "dirty build receipt claims verified source" )
}
cliGitRun ( t , repository , "restore" , "main.go" )
t . Setenv ( "RELEASEKIT_GITEA_TOKEN" , "fixture-token" )
stdout . Reset ( )
stderr . Reset ( )
status := run ( context . Background ( ) , [ ] string { "deliver" , "--repo" , repository , "--assets" , assets , "v1.2.3" } , & stdout , & stderr )
if status != 1 || state . apiRequests != 0 || ! strings . Contains ( stderr . String ( ) , "build source" ) {
t . Fatalf ( "status=%d requests=%d %s" , status , state . apiRequests , stderr . String ( ) )
}
}
2026-10-06 00:53:33 +00:00
func TestRecoveredPageCreatePreservesPartialDeliveryOnInspectionFailure ( t * testing . T ) {
repository := newCLIRepository ( t )
state := & deliveryServerState { malformedCreate : true , failAssetListing : true }
server := newDeliveryServer ( t , state )
defer server . Close ( )
build := cli . BuildSettings { MainPackage : "." , BinaryName : "helper" , Targets : [ ] string { "linux/amd64" } }
writeDeliveryProfile ( t , repository , cli . Settings { Product : "Example Product" , Build : & build , Delivery : & cli . DeliverySettings { BaseURL : server . URL , Owner : "owner" , Repository : "example" } } )
state . commit = cliGitOutput ( t , repository , "rev-parse" , "HEAD" )
cliGitRun ( t , repository , "tag" , "v1.2.3" )
assets , _ := writeDeliveryReceipt ( t , repository , build )
t . Setenv ( "RELEASEKIT_GITEA_TOKEN" , "fixture-token" )
var stdout , stderr bytes . Buffer
status := run ( context . Background ( ) , [ ] string { "deliver" , "--repo" , repository , "--assets" , assets , "v1.2.3" } , & stdout , & stderr )
if status != 1 || ! strings . Contains ( stderr . String ( ) , "state=partial" ) || state . releaseCreates != 1 || state . assetListReads != 1 || state . assetUploads != 0 {
t . Fatalf ( "status=%d state=%#v %s" , status , state , stderr . String ( ) )
}
}
2026-10-06 00:54:49 +00:00
func TestUnreadableAssetsDoNotClaimAnUnknownWrite ( t * testing . T ) {
for _ , existingPage := range [ ] bool { false , true } {
t . Run ( fmt . Sprintf ( "existing-page-%t" , existingPage ) , func ( t * testing . T ) {
repository := newCLIRepository ( t )
state := & deliveryServerState { pageExists : existingPage , noteBody : "# Example Product v1.2.3\n\nRelease note.\n" , failAssetDownloads : true , assets : map [ string ] [ ] byte { "helper-v1.2.3-linux-amd64" : [ ] byte ( "fixture artifact" ) } }
server := newDeliveryServer ( t , state )
defer server . Close ( )
build := cli . BuildSettings { MainPackage : "." , BinaryName : "helper" , Targets : [ ] string { "linux/amd64" } }
writeDeliveryProfile ( t , repository , cli . Settings { Product : "Example Product" , Build : & build , Delivery : & cli . DeliverySettings { BaseURL : server . URL , Owner : "owner" , Repository : "example" } } )
state . commit = cliGitOutput ( t , repository , "rev-parse" , "HEAD" )
cliGitRun ( t , repository , "tag" , "v1.2.3" )
assets , _ := writeDeliveryReceipt ( t , repository , build )
t . Setenv ( "RELEASEKIT_GITEA_TOKEN" , "fixture-token" )
var stdout , stderr bytes . Buffer
status := run ( context . Background ( ) , [ ] string { "deliver" , "--repo" , repository , "--assets" , assets , "v1.2.3" } , & stdout , & stderr )
expectedState := "partial"
expectedCreates := 1
if existingPage {
expectedState = "not_attempted"
expectedCreates = 0
}
if status != 1 || ! strings . Contains ( stderr . String ( ) , "state=" + expectedState ) || state . assetUploads != 0 || state . releaseCreates != expectedCreates {
t . Fatalf ( "status=%d creates=%d uploads=%d %s" , status , state . releaseCreates , state . assetUploads , stderr . String ( ) )
}
2026-10-06 17:54:22 +00:00
if existingPage && ! strings . Contains ( stderr . String ( ) , "recovery: fix the local or preflight failure" ) {
t . Fatalf ( "not-attempted recovery guidance missing: %s" , stderr . String ( ) )
}
2026-10-06 00:54:49 +00:00
} )
}
}
2026-10-06 00:56:44 +00:00
func TestFailedRebuildInvalidatesPreviousReceipt ( t * testing . T ) {
const source = ` package main
import ( "fmt" ; "gitea.maximumdirect.net/eric/ReleaseKit/buildinfo" )
func main ( ) { fmt . Println ( "fixture" , buildinfo . Version ( ) ) }
`
repository := newCLIBuildRepository ( t , source )
var stdout , stderr bytes . Buffer
args := [ ] string { "build" , "--repo" , repository , "v1.2.3" }
for attempt := 0 ; attempt < 2 ; attempt ++ {
if status := run ( context . Background ( ) , args , & stdout , & stderr ) ; status != 0 {
t . Fatalf ( "build %d: status=%d %s" , attempt , status , stderr . String ( ) )
}
}
path := filepath . Join ( repository , "cmd/app/main.go" )
if err := os . WriteFile ( path , [ ] byte ( "package main\nfunc main(){}\n" ) , 0600 ) ; err != nil {
t . Fatal ( err )
}
if status := run ( context . Background ( ) , args , & stdout , & stderr ) ; status != 1 {
t . Fatalf ( "failed rebuild status=%d" , status )
}
if _ , err := os . Stat ( filepath . Join ( repository , "dist" , cli . BuildReceiptName ) ) ; ! os . IsNotExist ( err ) {
t . Fatalf ( "stale receipt remains: %v" , err )
}
}
2026-10-06 01:00:11 +00:00
func TestTemporaryDeliveryBuildDoesNotRequireWritableRepositoryParent ( t * testing . T ) {
if runtime . GOOS == "windows" || os . Getuid ( ) == 0 {
t . Skip ( "requires ordinary Unix permission checks" )
}
repository := newDeliveryBuildRepository ( t )
state := & deliveryServerState { }
server := newDeliveryServer ( t , state )
defer server . Close ( )
writeDeliveryProfile ( t , repository , cli . Settings { Product : "Example Product" , Build : & cli . BuildSettings { MainPackage : "." , BinaryName : "helper" , Targets : [ ] string { "linux/amd64" } } , Delivery : & cli . DeliverySettings { BaseURL : server . URL , Owner : "owner" , Repository : "example" } } )
state . commit = cliGitOutput ( t , repository , "rev-parse" , "HEAD" )
cliGitRun ( t , repository , "tag" , "v1.2.3" )
parent := t . TempDir ( )
moved := filepath . Join ( parent , "repo" )
if err := os . Rename ( repository , moved ) ; err != nil {
t . Fatal ( err )
}
if err := os . Chmod ( parent , 0555 ) ; err != nil {
t . Fatal ( err )
}
t . Cleanup ( func ( ) { os . Chmod ( parent , 0700 ) } )
t . Setenv ( "RELEASEKIT_GITEA_TOKEN" , "fixture-token" )
var stdout , stderr bytes . Buffer
if status := run ( context . Background ( ) , [ ] string { "deliver" , "--repo" , moved , "v1.2.3" } , & stdout , & stderr ) ; status != 0 {
t . Fatalf ( "status=%d %s" , status , stderr . String ( ) )
}
if status := cliGitOutput ( t , moved , "status" , "--porcelain" ) ; status != "" {
t . Fatalf ( "source dirtied: %s" , status )
}
}