tests/app-onboarding-builder-v4.db.test.ts
Rama main · réplica de lectura del repositorio original.
import { randomUUID } from 'node:crypto'
import { env } from 'node:process'
import { createClient } from '@supabase/supabase-js'
import { describe, expect, it } from 'vitest'
const admin = createClient(env.SUPABASE_URL!, env.SUPABASE_SERVICE_KEY!, { auth: { persistSession: false, autoRefreshToken: false } })
const anonymous = createClient(env.SUPABASE_URL!, env.SUPABASE_ANON_KEY!, { auth: { persistSession: false, autoRefreshToken: false } })
const IOS_STEPS = [
'start_setup',
'choose_destination',
'connect_app_store',
'prepare_certificate',
'prepare_profile',
'successful_cloud_build',
]
const ANDROID_STEPS = [
'start_setup',
'prepare_keystore',
'connect_google_play',
'successful_cloud_build',
]
describe('builder checklist v4 database initialization', () => {
it.concurrent('keeps the initializer unavailable to anonymous callers', async () => {
const { data, error } = await anonymous.rpc('new_builder_onboarding_setup_v1')
expect(data).toBeNull()
expect(error).not.toBeNull()
})
it.concurrent('defines the exact pending Builder-only setup without selecting a platform', async () => {
const { data: setup, error } = await admin.rpc('new_builder_onboarding_setup_v1')
expect(error).toBeNull()
expect(setup).toMatchObject({
todo_list_version: 4,
builder_todo_list_version: '1',
paths: ['builder'],
selected_path: 'builder',
outcome: 'in_progress',
})
expect(setup).not.toHaveProperty('ota_todo_list_version')
expect(setup).not.toHaveProperty('selected_builder_platform')
expect(Object.keys(setup.steps)).toEqual(['builder'])
for (const [platform, expectedSteps] of Object.entries({ ios: IOS_STEPS, android: ANDROID_STEPS })) {
expect(Object.keys(setup.steps.builder[platform]).sort()).toEqual([...expectedSteps].sort())
expect(Object.values(setup.steps.builder[platform])).toEqual(expectedSteps.map(() => ({ status: 'pending' })))
}
const merged = await admin.rpc('merge_app_onboarding_setup', {
p_existing: { setup, features: { builder: { stage: 'new' } } },
p_patch: { source: 'cli', steps: { login_cli_mcp: { status: 'done' } } },
})
expect(merged.error).toBeNull()
expect(merged.data.setup.steps).toEqual(setup.steps)
expect(merged.data.setup.paths).toEqual(['builder'])
expect(merged.data.setup.outcome).toBe('in_progress')
expect(merged.data.features).toEqual({ builder: { stage: 'new' } })
})
it.concurrent.each([
['builder', 'A', true, 'builder'],
['builder', null, true, 'ota'],
['builder', 'B', true, 'ota'],
['ota', 'A', true, 'ota'],
['builder', 'A', false, 'ota'],
] as const)('assigns creator intent %s, branch %s, creator-owned org %s to the %s checklist', async (intent, branch, ownOrg, expectedPath) => {
const email = `builder-v4-${randomUUID()}@example.com`
const orgId = randomUUID()
const appId = `com.test.builder.v4.${randomUUID()}`
let userId: string | undefined
let ownerId: string | undefined
let ownerEmail: string | undefined
try {
const created = await admin.auth.admin.createUser({ email, password: 'builder-v4-test-password', email_confirm: true })
expect(created.error).toBeNull()
userId = created.data.user!.id
if (!ownOrg) {
ownerEmail = `builder-v4-owner-${randomUUID()}@example.com`
const owner = await admin.auth.admin.createUser({ email: ownerEmail, password: 'builder-v4-test-password', email_confirm: true })
expect(owner.error).toBeNull()
ownerId = owner.data.user!.id
}
expect((await admin.from('users').upsert({
id: userId,
email,
onboarding: { intent, abtests: branch ? { builder_todo_list_v4: { branch, assigned_at: new Date().toISOString() } } : {} },
})).error).toBeNull()
if (ownerId)
expect((await admin.from('users').upsert({ id: ownerId, email: ownerEmail })).error).toBeNull()
expect((await admin.from('orgs').insert({ id: orgId, created_by: ownerId ?? userId, name: 'Builder checklist test', management_email: email, onboarding: { intent: 'builder' } })).error).toBeNull()
const app = await admin.from('apps').insert({
app_id: appId,
owner_org: orgId,
name: 'Builder checklist test',
icon_url: '',
onboarding: { created_by_user_id: userId, setup: { todo_list_version: 2, source: 'manual', outcome: 'in_progress', steps: {} } },
}).select('onboarding').single()
expect(app.error).toBeNull()
const setup = (app.data!.onboarding as any).setup
expect(setup.todo_list_version).toBe(4)
expect((app.data!.onboarding as any).created_by_user_id).toBe(userId)
if (expectedPath === 'ota') {
expect(setup.steps?.builder).toBeUndefined()
expect(setup.builder_todo_list_version).toBeUndefined()
expect(setup.ota_todo_list_version).toBe('1')
expect(setup.paths).toEqual(['ota'])
expect(Object.keys(setup.steps.ota)).toHaveLength(7)
}
else {
expect(setup.builder_todo_list_version).toBe('1')
expect(setup.paths).toEqual(['builder'])
expect(setup.selected_path).toBe('builder')
expect(Object.keys(setup.steps)).toEqual(['builder'])
for (const [platform, expectedSteps] of Object.entries({ ios: IOS_STEPS, android: ANDROID_STEPS })) {
expect(Object.keys(setup.steps.builder[platform]).sort()).toEqual([...expectedSteps].sort())
expect(Object.values(setup.steps.builder[platform])).toEqual(expectedSteps.map(() => ({ status: 'pending' })))
}
}
if (expectedPath === 'builder') {
expect(setup.ota_todo_list_version).toBeUndefined()
expect(setup.steps.ota).toBeUndefined()
}
expect((await admin.from('users').update({ onboarding: { intent: 'builder', abtests: {} } }).eq('id', userId)).error).toBeNull()
expect((await admin.from('orgs').update({ onboarding: { intent: 'ota' } }).eq('id', orgId)).error).toBeNull()
const renamed = await admin.from('apps').update({ name: 'Renamed' }).eq('app_id', appId).select('onboarding').single()
expect(renamed.error).toBeNull()
expect((renamed.data!.onboarding as any).setup).toEqual(setup)
}
finally {
await admin.from('apps').delete().eq('app_id', appId)
await admin.from('orgs').delete().eq('id', orgId)
if (userId) {
await admin.from('users').delete().eq('id', userId)
await admin.auth.admin.deleteUser(userId)
}
if (ownerId) {
await admin.from('users').delete().eq('id', ownerId)
await admin.auth.admin.deleteUser(ownerId)
}
}
})
})