2025-09-18 19:00:18 -07:00
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { vi , describe , it , expect , beforeEach , afterEach } from 'vitest' ;
import { execSync } from 'node:child_process' ;
import { join } from 'node:path' ;
/**
* Helper function to run the patch-create-comment script with given parameters
*/
function runPatchCreateComment ( args , env = { } ) {
const scriptPath = join (
process . cwd ( ) ,
'scripts/releasing/patch-create-comment.js' ,
) ;
const fullEnv = {
... process . env ,
... env ,
} ;
try {
const result = execSync ( ` node ${ scriptPath } ${ args } ` , {
encoding : 'utf8' ,
env : fullEnv ,
} ) ;
return { stdout : result , stderr : '' , success : true } ;
} catch ( error ) {
return {
stdout : error . stdout || '' ,
stderr : error . stderr || '' ,
success : false ,
code : error . status ,
} ;
}
}
describe ( 'patch-create-comment' , ( ) => {
beforeEach ( ( ) => {
2025-11-06 18:30:20 -05:00
vi . stubEnv ( ) ;
// Always run in test mode to avoid GitHub API calls
vi . stubEnv ( 'TEST_MODE' , 'true' ) ;
2025-09-18 19:00:18 -07:00
} ) ;
afterEach ( ( ) => {
vi . clearAllMocks ( ) ;
2025-11-06 18:30:20 -05:00
vi . unstubAllEnvs ( ) ;
} ) ;
describe ( 'Environment flag' , ( ) => {
it ( 'can be overridden with a flag' , ( ) => {
vi . stubEnv ( 'ENVIRONMENT' , 'dev' ) ;
const result = runPatchCreateComment (
'--original-pr 8655 --exit-code 0 --environment prod --commit abc1234 --channel preview --repository google-gemini/gemini-cli --test' ,
) ;
expect ( result . success ) . toBe ( true ) ;
2026-03-10 13:15:04 -04:00
expect ( result . stdout ) . toContain ( '🚀 **[Step 2/4] Patch PR Created!**' ) ;
2025-11-06 18:30:20 -05:00
expect ( result . stdout ) . toContain ( 'Environment**: `prod`' ) ;
} ) ;
it ( 'reads from the ENVIRONMENT env variable' , ( ) => {
vi . stubEnv ( 'ENVIRONMENT' , 'dev' ) ;
const result = runPatchCreateComment (
'--original-pr 8655 --exit-code 0 --commit abc1234 --channel preview --repository google-gemini/gemini-cli --test' ,
) ;
expect ( result . success ) . toBe ( true ) ;
2026-03-10 13:15:04 -04:00
expect ( result . stdout ) . toContain ( '🚀 **[Step 2/4] Patch PR Created!**' ) ;
2025-11-06 18:30:20 -05:00
expect ( result . stdout ) . toContain ( 'Environment**: `dev`' ) ;
} ) ;
it ( 'fails if the ENVIRONMENT is bogus' , ( ) => {
vi . stubEnv ( 'ENVIRONMENT' , 'totally-bogus' ) ;
const result = runPatchCreateComment (
'--original-pr 8655 --exit-code 0 --commit abc1234 --channel preview --repository google-gemini/gemini-cli --test' ,
) ;
expect ( result . success ) . toBe ( false ) ;
expect ( result . stderr ) . toContain (
'Argument: environment, Given: "totally-bogus", Choices: "prod", "dev"' ,
) ;
} ) ;
it ( 'defaults to prod if not specified' , ( ) => {
const result = runPatchCreateComment (
'--original-pr 8655 --exit-code 0 --commit abc1234 --channel preview --repository google-gemini/gemini-cli --test' ,
) ;
expect ( result . success ) . toBe ( true ) ;
2026-03-10 13:15:04 -04:00
expect ( result . stdout ) . toContain ( '🚀 **[Step 2/4] Patch PR Created!**' ) ;
2025-11-06 18:30:20 -05:00
expect ( result . stdout ) . toContain ( 'Environment**: `prod`' ) ;
} ) ;
2025-09-18 19:00:18 -07:00
} ) ;
2025-11-06 18:30:20 -05:00
2025-09-18 19:00:18 -07:00
describe ( 'Environment Variable vs File Reading' , ( ) => {
it ( 'should prefer LOG_CONTENT environment variable over file' , ( ) => {
const result = runPatchCreateComment (
' -- original - pr 8655 -- exit - code 0 -- commit abc1234 -- channel preview -- repository google - gemini / gemini - cli -- test ',
{
LOG_CONTENT:
' Creating hotfix branch hotfix / v0 . 5.3 / preview / cherry - pick - abc1234 from release / v0 . 5.3 ',
},
);
expect(result.success).toBe(true);
2026-03-10 13:15:04 -04:00
expect(result.stdout).toContain(' 🚀 * * [ Step 2 / 4 ] Patch PR Created ! * * ');
2025-09-18 19:00:18 -07:00
expect(result.stdout).toContain(' Channel * * : ` preview ` ');
expect(result.stdout).toContain(' Commit * * : ` abc1234 ` ');
});
it(' should use empty log content when LOG _CONTENT is not set ', () => {
const result = runPatchCreateComment(
' -- original - pr 8655 -- exit - code 1 -- commit abc1234 -- channel stable -- repository google - gemini / gemini - cli -- test ',
{}, // No LOG_CONTENT env var
);
expect(result.success).toBe(true);
2026-03-10 13:15:04 -04:00
expect(result.stdout).toContain(
' ❌ * * [ Step 2 / 4 ] Patch creation failed ! * * ',
);
2025-09-18 19:00:18 -07:00
expect(result.stdout).toContain(
' There was an error creating the patch release ',
);
});
});
describe(' Log Content Parsing - Success Scenarios ', () => {
it(' should generate success comment for clean cherry - pick ', () => {
const result = runPatchCreateComment(
' -- original - pr 8655 -- exit - code 0 -- commit abc1234 -- channel stable -- repository google - gemini / gemini - cli -- test ',
{
LOG_CONTENT:
' Creating hotfix branch hotfix / v0 . 4.1 / stable / cherry - pick - abc1234 from release / v0 . 4.1 \ n ✅ Cherry - pick successful - no conflicts detected ',
},
);
expect(result.success).toBe(true);
2026-03-10 13:15:04 -04:00
expect(result.stdout).toContain(' 🚀 * * [ Step 2 / 4 ] Patch PR Created ! * * ');
2025-09-18 19:00:18 -07:00
expect(result.stdout).toContain(' Channel * * : ` stable ` ');
expect(result.stdout).toContain(' Commit * * : ` abc1234 ` ');
expect(result.stdout).not.toContain(' ⚠ ️ Status ');
});
it(' should generate conflict comment for cherry - pick with conflicts ', () => {
const result = runPatchCreateComment(
' -- original - pr 8655 -- exit - code 0 -- commit def5678 -- channel preview -- repository google - gemini / gemini - cli -- test ',
{
LOG_CONTENT:
' Creating hotfix branch hotfix / v0 . 5.0 - preview . 2 / preview / cherry - pick - def5678 from release / v0 . 5.0 - preview . 2 \ nCherry - pick has conflicts in 2 file ( s ) : \ nCONFLICT ( content ) : Merge conflict in package . json ',
},
);
expect(result.success).toBe(true);
2026-03-10 13:15:04 -04:00
expect(result.stdout).toContain(' 🚀 * * [ Step 2 / 4 ] Patch PR Created ! * * ');
2025-09-18 19:00:18 -07:00
expect(result.stdout).toContain(
' ⚠ ️ Status * * : Cherry - pick conflicts detected ',
);
expect(result.stdout).toContain(
' ⚠ ️ * * Resolve conflicts * * in the hotfix PR first ',
);
expect(result.stdout).toContain(' Channel * * : ` preview ` ');
});
});
describe(' Log Content Parsing - Existing PR Scenarios ', () => {
it(' should detect existing PR and generate appropriate comment ', () => {
const result = runPatchCreateComment(
' -- original - pr 8655 -- exit - code 0 -- commit ghi9012 -- channel stable -- repository google - gemini / gemini - cli -- test ',
{
LOG_CONTENT:
' Hotfix branch hotfix / v0 . 4.1 / stable / cherry - pick - ghi9012 already has an open PR . \ nFound existing PR # 8700 : https : //github.com/google-gemini/gemini-cli/pull/8700',
} ,
) ;
expect ( result . success ) . toBe ( true ) ;
2026-03-10 13:15:04 -04:00
expect ( result . stdout ) . toContain (
'ℹ ️ **[Step 2/4] Patch PR already exists!**' ,
) ;
2025-09-18 19:00:18 -07:00
expect ( result . stdout ) . toContain (
'A patch PR for this change already exists: [#8700](https://github.com/google-gemini/gemini-cli/pull/8700)' ,
) ;
expect ( result . stdout ) . toContain (
'Review and approve the existing patch PR' ,
) ;
} ) ;
it ( 'should detect branch exists but no PR scenario' , ( ) => {
const result = runPatchCreateComment (
'--original-pr 8655 --exit-code 0 --commit jkl3456 --channel preview --repository google-gemini/gemini-cli --test' ,
{
LOG _CONTENT :
'Hotfix branch hotfix/v0.5.0-preview.2/preview/cherry-pick-jkl3456 exists but has no open PR.\nHotfix branch hotfix/v0.5.0-preview.2/preview/cherry-pick-jkl3456 already exists.' ,
} ,
) ;
expect ( result . success ) . toBe ( true ) ;
expect ( result . stdout ) . toContain (
2026-03-10 13:15:04 -04:00
'ℹ ️ **[Step 2/4] Patch branch exists but no PR found!**' ,
2025-09-18 19:00:18 -07:00
) ;
expect ( result . stdout ) . toContain (
'Delete the branch: `git branch -D hotfix/v0.5.0-preview.2/preview/cherry-pick-jkl3456`' ,
) ;
expect ( result . stdout ) . toContain ( 'Run the patch command again' ) ;
} ) ;
} ) ;
describe ( 'Log Content Parsing - Failure Scenarios' , ( ) => {
it ( 'should generate failure comment when exit code is non-zero' , ( ) => {
const result = runPatchCreateComment (
'--original-pr 8655 --exit-code 1 --commit mno7890 --channel stable --repository google-gemini/gemini-cli --run-id 12345 --test' ,
{
LOG _CONTENT : 'Error: Failed to create patch' ,
} ,
) ;
expect ( result . success ) . toBe ( true ) ;
2026-03-10 13:15:04 -04:00
expect ( result . stdout ) . toContain (
'❌ **[Step 2/4] Patch creation failed!**' ,
) ;
2025-09-18 19:00:18 -07:00
expect ( result . stdout ) . toContain (
'There was an error creating the patch release' ,
) ;
expect ( result . stdout ) . toContain (
'View workflow run](https://github.com/google-gemini/gemini-cli/actions/runs/12345)' ,
) ;
} ) ;
it ( 'should generate fallback failure comment when no output is generated' , ( ) => {
const result = runPatchCreateComment (
'--original-pr 8655 --exit-code 1 --commit pqr4567 --channel preview --repository google-gemini/gemini-cli --run-id 67890 --test' ,
{
LOG _CONTENT : '' ,
} ,
) ;
expect ( result . success ) . toBe ( true ) ;
2026-03-10 13:15:04 -04:00
expect ( result . stdout ) . toContain (
'❌ **[Step 2/4] Patch creation failed!**' ,
) ;
2025-09-18 19:00:18 -07:00
expect ( result . stdout ) . toContain (
'There was an error creating the patch release' ,
) ;
} ) ;
} ) ;
describe ( 'Channel and NPM Tag Detection' , ( ) => {
it ( 'should correctly map stable channel to latest npm tag' , ( ) => {
const result = runPatchCreateComment (
'--original-pr 8655 --exit-code 0 --commit stu8901 --channel stable --repository google-gemini/gemini-cli --test' ,
{
LOG _CONTENT :
'Creating hotfix branch hotfix/v0.4.1/stable/cherry-pick-stu8901 from release/v0.4.1' ,
} ,
) ;
expect ( result . success ) . toBe ( true ) ;
expect ( result . stdout ) . toContain ( 'will publish to npm tag `latest`' ) ;
} ) ;
it ( 'should correctly map preview channel to preview npm tag' , ( ) => {
const result = runPatchCreateComment (
'--original-pr 8655 --exit-code 0 --commit vwx2345 --channel preview --repository google-gemini/gemini-cli --test' ,
{
LOG _CONTENT :
'Creating hotfix branch hotfix/v0.5.0-preview.2/preview/cherry-pick-vwx2345 from release/v0.5.0-preview.2' ,
} ,
) ;
expect ( result . success ) . toBe ( true ) ;
expect ( result . stdout ) . toContain ( 'will publish to npm tag `preview`' ) ;
} ) ;
} ) ;
describe ( 'No Original PR Scenario' , ( ) => {
it ( 'should skip comment when no original PR is specified' , ( ) => {
const result = runPatchCreateComment (
'--original-pr 0 --exit-code 0 --commit yza6789 --channel stable --repository google-gemini/gemini-cli --test' ,
{
LOG _CONTENT :
'Creating hotfix branch hotfix/v0.4.1/stable/cherry-pick-yza6789 from release/v0.4.1' ,
ORIGINAL _PR : '' , // Override with empty PR
} ,
) ;
expect ( result . success ) . toBe ( true ) ;
expect ( result . stdout ) . toContain (
'No original PR specified, skipping comment' ,
) ;
} ) ;
} ) ;
describe ( 'Error Handling' , ( ) => {
it ( 'should handle empty LOG_CONTENT gracefully' , ( ) => {
const result = runPatchCreateComment (
'--original-pr 8655 --exit-code 1 --commit bcd0123 --channel stable --repository google-gemini/gemini-cli --test' ,
{ LOG _CONTENT : '' } , // Empty log content
) ;
expect ( result . success ) . toBe ( true ) ;
2026-03-10 13:15:04 -04:00
expect ( result . stdout ) . toContain (
'❌ **[Step 2/4] Patch creation failed!**' ,
) ;
2025-09-18 19:00:18 -07:00
expect ( result . stdout ) . toContain (
'There was an error creating the patch release' ,
) ;
} ) ;
} ) ;
2025-09-19 04:07:26 -07:00
describe ( 'GitHub App Permission Scenarios' , ( ) => {
it ( 'should parse manual commands with clipboard emoji correctly' , ( ) => {
const result = runPatchCreateComment (
'--original-pr 8655 --exit-code 1 --commit abc1234 --channel stable --repository google-gemini/gemini-cli --test' ,
{
LOG _CONTENT : ` ❌ Failed to create release branch due to insufficient GitHub App permissions.
📋 Please run these commands manually to create the branch:
\` \` \` bash
git checkout -b hotfix/v0.4.1/stable/cherry-pick-abc1234 v0.4.1
git push origin hotfix/v0.4.1/stable/cherry-pick-abc1234
\` \` \` ` ,
} ,
) ;
expect ( result . success ) . toBe ( true ) ;
2026-03-10 13:15:04 -04:00
expect ( result . stdout ) . toContain (
'🔒 **[Step 2/4] GitHub App Permission Issue**' ,
) ;
2025-09-19 04:07:26 -07:00
expect ( result . stdout ) . toContain (
'Please run these commands manually to create the release branch:' ,
) ;
expect ( result . stdout ) . toContain (
'git checkout -b hotfix/v0.4.1/stable/cherry-pick-abc1234 v0.4.1' ,
) ;
expect ( result . stdout ) . toContain (
'git push origin hotfix/v0.4.1/stable/cherry-pick-abc1234' ,
) ;
} ) ;
} ) ;
2025-09-18 19:00:18 -07:00
describe ( 'Test Mode Flag' , ( ) => {
it ( 'should generate mock content in test mode for success' , ( ) => {
const result = runPatchCreateComment (
'--original-pr 8655 --exit-code 0 --commit efg4567 --channel preview --repository google-gemini/gemini-cli --test' ,
) ;
expect ( result . success ) . toBe ( true ) ;
expect ( result . stdout ) . toContain (
'🧪 TEST MODE - No API calls will be made' ,
) ;
2026-03-10 13:15:04 -04:00
expect ( result . stdout ) . toContain ( '🚀 **[Step 2/4] Patch PR Created!**' ) ;
2025-09-18 19:00:18 -07:00
} ) ;
it ( 'should generate mock content in test mode for failure' , ( ) => {
const result = runPatchCreateComment (
'--original-pr 8655 --exit-code 1 --commit hij8901 --channel stable --repository google-gemini/gemini-cli --test' ,
) ;
expect ( result . success ) . toBe ( true ) ;
2026-03-10 13:15:04 -04:00
expect ( result . stdout ) . toContain (
'❌ **[Step 2/4] Patch creation failed!**' ,
) ;
2025-09-18 19:00:18 -07:00
} ) ;
} ) ;
} ) ;