Files
Webklar-Kundenbereich/test/customerProjectView.test.js
T
ClaudeandClaude Opus 5.5 ba9916cfe6 Projektdetails und Fortschritt kundenverständlich
Der Server bereitet Commits, Tickets und Arbeitseinträge für Kunden auf
(server/services/customerProjectView.js), die Rohdaten bleiben intern:
- keine Co-Authored-By-/Signed-off-by-Zeilen und KI-Hinweise, keine
  Commit-Kürzel, Kontonamen, Git-Links oder Branch
- technische Commits (Preview-Config, Merges) ausgeblendet, bekannte
  Automatik-Texte übersetzt, "fix"/"upload" -> "Website aktualisiert"
- Ticketstatus und Arbeitsarten auf Deutsch (Open -> Eingegangen,
  Remote -> Online erledigt, ...), Fortschritt in 4 Schritten
- Akquise-Tickets auch in den Projektdetails verborgen
- /api/projects ohne Gitea-URL, Repo- und Vorlagennamen

Darstellung: Auftragskarte mit Status und Fortschrittsleiste, Zeitleiste
"Letzte Änderungen" nach Tagen, Arbeiten mit Farbe/Symbol je Art und
Arbeitszeit; der Tab "Fortschritt" nutzt dieselben Bausteine. In der
App-Ansicht öffnen sich die Projektdetails als eigene Seite mit Zurück
zu "Projekte".

Tests: npm test (node --test, 22 Fälle).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 18:46:22 +00:00

