Files
Webklar-Kundenbereich/test/projectKindAssign.test.js
T

240 lines
9.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test, before, after } from 'node:test'
import assert from 'node:assert/strict'
import express from 'express'
import { startFakeAppwrite } from './helpers/fakeAppwrite.js'
let aw
let server
let baseUrl
let completeAssignment
const now = new Date('2026-09-30T10:00:00Z')
const newProj = (id, repo, extra = {}) => ({ $id: id, repoFullName: `JUSN/${repo}`, projectName: repo, customerId: '', ticketId: '', ...extra })
function stubs({ failAssign = false } = {}) {
const calls = { assign: [], update: [] }
return {
calls,
deps: {
assignProject: async (id, data) => {
calls.assign.push([id, data])
if (failAssign) throw new Error('Gitea kaputt')
return { ok: true }
},
updateWebsiteProject: async (id, data) => {
calls.update.push([id, data])
return {}
},
},
}
}
before(async () => {
aw = await startFakeAppwrite({
seed: {
websiteProjects: [
newProj('p1', 'intern'),
newProj('p2', 'vorhanden'),
newProj('p3', 'foo'),
newProj('p4', 'umzug'),
newProj('p5', 'fremd'),
newProj('p6', 'zu'),
newProj('p7', 'art'),
newProj('p8', 'fehler'),
newProj('p9', 'doppelt'),
newProj('p10', 'prev'),
newProj('p11', 'route'),
newProj('p13', 'warn'),
newProj('p14', 'race'),
newProj('p15', 'kunde'),
newProj('p16', 'eins'),
newProj('p12', 'schon', { kind: 'app', kindSetBy: 'Max', kindSetAt: '2026-09-29T08:00:00.000Z' }),
],
workorders: [
{ $id: 't1', woid: '100115', customerId: 'c1', status: 'Open' },
{ $id: 't2', woid: '100116', customerId: 'c2', status: 'Open' },
{ $id: 't3', woid: '100117', customerId: 'c1', status: 'Closed' },
],
customers: [{ $id: 'c1', name: 'Döner Nation', location: 'Berlin' }, { $id: 'c2', name: 'Andere' }],
},
})
Object.assign(process.env, { APPWRITE_ENDPOINT: aw.endpoint, APPWRITE_API_KEY: 'test-key' })
;({ completeAssignment } = await import('../server/services/projectKindAssign.js'))
const { default: routes } = await import('../server/routes/admin/websiteProjects.js')
const app = express()
app.use(express.json())
app.use('/api/admin/website-projects', routes)
server = await new Promise((resolve) => {
const s = app.listen(0, '127.0.0.1', () => resolve(s))
})
baseUrl = `http://127.0.0.1:${server.address().port}`
})
after(async () => {
await new Promise((resolve) => server.close(resolve))
await aw.close()
})
const opts = (deps) => ({ staffName: 'Kenso', now, deps })
test('intern speichert nur Art', async () => {
const s = stubs()
const r = await completeAssignment('p1', { kind: 'other', forCustomer: false }, opts(s.deps))
assert.deepEqual(r, { ticketId: null })
const p = aw.get('websiteProjects', 'p1')
assert.equal(p.kind, 'other')
assert.equal(p.forCustomer, false)
assert.equal(p.kindSetBy, 'Kenso')
assert.equal(p.kindSetAt, now.toISOString())
assert.equal(s.calls.assign.length, 0)
})
test('vorhandenes Ticket wird zugewiesen', async () => {
const s = stubs()
const before = aw.all('workorders').length
const r = await completeAssignment('p2', { kind: 'webapp', forCustomer: true, customerId: 'c1', ticketId: 't1' }, opts(s.deps))
assert.deepEqual(r, { ticketId: 't1' })
assert.deepEqual(s.calls.assign, [['p2', { customerId: 'c1', ticketId: 't1' }]])
assert.equal(aw.all('workorders').length, before)
assert.equal(aw.get('websiteProjects', 'p2').kind, 'webapp')
assert.equal(aw.get('websiteProjects', 'p2').forCustomer, true)
})
test('neues Ticket nur beim Abschliessen, Thema "Website – foo"', async () => {
const s = stubs()
const before = aw.all('workorders').length
const r = await completeAssignment(
'p3',
{ kind: 'website', forCustomer: true, customerId: 'c1', newTicket: { topic: '', requestedBy: 'Max', startDate: '2026-10-05', details: 'd' } },
opts(s.deps)
)
assert.equal(aw.all('workorders').length, before + 1)
const t = aw.get('workorders', r.ticketId)
assert.equal(t.topic, 'Website – foo')
assert.equal(t.type, 'Webpage')
assert.equal(t.status, 'Open')
assert.equal(t.startDate, '05.10.2026')
assert.deepEqual(s.calls.assign, [['p3', { customerId: 'c1', ticketId: r.ticketId }]])
})
test('Migration ergibt Ticket-Art Migration', async () => {
const s = stubs()
const r = await completeAssignment('p4', { kind: 'migration', forCustomer: true, customerId: 'c1', newTicket: { topic: 'Umzug X' } }, opts(s.deps))
const t = aw.get('workorders', r.ticketId)
assert.equal(t.type, 'Migration')
assert.equal(t.topic, 'Umzug X')
})
test('Ticket anderer Kunde ⇒ 400', async () => {
const s = stubs()
await assert.rejects(
() => completeAssignment('p5', { kind: 'website', forCustomer: true, customerId: 'c1', ticketId: 't2' }, opts(s.deps)),
(e) => e.status === 400
)
assert.equal(aw.get('websiteProjects', 'p5').kind, undefined)
})
test('Ticket Closed ⇒ 400', async () => {
const s = stubs()
await assert.rejects(
() => completeAssignment('p6', { kind: 'website', forCustomer: true, customerId: 'c1', ticketId: 't3' }, opts(s.deps)),
(e) => e.status === 400
)
})
test('unbekannte Art ⇒ 400', async () => {
const s = stubs()
await assert.rejects(
() => completeAssignment('p7', { kind: 'nix', forCustomer: false }, opts(s.deps)),
(e) => e.status === 400
)
assert.equal(aw.get('websiteProjects', 'p7').kind, undefined)
})
test('assignProject wirft ⇒ neues Ticket geloescht, kind leer', async () => {
const s = stubs({ failAssign: true })
const before = aw.all('workorders').length
await assert.rejects(
() => completeAssignment('p8', { kind: 'website', forCustomer: true, customerId: 'c1', newTicket: { topic: 'x' } }, opts(s.deps)),
/Gitea kaputt/
)
assert.equal(aw.all('workorders').length, before)
assert.equal(aw.get('websiteProjects', 'p8').kind, undefined)
})
test('zweiter Aufruf ⇒ 409, nur ein Ticket', async () => {
const s = stubs()
const before = aw.all('workorders').length
const body = { kind: 'app', forCustomer: true, customerId: 'c1', newTicket: { topic: 'eins' } }
await completeAssignment('p9', body, opts(s.deps))
await assert.rejects(
() => completeAssignment('p9', body, opts(s.deps)),
(e) => e.status === 409 && e.kindSetBy === 'Kenso'
)
assert.equal(aw.all('workorders').length, before + 1)
})
test('Website mit Subdomain ⇒ updateWebsiteProject(preview)', async () => {
const s = stubs()
await completeAssignment('p10', { kind: 'website', forCustomer: true, customerId: 'c1', ticketId: 't1', preview: { subdomain: 'prev' } }, opts(s.deps))
assert.deepEqual(s.calls.update, [['p10', { subdomain: 'prev', projectType: 'preview' }]])
})
test('Warnungen von assignProject und Preview-Fehler werden zusammengefuehrt', async () => {
const s = stubs()
s.deps.assignProject = async () => ({ ok: true, warnings: ['Umzug fehlgeschlagen'] })
s.deps.updateWebsiteProject = async () => { throw new Error('Gitea aus') }
const r = await completeAssignment('p13', { kind: 'website', forCustomer: true, customerId: 'c1', ticketId: 't1', preview: { subdomain: 'w' } }, opts(s.deps))
assert.equal(r.ticketId, 't1')
assert.equal(r.warnings.length, 2)
assert.equal(r.warnings[0], 'Umzug fehlgeschlagen')
assert.match(r.warnings[1], /Preview/)
assert.equal(aw.get('websiteProjects', 'p13').kind, 'website')
})
test('gleichzeitiger Doppelaufruf ⇒ genau ein Ticket und ein 409', async () => {
const s = stubs()
const before = aw.all('workorders').length
const body = { kind: 'app', forCustomer: true, customerId: 'c1', newTicket: { topic: 'race' } }
const res = await Promise.allSettled([completeAssignment('p14', body, opts(s.deps)), completeAssignment('p14', body, opts(s.deps))])
assert.equal(res.filter((x) => x.status === 'fulfilled').length, 1)
const rej = res.find((x) => x.status === 'rejected')
assert.equal(rej.reason.status, 409)
assert.equal(aw.all('workorders').length, before + 1)
})
test('forCustomer mit unbekanntem Kunden ⇒ 400', async () => {
const s = stubs()
await assert.rejects(
() => completeAssignment('p15', { kind: 'website', forCustomer: true, customerId: 'nix', newTicket: { topic: 'x' } }, opts(s.deps)),
(e) => e.status === 400
)
assert.equal(aw.get('websiteProjects', 'p15').kind, undefined)
})
test('ticketId und newTicket beide oder keins ⇒ 400', async () => {
const s = stubs()
const base = { kind: 'website', forCustomer: true, customerId: 'c1' }
for (const extra of [{ ticketId: 't1', newTicket: { topic: 'x' } }, {}]) {
await assert.rejects(() => completeAssignment('p16', { ...base, ...extra }, opts(s.deps)), (e) => e.status === 400)
}
assert.equal(aw.get('websiteProjects', 'p16').kind, undefined)
})
test('Route: ohne Staff-JWT ⇒ 401, mit JWT intern 200, bereits zugeordnet 409, unbekannt 404', async () => {
const post = (id, body, jwt) =>
fetch(`${baseUrl}/api/admin/website-projects/${id}/zuordnen`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...(jwt ? { Authorization: `Bearer ${jwt}` } : {}) },
body: JSON.stringify(body),
}).then(async (r) => ({ status: r.status, body: await r.json() }))
assert.equal((await post('p11', { kind: 'other', forCustomer: false })).status, 401)
const ok = await post('p11', { kind: 'other', forCustomer: false }, 'jwt-admin')
assert.deepEqual([ok.status, ok.body], [200, { ticketId: null }])
assert.ok(aw.get('websiteProjects', 'p11').kindSetAt) // Fake-Nutzer hat weder name noch email
const again = await post('p12', { kind: 'other', forCustomer: false }, 'jwt-admin')
assert.deepEqual([again.status, again.body], [409, { error: 'bereits zugeordnet', kindSetBy: 'Max', kindSetAt: '2026-09-29T08:00:00.000Z' }])
assert.equal((await post('nix', { kind: 'other', forCustomer: false }, 'jwt-admin')).status, 404)
})