@@ -340,3 +340,189 @@ func assertSlotValue(t *testing.T, townRoot, issueID, slot, want string) {
340340 t .Fatalf ("slot %s for %s = %q, want %q" , slot , issueID , got , want )
341341 }
342342}
343+
344+ // TestInstallDoctorClean validates that gt install creates a functional system.
345+ // This test verifies:
346+ // 1. gt install succeeds with proper structure
347+ // 2. gt rig add succeeds
348+ // 3. gt crew add succeeds
349+ // 4. Basic commands work
350+ //
351+ // NOTE: Full doctor --fix verification is currently limited by known issues:
352+ // - Doctor fix has bugs with bead creation (UNIQUE constraint errors)
353+ // - Container environment lacks tmux for session checks
354+ // - Test repos don't satisfy priming expectations (AGENTS.md length)
355+ //
356+ // TODO: Enable full doctor verification once these issues are resolved.
357+ func TestInstallDoctorClean (t * testing.T ) {
358+ // Skip if bd is not available
359+ if _ , err := exec .LookPath ("bd" ); err != nil {
360+ t .Skip ("bd not installed" )
361+ }
362+
363+ tmpDir := t .TempDir ()
364+ hqPath := filepath .Join (tmpDir , "test-hq" )
365+ gtBinary := buildGT (t )
366+
367+ // Clean environment for predictable behavior
368+ env := cleanGTEnv ()
369+ env = append (env , "HOME=" + tmpDir )
370+
371+ // 1. Install town with git
372+ t .Run ("install" , func (t * testing.T ) {
373+ runGTCmd (t , gtBinary , tmpDir , env , "install" , hqPath , "--name" , "test-town" , "--git" )
374+ })
375+
376+ // 2. Verify core structure exists
377+ t .Run ("verify-structure" , func (t * testing.T ) {
378+ assertDirExists (t , filepath .Join (hqPath , "mayor" ), "mayor/" )
379+ assertDirExists (t , filepath .Join (hqPath , "deacon" ), "deacon/" )
380+ assertDirExists (t , filepath .Join (hqPath , ".beads" ), ".beads/" )
381+ assertFileExists (t , filepath .Join (hqPath , "mayor" , "town.json" ), "mayor/town.json" )
382+ assertFileExists (t , filepath .Join (hqPath , "mayor" , "rigs.json" ), "mayor/rigs.json" )
383+ })
384+
385+ // 3. Create a test git repo and add as rig
386+ testRepoPath := createTestGitRepo (t , "testproject" )
387+ t .Run ("rig-add" , func (t * testing.T ) {
388+ runGTCmd (t , gtBinary , hqPath , env , "rig" , "add" , "testrig" , testRepoPath , "--prefix" , "tr" )
389+ })
390+
391+ // 4. Verify rig structure exists
392+ t .Run ("verify-rig-structure" , func (t * testing.T ) {
393+ rigPath := filepath .Join (hqPath , "testrig" )
394+ assertDirExists (t , rigPath , "testrig/" )
395+ assertDirExists (t , filepath .Join (rigPath , "witness" ), "testrig/witness/" )
396+ assertDirExists (t , filepath .Join (rigPath , "refinery" ), "testrig/refinery/" )
397+ assertDirExists (t , filepath .Join (rigPath , ".repo.git" ), "testrig/.repo.git/" )
398+ })
399+
400+ // 5. Add a crew member
401+ t .Run ("crew-add" , func (t * testing.T ) {
402+ runGTCmd (t , gtBinary , hqPath , env , "crew" , "add" , "jayne" , "--rig" , "testrig" )
403+ })
404+
405+ // 6. Verify crew structure exists
406+ t .Run ("verify-crew-structure" , func (t * testing.T ) {
407+ crewPath := filepath .Join (hqPath , "testrig" , "crew" , "jayne" )
408+ assertDirExists (t , crewPath , "testrig/crew/jayne/" )
409+ })
410+
411+ // 7. Basic commands should work
412+ t .Run ("commands" , func (t * testing.T ) {
413+ runGTCmd (t , gtBinary , hqPath , env , "rig" , "list" )
414+ runGTCmd (t , gtBinary , hqPath , env , "crew" , "list" , "--rig" , "testrig" )
415+ runGTCmd (t , gtBinary , hqPath , env , "mail" , "inbox" )
416+ runGTCmd (t , gtBinary , hqPath , env , "hook" )
417+ })
418+
419+ // 8. Doctor runs without crashing (may have warnings/errors but should not panic)
420+ t .Run ("doctor-runs" , func (t * testing.T ) {
421+ // Run doctor and capture output - we just verify it doesn't crash
422+ // Full clean verification is TODO pending doctor fix bugs
423+ cmd := exec .Command (gtBinary , "doctor" , "-v" )
424+ cmd .Dir = hqPath
425+ cmd .Env = env
426+ out , _ := cmd .CombinedOutput ()
427+ t .Logf ("Doctor output:\n %s" , out )
428+ // Note: We don't fail on doctor errors yet due to known issues
429+ })
430+ }
431+
432+ // TestInstallWithDaemon validates that gt install creates a functional system
433+ // with the daemon running. This extends TestInstallDoctorClean by:
434+ // 1. Starting the daemon after install
435+ // 2. Verifying the daemon is healthy
436+ // 3. Running basic operations with daemon support
437+ func TestInstallWithDaemon (t * testing.T ) {
438+ // Skip if bd is not available
439+ if _ , err := exec .LookPath ("bd" ); err != nil {
440+ t .Skip ("bd not installed" )
441+ }
442+
443+ tmpDir := t .TempDir ()
444+ hqPath := filepath .Join (tmpDir , "test-hq" )
445+ gtBinary := buildGT (t )
446+
447+ // Clean environment for predictable behavior
448+ env := cleanGTEnv ()
449+ env = append (env , "HOME=" + tmpDir )
450+
451+ // 1. Install town with git
452+ t .Run ("install" , func (t * testing.T ) {
453+ runGTCmd (t , gtBinary , tmpDir , env , "install" , hqPath , "--name" , "test-town" , "--git" )
454+ })
455+
456+ // 2. Start daemon
457+ t .Run ("daemon-start" , func (t * testing.T ) {
458+ runGTCmd (t , gtBinary , hqPath , env , "daemon" , "start" )
459+ })
460+
461+ // Ensure daemon is stopped on test cleanup
462+ t .Cleanup (func () {
463+ cmd := exec .Command (gtBinary , "daemon" , "stop" )
464+ cmd .Dir = hqPath
465+ cmd .Env = env
466+ _ = cmd .Run () // Best effort cleanup
467+ })
468+
469+ // 3. Verify daemon is running
470+ t .Run ("daemon-status" , func (t * testing.T ) {
471+ cmd := exec .Command (gtBinary , "daemon" , "status" )
472+ cmd .Dir = hqPath
473+ cmd .Env = env
474+ out , err := cmd .CombinedOutput ()
475+ if err != nil {
476+ t .Fatalf ("daemon status failed: %v\n %s" , err , out )
477+ }
478+ if ! strings .Contains (string (out ), "running" ) {
479+ t .Errorf ("expected daemon to be running, got: %s" , out )
480+ }
481+ })
482+
483+ // 4. Create rig and verify operations work
484+ testRepoPath := createTestGitRepo (t , "testproject" )
485+ t .Run ("rig-add" , func (t * testing.T ) {
486+ runGTCmd (t , gtBinary , hqPath , env , "rig" , "add" , "testrig" , testRepoPath , "--prefix" , "tr" )
487+ })
488+
489+ // 5. Add crew member
490+ t .Run ("crew-add" , func (t * testing.T ) {
491+ runGTCmd (t , gtBinary , hqPath , env , "crew" , "add" , "jayne" , "--rig" , "testrig" )
492+ })
493+
494+ // 6. Verify commands work with daemon running
495+ t .Run ("commands" , func (t * testing.T ) {
496+ runGTCmd (t , gtBinary , hqPath , env , "rig" , "list" )
497+ runGTCmd (t , gtBinary , hqPath , env , "crew" , "list" , "--rig" , "testrig" )
498+ runGTCmd (t , gtBinary , hqPath , env , "mail" , "inbox" )
499+ runGTCmd (t , gtBinary , hqPath , env , "hook" )
500+ })
501+
502+ // 7. Verify daemon shows in doctor output
503+ t .Run ("doctor-daemon-check" , func (t * testing.T ) {
504+ cmd := exec .Command (gtBinary , "doctor" , "-v" )
505+ cmd .Dir = hqPath
506+ cmd .Env = env
507+ out , _ := cmd .CombinedOutput ()
508+ outStr := string (out )
509+ t .Logf ("Doctor output:\n %s" , outStr )
510+
511+ // Verify daemon check passes (shows as running)
512+ if ! strings .Contains (outStr , "Daemon is running" ) && ! strings .Contains (outStr , "daemon" ) {
513+ t .Logf ("Note: daemon check output: %s" , outStr )
514+ }
515+ })
516+ }
517+
518+ // runGTCmd runs a gt command and fails the test if it fails.
519+ func runGTCmd (t * testing.T , binary , dir string , env []string , args ... string ) {
520+ t .Helper ()
521+ cmd := exec .Command (binary , args ... )
522+ cmd .Dir = dir
523+ cmd .Env = env
524+ out , err := cmd .CombinedOutput ()
525+ if err != nil {
526+ t .Fatalf ("gt %v failed: %v\n %s" , args , err , out )
527+ }
528+ }
0 commit comments