312 lines
13 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 } from 'node:test'
import assert from 'node:assert/strict'
import {
customerChanges,
customerTicketStatus,
isInternalTicket,
customerOrder,
customerWorkEntries,
} from '../server/services/customerProjectView.js'
// Commit wie listRepoCommits() ihn liefert (giteaAdmin.js)
function commit(message, date = '2026-09-24T12:05:00Z') {
return {
sha: '88d3c37ab7b15337772cf59a5a9294ec0cc8cca4',
shortSha: '88d3c37',
message,
authorName: 'Claude',
date,
url: 'https://git.webklar.com/knso/ff-lack/commit/88d3c37ab7b15337772cf59a5a9294ec0cc8cca4',
}
}
const titles = (commits) => customerChanges(commits).map((c) => c.title)
/* --------------------------------------------------------- Änderungen --- */
test('Änderung: KI-Trailer fallen weg, Kürzel/Autor/Link werden nicht ausgeliefert', () => {
const changes = customerChanges([
commit('Öffnungszeiten für den Winter\n\nTeamfotos ausgetauscht\n\nCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>\n'),
])
assert.deepEqual(changes, [
{ date: '2026-09-24T12:05:00Z', title: 'Öffnungszeiten für den Winter', details: ['Teamfotos ausgetauscht'] },
])
})
test('Änderung: auch andere Modelle, Tools und Git-Trailer fallen weg', () => {
const [change] = customerChanges([
commit([
'Kontaktformular',
'',
'Co-authored-by: Copilot <copilot@github.com>',
'Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>',
'Signed-off-by: Kenso Grimm <kenso@webklar.com>',
'Change-Id: I8d2f0a1b',
'🤖 Generated with [Claude Code](https://claude.com/claude-code)',
'https://claude.ai/code/session_01AbCdEf',
].join('\n')),
])
assert.deepEqual(change.details, [])
})
test('Änderung: Präfixe wie feat(...)/fix: verschwinden, erster Buchstabe groß', () => {
assert.deepEqual(
titles([commit('feat(roadmaps): Ticket-Integration'), commit('fix: kaputten Link im Footer repariert', '2026-09-23T10:00:00Z')]),
['Ticket-Integration', 'Kaputten Link im Footer repariert']
)
})
test('Änderung: rein technische Commits werden ausgeblendet', () => {
assert.deepEqual(
titles([
commit('Update preview config from ticket system\n'),
commit('Preview-Config: unger-reisen.project.webklar.com\n'),
commit('Hosting: Preview aktivieren (automatisch)\n'),
commit('Migration: Preview auf Pipeline-Repo umgezogen (identica-kaiser)\n'),
commit("Merge branch 'main' of https://git.webklar.com/knso/lackwerk-kl\n"),
commit("Merge pull request 'Neue Startseite' (#3) from dev into main\n"),
]),
[]
)
})
test('Änderung: bekannte Automatik-Texte werden übersetzt', () => {
const day = (d) => `2026-09-${d}T08:00:00Z`
assert.deepEqual(
titles([
commit('Initial commit\n', day(10)),
commit('Automatisch erstellte Preview-Seite\n', day(11)),
commit('Anreicherung: Bilder, Logo, Text-Feinarbeit (Cloud-Routine)\n', day(12)),
commit('Google Fonts self-hosten (DSGVO): assets/fonts.css statt fonts.googleapis.com\n', day(13)),
commit('Website ff-lack – aus tamplate-lack, auf Fahrzeuglackierer Fruck – Altenglan angepasst\n', day(14)),
]),
[
'Projekt angelegt',
'Erste Vorschau der Website erstellt',
'Inhalte ergänzt: Bilder, Logo, Text-Feinarbeit',
'Schriftarten datenschutzkonform eingebunden (DSGVO)',
'Website erstellt und an den Betrieb angepasst',
]
)
})
test('Änderung: nichtssagende Titel werden zu "Website aktualisiert", echte Wörter bleiben', () => {
const day = (d) => `2026-09-${d}T08:00:00Z`
assert.deepEqual(
titles([
commit('fix\n', day(10)),
commit('upload\n', day(11)),
commit('pagev1\n', day(12)),
commit('webseite-version-01\n', day(13)),
commit('Teamseite\n', day(14)),
]),
['Website aktualisiert', 'Website aktualisiert', 'Website aktualisiert', 'Website aktualisiert', 'Teamseite']
)
})
test('Änderung: Revert wird verständlich, die "This reverts commit"-Zeile fällt weg', () => {
const [change] = customerChanges([
commit('Revert "Anreicherung: Bilder, Logo, Farbwelt, Texte (Cloud-Routine)"\n\nThis reverts commit 4ff6e7a0348c5553769fbff2ac4bc1e44817ff28.\n'),
])
assert.deepEqual(change, {
date: '2026-09-24T12:05:00Z',
title: '„Inhalte ergänzt: Bilder, Logo, Farbwelt, Texte“ zurückgenommen',
details: [],
})
})
test('Änderung: umbrochene Aufzählungen und Absätze werden wieder zusammengefügt', () => {
const [bullets] = customerChanges([
commit([
'Website stärker personalisiert',
'',
'- Echte Betriebsfotos statt Stock: Luftbild als Hero, Galerie',
' mit rotem Lackier-Fahrzeug, Farbtonanalyse-',
' Mischsystem',
'- Marken-Rot statt Blau',
'',
'Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>',
].join('\n')),
])
assert.deepEqual(bullets.details, [
'Echte Betriebsfotos statt Stock: Luftbild als Hero, Galerie mit rotem Lackier-Fahrzeug, Farbtonanalyse-Mischsystem',
'Marken-Rot statt Blau',
])
const [paragraphs] = customerChanges([
commit('Planboard\n\nDas Planboard war eine Hülle - sieben Spalten\nmit "Keine Termine".\n\nZweiter Absatz.\n'),
])
assert.deepEqual(paragraphs.details, ['Das Planboard war eine Hülle - sieben Spalten mit "Keine Termine".', 'Zweiter Absatz.'])
})
test('Änderung: Backticks aus Markdown verschwinden', () => {
assert.deepEqual(titles([commit('Kontakt: `info@autohaus-meier.de` ergänzt')]), ['Kontakt: info@autohaus-meier.de ergänzt'])
})
test('Änderung: gleiche Titel am selben Tag stehen nur einmal da, an verschiedenen Tagen zweimal', () => {
const changes = customerChanges([
commit('fix\n', '2026-09-24T15:00:00Z'),
commit('upload\n', '2026-09-24T09:00:00Z'),
commit('fix\n', '2026-09-20T09:00:00Z'),
])
assert.deepEqual(changes.map((c) => c.date), ['2026-09-24T15:00:00Z', '2026-09-20T09:00:00Z'])
})
/* ------------------------------------------------------ Ticketstatus --- */
test('Status: kein Status aus dem Ticketsystem kommt unübersetzt beim Kunden an', () => {
const woms = ['Open', 'Occupied', 'Assigned', 'Awaiting', 'Added Info', 'In Test', 'Halted', 'Aborted', 'Cancelled', 'Closed']
for (const status of woms) {
const { label, step } = customerTicketStatus(status)
assert.notEqual(label, status, status)
assert.ok(Number.isInteger(step) && step >= 0 && step <= 4, `${status}: step ${step}`)
}
})
test('Status: Fortschritt und Ton passen zur Bedeutung', () => {
assert.deepEqual(customerTicketStatus('Open'), { label: 'Eingegangen', tone: 'new', step: 1 })
assert.deepEqual(customerTicketStatus('Occupied'), { label: 'In Arbeit', tone: 'active', step: 2 })
assert.deepEqual(customerTicketStatus('Awaiting'), { label: 'Wartet auf deine Rückmeldung', tone: 'waiting', step: 2 })
assert.deepEqual(customerTicketStatus('In Test'), { label: 'Wird geprüft', tone: 'review', step: 3 })
assert.deepEqual(customerTicketStatus('Closed'), { label: 'Erledigt', tone: 'done', step: 4 })
assert.deepEqual(customerTicketStatus('Cancelled'), { label: 'Storniert', tone: 'stopped', step: 0 })
})
test('Status: leer heißt "Eingegangen", Unbekanntes wird nie roh durchgereicht', () => {
assert.equal(customerTicketStatus('').label, 'Eingegangen')
assert.equal(customerTicketStatus(undefined).label, 'Eingegangen')
assert.deepEqual(customerTicketStatus('Zugesagt'), { label: 'In Bearbeitung', tone: 'active', step: 2 })
})
/* ------------------------------------------------- Auftrag / Akquise --- */
test('Auftrag: Akquise-Tickets sind intern, auch wenn nur der Betreff so beginnt', () => {
assert.equal(isInternalTicket({ type: 'Akquise', topic: 'Akquise: Mover GmbH' }), true)
assert.equal(isInternalTicket({ type: 'akquise', topic: 'Lead' }), true)
assert.equal(isInternalTicket({ type: 'Webpage', topic: 'Akquise: Mover GmbH' }), true)
assert.equal(isInternalTicket({ type: 'Webpage', topic: 'Relaunch der Website' }), false)
})
test('Auftrag: Nummer, Betreff und übersetzter Status; intern -> null', () => {
assert.deepEqual(customerOrder({ woid: '100004', topic: 'Demo Webseite', status: 'Closed', type: 'Webpage' }), {
number: '100004',
topic: 'Demo Webseite',
status: { label: 'Erledigt', tone: 'done', step: 4 },
})
assert.equal(customerOrder({ woid: '100015', topic: 'Akquise: Mover GmbH', status: 'Open', type: 'Akquise' }), null)
assert.equal(customerOrder(null), null)
})
/* --------------------------------------------------- Arbeitseinträge --- */
// Worksheet wie gitWorksheet.js ihn beim Push anlegt
const gitSheet = {
serviceType: 'GIT',
employeeName: 'knso',
employeeShort: 'GIT',
totalTime: 0,
startTime: '',
startDate: '24.09.2026',
endDate: '24.09.2026',
endTime: '1405',
isComment: true,
details: [
'Git Push auf `knso/ff-lack` (Branch: main)',
'Commit: 88d3c37',
'',
'- [88d3c37](https://git.webklar.com/knso/ff-lack/commit/88d3c37ab7b15337772cf59a5a9294ec0cc8cca4): Hintergrundbilder leicht rot einfaerben (knso)',
' Dezenter roter Tint auf den Hintergrundbildern',
' Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>',
'- [1234567](https://git.webklar.com/knso/ff-lack/commit/1234567890abcdef1234567890abcdef12345678): Update preview config from ticket system (knso)',
'- [2ca14eb](https://git.webklar.com/knso/ff-lack/commit/2ca14ebd93d53c45dee69e1beb8e668c4ce2a016): Anreicherung: Bilder, Logo, Text-Feinarbeit (Cloud-Routine) (webklar-automation)',
].join('\n'),
}
test('Arbeit: Git-Push wird zu "Website aktualisiert" mit bereinigten Änderungstiteln', () => {
assert.deepEqual(customerWorkEntries([gitSheet], { includeUpdates: true }), [
{
kind: 'update',
label: 'Website aktualisiert',
date: '2026-09-24',
minutes: 0,
changes: ['Hintergrundbilder leicht rot einfaerben', 'Inhalte ergänzt: Bilder, Logo, Text-Feinarbeit'],
},
])
})
test('Arbeit: mehrere Pushes am selben Tag werden ein Eintrag, andere Tage bleiben getrennt', () => {
const push = (day, sha, title, totalTime = 0) => ({
...gitSheet,
endDate: day,
totalTime,
details: `Git Push auf \`knso/ff-lack\` (Branch: main)\nCommit: ${sha}\n\n- [${sha}](https://git.webklar.com/knso/ff-lack/commit/${sha}): ${title} (knso)`,
})
const entries = customerWorkEntries(
[
push('24.09.2026', 'aaaaaaa', 'Handy-Ansicht überarbeitet', 15),
push('24.09.2026', 'bbbbbbb', 'Trust-Icons repariert', 30),
push('23.09.2026', 'ccccccc', 'Anfahrt ergänzt'),
],
{ includeUpdates: true }
)
assert.deepEqual(entries, [
{ kind: 'update', label: 'Website aktualisiert', date: '2026-09-24', minutes: 45, changes: ['Handy-Ansicht überarbeitet', 'Trust-Icons repariert'] },
{ kind: 'update', label: 'Website aktualisiert', date: '2026-09-23', minutes: 0, changes: ['Anfahrt ergänzt'] },
])
})
test('Arbeit: Push mit nur technischen Commits verschwindet ganz', () => {
const onlyConfig = {
...gitSheet,
details: 'Git Push auf `knso/ff-lack` (Branch: main)\nCommit: 1234567\n\n- [1234567](https://git.webklar.com/x/y/commit/1234567): Update preview config from ticket system (knso)',
}
const noDetails = { ...gitSheet, details: 'Git Push auf `knso/ff-lack` (Branch: main)\nCommit: ?\n\n- (keine Commit-Details im Webhook)' }
assert.deepEqual(customerWorkEntries([onlyConfig, noDetails], { includeUpdates: true }), [])
})
test('Arbeit: ohne includeUpdates bleiben nur Git-Einträge mit nachgetragener Zeit', () => {
const withTime = { ...gitSheet, totalTime: 45, startTime: '1320' }
const entries = customerWorkEntries([gitSheet, withTime], { includeUpdates: false })
assert.deepEqual(entries.map((e) => [e.kind, e.minutes]), [['update', 45]])
})
test('Arbeit: manuelle Einträge bekommen deutsche Art, Person, Zeit und Text', () => {
const sheet = (serviceType, extra = {}) => ({
serviceType,
employeeName: 'Kenso Grimm',
totalTime: 150,
startDate: '20.09.2026',
endDate: '20.09.2026',
details: 'Website neu erstellt und als Vorschau bereitgestellt.',
isComment: false,
...extra,
})
assert.deepEqual(customerWorkEntries([sheet('Remote')]), [
{
kind: 'online',
label: 'Online erledigt',
date: '2026-09-20',
minutes: 150,
person: 'Kenso Grimm',
text: 'Website neu erstellt und als Vorschau bereitgestellt.',
},
])
assert.deepEqual(
customerWorkEntries([sheet('On Site'), sheet('Off Site'), sheet('Sonderwunsch')]).map((e) => [e.kind, e.label]),
[['onsite', 'Vor Ort bei dir'], ['office', 'In unserem Büro'], ['work', 'Arbeit am Projekt']]
)
})
test('Arbeit: interne Kommentare tauchen nicht auf', () => {
const comment = { serviceType: 'Remote', employeeName: 'Kenso Grimm', totalTime: 0, endDate: '20.09.2026', details: 'Roadmap-Knoten "Erstellen": Offen -> In Arbeit', isComment: true }
const commentType = { serviceType: 'COMMENT', employeeName: 'Kenso Grimm', totalTime: 0, endDate: '20.09.2026', details: 'intern', isComment: false }
assert.deepEqual(customerWorkEntries([comment, commentType], { includeUpdates: true }), [])
})
test('Arbeit: Einträge aus getCustomerActivity (date statt endDate, Zeit als Text) werden verstanden', () => {
const fromActivity = { serviceType: 'Remote', employeeName: 'Andrej Stevanovski', totalTime: '30', date: '18.09.2026', details: 'Demo-Version erstellt', isGit: false, pending: false }
assert.deepEqual(customerWorkEntries([fromActivity]), [
{ kind: 'online', label: 'Online erledigt', date: '2026-09-18', minutes: 30, person: 'Andrej Stevanovski', text: 'Demo-Version erstellt' },
])
})