Files
selfphoneng/apps/api/test/paging.test.ts
T
iamdoubz aa6681da03 numbers: any free internal number of 1 to 6 digits; ranges only suggest
Owner wanted the first user on 1. Numbering ranges now choose the Next free suggestion
(Settings → Numbering), the setup wizard takes a number per person, and a one-digit number
can't also be a menu key of a digital receptionist with direct dialing. Dialplan patterns that
take an internal number match one digit (_X!); golden case short-numbers proves it in Asterisk.
2026-10-21 13:17:25 -05:00

77 lines
3.0 KiB
TypeScript

// SPDX-License-Identifier: AGPL-3.0-or-later
// M8-T4: /api/paging-groups (03 §6.11, 04 §7.3).
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import type { LightMyRequestResponse } from 'fastify';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { hasTestDb } from './db.ts';
import { ADMIN, H, createTestApp } from './app.ts';
describe.skipIf(!hasTestDb)('/api/paging-groups', () => {
let t: Awaited<ReturnType<typeof createTestApp>>;
let req: (method: string, url: string, payload?: unknown) => Promise<LightMyRequestResponse>;
const ext: Record<string, string> = {};
let groupId: string;
const dialplan = async () => {
await t.app.generator.apply({ trigger: 'test' });
return readFile(join(t.genDir, 'extensions_selfphoneng.conf'), 'utf8');
};
beforeAll(async () => {
t = await createTestApp();
await t.clearMustChange(ADMIN.email);
const cookie = await t.login(ADMIN.email, ADMIN.password);
req = (method, url, payload) =>
t.app.inject({
method: method as 'GET',
url,
payload: payload as object,
headers: { ...H, cookie },
});
for (const [number, name] of [
['102', 'Ann Lee'],
['101', 'Dan Doe'],
] as const)
ext[number] = (await req('POST', '/api/extensions', { number, name })).json().id;
});
afterAll(() => t?.close());
it('creates a group: desk phones only, auto-answer, busy phones skipped', async () => {
const r = await req('POST', '/api/paging-groups', {
number: '850',
name: 'Warehouse',
memberIds: [ext['102'], ext['101']],
});
expect(r.statusCode, r.body).toBe(201);
groupId = r.json().id;
expect(r.json()).toMatchObject({ duplex: false, memberIds: [ext['101'], ext['102']] });
const d = await dialplan();
expect(d).toContain(`exten => 850,1,Goto(sp-page-${groupId},s,1)`);
expect(d).toContain('exten => s,1,Page(PJSIP/101&PJSIP/102,iqsb(sp-sub-autoanswer^s^1),30)');
});
it('checks the number and the members', async () => {
const bad = (body: object) =>
req('POST', '/api/paging-groups', { number: '851', name: 'X', ...body });
expect((await bad({ number: '1234567' })).statusCode).toBe(400); // 1 to 6 digits
expect((await bad({ number: '850' })).statusCode).toBe(409);
expect((await bad({ memberIds: [ext['101'], ext['101']] })).statusCode).toBe(400);
expect((await bad({ memberIds: ['00000000-0000-4000-8000-000000000000'] })).statusCode).toBe(
400,
);
});
it('two-way and fewer members; delete removes it from the dialplan', async () => {
const r = await req('PATCH', `/api/paging-groups/${groupId}`, {
duplex: true,
memberIds: [ext['102']],
});
expect(r.statusCode, r.body).toBe(200);
expect(await dialplan()).toContain(
'exten => s,1,Page(PJSIP/102,diqsb(sp-sub-autoanswer^s^1),30)',
);
expect((await req('DELETE', `/api/paging-groups/${groupId}`)).statusCode).toBe(200);
expect(await dialplan()).not.toContain('sp-page-');
});
});