import assert from 'node:assert/strict' import { describe, it } from 'node:test' import { MAX_COVER_BYTES, decodeCoverImage, parsePostBody, parsePostPatch, postResponse, } from '../src/app/api/agent/posts/post-contract.ts' const content = { root: { type: 'root', format: '', indent: 0, version: 1, direction: 'rtl', children: [{ type: 'paragraph', version: 1, children: [{ type: 'text', version: 1, text: 'محتوى' }] }], }, } const validBody = { title: 'دليل عربي', slug: 'arabic-ai-guide', dek: 'وصف عملي', content, category: 'أدوات', access: 'free', readingMinutes: 7, publish: true, } describe('parsePostBody', () => { it('accepts the exact publishing contract', () => { const parsed = parsePostBody(validBody) assert.equal(parsed.slug, 'arabic-ai-guide') assert.equal(parsed.publish, true) }) it('rejects direct status and level writes', () => { assert.throws(() => parsePostBody({ ...validBody, status: 'published' }), /unsupported field: status/) assert.throws(() => parsePostBody({ ...validBody, level: 'متقدم' }), /unsupported field: level/) }) it('requires a non-empty VIP teaser', () => { assert.throws(() => parsePostBody({ ...validBody, access: 'vip', teaser: ' ' }), /VIP teaser/) }) it('rejects invalid access, category, slug, and publish values', () => { assert.throws(() => parsePostBody({ ...validBody, access: 'paid' }), /access/) assert.throws(() => parsePostBody({ ...validBody, category: 'عام' }), /category/) assert.throws(() => parsePostBody({ ...validBody, slug: 'عنوان عربي' }), /slug/) assert.throws(() => parsePostBody({ ...validBody, publish: 'true' }), /publish/) }) }) describe('parsePostPatch', () => { it('accepts publish-only draft promotion', () => { assert.deepEqual(parsePostPatch({ publish: true }, 'arabic-ai-guide'), { publish: true }) }) it('accepts a full republish only for the path slug', () => { assert.equal(parsePostPatch(validBody, 'arabic-ai-guide').slug, 'arabic-ai-guide') assert.throws(() => parsePostPatch(validBody, 'different-slug'), /slug must match/) }) it('rejects status writes and empty updates', () => { assert.throws(() => parsePostPatch({ status: 'published' }, 'arabic-ai-guide'), /unsupported field: status/) assert.throws(() => parsePostPatch({}, 'arabic-ai-guide'), /at least one field/) }) }) describe('decodeCoverImage', () => { it('accepts a small JPEG and normalizes its filename', () => { const jpeg = Buffer.from([0xff, 0xd8, 0xff, 0xdb, 0xff, 0xd9]) const file = decodeCoverImage({ dataBase64: jpeg.toString('base64'), filename: '../cover.jpg' }) assert.equal(file.mimetype, 'image/jpeg') assert.equal(file.name, 'cover.jpg') assert.deepEqual(file.data, jpeg) }) it('rejects non-JPEG data and oversized files', () => { assert.throws(() => decodeCoverImage({ dataBase64: Buffer.from('png').toString('base64') }), /JPEG/) const oversized = Buffer.alloc(MAX_COVER_BYTES + 1, 0) oversized[0] = 0xff oversized[1] = 0xd8 oversized[oversized.length - 2] = 0xff oversized[oversized.length - 1] = 0xd9 assert.throws(() => decodeCoverImage({ dataBase64: oversized.toString('base64') }), /5MB/) }) }) describe('postResponse', () => { it('returns the minimal deterministic response and canonical URL', () => { assert.deepEqual( postResponse({ id: 42, title: 'دليل عربي', slug: 'arabic-ai-guide', status: 'published', access: 'vip', category: 'أدوات', publishedAt: '2026-10-09T12:00:00.000Z', }), { id: 42, title: 'دليل عربي', slug: 'arabic-ai-guide', status: 'published', access: 'vip', category: 'أدوات', publishedAt: '2026-10-09T12:00:00.000Z', url: 'https://anwarpy.ai/posts/arabic-ai-guide', }, ) }) })