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

94 lines
6.3 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 {
stundenText, leistungszeitraumText, kalenderwoche, gueltigerTag, kuerzen,
festePositionen, zusatzPositionen, pruefePositionen,
} from '../server/services/auftraege/positionen.js'
/** Wirft die Funktion einen AuftragError mit diesem Status und dieser Meldung? */
const wirft = (fn, status, message) =>
assert.throws(fn, (err) => {
assert.equal(err.status, status, `Status ${err.status}: ${err.message}`)
assert.equal(err.message, message)
return true
})
const ANZAHL = 'Eine Rechnung braucht 1 bis 20 Positionen.'
const ok = { description: 'Domain', quantity: 1, unitAmountCents: 1200 }
test('stundenText: Minuten als Stunden mit Komma, ohne überflüssige Nullen', () => {
const faelle = [[0, '0'], [15, '0,25'], [30, '0,5'], [45, '0,75'], [60, '1'], [90, '1,5'], [135, '2,25'], [390, '6,5'], [600, '10']]
for (const [minuten, text] of faelle) assert.equal(stundenText(minuten), text, `${minuten} Minuten`)
})
test('leistungszeitraumText: ein Tag, ein Jahr, Jahreswechsel (Halbgeviertstrich U+2013 ohne Leerzeichen)', () => {
assert.equal(leistungszeitraumText('2026-10-06', '2026-10-06'), '06.10.2026')
assert.equal(leistungszeitraumText('2026-10-01', '2026-10-20'), '01.10.–20.10.2026')
assert.equal(leistungszeitraumText('2026-09-28', '2026-10-05'), '28.09.–05.10.2026')
assert.equal(leistungszeitraumText('2026-12-28', '2027-01-05'), '28.12.2026–05.01.2027')
})
test('kalenderwoche: ISO-Woche samt ISO-Jahr, gueltigerTag: nur Tage, die es gibt', () => {
const faelle = [
['2026-10-05', 2026, 41], ['2026-10-06', 2026, 41], ['2026-10-11', 2026, 41], ['2026-10-12', 2026, 42],
['2025-12-29', 2026, 1], ['2026-01-01', 2026, 1], ['2026-12-31', 2026, 53], ['2027-01-03', 2026, 53],
['2027-01-04', 2027, 1], ['2024-12-30', 2025, 1], ['2021-01-03', 2020, 53],
]
for (const [tag, jahr, woche] of faelle) assert.deepEqual(kalenderwoche(tag), { jahr, woche }, tag)
assert.throws(() => kalenderwoche('2026-02-30'), RangeError)
assert.equal(gueltigerTag('2026-02-28'), true)
for (const tag of ['2026-02-30', '2026-13-01', '06.10.2026', '2026-1-1', '', undefined, 20261006]) assert.equal(gueltigerTag(tag), false, String(tag))
})
test('kuerzen: höchstens max Zeichen, Auslassungszeichen am Ende, kein halbes Emoji', () => {
assert.equal(kuerzen('kurz', 10), 'kurz')
assert.equal(kuerzen('abcdefghij', 10), 'abcdefghij')
assert.equal(kuerzen('abcdefghijk', 10), 'abcdefghi…')
assert.equal(kuerzen('ab cdefghijk', 4), 'ab…')
assert.equal(kuerzen('abcd😀fghij', 6), 'abcd…')
})
test('festePositionen: Eingabe wie bisher, Beschreibung getrimmt, Schlüssel pos-n, Betrag = Menge × Einzelpreis', () => {
assert.deepEqual(festePositionen([{ description: ' Website ', quantity: 2, unitAmountCents: 5000 }, ok]), [
{ schluessel: 'pos-1', beschreibung: 'Website', menge: 2, einzelpreisCents: 5000, betragCents: 10000 },
{ schluessel: 'pos-2', beschreibung: 'Domain', menge: 1, einzelpreisCents: 1200, betragCents: 1200 },
])
assert.equal(festePositionen(Array.from({ length: 20 }, () => ok)).length, 20)
})
test('festePositionen: Grenzen und Meldungen wie bei der bisherigen Rechnung', () => {
wirft(() => festePositionen([]), 400, ANZAHL)
wirft(() => festePositionen(undefined), 400, ANZAHL)
wirft(() => festePositionen(Array.from({ length: 21 }, () => ok)), 400, ANZAHL)
for (const description of ['', ' ', 'x'.repeat(201), 5]) wirft(() => festePositionen([{ ...ok, description }]), 400, 'Position 1: Die Beschreibung braucht 1 bis 200 Zeichen.')
for (const quantity of [0, 1000, 1.5, '2', -1, null]) wirft(() => festePositionen([ok, { ...ok, quantity }]), 400, 'Position 2: Die Menge muss eine ganze Zahl von 1 bis 999 sein.')
for (const unitAmountCents of [0, 10_000_001, 12.5, '5000', -100, null]) {
wirft(() => festePositionen([{ ...ok, unitAmountCents }]), 400, 'Position 1: Der Einzelpreis muss zwischen 0,01 € und 100.000,00 € liegen.')
}
const neun = Array.from({ length: 9 }, (_, i) => ({ description: `Teil ${i + 1}`, quantity: 1, unitAmountCents: 10_000_000 }))
wirft(() => festePositionen([...neun, { description: 'Rest', quantity: 1, unitAmountCents: 10_000_000 }]), 400, 'Rechnungssumme zu hoch.')
assert.equal(festePositionen([...neun, { description: 'Rest', quantity: 1, unitAmountCents: 9_999_999 }]).at(-1).betragCents, 9_999_999)
})
test('zusatzPositionen: leer erlaubt, Schlüssel zusatz-n, gleiche Prüfungen; pruefePositionen: Zahl (mit wählbarer Obergrenze), Texte, Summe mit Minuszeilen', () => {
for (const leer of [undefined, null, []]) assert.deepEqual(zusatzPositionen(leer), [])
assert.deepEqual(zusatzPositionen([{ description: 'Domain', quantity: 2, unitAmountCents: 1500 }]), [
{ schluessel: 'zusatz-1', beschreibung: 'Domain', menge: 2, einzelpreisCents: 1500, betragCents: 3000 },
])
wirft(() => zusatzPositionen('Domain'), 400, ANZAHL)
wirft(() => zusatzPositionen(Array.from({ length: 21 }, () => ok)), 400, ANZAHL)
wirft(() => zusatzPositionen([{ ...ok, description: '' }]), 400, 'Position 1: Die Beschreibung braucht 1 bis 200 Zeichen.')
const pos = (beschreibung, cents) => ({ schluessel: 'x', beschreibung, menge: 1, einzelpreisCents: cents, betragCents: cents })
assert.equal(pruefePositionen([pos('A', 5000), pos('abzüglich Anzahlung', -2000)]), 3000)
assert.equal(pruefePositionen([pos('A', 99_999_999)]), 99_999_999)
wirft(() => pruefePositionen([]), 400, ANZAHL)
wirft(() => pruefePositionen(Array.from({ length: 21 }, () => pos('A', 1))), 400, ANZAHL)
// eine Schlussrechnung darf bis 40 Zeilen haben: der Aufrufer hebt die Obergrenze an
assert.equal(pruefePositionen(Array.from({ length: 40 }, () => pos('A', 1)), { max: 40 }), 40)
wirft(() => pruefePositionen(Array.from({ length: 41 }, () => pos('A', 1)), { max: 40 }), 400, 'Eine Rechnung braucht 1 bis 40 Positionen.')
wirft(() => pruefePositionen([], { max: 40 }), 400, 'Eine Rechnung braucht 1 bis 40 Positionen.')
wirft(() => pruefePositionen([pos('A', 1), pos('', 1)]), 400, 'Position 2: Die Beschreibung braucht 1 bis 200 Zeichen.')
wirft(() => pruefePositionen([pos('x'.repeat(201), 1)]), 400, 'Position 1: Die Beschreibung braucht 1 bis 200 Zeichen.')
wirft(() => pruefePositionen([pos('A', 99_999_999), pos('B', 1)]), 400, 'Rechnungssumme zu hoch.')
})