From dd3f8d62d4380c2daaa16103017c586d3f1bba2f Mon Sep 17 00:00:00 2001 From: Jason Lin Date: Fri, 14 Jun 2024 14:22:10 -0400 Subject: [PATCH 01/13] Fix fillOutLinkedRate to link to another rate. --- .../support/stateSubmissionFormCommands.ts | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/services/cypress/support/stateSubmissionFormCommands.ts b/services/cypress/support/stateSubmissionFormCommands.ts index e1a7801e0e..4b0557124d 100644 --- a/services/cypress/support/stateSubmissionFormCommands.ts +++ b/services/cypress/support/stateSubmissionFormCommands.ts @@ -393,28 +393,31 @@ Cypress.Commands.add('fillOutNewRateCertification', () => { Cypress.Commands.add('fillOutLinkedRate', () => { // Must be on '/submissions/:id/edit/rate-details' // Must be a contract and rates submission - cy.getFeatureFlagStore(['link-rates']).then((store) => { - //If this flag value is true, then it will test this code hidden behind the feature flag - cy.findByRole('radiogroup', { - name: /Was this rate certification included with another submission?/, + // Must have existing rates to work. Otherwise, there will be no options + cy.getFeatureFlagStore(['link-rates']).then((store) => { + //If this flag value is true, then it will test this code hidden behind the feature flag + cy.findByRole('radiogroup', { + name: /Was this rate certification included with another submission?/, + }) + .should('exist') + .within(() => { + cy.findByText('Yes, this rate certification is part of another submission').click() }) - .should('exist') - .within(() => { - cy.findByText('Yes, this rate certification is part of another submission').click() - }) - - if (store['link-rates']) { - cy.findByRole('combobox', { name: 'Which rate certification was it?' }).click({ - force: true, - }) - cy.findAllByRole('option').first().click() - cy.findByText(/`Rate ID:/).should('be.visible') - } - cy.verifyDocumentsHaveNoErrors() - cy.waitForDocumentsToLoad() - cy.findAllByTestId('errorMessage').should('have.length', 0) - }) + if (store['link-rates']) { + cy.findByLabelText('Which rate certification was it?').should('be.visible').click({ + force: true + }) + let rateName = '' + cy.findAllByRole('option').first().within(current => { + cy.get('strong').should($div => { + rateName = $div.text() + }).click() + }).then(() => { + cy.root().findByRole('heading', { level: 3, name: `Rate ID: ${rateName}`}).should('exist') + }) + } + }) }) Cypress.Commands.add('fillOutAmendmentToPriorRateCertification', (id = 0) => { From 4168f92bb1ad017a47f389a5d92a38aabb8c821c Mon Sep 17 00:00:00 2001 From: Jason Lin Date: Fri, 14 Jun 2024 14:22:32 -0400 Subject: [PATCH 02/13] Use new API to create a rate. --- services/cypress/support/apiCommands.ts | 26 +++++++++++++++-- services/cypress/utils/apollo-test-utils.ts | 31 +++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/services/cypress/support/apiCommands.ts b/services/cypress/support/apiCommands.ts index 901ca24d7f..2e35142712 100644 --- a/services/cypress/support/apiCommands.ts +++ b/services/cypress/support/apiCommands.ts @@ -7,7 +7,7 @@ import { UserEdge, User, UpdateCmsUserDocument, - FetchCurrentUserDocument, + FetchCurrentUserDocument, UpdateContractRateInput, UpdateDraftContractRatesDocument, UpdateDraftContractRatesInput, } from '../gen/gqlClient' import { domainToBase64, @@ -20,6 +20,7 @@ import { contractOnlyData, contractAndRatesData, newSubmissionInput, + rateFormData, CMSUserType, } from '../utils/apollo-test-utils' import { ApolloClient, DocumentNode, NormalizedCacheObject } from '@apollo/client' @@ -90,9 +91,11 @@ const createAndSubmitContractWithRates = async ( throw new Error(formData1.message) } - const fullFormData1 = { + const fullFormData1: UnlockedHealthPlanFormDataType = { ...formData1, ...contractAndRatesData(), + status: 'DRAFT', + rateInfos: [] } const formDataProto = domainToBase64(fullFormData1 as UnlockedHealthPlanFormDataType) @@ -107,6 +110,25 @@ const createAndSubmitContractWithRates = async ( }, }) + // Using new API to create child rates + const updateDraftContractRatesInput: UpdateDraftContractRatesInput = { + contractID: pkg1.id, + updatedRates: [ + { + formData: rateFormData(), + rateID: undefined, + type: 'CREATE' + } + ] + } + + await apolloClient.mutate({ + mutation: UpdateDraftContractRatesDocument, + variables: { + input: updateDraftContractRatesInput + } + }) + const submission1 = await apolloClient.mutate({ mutation: SubmitHealthPlanPackageDocument, variables: { diff --git a/services/cypress/utils/apollo-test-utils.ts b/services/cypress/utils/apollo-test-utils.ts index 7dc1e86e9c..914f5c91a9 100644 --- a/services/cypress/utils/apollo-test-utils.ts +++ b/services/cypress/utils/apollo-test-utils.ts @@ -9,6 +9,7 @@ import { } from '@apollo/client' import { Amplify, Auth as AmplifyAuth, API } from 'aws-amplify' import { UnlockedHealthPlanFormDataType } from '../../app-web/src/common-code/healthPlanFormDataType' +import { RateFormDataInput } from '../gen/gqlClient'; type StateUserType = { id: string @@ -205,6 +206,35 @@ const contractAndRatesData = (): Partial=> ({ statutoryRegulatoryAttestationDescription: 'No compliance' }) +const rateFormData = (): RateFormDataInput => ({ + rateType: 'NEW', + rateCapitationType: 'RATE_CELL', + rateDocuments: [ + { + name: 'rate1Document1.pdf', + s3URL: 's3://local-uploads/1684382956834-rate1Document1.pdf/rate1Document1.pdf', + sha256: 'fakesha', + }, + ], + supportingDocuments: [], + rateDateStart: '2025-05-01', + rateDateEnd: '2026-04-30', + rateDateCertified: '2025-03-15', + rateProgramIDs: [minnesotaStatePrograms[0].id], + certifyingActuaryContacts: [ + { + name: 'actuary1', + titleRole: 'test title', + email: 'email@example.com', + actuarialFirm: 'MERCER' as const, + actuarialFirmOther: '', + }, + ], + deprecatedRateProgramIDs: [], + addtlActuaryContacts: [], + actuaryCommunicationPreference: 'OACT_TO_ACTUARY' as const, +}) + const newSubmissionInput = (overrides?: Partial ): Partial => { return Object.assign( { @@ -436,6 +466,7 @@ export { cmsUser, adminUser, stateUser, + rateFormData, minnesotaStatePrograms } export type { From 7e125eddc86232707e27191ac120a7c66be6a697 Mon Sep 17 00:00:00 2001 From: Jason Lin Date: Fri, 14 Jun 2024 14:22:59 -0400 Subject: [PATCH 03/13] Add test for submit, unlock, and resubmit linked rate submission. --- .../cmsWorkflow/unlockResubmit.spec.ts | 214 +++++++++++++++++- 1 file changed, 213 insertions(+), 1 deletion(-) diff --git a/services/cypress/integration/cmsWorkflow/unlockResubmit.spec.ts b/services/cypress/integration/cmsWorkflow/unlockResubmit.spec.ts index 0b099ef75b..65582cba7a 100644 --- a/services/cypress/integration/cmsWorkflow/unlockResubmit.spec.ts +++ b/services/cypress/integration/cmsWorkflow/unlockResubmit.spec.ts @@ -1,3 +1,5 @@ +import {stateUser} from '../../utils/apollo-test-utils'; + describe('CMS user', () => { beforeEach(() => { cy.stubFeatureFlags() @@ -454,6 +456,216 @@ describe('CMS user', () => { }) // TODO AFTER LINKED RATES AND LINKED RATES CHANGE HISTORY SHIPS - // it('can unlock and resubmit a linked rate and change history updates') + it.only('can unlock and resubmit a linked rate and change history updates', () => { + // turn on feature flag + cy.interceptFeatureFlags({'link-rates': true, '438-attestation': true}) + + // Set up a submission with linked rates + cy.apiCreateAndSubmitContractWithRates(stateUser()).then((pkg) => { + cy.logInAsStateUser() + + cy.startNewContractAndRatesSubmission() + cy.fillOutBaseContractDetails() + + cy.navigateFormByButtonClick('CONTINUE') + + cy.findByRole('heading', { level: 2, name: /Rate details/ }) + cy.fillOutLinkedRate() + cy.navigateContractRatesFormByButtonClick('CONTINUE') + + cy.findByRole('heading', { level: 2, name: /Contacts/ }) + cy.fillOutStateContact() + cy.navigateFormByButtonClick('CONTINUE') + + cy.findByRole('heading', { level: 2, name: /Supporting documents/ }) + cy.navigateFormByButtonClick('CONTINUE') + + cy.findByRole('heading', { level: 2, name: /Review and submit/ }) + + // Test unlock and resubmit with a linked rate submission + cy.location().then((fullUrl) => { + const reviewURL = fullUrl.toString() + const submissionURL = reviewURL.replace( + 'edit/review-and-submit', + '' + ) + + // Submit, sent to dashboard + cy.submitStateSubmissionForm() + + // Login as CMS User + cy.logOut() + cy.logInAsCMSUser({ initialURL: submissionURL }) + cy.wait('@fetchContractQuery', { timeout: 20_000 }) + // click on the unlock button, type in reason and confirm + cy.unlockSubmission() + + //Unlock banner for CMS user to be present with correct data. + cy.findByTestId('unlockedBanner') + .should('exist') + .and('contain.text', 'zuko@example.com') + .and('contain.text', 'Unlock submission reason.') + .contains( + /Unlocked on: (0?[1-9]|[12][0-9]|3[01])\/[0-9]+\/[0-9]+\s[0-9]+:[0-9]+[a-zA-Z]+ ET/i + ) + .should('exist') + + //Find unlocked submission name + cy.get('#submissionName', {timeout: 2_000}).then(($h2) => { + //Set name to variable for later use in finding the unlocked submission + const submissionName = $h2.text() + + // Login as state user + cy.logOut() + cy.logInAsStateUser() + + // State user sees unlocked submission - check tag then submission link + cy.get('table') + .should('exist') + .findByText(submissionName) + .parent() + .siblings('[data-testid="submission-status"]') + .should('have.text', 'Unlocked') + + cy.get('table') + .should('exist') + .findByText(submissionName) + .should('have.attr', 'href') + .and('include', 'review-and-submit') + + cy.navigateFormByDirectLink(reviewURL) + cy.wait('@fetchContractQuery', { timeout: 20_000 }) + + //Unlock banner for state user to be present with correct data. + cy.findByRole('heading', { + level: 2, + name: /Review and submit/, + }) + cy.findByRole('heading', { + name: `Minnesota ${submissionName}`, + }).should('exist') + cy.findByTestId('unlockedBanner') + .should('exist') + .and('contain.text', 'zuko@example.com') + .and('contain.text', 'Unlock submission reason.') + .contains( + /Unlocked on: (0?[1-9]|[12][0-9]|3[01])\/[0-9]+\/[0-9]+\s[0-9]+:[0-9]+[a-zA-Z]+ ET+/i + ) + .should('exist') + + cy.submitStateSubmissionForm({success: true, resubmission: true}) + + cy.get('table') + .should('exist') + .findByText(submissionName) + .parent() + .siblings('[data-testid="submission-status"]') + .should('have.text', 'Submitted') + + cy.get('table') + .findByText(submissionName) + .should('have.attr', 'href') + .and('not.include', 'review-and-submit') + + // Navigate to resubmitted submission and check for submission updated banner + cy.get('table') + .findByRole('link', { name: submissionName }) + .should('exist') + .click() + + cy.findByTestId('updatedSubmissionBanner').should('exist') + + // Login as CMS User + cy.logOut() + cy.logInAsCMSUser({ initialURL: submissionURL }) + cy.wait('@fetchContractQuery', { timeout: 20_000 }) + + // CMS user sees resubmitted submission and active unlock button + cy.findByTestId('submission-summary', {timeout: 4_000}).should('exist') + cy.findByRole('button', { name: 'Unlock submission' }).should( + 'not.be.disabled' + ) + + //CMS user should not see unlock banner and should see updated submission banner + cy.findByTestId('unlockedBanner').should('not.exist') + cy.findByTestId('updatedSubmissionBanner').should('exist') + + //Open all change history accordion items + cy.findByTestId('accordion').should('exist') + + cy.get('[data-testid^="accordionButton_"]').each((button) => { + button.trigger('click') + button.siblings().hasClass('usa-accordion__content') /// make sure accordion is expanded + }) + + //Check for view previous submission link in the initial accordion item to exist + cy.findByTestId('revision-link-1').should('be.visible') + cy.clickSubmissionLink('revision-link-1') + //Making sure we are on SubmissionRevisionSummary page and contains version text + cy.findByTestId('revision-version') + .should('exist') + .contains( + /(0?[1-9]|[12][0-9]|3[01])\/[0-9]+\/[0-9]+\s[0-9]+:[0-9]+[a-zA-Z]+ ET version/i + ) + //Previous submission banner should exist and able to click link to go back to current submission + cy.findByTestId('previous-submission-banner').should('exist') + //Navigate back to current submission using link inside banner. + cy.clickSubmissionLink('currentSubmissionLink') + //Make sure banner and revision version text are gone. + cy.findByTestId('previous-submission-banner').should( + 'not.exist' + ) + cy.findByTestId('revision-version').should('not.exist') + + // Unlock again and resubmit to test change history + cy.unlockSubmission('Second Unlock') + + // Resubmit again + cy.logOut() + cy.logInAsStateUser() + cy.navigateFormByDirectLink(reviewURL) + cy.wait('@fetchContractQuery', { timeout: 20_000 }) + cy.findByTestId('unlockedBanner').should('exist') + cy.submitStateSubmissionForm({ + success: true, + resubmission: true, + summary: 'Second resubmit' + } + ) + + // Visit the submission url and check the history + cy.navigateFormByDirectLink(submissionURL) + cy.findByTestId('updatedSubmissionBanner').should('exist') + + // No document dates or other fields are undefined + cy.findByText('N/A').should('not.exist') + + // Should have change history records + cy.findAllByTestId('change-history-record').should('have.length', 5) + + cy.findAllByTestId('change-history-record').then(records => { + // We put all the text of each record into an array + const recordText = records.map((index, record) => Cypress.$(record).text()) + + // Records are in reverse + // Second set of unlock and resubmit + expect(recordText[0]).to.contain('Changes made: Second resubmit') + expect(recordText[1]).to.contain('Reason for unlock: Second Unlock') + + // First set of unlock and resubmit + expect(recordText[2]).to.contain('Changes made: Resubmission summary') + expect(recordText[3]).to.contain('Reason for unlock: Unlock submission reason.') + + // Test for initial submission + expect(recordText[4]).to.contain('aang@example.com') + expect(recordText[4]).to.contain('View past submission version') + expect(recordText[4]).to.not.contain('Changes made:') + expect(recordText[4]).to.not.contain('Reason for unlock:') + + }) + }) + }) + }) + }) // it('can unlock and resubmit combination of linked and child rates as expected' ) }) From bf6bd34b96e043c6e7d0c95c8a975ececa703b6d Mon Sep 17 00:00:00 2001 From: Jason Lin Date: Fri, 14 Jun 2024 15:37:48 -0400 Subject: [PATCH 04/13] Change isRateProgram from string to boolean. --- services/cypress/utils/apollo-test-utils.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/services/cypress/utils/apollo-test-utils.ts b/services/cypress/utils/apollo-test-utils.ts index 914f5c91a9..ba38d05da5 100644 --- a/services/cypress/utils/apollo-test-utils.ts +++ b/services/cypress/utils/apollo-test-utils.ts @@ -42,30 +42,30 @@ type DivisionType = 'DMCO' | 'DMCP' | 'OACT' type UserType = StateUserType | AdminUserType | CMSUserType // programs for state used in tests -const minnesotaStatePrograms =[ +const minnesotaStatePrograms = [ { "id": "abbdf9b0-c49e-4c4c-bb6f-040cb7b51cce", "fullName": "Special Needs Basic Care", "name": "SNBC", - "isRateProgram": "false" + "isRateProgram": false }, { "id": "d95394e5-44d1-45df-8151-1cc1ee66f100", "fullName": "Prepaid Medical Assistance Program", "name": "PMAP", - "isRateProgram": "false" + "isRateProgram": false }, { "id": "ea16a6c0-5fc6-4df8-adac-c627e76660ab", "fullName": "Minnesota Senior Care Plus ", "name": "MSC+", - "isRateProgram": "false" + "isRateProgram": false }, { "id": "3fd36500-bf2c-47bc-80e8-e7aa417184c5", "fullName": "Minnesota Senior Health Options", "name": "MSHO", - "isRateProgram": "false" + "isRateProgram": false } ] From c25619f606f2b9d3fb584dd25031206c95f8d831 Mon Sep 17 00:00:00 2001 From: Jason Lin Date: Fri, 14 Jun 2024 16:56:09 -0400 Subject: [PATCH 05/13] Add another rate to `createAndSubmitContractWithRates` API command. --- services/cypress/support/apiCommands.ts | 19 +++++++++++++++++-- services/cypress/utils/apollo-test-utils.ts | 3 ++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/services/cypress/support/apiCommands.ts b/services/cypress/support/apiCommands.ts index 2e35142712..6ec558ef81 100644 --- a/services/cypress/support/apiCommands.ts +++ b/services/cypress/support/apiCommands.ts @@ -21,7 +21,7 @@ import { contractAndRatesData, newSubmissionInput, rateFormData, - CMSUserType, + CMSUserType, minnesotaStatePrograms, } from '../utils/apollo-test-utils' import { ApolloClient, DocumentNode, NormalizedCacheObject } from '@apollo/client' import {UnlockedHealthPlanFormDataType} from 'app-web/src/common-code/healthPlanFormDataType'; @@ -115,7 +115,22 @@ const createAndSubmitContractWithRates = async ( contractID: pkg1.id, updatedRates: [ { - formData: rateFormData(), + formData: rateFormData({ + rateDateStart: '2025-05-01', + rateDateEnd: '2026-04-30', + rateDateCertified: '2025-03-15', + rateProgramIDs: [minnesotaStatePrograms[0].id] + }), + rateID: undefined, + type: 'CREATE' + }, + { + formData: rateFormData({ + rateDateStart: '2024-03-01', + rateDateEnd: '2025-04-30', + rateDateCertified: '2025-03-15', + rateProgramIDs: [minnesotaStatePrograms[1].id] + }), rateID: undefined, type: 'CREATE' } diff --git a/services/cypress/utils/apollo-test-utils.ts b/services/cypress/utils/apollo-test-utils.ts index ba38d05da5..2c19d47a2d 100644 --- a/services/cypress/utils/apollo-test-utils.ts +++ b/services/cypress/utils/apollo-test-utils.ts @@ -206,7 +206,7 @@ const contractAndRatesData = (): Partial=> ({ statutoryRegulatoryAttestationDescription: 'No compliance' }) -const rateFormData = (): RateFormDataInput => ({ +const rateFormData = (data?: Partial): RateFormDataInput => ({ rateType: 'NEW', rateCapitationType: 'RATE_CELL', rateDocuments: [ @@ -233,6 +233,7 @@ const rateFormData = (): RateFormDataInput => ({ deprecatedRateProgramIDs: [], addtlActuaryContacts: [], actuaryCommunicationPreference: 'OACT_TO_ACTUARY' as const, + ...data }) const newSubmissionInput = (overrides?: Partial ): Partial => { From 9141ce9b824dc3482e1dce25619905893d392962 Mon Sep 17 00:00:00 2001 From: Jason Lin Date: Mon, 17 Jun 2024 10:37:35 -0400 Subject: [PATCH 06/13] Update `createAndSubmitContractWithRates` to return Contract instead of HPP. --- services/cypress/support/apiCommands.ts | 34 ++++++++++++++++----- services/cypress/support/index.ts | 4 +-- services/cypress/utils/apollo-test-utils.ts | 6 +++- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/services/cypress/support/apiCommands.ts b/services/cypress/support/apiCommands.ts index 6ec558ef81..aa5c9819b1 100644 --- a/services/cypress/support/apiCommands.ts +++ b/services/cypress/support/apiCommands.ts @@ -7,7 +7,10 @@ import { UserEdge, User, UpdateCmsUserDocument, - FetchCurrentUserDocument, UpdateContractRateInput, UpdateDraftContractRatesDocument, UpdateDraftContractRatesInput, + FetchCurrentUserDocument, + UpdateDraftContractRatesDocument, + UpdateDraftContractRatesInput, + FetchContractDocument, FetchContractPayload, Contract, } from '../gen/gqlClient' import { domainToBase64, @@ -76,7 +79,7 @@ const createAndSubmitContractOnlyPackage = async ( const createAndSubmitContractWithRates = async ( apolloClient: ApolloClient -): Promise => { +): Promise => { const newSubmission1 = await apolloClient.mutate({ mutation: CreateHealthPlanPackageDocument, variables: { @@ -116,9 +119,9 @@ const createAndSubmitContractWithRates = async ( updatedRates: [ { formData: rateFormData({ - rateDateStart: '2025-05-01', - rateDateEnd: '2026-04-30', - rateDateCertified: '2025-03-15', + rateDateStart: '2025-06-01', + rateDateEnd: '2026-05-30', + rateDateCertified: '2025-04-15', rateProgramIDs: [minnesotaStatePrograms[0].id] }), rateID: undefined, @@ -144,7 +147,9 @@ const createAndSubmitContractWithRates = async ( } }) - const submission1 = await apolloClient.mutate({ + // We will want to replace submit with the contract API when it's made for now we will + // make an additional call for the contract using fetchContract. + await apolloClient.mutate({ mutation: SubmitHealthPlanPackageDocument, variables: { input: { @@ -153,7 +158,20 @@ const createAndSubmitContractWithRates = async ( }, }, }) - return submission1.data.submitHealthPlanPackage.pkg + + // We are returning Contract instead of HPP because rate names are different between the two. + // Instead of fixing HPP rate names, lets just use Contract since HPP will go away. + const submission1 = await apolloClient.query({ + query: FetchContractDocument, + variables: { + input: { + contractID: pkg1.id + } + } + }) + + // Return with contract api FetchContract + return submission1.data.fetchContract.contract } @@ -216,7 +234,7 @@ Cypress.Commands.add( Cypress.Commands.add( 'apiCreateAndSubmitContractWithRates', - (stateUser): Cypress.Chainable => + (stateUser): Cypress.Chainable => cy.task('readGraphQLSchema').then((schema) => apolloClientWrapper( schema, diff --git a/services/cypress/support/index.ts b/services/cypress/support/index.ts index b5f7b92a46..041844f133 100644 --- a/services/cypress/support/index.ts +++ b/services/cypress/support/index.ts @@ -26,7 +26,7 @@ import { FeatureFlagSettings, } from '../../app-web/src/common-code/featureFlags' import './apiCommands' -import { HealthPlanPackage } from '../gen/gqlClient'; +import { HealthPlanPackage, Contract } from '../gen/gqlClient'; import { CMSUserType, DivisionType } from '../utils/apollo-test-utils'; import { StateUserType } from 'app-api/src/domain-models'; @@ -105,7 +105,7 @@ declare global { }): void apiCreateAndSubmitContractOnlySubmission(stateUser: StateUserType): Cypress.Chainable - apiCreateAndSubmitContractWithRates(stateUser: StateUserType): Cypress.Chainable + apiCreateAndSubmitContractWithRates(stateUser: StateUserType): Cypress.Chainable apiAssignDivisionToCMSUser(cmsUser: CMSUserType, division: DivisionType): Cypress.Chainable interceptGraphQL(): void diff --git a/services/cypress/utils/apollo-test-utils.ts b/services/cypress/utils/apollo-test-utils.ts index 2c19d47a2d..b1c30d0bb2 100644 --- a/services/cypress/utils/apollo-test-utils.ts +++ b/services/cypress/utils/apollo-test-utils.ts @@ -216,7 +216,11 @@ const rateFormData = (data?: Partial): RateFormDataInput => ( sha256: 'fakesha', }, ], - supportingDocuments: [], + supportingDocuments: [ { + name: 'rate1SupportingDocument1.pdf', + s3URL: 's3://local-uploads/1684382956834-rate1SupportingDocument1.pdf/rate1SupportingDocument1.pdf', + sha256: 'fakesha2', + }], rateDateStart: '2025-05-01', rateDateEnd: '2026-04-30', rateDateCertified: '2025-03-15', From eb7c457262cf236f87a127e994c345a496ea888a Mon Sep 17 00:00:00 2001 From: Jason Lin Date: Mon, 17 Jun 2024 10:37:57 -0400 Subject: [PATCH 07/13] Refactor test to use Contract instead of HPP. --- .../cmsWorkflow/rateReview.spec.ts | 88 ++++++++++--------- 1 file changed, 46 insertions(+), 42 deletions(-) diff --git a/services/cypress/integration/cmsWorkflow/rateReview.spec.ts b/services/cypress/integration/cmsWorkflow/rateReview.spec.ts index b4e03ffed7..b8beb283ca 100644 --- a/services/cypress/integration/cmsWorkflow/rateReview.spec.ts +++ b/services/cypress/integration/cmsWorkflow/rateReview.spec.ts @@ -29,49 +29,54 @@ describe('CMS user can view rate reviews', () => { // 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') + (contract) => { + + const latestSubmission = contract.packageSubmissions[0] + + const rate1 = latestSubmission.rateRevisions[0] + const rate2 = latestSubmission.rateRevisions[1] + let rate1Name = rate1.formData.rateCertificationName + let rate2Name = rate2.formData.rateCertificationName + + if (!rate1Name || !rate2Name) { + throw new Error(`Unexpected error: Rate name(s) did not exist. Rate1Name: ${rate1Name}, Rate2Name: ${rate2Name}`) + } - // 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') + // Then check both rates in rate reviews table + cy.logInAsCMSUser({ + initialURL: `/dashboard/rate-reviews`, }) + + // Rate names can be the same, rare in prod, but common in automated tests and manual tests. + // Here were just checking to make sure the first exists in findAll. + cy.get('table') + .findAllByRole('link', { name: rate1Name }).first() + .should('exist') + cy.get('table') + .findAllByRole('link', { name: rate2Name }).first() + .should('exist') + + // click the first rate to navigate to rate summary page + cy.get('table') + .findAllByRole('link', { name: rate1Name }).first().click() + cy.url({ timeout: 10_000 }).should('contain',rate1.rateID) + cy.findByRole('heading', { + name: `${rate1.formData.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', latestSubmission.contractRevision.contractName) + 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') // No document dates or other fields are undefined @@ -83,7 +88,6 @@ describe('CMS user can view rate reviews', () => { 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 - }) }) }) From 95d98724ae93f89dd529e4f5994b8e17bc21a769 Mon Sep 17 00:00:00 2001 From: Jason Lin Date: Mon, 17 Jun 2024 12:49:07 -0400 Subject: [PATCH 08/13] Set link-rates flag again. --- .../cypress/integration/cmsWorkflow/unlockResubmit.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/cypress/integration/cmsWorkflow/unlockResubmit.spec.ts b/services/cypress/integration/cmsWorkflow/unlockResubmit.spec.ts index 65582cba7a..856eb79433 100644 --- a/services/cypress/integration/cmsWorkflow/unlockResubmit.spec.ts +++ b/services/cypress/integration/cmsWorkflow/unlockResubmit.spec.ts @@ -461,7 +461,7 @@ describe('CMS user', () => { cy.interceptFeatureFlags({'link-rates': true, '438-attestation': true}) // Set up a submission with linked rates - cy.apiCreateAndSubmitContractWithRates(stateUser()).then((pkg) => { + cy.apiCreateAndSubmitContractWithRates(stateUser()).then((contract) => { cy.logInAsStateUser() cy.startNewContractAndRatesSubmission() @@ -473,6 +473,8 @@ describe('CMS user', () => { cy.fillOutLinkedRate() cy.navigateContractRatesFormByButtonClick('CONTINUE') + // Set link-rates to true again. + cy.interceptFeatureFlags({'link-rates': true, '438-attestation': true}) cy.findByRole('heading', { level: 2, name: /Contacts/ }) cy.fillOutStateContact() cy.navigateFormByButtonClick('CONTINUE') From 7d442c1a164cae3c0cb94e9062b755d06d27c65b Mon Sep 17 00:00:00 2001 From: Jason Lin Date: Mon, 17 Jun 2024 13:31:52 -0400 Subject: [PATCH 09/13] Use submitContract. --- .../cmsWorkflow/unlockResubmit.spec.ts | 4 +-- services/cypress/support/apiCommands.ts | 25 ++++--------------- 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/services/cypress/integration/cmsWorkflow/unlockResubmit.spec.ts b/services/cypress/integration/cmsWorkflow/unlockResubmit.spec.ts index 856eb79433..6745a4af64 100644 --- a/services/cypress/integration/cmsWorkflow/unlockResubmit.spec.ts +++ b/services/cypress/integration/cmsWorkflow/unlockResubmit.spec.ts @@ -461,7 +461,7 @@ describe('CMS user', () => { cy.interceptFeatureFlags({'link-rates': true, '438-attestation': true}) // Set up a submission with linked rates - cy.apiCreateAndSubmitContractWithRates(stateUser()).then((contract) => { + cy.apiCreateAndSubmitContractWithRates(stateUser()).then(() => { cy.logInAsStateUser() cy.startNewContractAndRatesSubmission() @@ -473,8 +473,6 @@ describe('CMS user', () => { cy.fillOutLinkedRate() cy.navigateContractRatesFormByButtonClick('CONTINUE') - // Set link-rates to true again. - cy.interceptFeatureFlags({'link-rates': true, '438-attestation': true}) cy.findByRole('heading', { level: 2, name: /Contacts/ }) cy.fillOutStateContact() cy.navigateFormByButtonClick('CONTINUE') diff --git a/services/cypress/support/apiCommands.ts b/services/cypress/support/apiCommands.ts index aa5c9819b1..efc35aef0a 100644 --- a/services/cypress/support/apiCommands.ts +++ b/services/cypress/support/apiCommands.ts @@ -10,7 +10,7 @@ import { FetchCurrentUserDocument, UpdateDraftContractRatesDocument, UpdateDraftContractRatesInput, - FetchContractDocument, FetchContractPayload, Contract, + Contract, SubmitContractDocument, } from '../gen/gqlClient' import { domainToBase64, @@ -147,31 +147,16 @@ const createAndSubmitContractWithRates = async ( } }) - // We will want to replace submit with the contract API when it's made for now we will - // make an additional call for the contract using fetchContract. - await apolloClient.mutate({ - mutation: SubmitHealthPlanPackageDocument, + const submission = await apolloClient.mutate({ + mutation: SubmitContractDocument, variables: { input: { - pkgID: pkg1.id, - submittedReason: 'Submit package for Rates Dashboard tests', + contractID: pkg1.id, }, }, }) - // We are returning Contract instead of HPP because rate names are different between the two. - // Instead of fixing HPP rate names, lets just use Contract since HPP will go away. - const submission1 = await apolloClient.query({ - query: FetchContractDocument, - variables: { - input: { - contractID: pkg1.id - } - } - }) - - // Return with contract api FetchContract - return submission1.data.fetchContract.contract + return submission.data.submitContract.contract } From b307f3d0e6bc8249e89e70398796e46efbc38ad2 Mon Sep 17 00:00:00 2001 From: Jason Lin Date: Mon, 17 Jun 2024 14:52:48 -0400 Subject: [PATCH 10/13] work-around for inability of toggling flags on the backend. --- .../cmsWorkflow/unlockResubmit.spec.ts | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/services/cypress/integration/cmsWorkflow/unlockResubmit.spec.ts b/services/cypress/integration/cmsWorkflow/unlockResubmit.spec.ts index 6745a4af64..8c6bd5c312 100644 --- a/services/cypress/integration/cmsWorkflow/unlockResubmit.spec.ts +++ b/services/cypress/integration/cmsWorkflow/unlockResubmit.spec.ts @@ -227,7 +227,7 @@ describe('CMS user', () => { }) }) - it.only('can unlock and resubmit child rates with linked rates flag', () => { + it('can unlock and resubmit child rates with linked rates flag', () => { cy.interceptFeatureFlags({"link-rates": true, '438-attestation': true}) cy.logInAsStateUser() @@ -464,31 +464,44 @@ describe('CMS user', () => { cy.apiCreateAndSubmitContractWithRates(stateUser()).then(() => { cy.logInAsStateUser() + // This section still uses old API, so we want to do that first before using the new link-rates UI cy.startNewContractAndRatesSubmission() cy.fillOutBaseContractDetails() cy.navigateFormByButtonClick('CONTINUE') - cy.findByRole('heading', { level: 2, name: /Rate details/ }) - cy.fillOutLinkedRate() - cy.navigateContractRatesFormByButtonClick('CONTINUE') - - cy.findByRole('heading', { level: 2, name: /Contacts/ }) - cy.fillOutStateContact() - cy.navigateFormByButtonClick('CONTINUE') - - cy.findByRole('heading', { level: 2, name: /Supporting documents/ }) - cy.navigateFormByButtonClick('CONTINUE') - - cy.findByRole('heading', { level: 2, name: /Review and submit/ }) // Test unlock and resubmit with a linked rate submission cy.location().then((fullUrl) => { - const reviewURL = fullUrl.toString() - const submissionURL = reviewURL.replace( - 'edit/review-and-submit', + const submissionURL = fullUrl.toString().replace( + 'edit/rate-details', '' ) + const reviewURL = `${submissionURL}edit/review-and-submit` + + /** + * The updateHealthPlanFormData endpoint fails to update rates when the flag is on, causing an error after + * the rate details page due to Cypress's inability to toggle backend flags. This is due to the API's + * attempt to update the new rate format with HPP. The solution is to apply old API updates before new + * rate API updates in a non-sequential order. + */ + cy.navigateFormByDirectLink(`${submissionURL}edit/contacts`) + cy.findByRole('heading', { level: 2, name: /Contacts/ }) + cy.fillOutStateContact() + + cy.navigateFormByButtonClick('CONTINUE') + cy.findByRole('heading', { level: 2, name: /Supporting documents/ }) + + // New API + cy.navigateFormByDirectLink(`${submissionURL}edit/rate-details`) + cy.findByRole('heading', { level: 2, name: /Rate details/ }) + cy.fillOutLinkedRate() + + cy.navigateContractRatesFormByButtonClick('CONTINUE') + cy.findByRole('heading', { level: 2, name: /Contacts/ }) + + cy.navigateFormByDirectLink(`${submissionURL}edit/review-and-submit`) + cy.findByRole('heading', { level: 2, name: /Review and submit/ }) // Submit, sent to dashboard cy.submitStateSubmissionForm() From 748719d6c0d206a53664288f9a85155042d7478d Mon Sep 17 00:00:00 2001 From: Jason Lin Date: Mon, 17 Jun 2024 15:11:36 -0400 Subject: [PATCH 11/13] Find rate by testid. --- .../RateReviewsDashboard/RateReviewsTable.tsx | 6 +++--- .../integration/cmsWorkflow/rateReview.spec.ts | 16 +++------------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/services/app-web/src/pages/CMSDashboard/RateReviewsDashboard/RateReviewsTable.tsx b/services/app-web/src/pages/CMSDashboard/RateReviewsDashboard/RateReviewsTable.tsx index a45234174c..12ba2faef9 100644 --- a/services/app-web/src/pages/CMSDashboard/RateReviewsDashboard/RateReviewsTable.tsx +++ b/services/app-web/src/pages/CMSDashboard/RateReviewsDashboard/RateReviewsTable.tsx @@ -54,7 +54,7 @@ declare module '@tanstack/table-core' { export type RateInDashboardType = { id: string name: string - rateNumber: number, + rateNumber: number submittedAt: string updatedAt: Date status: HealthPlanPackageStatus @@ -251,6 +251,7 @@ export const RateReviewsTable = ({ asCustom={NavLink} to={rateURL(info.getValue())} className={`${styles.ID}`} + data-testid={`rate-link-${info.getValue().id}`} > {info.getValue().name} @@ -343,7 +344,7 @@ export const RateReviewsTable = ({ ), initialState: { columnVisibility: { - rateNumber: isAdminUser, + rateNumber: isAdminUser, }, }, columns: tableColumns, @@ -359,7 +360,6 @@ export const RateReviewsTable = ({ getFilteredRowModel: getFilteredRowModel(), getSortedRowModel: getSortedRowModel(), getFacetedMinMaxValues: getFacetedMinMaxValues(), - }) const filteredRows = reactTable.getRowModel().rows diff --git a/services/cypress/integration/cmsWorkflow/rateReview.spec.ts b/services/cypress/integration/cmsWorkflow/rateReview.spec.ts index b8beb283ca..d737beee50 100644 --- a/services/cypress/integration/cmsWorkflow/rateReview.spec.ts +++ b/services/cypress/integration/cmsWorkflow/rateReview.spec.ts @@ -35,30 +35,20 @@ describe('CMS user can view rate reviews', () => { const rate1 = latestSubmission.rateRevisions[0] const rate2 = latestSubmission.rateRevisions[1] - let rate1Name = rate1.formData.rateCertificationName - let rate2Name = rate2.formData.rateCertificationName - - if (!rate1Name || !rate2Name) { - throw new Error(`Unexpected error: Rate name(s) did not exist. Rate1Name: ${rate1Name}, Rate2Name: ${rate2Name}`) - } // Then check both rates in rate reviews table cy.logInAsCMSUser({ initialURL: `/dashboard/rate-reviews`, }) - // Rate names can be the same, rare in prod, but common in automated tests and manual tests. - // Here were just checking to make sure the first exists in findAll. cy.get('table') - .findAllByRole('link', { name: rate1Name }).first() - .should('exist') + .findByTestId(`rate-link-${rate1.rateID}`).should('exist') cy.get('table') - .findAllByRole('link', { name: rate2Name }).first() - .should('exist') + .findByTestId(`rate-link-${rate2.rateID}`).should('exist') // click the first rate to navigate to rate summary page cy.get('table') - .findAllByRole('link', { name: rate1Name }).first().click() + .findByTestId(`rate-link-${rate1.rateID}`).click() cy.url({ timeout: 10_000 }).should('contain',rate1.rateID) cy.findByRole('heading', { name: `${rate1.formData.rateCertificationName}`, From 9d1ec336b4f7d87d1dada23bc08113046be3ec49 Mon Sep 17 00:00:00 2001 From: Jason Lin Date: Mon, 17 Jun 2024 15:46:28 -0400 Subject: [PATCH 12/13] Add missing contract data. --- services/cypress/utils/apollo-test-utils.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/services/cypress/utils/apollo-test-utils.ts b/services/cypress/utils/apollo-test-utils.ts index b1c30d0bb2..56397aaec9 100644 --- a/services/cypress/utils/apollo-test-utils.ts +++ b/services/cypress/utils/apollo-test-utils.ts @@ -69,7 +69,8 @@ const minnesotaStatePrograms = [ } ] -const contractOnlyData = (): Partial=> ({ +const contractOnlyData = (): Partial => ({ + stateCode: 'MN', stateContacts: [ { name: 'Name', @@ -104,7 +105,8 @@ const contractOnlyData = (): Partial=> ({ managedCareEntities: ['MCO'], federalAuthorities: ['STATE_PLAN'], rateInfos: [], - statutoryRegulatoryAttestation: true + statutoryRegulatoryAttestation: true, + programIDs: [minnesotaStatePrograms[0].id] }) const contractAndRatesData = (): Partial=> ({ @@ -203,7 +205,8 @@ const contractAndRatesData = (): Partial=> ({ }, ], statutoryRegulatoryAttestation: false, - statutoryRegulatoryAttestationDescription: 'No compliance' + statutoryRegulatoryAttestationDescription: 'No compliance', + programIDs: [minnesotaStatePrograms[0].id] }) const rateFormData = (data?: Partial): RateFormDataInput => ({ From 3a1e058c544fead2988f506b45ecbcfb82d99bbc Mon Sep 17 00:00:00 2001 From: Jason Lin Date: Mon, 17 Jun 2024 15:47:55 -0400 Subject: [PATCH 13/13] Turn test back on --- services/cypress/integration/cmsWorkflow/unlockResubmit.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/cypress/integration/cmsWorkflow/unlockResubmit.spec.ts b/services/cypress/integration/cmsWorkflow/unlockResubmit.spec.ts index 8c6bd5c312..4b57240b78 100644 --- a/services/cypress/integration/cmsWorkflow/unlockResubmit.spec.ts +++ b/services/cypress/integration/cmsWorkflow/unlockResubmit.spec.ts @@ -227,7 +227,7 @@ describe('CMS user', () => { }) }) - it('can unlock and resubmit child rates with linked rates flag', () => { + it.only('can unlock and resubmit child rates with linked rates flag', () => { cy.interceptFeatureFlags({"link-rates": true, '438-attestation': true}) cy.logInAsStateUser()