From 561d63d3d4c7e304881f28bd57f04c679e0abcd0 Mon Sep 17 00:00:00 2001 From: Jessica Willis Date: Tue, 3 Sep 2024 14:27:08 -0400 Subject: [PATCH] Moving download report over from being A CLI command to being an API --- prime-router/docs/api/reports.yml | 31 +++- .../src/main/kotlin/azure/ReportFunction.kt | 72 ++++++++ .../src/main/kotlin/azure/RequestFunction.kt | 2 + .../src/main/kotlin/cli/DownloadReport.kt | 67 -------- prime-router/src/main/kotlin/cli/main.kt | 3 +- .../test/kotlin/azure/ReportFunctionTests.kt | 118 +++++++++++++ .../src/test/kotlin/cli/DownloadReportTest.kt | 155 ------------------ 7 files changed, 223 insertions(+), 225 deletions(-) delete mode 100644 prime-router/src/main/kotlin/cli/DownloadReport.kt delete mode 100644 prime-router/src/test/kotlin/cli/DownloadReportTest.kt diff --git a/prime-router/docs/api/reports.yml b/prime-router/docs/api/reports.yml index 523f6ee695e..de6a27348d0 100644 --- a/prime-router/docs/api/reports.yml +++ b/prime-router/docs/api/reports.yml @@ -102,7 +102,36 @@ paths: $ref: '#/components/schemas/Report' '500': description: Internal Server Error - + /reports/download: + get: + summary: Downloads a message based on the report id + security: + - OAuth2: [ system_admin ] + parameters: + - in: query + name: reportId + description: The report id to look for to download. + schema: + type: string + required: true + example: e491f4fb-f2c5-4473-8db2-206ea04991e8 + - in: query + name: removePII + description: Boolean that determines if PII will be removed from the message. If missing will default to true. + Required to be true if prod env. + required: false + schema: + type: boolean + example: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Report' + '500': + description: Internal Server Error # Building components: schemas: diff --git a/prime-router/src/main/kotlin/azure/ReportFunction.kt b/prime-router/src/main/kotlin/azure/ReportFunction.kt index 540ea89d3d3..34457b39351 100644 --- a/prime-router/src/main/kotlin/azure/ReportFunction.kt +++ b/prime-router/src/main/kotlin/azure/ReportFunction.kt @@ -15,6 +15,7 @@ import gov.cdc.prime.router.ActionLogLevel import gov.cdc.prime.router.InvalidParamMessage import gov.cdc.prime.router.InvalidReportMessage import gov.cdc.prime.router.Options +import gov.cdc.prime.router.ReportId import gov.cdc.prime.router.Sender import gov.cdc.prime.router.Sender.ProcessingType import gov.cdc.prime.router.SubmissionReceiver @@ -24,13 +25,19 @@ import gov.cdc.prime.router.azure.observability.event.IReportStreamEventService import gov.cdc.prime.router.azure.observability.event.ReportStreamEventName import gov.cdc.prime.router.azure.observability.event.ReportStreamEventProperties import gov.cdc.prime.router.azure.observability.event.ReportStreamEventService +import gov.cdc.prime.router.cli.CommandUtilities.Companion.abort +import gov.cdc.prime.router.cli.PIIRemovalCommands import gov.cdc.prime.router.common.AzureHttpUtils.getSenderIP +import gov.cdc.prime.router.common.Environment import gov.cdc.prime.router.common.JacksonMapperUtilities +import gov.cdc.prime.router.fhirengine.utils.FhirTranscoder import gov.cdc.prime.router.history.azure.SubmissionsFacade import gov.cdc.prime.router.tokens.AuthenticatedClaims +import gov.cdc.prime.router.tokens.Scope import gov.cdc.prime.router.tokens.authenticationFailure import gov.cdc.prime.router.tokens.authorizationFailure import org.apache.logging.log4j.kotlin.Logging +import java.util.UUID private const val PROCESSING_TYPE_PARAMETER = "processing" @@ -85,6 +92,71 @@ class ReportFunction( } } + /** + * GET report to download + * + * @see ../../../docs/api/reports.yml + */ + @FunctionName("getMessagesFromTestBank") + fun downloadReport( + @HttpTrigger( + name = "downloadReport", + methods = [HttpMethod.GET], + authLevel = AuthorizationLevel.ANONYMOUS, + route = "reports/download" + ) request: HttpRequestMessage, + ): HttpResponseMessage { + val claims = AuthenticatedClaims.authenticate(request) + if (claims != null && claims.authorized(setOf(Scope.primeAdminScope))) { + val reportId = request.queryParameters[REPORT_ID_PARAMETER] + val removePIIRaw = request.queryParameters[REMOVE_PII] + var removePII = false + if (removePIIRaw.isNullOrBlank() || removePIIRaw.toBoolean()) { + removePII = true + } + if (reportId.isNullOrBlank()) { + return HttpUtilities.badRequestResponse(request, "Must provide a reportId.") + } + return processDownloadReport( + request, + ReportId.fromString(reportId), + removePII + ) + } + return HttpUtilities.unauthorizedResponse(request) + } + + fun processDownloadReport( + request: HttpRequestMessage, + reportId: UUID, + removePII: Boolean?, + databaseAccess: DatabaseAccess = DatabaseAccess(), + ): HttpResponseMessage { + val requestedReport = databaseAccess.fetchReportFile(reportId) + + return if (requestedReport.bodyUrl != null && requestedReport.bodyUrl.toString().lowercase().endsWith("fhir")) { + val contents = BlobAccess.downloadBlobAsByteArray(requestedReport.bodyUrl) + + val content = if (removePII == null || removePII) { + PIIRemovalCommands().removePii(FhirTranscoder.decode(contents.toString(Charsets.UTF_8))) + } else { + if (Environment.get().envName == "prod") { + abort("Must remove PII for messages from prod.") + } + + val jsonObject = JacksonMapperUtilities.defaultMapper + .readValue(contents.toString(Charsets.UTF_8), Any::class.java) + JacksonMapperUtilities.defaultMapper.writeValueAsString(jsonObject) + } + + HttpUtilities.okJSONResponse(request, content) + } else if (requestedReport.bodyUrl == null) { + HttpUtilities.badRequestResponse(request, "The requested report does not exist.") + } else { + HttpUtilities.badRequestResponse(request, "The requested report is not fhir.") + } + } + /** * The Waters API, in memory of Dr. Michael Waters * (The older version of this API is "/api/reports") diff --git a/prime-router/src/main/kotlin/azure/RequestFunction.kt b/prime-router/src/main/kotlin/azure/RequestFunction.kt index 47608913daf..e892befec42 100644 --- a/prime-router/src/main/kotlin/azure/RequestFunction.kt +++ b/prime-router/src/main/kotlin/azure/RequestFunction.kt @@ -23,6 +23,8 @@ const val ALLOW_DUPLICATES_PARAMETER = "allowDuplicate" const val TOPIC_PARAMETER = "topic" const val SCHEMA_PARAMETER = "schema" const val FORMAT_PARAMETER = "format" +const val REPORT_ID_PARAMETER = "reportId" +const val REMOVE_PII = "removePII" /** * Base class for ReportFunction and ValidateFunction diff --git a/prime-router/src/main/kotlin/cli/DownloadReport.kt b/prime-router/src/main/kotlin/cli/DownloadReport.kt deleted file mode 100644 index eb06b06c60e..00000000000 --- a/prime-router/src/main/kotlin/cli/DownloadReport.kt +++ /dev/null @@ -1,67 +0,0 @@ -package gov.cdc.prime.router.cli - -import com.github.ajalt.clikt.core.CliktCommand -import com.github.ajalt.clikt.parameters.options.option -import com.github.ajalt.clikt.parameters.options.required -import com.github.ajalt.clikt.parameters.types.file -import gov.cdc.prime.router.ReportId -import gov.cdc.prime.router.azure.BlobAccess -import gov.cdc.prime.router.azure.DatabaseAccess -import gov.cdc.prime.router.cli.CommandUtilities.Companion.abort -import gov.cdc.prime.router.common.Environment -import gov.cdc.prime.router.common.JacksonMapperUtilities -import gov.cdc.prime.router.fhirengine.utils.FhirTranscoder - -class DownloadReport : CliktCommand( - name = "downloadMessage", - help = "Download a message from a given environment with PII removed." -) { - private val env by option("-e", "--env", help = "The environment to grab the record from").required() - - private val reportId by option("-r", "--report-id", help = "The report id to grab").required() - - private val outputFile by option("-o", "--output-file", help = "output file") - .file() - - private val removePII by option("--remove-pii", help = "True or false value. Must be true or not set for prod.") - - // Used to make the tests work - var databaseAccess = DatabaseAccess() - - override fun run() { - if (!CommandUtilities.isApiAvailable(Environment.get(env))) { - abort("The $env environment's API is not available or you have an invalid access token.") - } - - val reportId = ReportId.fromString(reportId) - val requestedReport = databaseAccess.fetchReportFile(reportId) - - if (requestedReport.bodyUrl != null && requestedReport.bodyUrl.toString().lowercase().endsWith("fhir")) { - val contents = BlobAccess.downloadBlobAsByteArray(requestedReport.bodyUrl) - - val content = if (removePII == null || removePII.toBoolean()) { - PIIRemovalCommands().removePii(FhirTranscoder.decode(contents.toString(Charsets.UTF_8))) - } else { - if (env == "prod") { - abort("Must remove PII for messages from prod.") - } - - val jsonObject = JacksonMapperUtilities.defaultMapper - .readValue(contents.toString(Charsets.UTF_8), Any::class.java) - JacksonMapperUtilities.defaultMapper.writeValueAsString(jsonObject) - } - - if (outputFile != null) { - outputFile!!.writeText(content, Charsets.UTF_8) - } else { - echo("-- MESSAGE OUTPUT ------------------------------------------") - echo(content) - echo("-- END MESSAGE OUTPUT --------------------------------------") - } - } else if (requestedReport.bodyUrl == null) { - abort("The requested report does not exist.") - } else { - abort("The requested report is not fhir.") - } - } -} \ No newline at end of file diff --git a/prime-router/src/main/kotlin/cli/main.kt b/prime-router/src/main/kotlin/cli/main.kt index 21066eae17a..d930724a661 100644 --- a/prime-router/src/main/kotlin/cli/main.kt +++ b/prime-router/src/main/kotlin/cli/main.kt @@ -300,7 +300,6 @@ fun main(args: Array) = RouterCli() ValidateTranslationSchemaCommand(), SyncTranslationSchemaCommand(), ValidateYAMLCommand(), - PIIRemovalCommands(), - DownloadReport() + PIIRemovalCommands() ).context { terminal = Terminal(ansiLevel = AnsiLevel.TRUECOLOR) } .main(args) \ No newline at end of file diff --git a/prime-router/src/test/kotlin/azure/ReportFunctionTests.kt b/prime-router/src/test/kotlin/azure/ReportFunctionTests.kt index e08577b2ad4..acf9a62f1a2 100644 --- a/prime-router/src/test/kotlin/azure/ReportFunctionTests.kt +++ b/prime-router/src/test/kotlin/azure/ReportFunctionTests.kt @@ -24,6 +24,7 @@ import gov.cdc.prime.router.Topic import gov.cdc.prime.router.TopicReceiver import gov.cdc.prime.router.UniversalPipelineSender import gov.cdc.prime.router.azure.db.enums.TaskAction +import gov.cdc.prime.router.azure.db.tables.pojos.ReportFile import gov.cdc.prime.router.history.DetailedSubmissionHistory import gov.cdc.prime.router.history.azure.SubmissionsFacade import gov.cdc.prime.router.serializers.Hl7Serializer @@ -45,6 +46,7 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import java.time.OffsetDateTime import java.util.UUID +import kotlin.test.assertFailsWith class ReportFunctionTests { val dataProvider = MockDataProvider { emptyArray() } @@ -172,6 +174,11 @@ class ReportFunctionTests { "05D2222542&ISO||445297001^Swab of internal nose^SCT^^^^2.67||||53342003^Internal nose structure" + " (body structure)^SCT^^^^2020-09-01|||||||||202108020000-0500|20210802000006.0000-0500" + @Suppress("ktlint:standard:max-line-length") + val fhirReport = """{"resourceType":"Bundle","id":"1667861767830636000.7db38d22-b713-49fc-abfa-2edba9c12347", + |"meta":{"lastUpdated":"2022-11-07T22:56:07.832+00:00"},"identifier":{"value":"1234d1d1-95fe-462c-8ac6-46728dba581c"},"type":"message","timestamp":"2021-08-03T13:15:11.015+00:00","entry":[{"fullUrl":"Observation/d683b42a-bf50-45e8-9fce-6c0531994f09","resource":{"resourceType":"Observation","id":"d683b42a-bf50-45e8-9fce-6c0531994f09","status":"final","code":{"coding":[{"system":"http://loinc.org","code":"80382-5"}],"text":"Flu A"},"subject":{"reference":"Patient/9473889b-b2b9-45ac-a8d8-191f27132912"},"performer":[{"reference":"Organization/1a0139b9-fc23-450b-9b6c-cd081e5cea9d"}],"valueCodeableConcept":{"coding":[{"system":"http://snomed.info/sct","code":"260373001","display":"Detected"}]},"interpretation":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0078","code":"A","display":"Abnormal"}]}],"method":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/testkit-name-id","valueCoding":{"code":"BD Veritor System for Rapid Detection of SARS-CoV-2 & Flu A+B_Becton, Dickinson and Company (BD)"}},{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/equipment-uid","valueCoding":{"code":"BD Veritor System for Rapid Detection of SARS-CoV-2 & Flu A+B_Becton, Dickinson and Company (BD)"}}],"coding":[{"display":"BD Veritor System for Rapid Detection of SARS-CoV-2 & Flu A+B*"}]},"specimen":{"reference":"Specimen/52a582e4-d389-42d0-b738-bee51cf5244d"},"device":{"reference":"Device/78dc4d98-2958-43a3-a445-76ceef8c0698"}}}]} +""".trimMargin() + private fun makeEngine(metadata: Metadata, settings: SettingsProvider): WorkflowEngine = spyk( WorkflowEngine.Builder().metadata(metadata).settingsProvider(settings).databaseAccess(accessSpy) .blobAccess(blobMock).queueAccess(queueMock).hl7Serializer(serializer).build() @@ -730,4 +737,115 @@ class ReportFunctionTests { reportFunc.extractPayloadName(mockHttpRequest) } } + + @Test + fun `No report`() { + val mockDb = mockk() + val reportId = UUID.randomUUID() + val reportFunc = spyk(ReportFunction(engine, actionHistory)) + every { mockDb.fetchReportFile(reportId, null, null) } returns ReportFile() +// every { mockDb.fetchReportFile(reportId, null, null) } returns error("Could not find $reportId in REPORT_FILE") + assertFailsWith( + block = { + ReportFunction().processDownloadReport(MockHttpRequestMessage(), reportId, true, mockDb) + } + ) + } +// +// @Test +// fun `valid access token, report found, PII removal`() { +// val reportFile = ReportFile() +// reportFile.bodyUrl = "fakeurl.fhir" +// mockkObject(AuthenticatedClaims) +// val mockDb = mockk() +// every { mockDb.fetchReportFile(any()) } returns reportFile +// mockkClass(BlobAccess::class) +// mockkObject(BlobAccess.Companion) +// every { BlobAccess.Companion.getBlobConnection(any()) } returns "testconnection" +// val blobConnectionInfo = mockk() +// every { blobConnectionInfo.getBlobEndpoint() } returns "http://endpoint/metadata" +// every { BlobAccess.downloadBlobAsByteArray(any()) } returns report.toByteArray(Charsets.UTF_8) +// val downloadReport = DownloadReport() +// downloadReport.databaseAccess = mockDb +// +// val result = downloadReport.test( +// "-r ${UUID.randomUUID()} -e local --remove-pii true", +// ansiLevel = AnsiLevel.TRUECOLOR +// ) +// +// assert(result.output.contains("MESSAGE OUTPUT")) +// } +// +// @Test +// fun `valid access token, report found, asked for no removal on prod`() { +// val reportFile = ReportFile() +// reportFile.bodyUrl = "fakeurl.fhir" +// mockkObject(AuthenticatedClaims) +// val mockDb = mockk() +// every { mockDb.fetchReportFile(any()) } returns reportFile +// mockkClass(BlobAccess::class) +// mockkObject(BlobAccess.Companion) +// every { BlobAccess.Companion.getBlobConnection(any()) } returns "testconnection" +// val blobConnectionInfo = mockk() +// every { blobConnectionInfo.getBlobEndpoint() } returns "http://endpoint/metadata" +// every { BlobAccess.downloadBlobAsByteArray(any()) } returns report.toByteArray(Charsets.UTF_8) +// every { mockDb.fetchReportFile(reportId = any(), null, null) } returns reportFile +// mockkObject(CommandUtilities) +// every { CommandUtilities.isApiAvailable(any(), any()) } returns true +// val downloadReport = DownloadReport() +// downloadReport.databaseAccess = mockDb +// +// val result = downloadReport.test( +// "-r ${UUID.randomUUID()} -e prod --remove-pii false", +// ansiLevel = AnsiLevel.TRUECOLOR +// ) +// +// assert(result.stderr.isNotBlank()) +// } +// +// @Test +// fun `valid access token, report found, no PII removal`() { +// val reportFile = ReportFile() +// reportFile.bodyUrl = "fakeurl.fhir" +// mockkObject(AuthenticatedClaims) +// val mockDb = mockk() +// every { mockDb.fetchReportFile(any()) } returns reportFile +// mockkClass(BlobAccess::class) +// mockkObject(BlobAccess.Companion) +// every { BlobAccess.Companion.getBlobConnection(any()) } returns "testconnection" +// val blobConnectionInfo = mockk() +// every { blobConnectionInfo.getBlobEndpoint() } returns "http://endpoint/metadata" +// every { BlobAccess.downloadBlobAsByteArray(any()) } returns report.toByteArray(Charsets.UTF_8) +// every { mockDb.fetchReportFile(reportId = any(), null, null) } returns reportFile +// val downloadReport = DownloadReport() +// downloadReport.databaseAccess = mockDb +// +// val result = downloadReport.test( +// "-r ${UUID.randomUUID()} -e local --remove-pii false", +// ansiLevel = AnsiLevel.TRUECOLOR +// ) +// +// assert(result.output.contains("MESSAGE OUTPUT")) +// } +// +// @Test +// fun `valid access token, report found, body URL not FHIR`() { +// val reportFile = ReportFile() +// reportFile.bodyUrl = "fakeurl.hl7" +// mockkObject(AuthenticatedClaims) +// val mockDb = mockk() +// every { mockDb.fetchReportFile(any()) } returns reportFile +// mockkConstructor(WorkflowEngine::class) +// every { anyConstructed().db } returns mockDb +// every { mockDb.fetchReportFile(reportId = any(), null, null) } returns reportFile +// val downloadReport = DownloadReport() +// downloadReport.databaseAccess = mockDb +// +// val result = downloadReport.test( +// "-r ${UUID.randomUUID()} -e local --remove-pii true", +// ansiLevel = AnsiLevel.TRUECOLOR +// ) +// +// assert(result.stderr.contains("not fhir")) +// } } \ No newline at end of file diff --git a/prime-router/src/test/kotlin/cli/DownloadReportTest.kt b/prime-router/src/test/kotlin/cli/DownloadReportTest.kt deleted file mode 100644 index 97a776f9d44..00000000000 --- a/prime-router/src/test/kotlin/cli/DownloadReportTest.kt +++ /dev/null @@ -1,155 +0,0 @@ -package gov.cdc.prime.router.cli - -import com.github.ajalt.clikt.testing.test -import com.github.ajalt.mordant.rendering.AnsiLevel -import gov.cdc.prime.router.azure.BlobAccess -import gov.cdc.prime.router.azure.DatabaseAccess -import gov.cdc.prime.router.azure.WorkflowEngine -import gov.cdc.prime.router.azure.db.tables.pojos.ReportFile -import gov.cdc.prime.router.tokens.AuthenticatedClaims -import io.mockk.clearAllMocks -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkClass -import io.mockk.mockkConstructor -import io.mockk.mockkObject -import org.junit.jupiter.api.BeforeEach -import java.util.UUID -import kotlin.test.Test -import kotlin.test.assertFailsWith - -class DownloadReportTest { - @Suppress("ktlint:standard:max-line-length") - val report = """{"resourceType":"Bundle","id":"1667861767830636000.7db38d22-b713-49fc-abfa-2edba9c12347", - |"meta":{"lastUpdated":"2022-11-07T22:56:07.832+00:00"},"identifier":{"value":"1234d1d1-95fe-462c-8ac6-46728dba581c"},"type":"message","timestamp":"2021-08-03T13:15:11.015+00:00","entry":[{"fullUrl":"Observation/d683b42a-bf50-45e8-9fce-6c0531994f09","resource":{"resourceType":"Observation","id":"d683b42a-bf50-45e8-9fce-6c0531994f09","status":"final","code":{"coding":[{"system":"http://loinc.org","code":"80382-5"}],"text":"Flu A"},"subject":{"reference":"Patient/9473889b-b2b9-45ac-a8d8-191f27132912"},"performer":[{"reference":"Organization/1a0139b9-fc23-450b-9b6c-cd081e5cea9d"}],"valueCodeableConcept":{"coding":[{"system":"http://snomed.info/sct","code":"260373001","display":"Detected"}]},"interpretation":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0078","code":"A","display":"Abnormal"}]}],"method":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/testkit-name-id","valueCoding":{"code":"BD Veritor System for Rapid Detection of SARS-CoV-2 & Flu A+B_Becton, Dickinson and Company (BD)"}},{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/equipment-uid","valueCoding":{"code":"BD Veritor System for Rapid Detection of SARS-CoV-2 & Flu A+B_Becton, Dickinson and Company (BD)"}}],"coding":[{"display":"BD Veritor System for Rapid Detection of SARS-CoV-2 & Flu A+B*"}]},"specimen":{"reference":"Specimen/52a582e4-d389-42d0-b738-bee51cf5244d"},"device":{"reference":"Device/78dc4d98-2958-43a3-a445-76ceef8c0698"}}}]} -""".trimMargin() - - @BeforeEach - fun setup() { - clearAllMocks() - } - - @Test - fun `invalid access token`() { - val mockDb = mockk() - val downloadReport = DownloadReport() - downloadReport.databaseAccess = mockDb - assertFailsWith( - block = { - downloadReport.test( - "-r ${UUID.randomUUID()} -e staging --remove-pii true", - ansiLevel = AnsiLevel.TRUECOLOR - ) - } - ) - } - - @Test - fun `valid access token, no report`() { - assertFailsWith( - block = { - DownloadReport().test( - "-r ${UUID.randomUUID()} -e local --remove-pii true", - ansiLevel = AnsiLevel.TRUECOLOR - ) - } - ) - } - - @Test - fun `valid access token, report found, PII removal`() { - val reportFile = ReportFile() - reportFile.bodyUrl = "fakeurl.fhir" - mockkObject(AuthenticatedClaims) - val mockDb = mockk() - every { mockDb.fetchReportFile(any()) } returns reportFile - mockkClass(BlobAccess::class) - mockkObject(BlobAccess.Companion) - every { BlobAccess.Companion.getBlobConnection(any()) } returns "testconnection" - val blobConnectionInfo = mockk() - every { blobConnectionInfo.getBlobEndpoint() } returns "http://endpoint/metadata" - every { BlobAccess.downloadBlobAsByteArray(any()) } returns report.toByteArray(Charsets.UTF_8) - val downloadReport = DownloadReport() - downloadReport.databaseAccess = mockDb - - val result = downloadReport.test( - "-r ${UUID.randomUUID()} -e local --remove-pii true", - ansiLevel = AnsiLevel.TRUECOLOR - ) - - assert(result.output.contains("MESSAGE OUTPUT")) - } - - @Test - fun `valid access token, report found, asked for no removal on prod`() { - val reportFile = ReportFile() - reportFile.bodyUrl = "fakeurl.fhir" - mockkObject(AuthenticatedClaims) - val mockDb = mockk() - every { mockDb.fetchReportFile(any()) } returns reportFile - mockkClass(BlobAccess::class) - mockkObject(BlobAccess.Companion) - every { BlobAccess.Companion.getBlobConnection(any()) } returns "testconnection" - val blobConnectionInfo = mockk() - every { blobConnectionInfo.getBlobEndpoint() } returns "http://endpoint/metadata" - every { BlobAccess.downloadBlobAsByteArray(any()) } returns report.toByteArray(Charsets.UTF_8) - every { mockDb.fetchReportFile(reportId = any(), null, null) } returns reportFile - mockkObject(CommandUtilities) - every { CommandUtilities.isApiAvailable(any(), any()) } returns true - val downloadReport = DownloadReport() - downloadReport.databaseAccess = mockDb - - val result = downloadReport.test( - "-r ${UUID.randomUUID()} -e prod --remove-pii false", - ansiLevel = AnsiLevel.TRUECOLOR - ) - - assert(result.stderr.isNotBlank()) - } - - @Test - fun `valid access token, report found, no PII removal`() { - val reportFile = ReportFile() - reportFile.bodyUrl = "fakeurl.fhir" - mockkObject(AuthenticatedClaims) - val mockDb = mockk() - every { mockDb.fetchReportFile(any()) } returns reportFile - mockkClass(BlobAccess::class) - mockkObject(BlobAccess.Companion) - every { BlobAccess.Companion.getBlobConnection(any()) } returns "testconnection" - val blobConnectionInfo = mockk() - every { blobConnectionInfo.getBlobEndpoint() } returns "http://endpoint/metadata" - every { BlobAccess.downloadBlobAsByteArray(any()) } returns report.toByteArray(Charsets.UTF_8) - every { mockDb.fetchReportFile(reportId = any(), null, null) } returns reportFile - val downloadReport = DownloadReport() - downloadReport.databaseAccess = mockDb - - val result = downloadReport.test( - "-r ${UUID.randomUUID()} -e local --remove-pii false", - ansiLevel = AnsiLevel.TRUECOLOR - ) - - assert(result.output.contains("MESSAGE OUTPUT")) - } - - @Test - fun `valid access token, report found, body URL not FHIR`() { - val reportFile = ReportFile() - reportFile.bodyUrl = "fakeurl.hl7" - mockkObject(AuthenticatedClaims) - val mockDb = mockk() - every { mockDb.fetchReportFile(any()) } returns reportFile - mockkConstructor(WorkflowEngine::class) - every { anyConstructed().db } returns mockDb - every { mockDb.fetchReportFile(reportId = any(), null, null) } returns reportFile - val downloadReport = DownloadReport() - downloadReport.databaseAccess = mockDb - - val result = downloadReport.test( - "-r ${UUID.randomUUID()} -e local --remove-pii true", - ansiLevel = AnsiLevel.TRUECOLOR - ) - - assert(result.stderr.contains("not fhir")) - } -} \ No newline at end of file