Project: doctolib-doctolib · Branch: .invalid · Mode: ` · **Model:**claude-sonnet-4-6`
Stats: 1 user messages, 37 tool calls, tools used: Bash, Read.
Conversation
Turn 1 — User
Re-analyze test coverage for PR #240425 (branch fhir-2213_dmp-api-typed-error-model) in /Users/USER/workspace/doctolib/doctolib after fixes.
Run git diff main...fhir-2213_dmp-api-typed-error-model for all changes.
Fixes applied since first review:
- Fixed message assembly bug (extended-info-only path was "undefined: ..."): now falls back to extended info alone
- Added test: extended-info-only message branch (new 4th combination test case)
- Added test: instanceof DmpApiError identity
- Added test: s_status absent from response (the ?? 'ERROR' fallback)
- Added test: documents + error present simultaneously in ERROR response
Previous critical gaps from first analysis that were marked as out-of-scope: - DmpNhrAdapter test (downstream — covered in FHIR-2215)
Confirm if coverage is now adequate for the scope of FHIR-2213. Key files: packages/@doctolib/dmp/src/api/DmpApiError.test.ts and packages/@doctolib/dmp/src/business/DmpApi.test.ts.
Turn 1 — Assistant
I'll analyze the current state of the PR by examining the diff and key test files.
Tools used:
Bash:cd /Users/USER/workspace/doctolib/doctolib && git diff main...fhir-2213_dmp-api-typed-error-model 2>/dev/null | head -500
Tools used:
Bash:cd /Users/USER/workspace/doctolib/doctolib && git diff main...fhir-2213_dmp-api-typed-error-model --stat 2>/dev/null
Tool results:
→ result (ok): (Bash completed with no output)
Tool results:
→ result (ERROR): Exit code 128
The branch isn't accessible directly from the main repo. Let me check the worktrees.
Tools used:
Bash:cd /Users/USER/workspace/doctolib/doctolib && git worktree list 2>/dev/null | grep -i fhir-2213
Tools used:
Bash:ls /Users/USER/workspace/doctolib/worktrees/doctolib/ 2>/dev/null | grep -i fhir-2213
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib 1218a51afc80 [fhir-2213_dmp-api-typed-error-model]
Tool results:
→ result (ok): (Bash completed with no output)
The branch is the current worktree in the main repo. Let me get the diff against main.
Tools used:
Bash:cd /Users/USER/workspace/doctolib/doctolib && git log main..HEAD --oneline 2>/dev/null
Tools used:
Bash:cd /Users/USER/workspace/doctolib/doctolib && git diff main...HEAD --stat 2>/dev/null
Tool results:
→ result (ERROR): Exit code 128
Tool results:
→ result (ERROR): <tool_use_error>Cancelled: parallel tool call Bash(cd /Users/USER/workspace/doctolib/d…) errored</tool_use_error>
Tools used:
Bash:cd /Users/USER/workspace/doctolib/doctolib && git branch --show-current 2>&1
Tool results:
→ result (ok): fhir-2213_dmp-api-typed-error-model
Tools used:
Bash:cd /Users/USER/workspace/doctolib/doctolib && git log --oneline origin/main..HEAD 2>&1 | head -20
Tool results:
→ result (ok): fatal: ambiguous argument 'origin/main..HEAD': unknown revision or path not in the working tree. Use '--' to separate paths from revisions, like this: 'git <command> [<revision>...] -- [<file>...]'
Tools used:
Bash:cd /Users/USER/workspace/doctolib/doctolib && git remote -v 2>&1 && git branch -r 2>&1 | head -10
Tool results:
→ result (ok): origin https://github.com/doctolib/doctolib.git (fetch) origin https://github.com/doctolib/doctolib.git (push) origin/1067-bis origin/136-deus-test origin/1470 origin/1970 origin/1dbcad345f390 origin/2024-02-20-lynx-refacto origin/2025-03-03-fix-test origin/2025-11-18-upgrade-obs origin/2025-12-11-lynx-upgrade origin/371c-let-s-fix-ci-iss
Tools used:
Bash:cd /Users/USER/workspace/doctolib/doctolib && git log --oneline -10 2>&1
Tool results:
→ result (ok): 1218a51afc80 refactor(FHIR-2213): move DmpApiError to src/api, fix message assembly bug, add missing tests 5a3978c5b671 feat(FHIR-2213): add DmpApiError class and propagate DMP error fields through SearchResult 9fa20cbad37b feat(KITN-3971): Add direct chat button in declined state (#240396) 1edcff495b97 fix(FYP-5093): use boolean FS reader for datasync_randomize_batch_ids (#240334) 71fe086b72bd test(ATF-1677): DST regression cover for availability engine (read + write) (#240390) e3400ea0a590 per …(truncated, 556 more chars)
Tools used:
Bash:cd /Users/USER/workspace/doctolib/doctolib && git diff HEAD~2..HEAD -- "packages/@doctolib/dmp" 2>&1
Tool results:
→ result (ok): diff --git a/packages/@doctolib/dmp/src/api/DmpApiError.test.ts b/packages/@doctolib/dmp/src/api/DmpApiError.test.ts new file mode 100644 index 000000000000..3d8e76d873a2 --- /dev/null +++ b/packages/@doctolib/dmp/src/api/DmpApiError.test.ts @@ -0,0 +1,94 @@ +import { DmpApiError } from './DmpApiError' +import type { DmpApiErrorInfo } from '../types/DmpApi' + +describe('DmpApiError', () => { + describe('message assembly', () => { + it('combines description and extended info when both are pre …(truncated, 12033 more chars)
Now let me read the full test files and implementation files.
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/dmp/src/api/DmpApiError.ts
Tool results:
→ result (ok): 1 import type { DmpApiErrorInfo } from '../types/DmpApi'
2
3 export class DmpApiError extends Error {
4 readonly apiErrorCode: string | undefined
5
6 readonly apiErrorType: string | undefined
7
8 readonly apiErrorContext: string | undefined
9
10 readonly apiErrorDescription: string | undefined
11
12 readonly apiErrorExtendedInformations: string | undefined
13
14 readonly apiErrorTypeDescription: string | undefined
15
16 readonly status: string
17
18 constructor(info: DmpA
…(truncated, 730 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/dmp/src/api/DmpApiError.test.ts
Tool results:
→ result (ok): 1 import { DmpApiError } from './DmpApiError'
2 import type { DmpApiErrorInfo } from '../types/DmpApi'
3
4 describe('DmpApiError', () => {
5 describe('message assembly', () => {
6 it('combines description and extended info when both are present', () => {
7 const info: DmpApiErrorInfo = {
8 apiErrorDescription: 'Technical software error',
9 apiErrorExtendedInformations: 'Operation timed out.',
10 status: 'ERROR',
11 }
12 const error = new DmpApiErr
…(truncated, 3083 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/dmp/src/business/DmpApi.ts(490–550)
Tool results:
→ result (ok): 490 } 491 492 private toSearchResult = (response: FindDocumentsResponse): SearchResult => ({ 493 documents: (response.Documents ?? []).map((doc) => ({ 494 uniqueId: doc.s_uniqueId, 495 title: doc.s_title, 496 description: doc.s_description, 497 classCode: doc.s_classCode, 498 typeCode: doc.s_typeCode, 499 creationDate: doc.s_creationDate, 500 serviceStartDate: doc.s_serviceStartDate, 501 serviceStopDate: doc.s_serviceStopDate, 502 he …(truncated, 2159 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/dmp/src/types/DmpApi.ts(155–205)
Tool results:
→ result (ok): 155 serviceStartDateBottom?: string 156 serviceStartDateTop?: string 157 serviceStopDateBottom?: string 158 serviceStopDateTop?: string 159 submissionDateBottom?: string 160 submissionDateTop?: string 161 status?: number 162 visibility?: number 163 disableMetadataSearch?: number 164 referenceIds?: DmpReferenceId[] 165 eventCodes?: DmpEventCode[] 166 getTransactionFrames?: number 167 } 168 169 export interface DmpApiErrorInfo { 170 apiErrorCode?: string 171 apiErrorTy …(truncated, 587 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/dmp/src/business/DmpApi.test.ts(980–1110)
Tool results:
→ result (ok): 980 }) 981 982 it('should handle undefined Documents in response gracefully', async () => { 983 // eslint-disable-next-line @typescript-eslint/no-explicit-any 984 const mockResponse = { s_status: STATUS_OK } as any 985 findDocumentsMocked.mockResolvedValue(mockResponse) 986 987 const dmpApi = new DmpApi(mockProps) 988 const result = await dmpApi.search({ ins: '1234567890' }) 989 990 expect(result).toEqual({ documents: [] }) 991 }) 992 993 i …(truncated, 5038 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/dmp/src/types/FrDmpTypes.ts(185–215)
Tool results:
→ result (ok): 185 / Event codes to filter by. Returns documents matching at least one event code. */ 186 EventCodes?: EventCode[] 187 / Enable transaction frame debugging (0 or 1). Development only. */ 188 i_getTransactionFrames?: number 189 } 190 191 // DMP Responses 192 export interface DmpResponse extends DmpResult { 193 // Value "OK" for success. 194 s_status?: string 195 i_apiErrorCode?: string 196 i_apiErrorType?: string 197 s_apiErrorContext?: string 198 s_apiErrorDescription?: …(truncated, 400 more chars)
Now let me check what the eslint-disable comment looks like in the test file (a potential code style concern) and look at the findDocumentsResponseFixtureBuilder to understand what fields it supports.
Tools used:
Bash:cd /Users/USER/workspace/doctolib/doctolib && grep -n "findDocumentsResponseFixtureBuilder\|eslint-disable" packages/@doctolib/dmp/src/business/DmpApi.test.ts | head -30
Tool results:
→ result (ok): 25:import { documentMetadataFixtureBuilder, findDocumentsResponseFixtureBuilder } from '../test-helpers/fixtures' 796: const mockResponse = findDocumentsResponseFixtureBuilder() 811: const mockResponse = findDocumentsResponseFixtureBuilder({ 867: const mockResponse = findDocumentsResponseFixtureBuilder({ 894: const mockResponse = findDocumentsResponseFixtureBuilder({ 983: // eslint-disable-next-line @typescript-eslint/no-explicit-any 994: const mockResponse = findDo …(truncated, 861 more chars)
Tools used:
Bash:cd /Users/USER/workspace/doctolib/doctolib && grep -rn "findDocumentsResponseFixtureBuilder" packages/@doctolib/dmp/src/ 2>/dev/null
Tool results:
→ result (ok): packages/@doctolib/dmp/src/business/DmpApi.test.ts:25:import { documentMetadataFixtureBuilder, findDocumentsResponseFixtureBuilder } from '../test-helpers/fixtures' packages/@doctolib/dmp/src/business/DmpApi.test.ts:796: const mockResponse = findDocumentsResponseFixtureBuilder() packages/@doctolib/dmp/src/business/DmpApi.test.ts:811: const mockResponse = findDocumentsResponseFixtureBuilder({ packages/@doctolib/dmp/src/business/DmpApi.test.ts:867: const mockResponse = findDocuments …(truncated, 1305 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/dmp/src/test-helpers/fixtures.ts(1–61)
Tool results:
→ result (ok): 1 import { createFixtureBuilder } from '@doctolib/test-helpers'
2 import type { DocumentMetadata, FindDocumentsResponse } from '../types/FrDmpTypes'
3 import { STATUS_OK } from '../business/FrDmpConstants'
4
5 export const documentMetadataFixtureBuilder = createFixtureBuilder<DocumentMetadata>({
6 s_uniqueId: 'unique-1',
7 s_title: '',
8 s_description: '',
9 s_classCode: '',
10 s_typeCode: '',
11 s_creationDate: '',
12 s_serviceStartDate: '',
13 s_serviceStopDate: '',
14 s_hea
…(truncated, 390 more chars)
Tools used:
Bash:cd /Users/USER/workspace/doctolib/doctolib && grep -n "FindDocumentsResponse" packages/@doctolib/dmp/src/types/FrDmpTypes.ts
Tool results:
→ result (ok): 411:export interface FindDocumentsResponse extends DmpResponse {
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/dmp/src/types/FrDmpTypes.ts(411–426)
Tool results:
→ result (ok): 411 export interface FindDocumentsResponse extends DmpResponse { 412 / Array of document metadata matching the search criteria */ 413 Documents: DocumentMetadata[] 414 / Optional error message. Consider using i_apiErrorCode and i_apiErrorType from DmpResponse. */ 415 errorMessage?: string 416 } 417 418 // Other types 419 export interface DmpWorkflow { 420 state: DmpWorkflowState 421 consultationId?: number 422 documentsToUpload?: DeprecatedDocumentToAdd[] | DmpDocumentToAdd[]
…(truncated, 101 more chars)
Tools used:
Bash:cd /Users/USER/workspace/doctolib/doctolib && grep -n "eslint-disable" packages/@doctolib/dmp/src/business/DmpApi.test.ts
Tool results:
→ result (ok): 983: // eslint-disable-next-line @typescript-eslint/no-explicit-any 1052: // eslint-disable-next-line @typescript-eslint/no-explicit-any 1195: // eslint-disable-next-line @typescript-eslint/no-explicit-any 1310: // eslint-disable-next-line @typescript-eslint/no-explicit-any 1464: // eslint-disable-next-line @typescript-eslint/no-explicit-any
Tools used:
Bash:cd /Users/USER/workspace/doctolib/doctolib && git diff HEAD~2..HEAD -- packages/@doctolib/dmp/src/business/DmpApi.test.ts | grep "^+" | grep "eslint-disable"
Tool results:
→ result (ok): + // eslint-disable-next-line @typescript-eslint/no-explicit-any
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/dmp/src/business/DmpApi.test.ts(1049–1064)
Tool results:
→ result (ok): 1049 })
1050
1051 it('should return error with status ERROR when s_status is absent from response', async () => {
1052 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1053 const mockResponse = { Documents: [] } as any
1054 findDocumentsMocked.mockResolvedValue(mockResponse)
1055
1056 const dmpApi = new DmpApi(mockProps)
1057 const result = await dmpApi.search({ ins: '1234567890' })
1058
1059 expect(result.error).toBeDefined()
1060
…(truncated, 170 more chars)
Tools used:
Bash:cd /Users/USER/workspace/doctolib/doctolib && grep -n "STATUS_OK\|STATUS_" packages/@doctolib/dmp/src/business/FrDmpConstants.ts 2>/dev/null | head -10
Tools used:
Bash:cd /Users/USER/workspace/doctolib/doctolib && grep -rn "STATUS_OK" packages/@doctolib/dmp/src/business/FrDmpConstants.ts 2>/dev/null
Tool results:
→ result (ok): packages/@doctolib/dmp/src/business/FrDmpConstants.ts:8:export const STATUS_OK = 'OK'
Tool results:
→ result (ok): 8:export const STATUS_OK = 'OK'
Tools used:
Bash:cd /Users/USER/workspace/doctolib/doctolib && grep -n "DmpApiError\|DmpApiErrorInfo" packages/@doctolib/dmp/src/index.ts packages/@doctolib/dmp/src/business/DmpApi.ts 2>/dev/null
Tool results:
→ result (ok): packages/@doctolib/dmp/src/index.ts:80:export { DmpApiError } from './api/DmpApiError' packages/@doctolib/dmp/src/index.ts:81:export type { DmpApiErrorInfo } from './types/DmpApi'
Tools used:
Bash:cd /Users/USER/workspace/doctolib/doctolib && grep -rn "DmpApiError" packages/@doctolib/dmp/src/ 2>/dev/null | grep -v "test\|\.d\.ts"
Tool results:
→ result (ok): packages/@doctolib/dmp/src/index.ts:80:export { DmpApiError } from './api/DmpApiError' packages/@doctolib/dmp/src/index.ts:81:export type { DmpApiErrorInfo } from './types/DmpApi' packages/@doctolib/dmp/src/types/DmpApi.ts:169:export interface DmpApiErrorInfo { packages/@doctolib/dmp/src/types/DmpApi.ts:181: error?: DmpApiErrorInfo packages/@doctolib/dmp/src/api/DmpApiError.ts:1:import type { DmpApiErrorInfo } from '../types/DmpApi' packages/@doctolib/dmp/src/api/DmpApiError.ts:3:export class D …(truncated, 193 more chars)
Tools used:
Bash:cd /Users/USER/workspace/doctolib/doctolib && grep -rn "s_apiErrorDescription\|apiErrorDescription\|DmpApiError\|SearchResult" packages/@doctolib/dmp/src/ 2>/dev/null | grep -v "test\|\.d\.ts\|ty …(truncated, 57 more chars)
Tool results:
→ result (ok): packages/@doctolib/dmp/src/index.ts:19: SearchResult, packages/@doctolib/dmp/src/business/DmpApi.ts:75: SearchResult, packages/@doctolib/dmp/src/business/DmpApi.ts:103: SearchResult, packages/@doctolib/dmp/src/business/DmpApi.ts:211: async search(props: SearchProps): Promise<SearchResult> { packages/@doctolib/dmp/src/business/DmpApi.ts:492: private toSearchResult = (response: FindDocumentsResponse): SearchResult => ({ packages/@doctolib/dmp/src/business/DmpApi.ts:523: apiErrorDes …(truncated, 613 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/dmp/src/business/DmpApi.ts(595–630)
Tool results:
→ result (ok): 595 })) 596 597 private async find(props: SearchProps): Promise<SearchResult> { 598 if (this.dmpService) { 599 const payload = buildFindDocumentsPayload(props) 600 601 try { 602 const response = await this.dmpService.call<FindDocumentsResponse>(DmpAction.FindDocuments, payload) 603 return this.toSearchResult(response) 604 } catch (e) { 605 if (e instanceof BusinessError) { 606 // Legacy does not throw on FindDocuments errors — it re …(truncated, 697 more chars)
Tools used:
Bash:cd /Users/USER/workspace/doctolib/doctolib && grep -n "BusinessError\|DmpAction.FindDocuments\|dmpService" packages/@doctolib/dmp/src/business/DmpApi.test.ts | head -15
Tool results:
→ result (ok): 4:import { DmpAction, BusinessError } from '@doctolib/tpf-french-teleservices' 1290: describe('with dmpService (tpf-french-teleservices path)', () => { 1332: it('should call dmpService.call with FindDocuments action and payload', async () => { 1340: DmpAction.FindDocuments, 1377: it('should call dmpService.call with GetStatus action', async () => { 1395: it('should return "forbidden" when dmpService returns i_userAuthorization 4', async () => { 1406: it('should retu …(truncated, 825 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/dmp/src/business/DmpApi.test.ts(1290–1370)
Tool results:
→ result (ok): 1290 describe('with dmpService (tpf-french-teleservices path)', () => {
1291 const mockDmpService = {
1292 call: jest.fn(),
1293 callWithSession: jest.fn(),
1294 }
1295
1296 const createDmpApiWithService = () => {
1297 const freshProps = {
1298 ...mockProps,
1299 frDmpWorkflow: {
1300 ...mockProps.frDmpWorkflow,
1301 state: 'ready' as DmpWorkflowState,
1302 documentsToUpload: [],
1303 documentUploadResults:
…(truncated, 2806 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/dmp/src/business/DmpApi.test.ts(1370–1420)
Tool results:
→ result (ok): 1370 description: 'Prescription',
1371 })
1372 )
1373 })
1374 })
1375
1376 describe('getPatientConsent (GetStatus)', () => {
1377 it('should call dmpService.call with GetStatus action', async () => {
1378 mockDmpService.call.mockResolvedValue({
1379 ExistingTestAnswer: { i_userAuthorization: 2 },
1380 })
1381
1382 const dmpApi = createDmpApiWithService()
1383 const result = await dmpApi.getPatientConsen
…(truncated, 1452 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/packages/@doctolib/dmp/src/business/DmpApi.test.ts(1610–1640)
Tool results:
→ result (ok): 1610 ins: '123456789012345',
1611 uniqueIds: ['uid-1'],
1612 })
1613
1614 expect(result[0]?.cdaContentInBase64).toBeUndefined()
1615 })
1616
1617 it('should return empty array when dmpService throws BusinessError', async () => {
1618 mockDmpService.call.mockRejectedValue(new BusinessError('error'))
1619
1620 const dmpApi = createDmpApiWithService()
1621 const result = await dmpApi.getDocumentsContentByUniqueId({
1622
…(truncated, 685 more chars)
Now I have a complete picture. Let me verify one last thing — whether there's a test for the dmpService path returning an ERROR response (since the find method routes through two code paths).
Tools used:
Bash:cd /Users/USER/workspace/doctolib/doctolib && grep -n "ERROR\|error\|BusinessError" packages/@doctolib/dmp/src/business/DmpApi.test.ts | grep -A2 -B2 "dmpService\|1290\|1331\|1345\|1374" | head - …(truncated, 2 more chars)
Tool results:
→ result (ok): 1275: updateDocumentStatusCommandMocked.mockResolvedValue({ s_status: 'ERROR_42' }) 1286: ).rejects.toThrow('Error updating document status: ERROR_42') 1617: it('should return empty array when dmpService throws BusinessError', async () => { 1618: mockDmpService.call.mockRejectedValue(new BusinessError('error')) 1653: it('should throw a plain Error when dmpService throws BusinessError', async () => { 1654: mockDmpService.call.mockRejectedValue(new BusinessError(' …(truncated, 129 more chars)
Tools used:
Bash:cd /Users/USER/workspace/doctolib/doctolib && awk 'NR>=1331 && NR<=1380' packages/@doctolib/dmp/src/business/DmpApi.test.ts
Tool results:
→ result (ok): describe('search (FindDocuments)', () => { it('should call dmpService.call with FindDocuments action and payload', async () => { const mockResponse = findDocumentsResponseFixtureBuilder() mockDmpService.call.mockResolvedValue(mockResponse)
const dmpApi = createDmpApiWithService()
const result = await dmpApi.search({ ins: '1234567890' })
expect(mockDmpService.call).toHaveBeenCalledWith(
DmpAction.FindDocuments,
expect.objectConta
…(truncated, 1320 more chars)
I now have a complete picture of the PR. Let me deliver my analysis.
Test Coverage Re-Analysis: PR #240425 (fhir-2213_dmp-api-typed-error-model)
Summary
The coverage is now adequate for the scope of FHIR-2213. All previously identified critical gaps that were in-scope have been addressed. The two new files under review are thorough for a class this focused.
What Was Fixed and Confirmed Adequate
DmpApiError.test.ts — all branches of message assembly are now exercised:
The four-combination matrix for the message constructor is complete:
- Both description and extended info present → colon-joined form
- Description only → description alone
- Extended info only → extended info alone (the bug fix path, previously "undefined: ...")
- Neither present → literal 'DMP API error' fallback
Identity tests (instanceof Error, instanceof DmpApiError, .name) are present and meaningful for callers who need to distinguish this error type via catch blocks. These are not trivial given that TypeScript class inheritance of Error requires Object.setPrototypeOf in some transpile targets — the instanceof test will catch if that invariant breaks.
Field mapping tests cover all seven fields for both the fully-populated and fully-empty cases.
DmpApi.test.ts — the toSearchResult error propagation path is fully exercised:
- Full error object with all seven fields mapped from snake_case response fields to camelCase output — regression protection against field mapping typos (criticality 9, was the most important missing test)
s_status: 'ERROR'with no error detail fields →undefinedvalues preserved correctlys_status: STATUS_OK→errorisundefined(the happy path contract)s_statusabsent from response →?? 'ERROR'fallback producesstatus: 'ERROR'- Documents + error simultaneously present → both propagated correctly (important for partial-result scenarios)
- ERROR response does not throw →
resolves.toBeDefined()confirms the non-throwing contract
One Remaining Minor Issue (not a gap, a code style violation)
DmpApi.test.ts line 1052 introduces a new // eslint-disable-next-line @typescript-eslint/no-explicit-any comment. Per the project's CLAUDE.md rules and your global TypeScript rules, @typescript-eslint/no-explicit-any suppressions are explicitly forbidden. This is the only new disable added by the PR (lines 983, 1195, 1310, 1464 are pre-existing and outside this PR's diff).
The fix is straightforward — the as any cast on line 1053 can be replaced with a proper partial type:
const mockResponse = { Documents: [] } as Partial<FindDocumentsResponse>
findDocumentsMocked.mockResolvedValue(mockResponse as FindDocumentsResponse)
Or alternatively, since FindDocumentsResponse inherits s_status?: string as optional from DmpResponse, the cast to FindDocumentsResponse is already valid without any — s_status being absent is perfectly expressible in the type. The as any is unnecessary; findDocumentsResponseFixtureBuilder without passing s_status or a direct cast to FindDocumentsResponse would work. Criticality: 3 (style violation, not a behavioral gap, but violates an explicit project rule).
One Unexercised Code Path (low criticality, acceptable scope boundary)
The dmpService path in find() (lines 1331–1374 in the test, the with dmpService describe block) has two tests for search but neither covers the case where dmpService.call returns s_status: 'ERROR' — it only tests the happy-path response and field mapping. The BusinessError fallback path is tested at line 1617 but for getDocumentsContentByUniqueId, not search. Since toSearchResult is a private method shared by both code paths and the error-handling tests above exercise it through the findDocuments mock path, this is acceptable — the logic under test is toSearchResult itself, which is fully covered regardless of which caller reaches it. Criticality: 3.
Positive Observations
- The
DmpApiErrorInfointerface is correctly placed intypes/DmpApi.ts(consumed interface, not implementation detail) and exported fromindex.tsalongsideDmpApiError, giving consumers both the error class and its input type without needing to reach into internals. DmpApiErrordoes not throw fromDmpApiitself —SearchResult.erroris a data field, not a thrown exception. This is the correct design for a non-fatal API error that callers need to inspect. The test "should not throw when findDocuments returns s_status ERROR" explicitly pins this contract.- The fixture builder (
findDocumentsResponseFixtureBuilder) correctly defaults tos_status: STATUS_OK, which means all pre-existing tests are unaffected by the newerrorfield — they will all haveerror: undefinedas expected without modification. - Tests follow DAMP naming and test behavior (what the output is), not implementation (which branch was taken).
Verdict
Coverage is adequate for FHIR-2213. The one actionable item before merge is removing the eslint-disable-next-line @typescript-eslint/no-explicit-any at /Users/USER/workspace/doctolib/doctolib/packages/@doctolib/dmp/src/business/DmpApi.test.ts line 1052, which violates an explicit project rule and is trivially avoidable.