TASK22224: modernize involvement and updatecase handlers

This commit is contained in:
2026-03-23 18:23:54 +00:00
parent 3f2b945172
commit ba9beda65e
5 changed files with 195 additions and 60 deletions
+46
View File
@@ -379,3 +379,49 @@ Validation:
Follow-ups:
- Optional next big slice: bring remaining proxy/message routes using raw axios promise chains (`createcaseinvolvement_api.js`, `createrepinvolvement_api.js`, `updatecase_api.js`) onto the same async/await + explicit contract pattern.
---
### CL-010: TASK22224 involvement/update route async contract hardening bundle
date: 2026-03-23
author: Cline
scope: `pages/api/file/{createcaseinvolvement_api,createrepinvolvement_api,updatecase_api}.js`, `tests/phase21/file-handler-contract.test.cjs`
type: change
rationale: Execute the next requested slice by modernizing remaining relay-backed involvement/update handlers that still used axios promise chains and legacy dead imports.
impact: Improves reliability/readability and preserves existing response contracts, including 412 "record exists" semantics for involvement creation flows.
status: completed
Summary:
- `createcaseinvolvement_api.js`
- removed unused `CryptoJS` import
- refactored axios `.then/.catch` to explicit `try/catch`
- preserved conflict behavior: status 412 -> success `{ record: "exists" }`
- preserved failure contract: `CREATE_CASE_INVOLVEMENT_FAILED`
- `createrepinvolvement_api.js`
- removed unused `CryptoJS` import
- refactored axios `.then/.catch` to explicit `try/catch`
- preserved conflict behavior: status 412 -> success `{ record: "exists" }`
- preserved failure contract: `CREATE_REP_INVOLVEMENT_FAILED`
- `updatecase_api.js`
- removed unused `CryptoJS` import
- refactored axios `.then/.catch` to explicit `try/catch`
- preserved failure contract: `UPDATE_CASE_FAILED`
- Phase21 tests expanded:
- createcaseinvolvement 412 conflict success contract
- createrepinvolvement dependency failure contract
- updatecase dependency failure contract
Validation:
- `node tests/phase21/file-handler-contract.test.cjs` -> pass (49/49)
- `node tests/phase21/api-contract-slice1.test.cjs` -> pass
- helper: 4/4
- file-handler: 49/49
- email-handler: 12/12
- endpoint-handler: 149/149
Follow-ups:
- Optional: apply equivalent modernization to any remaining relay-backed handlers outside `pages/api/file/` that still use raw axios promise chains and have no explicit phase21 contract assertions.
+21 -20
View File
@@ -11,7 +11,6 @@
*/
import axios from "axios";
import CryptoJS from "crypto-js";
import { consoleLogger } from "../../../actions/core/logger";
import { getToken } from "../../../actions/core/token";
import { hashAPIPath } from "../../../actions/core/hash";
@@ -36,20 +35,20 @@ export default async function ApiProxy(req, res) {
});
}
var token = await getToken();
var contactid = req.body.contactid;
var queryUrl =
const token = await getToken();
const contactid = req.body.contactid;
const queryUrl =
"incidents(" +
req.body.incidentid +
")/pinswg_incident_contact_case_involvement/$ref";
var crmUrl = "https://" + process.env.CRMURL;
const crmUrl = "https://" + process.env.CRMURL;
var data = {
const data = {
"@odata.id": crmUrl + "/api/data/v8.2/contacts(" + contactid + ")"
};
var config = {
const config = {
method: "post",
url: WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
headers: {
@@ -64,18 +63,20 @@ export default async function ApiProxy(req, res) {
data: data
};
return axios(config)
.then(({ data }) => {
return respondSuccess(res, data);
})
.catch((error) => {
return error.status == 412
? respondSuccess(res, { "record": "exists" })
: (consoleLogger(Object.assign(error, data)),
respondError(res, {
status: 400,
code: "CREATE_CASE_INVOLVEMENT_FAILED",
message: "Failed to create case involvement"
}));
try {
const { data: responseData } = await axios(config);
return respondSuccess(res, responseData);
} catch (error) {
const errorStatus = error?.status || error?.response?.status;
if (errorStatus == 412) {
return respondSuccess(res, { record: "exists" });
}
consoleLogger(Object.assign(error, data));
return respondError(res, {
status: 400,
code: "CREATE_CASE_INVOLVEMENT_FAILED",
message: "Failed to create case involvement"
});
}
}
+22 -21
View File
@@ -11,7 +11,6 @@
*/
import axios from "axios";
import CryptoJS from "crypto-js";
import { consoleLogger } from "../../../actions/core/logger";
import { getToken } from "../../../actions/core/token";
import { hashAPIPath } from "../../../actions/core/hash";
@@ -36,22 +35,22 @@ export default async function ApiProxy(req, res) {
});
}
var token = await getToken();
var contactid = req.body.contactid;
var queryUrl =
const token = await getToken();
const contactid = req.body.contactid;
const queryUrl =
"incidents(" +
req.body.incidentid +
")/pinswg_incident_contact_case_involvement/$ref";
var crmUrl = "https://" + process.env.CRMURL;
var crmVersion = process.env.CRMURL_VERSION;
const crmUrl = "https://" + process.env.CRMURL;
const crmVersion = process.env.CRMURL_VERSION;
var data = {
const data = {
"@odata.id":
crmUrl + "/api/data/" + crmVersion + "/contacts(" + contactid + ")"
};
var config = {
const config = {
method: "post",
url: WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
headers: {
@@ -66,18 +65,20 @@ export default async function ApiProxy(req, res) {
data: data
};
return axios(config)
.then(({ data }) => {
return respondSuccess(res, data);
})
.catch((error) => {
return error.status == 412
? respondSuccess(res, { "record": "exists" })
: (consoleLogger(Object.assign(error, data)),
respondError(res, {
status: 400,
code: "CREATE_REP_INVOLVEMENT_FAILED",
message: "Failed to create representation involvement"
}));
try {
const { data: responseData } = await axios(config);
return respondSuccess(res, responseData);
} catch (error) {
const errorStatus = error?.status || error?.response?.status;
if (errorStatus == 412) {
return respondSuccess(res, { record: "exists" });
}
consoleLogger(Object.assign(error, data));
return respondError(res, {
status: 400,
code: "CREATE_REP_INVOLVEMENT_FAILED",
message: "Failed to create representation involvement"
});
}
}
+17 -19
View File
@@ -1,5 +1,4 @@
import axios from "axios";
import CryptoJS from "crypto-js";
import { consoleLogger } from "../../../actions/core/logger";
import { getToken } from "../../../actions/core/token";
import { hashAPIPath } from "../../../actions/core/hash";
@@ -10,12 +9,12 @@ const WEBAPI_URL =
"https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/";
export default async function ApiProxy(req, res) {
var data = JSON.stringify(req.body);
var appealObj = req.query.appealObj;
var incidentID = req.query.incident;
const data = JSON.stringify(req.body);
const appealObj = req.query.appealObj;
const incidentID = req.query.incident;
var updateFormCollection = req.query.updateFormCollection;
var queryUrl = updateFormCollection + "(" + appealObj + ")";
const updateFormCollection = req.query.updateFormCollection;
const queryUrl = updateFormCollection + "(" + appealObj + ")";
if (
typeof updateFormCollection === "undefined" ||
@@ -35,9 +34,9 @@ export default async function ApiProxy(req, res) {
});
}
var token = await getToken();
const token = await getToken();
var config = {
const config = {
method: "patch",
url: WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
headers: {
@@ -53,16 +52,15 @@ export default async function ApiProxy(req, res) {
//console.log("update case:", data);
return axios(config)
.then(({ data }) => {
return respondSuccess(res, data);
})
.catch((error) => {
consoleLogger(error);
return respondError(res, {
status: 400,
code: "UPDATE_CASE_FAILED",
message: "Failed to update case"
});
try {
const { data: responseData } = await axios(config);
return respondSuccess(res, responseData);
} catch (error) {
consoleLogger(error);
return respondError(res, {
status: 400,
code: "UPDATE_CASE_FAILED",
message: "Failed to update case"
});
}
}
@@ -694,6 +694,95 @@ test("createappealcompletemessageproxy dependency failure returns CREATE_APPEAL_
);
});
test("createcaseinvolvement returns record exists on 412 conflict", async () => {
const mod = loadModule("pages/api/file/createcaseinvolvement_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "tok" }),
hashAPIPath: () => "?hash=abc",
consoleLogger: () => {},
process: {
env: { CRMURL: "crm.example", RELAY_ROOT: "https://relay/" }
},
axios: async () => {
const error = new Error("exists");
error.status = 412;
throw error;
}
});
const req = {
body: { contactid: "c1", incidentid: "i1" }
};
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 200);
assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), {
record: "exists"
});
});
test("createrepinvolvement dependency failure returns CREATE_REP_INVOLVEMENT_FAILED", async () => {
const mod = loadModule("pages/api/file/createrepinvolvement_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "tok" }),
hashAPIPath: () => "?hash=abc",
consoleLogger: () => {},
process: {
env: {
CRMURL: "crm.example",
CRMURL_VERSION: "v9.2",
RELAY_ROOT: "https://relay/"
}
},
axios: async () => {
throw new Error("rep involvement failed");
}
});
const req = {
body: { contactid: "c1", incidentid: "i1" }
};
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"CREATE_REP_INVOLVEMENT_FAILED"
);
});
test("updatecase dependency failure returns UPDATE_CASE_FAILED", async () => {
const mod = loadModule("pages/api/file/updatecase_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "tok" }),
hashAPIPath: () => "?hash=abc",
consoleLogger: () => {},
process: { env: { RELAY_ROOT: "https://relay/" } },
axios: async () => {
throw new Error("update failed");
}
});
const req = {
query: {
updateFormCollection: "pinswg_forms",
appealObj: "obj1",
incident: "inc1"
},
body: { field: "value" }
};
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "UPDATE_CASE_FAILED");
});
test("getawaitingsubmissionfromblob handler success returns payload", async () => {
const mod = loadModule("pages/api/file/getawaitingsubmissionfromblob.js", {
nextConnect: createNextConnectMock(),