From b682d5878b7afc970a45ef13799d7bd7a59c3d60 Mon Sep 17 00:00:00 2001 From: Rory Doak Date: Wed, 29 Jan 2025 18:58:55 +0000 Subject: [PATCH] update fn for checking stat app and adjust files --- .../modules/flows/publish/helpers.test.ts | 95 ++++++++ api.planx.uk/modules/flows/publish/helpers.ts | 45 ++++ .../modules/flows/publish/publish.test.ts | 212 +----------------- api.planx.uk/modules/flows/publish/service.ts | 8 +- .../flows/publish/service/applicationTypes.ts | 50 ----- 5 files changed, 145 insertions(+), 265 deletions(-) create mode 100644 api.planx.uk/modules/flows/publish/helpers.test.ts create mode 100644 api.planx.uk/modules/flows/publish/helpers.ts delete mode 100644 api.planx.uk/modules/flows/publish/service/applicationTypes.ts diff --git a/api.planx.uk/modules/flows/publish/helpers.test.ts b/api.planx.uk/modules/flows/publish/helpers.test.ts new file mode 100644 index 0000000000..79756d6655 --- /dev/null +++ b/api.planx.uk/modules/flows/publish/helpers.test.ts @@ -0,0 +1,95 @@ +import { + ComponentType as TYPES, + type FlowGraph, +} from "@opensystemslab/planx-core/types"; +import { hasStatutoryApplicationType } from "./helpers.js"; + +const mockStatutoryFlow: FlowGraph = { + _root: { + edges: ["QuestionOne", "Send"], + }, + QuestionTwo: { + data: { + fn: "application.type", + text: "What type of application is it?", + neverAutoAnswer: false, + }, + type: TYPES.Question, + edges: [ + "AnswerWithDiscretionaryApplicationValue", + "AnswerWithStatutoryApplicationValue", + "VeeQdrkcef", + ], + }, + AnswerWithDiscretionaryApplicationValue: { + data: { + val: "findOut", + text: "Find out if", + }, + type: TYPES.Answer, + }, + VeeQdrkcef: { + data: { + text: "Something else", + }, + type: TYPES.Answer, + }, + AnswerWithStatutoryApplicationValue: { + data: { + val: "ldc", + text: "LDC", + }, + type: TYPES.Answer, + }, + QuestionOne: { + type: TYPES.Question, + data: { + text: "Branching question", + neverAutoAnswer: false, + }, + edges: ["7lDopQVOjk", "V5ZV8milBj"], + }, + "7lDopQVOjk": { + type: TYPES.Answer, + data: { + text: "Left", + }, + edges: ["QuestionTwo"], + }, + V5ZV8milBj: { + type: TYPES.Answer, + data: { + text: "Right", + }, + }, + Send: { + type: TYPES.Send, + data: { + title: "Send to email", + destinations: ["email"], + }, + }, +}; + +const mockStatutoryFlowWithoutSend = { ...mockStatutoryFlow }; +delete mockStatutoryFlowWithoutSend["Send"]; + +describe("hasStatutoryApplicationPath", () => { + test("returns false for a flow that doesn't have a Send", () => { + expect(hasStatutoryApplicationType(mockStatutoryFlowWithoutSend)).toEqual( + false, + ); + }); + + test.todo("returns false for a flow with Send but not any application.type"); + + test.todo( + "returns false for a flow with Send but only discretionary application.type values", + ); + + test("returns true for a flow with Send and at least one statutory application.type value", () => { + expect(hasStatutoryApplicationType(mockStatutoryFlow)).toEqual(true); + }); +}); + +// TODO also add mocks which use SetValue, etc diff --git a/api.planx.uk/modules/flows/publish/helpers.ts b/api.planx.uk/modules/flows/publish/helpers.ts new file mode 100644 index 0000000000..fbb68fd6b5 --- /dev/null +++ b/api.planx.uk/modules/flows/publish/helpers.ts @@ -0,0 +1,45 @@ +import { + ComponentType, + type FlowGraph, +} from "@opensystemslab/planx-core/types"; +import { hasComponentType } from "../validate/helpers.js"; +import { getValidSchemaValues } from "@opensystemslab/planx-core"; + +export const hasStatutoryApplicationType = (flattenedFlow: FlowGraph) => { + const hasSendComponent = hasComponentType(flattenedFlow, ComponentType.Send); + if (!hasSendComponent) return false; + + const statutoryApplicationTypes = getValidSchemaValues("ApplicationType"); + if (!statutoryApplicationTypes) return false; + + let isStatutoryApplication = false; + Object.entries(flattenedFlow).some(([nodeId, _nodeData]) => { + const nodeToCheck = flattenedFlow[nodeId]; + + // Only continue if application.type exists in a Node + if (nodeToCheck?.data?.fn === "application.type") { + // Check SetValue as data.val will be in node, not edge + if (typeof nodeToCheck.data?.val === "string") { + isStatutoryApplication = statutoryApplicationTypes.includes( + nodeToCheck.data?.val, + ); + return isStatutoryApplication; + } + + // Check other Nodes which have Edges + if (nodeToCheck.edges) { + // Loop through each edge and check the value + nodeToCheck.edges.some((edge) => { + const edgeData = flattenedFlow[edge].data; + if (typeof edgeData?.val === "string") { + isStatutoryApplication = statutoryApplicationTypes.includes( + edgeData.val, + ); + return isStatutoryApplication; + } + }); + } + } + }); + return isStatutoryApplication; +}; diff --git a/api.planx.uk/modules/flows/publish/publish.test.ts b/api.planx.uk/modules/flows/publish/publish.test.ts index 9bc9e321fa..b9a0571013 100644 --- a/api.planx.uk/modules/flows/publish/publish.test.ts +++ b/api.planx.uk/modules/flows/publish/publish.test.ts @@ -5,13 +5,6 @@ import { authHeader, getTestJWT } from "../../../tests/mockJWT.js"; import app from "../../../server.js"; import { userContext } from "../../auth/middleware.js"; import { mockFlowData } from "../../../tests/mocks/validateAndPublishMocks.js"; -import { - applicationTypeFail, - checklistApplicationTypePass, - questionApplicationTypePass, - setValueApplicationTypePass, -} from "../../../tests/mocks/applicationTypeCheckMocks.js"; -import * as applicationTypes from "./service/applicationTypes.js"; beforeAll(() => { const getStoreMock = vi.spyOn(userContext, "getStore"); @@ -89,7 +82,10 @@ describe("publish", () => { }, }, }); - await supertest(app).post("/flows/1/publish").set(auth).expect(200); + await supertest(app) + .post("/flows/1/publish") + .set(authHeader({ role: "platformAdmin" })) + .expect(200); }); it("does not update if there are no new changes", async () => { @@ -185,203 +181,3 @@ describe("publish", () => { }); }); }); - -describe("how 'is_statutory_application_Type' is updated when a service is published", () => { - beforeEach(() => { - const getStoreMock = vi.spyOn(userContext, "getStore"); - getStoreMock.mockReturnValue({ - user: { - sub: "123", - jwt: getTestJWT({ role: "teamEditor" }), - }, - }); - }); - it("checks that is_statutory_application_type is true for SetValue component", async () => { - const checkStatutoryApplicationMock = vi.spyOn( - applicationTypes, - "checkStatutoryApplicationTypes", - ); - - const alteredFlow = { - ...mockFlowData, - ...setValueApplicationTypePass, - }; - - queryMock.mockQuery({ - name: "GetFlowData", - matchOnVariables: false, - data: { - flow: { - data: alteredFlow, - slug: "stat-app-set-value", - team_id: 1, - team: { - slug: "testing", - }, - publishedFlows: [{ data: alteredFlow }], - }, - }, - }); - - queryMock.mockQuery({ - name: "PublishFlow", - matchOnVariables: false, - data: { - publishedFlow: { - data: alteredFlow, - }, - }, - }); - - await supertest(app) - .post("/flows/1/publish") - .set(auth) - .expect(200) - .then(() => { - const [isStatutoryApplicationType] = - checkStatutoryApplicationMock.mock.results; - expect(isStatutoryApplicationType.value).toEqual(true); - }); - }); - it("checks whether is_statutory_application_type is true for Checklist component", async () => { - const checkStatutoryApplicationMock = vi.spyOn( - applicationTypes, - "checkStatutoryApplicationTypes", - ); - - const alteredFlow = { - ...mockFlowData, - ...checklistApplicationTypePass, - }; - - queryMock.mockQuery({ - name: "GetFlowData", - matchOnVariables: false, - data: { - flow: { - data: alteredFlow, - slug: "stat-app-checklist", - team_id: 1, - team: { - slug: "testing", - }, - publishedFlows: [{ data: alteredFlow }], - }, - }, - }); - - queryMock.mockQuery({ - name: "PublishFlow", - matchOnVariables: false, - data: { - publishedFlow: { - data: alteredFlow, - }, - }, - }); - - await supertest(app) - .post("/flows/1/publish") - .set(auth) - .expect(200) - .then(() => { - const [isStatutoryApplicationType] = - checkStatutoryApplicationMock.mock.results; - expect(isStatutoryApplicationType.value).toEqual(true); - }); - }); - it("checks is_statutory_application_type is true for Question component", async () => { - const checkStatutoryApplicationMock = vi.spyOn( - applicationTypes, - "checkStatutoryApplicationTypes", - ); - - const alteredFlow = { - ...mockFlowData, - ...questionApplicationTypePass, - }; - - queryMock.mockQuery({ - name: "GetFlowData", - matchOnVariables: false, - data: { - flow: { - data: alteredFlow, - slug: "stat-app-question", - team_id: 1, - team: { - slug: "testing", - }, - publishedFlows: [{ data: alteredFlow }], - }, - }, - }); - - queryMock.mockQuery({ - name: "PublishFlow", - matchOnVariables: false, - data: { - publishedFlow: { - data: alteredFlow, - }, - }, - }); - - await supertest(app) - .post("/flows/1/publish") - .set(auth) - .expect(200) - .then(() => { - const [isStatutoryApplicationType] = - checkStatutoryApplicationMock.mock.results; - expect(isStatutoryApplicationType.value).toEqual(true); - }); - }); - it("checks is_statutory_application_type is false when no application type matches schema", async () => { - const checkStatutoryApplicationMock = vi.spyOn( - applicationTypes, - "checkStatutoryApplicationTypes", - ); - - const alteredFlow = { - ...mockFlowData, - ...applicationTypeFail, - }; - - queryMock.mockQuery({ - name: "GetFlowData", - matchOnVariables: false, - data: { - flow: { - data: alteredFlow, - slug: "stat-app-fail", - team_id: 1, - team: { - slug: "testing", - }, - publishedFlows: [{ data: alteredFlow }], - }, - }, - }); - - queryMock.mockQuery({ - name: "PublishFlow", - matchOnVariables: false, - data: { - publishedFlow: { - data: alteredFlow, - }, - }, - }); - - await supertest(app) - .post("/flows/1/publish") - .set(auth) - .expect(200) - .then(() => { - const [isStatutoryApplicationType] = - checkStatutoryApplicationMock.mock.results; - expect(isStatutoryApplicationType.value).toEqual(false); - }); - }); -}); diff --git a/api.planx.uk/modules/flows/publish/service.ts b/api.planx.uk/modules/flows/publish/service.ts index 7fd2361102..adf1d4e12a 100644 --- a/api.planx.uk/modules/flows/publish/service.ts +++ b/api.planx.uk/modules/flows/publish/service.ts @@ -9,10 +9,6 @@ import { import { userContext } from "../../auth/middleware.js"; import { getClient } from "../../../client/index.js"; import { hasComponentType } from "../validate/helpers.js"; -import { - checkStatutoryApplicationTypes, - getApplicationTypeVals, -} from "./service/applicationTypes.js"; interface PublishFlow { publishedFlow: { @@ -29,8 +25,6 @@ export const publishFlow = async (flowId: string, summary?: string) => { if (!userId) throw Error("User details missing from request"); const flattenedFlow = await dataMerged(flowId); - const isStatutoryApplication = checkStatutoryApplicationTypes(flattenedFlow); - const typeVals = getApplicationTypeVals(flattenedFlow); const mostRecent = await getMostRecentPublishedFlow(flowId); const hasSendComponent = hasComponentType(flattenedFlow, ComponentType.Send); const delta = jsondiffpatch.diff(mostRecent, flattenedFlow); @@ -73,7 +67,7 @@ export const publishFlow = async (flowId: string, summary?: string) => { publisher_id: parseInt(userId), summary: summary ?? null, has_send_component: hasSendComponent, - is_statutory_application_type: isStatutoryApplication, + is_statutory_application_type: false, }, ); diff --git a/api.planx.uk/modules/flows/publish/service/applicationTypes.ts b/api.planx.uk/modules/flows/publish/service/applicationTypes.ts deleted file mode 100644 index 57a3d1b5ca..0000000000 --- a/api.planx.uk/modules/flows/publish/service/applicationTypes.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { - ComponentType, - type FlowGraph, -} from "@opensystemslab/planx-core/types"; -import { getValidSchemaValues } from "@opensystemslab/planx-core"; -import { isComponentType } from "../../helpers.js"; - -export const checkStatutoryApplicationTypes = (flattenedFlow: FlowGraph) => { - const applicationTypes = getApplicationTypeVals(flattenedFlow); - const validApplicationTypes = getValidSchemaValues("ApplicationType"); - const isStatutoryApplication = applicationTypes.some((value) => - validApplicationTypes?.includes(value), - ); - return isStatutoryApplication; -}; - -export const getApplicationTypeVals = (flowGraph: FlowGraph): string[] => { - const nodesWithApplicationType = Object.entries(flowGraph) - .map((node) => { - const checkComponentTypes = - isComponentType(node, ComponentType.Checklist) || - isComponentType(node, ComponentType.Question) || - isComponentType(node, ComponentType.SetValue); - - if (checkComponentTypes && node[1].data?.fn === "application.type") { - return node[0]; - } - - return; - }) - .filter((node) => node !== undefined); - - const answerVals: string[] = []; - nodesWithApplicationType.forEach((node) => { - if (flowGraph[node].edges) { - flowGraph[node].edges.forEach( - (edge) => - typeof flowGraph[edge]?.data?.val === "string" && - answerVals.push(flowGraph[edge]?.data?.val), - ); - } - if (typeof flowGraph[node]?.data?.val === "string") { - answerVals.push(flowGraph[node]?.data?.val); - } - }); - - const uniqueAnswerVals = [...new Set(answerVals)]; - - return uniqueAnswerVals; -};