Fixtures for the receipt bodies operator documentation publishes (#93)

* Fixtures for the receipt bodies operator documentation publishes

* Read the stat:FAILED several operators write as UNDELIVERABLE

* Cover the 16-bit UDH concatenation element with a fixture

* Record phase 9 of the interoperability plan

* Share the dummy SMSC and tighten the operator fixtures

* Correct what the test conventions claim about the doubles and the tree

* Read a receipt date that carries its century

* Read the stat spelling CM.com publishes

* Say what the receipt status table actually holds
This commit is contained in:
2026-09-08 18:56:57 +02:00
committed by GitHub
parent 83176f53b0
commit 5a0cf56754
11 changed files with 946 additions and 58 deletions
+16 -8
View File
@@ -10,15 +10,19 @@ import { paramNumber, paramText } from './defs/types.ts';
import { udhLength } from './udh.ts';
/**
* The seven-character status codes carried in a receipt's `stat:` field, mapped to the
* message_state values they correspond to.
* The status codes carried in a receipt's `stat:` field, mapped to the message_state values they
* correspond to. Two of them are not the seven characters the field is meant to hold.
*/
const receiptStates: Record<string, MessageState> = {
ACCEPTD: 'ACCEPTED',
DELETED: 'DELETED',
// CM.com publishes this eight-character spelling of DELIVRD in its own code table.
DELIVERD: 'DELIVERED',
DELIVRD: 'DELIVERED',
ENROUTE: 'ENROUTE',
EXPIRED: 'EXPIRED',
// Kaleyra and Route Mobile document it as a terminal failure; Appendix B does not define it.
FAILED: 'UNDELIVERABLE',
REJECTD: 'REJECTED',
UNDELIV: 'UNDELIVERABLE',
UNKNOWN: 'UNKNOWN',
@@ -82,18 +86,21 @@ function toNumber(value: string | undefined): number | undefined {
return Number.isFinite(parsed) ? parsed : undefined;
}
/** Delivery receipt dates are YYMMDDhhmm, sometimes with seconds. */
/** Delivery receipt dates are YYMMDDhhmm, sometimes with seconds, and CM.com states the century. */
function receiptDate(value: string | undefined): Date | undefined {
if (value === undefined) return undefined;
const match = /^(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)?$/.exec(value);
// Ten, twelve and fourteen digits, so no width can be read as another.
const match = /^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/.exec(value)
?? /^(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)?$/.exec(value);
if (!match) return undefined;
const [, years, months, days, hours, minutes, seconds] = match;
const century = Math.floor(new Date().getUTCFullYear() / 100) * 100;
const century = years?.length === 4 ? 0 : Math.floor(new Date().getUTCFullYear() / 100) * 100;
const year = century + Number(years);
const date = new Date(Date.UTC(
century + Number(years),
year,
Number(months) - 1,
Number(days),
Number(hours),
@@ -101,8 +108,9 @@ function receiptDate(value: string | undefined): Date | undefined {
Number(seconds ?? 0),
));
// Date.UTC rolls 31 February over into March rather than refusing it.
const rolled = date.getUTCMonth() !== Number(months) - 1
// Date.UTC rolls 31 February into March rather than refusing it, and reads year 26 as 1926.
const rolled = date.getUTCFullYear() !== year
|| date.getUTCMonth() !== Number(months) - 1
|| date.getUTCDate() !== Number(days)
|| date.getUTCHours() !== Number(hours)
|| date.getUTCMinutes() !== Number(minutes)