Skip to content

test(downgrader): close test gaps found by mutation testing - #79

Merged
dinwwwh merged 2 commits into
mainfrom
claude/project-thread-k95hv1
Oct 24, 2026
Merged

dinwwwh merged 2 commits into
mainfrom
claude/project-thread-k95hv1

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Oct 24, 2026

Copy link
Copy Markdown
Member

Requested by dinwwwh · project thread

Test-only. Stryker on packages/downgrader/src left 155 of 1,211 mutants alive on main. With these tests, 81 are left, and no mutant survives that was killed before.

What the new cases cover

3.1 to 3.0

  • A schema using every keyword 3.0 shares (readOnly, writeOnly, deprecated, discriminator, externalDocs, xml, multipleOf, min/maxProperties, and the rest) comes out unchanged, not renamed to x-.
  • it.each over every in-place applicator beside unevaluatedProperties: false: none of them turns it into additionalProperties: false.
  • Exclusive bounds as a table: each bound alone, the plain bound stricter, the exclusive bound stricter, and both equal.
  • Tuple bounds as a table, including ArkType's { prefixItems: [a, b], minItems: 1, items: false }, which gets maxItems: 2.
  • $dynamicRef and $vocabulary are dropped.
  • $ref pointers decode percent-encoding, ~1, and ~0 in that order, and non-pointer or inherited-key refs stay as written.
  • Extensions in paths, responses, and callbacks are copied as they are.

3.2 to 3.1

  • it.each over every subschema keyword, map keyword, and list keyword: xml.nodeType converts inside each one. nodeType: none on an array doesn't set wrapped.
  • Extensions in paths, responses, and callbacks are copied as they are. A Link's server is compared exactly.

Shared helpers

  • Own __proto__ keys stay ordinary, writable, enumerable keys.
  • Keys holding undefined are compared with toStrictEqual, so a missing key and one holding undefined count as different.
  • Cycles through arrays stay cycles.
  • An Object.create(null) schema is read like any other object.

README input promises (orpc.test.ts)

  • The oRPC document converts to 3.1 the same when it's built from null-prototype objects or has a key holding undefined in every object. It also converts to 3.0 the same when built from null-prototype objects.
  • The undefined twin is not yet checked for the 3.1-to-3.0 step, because that step fails today. isMediaTypeOnly and the propertyNames check count keys that hold undefined. That bug is left for its own fix PR, which can add the check.

Remaining survivors

