feat: WS /t2/live — proxy Gemini Live API — 124/124 tests
This commit is contained in:
parent
f08be960b0
commit
653fc3150e
8 changed files with 422 additions and 66 deletions
134
src/lib/__tests__/geminiLive.test.ts
Normal file
134
src/lib/__tests__/geminiLive.test.ts
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
||||
import { EventEmitter } from 'node:events'
|
||||
import {
|
||||
openGeminiLiveSession,
|
||||
T2_SYSTEM_PROMPT,
|
||||
type WebSocketLike,
|
||||
} from '../geminiLive'
|
||||
|
||||
class FakeWs extends EventEmitter implements WebSocketLike {
|
||||
public sent: unknown[] = []
|
||||
public closed = false
|
||||
public closeCode?: number
|
||||
public closeReason?: string
|
||||
|
||||
send(data: unknown): void {
|
||||
this.sent.push(data)
|
||||
}
|
||||
|
||||
close(code?: number, reason?: string): void {
|
||||
if (this.closed) return
|
||||
this.closed = true
|
||||
this.closeCode = code
|
||||
this.closeReason = reason
|
||||
}
|
||||
}
|
||||
|
||||
describe('openGeminiLiveSession', () => {
|
||||
let originalKey: string | undefined
|
||||
|
||||
beforeEach(() => {
|
||||
originalKey = process.env.GEMINI_API_KEY
|
||||
process.env.GEMINI_API_KEY = 'test-key'
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
if (originalKey === undefined) {
|
||||
delete process.env.GEMINI_API_KEY
|
||||
} else {
|
||||
process.env.GEMINI_API_KEY = originalKey
|
||||
}
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it("envoie le setup frame avec T2_SYSTEM_PROMPT a l'open Gemini", () => {
|
||||
const client = new FakeWs()
|
||||
const gemini = new FakeWs()
|
||||
|
||||
openGeminiLiveSession(client, { geminiFactory: () => gemini })
|
||||
gemini.emit('open')
|
||||
|
||||
expect(gemini.sent).toHaveLength(1)
|
||||
const setup = JSON.parse(gemini.sent[0] as string)
|
||||
expect(setup.setup.model).toMatch(/gemini/)
|
||||
expect(setup.setup.system_instruction.parts[0].text).toBe(T2_SYSTEM_PROMPT)
|
||||
expect(setup.setup.generation_config.response_modalities).toContain('AUDIO')
|
||||
})
|
||||
|
||||
it('forwarde un message client (Buffer audio) vers Gemini', () => {
|
||||
const client = new FakeWs()
|
||||
const gemini = new FakeWs()
|
||||
openGeminiLiveSession(client, { geminiFactory: () => gemini })
|
||||
gemini.emit('open')
|
||||
|
||||
const audioChunk = Buffer.from([0x01, 0x02, 0x03, 0x04])
|
||||
client.emit('message', audioChunk)
|
||||
|
||||
// [0] = setup frame, [1] = audio forwarde
|
||||
expect(gemini.sent).toHaveLength(2)
|
||||
expect(gemini.sent[1]).toBe(audioChunk)
|
||||
})
|
||||
|
||||
it('forwarde un message Gemini vers le client', () => {
|
||||
const client = new FakeWs()
|
||||
const gemini = new FakeWs()
|
||||
openGeminiLiveSession(client, { geminiFactory: () => gemini })
|
||||
gemini.emit('open')
|
||||
|
||||
const examinerAudio = Buffer.from([0x10, 0x20])
|
||||
gemini.emit('message', examinerAudio)
|
||||
|
||||
expect(client.sent).toHaveLength(1)
|
||||
expect(client.sent[0]).toBe(examinerAudio)
|
||||
})
|
||||
|
||||
it('fermeture client → ferme Gemini avec code 1000', () => {
|
||||
const client = new FakeWs()
|
||||
const gemini = new FakeWs()
|
||||
openGeminiLiveSession(client, { geminiFactory: () => gemini })
|
||||
gemini.emit('open')
|
||||
|
||||
client.emit('close')
|
||||
|
||||
expect(gemini.closed).toBe(true)
|
||||
expect(gemini.closeCode).toBe(1000)
|
||||
})
|
||||
|
||||
it('fermeture Gemini → ferme client avec code 1000', () => {
|
||||
const client = new FakeWs()
|
||||
const gemini = new FakeWs()
|
||||
openGeminiLiveSession(client, { geminiFactory: () => gemini })
|
||||
gemini.emit('open')
|
||||
|
||||
gemini.emit('close')
|
||||
|
||||
expect(client.closed).toBe(true)
|
||||
expect(client.closeCode).toBe(1000)
|
||||
})
|
||||
|
||||
it('erreur Gemini → ferme client avec code 1011 GEMINI_ERROR', () => {
|
||||
const client = new FakeWs()
|
||||
const gemini = new FakeWs()
|
||||
openGeminiLiveSession(client, { geminiFactory: () => gemini })
|
||||
gemini.emit('open')
|
||||
|
||||
gemini.emit('error', new Error('boom'))
|
||||
|
||||
expect(client.closed).toBe(true)
|
||||
expect(client.closeCode).toBe(1011)
|
||||
expect(client.closeReason).toBe('GEMINI_ERROR')
|
||||
})
|
||||
|
||||
it("absence de GEMINI_API_KEY → close client 1011 CONFIG_ERROR sans appel a la factory", () => {
|
||||
delete process.env.GEMINI_API_KEY
|
||||
const client = new FakeWs()
|
||||
const factory = vi.fn(() => new FakeWs())
|
||||
|
||||
openGeminiLiveSession(client, { geminiFactory: factory })
|
||||
|
||||
expect(factory).not.toHaveBeenCalled()
|
||||
expect(client.closed).toBe(true)
|
||||
expect(client.closeCode).toBe(1011)
|
||||
expect(client.closeReason).toBe('CONFIG_ERROR')
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue