teqo-backup/tests/unit/flickrImportCli.unit.spec.ts
Francisco Solla ad2b165e5e fix(C231): EXIF do acervo volta ao mapper e ganha backfill --refresh-metadata
A API do Flickr responde raw/clean como { _content } e o mapper só aceitava
string — as 6.492 fotos do backfill gravaram exif: []. archivePhotoExifEntries
desembrulha os dois shapes (contentScalar também substitui o unwrap ad-hoc do
description) e o CLI ganha o modo --refresh-metadata, que re-busca o EXIF das
linhas existentes e atualiza só o que mudou: write-only no campo exif, sem S3,
guardas de confirmação/TEQO_ENV e recibo atualizadas/inalteradas/falhas.
Runbook §C231 documenta a operação. Closes #1383.
2026-09-29 09:11:49 -03:00

111 lines
3.8 KiB
TypeScript

import { spawnSync } from 'node:child_process'
import { join } from 'node:path'
import { describe, expect, it } from 'vitest'
// C231 — the CLI's fail-closed write guards, exercised as a subprocess: each
// refusal must happen before any Flickr call, S3 write or DB connection.
const repoRoot = process.cwd()
const scriptPath = join(repoRoot, 'scripts', 'ingest-flickr-archive.mjs')
const run = (args: string[], env: Record<string, string> = {}) =>
spawnSync(process.execPath, [scriptPath, ...args], {
cwd: repoRoot,
encoding: 'utf8',
timeout: 30_000,
env: {
...process.env,
NODE_OPTIONS: '--no-deprecation --import=tsx/esm --import=./scripts/seed-loader.mjs',
NODE_ENV: 'production',
VITEST: '',
DATABASE_URL: 'postgresql://teqo:teqo@127.0.0.1:5432/teqo_1313',
PAYLOAD_SECRET: 'test-secret',
FLICKR_API_KEY: 'test-key',
FLICKR_USER_ID: '123@N00',
FLICKR_IMPORT_CONFIRM: '',
TEQO_ENV: '',
ALLOW_REMOTE_DB: '',
S3_BUCKET: '',
S3_ENDPOINT: '',
S3_ACCESS_KEY_ID: '',
S3_SECRET_ACCESS_KEY: '',
...env,
},
})
const output = (result: ReturnType<typeof run>) => `${result.stdout}${result.stderr}`
describe('flickr:import write guards (C231)', () => {
it('prints the help even without a database', () => {
const result = run(['--help'], { DATABASE_URL: '' })
expect(result.status).toBe(0)
expect(result.stdout).toContain('FLICKR_IMPORT_CONFIRM')
expect(result.stdout).toContain('pnpm flickr:import')
})
it('refuses --apply on a production target without the intent flag, before the key check', () => {
const result = run(['--apply'])
expect(result.status).toBe(1)
expect(output(result)).toContain('FLICKR_IMPORT_CONFIRM=1')
expect(output(result)).not.toContain('FLICKR_API_KEY')
})
it('requires the declared TEQO_ENV once the intent flag is set', () => {
const result = run(['--apply'], { FLICKR_IMPORT_CONFIRM: '1' })
expect(result.status).toBe(1)
expect(output(result)).toContain('TEQO_ENV')
})
it('refuses a target database that does not match the declared environment', () => {
const result = run(['--apply'], { FLICKR_IMPORT_CONFIRM: '1', TEQO_ENV: 'staging' })
expect(result.status).toBe(1)
expect(output(result)).toContain('≠ "teqo_staging"')
})
it('refuses --apply without the complete S3_* set on a declared production target', () => {
const result = run(['--apply'], { FLICKR_IMPORT_CONFIRM: '1', TEQO_ENV: 'production' })
expect(result.status).toBe(1)
expect(output(result)).toContain('S3_BUCKET')
})
it('refuses --refresh-metadata without the intent flag, before the key check', () => {
const result = run(['--refresh-metadata'])
expect(result.status).toBe(1)
expect(output(result)).toContain('FLICKR_IMPORT_CONFIRM=1')
expect(output(result)).toContain('--refresh-metadata')
expect(output(result)).not.toContain('FLICKR_API_KEY')
})
it('requires the declared TEQO_ENV for --refresh-metadata once the intent flag is set', () => {
const result = run(['--refresh-metadata'], { FLICKR_IMPORT_CONFIRM: '1' })
expect(result.status).toBe(1)
expect(output(result)).toContain('TEQO_ENV')
})
it('does not demand S3 for the metadata-only refresh (dies on the missing key instead)', () => {
const result = run(['--refresh-metadata'], {
FLICKR_IMPORT_CONFIRM: '1',
TEQO_ENV: 'production',
FLICKR_API_KEY: '',
})
expect(result.status).toBe(1)
expect(output(result)).toContain('FLICKR_API_KEY')
expect(output(result)).not.toContain('S3_BUCKET')
})
it('refuses an unknown argument before touching anything', () => {
const result = run(['--force'])
expect(result.status).toBe(1)
expect(output(result)).toContain('argumento desconhecido')
})
})