Files
logbuch/tests/lib/weekly-report.test.ts
T
Matthias G 65346d0c09 Send a weekly digest to Slack
POST /api/v1/weekly-report collects everything published in the last seven
days, groups it by project and posts one message. dry=1 returns the message
instead of sending it, no publications means no message, a missing webhook
answers 501 instead of failing quietly. scripts/cron.mjs runs the due
publishing first, then the digest.
2026-08-03 11:39:54 +02:00

86 lines
2.8 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { buildMessage, groupByProject, windowFor } from '~/lib/weekly-report'
import type { PostListItem } from '~/data/repositories/posts'
function item(overrides: Partial<PostListItem> = {}): PostListItem {
return {
id: 'c0ffee00-0000-4000-8000-000000000001',
slug: 'regel-engine',
title: 'Regel-Engine: Bedingung trifft Aktion',
teaser: null,
type: { id: 't1', key: 'feature', labelDe: 'Feature', labelEn: 'Feature', color: '#2b4a9b' },
audience: 'internal',
publishAt: new Date('2026-07-30T09:00:00.000Z'),
authorName: 'Paul',
coverMediaId: null,
number: 142,
projectSlug: 'trakk',
projectName: 'Trakk',
projectCode: 'TRK',
projectColor: '#2e7d5b',
...overrides,
}
}
const origin = 'https://logbuch.nyo.de'
describe('windowFor', () => {
it('spannt sieben Tage bis zum Aufruf', () => {
const { from, to } = windowFor(new Date('2026-08-03T06:00:00.000Z'))
expect(to.toISOString()).toBe('2026-08-03T06:00:00.000Z')
expect(from.toISOString()).toBe('2026-07-27T06:00:00.000Z')
})
})
describe('groupByProject', () => {
it('fasst Einträge je Projekt zusammen und behält die Reihenfolge', () => {
const groups = groupByProject([
item(),
item({ id: 'b', slug: 'zwei', projectSlug: 'orbit', projectName: 'Orbit', projectCode: 'ORB' }),
item({ id: 'c', slug: 'drei', number: 143 }),
])
expect(groups.map(group => group.slug)).toEqual(['trakk', 'orbit'])
expect(groups[0]!.items).toHaveLength(2)
})
})
describe('buildMessage', () => {
const base = {
from: new Date('2026-07-27T06:00:00.000Z'),
to: new Date('2026-08-03T06:00:00.000Z'),
}
it('bleibt still, wenn nichts veröffentlicht wurde', () => {
expect(buildMessage({ ...base, total: 0, projects: [] }, origin)).toBeNull()
})
it('nennt Zeitraum, Anzahl und jedes Projekt', () => {
const projects = groupByProject([item(), item({ id: 'b', slug: 'zwei', projectSlug: 'orbit', projectName: 'Orbit', projectCode: 'ORB' })])
const message = buildMessage({ ...base, total: 2, projects }, origin)
expect(message?.text).toBe('Logbuch, 27.07.2026 bis 03.08.2026: 2 Einträge')
const dump = JSON.stringify(message?.blocks)
expect(dump).toContain('27.07.2026 bis 03.08.2026')
expect(dump).toContain('*Trakk*')
expect(dump).toContain('*Orbit*')
expect(dump).toContain('TRK-0142')
expect(dump).toContain('https://logbuch.nyo.de/trakk/regel-engine')
})
it('zählt einen einzelnen Eintrag im Singular', () => {
const message = buildMessage({ ...base, total: 1, projects: groupByProject([item()]) }, origin)
expect(message?.text).toContain('1 Eintrag')
})
it('verzichtet auf Emojis', () => {
const message = buildMessage({ ...base, total: 1, projects: groupByProject([item()]) }, origin)
expect(JSON.stringify(message)).not.toMatch(/\p{Extended_Pictographic}/u)
})
})