mission-control/scripts/smoke-staging.mjs

169 lines
5.3 KiB
JavaScript
Executable File

#!/usr/bin/env node
const baseUrl = (process.env.STAGING_BASE_URL || process.env.BASE_URL || '').replace(/\/$/, '')
const apiKey = process.env.STAGING_API_KEY || process.env.API_KEY || ''
const authUser = process.env.STAGING_AUTH_USER || process.env.AUTH_USER || ''
const authPass = process.env.STAGING_AUTH_PASS || process.env.AUTH_PASS || ''
if (!baseUrl) {
console.error('Missing STAGING_BASE_URL (or BASE_URL).')
process.exit(1)
}
if (!apiKey) {
console.error('Missing STAGING_API_KEY (or API_KEY).')
process.exit(1)
}
if (!authUser || !authPass) {
console.error('Missing STAGING_AUTH_USER/STAGING_AUTH_PASS (or AUTH_USER/AUTH_PASS).')
process.exit(1)
}
const headers = {
'x-api-key': apiKey,
'content-type': 'application/json',
}
let createdProjectId = null
let createdTaskId = null
let createdAgentId = null
async function call(path, options = {}) {
const res = await fetch(`${baseUrl}${path}`, options)
const text = await res.text()
let body = null
try {
body = text ? JSON.parse(text) : null
} catch {
body = { raw: text }
}
return { res, body }
}
function assertStatus(actual, expected, label) {
if (actual !== expected) {
throw new Error(`${label} failed: expected ${expected}, got ${actual}`)
}
console.log(`PASS ${label}`)
}
async function run() {
const login = await call('/api/auth/login', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ username: authUser, password: authPass }),
})
assertStatus(login.res.status, 200, 'login')
const workspaces = await call('/api/workspaces', { headers })
assertStatus(workspaces.res.status, 200, 'GET /api/workspaces')
const suffix = `${Date.now()}-${Math.random().toString(36).slice(2, 7)}`
const ticketPrefix = `S${String(Date.now()).slice(-5)}`
const projectCreate = await call('/api/projects', {
method: 'POST',
headers,
body: JSON.stringify({
name: `staging-smoke-${suffix}`,
ticket_prefix: ticketPrefix,
}),
})
assertStatus(projectCreate.res.status, 201, 'POST /api/projects')
createdProjectId = projectCreate.body?.project?.id
if (!createdProjectId) throw new Error('project id missing')
const projectGet = await call(`/api/projects/${createdProjectId}`, { headers })
assertStatus(projectGet.res.status, 200, 'GET /api/projects/[id]')
const projectPatch = await call(`/api/projects/${createdProjectId}`, {
method: 'PATCH',
headers,
body: JSON.stringify({ description: 'staging smoke update' }),
})
assertStatus(projectPatch.res.status, 200, 'PATCH /api/projects/[id]')
const agentCreate = await call('/api/agents', {
method: 'POST',
headers,
body: JSON.stringify({ name: `smoke-agent-${suffix}`, role: 'tester' }),
})
assertStatus(agentCreate.res.status, 201, 'POST /api/agents')
createdAgentId = agentCreate.body?.agent?.id
const assign = await call(`/api/projects/${createdProjectId}/agents`, {
method: 'POST',
headers,
body: JSON.stringify({ agent_name: `smoke-agent-${suffix}`, role: 'member' }),
})
assertStatus(assign.res.status, 201, 'POST /api/projects/[id]/agents')
const projectTasksCreate = await call('/api/tasks', {
method: 'POST',
headers,
body: JSON.stringify({
title: `smoke-task-${suffix}`,
project_id: createdProjectId,
priority: 'medium',
status: 'inbox',
}),
})
assertStatus(projectTasksCreate.res.status, 201, 'POST /api/tasks (project scoped)')
createdTaskId = projectTasksCreate.body?.task?.id
const projectTasksGet = await call(`/api/projects/${createdProjectId}/tasks`, { headers })
assertStatus(projectTasksGet.res.status, 200, 'GET /api/projects/[id]/tasks')
const unassign = await call(`/api/projects/${createdProjectId}/agents?agent_name=${encodeURIComponent(`smoke-agent-${suffix}`)}`, {
method: 'DELETE',
headers,
})
assertStatus(unassign.res.status, 200, 'DELETE /api/projects/[id]/agents')
if (createdTaskId) {
const deleteTask = await call(`/api/tasks/${createdTaskId}`, {
method: 'DELETE',
headers,
})
assertStatus(deleteTask.res.status, 200, 'DELETE /api/tasks/[id]')
createdTaskId = null
}
if (createdProjectId) {
const deleteProject = await call(`/api/projects/${createdProjectId}?mode=delete`, {
method: 'DELETE',
headers,
})
assertStatus(deleteProject.res.status, 200, 'DELETE /api/projects/[id]?mode=delete')
createdProjectId = null
}
if (createdAgentId) {
const deleteAgent = await call(`/api/agents/${createdAgentId}`, {
method: 'DELETE',
headers,
})
if (deleteAgent.res.status !== 200 && deleteAgent.res.status !== 404) {
throw new Error(`DELETE /api/agents/[id] cleanup failed: ${deleteAgent.res.status}`)
}
createdAgentId = null
console.log('PASS cleanup agent')
}
console.log(`\nSmoke test passed for ${baseUrl}`)
}
run().catch(async (error) => {
console.error(`\nSmoke test failed: ${error.message}`)
if (createdTaskId) {
await call(`/api/tasks/${createdTaskId}`, { method: 'DELETE', headers }).catch(() => {})
}
if (createdProjectId) {
await call(`/api/projects/${createdProjectId}?mode=delete`, { method: 'DELETE', headers }).catch(() => {})
}
if (createdAgentId) {
await call(`/api/agents/${createdAgentId}`, { method: 'DELETE', headers }).catch(() => {})
}
process.exit(1)
})