Files
Webklar-Kundenbereich/test/projectAssignment.test.js
T
2026-09-30 10:21:34 +00:00

158 lines
6.3 KiB
JavaScript

import { test, before, after } from 'node:test'
import assert from 'node:assert/strict'
import fs from 'node:fs/promises'
import os from 'node:os'
import path from 'node:path'
import { startFakeGitea } from './helpers/fakeGitea.js'
import { startFakeAppwrite } from './helpers/fakeAppwrite.js'
let gitea
let aw
let tmp
let originalCwd
let assignProject
let reconcileProject
const now = new Date('2026-09-27T16:00:00Z')
let nextFeedId = 1
/**
* Baut einen Gitea-1.25-Feed-Eintrag vom Typ `commit_repo` nach (ein Commit), siehe
* `test/pushHistory.test.js` fuer denselben Aufbau: `content` traegt das JSON
* `{Commits:[...], HeadCommit:{...}}` als String, wie im echten Aktivitaeten-Feed.
*/
function pushEntry({ sha, at, user, msg }) {
const login = user || 'JUSN'
const commit = {
Sha1: sha,
Message: msg || `Commit ${sha}`,
AuthorEmail: `${login.toLowerCase()}@example.com`,
AuthorName: login,
CommitterEmail: `${login.toLowerCase()}@example.com`,
CommitterName: login,
Timestamp: at,
}
return {
id: nextFeedId++,
op_type: 'commit_repo',
act_user: { login, full_name: '' },
ref_name: 'refs/heads/main',
created: at,
content: JSON.stringify({ Commits: [commit], HeadCommit: { Sha1: sha }, CompareURL: '', Len: 1 }),
}
}
before(async () => {
gitea = await startFakeGitea({
repos: [
{ full_name: 'JUSN/D-ner-Nation' },
{ full_name: 'JUSN/leer' },
{ full_name: 'JUSN/nur-kunde' },
{ full_name: 'JUSN/doppelt' },
],
feeds: {
// Newest first, wie der echte Gitea-Feed.
'JUSN/D-ner-Nation': [
pushEntry({ sha: 's1', user: 'webklar-system', at: '2026-09-27T14:50:00Z', msg: 'System-Push' }),
pushEntry({ sha: 'k1', user: 'knso', at: '2026-09-27T14:48:00Z', msg: 'Update preview config from ticket system' }),
pushEntry({ sha: '26bc22ecf17e2e5a5a8794dcb4cf3892f802c51d', user: 'JUSN', at: '2026-09-27T14:46:34Z', msg: 'Bilder aktualisiert' }),
{ ...pushEntry({ sha: 'x1', at: '2026-09-27T14:46:33Z' }), content: '' },
{ id: nextFeedId++, op_type: 'create_repo', created: '2026-09-27T14:02:46Z', act_user: { login: 'JUSN' } },
],
'JUSN/nur-kunde': [pushEntry({ sha: 'n1', user: 'JUSN', at: '2026-09-27T12:00:00Z', msg: 'Kundenseite live' })],
'JUSN/doppelt': [pushEntry({ sha: 'd1', user: 'JUSN', at: '2026-09-27T11:00:00Z', msg: 'Doppelt-Push' })],
'JUSN/leer': [],
},
})
aw = await startFakeAppwrite({
seed: {
websiteProjects: [
{ $id: 'p1', repoFullName: 'JUSN/D-ner-Nation', projectName: 'D-ner-Nation', customerId: '', ticketId: '' },
{ $id: 'p2', repoFullName: 'JUSN/leer', projectName: 'leer', customerId: '', ticketId: '' },
{ $id: 'p3', repoFullName: 'JUSN/nur-kunde', projectName: 'nur-kunde', customerId: '', ticketId: '' },
{ $id: 'p4', repoFullName: 'JUSN/doppelt', projectName: 'doppelt', customerId: '', ticketId: '' },
],
workorders: [{ $id: 't1', woid: '100115' }],
customers: [{ $id: 'c1', name: 'Döner Nation' }],
},
})
originalCwd = process.cwd()
tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'project-assignment-'))
process.chdir(tmp)
Object.assign(process.env, {
GITEA_BASE_URL: gitea.baseUrl,
GITEA_API_TOKEN: 'test-token',
APPWRITE_ENDPOINT: aw.endpoint,
APPWRITE_API_KEY: 'test-key',
PREVIEW_DEPLOY_ROOT: '',
TRAEFIK_DYNAMIC_DIR: '',
})
// config.js liest die Umgebung beim Import
;({ assignProject, reconcileProject } = await import('../server/services/projectAssignment.js'))
})
after(async () => {
process.chdir(originalCwd)
await gitea.close()
await aw.close()
await fs.rm(tmp, { recursive: true, force: true })
})
test('assign speichert, verschiebt und trägt nach', async () => {
const r = await assignProject('p1', { customerId: 'c1', ticketId: 't1' }, { now })
assert.equal(r.ok, true)
assert.deepEqual(r.moved, { from: 'JUSN/D-ner-Nation', to: 'WEBklar/D-ner-Nation' })
assert.equal(r.backfilled, 1); assert.deepEqual(r.warnings, [])
const d = aw.get('websiteProjects', 'p1')
assert.deepEqual([d.customerId, d.ticketId], ['c1', 't1'])
const ws = aw.all('worksheets').filter((w) => w.workorderId === 't1')
assert.equal(ws.length, 1)
assert.deepEqual([ws[0].employeeName, ws[0].endDate, ws[0].endTime], ['JUSN', '27.09.2026', '1646'])
})
test('zweites Nachtragen erzeugt nichts Neues', async () => {
const r = await reconcileProject('p1', { now })
assert.deepEqual([r.moved, r.backfilled], [null, 0])
})
test('nur Kunde ohne Pushes: kein Ticket', async () => {
const before = aw.all('workorders').length
await assignProject('p2', { customerId: 'c1' }, { now })
assert.equal(aw.all('workorders').length, before)
})
test('nur Kunde mit Pushes: Webpage-Ticket entsteht', async () => {
const r = await assignProject('p3', { customerId: 'c1' }, { now })
const t = aw.all('workorders').find((w) => w.$id === aw.get('websiteProjects', 'p3').ticketId)
assert.equal(t.type, 'Webpage'); assert.equal(r.backfilled, 1)
})
test('Doppelklick: ein Umzug, keine doppelten Arbeitsblätter', async () => {
await Promise.all([assignProject('p4', { customerId: 'c1', ticketId: 't1' }, { now }), assignProject('p4', { customerId: 'c1', ticketId: 't1' }, { now })])
assert.equal(gitea.calls.filter((c) => c.path === '/api/v1/repos/JUSN/doppelt/transfer').length, 1)
assert.equal(aw.all('worksheets').filter((w) => w.details.includes('WEBklar/doppelt') || w.details.includes('JUSN/doppelt')).length, 1)
})
test('Gitea-Ausfall: Zuweisung bleibt', async () => {
// Diese Zuweisung loest bewusst einen Gitea-Ausfall aus (`deletePortalHooks` in `repoMove.js`
// loggt einen fehlgeschlagenen Hook-Abruf mit console.error) - Konsole hier abfangen, damit
// die Testausgabe sauber bleibt.
const originalError = console.error
const originalWarn = console.warn
console.error = () => {}
console.warn = () => {}
gitea.setDown(true)
let r
try {
r = await assignProject('p2', { customerId: 'c1', ticketId: 't1' }, { now })
} finally {
gitea.setDown(false)
console.error = originalError
console.warn = originalWarn
}
assert.equal(r.ok, true); assert.equal(aw.get('websiteProjects', 'p2').ticketId, 't1')
assert.match(r.warnings[0], /^Gitea ist gerade nicht erreichbar/)
})
test('unbekanntes Projekt', async () => {
await assert.rejects(assignProject('nope', { ticketId: 't1' }, { now }), (e) => e.status === 404)
})