teqo-backup/tests/unit/reelIngest.unit.spec.ts

306 lines
11 KiB
TypeScript

// @vitest-environment node
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { reelArtifactAlt, reelArtifactMimetype, reelArtifactStorageFilename } from '@/lib/reel'
import {
assertReelIngestApplyConfirm,
assertReelIngestTarget,
parseReelIngestArgs,
readReelPackage,
reelPackageIssues,
} from '../../scripts/lib/reel-ingest.mjs'
const PNG_BYTES = Buffer.from(
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
'base64',
)
const MP4_BYTES = Buffer.from('fixture-mp4-bytes')
const SRT_BYTES = Buffer.from('1\n00:00:00,000 --> 00:00:01,000\nOlá\n')
const validMetadata = (overrides: Record<string, unknown> = {}) => ({
title: 'Tutorial dos cards',
feature: 'cards',
shotListHash: 'a'.repeat(64),
coverAlt: 'Tela dos cards de apoio da home',
durationSeconds: 42,
createdAt: '2026-09-18T12:00:00.000Z',
...overrides,
})
const tempDirs: string[] = []
const makePackage = async ({
metadata = validMetadata(),
files = { 'reel.mp4': MP4_BYTES, 'capa.png': PNG_BYTES, 'narracao.srt': SRT_BYTES },
omitMetadata = false,
}: {
metadata?: unknown
files?: Record<string, Buffer>
omitMetadata?: boolean
} = {}) => {
const directory = await mkdtemp(join(tmpdir(), 'reel-package-'))
tempDirs.push(directory)
for (const [name, body] of Object.entries(files)) {
await writeFile(join(directory, name), body)
}
if (!omitMetadata) {
await writeFile(
join(directory, 'metadata.json'),
typeof metadata === 'string' ? metadata : JSON.stringify(metadata),
)
}
return directory
}
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })))
})
describe('parseReelIngestArgs (C195)', () => {
it('reads the package directory and defaults to dry-run', () => {
expect(parseReelIngestArgs(['/srv/reels/cards'])).toEqual({
directory: '/srv/reels/cards',
apply: false,
})
})
it('accepts the bare --apply write flag', () => {
expect(parseReelIngestArgs(['/srv/reels/cards', '--apply'])).toEqual({
directory: '/srv/reels/cards',
apply: true,
})
})
it('refuses a missing directory, extra positionals, unknown flags and valued --apply', () => {
expect(() => parseReelIngestArgs([])).toThrow(/diretório do pacote/)
expect(() => parseReelIngestArgs(['a', 'b'])).toThrow(/posicional extra/)
expect(() => parseReelIngestArgs(['/pkg', '--force'])).toThrow(/desconhecido/)
expect(() => parseReelIngestArgs(['/pkg', '--apply=1'])).toThrow(/não aceita valor/)
for (const argv of [[], ['a', 'b'], ['/pkg', '--force'], ['/pkg', '--apply=1']]) {
expect(() => parseReelIngestArgs(argv)).toThrow(/pnpm reels:ingest/)
}
})
})
describe('assertReelIngestApplyConfirm (C195 write gate)', () => {
it('requires the intent flag only on --apply', () => {
expect(() => assertReelIngestApplyConfirm({ apply: false, confirm: false })).not.toThrow()
expect(() => assertReelIngestApplyConfirm({ apply: true, confirm: true })).not.toThrow()
expect(() => assertReelIngestApplyConfirm({ apply: true, confirm: false })).toThrow(
/REELS_INGEST_CONFIRM=1/,
)
expect(() => assertReelIngestApplyConfirm()).not.toThrow()
})
})
describe('assertReelIngestTarget (C195 fail-closed)', () => {
it('accepts the declared environment paired with its exact database', () => {
expect(
assertReelIngestTarget({
databaseUrl: 'postgresql://teqo:teqo@127.0.0.1:5432/teqo_1313',
teqoEnv: 'production',
isTest: false,
}),
).toEqual({ environment: 'production', databaseName: 'teqo_1313' })
expect(
assertReelIngestTarget({
databaseUrl: 'postgresql://teqo:teqo@postgres:5432/teqo_staging',
teqoEnv: 'staging',
isTest: false,
}),
).toEqual({ environment: 'staging', databaseName: 'teqo_staging' })
})
it('refuses a database that does not match the declared environment', () => {
expect(() =>
assertReelIngestTarget({
databaseUrl: 'postgresql://teqo:teqo@127.0.0.1:5432/teqo_staging',
teqoEnv: 'production',
isTest: false,
}),
).toThrow(/≠ "teqo_1313"/)
})
it('refuses a missing or unknown TEQO_ENV', () => {
expect(() =>
assertReelIngestTarget({
databaseUrl: 'postgresql://teqo:teqo@127.0.0.1:5432/teqo_1313',
teqoEnv: undefined,
isTest: false,
}),
).toThrow(/TEQO_ENV/)
expect(() =>
assertReelIngestTarget({ databaseUrl: '', teqoEnv: 'production', isTest: false }),
).toThrow(/DATABASE_URL ausente/)
expect(() =>
assertReelIngestTarget({
databaseUrl: 'postgresql://teqo:teqo@127.0.0.1:5432/teqo',
teqoEnv: 'dev',
isTest: false,
}),
).toThrow(/TEQO_ENV/)
})
it('refuses the remote override, a remote host and a non-Postgres protocol', () => {
expect(() =>
assertReelIngestTarget({
databaseUrl: 'postgresql://teqo:teqo@127.0.0.1:5432/teqo_1313',
teqoEnv: 'production',
allowRemoteDb: true,
isTest: false,
}),
).toThrow(/ALLOW_REMOTE_DB/)
expect(() =>
assertReelIngestTarget({
databaseUrl: 'postgresql://teqo:teqo@db.example.com:5432/teqo_1313',
teqoEnv: 'production',
isTest: false,
}),
).toThrow(/allowlist local/)
expect(() =>
assertReelIngestTarget({
databaseUrl: 'mysql://teqo:teqo@127.0.0.1:3306/teqo_1313',
teqoEnv: 'production',
isTest: false,
}),
).toThrow(/postgresql/)
})
it('accepts only teqo*_test databases inside a test run', () => {
expect(
assertReelIngestTarget({
databaseUrl: 'postgresql://teqo:teqo@localhost:5432/teqo_wt195_test',
isTest: true,
}),
).toEqual({ environment: 'test', databaseName: 'teqo_wt195_test' })
expect(() =>
assertReelIngestTarget({
databaseUrl: 'postgresql://teqo:teqo@localhost:5432/teqo_1313',
isTest: true,
}),
).toThrow(/não é de teste/)
})
})
describe('readReelPackage (C195 package contract)', () => {
it('reads a complete package and flags the present artifacts', async () => {
const directory = await makePackage()
const reelPackage = await readReelPackage(directory)
expect(reelPackage.metadata.title).toBe('Tutorial dos cards')
expect(reelPackage.transcriptPath).toBeNull()
const byKind = Object.fromEntries(reelPackage.artifacts.map((a) => [a.kind, a]))
expect(byKind.video).toMatchObject({ field: 'video', required: true, present: true })
expect(byKind.cover).toMatchObject({ field: 'cover', required: true, present: true })
expect(byKind.captions).toMatchObject({ field: 'captions', required: true, present: true })
expect(byKind.narration).toMatchObject({
field: 'narrationAudio',
required: false,
present: false,
})
expect(byKind['video-audio']).toMatchObject({ field: 'videoWithAudio', present: false })
})
it('reads the optional transcript when the package carries it', async () => {
const directory = await makePackage({
files: {
'reel.mp4': MP4_BYTES,
'capa.png': PNG_BYTES,
'narracao.srt': SRT_BYTES,
'roteiro.md': Buffer.from('# Roteiro'),
},
})
const reelPackage = await readReelPackage(directory)
expect(reelPackage.transcriptPath).toBe(join(directory, 'roteiro.md'))
})
it('refuses an incomplete or invalid package listing every pending item', async () => {
const directory = await makePackage({
metadata: validMetadata({ feature: 'inventada', shotListHash: 'abc', coverAlt: '' }),
files: { 'reel.mp4': MP4_BYTES, 'capa.png': PNG_BYTES },
})
await expect(readReelPackage(directory)).rejects.toThrow(
/feature[\s\S]*shotListHash[\s\S]*coverAlt[\s\S]*narracao\.srt/,
)
})
it('refuses a missing metadata.json, an unreadable one and an unknown directory', async () => {
const noMetadata = await makePackage({ omitMetadata: true })
await expect(readReelPackage(noMetadata)).rejects.toThrow(/metadata\.json/)
const brokenMetadata = await makePackage({ metadata: '{ not json' })
await expect(readReelPackage(brokenMetadata)).rejects.toThrow(/ilegível/)
await expect(readReelPackage('/nonexistent/reel-package')).rejects.toThrow(
/diretório do pacote inválido/,
)
})
it('validates the optional duration and date when present', async () => {
const directory = await makePackage({
metadata: validMetadata({ durationSeconds: -1, createdAt: 'ontem' }),
})
await expect(readReelPackage(directory)).rejects.toThrow(/durationSeconds[\s\S]*createdAt/)
})
it('refuses an empty artifact before the dry-run says OK', async () => {
const directory = await makePackage({
files: { 'reel.mp4': Buffer.alloc(0), 'capa.png': PNG_BYTES, 'narracao.srt': SRT_BYTES },
})
await expect(readReelPackage(directory)).rejects.toThrow(
/artefato vazio ou ilegível: reel\.mp4/,
)
})
it('collects issues without a filesystem through reelPackageIssues', () => {
const issues = reelPackageIssues({
metadata: validMetadata({ title: 'x'.repeat(201) }),
fileNames: new Set(['metadata.json', 'reel.mp4', 'capa.png', 'narracao.srt']),
})
expect(issues).toHaveLength(1)
expect(issues[0]).toMatch(/title/)
expect(reelPackageIssues({ metadata: 42, fileNames: new Set() })).toHaveLength(1)
})
})
describe('artifact naming, mimetype and alt (C195)', () => {
it('names stored files deterministically by hash prefix, kind and extension', () => {
const hash = '0123456789abcdef'.repeat(4)
const video = { filename: 'reel.mp4', kind: 'video' } as const
const captions = { filename: 'narracao.srt', kind: 'captions' } as const
expect(reelArtifactStorageFilename(hash, video)).toBe(`reel-${hash}-video.mp4`)
expect(reelArtifactStorageFilename(hash, captions)).toBe(`reel-${hash}-captions.srt`)
expect(reelArtifactStorageFilename(hash, video)).toBe(reelArtifactStorageFilename(hash, video))
})
it('maps the fixed content types of the contract', () => {
expect(reelArtifactMimetype('video')).toBe('video/mp4')
expect(reelArtifactMimetype('video-audio')).toBe('video/mp4')
expect(reelArtifactMimetype('narration')).toBe('audio/mpeg')
expect(reelArtifactMimetype('captions')).toBe('application/x-subrip')
expect(reelArtifactMimetype('cover')).toBe('image/png')
})
it('uses the manifest alt for the cover and derives the others from the title', () => {
const input = { title: 'Tutorial dos cards', coverAlt: 'Tela dos cards' }
expect(reelArtifactAlt({ ...input, kind: 'cover' })).toBe('Tela dos cards')
expect(reelArtifactAlt({ ...input, kind: 'video' })).toBe('Vídeo do reel "Tutorial dos cards"')
expect(reelArtifactAlt({ ...input, kind: 'narration' })).toBe(
'Narração do reel "Tutorial dos cards"',
)
})
})