135 lines
5.1 KiB
JavaScript
135 lines
5.1 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import { readFile } from 'node:fs/promises'
|
|
import { createClient } from '@supabase/supabase-js'
|
|
|
|
const supabaseUrl = process.env.D3RO_LOCAL_SUPABASE_URL?.trim() ?? ''
|
|
const anonKey = process.env.D3RO_LOCAL_SUPABASE_ANON_KEY?.trim() ?? ''
|
|
const serviceRoleKey = process.env.D3RO_LOCAL_SUPABASE_SERVICE_ROLE_KEY?.trim() ?? ''
|
|
|
|
if (!supabaseUrl.startsWith('http://127.0.0.1:55321') || anonKey.length < 20 || serviceRoleKey.length < 20) {
|
|
throw new Error('D3RO local Supabase credentials for port 55321 are required')
|
|
}
|
|
|
|
const clientOptions = {
|
|
auth: { persistSession: false, autoRefreshToken: false, detectSessionInUrl: false },
|
|
}
|
|
const admin = createClient(supabaseUrl, serviceRoleKey, clientOptions)
|
|
const userClient = createClient(supabaseUrl, anonKey, clientOptions)
|
|
const suffix = `${Date.now()}-${Math.floor(Math.random() * 1_000_000)}`
|
|
const password = `D3ro-${suffix}-Strong!`
|
|
let createdUserId = null
|
|
let assertions = 0
|
|
|
|
function checked(condition, message) {
|
|
assert.ok(condition, message)
|
|
assertions += 1
|
|
}
|
|
|
|
async function invokeAnonymous(name, body, contentType = 'application/json') {
|
|
return fetch(`${supabaseUrl}/functions/v1/${name}`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': contentType },
|
|
body,
|
|
})
|
|
}
|
|
|
|
function parseAnthropicSse(raw) {
|
|
let text = ''
|
|
let stopped = false
|
|
for (const block of raw.replace(/\r\n/g, '\n').split('\n\n')) {
|
|
const data = block
|
|
.split('\n')
|
|
.filter((line) => line.startsWith('data:'))
|
|
.map((line) => line.slice(5).trimStart())
|
|
.join('\n')
|
|
if (!data || data === '[DONE]') continue
|
|
const event = JSON.parse(data)
|
|
if (event.type === 'content_block_delta' && event.delta?.type === 'text_delta') {
|
|
text += typeof event.delta.text === 'string' ? event.delta.text : ''
|
|
}
|
|
if (event.type === 'message_stop') stopped = true
|
|
}
|
|
return { text: text.trim(), stopped }
|
|
}
|
|
|
|
try {
|
|
const signUp = await userClient.auth.signUp({
|
|
email: `d3ro.talk.${suffix}@gmail.com`,
|
|
password,
|
|
})
|
|
assert.equal(signUp.error, null, 'Could not create the disposable Talk user')
|
|
assert.ok(signUp.data.user && signUp.data.session, 'Disposable Talk user has no local session')
|
|
createdUserId = signUp.data.user.id
|
|
const accessToken = signUp.data.session.access_token
|
|
|
|
const anonymousLlm = await invokeAnonymous(
|
|
'llm-proxy',
|
|
JSON.stringify({ messages: [{ role: 'user', content: 'hello' }], stream: true }),
|
|
)
|
|
checked(anonymousLlm.status === 401, 'llm-proxy accepted an unauthenticated Talk request')
|
|
|
|
const anonymousStt = await invokeAnonymous(
|
|
'stt-proxy',
|
|
JSON.stringify({ audio: 'not-authorized' }),
|
|
)
|
|
checked(anonymousStt.status === 401, 'stt-proxy accepted an unauthenticated Talk request')
|
|
|
|
const llmResponse = await fetch(`${supabaseUrl}/functions/v1/llm-proxy`, {
|
|
method: 'POST',
|
|
headers: {
|
|
apikey: anonKey,
|
|
Authorization: `Bearer ${accessToken}`,
|
|
Accept: 'text/event-stream',
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
messages: [{ role: 'user', content: 'Reply with the single word ready.' }],
|
|
max_tokens: 32,
|
|
stream: true,
|
|
}),
|
|
})
|
|
const llmBody = await llmResponse.text()
|
|
if (llmResponse.status === 503) {
|
|
checked(JSON.parse(llmBody).error === 'provider_unavailable', 'Missing LLM provider did not fail closed')
|
|
} else {
|
|
checked(llmResponse.status === 200, `Authenticated llm-proxy returned ${llmResponse.status}`)
|
|
checked(
|
|
llmResponse.headers.get('content-type')?.includes('text/event-stream') === true,
|
|
'Authenticated streaming Talk response was not SSE',
|
|
)
|
|
const parsed = parseAnthropicSse(llmBody)
|
|
checked(parsed.text.length > 0 && parsed.stopped, 'Authenticated SSE did not contain a complete real response')
|
|
}
|
|
|
|
const wavBytes = await readFile(new URL('../../desktop/resources/sounds/recording-start.wav', import.meta.url))
|
|
const form = new FormData()
|
|
form.append('audio', new Blob([wavBytes], { type: 'audio/wav' }), 'recording-start.wav')
|
|
form.append('language_code', 'ko')
|
|
const sttResponse = await fetch(`${supabaseUrl}/functions/v1/stt-proxy`, {
|
|
method: 'POST',
|
|
headers: {
|
|
apikey: anonKey,
|
|
Authorization: `Bearer ${accessToken}`,
|
|
},
|
|
body: form,
|
|
})
|
|
const sttBody = await sttResponse.json().catch(() => null)
|
|
if (sttResponse.status === 503) {
|
|
checked(sttBody?.error === 'stt_provider_unavailable', 'Missing STT provider did not fail closed')
|
|
} else if (sttResponse.status === 502) {
|
|
checked(sttBody?.error === 'stt_upstream_failed', 'STT upstream failure returned an unsafe contract')
|
|
} else {
|
|
checked(sttResponse.status === 200, `Authenticated stt-proxy returned ${sttResponse.status}`)
|
|
checked(
|
|
typeof sttBody?.transcript === 'string'
|
|
&& typeof sttBody?.provider === 'string'
|
|
&& typeof sttBody?.language_code === 'string'
|
|
&& typeof sttBody?.duration_seconds === 'number',
|
|
'Authenticated stt-proxy returned an invalid real-provider result',
|
|
)
|
|
}
|
|
|
|
console.log(`Talk local auth integration passed: ${assertions} assertions`)
|
|
} finally {
|
|
if (createdUserId !== null) await admin.auth.admin.deleteUser(createdUserId)
|
|
}
|