Skip to content

Commit 91c8e34

Browse files
authored
fix(codegen): skip assertions that depend on an unmapped do-step (#191)
Assertions (match/is_*/length/comparison/contains) and `set` steps read $RESPONSE, which is written by the preceding `do`. When a do-step is skipped — because the CLI action isn't registered or the catch clause is unsupported — $RESPONSE is stale or empty, so downstream assertions would run against the wrong data and emit spurious failures like: FAIL: expected queries = [object Object] FAIL: expected acknowledged = true renderSteps now tracks whether the most recent do produced a response and emits a skip-comment for any assertion/set that follows a skipped do, until the next executed do resets the response. renderDo returns a boolean so the caller knows whether to trust $RESPONSE. Closes #189
1 parent 9245d80 commit 91c8e34

3 files changed

Lines changed: 74 additions & 8 deletions

File tree

codegen/functional/generator.ts

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,25 @@ function renderSteps (
140140
skippedActions: string[],
141141
indent: string
142142
): void {
143+
// Assertions and set-steps read $RESPONSE, which is written by the most
144+
// recent successful `do`. If the last `do` was skipped (unmapped action,
145+
// unsupported catch, etc.) $RESPONSE is stale or empty, so any assertion
146+
// that follows would assert against the wrong data — skip those too
147+
// until the next executed `do` resets the response.
148+
let responseFromLastDo = false
143149
for (const step of steps) {
150+
if (step.kind === 'do') {
151+
responseFromLastDo = renderDo(step, actionMap, lines, skippedActions, indent)
152+
continue
153+
}
154+
if (step.kind === 'skip') continue
155+
156+
if (!responseFromLastDo) {
157+
lines.push(`${indent}# SKIPPED: ${step.kind} assertion follows skipped do-step`)
158+
continue
159+
}
160+
144161
switch (step.kind) {
145-
case 'do':
146-
renderDo(step, actionMap, lines, skippedActions, indent)
147-
break
148162
case 'set':
149163
renderSet(step, lines, indent)
150164
break
@@ -169,22 +183,26 @@ function renderSteps (
169183
case 'contains':
170184
renderContains(step, lines, indent)
171185
break
172-
case 'skip':
173-
break
174186
}
175187
}
176188
}
177189

190+
/**
191+
* Render a do-step. Returns true if an executable command was emitted and
192+
* $RESPONSE will hold the result afterwards; false when the step was
193+
* skipped (unsupported catch or unmapped action) and $RESPONSE is now
194+
* stale/empty.
195+
*/
178196
function renderDo (
179197
step: DoStep,
180198
actionMap: Map<string, EsApiDefinition>,
181199
lines: string[],
182200
skippedActions: string[],
183201
indent: string
184-
): void {
202+
): boolean {
185203
if (step.catch != null) {
186204
lines.push(`${indent}# SKIPPED: catch not supported in MVP (catch: ${step.catch})`)
187-
return
205+
return false
188206
}
189207

190208
if (step.headers != null) {
@@ -195,7 +213,7 @@ function renderDo (
195213
if (mapped == null) {
196214
skippedActions.push(step.action)
197215
lines.push(`${indent}# SKIPPED: action "${step.action}" not registered in CLI`)
198-
return
216+
return false
199217
}
200218

201219
const cmd = buildCommand(mapped, step)
@@ -205,6 +223,7 @@ function renderDo (
205223
} else {
206224
lines.push(`${indent}RESPONSE=$(${cmd})`)
207225
}
226+
return true
208227
}
209228

210229
function buildCommand (mapped: MappedAction, step: DoStep): string {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
requires:
3+
serverless: true
4+
stack: true
5+
---
6+
'skipped do then assertion should not run':
7+
- do:
8+
unregistered.action: { x: 'y' }
9+
- match: { acknowledged: true }
10+
- is_true: some.field
11+
- set: { _id: id }
12+
13+
- do:
14+
indices.create: { index: 'realindex' }
15+
- match: { acknowledged: true }

codegen/functional/test/generator.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,38 @@ describe('generateScript', () => {
173173
assert.equal(parsed.status, 0, `bash -n failed: ${parsed.stderr.toString()}`)
174174
})
175175

176+
it('skips assertions that follow an unmapped do-step', () => {
177+
const content = readFileSync(join(fixturesDir, 'skipped-do-then-assert.yml'), 'utf-8')
178+
const testFile = parseTestFile(content, 'skipped-do-then-assert.yml')
179+
const result = generateScript(testFile, testDefs)
180+
181+
const matches = result.script.match(/assertion follows skipped do-step/g) ?? []
182+
assert.ok(
183+
matches.length >= 3,
184+
`expected at least 3 skip-comments for match/is_true/set after unmapped do, got ${matches.length}`
185+
)
186+
187+
// Assertions that follow a mapped do (indices.create) should still render.
188+
assert.ok(
189+
result.script.includes('FAIL: expected acknowledged = true'),
190+
'assertion after a mapped do should still emit'
191+
)
192+
})
193+
194+
it('still emits assertions after a successful do resets the response', () => {
195+
const content = readFileSync(join(fixturesDir, 'skipped-do-then-assert.yml'), 'utf-8')
196+
const testFile = parseTestFile(content, 'skipped-do-then-assert.yml')
197+
const result = generateScript(testFile, testDefs)
198+
199+
// Exactly one `match` should render as an actual assertion line — the
200+
// one after indices.create. The earlier `match` after the unmapped do
201+
// must not produce an executable comparison.
202+
const emittedAssertions = result.script
203+
.split('\n')
204+
.filter((l) => l.includes('FAIL: expected acknowledged'))
205+
assert.equal(emittedAssertions.length, 1)
206+
})
207+
176208
it('prints PASS on success', () => {
177209
const content = readFileSync(join(fixturesDir, 'get.yml'), 'utf-8')
178210
const testFile = parseTestFile(content, 'get.yml')

0 commit comments

Comments
 (0)