Say undefined where the SMSC named no id for a segment it took (#101)
* Cover the ids a send gets back where the SMSC named only some of them * Say undefined where the SMSC named no id for a segment it took * Record why the merger takes the wider type, and de-duplicate the interop narrowing * Guard the numbering hole the merger must refuse, and correct the phase record
This commit is contained in:
@@ -177,9 +177,9 @@ the fixtures pin that a reader would not otherwise expect:
|
||||
application wanting one report per message compares each `dlr.smsId` against the `smsIds` array
|
||||
`sendSms()` returned and merges them itself.
|
||||
- **Telesign answers only the first segment of a concatenated submit with a `message_id`.**
|
||||
`sendSms()` returns `['<id>', '', '']` — one entry per segment, positional with `pduObjs`, empty
|
||||
where the SMSC named nothing. `dlr.smsId` is never empty, so an empty entry matches no receipt,
|
||||
and no merge is armed.
|
||||
`sendSms()` returns `['<id>', undefined, undefined]` — one entry per segment, positional with
|
||||
`pduObjs`, `undefined` where the SMSC named nothing; it returned `''` there until #101.
|
||||
`dlr.smsId` is never empty, so an unnamed entry matches no receipt, and no merge is armed.
|
||||
|
||||
## Open questions
|
||||
|
||||
@@ -203,6 +203,5 @@ the fixtures pin that a reader would not otherwise expect:
|
||||
`FAILED` and CM.com's `DELIVERD`. The documented-codes table in `operator-receipts.test.ts` is
|
||||
the place a new one goes, and it fails loudly for anything nothing names.
|
||||
- Whether `smsIds` carrying an empty entry for a segment the SMSC took but named no id for is the
|
||||
right shape for a caller, or whether that case wants saying differently. It is documented in
|
||||
`README.md` and pinned by the Telesign scenario; the question is a product one, not a correctness
|
||||
one, and belongs to the phase 11 product-owner pass.
|
||||
right shape for a caller. Settled in #101: the entry is `undefined`, and the decision with its
|
||||
rejected alternatives is recorded in `AGENTS.md`.
|
||||
|
||||
@@ -476,6 +476,7 @@ describe('C3+C7 - long MT through the fake upstream, receipts and id consistency
|
||||
assert.equal(sent.smsIds.length, testCase.expectedSegments);
|
||||
|
||||
for (const id of sent.smsIds) {
|
||||
assert.ok(id, 'expected Jasmin to name a message id for every segment');
|
||||
assert.match(id, uuidPattern, 'expected a UUID-shaped message id from Jasmin\'s submit_sm_resp');
|
||||
|
||||
// The full round trip - submit_sm to Jasmin's smpps, mtrouter, AMQP, the connector bind,
|
||||
|
||||
@@ -97,6 +97,15 @@ function dlrLooksIntact(received: Received | undefined): received is Received {
|
||||
&& received.pduObj.tlvs.message_state?.tagValue !== undefined;
|
||||
}
|
||||
|
||||
/** SMPPSim names an id for every segment, so an unnamed one is the finding, not the norm. */
|
||||
function namedIds(smsIds: (string | undefined)[]): string[] {
|
||||
const ids = smsIds.filter(id => id !== undefined);
|
||||
|
||||
assert.equal(ids.length, smsIds.length, 'expected SMPPSim to name a message id for every segment');
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resends a fresh message until one attempt's segments are every one matched by an intact `dlr`.
|
||||
* Retrying works around the peer+library interaction above without hiding it: a scenario only
|
||||
@@ -119,12 +128,14 @@ async function sendUntilAllDlrsArrive(
|
||||
|
||||
assert.equal(sent.err, undefined);
|
||||
|
||||
const ids = namedIds(sent.smsIds);
|
||||
|
||||
const complete = await waitFor(
|
||||
() => (sent.smsIds.every(id => dlrLooksIntact(dlrs.find(r => r.dlr.smsId === id))) ? true : undefined),
|
||||
() => (ids.every(id => dlrLooksIntact(dlrs.find(r => r.dlr.smsId === id))) ? true : undefined),
|
||||
DLR_RETRY_BUDGET_MS,
|
||||
);
|
||||
|
||||
if (complete) return sent.smsIds;
|
||||
if (complete) return ids;
|
||||
}
|
||||
|
||||
throw new Error(`no attempt got an intact DLR for every segment within ${String(DLR_MAX_ATTEMPTS)} tries`);
|
||||
@@ -149,14 +160,16 @@ async function sendUntilComplete(
|
||||
|
||||
assert.equal(sent.err, undefined);
|
||||
|
||||
const ids = namedIds(sent.smsIds);
|
||||
|
||||
const complete = await waitFor(() => {
|
||||
const allIntact = sent.smsIds.every(id => dlrLooksIntact(dlrs.find(r => r.dlr.smsId === id)));
|
||||
const allIntact = ids.every(id => dlrLooksIntact(dlrs.find(r => r.dlr.smsId === id)));
|
||||
const reassembled = sms.find(s => s.message === message);
|
||||
|
||||
return allIntact && reassembled ? { reassembled } : undefined;
|
||||
}, DLR_RETRY_BUDGET_MS);
|
||||
|
||||
if (complete) return { reassembled: complete.reassembled, smsIds: sent.smsIds };
|
||||
if (complete) return { reassembled: complete.reassembled, smsIds: ids };
|
||||
}
|
||||
|
||||
throw new Error(`no attempt got both an intact DLR per segment and a loopback reassembly within ${String(DLR_MAX_ATTEMPTS)} tries`);
|
||||
|
||||
@@ -38,13 +38,17 @@ async function sendAndAwaitDlrs(session: Session, dlrs: Dlr[], message: string):
|
||||
|
||||
assert.equal(sent.err, undefined);
|
||||
|
||||
const ids = sent.smsIds.filter(id => id !== undefined);
|
||||
|
||||
assert.equal(ids.length, sent.smsIds.length, 'expected smscsim to name a message id for every segment');
|
||||
|
||||
const complete = await waitFor(() => (
|
||||
sent.smsIds.every(id => dlrs.some(dlr => dlr.smsId === id)) ? true : undefined
|
||||
ids.every(id => dlrs.some(dlr => dlr.smsId === id)) ? true : undefined
|
||||
));
|
||||
|
||||
assert.ok(complete, 'every segment of the first send should get a DLR');
|
||||
|
||||
return sent.smsIds;
|
||||
return ids;
|
||||
}
|
||||
|
||||
describe('smscsim - C1 bind, keepalive, unbind', () => {
|
||||
|
||||
Reference in New Issue
Block a user