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:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user