11/**
2- * auto-format.yml's push step against local fixture repositories: the branches the workflow text cannot show,
3- * which checkout the patch is committed on, under whose identity, and what a moved branch does to the push.
2+ * auto-format.yml's two steps against local fixture repositories: the branches the workflow text cannot show.
3+ * For the format step, what the patch holds and when the step stops; for the push step, which checkout the patch
4+ * is committed on, under whose identity, and what a moved branch does to the push.
45 */
56
67import { describe , expect , setDefaultTimeout , test } from "bun:test" ;
78import { execFileSync } from "node:child_process" ;
9+ import { existsSync , readFileSync } from "node:fs" ;
810import { join } from "node:path" ;
911import {
1012 clone ,
@@ -22,6 +24,67 @@ installReleasePipelineFixture();
2224
2325const REMOTE_URL = "https://x-access-token:t0ken@github.com/o/r.git" ;
2426
27+ /** The format job's checkout of a same-repo PR branch: its lint:fix runs whatever LINT_FIX says. */
28+ function formatFixture ( ) : { pr : string ; headSha : string ; temp : string } {
29+ const fx = seedFixture ( ) ;
30+ const pr = clone ( fx . root , fx . origin , "pr" ) ;
31+ write (
32+ pr ,
33+ "package.json" ,
34+ `${ JSON . stringify (
35+ { name : "fixture" , version : "0.0.0" , scripts : { "lint:fix" : 'sh -c "$LINT_FIX"' } } ,
36+ null ,
37+ 2 ,
38+ ) } \n`,
39+ ) ;
40+ const headSha = commitAll ( pr , "feat: a pull request" ) ;
41+ return { pr, headSha, temp : join ( fx . root , "runner-temp" ) } ;
42+ }
43+
44+ const format = ( pr : string , temp : string , lintFix : string ) =>
45+ runStep ( "auto-format-steps" , pr , temp , { LINT_FIX : lintFix } , "format" ) ;
46+
47+ function patchPaths ( temp : string ) : string [ ] {
48+ return [ ...readFileSync ( join ( temp , "format.patch" ) , "utf8" ) . matchAll ( / ^ d i f f - - g i t a \/ ( \S + ) / gm) ]
49+ . map ( ( match ) => match [ 1 ] ?? "" )
50+ . sort ( ) ;
51+ }
52+
53+ describe ( "format" , ( ) => {
54+ test ( "a formatter that changes nothing leaves an empty patch and changed=false, and an addition staged before it is not the fix" , ( ) => {
55+ const { pr, headSha, temp } = formatFixture ( ) ;
56+ write ( pr , "staged.ts" , "export const staged = 1;\n" ) ;
57+ git ( pr , "add" , "staged.ts" ) ;
58+ const result = format ( pr , temp , "true" ) ;
59+ expect ( result ) . toMatchObject ( {
60+ status : 0 ,
61+ stdout : "nothing to format\n" ,
62+ outputs : [ `head=${ headSha } ` , "changed=false" ] ,
63+ } ) ;
64+ expect ( readFileSync ( join ( temp , "format.patch" ) , "utf8" ) ) . toBe ( "" ) ;
65+ expect ( git ( pr , "diff" , "--cached" , "--name-only" ) ) . toBe ( "" ) ;
66+ } ) ;
67+
68+ test ( "the formatter's modifications and deletions of tracked files enter the patch; a file it adds does not" , ( ) => {
69+ const { pr, headSha, temp } = formatFixture ( ) ;
70+ const result = format (
71+ pr ,
72+ temp ,
73+ "echo 'export const marker=1' > src/marker.ts && rm .github/dependabot.yml && echo new > added.ts" ,
74+ ) ;
75+ expect ( result ) . toMatchObject ( { status : 0 , outputs : [ `head=${ headSha } ` , "changed=true" ] } ) ;
76+ expect ( result . stdout ) . toContain ( "2 files changed, 1 insertion(+), 3 deletions(-)" ) ;
77+ expect ( patchPaths ( temp ) ) . toEqual ( [ ".github/dependabot.yml" , "src/marker.ts" ] ) ;
78+ } ) ;
79+
80+ test ( "a failing formatter ends the step with its status after head= is written, with no patch cut" , ( ) => {
81+ const { pr, headSha, temp } = formatFixture ( ) ;
82+ const result = format ( pr , temp , "exit 3" ) ;
83+ expect ( result ) . toMatchObject ( { status : 3 , outputs : [ `head=${ headSha } ` ] } ) ;
84+ expect ( existsSync ( join ( temp , "format.patch" ) ) ) . toBe ( false ) ;
85+ } ) ;
86+ } ) ;
87+
2588/** The push job's checkout of a same-repo PR branch, with the format job's patch downloaded beside it. */
2689function prFixture ( ) : { fx : Fixture ; pr : string ; headSha : string ; temp : string } {
2790 const fx = seedFixture ( ) ;
0 commit comments