TASK22224: harden proxy file routes and expand contracts
This commit is contained in:
@@ -331,3 +331,51 @@ Validation:
|
||||
Follow-ups:
|
||||
|
||||
- Optional parity sweep: apply the same explicit catch-path contract pattern to remaining file routes that still rely on implicit promise-chain errors.
|
||||
|
||||
---
|
||||
|
||||
### CL-009: TASK22224 proxy-route resilience and encoding parity bundle
|
||||
|
||||
date: 2026-03-23
|
||||
author: Cline
|
||||
scope: `pages/api/file/{getbloblistproxy,getrepsblobproxy,getawaitingsubmissionfromblobproxy,createappealcompletemessageproxy_api}.js`, `tests/phase21/file-handler-contract.test.cjs`
|
||||
type: change
|
||||
rationale: Continue larger-slice hardening by aligning proxy handlers with explicit async error handling and safer encoded upstream query forwarding for hash-based downstream calls.
|
||||
impact: Improves proxy reliability and compatibility for encoded query values while preserving existing proxy error contracts and response behavior.
|
||||
status: completed
|
||||
|
||||
Summary:
|
||||
|
||||
- `getbloblistproxy.js`
|
||||
- converted `.then/.catch` chain to explicit `try/catch`
|
||||
- encoded forwarded `container` and `casefolderID` query values
|
||||
- preserved error contract: `GET_BLOB_LIST_PROXY_FAILED`
|
||||
- `getrepsblobproxy.js`
|
||||
- converted `.then/.catch` chain to explicit `try/catch`
|
||||
- encoded forwarded `container`
|
||||
- preserved error contract: `GET_REPS_BLOB_PROXY_FAILED`
|
||||
- `getawaitingsubmissionfromblobproxy.js`
|
||||
- converted `.then/.catch` chain to explicit `try/catch`
|
||||
- preserved error contract: `GET_AWAITING_SUBMISSION_PROXY_FAILED`
|
||||
- `createappealcompletemessageproxy_api.js`
|
||||
- converted `.then/.catch` chain to explicit `try/catch`
|
||||
- encoded forwarded `container` and `tempcaseref`
|
||||
- preserved error contract: `CREATE_APPEAL_COMPLETE_MESSAGE_PROXY_FAILED`
|
||||
- Phase21 tests expanded for proxy paths:
|
||||
- getbloblistproxy success + dependency failure
|
||||
- getrepsblobproxy success
|
||||
- getawaitingsubmissionfromblobproxy dependency failure
|
||||
- createappealcompletemessageproxy dependency failure
|
||||
|
||||
Validation:
|
||||
|
||||
- `node tests/phase21/file-handler-contract.test.cjs` -> pass (46/46)
|
||||
- `node tests/phase21/api-contract-slice1.test.cjs` -> pass
|
||||
- helper: 4/4
|
||||
- file-handler: 46/46
|
||||
- email-handler: 12/12
|
||||
- endpoint-handler: 149/149
|
||||
|
||||
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.
|
||||
|
||||
@@ -17,8 +17,8 @@ const hasValue = (value) =>
|
||||
typeof value === "string" && value.trim().length > 0;
|
||||
|
||||
ApiProxy.get(async (req, res) => {
|
||||
var containerName = req.query.container;
|
||||
var tempCaseRef = req.query.tempcaseref;
|
||||
const containerName = req.query.container;
|
||||
const tempCaseRef = req.query.tempcaseref;
|
||||
|
||||
if (!hasValue(containerName) || !hasValue(tempCaseRef)) {
|
||||
return respondError(res, {
|
||||
@@ -28,30 +28,28 @@ ApiProxy.get(async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
var token = await getToken();
|
||||
const token = await getToken();
|
||||
|
||||
var queryUrl =
|
||||
const queryUrl =
|
||||
"/api/file/createappealcompletemessage_api?container=" +
|
||||
containerName +
|
||||
encodeURIComponent(containerName) +
|
||||
"&tempcaseref=" +
|
||||
tempCaseRef;
|
||||
encodeURIComponent(tempCaseRef);
|
||||
|
||||
return axios
|
||||
.get(
|
||||
try {
|
||||
const { data } = await axios.get(
|
||||
BASE_URL + queryUrl + hashAPIPath(queryUrl),
|
||||
azureHeaders(token.access_token)
|
||||
)
|
||||
.then(({ data }) => {
|
||||
return respondSuccess(res, data);
|
||||
})
|
||||
.catch((error) => {
|
||||
consoleLogger(error);
|
||||
return respondError(res, {
|
||||
status: 400,
|
||||
code: "CREATE_APPEAL_COMPLETE_MESSAGE_PROXY_FAILED",
|
||||
message: "Failed to create appeal complete message"
|
||||
});
|
||||
);
|
||||
return respondSuccess(res, data);
|
||||
} catch (error) {
|
||||
consoleLogger(error);
|
||||
return respondError(res, {
|
||||
status: 400,
|
||||
code: "CREATE_APPEAL_COMPLETE_MESSAGE_PROXY_FAILED",
|
||||
message: "Failed to create appeal complete message"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export const config = {
|
||||
|
||||
@@ -11,7 +11,7 @@ const hasValue = (value) =>
|
||||
typeof value === "string" && value.trim().length > 0;
|
||||
|
||||
export default async function ApiProxy(req, res) {
|
||||
var containerName = req.query.container;
|
||||
const containerName = req.query.container;
|
||||
|
||||
if (!hasValue(containerName)) {
|
||||
return respondError(res, {
|
||||
@@ -21,25 +21,23 @@ export default async function ApiProxy(req, res) {
|
||||
});
|
||||
}
|
||||
|
||||
var token = await getToken();
|
||||
const token = await getToken();
|
||||
|
||||
var queryUrl =
|
||||
const queryUrl =
|
||||
"/api/file/getawaitingsubmissionfromblob?container=" + containerName;
|
||||
|
||||
return axios
|
||||
.get(
|
||||
try {
|
||||
const { data } = await axios.get(
|
||||
BASE_URL + queryUrl + hashAPIPath(queryUrl),
|
||||
azureHeaders(token.access_token)
|
||||
)
|
||||
.then(({ data }) => {
|
||||
return respondSuccess(res, data);
|
||||
})
|
||||
.catch((error) => {
|
||||
consoleLogger(error);
|
||||
return respondError(res, {
|
||||
status: 400,
|
||||
code: "GET_AWAITING_SUBMISSION_PROXY_FAILED",
|
||||
message: "Failed to fetch awaiting submission blob"
|
||||
});
|
||||
);
|
||||
return respondSuccess(res, data);
|
||||
} catch (error) {
|
||||
consoleLogger(error);
|
||||
return respondError(res, {
|
||||
status: 400,
|
||||
code: "GET_AWAITING_SUBMISSION_PROXY_FAILED",
|
||||
message: "Failed to fetch awaiting submission blob"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ const hasValue = (value) =>
|
||||
typeof value === "string" && value.trim().length > 0;
|
||||
|
||||
export default async function ApiProxy(req, res) {
|
||||
var containerName = req.query.container;
|
||||
var casefolderID = req.query.casefolderID;
|
||||
const containerName = req.query.container;
|
||||
const casefolderID = req.query.casefolderID;
|
||||
|
||||
if (!hasValue(containerName) || !hasValue(casefolderID)) {
|
||||
return respondError(res, {
|
||||
@@ -22,28 +22,26 @@ export default async function ApiProxy(req, res) {
|
||||
});
|
||||
}
|
||||
|
||||
var token = await getToken();
|
||||
const token = await getToken();
|
||||
|
||||
var queryUrl =
|
||||
const queryUrl =
|
||||
"/api/file/getbloblist?container=" +
|
||||
containerName +
|
||||
encodeURIComponent(containerName) +
|
||||
"&casefolderID=" +
|
||||
casefolderID;
|
||||
encodeURIComponent(casefolderID);
|
||||
|
||||
return axios
|
||||
.get(
|
||||
try {
|
||||
const { data } = await axios.get(
|
||||
BASE_URL + queryUrl + hashAPIPath(queryUrl),
|
||||
azureHeaders(token.access_token)
|
||||
)
|
||||
.then(({ data }) => {
|
||||
return respondSuccess(res, data);
|
||||
})
|
||||
.catch((error) => {
|
||||
consoleLogger(error);
|
||||
return respondError(res, {
|
||||
status: 400,
|
||||
code: "GET_BLOB_LIST_PROXY_FAILED",
|
||||
message: "Failed to fetch blob list"
|
||||
});
|
||||
);
|
||||
return respondSuccess(res, data);
|
||||
} catch (error) {
|
||||
consoleLogger(error);
|
||||
return respondError(res, {
|
||||
status: 400,
|
||||
code: "GET_BLOB_LIST_PROXY_FAILED",
|
||||
message: "Failed to fetch blob list"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ const hasValue = (value) =>
|
||||
typeof value === "string" && value.trim().length > 0;
|
||||
|
||||
export default async function ApiProxy(req, res) {
|
||||
var containerName = req.query.container;
|
||||
const containerName = req.query.container;
|
||||
|
||||
if (!hasValue(containerName)) {
|
||||
return respondError(res, {
|
||||
@@ -21,24 +21,23 @@ export default async function ApiProxy(req, res) {
|
||||
});
|
||||
}
|
||||
|
||||
var token = await getToken();
|
||||
const token = await getToken();
|
||||
|
||||
var queryUrl = "/api/file/getrepsblob?container=" + containerName;
|
||||
const queryUrl =
|
||||
"/api/file/getrepsblob?container=" + encodeURIComponent(containerName);
|
||||
|
||||
return axios
|
||||
.get(
|
||||
try {
|
||||
const { data } = await axios.get(
|
||||
BASE_URL + queryUrl + hashAPIPath(queryUrl),
|
||||
azureHeaders(token.access_token)
|
||||
)
|
||||
.then(({ data }) => {
|
||||
return respondSuccess(res, data);
|
||||
})
|
||||
.catch((error) => {
|
||||
consoleLogger(error);
|
||||
return respondError(res, {
|
||||
status: 400,
|
||||
code: "GET_REPS_BLOB_PROXY_FAILED",
|
||||
message: "Failed to fetch representation blobs"
|
||||
});
|
||||
);
|
||||
return respondSuccess(res, data);
|
||||
} catch (error) {
|
||||
consoleLogger(error);
|
||||
return respondError(res, {
|
||||
status: 400,
|
||||
code: "GET_REPS_BLOB_PROXY_FAILED",
|
||||
message: "Failed to fetch representation blobs"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -547,6 +547,153 @@ test("createrepcompletemessage handler dependency failure returns CREATE_REP_COM
|
||||
);
|
||||
});
|
||||
|
||||
test("getbloblistproxy success returns proxied payload", async () => {
|
||||
const mod = loadModule("pages/api/file/getbloblistproxy.js", {
|
||||
respondError: respondErrorMock,
|
||||
respondSuccess: respondSuccessMock,
|
||||
getToken: async () => ({ access_token: "tok" }),
|
||||
azureHeaders: () => ({ headers: { Authorization: "Bearer tok" } }),
|
||||
hashAPIPath: () => "&hash=abc",
|
||||
consoleLogger: () => {},
|
||||
process: { env: { API_ROOT: "http://example.com" } },
|
||||
axios: {
|
||||
get: async () => ({ data: { value: [{ id: "b1" }] } })
|
||||
}
|
||||
});
|
||||
|
||||
const req = {
|
||||
query: { container: "cont/1", casefolderID: "CASE/1" }
|
||||
};
|
||||
const res = createRes();
|
||||
await mod.default(req, res);
|
||||
|
||||
assert.strictEqual(res.state.statusCode, 200);
|
||||
assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), {
|
||||
value: [{ id: "b1" }]
|
||||
});
|
||||
});
|
||||
|
||||
test("getbloblistproxy dependency failure returns GET_BLOB_LIST_PROXY_FAILED", async () => {
|
||||
const mod = loadModule("pages/api/file/getbloblistproxy.js", {
|
||||
respondError: respondErrorMock,
|
||||
respondSuccess: respondSuccessMock,
|
||||
getToken: async () => ({ access_token: "tok" }),
|
||||
azureHeaders: () => ({ headers: {} }),
|
||||
hashAPIPath: () => "&hash=abc",
|
||||
consoleLogger: () => {},
|
||||
process: { env: { API_ROOT: "http://example.com" } },
|
||||
axios: {
|
||||
get: async () => {
|
||||
throw new Error("proxy failed");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const req = {
|
||||
query: { container: "c1", casefolderID: "f1" }
|
||||
};
|
||||
const res = createRes();
|
||||
await mod.default(req, res);
|
||||
|
||||
assert.strictEqual(res.state.statusCode, 400);
|
||||
assert.strictEqual(
|
||||
res.state.jsonBody.error.code,
|
||||
"GET_BLOB_LIST_PROXY_FAILED"
|
||||
);
|
||||
});
|
||||
|
||||
test("getrepsblobproxy success returns proxied payload", async () => {
|
||||
const mod = loadModule("pages/api/file/getrepsblobproxy.js", {
|
||||
respondError: respondErrorMock,
|
||||
respondSuccess: respondSuccessMock,
|
||||
getToken: async () => ({ access_token: "tok" }),
|
||||
azureHeaders: () => ({ headers: {} }),
|
||||
hashAPIPath: () => "&hash=abc",
|
||||
consoleLogger: () => {},
|
||||
process: { env: { API_ROOT: "http://example.com" } },
|
||||
axios: {
|
||||
get: async () => ({ data: { value: [{ id: "r1" }] } })
|
||||
}
|
||||
});
|
||||
|
||||
const req = {
|
||||
query: { container: "c1" }
|
||||
};
|
||||
const res = createRes();
|
||||
await mod.default(req, res);
|
||||
|
||||
assert.strictEqual(res.state.statusCode, 200);
|
||||
assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), {
|
||||
value: [{ id: "r1" }]
|
||||
});
|
||||
});
|
||||
|
||||
test("getawaitingsubmissionfromblobproxy dependency failure returns GET_AWAITING_SUBMISSION_PROXY_FAILED", async () => {
|
||||
const mod = loadModule(
|
||||
"pages/api/file/getawaitingsubmissionfromblobproxy.js",
|
||||
{
|
||||
respondError: respondErrorMock,
|
||||
respondSuccess: respondSuccessMock,
|
||||
getToken: async () => ({ access_token: "tok" }),
|
||||
azureHeaders: () => ({ headers: {} }),
|
||||
hashAPIPath: () => "&hash=abc",
|
||||
consoleLogger: () => {},
|
||||
process: { env: { API_ROOT: "http://example.com" } },
|
||||
axios: {
|
||||
get: async () => {
|
||||
throw new Error("awaiting failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const req = {
|
||||
query: { container: "c1" }
|
||||
};
|
||||
const res = createRes();
|
||||
await mod.default(req, res);
|
||||
|
||||
assert.strictEqual(res.state.statusCode, 400);
|
||||
assert.strictEqual(
|
||||
res.state.jsonBody.error.code,
|
||||
"GET_AWAITING_SUBMISSION_PROXY_FAILED"
|
||||
);
|
||||
});
|
||||
|
||||
test("createappealcompletemessageproxy dependency failure returns CREATE_APPEAL_COMPLETE_MESSAGE_PROXY_FAILED", async () => {
|
||||
const mod = loadModule(
|
||||
"pages/api/file/createappealcompletemessageproxy_api.js",
|
||||
{
|
||||
nextConnect: createNextConnectMock(),
|
||||
middleware: () => {},
|
||||
respondError: respondErrorMock,
|
||||
respondSuccess: respondSuccessMock,
|
||||
getToken: async () => ({ access_token: "tok" }),
|
||||
azureHeaders: () => ({ headers: {} }),
|
||||
hashAPIPath: () => "&hash=abc",
|
||||
consoleLogger: () => {},
|
||||
process: { env: { API_ROOT: "http://example.com" } },
|
||||
axios: {
|
||||
get: async () => {
|
||||
throw new Error("create appeal proxy failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const req = {
|
||||
query: { container: "c1", tempcaseref: "TMP1" }
|
||||
};
|
||||
const res = createRes();
|
||||
await mod.default.handler(req, res);
|
||||
|
||||
assert.strictEqual(res.state.statusCode, 400);
|
||||
assert.strictEqual(
|
||||
res.state.jsonBody.error.code,
|
||||
"CREATE_APPEAL_COMPLETE_MESSAGE_PROXY_FAILED"
|
||||
);
|
||||
});
|
||||
|
||||
test("getawaitingsubmissionfromblob handler success returns payload", async () => {
|
||||
const mod = loadModule("pages/api/file/getawaitingsubmissionfromblob.js", {
|
||||
nextConnect: createNextConnectMock(),
|
||||
|
||||
Reference in New Issue
Block a user