import { convexTest } from 'convex-test'; import { describe, expect, test } from 'vitest'; import type { Id } from '../../convex/_generated/dataModel.js'; import { api } from '../../convex/_generated/api.js'; import schema from '../../convex/schema'; const modules = import.meta.glob('../../convex/**/*.*s'); const createUser = async (t: ReturnType, email: string) => await t.mutation(async (ctx) => { return await ctx.db.insert('users', { email, name: email }); }); const authed = (t: ReturnType, userId: string) => t.withIdentity({ subject: `${userId}|session`, issuer: 'https://convex.test', }); const seedDetails = async ( t: ReturnType, ownerId: Id<'users'>, ) => await t.mutation(async (ctx) => { const now = Date.now(); const spoonId = await ctx.db.insert('spoons', { ownerId, name: 'Detail Spoon', provider: 'gitea', upstreamOwner: 'upstream', upstreamRepo: 'detail', upstreamDefaultBranch: 'main', upstreamUrl: 'https://git.example.com/upstream/detail', forkOwner: 'team', forkRepo: 'detail-spoon', forkUrl: 'https://git.example.com/team/detail-spoon', visibility: 'private', maintenanceMode: 'watch', syncCadence: 'daily', productionRefStrategy: 'default_branch', status: 'active', createdAt: now, updatedAt: now, }); const threadId = await ctx.db.insert('threads', { ownerId, spoonId, title: 'Private thread', source: 'user_request', status: 'open', priority: 'normal', createdAt: now, updatedAt: now, }); const requestId = await ctx.db.insert('agentRequests', { spoonId, ownerId, prompt: 'Private work', status: 'running', createdAt: now, updatedAt: now, }); const jobId = await ctx.db.insert('agentJobs', { spoonId, ownerId, agentRequestId: requestId, threadId, status: 'running', prompt: 'Private work', runtime: 'opencode', baseBranch: 'main', workBranch: 'spoon/private', forkOwner: 'team', forkRepo: 'detail-spoon', forkUrl: 'https://git.example.com/team/detail-spoon', upstreamOwner: 'upstream', upstreamRepo: 'detail', selectedSecretIds: [], model: 'openai/gpt-5.1-codex', reasoningEffort: 'medium', createdAt: now, updatedAt: now, }); await ctx.db.patch(threadId, { latestAgentJobId: jobId }); return { threadId, jobId }; }); describe('detail queries for unavailable records', () => { test('return null for records owned by another user', async () => { const t = convexTest(schema, modules); const ownerId = (await createUser(t, 'owner@example.com')) as Id<'users'>; const otherId = await createUser(t, 'other@example.com'); const { threadId, jobId } = await seedDetails(t, ownerId); const other = authed(t, otherId); await expect( other.query(api.threads.get, { threadId }), ).resolves.toBeNull(); await expect(other.query(api.agentJobs.get, { jobId })).resolves.toBeNull(); }); test('return null for records that do not exist', async () => { const t = convexTest(schema, modules); const ownerId = (await createUser(t, 'owner@example.com')) as Id<'users'>; const owner = authed(t, ownerId); const { threadId, jobId } = await seedDetails(t, ownerId); await t.mutation(async (ctx) => { await ctx.db.delete(jobId); await ctx.db.delete(threadId); }); await expect( owner.query(api.threads.get, { threadId }), ).resolves.toBeNull(); await expect(owner.query(api.agentJobs.get, { jobId })).resolves.toBeNull(); }); });