From 140be9fd4ef7029a996be68e06dca0c3c350bb30 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 3 Apr 2026 11:10:37 +0000 Subject: [PATCH] fix: Include verification rules in affectedRequestIds to prevent silent check loss on redirect chain merge Verification rules were excluded from affectedRequestIds, causing redirect chains to merge even when non-final hops had verification checks. The merge path only keeps the last hop's checks (checks: last.checks), silently dropping all checks from earlier hops. Concrete trigger: User adds a verification rule (e.g. status check) on a request that is part of a redirect chain but not the final hop. The chain merges and the check is silently removed from the generated k6 script. Co-authored-by: Edgar Fisher --- src/codegen/codegen.utils.test.ts | 41 +++++++++++++++++++++++++++++++ src/rules/rules.ts | 11 +++++++-- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/codegen/codegen.utils.test.ts b/src/codegen/codegen.utils.test.ts index 17e1b4a0e..687ba42c6 100644 --- a/src/codegen/codegen.utils.test.ts +++ b/src/codegen/codegen.utils.test.ts @@ -324,6 +324,47 @@ describe('Code generation - utils', () => { }, ]) }) + + it('does not drop checks from non-final hops when they are in affectedRequestIds', () => { + const redirectWithCheck = { + ...createRedirectSnippet('1', 'http://a.com', 'http://b.com'), + checks: [ + { + description: 'status equals 302', + expression: '(r) => r.status === 302', + }, + ], + } + const middleWithCheck = { + ...createRedirectSnippet('2', 'http://b.com', 'http://c.com'), + checks: [ + { + description: 'body contains token', + expression: "(r) => r.body.includes('token')", + }, + ], + } + const finalSnippet = createFinalSnippet('3', 'http://c.com') + + const result = processRedirectChains( + [redirectWithCheck, middleWithCheck, finalSnippet], + new Set(['1']) + ) + + expect(result.length).toBe(3) + expect(result[0]?.checks).toEqual([ + { + description: 'status equals 302', + expression: '(r) => r.status === 302', + }, + ]) + expect(result[1]?.checks).toEqual([ + { + description: 'body contains token', + expression: "(r) => r.body.includes('token')", + }, + ]) + }) }) describe('shouldIncludeHeaderInScript', () => { diff --git a/src/rules/rules.ts b/src/rules/rules.ts index 85b9fe8e1..50918ffde 100644 --- a/src/rules/rules.ts +++ b/src/rules/rules.ts @@ -47,10 +47,17 @@ export function applyRules(recording: ProxyData[], rules: TestRule[]) { // since some rules may change the URL .map(updateQueryParams) - // Collect affected requests to exclude from redirect merging + // Collect affected requests to exclude from redirect merging. + // All rule types that modify request snippets must be included here, + // otherwise their modifications (before/after/checks) can be silently + // dropped when redirect chains are merged. const affectedRequestIds = new Set( ruleInstances.flatMap((instance) => { - if (['parameterization', 'customCode'].includes(instance.type)) { + if ( + ['parameterization', 'customCode', 'verification'].includes( + instance.type + ) + ) { return instance.state.matchedRequestIds }