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 e69de29bb2..8866d1ab69 100644 --- a/services/cypress/integration/cmsWorkflow/rateReview.spec.ts +++ b/services/cypress/integration/cmsWorkflow/rateReview.spec.ts @@ -0,0 +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() + }) + // 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) + } + + const unwrapResult = base64ToDomain(latestRevision.formDataProto) + if (unwrapResult instanceof Error) { + throw unwrapResult + } + + return unwrapResult + } + + + it('and navigate to a specific rate from the rates dashboard', () => { + cy.interceptFeatureFlags({ + 'rates-db-refactor':true, + 'rate-reviews-dashboard': true + }) + + cy.apiAssignDivisionToCMSUser(cmsUser(), 'DMCO').then(() => { + + // 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') + cy.get('table') + .findByRole('link', { name: rate2.rateCertificationName }) + .should('exist') + + // 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') + + // 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 + + }) + }) +}) diff --git a/services/cypress/support/apiCommands.ts b/services/cypress/support/apiCommands.ts index ccd2ca8f31..e19fa65f83 100644 --- a/services/cypress/support/apiCommands.ts +++ b/services/cypress/support/apiCommands.ts @@ -18,6 +18,7 @@ import { DivisionType, adminUser, contractOnlyData, + contractAndRatesData, newSubmissionInput, CMSUserType, } from '../utils/apollo-test-utils' @@ -71,6 +72,53 @@ const createAndSubmitContractOnlyPackage = async ( return submission.data.submitHealthPlanPackage.pkg } +const createAndSubmitContractWithRates = async ( + apolloClient: ApolloClient +): Promise => { + const newSubmission1 = await apolloClient.mutate({ + mutation: CreateHealthPlanPackageDocument, + variables: { + input: newSubmissionInput({submissionType: 'CONTRACT_AND_RATES'}), + }, + }) + const pkg1 = newSubmission1.data.createHealthPlanPackage.pkg + const pkg1FirstRev = pkg1.revisions[0].node + + const formData1 = base64ToDomain(pkg1FirstRev.formDataProto) + if (formData1 instanceof Error) { + throw new Error(formData1.message) + } + + const fullFormData1 = { + ...formData1, + ...contractAndRatesData(), + } + + const formDataProto = domainToBase64(fullFormData1) + + await apolloClient.mutate({ + mutation: UpdateHealthPlanFormDataDocument, + variables: { + input: { + healthPlanFormData: formDataProto, + pkgID: pkg1.id, + }, + }, + }) + + const submission1 = await apolloClient.mutate({ + mutation: SubmitHealthPlanPackageDocument, + variables: { + input: { + pkgID: pkg1.id, + submittedReason: 'Submit package for Rates Dashboard tests', + }, + }, + }) + return submission1.data.submitHealthPlanPackage.pkg +} + + const assignCmsDivision = async ( apolloClient: ApolloClient, cmsUser: CMSUserType, @@ -127,6 +175,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 4e0e11cc67..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: [ { @@ -77,15 +102,123 @@ const contractOnlyData = (): Partial=> ({ rateInfos: [], }) -const newSubmissionInput = (): Partial => ({ - populationCovered: 'MEDICAID', - programIDs: ['abbdf9b0-c49e-4c4c-bb6f-040cb7b51cce'], - submissionType: 'CONTRACT_ONLY', - riskBasedContract: false, - submissionDescription: 'Test Q&A', - contractType: 'BASE', +const contractAndRatesData = (): Partial=> ({ + stateCode: 'MN', + stateContacts: [ + { + name: 'Name', + titleRole: 'Title', + email: 'example@example.com', + }, + ], + addtlActuaryContacts: [], + documents: [], + contractExecutionStatus: 'EXECUTED' as const, + contractDocuments: [ + { + name: 'Contract Cert.pdf', + s3URL: 's3://local-uploads/1684382956834-Contract Cert.pdf/Contract Cert.pdf', + documentCategories: ['CONTRACT'], + sha256: 'abc123', + }, + ], + contractDateStart: new Date('2023-05-01T00:00:00.000Z'), + contractDateEnd: new Date('2023-05-31T00:00:00.000Z'), + contractAmendmentInfo: { + modifiedProvisions: { + inLieuServicesAndSettings: false, + modifiedRiskSharingStrategy: false, + modifiedIncentiveArrangements: false, + modifiedWitholdAgreements: false, + modifiedStateDirectedPayments: false, + modifiedPassThroughPayments: false, + modifiedPaymentsForMentalDiseaseInstitutions: false, + modifiedNonRiskPaymentArrangements: false, + }, + }, + managedCareEntities: ['MCO'], + federalAuthorities: ['STATE_PLAN'], + rateInfos:[ + { + 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: 'rate1Document1.pdf', + s3URL: 'fakeS3URL', + sha256: 'fakesha', + documentCategories: ['RATES' as const], + }, + ], + supportingDocuments: [ { + name: 'rate1SupportingDocument1.pdf', + s3URL: 'fakeS3URL', + sha256: 'fakesha', + documentCategories: ['RATES' as const], + }], + rateProgramIDs: [minnesotaStatePrograms[0].id], + actuaryContacts: [ + { + name: 'actuary1', + titleRole: 'test title', + email: 'email@example.com', + actuarialFirm: 'MERCER' as const, + actuarialFirmOther: '', + }, + ], + 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 = (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', email: 'aang@example.com', @@ -298,10 +431,12 @@ const apolloClientWrapper = async ( export { apolloClientWrapper, contractOnlyData, + contractAndRatesData, newSubmissionInput, cmsUser, adminUser, stateUser, + minnesotaStatePrograms } export type { StateUserType,