diff --git a/services/app-api/prisma/migrations/20231113040740_add_statutory_regulatory_attestation/migration.sql b/services/app-api/prisma/migrations/20231113040740_add_statutory_regulatory_attestation/migration.sql new file mode 100644 index 0000000000..c718b645a3 --- /dev/null +++ b/services/app-api/prisma/migrations/20231113040740_add_statutory_regulatory_attestation/migration.sql @@ -0,0 +1,6 @@ +BEGIN; + +-- AlterTable +ALTER TABLE "ContractRevisionTable" ADD COLUMN "statutoryRegulatoryAttestation" BOOLEAN; + +COMMIT; diff --git a/services/app-api/prisma/schema.prisma b/services/app-api/prisma/schema.prisma index 3f89e14ca5..9cae39f028 100644 --- a/services/app-api/prisma/schema.prisma +++ b/services/app-api/prisma/schema.prisma @@ -115,6 +115,7 @@ model ContractRevisionTable { modifiedLengthOfContract Boolean? modifiedNonRiskPaymentArrangements Boolean? inLieuServicesAndSettings Boolean? + statutoryRegulatoryAttestation Boolean? } model RateRevisionTable { diff --git a/services/app-api/src/domain-models/contractAndRates/convertContractWithRatesToHPP.ts b/services/app-api/src/domain-models/contractAndRates/convertContractWithRatesToHPP.ts index dacdbee7db..40d6f0b1b3 100644 --- a/services/app-api/src/domain-models/contractAndRates/convertContractWithRatesToHPP.ts +++ b/services/app-api/src/domain-models/contractAndRates/convertContractWithRatesToHPP.ts @@ -231,6 +231,8 @@ function convertContractWithRatesToFormData( contractRev.formData.modifiedNonRiskPaymentArrangements, }, }, + statutoryRegulatoryAttestation: + contractRev.formData.statutoryRegulatoryAttestation, rateInfos, } diff --git a/services/app-api/src/domain-models/contractAndRates/formDataTypes.ts b/services/app-api/src/domain-models/contractAndRates/formDataTypes.ts index 630aa73b28..ec52c86414 100644 --- a/services/app-api/src/domain-models/contractAndRates/formDataTypes.ts +++ b/services/app-api/src/domain-models/contractAndRates/formDataTypes.ts @@ -64,6 +64,7 @@ const contractFormDataSchema = z.object({ modifiedNetworkAdequacyStandards: z.boolean().optional(), modifiedLengthOfContract: z.boolean().optional(), modifiedNonRiskPaymentArrangements: z.boolean().optional(), + statutoryRegulatoryAttestation: z.boolean().optional(), }) const rateFormDataSchema = z.object({ diff --git a/services/app-api/src/postgres/contractAndRates/prismaSharedContractRateHelpers.ts b/services/app-api/src/postgres/contractAndRates/prismaSharedContractRateHelpers.ts index 18ed03cb70..41496233f6 100644 --- a/services/app-api/src/postgres/contractAndRates/prismaSharedContractRateHelpers.ts +++ b/services/app-api/src/postgres/contractAndRates/prismaSharedContractRateHelpers.ts @@ -339,6 +339,8 @@ function contractFormDataToDomainModel( contractRevision.modifiedNonRiskPaymentArrangements ?? undefined, inLieuServicesAndSettings: contractRevision.inLieuServicesAndSettings ?? undefined, + statutoryRegulatoryAttestation: + contractRevision.statutoryRegulatoryAttestation ?? undefined, } } diff --git a/services/app-api/src/postgres/contractAndRates/unlockContract.ts b/services/app-api/src/postgres/contractAndRates/unlockContract.ts index 2628919594..2cda27d7d1 100644 --- a/services/app-api/src/postgres/contractAndRates/unlockContract.ts +++ b/services/app-api/src/postgres/contractAndRates/unlockContract.ts @@ -141,6 +141,8 @@ async function unlockContract( currentRev.modifiedLengthOfContract, modifiedNonRiskPaymentArrangements: currentRev.modifiedNonRiskPaymentArrangements, + statutoryRegulatoryAttestation: + currentRev.statutoryRegulatoryAttestation, contractDocuments: { create: currentRev.contractDocuments.map((d) => ({ diff --git a/services/app-api/src/postgres/contractAndRates/updateDraftContractWithRates.ts b/services/app-api/src/postgres/contractAndRates/updateDraftContractWithRates.ts index 77b0092ee6..4e11935a89 100644 --- a/services/app-api/src/postgres/contractAndRates/updateDraftContractWithRates.ts +++ b/services/app-api/src/postgres/contractAndRates/updateDraftContractWithRates.ts @@ -130,6 +130,7 @@ async function updateDraftContractWithRates( modifiedLengthOfContract, modifiedNonRiskPaymentArrangements, inLieuServicesAndSettings, + statutoryRegulatoryAttestation, } = formData try { @@ -504,6 +505,9 @@ async function updateDraftContractWithRates( modifiedNonRiskPaymentArrangements: nullify( modifiedNonRiskPaymentArrangements ), + statutoryRegulatoryAttestation: nullify( + statutoryRegulatoryAttestation + ), draftRates: { disconnect: updateRates?.disconnectRates ? updateRates.disconnectRates.map((rate) => ({ diff --git a/services/app-api/src/resolvers/healthPlanPackage/submitHealthPlanPackage.test.ts b/services/app-api/src/resolvers/healthPlanPackage/submitHealthPlanPackage.test.ts index ac886eecd2..8904376bec 100644 --- a/services/app-api/src/resolvers/healthPlanPackage/submitHealthPlanPackage.test.ts +++ b/services/app-api/src/resolvers/healthPlanPackage/submitHealthPlanPackage.test.ts @@ -57,10 +57,12 @@ describe.each(flagValueTestParameters)( `Tests $testName`, ({ flagName, flagValue }) => { const cmsUser = testCMSUser() - const mockLDService = testLDService({ [flagName]: flagValue }) + const mockLDService = testLDService({ + [flagName]: flagValue, + }) afterEach(() => { - jest.clearAllMocks() + jest.restoreAllMocks() }) it('returns a StateSubmission if complete', async () => { const server = await constructTestPostgresServer({ @@ -1252,5 +1254,74 @@ describe.each(flagValueTestParameters)( ) }, 20000) }) + + describe('Feature flagged 4348 attestation question test', () => { + const ldService = testLDService({ + ...mockLDService, + '438-attestation': true, + }) + + it('errors when contract 4348 attestation question is undefined', async () => { + const server = await constructTestPostgresServer({ + ldService: ldService, + }) + + // setup + const initialPkg = await createAndUpdateTestHealthPlanPackage( + server, + { + statutoryRegulatoryAttestation: undefined, + } + ) + const draft = latestFormData(initialPkg) + const draftID = draft.id + + await new Promise((resolve) => setTimeout(resolve, 2000)) + + // submit + const submitResult = await server.executeOperation({ + query: SUBMIT_HEALTH_PLAN_PACKAGE, + variables: { + input: { + pkgID: draftID, + }, + }, + }) + + expect(submitResult.errors).toBeDefined() + expect(submitResult.errors?.[0].extensions?.message).toBe( + 'formData is missing required contract fields' + ) + }, 20000) + it('successfully submits when contract 4348 attestation question is valid', async () => { + const server = await constructTestPostgresServer({ + ldService: ldService, + }) + + // setup + const initialPkg = await createAndUpdateTestHealthPlanPackage( + server, + { + statutoryRegulatoryAttestation: false, + } + ) + const draft = latestFormData(initialPkg) + const draftID = draft.id + + await new Promise((resolve) => setTimeout(resolve, 2000)) + + // submit + const submitResult = await server.executeOperation({ + query: SUBMIT_HEALTH_PLAN_PACKAGE, + variables: { + input: { + pkgID: draftID, + }, + }, + }) + + expect(submitResult.errors).toBeUndefined() + }, 20000) + }) } ) diff --git a/services/app-api/src/resolvers/healthPlanPackage/submitHealthPlanPackage.ts b/services/app-api/src/resolvers/healthPlanPackage/submitHealthPlanPackage.ts index b74e363735..c3cbab5fb7 100644 --- a/services/app-api/src/resolvers/healthPlanPackage/submitHealthPlanPackage.ts +++ b/services/app-api/src/resolvers/healthPlanPackage/submitHealthPlanPackage.ts @@ -36,7 +36,10 @@ import type { HealthPlanFormDataType, LockedHealthPlanFormDataType, } from '../../../../app-web/src/common-code/healthPlanFormDataType' -import type { LDService } from '../../launchDarkly/launchDarkly' +import type { + FeatureFlagSettings, + LDService, +} from '../../launchDarkly/launchDarkly' import { convertContractWithRatesToFormData, convertContractWithRatesToUnlockedHPP, @@ -106,7 +109,8 @@ const validateStatusAndUpdateInfo = ( // This strategy (returning a different type from validation) is taken from the // "parse, don't validate" article: https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/ export function parseAndSubmit( - draft: HealthPlanFormDataType + draft: HealthPlanFormDataType, + featureFlag: FeatureFlagSettings ): LockedHealthPlanFormDataType | SubmissionError { // Remove fields from edits on irrelevant logic branches // - CONTRACT_ONLY submission type should not contain any CONTRACT_AND_RATE rates data. @@ -125,10 +129,22 @@ export function parseAndSubmit( submittedAt: new Date(), } - if (isValidAndCurrentLockedHealthPlanFormData(maybeStateSubmission)) + // Check for valid attestation. Returns true if flag is off. + const isValid438Attestation = !featureFlag['438-attestation'] + ? true + : Boolean(featureFlag['438-attestation']) && + draft.statutoryRegulatoryAttestation !== undefined + + if ( + isValidAndCurrentLockedHealthPlanFormData(maybeStateSubmission) && + isValid438Attestation + ) return maybeStateSubmission else if ( - !hasValidContract(maybeStateSubmission as LockedHealthPlanFormDataType) + !hasValidContract( + maybeStateSubmission as LockedHealthPlanFormDataType + ) || + !isValid438Attestation ) { return { code: 'INCOMPLETE', @@ -184,6 +200,10 @@ export function submitHealthPlanPackageResolver( context, 'rates-db-refactor' ) + const contract438Attestation = await launchDarkly.getFeatureFlag( + context, + '438-attestation' + ) const { user, span } = context const { submittedReason, pkgID } = input setResolverDetailsOnActiveSpan('submitHealthPlanPackage', user, span) @@ -307,7 +327,9 @@ export function submitHealthPlanPackageResolver( contractRevisionID = contractWithHistory.draftRevision.id // Final clean + check of data before submit - parse to state submission - const maybeLocked = parseAndSubmit(initialFormData) + const maybeLocked = parseAndSubmit(initialFormData, { + '438-attestation': contract438Attestation, + }) if (isSubmissionError(maybeLocked)) { const errMessage = maybeLocked.message @@ -530,7 +552,9 @@ export function submitHealthPlanPackageResolver( contractRevisionID = initialPackage.revisions[0].id // Final clean + check of data before submit - parse to state submission - const maybeLocked = parseAndSubmit(initialFormData) + const maybeLocked = parseAndSubmit(initialFormData, { + '438-attestation': contract438Attestation, + }) if (isSubmissionError(maybeLocked)) { const errMessage = maybeLocked.message diff --git a/services/app-api/src/resolvers/healthPlanPackage/updateHealthPlanFormData.test.ts b/services/app-api/src/resolvers/healthPlanPackage/updateHealthPlanFormData.test.ts index b957e1cf29..bd4be402af 100644 --- a/services/app-api/src/resolvers/healthPlanPackage/updateHealthPlanFormData.test.ts +++ b/services/app-api/src/resolvers/healthPlanPackage/updateHealthPlanFormData.test.ts @@ -101,6 +101,7 @@ describe.each(flagValueTestParameters)( modifiedNonRiskPaymentArrangements: undefined, }, }, + statutoryRegulatoryAttestation: true, rateInfos: [], }) diff --git a/services/app-api/src/testHelpers/contractAndRates/contractHelpers.ts b/services/app-api/src/testHelpers/contractAndRates/contractHelpers.ts index baf1843361..58386dbf4d 100644 --- a/services/app-api/src/testHelpers/contractAndRates/contractHelpers.ts +++ b/services/app-api/src/testHelpers/contractAndRates/contractHelpers.ts @@ -206,6 +206,7 @@ const createContractRevision = ( inLieuServicesAndSettings: null, rateRevisions: [], draftRates: [], + statutoryRegulatoryAttestation: null, ...revision, } } diff --git a/services/app-api/src/testHelpers/emailerHelpers.ts b/services/app-api/src/testHelpers/emailerHelpers.ts index bb98df4211..13a6242c98 100644 --- a/services/app-api/src/testHelpers/emailerHelpers.ts +++ b/services/app-api/src/testHelpers/emailerHelpers.ts @@ -337,6 +337,7 @@ const mockContractAndRatesFormData = ( ], addtlActuaryCommunicationPreference: 'OACT_TO_ACTUARY', ...submissionPartial, + statutoryRegulatoryAttestation: false, } } @@ -520,6 +521,7 @@ const mockContractOnlyFormData = ( }, ], addtlActuaryContacts: [], + statutoryRegulatoryAttestation: false, ...submissionPartial, } } @@ -606,6 +608,7 @@ const mockContractAmendmentFormData = ( }, ], addtlActuaryCommunicationPreference: 'OACT_TO_ACTUARY', + statutoryRegulatoryAttestation: false, ...submissionPartial, } } diff --git a/services/app-api/src/testHelpers/gqlHelpers.ts b/services/app-api/src/testHelpers/gqlHelpers.ts index f28158f113..725dcdce2d 100644 --- a/services/app-api/src/testHelpers/gqlHelpers.ts +++ b/services/app-api/src/testHelpers/gqlHelpers.ts @@ -299,6 +299,7 @@ const createAndUpdateTestHealthPlanPackage = async ( modifiedNonRiskPaymentArrangements: true, }, } + draft.statutoryRegulatoryAttestation = true Object.assign(draft, partialUpdates) diff --git a/services/app-proto/src/health_plan_form_data.proto b/services/app-proto/src/health_plan_form_data.proto index dbd9883393..126eb993c5 100644 --- a/services/app-proto/src/health_plan_form_data.proto +++ b/services/app-proto/src/health_plan_form_data.proto @@ -81,6 +81,7 @@ message ContractInfo { repeated FederalAuthority federal_authorities = 5; repeated Document contract_documents = 6; optional ContractExecutionStatus contract_execution_status = 7; + optional bool statutory_regulatory_attestation = 8; // Rates Refactor: No need for nested contract amendment info optional ContractAmendmentInfo contract_amendment_info = 50; diff --git a/services/app-web/src/common-code/featureFlags/flags.ts b/services/app-web/src/common-code/featureFlags/flags.ts index b25c088eda..e9f1f25051 100644 --- a/services/app-web/src/common-code/featureFlags/flags.ts +++ b/services/app-web/src/common-code/featureFlags/flags.ts @@ -76,6 +76,10 @@ const featureFlags = { flag: 'rate-filters', defaultValue: false, }, + CONTRACT_438_ATTESTATION: { + flag: '438-attestation', + defaultValue: false, + }, /** * Used in testing to simulate errors in fetching flag value. * This flag does not exist in LaunchDarkly dashboard so fetching this will return the defaultValue. diff --git a/services/app-web/src/common-code/healthPlanFormDataMocks/healthPlanFormData.ts b/services/app-web/src/common-code/healthPlanFormDataMocks/healthPlanFormData.ts index 29d323ad48..451e982f85 100644 --- a/services/app-web/src/common-code/healthPlanFormDataMocks/healthPlanFormData.ts +++ b/services/app-web/src/common-code/healthPlanFormDataMocks/healthPlanFormData.ts @@ -763,6 +763,7 @@ function basicLockedHealthPlanFormData(): LockedHealthPlanFormDataType { programIDs: [mockMNState().programs[0].id], submissionType: 'CONTRACT_ONLY', riskBasedContract: false, + statutoryRegulatoryAttestation: false, submissionDescription: 'A real submission', documents: [], contractType: 'BASE', diff --git a/services/app-web/src/common-code/healthPlanFormDataType/LockedHealthPlanFormDataType.ts b/services/app-web/src/common-code/healthPlanFormDataType/LockedHealthPlanFormDataType.ts index 93ecad5dfc..a51771cd02 100644 --- a/services/app-web/src/common-code/healthPlanFormDataType/LockedHealthPlanFormDataType.ts +++ b/services/app-web/src/common-code/healthPlanFormDataType/LockedHealthPlanFormDataType.ts @@ -42,4 +42,5 @@ export type LockedHealthPlanFormDataType = { stateContacts: StateContact[] addtlActuaryContacts: ActuaryContact[] addtlActuaryCommunicationPreference?: ActuaryCommunicationType + statutoryRegulatoryAttestation: boolean } diff --git a/services/app-web/src/common-code/healthPlanFormDataType/UnlockedHealthPlanFormDataType.ts b/services/app-web/src/common-code/healthPlanFormDataType/UnlockedHealthPlanFormDataType.ts index 437ef73644..a55547ad02 100644 --- a/services/app-web/src/common-code/healthPlanFormDataType/UnlockedHealthPlanFormDataType.ts +++ b/services/app-web/src/common-code/healthPlanFormDataType/UnlockedHealthPlanFormDataType.ts @@ -118,6 +118,7 @@ type UnlockedHealthPlanFormDataType = { federalAuthorities: FederalAuthority[] contractAmendmentInfo?: UnlockedContractAmendmentInfo rateInfos: RateInfoType[] + statutoryRegulatoryAttestation?: boolean } export type { diff --git a/services/app-web/src/common-code/proto/healthPlanFormDataProto/toDomain.ts b/services/app-web/src/common-code/proto/healthPlanFormDataProto/toDomain.ts index 7afddfecdf..7be8b9534b 100644 --- a/services/app-web/src/common-code/proto/healthPlanFormDataProto/toDomain.ts +++ b/services/app-web/src/common-code/proto/healthPlanFormDataProto/toDomain.ts @@ -543,6 +543,8 @@ const toDomain = ( stateContacts: cleanedStateContacts, addtlActuaryContacts: parseActuaryContacts(addtlActuaryContacts), documents: parseProtoDocuments(formDataMessage.documents), + statutoryRegulatoryAttestation: + contractInfo?.statutoryRegulatoryAttestation ?? undefined, } // Now that we've gotten things into our combined draft & state domain format. 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 e333fb2bbd..917314defb 100644 --- a/services/app-web/src/common-code/proto/healthPlanFormDataProto/toProtoBuffer.ts +++ b/services/app-web/src/common-code/proto/healthPlanFormDataProto/toProtoBuffer.ts @@ -65,7 +65,9 @@ function domainEnumStringToProtoString( return protoEnumString } -const domainDocsToProtoDocs = (domainDocs:SubmissionDocument[]): mcreviewproto.IDocument[] | null | undefined => { +const domainDocsToProtoDocs = ( + domainDocs: SubmissionDocument[] +): mcreviewproto.IDocument[] | null | undefined => { return domainDocs.map((doc) => ({ s3Url: doc.s3URL, name: doc.name, @@ -190,8 +192,12 @@ const toProtoBuffer = ( mcreviewproto.FederalAuthority, domainData.federalAuthorities ), - contractDocuments: domainDocsToProtoDocs(domainData.contractDocuments), + contractDocuments: domainDocsToProtoDocs( + domainData.contractDocuments + ), contractAmendmentInfo: contractAmendmentInfo, + statutoryRegulatoryAttestation: + domainData.statutoryRegulatoryAttestation, }, rateInfos: domainData.rateInfos && domainData.rateInfos.length @@ -215,8 +221,12 @@ const toProtoBuffer = ( rateDateCertified: domainDateToProtoDate( rateInfo.rateDateCertified ), - rateDocuments: domainDocsToProtoDocs(rateInfo.rateDocuments), - supportingDocuments: domainDocsToProtoDocs(rateInfo.supportingDocuments), + rateDocuments: domainDocsToProtoDocs( + rateInfo.rateDocuments + ), + supportingDocuments: domainDocsToProtoDocs( + rateInfo.supportingDocuments + ), rateCertificationName: generateRateName( domainData, rateInfo, diff --git a/services/app-web/src/common-code/proto/healthPlanFormDataProto/zodSchemas.ts b/services/app-web/src/common-code/proto/healthPlanFormDataProto/zodSchemas.ts index 0b10fde7f1..4746556002 100644 --- a/services/app-web/src/common-code/proto/healthPlanFormDataProto/zodSchemas.ts +++ b/services/app-web/src/common-code/proto/healthPlanFormDataProto/zodSchemas.ts @@ -38,7 +38,7 @@ const submissionDocumentSchema = z.object({ .optional() ), sha256: z.string().optional(), - id: z.string().optional() // doesn't exist for newly created + id: z.string().optional(), // doesn't exist for newly created }) const contractAmendmentInfoSchema = z.object({ @@ -168,6 +168,7 @@ const unlockedHealthPlanFormDataZodSchema = z.object({ federalAuthorities: z.array(federalAuthoritySchema), contractAmendmentInfo: contractAmendmentInfoSchema.optional(), rateInfos: z.array(rateInfosTypeSchema), + statutoryRegulatoryAttestation: z.boolean().optional(), }) /* diff --git a/services/app-web/src/components/SubmissionSummarySection/ContractDetailsSummarySection/ContractDetailsSummarySection.test.tsx b/services/app-web/src/components/SubmissionSummarySection/ContractDetailsSummarySection/ContractDetailsSummarySection.test.tsx index 3d223b520e..97e33b3f3b 100644 --- a/services/app-web/src/components/SubmissionSummarySection/ContractDetailsSummarySection/ContractDetailsSummarySection.test.tsx +++ b/services/app-web/src/components/SubmissionSummarySection/ContractDetailsSummarySection/ContractDetailsSummarySection.test.tsx @@ -1,5 +1,8 @@ import { screen, waitFor, within } from '@testing-library/react' -import { renderWithProviders } from '../../../testHelpers/jestHelpers' +import { + ldUseClientSpy, + renderWithProviders, +} from '../../../testHelpers/jestHelpers' import { ContractDetailsSummarySection } from './ContractDetailsSummarySection' import { fetchCurrentUserMock, @@ -8,8 +11,19 @@ import { } from '../../../testHelpers/apolloMocks' import { UnlockedHealthPlanFormDataType } from '../../../common-code/healthPlanFormDataType' import { testS3Client } from '../../../testHelpers/s3Helpers' +import { + StatutoryRegulatoryAttestation, + StatutoryRegulatoryAttestationQuestion, +} from '../../../constants/statutoryRegulatoryAttestation' describe('ContractDetailsSummarySection', () => { + afterEach(() => { + jest.restoreAllMocks() + }) + const defaultApolloMocks = { + mocks: [fetchCurrentUserMock({ statusCode: 200 })], + } + it('can render draft submission without errors (review and submit behavior)', async () => { const testSubmission = { ...mockContractAndRatesDraft(), @@ -40,9 +54,7 @@ describe('ContractDetailsSummarySection', () => { submissionName="MN-PMAP-0001" />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloMocks, } ) @@ -76,7 +88,10 @@ describe('ContractDetailsSummarySection', () => { status: 'SUBMITTED', }} submissionName="MN-PMAP-0001" - /> + />, + { + apolloProvider: defaultApolloMocks, + } ) expect( @@ -101,14 +116,27 @@ describe('ContractDetailsSummarySection', () => { }) it('can render all contract details fields', () => { + ldUseClientSpy({ '438-attestation': true }) + const submission = mockContractAndRatesDraft({ + statutoryRegulatoryAttestation: true, + }) renderWithProviders( + />, + { + apolloProvider: defaultApolloMocks, + } ) + + expect( + screen.getByRole('definition', { + name: StatutoryRegulatoryAttestationQuestion, + }) + ).toBeInTheDocument() expect( screen.getByRole('definition', { name: 'Contract status' }) ).toBeInTheDocument() @@ -137,14 +165,49 @@ describe('ContractDetailsSummarySection', () => { ).toBeInTheDocument() }) - it('displays correct effective dates text for base contract', () => { + it('displays correct contract 438 attestation yes and no text', async () => { + ldUseClientSpy({ '438-attestation': true }) + const submission = mockContractAndRatesDraft({ + statutoryRegulatoryAttestation: true, + }) renderWithProviders( + />, + { + apolloProvider: defaultApolloMocks, + } ) + + const attestationYes = StatutoryRegulatoryAttestation.YES + + expect( + screen.getByRole('definition', { + name: StatutoryRegulatoryAttestationQuestion, + }) + ).toBeInTheDocument() + expect(await screen.findByText(attestationYes)).toBeInTheDocument() + }) + + it('displays correct effective dates text for base contract', async () => { + await waitFor(() => { + renderWithProviders( + , + { + apolloProvider: defaultApolloMocks, + } + ) + }) + expect(screen.getByText('Contract effective dates')).toBeInTheDocument() }) @@ -154,7 +217,10 @@ describe('ContractDetailsSummarySection', () => { documentDateLookupTable={{ previousSubmissionDate: '01/01/01' }} submission={mockContractAndRatesDraft()} submissionName="MN-PMAP-0001" - /> + />, + { + apolloProvider: defaultApolloMocks, + } ) expect( screen.getByText('Contract amendment effective dates') @@ -201,7 +267,10 @@ describe('ContractDetailsSummarySection', () => { documentDateLookupTable={{ previousSubmissionDate: '01/01/01' }} submission={testSubmission} submissionName="MN-PMAP-0001" - /> + />, + { + apolloProvider: defaultApolloMocks, + } ) await waitFor(() => { @@ -243,7 +312,10 @@ describe('ContractDetailsSummarySection', () => { documentDateLookupTable={{ previousSubmissionDate: '01/01/01' }} submission={mockContractAndRatesDraft()} submissionName="MN-PMAP-0001" - /> + />, + { + apolloProvider: defaultApolloMocks, + } ) expect( @@ -259,7 +331,10 @@ describe('ContractDetailsSummarySection', () => { documentDateLookupTable={{ previousSubmissionDate: '01/01/01' }} submission={mockContractAndRatesDraft()} submissionName="MN-PMAP-0001" - /> + />, + { + apolloProvider: defaultApolloMocks, + } ) expect( screen.queryByRole('button', { @@ -287,9 +362,7 @@ describe('ContractDetailsSummarySection', () => { submissionName="MN-PMAP-0001" />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloMocks, } ) @@ -329,9 +402,7 @@ describe('ContractDetailsSummarySection', () => { submissionName="MN-PMAP-0001" />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloMocks, } ) @@ -370,6 +441,7 @@ describe('ContractDetailsSummarySection', () => { submissionName="MN-PMAP-0001" />, { + apolloProvider: defaultApolloMocks, s3Provider, } ) @@ -389,7 +461,10 @@ describe('ContractDetailsSummarySection', () => { }} submission={mockContractAndRatesDraft()} submissionName="MN-PMAP-0001" - /> + />, + { + apolloProvider: defaultApolloMocks, + } ) expect( @@ -487,7 +562,10 @@ describe('ContractDetailsSummarySection', () => { contractType: 'BASE', })} submissionName="MN-PMAP-0001" - /> + />, + { + apolloProvider: defaultApolloMocks, + } ) const modifiedProvisions = screen.getByLabelText( @@ -541,9 +619,7 @@ describe('ContractDetailsSummarySection', () => { submissionName="MN-PMAP-0001" />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloMocks, } ) expect( @@ -619,9 +695,7 @@ describe('ContractDetailsSummarySection', () => { editNavigateTo="contract-details" />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloMocks, } ) @@ -670,9 +744,7 @@ describe('ContractDetailsSummarySection', () => { editNavigateTo="contract-details" />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloMocks, } ) @@ -720,9 +792,7 @@ describe('ContractDetailsSummarySection', () => { submissionName="MN-PMAP-0001" />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloMocks, } ) @@ -775,9 +845,7 @@ describe('ContractDetailsSummarySection', () => { editNavigateTo="contract-details" />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloMocks, } ) @@ -836,9 +904,7 @@ describe('ContractDetailsSummarySection', () => { submissionName="MN-PMAP-0001" />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloMocks, } ) diff --git a/services/app-web/src/components/SubmissionSummarySection/ContractDetailsSummarySection/ContractDetailsSummarySection.tsx b/services/app-web/src/components/SubmissionSummarySection/ContractDetailsSummarySection/ContractDetailsSummarySection.tsx index b360938a97..67947e6e18 100644 --- a/services/app-web/src/components/SubmissionSummarySection/ContractDetailsSummarySection/ContractDetailsSummarySection.tsx +++ b/services/app-web/src/components/SubmissionSummarySection/ContractDetailsSummarySection/ContractDetailsSummarySection.tsx @@ -34,6 +34,14 @@ import { DocumentDateLookupTableType } from '../../../documentHelpers/makeDocume import { recordJSException } from '../../../otelHelpers' import useDeepCompareEffect from 'use-deep-compare-effect' import { InlineDocumentWarning } from '../../DocumentWarning' +import { useLDClient } from 'launchdarkly-react-client-sdk' +import { featureFlags } from '../../../common-code/featureFlags' +import { Grid } from '@trussworks/react-uswds' +import { booleanAsYesNoFormValue } from '../../Form/FieldYesNo' +import { + StatutoryRegulatoryAttestation, + StatutoryRegulatoryAttestationQuestion, +} from '../../../constants/statutoryRegulatoryAttestation' export type ContractDetailsSummarySectionProps = { submission: HealthPlanFormDataType @@ -72,6 +80,17 @@ export const ContractDetailsSummarySection = ({ const [zippedFilesURL, setZippedFilesURL] = useState< string | undefined | Error >(undefined) + const ldClient = useLDClient() + + const contract438Attestation = ldClient?.variation( + featureFlags.CONTRACT_438_ATTESTATION.flag, + featureFlags.CONTRACT_438_ATTESTATION.defaultValue + ) + + const attestationYesNo = booleanAsYesNoFormValue( + submission.statutoryRegulatoryAttestation + ) + const contractSupportingDocuments = submission.documents.filter((doc) => doc.documentCategories.includes('CONTRACT_RELATED' as const) ) @@ -143,6 +162,23 @@ export const ContractDetailsSummarySection = ({ renderDownloadButton(zippedFilesURL)}
+ {contract438Attestation && ( + + + + + + )} { ...mockDraft(), } + const defaultApolloProvider = { + mocks: [fetchCurrentUserMock({ statusCode: 200 })], + } + it('displays correct form guidance', async () => { renderWithProviders( { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) expect( @@ -69,9 +77,7 @@ describe('ContractDetails', () => { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) @@ -106,9 +112,7 @@ describe('ContractDetails', () => { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) @@ -129,9 +133,7 @@ describe('ContractDetails', () => { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) @@ -155,20 +157,27 @@ describe('ContractDetails', () => { }) describe('Federal authorities', () => { - it('displays correct form fields for federal authorities with medicaid contract', () => { - renderWithProviders( - - ) + it('displays correct form fields for federal authorities with medicaid contract', async () => { + await waitFor(() => { + renderWithProviders( + , + { + apolloProvider: defaultApolloProvider, + } + ) + }) + const fedAuthQuestion = screen.getByRole('group', { name: 'Active federal operating authority', }) + expect(fedAuthQuestion).toBeInTheDocument() expect( within(fedAuthQuestion).getAllByRole('checkbox') @@ -189,7 +198,10 @@ describe('ContractDetails', () => { }} updateDraft={jest.fn()} previousDocuments={[]} - /> + />, + { + apolloProvider: defaultApolloProvider, + } ) const fedAuthQuestion = await screen.findByRole('group', { name: 'Active federal operating authority', @@ -233,9 +245,7 @@ describe('ContractDetails', () => { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) await screen.findByRole('form') @@ -281,9 +291,7 @@ describe('ContractDetails', () => { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) // trigger validations @@ -343,9 +351,7 @@ describe('ContractDetails', () => { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) await screen.findByRole('form') @@ -378,9 +384,7 @@ describe('ContractDetails', () => { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) @@ -433,9 +437,7 @@ describe('ContractDetails', () => { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) await screen.findByRole('form') @@ -457,9 +459,7 @@ describe('ContractDetails', () => { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) await screen.findByRole('form') @@ -501,9 +501,7 @@ describe('ContractDetails', () => { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) @@ -566,9 +564,7 @@ describe('ContractDetails', () => { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) @@ -592,9 +588,7 @@ describe('ContractDetails', () => { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) @@ -623,9 +617,7 @@ describe('ContractDetails', () => { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) @@ -634,7 +626,7 @@ describe('ContractDetails', () => { }) expect(continueButton).not.toHaveAttribute('aria-disabled') - continueButton.click() + await userEvent.click(continueButton) await waitFor(() => { expect( @@ -653,9 +645,7 @@ describe('ContractDetails', () => { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) @@ -667,9 +657,10 @@ describe('ContractDetails', () => { await userEvent.upload(input, [TEST_DOC_FILE]) await userEvent.upload(input, []) // clear input and ensure we add same file twice await userEvent.upload(input, [TEST_DOC_FILE]) + expect(continueButton).not.toHaveAttribute('aria-disabled') + await userEvent.click(continueButton) - continueButton.click() await waitFor(() => { expect( screen.getAllByText( @@ -689,9 +680,7 @@ describe('ContractDetails', () => { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) const continueButton = screen.getByRole('button', { @@ -706,7 +695,7 @@ describe('ContractDetails', () => { ).toBeInTheDocument() expect(continueButton).not.toHaveAttribute('aria-disabled') - continueButton.click() + await userEvent.click(continueButton) expect( await screen.findAllByText( @@ -724,9 +713,7 @@ describe('ContractDetails', () => { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) const continueButton = screen.getByRole('button', { @@ -749,14 +736,16 @@ describe('ContractDetails', () => { 'file-input-preview-image' )[1] expect(imageElFile2).toHaveClass('is-loading') - fireEvent.click(continueButton) - expect(continueButton).toHaveAttribute('aria-disabled', 'true') + await waitFor(() => { + fireEvent.click(continueButton) + expect(continueButton).toHaveAttribute('aria-disabled', 'true') - expect( - screen.getAllByText( - 'You must wait for all documents to finish uploading before continuing' - ) - ).toHaveLength(2) + expect( + screen.getAllByText( + 'You must wait for all documents to finish uploading before continuing' + ) + ).toHaveLength(2) + }) }) }) @@ -770,9 +759,7 @@ describe('ContractDetails', () => { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) @@ -797,9 +784,7 @@ describe('ContractDetails', () => { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) @@ -826,9 +811,7 @@ describe('ContractDetails', () => { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) @@ -864,9 +847,7 @@ describe('ContractDetails', () => { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) @@ -891,9 +872,7 @@ describe('ContractDetails', () => { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) const input = screen.getByLabelText('Upload contract') @@ -931,9 +910,7 @@ describe('ContractDetails', () => { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) @@ -957,9 +934,7 @@ describe('ContractDetails', () => { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) @@ -986,9 +961,7 @@ describe('ContractDetails', () => { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) @@ -1013,9 +986,7 @@ describe('ContractDetails', () => { previousDocuments={[]} />, { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, + apolloProvider: defaultApolloProvider, } ) @@ -1055,4 +1026,94 @@ describe('ContractDetails', () => { ) }) }) + + describe('Contract 438 attestation', () => { + it('renders 438 attestation question without errors', async () => { + ldUseClientSpy({ '438-attestation': true }) + const draft = mockBaseContract({ + statutoryRegulatoryAttestation: true, + }) + await waitFor(() => { + renderWithProviders( + , + { + apolloProvider: defaultApolloProvider, + } + ) + }) + + // expect 438 attestation question to be on the page + expect( + screen.getByText(StatutoryRegulatoryAttestationQuestion) + ).toBeInTheDocument() + + const yesRadio = screen.getByRole('radio', { + name: StatutoryRegulatoryAttestation.YES, + }) + const noRadio = screen.getByRole('radio', { + name: StatutoryRegulatoryAttestation.YES, + }) + + // expect both yes and no answers on the page and yes to be checked + expect(yesRadio).toBeChecked() + expect(noRadio).toBeInTheDocument() + }) + it('errors when continuing without answering 438 attestation question', async () => { + ldUseClientSpy({ '438-attestation': true }) + const draft = mockBaseContract({ + statutoryRegulatoryAttestation: undefined, + }) + const mockUpdateDraftFn = jest.fn() + await waitFor(() => { + renderWithProviders( + , + { + apolloProvider: defaultApolloProvider, + } + ) + }) + + // expect 438 attestation question to be on the page + expect( + screen.getByText(StatutoryRegulatoryAttestationQuestion) + ).toBeInTheDocument() + + const yesRadio = screen.getByRole('radio', { + name: StatutoryRegulatoryAttestation.YES, + }) + const noRadio = screen.getByRole('radio', { + name: StatutoryRegulatoryAttestation.YES, + }) + + // expect both yes and no answers on the page and yes to be checked + expect(yesRadio).toBeInTheDocument() + expect(noRadio).toBeInTheDocument() + + // check no radio + await userEvent.click(noRadio) + + const continueButton = screen.getByRole('button', { + name: 'Continue', + }) + + // click continue + await userEvent.click(continueButton) + + // expect errors for attestation question + await waitFor(() => { + expect(mockUpdateDraftFn).not.toHaveBeenCalled() + expect( + screen.queryAllByText('You must select yes or no') + ).toHaveLength(2) + }) + }) + }) }) diff --git a/services/app-web/src/pages/StateSubmission/ContractDetails/ContractDetails.tsx b/services/app-web/src/pages/StateSubmission/ContractDetails/ContractDetails.tsx index 9fe22fde41..33c45bd92d 100644 --- a/services/app-web/src/pages/StateSubmission/ContractDetails/ContractDetails.tsx +++ b/services/app-web/src/pages/StateSubmission/ContractDetails/ContractDetails.tsx @@ -62,6 +62,12 @@ import { isContractWithProvisions, } from '../../../common-code/healthPlanFormDataType/healthPlanFormData' import { RoutesRecord } from '../../../constants' +import { useLDClient } from 'launchdarkly-react-client-sdk' +import { featureFlags } from '../../../common-code/featureFlags' +import { + StatutoryRegulatoryAttestation, + StatutoryRegulatoryAttestationQuestion, +} from '../../../constants/statutoryRegulatoryAttestation' function formattedDatePlusOneDay(initialValue: string): string { const dayjsValue = dayjs(initialValue) @@ -113,6 +119,7 @@ export interface ContractDetailsFormValues { modifiedNetworkAdequacyStandards: string | undefined modifiedLengthOfContract: string | undefined modifiedNonRiskPaymentArrangements: string | undefined + statutoryRegulatoryAttestation: string | undefined } type FormError = FormikErrors[keyof FormikErrors] @@ -125,6 +132,12 @@ export const ContractDetails = ({ }: HealthPlanFormPageProps): React.ReactElement => { const [shouldValidate, setShouldValidate] = React.useState(showValidations) const navigate = useNavigate() + const ldClient = useLDClient() + + const contract438Attestation = ldClient?.variation( + featureFlags.CONTRACT_438_ATTESTATION.flag, + featureFlags.CONTRACT_438_ATTESTATION.defaultValue + ) // Contract documents state management const { deleteFile, uploadFile, scanFile, getKey, getS3URL } = useS3() @@ -322,6 +335,9 @@ export const ContractDetails = ({ draftSubmission?.contractAmendmentInfo?.modifiedProvisions .modifiedNonRiskPaymentArrangements ), + statutoryRegulatoryAttestation: formatForForm( + draftSubmission?.statutoryRegulatoryAttestation + ), } const showFieldErrors = (error?: FormError) => @@ -391,6 +407,9 @@ export const ContractDetails = ({ draftSubmission.managedCareEntities = values.managedCareEntities draftSubmission.federalAuthorities = values.federalAuthorities draftSubmission.contractDocuments = contractDocuments + draftSubmission.statutoryRegulatoryAttestation = formatYesNoForProto( + values.statutoryRegulatoryAttestation + ) if (isContractWithProvisions(draftSubmission)) { draftSubmission.contractAmendmentInfo = { @@ -480,7 +499,9 @@ export const ContractDetails = ({ : `../rate-details`, }) }} - validationSchema={() => ContractDetailsFormSchema(draftSubmission)} + validationSchema={() => + ContractDetailsFormSchema(draftSubmission, ldClient?.allFlags()) + } > {({ values, @@ -565,6 +586,93 @@ export const ContractDetails = ({ onFileItemsUpdate={onFileItemsUpdate} /> + {contract438Attestation && ( + +
+ + + { + StatutoryRegulatoryAttestationQuestion + } + + +
+ + Required + + + + Managed Care Contract Review + and Approval State Guide + + + CHIP Managed Care Contract + Review and Approval State + Guide + + +
+ {showFieldErrors( + errors.statutoryRegulatoryAttestation + ) && ( + + { + errors.statutoryRegulatoryAttestation + } + + )} + + +
+
+ )} { const yesNoError = (provision: GeneralizedProvisionType) => { const noValidation = Yup.string().nullable() @@ -50,7 +52,11 @@ export const ContractDetailsFormSchema = ( } } + // Errors in the error summary is ordered by the position of each ket in the yup object. return Yup.object().shape({ + statutoryRegulatoryAttestation: activeFeatureFlags['438-attestation'] + ? Yup.string().defined('You must select yes or no') + : Yup.string(), contractExecutionStatus: Yup.string().defined( 'You must select a contract status' ), diff --git a/services/app-web/src/pages/StateSubmission/StateSubmissionForm.module.scss b/services/app-web/src/pages/StateSubmission/StateSubmissionForm.module.scss index 002c9f1f8d..e8c4cb5ff7 100644 --- a/services/app-web/src/pages/StateSubmission/StateSubmissionForm.module.scss +++ b/services/app-web/src/pages/StateSubmission/StateSubmissionForm.module.scss @@ -149,3 +149,10 @@ display: flex; flex-direction: column; } + +.contractAttestationHint { + margin-bottom: units(3); + a { + margin-top: units(1); + } +} diff --git a/services/app-web/src/testHelpers/apolloMocks/healthPlanFormDataMock.ts b/services/app-web/src/testHelpers/apolloMocks/healthPlanFormDataMock.ts index 7f6a5f077a..157f4ddd16 100644 --- a/services/app-web/src/testHelpers/apolloMocks/healthPlanFormDataMock.ts +++ b/services/app-web/src/testHelpers/apolloMocks/healthPlanFormDataMock.ts @@ -116,6 +116,7 @@ function mockBaseContract( ], addtlActuaryContacts: [], addtlActuaryCommunicationPreference: undefined, + statutoryRegulatoryAttestation: true, ...partial, } } @@ -211,6 +212,7 @@ function mockContractAndRatesDraft( }, ], addtlActuaryCommunicationPreference: 'OACT_TO_ACTUARY', + statutoryRegulatoryAttestation: true, ...partial, } } @@ -318,6 +320,7 @@ function mockStateSubmission(): LockedHealthPlanFormDataType { ], addtlActuaryContacts: [], addtlActuaryCommunicationPreference: undefined, + statutoryRegulatoryAttestation: false, } } @@ -417,6 +420,7 @@ function mockStateSubmissionContractAmendment(): LockedHealthPlanFormDataType { ], addtlActuaryContacts: [], addtlActuaryCommunicationPreference: undefined, + statutoryRegulatoryAttestation: false, } } diff --git a/services/app-web/src/testHelpers/jestHelpers.tsx b/services/app-web/src/testHelpers/jestHelpers.tsx index cd8b68ed16..278369d6c0 100644 --- a/services/app-web/src/testHelpers/jestHelpers.tsx +++ b/services/app-web/src/testHelpers/jestHelpers.tsx @@ -23,6 +23,8 @@ import { FeatureFlagLDConstant, FlagValue, FeatureFlagSettings, + featureFlagKeys, + featureFlags, } from '../common-code/featureFlags' /* Render */ @@ -77,6 +79,13 @@ const WithLocation = ({ return null } +const getDefaultFeatureFlags = (): FeatureFlagSettings => + featureFlagKeys.reduce((a, c) => { + const flag = featureFlags[c].flag + const defaultValue = featureFlags[c].defaultValue + return Object.assign(a, { [flag]: defaultValue }) + }, {} as FeatureFlagSettings) + //WARNING: This required tests using this function to clear mocks afterwards. const ldUseClientSpy = (featureFlags: FeatureFlagSettings) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -109,6 +118,11 @@ const ldUseClientSpy = (featureFlags: FeatureFlagSettings) => { ? defaultValue : featureFlags[flag] }, + allFlags: () => { + const defaultFeatureFlags = getDefaultFeatureFlags() + Object.assign(defaultFeatureFlags, featureFlags) + return defaultFeatureFlags + }, } }) } diff --git a/services/cypress/support/launchDarklyCommands.ts b/services/cypress/support/launchDarklyCommands.ts index d70fea0d85..1e0c55d156 100644 --- a/services/cypress/support/launchDarklyCommands.ts +++ b/services/cypress/support/launchDarklyCommands.ts @@ -86,7 +86,8 @@ Cypress.Commands.add('stubFeatureFlags', () => { cy.interceptFeatureFlags({ 'packages-with-shared-rates': true, 'rates-db-refactor': true, - 'supporting-docs-by-rate': true + 'supporting-docs-by-rate': true, + '438-attestation': true }) }) diff --git a/services/cypress/support/stateSubmissionFormCommands.ts b/services/cypress/support/stateSubmissionFormCommands.ts index 2fca38629d..9419910183 100644 --- a/services/cypress/support/stateSubmissionFormCommands.ts +++ b/services/cypress/support/stateSubmissionFormCommands.ts @@ -99,6 +99,9 @@ Cypress.Commands.add('fillOutContractActionAndRateCertification', () => { }) Cypress.Commands.add('fillOutBaseContractDetails', () => { // Must be on '/submissions/:id/edit/contract-details' + // Contract 438 attestation question + cy.findByText('Yes, the contract fully complies with all applicable requirements').click() + cy.findByText('Fully executed').click() cy.findAllByLabelText('Start date', {timeout: 2000}) .parents() @@ -174,6 +177,9 @@ Cypress.Commands.add('fillOutBaseContractDetails', () => { Cypress.Commands.add('fillOutAmendmentToBaseContractDetails', () => { // Must be on '/submissions/:id/edit/contract-details' + // Contract 438 attestation question + cy.findByText('No, the contract does not fully comply with all applicable requirements').click() + cy.findByText('Unexecuted by some or all parties').click() cy.findAllByLabelText('Start date', {timeout: 2000}) diff --git a/services/cypress/utils/apollo-test-utils.ts b/services/cypress/utils/apollo-test-utils.ts index 6fb2f85fc3..ab5b43bab4 100644 --- a/services/cypress/utils/apollo-test-utils.ts +++ b/services/cypress/utils/apollo-test-utils.ts @@ -100,6 +100,7 @@ const contractOnlyData = (): Partial=> ({ managedCareEntities: ['MCO'], federalAuthorities: ['STATE_PLAN'], rateInfos: [], + statutoryRegulatoryAttestation: true }) const contractAndRatesData = (): Partial=> ({ @@ -200,9 +201,8 @@ const contractAndRatesData = (): Partial=> ({ actuaryCommunicationPreference: 'OACT_TO_ACTUARY' as const, packagesWithSharedRateCerts: [], }, - ] - - + ], + statutoryRegulatoryAttestation: true }) const newSubmissionInput = (overrides?: Partial ): Partial => {