Skip to content

Commit 15125a4

Browse files
committed
tests
1 parent 844ca45 commit 15125a4

File tree

2 files changed

+201
-2
lines changed

2 files changed

+201
-2
lines changed

integration/customcode_multitarget_test.go

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

internal/registercustomcode/registercustomcode.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -456,8 +456,19 @@ func completeConflictResolution(ctx context.Context, wf *workflow.Workflow) erro
456456
checkCommitCmd := exec.Command("git", "log", "-1", "--grep=clean generation (conflict resolution)", "--format=%H", "--", outDir)
457457
commitOutput, err := checkCommitCmd.Output()
458458
if err == nil && strings.TrimSpace(string(commitOutput)) != "" {
459-
targetsInResolution[targetName] = true
460-
logger.Info(fmt.Sprintf("Target %s was part of conflict resolution", targetName))
459+
// Get the most recent commit hash
460+
headCommitCmd := exec.Command("git", "rev-parse", "HEAD")
461+
headCommitOutput, headErr := headCommitCmd.Output()
462+
if headErr == nil {
463+
commitHash := strings.TrimSpace(string(commitOutput))
464+
headCommitHash := strings.TrimSpace(string(headCommitOutput))
465+
466+
// Only consider it part of resolution if the commit is the HEAD commit
467+
if commitHash == headCommitHash {
468+
targetsInResolution[targetName] = true
469+
logger.Info(fmt.Sprintf("Target %s was part of conflict resolution", targetName))
470+
}
471+
}
461472
}
462473
}
463474

0 commit comments

Comments
 (0)