From bb1f593d9aae0381842d35029fbe69111fe05458 Mon Sep 17 00:00:00 2001 From: notgitika Date: Fri, 8 May 2026 19:48:53 -0400 Subject: [PATCH 1/2] fix: resolve target-based AB test target name mismatch The resolveTargetName() function had a false-positive in its startsWith check that prevented it from applying the project prefix. When the runtime name equals the project name (the default case), target names like "Foo-prod" already start with "Foo-" so the guard incorrectly assumed they were already prefixed and returned them unchanged. However, post-deploy-http-gateways.ts unconditionally creates targets as `${projectName}-${tgt.name}` (e.g., "Foo-Foo-prod"), so the AB test referenced a non-existent target and stayed in NOT_STARTED forever. The fix removes the startsWith heuristic and always applies the prefix, matching what post-deploy-http-gateways.ts does. --- src/cli/operations/deploy/post-deploy-ab-tests.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/cli/operations/deploy/post-deploy-ab-tests.ts b/src/cli/operations/deploy/post-deploy-ab-tests.ts index d4c6d4314..9c5481d93 100644 --- a/src/cli/operations/deploy/post-deploy-ab-tests.ts +++ b/src/cli/operations/deploy/post-deploy-ab-tests.ts @@ -476,14 +476,11 @@ function resolveConfigBundleVersion( } /** - * Resolve a variant target name, applying the project prefix if not already present. - * This handles legacy configs that were created before the prefix requirement. + * Resolve a variant target name by applying the project prefix unconditionally. + * post-deploy-http-gateways.ts always creates targets as `${projectName}-${tgt.name}`, + * so the AB test must reference the same prefixed name. */ function resolveTargetName(targetName: string, projectName: string): string { - // If the target name already starts with the project prefix, use as-is to avoid double-prefixing - if (targetName.startsWith(`${projectName}-`)) { - return targetName; - } return `${projectName}-${targetName}`; } From 9cfed0d13b0ec85548e3377717704bdf415ba601 Mon Sep 17 00:00:00 2001 From: notgitika Date: Fri, 8 May 2026 19:55:18 -0400 Subject: [PATCH 2/2] test: add unit tests for target-based variant name resolution Covers the resolveTargetName fix with two cases: - runtime === projectName (the false-positive case that caused the bug) - different project/runtime names (standard prefixing) --- .../__tests__/post-deploy-ab-tests.test.ts | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/src/cli/operations/deploy/__tests__/post-deploy-ab-tests.test.ts b/src/cli/operations/deploy/__tests__/post-deploy-ab-tests.test.ts index 39a32d20f..75f36ebcc 100644 --- a/src/cli/operations/deploy/__tests__/post-deploy-ab-tests.test.ts +++ b/src/cli/operations/deploy/__tests__/post-deploy-ab-tests.test.ts @@ -298,6 +298,67 @@ describe('setupABTests', () => { expect(mockCreateABTest.mock.calls[0]![0].evaluationConfig.onlineEvaluationConfigArn).toBe('arn:eval:resolved'); }); + + it('resolves target-based variant with project prefix (runtime === projectName)', async () => { + const targetBasedTest = { + ...sampleABTest, + mode: 'target-based' as const, + variants: [ + { + name: 'C' as const, + weight: 90, + variantConfiguration: { target: { targetName: 'MyAgent-prod' } }, + }, + { + name: 'T1' as const, + weight: 10, + variantConfiguration: { target: { targetName: 'MyAgent-staging' } }, + }, + ], + }; + mockCreateABTest.mockResolvedValue({ abTestId: 'abt-tgt-1', abTestArn: 'arn:abt:tgt1' }); + + await setupABTests({ + region: 'us-east-1', + projectSpec: makeProjectSpec([targetBasedTest]), + }); + + const callArgs = mockCreateABTest.mock.calls[0]![0]; + expect(callArgs.variants[0].variantConfiguration.target.name).toBe('TestProject-MyAgent-prod'); + expect(callArgs.variants[1].variantConfiguration.target.name).toBe('TestProject-MyAgent-staging'); + }); + + it('resolves target-based variant with project prefix (different project name)', async () => { + const targetBasedTest = { + ...sampleABTest, + mode: 'target-based' as const, + variants: [ + { + name: 'C' as const, + weight: 90, + variantConfiguration: { target: { targetName: 'Bar-prod' } }, + }, + { + name: 'T1' as const, + weight: 10, + variantConfiguration: { target: { targetName: 'Bar-staging' } }, + }, + ], + }; + mockCreateABTest.mockResolvedValue({ abTestId: 'abt-tgt-2', abTestArn: 'arn:abt:tgt2' }); + + const spec = makeProjectSpec([targetBasedTest]); + spec.name = 'Foo'; + + await setupABTests({ + region: 'us-east-1', + projectSpec: spec, + }); + + const callArgs = mockCreateABTest.mock.calls[0]![0]; + expect(callArgs.variants[0].variantConfiguration.target.name).toBe('Foo-Bar-prod'); + expect(callArgs.variants[1].variantConfiguration.target.name).toBe('Foo-Bar-staging'); + }); }); describe('deletion (reconciliation)', () => {