2026-10-10 02:10:18 +00:00
package releaseidentity
import (
"context"
2026-10-10 14:20:09 +00:00
"errors"
2026-10-10 02:10:18 +00:00
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
func TestDeriveUsesModuleDeclarationAndPhysicalLayout ( t * testing . T ) {
tests := [ ] struct {
name string
subdir string
modulePath string
version string
wantTag string
wantNote string
} {
{ name : "root v1" , subdir : "." , modulePath : "example.com/root" , version : "v1.2.3" , wantTag : "v1.2.3" , wantNote : "docs/releases/v1.2.3.md" } ,
2026-10-10 14:12:50 +00:00
{ name : "local main module" , subdir : "." , modulePath : "example" , version : "v1.2.3" , wantTag : "v1.2.3" , wantNote : "docs/releases/v1.2.3.md" } ,
{ name : "local major module" , subdir : "apps/local/v2" , modulePath : "example/v2" , version : "v2.3.4" , wantTag : "apps/local/v2.3.4" , wantNote : "apps/local/v2/docs/releases/v2.3.4.md" } ,
2026-10-10 02:10:18 +00:00
{ name : "nested" , subdir : "integrations/check" , modulePath : "example.com/check" , version : "v1.2.3" , wantTag : "integrations/check/v1.2.3" , wantNote : "integrations/check/docs/releases/v1.2.3.md" } ,
{ name : "root major path" , subdir : "." , modulePath : "example.com/root/v2" , version : "v2.3.4" , wantTag : "v2.3.4" , wantNote : "docs/releases/v2.3.4.md" } ,
{ name : "root physical major directory" , subdir : "v2" , modulePath : "example.com/root/v2" , version : "v2.3.4" , wantTag : "v2.3.4" , wantNote : "v2/docs/releases/v2.3.4.md" } ,
{ name : "physical major directory" , subdir : "integrations/check/v2" , modulePath : "example.com/check/v2" , version : "v2.3.4" , wantTag : "integrations/check/v2.3.4" , wantNote : "integrations/check/v2/docs/releases/v2.3.4.md" } ,
{ name : "logical major without physical directory" , subdir : "integrations/check" , modulePath : "example.com/check/v2" , version : "v2.3.4" , wantTag : "integrations/check/v2.3.4" , wantNote : "integrations/check/docs/releases/v2.3.4.md" } ,
{ name : "ordinary directory named v2" , subdir : "integrations/v2" , modulePath : "example.com/check" , version : "v1.2.3" , wantTag : "integrations/v2/v1.2.3" , wantNote : "integrations/v2/docs/releases/v1.2.3.md" } ,
{ name : "gopkg special major" , subdir : "integrations/example.v2" , modulePath : "gopkg.in/example.v2" , version : "v2.3.4" , wantTag : "integrations/example.v2/v2.3.4" , wantNote : "integrations/example.v2/docs/releases/v2.3.4.md" } ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
selection := Selection { RepositoryDir : "/repo" , ModuleDir : filepath . Join ( "/repo" , filepath . FromSlash ( tt . subdir ) ) , ModuleSubdir : tt . subdir }
if tt . subdir == "." {
selection . ModuleDir = "/repo"
}
got , err := Derive ( selection , [ ] byte ( "module " + tt . modulePath + "\n" ) , tt . version )
if err != nil {
t . Fatal ( err )
}
if got . Tag != tt . wantTag || got . NotePath != tt . wantNote {
t . Fatalf ( "identity tag/note = %q, %q; want %q, %q" , got . Tag , got . NotePath , tt . wantTag , tt . wantNote )
}
} )
}
}
func TestDeriveRejectsInvalidModuleAndVersionIdentity ( t * testing . T ) {
selection := Selection { RepositoryDir : "/repo" , ModuleDir : "/repo" , ModuleSubdir : "." }
for _ , tt := range [ ] struct {
name , declaration , version string
} {
{ name : "missing module directive" , declaration : "go 1.26\n" , version : "v1.2.3" } ,
{ name : "malformed module path" , declaration : "module example.com/../bad\n" , version : "v1.2.3" } ,
{ name : "incompatible major" , declaration : "module example.com/service/v2\n" , version : "v1.2.3" } ,
2026-10-10 14:12:50 +00:00
{ name : "incompatible local major" , declaration : "module example/v2\n" , version : "v1.2.3" } ,
{ name : "invalid local module path" , declaration : "module example/../bad\n" , version : "v1.2.3" } ,
2026-10-10 02:10:18 +00:00
{ name : "unstable version" , declaration : "module example.com/service\n" , version : "v1.2.3-rc.1" } ,
} {
t . Run ( tt . name , func ( t * testing . T ) {
if _ , err := Derive ( selection , [ ] byte ( tt . declaration ) , tt . version ) ; err == nil {
t . Fatal ( "Derive() succeeded for invalid identity" )
}
} )
}
}
func TestResolveSelectionNormalizesAndRejectsEscape ( t * testing . T ) {
repository := t . TempDir ( )
moduleDir := filepath . Join ( repository , "integrations" , "one" )
if err := os . MkdirAll ( moduleDir , 0 o755 ) ; err != nil {
t . Fatal ( err )
}
selection , err := ResolveSelection ( repository , "integrations/one/../one" )
if err != nil {
t . Fatal ( err )
}
if selection . ModuleSubdir != "integrations/one" || selection . ModuleDir != moduleDir {
t . Fatalf ( "normalized selection = %#v" , selection )
}
root , err := ResolveSelection ( repository , "" )
if err != nil || root . ModuleSubdir != "." || root . ModuleDir != repository {
t . Fatalf ( "default root selection = %#v, error = %v" , root , err )
}
if _ , err := ResolveSelection ( repository , "../outside" ) ; err == nil {
t . Fatal ( "ResolveSelection() accepted a path outside the repository" )
}
if _ , err := ResolveSelection ( repository , moduleDir ) ; err == nil {
t . Fatal ( "ResolveSelection() accepted an absolute module path" )
}
outside := t . TempDir ( )
link := filepath . Join ( repository , "linked" )
if err := os . Symlink ( outside , link ) ; err != nil {
t . Fatal ( err )
}
if _ , err := ResolveSelection ( repository , "linked" ) ; err == nil {
t . Fatal ( "ResolveSelection() accepted a symlink that resolves outside the repository" )
}
}
func TestCommittedAndWorkingTreeDeclarationsUseTheirOwnSources ( t * testing . T ) {
repository := t . TempDir ( )
gitIdentityRun ( t , repository , "init" , "-q" )
gitIdentityRun ( t , repository , "config" , "user.name" , "ReleaseKit test" )
gitIdentityRun ( t , repository , "config" , "user.email" , "releasekit@example.invalid" )
moduleDir := filepath . Join ( repository , "nested" , "mod" )
if err := os . MkdirAll ( moduleDir , 0 o755 ) ; err != nil {
t . Fatal ( err )
}
declaration := filepath . Join ( moduleDir , "go.mod" )
if err := os . WriteFile ( declaration , [ ] byte ( "module example.com/committed\n" ) , 0 o644 ) ; err != nil {
t . Fatal ( err )
}
gitIdentityRun ( t , repository , "add" , "." )
gitIdentityRun ( t , repository , "commit" , "-qm" , "add module" )
commit := strings . TrimSpace ( gitIdentityOutput ( t , repository , "rev-parse" , "HEAD" ) )
if err := os . WriteFile ( declaration , [ ] byte ( "module example.com/working\n" ) , 0 o644 ) ; err != nil {
t . Fatal ( err )
}
committed , err := FromCommitted ( context . Background ( ) , repository , "nested/mod" , "v1.2.3" , commit )
if err != nil {
t . Fatal ( err )
}
working , err := FromWorkingTree ( context . Background ( ) , repository , "nested/mod" , "v1.2.3" )
if err != nil {
t . Fatal ( err )
}
if committed . ModulePath != "example.com/committed" || committed . Tag != "nested/mod/v1.2.3" {
t . Fatalf ( "committed identity = %#v" , committed )
}
if working . ModulePath != "example.com/working" || working . Tag != "nested/mod/v1.2.3" {
t . Fatalf ( "working identity = %#v" , working )
}
}
2026-10-10 14:20:09 +00:00
func TestCommittedIdentityFollowsCapturedDeclarationAndDirectoryLinks ( t * testing . T ) {
repository := t . TempDir ( )
gitIdentityRun ( t , repository , "init" , "-q" )
gitIdentityRun ( t , repository , "config" , "user.name" , "ReleaseKit test" )
gitIdentityRun ( t , repository , "config" , "user.email" , "releasekit@example.invalid" )
moduleDir := filepath . Join ( repository , "nested" )
if err := os . Mkdir ( moduleDir , 0 o755 ) ; err != nil {
t . Fatal ( err )
}
declaration := filepath . Join ( repository , "module.txt" )
if err := os . WriteFile ( declaration , [ ] byte ( "module example.com/committed\n" ) , 0 o644 ) ; err != nil {
t . Fatal ( err )
}
if err := os . Symlink ( "../module.txt" , filepath . Join ( moduleDir , "go.mod" ) ) ; err != nil {
t . Fatal ( err )
}
if err := os . Symlink ( "nested" , filepath . Join ( repository , "selected" ) ) ; err != nil {
t . Fatal ( err )
}
gitIdentityRun ( t , repository , "add" , "." )
gitIdentityRun ( t , repository , "commit" , "-qm" , "Add linked declaration" )
commit := strings . TrimSpace ( gitIdentityOutput ( t , repository , "rev-parse" , "HEAD" ) )
if err := os . WriteFile ( declaration , [ ] byte ( "module example.com/working\n" ) , 0 o644 ) ; err != nil {
t . Fatal ( err )
}
committed , err := FromCommitted ( t . Context ( ) , repository , "selected" , "v1.2.3" , commit )
if err != nil || committed . ModulePath != "example.com/committed" || committed . ModuleDir != moduleDir || committed . Tag != "nested/v1.2.3" {
t . Fatalf ( "committed linked identity = %#v, error = %v" , committed , err )
}
working , err := FromWorkingTree ( t . Context ( ) , repository , "selected" , "v1.2.3" )
if err != nil || working . ModulePath != "example.com/working" || working . ModuleDir != moduleDir {
t . Fatalf ( "working linked identity = %#v, error = %v" , working , err )
}
ctx , cancel := context . WithCancel ( t . Context ( ) )
cancel ( )
if _ , err := FromCommitted ( ctx , repository , "selected" , "v1.2.3" , commit ) ; ! errors . Is ( err , context . Canceled ) {
t . Fatalf ( "canceled committed identity error = %v" , err )
}
}
func TestCommittedIdentityPreservesReferenceFailures ( t * testing . T ) {
for _ , mode := range [ ] string { "cycle" , "outside" , "missing" , "unavailable blob" } {
t . Run ( mode , func ( t * testing . T ) {
repository := t . TempDir ( )
gitIdentityRun ( t , repository , "init" , "-q" )
gitIdentityRun ( t , repository , "config" , "user.name" , "ReleaseKit test" )
gitIdentityRun ( t , repository , "config" , "user.email" , "releasekit@example.invalid" )
target , wantCause := "go.mod" , "symlink-cycle"
switch mode {
case "outside" :
target , wantCause = "../module.txt" , "outside-checkout"
case "missing" :
target , wantCause = "missing.mod" , "missing"
case "unavailable blob" :
target , wantCause = "module.txt" , "read selected module declaration"
if err := os . WriteFile ( filepath . Join ( repository , target ) , [ ] byte ( "module example.com/linked\n" ) , 0 o644 ) ; err != nil {
t . Fatal ( err )
}
}
if err := os . Symlink ( target , filepath . Join ( repository , "go.mod" ) ) ; err != nil {
t . Fatal ( err )
}
gitIdentityRun ( t , repository , "add" , "." )
gitIdentityRun ( t , repository , "commit" , "-qm" , "Add reference fixture" )
commit := strings . TrimSpace ( gitIdentityOutput ( t , repository , "rev-parse" , "HEAD" ) )
if mode == "unavailable blob" {
object := strings . TrimSpace ( gitIdentityOutput ( t , repository , "rev-parse" , "HEAD:module.txt" ) )
if err := os . Remove ( filepath . Join ( repository , ".git" , "objects" , object [ : 2 ] , object [ 2 : ] ) ) ; err != nil {
t . Fatal ( err )
}
}
identity , err := FromCommitted ( t . Context ( ) , repository , "." , "v1.2.3" , commit )
if err == nil || ! strings . Contains ( err . Error ( ) , wantCause ) || identity . RepositoryDir != repository || identity . ModuleDir != repository {
t . Fatalf ( "identity = %#v, error = %v; want %s" , identity , err , wantCause )
}
} )
}
}
2026-10-10 02:10:18 +00:00
func TestSelectedNestedModuleDoesNotRequireRootDeclaration ( t * testing . T ) {
repository := t . TempDir ( )
moduleDir := filepath . Join ( repository , "nested" )
if err := os . MkdirAll ( moduleDir , 0 o755 ) ; err != nil {
t . Fatal ( err )
}
if err := os . WriteFile ( filepath . Join ( moduleDir , "go.mod" ) , [ ] byte ( "module example.com/nested\n" ) , 0 o644 ) ; err != nil {
t . Fatal ( err )
}
identity , err := FromWorkingTree ( context . Background ( ) , repository , "nested" , "v1.2.3" )
if err != nil || identity . ModulePath != "example.com/nested" {
t . Fatalf ( "nested identity = %#v, error = %v" , identity , err )
}
if _ , err := FromWorkingTree ( context . Background ( ) , repository , "." , "v1.2.3" ) ; err == nil {
t . Fatal ( "root selection silently fell back to a nested module declaration" )
}
}
func TestReadersPreserveCancellation ( t * testing . T ) {
repository := t . TempDir ( )
ctx , cancel := context . WithCancel ( context . Background ( ) )
cancel ( )
if _ , err := FromWorkingTree ( ctx , repository , "." , "v1.2.3" ) ; err == nil || ! strings . Contains ( err . Error ( ) , context . Canceled . Error ( ) ) {
t . Fatalf ( "working-tree error = %v, want cancellation" , err )
}
}
func gitIdentityRun ( t * testing . T , dir string , args ... string ) {
t . Helper ( )
command := exec . Command ( "git" , args ... )
command . Dir = dir
if output , err := command . CombinedOutput ( ) ; err != nil {
t . Fatalf ( "git %v: %v\n%s" , args , err , output )
}
}
func gitIdentityOutput ( t * testing . T , dir string , args ... string ) string {
t . Helper ( )
command := exec . Command ( "git" , args ... )
command . Dir = dir
output , err := command . Output ( )
if err != nil {
t . Fatalf ( "git %v: %v" , args , err )
}
return string ( output )
}