Skip to main content
Glama
utterance-states.test.ts9.93 kB
import { TestServer } from '../test-utils/test-server.js'; describe('utterance state transitions', () => { let server: TestServer; beforeEach(async () => { server = new TestServer(); await server.start(); // Enable voice input and voice responses for tests await fetch(`${server.url}/api/voice-input`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ active: true }) }); await fetch(`${server.url}/api/voice-responses`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled: true }) }); }); afterEach(async () => { await server.stop(); }); describe('state transition: pending -> delivered', () => { it('should transition from pending to delivered when dequeued', async () => { // Add an utterance (starts as pending) const addResponse = await fetch(`${server.url}/api/potential-utterances`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: 'Hello world' }) }); const addData = await addResponse.json() as any; expect(addData.utterance.status).toBe('pending'); // Dequeue it (should become delivered) const dequeueResponse = await fetch(`${server.url}/api/dequeue-utterances`, { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const dequeueData = await dequeueResponse.json() as any; expect(dequeueData.success).toBe(true); expect(dequeueData.utterances.length).toBe(1); // Check status const statusResponse = await fetch(`${server.url}/api/utterances/status`); const statusData = await statusResponse.json() as any; expect(statusData.pending).toBe(0); expect(statusData.delivered).toBe(1); expect(statusData.responded).toBe(0); }); it('should handle multiple utterances', async () => { // Add three utterances await fetch(`${server.url}/api/potential-utterances`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: 'First' }) }); await fetch(`${server.url}/api/potential-utterances`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: 'Second' }) }); await fetch(`${server.url}/api/potential-utterances`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: 'Third' }) }); // Check status - all pending let statusResponse = await fetch(`${server.url}/api/utterances/status`); let statusData = await statusResponse.json() as any; expect(statusData.total).toBe(3); expect(statusData.pending).toBe(3); expect(statusData.delivered).toBe(0); // Dequeue all await fetch(`${server.url}/api/dequeue-utterances`, { method: 'POST', headers: { 'Content-Type': 'application/json' } }); // Check status - all delivered statusResponse = await fetch(`${server.url}/api/utterances/status`); statusData = await statusResponse.json() as any; expect(statusData.total).toBe(3); expect(statusData.pending).toBe(0); expect(statusData.delivered).toBe(3); }); }); describe('state transition: delivered -> responded', () => { it('should transition from delivered to responded when speak is called', async () => { // Add and dequeue an utterance await fetch(`${server.url}/api/potential-utterances`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: 'Hello' }) }); await fetch(`${server.url}/api/dequeue-utterances`, { method: 'POST', headers: { 'Content-Type': 'application/json' } }); // Check status - should be delivered let statusResponse = await fetch(`${server.url}/api/utterances/status`); let statusData = await statusResponse.json() as any; expect(statusData.delivered).toBe(1); expect(statusData.responded).toBe(0); // Speak const speakResponse = await fetch(`${server.url}/api/speak`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: 'Response text' }) }); const speakData = await speakResponse.json() as any; expect(speakResponse.status).toBe(200); expect(speakData.success).toBe(true); expect(speakData.respondedCount).toBe(1); // Check status - should be responded statusResponse = await fetch(`${server.url}/api/utterances/status`); statusData = await statusResponse.json() as any; expect(statusData.delivered).toBe(0); expect(statusData.responded).toBe(1); }); it('should mark all delivered utterances as responded', async () => { // Add and dequeue multiple utterances await fetch(`${server.url}/api/potential-utterances`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: 'First' }) }); await fetch(`${server.url}/api/potential-utterances`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: 'Second' }) }); await fetch(`${server.url}/api/dequeue-utterances`, { method: 'POST', headers: { 'Content-Type': 'application/json' } }); // Speak should mark all as responded const speakResponse = await fetch(`${server.url}/api/speak`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: 'Response' }) }); const speakData = await speakResponse.json() as any; expect(speakData.respondedCount).toBe(2); // Check status const statusResponse = await fetch(`${server.url}/api/utterances/status`); const statusData = await statusResponse.json() as any; expect(statusData.delivered).toBe(0); expect(statusData.responded).toBe(2); }); it('should handle speak with no delivered utterances', async () => { // Speak without any delivered utterances const speakResponse = await fetch(`${server.url}/api/speak`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: 'Response' }) }); const speakData = await speakResponse.json() as any; expect(speakResponse.status).toBe(200); expect(speakData.success).toBe(true); expect(speakData.respondedCount).toBe(0); }); }); describe('complete conversation flow', () => { it('should follow pending -> delivered -> responded cycle', async () => { // 1. Add utterance (pending) await fetch(`${server.url}/api/potential-utterances`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: 'User input' }) }); let statusResponse = await fetch(`${server.url}/api/utterances/status`); let statusData = await statusResponse.json() as any; expect(statusData.pending).toBe(1); expect(statusData.delivered).toBe(0); expect(statusData.responded).toBe(0); // 2. Dequeue (delivered) await fetch(`${server.url}/api/dequeue-utterances`, { method: 'POST', headers: { 'Content-Type': 'application/json' } }); statusResponse = await fetch(`${server.url}/api/utterances/status`); statusData = await statusResponse.json() as any; expect(statusData.pending).toBe(0); expect(statusData.delivered).toBe(1); expect(statusData.responded).toBe(0); // 3. Speak (responded) await fetch(`${server.url}/api/speak`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: 'Assistant response' }) }); statusResponse = await fetch(`${server.url}/api/utterances/status`); statusData = await statusResponse.json() as any; expect(statusData.pending).toBe(0); expect(statusData.delivered).toBe(0); expect(statusData.responded).toBe(1); }); it('should handle multiple conversation turns', async () => { // First turn await fetch(`${server.url}/api/potential-utterances`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: 'First input' }) }); await fetch(`${server.url}/api/dequeue-utterances`, { method: 'POST', headers: { 'Content-Type': 'application/json' } }); await fetch(`${server.url}/api/speak`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: 'First response' }) }); // Second turn await fetch(`${server.url}/api/potential-utterances`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: 'Second input' }) }); await fetch(`${server.url}/api/dequeue-utterances`, { method: 'POST', headers: { 'Content-Type': 'application/json' } }); await fetch(`${server.url}/api/speak`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: 'Second response' }) }); // Final status const statusResponse = await fetch(`${server.url}/api/utterances/status`); const statusData = await statusResponse.json() as any; expect(statusData.total).toBe(2); expect(statusData.pending).toBe(0); expect(statusData.delivered).toBe(0); expect(statusData.responded).toBe(2); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/johnmatthewtennant/mcp-voice-hooks'

If you have feedback or need assistance with the MCP directory API, please join our Discord server