460 lines
22 KiB
JavaScript
460 lines
22 KiB
JavaScript
import { test, before, after, beforeEach } from 'node:test'
|
||
import assert from 'node:assert/strict'
|
||
import http from 'node:http'
|
||
import fs from 'node:fs/promises'
|
||
import os from 'node:os'
|
||
import path from 'node:path'
|
||
import crypto from 'node:crypto'
|
||
import { startFakeAppwrite } from './helpers/fakeAppwrite.js'
|
||
|
||
let aw, runner, gitea, tmp, pd, logs, envLib
|
||
const key = crypto.randomBytes(32)
|
||
const SHA = 'a'.repeat(40)
|
||
|
||
// Fake-Runner: zaehlt Aufrufe, Laeufe per Skript steuerbar
|
||
const rs = { calls: [], runs: new Map(), next: 0, script: null, submitError: null }
|
||
function startRunner() {
|
||
return http.createServer((req, res) => {
|
||
let raw = ''
|
||
req.on('data', (c) => (raw += c))
|
||
req.on('end', () => {
|
||
const body = raw ? JSON.parse(raw) : null
|
||
rs.calls.push({ method: req.method, url: req.url, auth: req.headers.authorization, body })
|
||
res.setHeader('Content-Type', 'application/json')
|
||
const send = (code, obj) => { res.statusCode = code; res.end(JSON.stringify(obj)) }
|
||
if (req.method === 'POST' && req.url === '/jobs/build') {
|
||
if (rs.submitError) return send(rs.submitError, { error: 'Server ausgelastet' })
|
||
const runId = `run-${++rs.next}`
|
||
rs.runs.set(runId, rs.script(body))
|
||
return send(200, { runId })
|
||
}
|
||
const m = req.url.match(/^\/jobs\/(.+)$/)
|
||
if (m && req.method === 'GET') return rs.runs.has(m[1]) ? send(200, rs.runs.get(m[1])) : send(404, { error: 'unbekannt' })
|
||
if (req.method === 'PUT' && req.url.startsWith('/dev/')) return send(200, { state: 'sleeping' })
|
||
send(404, { error: 'x' })
|
||
})
|
||
})
|
||
}
|
||
const done = (over = {}) => (job) => ({ state: 'done', detected: { kind: 'static', pm: 'npm' }, sha: job.sha, log: ['ok'], error: null, result: 'static', ...over })
|
||
const submits = () => rs.calls.filter((c) => c.url === '/jobs/build')
|
||
|
||
before(async () => {
|
||
aw = await startFakeAppwrite({ seed: { websiteProjects: [] } })
|
||
runner = startRunner()
|
||
await new Promise((r) => runner.listen(0, '127.0.0.1', r))
|
||
gitea = http.createServer((req, res) => {
|
||
res.setHeader('Content-Type', 'application/json')
|
||
res.end(JSON.stringify({ commit: { id: 'b'.repeat(40) } }))
|
||
})
|
||
await new Promise((r) => gitea.listen(0, '127.0.0.1', r))
|
||
tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'preview-runner-'))
|
||
Object.assign(process.env, {
|
||
APPWRITE_ENDPOINT: aw.endpoint,
|
||
APPWRITE_API_KEY: 'test-key',
|
||
PREVIEW_DEPLOY_ROOT: path.join(tmp, 'previews'),
|
||
TRAEFIK_DYNAMIC_DIR: path.join(tmp, 'dynamic'),
|
||
GITEA_BASE_URL: `http://127.0.0.1:${gitea.address().port}`,
|
||
GITEA_API_TOKEN: 'tok',
|
||
PREVIEW_RUNNER_URL: `http://127.0.0.1:${runner.address().port}`,
|
||
PREVIEW_RUNNER_TOKEN: 'sekret',
|
||
PREVIEW_ENV_KEY: key.toString('base64'),
|
||
})
|
||
const { config } = await import('../server/config.js')
|
||
config.previewRunner.enabled = true
|
||
config.previewRunner.logDir = path.join(tmp, 'logs')
|
||
pd = await import('../server/services/previewDeploy.js')
|
||
pd.runnerTiming.pollMs = 10
|
||
pd.runnerTiming.maxMs = 2000
|
||
logs = await import('../server/services/previewLogs.js')
|
||
envLib = await import('../server/services/previewEnv.js')
|
||
globalThis.__cfg = config
|
||
})
|
||
|
||
after(async () => {
|
||
await new Promise((r) => runner.close(r))
|
||
await new Promise((r) => gitea.close(r))
|
||
await aw.close()
|
||
await fs.rm(tmp, { recursive: true, force: true })
|
||
})
|
||
|
||
let n = 0
|
||
async function project(extra = {}) {
|
||
const id = `proj${++n}`
|
||
const repo = `JUSN/site-${n}`
|
||
const { createDocument } = await import('../server/services/appwriteAdmin.js')
|
||
const doc = await createDocument('websiteProjects', { repoFullName: repo, projectName: `S${n}`, ...extra }, id)
|
||
return { id, repo, doc }
|
||
}
|
||
const cfg = (repo) => ({ subdomain: repo.split('/')[1], type: 'static', branch: 'main' })
|
||
|
||
beforeEach(() => {
|
||
rs.calls = []; rs.runs = new Map(); rs.script = done(); rs.submitError = null
|
||
globalThis.__cfg.previewRunner.enabled = true
|
||
})
|
||
|
||
test('Schalter aus: kein Runner-Aufruf, alter Pfad', async () => {
|
||
const { repo } = await project()
|
||
globalThis.__cfg.previewRunner.enabled = false
|
||
globalThis.__cfg.preview.deployRoot = ''
|
||
const r = await pd.runDeploy(repo, 'main', cfg(repo))
|
||
globalThis.__cfg.preview.deployRoot = path.join(tmp, 'previews')
|
||
assert.equal(r.deployed, false)
|
||
assert.match(r.reason, /PREVIEW_DEPLOY_ROOT/)
|
||
assert.equal(rs.calls.length, 0)
|
||
})
|
||
|
||
test('an: Auftrag mit entschlüsselten Variablen, sofortige Rückgabe, Ergebnis online', async () => {
|
||
const { id, repo, doc } = await project({ projectName: 'Mein Projekt', previewEnv: envLib.encryptEnv({ API_URL: 'https://x.test' }, key) })
|
||
const r = await pd.runDeploy(repo, 'main', cfg(repo), { project: doc, sha: SHA })
|
||
assert.equal(r.queued, true)
|
||
assert.equal(r.deployed, false)
|
||
assert.equal(r.runId, 'run-' + rs.next)
|
||
assert.equal(aw.get('websiteProjects', id).previewState, 'building')
|
||
const [sub] = submits()
|
||
assert.equal(sub.auth, 'Bearer sekret')
|
||
assert.deepEqual(sub.body, { sub: repo.split('/')[1], repo, sha: SHA, branch: 'main', mode: 'auto', displayName: 'Mein Projekt', env: { API_URL: 'https://x.test' }, analytics: null })
|
||
await pd.waitForRunFollowers()
|
||
const d = aw.get('websiteProjects', id)
|
||
assert.equal(d.previewState, 'online')
|
||
assert.equal(d.previewDetected, 'static')
|
||
assert.equal(d.previewBuiltSha, SHA)
|
||
assert.ok(d.previewBuiltAt)
|
||
assert.equal(d.previewError, '')
|
||
assert.equal((await logs.listRunLogs(id)).length, 1)
|
||
})
|
||
|
||
test('displayName: previewConfig.displayName hat Vorrang vor projectName, sonst projectName', async () => {
|
||
const a = await project({ projectName: 'Projektname' })
|
||
await pd.runDeploy(a.repo, 'main', { ...cfg(a.repo), displayName: 'Doener Nation' }, { project: a.doc, sha: SHA })
|
||
await pd.waitForRunFollowers()
|
||
assert.equal(submits().at(-1).body.displayName, 'Doener Nation')
|
||
const b = await project({ projectName: 'Projektname' })
|
||
await pd.runDeploy(b.repo, 'main', cfg(b.repo), { project: b.doc, sha: SHA })
|
||
await pd.waitForRunFollowers()
|
||
assert.equal(submits().at(-1).body.displayName, 'Projektname')
|
||
})
|
||
|
||
test('Sha fehlt: Branch-Kopf per Gitea-API', async () => {
|
||
const { repo, doc } = await project()
|
||
await pd.runDeploy(repo, 'main', cfg(repo), { project: doc })
|
||
await pd.waitForRunFollowers()
|
||
assert.equal(submits()[0].body.sha, 'b'.repeat(40))
|
||
})
|
||
|
||
test('ohne project-Option: Dokument per Repo gefunden', async () => {
|
||
const { id, repo } = await project({ previewMode: 'build' })
|
||
await pd.runDeploy(repo, 'main', cfg(repo), { sha: SHA })
|
||
await pd.waitForRunFollowers()
|
||
assert.equal(submits()[0].body.mode, 'build')
|
||
assert.equal(aw.get('websiteProjects', id).previewState, 'online')
|
||
})
|
||
|
||
test('failed: Zustand failed, Fehler gespeichert, Folge-Stand nicht online', async () => {
|
||
rs.script = done({ state: 'failed', result: null, error: 'Build fehlgeschlagen', detected: { kind: 'build', pm: 'pnpm' }, log: ['boom'] })
|
||
const { id, repo, doc } = await project({ previewBuiltSha: 'c'.repeat(40), previewState: 'online' })
|
||
await pd.runDeploy(repo, 'main', cfg(repo), { project: doc, sha: SHA })
|
||
await pd.waitForRunFollowers()
|
||
const d = aw.get('websiteProjects', id)
|
||
assert.equal(d.previewState, 'failed')
|
||
assert.equal(d.previewError, 'Build fehlgeschlagen')
|
||
assert.equal(d.previewBuiltSha, 'c'.repeat(40))
|
||
assert.equal(await fs.stat(path.join(tmp, 'previews', repo.split('/')[1])).catch(() => null), null)
|
||
})
|
||
|
||
test('dev: sleeping + Registrierung per PUT /dev/<sub>', async () => {
|
||
rs.script = done({ result: 'dev', detected: { kind: 'dev', pm: 'pnpm' } })
|
||
const { id, repo, doc } = await project()
|
||
await pd.runDeploy(repo, 'main', cfg(repo), { project: doc, sha: SHA })
|
||
await pd.waitForRunFollowers()
|
||
const d = aw.get('websiteProjects', id)
|
||
assert.equal(d.previewState, 'sleeping')
|
||
assert.equal(d.previewBranch, 'main')
|
||
assert.equal(d.previewBuiltSha, SHA)
|
||
assert.equal(d.previewDetected, 'dev:pnpm')
|
||
const put = rs.calls.find((c) => c.method === 'PUT')
|
||
assert.equal(put.url, `/dev/${repo.split('/')[1]}`)
|
||
assert.equal(put.body.sha, SHA)
|
||
})
|
||
|
||
test('build: previewDetected build:<pm>', async () => {
|
||
rs.script = done({ result: 'build', detected: { kind: 'build', pm: 'yarn' } })
|
||
const { id, repo, doc } = await project()
|
||
await pd.runDeploy(repo, 'main', cfg(repo), { project: doc, sha: SHA })
|
||
await pd.waitForRunFollowers()
|
||
assert.equal(aw.get('websiteProjects', id).previewDetected, 'build:yarn')
|
||
})
|
||
|
||
test('Modus off: kein Auftrag, previewState off', async () => {
|
||
const { id, repo, doc } = await project({ previewMode: 'off' })
|
||
const r = await pd.runDeploy(repo, 'main', cfg(repo), { project: doc, sha: SHA })
|
||
assert.equal(r.skipped, true)
|
||
assert.equal(rs.calls.length, 0)
|
||
assert.equal(aw.get('websiteProjects', id).previewState, 'off')
|
||
})
|
||
|
||
test('Protokoll maskiert und nur 5 behalten', async () => {
|
||
const env = { TOKEN: 'supergeheim' }
|
||
const id = 'logproj'
|
||
const files = []
|
||
for (let i = 0; i < 7; i++) files.push(await logs.saveRunLog(id, [`Zeile ${i}`, 'nutzt supergeheim hier'], env))
|
||
const list = await logs.listRunLogs(id)
|
||
assert.equal(list.length, 5)
|
||
assert.equal(list[0].runFile, files[6].toString())
|
||
const text = await logs.readRunLog(id, list[0].runFile)
|
||
assert.ok(!text.includes('supergeheim'))
|
||
assert.ok(text.includes('•••'))
|
||
assert.equal(await logs.readRunLog(id, files[0]), null)
|
||
})
|
||
|
||
test('Protokoll: nur die letzten 300 Zeilen, keine Pfadtricks', async () => {
|
||
const lines = Array.from({ length: 400 }, (_, i) => `z${i}`)
|
||
const f = await logs.saveRunLog('logproj2', lines, {})
|
||
const text = (await logs.readRunLog('logproj2', f)).trim().split('\n')
|
||
assert.equal(text.length, 300)
|
||
assert.equal(text[0], 'z100')
|
||
assert.equal(await logs.readRunLog('logproj2', '../../x.log'), null)
|
||
assert.equal(await logs.readRunLog('logproj2', '../logproj/' + f), null)
|
||
assert.equal(await logs.readRunLog('..', f), null)
|
||
assert.deepEqual(await logs.listRunLogs('..'), [])
|
||
})
|
||
|
||
test('Variablen im Lauf-Protokoll maskiert (Ende-zu-Ende)', async () => {
|
||
rs.script = done({ log: ['Schluessel=supergeheim123'] })
|
||
const { id, repo, doc } = await project({ previewEnv: envLib.encryptEnv({ K: 'supergeheim123' }, key) })
|
||
await pd.runDeploy(repo, 'main', cfg(repo), { project: doc, sha: SHA })
|
||
await pd.waitForRunFollowers()
|
||
const [{ runFile }] = await logs.listRunLogs(id)
|
||
assert.equal((await logs.readRunLog(id, runFile)).trim(), 'Schluessel=•••')
|
||
})
|
||
|
||
test('neuerer Lauf verdrängt den älteren Verfolger', async () => {
|
||
// Lauf 1 bleibt lange "running", Lauf 2 ist sofort fertig
|
||
const { id, repo, doc } = await project()
|
||
rs.script = () => ({ state: 'running', detected: null, sha: SHA, log: [], error: null, result: null })
|
||
const r1 = await pd.runDeploy(repo, 'main', cfg(repo), { project: doc, sha: SHA })
|
||
rs.script = done({ log: ['zwei'] })
|
||
const sha2 = 'd'.repeat(40)
|
||
const r2 = await pd.runDeploy(repo, 'main', cfg(repo), { project: doc, sha: sha2 })
|
||
assert.notEqual(r1.runId, r2.runId)
|
||
// Lauf 1 meldet jetzt "failed" - darf nichts mehr schreiben
|
||
rs.runs.set(r1.runId, { state: 'failed', error: 'alt', log: ['eins'], detected: null, sha: SHA, result: null })
|
||
await pd.waitForRunFollowers()
|
||
const d = aw.get('websiteProjects', id)
|
||
assert.equal(d.previewState, 'online')
|
||
assert.equal(d.previewBuiltSha, sha2)
|
||
assert.equal((await logs.listRunLogs(id)).length, 1)
|
||
})
|
||
|
||
test('PREVIEW_ENV_KEY fehlt bei gespeicherten Variablen: Build ohne Variablen, Fehlertext', async () => {
|
||
const { id, repo, doc } = await project({ previewEnv: envLib.encryptEnv({ A: 'xxxxxx' }, key) })
|
||
const saved = process.env.PREVIEW_ENV_KEY
|
||
delete process.env.PREVIEW_ENV_KEY
|
||
try {
|
||
await pd.runDeploy(repo, 'main', cfg(repo), { project: doc, sha: SHA })
|
||
await pd.waitForRunFollowers()
|
||
} finally {
|
||
process.env.PREVIEW_ENV_KEY = saved
|
||
}
|
||
assert.deepEqual(submits()[0].body.env, {})
|
||
const d = aw.get('websiteProjects', id)
|
||
assert.equal(d.previewState, 'online')
|
||
assert.equal(d.previewError, 'Preview-Variablen nicht entschlüsselbar (PREVIEW_ENV_KEY fehlt)')
|
||
})
|
||
|
||
test('Runner ausgelastet (503): failed mit Meldung, kein Wurf', async () => {
|
||
rs.submitError = 503
|
||
const { id, repo, doc } = await project()
|
||
const r = await pd.runDeploy(repo, 'main', cfg(repo), { project: doc, sha: SHA })
|
||
assert.equal(r.deployed, false)
|
||
assert.equal(r.error, 'Server ausgelastet')
|
||
assert.equal(aw.get('websiteProjects', id).previewState, 'failed')
|
||
})
|
||
|
||
test('Modus off während ein Lauf läuft: Zustand bleibt off, kein TLS-Sync vom alten Verfolger', async () => {
|
||
const { id, repo, doc } = await project()
|
||
rs.script = () => ({ state: 'running', detected: null, sha: SHA, log: [], error: null, result: null })
|
||
const r1 = await pd.runDeploy(repo, 'main', cfg(repo), { project: doc, sha: SHA })
|
||
const { updateDocument } = await import('../server/services/appwriteAdmin.js')
|
||
const off = await updateDocument('websiteProjects', id, { previewMode: 'off' })
|
||
await pd.runDeploy(repo, 'main', cfg(repo), { project: off, sha: SHA })
|
||
rs.runs.set(r1.runId, { state: 'done', detected: { kind: 'static', pm: 'npm' }, sha: SHA, log: ['alt'], error: null, result: 'static' })
|
||
await pd.waitForRunFollowers()
|
||
assert.equal(aw.get('websiteProjects', id).previewState, 'off')
|
||
assert.equal((await logs.listRunLogs(id)).length, 0)
|
||
})
|
||
|
||
test('gleichzeitige Aufrufe: der neueste gewinnt', async () => {
|
||
const { id, repo, doc } = await project()
|
||
rs.script = (job) => ({ state: 'done', detected: { kind: 'static', pm: 'npm' }, sha: job.sha, log: [job.sha], error: null, result: 'static' })
|
||
const sha2 = 'e'.repeat(40)
|
||
const [, r2] = await Promise.all([
|
||
pd.runDeploy(repo, 'main', cfg(repo), { project: doc, sha: SHA }),
|
||
pd.runDeploy(repo, 'main', cfg(repo), { project: doc, sha: sha2 }),
|
||
])
|
||
await pd.waitForRunFollowers()
|
||
assert.ok(r2.queued)
|
||
assert.equal(aw.get('websiteProjects', id).previewBuiltSha, sha2)
|
||
})
|
||
|
||
test('fehlgeschlagener neuer Auftrag verdrängt den alten Verfolger', async () => {
|
||
const { id, repo, doc } = await project()
|
||
rs.script = () => ({ state: 'running', detected: null, sha: SHA, log: [], error: null, result: null })
|
||
const r1 = await pd.runDeploy(repo, 'main', cfg(repo), { project: doc, sha: SHA })
|
||
rs.submitError = 503
|
||
await pd.runDeploy(repo, 'main', cfg(repo), { project: doc, sha: SHA })
|
||
rs.runs.set(r1.runId, { state: 'done', detected: { kind: 'static' }, sha: SHA, log: [], error: null, result: 'static' })
|
||
await pd.waitForRunFollowers()
|
||
assert.equal(aw.get('websiteProjects', id).previewState, 'failed')
|
||
})
|
||
|
||
test('R15: folgt queued/running über die alte 16-Minuten-Grenze, Runner-Ausfall lange toleriert', async () => {
|
||
const { id, repo, doc } = await project()
|
||
let polls = 0
|
||
rs.script = () => ({ state: 'queued', detected: null, sha: SHA, log: [], error: null, result: null })
|
||
const r = await pd.runDeploy(repo, 'main', cfg(repo), { project: doc, sha: SHA })
|
||
const orig = rs.runs.get(r.runId)
|
||
Object.defineProperty(orig, 'state', { get() { polls++; return polls < 15 ? 'queued' : 'done' }, enumerable: true })
|
||
await pd.waitForRunFollowers()
|
||
assert.ok(polls >= 15)
|
||
assert.equal(aw.get('websiteProjects', id).previewState, 'online')
|
||
})
|
||
|
||
test('R15: Gesamtzeit überschritten => failed mit "Status unbekannt"', async () => {
|
||
const { id, repo, doc } = await project()
|
||
rs.script = () => ({ state: 'running', detected: null, sha: SHA, log: [], error: null, result: null })
|
||
const saved = pd.runnerTiming.maxMs
|
||
pd.runnerTiming.maxMs = 60
|
||
try {
|
||
await pd.runDeploy(repo, 'main', cfg(repo), { project: doc, sha: SHA })
|
||
await pd.waitForRunFollowers()
|
||
} finally { pd.runnerTiming.maxMs = saved }
|
||
const d = aw.get('websiteProjects', id)
|
||
assert.equal(d.previewState, 'failed')
|
||
assert.equal(d.previewError, 'Status unbekannt – bitte neu bauen')
|
||
})
|
||
|
||
test('R15: Runner dauerhaft unerreichbar (> unreachableMs) => failed', async () => {
|
||
const { id, repo, doc } = await project()
|
||
rs.script = () => ({ state: 'running', detected: null, sha: SHA, log: [], error: null, result: null })
|
||
const { config } = await import('../server/config.js')
|
||
const savedUrl = config.previewRunner.url
|
||
const saved = pd.runnerTiming.unreachableMs
|
||
pd.runnerTiming.unreachableMs = 50
|
||
try {
|
||
await pd.runDeploy(repo, 'main', cfg(repo), { project: doc, sha: SHA })
|
||
config.previewRunner.url = 'http://127.0.0.1:1'
|
||
await pd.waitForRunFollowers()
|
||
} finally { config.previewRunner.url = savedUrl; pd.runnerTiming.unreachableMs = saved }
|
||
const d = aw.get('websiteProjects', id)
|
||
assert.equal(d.previewState, 'failed')
|
||
assert.match(d.previewError, /nicht erreichbar/)
|
||
})
|
||
|
||
test('Runner-Fehlertext wird maskiert', async () => {
|
||
rs.script = done({ state: 'failed', result: null, error: 'Fehler mit supergeheim99', log: [] })
|
||
const { id, repo, doc } = await project({ previewEnv: envLib.encryptEnv({ K: 'supergeheim99' }, key) })
|
||
await pd.runDeploy(repo, 'main', cfg(repo), { project: doc, sha: SHA })
|
||
await pd.waitForRunFollowers()
|
||
assert.equal(aw.get('websiteProjects', id).previewError, 'Fehler mit •••')
|
||
})
|
||
|
||
test('dev-Ergebnis: PUT /dev/<sub> mit vollständigem Auftrag inkl. Variablen', async () => {
|
||
rs.script = done({ result: 'dev', detected: { kind: 'dev', pm: 'npm' } })
|
||
const { repo, doc } = await project({ previewEnv: envLib.encryptEnv({ A: 'wert1234' }, key), previewMode: 'dev' })
|
||
await pd.runDeploy(repo, 'main', cfg(repo), { project: doc, sha: SHA })
|
||
await pd.waitForRunFollowers()
|
||
const put = rs.calls.find((c) => c.method === 'PUT')
|
||
assert.deepEqual(put.body, { sub: repo.split('/')[1], repo, sha: SHA, branch: 'main', mode: 'dev', env: { A: 'wert1234' } })
|
||
})
|
||
|
||
// ---- Analyse-Feld im Build-Auftrag (Task 10) ----
|
||
async function analyseProject({ feature = {}, customer = {}, project: pextra = {}, noFeature = false, noCustomer = false } = {}) {
|
||
const { createDocument } = await import('../server/services/appwriteAdmin.js')
|
||
const cid = `cust-an-${n + 1}`
|
||
const p = await project({ customerId: cid, subdomain: `an-${n + 1}`, isPublic: true, ...pextra })
|
||
if (!noCustomer) {
|
||
await createDocument('customers', { name: 'Max Muster', companyName: 'Muster GmbH', email: 'info@muster.test', street: 'Hauptstr. 1', postalCode: '12345', city: 'Berlin', ...customer }, cid)
|
||
}
|
||
if (!noFeature) {
|
||
await createDocument('portalFeatures', { customerId: cid, projectId: p.id, featureKey: 'analyse', state: 'active', enabled: true, siteKey: 'abcdef0123456789abcd', accentColor: '#ff0066', ...feature }, `f_${p.id}_analyse`)
|
||
}
|
||
return p
|
||
}
|
||
async function submitFor(p) {
|
||
await pd.runDeploy(p.repo, 'main', cfg(p.repo), { project: p.doc, sha: SHA })
|
||
await pd.waitForRunFollowers()
|
||
return submits().at(-1).body
|
||
}
|
||
|
||
test('Analyse: aktiv + öffentlich ⇒ analytics mit siteKey, Farbe, Datenschutzangaben', async () => {
|
||
const body = await submitFor(await analyseProject())
|
||
assert.deepEqual(body.analytics, {
|
||
siteKey: 'abcdef0123456789abcd',
|
||
color: '#ff0066',
|
||
privacy: { company: 'Muster GmbH', address: 'Hauptstr. 1, 12345 Berlin', email: 'info@muster.test' },
|
||
})
|
||
})
|
||
|
||
test('Analyse: Standardfarbe, Firma fällt auf Name zurück, Adresse optional', async () => {
|
||
const p = await analyseProject({ feature: { accentColor: '' }, customer: { companyName: '', street: '', postalCode: '', city: '' } })
|
||
const body = await submitFor(p)
|
||
assert.equal(body.analytics.color, '#111111')
|
||
assert.equal(body.analytics.privacy.company, 'Max Muster')
|
||
assert.equal(body.analytics.privacy.address, '')
|
||
})
|
||
|
||
test('Analyse: aus ⇒ null', async () => {
|
||
assert.equal((await submitFor(await analyseProject({ feature: { state: 'off', enabled: false } }))).analytics, null)
|
||
assert.equal((await submitFor(await analyseProject({ noFeature: true }))).analytics, null)
|
||
})
|
||
|
||
test('Analyse: nicht öffentlich oder ohne Subdomain ⇒ null', async () => {
|
||
assert.equal((await submitFor(await analyseProject({ project: { isPublic: false } }))).analytics, null)
|
||
assert.equal((await submitFor(await analyseProject({ project: { subdomain: '' } }))).analytics, null)
|
||
})
|
||
|
||
test('Analyse: fehlende E-Mail oder Firma ⇒ null mit Warnung, Build läuft trotzdem', async (t) => {
|
||
const warn = t.mock.method(console, 'warn', () => {})
|
||
const a = await analyseProject({ customer: { email: '' } })
|
||
assert.equal((await submitFor(a)).analytics, null)
|
||
assert.ok(warn.mock.calls.some((c) => String(c.arguments[0]).includes(a.id)))
|
||
assert.equal((await submitFor(await analyseProject({ customer: { companyName: '', name: '' } }))).analytics, null)
|
||
assert.equal((await submitFor(await analyseProject({ noCustomer: true }))).analytics, null)
|
||
})
|
||
|
||
test('Analyse: Fehler bei der Funktions-Abfrage ⇒ null, Auftrag wird trotzdem gesendet', async (t) => {
|
||
t.mock.method(console, 'warn', () => {})
|
||
const p = await analyseProject()
|
||
aw.failNext({ method: 'GET', pathIncludes: 'portalFeatures', status: 500 })
|
||
const before = submits().length
|
||
const body = await submitFor(p)
|
||
assert.equal(submits().length, before + 1)
|
||
assert.equal(body.analytics, null)
|
||
})
|
||
|
||
test('Analyse: Dev-PUT nach erfolgreichem Build (Modus dev) enthält kein analytics', async () => {
|
||
rs.script = done({ result: 'dev' })
|
||
const p = await analyseProject({ project: { previewMode: 'dev' } })
|
||
const body = await submitFor(p)
|
||
assert.ok(body.analytics)
|
||
const puts = rs.calls.filter((c) => c.method === 'PUT' && c.url.startsWith('/dev/'))
|
||
assert.ok(puts.length >= 1)
|
||
for (const c of puts) assert.equal('analytics' in c.body, false)
|
||
})
|
||
|
||
test('Analyse: überlange Angaben, Steuerzeichen, schlechte Farbe ⇒ Grenzen eingehalten, Auftrag geht raus', async (t) => {
|
||
t.mock.method(console, 'warn', () => {})
|
||
const p = await analyseProject({
|
||
feature: { accentColor: 'rot' },
|
||
customer: { companyName: 'F\u0007irma ' + 'x'.repeat(250), street: 's'.repeat(400) },
|
||
})
|
||
const a = (await submitFor(p)).analytics
|
||
assert.equal(a.color, '#111111')
|
||
assert.ok(a.privacy.company.length <= 200 && !/[\x00-\x1f]/.test(a.privacy.company))
|
||
assert.ok(a.privacy.address.length <= 300)
|
||
const q = await analyseProject({ customer: { email: 'a'.repeat(250) + '@x.de' } })
|
||
assert.equal((await submitFor(q)).analytics, null)
|
||
const r = await analyseProject({ customer: { email: 'keine-mail' } })
|
||
assert.equal((await submitFor(r)).analytics, null)
|
||
})
|