diff --git a/server/routes/admin/preview.js b/server/routes/admin/preview.js index a31a613..df1d94b 100644 --- a/server/routes/admin/preview.js +++ b/server/routes/admin/preview.js @@ -13,6 +13,7 @@ import { getRepoInfo } from '../../services/giteaAdmin.js' const router = Router() router.use(requireAppwriteStaff) +const BUSY = 'Server ausgelastet, versuche es gleich noch mal' const MODES = ['auto', 'build', 'dev', 'off'] const SHA_RE = /^[0-9a-f]{7,40}$/ @@ -74,13 +75,17 @@ async function triggerRebuild(doc) { branch, subdomain: doc.subdomain, } - return runDeploy(repo, branch, cfg, { project: doc }) + const result = await runDeploy(repo, branch, cfg, { project: doc }) + // runDeploy meldet Runner-Fehler im Ergebnis; Schutzschalter (503) an WOMS durchreichen + if (result?.error && /Server ausgelastet/.test(result.error)) throw fail(503, BUSY) + return result } const wrap = (fn) => async (req, res) => { try { await fn(req, res) } catch (err) { + if (!err.expected && err.status === 503) return res.status(503).json({ error: BUSY }) const status = err.expected ? err.status : 500 if (!err.expected) console.error('[admin/preview]', err.message) res.status(status).json({ error: !err.expected ? 'Preview-Aktion fehlgeschlagen' : err.message }) diff --git a/test/previewAdmin.test.js b/test/previewAdmin.test.js index 68b041f..32a83f3 100644 --- a/test/previewAdmin.test.js +++ b/test/previewAdmin.test.js @@ -11,7 +11,7 @@ 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: [], slow: false, result: 'static' } +const rs = { calls: [], busy: false, slow: false, result: 'static' } before(async () => { const seed = [ @@ -30,6 +30,7 @@ before(async () => { let raw = '' req.on('data', (c) => (raw += c)) req.on('end', () => { + if (rs.busy && (req.url === '/jobs/build' || req.url.endsWith('/start'))) { res.statusCode = 503; res.setHeader('Content-Type', 'application/json'); return res.end(JSON.stringify({ error: 'Server ausgelastet' })) } 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' })) @@ -87,7 +88,7 @@ after(async () => { await fs.rm(tmp, { recursive: true, force: true }) }) -beforeEach(() => { rs.slow = false; rs.result = 'static'; rs.calls = []; cfg.previewRunner.enabled = true; process.env.PREVIEW_ENV_KEY = key.toString('base64') }) +beforeEach(() => { rs.busy = false; 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}`, { @@ -273,3 +274,12 @@ test('dev/start ohne gespeicherten Stand: 409; dev/stop ändert failed nicht', a const r = await call('POST', '/pns/preview/dev/stop') assert.equal((await r.json()).state, 'failed') }) + +test('Runner-Schutzschalter 503: rebuild und dev/start melden Server ausgelastet', async () => { + rs.busy = true + for (const p of ['/pe/preview/rebuild', '/pw/preview/dev/start']) { + const r = await call('POST', p) + assert.equal(r.status, 503, p) + assert.deepEqual(await r.json(), { error: 'Server ausgelastet, versuche es gleich noch mal' }) + } +})