mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 23:45:18 +00:00
A importação do perfil oficial deixa a janela fixa de 12 mídias e ganha o recorte por período (desde; até opcional), com prévia honesta (novas · já estavam · total) e confirmação antes de criar; "Recentes" segue o default e o clique único. O planner puro de janela/dedupe passa ao dono em src/lib (CLI re-exporta) e a varredura usa o teto de páginas derivado de maxResults, com early-stop por data e corte denunciado (truncated). Sem migration/Consent; tudo Rascunho; fail-closed de credencial intacto. Design tier: DEGRADED (central-conteudos-importar-janela-ui-design) — port classe-a-classes das cenas 01–08 com sign-off humano explícito (2026-09-29); a crítica de paridade do designer no tier primário não pôde rodar por quota.
223 lines
7.5 KiB
TypeScript
223 lines
7.5 KiB
TypeScript
// @vitest-environment node
|
|
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
import {
|
|
contentPieceProfileFeedIdentityUrls,
|
|
contentPieceProfilePeriodBounds,
|
|
contentPieceProfilePeriodError,
|
|
contentPieceProfileWindowLabel,
|
|
contentPieceProfileWindowStartReached,
|
|
planContentPieceProfileWindow,
|
|
} from '@/lib/contentPieceProfileWindow'
|
|
|
|
// C235 — the window owner: the civil-date bounds of the operator's period, the
|
|
// recency filter over the feed timestamps, the identity dedupe (Central and
|
|
// feed itself) and the honest coverage check. Pure: no Payload, no network.
|
|
|
|
const post = (
|
|
shortcode: string,
|
|
{
|
|
mediaType = 'REEL',
|
|
mediaUrl = `https://cdn.example/${shortcode}.mp4`,
|
|
timestamp = '2026-09-20T12:00:00.000Z',
|
|
permalink,
|
|
}: {
|
|
mediaType?: string
|
|
mediaUrl?: string | null
|
|
timestamp?: string
|
|
permalink?: string
|
|
} = {},
|
|
) => ({
|
|
id: shortcode,
|
|
permalink: permalink ?? `https://www.instagram.com/reel/${shortcode}/`,
|
|
mediaType,
|
|
mediaUrl,
|
|
timestamp,
|
|
})
|
|
|
|
describe('planContentPieceProfileWindow', () => {
|
|
const from = '2026-08-01T03:00:00.000Z'
|
|
const to = '2026-09-21T03:00:00.000Z'
|
|
|
|
it('keeps the window novelties, newest first, and classifies each link-only kind', () => {
|
|
const plan = planContentPieceProfileWindow({
|
|
posts: [
|
|
post('NOVO'),
|
|
post('CARROSSEL', { mediaType: 'CAROUSEL_ALBUM', mediaUrl: null }),
|
|
post('PROTEGIDO', { mediaUrl: null }),
|
|
],
|
|
from,
|
|
to,
|
|
})
|
|
|
|
expect(plan.found).toBe(3)
|
|
expect(plan.existingCount).toBe(0)
|
|
expect(plan.outsideWindow).toBe(0)
|
|
expect(plan.malformed).toBe(0)
|
|
expect(plan.window).toEqual({ from, to })
|
|
expect(plan.candidates.map((candidate) => candidate.shortcode)).toEqual([
|
|
'NOVO',
|
|
'CARROSSEL',
|
|
'PROTEGIDO',
|
|
])
|
|
expect(plan.candidates.map((candidate) => candidate.linkOnlyReason)).toEqual([
|
|
null,
|
|
'carrossel',
|
|
'indisponivel',
|
|
])
|
|
})
|
|
|
|
it('drops what falls outside the window, the malformed and the feed twins', () => {
|
|
const plan = planContentPieceProfileWindow({
|
|
posts: [
|
|
post('JA', { permalink: 'https://www.instagram.com/reel/JA/' }),
|
|
post('ANTES', { timestamp: '2026-07-31T23:59:59.000Z' }),
|
|
post('DEPOIS', { timestamp: '2026-09-22T00:00:00.000Z' }),
|
|
post('SEMID', { permalink: 'https://example.com/no-id/' }),
|
|
post('REPETIDA'),
|
|
post('REPETIDA', { permalink: 'https://www.instagram.com/p/REPETIDA/' }),
|
|
],
|
|
existingSourceUrls: ['https://www.instagram.com/p/JA/'],
|
|
from,
|
|
to,
|
|
})
|
|
|
|
expect(plan.found).toBe(3)
|
|
expect(plan.existingCount).toBe(1)
|
|
expect(plan.existingShortcodes).toEqual(['JA'])
|
|
expect(plan.outsideWindow).toBe(2)
|
|
expect(plan.malformed).toBe(1)
|
|
expect(plan.feedDuplicates).toBe(1)
|
|
expect(plan.candidates.map((candidate) => candidate.shortcode)).toEqual(['REPETIDA'])
|
|
})
|
|
|
|
it('treats an unbounded window as the whole feed (the recency mode)', () => {
|
|
const plan = planContentPieceProfileWindow({
|
|
posts: [
|
|
post('ANTIGA', { timestamp: '2020-01-01T00:00:00.000Z' }),
|
|
post('NOVA', { timestamp: '2026-09-20T12:00:00.000Z' }),
|
|
],
|
|
})
|
|
|
|
expect(plan.window).toEqual({ from: null, to: null })
|
|
expect(plan.found).toBe(2)
|
|
expect(plan.outsideWindow).toBe(0)
|
|
expect(plan.candidates.map((candidate) => candidate.shortcode)).toEqual(['ANTIGA', 'NOVA'])
|
|
})
|
|
|
|
it('refuses an invalid bound', () => {
|
|
expect(() => planContentPieceProfileWindow({ posts: [], from: 'ontem' })).toThrow(/janela/)
|
|
expect(() => planContentPieceProfileWindow({ posts: [], to: 'amanhã' })).toThrow(/janela/)
|
|
})
|
|
})
|
|
|
|
describe('contentPieceProfilePeriodError', () => {
|
|
const today = '2026-09-29'
|
|
|
|
it('accepts a real, ordered, non-future period', () => {
|
|
expect(contentPieceProfilePeriodError({ since: '2026-08-01', today })).toBeNull()
|
|
expect(
|
|
contentPieceProfilePeriodError({ since: '2026-08-01', until: '2026-08-31', today }),
|
|
).toBeNull()
|
|
expect(contentPieceProfilePeriodError({ since: today, today })).toBeNull()
|
|
})
|
|
|
|
it('rejects a malformed or impossible date', () => {
|
|
expect(contentPieceProfilePeriodError({ since: '01/08/2026', today })).toBe(
|
|
'Informe uma data inicial válida.',
|
|
)
|
|
expect(contentPieceProfilePeriodError({ since: '2026-02-30', today })).toBe(
|
|
'Informe uma data inicial válida.',
|
|
)
|
|
expect(
|
|
contentPieceProfilePeriodError({ since: '2026-08-01', until: '2026-02-30', today }),
|
|
).toBe('Informe uma data final válida.')
|
|
})
|
|
|
|
it('rejects an inverted or future period', () => {
|
|
expect(
|
|
contentPieceProfilePeriodError({ since: '2026-08-31', until: '2026-08-01', today }),
|
|
).toBe('A data final não pode ser anterior à data inicial.')
|
|
expect(contentPieceProfilePeriodError({ since: '2026-10-01', today })).toBe(
|
|
'A data inicial não pode estar no futuro.',
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('contentPieceProfilePeriodBounds', () => {
|
|
it('resolves the start of the since day and the exclusive end of the until day in Bahia', () => {
|
|
expect(contentPieceProfilePeriodBounds({ since: '2026-08-01' })).toEqual({
|
|
fromIso: '2026-08-01T03:00:00.000Z',
|
|
toIso: null,
|
|
})
|
|
expect(contentPieceProfilePeriodBounds({ since: '2026-08-01', until: '2026-08-31' })).toEqual({
|
|
fromIso: '2026-08-01T03:00:00.000Z',
|
|
toIso: '2026-09-01T03:00:00.000Z',
|
|
})
|
|
})
|
|
|
|
it('refuses an impossible date defensively', () => {
|
|
expect(() => contentPieceProfilePeriodBounds({ since: '2026-13-01' })).toThrow(
|
|
'Informe uma data inicial válida.',
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('contentPieceProfileWindowStartReached', () => {
|
|
const fromIso = '2026-08-01T03:00:00.000Z'
|
|
|
|
it('is reached when the walk got a post older than the window start', () => {
|
|
expect(
|
|
contentPieceProfileWindowStartReached({
|
|
posts: [post('NOVA'), post('ANTES', { timestamp: '2026-07-31T23:59:59.000Z' })],
|
|
fromIso,
|
|
}),
|
|
).toBe(true)
|
|
})
|
|
|
|
it('is not reached when every post is still inside the window', () => {
|
|
expect(contentPieceProfileWindowStartReached({ posts: [post('NOVA')], fromIso })).toBe(false)
|
|
expect(contentPieceProfileWindowStartReached({ posts: [], fromIso })).toBe(false)
|
|
})
|
|
|
|
it('is reached when there is no bound to reach', () => {
|
|
expect(contentPieceProfileWindowStartReached({ posts: [], fromIso: null })).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('contentPieceProfileWindowLabel', () => {
|
|
it('labels the recency and the civil period', () => {
|
|
expect(contentPieceProfileWindowLabel({ mode: 'recent' })).toBe('Recentes')
|
|
expect(contentPieceProfileWindowLabel({ mode: 'period', since: '2026-08-01' })).toBe(
|
|
'01/08/2026 → hoje',
|
|
)
|
|
expect(
|
|
contentPieceProfileWindowLabel({
|
|
mode: 'period',
|
|
since: '2026-08-01',
|
|
until: '2026-08-31',
|
|
}),
|
|
).toBe('01/08/2026 → 31/08/2026')
|
|
})
|
|
})
|
|
|
|
describe('contentPieceProfileFeedIdentityUrls', () => {
|
|
it('spells every canonical kind of each parseable post once', () => {
|
|
expect(
|
|
contentPieceProfileFeedIdentityUrls([
|
|
post('A'),
|
|
post('B', { permalink: 'https://www.instagram.com/p/B/' }),
|
|
post('A', { permalink: 'https://www.instagram.com/p/A/' }),
|
|
post('X', { permalink: 'https://example.com/x/' }),
|
|
]),
|
|
).toEqual([
|
|
'https://www.instagram.com/p/A/',
|
|
'https://www.instagram.com/reel/A/',
|
|
'https://www.instagram.com/tv/A/',
|
|
'https://www.instagram.com/p/B/',
|
|
'https://www.instagram.com/reel/B/',
|
|
'https://www.instagram.com/tv/B/',
|
|
])
|
|
})
|
|
})
|