@@ -1591,22 +1591,66 @@ describe('runCodeGet', () => {
15911591 expect ( leftovers ) . toEqual ( [ ] ) ;
15921592 } ) ;
15931593
1594- // Regression: the "no code generated yet" branch writes nothing but
1595- // previously still closed (and thus truncated) the opened file.
1594+ it ( '--out (text mode) rejects empty inline code with VALIDATION_ERROR and leaves no artifact' , async ( ) => {
1595+ const { credentialsPath } = makeCreds ( ) ;
1596+ const emptyCode : CliTestCode = { ...TEST_CODE_INLINE , code : '' } ;
1597+ const fetchImpl = makeFetch ( ( ) => ( { body : emptyCode } ) ) ;
1598+ const dir = mkdtempSync ( join ( tmpdir ( ) , 'cli-test-code-empty-out-' ) ) ;
1599+ const target = join ( dir , 'empty.ts' ) ;
1600+ await expect (
1601+ runCodeGet (
1602+ {
1603+ profile : 'default' ,
1604+ output : 'text' ,
1605+ debug : false ,
1606+ testId : 'test_fe' ,
1607+ out : target ,
1608+ } ,
1609+ { credentialsPath, fetchImpl } ,
1610+ ) ,
1611+ ) . rejects . toMatchObject ( {
1612+ code : 'VALIDATION_ERROR' ,
1613+ exitCode : 5 ,
1614+ details : expect . objectContaining ( { field : 'out' } ) ,
1615+ } ) ;
1616+ expect ( existsSync ( target ) ) . toBe ( false ) ;
1617+ } ) ;
1618+
1619+ // Regression: empty inline code with --out must reject (exit 5) without
1620+ // truncating or replacing a pre-existing destination file.
15961621 it ( '--out: "no code generated yet" leaves a pre-existing file untouched' , async ( ) => {
15971622 const { credentialsPath } = makeCreds ( ) ;
15981623 const dir = mkdtempSync ( join ( tmpdir ( ) , 'cli-test-code-out-empty-' ) ) ;
15991624 const target = join ( dir , 'existing.ts' ) ;
16001625 writeFileSync ( target , 'PRE-EXISTING CONTENT' , 'utf8' ) ;
16011626 const fetchImpl = makeFetch ( ( ) => ( { body : { ...TEST_CODE_INLINE , code : '' } } ) ) ;
1602- await runCodeGet (
1603- { profile : 'default' , output : 'text' , debug : false , testId : 'test_fe' , out : target } ,
1604- { credentialsPath, fetchImpl, stderr : ( ) => undefined } ,
1605- ) ;
1627+ await expect (
1628+ runCodeGet (
1629+ { profile : 'default' , output : 'text' , debug : false , testId : 'test_fe' , out : target } ,
1630+ { credentialsPath, fetchImpl, stderr : ( ) => undefined } ,
1631+ ) ,
1632+ ) . rejects . toMatchObject ( {
1633+ code : 'VALIDATION_ERROR' ,
1634+ exitCode : 5 ,
1635+ details : expect . objectContaining ( { field : 'out' } ) ,
1636+ } ) ;
16061637 expect ( readFileSync ( target , 'utf-8' ) ) . toBe ( 'PRE-EXISTING CONTENT' ) ;
16071638 const leftovers = readdirSync ( dir ) . filter ( f => f !== 'existing.ts' ) ;
16081639 expect ( leftovers ) . toEqual ( [ ] ) ;
16091640 } ) ;
1641+
1642+ it ( 'text mode without --out still hints on stderr when inline code is empty' , async ( ) => {
1643+ const { credentialsPath } = makeCreds ( ) ;
1644+ const emptyCode : CliTestCode = { ...TEST_CODE_INLINE , code : '' } ;
1645+ const fetchImpl = makeFetch ( ( ) => ( { body : emptyCode } ) ) ;
1646+ const stderr : string [ ] = [ ] ;
1647+ const got = await runCodeGet (
1648+ { profile : 'default' , output : 'text' , debug : false , testId : 'test_fe' } ,
1649+ { credentialsPath, fetchImpl, stderr : line => stderr . push ( line ) } ,
1650+ ) ;
1651+ expect ( got . code ) . toBe ( '' ) ;
1652+ expect ( stderr . join ( '\n' ) ) . toContain ( 'no code generated yet' ) ;
1653+ } ) ;
16101654} ) ;
16111655
16121656describe ( 'runCodePut' , ( ) => {
@@ -1661,6 +1705,30 @@ describe('runCodePut', () => {
16611705 expect ( sent . headers . get ( 'content-type' ) ) . toBe ( 'application/json' ) ;
16621706 } ) ;
16631707
1708+ it ( 'strips a UTF-8 BOM from --code-file before uploading (Windows PowerShell 5.1 default)' , async ( ) => {
1709+ const { credentialsPath } = makeCreds ( ) ;
1710+ const dir = mkdtempSync ( join ( tmpdir ( ) , 'cli-p4-bom-' ) ) ;
1711+ const codeFile = join ( dir , 'updated.spec.ts' ) ;
1712+ writeFileSync ( codeFile , '\uFEFF' + 'updated body' , 'utf8' ) ;
1713+ let seenBody : unknown ;
1714+ const fetchImpl = makeFetch ( ( _url , init ) => {
1715+ seenBody = init . body ? JSON . parse ( init . body as string ) : undefined ;
1716+ return { body : SAMPLE_RESPONSE } ;
1717+ } ) ;
1718+ await runCodePut (
1719+ {
1720+ profile : 'default' ,
1721+ output : 'json' ,
1722+ debug : false ,
1723+ testId : 'test_alpha' ,
1724+ codeFile,
1725+ expectedVersion : 'v3' ,
1726+ } ,
1727+ { credentialsPath, fetchImpl, stdout : ( ) => undefined , stderr : ( ) => undefined } ,
1728+ ) ;
1729+ expect ( seenBody ) . toEqual ( { code : 'updated body' } ) ;
1730+ } ) ;
1731+
16641732 it ( 'forwards --language in the body when set' , async ( ) => {
16651733 const { credentialsPath } = makeCreds ( ) ;
16661734 const codeFile = writeCodeFile ( 'print("hi")' ) ;
0 commit comments