announcements: play a sound, then continue
/api/announcements CRUD (sound must be a prompt, destination must exist
and not be itself, delete refused while referenced); the generator
renders sp-ann-{id} (Answer, Playback, destApp) and destApp sends
announcement destinations there. The ivr golden case gains "press 3:
hours announcement, then back to the menu". Announcements are managed in
a section of Sounds & music (05 updated); the DestinationPicker gains
"Announcement".
Task: M6-T5
This commit is contained in:
1 parent
108547046c
commit
dd640b368b
19 files changed
+553
-17
No files matched your search
@@ -0,0 +1,91 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// M6-T5: /api/announcements (03 §6.11).
|
||||
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 { soundFiles } from '../src/db/schema.ts';
|
||||
import { hasTestDb } from './db.ts';
|
||||
import { ADMIN, H, createTestApp } from './app.ts';
|
||||
|
||||
describe.skipIf(!hasTestDb)('/api/announcements', () => {
|
||||
let t: Awaited<ReturnType<typeof createTestApp>>;
|
||||
let req: (method: string, url: string, payload?: unknown) => Promise<LightMyRequestResponse>;
|
||||
let dan: string;
|
||||
const sound = '44444444-4444-4444-8444-444444444447';
|
||||
let id: string;
|
||||
|
||||
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 },
|
||||
});
|
||||
dan = (await req('POST', '/api/extensions', { number: '101', name: 'Dan Doe' })).json().id;
|
||||
await t.db.insert(soundFiles).values({
|
||||
id: sound,
|
||||
name: 'Our hours',
|
||||
kind: 'prompt',
|
||||
source: 'upload',
|
||||
asteriskName: `custom/${sound}`,
|
||||
durationMs: 4000,
|
||||
});
|
||||
});
|
||||
afterAll(() => t?.close());
|
||||
|
||||
it('plays the sound, then continues; shows up in the dialplan', async () => {
|
||||
const r = await req('POST', '/api/announcements', {
|
||||
name: 'Hours',
|
||||
soundId: sound,
|
||||
destination: { type: 'voicemail', id: dan },
|
||||
});
|
||||
expect(r.statusCode, r.body).toBe(201);
|
||||
id = r.json().id;
|
||||
expect(r.json()).toMatchObject({
|
||||
soundName: 'Our hours',
|
||||
destinationSummary: 'Voicemail of Dan Doe (101)',
|
||||
});
|
||||
await t.app.generator.apply({ trigger: 'test' });
|
||||
const conf = await readFile(join(t.genDir, 'extensions_selfphoneng.conf'), 'utf8');
|
||||
expect(conf).toContain(
|
||||
`[sp-ann-${id}]\nexten => s,1,Answer()\n same => n,Playback(custom/${sound})\n same => n,Goto(sp-vm-direct,101,1)`,
|
||||
);
|
||||
});
|
||||
|
||||
it('refuses a missing sound, itself as destination; sound and announcement are protected', async () => {
|
||||
expect(
|
||||
(
|
||||
await req('POST', '/api/announcements', {
|
||||
name: 'X',
|
||||
soundId: '00000000-0000-4000-8000-000000000999',
|
||||
destination: { type: 'hangup' },
|
||||
})
|
||||
).statusCode,
|
||||
).toBe(400);
|
||||
expect(
|
||||
(
|
||||
await req('PATCH', `/api/announcements/${id}`, {
|
||||
destination: { type: 'announcement', id },
|
||||
})
|
||||
).statusCode,
|
||||
).toBe(400);
|
||||
const s = await req('DELETE', `/api/sounds/${sound}`);
|
||||
expect(s.json().error.details.usedBy).toEqual(['announcement Hours']);
|
||||
const ann2 = (
|
||||
await req('POST', '/api/announcements', {
|
||||
name: 'Before hours',
|
||||
soundId: sound,
|
||||
destination: { type: 'announcement', id },
|
||||
})
|
||||
).json();
|
||||
expect(ann2.destinationSummary).toBe('Announcement Hours');
|
||||
expect((await req('DELETE', `/api/announcements/${id}`)).statusCode).toBe(409);
|
||||
expect((await req('DELETE', `/api/announcements/${ann2.id}`)).statusCode).toBe(200);
|
||||
expect((await req('DELETE', `/api/announcements/${id}`)).statusCode).toBe(200);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user