scripts/setup-smoke-test.mjs
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env node
/**
* Inserts org/project/api_key/provider_keys into the local Supabase DB so the
* proxy can be exercised end-to-end without the web signup flow.
*
* Uses raw fetch to PostgREST so we don't need supabase-js installed at the
* scripts/ root.
*
* Throwaway: smoke test session 2026-05-22.
*/
const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY
const SUPABASE_URL = process.env.SUPABASE_URL ?? 'http://127.0.0.1:54321'
const SUPABASE_SERVICE_ROLE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY
const OPENAI_KEY = process.env.SMOKE_OPENAI_KEY
const GEMINI_KEY = process.env.SMOKE_GEMINI_KEY
const ANTHROPIC_KEY = process.env.SMOKE_ANTHROPIC_KEY
if (!ENCRYPTION_KEY || !SUPABASE_SERVICE_ROLE_KEY) {
console.error('Set ENCRYPTION_KEY and SUPABASE_SERVICE_ROLE_KEY')
process.exit(1)
}
if (!OPENAI_KEY || !GEMINI_KEY || !ANTHROPIC_KEY) {
console.error('Set SMOKE_OPENAI_KEY, SMOKE_GEMINI_KEY, SMOKE_ANTHROPIC_KEY')
process.exit(1)
}
// ── crypto helpers (mirrored from apps/server/src/lib/crypto.ts) ────────────
const ALGORITHM = 'AES-GCM'
const IV_LENGTH = 12
const TAG_LENGTH = 16
function base64ToBytes(b64) {
return Uint8Array.from(Buffer.from(b64, 'base64'))
}
function bytesToBase64(bytes) {
return Buffer.from(bytes).toString('base64')
}
async function getKey() {
const keyBytes = base64ToBytes(ENCRYPTION_KEY)
if (keyBytes.length !== 32) throw new Error('ENCRYPTION_KEY must decode to 32 bytes')
return crypto.subtle.importKey('raw', keyBytes, { name: ALGORITHM }, false, ['encrypt'])
}
async function aes256Encrypt(plaintext) {
const key = await getKey()
const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH))
const encoded = new TextEncoder().encode(plaintext)
const encrypted = new Uint8Array(
await crypto.subtle.encrypt({ name: ALGORITHM, iv }, key, encoded),
)
const cipherOnly = encrypted.subarray(0, encrypted.length - TAG_LENGTH)
const tag = encrypted.subarray(encrypted.length - TAG_LENGTH)
const result = new Uint8Array(IV_LENGTH + TAG_LENGTH + cipherOnly.length)
result.set(iv, 0)
result.set(tag, IV_LENGTH)
result.set(cipherOnly, IV_LENGTH + TAG_LENGTH)
return bytesToBase64(result)
}
function randomHex(bytes) {
return Array.from(crypto.getRandomValues(new Uint8Array(bytes)), (b) =>
b.toString(16).padStart(2, '0'),
).join('')
}
async function sha256Hex(text) {
const data = new TextEncoder().encode(text)
const buf = await crypto.subtle.digest('SHA-256', data)
return Array.from(new Uint8Array(buf), (b) => b.toString(16).padStart(2, '0')).join('')
}
// ── PostgREST helper ────────────────────────────────────────────────────────
async function pgInsert(table, body) {
const res = await fetch(`${SUPABASE_URL}/rest/v1/${table}`, {
method: 'POST',
headers: {
'apikey': SUPABASE_SERVICE_ROLE_KEY,
'Authorization': `Bearer ${SUPABASE_SERVICE_ROLE_KEY}`,
'Content-Type': 'application/json',
'Prefer': 'return=representation',
},
body: JSON.stringify(body),
})
const text = await res.text()
if (!res.ok) {
throw new Error(`${table} INSERT failed (${res.status}): ${text}`)
}
return JSON.parse(text)
}
// ── set up DB rows ──────────────────────────────────────────────────────────
console.log('1. Creating auth user (owner)...')
const userRes = await fetch(`${SUPABASE_URL}/auth/v1/admin/users`, {
method: 'POST',
headers: {
'apikey': SUPABASE_SERVICE_ROLE_KEY,
'Authorization': `Bearer ${SUPABASE_SERVICE_ROLE_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: `smoke-${Date.now()}@example.com`,
password: 'smoke-test-' + randomHex(8),
email_confirm: true,
}),
})
const userData = await userRes.json()
if (!userRes.ok) {
console.error('auth user create failed:', userData)
process.exit(1)
}
const ownerId = userData.id
console.log(' owner_id =', ownerId)
console.log('2. Creating organization...')
const [org] = await pgInsert('organizations', {
name: 'Smoke Test Org',
plan: 'team',
owner_id: ownerId,
})
console.log(' org_id =', org.id)
console.log('3. Creating project...')
const [project] = await pgInsert('projects', {
organization_id: org.id,
name: 'smoke-test',
})
console.log(' project_id =', project.id)
console.log('4. Generating Spanlens key...')
const rawKey = 'sl_live_' + randomHex(24)
const keyHash = await sha256Hex(rawKey)
const keyPrefix = rawKey.slice(0, 12)
const [apiKey] = await pgInsert('api_keys', {
project_id: project.id,
name: 'smoke-test-key',
key_hash: keyHash,
key_prefix: keyPrefix,
})
console.log(' api_key_id =', apiKey.id)
console.log('5. Encrypting + inserting provider keys...')
const providers = [
{ provider: 'openai', plain: OPENAI_KEY, name: 'smoke-openai' },
{ provider: 'gemini', plain: GEMINI_KEY, name: 'smoke-gemini' },
{ provider: 'anthropic', plain: ANTHROPIC_KEY, name: 'smoke-anthropic' },
]
for (const p of providers) {
const encrypted = await aes256Encrypt(p.plain)
await pgInsert('provider_keys', {
organization_id: org.id,
api_key_id: apiKey.id,
provider: p.provider,
name: p.name,
encrypted_key: encrypted,
})
console.log(` ${p.provider}: stored (encrypted ${encrypted.length}b)`)
}
console.log('\n── READY ────────────────────────────────────────────────────')
console.log('export SPANLENS_KEY=' + rawKey)
console.log('export ORG_ID=' + org.id)
console.log('export PROJECT_ID=' + project.id)
console.log('─────────────────────────────────────────────────────────────')