From bd7d9b2142f2ddc94919657dea34ba1d574c05cb Mon Sep 17 00:00:00 2001 From: Jason Lin <98117700+JasonLin0991@users.noreply.github.com> Date: Wed, 18 Oct 2023 16:40:47 -0400 Subject: [PATCH 1/7] Rate dashboard, rate table, and rate summary tests (#1987) * Copy contractsWithSharedRateRevision when unlocking rate. * Remove duplicate mock function. * Add test for SingleRateSummarySection and rate data mock. * Remove hard coded ids. * Add mock rate GQL requests. * Add RateReviewDashboard tests. * Put it back because backend API uses this file. * Add rate table tests and move file location. * Add another test. * Fix jest warnings. --- .../ProgramSelect/ProgramSelect.test.tsx | 6 +- .../RateDetailsSummarySection.test.tsx | 182 +++++++++++---- .../SingleRateSummarySection.test.tsx | 197 +++++++++++++++++ .../RateReviewsDashboard.test.tsx | 58 +++++ .../RateReviewsDashboard.tsx | 2 +- .../RateReviewsTable.test.tsx | 125 +++++++++++ .../RateReviewsTable.tsx | 10 +- .../src/testHelpers/apolloMocks/index.ts | 4 + .../testHelpers/apolloMocks/rateDataMock.ts | 207 ++++++++++++++++++ .../testHelpers/apolloMocks/rateGQLMocks.ts | 51 +++++ 10 files changed, 789 insertions(+), 53 deletions(-) create mode 100644 services/app-web/src/components/SubmissionSummarySection/RateDetailsSummarySection/SingleRateSummarySection.test.tsx create mode 100644 services/app-web/src/pages/CMSDashboard/RateReviewsDashboard/RateReviewsDashboard.test.tsx create mode 100644 services/app-web/src/pages/CMSDashboard/RateReviewsDashboard/RateReviewsTable.test.tsx rename services/app-web/src/pages/CMSDashboard/{ => RateReviewsDashboard}/RateReviewsTable.tsx (98%) create mode 100644 services/app-web/src/testHelpers/apolloMocks/rateDataMock.ts create mode 100644 services/app-web/src/testHelpers/apolloMocks/rateGQLMocks.ts diff --git a/services/app-web/src/components/Select/ProgramSelect/ProgramSelect.test.tsx b/services/app-web/src/components/Select/ProgramSelect/ProgramSelect.test.tsx index 7ee3e52d72..d74633c368 100644 --- a/services/app-web/src/components/Select/ProgramSelect/ProgramSelect.test.tsx +++ b/services/app-web/src/components/Select/ProgramSelect/ProgramSelect.test.tsx @@ -1,11 +1,13 @@ import { renderWithProviders } from '../../../testHelpers/jestHelpers' import { ProgramSelect } from './ProgramSelect' -import { fetchCurrentUserMock } from '../../../testHelpers/apolloMocks' +import { + fetchCurrentUserMock, + mockMNState, +} from '../../../testHelpers/apolloMocks' import { screen, waitFor } from '@testing-library/react' import selectEvent from 'react-select-event' import userEvent from '@testing-library/user-event' import * as useStatePrograms from '../../../hooks/useStatePrograms' -import { mockMNState } from '../../../common-code/healthPlanFormDataMocks/healthPlanFormData' const mockOnChange = jest.fn() const mockSetValue = jest.fn() diff --git a/services/app-web/src/components/SubmissionSummarySection/RateDetailsSummarySection/RateDetailsSummarySection.test.tsx b/services/app-web/src/components/SubmissionSummarySection/RateDetailsSummarySection/RateDetailsSummarySection.test.tsx index 9483824f33..a0474913b7 100644 --- a/services/app-web/src/components/SubmissionSummarySection/RateDetailsSummarySection/RateDetailsSummarySection.test.tsx +++ b/services/app-web/src/components/SubmissionSummarySection/RateDetailsSummarySection/RateDetailsSummarySection.test.tsx @@ -3,6 +3,9 @@ import { mockContractAndRatesDraft, mockStateSubmission, mockMNState, + fetchCurrentUserMock, + mockValidCMSUser, + indexHealthPlanPackagesMockSuccess, } from '../../../testHelpers/apolloMocks' import { renderWithProviders } from '../../../testHelpers/jestHelpers' import * as usePreviousSubmission from '../../../hooks/usePreviousSubmission' @@ -77,6 +80,16 @@ describe('RateDetailsSummarySection', () => { }, ] + const apolloProvider = { + mocks: [ + fetchCurrentUserMock({ + statusCode: 200, + user: mockValidCMSUser(), + }), + indexHealthPlanPackagesMockSuccess(), + ], + } + afterEach(() => jest.clearAllMocks()) it('can render draft submission without errors', () => { @@ -87,7 +100,10 @@ describe('RateDetailsSummarySection', () => { navigateTo="rate-details" submissionName="MN-PMAP-0001" statePrograms={statePrograms} - /> + />, + { + apolloProvider, + } ) expect( @@ -108,7 +124,10 @@ describe('RateDetailsSummarySection', () => { submission={stateSubmission} submissionName="MN-MSHO-0003" statePrograms={statePrograms} - /> + />, + { + apolloProvider, + } ) expect( @@ -141,7 +160,10 @@ describe('RateDetailsSummarySection', () => { navigateTo="rate-details" submissionName="MN-PMAP-0001" statePrograms={statePrograms} - /> + />, + { + apolloProvider, + } ) expect( screen.getByRole('definition', { name: 'Rate certification type' }) @@ -168,21 +190,28 @@ describe('RateDetailsSummarySection', () => { ).toBeInTheDocument() }) - it('can render correct rate name for new rate submission', () => { + it('can render correct rate name for new rate submission', async () => { const submission = mockStateSubmission() submission.rateInfos[0].rateCertificationName = 'MCR-MN-0005-SNBC-RATE-20221013-20221013-CERTIFICATION-20221013' const statePrograms = mockMNState().programs - renderWithProviders( - - ) + await waitFor(() => { + renderWithProviders( + , + { + apolloProvider, + } + ) + }) const rateName = 'MCR-MN-0005-SNBC-RATE-20221013-20221013-CERTIFICATION-20221013' expect(screen.getByText(rateName)).toBeInTheDocument() @@ -212,7 +241,10 @@ describe('RateDetailsSummarySection', () => { navigateTo="rate-details" submissionName="MN-PMAP-0001" statePrograms={statePrograms} - /> + />, + { + apolloProvider, + } ) const rateName = @@ -221,18 +253,26 @@ describe('RateDetailsSummarySection', () => { expect(screen.getByText(rateName)).toBeInTheDocument() }) - it('can render all rate details fields for new rate certification submission', () => { + it('can render all rate details fields for new rate certification submission', async () => { const statePrograms = mockMNState().programs stateSubmission.rateInfos[0].rateCertificationName = 'MCR-MN-0005-SNBC-RATE-20221014-20221014-CERTIFICATION-20221014' - renderWithProviders( - - ) + + await waitFor(() => { + renderWithProviders( + , + { + apolloProvider, + } + ) + }) const rateName = 'MCR-MN-0005-SNBC-RATE-20221014-20221014-CERTIFICATION-20221014' @@ -289,6 +329,7 @@ describe('RateDetailsSummarySection', () => { }, ], } + renderWithProviders( { navigateTo="/rate-details'" submissionName="MN-PMAP-0001" statePrograms={statePrograms} - /> + />, + { + apolloProvider, + } ) await waitFor(() => { @@ -346,7 +390,10 @@ describe('RateDetailsSummarySection', () => { submission={draftSubmission} submissionName="MN-PMAP-0001" statePrograms={statePrograms} - /> + />, + { + apolloProvider, + } ) expect( @@ -356,15 +403,23 @@ describe('RateDetailsSummarySection', () => { ).toBeNull() }) - it('does not render download all button when on previous submission', () => { - renderWithProviders( - + it('does not render download all button when on previous submission', async () => { + await waitFor(() => + renderWithProviders( + , + { + apolloProvider, + } + ) ) + expect( screen.queryByRole('button', { name: 'Download all rate documents', @@ -380,7 +435,10 @@ describe('RateDetailsSummarySection', () => { navigateTo="rate-details" submissionName="MN-PMAP-0001" statePrograms={statePrograms} - /> + />, + { + apolloProvider, + } ) expect( screen.getByRole('definition', { @@ -404,7 +462,10 @@ describe('RateDetailsSummarySection', () => { navigateTo="rate-details" submissionName="MN-PMAP-0001" statePrograms={statePrograms} - /> + />, + { + apolloProvider, + } ) expect( screen.getByRole('definition', { @@ -431,7 +492,10 @@ describe('RateDetailsSummarySection', () => { navigateTo="rate-details" submissionName="MN-PMAP-0001" statePrograms={statePrograms} - /> + />, + { + apolloProvider, + } ) const programElement = screen.getByRole('definition', { name: 'Programs this rate certification covers', @@ -455,7 +519,10 @@ describe('RateDetailsSummarySection', () => { navigateTo="rate-details" submissionName="MN-PMAP-0001" statePrograms={statePrograms} - /> + />, + { + apolloProvider, + } ) const programElement = screen.getByRole('definition', { name: 'Programs this rate certification covers', @@ -475,7 +542,10 @@ describe('RateDetailsSummarySection', () => { navigateTo="rate-details" submissionName="MN-PMAP-0001" statePrograms={statePrograms} - /> + />, + { + apolloProvider, + } ) const programList = screen.getAllByRole('definition', { name: 'Programs this rate certification covers', @@ -495,7 +565,10 @@ describe('RateDetailsSummarySection', () => { navigateTo="rate-details" submissionName="MN-PMAP-0001" statePrograms={statePrograms} - /> + />, + { + apolloProvider, + } ) const certType = screen.getAllByRole('definition', { name: 'Rate certification type', @@ -515,7 +588,10 @@ describe('RateDetailsSummarySection', () => { navigateTo="rate-details" submissionName="MN-PMAP-0001" statePrograms={statePrograms} - /> + />, + { + apolloProvider, + } ) await waitFor(() => { const rateDocsTables = screen.getAllByRole('table', { @@ -545,7 +621,10 @@ describe('RateDetailsSummarySection', () => { navigateTo="rate-details" submissionName="MN-PMAP-0001" statePrograms={statePrograms} - /> + />, + { + apolloProvider, + } ) await waitFor(() => { const certifyingActuary = screen.getAllByRole('definition', { @@ -621,7 +700,10 @@ describe('RateDetailsSummarySection', () => { navigateTo="rate-details" submissionName="MN-PMAP-0001" statePrograms={statePrograms} - /> + />, + { + apolloProvider, + } ) await waitFor(() => { const rateDocsTable = screen.getByRole('table', { @@ -722,7 +804,10 @@ describe('RateDetailsSummarySection', () => { navigateTo="rate-details" submissionName="MN-PMAP-0001" statePrograms={statePrograms} - /> + />, + { + apolloProvider, + } ) await waitFor(() => { const rateDocsTable = screen.getByRole('table', { @@ -789,7 +874,10 @@ describe('RateDetailsSummarySection', () => { navigateTo="rate-details" submissionName="MN-PMAP-0001" statePrograms={statePrograms} - /> + />, + { + apolloProvider, + } ) await waitFor(() => { const rateDocsTable = screen.getByRole('table', { @@ -812,7 +900,10 @@ describe('RateDetailsSummarySection', () => { navigateTo="rate-details" submissionName="MN-PMAP-0001" statePrograms={statePrograms} - /> + />, + { + apolloProvider, + } ) expect(screen.queryByRole('link', { name: 'Edit' })).toBeNull() @@ -841,6 +932,7 @@ describe('RateDetailsSummarySection', () => { />, { s3Provider, + apolloProvider, } ) diff --git a/services/app-web/src/components/SubmissionSummarySection/RateDetailsSummarySection/SingleRateSummarySection.test.tsx b/services/app-web/src/components/SubmissionSummarySection/RateDetailsSummarySection/SingleRateSummarySection.test.tsx new file mode 100644 index 0000000000..d71a7b1e54 --- /dev/null +++ b/services/app-web/src/components/SubmissionSummarySection/RateDetailsSummarySection/SingleRateSummarySection.test.tsx @@ -0,0 +1,197 @@ +import { renderWithProviders } from '../../../testHelpers/jestHelpers' +import { SingleRateSummarySection } from './SingleRateSummarySection' +import { + fetchCurrentUserMock, + mockValidCMSUser, + rateDataMock, +} from '../../../testHelpers/apolloMocks' +import { screen, waitFor, within } from '@testing-library/react' +import { packageName } from '../../../common-code/healthPlanFormDataType' + +describe('SingleRateSummarySection', () => { + it('can render rate details without errors', async () => { + const rateData = rateDataMock() + await waitFor(() => { + renderWithProviders( + , + { + apolloProvider: { + mocks: [ + fetchCurrentUserMock({ + statusCode: 200, + user: mockValidCMSUser(), + }), + ], + }, + } + ) + }) + + const rateName = rateData.revisions[0].formData + .rateCertificationName as string + + expect(screen.getByText(rateName)).toBeInTheDocument() + expect( + screen.getByRole('definition', { + name: 'Programs this rate certification covers', + }) + ).toBeInTheDocument() + expect( + screen.getByRole('definition', { name: 'Rate certification type' }) + ).toBeInTheDocument() + expect( + screen.getByRole('definition', { + name: 'Rating period of original rate certification', + }) + ).toBeInTheDocument() + expect( + screen.getByRole('definition', { + name: 'Date certified for rate amendment', + }) + ).toBeInTheDocument() + expect( + screen.getByRole('definition', { + name: 'Rate amendment effective dates', + }) + ).toBeInTheDocument() + expect( + screen.getByRole('definition', { name: 'Certifying actuary' }) + ).toBeInTheDocument() + expect( + screen.getByRole('definition', { + name: 'Does the actuary certify capitation rates specific to each rate cell or a rate range?', + }) + ).toBeInTheDocument() + expect( + screen.getByRole('definition', { + name: 'Submission this rate was submitted with', + }) + ).toBeInTheDocument() + + expect( + screen.getByRole('heading', { name: 'Rate documents' }) + ).toBeInTheDocument() + }) + it('renders documents with linked submissions correctly', async () => { + const rateData = rateDataMock() + const parentContractRev = rateData.revisions[0].contractRevisions[0] + const rateDoc = rateData.revisions[0].formData.rateDocuments[0] + const supportingDoc = + rateData.revisions[0].formData.supportingDocuments[0] + const linkedSubmissionOne = + rateData.revisions[0].formData.packagesWithSharedRateCerts[0] + const linkedSubmissionTwo = + rateData.revisions[0].formData.packagesWithSharedRateCerts[1] + + const contractPackageName = packageName( + parentContractRev.contract.stateCode, + parentContractRev.contract.stateNumber, + parentContractRev.formData.programIDs, + rateData.state.programs + ) + + await waitFor(() => { + renderWithProviders( + , + { + apolloProvider: { + mocks: [ + fetchCurrentUserMock({ + statusCode: 200, + user: mockValidCMSUser(), + }), + ], + }, + } + ) + }) + + expect( + screen.getByRole('heading', { name: 'Rate documents' }) + ).toBeInTheDocument() + + const rateDocsTable = screen.getByRole('table', { + name: /Rate certification/, + }) + const supportingDocsTable = screen.getByRole('table', { + name: /Rate supporting documents/, + }) + + // Wait for all the documents to be in the table + await waitFor(() => { + expect( + screen.getByRole('link', { + name: 'Download all rate documents', + }) + ).toBeInTheDocument() + expect(rateDocsTable).toBeInTheDocument() + expect(supportingDocsTable).toBeInTheDocument() + }) + + const parentContractSubmission = screen.getByRole('definition', { + name: 'Submission this rate was submitted with', + }) + + // Expect submissions this rate was submitted with link to exists + expect(parentContractSubmission).toBeInTheDocument() + expect( + within(parentContractSubmission).getByRole('link', { + name: contractPackageName, + }) + ).toBeInTheDocument() + expect( + within(parentContractSubmission).getByRole('link', { + name: contractPackageName, + }) + ).toHaveAttribute( + 'href', + `/submissions/${parentContractRev.contract.id}` + ) + + // Expect rate certification document and linked submissions + expect( + within(rateDocsTable).getByText(rateDoc.name) + ).toBeInTheDocument() + expect( + within(within(rateDocsTable).getByTestId('tag')).getByText('SHARED') + ).toBeInTheDocument() + expect( + within(rateDocsTable).getByText( + `${linkedSubmissionOne.packageName} (Draft)` + ) + ).toBeInTheDocument() + expect( + within(rateDocsTable).getByText( + `${linkedSubmissionTwo.packageName}` + ) + ).toBeInTheDocument() + + // Expect supporting document and linked submissions + expect( + within(supportingDocsTable).getByText(supportingDoc.name) + ).toBeInTheDocument() + expect( + within(within(supportingDocsTable).getByTestId('tag')).getByText( + 'SHARED' + ) + ).toBeInTheDocument() + expect( + within(supportingDocsTable).getByText( + `${linkedSubmissionOne.packageName} (Draft)` + ) + ).toBeInTheDocument() + expect( + within(supportingDocsTable).getByText( + `${linkedSubmissionTwo.packageName}` + ) + ).toBeInTheDocument() + }) +}) diff --git a/services/app-web/src/pages/CMSDashboard/RateReviewsDashboard/RateReviewsDashboard.test.tsx b/services/app-web/src/pages/CMSDashboard/RateReviewsDashboard/RateReviewsDashboard.test.tsx new file mode 100644 index 0000000000..f4ca865618 --- /dev/null +++ b/services/app-web/src/pages/CMSDashboard/RateReviewsDashboard/RateReviewsDashboard.test.tsx @@ -0,0 +1,58 @@ +import { ldUseClientSpy, renderWithProviders } from '../../../testHelpers' +import { RateReviewsDashboard } from './RateReviewsDashboard' +import { + fetchCurrentUserMock, + indexRatesMockFailure, + mockValidCMSUser, +} from '../../../testHelpers/apolloMocks' +import { indexRatesMockSuccess } from '../../../testHelpers/apolloMocks' +import { screen, waitFor } from '@testing-library/react' + +describe('RateReviewsDashboard', () => { + it('renders dashboard with rates correctly', async () => { + ldUseClientSpy({ + 'rate-reviews-dashboard': true, + 'rate-filters': true, + }) + renderWithProviders(, { + apolloProvider: { + mocks: [ + fetchCurrentUserMock({ + statusCode: 200, + user: mockValidCMSUser(), + }), + indexRatesMockSuccess(), + ], + }, + }) + + // Wait for accordion and table components to load on page + await waitFor(() => { + expect(screen.queryByTestId('accordion')).toBeInTheDocument() + expect(screen.queryByTestId('table')).toBeInTheDocument() + }) + + // Expect 3 rates to be displayed + expect(screen.getByText('Displaying 3 of 3 rates')).toBeInTheDocument() + }) + + it('renders error failed request page', async () => { + renderWithProviders(, { + apolloProvider: { + mocks: [ + fetchCurrentUserMock({ + statusCode: 200, + user: mockValidCMSUser(), + }), + indexRatesMockFailure(), + ], + }, + }) + + await waitFor(() => { + expect( + screen.queryByText("We're having trouble loading this page.") + ).toBeInTheDocument() + }) + }) +}) diff --git a/services/app-web/src/pages/CMSDashboard/RateReviewsDashboard/RateReviewsDashboard.tsx b/services/app-web/src/pages/CMSDashboard/RateReviewsDashboard/RateReviewsDashboard.tsx index 2172acb3d7..2fb0028afe 100644 --- a/services/app-web/src/pages/CMSDashboard/RateReviewsDashboard/RateReviewsDashboard.tsx +++ b/services/app-web/src/pages/CMSDashboard/RateReviewsDashboard/RateReviewsDashboard.tsx @@ -6,7 +6,7 @@ import styles from '../../StateDashboard/StateDashboard.module.scss' import { recordJSException } from '../../../otelHelpers/tracingHelper' import { Loading } from '../../../components' -import { RateInDashboardType, RateReviewsTable } from '../RateReviewsTable' +import { RateInDashboardType, RateReviewsTable } from './RateReviewsTable' import { useLDClient } from 'launchdarkly-react-client-sdk' import { featureFlags } from '../../../common-code/featureFlags' import { ErrorFailedRequestPage } from '../../Errors/ErrorFailedRequestPage' diff --git a/services/app-web/src/pages/CMSDashboard/RateReviewsDashboard/RateReviewsTable.test.tsx b/services/app-web/src/pages/CMSDashboard/RateReviewsDashboard/RateReviewsTable.test.tsx new file mode 100644 index 0000000000..82a3044563 --- /dev/null +++ b/services/app-web/src/pages/CMSDashboard/RateReviewsDashboard/RateReviewsTable.test.tsx @@ -0,0 +1,125 @@ +import { renderWithProviders } from '../../../testHelpers' +import { RateInDashboardType } from './RateReviewsTable' +import { + fetchCurrentUserMock, + mockMNState, + mockValidCMSUser, +} from '../../../testHelpers/apolloMocks' +import { RateReviewsTable } from './RateReviewsTable' +import { waitFor, screen, within } from '@testing-library/react' + +describe('RateReviewsTable', () => { + const statePrograms = mockMNState().programs + const tableData = (): RateInDashboardType[] => [ + { + id: 'rate-1-id', + name: 'rate-1-certification-name', + programs: [statePrograms[0]], + submittedAt: '2023-10-16', + rateDateStart: new Date('2023-10-16'), + rateDateEnd: new Date('2024-10-16'), + status: 'SUBMITTED', + updatedAt: new Date('2023-10-16'), + rateType: 'NEW', + stateName: 'Minnesota', + contractRevisions: [], + }, + { + id: 'rate-2-id', + name: 'rate-2-certification-name', + programs: [statePrograms[0]], + submittedAt: '2023-11-18', + rateDateStart: new Date('2023-11-18'), + rateDateEnd: new Date('2024-11-18'), + status: 'SUBMITTED', + updatedAt: new Date('2023-11-18'), + rateType: 'AMENDMENT', + stateName: 'Minnesota', + contractRevisions: [], + }, + { + id: 'rate-3-id', + name: 'rate-3-certification-name', + programs: [statePrograms[0]], + submittedAt: '2023-12-01', + rateDateStart: new Date('2023-12-01'), + rateDateEnd: new Date('2024-12-01'), + status: 'UNLOCKED', + updatedAt: new Date('2023-12-01'), + rateType: 'NEW', + stateName: 'Minnesota', + contractRevisions: [], + }, + ] + it('renders rates table correctly', async () => { + renderWithProviders( + , + { + apolloProvider: { + mocks: [ + fetchCurrentUserMock({ + statusCode: 200, + user: mockValidCMSUser(), + }), + ], + }, + } + ) + + await waitFor(() => { + expect( + screen.queryByText('Displaying 3 of 3 rates') + ).toBeInTheDocument() + }) + // expect filter accordion to be present + expect(screen.getByTestId('accordion')).toBeInTheDocument() + expect(screen.getByText('0 filters applied')).toBeInTheDocument() + + // expect table caption + expect(screen.getByText('Test table caption')).toBeInTheDocument() + + const tableRows = screen.getAllByRole('row') + + // expect there to be 4 rows, one of which is the header row + expect(tableRows).toHaveLength(4) + + // expect rows to be in order where latest updatedAt is first in the array + expect( + within(tableRows[1]).getByText('rate-3-certification-name') + ).toBeInTheDocument() + expect( + within(tableRows[2]).getByText('rate-2-certification-name') + ).toBeInTheDocument() + expect( + within(tableRows[3]).getByText('rate-1-certification-name') + ).toBeInTheDocument() + }) + it('renders rates table correctly without filters, captions and no rates', async () => { + renderWithProviders(, { + apolloProvider: { + mocks: [ + fetchCurrentUserMock({ + statusCode: 200, + user: mockValidCMSUser(), + }), + ], + }, + }) + + // expect no filter accordion to be present + expect(screen.queryByTestId('accordion')).not.toBeInTheDocument() + expect(screen.queryByText('0 filters applied')).not.toBeInTheDocument() + + // expect no table + expect(screen.queryByRole('table')).not.toBeInTheDocument() + + // expect now rate review text + expect( + screen.getByText('You have no rate reviews yet') + ).toBeInTheDocument() + }) +}) diff --git a/services/app-web/src/pages/CMSDashboard/RateReviewsTable.tsx b/services/app-web/src/pages/CMSDashboard/RateReviewsDashboard/RateReviewsTable.tsx similarity index 98% rename from services/app-web/src/pages/CMSDashboard/RateReviewsTable.tsx rename to services/app-web/src/pages/CMSDashboard/RateReviewsDashboard/RateReviewsTable.tsx index 3546b1092b..a4d403b2ab 100644 --- a/services/app-web/src/pages/CMSDashboard/RateReviewsTable.tsx +++ b/services/app-web/src/pages/CMSDashboard/RateReviewsDashboard/RateReviewsTable.tsx @@ -18,8 +18,8 @@ import { Program, RelatedContractRevisions, RateType, -} from '../../gen/gqlClient' -import styles from '../../components/HealthPlanPackageTable/HealthPlanPackageTable.module.scss' +} from '../../../gen/gqlClient' +import styles from '../../../components/HealthPlanPackageTable/HealthPlanPackageTable.module.scss' import { Table, Tag, Link } from '@trussworks/react-uswds' import { NavLink } from 'react-router-dom' import dayjs from 'dayjs' @@ -29,9 +29,9 @@ import { FilterSelect, FilterSelectedOptionsType, FilterOptionType, -} from '../../components/FilterAccordion' -import { pluralize } from '../../common-code/formatters' -import { RateTypeRecord } from '../../constants/healthPlanPackages' +} from '../../../components/FilterAccordion' +import { pluralize } from '../../../common-code/formatters' +import { RateTypeRecord } from '../../../constants/healthPlanPackages' declare module '@tanstack/table-core' { // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/services/app-web/src/testHelpers/apolloMocks/index.ts b/services/app-web/src/testHelpers/apolloMocks/index.ts index 5569815c41..c0d3439027 100644 --- a/services/app-web/src/testHelpers/apolloMocks/index.ts +++ b/services/app-web/src/testHelpers/apolloMocks/index.ts @@ -50,4 +50,8 @@ export { mockQuestionsPayload } from './questionResponseDataMocks' export { fetchEmailSettings } from './emailGQLMock' export { mockMNState } from './stateMock' +export { rateDataMock } from './rateDataMock' + +export { indexRatesMockSuccess, indexRatesMockFailure } from './rateGQLMocks' + export { updateUserMockError, updateUserMockSuccess } from './updateUserMock' diff --git a/services/app-web/src/testHelpers/apolloMocks/rateDataMock.ts b/services/app-web/src/testHelpers/apolloMocks/rateDataMock.ts new file mode 100644 index 0000000000..75fc6b5065 --- /dev/null +++ b/services/app-web/src/testHelpers/apolloMocks/rateDataMock.ts @@ -0,0 +1,207 @@ +import { + Rate, + RateRevision, + RelatedContractRevisions, +} from '../../gen/gqlClient' +import { mockMNState } from './stateMock' +import { v4 as uuidv4 } from 'uuid' + +const contractRevisionOnRateDataMock = ( + data?: Partial +): RelatedContractRevisions => ({ + __typename: 'RelatedContractRevisions', + id: uuidv4(), + contract: { + __typename: 'ContractOnRevisionType', + id: uuidv4(), + stateCode: 'MN', + stateNumber: 3, + }, + createdAt: '2023-10-16T18:52:16.295Z', + updatedAt: '2023-10-16T19:02:26.795Z', + submitInfo: { + __typename: 'UpdateInformation', + updatedAt: '2023-10-16T19:02:26.795Z', + updatedBy: 'aang@example.com', + updatedReason: 'Initial submission', + }, + unlockInfo: null, + formData: { + __typename: 'ContractFormData', + programIDs: ['d95394e5-44d1-45df-8151-1cc1ee66f100'], + populationCovered: 'MEDICAID', + submissionType: 'CONTRACT_AND_RATES', + riskBasedContract: false, + submissionDescription: 'description of contract and rates submission', + stateContacts: [ + { + __typename: 'StateContact', + name: 'Name', + title: null, + email: 'example@example.com', + }, + ], + supportingDocuments: [], + contractType: 'BASE', + contractExecutionStatus: 'EXECUTED', + contractDocuments: [ + { + __typename: 'GenericDocument', + name: 'contract-document.pdf', + s3URL: 's3://bucketname/key/contract-document', + sha256: 'fakeSha', + }, + ], + contractDateStart: '2024-04-01', + contractDateEnd: '2025-03-31', + managedCareEntities: ['MCO'], + federalAuthorities: ['STATE_PLAN'], + inLieuServicesAndSettings: false, + modifiedBenefitsProvided: null, + modifiedGeoAreaServed: null, + modifiedMedicaidBeneficiaries: null, + modifiedRiskSharingStrategy: true, + modifiedIncentiveArrangements: true, + modifiedWitholdAgreements: false, + modifiedStateDirectedPayments: false, + modifiedPassThroughPayments: false, + modifiedPaymentsForMentalDiseaseInstitutions: true, + modifiedMedicalLossRatioStandards: null, + modifiedOtherFinancialPaymentIncentive: null, + modifiedEnrollmentProcess: null, + modifiedGrevienceAndAppeal: null, + modifiedNetworkAdequacyStandards: null, + modifiedLengthOfContract: null, + modifiedNonRiskPaymentArrangements: true, + }, + ...data, +}) +const rateRevisionDataMock = (data?: Partial): RateRevision => { + return { + __typename: 'RateRevision', + id: uuidv4(), + createdAt: '2023-10-16T19:01:21.389Z', + updatedAt: '2023-10-16T19:02:26.767Z', + unlockInfo: null, + submitInfo: { + __typename: 'UpdateInformation', + updatedAt: '2023-10-16T19:02:26.766Z', + updatedBy: 'aang@example.com', + updatedReason: 'Initial submission', + }, + formData: { + __typename: 'RateFormData', + rateType: 'AMENDMENT', + rateCapitationType: 'RATE_CELL', + rateDocuments: [ + { + __typename: 'GenericDocument', + name: 'rate-document.pdf', + s3URL: 's3://bucketname/key/rate-document', + sha256: 'fakeSha', + }, + ], + supportingDocuments: [ + { + __typename: 'GenericDocument', + name: 'rate-supporting-document.pdf', + s3URL: 's3://bucketname/key/rate-supporting-document', + sha256: 'fakeSha', + }, + ], + rateDateStart: '2023-02-01', + rateDateEnd: '2025-03-01', + rateDateCertified: '2024-03-01', + amendmentEffectiveDateStart: '2024-03-01', + amendmentEffectiveDateEnd: '2025-03-01', + rateProgramIDs: ['d95394e5-44d1-45df-8151-1cc1ee66f100'], + rateCertificationName: + 'MCR-MN-0003-PMAP-RATE-20240301-20250301-AMENDMENT-20240301', + certifyingActuaryContacts: [ + { + __typename: 'ActuaryContact', + name: 'Actuary Contact Person', + titleRole: 'Actuary Contact Title', + email: 'actuarycontact@example.com', + actuarialFirm: 'MERCER', + actuarialFirmOther: '', + }, + ], + addtlActuaryContacts: [ + { + __typename: 'ActuaryContact', + name: 'Additional actuary name', + titleRole: 'Additional actuary title', + email: 'additonalactuary@example.com', + actuarialFirm: 'MILLIMAN', + actuarialFirmOther: '', + }, + ], + actuaryCommunicationPreference: 'OACT_TO_ACTUARY', + packagesWithSharedRateCerts: [ + { + __typename: 'PackageWithSameRate', + packageName: 'MCR-MN-0001-PMAP', + packageId: 'f3306599-a1c9-411a-87ac-fa3541c3e723', + packageStatus: 'DRAFT', + }, + { + __typename: 'PackageWithSameRate', + packageName: 'MCR-MN-0002-PMAP', + packageId: '1bf66cff-512b-4a30-bea8-5e27d1764810', + packageStatus: 'SUBMITTED', + }, + ], + }, + contractRevisions: [contractRevisionOnRateDataMock()], + ...data, + } +} + +const rateDataMock = (rateData?: Partial): Rate => ({ + __typename: 'Rate', + id: uuidv4(), + createdAt: '2023-10-16T19:01:21.389Z', + updatedAt: '2023-10-16T19:01:21.389Z', + stateCode: 'MN', + stateNumber: 10, + state: mockMNState(), + status: 'RESUBMITTED', + initiallySubmittedAt: '2023-10-16', + draftRevision: null, + revisions: [ + rateRevisionDataMock({ + unlockInfo: { + __typename: 'UpdateInformation', + updatedAt: '2023-10-16T19:05:26.585Z', + updatedBy: 'zuko@example.com', + updatedReason: 'Unlock', + }, + submitInfo: { + __typename: 'UpdateInformation', + updatedAt: '2023-10-16T19:06:20.581Z', + updatedBy: 'aang@example.com', + updatedReason: 'Resubmit', + }, + contractRevisions: [ + contractRevisionOnRateDataMock({ + submitInfo: { + __typename: 'UpdateInformation', + updatedAt: '2023-10-16T19:06:20.643Z', + updatedBy: 'aang@example.com', + updatedReason: 'Resubmit', + }, + unlockInfo: { + __typename: 'UpdateInformation', + updatedAt: '2023-10-16T19:05:26.660Z', + updatedBy: 'zuko@example.com', + updatedReason: 'Unlock', + }, + }), + ], + }), + rateRevisionDataMock(), + ], +}) + +export { rateDataMock, rateRevisionDataMock } diff --git a/services/app-web/src/testHelpers/apolloMocks/rateGQLMocks.ts b/services/app-web/src/testHelpers/apolloMocks/rateGQLMocks.ts new file mode 100644 index 0000000000..300cbcdc06 --- /dev/null +++ b/services/app-web/src/testHelpers/apolloMocks/rateGQLMocks.ts @@ -0,0 +1,51 @@ +import { IndexRatesDocument, IndexRatesQuery, Rate } from '../../gen/gqlClient' +import { MockedResponse } from '@apollo/client/testing' +import { rateDataMock } from './rateDataMock' +import { GraphQLError } from 'graphql/index' + +const indexRatesMockSuccess = ( + rates: Rate[] = [ + { ...rateDataMock(), id: 'test-id-123', stateNumber: 3 }, + { ...rateDataMock(), id: 'test-id-123', stateNumber: 2 }, + { ...rateDataMock(), id: 'test-id-124', stateNumber: 1 }, + ] +): MockedResponse => { + const ratesEdge = rates.map((rate) => { + return { + node: rate, + } + }) + return { + request: { + query: IndexRatesDocument, + }, + result: { + data: { + indexRates: { + totalCount: ratesEdge.length, + edges: ratesEdge, + }, + }, + }, + } +} + +const indexRatesMockFailure = (): MockedResponse => { + const graphQLError = new GraphQLError('Issue finding rates with history', { + extensions: { + code: 'NOT_FOUND', + cause: 'DB_ERROR', + }, + }) + return { + request: { + query: IndexRatesDocument, + }, + result: { + data: null, + errors: [graphQLError], + }, + } +} + +export { indexRatesMockSuccess, indexRatesMockFailure } From 2da429abcf44a2d897191cda05cbc27cef7ea4fc Mon Sep 17 00:00:00 2001 From: MacRae Linton <55759+macrael@users.noreply.github.com> Date: Wed, 18 Oct 2023 15:18:23 -0700 Subject: [PATCH 2/7] get stricter about rateinfo ids (#1992) --- .../app-api/src/handlers/proto_to_db.test.ts | 5 +- .../proto_to_db_CleanupLastMigration.ts | 1 - .../submitHealthPlanPackage.test.ts | 3 + .../unlockHealthPlanPackage.test.ts | 3 + .../updateHealthPlanFormData.test.ts | 166 ++++++++++++++++++ .../updateHealthPlanFormData.ts | 12 ++ .../app-api/src/testHelpers/gqlHelpers.ts | 2 + .../healthPlanFormDataEncoding.test.ts | 14 -- .../healthPlanFormDataProto/toProtoBuffer.ts | 3 +- .../RateDetails/RateDetails.tsx | 1 + 10 files changed, 192 insertions(+), 18 deletions(-) diff --git a/services/app-api/src/handlers/proto_to_db.test.ts b/services/app-api/src/handlers/proto_to_db.test.ts index 9413ad2428..d1ffbe7ca1 100644 --- a/services/app-api/src/handlers/proto_to_db.test.ts +++ b/services/app-api/src/handlers/proto_to_db.test.ts @@ -10,6 +10,7 @@ import { unlockTestHealthPlanPackage, updateTestHealthPlanFormData, } from '../testHelpers/gqlHelpers' +import { v4 as uuidv4 } from 'uuid' import { latestFormData } from '../testHelpers/healthPlanPackageHelpers' import { testLDService } from '../testHelpers/launchDarklyHelpers' import { testCMSUser } from '../testHelpers/userHelpers' @@ -33,7 +34,7 @@ import type { LockedHealthPlanFormDataType, } from '../../../app-web/src/common-code/healthPlanFormDataType' -describe('test that we migrate things', () => { +describe.skip('test that we migrate things', () => { const mockPreRefactorLDService = testLDService({ 'rates-db-refactor': false, }) @@ -99,6 +100,7 @@ describe('test that we migrate things', () => { formData.rateInfos.push( { + id: uuidv4(), rateDateStart: new Date(), rateDateEnd: new Date(), rateProgramIDs: ['5c10fe9f-bec9-416f-a20c-718b152ad633'], @@ -123,6 +125,7 @@ describe('test that we migrate things', () => { ], }, { + id: uuidv4(), rateDateStart: new Date(), rateDateEnd: new Date(), rateProgramIDs: ['5c10fe9f-bec9-416f-a20c-718b152ad633'], diff --git a/services/app-api/src/postgres/contractAndRates/proto_to_db_CleanupLastMigration.ts b/services/app-api/src/postgres/contractAndRates/proto_to_db_CleanupLastMigration.ts index 4056226750..99c1de2c82 100644 --- a/services/app-api/src/postgres/contractAndRates/proto_to_db_CleanupLastMigration.ts +++ b/services/app-api/src/postgres/contractAndRates/proto_to_db_CleanupLastMigration.ts @@ -18,7 +18,6 @@ export async function cleanupLastMigration( client.rateRevisionsOnContractRevisionsTable.deleteMany(), client.contractRevisionTable.deleteMany(), client.rateRevisionTable.deleteMany(), - client.rateRevisionsOnContractRevisionsTable.deleteMany(), client.updateInfoTable.deleteMany(), // must be last due to foreign keys diff --git a/services/app-api/src/resolvers/healthPlanPackage/submitHealthPlanPackage.test.ts b/services/app-api/src/resolvers/healthPlanPackage/submitHealthPlanPackage.test.ts index d2a8e154a2..3be2fe0ed0 100644 --- a/services/app-api/src/resolvers/healthPlanPackage/submitHealthPlanPackage.test.ts +++ b/services/app-api/src/resolvers/healthPlanPackage/submitHealthPlanPackage.test.ts @@ -11,6 +11,7 @@ import { defaultFloridaRateProgram, submitTestHealthPlanPackage, } from '../../testHelpers/gqlHelpers' +import { v4 as uuidv4 } from 'uuid' import { testEmailConfig, testEmailer } from '../../testHelpers/emailerHelpers' import { base64ToDomain } from '../../../../app-web/src/common-code/proto/healthPlanFormDataProto' import { @@ -242,6 +243,7 @@ describe.each(flagValueTestParameters)( submissionType: 'CONTRACT_AND_RATES', rateInfos: [ { + id: uuidv4(), rateType: 'NEW' as const, rateDateStart: new Date(Date.UTC(2025, 5, 1)), rateDateEnd: new Date(Date.UTC(2026, 4, 30)), @@ -909,6 +911,7 @@ describe.each(flagValueTestParameters)( submissionType: 'CONTRACT_AND_RATES', rateInfos: [ { + id: uuidv4(), rateDateStart: new Date(Date.UTC(2025, 5, 1)), rateDateEnd: new Date(Date.UTC(2026, 4, 30)), rateDateCertified: undefined, diff --git a/services/app-api/src/resolvers/healthPlanPackage/unlockHealthPlanPackage.test.ts b/services/app-api/src/resolvers/healthPlanPackage/unlockHealthPlanPackage.test.ts index ed3d7884f4..8b30e08320 100644 --- a/services/app-api/src/resolvers/healthPlanPackage/unlockHealthPlanPackage.test.ts +++ b/services/app-api/src/resolvers/healthPlanPackage/unlockHealthPlanPackage.test.ts @@ -1,5 +1,6 @@ import type { GraphQLError } from 'graphql' import UNLOCK_HEALTH_PLAN_PACKAGE from '../../../../app-graphql/src/mutations/unlockHealthPlanPackage.graphql' +import { v4 as uuidv4 } from 'uuid' import type { HealthPlanPackage, HealthPlanRevisionEdge, @@ -259,6 +260,7 @@ describe.each(flagValueTestParameters)( formData.rateInfos.push( { + id: uuidv4(), rateDateStart: new Date(), rateDateEnd: new Date(), rateProgramIDs: ['5c10fe9f-bec9-416f-a20c-718b152ad633'], @@ -313,6 +315,7 @@ describe.each(flagValueTestParameters)( ], }, { + id: uuidv4(), rateDateStart: new Date(), rateDateEnd: new Date(), rateProgramIDs: ['08d114c2-0c01-4a1a-b8ff-e2b79336672d'], diff --git a/services/app-api/src/resolvers/healthPlanPackage/updateHealthPlanFormData.test.ts b/services/app-api/src/resolvers/healthPlanPackage/updateHealthPlanFormData.test.ts index c39c52967d..b957e1cf29 100644 --- a/services/app-api/src/resolvers/healthPlanPackage/updateHealthPlanFormData.test.ts +++ b/services/app-api/src/resolvers/healthPlanPackage/updateHealthPlanFormData.test.ts @@ -191,6 +191,7 @@ describe.each(flagValueTestParameters)( // Create 2 rate data for insertion const rate1 = { + id: uuidv4(), rateType: 'NEW' as const, rateDateStart: new Date(Date.UTC(2025, 5, 1)), rateDateEnd: new Date(Date.UTC(2026, 4, 30)), @@ -225,6 +226,7 @@ describe.each(flagValueTestParameters)( } const rate2 = { + id: uuidv4(), rateType: 'NEW' as const, rateDateStart: new Date(Date.UTC(2025, 5, 1)), rateDateEnd: new Date(Date.UTC(2026, 4, 30)), @@ -334,6 +336,7 @@ describe.each(flagValueTestParameters)( ) const rate3 = { + id: uuidv4(), rateType: 'AMENDMENT' as const, rateDateStart: new Date(Date.UTC(2025, 5, 1)), rateDateEnd: new Date(Date.UTC(2026, 4, 30)), @@ -418,6 +421,169 @@ describe.each(flagValueTestParameters)( ) }) + it('errors on a rate with no ID.', async () => { + const stateUser = { + id: uuidv4(), + givenName: 'Aang', + familyName: 'Avatar', + email: 'aang@example.com', + role: 'STATE_USER' as const, + stateCode: 'MN', + } + const server = await constructTestPostgresServer({ + ldService: mockLDService, + context: { + user: stateUser, + }, + }) + + const stateCode = 'MN' + const createdDraft = await createTestHealthPlanPackage( + server, + stateCode + ) + const statePrograms = must( + findStatePrograms(createdDraft.stateCode) + ) + + // Create 2 valid contracts to attached to packagesWithSharedRateCerts + const createdDraftTwo = await createTestHealthPlanPackage( + server, + stateCode + ) + const createdDraftThree = await createTestHealthPlanPackage( + server, + stateCode + ) + + const createdDraftTwoFormData = latestFormData(createdDraftTwo) + const createdDraftThreeFormData = latestFormData(createdDraftThree) + + const packageWithSharedRate1 = { + packageId: createdDraftTwo.id, + packageName: packageName( + createdDraftTwo.stateCode, + createdDraftTwoFormData.stateNumber, + createdDraftTwoFormData.programIDs, + statePrograms + ), + } as const + + const packageWithSharedRate2 = { + packageId: createdDraftThree.id, + packageName: packageName( + createdDraftThree.stateCode, + createdDraftThreeFormData.stateNumber, + createdDraftThreeFormData.programIDs, + statePrograms + ), + } as const + + // Create 2 rate data for insertion + const rate1 = { + rateType: 'NEW' as const, + rateDateStart: new Date(Date.UTC(2025, 5, 1)), + rateDateEnd: new Date(Date.UTC(2026, 4, 30)), + rateDateCertified: new Date(Date.UTC(2025, 3, 15)), + rateDocuments: [ + { + name: 'rateDocument.pdf', + s3URL: 's3://bucketname/key/supporting-documents', + documentCategories: ['RATES' as const], + sha256: 'rate1-sha', + }, + ], + rateAmendmentInfo: undefined, + rateCapitationType: undefined, + rateCertificationName: undefined, + supportingDocuments: [], + //We only want one rate ID and use last program in list to differentiate from programID if possible. + rateProgramIDs: [statePrograms.reverse()[0].id], + actuaryContacts: [ + { + name: 'test name', + titleRole: 'test title', + email: 'email@example.com', + actuarialFirm: 'MERCER' as const, + actuarialFirmOther: '', + }, + ], + packagesWithSharedRateCerts: [ + packageWithSharedRate1, + packageWithSharedRate2, + ], + } + + const rate2 = { + id: uuidv4(), + rateType: 'NEW' as const, + rateDateStart: new Date(Date.UTC(2025, 5, 1)), + rateDateEnd: new Date(Date.UTC(2026, 4, 30)), + rateDateCertified: new Date(Date.UTC(2025, 3, 15)), + rateDocuments: [ + { + name: 'rateDocument.pdf', + s3URL: 's3://bucketname/key/supporting-documents', + documentCategories: ['RATES' as const], + sha256: 'rate2-sha', + }, + ], + rateAmendmentInfo: undefined, + rateCapitationType: undefined, + rateCertificationName: undefined, + supportingDocuments: [], + //We only want one rate ID and use last program in list to differentiate from programID if possible. + rateProgramIDs: [statePrograms.reverse()[0].id], + actuaryContacts: [ + { + name: 'test name', + titleRole: 'test title', + email: 'email@example.com', + actuarialFirm: 'MERCER' as const, + actuarialFirmOther: '', + }, + ], + packagesWithSharedRateCerts: [], + } + + // update that draft form data. + const formData = Object.assign(latestFormData(createdDraft), { + addtlActuaryContacts: [ + { + name: 'additional actuary 1', + titleRole: 'additional actuary title 1', + email: 'additionalactuary1@example.com', + actuarialFirm: 'MERCER' as const, + actuarialFirmOther: '', + }, + { + name: 'additional actuary 2', + titleRole: 'additional actuary title 2', + email: 'additionalactuary1@example.com', + actuarialFirm: 'MERCER' as const, + actuarialFirmOther: '', + }, + ], + rateInfos: [rate1, rate2], + }) + + // convert to base64 proto + const updatedB64 = domainToBase64(formData) + + // update the DB contract + const updateResult = await server.executeOperation({ + query: UPDATE_HEALTH_PLAN_FORM_DATA, + variables: { + input: { + pkgID: createdDraft.id, + healthPlanFormData: updatedB64, + }, + }, + }) + + expect(updateResult.errors).toBeDefined() + }) + it('updates relational fields such as documents and contacts', async () => { const server = await constructTestPostgresServer({ ldService: mockLDService, diff --git a/services/app-api/src/resolvers/healthPlanPackage/updateHealthPlanFormData.ts b/services/app-api/src/resolvers/healthPlanPackage/updateHealthPlanFormData.ts index 5987bb5220..8b9fee561c 100644 --- a/services/app-api/src/resolvers/healthPlanPackage/updateHealthPlanFormData.ts +++ b/services/app-api/src/resolvers/healthPlanPackage/updateHealthPlanFormData.ts @@ -116,6 +116,18 @@ export function updateHealthPlanFormDataResolver( const unlockedFormData: UnlockedHealthPlanFormDataType = formDataResult + // If the client tries to update a rate without setting its ID that's an error. + for (const rateFD of unlockedFormData.rateInfos) { + if (!rateFD.id) { + const errMessage = `Attempted to update a rateInfo that has no ID: ${input.pkgID} ${rateFD}` + logError('updateHealthPlanFormData', errMessage) + setErrorAttributesOnActiveSpan(errMessage, span) + throw new UserInputError(errMessage, { + argumentName: 'healthPlanFormData.rateInfo', + }) + } + } + // Uses new DB if flag is on if (ratesDatabaseRefactor) { // Find contract from DB diff --git a/services/app-api/src/testHelpers/gqlHelpers.ts b/services/app-api/src/testHelpers/gqlHelpers.ts index df98810a04..9e5498ffe5 100644 --- a/services/app-api/src/testHelpers/gqlHelpers.ts +++ b/services/app-api/src/testHelpers/gqlHelpers.ts @@ -1,4 +1,5 @@ import { ApolloServer } from 'apollo-server-lambda' +import { v4 as uuidv4 } from 'uuid' import CREATE_HEALTH_PLAN_PACKAGE from 'app-graphql/src/mutations/createHealthPlanPackage.graphql' import SUBMIT_HEALTH_PLAN_PACKAGE from 'app-graphql/src/mutations/submitHealthPlanPackage.graphql' import UNLOCK_HEALTH_PLAN_PACKAGE from 'app-graphql/src/mutations/unlockHealthPlanPackage.graphql' @@ -200,6 +201,7 @@ const createAndUpdateTestHealthPlanPackage = async ( ] draft.rateInfos = [ { + id: uuidv4(), rateType: 'NEW' as const, rateDateStart: new Date(Date.UTC(2025, 5, 1)), rateDateEnd: new Date(Date.UTC(2026, 4, 30)), diff --git a/services/app-web/src/common-code/proto/healthPlanFormDataProto/healthPlanFormDataEncoding.test.ts b/services/app-web/src/common-code/proto/healthPlanFormDataProto/healthPlanFormDataEncoding.test.ts index 4500bb4490..c1249f30b2 100644 --- a/services/app-web/src/common-code/proto/healthPlanFormDataProto/healthPlanFormDataEncoding.test.ts +++ b/services/app-web/src/common-code/proto/healthPlanFormDataProto/healthPlanFormDataEncoding.test.ts @@ -49,20 +49,6 @@ describe('Validate encoding to protobuf and decoding back to domain model', () = } ) - it('encodes to protobuf and generates rate id', () => { - const draftFormDataWithNoRateID = unlockedWithFullRates() - draftFormDataWithNoRateID.rateInfos[0].id = undefined - - //Encode data to protobuf and back to domain model - const domainData = toDomain(toProtoBuffer(draftFormDataWithNoRateID)) - - if (domainData instanceof Error) { - throw Error(domainData.message) - } - - expect(domainData.rateInfos[0]?.id).toBeDefined() - }) - it('encodes to protobuf and back to domain model without corrupting existing rate info id', () => { const draftFormDataWithNoRateID = unlockedWithFullRates() draftFormDataWithNoRateID.rateInfos[0].id = diff --git a/services/app-web/src/common-code/proto/healthPlanFormDataProto/toProtoBuffer.ts b/services/app-web/src/common-code/proto/healthPlanFormDataProto/toProtoBuffer.ts index 04e56eeb85..e333fb2bbd 100644 --- a/services/app-web/src/common-code/proto/healthPlanFormDataProto/toProtoBuffer.ts +++ b/services/app-web/src/common-code/proto/healthPlanFormDataProto/toProtoBuffer.ts @@ -9,7 +9,6 @@ import { import statePrograms from '../../data/statePrograms.json' import { ProgramArgType } from '../../healthPlanFormDataType/State' import { CURRENT_PROTO_VERSION } from './toLatestVersion' -import { v4 as uuidv4 } from 'uuid' const findStatePrograms = (stateCode: string): ProgramArgType[] => { const programs = statePrograms.states.find( @@ -198,7 +197,7 @@ const toProtoBuffer = ( domainData.rateInfos && domainData.rateInfos.length ? domainData.rateInfos.map((rateInfo) => { return { - id: rateInfo.id ?? uuidv4(), + id: rateInfo.id, rateType: domainEnumToProto( rateInfo.rateType, mcreviewproto.RateType diff --git a/services/app-web/src/pages/StateSubmission/RateDetails/RateDetails.tsx b/services/app-web/src/pages/StateSubmission/RateDetails/RateDetails.tsx index 7dc39210e0..1a30906fb8 100644 --- a/services/app-web/src/pages/StateSubmission/RateDetails/RateDetails.tsx +++ b/services/app-web/src/pages/StateSubmission/RateDetails/RateDetails.tsx @@ -185,6 +185,7 @@ export const RateDetails = ({ const cleanedRateInfos = rateInfos.map((rateInfo) => { return { + id: rateInfo.id, rateType: rateInfo.rateType, rateCapitationType: rateInfo.rateCapitationType, rateDocuments: formatDocumentsForDomain( From 67572c9276c30a383cf0e1ff78c39a5ffccf7838 Mon Sep 17 00:00:00 2001 From: haworku Date: Thu, 19 Oct 2023 11:43:27 -0500 Subject: [PATCH 3/7] Fix VAL rates dashboard crash (#1993) * Loosen the actuary contact graphql types- throwing an error that was breaking rates dash * Don't return all rates - leave off the ones associated with contract only * Revert "Don't return all rates - leave off the ones associated with contract only" This reverts commit 13d433b556de50c9d6b0bf68c2f535674bae30d8. --- services/app-graphql/src/schema.graphql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/services/app-graphql/src/schema.graphql b/services/app-graphql/src/schema.graphql index 0b82dae8d1..bbc37cb94f 100644 --- a/services/app-graphql/src/schema.graphql +++ b/services/app-graphql/src/schema.graphql @@ -835,10 +835,10 @@ enum ActuarialFirm { } type ActuaryContact { - name: String! - titleRole: String! - email: String! - actuarialFirm: ActuarialFirm! + name: String + titleRole: String + email: String + actuarialFirm: ActuarialFirm actuarialFirmOther: String } From ce40d1cc502aed4c43f21db5c3b8a4a856b5c547 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Oct 2023 14:26:45 +0000 Subject: [PATCH 4/7] Bump lerna from 7.0.1 to 7.4.1 (#1994) --- yarn.lock | 415 +++++++++++++++++++++++++++++------------------------- 1 file changed, 221 insertions(+), 194 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7d07a37bc8..35117a977d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9830,13 +9830,6 @@ dependencies: "@sinclair/typebox" "^0.24.1" -"@jest/schemas@^29.4.3": - version "29.4.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.4.3.tgz#39cf1b8469afc40b6f5a2baaa146e332c4151788" - integrity sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg== - dependencies: - "@sinclair/typebox" "^0.25.16" - "@jest/schemas@^29.6.0": version "29.6.0" resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.0.tgz#0f4cb2c8e3dca80c135507ba5635a4fd755b0040" @@ -9844,6 +9837,13 @@ dependencies: "@sinclair/typebox" "^0.27.8" +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + "@jest/source-map@^27.5.1": version "27.5.1" resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf" @@ -10148,32 +10148,84 @@ resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== -"@lerna/child-process@7.0.1": - version "7.0.1" - resolved "https://registry.yarnpkg.com/@lerna/child-process/-/child-process-7.0.1.tgz#b4edc965c88b2247ccc1c9bfb3dce1f42baec928" - integrity sha512-lov3hXcX+g76fjQ5kg6+QdffL6kFw/aH3sG7NGk61mZfsWCDum9kcp9biqIRAmD6xJbrQsr1i0i9YeCMnHJ6pA== +"@lerna/child-process@7.4.1": + version "7.4.1" + resolved "https://registry.yarnpkg.com/@lerna/child-process/-/child-process-7.4.1.tgz#efacbbe79794ef977feb86873d853bb8708707be" + integrity sha512-Bx1cRCZcVcWoz+atDQc4CSVzGuEgGJPOpIAXjQbBEA2cX5nqIBWdbye8eHu31En/F03aH9BhpNEJghs6wy4iTg== dependencies: chalk "^4.1.0" execa "^5.0.0" strong-log-transformer "^2.1.0" -"@lerna/create@7.0.1": - version "7.0.1" - resolved "https://registry.yarnpkg.com/@lerna/create/-/create-7.0.1.tgz#a204a087b3b7a2ea7bb00d374faf4f306cd48aca" - integrity sha512-vv9gtbrn/gBwQLdDlUeatO3uY58nxMktv9h9/5GEFcBesV9MAeQ0zRz8zkr2C0DX/m25lE+J5KTzJqZtMb03kw== +"@lerna/create@7.4.1": + version "7.4.1" + resolved "https://registry.yarnpkg.com/@lerna/create/-/create-7.4.1.tgz#3e4bb7235bf5700e7e63c470eba5619171331c1a" + integrity sha512-zPO9GyWceRimtMD+j+aQ8xJgNPYn/Q/SzHf4wYN+4Rj5nrFKMyX+Et7FbWgUNpj0dRgyCCKBDYmTB7xQVVq4gQ== dependencies: - "@lerna/child-process" "7.0.1" + "@lerna/child-process" "7.4.1" + "@npmcli/run-script" "6.0.2" + "@nx/devkit" ">=16.5.1 < 17" + "@octokit/plugin-enterprise-rest" "6.0.1" + "@octokit/rest" "19.0.11" + byte-size "8.1.1" + chalk "4.1.0" + clone-deep "4.0.1" + cmd-shim "6.0.1" + columnify "1.6.0" + conventional-changelog-core "5.0.1" + conventional-recommended-bump "7.0.1" + cosmiconfig "^8.2.0" dedent "0.7.0" + execa "5.0.0" fs-extra "^11.1.1" + get-stream "6.0.0" + git-url-parse "13.1.0" + glob-parent "5.1.2" + globby "11.1.0" + graceful-fs "4.2.11" + has-unicode "2.0.1" + ini "^1.3.8" init-package-json "5.0.0" + inquirer "^8.2.4" + is-ci "3.0.1" + is-stream "2.0.0" + js-yaml "4.1.0" + libnpmpublish "7.3.0" + load-json-file "6.2.0" + lodash "^4.17.21" + make-dir "4.0.0" + minimatch "3.0.5" + multimatch "5.0.0" + node-fetch "2.6.7" npm-package-arg "8.1.1" + npm-packlist "5.1.1" + npm-registry-fetch "^14.0.5" + npmlog "^6.0.2" + nx ">=16.5.1 < 17" + p-map "4.0.0" + p-map-series "2.1.0" + p-queue "6.6.2" p-reduce "^2.1.0" pacote "^15.2.0" pify "5.0.0" + read-cmd-shim "4.0.0" + read-package-json "6.0.4" + resolve-from "5.0.0" + rimraf "^4.4.1" semver "^7.3.4" + signal-exit "3.0.7" slash "^3.0.0" + ssri "^9.0.1" + strong-log-transformer "2.1.0" + tar "6.1.11" + temp-dir "1.0.0" + upath "2.0.1" + uuid "^9.0.0" validate-npm-package-license "^3.0.4" validate-npm-package-name "5.0.0" + write-file-atomic "5.0.1" + write-pkg "4.0.0" + yargs "16.2.0" yargs-parser "20.2.4" "@mdx-js/mdx@^1.6.22": @@ -10318,7 +10370,7 @@ dependencies: which "^3.0.0" -"@npmcli/run-script@6.0.2": +"@npmcli/run-script@6.0.2", "@npmcli/run-script@^6.0.0": version "6.0.2" resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-6.0.2.tgz#a25452d45ee7f7fb8c16dfaf9624423c0c0eb885" integrity sha512-NCcr1uQo1k5U+SYlnIrbAh3cxy+OQT1VtqiAbxdymSlptbzBb62AjH2xXgjNCoP073hoa1CfCAcwoZ8k96C4nA== @@ -10329,92 +10381,83 @@ read-package-json-fast "^3.0.0" which "^3.0.0" -"@npmcli/run-script@^6.0.0": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-6.0.1.tgz#a94404783d9afaff62decb71944435d0d8a29f8e" - integrity sha512-Yi04ZSold8jcbBJD/ahKMJSQCQifH8DAbMwkBvoLaTpGFxzHC3B/5ZyoVR69q/4xedz84tvi9DJOJjNe17h+LA== +"@nrwl/devkit@16.10.0": + version "16.10.0" + resolved "https://registry.yarnpkg.com/@nrwl/devkit/-/devkit-16.10.0.tgz#ac8c5b4db00f12c4b817c937be2f7c4eb8f2593c" + integrity sha512-fRloARtsDQoQgQ7HKEy0RJiusg/HSygnmg4gX/0n/Z+SUS+4KoZzvHjXc6T5ZdEiSjvLypJ+HBM8dQzIcVACPQ== dependencies: - "@npmcli/node-gyp" "^3.0.0" - "@npmcli/promise-spawn" "^6.0.0" - node-gyp "^9.0.0" - read-package-json-fast "^3.0.0" - which "^3.0.0" - -"@nrwl/devkit@16.3.2": - version "16.3.2" - resolved "https://registry.yarnpkg.com/@nrwl/devkit/-/devkit-16.3.2.tgz#b45393dfd62dcb75554ff0c2dff6715a907e3877" - integrity sha512-EiDwVIvh6AcClXv22Q7auQh7Iy/ONISEFWzTswy/J6ZmVGCQesbiwg4cGV0MKiScr+awdVzqyNey+wD6IR5Lkw== - dependencies: - "@nx/devkit" "16.3.2" + "@nx/devkit" "16.10.0" -"@nrwl/tao@16.3.2": - version "16.3.2" - resolved "https://registry.yarnpkg.com/@nrwl/tao/-/tao-16.3.2.tgz#eefc1974342afbbe48e4e5351d6707ad2f9fb179" - integrity sha512-2Kg7dtv6JcQagCZPSq+okceI81NqmXGGgbKWqS7sOfdmp1otxS9uiUFNXw+Pdtnw38mdRviMtSOXScntu4sUKg== +"@nrwl/tao@16.10.0": + version "16.10.0" + resolved "https://registry.yarnpkg.com/@nrwl/tao/-/tao-16.10.0.tgz#94642a0380709b8e387e1e33705a5a9624933375" + integrity sha512-QNAanpINbr+Pod6e1xNgFbzK1x5wmZl+jMocgiEFXZ67KHvmbD6MAQQr0MMz+GPhIu7EE4QCTLTyCEMlAG+K5Q== dependencies: - nx "16.3.2" + nx "16.10.0" + tslib "^2.3.0" -"@nx/devkit@16.3.2", "@nx/devkit@>=16.1.3 < 17": - version "16.3.2" - resolved "https://registry.yarnpkg.com/@nx/devkit/-/devkit-16.3.2.tgz#95d58d104449c54bdc276fa1c9166fcad867cfa8" - integrity sha512-1ev3EDm2Sx/ibziZroL1SheqxDR7UgC49tkBgJz1GrQLQnfdhBYroCPSyBSWGPMLHjIuHb3+hyGSV1Bz+BIYOA== +"@nx/devkit@16.10.0", "@nx/devkit@>=16.5.1 < 17": + version "16.10.0" + resolved "https://registry.yarnpkg.com/@nx/devkit/-/devkit-16.10.0.tgz#7e466be2dee2dcb1ccaf286786ca2a0a639aa007" + integrity sha512-IvKQqRJFDDiaj33SPfGd3ckNHhHi6ceEoqCbAP4UuMXOPPVOX6H0KVk+9tknkPb48B7jWIw6/AgOeWkBxPRO5w== dependencies: - "@nrwl/devkit" "16.3.2" + "@nrwl/devkit" "16.10.0" ejs "^3.1.7" + enquirer "~2.3.6" ignore "^5.0.4" - semver "7.3.4" + semver "7.5.3" tmp "~0.2.1" tslib "^2.3.0" -"@nx/nx-darwin-arm64@16.3.2": - version "16.3.2" - resolved "https://registry.yarnpkg.com/@nx/nx-darwin-arm64/-/nx-darwin-arm64-16.3.2.tgz#83b6e78b27d2d7da8f7626560f52070c8735d28a" - integrity sha512-YfYVNfsJBzBcBnJUU4AcA6A4QMkgnVlETfp4KGL36Otq542mRY1ISGHdox63ocI5AKh5gay5AaGcR4wR9PU9Vg== - -"@nx/nx-darwin-x64@16.3.2": - version "16.3.2" - resolved "https://registry.yarnpkg.com/@nx/nx-darwin-x64/-/nx-darwin-x64-16.3.2.tgz#0ae2a64356542c5fb73ca8038ce10ec4512e7fcb" - integrity sha512-bJtpozz0zSRVRrcQ76GrlT3TWEGTymLYWrVG51bH5KZ46t6/a4EQBI3uL3vubMmOZ0jR4ywybOcPBBhxmBJ68w== - -"@nx/nx-freebsd-x64@16.3.2": - version "16.3.2" - resolved "https://registry.yarnpkg.com/@nx/nx-freebsd-x64/-/nx-freebsd-x64-16.3.2.tgz#202adf4d6070f47ed46450f006ecd50851147c74" - integrity sha512-ZvufI0bWqT67nLbBo6ejrIGxypdoedRQTP/tudWbs/4isvxLe1uVku1BfKCTQUsJG367SqNOU1H5kzI/MRr3ow== - -"@nx/nx-linux-arm-gnueabihf@16.3.2": - version "16.3.2" - resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-16.3.2.tgz#62314a82566e3647866b9dd4167a2d0e1397f001" - integrity sha512-IQL4kxdiZLvifar7+SIum3glRuVsxtE0dL8RvteSDXrxDQnaTUrjILC+VGhalRmk7ngBbGKNrhWOeeL7390CzQ== - -"@nx/nx-linux-arm64-gnu@16.3.2": - version "16.3.2" - resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-16.3.2.tgz#02826400aa55b8f44bac83332dd29647d0e95001" - integrity sha512-f6AWgPVu3mfUEoOBa0rY2/7QY0Or9eR0KtLFpcPh7RUpxPw2EXzIbjD/0RGipdpspSrgiMKbZpsUjo6mXBFsQA== - -"@nx/nx-linux-arm64-musl@16.3.2": - version "16.3.2" - resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-16.3.2.tgz#a0a81520e0904aa026a7ab0a8a3bf3facec9f14c" - integrity sha512-AvrWcYz7021E3b5P9/0i26p60XMZfw86Epks51L6AhlflarlOH4AcEChc7APMtb1ELAIbDWx2S6oIDRbQ7rtVA== - -"@nx/nx-linux-x64-gnu@16.3.2": - version "16.3.2" - resolved "https://registry.yarnpkg.com/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-16.3.2.tgz#e79b5c142ec8d9bfb458ea5803bc4b62abbcf296" - integrity sha512-K2pWGAcbCNm6b7UZI9cc8z4Rb540QcuepBXD7akjPjWerzXriT6VCn4i9mVKsCg2mwSfknTJJVJ1PZwJSmTl/Q== - -"@nx/nx-linux-x64-musl@16.3.2": - version "16.3.2" - resolved "https://registry.yarnpkg.com/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-16.3.2.tgz#900aee8f171638b9fb44378e2ac0548cb4aa99a7" - integrity sha512-sY1QDuQlqyYiRPJZanrtV07tU0DOXiCrWb0pDsGiO0qHuUSmW5Vw17GWEY4z3rt0/5U8fJ+/9WQrneviOmsOKg== - -"@nx/nx-win32-arm64-msvc@16.3.2": - version "16.3.2" - resolved "https://registry.yarnpkg.com/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-16.3.2.tgz#88db772b3535648e147b1a0206b1a1fe875fa9a5" - integrity sha512-wBfohT2hjrLKn9WFHvG0MFVk7uYhgYNiptnTLdTouziHgFyZ08vyl7XYBq55BwHPMQ5iswVoEfjn/5ZBfCPscg== - -"@nx/nx-win32-x64-msvc@16.3.2": - version "16.3.2" - resolved "https://registry.yarnpkg.com/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-16.3.2.tgz#2195faaf1fc465c7a89bfdd62323fdd2a5d91f15" - integrity sha512-QC0sWrfQm0/WdvvM//7UAgm+otbak6bznZ0zawTeqmLBh1hLjNeweyzSVKQEtZtlzDMKpzCVuuwkJq+VKBLvmw== +"@nx/nx-darwin-arm64@16.10.0": + version "16.10.0" + resolved "https://registry.yarnpkg.com/@nx/nx-darwin-arm64/-/nx-darwin-arm64-16.10.0.tgz#0c73010cac7a502549483b12bad347da9014e6f1" + integrity sha512-YF+MIpeuwFkyvM5OwgY/rTNRpgVAI/YiR0yTYCZR+X3AAvP775IVlusNgQ3oedTBRUzyRnI4Tknj1WniENFsvQ== + +"@nx/nx-darwin-x64@16.10.0": + version "16.10.0" + resolved "https://registry.yarnpkg.com/@nx/nx-darwin-x64/-/nx-darwin-x64-16.10.0.tgz#2ccf270418d552fd0a8e0d6089aee4944315adaa" + integrity sha512-ypi6YxwXgb0kg2ixKXE3pwf5myVNUgWf1CsV5OzVccCM8NzheMO51KDXTDmEpXdzUsfT0AkO1sk5GZeCjhVONg== + +"@nx/nx-freebsd-x64@16.10.0": + version "16.10.0" + resolved "https://registry.yarnpkg.com/@nx/nx-freebsd-x64/-/nx-freebsd-x64-16.10.0.tgz#c3ee6914256e69493fed9355b0d6661d0e86da44" + integrity sha512-UeEYFDmdbbDkTQamqvtU8ibgu5jQLgFF1ruNb/U4Ywvwutw2d4ruOMl2e0u9hiNja9NFFAnDbvzrDcMo7jYqYw== + +"@nx/nx-linux-arm-gnueabihf@16.10.0": + version "16.10.0" + resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-16.10.0.tgz#a961eccbb38acb2da7fc125b29d1fead0b39152f" + integrity sha512-WV3XUC2DB6/+bz1sx+d1Ai9q2Cdr+kTZRN50SOkfmZUQyEBaF6DRYpx/a4ahhxH3ktpNfyY8Maa9OEYxGCBkQA== + +"@nx/nx-linux-arm64-gnu@16.10.0": + version "16.10.0" + resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-16.10.0.tgz#795f20072549d03822b5c4639ef438e473dbb541" + integrity sha512-aWIkOUw995V3ItfpAi5FuxQ+1e9EWLS1cjWM1jmeuo+5WtaKToJn5itgQOkvSlPz+HSLgM3VfXMvOFALNk125g== + +"@nx/nx-linux-arm64-musl@16.10.0": + version "16.10.0" + resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-16.10.0.tgz#f2428ee6dbe2b2c326e8973f76c97666def33607" + integrity sha512-uO6Gg+irqpVcCKMcEPIQcTFZ+tDI02AZkqkP7koQAjniLEappd8DnUBSQdcn53T086pHpdc264X/ZEpXFfrKWQ== + +"@nx/nx-linux-x64-gnu@16.10.0": + version "16.10.0" + resolved "https://registry.yarnpkg.com/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-16.10.0.tgz#d36c2bcf94d49eaa24e3880ddaf6f1f617de539b" + integrity sha512-134PW/u/arNFAQKpqMJniC7irbChMPz+W+qtyKPAUXE0XFKPa7c1GtlI/wK2dvP9qJDZ6bKf0KtA0U/m2HMUOA== + +"@nx/nx-linux-x64-musl@16.10.0": + version "16.10.0" + resolved "https://registry.yarnpkg.com/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-16.10.0.tgz#78bd2ab97a583b3d4ea3387b67fd7b136907493c" + integrity sha512-q8sINYLdIJxK/iUx9vRk5jWAWb/2O0PAbOJFwv4qkxBv4rLoN7y+otgCZ5v0xfx/zztFgk/oNY4lg5xYjIso2Q== + +"@nx/nx-win32-arm64-msvc@16.10.0": + version "16.10.0" + resolved "https://registry.yarnpkg.com/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-16.10.0.tgz#ef20ec8d0c83d66e73e20df12d2c788b8f866396" + integrity sha512-moJkL9kcqxUdJSRpG7dET3UeLIciwrfP08mzBQ12ewo8K8FzxU8ZUsTIVVdNrwt01CXOdXoweGfdQLjJ4qTURA== + +"@nx/nx-win32-x64-msvc@16.10.0": + version "16.10.0" + resolved "https://registry.yarnpkg.com/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-16.10.0.tgz#7410a51d0f8be631eec9552f01b2e5946285927c" + integrity sha512-5iV2NKZnzxJwZZ4DM5JVbRG/nkhAbzEskKaLBB82PmYGKzaDHuMHP1lcPoD/rtYMlowZgNA/RQndfKvPBPwmXA== "@octokit/action@^5.0.2": version "5.0.2" @@ -11798,11 +11841,6 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.28.tgz#15aa0b416f82c268b1573ab653e4413c965fe794" integrity sha512-dgJd3HLOkLmz4Bw50eZx/zJwtBq65nms3N9VBYu5LTjJ883oBFkTyXRlCB/ZGGwqYpJJHA5zW2Ibhl5ngITfow== -"@sinclair/typebox@^0.25.16": - version "0.25.24" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718" - integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ== - "@sinclair/typebox@^0.27.8": version "0.27.8" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" @@ -15999,10 +16037,10 @@ resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== -"@yarnpkg/parsers@^3.0.0-rc.18": - version "3.0.0-rc.34" - resolved "https://registry.yarnpkg.com/@yarnpkg/parsers/-/parsers-3.0.0-rc.34.tgz#db1d16e082e167db6dbc67f1c264639e0b4c5e1a" - integrity sha512-NhEA0BusInyk7EiJ7i7qF1Mkrb6gGjZcQQ/W1xxGazxapubEmGO7v5WSll6hWxFXE2ngtLj8lflq1Ff5VtqEww== +"@yarnpkg/parsers@3.0.0-rc.46": + version "3.0.0-rc.46" + resolved "https://registry.yarnpkg.com/@yarnpkg/parsers/-/parsers-3.0.0-rc.46.tgz#03f8363111efc0ea670e53b0282cd3ef62de4e01" + integrity sha512-aiATs7pSutzda/rq8fnuPwTglyVwjM22bNnK2ZgjrpAjQHSSl3lztd2f9evst1W/qnC58DRz7T7QndUDumAR4Q== dependencies: js-yaml "^3.10.0" tslib "^2.4.0" @@ -19992,10 +20030,10 @@ diff-sequences@^27.5.1: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== -diff-sequences@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" - integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== diff@^4.0.1: version "4.0.2" @@ -20164,7 +20202,7 @@ dot-prop@^5.1.0: dependencies: is-obj "^2.0.0" -dotenv-expand@^10.0.0: +dotenv-expand@^10.0.0, dotenv-expand@~10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-10.0.0.tgz#12605d00fb0af6d0a592e6558585784032e4ef37" integrity sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A== @@ -20174,12 +20212,12 @@ dotenv-expand@^5.1.0: resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== -dotenv@^10.0.0, dotenv@~10.0.0: +dotenv@^10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== -dotenv@^16.0.0, dotenv@^16.3.1: +dotenv@^16.0.0, dotenv@^16.3.1, dotenv@~16.3.1: version "16.3.1" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== @@ -21465,17 +21503,6 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== -fast-glob@3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" - integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - fast-glob@^2.2.6: version "2.2.7" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" @@ -24652,15 +24679,15 @@ jest-config@^29.6.2: slash "^3.0.0" strip-json-comments "^3.1.1" -"jest-diff@>=29.4.3 < 30": - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.5.0.tgz#e0d83a58eb5451dcc1fa61b1c3ee4e8f5a290d63" - integrity sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw== +"jest-diff@>=29.4.3 < 30", jest-diff@^29.4.1, jest-diff@^29.6.2: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== dependencies: chalk "^4.0.0" - diff-sequences "^29.4.3" - jest-get-type "^29.4.3" - pretty-format "^29.5.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" jest-diff@^27.5.1: version "27.5.1" @@ -24672,16 +24699,6 @@ jest-diff@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" -jest-diff@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.6.2.tgz#c36001e5543e82a0805051d3ceac32e6825c1c46" - integrity sha512-t+ST7CB9GX5F2xKwhwCf0TAR17uNDiaPTZnVymP9lw0lssa9vG+AFyDZoeIHStU3WowFFwT+ky+er0WVl2yGhA== - dependencies: - chalk "^4.0.0" - diff-sequences "^29.4.3" - jest-get-type "^29.4.3" - pretty-format "^29.6.2" - jest-docblock@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.5.1.tgz#14092f364a42c6108d42c33c8cf30e058e25f6c0" @@ -24765,6 +24782,11 @@ jest-get-type@^29.4.3: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5" integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + jest-haste-map@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" @@ -25923,14 +25945,14 @@ lazystream@^1.0.0: readable-stream "^2.0.5" lerna@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/lerna/-/lerna-7.0.1.tgz#056fc44f0e4852a06e943197d3d0d05d59d84559" - integrity sha512-kX279o8N/L2URwoR3Pf4TdIl5P8G443qAFy095ZD+Vu1tOMo8U6xOc221EgHoMuYhdqlT3f0vgn5bMMr/xNYhQ== + version "7.4.1" + resolved "https://registry.yarnpkg.com/lerna/-/lerna-7.4.1.tgz#d124fa5f0a1fe10ae9a6081bc363d98f3f6caca9" + integrity sha512-c6sOO0dlJU689vStIsko+zjRdn2fJOWH8aNjePLNv2AubAdABKqfrDCpE2H/Q7+O80Duo68ZQtWYkUUk7hRWDw== dependencies: - "@lerna/child-process" "7.0.1" - "@lerna/create" "7.0.1" + "@lerna/child-process" "7.4.1" + "@lerna/create" "7.4.1" "@npmcli/run-script" "6.0.2" - "@nx/devkit" ">=16.1.3 < 17" + "@nx/devkit" ">=16.5.1 < 17" "@octokit/plugin-enterprise-rest" "6.0.1" "@octokit/rest" "19.0.11" byte-size "8.1.1" @@ -25964,7 +25986,8 @@ lerna@^7.0.0: libnpmaccess "7.0.2" libnpmpublish "7.3.0" load-json-file "6.2.0" - make-dir "3.1.0" + lodash "^4.17.21" + make-dir "4.0.0" minimatch "3.0.5" multimatch "5.0.0" node-fetch "2.6.7" @@ -25972,7 +25995,7 @@ lerna@^7.0.0: npm-packlist "5.1.1" npm-registry-fetch "^14.0.5" npmlog "^6.0.2" - nx ">=16.1.3 < 17" + nx ">=16.5.1 < 17" p-map "4.0.0" p-map-series "2.1.0" p-pipe "3.1.0" @@ -26531,12 +26554,12 @@ magic-string@^0.25.0, magic-string@^0.25.7: dependencies: sourcemap-codec "^1.4.8" -make-dir@3.1.0, make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== +make-dir@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== dependencies: - semver "^6.0.0" + semver "^7.5.3" make-dir@^1.0.0: version "1.3.0" @@ -26553,6 +26576,13 @@ make-dir@^2.0.0, make-dir@^2.1.0: pify "^4.0.1" semver "^5.6.0" +make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + make-error@1.x, make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" @@ -27468,6 +27498,11 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" +node-machine-id@1.1.12: + version "1.1.12" + resolved "https://registry.yarnpkg.com/node-machine-id/-/node-machine-id-1.1.12.tgz#37904eee1e59b320bb9c5d6c0a59f3b469cb6267" + integrity sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ== + node-preload@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/node-preload/-/node-preload-0.2.1.tgz#c03043bb327f417a18fee7ab7ee57b408a144301" @@ -27729,36 +27764,38 @@ nwsapi@^2.2.0: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.1.tgz#10a9f268fbf4c461249ebcfe38e359aa36e2577c" integrity sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg== -nx@16.3.2, "nx@>=16.1.3 < 17": - version "16.3.2" - resolved "https://registry.yarnpkg.com/nx/-/nx-16.3.2.tgz#92a2d7ef06d15b3b111b7cf9d35de08de0a22d90" - integrity sha512-fOzCVL7qoCJAcYTJwvJ9j+PSaL791ro4AICWuLxaphZsp2jcLoav4Ev7ONPks2Wlkt8FS9bee3nqQ3w1ya36Og== +nx@16.10.0, "nx@>=16.5.1 < 17": + version "16.10.0" + resolved "https://registry.yarnpkg.com/nx/-/nx-16.10.0.tgz#b070461f7de0a3d7988bd78558ea84cda3543ace" + integrity sha512-gZl4iCC0Hx0Qe1VWmO4Bkeul2nttuXdPpfnlcDKSACGu3ZIo+uySqwOF8yBAxSTIf8xe2JRhgzJN1aFkuezEBg== dependencies: - "@nrwl/tao" "16.3.2" + "@nrwl/tao" "16.10.0" "@parcel/watcher" "2.0.4" "@yarnpkg/lockfile" "^1.1.0" - "@yarnpkg/parsers" "^3.0.0-rc.18" + "@yarnpkg/parsers" "3.0.0-rc.46" "@zkochan/js-yaml" "0.0.6" axios "^1.0.0" chalk "^4.1.0" cli-cursor "3.1.0" cli-spinners "2.6.1" - cliui "^7.0.2" - dotenv "~10.0.0" + cliui "^8.0.1" + dotenv "~16.3.1" + dotenv-expand "~10.0.0" enquirer "~2.3.6" - fast-glob "3.2.7" figures "3.2.0" flat "^5.0.2" fs-extra "^11.1.0" glob "7.1.4" ignore "^5.0.4" + jest-diff "^29.4.1" js-yaml "4.1.0" jsonc-parser "3.2.0" lines-and-columns "~2.0.3" minimatch "3.0.5" + node-machine-id "1.1.12" npm-run-path "^4.0.1" open "^8.4.0" - semver "7.3.4" + semver "7.5.3" string-width "^4.2.3" strong-log-transformer "^2.1.0" tar-stream "~2.2.0" @@ -27769,16 +27806,16 @@ nx@16.3.2, "nx@>=16.1.3 < 17": yargs "^17.6.2" yargs-parser "21.1.1" optionalDependencies: - "@nx/nx-darwin-arm64" "16.3.2" - "@nx/nx-darwin-x64" "16.3.2" - "@nx/nx-freebsd-x64" "16.3.2" - "@nx/nx-linux-arm-gnueabihf" "16.3.2" - "@nx/nx-linux-arm64-gnu" "16.3.2" - "@nx/nx-linux-arm64-musl" "16.3.2" - "@nx/nx-linux-x64-gnu" "16.3.2" - "@nx/nx-linux-x64-musl" "16.3.2" - "@nx/nx-win32-arm64-msvc" "16.3.2" - "@nx/nx-win32-x64-msvc" "16.3.2" + "@nx/nx-darwin-arm64" "16.10.0" + "@nx/nx-darwin-x64" "16.10.0" + "@nx/nx-freebsd-x64" "16.10.0" + "@nx/nx-linux-arm-gnueabihf" "16.10.0" + "@nx/nx-linux-arm64-gnu" "16.10.0" + "@nx/nx-linux-arm64-musl" "16.10.0" + "@nx/nx-linux-x64-gnu" "16.10.0" + "@nx/nx-linux-x64-musl" "16.10.0" + "@nx/nx-win32-arm64-msvc" "16.10.0" + "@nx/nx-win32-x64-msvc" "16.10.0" nyc@15.1.0: version "15.1.0" @@ -29505,15 +29542,6 @@ pretty-format@^29.0.0: ansi-styles "^5.0.0" react-is "^18.0.0" -pretty-format@^29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.5.0.tgz#283134e74f70e2e3e7229336de0e4fce94ccde5a" - integrity sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw== - dependencies: - "@jest/schemas" "^29.4.3" - ansi-styles "^5.0.0" - react-is "^18.0.0" - pretty-format@^29.6.2: version "29.6.2" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.6.2.tgz#3d5829261a8a4d89d8b9769064b29c50ed486a47" @@ -29523,6 +29551,15 @@ pretty-format@^29.6.2: ansi-styles "^5.0.0" react-is "^18.0.0" +pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + pretty-hrtime@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" @@ -30290,7 +30327,7 @@ read-package-json-fast@^3.0.0: json-parse-even-better-errors "^3.0.0" npm-normalize-package-bin "^3.0.0" -read-package-json@6.0.4: +read-package-json@6.0.4, read-package-json@^6.0.0: version "6.0.4" resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-6.0.4.tgz#90318824ec456c287437ea79595f4c2854708836" integrity sha512-AEtWXYfopBj2z5N5PbkAOeNHRPUg5q+Nen7QLxV8M2zJq1ym6/lCz3fYNTCXe19puu2d06jfHhrP7v/S2PtMMw== @@ -30300,16 +30337,6 @@ read-package-json@6.0.4: normalize-package-data "^5.0.0" npm-normalize-package-bin "^3.0.0" -read-package-json@^6.0.0: - version "6.0.3" - resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-6.0.3.tgz#726116b75e00eac2075240995f05681af4ca7122" - integrity sha512-4QbpReW4kxFgeBQ0vPAqh2y8sXEB3D4t3jsXbJKIhBiF80KT6XRo45reqwtftju5J6ru1ax06A2Gb/wM1qCOEQ== - dependencies: - glob "^10.2.2" - json-parse-even-better-errors "^3.0.0" - normalize-package-data "^5.0.0" - npm-normalize-package-bin "^3.0.0" - read-pkg-up@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" @@ -31238,10 +31265,10 @@ semver@7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -semver@7.3.4: - version "7.3.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" - integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== +semver@7.5.3: + version "7.5.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.3.tgz#161ce8c2c6b4b3bdca6caadc9fa3317a4c4fe88e" + integrity sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ== dependencies: lru-cache "^6.0.0" From d016a8e7b2d9eb3df39c126e8bca4eaea0995f44 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Oct 2023 14:28:24 +0000 Subject: [PATCH 5/7] Bump undici from 5.19.1 to 5.26.3 (#1982) --- yarn.lock | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index 35117a977d..f740a7c7de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8482,6 +8482,11 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.49.0.tgz#86f79756004a97fa4df866835093f1df3d03c333" integrity sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w== +"@fastify/busboy@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.0.0.tgz#f22824caff3ae506b18207bad4126dbc6ccdb6b8" + integrity sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ== + "@floating-ui/core@^1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.0.4.tgz#03066eaea8e9b2a2cd3f5aaa60f1e0f580ebe88e" @@ -33598,11 +33603,11 @@ unc-path-regex@^0.1.2: integrity sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg== undici@^5.12.0, undici@^5.8.0: - version "5.19.1" - resolved "https://registry.yarnpkg.com/undici/-/undici-5.19.1.tgz#92b1fd3ab2c089b5a6bd3e579dcda8f1934ebf6d" - integrity sha512-YiZ61LPIgY73E7syxCDxxa3LV2yl3sN8spnIuTct60boiiRaE1J8mNWHO8Im2Zi/sFrPusjLlmRPrsyraSqX6A== + version "5.26.3" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.26.3.tgz#ab3527b3d5bb25b12f898dfd22165d472dd71b79" + integrity sha512-H7n2zmKEWgOllKkIUkLvFmsJQj062lSm3uA4EYApG8gLuiOM0/go9bIoC3HVaSnfg4xunowDE2i9p8drkXuvDw== dependencies: - busboy "^1.6.0" + "@fastify/busboy" "^2.0.0" unfetch@^4.2.0: version "4.2.0" From 663f0c11763951b16cf67dbadf478252e5b5272a Mon Sep 17 00:00:00 2001 From: haworku Date: Fri, 20 Oct 2023 10:28:33 -0500 Subject: [PATCH 6/7] MCR-3570 fix: Previous contract submissions contained incorrect rate data (#1991) * Add failing test for rates refactor with submit * Clean up submitHPP test * Add passing fetchRate tests * clarify which ids are used * Make parseRateWithHistory look more like parseContractWithHistory * Revert "Make parseRateWithHistory look more like parseContractWithHistory" This reverts commit 571bbf8aa708322012cd35364a19caf5846c8ddf. * Fix adding rate revisions submitted after contract revision into history. * Fix test for strict rate id checks. * Make types play nice - right now common code actuary contact and graphql actuary contact slightly different * Refactor loop of rate revisions of a contract revision to retain the rate order. * Add more to docs about contract history * Update comment. * Refactor some more. * Fix to retain rate order. * cypress re-run * cypress re-run --------- Co-authored-by: Jason Lin --- .../contract-rate-change-history.md | 18 +- .../contractAndRates/revisionTypes.ts | 7 +- .../parseContractAndRates.test.ts | 7 + .../parseContractWithHistory.ts | 29 +- .../contractAndRates/parseRateWithHistory.ts | 1 + .../prismaDraftRatesHelpers.ts | 1 + .../prismaSharedContractRateHelpers.ts | 4 +- .../contractAndRates/unlockContract.test.ts | 7 +- .../contractAndRates/fetchRate.test.ts | 271 ++++++++++++++++++ .../submitHealthPlanPackage.test.ts | 172 ++++++++++- .../submitHealthPlanPackage.ts | 8 +- .../contractAndRates/rateHelpers.ts | 15 + .../app-api/src/testHelpers/gqlHelpers.ts | 43 ++- .../testHelpers/healthPlanPackageHelpers.ts | 16 ++ .../DataDetailContactField.tsx | 4 +- 15 files changed, 572 insertions(+), 31 deletions(-) diff --git a/docs/technical-design/contract-rate-change-history.md b/docs/technical-design/contract-rate-change-history.md index e00451153b..9f310c8ea1 100644 --- a/docs/technical-design/contract-rate-change-history.md +++ b/docs/technical-design/contract-rate-change-history.md @@ -29,9 +29,23 @@ At the Postgres Table level, draft revisions and submitted revisions live in the The list of revisions returned from prisma is run through [Zod](https://zod.dev/) to return [domain mode types](../../services/app-api/src/domain-models/contractAndRates). This is initiated by the `*WithHistory` database functions. See [parseContractWithHistory](../../services/app-api/src/postgres/contractAndRates/parseContractWithHistory.ts) and [parseRateWithHistory](../../services/app-api/src/postgres/contractAndRates/parseRateWithHistory.ts). +#### Contract History +- `parseContractWithHistory` takes our prisma contract data and parses into our domain `ContractType`. In `ContractType` the `revisions` is an array of **contract** **revisions**; this is the contract history. +- `revisions` differs from `draftRevision` in the `ContractType`. The `draftRevision` is a singular revision that is not submitted and this data has no historical significance until it is submitted. Most of the data in this revision can be updated. +- Each **contract revision** in `revisions` is submitted and retains data at the time of the submission. These revision's data will never be updated to retain its historical integrity. +- An important note about the `rateRevisions` field in each contract revision in `revision`. + - Like contracts, rates also have **rate revisions** which are used to construct a rate history through submissions, but the purpose of `rateRevisions` on a contract revision is not for rate history. + - The purpose of `rateRevisions` is to retain the data of a rate linked to this contract revision at the time of submission. + - For that we need the single rate revision that was submitted at the time this contract revision was submitted. + - Here are some guidelines for each rate revision in `rateRevisions` of a contract revision. + - Each rate revision in `rateRevisions` is unique by rate id, meaning there will never be two rate revisions with the same rate id in `rateRevisions` + - Each rate revision is the latest submitted up till the contract revision submitted time. + - Like contract revision, rate revision is read only and cannot be updated to retain its historical integrity. + + *Dev Note*: If the `draftRevision` field has a value and the `revisions` field is an empty array, we know the Contract or Rate we are looking at is an initial draft that has never been submitted. -## The link between contract and rates is versioned. That link is solidified on submit. +### The link between contract and rates is versioned. That link is solidified on submit. It's possible to tell if a link between a contract and rate has become outdated by refencing the `valid After` and `validUntil` fields on the join table between contract and rate revisions. The `validFrom` is set when a link is created (when a contract is submitted with a link to rates). At the point of creation, the `validUntil` is still null. @@ -50,4 +64,4 @@ The `unlockInfo` and `submitInfo` associated with that revision is important met When constructing a new package with a draft contract that has a draft rate, one of them must be submitted first. We have chosen that rates submit first, then contracts are submitted with a relationship to a set of submitted rates. ## Related documentation -- [Contract and Rates Refactor Relationships](./contract-rate-refactor-relationships.md). \ No newline at end of file +- [Contract and Rates Refactor Relationships](./contract-rate-refactor-relationships.md). diff --git a/services/app-api/src/domain-models/contractAndRates/revisionTypes.ts b/services/app-api/src/domain-models/contractAndRates/revisionTypes.ts index 168fba1bd8..8377d66879 100644 --- a/services/app-api/src/domain-models/contractAndRates/revisionTypes.ts +++ b/services/app-api/src/domain-models/contractAndRates/revisionTypes.ts @@ -18,7 +18,12 @@ const contractRevisionSchema = z.object({ const rateRevisionSchema = z.object({ id: z.string().uuid(), - // rateID: z.string(), // TODO we have this data in prisma but we lose it in the domain type - its needed for frontend which uses parent ids for routing + rate: z.object({ + id: z.string().uuid(), + stateCode: z.string(), + stateNumber: z.number().min(1), + createdAt: z.date(), + }), submitInfo: updateInfoSchema.optional(), unlockInfo: updateInfoSchema.optional(), createdAt: z.date(), diff --git a/services/app-api/src/postgres/contractAndRates/parseContractAndRates.test.ts b/services/app-api/src/postgres/contractAndRates/parseContractAndRates.test.ts index 7383e0b979..cd8436874f 100644 --- a/services/app-api/src/postgres/contractAndRates/parseContractAndRates.test.ts +++ b/services/app-api/src/postgres/contractAndRates/parseContractAndRates.test.ts @@ -163,6 +163,13 @@ describe('parseDomainData', () => { isRemoval: false, rateRevision: { id: uuidv4(), + rate: { + id: '24fb2a5f-6d0d-4e26-9906-4de28927c882', + createdAt: new Date(), + updatedAt: new Date(), + stateCode: 'MN', + stateNumber: 111, + }, rateID: 'Rate ID', createdAt: new Date(), updatedAt: new Date(), diff --git a/services/app-api/src/postgres/contractAndRates/parseContractWithHistory.ts b/services/app-api/src/postgres/contractAndRates/parseContractWithHistory.ts index 21b7d6fcdf..c9937e2d9a 100644 --- a/services/app-api/src/postgres/contractAndRates/parseContractWithHistory.ts +++ b/services/app-api/src/postgres/contractAndRates/parseContractWithHistory.ts @@ -194,7 +194,8 @@ function contractWithHistoryToDomainModel( // } // } - // Basically the same as above, except we do not create new contract revisions for rate changes. + // This loop is finding the rate revision submitted along with this contract revision to preserve the historical + // rate data for the submission history. for (const rateRev of contractRev.rateRevisions) { if (!rateRev.rateRevision.submitInfo) { return new Error( @@ -202,28 +203,22 @@ function contractWithHistoryToDomainModel( ) } - // if it's from before this contract was submitted, it's there at the beginning. + // Make sure this rate revision was not submitted after this contract revision, and it was not removed. if ( rateRev.rateRevision.submitInfo.updatedAt <= - contractRev.submitInfo.updatedAt + contractRev.submitInfo.updatedAt && + !rateRev.isRemoval ) { - if (!rateRev.isRemoval) { - initialEntry.rateRevisions.push(rateRev.rateRevision) - } - } else { - // if after, then it's always a new entry in the list - let lastRates = [...initialEntry.rateRevisions] - // take out the previous rate revision this revision supersedes - lastRates = lastRates.filter( - (r) => r.rateID !== rateRev.rateRevision.rateID + const filteredRevisions = initialEntry.rateRevisions.filter( + (rr) => rr.rateID !== rateRev.rateRevision.rateID ) - // an isRemoval entry indicates that this rate was removed from this contract. - if (!rateRev.isRemoval) { - lastRates.push(rateRev.rateRevision) - } - initialEntry.rateRevisions = lastRates + // add latest revision + filteredRevisions.push(rateRev.rateRevision) + + // Sort to retain order by rate.createdAt. + initialEntry.rateRevisions = filteredRevisions } } } diff --git a/services/app-api/src/postgres/contractAndRates/parseRateWithHistory.ts b/services/app-api/src/postgres/contractAndRates/parseRateWithHistory.ts index 13c3257039..561f4f4064 100644 --- a/services/app-api/src/postgres/contractAndRates/parseRateWithHistory.ts +++ b/services/app-api/src/postgres/contractAndRates/parseRateWithHistory.ts @@ -85,6 +85,7 @@ function rateRevisionToDomainModel( return { id: revision.id, + rate: revision.rate, createdAt: revision.createdAt, updatedAt: revision.updatedAt, submitInfo: convertUpdateInfoToDomainModel(revision.submitInfo), diff --git a/services/app-api/src/postgres/contractAndRates/prismaDraftRatesHelpers.ts b/services/app-api/src/postgres/contractAndRates/prismaDraftRatesHelpers.ts index d1c7e8c4a4..9b844154c7 100644 --- a/services/app-api/src/postgres/contractAndRates/prismaDraftRatesHelpers.ts +++ b/services/app-api/src/postgres/contractAndRates/prismaDraftRatesHelpers.ts @@ -51,6 +51,7 @@ function draftRateRevToDomainModel( return { id: revision.id, + rate: revision.rate, createdAt: revision.createdAt, updatedAt: revision.updatedAt, formData, diff --git a/services/app-api/src/postgres/contractAndRates/prismaSharedContractRateHelpers.ts b/services/app-api/src/postgres/contractAndRates/prismaSharedContractRateHelpers.ts index 9b04f67d67..18ed03cb70 100644 --- a/services/app-api/src/postgres/contractAndRates/prismaSharedContractRateHelpers.ts +++ b/services/app-api/src/postgres/contractAndRates/prismaSharedContractRateHelpers.ts @@ -89,6 +89,7 @@ function getContractRateStatus( const includeRateFormData = { submitInfo: includeUpdateInfo, unlockInfo: includeUpdateInfo, + rate: true, rateDocuments: { orderBy: { @@ -224,6 +225,7 @@ function rateRevisionToDomainModel( return { id: revision.id, + rate: revision.rate, createdAt: revision.createdAt, updatedAt: revision.updatedAt, unlockInfo: convertUpdateInfoToDomainModel(revision.unlockInfo), @@ -248,7 +250,7 @@ function ratesRevisionsToDomainModel( } domainRevisions.sort( - (a, b) => a.createdAt.getTime() - b.createdAt.getTime() + (a, b) => a.rate.createdAt.getTime() - b.rate.createdAt.getTime() ) return domainRevisions diff --git a/services/app-api/src/postgres/contractAndRates/unlockContract.test.ts b/services/app-api/src/postgres/contractAndRates/unlockContract.test.ts index 8081370d54..1a5e5c734f 100644 --- a/services/app-api/src/postgres/contractAndRates/unlockContract.test.ts +++ b/services/app-api/src/postgres/contractAndRates/unlockContract.test.ts @@ -131,7 +131,12 @@ describe('unlockContract', () => { ) }) - it('Unlocks a rate without breaking connected submitted contract', async () => { + // This is unlocking a rate without unlocking the contract that this rate belongs to. Then it updates the rate and resubmits. + // The rate gets a new revision, but the submitted contract does not. + // This test does not simulate how creating/updating a rate currently works in our app and the contract revision history + // will not match. + // Skipping this for now, revisit during rate only feature work. + it.skip('Unlocks a rate without breaking connected submitted contract', async () => { const client = await sharedTestPrismaClient() const stateUser = await client.user.create({ diff --git a/services/app-api/src/resolvers/contractAndRates/fetchRate.test.ts b/services/app-api/src/resolvers/contractAndRates/fetchRate.test.ts index fa2c31137d..5ceb46f7c8 100644 --- a/services/app-api/src/resolvers/contractAndRates/fetchRate.test.ts +++ b/services/app-api/src/resolvers/contractAndRates/fetchRate.test.ts @@ -2,15 +2,286 @@ import FETCH_RATE from '../../../../app-graphql/src/queries/fetchRate.graphql' import { constructTestPostgresServer, createAndSubmitTestHealthPlanPackage, + defaultFloridaRateProgram, + resubmitTestHealthPlanPackage, unlockTestHealthPlanPackage, + updateTestHealthPlanPackage, } from '../../testHelpers/gqlHelpers' import { testCMSUser } from '../../testHelpers/userHelpers' import { testLDService } from '../../testHelpers/launchDarklyHelpers' import { latestFormData } from '../../testHelpers/healthPlanPackageHelpers' +import { must } from '../../testHelpers' +import { v4 as uuidv4 } from 'uuid' describe('fetchRate', () => { const mockLDService = testLDService({ 'rates-db-refactor': true }) + it('returns correct rate revisions on resubmit when existing rate is edited', async () => { + const cmsUser = testCMSUser() + const server = await constructTestPostgresServer({ + ldService: mockLDService, + }) + + const cmsServer = await constructTestPostgresServer({ + context: { + user: cmsUser, + }, + ldService: mockLDService, + }) + + const initialRateInfos = () => ({ + id: uuidv4(), + rateType: 'NEW' as const, + rateDateStart: new Date(Date.UTC(2025, 5, 1)), + rateDateEnd: new Date(Date.UTC(2026, 4, 30)), + rateDateCertified: new Date(Date.UTC(2025, 3, 15)), + rateDocuments: [ + { + name: 'rateDocument.pdf', + s3URL: 'fakeS3URL', + sha256: 'fakesha', + documentCategories: ['RATES' as const], + }, + ], + supportingDocuments: [], + rateProgramIDs: [defaultFloridaRateProgram().id], + actuaryContacts: [ + { + name: 'test name', + titleRole: 'test title', + email: 'email@example.com', + actuarialFirm: 'MERCER' as const, + actuarialFirmOther: '', + }, + ], + actuaryCommunicationPreference: 'OACT_TO_ACTUARY' as const, + packagesWithSharedRateCerts: [], + }) + + // First, create new submission and unlock to edit rate + const submittedEditedRates = await createAndSubmitTestHealthPlanPackage( + server, + { + rateInfos: [initialRateInfos()], + } + ) + const packageID = submittedEditedRates.id + + // Unlock submission + const unlockedHPP = await unlockTestHealthPlanPackage( + cmsServer, + packageID, + 'Unlock to edit an existing rate' + ) + + // Decode unlocked submission form data + const unlockedHppFormData = latestFormData(unlockedHPP) + // Get the data of the first and only rate in the HPP + const unlockedRate = unlockedHppFormData.rateInfos[0] + + // edit the same rate + await updateTestHealthPlanPackage(server, packageID, { + rateInfos: [ + { + ...unlockedRate, + id: unlockedRate.id, // edit same rate, use same id + rateDateStart: new Date(Date.UTC(2025, 1, 1)), + rateDateEnd: new Date(Date.UTC(2027, 1, 1)), + }, + ], + }) + + // Resubmit + const resubmittedHPP = await resubmitTestHealthPlanPackage( + server, + packageID, + 'Resubmit with edited rate description' + ) + + // Decode resubmitted HPP form data + const resubmittedHppFormData = latestFormData(resubmittedHPP) + + // fetch and check rate + const result = await cmsServer.executeOperation({ + query: FETCH_RATE, + variables: { + input: { + rateID: resubmittedHppFormData.rateInfos[0].id, + }, + }, + }) + + const resubmittedRate = result.data?.fetchRate.rate + expect(result.errors).toBeUndefined() + expect(resubmittedRate).toBeDefined() + + // check that we have two revisions of the same rate + expect(resubmittedRate.revisions).toHaveLength(2) + + // newest revision data is correct + expect(resubmittedRate.revisions[0].formData.rateDateStart).toBe( + '2025-02-01' + ) + expect(resubmittedRate.revisions[0].formData.rateDateEnd).toBe( + '2027-02-01' + ) + expect(resubmittedRate.revisions[0].submitInfo.updatedReason).toBe( + 'Resubmit with edited rate description' + ) + // the initial submit data is correct + expect(resubmittedRate.revisions[1].formData.rateDateStart).toBe( + '2025-06-01' + ) + expect(resubmittedRate.revisions[1].formData.rateDateEnd).toBe( + '2026-05-30' + ) + expect(resubmittedRate.revisions[1].submitInfo.updatedReason).toBe( + 'Initial submission' + ) + }) + + it('returns correct rate revisions on resubmit when new rate added', async () => { + const cmsUser = testCMSUser() + const server = await constructTestPostgresServer({ + ldService: mockLDService, + }) + + const cmsServer = await constructTestPostgresServer({ + context: { + user: cmsUser, + }, + ldService: mockLDService, + }) + + const initialRateInfos = () => ({ + id: uuidv4(), + rateType: 'NEW' as const, + rateDateStart: new Date(Date.UTC(2025, 5, 1)), + rateDateEnd: new Date(Date.UTC(2026, 4, 30)), + rateDateCertified: new Date(Date.UTC(2025, 3, 15)), + rateDocuments: [ + { + name: 'rateDocument.pdf', + s3URL: 'fakeS3URL', + sha256: 'fakesha', + documentCategories: ['RATES' as const], + }, + ], + supportingDocuments: [], + rateProgramIDs: [defaultFloridaRateProgram().id], + actuaryContacts: [ + { + name: 'test name', + titleRole: 'test title', + email: 'email@example.com', + actuarialFirm: 'MERCER' as const, + actuarialFirmOther: '', + }, + ], + actuaryCommunicationPreference: 'OACT_TO_ACTUARY' as const, + packagesWithSharedRateCerts: [], + }) + + // First, create new submission and unlock to edit rate + const submittedInitial = await createAndSubmitTestHealthPlanPackage( + server, + { + rateInfos: [initialRateInfos()], + } + ) + + const existingRate = await unlockTestHealthPlanPackage( + cmsServer, + submittedInitial.id, + 'Unlock to edit add a new rate' + ) + + // add new rate + const existingFormData = latestFormData(existingRate) + const firstRateID = existingFormData.rateInfos[0].id + expect(existingFormData.rateInfos).toHaveLength(1) + await updateTestHealthPlanPackage(server, submittedInitial.id, { + rateInfos: [ + existingFormData.rateInfos[0], // first rate unchanged + { + ...initialRateInfos(), + rateDateStart: new Date(Date.UTC(2030, 1, 1)), + rateDateEnd: new Date(Date.UTC(2030, 12, 1)), + rateDateCertified: new Date(Date.UTC(2029, 10, 31)), + }, + ], + }) + + const resubmitResult = await resubmitTestHealthPlanPackage( + server, + submittedInitial.id, + 'Resubmit with an additional rate' + ) + + // fetch and check rate 1 which was resubmitted with no changese + expect(firstRateID).toBe(latestFormData(resubmitResult).rateInfos[0].id) // first rate ID should be unchanged + + const result1 = must( + await cmsServer.executeOperation({ + query: FETCH_RATE, + variables: { + input: { rateID: firstRateID }, + }, + }) + ) + const resubmittedRate1 = result1.data?.fetchRate.rate + expect(resubmittedRate1.revisions).toHaveLength(2) + // dates for first rate should be unchanged + expect(resubmittedRate1.revisions[0].formData.rateDateStart).toBe( + '2025-06-01' + ) + expect(resubmittedRate1.revisions[0].formData.rateDateEnd).toBe( + '2026-05-30' + ) + expect(resubmittedRate1.revisions[0].submitInfo.updatedReason).toBe( + 'Resubmit with an additional rate' + ) + + // check that initial submission is correct + expect(resubmittedRate1.revisions[1].formData.rateDateStart).toBe( + '2025-06-01' + ) + expect(resubmittedRate1.revisions[1].formData.rateDateEnd).toBe( + '2026-05-30' + ) + expect(resubmittedRate1.revisions[1].submitInfo.updatedReason).toBe( + 'Initial submission' + ) + + // Check our second test rate which was added in unlock + const secondRateID = latestFormData(resubmitResult).rateInfos[1].id + const result2 = await cmsServer.executeOperation({ + query: FETCH_RATE, + variables: { + input: { rateID: secondRateID }, + }, + }) + + const resubmitted2 = result2.data?.fetchRate.rate + expect(result2.errors).toBeUndefined() + expect(resubmitted2).toBeDefined() + + // second test rate should only have one revision with the correct data + expect(resubmitted2.revisions).toHaveLength(1) + expect(resubmitted2.revisions[0].submitInfo.updatedReason).toBe( + 'Resubmit with an additional rate' + ) + expect(resubmitted2.revisions[0].formData.rateDateStart).toBe( + '2030-02-01' + ) + expect(resubmitted2.revisions[0].formData.rateDateEnd).toBe( + '2031-01-01' + ) + expect(resubmitted2.revisions[0].formData.rateDateCertified).toBe( + '2029-12-01' + ) + }) + it('returns the right revisions as a rate is unlocked', async () => { const cmsUser = testCMSUser() const server = await constructTestPostgresServer({ diff --git a/services/app-api/src/resolvers/healthPlanPackage/submitHealthPlanPackage.test.ts b/services/app-api/src/resolvers/healthPlanPackage/submitHealthPlanPackage.test.ts index 3be2fe0ed0..ce1565f061 100644 --- a/services/app-api/src/resolvers/healthPlanPackage/submitHealthPlanPackage.test.ts +++ b/services/app-api/src/resolvers/healthPlanPackage/submitHealthPlanPackage.test.ts @@ -10,6 +10,7 @@ import { createAndSubmitTestHealthPlanPackage, defaultFloridaRateProgram, submitTestHealthPlanPackage, + updateTestHealthPlanPackage, } from '../../testHelpers/gqlHelpers' import { v4 as uuidv4 } from 'uuid' import { testEmailConfig, testEmailer } from '../../testHelpers/emailerHelpers' @@ -18,7 +19,10 @@ import { generateRateName, packageName, } from '../../../../app-web/src/common-code/healthPlanFormDataType' -import { latestFormData } from '../../testHelpers/healthPlanPackageHelpers' +import { + latestFormData, + previousFormData, +} from '../../testHelpers/healthPlanPackageHelpers' import { mockEmailParameterStoreError, getTestStateAnalystsEmails, @@ -135,6 +139,172 @@ describe.each(flagValueTestParameters)( ).toBeGreaterThan(0) }, 20000) + it('returns a state submission with the correct rate data on resubmit', async () => { + const cmsUser = testCMSUser() + const server = await constructTestPostgresServer({ + ldService: mockLDService, + }) + + const cmsServer = await constructTestPostgresServer({ + context: { + user: cmsUser, + }, + ldService: mockLDService, + }) + + const initialRateInfos = () => ({ + id: uuidv4(), + rateType: 'NEW' as const, + rateDateStart: new Date(Date.UTC(2025, 5, 1)), + rateDateEnd: new Date(Date.UTC(2026, 4, 30)), + rateDateCertified: new Date(Date.UTC(2025, 3, 15)), + rateDocuments: [ + { + name: 'rateDocument.pdf', + s3URL: 'fakeS3URL', + sha256: 'fakesha', + documentCategories: ['RATES' as const], + }, + ], + supportingDocuments: [], + rateProgramIDs: [defaultFloridaRateProgram().id], + actuaryContacts: [ + { + name: 'test name', + titleRole: 'test title', + email: 'email@example.com', + actuarialFirm: 'MERCER' as const, + actuarialFirmOther: '', + }, + ], + actuaryCommunicationPreference: 'OACT_TO_ACTUARY' as const, + packagesWithSharedRateCerts: [], + }) + + // First, create new submissions + const submittedEditedRates = + await createAndSubmitTestHealthPlanPackage(server, { + rateInfos: [initialRateInfos()], + }) + const submittedNewRates = + await createAndSubmitTestHealthPlanPackage(server, { + rateInfos: [initialRateInfos()], + }) + + // Unlock both - one to be rate edited in place, the other to add new rate + const existingRate1 = await unlockTestHealthPlanPackage( + cmsServer, + submittedEditedRates.id, + 'Unlock to edit an existing rate' + ) + const existingRate2 = await unlockTestHealthPlanPackage( + cmsServer, + submittedNewRates.id, + 'Unlock to add a new rate' + ) + + // update one with a new rate start and end date + const existingFormData = latestFormData(existingRate1) + expect(existingFormData.rateInfos).toHaveLength(1) + await updateTestHealthPlanPackage(server, submittedEditedRates.id, { + rateInfos: [ + { + ...existingFormData.rateInfos[0], + rateDateStart: new Date(Date.UTC(2025, 1, 1)), + rateDateEnd: new Date(Date.UTC(2027, 1, 1)), + }, + ], + }) + + // update the other with additional new rate + const existingFormData2 = latestFormData(existingRate2) + expect(existingFormData2.rateInfos).toHaveLength(1) + await updateTestHealthPlanPackage(server, submittedNewRates.id, { + rateInfos: [ + existingFormData2.rateInfos[0], + { + ...initialRateInfos(), + id: uuidv4(), // this is a new rate + rateDateStart: new Date(Date.UTC(2030, 1, 1)), + rateDateEnd: new Date(Date.UTC(2030, 12, 1)), + }, + ], + }) + // resubmit both + await resubmitTestHealthPlanPackage( + server, + submittedEditedRates.id, + 'Resubmit with edited rate description' + ) + await resubmitTestHealthPlanPackage( + server, + submittedNewRates.id, + 'Resubmit with an additional rate added' + ) + + // fetch both packages and check that the latest data is correct + const editedRatesPackage = await fetchTestHealthPlanPackageById( + server, + submittedEditedRates.id + ) + expect(latestFormData(editedRatesPackage).rateInfos).toHaveLength(1) + expect( + latestFormData(editedRatesPackage).rateInfos[0].rateDateStart + ).toMatchObject(new Date(Date.UTC(2025, 1, 1))) + expect( + latestFormData(editedRatesPackage).rateInfos[0].rateDateEnd + ).toMatchObject(new Date(Date.UTC(2027, 1, 1))) + expect( + editedRatesPackage.revisions[0].node.submitInfo?.updatedReason + ).toBe('Resubmit with edited rate description') + + const newRatesPackage = await fetchTestHealthPlanPackageById( + server, + submittedNewRates.id + ) + expect(latestFormData(newRatesPackage).rateInfos).toHaveLength(2) + expect( + latestFormData(newRatesPackage).rateInfos[0].rateDateStart + ).toMatchObject(initialRateInfos().rateDateStart) + expect( + latestFormData(newRatesPackage).rateInfos[0].rateDateEnd + ).toMatchObject(initialRateInfos().rateDateEnd) + expect( + latestFormData(newRatesPackage).rateInfos[1].rateDateStart + ).toMatchObject(new Date(Date.UTC(2030, 1, 1))) + expect( + latestFormData(newRatesPackage).rateInfos[1].rateDateEnd + ).toMatchObject(new Date(Date.UTC(2030, 12, 1))) + expect( + newRatesPackage.revisions[0].node.submitInfo?.updatedReason + ).toBe('Resubmit with an additional rate added') + + // also check both packages to ensure previous revision data is unchanged + expect(previousFormData(editedRatesPackage).rateInfos).toHaveLength( + 1 + ) + expect( + previousFormData(editedRatesPackage).rateInfos[0].rateDateStart + ).toMatchObject(initialRateInfos().rateDateStart) + expect( + previousFormData(editedRatesPackage).rateInfos[0].rateDateEnd + ).toMatchObject(initialRateInfos().rateDateEnd) + expect( + editedRatesPackage.revisions[1].node.submitInfo?.updatedReason + ).toBe('Initial submission') + + expect(previousFormData(newRatesPackage).rateInfos).toHaveLength(1) + expect( + previousFormData(newRatesPackage).rateInfos[0].rateDateStart + ).toMatchObject(initialRateInfos().rateDateStart) + expect( + previousFormData(newRatesPackage).rateInfos[0].rateDateEnd + ).toMatchObject(initialRateInfos().rateDateEnd) + expect( + newRatesPackage.revisions[1].node.submitInfo?.updatedReason + ).toBe('Initial submission') + }) + it('returns an error if there are no contract documents attached', async () => { const server = await constructTestPostgresServer({ ldService: mockLDService, diff --git a/services/app-api/src/resolvers/healthPlanPackage/submitHealthPlanPackage.ts b/services/app-api/src/resolvers/healthPlanPackage/submitHealthPlanPackage.ts index ebc3680b8c..d60bc9b518 100644 --- a/services/app-api/src/resolvers/healthPlanPackage/submitHealthPlanPackage.ts +++ b/services/app-api/src/resolvers/healthPlanPackage/submitHealthPlanPackage.ts @@ -290,7 +290,7 @@ export function submitHealthPlanPackageResolver( // reassign variable set up before rates feature flag const conversionResult = convertContractWithRatesToFormData( - contractWithHistory.revisions[0], + contractWithHistory.draftRevision, contractWithHistory.id, contractWithHistory.stateCode, contractWithHistory.stateNumber @@ -304,7 +304,7 @@ export function submitHealthPlanPackageResolver( } initialFormData = conversionResult - contractRevisionID = contractWithHistory.revisions[0].id + contractRevisionID = contractWithHistory.draftRevision.id // Final clean + check of data before submit - parse to state submission const maybeLocked = parseAndSubmit(initialFormData) @@ -367,9 +367,9 @@ export function submitHealthPlanPackageResolver( } // If there are rates, submit those first - if (contractWithHistory.revisions[0].rateRevisions.length > 0) { + if (contractWithHistory.draftRevision.rateRevisions.length > 0) { const ratePromises: Promise[] = [] - contractWithHistory.revisions[0].rateRevisions.forEach( + contractWithHistory.draftRevision.rateRevisions.forEach( (rateRev) => { ratePromises.push( store.submitRate({ diff --git a/services/app-api/src/testHelpers/contractAndRates/rateHelpers.ts b/services/app-api/src/testHelpers/contractAndRates/rateHelpers.ts index a9baf0faba..c8652e98e2 100644 --- a/services/app-api/src/testHelpers/contractAndRates/rateHelpers.ts +++ b/services/app-api/src/testHelpers/contractAndRates/rateHelpers.ts @@ -8,6 +8,14 @@ import type { StateCodeType } from '../../../../app-web/src/common-code/healthPl import { findStatePrograms } from '../../postgres' import { must } from '../errorHelpers' +const defaultRateData = () => ({ + id: '24fb2a5f-6d0d-4e26-9906-4de28927c882', + createdAt: new Date(), + updatedAt: new Date(), + stateCode: 'MN', + stateNumber: 111, +}) + const createInsertRateData = ( rateArgs?: Partial ): InsertRateArgsType => { @@ -27,6 +35,7 @@ const createDraftRateData = ( stateNumber: 111, revisions: rate?.revisions ?? [ createRateRevision( + rate, { contractRevisions: undefined, submitInfo: null, @@ -47,6 +56,7 @@ const createRateData = ( stateNumber: 111, revisions: rate?.revisions ?? [ createRateRevision( + rate, { draftContracts: undefined, }, @@ -57,6 +67,7 @@ const createRateData = ( }) const createRateRevision = ( + rate?: Partial, revision?: Partial, stateCode: StateCodeType = 'MN' ): RateRevisionTableWithContracts => { @@ -64,6 +75,10 @@ const createRateRevision = ( return { id: uuidv4(), + rate: { + ...defaultRateData(), + ...rate, + }, createdAt: new Date(), updatedAt: new Date(), submitInfo: { diff --git a/services/app-api/src/testHelpers/gqlHelpers.ts b/services/app-api/src/testHelpers/gqlHelpers.ts index 9e5498ffe5..3ccd40a2d3 100644 --- a/services/app-api/src/testHelpers/gqlHelpers.ts +++ b/services/app-api/src/testHelpers/gqlHelpers.ts @@ -178,6 +178,40 @@ const updateTestHealthPlanFormData = async ( return updateResult.data.updateHealthPlanFormData.pkg } +const updateTestHealthPlanPackage = async ( + server: ApolloServer, + pkgID: string, + partialUpdates?: Partial +): Promise => { + const pkg = await fetchTestHealthPlanPackageById(server, pkgID) + const draft = latestFormData(pkg) + const updatedFormData = { + ...draft, + ...partialUpdates, + status: 'DRAFT' as const, + } + const updateResult = await server.executeOperation({ + query: UPDATE_HEALTH_PLAN_FORM_DATA, + variables: { + input: { + pkgID: pkgID, + healthPlanFormData: domainToBase64(updatedFormData), + }, + }, + }) + if (updateResult.errors) { + console.info('errors', JSON.stringify(updateResult.errors)) + throw new Error( + `updateTestHealthPlanFormData mutation failed with errors ${updateResult.errors}` + ) + } + + if (!updateResult.data) { + throw new Error('updateTestHealthPlanFormData returned nothing') + } + return updateResult.data.updateHealthPlanFormData.pkg +} + const createAndUpdateTestHealthPlanPackage = async ( server: ApolloServer, partialUpdates?: Partial, @@ -276,9 +310,13 @@ const createAndUpdateTestHealthPlanPackage = async ( } const createAndSubmitTestHealthPlanPackage = async ( - server: ApolloServer + server: ApolloServer, + partialUpdates?: Partial ): Promise => { - const pkg = await createAndUpdateTestHealthPlanPackage(server) + const pkg = await createAndUpdateTestHealthPlanPackage( + server, + partialUpdates + ) return await submitTestHealthPlanPackage(server, pkg.id) } @@ -499,4 +537,5 @@ export { createTestQuestion, indexTestQuestions, createTestQuestionResponse, + updateTestHealthPlanPackage, } diff --git a/services/app-api/src/testHelpers/healthPlanPackageHelpers.ts b/services/app-api/src/testHelpers/healthPlanPackageHelpers.ts index 98a6476b38..46590d102b 100644 --- a/services/app-api/src/testHelpers/healthPlanPackageHelpers.ts +++ b/services/app-api/src/testHelpers/healthPlanPackageHelpers.ts @@ -17,3 +17,19 @@ export function latestFormData(pkg: HealthPlanPackage): HealthPlanFormDataType { return unwrapResult } + +export function previousFormData( + pkg: HealthPlanPackage +): HealthPlanFormDataType { + const latestRevision = pkg.revisions[1].node + if (!latestRevision) { + throw new Error('no previous revisions found for package' + pkg.id) + } + + const unwrapResult = base64ToDomain(latestRevision.formDataProto) + if (unwrapResult instanceof Error) { + throw unwrapResult + } + + return unwrapResult +} diff --git a/services/app-web/src/components/DataDetail/DataDetailContactField/DataDetailContactField.tsx b/services/app-web/src/components/DataDetail/DataDetailContactField/DataDetailContactField.tsx index 977e8239ff..3b524904c5 100644 --- a/services/app-web/src/components/DataDetail/DataDetailContactField/DataDetailContactField.tsx +++ b/services/app-web/src/components/DataDetail/DataDetailContactField/DataDetailContactField.tsx @@ -5,9 +5,9 @@ import { } from '../../../common-code/healthPlanFormDataType' import { getActuaryFirm } from '../../SubmissionSummarySection' import { DataDetailMissingField } from '../DataDetailMissingField' +import { ActuaryContact as GQLActuaryContact } from '../../../gen/gqlClient' -type Contact = ActuaryContact | StateContact - +type Contact = ActuaryContact | StateContact | GQLActuaryContact function isCertainActuaryContact(contact: Contact): contact is ActuaryContact { return (contact as ActuaryContact).actuarialFirm !== undefined } From b9392633c53b880b47dd1803e07c7212b2b8355b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Oct 2023 15:45:41 +0000 Subject: [PATCH 7/7] Bump graphql-scalars from 1.22.2 to 1.22.4 (#1980) --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index f740a7c7de..d944f46244 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22780,9 +22780,9 @@ graphql-request@^5.0.0: form-data "^3.0.0" graphql-scalars@^1.11.1: - version "1.22.2" - resolved "https://registry.yarnpkg.com/graphql-scalars/-/graphql-scalars-1.22.2.tgz#6326e6fe2d0ad4228a9fea72a977e2bf26b86362" - integrity sha512-my9FB4GtghqXqi/lWSVAOPiTzTnnEzdOXCsAC2bb5V7EFNQjVjwy3cSSbUvgYOtDuDibd+ZsCDhz+4eykYOlhQ== + version "1.22.4" + resolved "https://registry.yarnpkg.com/graphql-scalars/-/graphql-scalars-1.22.4.tgz#af092b142bcfd5c1f8c53cb70ee1955ecd4ddb03" + integrity sha512-ILnv7jq5VKHLUyoaTFX7lgYrjCd6vTee9i8/B+D4zJKJT5TguOl0KkpPEbXHjmeor8AZYrVsrYUHdqRBMX1pjA== dependencies: tslib "^2.5.0"