d3ro-voice/apps/mobile-rn/__tests__/knowledge-actions.local.integration.mjs
2026-08-29 18:33:45 +09:00

206 lines
7.7 KiB
JavaScript

import assert from 'node:assert/strict'
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 options = {
auth: { persistSession: false, autoRefreshToken: false, detectSessionInUrl: false },
}
const admin = createClient(supabaseUrl, serviceRoleKey, options)
const userAClient = createClient(supabaseUrl, anonKey, options)
const userBClient = createClient(supabaseUrl, anonKey, options)
const suffix = `${Date.now()}-${Math.floor(Math.random() * 1_000_000)}`
const password = `D3ro-${suffix}-Strong!`
const createdUserIds = []
let assertions = 0
let embeddingMode = 'not-run'
let searchMode = 'not-run'
function checked(condition, message) {
assert.ok(condition, message)
assertions += 1
}
async function createDisposableUser(client, marker) {
const response = await client.auth.signUp({
email: `d3ro.mobile.${suffix}.${marker}@gmail.com`,
password,
})
assert.equal(response.error, null, `Could not create local user ${marker}`)
assert.ok(response.data.user && response.data.session, `Local user ${marker} has no session`)
createdUserIds.push(response.data.user.id)
return response.data
}
async function invokeEdge(client, functionName, body) {
const session = (await client.auth.getSession()).data.session
assert.ok(session, `${functionName} requires a local authenticated session`)
const response = await fetch(`${supabaseUrl}/functions/v1/${functionName}`, {
method: 'POST',
headers: {
Authorization: `Bearer ${session.access_token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
})
return {
status: response.status,
body: await response.json().catch(() => null),
}
}
try {
const [userA, userB] = await Promise.all([
createDisposableUser(userAClient, 'a'),
createDisposableUser(userBClient, 'b'),
])
const documentInsert = await userAClient
.from('knowledge_documents')
.insert({
user_id: userA.user.id,
title: 'Mobile RLS document',
file_name: 'mobile-rls.md',
file_type: 'md',
chunk_count: 1,
indexed: false,
indexed_at: null,
})
.select('id,user_id,indexed')
.single()
assert.equal(documentInsert.error, null, 'Owner could not insert knowledge document')
const documentId = documentInsert.data.id
checked(documentInsert.data.user_id === userA.user.id, 'Knowledge owner was not preserved')
const chunkInsert = await userAClient
.from('knowledge_chunks')
.insert({ document_id: documentId, chunk_index: 0, content: 'Private mobile knowledge' })
.select('id,document_id')
.single()
assert.equal(chunkInsert.error, null, 'Owner could not insert knowledge chunk')
checked(chunkInsert.data.document_id === documentId, 'Knowledge chunk was linked to another document')
const ownerDocuments = await userAClient
.from('knowledge_documents')
.select('id')
.eq('id', documentId)
assert.equal(ownerDocuments.error, null)
checked(ownerDocuments.data.length === 1, 'Owner cannot read the knowledge document')
const strangerDocuments = await userBClient
.from('knowledge_documents')
.select('id')
.eq('id', documentId)
assert.equal(strangerDocuments.error, null)
checked(strangerDocuments.data.length === 0, 'Another account can read a private knowledge document')
const strangerChunks = await userBClient
.from('knowledge_chunks')
.select('id')
.eq('document_id', documentId)
assert.equal(strangerChunks.error, null)
checked(strangerChunks.data.length === 0, 'Another account can read private knowledge chunks')
const strangerDelete = await userBClient
.from('knowledge_documents')
.delete()
.eq('id', documentId)
.select('id')
assert.equal(strangerDelete.error, null)
checked(strangerDelete.data.length === 0, 'Another account deleted a private knowledge document')
const documentAfterDeleteAttempt = await userAClient
.from('knowledge_documents')
.select('id')
.eq('id', documentId)
checked(documentAfterDeleteAttempt.data?.length === 1, 'Cross-account delete changed owner data')
const embedding = await invokeEdge(userAClient, 'embed-chunks', { document_id: documentId })
checked(
(embedding.status === 200 && embedding.body?.indexed === true)
|| (embedding.status === 503 && embedding.body?.error === 'embedding_provider_unavailable'),
'Embedding did not either complete or fail closed as unavailable',
)
embeddingMode = embedding.status === 200 ? 'indexed' : 'provider-unavailable'
if (embedding.status === 503) {
const pending = await userAClient
.from('knowledge_documents')
.select('indexed,indexed_at')
.eq('id', documentId)
.single()
checked(pending.data?.indexed === false && pending.data?.indexed_at === null, 'Unavailable embeddings marked a document ready')
}
const search = await invokeEdge(userAClient, 'search-knowledge', {
query: 'Private mobile knowledge',
count: 5,
})
checked(
(search.status === 200 && Array.isArray(search.body?.results))
|| (search.status === 503 && search.body?.error === 'embedding_provider_unavailable'),
'Knowledge search did not either return verified results or fail closed as unavailable',
)
searchMode = search.status === 200 ? 'verified-results' : 'provider-unavailable'
const meetingInsert = await userAClient
.from('meetings')
.insert({ user_id: userA.user.id, title: 'Mobile action meeting', status: 'recording' })
.select('id,user_id,title')
.single()
assert.equal(meetingInsert.error, null, 'Allowlisted create_meeting could not persist')
const meetingId = meetingInsert.data.id
checked(meetingInsert.data.user_id === userA.user.id, 'Meeting action changed ownership')
const strangerMeeting = await userBClient
.from('meetings')
.select('id')
.eq('id', meetingId)
assert.equal(strangerMeeting.error, null)
checked(strangerMeeting.data.length === 0, 'Another account can read a private action-created meeting')
const memoInsert = await userAClient
.from('meeting_memos')
.insert({
meeting_id: meetingId,
user_id: userA.user.id,
content: 'Confirmed mobile action memo',
timestamp_ms: 0,
})
.select('id,user_id,meeting_id')
.single()
assert.equal(memoInsert.error, null, 'Allowlisted create_memo could not persist')
checked(
memoInsert.data.user_id === userA.user.id && memoInsert.data.meeting_id === meetingId,
'Memo action changed ownership or meeting linkage',
)
const strangerMemo = await userBClient
.from('meeting_memos')
.insert({
meeting_id: meetingId,
user_id: userB.user.id,
content: 'Cross-account memo',
timestamp_ms: 0,
})
.select('id')
checked(strangerMemo.error !== null && strangerMemo.data === null, 'Another account inserted a memo into a private meeting')
const anonymousLlm = await fetch(`${supabaseUrl}/functions/v1/llm-proxy`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ messages: [{ role: 'user', content: 'parse this' }] }),
})
checked(anonymousLlm.status === 401, 'llm-proxy accepted an unauthenticated action parser request')
console.log(
`knowledge/actions local integration passed: ${assertions} assertions; embedding=${embeddingMode}; search=${searchMode}`,
)
} finally {
await Promise.allSettled(createdUserIds.map((userId) => admin.auth.admin.deleteUser(userId)))
}