Skip to content

fix(api): enum validation messages named no allowed values - #363

Closed
modernitconsultants wants to merge 1 commit into
TelivityAI:mainfrom
modernit-com-au:enum-validation-messages
Closed

fix(api): enum validation messages named no allowed values#363
modernitconsultants wants to merge 1 commit into
TelivityAI:mainfrom
modernit-com-au:enum-validation-messages

Conversation

@modernitconsultants

Copy link
Copy Markdown
Collaborator

The bug

Post a folio charge with type: "beverage" and the API answers:

type must be one of the following values:

Nothing after the colon. The caller is told they are wrong and never told what right looks like.

Why

@IsEnum(['room','tax',…]) reads correctly and validates correctly. class-validator's IsEnum is for TypeScript enum objects:

  • it validates with Object.keys(entity).map(k => entity[k]), which happens to read an array's values, so the check works
  • it builds its message from Object.entries(entity).filter(([k]) => isNaN(parseInt(k))), which drops every key of an array, because array keys are all numeric

So the allowed-value list comes out empty. @IsIn([...]) is class-validator's decorator for a list of allowed values, and interpolates it.

Scope

88 decorators across 64 files — every enum-shaped 400 the API returns. Accounting, folio, payment, reservation, rate plan, housekeeping, media, groups, waitlist, and the booking-requests package.

Validation behaviour does not change: both decorators compare the same value against the same list. The diff is 88 one-word substitutions plus the import specifier, and no logic.

The test

apps/api/src/validation-messages.spec.ts reads class-validator's metadata registry rather than the source. A grep only finds the spelling it was told to look for; the registry holds what actually runs, including the copies PartialType and OmitType generate.

It also asserts a rendered message, and that is what saved it. The first version of the metadata assertion filtered on m.type === 'isEnum' — but type is 'customValidation' for every decorator built on ValidateBy, so it matched nothing and went green with an @IsEnum deliberately planted. Both arms were then re-checked against that planted decorator and both name the offending site.

Checks run

pnpm lint and pnpm typecheck are clean (0 errors). The new spec passes.

The README/test-stats counts were derived, not measured, and I want to flag that plainly. This machine has no Postgres, Redis or Docker, so pnpm test cannot run here and sync-test-count.mjs cannot produce a count. The change adds one spec file with four cases and modifies no existing spec, so 2245/268 → 2249/269. I applied that with the script's own exported applyCounts and buildStatsDocument so the bytes match what a real run writes, and isReadmeOutOfDate agrees. If your Test job disagrees, that delta is the thing to look at.

Aside, not fixed here

channex-review.mapper.spec.ts fails on any machine east of about UTC+7. Its fixture received_at: '2024-08-05T07:35:21.000000' carries no timezone, so new Date() reads it as local and stayDate lands a day early. Green in CI because CI runs UTC. Happy to send a separate PR if you want it.

Post a folio charge with type "beverage" and the API answers

    type must be one of the following values:

with nothing after the colon. The caller is told they are wrong and never
told what right looks like.

class-validator's IsEnum is for TypeScript enum objects. It validates with
Object.keys(entity).map(k => entity[k]), which happens to read an array's
values, so the check works; but it builds its message from
Object.entries(entity).filter(([k]) => isNaN(parseInt(k))), which drops every
key of an array because array keys are all numeric. So the allowed-value list
comes out empty. IsIn is the decorator for a list of allowed values, and
interpolates it.

Every enum decorator in the API was affected: 88 across 64 files, covering
accounting, folio, payment, reservation, rate plan, housekeeping, media,
groups, waitlist and the booking-requests package.

Validation behaviour does not change. Both decorators compare the same value
against the same list.

apps/api/src/validation-messages.spec.ts reads class-validator's metadata
registry rather than the source, so it covers the copies PartialType and
OmitType generate, which no grep sees. It asserts a rendered message too, and
that is what caught the first version of the metadata assertion: it filtered
on `type`, which is 'customValidation' for every ValidateBy decorator, so it
matched nothing and passed with an @IsEnum deliberately planted.
@cursor

cursor Bot commented Sep 9, 2026

Copy link
Copy Markdown

Maintainer note: we can’t push to your fork from the bot (maintainerCanModify is on, but the integration token still gets 403). If a Telivity maintainer can push with their own creds:

git fetch https://github.com/TelivityAI/haip.git cursor/enum-validation-booking-requests-guard-4a4f
git push https://github.com/modernit-com-au/haip.git FETCH_HEAD:enum-validation-messages

That lands one hygiene commit on this PR (booking-requests DTOs in the enum-message guardrail). Optional — #363 is already mergeable without it.

// contributes no metadata, which would make this test pass by not looking.
// `!**/*.spec.ts` matters: one DTO directory holds its own spec, and pulling
// it in here would run that file's `describe` blocks inside this one.
const dtoModules = import.meta.glob(['./modules/**/dto/**/*.ts', '!**/*.spec.ts'], { eager: true });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Maintainer hygiene (optional — not blocking merge): the enum-message guardrail only eager-loads apps/api/src/modules/**/dto/**, so a future @IsEnum([...]) in packages/booking-requests wouldn’t trip it.

Suggested fix already written + green locally on cursor/enum-validation-booking-requests-guard-4a4f (commit 1cc3595). Happy for you to ignore or cherry-pick:

git fetch https://github.com/TelivityAI/haip.git cursor/enum-validation-booking-requests-guard-4a4f
git cherry-pick 1cc3595
git push

Or a maintainer with fork write can fast-forward this branch to that tip. Still 4 test cases — just broader coverage.

@cursor

cursor Bot commented Sep 9, 2026

Copy link
Copy Markdown

Couldn’t push the hygiene commit to your fork (GitHub 403 for Telivity maintainers on modernit-com-au/haip). Opened #364 as your #363 plus that one spec glob — merge either; this one is only so we don’t keep bugging you. Credit stays yours.

@cursor

cursor Bot commented Sep 9, 2026

Copy link
Copy Markdown

Superseded and merged via #364 — your #363 plus the booking-requests DTO guardrail. Thank you again.

telivity-otaip added a commit that referenced this pull request Sep 9, 2026
Maintainer hygiene on #363: the metadata registry check only loaded
apps/api modules/**/dto, so a future @IsEnum([...]) in
packages/booking-requests could regress unnoticed. Eager-load those
HTTP DTOs too and assert ListBookingRequestsDto registers.

Co-authored-by: telivity-otaip <telivity-otaip@users.noreply.github.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.

1 participant