mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-12 22:13:05 +00:00
Grava um único campo do global via Local API e busta a tag no processo Next vivo pelo POST /api/revalidate (allowlist ampliada com REVALIDATE_PHOTO_ALBUM_CACHE_TAG), com guardas de escrita, plan/apply/verify, recibo com rollback e int pin do update parcial.
228 lines
7.5 KiB
TypeScript
228 lines
7.5 KiB
TypeScript
// @vitest-environment node
|
|
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
import {
|
|
OPS_GLOBAL_TARGETS,
|
|
buildOpsGlobalWrite,
|
|
formatOpsGlobalOperations,
|
|
formatOpsGlobalRecoveryCommand,
|
|
formatOpsGlobalReport,
|
|
opsGlobalReportStamp,
|
|
parseOpsGlobalCliArgs,
|
|
} from '../../scripts/lib/opsGlobal.mjs'
|
|
|
|
// C247 — the pure parser/plan of `pnpm ops:global`: the closed write surface,
|
|
// the dry-run default, the previous→new plan with rollback and the receipt
|
|
// helpers (the recovery recipe never carries the secret value).
|
|
|
|
describe('OPS_GLOBAL_TARGETS', () => {
|
|
it('is a closed table: photoAlbum with the two operational flags only', () => {
|
|
expect(Object.keys(OPS_GLOBAL_TARGETS)).toEqual(['photoAlbum'])
|
|
expect(Object.keys(OPS_GLOBAL_TARGETS.photoAlbum.flags)).toEqual(['selfie-search', 'published'])
|
|
})
|
|
|
|
it('generates the HELP operation block from the table', () => {
|
|
const lines = formatOpsGlobalOperations().join('\n')
|
|
expect(lines).toContain('photoAlbum --selfie-search <on|off>')
|
|
expect(lines).toContain('photoAlbum --published <on|off>')
|
|
expect(lines).toContain('Busca por selfie')
|
|
})
|
|
})
|
|
|
|
describe('parseOpsGlobalCliArgs', () => {
|
|
it('defaults to the plan mode and parses the operation', () => {
|
|
expect(parseOpsGlobalCliArgs(['photoAlbum', '--selfie-search', 'on'])).toEqual({
|
|
slug: 'photoAlbum',
|
|
operation: 'selfie-search',
|
|
value: true,
|
|
mode: 'plan',
|
|
help: false,
|
|
})
|
|
expect(parseOpsGlobalCliArgs(['photoAlbum', '--published', 'off', '--apply'])).toEqual({
|
|
slug: 'photoAlbum',
|
|
operation: 'published',
|
|
value: false,
|
|
mode: 'apply',
|
|
help: false,
|
|
})
|
|
expect(
|
|
parseOpsGlobalCliArgs(['--verify', 'photoAlbum', '--selfie-search', 'off']),
|
|
).toMatchObject({
|
|
operation: 'selfie-search',
|
|
value: false,
|
|
mode: 'verify',
|
|
})
|
|
expect(parseOpsGlobalCliArgs(['--help']).help).toBe(true)
|
|
})
|
|
|
|
it('fails closed on unknown slug/flag/value, missing value and mode conflicts', () => {
|
|
expect(() => parseOpsGlobalCliArgs([])).toThrow(/informe o global/)
|
|
expect(() => parseOpsGlobalCliArgs(['homePage', '--selfie-search', 'on'])).toThrow(
|
|
/fora da tabela/,
|
|
)
|
|
expect(() => parseOpsGlobalCliArgs(['photoAlbum', '--nope', 'on'])).toThrow(/fora da tabela/)
|
|
expect(() => parseOpsGlobalCliArgs(['photoAlbum', '--selfie-search', 'maybe'])).toThrow(
|
|
/valor inválido/,
|
|
)
|
|
expect(() => parseOpsGlobalCliArgs(['photoAlbum', '--selfie-search'])).toThrow(/faltou valor/)
|
|
expect(() => parseOpsGlobalCliArgs(['photoAlbum', '--selfie-search', 'on', 'extra'])).toThrow(
|
|
/posicional desconhecido/,
|
|
)
|
|
expect(() =>
|
|
parseOpsGlobalCliArgs(['photoAlbum', '--selfie-search', 'on', '--published', 'off']),
|
|
).toThrow(/mais de uma operação/)
|
|
expect(() => parseOpsGlobalCliArgs(['photoAlbum'])).toThrow(/informe a operação/)
|
|
expect(() =>
|
|
parseOpsGlobalCliArgs(['photoAlbum', '--selfie-search', 'on', '--apply', '--verify']),
|
|
).toThrow(/mutuamente exclusivos/)
|
|
})
|
|
})
|
|
|
|
describe('buildOpsGlobalWrite', () => {
|
|
it('plans a single-field write with the previous value and the rollback command', () => {
|
|
const fromAbsent = buildOpsGlobalWrite({
|
|
slug: 'photoAlbum',
|
|
operation: 'selfie-search',
|
|
value: true,
|
|
currentDoc: {},
|
|
})
|
|
expect(fromAbsent).toMatchObject({
|
|
field: 'selfieSearchEnabled',
|
|
previousValue: false,
|
|
newValue: true,
|
|
changed: true,
|
|
data: { selfieSearchEnabled: true },
|
|
rollbackCommand: 'pnpm ops:global photoAlbum --selfie-search off --apply',
|
|
})
|
|
|
|
const fromFalse = buildOpsGlobalWrite({
|
|
slug: 'photoAlbum',
|
|
operation: 'published',
|
|
value: true,
|
|
currentDoc: { published: false },
|
|
})
|
|
expect(fromFalse).toMatchObject({
|
|
previousValue: false,
|
|
changed: true,
|
|
rollbackCommand: 'pnpm ops:global photoAlbum --published off --apply',
|
|
})
|
|
})
|
|
|
|
it('reports no change when the flag already holds the requested value', () => {
|
|
const write = buildOpsGlobalWrite({
|
|
slug: 'photoAlbum',
|
|
operation: 'published',
|
|
value: true,
|
|
currentDoc: { published: true },
|
|
})
|
|
expect(write.changed).toBe(false)
|
|
expect(write.data).toEqual({ published: true })
|
|
})
|
|
|
|
it('falls back to the table default when the row/slot is absent', () => {
|
|
// `published` defaults to true in the global; an absent row is an open album.
|
|
const write = buildOpsGlobalWrite({
|
|
slug: 'photoAlbum',
|
|
operation: 'published',
|
|
value: true,
|
|
currentDoc: null,
|
|
})
|
|
expect(write.previousValue).toBe(true)
|
|
expect(write.changed).toBe(false)
|
|
})
|
|
|
|
it('rolls back to on when the previous value was true, and refuses unknown operations', () => {
|
|
const write = buildOpsGlobalWrite({
|
|
slug: 'photoAlbum',
|
|
operation: 'published',
|
|
value: false,
|
|
currentDoc: { published: true },
|
|
})
|
|
expect(write.rollbackCommand).toBe('pnpm ops:global photoAlbum --published on --apply')
|
|
|
|
expect(() =>
|
|
buildOpsGlobalWrite({
|
|
slug: 'photoAlbum',
|
|
operation: 'nope',
|
|
value: true,
|
|
currentDoc: {},
|
|
}),
|
|
).toThrow(/fora da tabela/)
|
|
})
|
|
})
|
|
|
|
describe('receipt helpers', () => {
|
|
it('stamps the receipt name like the other CLIs', () => {
|
|
expect(opsGlobalReportStamp('2026-10-01T04:09:04.123Z')).toBe('2026-10-01T04-09-04-123Z')
|
|
})
|
|
|
|
it('builds the recovery command without ever printing the secret value', () => {
|
|
const command = formatOpsGlobalRecoveryCommand({
|
|
baseUrl: 'https://jorgesolla1313.com.br',
|
|
tag: 'global_photoAlbum',
|
|
})
|
|
expect(command).toContain('/api/revalidate?tag=global_photoAlbum')
|
|
expect(command).toContain('x-revalidate-secret: $REVALIDATE_SECRET')
|
|
})
|
|
|
|
it('prints the plan line and the divergence verdict', () => {
|
|
const plan = formatOpsGlobalReport({
|
|
mode: 'plan',
|
|
target: 'host/db',
|
|
slug: 'photoAlbum',
|
|
field: 'selfieSearchEnabled',
|
|
previousValue: false,
|
|
newValue: true,
|
|
changed: true,
|
|
write: 'none',
|
|
revalidation: null,
|
|
}).join('\n')
|
|
expect(plan).toContain('modo: plan')
|
|
expect(plan).toContain('photoAlbum.selfieSearchEnabled: off → on')
|
|
expect(plan).toContain('use --apply')
|
|
|
|
const verify = formatOpsGlobalReport({
|
|
mode: 'verify',
|
|
target: 'host/db',
|
|
slug: 'photoAlbum',
|
|
field: 'selfieSearchEnabled',
|
|
previousValue: false,
|
|
newValue: true,
|
|
changed: true,
|
|
write: 'none',
|
|
revalidation: null,
|
|
}).join('\n')
|
|
expect(verify).toContain('diverge do valor pedido')
|
|
})
|
|
|
|
it('distinguishes the applied and skipped writes and the bust outcome', () => {
|
|
const applied = formatOpsGlobalReport({
|
|
mode: 'apply',
|
|
target: 'host/db',
|
|
slug: 'photoAlbum',
|
|
field: 'selfieSearchEnabled',
|
|
previousValue: false,
|
|
newValue: true,
|
|
changed: true,
|
|
write: 'applied',
|
|
revalidation: { ok: true, tag: 'global_photoAlbum', reason: null },
|
|
}).join('\n')
|
|
expect(applied).toContain('global atualizado pelo Local API.')
|
|
expect(applied).toContain('revalidação de global_photoAlbum: ok')
|
|
|
|
const skipped = formatOpsGlobalReport({
|
|
mode: 'apply',
|
|
target: 'host/db',
|
|
slug: 'photoAlbum',
|
|
field: 'selfieSearchEnabled',
|
|
previousValue: true,
|
|
newValue: true,
|
|
changed: false,
|
|
write: 'skipped',
|
|
revalidation: { ok: false, tag: 'global_photoAlbum', reason: 'HTTP 401' },
|
|
}).join('\n')
|
|
expect(skipped).toContain('escrita pulada')
|
|
expect(skipped).toContain('falhou (HTTP 401)')
|
|
})
|
|
})
|