From 3e4a2624664c5fbc51068edb55bdea4a84649918 Mon Sep 17 00:00:00 2001 From: utahkanz-ops <272119084+utahkanz-ops@users.noreply.github.com> Date: Sun, 31 May 2026 06:59:56 +0100 Subject: [PATCH] fix: log unexpected null creator field --- .../creators/creator-list-item.mapper.test.ts | 56 +++++++++++++++---- .../creators/creator-list-item.mapper.ts | 22 ++++++++ 2 files changed, 66 insertions(+), 12 deletions(-) diff --git a/src/modules/creators/creator-list-item.mapper.test.ts b/src/modules/creators/creator-list-item.mapper.test.ts index fb4e6db..b6ec0a3 100644 --- a/src/modules/creators/creator-list-item.mapper.test.ts +++ b/src/modules/creators/creator-list-item.mapper.test.ts @@ -1,19 +1,51 @@ -import { strict as assert } from 'assert'; import { mapCreatorListItem } from './creator-list-item.mapper'; +import { requestContextStorage } from '../../utils/als.utils'; +import { logger } from '../../utils/logger.utils'; -function run() { - const input = { id: '1', displayName: 'John', avatarUrl: null } as any; +jest.mock('../../utils/logger.utils', () => ({ + logger: { warn: jest.fn() }, +})); - const result = mapCreatorListItem(input); +const warnMock = logger.warn as jest.Mock; - assert.deepEqual(result, { - id: '1', - name: 'John', - avatar: null, - followers: 0, +beforeEach(() => { + warnMock.mockClear(); +}); + +describe('mapCreatorListItem()', () => { + it('maps the public creator list item shape', () => { + const input = { id: '1', displayName: 'John', avatarUrl: null } as any; + + const result = mapCreatorListItem(input); + + expect(result).toEqual({ + id: '1', + name: 'John', + avatar: null, + followers: 0, + }); + expect(warnMock).not.toHaveBeenCalled(); }); - console.log('creator-list-item.mapper test passed'); -} + it('warns when a schema-required creator field is unexpectedly null', () => { + const input = { id: 'creator-1', displayName: null, avatarUrl: null } as any; -run(); + const result = requestContextStorage.run( + { path: '/api/v1/creators', method: 'GET', requestId: 'req-333' }, + () => mapCreatorListItem(input) + ); + + expect(result).toEqual({ + id: 'creator-1', + name: null, + avatar: null, + followers: 0, + }); + expect(warnMock).toHaveBeenCalledWith({ + msg: 'Unexpected null creator field in database result', + fieldName: 'displayName', + creatorId: 'creator-1', + requestId: 'req-333', + }); + }); +}); diff --git a/src/modules/creators/creator-list-item.mapper.ts b/src/modules/creators/creator-list-item.mapper.ts index 746e4fd..31ca4b5 100644 --- a/src/modules/creators/creator-list-item.mapper.ts +++ b/src/modules/creators/creator-list-item.mapper.ts @@ -1,4 +1,6 @@ import { CreatorProfile } from '../../types/profile.types'; +import { requestContextStorage } from '../../utils/als.utils'; +import { logger } from '../../utils/logger.utils'; import { safeRead } from '../../utils/safe-nested-read.utils'; /** @@ -12,6 +14,24 @@ export type CreatorListItem = { followers: number; }; +function warnIfUnexpectedNullCreatorField( + creator: CreatorProfile, + fieldName: 'displayName' +): void { + const rawCreator = creator as CreatorProfile & Record; + + if (rawCreator[fieldName] !== null) { + return; + } + + logger.warn({ + msg: 'Unexpected null creator field in database result', + fieldName, + creatorId: creator.id, + requestId: requestContextStorage.getStore()?.requestId ?? null, + }); +} + /** * Pure, dumb mapper from a full `CreatorProfile` to a `CreatorListItem`. * No filtering, no business logic — deterministic and predictable. @@ -19,6 +39,8 @@ export type CreatorListItem = { export const mapCreatorListItem = ( creator: CreatorProfile ): CreatorListItem => { + warnIfUnexpectedNullCreatorField(creator, 'displayName'); + return { id: creator.id, name: safeRead(creator, 'displayName', null),