Most of the 81 that are left are equivalent mutants (the $ref target cache, clone's pre-check, typeof guards that only matter for malformed input). A few are .map(() => undefined) mutants that throw at import, which Stryker records as surviving. The rest are in the looseness and type logic, which this PR doesn't touch.


Generated by Claude Code

Add table-style cases for the 3.0 keyword allowlist, the applicators that
keep `unevaluatedProperties` from becoming `additionalProperties`, XML
`nodeType` under every 3.2 subschema keyword, exclusive bounds at and past
their plain bounds, ArkType's open-ended tuple getting `maxItems`, and the
shared helpers: own `__proto__` keys, keys holding `undefined`, cycles
through arrays, `~0`/`~1` in `$ref` pointers, and extensions in paths,
responses, and callbacks.

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

Bound tuples by maxItems in a table, drop `$dynamicRef` and `$vocabulary`,
leave non-pointer and inherited-key `$ref`s as written, tolerate a
non-string `$ref`, compare a dropped content `$ref`, a Link's `server`,
and XML `nodeType: none` exactly, and read a null-prototype schema.

Check that the oRPC document converts to 3.1 the same when built from
null-prototype objects or with a key holding undefined in every object,
and to 3.0 the same when built from null-prototype objects.

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

macroscopeapp Bot commented Oct 24, 2026

Copy link
Copy Markdown

Approvability

Verdict: Approved at 0165644

Macroscope's review found this PR approvable — This PR only expands downgrader test coverage for mutation survivors and unusual object/input shapes; it does not modify production converter code or shipped behavior. The changed files run only in the test harness.

Notes:

  • No code objects were reviewed. Approvability was decided on eligibility alone.

You can add or adjust custom eligibility rules. Learn more.

@codecov

codecov Bot commented Oct 24, 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 24, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 21 untouched benchmarks


Comparing claude/project-thread-k95hv1 (0165644) with main (f9427cd)

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

This run reviewed the full test-only diff at 0165644 (3 files). I ran the three touched test files (133 passed) and traced each new expectation against src/v3.1-to-v3.0.ts and src/v3.2-to-v3.1.ts.

  • 3.1→3.0 schema tables — "keeps every keyword 3.0 shares as it is"; exclusive bounds as an it.each (adds the equal-bound rows); tuple bounds as a table (items: false → maxItems, ArkType { prefixItems, minItems, items: false } → maxItems: 2); $dynamicRef/$vocabulary dropped; pointer percent/~1/~0 decode order; non-pointer and inherited-key $refs left as written; unevaluatedProperties beside each IN_PLACE_APPLICATORS entry stays out of additionalProperties; extensions copied as-is; a non-string $ref becomes {}.
  • 3.2→3.1 schema/keyword coverage — xml.nodeType converted inside every subschema/map/list keyword, nodeType: 'none' sets no wrapped, exact Link server and dropped content $ref, own __proto__ keys kept ordinary, toStrictEqual for undefined-holding keys, cycles through arrays preserved.
  • oRPC input promises — the document converts identically when rebuilt from null-prototype objects and from objects carrying missing: undefined (3.2→3.1), and from null-prototype objects for the 3.1→3.0 step.

Every expectation is consistent with the implementation. The keyword it.each tables mirror the source lists (SUBSCHEMA_KEYWORDS, IN_PLACE_APPLICATORS, SUBSCHEMA_MAP/LIST_KEYWORDS, the $ref-rewrite set), so coverage tracks the code rather than hard-coding arbitrary cases. The author's note that the undefined twin is not asserted for 3.1→3.0 is a pre-existing isMediaTypeOnly/propertyNames bug left for a separate fix PR — out of scope here.

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

@dinwwwh
dinwwwh merged commit ad3158b into main Oct 24, 2026
11 checks passed
dinwwwh added a commit that referenced this pull request Oct 24, 2026
…propertyNames checks (#80)

<!-- claude-projects-attribution: {"github_login":"dinwwwh"} -->
_Requested by **dinwwwh** · [project
thread](https://claude.ai/code/project/chan_01UcKMz91tMarPV1kWLVgLYE?thread=cmsg_01UcKMz91tMarPV1kWLVgLYENrRmND8vTmDypUMYJpJSLE)_

The README promises that a key holding `undefined` counts as missing,
but two checks in the 3.1 to 3.0 converter counted such keys:

- `isMediaTypeOnly` used `Object.keys(value).length === 1`, so a
Zod-style `anyOf` branch like `{ contentMediaType: 'image/png',
description: undefined }` was not recognized and became a binary string
branch instead of being dropped.
- `losesRestriction` for `propertyNames` treated `{ type: 'string', x:
undefined }` as a lost restriction, so an enclosing `oneOf` (directly or
through a `$ref`) became `anyOf`.

Both checks now skip entries holding `undefined`.

A unit test covers both cases. I also checked that the oRPC document
rebuilt with an extra `missing: undefined` key in every object (the
`withUndefined` twin from #79) converts to 3.0 exactly like the plain
one: it fails on main and passes with this change. Once #79 lands, that
twin can be extended to 3.0.

---
_Generated by [Claude
Code](https://claude.ai/code/session_0129me54jxy7ZKsYhwWvAmPP)_

Co-authored-by: Claude <noreply@anthropic.com>
dinwwwh added a commit that referenced this pull request Oct 24, 2026
<!-- claude-projects-attribution: {"github_login":"dinwwwh"} -->
_Requested by **dinwwwh** · [project
thread](https://claude.ai/code/project/chan_01UcKMz91tMarPV1kWLVgLYE?thread=cmsg_01UcKMz91tMarPV1kWLVgLYENrRmND8vTmDypUMYJpJSLE)_

Test-only follow-up to #79 and #80. #79 checked the `withUndefined` twin
of the oRPC document only for the 3.2 to 3.1 step, because 3.1 to 3.0
counted keys holding `undefined`. #80 fixed that, so the 3.0 twin test
now runs over both `nullPrototype` and `withUndefined`.

The new case fails on the commit before #80 and passes on main.

---
_Generated by [Claude
Code](https://claude.ai/code/session_0129me54jxy7ZKsYhwWvAmPP)_

Co-authored-by: Claude <noreply@anthropic.com>
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