diff --git a/.github/actions/post-deploy-smoke-test/action.yml b/.github/actions/post-deploy-smoke-test/action.yml index e83583d076..c4f27ba6c7 100644 --- a/.github/actions/post-deploy-smoke-test/action.yml +++ b/.github/actions/post-deploy-smoke-test/action.yml @@ -1,8 +1,8 @@ name: Smoke test post deploy description: Invoke a script that visits a deploy smoke check page that displays whether the backend / db are healthy. inputs: - deploy-env: - description: The environment being deployed (e.g. "prod" or "test") + base_domain_name: + description: The domain where the application is deployed (e.g. "simplereport.gov" or "test.simplereport.gov") required: true runs: using: composite @@ -12,7 +12,7 @@ runs: working-directory: frontend run: | touch .env - echo REACT_APP_BASE_URL=https://${{ inputs.deploy-env }}.simplereport.gov >> .env.production.local + echo REACT_APP_BASE_URL=${{ inputs.base_domain_name }}>> .env.production.local - name: Run smoke test script shell: bash working-directory: frontend diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 4230dc7f90..9ac5a61b4b 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,3 +1,4 @@ +<<<<<<< HEAD #name: "CodeQL" # #on: @@ -52,4 +53,61 @@ # - name: Perform CodeQL Analysis # uses: github/codeql-action/analyze@v2 # with: -# category: "/language:${{ matrix.language }}" \ No newline at end of file +# category: "/language:${{ matrix.language }}" +======= +name: "CodeQL" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + schedule: + - cron: "45 4 * * 3" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + JAVA_VERSION: 17 + JAVA_DISTRIBUTION: 'zulu' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ javascript, java ] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + queries: +security-and-quality + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: ${{env.JAVA_VERSION}} + distribution: ${{env.JAVA_DISTRIBUTION}} + + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{ matrix.language }}" +>>>>>>> bob/7019-prod-e2e-health-check diff --git a/.github/workflows/deployProd.yml b/.github/workflows/deployProd.yml index dde3162144..2b5f99b51a 100644 --- a/.github/workflows/deployProd.yml +++ b/.github/workflows/deployProd.yml @@ -2,7 +2,7 @@ name: Deploy Prod on: workflow_run: - workflows: ["Deploy Stg"] + workflows: [ "Deploy Stg" ] types: - completed @@ -28,6 +28,7 @@ jobs: build_frontend: runs-on: ubuntu-latest + environment: Production steps: - uses: actions/checkout@v4 - uses: ./.github/actions/build-frontend @@ -40,11 +41,11 @@ jobs: okta_enabled: true okta_url: https://hhs-prime.okta.com okta_client_id: 0oa5ahrdfSpxmNZO74h6 - base_domain_name: www.simplereport.gov + base_domain_name: ${{ vars.BASE_DOMAIN_NAME }} prerelease_backend: runs-on: ubuntu-latest - needs: [build_frontend, build_docker] + needs: [ build_frontend, build_docker ] steps: - uses: actions/checkout@v4 - uses: ./.github/actions/tf-deploy @@ -73,7 +74,7 @@ jobs: environment: name: Production url: https://simplereport.gov - needs: [prerelease_backend] + needs: [ prerelease_backend ] steps: - uses: actions/checkout@v4 - name: Promote and deploy @@ -86,7 +87,7 @@ jobs: slack_alert: runs-on: ubuntu-latest if: failure() - needs: [deploy] + needs: [ deploy ] steps: - uses: actions/checkout@v4 - name: Send alert to Slack @@ -97,3 +98,5 @@ jobs: :siren-gif: Deploy to ${{ env.DEPLOY_ENV }} failed. ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} :siren-gif: webhook_url: ${{ secrets.SR_ALERTS_SLACK_WEBHOOK_URL }} user_map: $${{ secrets.SR_ALERTS_GITHUB_SLACK_MAP }} + +# a post-prod health check workflow is defined in smokeTestDeployProd. See the Alert response wiki page for more details \ No newline at end of file diff --git a/.github/workflows/e2eRemote.yml b/.github/workflows/e2eRemote.yml index 516bbb2224..2d9e55108d 100644 --- a/.github/workflows/e2eRemote.yml +++ b/.github/workflows/e2eRemote.yml @@ -93,7 +93,7 @@ jobs: - name: Archive cypress failures if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: cypress-results path: | diff --git a/.github/workflows/ghcrCleanup.yml b/.github/workflows/ghcrCleanup.yml index f352b2e7ad..31507e7a8d 100644 --- a/.github/workflows/ghcrCleanup.yml +++ b/.github/workflows/ghcrCleanup.yml @@ -11,38 +11,44 @@ jobs: steps: # backend, cypress, database, frontend, frontend-lighthouse, nginx - uses: actions/delete-package-versions@v4 - with: - package-name: 'backend' + with: + package-name: 'prime-simplereport/backend' package-type: 'container' min-versions-to-keep: 10 delete-only-untagged-versions: 'true' - uses: actions/delete-package-versions@v4 - with: - package-name: 'cypress' + with: + package-name: 'prime-simplereport/cypress' package-type: 'container' min-versions-to-keep: 10 delete-only-untagged-versions: 'true' - uses: actions/delete-package-versions@v4 - with: - package-name: 'database' + with: + package-name: 'prime-simplereport/database' package-type: 'container' min-versions-to-keep: 10 delete-only-untagged-versions: 'true' - uses: actions/delete-package-versions@v4 - with: - package-name: 'frontend' + with: + package-name: 'prime-simplereport/db' package-type: 'container' min-versions-to-keep: 10 delete-only-untagged-versions: 'true' - uses: actions/delete-package-versions@v4 - with: - package-name: 'frontend-lighthouse' + with: + package-name: 'prime-simplereport/frontend' package-type: 'container' min-versions-to-keep: 10 delete-only-untagged-versions: 'true' - uses: actions/delete-package-versions@v4 - with: - package-name: 'nginx' + with: + package-name: 'prime-simplereport/frontend-lighthouse' + package-type: 'container' + min-versions-to-keep: 10 + delete-only-untagged-versions: 'true' + - uses: actions/delete-package-versions@v4 + with: + package-name: 'prime-simplereport/nginx' package-type: 'container' min-versions-to-keep: 10 delete-only-untagged-versions: 'true' diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml index 0b7116ad43..4c8b4f179b 100644 --- a/.github/workflows/lighthouse.yml +++ b/.github/workflows/lighthouse.yml @@ -82,4 +82,4 @@ # if: always() # with: # name: lighthouse-results -# path: lighthouse/* \ No newline at end of file +# path: lighthouse/* diff --git a/.github/workflows/maintenance.yml b/.github/workflows/maintenance.yml index 912b35388c..b8c7eafe99 100644 --- a/.github/workflows/maintenance.yml +++ b/.github/workflows/maintenance.yml @@ -31,7 +31,7 @@ jobs: working-directory: ./frontend steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4.0.0 + - uses: actions/setup-node@v4.0.1 with: node-version: ${{env.NODE_VERSION}} - uses: azure/login@v1 diff --git a/.github/workflows/smokeTestDeployProd.yml b/.github/workflows/smokeTestDeployProd.yml index a25573c29f..46e054509e 100644 --- a/.github/workflows/smokeTestDeployProd.yml +++ b/.github/workflows/smokeTestDeployProd.yml @@ -36,7 +36,7 @@ jobs: slack_alert: runs-on: ubuntu-latest if: failure() - needs: [ smoke-test-front-and-back-end ] + needs: [smoke-test-front-and-back-end] steps: - uses: actions/checkout@v4 - name: Send alert to Slack diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 39cf5bc858..256348dad7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -64,7 +64,7 @@ jobs: path: backend/build/** key: ${{ runner.os }}-backend-coverage-${{ github.run_id }}-${{ github.run_attempt }} - name: Archive failed test results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: name: backend-test-report @@ -75,7 +75,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version: ${{ env.NODE_VERSION }} - name: Cache yarn @@ -112,7 +112,7 @@ jobs: ops/services/app_functions/report_stream_batched_publisher/functions/coverage/** key: ${{ runner.os }}-function-coverage-${{ github.run_id }}-${{ github.run_attempt }} - name: Archive function coverage results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: function-coverage path: ops/services/app_functions/report_stream_batched_publisher/functions/coverage @@ -199,7 +199,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version: ${{ env.NODE_VERSION }} - name: Cache yarn diff --git a/backend/build.gradle b/backend/build.gradle index 2d7329f4d4..9f01c10827 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -84,7 +84,7 @@ dependencies { runtimeOnly 'org.springframework.session:spring-session-jdbc' // App insights instrumentation - implementation 'com.microsoft.azure:applicationinsights-core:3.4.17' + implementation 'com.microsoft.azure:applicationinsights-core:3.4.18' // Twilio for SMS implementation 'com.twilio.sdk:twilio:9.14.0' diff --git a/backend/gradle.lockfile b/backend/gradle.lockfile index a06f3406a3..9e72db9117 100644 --- a/backend/gradle.lockfile +++ b/backend/gradle.lockfile @@ -43,7 +43,7 @@ com.graphql-java:graphql-java-extended-validation:20.0=compileClasspath,runtimeC com.graphql-java:graphql-java:20.2=compileClasspath,runtimeClasspath com.graphql-java:java-dataloader:3.2.0=compileClasspath,runtimeClasspath com.ibm.icu:icu4j:72.1=compileClasspath,runtimeClasspath -com.microsoft.azure:applicationinsights-core:3.4.17=compileClasspath,runtimeClasspath +com.microsoft.azure:applicationinsights-core:3.4.18=compileClasspath,runtimeClasspath com.nimbusds:content-type:2.2=compileClasspath,runtimeClasspath com.nimbusds:lang-tag:1.7=compileClasspath,runtimeClasspath com.nimbusds:nimbus-jose-jwt:9.24.4=compileClasspath,runtimeClasspath diff --git a/backend/src/main/java/gov/cdc/usds/simplereport/api/heathcheck/BackendAndDatabaseHealthIndicator.java b/backend/src/main/java/gov/cdc/usds/simplereport/api/heathcheck/BackendAndDatabaseHealthIndicator.java index e10bb0c315..0a8cb830b2 100644 --- a/backend/src/main/java/gov/cdc/usds/simplereport/api/heathcheck/BackendAndDatabaseHealthIndicator.java +++ b/backend/src/main/java/gov/cdc/usds/simplereport/api/heathcheck/BackendAndDatabaseHealthIndicator.java @@ -16,6 +16,7 @@ public class BackendAndDatabaseHealthIndicator implements HealthIndicator { private final FeatureFlagRepository _ffRepo; private final OktaRepository _oktaRepo; + public static final String ACTIVE_LITERAL = "ACTIVE"; @Override public Health health() { @@ -23,8 +24,8 @@ public Health health() { _ffRepo.findAll(); String oktaStatus = _oktaRepo.getApplicationStatusForHealthCheck(); - if (oktaStatus.equals("ACTIVE")) { - log.info("Okta status didn't return active, instead returned %s", oktaStatus); + if (!ACTIVE_LITERAL.equals(oktaStatus)) { + log.info("Okta status didn't return ACTIVE, instead returned " + oktaStatus); return Health.down().build(); } diff --git a/backend/src/main/java/gov/cdc/usds/simplereport/idp/repository/DemoOktaRepository.java b/backend/src/main/java/gov/cdc/usds/simplereport/idp/repository/DemoOktaRepository.java index 481e580e49..883963df9c 100644 --- a/backend/src/main/java/gov/cdc/usds/simplereport/idp/repository/DemoOktaRepository.java +++ b/backend/src/main/java/gov/cdc/usds/simplereport/idp/repository/DemoOktaRepository.java @@ -1,5 +1,7 @@ package gov.cdc.usds.simplereport.idp.repository; +import static gov.cdc.usds.simplereport.api.heathcheck.BackendAndDatabaseHealthIndicator.ACTIVE_LITERAL; + import com.okta.sdk.resource.model.UserStatus; import gov.cdc.usds.simplereport.api.CurrentTenantDataAccessContextHolder; import gov.cdc.usds.simplereport.api.model.errors.ConflictingUserException; @@ -434,7 +436,6 @@ public Integer getUsersInSingleFacility(Facility facility) { @Override public String getApplicationStatusForHealthCheck() { - String FAKE_STATUS = "ACTIVE"; - return FAKE_STATUS; + return ACTIVE_LITERAL; } } diff --git a/backend/src/main/java/gov/cdc/usds/simplereport/service/AddressValidationService.java b/backend/src/main/java/gov/cdc/usds/simplereport/service/AddressValidationService.java index 194285a3dd..add0752ae7 100644 --- a/backend/src/main/java/gov/cdc/usds/simplereport/service/AddressValidationService.java +++ b/backend/src/main/java/gov/cdc/usds/simplereport/service/AddressValidationService.java @@ -138,7 +138,7 @@ public ZoneId getZoneIdByLookup(Lookup lookup) { try { timezoneInfo = getTimezoneInfoByLookup(lookup); } catch (InvalidAddressException | IllegalGraphqlArgumentException exception) { - log.error("Unable to find timezone by testing lab address", exception); + log.error("Unable to find timezone with provided address", exception); } if (timezoneInfo == null) { return null; diff --git a/backend/src/main/java/gov/cdc/usds/simplereport/service/TestResultUploadService.java b/backend/src/main/java/gov/cdc/usds/simplereport/service/TestResultUploadService.java index 3a96bca5d4..c6da51390c 100644 --- a/backend/src/main/java/gov/cdc/usds/simplereport/service/TestResultUploadService.java +++ b/backend/src/main/java/gov/cdc/usds/simplereport/service/TestResultUploadService.java @@ -236,14 +236,6 @@ private Map transformCsvRow(Map row) { var updatedSpecimenType = modifyRowSpecimenNameToSNOMED(row.get(SPECIMEN_TYPE_COLUMN_NAME).toLowerCase()); - var testingLabAddress = - new StreetAddress( - row.get("testing_lab_street"), - row.get("testing_lab_street2"), - row.get("testing_lab_city"), - row.get("testing_lab_state"), - row.get("testing_lab_zip_code"), - null); var providerAddress = new StreetAddress( row.get("ordering_provider_street"), @@ -255,9 +247,7 @@ private Map transformCsvRow(Map row) { var testResultDate = convertToZonedDateTime( - row.get(TEST_RESULT_DATE_COLUMN_NAME), - resultsUploaderCachingService, - testingLabAddress); + row.get(TEST_RESULT_DATE_COLUMN_NAME), resultsUploaderCachingService, providerAddress); var orderTestDate = convertToZonedDateTime( @@ -276,7 +266,7 @@ private Map transformCsvRow(Map row) { ? convertToZonedDateTime( row.get(TESTING_LAB_SPECIMEN_RECEIVED_DATE_COLUMN_NAME), resultsUploaderCachingService, - testingLabAddress) + providerAddress) : orderTestDate; var dateResultReleased = @@ -284,7 +274,7 @@ private Map transformCsvRow(Map row) { ? convertToZonedDateTime( row.get(DATE_RESULT_RELEASED_COLUMN_NAME), resultsUploaderCachingService, - testingLabAddress) + providerAddress) : testResultDate; row.put(SPECIMEN_TYPE_COLUMN_NAME, updatedSpecimenType); diff --git a/backend/src/main/resources/application-azure-prod.yaml b/backend/src/main/resources/application-azure-prod.yaml index 86c11ad201..30a44ed773 100644 --- a/backend/src/main/resources/application-azure-prod.yaml +++ b/backend/src/main/resources/application-azure-prod.yaml @@ -22,7 +22,7 @@ twilio: from-number: "+14045312484" features: hivEnabled: false - singleEntryRsvEnabled: false + singleEntryRsvEnabled: true agnosticEnabled: false - testCardRefactorEnabled: false + testCardRefactorEnabled: true agnosticBulkUploadEnabled: false diff --git a/backend/src/main/resources/application-azure-stg.yaml b/backend/src/main/resources/application-azure-stg.yaml index 8d717ee55a..efe0c57f1c 100644 --- a/backend/src/main/resources/application-azure-stg.yaml +++ b/backend/src/main/resources/application-azure-stg.yaml @@ -14,4 +14,4 @@ simple-report: experian: enabled: true features: - testCardRefactorEnabled: false + testCardRefactorEnabled: true diff --git a/backend/src/main/resources/application-azure-training.yaml b/backend/src/main/resources/application-azure-training.yaml index f1a98cca72..343f47c727 100644 --- a/backend/src/main/resources/application-azure-training.yaml +++ b/backend/src/main/resources/application-azure-training.yaml @@ -11,4 +11,4 @@ simple-report: twilio: enabled: false features: - testCardRefactorEnabled: false \ No newline at end of file + testCardRefactorEnabled: true \ No newline at end of file diff --git a/backend/src/test/java/gov/cdc/usds/simplereport/api/healthcheck/BackendAndDatabaseHealthIndicatorTest.java b/backend/src/test/java/gov/cdc/usds/simplereport/api/healthcheck/BackendAndDatabaseHealthIndicatorTest.java index 97e67f0573..ca35828467 100644 --- a/backend/src/test/java/gov/cdc/usds/simplereport/api/healthcheck/BackendAndDatabaseHealthIndicatorTest.java +++ b/backend/src/test/java/gov/cdc/usds/simplereport/api/healthcheck/BackendAndDatabaseHealthIndicatorTest.java @@ -1,5 +1,6 @@ package gov.cdc.usds.simplereport.api.healthcheck; +import static gov.cdc.usds.simplereport.api.heathcheck.BackendAndDatabaseHealthIndicator.ACTIVE_LITERAL; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; @@ -29,7 +30,7 @@ class BackendAndDatabaseHealthIndicatorTest extends BaseRepositoryTest { @Test void health_succeedsWhenReposDoesntThrow() { when(mockFeatureFlagRepo.findAll()).thenReturn(List.of()); - when(mockOktaRepo.getApplicationStatusForHealthCheck()).thenReturn("ACTIVE"); + when(mockOktaRepo.getApplicationStatusForHealthCheck()).thenReturn(ACTIVE_LITERAL); assertThat(indicator.health()).isEqualTo(Health.up().build()); } diff --git a/backend/src/test/java/gov/cdc/usds/simplereport/service/TestResultUploadServiceTest.java b/backend/src/test/java/gov/cdc/usds/simplereport/service/TestResultUploadServiceTest.java index 7ea15b9fd5..002200f524 100644 --- a/backend/src/test/java/gov/cdc/usds/simplereport/service/TestResultUploadServiceTest.java +++ b/backend/src/test/java/gov/cdc/usds/simplereport/service/TestResultUploadServiceTest.java @@ -29,6 +29,7 @@ import gov.cdc.usds.simplereport.db.model.TestResultUpload; import gov.cdc.usds.simplereport.db.model.auxiliary.FHIRBundleRecord; import gov.cdc.usds.simplereport.db.model.auxiliary.Pipeline; +import gov.cdc.usds.simplereport.db.model.auxiliary.StreetAddress; import gov.cdc.usds.simplereport.db.model.auxiliary.UploadStatus; import gov.cdc.usds.simplereport.db.repository.ResultUploadErrorRepository; import gov.cdc.usds.simplereport.db.repository.TestResultUploadRepository; @@ -47,6 +48,8 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.Arrays; import java.util.Collections; import java.util.Date; @@ -708,6 +711,77 @@ void uploadService_processCsv_handlesEscapedCommas_inTestingLabValues() { assertThat(row.getTestingLabPhoneNumber().getValue()).isEqualTo("205-888-2000"); } + @Test + @SliceTestConfiguration.WithSimpleReportStandardUser + void uploadService_processCsv_handlesDefaultDateTimeZone_withValidOrderingProviderAddress() { + // GIVEN + ZoneId zoneId = ZoneId.of("US/Pacific"); + InputStream input = loadCsv("testResultUpload/test-results-upload-valid-default-dates.csv"); + UploadResponse response = buildUploadResponse(); + when(dataHubMock.uploadCSV(any())).thenReturn(response); + when(resultsUploaderCachingServiceMock.getSpecimenTypeNameToSNOMEDMap()) + .thenReturn(Map.of("nasal swab", "000111222")); + when(resultsUploaderCachingServiceMock.getCovidEquipmentModelAndTestPerformedCodeSet()) + .thenReturn(Set.of(ResultsUploaderCachingService.getKey("ID NOW", "94534-5"))); + StreetAddress providerAddress = + new StreetAddress("400 Main Street", "", "Hayward", "CA", "94540", null); + when(resultsUploaderCachingServiceMock.getZoneIdByAddress(providerAddress)).thenReturn(zoneId); + when(repoMock.save(any())).thenReturn(mock(TestResultUpload.class)); + + // WHEN + sut.processResultCSV(input); + + // THEN + TestResultRow row = getRowFromUpload(dataHubMock); + String expectedTestResultDateTime = + ZonedDateTime.of(2021, 12, 23, 12, 0, 0, 0, zoneId).toOffsetDateTime().toString(); + String expectedOrderTestDateTime = + ZonedDateTime.of(2021, 12, 20, 12, 0, 0, 0, zoneId).toOffsetDateTime().toString(); + assertThat(row.getTestResultDate().getValue()).isEqualTo(expectedTestResultDateTime); + assertThat(row.getOrderTestDate().getValue()).isEqualTo(expectedOrderTestDateTime); + assertThat(row.getSpecimenCollectionDate().getValue()).isEqualTo(expectedOrderTestDateTime); + assertThat(row.getTestingLabSpecimenReceivedDate().getValue()) + .isEqualTo(expectedOrderTestDateTime); + assertThat(row.getDateResultReleased().getValue()).isEqualTo(expectedTestResultDateTime); + } + + @Test + @SliceTestConfiguration.WithSimpleReportStandardUser + void uploadService_processCsv_defaultsToEasternTimeZone_withInvalidOrderingProviderAddress() { + // GIVEN + InputStream input = loadCsv("testResultUpload/test-results-upload-valid-default-dates.csv"); + UploadResponse response = buildUploadResponse(); + when(dataHubMock.uploadCSV(any())).thenReturn(response); + when(resultsUploaderCachingServiceMock.getSpecimenTypeNameToSNOMEDMap()) + .thenReturn(Map.of("nasal swab", "000111222")); + when(resultsUploaderCachingServiceMock.getCovidEquipmentModelAndTestPerformedCodeSet()) + .thenReturn(Set.of(ResultsUploaderCachingService.getKey("ID NOW", "94534-5"))); + StreetAddress providerAddress = + new StreetAddress("400 Main Street", "", "Hayward", "CA", "94540", null); + when(resultsUploaderCachingServiceMock.getZoneIdByAddress(providerAddress)).thenReturn(null); + when(repoMock.save(any())).thenReturn(mock(TestResultUpload.class)); + + // WHEN + sut.processResultCSV(input); + + // THEN + TestResultRow row = getRowFromUpload(dataHubMock); + String expectedTestResultDateTime = + ZonedDateTime.of(2021, 12, 23, 12, 0, 0, 0, ZoneId.of("US/Eastern")) + .toOffsetDateTime() + .toString(); + String expectedOrderTestDateTime = + ZonedDateTime.of(2021, 12, 20, 12, 0, 0, 0, ZoneId.of("US/Eastern")) + .toOffsetDateTime() + .toString(); + assertThat(row.getTestResultDate().getValue()).isEqualTo(expectedTestResultDateTime); + assertThat(row.getOrderTestDate().getValue()).isEqualTo(expectedOrderTestDateTime); + assertThat(row.getSpecimenCollectionDate().getValue()).isEqualTo(expectedOrderTestDateTime); + assertThat(row.getTestingLabSpecimenReceivedDate().getValue()) + .isEqualTo(expectedOrderTestDateTime); + assertThat(row.getDateResultReleased().getValue()).isEqualTo(expectedTestResultDateTime); + } + @Test @SliceTestConfiguration.WithSimpleReportStandardUser void uploadService_processCsv_transforms_match_expected_csv() throws IOException { diff --git a/backend/src/test/java/gov/cdc/usds/simplereport/utils/BulkUploadResultsToFhirTest.java b/backend/src/test/java/gov/cdc/usds/simplereport/utils/BulkUploadResultsToFhirTest.java index 91d77e2c1e..b53940c37d 100644 --- a/backend/src/test/java/gov/cdc/usds/simplereport/utils/BulkUploadResultsToFhirTest.java +++ b/backend/src/test/java/gov/cdc/usds/simplereport/utils/BulkUploadResultsToFhirTest.java @@ -24,6 +24,7 @@ import java.nio.charset.StandardCharsets; import java.time.Instant; import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.Date; import java.util.Map; import java.util.UUID; @@ -538,13 +539,14 @@ void convertExistingCsv_populatesBlankFields() { @Test void convertExistingCsv_setsOrderingProviderTimezone_forNoTimezoneInDates() { - when(resultsUploaderCachingService.getZoneIdByAddress(any())) - .thenReturn(ZoneId.of("US/Mountain")); + ZoneId zoneId = ZoneId.of("US/Mountain"); + when(resultsUploaderCachingService.getZoneIdByAddress(any())).thenReturn(zoneId); InputStream input = loadCsv("testResultUpload/test-results-upload-valid-dates-no-timezone.csv"); - Instant orderTestDate = Instant.parse("2021-12-19T12:00:00-07:00"); - Instant specimenCollectionDate = Instant.parse("2021-12-20T12:00:00-07:00"); - Instant testResultDate = Instant.parse("2021-12-23T12:00:00-07:00"); + Instant orderTestDate = ZonedDateTime.of(2021, 12, 19, 12, 0, 0, 0, zoneId).toInstant(); + Instant specimenCollectionDate = + ZonedDateTime.of(2021, 12, 20, 12, 0, 0, 0, zoneId).toInstant(); + Instant testResultDate = ZonedDateTime.of(2021, 12, 23, 12, 0, 0, 0, zoneId).toInstant(); FHIRBundleRecord bundleRecord = sut.convertToFhirBundles(input, UUID.randomUUID()); var serializedBundles = bundleRecord.serializedBundle(); @@ -574,13 +576,15 @@ void convertExistingCsv_setsOrderingProviderTimezone_forNoTimezoneInDates() { @Test void convertExistingCsv_setsEasternTimeDefaultTimeZone_forIncorrectOrderingProviderAddress() { + ZoneId zoneId = ZoneId.of("US/Eastern"); // mock invalid address response when(resultsUploaderCachingService.getZoneIdByAddress(any())).thenReturn(null); InputStream input = loadCsv("testResultUpload/test-results-upload-valid-dates-no-timezone.csv"); - Instant orderTestDate = Instant.parse("2021-12-19T12:00:00-05:00"); - Instant specimenCollectionDate = Instant.parse("2021-12-20T12:00:00-05:00"); - Instant testResultDate = Instant.parse("2021-12-23T12:00:00-05:00"); + Instant orderTestDate = ZonedDateTime.of(2021, 12, 19, 12, 0, 0, 0, zoneId).toInstant(); + Instant specimenCollectionDate = + ZonedDateTime.of(2021, 12, 20, 12, 0, 0, 0, zoneId).toInstant(); + Instant testResultDate = ZonedDateTime.of(2021, 12, 23, 12, 0, 0, 0, zoneId).toInstant(); FHIRBundleRecord bundleRecord = sut.convertToFhirBundles(input, UUID.randomUUID()); var serializedBundles = bundleRecord.serializedBundle(); diff --git a/backend/src/test/resources/testResultUpload/test-results-upload-valid-default-dates.csv b/backend/src/test/resources/testResultUpload/test-results-upload-valid-default-dates.csv new file mode 100644 index 0000000000..97fe22004c --- /dev/null +++ b/backend/src/test/resources/testResultUpload/test-results-upload-valid-default-dates.csv @@ -0,0 +1,2 @@ +patient_id,patient_last_name,patient_first_name,patient_middle_name,patient_street,patient_street2,patient_city,patient_state,patient_zip_code,patient_county,patient_phone_number,patient_dob,patient_gender,patient_race,patient_ethnicity,patient_preferred_language,patient_email,accession_number,equipment_model_name,test_performed_code,test_result,order_test_date,specimen_collection_date,testing_lab_specimen_received_date,test_result_date,date_result_released,specimen_type,ordering_provider_id,ordering_provider_last_name,ordering_provider_first_name,ordering_provider_middle_name,ordering_provider_street,ordering_provider_street2,ordering_provider_city,ordering_provider_state,ordering_provider_zip_code,ordering_provider_phone_number,testing_lab_clia,testing_lab_name,testing_lab_street,testing_lab_street2,testing_lab_city,testing_lab_state,testing_lab_zip_code,testing_lab_phone_number,pregnant,employed_in_healthcare,symptomatic_for_disease,illness_onset_date,resident_congregate_setting,residence_type,hospitalized,icu,ordering_facility_name,ordering_facility_street,ordering_facility_street2,ordering_facility_city,ordering_facility_state,ordering_facility_zip_code,ordering_facility_phone_number,comment,test_result_status +1234,Doe,Jane,,123 Main St,,Birmingham,AL,35226,Jefferson,205-999-2800,1/20/1962,F,White,Not Hispanic or Latino,,,123,ID NOW,94534-5,Detected,12/20/2021,"","",12/23/2021,"",Nasal Swab,1013012657,Smith MD,John,,400 Main Street,,Hayward,CA,94540,205-888-2000,01D1058442,"My Testing Lab, Downtown Office",300 North Street,,Birmingham,AL,35228,205-888-2000,N,N,N,,N,,N,N,My Urgent Care,400 Main Street,Suite 100,Culver City,CA,90066,205-888-2000,Test Comment,F diff --git a/cypress/e2e/05-get_result_from_patient_link.cy.js b/cypress/e2e/05-get_result_from_patient_link.cy.js index 1343e50979..f4b16036f5 100644 --- a/cypress/e2e/05-get_result_from_patient_link.cy.js +++ b/cypress/e2e/05-get_result_from_patient_link.cy.js @@ -56,7 +56,6 @@ describe("Getting a test result from a patient link", () => { cy.get("#dob-submit-button").click(); }); it("shows the test result", () => { - cy.contains("Test result: COVID-19"); cy.contains("Test result"); cy.contains("Test date"); cy.contains("Test device"); diff --git a/cypress/package.json b/cypress/package.json index f523f441a9..9a75588d33 100644 --- a/cypress/package.json +++ b/cypress/package.json @@ -7,7 +7,7 @@ "cypress": "^11.0.0", "cypress-localstorage-commands": "^2.2.5", "dayjs": "^1.11.10", - "eslint": "^8.55.0", + "eslint": "^8.56.0", "eslint-plugin-cypress": "^2.15.1", "otplib": "^12.0.1", "prettier": "3.1.1" @@ -22,7 +22,7 @@ "lint:write": "eslint --fix . && prettier . --write" }, "dependencies": { - "axe-core": "^4.8.2", + "axe-core": "^4.8.3", "cypress-axe": "^1.5.0" } } diff --git a/cypress/yarn.lock b/cypress/yarn.lock index 7276ce2bf9..50a9b06656 100644 --- a/cypress/yarn.lock +++ b/cypress/yarn.lock @@ -66,10 +66,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.55.0": - version "8.55.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.55.0.tgz#b721d52060f369aa259cf97392403cb9ce892ec6" - integrity sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA== +"@eslint/js@8.56.0": + version "8.56.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.56.0.tgz#ef20350fec605a7f7035a01764731b2de0f3782b" + integrity sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A== "@faker-js/faker@^8.3.1": version "8.3.1" @@ -290,10 +290,10 @@ aws4@^1.8.0: resolved "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== -axe-core@^4.8.2: - version "4.8.2" - resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.8.2.tgz" - integrity sha512-/dlp0fxyM3R8YW7MFzaHWXrf4zzbr0vaYb23VBFCl83R7nWNPg/yaQw2Dc8jzCMmDVLhSdzH8MjrsuIUuvX+6g== +axe-core@^4.8.3: + version "4.8.3" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.8.3.tgz#205df863dd9917d5979e9435dab4d47692759051" + integrity sha512-d5ZQHPSPkF9Tw+yfyDcRoUOc4g/8UloJJe5J8m4L5+c7AtDdjDLRxew/knnI4CxvtdxEUVgWz4x3OIQUIFiMfw== balanced-match@^1.0.0: version "1.0.2" @@ -621,15 +621,15 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint@^8.55.0: - version "8.55.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.55.0.tgz#078cb7b847d66f2c254ea1794fa395bf8e7e03f8" - integrity sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA== +eslint@^8.56.0: + version "8.56.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.56.0.tgz#4957ce8da409dc0809f99ab07a1b94832ab74b15" + integrity sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.6.1" "@eslint/eslintrc" "^2.1.4" - "@eslint/js" "8.55.0" + "@eslint/js" "8.56.0" "@humanwhocodes/config-array" "^0.11.13" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" diff --git a/frontend/package.json b/frontend/package.json index dc09e3edf0..d53b4f7f28 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -192,7 +192,7 @@ "@testing-library/jest-dom": "^6.1.3", "@types/apollo-upload-client": "^17.0.0", "@types/classnames": "^2.2.11", - "@types/jest": "^29.5.1", + "@types/jest": "^29.5.11", "@types/jest-axe": "^3.5.5", "@types/node": "^18.11.9", "@types/react": "^18.0.27", @@ -204,7 +204,7 @@ "@types/uuid": "^9.0.4", "@typescript-eslint/eslint-plugin": "^6.12.0", "@typescript-eslint/parser": "^6.12.0", - "chromatic": "^6.10.2", + "chromatic": "^10.2.0", "dayjs": "^1.10.7", "depcheck": "^1.4.3", "dotenv": "^16.3.1", @@ -215,7 +215,7 @@ "eslint-plugin-storybook": "^0.6.15", "eslint-plugin-testing-library": "^5.11.0", "eslint-plugin-unused-imports": "3.0.0", - "jest": "29.5.0", + "jest": "29.7.0", "jest-axe": "^8.0.0", "jest-fetch-mock": "^3.0.3", "jest-location-mock": "^1.0.10", diff --git a/frontend/src/app/commonComponents/MultiplexResultsGuidance.tsx b/frontend/src/app/commonComponents/MultiplexResultsGuidance.tsx index 70e1b42394..71374cbe04 100644 --- a/frontend/src/app/commonComponents/MultiplexResultsGuidance.tsx +++ b/frontend/src/app/commonComponents/MultiplexResultsGuidance.tsx @@ -6,6 +6,7 @@ import { hasPositiveFluResults, getResultByDiseaseName, hasCovidResults, + hasPositiveRsvResults, } from "../utils/testResults"; type PositiveFluResultInfoProps = { @@ -56,6 +57,41 @@ const PositiveFluResultInfo = ({ ); }; +type PositiveRsvResultInfoProps = { + needsHeading: boolean; + t: translateFn; +}; +const PositiveRsvResultInfo = ({ + needsHeading, + t, +}: PositiveRsvResultInfoProps) => { + return ( + <> + {needsHeading && ( +

