diff --git a/services/app-api/src/domain-models/healthPlanPackage.ts b/services/app-api/src/domain-models/healthPlanPackage.ts index 4bf659cf01..0ce789d595 100644 --- a/services/app-api/src/domain-models/healthPlanPackage.ts +++ b/services/app-api/src/domain-models/healthPlanPackage.ts @@ -72,8 +72,6 @@ function packageSubmitters(pkg: HealthPlanPackageType): string[] { function convertContractToUnlockedHealthPlanPackage( contract: ContractType ): HealthPlanPackageType | Error { - console.info('Attempting to convert contract to health plan package') - // Since drafts come in separate on the Contract type, we push it onto the revisions before converting below if (contract.draftRevision) { contract.revisions.unshift(contract.draftRevision) diff --git a/services/app-api/src/postgres/contractAndRates/findAllContractsWithHistoryByState.ts b/services/app-api/src/postgres/contractAndRates/findAllContractsWithHistoryByState.ts new file mode 100644 index 0000000000..807c1325a6 --- /dev/null +++ b/services/app-api/src/postgres/contractAndRates/findAllContractsWithHistoryByState.ts @@ -0,0 +1,49 @@ +import type { PrismaTransactionType } from '../prismaTypes' +import type { ContractType } from '../../domain-models/contractAndRates' +import { NotFoundError } from '../storeError' +import { parseContractWithHistory } from './parseContractWithHistory' +import { includeFullContract } from './prismaSubmittedContractHelpers' + +type ContractOrErrorType = { + contractID: string + contract: ContractType | Error +} + +type ContractOrErrorArrayType = ContractOrErrorType[] + +async function findAllContractsWithHistoryByState( + client: PrismaTransactionType, + stateCode: string +): Promise { + try { + const contracts = await client.contractTable.findMany({ + where: { + stateCode: { + equals: stateCode, + }, + }, + include: includeFullContract, + }) + + if (!contracts) { + const err = `PRISMA ERROR: Cannot find contracts with state code: ${stateCode}` + console.error(err) + return new NotFoundError(err) + } + + const parsedContractsOrErrors: ContractOrErrorArrayType = contracts.map( + (contract) => ({ + contractID: contract.id, + contract: parseContractWithHistory(contract), + }) + ) + + return parsedContractsOrErrors + } catch (err) { + console.error('PRISMA ERROR', err) + return err + } +} + +export { findAllContractsWithHistoryByState } +export type { ContractOrErrorArrayType } diff --git a/services/app-api/src/postgres/contractAndRates/findAllContractsWithHistoryBySubmitInfo.test.ts b/services/app-api/src/postgres/contractAndRates/findAllContractsWithHistoryBySubmitInfo.test.ts new file mode 100644 index 0000000000..57971c69b2 --- /dev/null +++ b/services/app-api/src/postgres/contractAndRates/findAllContractsWithHistoryBySubmitInfo.test.ts @@ -0,0 +1,128 @@ +import { findAllContractsWithHistoryBySubmitInfo } from './findAllContractsWithHistoryBySubmitInfo' +import { sharedTestPrismaClient } from '../../testHelpers/storeHelpers' +import { createInsertContractData, must } from '../../testHelpers' +import { v4 as uuidv4 } from 'uuid' +import { insertDraftContract } from './insertContract' +import { submitContract } from './submitContract' +import { unlockContract } from './unlockContract' + +describe('findAllContractsWithHistoryBySubmittedInfo', () => { + it('returns only contracts that have been submitted or unlocked', async () => { + const client = await sharedTestPrismaClient() + const stateUser = await client.user.create({ + data: { + id: uuidv4(), + givenName: 'Aang', + familyName: 'Avatar', + email: 'aang@example.com', + role: 'STATE_USER', + stateCode: 'NM', + }, + }) + + const cmsUser = await client.user.create({ + data: { + id: uuidv4(), + givenName: 'Zuko', + familyName: 'Hotman', + email: 'zuko@example.com', + role: 'CMS_USER', + }, + }) + + const draftContractData = createInsertContractData({ + submissionDescription: 'one contract', + }) + + // make two submitted contracts and submit them + const contractOne = must( + await insertDraftContract(client, draftContractData) + ) + const contractTwo = must( + await insertDraftContract(client, draftContractData) + ) + const submittedContractOne = must( + await submitContract( + client, + contractOne.id, + stateUser.id, + 'contractOne submit' + ) + ) + const submittedContractTwo = must( + await submitContract( + client, + contractTwo.id, + stateUser.id, + 'contractTwo submit' + ) + ) + + // make two draft contracts + const draftContractOne = must( + await insertDraftContract(client, draftContractData) + ) + const draftContractTwo = must( + await insertDraftContract(client, draftContractData) + ) + + // make one unlocked contract + const contractThree = must( + await insertDraftContract(client, draftContractData) + ) + must( + await submitContract( + client, + contractThree.id, + stateUser.id, + 'unlockContractOne submit' + ) + ) + const unlockedContract = must( + await unlockContract( + client, + contractThree.id, + cmsUser.id, + 'unlock unlockContractOne' + ) + ) + + // call the find by submit info function + const contracts = must( + await findAllContractsWithHistoryBySubmitInfo(client) + ) + + // expect our two submitted contracts + expect(contracts).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + contractID: submittedContractOne.id, + }), + expect.objectContaining({ + contractID: submittedContractTwo.id, + }), + ]) + ) + + // expect our one unlocked contract + expect(contracts).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + contractID: unlockedContract.id, + }), + ]) + ) + + // expect our two draft contracts to not be in the results + expect(contracts).not.toEqual( + expect.arrayContaining([ + expect.objectContaining({ + contractID: draftContractOne.id, + }), + expect.objectContaining({ + contractID: draftContractTwo.id, + }), + ]) + ) + }) +}) diff --git a/services/app-api/src/postgres/contractAndRates/findAllContractsWithHistoryBySubmitInfo.ts b/services/app-api/src/postgres/contractAndRates/findAllContractsWithHistoryBySubmitInfo.ts new file mode 100644 index 0000000000..836f3de513 --- /dev/null +++ b/services/app-api/src/postgres/contractAndRates/findAllContractsWithHistoryBySubmitInfo.ts @@ -0,0 +1,47 @@ +import type { PrismaTransactionType } from '../prismaTypes' +import { NotFoundError } from '../storeError' +import { parseContractWithHistory } from './parseContractWithHistory' +import { includeFullContract } from './prismaSubmittedContractHelpers' +import type { ContractOrErrorArrayType } from './findAllContractsWithHistoryByState' + +async function findAllContractsWithHistoryBySubmitInfo( + client: PrismaTransactionType +): Promise { + try { + const contracts = await client.contractTable.findMany({ + where: { + revisions: { + some: { + submitInfo: { + isNot: null, + }, + }, + }, + stateCode: { + not: 'AS', // exclude test state as per ADR 019 + }, + }, + include: includeFullContract, + }) + + if (!contracts) { + const err = `PRISMA ERROR: Cannot find all contracts by submit info` + console.error(err) + return new NotFoundError(err) + } + + const parsedContracts: ContractOrErrorArrayType = contracts.map( + (contract) => ({ + contractID: contract.id, + contract: parseContractWithHistory(contract), + }) + ) + + return parsedContracts + } catch (err) { + console.error('PRISMA ERROR', err) + return err + } +} + +export { findAllContractsWithHistoryBySubmitInfo } diff --git a/services/app-api/src/postgres/contractAndRates/findRateWithHistory.test.ts b/services/app-api/src/postgres/contractAndRates/findRateWithHistory.test.ts index 90324f1371..5d8fc6a5a2 100644 --- a/services/app-api/src/postgres/contractAndRates/findRateWithHistory.test.ts +++ b/services/app-api/src/postgres/contractAndRates/findRateWithHistory.test.ts @@ -459,7 +459,7 @@ describe('findRate', () => { submissionType: 'CONTRACT_AND_RATES', submissionDescription: 'a.1 body', contractType: 'BASE', - programIDs: ['PMAP'], + programIDs: draftContractData.programIDs, populationCovered: 'MEDICAID', riskBasedContract: false, }, @@ -491,7 +491,7 @@ describe('findRate', () => { submissionType: 'CONTRACT_AND_RATES', submissionDescription: 'a.2 body', contractType: 'BASE', - programIDs: ['PMAP'], + programIDs: draftContractData.programIDs, populationCovered: 'MEDICAID', riskBasedContract: false, }, @@ -532,7 +532,7 @@ describe('findRate', () => { submissionType: 'CONTRACT_AND_RATES', submissionDescription: 'one contract', contractType: 'BASE', - programIDs: ['PMAP'], + programIDs: draftContractData.programIDs, populationCovered: 'MEDICAID', riskBasedContract: false, }) @@ -549,7 +549,7 @@ describe('findRate', () => { submissionType: 'CONTRACT_AND_RATES', submissionDescription: 'one contract', contractType: 'BASE', - programIDs: ['PMAP'], + programIDs: draftContractData.programIDs, populationCovered: 'MEDICAID', riskBasedContract: false, }) @@ -567,7 +567,7 @@ describe('findRate', () => { submissionType: 'CONTRACT_AND_RATES', submissionDescription: 'a.1 body', contractType: 'BASE', - programIDs: ['PMAP'], + programIDs: draftContractData.programIDs, populationCovered: 'MEDICAID', riskBasedContract: false, }) diff --git a/services/app-api/src/postgres/contractAndRates/index.ts b/services/app-api/src/postgres/contractAndRates/index.ts index 46c9a05405..e3922c6c2c 100644 --- a/services/app-api/src/postgres/contractAndRates/index.ts +++ b/services/app-api/src/postgres/contractAndRates/index.ts @@ -1,5 +1,8 @@ export type { InsertContractArgsType } from './insertContract' export type { UpdateContractArgsType } from './updateDraftContract' +export type { ContractOrErrorArrayType } from './findAllContractsWithHistoryByState' export { insertDraftContract } from './insertContract' export { findContractWithHistory } from './findContractWithHistory' export { updateDraftContract } from './updateDraftContract' +export { findAllContractsWithHistoryByState } from './findAllContractsWithHistoryByState' +export { findAllContractsWithHistoryBySubmitInfo } from './findAllContractsWithHistoryBySubmitInfo' diff --git a/services/app-api/src/postgres/contractAndRates/insertContract.test.ts b/services/app-api/src/postgres/contractAndRates/insertContract.test.ts index 9eb300bbdf..2bb8252293 100644 --- a/services/app-api/src/postgres/contractAndRates/insertContract.test.ts +++ b/services/app-api/src/postgres/contractAndRates/insertContract.test.ts @@ -40,7 +40,7 @@ describe('insertContract', () => { submissionType: 'CONTRACT_AND_RATES', submissionDescription: 'Contract 1.0', contractType: 'BASE', - programIDs: ['PMAP'], + programIDs: draftContractData.programIDs, populationCovered: 'MEDICAID', riskBasedContract: false, }), @@ -83,6 +83,7 @@ describe('insertContract', () => { const draftContractData = createInsertContractData({ stateCode: 'CANADA' as StateCodeType, + programIDs: [], }) const draftContract = await insertDraftContract( client, diff --git a/services/app-api/src/postgres/contractAndRates/submitContract.test.ts b/services/app-api/src/postgres/contractAndRates/submitContract.test.ts index 30f731a0a1..084df63290 100644 --- a/services/app-api/src/postgres/contractAndRates/submitContract.test.ts +++ b/services/app-api/src/postgres/contractAndRates/submitContract.test.ts @@ -59,7 +59,7 @@ describe('submitContract', () => { submissionType: 'CONTRACT_AND_RATES', submissionDescription: 'one contract', contractType: 'BASE', - programIDs: ['PMAP'], + programIDs: draftContractData.programIDs, populationCovered: 'MEDICAID', riskBasedContract: false, }), @@ -149,7 +149,7 @@ describe('submitContract', () => { submissionType: 'CONTRACT_AND_RATES', submissionDescription: 'second contract revision', contractType: 'BASE', - programIDs: ['PMAP'], + programIDs: draftContractData.programIDs, populationCovered: 'MEDICAID', riskBasedContract: false, }, diff --git a/services/app-api/src/postgres/postgresStore.ts b/services/app-api/src/postgres/postgresStore.ts index 2fb7891ee6..2c89d0b8e0 100644 --- a/services/app-api/src/postgres/postgresStore.ts +++ b/services/app-api/src/postgres/postgresStore.ts @@ -52,10 +52,13 @@ import { insertDraftContract, findContractWithHistory, updateDraftContract, + findAllContractsWithHistoryByState, + findAllContractsWithHistoryBySubmitInfo, } from './contractAndRates' import type { InsertContractArgsType, UpdateContractArgsType, + ContractOrErrorArrayType, } from './contractAndRates' type Store = { @@ -145,6 +148,14 @@ type Store = { updateDraftContract: ( args: UpdateContractArgsType ) => Promise + + findAllContractsWithHistoryByState: ( + stateCode: string + ) => Promise + + findAllContractsWithHistoryBySubmitInfo: () => Promise< + ContractOrErrorArrayType | Error + > } function NewPostgresStore(client: PrismaClient): Store { @@ -206,6 +217,10 @@ function NewPostgresStore(client: PrismaClient): Store { findContractWithHistory: (args) => findContractWithHistory(client, args), updateDraftContract: (args) => updateDraftContract(client, args), + findAllContractsWithHistoryByState: (args) => + findAllContractsWithHistoryByState(client, args), + findAllContractsWithHistoryBySubmitInfo: () => + findAllContractsWithHistoryBySubmitInfo(client), } } diff --git a/services/app-api/src/resolvers/configureResolvers.ts b/services/app-api/src/resolvers/configureResolvers.ts index 277dd7fc30..4ed8e2d685 100644 --- a/services/app-api/src/resolvers/configureResolvers.ts +++ b/services/app-api/src/resolvers/configureResolvers.ts @@ -42,7 +42,10 @@ export function configureResolvers( store, launchDarkly ), - indexHealthPlanPackages: indexHealthPlanPackagesResolver(store), + indexHealthPlanPackages: indexHealthPlanPackagesResolver( + store, + launchDarkly + ), indexUsers: indexUsersResolver(store), indexQuestions: indexQuestionsResolver(store), fetchEmailSettings: fetchEmailSettingsResolver( diff --git a/services/app-api/src/resolvers/healthPlanPackage/contractAndRates/resolverHelpers.ts b/services/app-api/src/resolvers/healthPlanPackage/contractAndRates/resolverHelpers.ts new file mode 100644 index 0000000000..bcf66f0bda --- /dev/null +++ b/services/app-api/src/resolvers/healthPlanPackage/contractAndRates/resolverHelpers.ts @@ -0,0 +1,62 @@ +import type { ContractOrErrorArrayType } from '../../../postgres/contractAndRates' +import type { Span } from '@opentelemetry/api' +import type { HealthPlanPackageType } from '../../../domain-models' +import type { ContractType } from '../../../domain-models/contractAndRates' +import { convertContractToUnlockedHealthPlanPackage } from '../../../domain-models' +import { logError } from '../../../logger' +import { setErrorAttributesOnActiveSpan } from '../../attributeHelper' + +const validateContractsAndConvert = ( + contractsWithHistory: ContractOrErrorArrayType, + span?: Span +): HealthPlanPackageType[] => { + // separate valid contracts and errors + const parsedContracts: ContractType[] = [] + const errorParseContracts: string[] = [] + contractsWithHistory.forEach((parsed) => { + if (parsed.contract instanceof Error) { + errorParseContracts.push( + `${parsed.contractID}: ${parsed.contract.message}` + ) + } else { + parsedContracts.push(parsed.contract) + } + }) + + // log all contracts that failed parsing to otel. + if (errorParseContracts.length > 0) { + const errMessage = `Failed to parse the following contracts:\n${errorParseContracts.join( + '\n' + )}` + logError('indexHealthPlanPackagesResolver', errMessage) + setErrorAttributesOnActiveSpan(errMessage, span) + } + + // convert contract type to health plan package type and filter out failures + const convertedContracts: HealthPlanPackageType[] = [] + const errorConvertContracts: string[] = [] + parsedContracts.forEach((contract) => { + const parsedContract = + convertContractToUnlockedHealthPlanPackage(contract) + if (parsedContract instanceof Error) { + errorConvertContracts.push( + `${contract.id}: ${parsedContract.message}` + ) + } else { + convertedContracts.push(parsedContract) + } + }) + + // log all contracts that failed converting + if (errorConvertContracts.length > 0) { + const errMessage = `Failed to covert the following contracts to health plan packages:\n${errorConvertContracts.join( + '\n' + )}` + logError('indexHealthPlanPackagesResolver', errMessage) + setErrorAttributesOnActiveSpan(errMessage, span) + } + + return convertedContracts +} + +export { validateContractsAndConvert } diff --git a/services/app-api/src/resolvers/healthPlanPackage/indexHealthPlanPackages.test.ts b/services/app-api/src/resolvers/healthPlanPackage/indexHealthPlanPackages.test.ts index e55dac4de3..f3b5306b63 100644 --- a/services/app-api/src/resolvers/healthPlanPackage/indexHealthPlanPackages.test.ts +++ b/services/app-api/src/resolvers/healthPlanPackage/indexHealthPlanPackages.test.ts @@ -15,8 +15,19 @@ import type { } from '../../gen/gqlServer' import { latestFormData } from '../../testHelpers/healthPlanPackageHelpers' import { testCMSUser, testStateUser } from '../../testHelpers/userHelpers' +import { NewPostgresStore } from '../../postgres' +import type { NotFoundError, Store } from '../../postgres' +import { sharedTestPrismaClient } from '../../testHelpers/storeHelpers' +import type { PrismaTransactionType } from '../../postgres/prismaTypes' +import type { ContractOrErrorArrayType } from '../../postgres/contractAndRates' +import { createContractData, createDraftContractData } from '../../testHelpers' +import { parseContractWithHistory } from '../../postgres/contractAndRates/parseContractWithHistory' +import { testLDService } from '../../testHelpers/launchDarklyHelpers' describe('indexHealthPlanPackages', () => { + it.todo( + 'run all tests with rates-db-refactor on after submit and unlock resolvers have been migrated' + ) const cmsUser = testCMSUser() describe('isStateUser', () => { it('returns a list of submissions that includes newly created entries', async () => { @@ -367,3 +378,111 @@ describe('indexHealthPlanPackages', () => { }) }) }) +describe('indexHealthPlanPackages test rates-db-refactor flag on only', () => { + afterEach(() => { + jest.restoreAllMocks() + }) + + it('correctly filters and log contracts that failed parsing or converting', async () => { + const mockFeatureFlag = testLDService({ 'rates-db-refactor': true }) + const client = await sharedTestPrismaClient() + const errors = jest.spyOn(global.console, 'error').mockImplementation() + + const stateUser = testStateUser() + + // Valid draft contract + const validParsedDraftContract = parseContractWithHistory( + createDraftContractData({ + stateCode: stateUser.stateCode, + }) + ) + + if (validParsedDraftContract instanceof Error) { + throw new Error('Unexpected error in parsing contract in test') + } + + // Valid submitted contract that will error because we cannot convert submitted contract yet, after we implement + // the function to convert submitted contracts we need to figure out how to get this to error, or remove testing + // conversion errors. + const validParsedSubmittedContract = parseContractWithHistory( + createContractData({ + stateCode: stateUser.stateCode, + }) + ) + + if (validParsedSubmittedContract instanceof Error) { + throw new Error('Unexpected error in parsing contract in test') + } + + // create find all that returns parsed contracts and errors + const findAllContractsWithHistoryByState = ( + client: PrismaTransactionType, + stateCode: string + ): Promise => { + const contracts: ContractOrErrorArrayType = [ + { + contractID: validParsedDraftContract.id, + contract: validParsedDraftContract, + }, + { + contractID: validParsedSubmittedContract.id, + contract: validParsedSubmittedContract, + }, + { + contractID: 'errorParsingContract', + contract: new Error('Parsing Error'), + }, + ] + return new Promise((resolve) => resolve(contracts)) + } + + const defaultStore = await NewPostgresStore(client) + const mockStore: Store = { + ...defaultStore, + findAllContractsWithHistoryByState: (args) => + findAllContractsWithHistoryByState(client, stateUser.stateCode), + } + + const server = await constructTestPostgresServer({ + store: mockStore, + ldService: mockFeatureFlag, + context: { + user: stateUser, + }, + }) + + // get all submissions by state + const result = await server.executeOperation({ + query: INDEX_HEALTH_PLAN_PACKAGES, + }) + + const contracts = result.data?.indexHealthPlanPackages.edges + + // expect console.error to log contract that failed parsing + expect(errors).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'indexHealthPlanPackagesResolver failed', + error: expect.stringContaining('errorParsingContract'), + }) + ) + + // expect console.error to log contract that failed coverting + expect(errors).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'indexHealthPlanPackagesResolver failed', + error: expect.stringContaining(validParsedSubmittedContract.id), + }) + ) + + // Expect our contract that passed checks + expect(contracts).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + node: expect.objectContaining({ + id: validParsedDraftContract.id, + }), + }), + ]) + ) + }) +}) diff --git a/services/app-api/src/resolvers/healthPlanPackage/indexHealthPlanPackages.ts b/services/app-api/src/resolvers/healthPlanPackage/indexHealthPlanPackages.ts index 1b43f8f3a0..f2d8dcad9e 100644 --- a/services/app-api/src/resolvers/healthPlanPackage/indexHealthPlanPackages.ts +++ b/services/app-api/src/resolvers/healthPlanPackage/indexHealthPlanPackages.ts @@ -6,12 +6,15 @@ import { isHelpdeskUser } from '../../domain-models/user' import type { QueryResolvers } from '../../gen/gqlServer' import { logError, logSuccess } from '../../logger' import type { Store, StoreError } from '../../postgres' -import { isStoreError } from '../../postgres' +import { isStoreError, NotFoundError } from '../../postgres' import { setErrorAttributesOnActiveSpan, setResolverDetailsOnActiveSpan, setSuccessAttributesOnActiveSpan, } from '../attributeHelper' +import type { LDService } from '../../launchDarkly/launchDarkly' +import { GraphQLError } from 'graphql/index' +import { validateContractsAndConvert } from './contractAndRates/resolverHelpers' const validateAndReturnHealthPlanPackages = ( results: HealthPlanPackageType[] | StoreError, @@ -40,23 +43,92 @@ const validateAndReturnHealthPlanPackages = ( } export function indexHealthPlanPackagesResolver( - store: Store + store: Store, + launchDarkly: LDService ): QueryResolvers['indexHealthPlanPackages'] { return async (_parent, _args, context) => { const { user, span } = context setResolverDetailsOnActiveSpan('fetchHealthPlanPackage', user, span) + const ratesDatabaseRefactor = await launchDarkly.getFeatureFlag( + context, + 'rates-db-refactor' + ) + if (isStateUser(user)) { - const results = await store.findAllHealthPlanPackagesByState( - user.stateCode - ) + let results: StoreError | HealthPlanPackageType[] = [] + if (ratesDatabaseRefactor) { + const contractsWithHistory = + await store.findAllContractsWithHistoryByState( + user.stateCode + ) + + if (contractsWithHistory instanceof Error) { + const errMessage = `Issue finding contracts with history by stateCode: ${user.stateCode}. Message: ${contractsWithHistory.message}` + logError('fetchHealthPlanPackage', errMessage) + setErrorAttributesOnActiveSpan(errMessage, span) + + if (contractsWithHistory instanceof NotFoundError) { + throw new GraphQLError(errMessage, { + extensions: { + code: 'NOT_FOUND', + cause: 'DB_ERROR', + }, + }) + } + + throw new GraphQLError(errMessage, { + extensions: { + code: 'INTERNAL_SERVER_ERROR', + cause: 'DB_ERROR', + }, + }) + } + + results = validateContractsAndConvert(contractsWithHistory) + } else { + results = await store.findAllHealthPlanPackagesByState( + user.stateCode + ) + } + return validateAndReturnHealthPlanPackages(results, span) } else if ( isCMSUser(user) || isAdminUser(user) || isHelpdeskUser(user) ) { - const results = await store.findAllHealthPlanPackagesBySubmittedAt() + let results: StoreError | HealthPlanPackageType[] = [] + if (ratesDatabaseRefactor) { + const contractsWithHistory = + await store.findAllContractsWithHistoryBySubmitInfo() + + if (contractsWithHistory instanceof Error) { + const errMessage = `Issue finding contracts with history by submit info. Message: ${contractsWithHistory.message}` + logError('fetchHealthPlanPackage', errMessage) + setErrorAttributesOnActiveSpan(errMessage, span) + + if (contractsWithHistory instanceof NotFoundError) { + throw new GraphQLError(errMessage, { + extensions: { + code: 'NOT_FOUND', + cause: 'DB_ERROR', + }, + }) + } + + throw new GraphQLError(errMessage, { + extensions: { + code: 'INTERNAL_SERVER_ERROR', + cause: 'DB_ERROR', + }, + }) + } + + results = validateContractsAndConvert(contractsWithHistory) + } else { + results = await store.findAllHealthPlanPackagesBySubmittedAt() + } return validateAndReturnHealthPlanPackages(results, span) } else { diff --git a/services/app-api/src/testHelpers/contractAndRates/contractHelpers.ts b/services/app-api/src/testHelpers/contractAndRates/contractHelpers.ts index ea8d53ad24..3464c0c8e0 100644 --- a/services/app-api/src/testHelpers/contractAndRates/contractHelpers.ts +++ b/services/app-api/src/testHelpers/contractAndRates/contractHelpers.ts @@ -1,7 +1,4 @@ import type { InsertContractArgsType } from '../../postgres/contractAndRates/insertContract' -import type { State } from '@prisma/client' -import { must } from '../errorHelpers' -import type { PrismaClient } from '@prisma/client' import { v4 as uuidv4 } from 'uuid' import type { ContractRevisionTableWithRates, @@ -9,57 +6,44 @@ import type { } from '../../postgres/contractAndRates/prismaSubmittedContractHelpers' import type { StateCodeType } from 'app-web/src/common-code/healthPlanFormDataType' import type { ContractFormDataType } from '../../domain-models/contractAndRates' +import { getProgramsFromState } from '../stateHelpers' const createInsertContractData = ({ - stateCode, + stateCode = 'MN', ...formData }: { stateCode?: StateCodeType } & Partial): InsertContractArgsType => { return { - stateCode: stateCode ?? 'MN', + stateCode: stateCode, submissionType: formData?.submissionType ?? 'CONTRACT_AND_RATES', submissionDescription: formData?.submissionDescription ?? 'Contract 1.0', contractType: formData?.contractType ?? 'BASE', - programIDs: formData?.programIDs ?? ['PMAP'], + programIDs: formData?.programIDs ?? [ + getProgramsFromState(stateCode ?? 'MN')[0].id, + ], populationCovered: formData?.populationCovered ?? 'MEDICAID', riskBasedContract: formData?.riskBasedContract ?? false, } } -const getStateRecord = async ( - client: PrismaClient, - stateCode: string -): Promise => { - const state = must( - await client.state.findFirst({ - where: { - stateCode, - }, - }) - ) - - if (!state) { - throw new Error('Unexpected prisma error: state record not found') - } - - return state -} - const createDraftContractData = ( contract?: Partial ): ContractTableFullPayload => ({ id: '24fb2a5f-6d0d-4e26-9906-4de28927c882', createdAt: new Date(), updatedAt: new Date(), - stateCode: 'FL', + stateCode: 'MN', stateNumber: 111, revisions: contract?.revisions ?? [ - createContractRevision({ - rateRevisions: undefined, - submitInfo: null, - }) as ContractRevisionTableWithRates, + createContractRevision( + { + rateRevisions: undefined, + submitInfo: null, + }, + contract?.stateCode as StateCodeType + ) as ContractRevisionTableWithRates, ], ...contract, }) @@ -70,18 +54,22 @@ const createContractData = ( id: '24fb2a5f-6d0d-4e26-9906-4de28927c882', createdAt: new Date(), updatedAt: new Date(), - stateCode: 'FL', + stateCode: 'MN', stateNumber: 111, revisions: contract?.revisions ?? [ - createContractRevision({ - draftRates: undefined, - }) as ContractRevisionTableWithRates, + createContractRevision( + { + draftRates: undefined, + }, + contract?.stateCode as StateCodeType + ) as ContractRevisionTableWithRates, ], ...contract, }) const createContractRevision = ( - revision?: Partial + revision?: Partial, + stateCode: StateCodeType = 'MN' ): ContractRevisionTableWithRates => ({ id: uuidv4(), createdAt: new Date(), @@ -100,14 +88,14 @@ const createContractRevision = ( email: 'boblaw@example.com', role: 'STATE_USER', divisionAssignment: null, - stateCode: 'OH', + stateCode: stateCode, }, }, unlockInfo: null, contractID: 'contractID', submitInfoID: null, unlockInfoID: null, - programIDs: ['Program'], + programIDs: [getProgramsFromState(stateCode)[0].id], populationCovered: 'MEDICAID' as const, submissionType: 'CONTRACT_ONLY' as const, riskBasedContract: false, @@ -174,7 +162,6 @@ const createContractRevision = ( export { createInsertContractData, - getStateRecord, createContractRevision, createContractData, createDraftContractData, diff --git a/services/app-api/src/testHelpers/contractAndRates/rateHelpers.ts b/services/app-api/src/testHelpers/contractAndRates/rateHelpers.ts index db6faa98dc..067a88dc1b 100644 --- a/services/app-api/src/testHelpers/contractAndRates/rateHelpers.ts +++ b/services/app-api/src/testHelpers/contractAndRates/rateHelpers.ts @@ -1,50 +1,37 @@ - -import { must } from '../errorHelpers' import { v4 as uuidv4 } from 'uuid' -import type { PrismaClient,State } from '@prisma/client' import type { InsertRateArgsType } from '../../postgres/contractAndRates/insertRate' -import type { RateTableFullPayload, RateRevisionTableWithContracts } from '../../postgres/contractAndRates/prismaSubmittedRateHelpers' +import type { + RateTableFullPayload, + RateRevisionTableWithContracts, +} from '../../postgres/contractAndRates/prismaSubmittedRateHelpers' +import { getProgramsFromState } from '../stateHelpers' +import type { StateCodeType } from 'app-web/src/common-code/healthPlanFormDataType' + const createInsertRateData = ( rateArgs?: Partial ): InsertRateArgsType => { return { stateCode: rateArgs?.stateCode ?? 'MN', - ...rateArgs + ...rateArgs, } } -const getStateRecord = async ( - client: PrismaClient, - stateCode: string -): Promise => { - const state = must( - await client.state.findFirst({ - where: { - stateCode, - }, - }) - ) - - if (!state) { - throw new Error('Unexpected prisma error: state record not found') - } - - return state -} - const createDraftRateData = ( rate?: Partial -): RateTableFullPayload=> ({ +): RateTableFullPayload => ({ id: '24fb2a5f-6d0d-4e26-9906-4de28927c882', createdAt: new Date(), updatedAt: new Date(), - stateCode: 'FL', + stateCode: 'MN', stateNumber: 111, revisions: rate?.revisions ?? [ - createRateRevision({ - contractRevisions: undefined, - submitInfo: null, - }) as RateRevisionTableWithContracts, + createRateRevision( + { + contractRevisions: undefined, + submitInfo: null, + }, + rate?.stateCode as StateCodeType + ) as RateRevisionTableWithContracts, ], ...rate, }) @@ -55,22 +42,23 @@ const createRateData = ( id: '24fb2a5f-6d0d-4e26-9906-4de28927c882', createdAt: new Date(), updatedAt: new Date(), - stateCode: 'FL', + stateCode: 'MN', stateNumber: 111, revisions: rate?.revisions ?? [ - createRateRevision({ - draftContracts: undefined, - }) as RateRevisionTableWithContracts, + createRateRevision( + { + draftContracts: undefined, + }, + rate?.stateCode as StateCodeType + ) as RateRevisionTableWithContracts, ], ...rate, }) const createRateRevision = ( - revision?: Partial< - RateRevisionTableWithContracts - > -): - RateRevisionTableWithContracts => ({ + revision?: Partial, + stateCode: StateCodeType = 'MN' +): RateRevisionTableWithContracts => ({ id: uuidv4(), createdAt: new Date(), updatedAt: new Date(), @@ -88,16 +76,16 @@ const createRateRevision = ( email: 'boblaw@example.com', role: 'STATE_USER', divisionAssignment: null, - stateCode: 'OH', + stateCode: stateCode, }, }, unlockInfo: null, submitInfoID: null, unlockInfoID: null, rateType: 'NEW', - rateID: 'rateID', + rateID: 'rateID', rateCertificationName: 'testState-123', - rateProgramIDs: ['Program'], + rateProgramIDs: [getProgramsFromState(stateCode)[0].id], rateCapitationType: 'RATE_CELL', rateDateStart: new Date(), rateDateEnd: new Date(), @@ -145,7 +133,6 @@ const createRateRevision = ( export { createInsertRateData, - getStateRecord, createRateRevision, createRateData, createDraftRateData, diff --git a/services/app-api/src/testHelpers/gqlHelpers.ts b/services/app-api/src/testHelpers/gqlHelpers.ts index a717b43712..3badabe096 100644 --- a/services/app-api/src/testHelpers/gqlHelpers.ts +++ b/services/app-api/src/testHelpers/gqlHelpers.ts @@ -36,11 +36,11 @@ import { sharedTestPrismaClient } from './storeHelpers' import { domainToBase64 } from 'app-web/src/common-code/proto/healthPlanFormDataProto' import type { EmailParameterStore } from '../parameterStore' import { newLocalEmailParameterStore } from '../parameterStore' -import statePrograms from 'app-web/src/common-code/data/statePrograms.json' import { testLDService } from './launchDarklyHelpers' import type { LDService } from '../launchDarkly/launchDarkly' import { insertUserToLocalAurora } from '../authn' import { testStateUser } from './userHelpers' +import { getProgramsFromState } from './stateHelpers' // Since our programs are checked into source code, we have a program we // use as our default @@ -60,12 +60,6 @@ function defaultFloridaRateProgram(): ProgramType { } } -function getProgramsFromState(stateCode: StateCodeType): ProgramType[] { - const state = statePrograms.states.find((st) => st.code === stateCode) - - return state?.programs || [] -} - const defaultContext = (): Context => { return { user: testStateUser(), diff --git a/services/app-api/src/testHelpers/index.ts b/services/app-api/src/testHelpers/index.ts index 31ff891c6e..1ed448dc40 100644 --- a/services/app-api/src/testHelpers/index.ts +++ b/services/app-api/src/testHelpers/index.ts @@ -8,8 +8,9 @@ export { must } from './errorHelpers' export { createInsertContractData, - getStateRecord, createContractData, createContractRevision, createDraftContractData, } from './contractAndRates/contractHelpers' + +export { getProgramsFromState, getStateRecord } from './stateHelpers' diff --git a/services/app-api/src/testHelpers/stateHelpers.ts b/services/app-api/src/testHelpers/stateHelpers.ts new file mode 100644 index 0000000000..cec1ba1d30 --- /dev/null +++ b/services/app-api/src/testHelpers/stateHelpers.ts @@ -0,0 +1,32 @@ +import type { StateCodeType } from 'app-web/src/common-code/healthPlanFormDataType' +import type { ProgramType } from '../domain-models' +import statePrograms from 'app-web/src/common-code/data/statePrograms.json' +import type { PrismaClient, State } from '@prisma/client' +import { must } from './errorHelpers' + +function getProgramsFromState(stateCode: StateCodeType): ProgramType[] { + const state = statePrograms.states.find((st) => st.code === stateCode) + + return state?.programs || [] +} + +async function getStateRecord( + client: PrismaClient, + stateCode: string +): Promise { + const state = must( + await client.state.findFirst({ + where: { + stateCode, + }, + }) + ) + + if (!state) { + throw new Error('Unexpected prisma error: state record not found') + } + + return state +} + +export { getProgramsFromState, getStateRecord } diff --git a/services/app-api/src/testHelpers/storeHelpers.ts b/services/app-api/src/testHelpers/storeHelpers.ts index 1d8770d3ba..34a8d8f625 100644 --- a/services/app-api/src/testHelpers/storeHelpers.ts +++ b/services/app-api/src/testHelpers/storeHelpers.ts @@ -38,6 +38,10 @@ function mockStoreThatErrors(): Store { message: 'this error came from the generic store with errors mock', } + const genericError: Error = new Error( + 'UNEXPECTED_EXCEPTION: This error came from the generic store with errors mock' + ) + return { findAllHealthPlanPackagesByState: async (_stateCode) => { return genericStoreError @@ -98,20 +102,20 @@ function mockStoreThatErrors(): Store { return genericStoreError }, insertDraftContract: async (_ID) => { - return new Error( - 'UNEXPECTED_EXCEPTION: This error came from the generic store with errors mock' - ) + return genericError }, findContractWithHistory: async (_ID) => { - return new Error( - 'UNEXPECTED_EXCEPTION: This error came from the generic store with errors mock' - ) + return genericError }, updateDraftContract: async (_ID) => { - return new Error( - 'UNEXPECTED_EXCEPTION: This error came from the generic store with errors mock' - ) + return genericError + }, + findAllContractsWithHistoryByState: async (_ID) => { + return genericError + }, + findAllContractsWithHistoryBySubmitInfo: async () => { + return genericError }, } }