From bc2679aebabadcb3545c44525ffc4b9f1c2bffca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Lohrer?= Date: Sat, 18 Apr 2026 05:23:18 +0200 Subject: [PATCH] publish(task 4): slug- und post-validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit validateSlug prüft regex ^[a-z0-9][a-z0-9-]*$ (lowercase, digits, hyphen; kein führender hyphen). validatePost checkt title, slug und date (muss Date-instanz mit gültigem timestamp sein). 7 tests grün. Co-Authored-By: Claude Opus 4.6 (1M context) --- publish/src/core/validation.ts | 22 +++++++++++++++++ publish/tests/validation_test.ts | 41 ++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 publish/src/core/validation.ts create mode 100644 publish/tests/validation_test.ts diff --git a/publish/src/core/validation.ts b/publish/src/core/validation.ts new file mode 100644 index 0000000..d51a299 --- /dev/null +++ b/publish/src/core/validation.ts @@ -0,0 +1,22 @@ +import type { Frontmatter } from './frontmatter.ts' + +const SLUG_RE = /^[a-z0-9][a-z0-9-]*$/ + +export function validateSlug(slug: string): void { + if (!SLUG_RE.test(slug)) { + throw new Error(`invalid slug: "${slug}" (must match ${SLUG_RE})`) + } +} + +export function validatePost(fm: Frontmatter): void { + if (!fm.title || typeof fm.title !== 'string') { + throw new Error('missing/invalid title') + } + if (!fm.slug || typeof fm.slug !== 'string') { + throw new Error('missing/invalid slug') + } + validateSlug(fm.slug) + if (!(fm.date instanceof Date) || isNaN(fm.date.getTime())) { + throw new Error('missing/invalid date (expected YAML date)') + } +} diff --git a/publish/tests/validation_test.ts b/publish/tests/validation_test.ts new file mode 100644 index 0000000..34c46d1 --- /dev/null +++ b/publish/tests/validation_test.ts @@ -0,0 +1,41 @@ +import { assertThrows } from '@std/assert' +import { validatePost, validateSlug } from '../src/core/validation.ts' +import type { Frontmatter } from '../src/core/frontmatter.ts' + +Deno.test('validateSlug: akzeptiert lowercase/digits/hyphen', () => { + validateSlug('abc-123') + validateSlug('a') + validateSlug('dezentrale-oep-oer') +}) + +Deno.test('validateSlug: lehnt Großbuchstaben ab', () => { + assertThrows(() => validateSlug('Abc'), Error, 'slug') +}) + +Deno.test('validateSlug: lehnt Unterstriche/Leerzeichen ab', () => { + assertThrows(() => validateSlug('a_b'), Error, 'slug') + assertThrows(() => validateSlug('a b'), Error, 'slug') +}) + +Deno.test('validateSlug: lehnt führenden Bindestrich ab', () => { + assertThrows(() => validateSlug('-abc'), Error, 'slug') +}) + +Deno.test('validatePost: ok bei vollständigem Frontmatter', () => { + const fm: Frontmatter = { + title: 'T', + slug: 'ok-slug', + date: new Date('2024-01-01'), + } + validatePost(fm) +}) + +Deno.test('validatePost: fehlt title', () => { + const fm = { slug: 'ok', date: new Date() } as unknown as Frontmatter + assertThrows(() => validatePost(fm), Error, 'title') +}) + +Deno.test('validatePost: date muss Date sein', () => { + const fm = { title: 'T', slug: 'ok', date: 'not-a-date' } as unknown as Frontmatter + assertThrows(() => validatePost(fm), Error, 'date') +})