Preview: Admin-API Nachbesserung (off verdraengt Bau, env-Sperre)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
07bef6971c
commit
b3cf78c82a
2 files changed
+82
-23
No files matched your search
@@ -11,11 +11,15 @@ import { startFakeAppwrite } from './helpers/fakeAppwrite.js'
|
||||
let aw, runner, gitea, app, base, tmp, cfg, envLib, logs
|
||||
const key = crypto.randomBytes(32)
|
||||
const SHA = 'c'.repeat(40)
|
||||
const rs = { calls: [] }
|
||||
const rs = { calls: [], slow: false, result: 'static' }
|
||||
|
||||
before(async () => {
|
||||
const seed = [
|
||||
{ $id: 'pw', projectName: 'Web', repoFullName: 'JUSN/web', subdomain: 'web', previewBranch: 'main', previewBuiltSha: SHA, previewState: 'online', previewDetected: 'static' },
|
||||
{ $id: 'pr', projectName: 'R', repoFullName: 'JUSN/race', subdomain: 'race', previewBranch: 'main', previewBuiltSha: SHA },
|
||||
{ $id: 'pe', projectName: 'E', repoFullName: 'JUSN/envp', subdomain: 'envp', previewBranch: 'main', previewBuiltSha: SHA },
|
||||
{ $id: 'pbad', projectName: 'B', repoFullName: 'JUSN/bad', subdomain: 'bad', previewEnv: 'v1:x:y:z' },
|
||||
{ $id: 'pns', projectName: 'N', repoFullName: 'JUSN/nosha', subdomain: 'nosha' },
|
||||
{ $id: 'pnosub', projectName: 'Ohne', repoFullName: 'JUSN/ohne' },
|
||||
{ $id: 'pa', projectName: 'App', repoFullName: 'JUSN/app', customerId: '', ticketId: '' },
|
||||
{ $id: 'pb', projectName: 'Web2', repoFullName: 'JUSN/web2', customerId: '', ticketId: '' },
|
||||
@@ -29,7 +33,7 @@ before(async () => {
|
||||
rs.calls.push({ method: req.method, url: req.url, body: raw ? JSON.parse(raw) : null })
|
||||
res.setHeader('Content-Type', 'application/json')
|
||||
if (req.method === 'POST' && req.url === '/jobs/build') return res.end(JSON.stringify({ runId: 'run-x' }))
|
||||
if (req.method === 'GET' && req.url.startsWith('/jobs/')) return res.end(JSON.stringify({ state: 'done', detected: { kind: 'static', pm: 'npm' }, sha: 'b'.repeat(40), log: ['ok'], error: null, result: 'static' }))
|
||||
if (req.method === 'GET' && req.url.startsWith('/jobs/')) return res.end(JSON.stringify(rs.slow ? { state: 'running' } : { state: 'done', detected: { kind: 'static', pm: 'npm' }, sha: 'b'.repeat(40), log: ['ok'], error: null, result: rs.result }))
|
||||
if (req.url.endsWith('/start')) return res.end(JSON.stringify({ state: 'starting' }))
|
||||
res.end(JSON.stringify({ state: 'sleeping', ok: true }))
|
||||
})
|
||||
@@ -62,7 +66,7 @@ before(async () => {
|
||||
const { default: preview } = await import('../server/routes/admin/preview.js')
|
||||
const { default: wp } = await import('../server/routes/admin/websiteProjects.js')
|
||||
app = express()
|
||||
app.use(express.json())
|
||||
app.use(express.json({ limit: '2mb' }))
|
||||
app.use('/api/admin/website-projects', preview)
|
||||
app.use('/api/admin/website-projects', wp)
|
||||
const server = await new Promise((r) => { const s = app.listen(0, '127.0.0.1', () => r(s)) })
|
||||
@@ -73,7 +77,7 @@ before(async () => {
|
||||
after(async () => {
|
||||
// Hintergrund-Verfolger ausklingen lassen, bevor der Fake-Runner schliesst
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const building = ['pw', 'pa', 'pb', 'pc'].some((id) => aw.get('websiteProjects', id)?.previewState === 'building')
|
||||
const building = ['pw', 'pa', 'pb', 'pc', 'pr', 'pe'].some((id) => aw.get('websiteProjects', id)?.previewState === 'building')
|
||||
if (!building) break
|
||||
await new Promise((r) => setTimeout(r, 20))
|
||||
}
|
||||
@@ -83,7 +87,7 @@ after(async () => {
|
||||
await fs.rm(tmp, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
beforeEach(() => { rs.calls = []; cfg.previewRunner.enabled = true; process.env.PREVIEW_ENV_KEY = key.toString('base64') })
|
||||
beforeEach(() => { rs.slow = false; rs.result = 'static'; rs.calls = []; cfg.previewRunner.enabled = true; process.env.PREVIEW_ENV_KEY = key.toString('base64') })
|
||||
|
||||
const call = (method, p, body, jwt = 'jwt-admin') =>
|
||||
fetch(`${base}${p}`, {
|
||||
@@ -164,7 +168,7 @@ test('env: ohne Schluessel 503, ungueltig 400, Werte nie in Antworten', async ()
|
||||
test('Protokolle: Liste, lesen, ../x => 400', async () => {
|
||||
const file = await logs.saveRunLog('pw', ['a', 'b'], {})
|
||||
const list = await (await call('GET', '/pw/preview')).json()
|
||||
assert.equal(list.logs[0].runFile, file)
|
||||
assert.ok(list.logs.some((l) => l.runFile === file))
|
||||
const r = await call('GET', `/pw/preview/logs/${file}`)
|
||||
assert.deepEqual(await r.json(), { lines: ['a', 'b'] })
|
||||
assert.equal((await call('GET', '/pw/preview/logs/..%2Fx')).status, 400)
|
||||
@@ -226,3 +230,46 @@ test('Services: Eintraege tragen previewState', async () => {
|
||||
})
|
||||
|
||||
void envLib
|
||||
|
||||
test('PATCH off während eines laufenden Baus: Zustand bleibt off, keine Dev-Registrierung', async () => {
|
||||
rs.slow = true
|
||||
rs.result = 'dev'
|
||||
assert.equal((await call('POST', '/pr/preview/rebuild')).status, 200)
|
||||
assert.equal(aw.get('websiteProjects', 'pr').previewState, 'building')
|
||||
const off = await call('PATCH', '/pr/preview', { mode: 'off' })
|
||||
assert.equal(off.status, 200)
|
||||
rs.slow = false
|
||||
await new Promise((r) => setTimeout(r, 300))
|
||||
assert.equal(aw.get('websiteProjects', 'pr').previewState, 'off')
|
||||
assert.equal(rs.calls.filter((c) => c.method === 'PUT').length, 0)
|
||||
})
|
||||
|
||||
test('parallele PUT env: beide Variablen bleiben erhalten', async () => {
|
||||
const [a, b] = await Promise.all([
|
||||
call('PUT', '/pe/preview/env', { set: { AAA: 'wert-eins' } }),
|
||||
call('PUT', '/pe/preview/env', { set: { BBB: 'wert-zwei' } }),
|
||||
])
|
||||
assert.equal(a.status, 200)
|
||||
assert.equal(b.status, 200)
|
||||
const names = (await (await call('GET', '/pe/preview')).json()).envNames
|
||||
assert.deepEqual(names, ['AAA', 'BBB'])
|
||||
})
|
||||
|
||||
test('env: set/remove falsch typisiert 400, zu lang 400, unentschlüsselbar 409', async () => {
|
||||
assert.equal((await call('PUT', '/pe/preview/env', { set: [1] })).status, 400)
|
||||
assert.equal((await call('PUT', '/pe/preview/env', { set: 'x' })).status, 400)
|
||||
assert.equal((await call('PUT', '/pe/preview/env', { remove: 'AAA' })).status, 400)
|
||||
const big = Object.fromEntries(Array.from({ length: 10 }, (_, i) => [`V${i}`, 'x'.repeat(4000)]))
|
||||
const r = await call('PUT', '/pe/preview/env', { set: big })
|
||||
assert.equal(r.status, 400)
|
||||
assert.equal((await r.json()).error, 'Zu viele oder zu lange Variablen')
|
||||
assert.equal((await call('PUT', '/pbad/preview/env', { set: { A: 'b' } })).status, 409)
|
||||
})
|
||||
|
||||
test('dev/start ohne gespeicherten Stand: 409; dev/stop ändert failed nicht', async () => {
|
||||
assert.equal((await call('POST', '/pns/preview/dev/start')).status, 409)
|
||||
const { updateDocument } = await import('../server/services/appwriteAdmin.js')
|
||||
await updateDocument('websiteProjects', 'pns', { previewState: 'failed' })
|
||||
const r = await call('POST', '/pns/preview/dev/stop')
|
||||
assert.equal((await r.json()).state, 'failed')
|
||||
})
|
||||
Reference in new issue
Block a user