The dispatch timestamp survived the deletion, so removing a message in Slack left the digest permanently swallowed. Deleting now rewinds the window to the newest digest that still stands.
181 lines
6.3 KiB
TypeScript
181 lines
6.3 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { db } from '~/data/db'
|
|
import { appSettings } from '~/data/schema'
|
|
import { listReportRuns, writeSetting } from '~/data/repositories/settings'
|
|
import { readSlackConfig, reportScope, runWeeklyReport } from '~/lib/report-settings'
|
|
import { performDeleteMessage } from '~/lib/admin-settings'
|
|
import { makeBrand, makeProject } from '../support/admin'
|
|
import { makePost } from '../support/entries'
|
|
|
|
const origin = 'https://logbuch.nyo.de'
|
|
|
|
beforeEach(async () => {
|
|
await db.delete(appSettings)
|
|
delete process.env.SLACK_WEBHOOK_URL
|
|
vi.unstubAllGlobals()
|
|
})
|
|
|
|
describe('readSlackConfig', () => {
|
|
it('nimmt den hinterlegten Webhook vor der Umgebungsvariable', async () => {
|
|
process.env.SLACK_WEBHOOK_URL = 'https://hooks.slack.com/services/aus/der/umgebung'
|
|
await writeSetting('slack.webhook', 'https://hooks.slack.com/services/aus/der/verwaltung')
|
|
|
|
const config = await readSlackConfig()
|
|
|
|
expect(config.target).toMatchObject({ kind: 'webhook' })
|
|
expect(config.target && 'url' in config.target ? config.target.url : '').toContain('verwaltung')
|
|
expect(config.fromEnv).toBe(false)
|
|
})
|
|
|
|
it('fällt auf die Umgebungsvariable zurück', async () => {
|
|
process.env.SLACK_WEBHOOK_URL = 'https://hooks.slack.com/services/aus/der/umgebung'
|
|
|
|
const config = await readSlackConfig()
|
|
|
|
expect(config.fromEnv).toBe(true)
|
|
})
|
|
|
|
it('schickt interne Beiträge mit, solange nichts anderes gesetzt ist', async () => {
|
|
expect(reportScope(await readSlackConfig())).toBe('internal')
|
|
|
|
await writeSetting('slack.includeInternal', 'false')
|
|
|
|
expect(reportScope(await readSlackConfig())).toBe('customer')
|
|
})
|
|
})
|
|
|
|
describe('runWeeklyReport', () => {
|
|
it('vermerkt einen Lauf ohne Veröffentlichungen, ohne zu senden', async () => {
|
|
const fetchMock = vi.fn()
|
|
vi.stubGlobal('fetch', fetchMock)
|
|
|
|
const outcome = await runWeeklyReport({ origin, trigger: 'test' })
|
|
|
|
expect(outcome.reason).toBe('nothing_published')
|
|
expect(fetchMock).not.toHaveBeenCalled()
|
|
|
|
const runs = await listReportRuns(1)
|
|
|
|
expect(runs[0]).toMatchObject({ sent: false, total: 0, trigger: 'test', detail: 'nothing_published' })
|
|
})
|
|
})
|
|
|
|
describe('Bot-Token', () => {
|
|
it('nimmt Bot-Token und Kanal vor dem Webhook', async () => {
|
|
await writeSetting('slack.webhook', 'https://hooks.slack.com/services/a/b/c')
|
|
await writeSetting('slack.botToken', 'xoxb-1234')
|
|
await writeSetting('slack.channel', 'logbuch')
|
|
|
|
const config = await readSlackConfig()
|
|
|
|
expect(config.target).toEqual({ kind: 'bot', token: 'xoxb-1234', channel: 'logbuch' })
|
|
expect(config.hasBot).toBe(true)
|
|
})
|
|
|
|
it('braucht zum Bot-Token einen Kanal', async () => {
|
|
await writeSetting('slack.botToken', 'xoxb-1234')
|
|
|
|
const config = await readSlackConfig()
|
|
|
|
expect(config.target).toBeUndefined()
|
|
expect(config.hasBot).toBe(true)
|
|
})
|
|
|
|
it('merkt sich Kanal und Nachrichtenkennung eines Versands', async () => {
|
|
const brand = await makeBrand()
|
|
const project = await makeProject(brand.id)
|
|
const post = await makePost(project.id, { number: 1, slug: 'eins', status: 'published', publishAt: new Date() })
|
|
|
|
expect(post.id).toBeDefined()
|
|
|
|
await writeSetting('slack.botToken', 'xoxb-1234')
|
|
await writeSetting('slack.channel', 'logbuch')
|
|
|
|
vi.stubGlobal('fetch', vi.fn(async () => new Response(
|
|
JSON.stringify({ ok: true, channel: 'C999', ts: '1785600000.000100' }),
|
|
{ headers: { 'content-type': 'application/json' } },
|
|
)))
|
|
|
|
const outcome = await runWeeklyReport({ origin, trigger: 'test' })
|
|
|
|
expect(outcome.reason).toBe('sent')
|
|
expect(outcome.run).toMatchObject({ sent: true, channel: 'C999', messageTs: '1785600000.000100' })
|
|
})
|
|
|
|
it('vermerkt eine Ablehnung mit dem Grund von Slack', async () => {
|
|
const brand = await makeBrand()
|
|
const project = await makeProject(brand.id)
|
|
|
|
await makePost(project.id, { number: 1, slug: 'eins', status: 'published', publishAt: new Date() })
|
|
|
|
await writeSetting('slack.botToken', 'xoxb-1234')
|
|
await writeSetting('slack.channel', 'logbuch')
|
|
|
|
vi.stubGlobal('fetch', vi.fn(async () => new Response(
|
|
JSON.stringify({ ok: false, error: 'channel_not_found' }),
|
|
{ headers: { 'content-type': 'application/json' } },
|
|
)))
|
|
|
|
const outcome = await runWeeklyReport({ origin, trigger: 'test' })
|
|
|
|
expect(outcome.reason).toBe('rejected')
|
|
expect(outcome.run).toMatchObject({ sent: false, detail: 'channel_not_found' })
|
|
})
|
|
})
|
|
|
|
describe('Doppelter Versand', () => {
|
|
it('schickt nach einem Versand nichts Zweites, solange nichts Neues erscheint', async () => {
|
|
const brand = await makeBrand()
|
|
const project = await makeProject(brand.id)
|
|
|
|
await makePost(project.id, { number: 1, slug: 'eins', status: 'published', publishAt: new Date() })
|
|
await writeSetting('slack.botToken', 'xoxb-1234')
|
|
await writeSetting('slack.channel', 'C0123456')
|
|
|
|
vi.stubGlobal('fetch', vi.fn(async () => new Response(
|
|
JSON.stringify({ ok: true, channel: 'C0123456', ts: '1785600000.000100' }),
|
|
{ headers: { 'content-type': 'application/json' } },
|
|
)))
|
|
|
|
const first = await runWeeklyReport({ origin, trigger: 'test' })
|
|
const second = await runWeeklyReport({ origin, trigger: 'test' })
|
|
|
|
expect(first.reason).toBe('sent')
|
|
expect(second.reason).toBe('nothing_published')
|
|
})
|
|
})
|
|
|
|
describe('Löschen nimmt den Versand zurück', () => {
|
|
it('macht den Bericht nach dem Löschen wieder sendbar', async () => {
|
|
const brand = await makeBrand()
|
|
const project = await makeProject(brand.id)
|
|
|
|
await makePost(project.id, { number: 1, slug: 'eins', status: 'published', publishAt: new Date() })
|
|
await writeSetting('slack.botToken', 'xoxb-1234')
|
|
await writeSetting('slack.channel', 'C0123456')
|
|
|
|
vi.stubGlobal('fetch', vi.fn(async () => new Response(
|
|
JSON.stringify({ ok: true, channel: 'C0123456', ts: '1785600000.000100' }),
|
|
{ headers: { 'content-type': 'application/json' } },
|
|
)))
|
|
|
|
const first = await runWeeklyReport({ origin, trigger: 'test' })
|
|
|
|
expect(first.reason).toBe('sent')
|
|
expect((await runWeeklyReport({ origin, trigger: 'test' })).reason).toBe('nothing_published')
|
|
|
|
const form = new FormData()
|
|
|
|
form.set('runId', first.run.id)
|
|
|
|
const deleted = await performDeleteMessage(
|
|
{ id: 'u1', name: 'Admin', email: '[email protected]', isAdmin: true, assignments: [] },
|
|
form,
|
|
)
|
|
|
|
expect(deleted.message).toBe('messageDeleted')
|
|
expect(deleted.status).toBe('ok')
|
|
expect((await runWeeklyReport({ origin, trigger: 'test' })).reason).toBe('sent')
|
|
})
|
|
})
|