diff --git a/services/app-web/src/pages/CMSDashboard/RateReviewsDashboard/RateReviewsTable.tsx b/services/app-web/src/pages/CMSDashboard/RateReviewsDashboard/RateReviewsTable.tsx index 02fad0109b..f63c375911 100644 --- a/services/app-web/src/pages/CMSDashboard/RateReviewsDashboard/RateReviewsTable.tsx +++ b/services/app-web/src/pages/CMSDashboard/RateReviewsDashboard/RateReviewsTable.tsx @@ -411,7 +411,7 @@ export const RateReviewsTable = ({ - + {reactTable.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( diff --git a/services/cypress/integration/cmsWorkflow/rateReview.spec.ts b/services/cypress/integration/cmsWorkflow/rateReview.spec.ts index b55559db6e..8866d1ab69 100644 --- a/services/cypress/integration/cmsWorkflow/rateReview.spec.ts +++ b/services/cypress/integration/cmsWorkflow/rateReview.spec.ts @@ -1,107 +1,91 @@ +import { HealthPlanFormDataType, packageName } from "../../../app-web/src/common-code/healthPlanFormDataType" +import { base64ToDomain } from "../../../app-web/src/common-code/proto/healthPlanFormDataProto" +import { HealthPlanPackage } from "../../gen/gqlClient" +import { cmsUser, minnesotaStatePrograms, stateUser } from "../../utils/apollo-test-utils" + describe('CMS user can view rate reviews', () => { beforeEach(() => { cy.stubFeatureFlags() cy.interceptGraphQL() }) - it('and navigate to a specific rate from the rates dashboard', () => { - // Assign Division to CMS user zuko - cy.apiAssignDivisionToCMSUser(cmsUser(), 'DMCO').then(() => { - // Create a new submission - cy.apiCreateAndSubmitContractOnlySubmission(stateUser()).then( - (pkg) => { - }) - }) - // state user adds a new package - cy.logInAsStateUser() - cy.startNewContractAndRatesSubmission() - cy.fillOutBaseContractDetails() - cy.navigateFormByButtonClick('CONTINUE') + // By default return lastest revision + const getFormData = (pkg: HealthPlanPackage, indx = 0): HealthPlanFormDataType => { + const latestRevision = pkg.revisions[indx].node + if (!latestRevision) { + throw new Error('no revisions found for package' + pkg.id) + } - cy.findByRole('heading', { - level: 2, - name: /Rate details/, - }).should('exist') - cy.fillOutNewRateCertification() - cy.navigateFormByButtonClick('CONTINUE') + const unwrapResult = base64ToDomain(latestRevision.formDataProto) + if (unwrapResult instanceof Error) { + throw unwrapResult + } - cy.findByRole('heading', { - level: 2, - name: /Contacts/, - }).should('exist') - cy.fillOutStateContact() - cy.fillOutAdditionalActuaryContact() - cy.navigateFormByButtonClick('CONTINUE') + return unwrapResult + } - cy.findByRole('heading', { - level: 2, - name: /Supporting documents/, - }).should('exist') - cy.fillOutSupportingDocuments() - cy.navigateFormByButtonClick('CONTINUE') - // store submission id for reference later - let submissionId = '' - cy.location().then((fullUrl) => { - const { pathname } = fullUrl - const pathnameArray = pathname.split('/') - submissionId = pathnameArray[2] + it('and navigate to a specific rate from the rates dashboard', () => { + cy.interceptFeatureFlags({ + 'rates-db-refactor':true, + 'rate-reviews-dashboard': true }) - // submit package - cy.findByRole('heading', { level: 2, name: /Review and submit/ }) - cy.submitStateSubmissionForm() - - // store submission name for later - cy.location().then((loc) => { - expect(loc.search).to.match(/.*justSubmitted=*/) - const submissionName = loc.search.split('=').pop() - if (submissionName === undefined) { - throw new Error('No submission name found' + loc.search) - } - - // sign out state user - cy.logOut() - // sign in CMS user - cy.logInAsCMSUser() - cy.findByTestId('cms-dashboard-page').should('exist') - cy.findByRole('table').should('exist') - cy.findByText(submissionName).should('exist') - // check the table of submissions - - // only one matching entry - cy.get('table') - .findAllByText(submissionName) - .should('have.length', 1) + cy.apiAssignDivisionToCMSUser(cmsUser(), 'DMCO').then(() => { - // has proper row data - cy.get('table') + // Create a new contract and rates submission with two attached rates + cy.apiCreateAndSubmitContractWithRates(stateUser()).then( + (pkg) => { + const submission = getFormData(pkg) + const submissionName = packageName( + pkg.stateCode, + submission.stateNumber, + submission.programIDs, + minnesotaStatePrograms + ) + // Then check both rates in rate reviews table + cy.logInAsCMSUser({ + initialURL: `/dashboard/rate-reviews`, + }) + const rate1 = submission.rateInfos[0] + const rate2 = submission.rateInfos[1] + cy.get('table') + .findByRole('link', { name: rate1.rateCertificationName }) .should('exist') - .findByText(submissionName) - .parent() - .findByTestId('submission-date') - .should('not.be.empty') - - cy.get('table') + cy.get('table') + .findByRole('link', { name: rate2.rateCertificationName }) .should('exist') - .findByText(submissionName) - .parent() - .siblings('[data-testid="submission-status"]') - .should('have.text', 'Submitted') - cy.get('table') - .contains('a', submissionName) - .parents('tr') - .findByTestId('submission-type') - .should('have.text', 'Contract action and rate certification') + // click the first rate to navigate to rate summary page + cy.get('table') + .findByRole('link', { name: rate1.rateCertificationName }) + .should('exist').click() + cy.url({ timeout: 10_000 }).should('contain',rate1.id) + cy.findByRole('heading', { + name: `${rate1.rateCertificationName}`, + }).should('exist') + cy.findByText('Rate certification type').should('exist').siblings('dd').should('have.text', 'New rate certification') + cy.findByText('Rating period').should('exist').siblings('dd').should('have.text', '06/01/2025 to 05/30/2026') + cy.findByText('Date certified').should('exist').siblings('dd').should('have.text', '04/15/2025') + cy.findByText('Submission this rate was submitted with').should('exist').siblings('dd').should('have.text', submissionName) + cy.findByText('Certifying actuary').should('exist').siblings('dd').should('have.text', 'actuary1test titleemail@example.comMercer') + // cy.findByText('Download all rate documents').should('exist') + cy.findByRole('table', { + name: 'Rate certification', + }).should('exist') + cy.findByText('rate1Document1.pdf').should('exist') + cy.findByRole('table', { + name: 'Rate supporting documents', + }).should('exist') + }) + cy.findByText('rate1SupportingDocument1.pdf').should('exist') - cy.get('table') - .contains('a', submissionName) - .should('have.attr', 'href') + // Go back to dashboard and check both rates in the table + // check the dashboard has the columns we expect + cy.findByText('Back to dashboard').should('exist').click() + cy.url({ timeout: 10_000 }).should('contain', 'rate-reviews') + cy.findByText('Rate reviews').should('exist') + cy.get('thead').should('have.attr', 'data-testid', 'rate-reviews-table').should('be.visible') // can't put id on table itself because data attributes not passing through in react-uswds component - // can navigate to submission summary by clicking link - cy.findByText(submissionName).should('exist').click() - cy.url({ timeout: 10_000 }).should('contain', submissionId) - cy.findByTestId('submission-summary').should('exist') }) }) }) diff --git a/services/cypress/support/apiCommands.ts b/services/cypress/support/apiCommands.ts index edc9cf09b3..46c8d23cdf 100644 --- a/services/cypress/support/apiCommands.ts +++ b/services/cypress/support/apiCommands.ts @@ -75,6 +75,7 @@ const createAndSubmitContractOnlyPackage = async ( const createAndSubmitContractWithRates = async ( apolloClient: ApolloClient ): Promise => { + console.log('in here') const newSubmission1 = await apolloClient.mutate({ mutation: CreateHealthPlanPackageDocument, variables: { @@ -115,8 +116,6 @@ const createAndSubmitContractWithRates = async ( }, }, }) - - return submission1.data.submitHealthPlanPackage.pkg } @@ -177,6 +176,19 @@ Cypress.Commands.add( ) ) + +Cypress.Commands.add( + 'apiCreateAndSubmitContractWithRates', + (stateUser): Cypress.Chainable => + cy.task('readGraphQLSchema').then((schema) => + apolloClientWrapper( + schema, + stateUser, + createAndSubmitContractWithRates + ) + ) +) + Cypress.Commands.add( 'apiAssignDivisionToCMSUser', (cmsUser, division): Cypress.Chainable => diff --git a/services/cypress/support/commands.ts b/services/cypress/support/commands.ts index 14d9bdd4b2..484c339c30 100644 --- a/services/cypress/support/commands.ts +++ b/services/cypress/support/commands.ts @@ -50,6 +50,7 @@ Cypress.Commands.add('interceptGraphQL', () => { aliasQuery(req, 'fetchHealthPlanPackage') aliasQuery(req, 'fetchHealthPlanPackageWithQuestions') aliasQuery(req, 'indexHealthPlanPackages') + aliasQuery(req, 'indexRates') aliasMutation(req, 'createHealthPlanPackage') aliasMutation(req, 'updateHealthPlanFormData') aliasMutation(req, 'submitHealthPlanPackage') diff --git a/services/cypress/support/index.ts b/services/cypress/support/index.ts index 5764d2de93..f1391d3cd1 100644 --- a/services/cypress/support/index.ts +++ b/services/cypress/support/index.ts @@ -101,6 +101,7 @@ declare global { }): void apiCreateAndSubmitContractOnlySubmission(stateUser: StateUserType): Cypress.Chainable + apiCreateAndSubmitContractWithRates(stateUser: StateUserType): Cypress.Chainable apiAssignDivisionToCMSUser(cmsUser: CMSUserType, division: DivisionType): Cypress.Chainable interceptGraphQL(): void diff --git a/services/cypress/support/loginCommands.ts b/services/cypress/support/loginCommands.ts index 8024db89a2..6b2e5c2ab9 100644 --- a/services/cypress/support/loginCommands.ts +++ b/services/cypress/support/loginCommands.ts @@ -64,11 +64,15 @@ Cypress.Commands.add( cy.wait('@fetchCurrentUserQuery', { timeout: 20_000 }) if (initialURL?.includes('submissions')) { cy.wait('@fetchHealthPlanPackageWithQuestionsQuery', { timeout: 20_000 }) // for cases where CMs user goes to specific submission on login, likely from email link + } else if (initialURL?.includes('rate-reviews')) { + cy.wait('@indexRatesQuery', { timeout: 80_000 }) + cy.findByTestId('cms-dashboard-page',{timeout: 10_000 }).should('exist') + cy.findByRole('heading', {name: /rate reviews/}).should('exist') } else { - // Default behavior on login is to go to CMS dashboard + // Default behavior on login is to go to CMS dashboard submissions cy.wait('@indexHealthPlanPackagesQuery', { timeout: 80_000 }) cy.findByTestId('cms-dashboard-page',{timeout: 10_000 }).should('exist') - cy.findByRole('heading', {name: 'Submissions'}).should('exist') + cy.findByRole('heading', {name: /Submissions/}).should('exist') } } ) diff --git a/services/cypress/utils/apollo-test-utils.ts b/services/cypress/utils/apollo-test-utils.ts index 68a1cd96f3..6fb2f85fc3 100644 --- a/services/cypress/utils/apollo-test-utils.ts +++ b/services/cypress/utils/apollo-test-utils.ts @@ -1,4 +1,5 @@ import { AxiosResponse } from 'axios' +import { v4 as uuidv4 } from 'uuid' import { ApolloClient, DocumentNode, @@ -39,6 +40,30 @@ type DivisionType = 'DMCO' | 'DMCP' | 'OACT' type UserType = StateUserType | AdminUserType | CMSUserType +// programs for state used in tests +const minnesotaStatePrograms =[ + { + "id": "abbdf9b0-c49e-4c4c-bb6f-040cb7b51cce", + "fullName": "Special Needs Basic Care", + "name": "SNBC" + }, + { + "id": "d95394e5-44d1-45df-8151-1cc1ee66f100", + "fullName": "Prepaid Medical Assistance Program", + "name": "PMAP" + }, + { + "id": "ea16a6c0-5fc6-4df8-adac-c627e76660ab", + "fullName": "Minnesota Senior Care Plus ", + "name": "MSC+" + }, + { + "id": "3fd36500-bf2c-47bc-80e8-e7aa417184c5", + "fullName": "Minnesota Senior Health Options", + "name": "MSHO" + } +] + const contractOnlyData = (): Partial=> ({ stateContacts: [ { @@ -78,6 +103,7 @@ const contractOnlyData = (): Partial=> ({ }) const contractAndRatesData = (): Partial=> ({ + stateCode: 'MN', stateContacts: [ { name: 'Name', @@ -121,18 +147,22 @@ const contractAndRatesData = (): Partial=> ({ rateDateCertified: new Date(Date.UTC(2025, 3, 15)), rateDocuments: [ { - name: 'rateDocument.pdf', + name: 'rate1Document1.pdf', s3URL: 'fakeS3URL', sha256: 'fakesha', documentCategories: ['RATES' as const], }, ], - supportingDocuments: [], - //We only want one rate ID and use last program in list to differentiate from programID if possible. - rateProgramIDs: [ratePrograms.reverse()[0].id], + supportingDocuments: [ { + name: 'rate1SupportingDocument1.pdf', + s3URL: 'fakeS3URL', + sha256: 'fakesha', + documentCategories: ['RATES' as const], + }], + rateProgramIDs: [minnesotaStatePrograms[0].id], actuaryContacts: [ { - name: 'test name', + name: 'actuary1', titleRole: 'test title', email: 'email@example.com', actuarialFirm: 'MERCER' as const, @@ -142,19 +172,52 @@ const contractAndRatesData = (): Partial=> ({ actuaryCommunicationPreference: 'OACT_TO_ACTUARY' as const, packagesWithSharedRateCerts: [], }, + { + id: uuidv4(), + rateType: 'NEW' as const, + rateDateStart: new Date(Date.UTC(2030, 5, 1)), + rateDateEnd: new Date(Date.UTC(2036, 4, 30)), + rateDateCertified: new Date(Date.UTC(2035, 3, 15)), + rateDocuments: [ + { + name: 'rate2Document1.pdf', + s3URL: 'fakeS3URL', + sha256: 'fakesha', + documentCategories: ['RATES' as const], + }, + ], + supportingDocuments: [], + rateProgramIDs: [minnesotaStatePrograms[0].id], + actuaryContacts: [ + { + name: 'actuary2', + titleRole: 'test title', + email: 'email@example.com', + actuarialFirm: 'MERCER' as const, + actuarialFirmOther: '', + }, + ], + actuaryCommunicationPreference: 'OACT_TO_ACTUARY' as const, + packagesWithSharedRateCerts: [], + }, ] }) -const newSubmissionInput = (): Partial => ({ - populationCovered: 'MEDICAID', - programIDs: ['abbdf9b0-c49e-4c4c-bb6f-040cb7b51cce'], - submissionType: 'CONTRACT_ONLY', - riskBasedContract: false, - submissionDescription: 'Test Q&A', - contractType: 'BASE', -}) +const newSubmissionInput = (overrides?: Partial ): Partial => { + return Object.assign( + { + populationCovered: 'MEDICAID', + programIDs: [minnesotaStatePrograms[0].id], + submissionType: 'CONTRACT_ONLY', + riskBasedContract: false, + submissionDescription: 'Test Q&A', + contractType: 'BASE' + }, + overrides + ) + } const stateUser = ():StateUserType => ({ id: 'user1', @@ -368,10 +431,12 @@ const apolloClientWrapper = async ( export { apolloClientWrapper, contractOnlyData, + contractAndRatesData, newSubmissionInput, cmsUser, adminUser, stateUser, + minnesotaStatePrograms } export type { StateUserType,