33 *
44 * Output goes through the structured CommandOutput/OutputError path, which
55 * renders via the `output` config (stdout). For failure paths, `OutputError`
6- * triggers `process.exit()` — tests mock it to capture the exit code .
6+ * is thrown — tests catch it and check the exitCode property .
77 *
88 * Tests run non-TTY so plain mode applies: markdown is parsed and ANSI
99 * stripped. Headings render without `###`, underscores are unescaped,
1010 * and code spans have backticks stripped.
1111 */
1212
1313import { Database } from "bun:sqlite" ;
14- import {
15- afterEach ,
16- beforeEach ,
17- describe ,
18- expect ,
19- mock ,
20- spyOn ,
21- test ,
22- } from "bun:test" ;
14+ import { afterEach , describe , expect , mock , spyOn , test } from "bun:test" ;
2315import { chmodSync , statSync } from "node:fs" ;
2416import { join } from "node:path" ;
2517import { fixCommand } from "../../../src/commands/cli/fix.js" ;
@@ -29,6 +21,7 @@ import {
2921 generatePreMigrationTableDDL ,
3022 initSchema ,
3123} from "../../../src/lib/db/schema.js" ;
24+ import { OutputError } from "../../../src/lib/errors.js" ;
3225import { useTestConfigDir } from "../../helpers.js" ;
3326
3427/**
@@ -77,24 +70,11 @@ function createDatabaseWithMissingTables(
7770 ) . run ( ) ;
7871}
7972
80- /**
81- * Thrown by the mock `process.exit()` to halt execution without actually
82- * exiting the process. The `code` field captures the requested exit code.
83- */
84- class MockExitError extends Error {
85- code : number ;
86- constructor ( code : number ) {
87- super ( `process.exit(${ code } )` ) ;
88- this . code = code ;
89- }
90- }
91-
9273/**
9374 * Create a mock Stricli context that captures stdout output.
9475 *
9576 * The `buildCommand` wrapper renders structured output to `context.stdout`.
96- * For failure paths (OutputError), it calls `process.exit()` — the mock
97- * intercepts this and throws MockExitError to halt execution.
77+ * For failure paths, `OutputError` is thrown after data is rendered.
9878 */
9979function createContext ( ) {
10080 const stdoutChunks : string [ ] = [ ] ;
@@ -120,30 +100,24 @@ const getTestDir = useTestConfigDir("fix-test-");
120100
121101/**
122102 * Run the fix command with the given flags and return captured output.
123- * Mocks `process.exit()` so OutputError paths don't terminate the test.
103+ * Catches `OutputError` to extract the exit code (data is already rendered
104+ * to stdout before the throw).
124105 */
125106async function runFix ( dryRun : boolean ) {
126107 const { context, getOutput } = createContext ( ) ;
127108
128109 let exitCode = 0 ;
129- const originalExit = process . exit ;
130- process . exit = ( ( code ?: number ) => {
131- exitCode = code ?? 0 ;
132- throw new MockExitError ( code ?? 0 ) ;
133- } ) as typeof process . exit ;
134110
135111 try {
136112 const func = await fixCommand . loader ( ) ;
137113 await func . call ( context , { "dry-run" : dryRun , json : false } ) ;
138114 // Successful return — exitCode stays 0
139115 } catch ( err ) {
140- if ( err instanceof MockExitError ) {
141- exitCode = err . code ;
116+ if ( err instanceof OutputError ) {
117+ exitCode = err . exitCode ;
142118 } else {
143119 throw err ;
144120 }
145- } finally {
146- process . exit = originalExit ;
147121 }
148122
149123 return {
@@ -491,25 +465,7 @@ describe("sentry cli fix", () => {
491465describe ( "sentry cli fix — ownership detection" , ( ) => {
492466 const getOwnershipTestDir = useTestConfigDir ( "fix-ownership-test-" ) ;
493467
494- let exitMock : { restore : ( ) => void ; exitCode : number } ;
495-
496- beforeEach ( ( ) => {
497- const originalExit = process . exit ;
498- const state = {
499- exitCode : 0 ,
500- restore : ( ) => {
501- process . exit = originalExit ;
502- } ,
503- } ;
504- process . exit = ( ( code ?: number ) => {
505- state . exitCode = code ?? 0 ;
506- throw new MockExitError ( code ?? 0 ) ;
507- } ) as typeof process . exit ;
508- exitMock = state ;
509- } ) ;
510-
511468 afterEach ( ( ) => {
512- exitMock . restore ( ) ;
513469 closeDatabase ( ) ;
514470 } ) ;
515471
@@ -521,13 +477,16 @@ describe("sentry cli fix — ownership detection", () => {
521477 async function runFixWithUid ( dryRun : boolean , getuid : ( ) => number ) {
522478 const { context, getOutput } = createContext ( ) ;
523479 const getuidSpy = spyOn ( process , "getuid" ) . mockImplementation ( getuid ) ;
524- exitMock . exitCode = 0 ;
480+
481+ let exitCode = 0 ;
525482
526483 try {
527484 const func = await fixCommand . loader ( ) ;
528485 await func . call ( context , { "dry-run" : dryRun , json : false } ) ;
529486 } catch ( err ) {
530- if ( ! ( err instanceof MockExitError ) ) {
487+ if ( err instanceof OutputError ) {
488+ exitCode = err . exitCode ;
489+ } else {
531490 throw err ;
532491 }
533492 } finally {
@@ -536,7 +495,7 @@ describe("sentry cli fix — ownership detection", () => {
536495
537496 return {
538497 output : getOutput ( ) ,
539- exitCode : exitMock . exitCode ,
498+ exitCode,
540499 } ;
541500 }
542501
0 commit comments