@@ -46,6 +46,12 @@ func TestMultiTargetCustomCode(t *testing.T) {
4646 t .Parallel ()
4747 testMultiTargetCustomCodeConflictResolutionAcceptTheirs (t , speakeasyBinary )
4848 })
49+
50+ t .Run ("ConflictResolutionMultipleResolutionsAcceptTheirs" , func (t * testing.T ) {
51+ t .Parallel ()
52+ testMultiTargetCustomCodeConflictMultipleResultionsAcceptTheirs (t , speakeasyBinary )
53+ })
54+
4955}
5056
5157// testMultiTargetCustomCodeBasicWorkflow tests basic custom code registration and reapplication
@@ -691,3 +697,185 @@ func testMultiTargetCustomCodeConflictResolutionAcceptTheirs(t *testing.T, speak
691697 require .NoError (t , err , "Failed to read typescript file after final regeneration" )
692698 require .Contains (t , string (tsContentFinal ), "custom code in typescript target" , "TypeScript file should still contain its custom code" )
693699}
700+
701+ // testMultiTargetCustomCodeConflictMultipleResultionsAcceptTheirsFromOurs tests conflict resolution in two targets
702+ // sequentially, accepting custom code changes (theirs) instead of spec changes.
703+ func testMultiTargetCustomCodeConflictMultipleResultionsAcceptTheirs (t * testing.T , speakeasyBinary string ) {
704+ temp := setupMultiTargetSDKGeneration (t , speakeasyBinary , "customcodespec.yaml" )
705+
706+ // Paths to all target files
707+ goFilePath := filepath .Join (temp , "go" , "models" , "operations" , "getuserbyname.go" )
708+ tsFilePath := filepath .Join (temp , "typescript" , "src" , "models" , "operations" , "getuserbyname.ts" )
709+
710+ // Step 1: Add custom code to ALL targets
711+ modifyLineInFileByPrefix (t , goFilePath , "// The name that needs to be" , "\t // custom code in go target" )
712+ modifyLineInFileByPrefix (t , tsFilePath , "* The name that needs to be" , "// custom code in typescript target" )
713+
714+ // Step 2: Register custom code for all targets
715+ customCodeCmd := exec .Command (speakeasyBinary , "customcode" , "--output" , "console" )
716+ customCodeCmd .Dir = temp
717+ customCodeOutput , customCodeErr := customCodeCmd .CombinedOutput ()
718+ require .NoError (t , customCodeErr , "customcode command should succeed: %s" , string (customCodeOutput ))
719+
720+ // Step 3: Verify patch files were created for both targets
721+ goPatchFile := filepath .Join (temp , "go" , ".speakeasy" , "patches" , "custom-code.diff" )
722+ _ , err := os .Stat (goPatchFile )
723+ require .NoError (t , err , "Go patch file should exist" )
724+
725+ tsPatchFile := filepath .Join (temp , "typescript" , ".speakeasy" , "patches" , "custom-code.diff" )
726+ _ , err = os .Stat (tsPatchFile )
727+ require .NoError (t , err , "TypeScript patch file should exist" )
728+
729+ // Step 4: Modify the spec to cause conflict in GO target only (line 477 affects GetUserByName)
730+ specPath := filepath .Join (temp , "customcodespec.yaml" )
731+ modifyLineInFileByPrefix (t , specPath , " description: 'The name that needs to be fetched" , " description: 'spec change'" )
732+
733+ // ------First Round
734+ // Step 5: Run speakeasy run - should detect conflict in GO target only
735+ regenCmd := exec .Command (speakeasyBinary , "run" , "-t" , "all" , "--pinned" , "--skip-compile" )
736+ regenCmd .Dir = temp
737+ regenOutput , regenErr := regenCmd .CombinedOutput ()
738+ require .Error (t , regenErr , "speakeasy run should exit with error after detecting conflicts: %s" , string (regenOutput ))
739+ require .Contains (t , string (regenOutput ), "CUSTOM CODE CONFLICTS DETECTED" , "Output should show conflict detection banner" )
740+ require .Contains (t , string (regenOutput ), "Entering automatic conflict resolution mode" , "Output should indicate automatic resolution mode" )
741+
742+ // Step 6: Verify conflict markers present in GO file only
743+ goContentAfterConflict , err := os .ReadFile (goFilePath )
744+ require .NoError (t , err , "Failed to read go file after conflict" )
745+ require .Contains (t , string (goContentAfterConflict ), "<<<<<<<" , "Go file should contain conflict markers" )
746+
747+ // TypeScript file should NOT have conflict markers
748+ tsContentAfterConflict , err := os .ReadFile (tsFilePath )
749+ require .NoError (t , err , "Failed to read typescript file after conflict" )
750+ require .NotContains (t , string (tsContentAfterConflict ), "<<<<<<<" , "TypeScript file should not contain conflict markers" )
751+ require .Contains (t , string (tsContentAfterConflict ), "custom code in typescript target" , "TypeScript file should still have its custom code" )
752+
753+ // Step 7: Resolve the go conflict by accepting custom code changes (theirs)
754+ checkoutCmd := exec .Command ("git" , "checkout" , "--theirs" , goFilePath )
755+ checkoutCmd .Dir = temp
756+ checkoutOutput , checkoutErr := checkoutCmd .CombinedOutput ()
757+ require .NoError (t , checkoutErr , "git checkout --theirs should succeed: %s" , string (checkoutOutput ))
758+
759+ // Step 8: Verify conflict markers are gone in go file
760+ goContentAfterCheckout , err := os .ReadFile (goFilePath )
761+ require .NoError (t , err , "Failed to read go file after checkout" )
762+ require .NotContains (t , string (goContentAfterCheckout ), "<<<<<<<" , "Go file should not contain conflict markers after checkout" )
763+
764+ // Step 9: Stage the resolved go file
765+ gitAddCmd := exec .Command ("git" , "add" , goFilePath )
766+ gitAddCmd .Dir = temp
767+ gitAddOutput , gitAddErr := gitAddCmd .CombinedOutput ()
768+ require .NoError (t , gitAddErr , "git add should succeed: %s" , string (gitAddOutput ))
769+
770+ // Step 10: Run customcode command to register the resolution
771+ customCodeCmd2 := exec .Command (speakeasyBinary , "customcode" , "--output" , "console" )
772+ customCodeCmd2 .Dir = temp
773+ customCodeOutput2 , customCodeErr2 := customCodeCmd2 .CombinedOutput ()
774+ require .NoError (t , customCodeErr2 , "customcode command should succeed after conflict resolution: %s" , string (customCodeOutput2 ))
775+
776+ // Step 11: Verify patch files status
777+ // Go patch file should still exist with content since we accepted theirs (custom code)
778+ goPatchContent , err := os .ReadFile (goPatchFile )
779+ require .NoError (t , err , "Go patch file should still exist" )
780+ require .NotEmpty (t , goPatchContent , "Go patch file should not be empty after accepting theirs" )
781+ require .Contains (t , string (goPatchContent ), "custom code in go target" , "Go patch should contain go custom code" )
782+
783+ // TypeScript patch file should still exist with its content
784+ tsPatchContent , err := os .ReadFile (tsPatchFile )
785+ require .NoError (t , err , "TypeScript patch file should still exist" )
786+ require .NotEmpty (t , tsPatchContent , "TypeScript patch file should not be empty" )
787+ require .Contains (t , string (tsPatchContent ), "custom code in typescript target" , "TypeScript patch should contain typescript custom code" )
788+
789+ // Step 12: Verify gen.lock files
790+ // Go's gen.lock should still contain customCodeCommitHash since we kept custom code
791+ goGenLockPath := filepath .Join (temp , "go" , ".speakeasy" , "gen.lock" )
792+ goGenLockContent , err := os .ReadFile (goGenLockPath )
793+ require .NoError (t , err , "Failed to read go gen.lock" )
794+ require .Contains (t , string (goGenLockContent ), "customCodeCommitHash" , "Go gen.lock should contain customCodeCommitHash after accepting theirs" )
795+
796+ // TypeScript's gen.lock should still contain customCodeCommitHash
797+ tsGenLockPath := filepath .Join (temp , "typescript" , ".speakeasy" , "gen.lock" )
798+ tsGenLockContent , err := os .ReadFile (tsGenLockPath )
799+ require .NoError (t , err , "Failed to read typescript gen.lock" )
800+ require .Contains (t , string (tsGenLockContent ), "customCodeCommitHash" , "TypeScript gen.lock should still contain customCodeCommitHash" )
801+
802+ // ------Second Round
803+
804+ // Step 13: Run regeneration again - should detect conflict in TS target.
805+ regenCmd2 := exec .Command (speakeasyBinary , "run" , "-t" , "all" , "--pinned" , "--skip-compile" )
806+ regenCmd2 .Dir = temp
807+ regenOutput2 , regenErr2 := regenCmd2 .CombinedOutput ()
808+ require .Error (t , regenErr2 , "speakeasy run should exit with error after detecting conflicts: %s" , string (regenOutput2 ))
809+ require .Contains (t , string (regenOutput2 ), "CUSTOM CODE CONFLICTS DETECTED" , "Output should show conflict detection banner" )
810+ require .Contains (t , string (regenOutput2 ), "Entering automatic conflict resolution mode" , "Output should indicate automatic resolution mode" )
811+
812+ // Step 14: Verify conflict markers present in TS file only
813+ tsContentAfterConflict2 , err := os .ReadFile (tsFilePath )
814+ require .NoError (t , err , "Failed to read ts file after conflict" )
815+ require .Contains (t , string (tsContentAfterConflict2 ), "<<<<<<<" , "TS file should contain conflict markers" )
816+
817+ // Go file should NOT have conflict markers
818+ goContentAfterConflict2 , err := os .ReadFile (goFilePath )
819+ require .NoError (t , err , "Failed to read go file after conflict" )
820+ require .NotContains (t , string (goContentAfterConflict2 ), "<<<<<<<" , "GO file should not contain conflict markers" )
821+
822+ // Step 15: Resolve the ts conflict by accepting custom code changes (theirs)
823+ checkoutCmd2 := exec .Command ("git" , "checkout" , "--theirs" , tsFilePath )
824+ checkoutCmd2 .Dir = temp
825+ checkoutOutput2 , checkoutErr2 := checkoutCmd2 .CombinedOutput ()
826+ require .NoError (t , checkoutErr2 , "git checkout --theirs should succeed: %s" , string (checkoutOutput2 ))
827+
828+ // Step 16: Verify conflict markers are gone in ts file
829+ tsContentAfterCheckout2 , err := os .ReadFile (tsFilePath )
830+ require .NoError (t , err , "Failed to read ts file after checkout" )
831+ require .NotContains (t , string (tsContentAfterCheckout2 ), "<<<<<<<" , "TS file should not contain conflict markers after checkout" )
832+
833+ // Step 17: Stage the resolved ts file
834+ gitAddCmd2 := exec .Command ("git" , "add" , tsFilePath )
835+ gitAddCmd2 .Dir = temp
836+ gitAddOutput2 , gitAddErr2 := gitAddCmd2 .CombinedOutput ()
837+ require .NoError (t , gitAddErr2 , "git add should succeed: %s" , string (gitAddOutput2 ))
838+
839+ // Step 18: Run customcode command to register the resolution
840+ customCodeCmd3 := exec .Command (speakeasyBinary , "customcode" , "--output" , "console" )
841+ customCodeCmd3 .Dir = temp
842+ customCodeOutput3 , customCodeErr3 := customCodeCmd3 .CombinedOutput ()
843+ require .NoError (t , customCodeErr3 , "customcode command should succeed after conflict resolution: %s" , string (customCodeOutput3 ))
844+
845+ // Step 19: Verify patch files status
846+ // TS patch file should still exist with content since we accepted theirs (custom code)
847+ tsPatchContent2 , err := os .ReadFile (tsPatchFile )
848+ require .NoError (t , err , "TS patch file should still exist" )
849+ require .NotEmpty (t , tsPatchContent2 , "TS patch file should not be empty after accepting theirs" )
850+ require .Contains (t , string (tsPatchContent2 ), "custom code in typescript target" , "TS patch should contain typescript custom code" )
851+
852+ // Step 20: Verify gen.lock files
853+ // Go's gen.lock should still contain customCodeCommitHash since we kept custom code
854+ goGenLockPath2 := filepath .Join (temp , "go" , ".speakeasy" , "gen.lock" )
855+ goGenLockContent2 , err := os .ReadFile (goGenLockPath2 )
856+ require .NoError (t , err , "Failed to read go gen.lock" )
857+ require .Contains (t , string (goGenLockContent2 ), "customCodeCommitHash" , "Go gen.lock should contain customCodeCommitHash after accepting theirs" )
858+
859+ // TypeScript's gen.lock should still contain customCodeCommitHash since we kept custom code
860+ tsGenLockPath2 := filepath .Join (temp , "typescript" , ".speakeasy" , "gen.lock" )
861+ tsGenLockContent2 , err := os .ReadFile (tsGenLockPath2 )
862+ require .NoError (t , err , "Failed to read typescript gen.lock" )
863+ require .Contains (t , string (tsGenLockContent2 ), "customCodeCommitHash" , "TS gen.lock should contain customCodeCommitHash after accepting theirs" )
864+ // -----------------
865+
866+ // Step 21: Run final regeneration to ensure everything works
867+ runRegeneration (t , speakeasyBinary , temp , true )
868+
869+ // Step 22: Verify final state
870+ // Go file should contain custom code (since we accepted theirs), and should NOT contain spec change
871+ goContentFinal , err := os .ReadFile (goFilePath )
872+ require .NoError (t , err , "Failed to read go file after final regeneration" )
873+ require .NotContains (t , string (goContentFinal ), "spec change" , "Go file should not contain spec change after accepting theirs" )
874+ require .Contains (t , string (goContentFinal ), "custom code in go target" , "Go file should contain custom code after accepting theirs" )
875+
876+ // TypeScript file should contain custom code (since we accepted theirs), and should NOT contain spec change
877+ tsContentFinal , err := os .ReadFile (tsFilePath )
878+ require .NoError (t , err , "Failed to read typescript file after final regeneration" )
879+ require .NotContains (t , string (tsContentFinal ), "spec change" , "TypeScript file should not contain spec change after accepting theirs" )
880+ require .Contains (t , string (tsContentFinal ), "custom code in typescript target" , "TypeScript file should contain custom code after accepting theirs" )
881+ }
0 commit comments