Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/morning-briefing

src/format.test.ts

141 lines · 4.3 KB · TypeScript
import assert from 'node:assert/strict'
import test from 'node:test'
import {
	DEFAULT_SOURCES,
	calloutsFromSections,
	deliveryPreview,
	failedSection,
	isDryRun,
	notifyBody,
	reportMarkdown,
	resolveDate,
	resolveOutput,
	resolveSources,
	resolveTimezone,
	skippedSection,
} from './format.ts'
import { aqiCategory, weatherLabel } from './open-meteo.ts'
import { homeSection } from './sources/home.ts'

test('resolveSources defaults to calendar and weather', () => {
	assert.deepEqual(resolveSources({}), DEFAULT_SOURCES)
})

test('resolveSources accepts optional inbox, health, and home', () => {
	assert.deepEqual(resolveSources({ sources: ['calendar', 'weather', 'inbox', 'health', 'home'] }), [
		'calendar',
		'weather',
		'inbox',
		'health',
		'home',
	])
})

test('resolveSources rejects unknown sources', () => {
	assert.throws(
		() => resolveSources({ sources: ['discord'] as never }),
		/Unknown source "discord"/,
	)
})

test('resolveOutput accepts email or notify and rejects others', () => {
	assert.equal(resolveOutput({}), 'email')
	assert.equal(resolveOutput({ output: 'notify' }), 'notify')
	assert.throws(() => resolveOutput({ output: 'discord' as never }), /Unknown output/)
})

test('isDryRun is opt-in', () => {
	assert.equal(isDryRun({}), false)
	assert.equal(isDryRun({ dryRun: true }), true)
})

test('resolveDate uses an explicit YYYY-MM-DD', () => {
	assert.equal(resolveDate({ date: '2026-08-22' }, 'UTC'), '2026-08-22')
})

test('resolveTimezone rejects invalid zones', () => {
	assert.equal(resolveTimezone({}), 'UTC')
	assert.throws(() => resolveTimezone({ timezone: 'Not/AZone' }), /RangeError|invalid/i)
})

test('report markdown and notify stay Discord-free', () => {
	const report = {
		title: 'Saturday, August 22 · Denver, CO',
		date: '2026-08-22',
		callouts: [{ title: 'US AQI 140', detail: 'Unhealthy for sensitive groups', severity: 'warning' as const }],
		sections: [
			{
				id: 'weather' as const,
				title: 'Weather',
				status: 'ok' as const,
				summary: 'Clear',
				items: [{ title: 'Clear sky, 28° / 14°C', detail: 'Current 22°C' }],
			},
		],
	}
	const markdown = reportMarkdown(report)
	const notify = notifyBody(report)
	assert.match(markdown, /Needs attention/)
	assert.match(notify, /US AQI 140/)
	assert.doesNotMatch(markdown, /discord/i)
	assert.doesNotMatch(notify, /discord/i)
	assert.doesNotMatch(deliveryPreview({ ...report, markdown, notifyText: notify } as never, 'email').text, /discord/i)
})

test('notify preview uses a short subject', () => {
	const report = {
		date: '2026-08-22',
		title: 'Saturday, August 22',
		callouts: [] as Array<{ title: string }>,
		sections: [],
		markdown: '# Saturday, August 22\n',
		notifyText: 'all clear',
	}
	const preview = deliveryPreview(report as never, 'notify')
	assert.equal(preview.subject, 'Briefing all clear · 2026-08-22')
	assert.equal(preview.text, 'all clear')
})

test('failed and skipped sections keep source ids', () => {
	const failed = failedSection('inbox', new Error('missing gmail.readonly'))
	assert.equal(failed.id, 'inbox')
	assert.equal(failed.status, 'error')
	const skipped = skippedSection('home', 'Pass home.items')
	assert.equal(skipped.status, 'skipped')
})

test('callouts only include warning and critical items', () => {
	const callouts = calloutsFromSections([
		{
			id: 'health',
			title: 'Health',
			status: 'warning',
			items: [
				{ title: 'ok', severity: 'good' },
				{ title: 'watch', severity: 'warning' },
				{ title: 'bad', severity: 'critical' },
			],
		},
	])
	assert.deepEqual(callouts.map((item) => item.title), ['watch', 'bad'])
})

test('home section uses caller items and skips Discord-style defaults', async () => {
	const skipped = await homeSection({})
	assert.equal(skipped.status, 'skipped')
	assert.match(skipped.summary || '', /home\.items/)
	assert.doesNotMatch(skipped.summary || '', /post to discord/i)
	const section = await homeSection({
		home: { items: [{ title: 'Thermostat 70°', detail: 'Heat' }] },
	})
	assert.equal(section.status, 'ok')
	assert.equal(section.items[0]?.title, 'Thermostat 70°')
})

test('Open-Meteo helpers map codes and AQI bands', () => {
	assert.equal(weatherLabel(0), 'Clear sky')
	assert.equal(weatherLabel(99), 'Thunderstorm with heavy hail')
	assert.equal(aqiCategory(12), 'Good')
	assert.equal(aqiCategory(120), 'Unhealthy for sensitive groups')
	assert.equal(aqiCategory(250), 'Very unhealthy')
})