forked from Yara724/api
Fixed images being removed, fixed v4/v5 wrong status on uploadDocument
This commit is contained in:
113
src/claim-request-management/capture-part-concurrency.spec.ts
Normal file
113
src/claim-request-management/capture-part-concurrency.spec.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Regression: concurrent capturePart writes must not clobber sibling slots.
|
||||
*
|
||||
* capturePartV2 used to `$set` the entire `media.damagedParts` array from a
|
||||
* stale read. Parallel uploads (common while Fanavaran attachment submit keeps
|
||||
* the HTTP request open) made the last writer win — Fanavaran still saw each
|
||||
* file on disk and could return errors, while Mongo was missing captures.
|
||||
*
|
||||
* Required strategy: per-index `$set` (`media.damagedParts.N`), matching
|
||||
* `media.carAngles.<key>`.
|
||||
*/
|
||||
describe("capture-part media.damagedParts write strategies", () => {
|
||||
type Row = { path?: string; fileName?: string; name?: string };
|
||||
type Claim = { media: { damagedParts: Row[] } };
|
||||
|
||||
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
||||
|
||||
/** Legacy (buggy) strategy: replace the entire array from a stale read. */
|
||||
async function writeWholeArray(
|
||||
store: { claim: Claim },
|
||||
index: number,
|
||||
capture: Row,
|
||||
readDelayMs: number,
|
||||
) {
|
||||
const snapshot = structuredClone(store.claim);
|
||||
await sleep(readDelayMs);
|
||||
const next = snapshot.media.damagedParts.map((row) => ({ ...row }));
|
||||
while (next.length <= index) next.push({});
|
||||
next[index] = { ...next[index], ...capture };
|
||||
store.claim = {
|
||||
...store.claim,
|
||||
media: { ...store.claim.media, damagedParts: next },
|
||||
};
|
||||
}
|
||||
|
||||
/** Required strategy: set only the target index (Mongo $set media.damagedParts.N). */
|
||||
async function writeSingleIndex(
|
||||
store: { claim: Claim },
|
||||
index: number,
|
||||
capture: Row,
|
||||
readDelayMs: number,
|
||||
) {
|
||||
await sleep(readDelayMs);
|
||||
const next = store.claim.media.damagedParts.map((row) => ({ ...row }));
|
||||
while (next.length <= index) next.push({});
|
||||
next[index] = { ...next[index], ...capture };
|
||||
store.claim.media.damagedParts[index] = next[index];
|
||||
}
|
||||
|
||||
it("documents that whole-array replace loses a concurrent capture", async () => {
|
||||
const store: { claim: Claim } = {
|
||||
claim: {
|
||||
media: {
|
||||
damagedParts: [{ name: "hood" }, { name: "front_bumper" }],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await Promise.all([
|
||||
writeWholeArray(
|
||||
store,
|
||||
0,
|
||||
{ path: "files/claim-captures/hood.jpg", fileName: "hood.jpg" },
|
||||
30,
|
||||
),
|
||||
writeWholeArray(
|
||||
store,
|
||||
1,
|
||||
{
|
||||
path: "files/claim-captures/bumper.jpg",
|
||||
fileName: "bumper.jpg",
|
||||
},
|
||||
10,
|
||||
),
|
||||
]);
|
||||
|
||||
expect(store.claim.media.damagedParts.map((r) => r.path).filter(Boolean))
|
||||
.toHaveLength(1);
|
||||
});
|
||||
|
||||
it("keeps both concurrent captures with per-index writes", async () => {
|
||||
const store: { claim: Claim } = {
|
||||
claim: {
|
||||
media: {
|
||||
damagedParts: [{ name: "hood" }, { name: "front_bumper" }],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await Promise.all([
|
||||
writeSingleIndex(
|
||||
store,
|
||||
0,
|
||||
{ path: "files/claim-captures/hood.jpg", fileName: "hood.jpg" },
|
||||
30,
|
||||
),
|
||||
writeSingleIndex(
|
||||
store,
|
||||
1,
|
||||
{
|
||||
path: "files/claim-captures/bumper.jpg",
|
||||
fileName: "bumper.jpg",
|
||||
},
|
||||
10,
|
||||
),
|
||||
]);
|
||||
|
||||
expect(store.claim.media.damagedParts.map((r) => r.path)).toEqual([
|
||||
"files/claim-captures/hood.jpg",
|
||||
"files/claim-captures/bumper.jpg",
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -6452,14 +6452,22 @@ export class ClaimRequestManagementService {
|
||||
);
|
||||
}
|
||||
|
||||
// Schedule retry for attachments
|
||||
await this.scheduleFanavaranRetry(
|
||||
claimCaseId,
|
||||
"attachments",
|
||||
() => this.autoSubmitFanavaranAttachment(claimCaseId, file, options),
|
||||
logPrefix,
|
||||
{ error, clientKey },
|
||||
);
|
||||
// Schedule retry for attachments — never let retry bookkeeping fail the
|
||||
// caller: local media/docs are already persisted before this submit.
|
||||
try {
|
||||
await this.scheduleFanavaranRetry(
|
||||
claimCaseId,
|
||||
"attachments",
|
||||
() => this.autoSubmitFanavaranAttachment(claimCaseId, file, options),
|
||||
logPrefix,
|
||||
{ error, clientKey },
|
||||
);
|
||||
} catch (retryScheduleError) {
|
||||
this.logger.error(
|
||||
`${logPrefix} Failed to schedule Fanavaran attachment retry`,
|
||||
retryScheduleError,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
attempted: true,
|
||||
@@ -10911,7 +10919,14 @@ export class ClaimRequestManagementService {
|
||||
...nextMedia[idx],
|
||||
...captureData,
|
||||
};
|
||||
updateData["media.damagedParts"] = nextMedia;
|
||||
// Per-index $set avoids lost updates when clients upload multiple parts
|
||||
// in parallel (whole-array replace raced with Fanavaran-awaited requests).
|
||||
// Legacy object maps still need a full write to migrate to array shape.
|
||||
if (!Array.isArray(claimCase.media?.damagedParts)) {
|
||||
updateData["media.damagedParts"] = nextMedia;
|
||||
} else {
|
||||
updateData[`media.damagedParts.${idx}`] = nextMedia[idx];
|
||||
}
|
||||
if (
|
||||
isResendCapture &&
|
||||
nextSelected.length !== selectedBeforeNorm.length
|
||||
@@ -10920,10 +10935,14 @@ export class ClaimRequestManagementService {
|
||||
}
|
||||
}
|
||||
|
||||
const updatedClaim = await this.claimCaseDbService.findByIdAndUpdate(
|
||||
await this.claimCaseDbService.findByIdAndUpdate(
|
||||
claimRequestId,
|
||||
updateData,
|
||||
);
|
||||
// Re-read so capture-progress sees sibling concurrent part/angle writes.
|
||||
const updatedClaim =
|
||||
(await this.claimCaseDbService.findById(claimRequestId)) ??
|
||||
claimCase;
|
||||
|
||||
if (isResendCapture) {
|
||||
await this.tryFinalizeExpertResendAfterUserAction(
|
||||
|
||||
Reference in New Issue
Block a user