Skip to content

test(cli): pin toCanonical failure modes for malformed frontmatter#81

Merged
SingleSourceStudios merged 2 commits into
mainfrom
test/fmt-canonical-failure-modes
May 26, 2026
Merged

test(cli): pin toCanonical failure modes for malformed frontmatter#81
SingleSourceStudios merged 2 commits into
mainfrom
test/fmt-canonical-failure-modes

Conversation

@SingleSourceStudios
Copy link
Copy Markdown
Collaborator

@SingleSourceStudios SingleSourceStudios commented May 26, 2026

Summary

packages/cli/tests/fmt.test.ts had a single test asserting toCanonical returns null for "no frontmatter", which codifies a silent null without distinguishing failure modes. A caller reading it could treat every null as "no frontmatter" when other malformed inputs behave differently.

This PR takes Option A: add companion tests pinning the behaviour observed today. toCanonical's signature is left unchanged (the discriminated-union refactor is Option B, a separate issue).

Observed behaviour (determined empirically, not assumed)

I ran each input through the real toCanonical (via a throwaway probe importing the function the same way the test does) and read the implementation, which calls parseYaml with no surrounding try/catch:

Input shape Observed result
(a) frontmatter delimiters present, YAML has bad indentation (key: value then bad: indent) throws YAMLParseError: Nested mappings are not allowed in compact mappings
(b) unbalanced delimiters (opening ---, no closing ---) returns null
(c) frontmatter using tabs for indentation throws YAMLParseError: Tabs are not allowed as indentation

So there are two distinct observable outcomes across the three shapes:

  • (a) and (c) both surface the parser's throw as a YAMLParseError (distinct underlying causes, same error class). The function does not swallow the throw, so these are clearly distinct from the null path.
  • (b) collapses with the existing no-frontmatter case into null, because the frontmatter regex (/^---\r?\n([\s\S]*?)\r?\n---/) requires a closing delimiter and simply does not match.

Tests added

Three companion tests in fmt.test.ts:

  • throws (not returns null) on malformed YAML frontmatter with bad indentationtoThrow(YAMLParseError)
  • throws (not returns null) on frontmatter using tabs for indentationtoThrow(YAMLParseError)
  • returns null on unbalanced delimiters, identical to missing frontmatter todaytoBeNull()

Each pins the distinct outcome, so a future refactor that collapses a throw into a silent null breaks a test instead of passing.

Behaviour worth tightening later

Two collapses are pinned explicitly and noted as candidates for the Option B discriminated-union return ({ ok: false; reason: "no-frontmatter" | "malformed-yaml" | "unbalanced-delimiters" }):

  1. Unbalanced delimiters (b) is observably identical to missing frontmatter: both return null. A caller cannot tell "you forgot the closing ---" from "there is no frontmatter".
  2. The two throwing shapes (a) and (c) surface as the same YAMLParseError class (different messages). They are distinguishable by message but not by type.

Both would be resolved by the discriminated-union signature; that is a separate refactor and out of scope here.

Gates (all pass, node v22.22.2)

  • npm run build:core and full npm run build: success
  • npm test: 476 passed, 0 failed (473 + 3 new)
  • npm run typecheck: clean
  • npm run lint: no new findings. fmt.test.ts carries one pre-existing noUnusedImports warning on its line 1 node:fs import, unrelated to this change and left untouched; the repo lint baseline is unchanged (5 errors / 22 warnings).

Closes #69


Summary by cubic

Add tests in packages/cli/tests/fmt.test.ts to pin toCanonical failure modes for malformed frontmatter. Tests assert YAMLParseError for malformed YAML (bad indent or tabs) and null for unbalanced delimiters, so future changes don’t collapse errors into null.

Written for commit 53aeb5f. Summary will update on new commits. Review in cubic

The single existing test asserted toCanonical returns null for "no
frontmatter", codifying a silent null without distinguishing failure modes. A
caller reading that test could assume every null means "no frontmatter", when
other malformed inputs behave differently.

Add three companion tests pinning the behaviour observed today (Option A; the
discriminated-union refactor is left for a separate issue, toCanonical's
signature is unchanged):

- malformed YAML with bad indentation: throws YAMLParseError
- frontmatter using tabs for indentation: throws YAMLParseError
- unbalanced delimiters (opening --- with no closing ---): returns null

toCanonical does not wrap parseYaml in a try/catch, so a malformed body
surfaces the parser's throw rather than a null. The two throwing shapes both
surface as YAMLParseError (distinct parser causes, same error class), and the
unbalanced-delimiters shape collapses with the no-frontmatter case into null
because the frontmatter regex requires a closing delimiter. That collapse is
pinned explicitly and is worth tightening later via a discriminated return.

A future refactor that swallows the parser throw and returns null on malformed
YAML now breaks a test instead of passing silently.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 2026

Warning

Review limit reached

@SingleSourceStudios, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 29 minutes and 48 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: db761436-f7c2-40f0-a8e2-5f7a44b9195a

📥 Commits

Reviewing files that changed from the base of the PR and between c995997 and 53aeb5f.

📒 Files selected for processing (1)
  • packages/cli/tests/fmt.test.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test/fmt-canonical-failure-modes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 1 file

Re-trigger cubic

@SingleSourceStudios SingleSourceStudios enabled auto-merge (squash) May 26, 2026 22:39
@SingleSourceStudios SingleSourceStudios merged commit c7f6d2b into main May 26, 2026
3 checks passed
@SingleSourceStudios SingleSourceStudios deleted the test/fmt-canonical-failure-modes branch May 26, 2026 22:41
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.

test(cli): fmt.test.ts asserts canonical=null without distinguishing failure modes

1 participant