@@ -16,6 +16,8 @@ public class ReportAbsManifestValidatorTests
1616{
1717 private const string ExpectedStart = "2025-01-01T00:00:00Z" ;
1818 private const string ExpectedEnd = "2025-01-31T23:59:59Z" ;
19+ private const string DeviceProfile = "http://hl7.org/fhir/us/nhsn-dqm/StructureDefinition/nhsn-submitting-device" ;
20+ private const string PatientListProfile = "http://hl7.org/fhir/us/nhsn-dqm/StructureDefinition/poi-list" ;
1921
2022 [ Fact ]
2123 public async Task ValidateAllAsync_WithMockExternalAbsFromGeneratedData_Passes ( )
@@ -269,12 +271,67 @@ public async Task ValidateAllAsync_WithUnexpectedMeasureId_Fails()
269271 Assert . Contains ( "VALIDATION failed" , ex . Message , StringComparison . OrdinalIgnoreCase ) ;
270272 }
271273
274+ [ Fact ]
275+ public async Task ValidateAllAsync_WhenDeviceIsMissingProfile_Fails ( )
276+ {
277+ var output = new BufferingAutomationOutput ( ) ;
278+ var ( patientIds , bundles ) = FhirBundleGenerator . Generate ( output , patientCount : 1 , totalResourcesPerPatient : 120 ) ;
279+ var patientId = patientIds . Single ( ) ;
280+
281+ var mockExternalAbsResources = BuildMockExternalAbsResources (
282+ patientId , bundles , "MEASURE-UT-4" , ExpectedStart , ExpectedEnd , deviceProfile : null ) ;
283+
284+ var validator = new ReportAbsManifestValidator ( output , CreatePipelineDataReader ( ) ) ;
285+
286+ await Assert . ThrowsAsync < InvalidOperationException > ( ( ) =>
287+ validator . ValidateAllAsync (
288+ mockExternalAbsResources ,
289+ new [ ] { patientId } ,
290+ "MEASURE-UT-4" ,
291+ ExpectedStart ,
292+ ExpectedEnd ) ) ;
293+
294+ Assert . Contains ( output . Lines , line =>
295+ line . Contains ( "Manifest Device is missing meta.profile" , StringComparison . OrdinalIgnoreCase ) ) ;
296+ }
297+
298+ [ Fact ]
299+ public async Task ValidateAllAsync_WhenPatientListProfileIsWrong_Fails ( )
300+ {
301+ var output = new BufferingAutomationOutput ( ) ;
302+ var ( patientIds , bundles ) = FhirBundleGenerator . Generate ( output , patientCount : 1 , totalResourcesPerPatient : 120 ) ;
303+ var patientId = patientIds . Single ( ) ;
304+
305+ var mockExternalAbsResources = BuildMockExternalAbsResources (
306+ patientId ,
307+ bundles ,
308+ "MEASURE-UT-5" ,
309+ ExpectedStart ,
310+ ExpectedEnd ,
311+ patientListProfile : "https://www.cdc.gov/nhsn/nhsn-measures/StructureDefinition/poi-list" ) ;
312+
313+ var validator = new ReportAbsManifestValidator ( output , CreatePipelineDataReader ( ) ) ;
314+
315+ await Assert . ThrowsAsync < InvalidOperationException > ( ( ) =>
316+ validator . ValidateAllAsync (
317+ mockExternalAbsResources ,
318+ new [ ] { patientId } ,
319+ "MEASURE-UT-5" ,
320+ ExpectedStart ,
321+ ExpectedEnd ) ) ;
322+
323+ Assert . Contains ( output . Lines , line =>
324+ line . Contains ( "Manifest List meta.profile mismatch" , StringComparison . OrdinalIgnoreCase ) ) ;
325+ }
326+
272327 private static Dictionary < string , object > BuildMockExternalAbsResources (
273328 string patientId ,
274329 IReadOnlyList < ( string Name , string Json ) > bundles ,
275330 string measureId ,
276331 string start ,
277- string end )
332+ string end ,
333+ string ? deviceProfile = DeviceProfile ,
334+ string ? patientListProfile = PatientListProfile )
278335 {
279336 var generatedPatientJson = ExtractGeneratedPatientJson ( patientId , bundles ) ;
280337
@@ -290,18 +347,18 @@ private static Dictionary<string, object> BuildMockExternalAbsResources(
290347 resourceType = "Organization" ,
291348 id = orgId
292349 } ) ,
293- JsonSerializer . Serialize ( new
350+ SerializeWithOptionalProfile ( deviceProfile , new Dictionary < string , object >
294351 {
295- resourceType = "Device" ,
296- id = deviceId
352+ [ " resourceType" ] = "Device" ,
353+ [ "id" ] = deviceId
297354 } ) ,
298- JsonSerializer . Serialize ( new
355+ SerializeWithOptionalProfile ( patientListProfile , new Dictionary < string , object >
299356 {
300- resourceType = "List" ,
301- id = "PatientList-UT" ,
302- status = "current" ,
303- mode = "snapshot" ,
304- extension = new object [ ]
357+ [ " resourceType" ] = "List" ,
358+ [ "id" ] = "PatientList-UT" ,
359+ [ " status" ] = "current" ,
360+ [ " mode" ] = "snapshot" ,
361+ [ " extension" ] = new object [ ]
305362 {
306363 new
307364 {
@@ -313,7 +370,7 @@ private static Dictionary<string, object> BuildMockExternalAbsResources(
313370 }
314371 }
315372 } ,
316- entry = new object [ ]
373+ [ " entry" ] = new object [ ]
317374 {
318375 new { item = new { reference = $ "Patient/{ patientId } " } }
319376 }
@@ -373,11 +430,12 @@ private static Dictionary<string, object> BuildAbsResourcesWithExtraOperationOut
373430 var manifestNdjson = string . Join ( "\n " , new [ ]
374431 {
375432 JsonSerializer . Serialize ( new { resourceType = "Organization" , id = "Org-UT" } ) ,
376- JsonSerializer . Serialize ( new { resourceType = "Device" , id = "Device-UT" } ) ,
433+ JsonSerializer . Serialize ( new { resourceType = "Device" , id = "Device-UT" , meta = new { profile = new [ ] { DeviceProfile } } } ) ,
377434 JsonSerializer . Serialize ( new
378435 {
379436 resourceType = "List" ,
380437 id = "PatientList-UT" ,
438+ meta = new { profile = new [ ] { PatientListProfile } } ,
381439 status = "current" ,
382440 mode = "snapshot" ,
383441 extension = new object [ ]
@@ -437,6 +495,18 @@ private static Dictionary<string, object> BuildAbsResourcesWithExtraOperationOut
437495 } ;
438496 }
439497
498+ /// <summary>
499+ /// Serializes a manifest resource, adding meta.profile only when a profile is supplied so
500+ /// tests can model a resource that is missing it entirely.
501+ /// </summary>
502+ private static string SerializeWithOptionalProfile ( string ? profile , Dictionary < string , object > resource )
503+ {
504+ if ( profile != null )
505+ resource [ "meta" ] = new { profile = new [ ] { profile } } ;
506+
507+ return JsonSerializer . Serialize ( resource ) ;
508+ }
509+
440510 private static string ExtractGeneratedPatientJson ( string patientId , IReadOnlyList < ( string Name , string Json ) > bundles )
441511 {
442512 foreach ( var ( _, bundleJson ) in bundles )
@@ -474,11 +544,12 @@ private static Dictionary<string, object> BuildAbsResourcesForSingleReportableMe
474544 var manifestNdjson = string . Join ( "\n " , new [ ]
475545 {
476546 JsonSerializer . Serialize ( new { resourceType = "Organization" , id = "Org-UT" } ) ,
477- JsonSerializer . Serialize ( new { resourceType = "Device" , id = "Device-UT" } ) ,
547+ JsonSerializer . Serialize ( new { resourceType = "Device" , id = "Device-UT" , meta = new { profile = new [ ] { DeviceProfile } } } ) ,
478548 JsonSerializer . Serialize ( new
479549 {
480550 resourceType = "List" ,
481551 id = "PatientList-UT" ,
552+ meta = new { profile = new [ ] { PatientListProfile } } ,
482553 status = "current" ,
483554 mode = "snapshot" ,
484555 extension = new object [ ]
0 commit comments