@@ -3,6 +3,8 @@ package cmd
33import (
44 "fmt"
55 "io"
6+ "os"
7+ "path/filepath"
68 "testing"
79
810 "github.com/cli/go-gh/v2/pkg/api"
@@ -1203,3 +1205,160 @@ func TestLink_SkipsBaseFix_ForNewlyCreatedPRs(t *testing.T) {
12031205
12041206// Silence "imported and not used" for fmt in case test helpers use it.
12051207var _ = fmt .Sprintf
1208+
1209+ func TestLink_FetchesBeforePush (t * testing.T ) {
1210+ var callOrder []string
1211+ var fetchedBranches []string
1212+
1213+ mock := newLinkGitMock ("feat-a" , "feat-b" )
1214+ mock .FetchBranchesFn = func (remote string , branches []string ) error {
1215+ callOrder = append (callOrder , "fetch" )
1216+ fetchedBranches = branches
1217+ assert .Equal (t , "origin" , remote )
1218+ return nil
1219+ }
1220+ mock .PushFn = func (remote string , branches []string , force , atomic bool ) error {
1221+ callOrder = append (callOrder , "push" )
1222+ return nil
1223+ }
1224+
1225+ restore := git .SetOps (mock )
1226+ defer restore ()
1227+
1228+ prNum := 0
1229+ cfg , _ , errR := config .NewTestConfig ()
1230+ cfg .GitHubClientOverride = & github.MockClient {
1231+ FindPRForBranchFn : func (branch string ) (* github.PullRequest , error ) {
1232+ prNum ++
1233+ return & github.PullRequest {
1234+ Number : prNum ,
1235+ URL : fmt .Sprintf ("https://github.com/o/r/pull/%d" , prNum ),
1236+ BaseRefName : "main" ,
1237+ HeadRefName : branch ,
1238+ State : "OPEN" ,
1239+ }, nil
1240+ },
1241+ ListStacksFn : func () ([]github.RemoteStack , error ) {
1242+ return []github.RemoteStack {}, nil
1243+ },
1244+ CreateStackFn : func (prNumbers []int ) (int , error ) {
1245+ return 42 , nil
1246+ },
1247+ }
1248+
1249+ cmd := LinkCmd (cfg )
1250+ cmd .SetArgs ([]string {"feat-a" , "feat-b" })
1251+ cmd .SetOut (io .Discard )
1252+ cmd .SetErr (io .Discard )
1253+ err := cmd .Execute ()
1254+
1255+ cfg .Err .Close ()
1256+ _ , _ = io .ReadAll (errR )
1257+
1258+ assert .NoError (t , err )
1259+ assert .Equal (t , []string {"feat-a" , "feat-b" }, fetchedBranches , "should fetch pushed branches" )
1260+ require .Len (t , callOrder , 2 )
1261+ assert .Equal (t , "fetch" , callOrder [0 ], "fetch must happen before push" )
1262+ assert .Equal (t , "push" , callOrder [1 ])
1263+ }
1264+
1265+ func TestLink_BranchNames_UsesPRTemplate (t * testing.T ) {
1266+ tmpDir := t .TempDir ()
1267+ ghDir := filepath .Join (tmpDir , ".github" )
1268+ require .NoError (t , os .MkdirAll (ghDir , 0o755 ))
1269+ require .NoError (t , os .WriteFile (
1270+ filepath .Join (ghDir , "pull_request_template.md" ),
1271+ []byte ("## Summary\n \n Describe your changes." ),
1272+ 0o644 ,
1273+ ))
1274+
1275+ mock := newLinkGitMock ("feat-a" , "feat-b" )
1276+ mock .RootDirFn = func () (string , error ) { return tmpDir , nil }
1277+ restore := git .SetOps (mock )
1278+ defer restore ()
1279+
1280+ var capturedBody string
1281+ cfg , _ , errR := config .NewTestConfig ()
1282+ cfg .GitHubClientOverride = & github.MockClient {
1283+ FindPRForBranchFn : func (string ) (* github.PullRequest , error ) {
1284+ return nil , nil // No existing PRs
1285+ },
1286+ CreatePRFn : func (base , head , title , body string , draft bool ) (* github.PullRequest , error ) {
1287+ capturedBody = body
1288+ return & github.PullRequest {
1289+ Number : 1 , HeadRefName : head , BaseRefName : base ,
1290+ URL : "https://github.com/o/r/pull/1" ,
1291+ }, nil
1292+ },
1293+ ListStacksFn : func () ([]github.RemoteStack , error ) {
1294+ return []github.RemoteStack {}, nil
1295+ },
1296+ CreateStackFn : func ([]int ) (int , error ) { return 42 , nil },
1297+ }
1298+
1299+ cmd := LinkCmd (cfg )
1300+ cmd .SetArgs ([]string {"feat-a" , "feat-b" })
1301+ cmd .SetOut (io .Discard )
1302+ cmd .SetErr (io .Discard )
1303+ err := cmd .Execute ()
1304+
1305+ cfg .Err .Close ()
1306+ _ , _ = io .ReadAll (errR )
1307+
1308+ assert .NoError (t , err )
1309+ assert .Contains (t , capturedBody , "## Summary" )
1310+ assert .Contains (t , capturedBody , "Describe your changes." )
1311+ assert .NotContains (t , capturedBody , "GitHub Stacks CLI" , "footer should not be present when template is used" )
1312+ }
1313+
1314+ func TestLink_PRNumbers_NoTemplateUsesFooter (t * testing.T ) {
1315+ // When using PR numbers (no local repo context), no template is found
1316+ // and the footer should be present for newly created PRs.
1317+ mock := & git.MockOps {
1318+ RootDirFn : func () (string , error ) {
1319+ return "" , fmt .Errorf ("not in a git repo" )
1320+ },
1321+ }
1322+ restore := git .SetOps (mock )
1323+ defer restore ()
1324+
1325+ var capturedBody string
1326+ cfg , _ , errR := config .NewTestConfig ()
1327+ cfg .GitHubClientOverride = & github.MockClient {
1328+ FindPRByNumberFn : func (n int ) (* github.PullRequest , error ) {
1329+ if n == 10 {
1330+ return & github.PullRequest {
1331+ Number : 10 , HeadRefName : "feat-a" , BaseRefName : "main" ,
1332+ URL : "https://github.com/o/r/pull/10" ,
1333+ }, nil
1334+ }
1335+ return nil , nil // PR 20 doesn't exist → will create
1336+ },
1337+ FindPRForBranchFn : func (branch string ) (* github.PullRequest , error ) {
1338+ return nil , nil
1339+ },
1340+ CreatePRFn : func (base , head , title , body string , draft bool ) (* github.PullRequest , error ) {
1341+ capturedBody = body
1342+ return & github.PullRequest {
1343+ Number : 20 , HeadRefName : head , BaseRefName : base ,
1344+ URL : "https://github.com/o/r/pull/20" ,
1345+ }, nil
1346+ },
1347+ ListStacksFn : func () ([]github.RemoteStack , error ) {
1348+ return []github.RemoteStack {}, nil
1349+ },
1350+ CreateStackFn : func ([]int ) (int , error ) { return 42 , nil },
1351+ }
1352+
1353+ cmd := LinkCmd (cfg )
1354+ cmd .SetArgs ([]string {"10" , "20" })
1355+ cmd .SetOut (io .Discard )
1356+ cmd .SetErr (io .Discard )
1357+ err := cmd .Execute ()
1358+
1359+ cfg .Err .Close ()
1360+ _ , _ = io .ReadAll (errR )
1361+
1362+ assert .NoError (t , err )
1363+ assert .Contains (t , capturedBody , "GitHub Stacks CLI" , "footer should be present when no template" )
1364+ }
0 commit comments