+ {t("testResult.rsvNotes.h1")} +

+ )} +

{t("testResult.rsvNotes.positive.p0")}

+

{t("testResult.rsvNotes.positive.p1")}

+ + rsv symptoms link + , + ]} + /> + + ); +}; + interface MultiplexResultsGuidanceProps { results: MultiplexResult[]; isPatientApp: boolean; @@ -67,6 +103,7 @@ const MultiplexResultsGuidance: React.FC = ({ const { t } = useTranslation(); const needsCovidHeading = hasCovidResults(results); const needsFluGuidance = hasPositiveFluResults(results); + const needsRsvGuidance = hasPositiveRsvResults(results); const covidResult = getResultByDiseaseName(results, "COVID-19") as TestResult; return ( @@ -85,6 +122,9 @@ const MultiplexResultsGuidance: React.FC = ({ {needsFluGuidance && ( )} + {needsRsvGuidance && ( + + )} ); }; diff --git a/frontend/src/app/testResults/viewResults/actionMenuModals/TestResultPrintModal.test.tsx b/frontend/src/app/testResults/viewResults/actionMenuModals/TestResultPrintModal.test.tsx index cc07692214..48a512c9e3 100644 --- a/frontend/src/app/testResults/viewResults/actionMenuModals/TestResultPrintModal.test.tsx +++ b/frontend/src/app/testResults/viewResults/actionMenuModals/TestResultPrintModal.test.tsx @@ -92,7 +92,7 @@ describe("TestResultPrintModal with only COVID results", () => { it("should render only COVID information", () => { renderWithUser(); - expect(screen.getByText("Test result: COVID-19")).toBeInTheDocument(); + expect(screen.getByText("For COVID-19:")).toBeInTheDocument(); }); it("matches screenshot", () => { @@ -136,9 +136,7 @@ describe("TestResultPrintModal with multiplex results in SimpleReport App", () = }); it("should render flu information", () => { - expect( - screen.getByText("Test results: COVID-19 and flu") - ).toBeInTheDocument(); + expect(screen.getByText("Test results")).toBeInTheDocument(); expect(screen.getByText("For flu A and B:")).toBeInTheDocument(); }); @@ -185,9 +183,7 @@ describe("TestResultPrintModal with multiplex results in Pxp App", () => { }); it("should render information", () => { - expect( - screen.getByText("Test results: COVID-19 and flu") - ).toBeInTheDocument(); + expect(screen.getByText("Test results")).toBeInTheDocument(); expect(screen.getByText("fake npi for pxp")).toBeInTheDocument(); expect(screen.getAllByText("Negative").length).toBe(3); }); @@ -231,11 +227,8 @@ describe("TestResultPrintModal with RSV and flu results", () => { ); }); - // this should be changed when we correct our copy to the right header text (#7000) it("should render flu information", () => { - expect( - screen.getByText("Test results: COVID-19 and flu") - ).toBeInTheDocument(); + expect(screen.getByText("Test results")).toBeInTheDocument(); expect(screen.getByText("For flu A and B:")).toBeInTheDocument(); }); diff --git a/frontend/src/app/testResults/viewResults/actionMenuModals/TestResultPrintModal.tsx b/frontend/src/app/testResults/viewResults/actionMenuModals/TestResultPrintModal.tsx index f692190a16..b2737cedf9 100644 --- a/frontend/src/app/testResults/viewResults/actionMenuModals/TestResultPrintModal.tsx +++ b/frontend/src/app/testResults/viewResults/actionMenuModals/TestResultPrintModal.tsx @@ -15,8 +15,9 @@ import { displayFullName } from "../../../utils"; import { formatDateWithTimeOption } from "../../../utils/date"; import { hasCovidResults, - hasMultiplexResults, + hasMultipleResults, hasPositiveFluResults, + hasPositiveRsvResults, } from "../../../utils/testResults"; import { setLanguage } from "../../../utils/languages"; @@ -86,9 +87,9 @@ export const StaticTestResultModal = ({ >

- {hasMultiplexResults(results) - ? t("testResult.multiplexResultHeader") - : t("testResult.covidResultHeader")} + {hasMultipleResults(results) + ? t("testResult.multipleResultHeader") + : t("testResult.singleResultHeader")}

SimpleReport logo - {(hasCovidResults(results) || hasPositiveFluResults(results)) && ( + {(hasCovidResults(results) || + hasPositiveFluResults(results) || + hasPositiveRsvResults(results)) && (

{t("testResult.moreInformation")}

- Test results: COVID-19 and flu + Test results

SimpleReport logo (cdc.gov/flu/treatment/takingcare.htm).

+

+ For RSV: +

+

+ RSV usually causes mild, cold-like symptoms. Most people can recover at home, but RSV can cause serious illness and hospitalization for infants and older adults. You can help prevent the spread of RSV by staying at home when sick, avoiding close contact with others, and washing your hands frequently. +

+

+ You can take steps to relieve symptoms, including managing fever and pain with over-the-counter fever reducers, and drinking enough fluids. If your child has RSV, talk to their healthcare provider before giving them non-prescription cold medicine. +

+

+ Contact your healthcare professional if your symptoms worsen, you are having trouble breathing, or are dehydrated. + + Learn more about RSV symptoms and care on the CDC website. + +