diff --git a/AGENTS.md b/AGENTS.md index 7f13a4a..c3858e6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -88,8 +88,7 @@ src/ Imports point one way: `defs` knows nothing above it but `result.ts`, `pdu` uses `defs`, `session` uses `pdu`, and `client`/`server` use `session`. The one way back up is the `Session` handed to -`createSms()`, imported as a type only; `IncomingRequests` reaches its session through the -`IncomingDeps` port. +`createSms()`, imported as a type only. **Parameter order is wire order.** The key order inside `cmds.*.params` is the order the fields are written to and read from the buffer. Never sort those alphabetically — the alphabetical-ordering diff --git a/src/incoming-requests.ts b/src/incoming-requests.ts index ae49491..7fa16f7 100644 --- a/src/incoming-requests.ts +++ b/src/incoming-requests.ts @@ -57,7 +57,7 @@ export type IncomingDeps = { offerSms: (sms: Sms) => boolean; /** The application's first refusal, answering true where it took the request itself. */ onRequest?: ((pduObj: PduObject) => Promise | boolean) | undefined; - peerUnbound: () => Promise; + peerUnbound: () => Promise; reportDlr: (dlr: Dlr, pduObj: PduObject) => void; reportError: (err: Error) => void; reportMessageDlr: (merged: MessageDlr) => void; diff --git a/test/session-extras.test.ts b/test/session-extras.test.ts index 2f533c0..25283b1 100644 --- a/test/session-extras.test.ts +++ b/test/session-extras.test.ts @@ -100,7 +100,6 @@ function abortAfter( }); } -/** A port with no link behind it; the session is only what an Sms carries and answers through. */ function stubPort(session: Session, port: Partial = {}): IncomingDeps { return { answer: (pduObj, status, params) => session.sendReturn(pduObj, status, params), @@ -109,7 +108,7 @@ function stubPort(session: Session, port: Partial = {}): IncomingD createSms: (input, handlers) => createSms({ ...input, session }, handlers), linkEnd: () => 'smsc', offerSms: sms => session.emit('sms', sms), - peerUnbound: () => Promise.resolve(), + peerUnbound: () => Promise.resolve({}), reportDlr: () => undefined, reportError: () => undefined, reportMessageDlr: () => undefined, @@ -773,36 +772,6 @@ describe('reconnect', () => { assert.equal(messages, 1, 'the harness delivers a message whose link stayed'); }); - test('answers an unbind before asking the session to end, and answers a command outside the bind', async t => { - const session = new Session({ sock: new net.Socket() }); - - closeAfter(t, session); - - const calls: string[] = []; - const incoming = new IncomingRequests({ - deps: stubPort(session, { - answer: (pduObj, status) => { - calls.push(`${pduObj.cmdName} ${status ?? 'ESME_ROK'}`); - - return Promise.resolve({}); - }, - bindAllows: cmdName => cmdName !== 'submit_sm', - peerUnbound: () => { - calls.push('peerUnbound'); - - return Promise.resolve(); - }, - }), - dlrMerger: new DlrMerger({ log: silentLog, max: 10, timeout: 10_000 }), - log: silentLog, - }); - - await incoming.handle(submitPdu(1)); - await incoming.handle({ ...submitPdu(2), cmdId: 0x00000006, cmdName: 'unbind', params: {} }); - - assert.deepEqual(calls, ['submit_sm ESME_RINVBNDSTS', 'unbind ESME_ROK', 'peerUnbound']); - }); - test('does not reconnect after an explicit close', async t => { const smpp = await startServer(t); const { session } = await connect(t, smpp, { reconnect: { maxDelay: 50, minDelay: 10 } }); @@ -3114,6 +3083,34 @@ describe('graceful shutdown', () => { assert.deepEqual(reported, []); }); + + test('answers a peer\'s unbind before asking the session to end', async t => { + const session = new Session({ sock: new net.Socket() }); + + closeAfter(t, session); + + const calls: string[] = []; + const incoming = new IncomingRequests({ + deps: stubPort(session, { + answer: pduObj => { + calls.push(pduObj.cmdName); + + return Promise.resolve({}); + }, + peerUnbound: () => { + calls.push('peerUnbound'); + + return Promise.resolve({}); + }, + }), + dlrMerger: new DlrMerger({ log: silentLog, max: 10, timeout: 10_000 }), + log: silentLog, + }); + + await incoming.handle({ ...submitPdu(1), cmdId: 0x00000006, cmdName: 'unbind', params: {} }); + + assert.deepEqual(calls, ['unbind', 'peerUnbound']); + }); }); describe('message id notation', () => { diff --git a/todo.md b/todo.md index ceb9900..f677cdf 100644 --- a/todo.md +++ b/todo.md @@ -301,8 +301,7 @@ next work ([decision](docs/decisions.md#internals-and-tests)). says "`src/` stays flat until a module has to move for another reason. Valid while that map is what a reader navigates by" — and both architects reported that the map is now AGENTS.md rather than the tree, which is that premise failing. `todo.md` already carries the opposite - instruction under Worth doing. Two records, opposite answers; one has to go. Do it in the same - change as the `IncomingRequests` port or the imports are rewritten twice. + instruction under Worth doing. Two records, opposite answers; one has to go. - [ ] **Split `test/session-extras.test.ts` by the question each block answers.** 3,010 lines, 19 unrelated `describe` blocks whose names are already the file names they should be. With