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
+104
View File
@@ -0,0 +1,104 @@
import assert from 'node:assert/strict';
import net from 'node:net';
import type { Session } from '../src/session.ts';
import type { TestContext } from 'node:test';
import { PduFramer } from '../src/pdu-framer.ts';
import { client } from '../src/client.ts';
import { closeAfter, closeListenerAfter } from './teardown.ts';
import { consts } from '../src/defs/constants.ts';
import { objToPdu, pduReturn, pduToObj } from '../src/pdu.ts';
import { uuidv7 } from '../src/uuid.ts';
export type DummySmsc = {
/** Writes a delivery receipt to the ESME, its body spelled as the test names it. */
deliver: (body: string) => void;
/** Every submit_sm the ESME wrote, exactly as it arrived on the socket. */
octets: Buffer[];
port: number;
};
export type DummySmscOptions = {
/** The id each submit is answered with, in order; a spent list answers with none, as Telesign does. */
messageIds?: readonly string[];
};
/**
* An SMSC that answers every request the ESME sends and starts nothing of its own. It exists for the
* peers `server()` cannot be — one whose message ids the test chooses, or one that answers a request
* differently from how this library would. A test that answers the peer's requests itself wants
* `smscPeer()` in `session.test.ts` instead, which answers the bind and hands over the rest.
*/
export async function dummySmsc(t: TestContext, options: DummySmscOptions = {}): Promise<DummySmsc> {
const octets: Buffer[] = [];
const sockets: net.Socket[] = [];
let answered = 0;
let delivered = 0;
const nextId = (): string => (options.messageIds ? options.messageIds[answered++] ?? '' : uuidv7());
const listener = net.createServer(sock => {
const framer = new PduFramer();
sockets.push(sock);
sock.on('data', chunk => {
framer.push(chunk);
for (const pdu of framer.next().pdus ?? []) {
const { pduObj } = pduToObj(pdu);
// A response answers nothing; the ESME's deliver_sm_resp is the one that arrives here.
if (!pduObj || pduObj.cmdName.endsWith('_resp')) continue;
// Only a submit takes an id from the list; a bind answered off it shifts every fixture.
const submitted = pduObj.cmdName === 'submit_sm';
const answer = submitted
? pduReturn(pduObj, 'ESME_ROK', { message_id: nextId() })
: pduReturn(pduObj, 'ESME_ROK', { system_id: 'dummy' });
if (submitted) octets.push(pdu);
// Writing nothing leaves the test waiting out its own timeout with nothing naming why.
assert.ok(answer.buffer, `the dummy SMSC has no answer for ${pduObj.cmdName}`);
sock.write(answer.buffer);
}
});
});
closeListenerAfter(t, listener, sockets);
await new Promise<void>(resolve => { listener.listen(0, resolve); });
const address = listener.address();
return {
deliver: (body: string) => {
const { buffer } = objToPdu({
cmdName: 'deliver_sm',
params: {
destination_addr: '46701113311',
esm_class: consts.ESM_CLASS.MC_DELIVERY_RECEIPT,
short_message: body,
source_addr: '46709771337',
},
seqNr: ++delivered,
});
assert.ok(buffer);
sockets[sockets.length - 1]?.write(buffer);
},
octets,
port: typeof address === 'object' && address !== null ? address.port : 0,
};
}
/** A client bound to one of the above, torn down with the test. */
export async function bindToSmsc(
t: TestContext,
port: number,
options: Parameters<typeof client>[0] = {},
): Promise<Session> {
const { err, session } = await client({ ...options, port });
assert.equal(err, undefined);
assert.ok(session);
closeAfter(t, session);
return session;
}