Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions test/model/Account.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,86 @@ const validAccount = (): Record<string, unknown> => ({

describe('Account.Schema', () => {
describe('field inventory', () => {
/**
* Exhaustive key inventory.
*
* The `toContain` checks below name 22 of these 55 keys, which makes them a
* spot-check rather than an inventory: a field deleted from the schema and
* not named in one of those lists is invisible to them. It is also invisible
* to {@link Account.SchemaOutput}, because that proof is assignability-based
* and `z.looseObject` infers a `[x: string]: unknown` index signature — an
* *optional* interface property is satisfied structurally whether or not the
* schema still declares it, so only the removal of a *required* field is a
* compile error.
*
* A field silently dropped from the schema is not a cosmetic loss. The field
* stops being validated entirely and falls through to the loose-object
* passthrough, where any value is accepted, while {@link Account.Interface}
* continues to promise it was checked. This assertion is what makes that a
* failure, and it is deliberately a literal list rather than anything
* derived from `Schema.shape`, which would delete itself alongside the field
* and pass.
*/
it('should declare exactly these 55 fields, so a silently dropped field fails here', () => {
expect(Object.keys(Account.Schema.shape).sort()).toEqual([
'alias',
'appToPersonUseCase',
'area',
'authorizedRepresentative1',
'authorizedRepresentative2',
'automaticHeader',
'backup',
'bca',
'brandType',
'businessIndustry',
'businessName',
'businessRegionsOfOperations',
'businessRegistrationIdentifier',
'businessRegistrationNumber',
'businessType',
'city',
'companyType',
'counted',
'country',
'created',
'description',
'domain',
'domainOk',
'domainTimestamp',
'estimatedVolume',
'expiry',
'id',
'image',
'imageURL',
'language',
'links',
'name',
'pending',
'postalCode',
'ready',
'sampleMessage1',
'sampleMessage2',
'sampleMessage3',
'sampleMessage4',
'sampleMessage5',
'sender',
'sending',
'status',
'stockExchange',
'stockTicker',
'street1',
'street2',
'tollFreeUseCase',
'type',
'uid',
'updated',
'useCaseDescription',
'useCaseDescriptionCTA',
'useName',
'utcOffset',
]);
});

it('should declare the account fields alongside the inherited audit and queue fields', () => {
const keys = Object.keys(Account.Schema.shape);
for (const field of ['id', 'backup', 'created', 'updated', 'expiry']) {
Expand Down
57 changes: 57 additions & 0 deletions test/model/EventData.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,63 @@ const validEvent = (): Record<string, unknown> => ({

describe('EventData.Schema', () => {
describe('field inventory', () => {
/**
* Exhaustive key inventory.
*
* The `toContain` checks below name 18 of these 37 keys, so a field deleted
* from the schema and absent from those lists passes them. It also passes
* {@link EventData.SchemaOutput}: that proof is assignability-based, and the
* `[x: string]: unknown` index signature `z.looseObject` infers satisfies an
* *optional* interface property whether or not the schema declares it.
*
* A dropped field stops being validated and falls through to the
* loose-object passthrough, accepting any value, while
* {@link EventData.Interface} still promises it was checked. The list is
* literal on purpose — deriving it from `Schema.shape` would shrink with the
* deletion and assert nothing.
*/
it('should declare exactly these 37 fields, so a silently dropped field fails here', () => {
expect(Object.keys(EventData.Schema.shape).sort()).toEqual([
'account',
'amount',
'area',
'backup',
'blocks',
'booked',
'checkout',
'clicks',
'country',
'created',
'currency',
'description',
'duration',
'endTime',
'expiry',
'frequency',
'geohash',
'hosts',
'id',
'language',
'latitude',
'limit',
'location',
'longitude',
'media',
'name',
'placeId',
'placeName',
'runHour',
'startTime',
'status',
'type',
'uid',
'updated',
'users',
'utcOffset',
'views',
]);
});

it('should declare the event fields alongside the inherited audit and place fields', () => {
const keys = Object.keys(EventData.Schema.shape);
for (const field of ['name', 'account', 'blocks', 'startTime', 'endTime', 'runHour', 'limit', 'amount']) {
Expand Down
Loading