Skip to content

test(downgrader): cover mixed literal union edge cases, note boolean-ish flags with coercing validators - #136

Merged
dinwwwh merged 3 commits into
mainfrom
claude/project-thread-579e34
Oct 26, 2026
Merged

dinwwwh merged 3 commits into
mainfrom
claude/project-thread-579e34

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Oct 26, 2026 •

Copy link
Copy Markdown
Member

Requested by dinwwwh · project thread

Follow-up to #135, tests and one README sentence, no source change.

Tests

Three cases in v3.1-to-v3.0.test.ts close the Stryker survivor gaps in the enum merge of a literal union:

  • a described branch stops the same-type merge in mergeEnums (anyOf [const a, const b + description, const c, null] keeps every branch)
  • typeless multi-value enums stay as written (mergeEnumsByType)
  • a lone described typed enum beside another type stays as written (mergeEnumsByType)

README

The "Validators that coerce" limitation now says that a query flag of "true", "false", "0", "1" or a boolean, as GrowthBook writes (its mine parameter in REST API 5.1.0), rejects ?flag=true after #135: it becomes oneOf [string enum ["true", "false", "0", "1"], boolean], and "true" matches the string and, coerced, the boolean. Checked with Ajv coerceTypes: true: the oneOf rejects { flag: "true" }, the old anyOf accepts it.

Checks

  • pnpm test:coverage: 280 tests pass, coverage stays at 100% statements, branches, functions and lines
  • pnpm check passes
  • Mutating typeof branch.type !== 'string' to false, or the final result.length < branches.length guard to true, passes every test on main and fails with these cases. Stryker on the merge code with these tests kills 125 of 127 mutants; the two left are equivalent.

🤖 Generated with Claude Code

https://claude.ai/code/session_018XRYDXpPjZLwYYLKH7PZWd

Three cases close the Stryker gaps in mergeEnumsByType: a described
branch stops the merge, typeless multi-value enums stay as written, and
a lone described typed enum stays as written.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018XRYDXpPjZLwYYLKH7PZWd
@dinwwwh dinwwwh self-assigned this Oct 26, 2026
@codecov

codecov Bot commented Oct 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed

codspeed Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 21 untouched benchmarks


Comparing claude/project-thread-579e34 (4a4e62b) with main (b10f7fa)

Open in CodSpeed

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes — test-only follow-up adding 3 assertions to packages/downgrader/tests/v3.1-to-v3.0.test.ts inside the existing const-union it block; no source change.

  • Described branch blocks a same-type merge — anyOf [const a, const b+description, const c, {type:null}] keeps all four branches, guarding mergeEnums's repeats-false path.
  • Described typed enum beside another type — {description:'D', anyOf:[{type:'string',enum:['a'],description:'D'}, {type:'boolean'}]} becomes a oneOf retaining description, guarding mergeEnumsByType's repeats-true path and its result.length < branches.length false guard.
  • Typeless mixed enums stay put — [{enum:['a',1]},{enum:['b',2]},{type:'boolean'}] is returned as written, guarding the typeof branch.type !== 'string' check and type-grouping.

I verified both mutations the body claims: with the new cases removed, mutating typeof branch.type !== 'string' to false, or result.length < branches.length to true, passes the full 280-test suite; with the new cases present each fails (at the new lines 1248 and 1244 respectively). The new tests pass (108 in file, 280 full suite) and uncheck is clean, so the cases are genuine, non-redundant coverage rather than theatre. One cosmetic note: the body attributes all three cases to mergeEnumsByType, but the first exercises mergeEnums's same-type branch (it returns before reaching mergeEnumsByType) — wording only, not a code concern.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

… flag

A union of "true", "false" and a boolean, as GrowthBook writes for query
flags, becomes a oneOf in 3.0, and a validator that coerces matches
"true" to both branches.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018XRYDXpPjZLwYYLKH7PZWd
@dinwwwh dinwwwh changed the title test(downgrader): cover mixed literal union edge cases in 3.1 to 3.0 test(downgrader): cover mixed literal union edge cases, note boolean-ish flags with coercing validators Oct 26, 2026

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — the core claim in the new sentence is correct; one attribution gap noted inline.

Reviewed changes — incremental delta since the prior pullfrog review (ecd10b2): one sentence added to the "Validators that coerce" limitation bullet in packages/downgrader/README.md; no source change.

  • GrowthBook coercion example — the bullet now says a GrowthBook-style query flag that takes "true", "false" or a boolean rejects ?flag=true, since "true" matches the string branch and, coerced, the boolean branch.

Pullfrog  | Fix it ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@pullfrog

pullfrog Bot commented Oct 26, 2026

Copy link
Copy Markdown

ℹ️ One minor note to supplement the review above (it could not be anchored inline — a second review submission is blocked as a duplicate in this session).

The new sentence says a query flag "takes "true", "false" or a boolean, as GrowthBook writes". GrowthBook's boolean query flags are a five-member union, not three: booleanQueryField/skipPaginationQueryField in packages/shared/src/validators/shared.ts are z.union([z.literal('true'), z.literal('false'), z.literal('0'), z.literal('1'), z.boolean()]), emitted in GrowthBook's docs/openapi.yaml (e.g. skipPagination, skipSchemaValidation) as an anyOf of 'true', 'false', '0', '1', and boolean. So the enumeration drops the "0"/"1" string forms.

The claim itself is unaffected: I confirmed the converted oneOf rejects { flag: 'true' } under Ajv coerceTypes: true (and the pre-#135 anyOf accepted it), so this is a wording fix only.

Pullfrog  | View workflow run | via Pullfrog | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018XRYDXpPjZLwYYLKH7PZWd

dinwwwh commented Oct 26, 2026

Copy link
Copy Markdown
Member Author

Agreed, fixed in the next commit: the sentence now lists "true", "false", "0", "1" or a boolean. Checked against GrowthBook REST API 5.1.0's generated/spec.yaml, where the mine query parameter is that five-member anyOf; the downgrader writes it as oneOf [{ type: string, enum: ["true", "false", "0", "1"] }, { type: boolean }], which Ajv with coerceTypes: true rejects for "true".


Generated by Claude Code

@dinwwwh
dinwwwh merged commit 81010aa into main Oct 26, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants