mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
223 lines
8.5 KiB
TypeScript
223 lines
8.5 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import {
|
|
canonicalWebSpeechUrl,
|
|
parseWebSpeechBatch,
|
|
parseWebSpeechFinding,
|
|
webSpeechAtFromPublishedAt,
|
|
webSpeechCreditLabel,
|
|
webSpeechDisplayTitle,
|
|
webSpeechDownloadFilename,
|
|
webSpeechMediaKind,
|
|
webSpeechPlatformLabel,
|
|
webSpeechSourceKey,
|
|
} from '@/lib/webSpeech'
|
|
|
|
// C215 — the pure web-speech contract: identity (sourceKey), canonical URL and
|
|
// the finding-batch validation that fails one entry without killing the batch.
|
|
|
|
describe('canonicalWebSpeechUrl', () => {
|
|
it('collapses every YouTube shape to the watch URL', () => {
|
|
expect(canonicalWebSpeechUrl('youtube', 'https://youtu.be/dQw4w9WgXcQ?si=track')).toBe(
|
|
'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
|
|
)
|
|
expect(
|
|
canonicalWebSpeechUrl('youtube', 'https://www.youtube.com/shorts/dQw4w9WgXcQ?feature=share'),
|
|
).toBe('https://www.youtube.com/watch?v=dQw4w9WgXcQ')
|
|
expect(
|
|
canonicalWebSpeechUrl('youtube', 'https://m.youtube.com/watch?v=dQw4w9WgXcQ&t=30s'),
|
|
).toBe('https://www.youtube.com/watch?v=dQw4w9WgXcQ')
|
|
expect(canonicalWebSpeechUrl('youtube', 'https://www.youtube.com/live/dQw4w9WgXcQ')).toBe(
|
|
'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
|
|
)
|
|
})
|
|
|
|
it('collapses Instagram post shapes and normalizes reels', () => {
|
|
expect(
|
|
canonicalWebSpeechUrl('instagram', 'https://www.instagram.com/reels/ABC123/?igsh=xyz'),
|
|
).toBe('https://www.instagram.com/reel/ABC123/')
|
|
expect(canonicalWebSpeechUrl('instagram', 'https://instagram.com/p/ABC123')).toBe(
|
|
'https://www.instagram.com/p/ABC123/',
|
|
)
|
|
})
|
|
|
|
it('drops tracking params, the fragment and the trailing slash for direct audio', () => {
|
|
expect(
|
|
canonicalWebSpeechUrl(
|
|
'radio',
|
|
'https://RadioBahia.com.br/audio/entrevista.mp3/?utm_source=share&id=7#t=1',
|
|
),
|
|
).toBe('https://radiobahia.com.br/audio/entrevista.mp3?id=7')
|
|
})
|
|
|
|
it('keeps an unparseable URL trimmed instead of inventing one', () => {
|
|
expect(canonicalWebSpeechUrl('audio', ' nao-e-url ')).toBe('nao-e-url')
|
|
})
|
|
})
|
|
|
|
describe('webSpeechSourceKey', () => {
|
|
it('prefers the external id and falls back to the canonical URL', () => {
|
|
expect(
|
|
webSpeechSourceKey({
|
|
platform: 'youtube',
|
|
url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
|
|
externalId: 'dQw4w9WgXcQ',
|
|
}),
|
|
).toBe('web:youtube:dQw4w9WgXcQ')
|
|
expect(
|
|
webSpeechSourceKey({ platform: 'audio', url: 'https://radio.example/a.mp3?utm_source=x' }),
|
|
).toBe('web:audio:https://radio.example/a.mp3')
|
|
})
|
|
|
|
it('is stable across YouTube URL shapes (same identity, no duplicate row)', () => {
|
|
const short = webSpeechSourceKey({ platform: 'youtube', url: 'https://youtu.be/dQw4w9WgXcQ' })
|
|
const long = webSpeechSourceKey({
|
|
platform: 'youtube',
|
|
url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ&t=90',
|
|
})
|
|
expect(short).toBe(long)
|
|
expect(short).toBe('web:youtube:https://www.youtube.com/watch?v=dQw4w9WgXcQ')
|
|
})
|
|
})
|
|
|
|
describe('webSpeechAtFromPublishedAt', () => {
|
|
it('normalizes date-only and converts an explicit timezone to Brasília', () => {
|
|
expect(webSpeechAtFromPublishedAt('2026-09-20')).toBe('2026-09-20T00:00')
|
|
// 14:35Z is 11:35 in America/Bahia (UTC-3, no DST).
|
|
expect(webSpeechAtFromPublishedAt('2026-09-20T14:35:00Z')).toBe('2026-09-20T11:35')
|
|
expect(webSpeechAtFromPublishedAt('2026-09-20T14:35:00-03:00')).toBe('2026-09-20T14:35')
|
|
expect(webSpeechAtFromPublishedAt('2026-09-20 14:35')).toBe('2026-09-20T14:35')
|
|
})
|
|
|
|
it('returns null for an unrecognized or impossible date', () => {
|
|
expect(webSpeechAtFromPublishedAt('20/09/2026')).toBeNull()
|
|
expect(webSpeechAtFromPublishedAt('2026-09-20lixo')).toBeNull()
|
|
expect(webSpeechAtFromPublishedAt('2026-99-99')).toBeNull()
|
|
expect(webSpeechAtFromPublishedAt('2026-09-20T25:00')).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('parseWebSpeechFinding', () => {
|
|
const validYoutube = {
|
|
platform: 'youtube',
|
|
url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
|
|
publishedAt: '2026-09-20',
|
|
}
|
|
|
|
it('accepts a YouTube finding without mediaUrl', () => {
|
|
const parsed = parseWebSpeechFinding(validYoutube)
|
|
expect(parsed.ok).toBe(true)
|
|
})
|
|
|
|
it('requires mediaUrl for radio/audio and publishedAt everywhere', () => {
|
|
const radio = parseWebSpeechFinding({ ...validYoutube, platform: 'radio' })
|
|
expect(radio.ok).toBe(false)
|
|
if (!radio.ok) expect(radio.error).toContain('mediaUrl')
|
|
|
|
const noDate = parseWebSpeechFinding({ ...validYoutube, publishedAt: undefined })
|
|
expect(noDate.ok).toBe(false)
|
|
|
|
const badDate = parseWebSpeechFinding({ ...validYoutube, publishedAt: 'ontem' })
|
|
expect(badDate.ok).toBe(false)
|
|
})
|
|
|
|
it('rejects an unknown platform and a non-URL', () => {
|
|
expect(parseWebSpeechFinding({ ...validYoutube, platform: 'tiktok' }).ok).toBe(false)
|
|
expect(parseWebSpeechFinding({ ...validYoutube, url: 'nada' }).ok).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('parseWebSpeechBatch', () => {
|
|
it('validates only the envelope and keeps the findings raw', () => {
|
|
const parsed = parseWebSpeechBatch({ generatedAt: '2026-09-24T10:00:00Z', findings: [{}] })
|
|
expect(parsed.ok).toBe(true)
|
|
if (parsed.ok) {
|
|
expect(parsed.batch.generatedAt).toBe('2026-09-24T10:00:00Z')
|
|
expect(parsed.batch.findings).toEqual([{}])
|
|
}
|
|
})
|
|
|
|
it('fails closed on a malformed envelope', () => {
|
|
expect(parseWebSpeechBatch({}).ok).toBe(false)
|
|
expect(parseWebSpeechBatch({ findings: 'nope' }).ok).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('webSpeechPlatformLabel', () => {
|
|
it('owns the pt-BR labels the acervo shows', () => {
|
|
expect(webSpeechPlatformLabel('youtube')).toBe('YouTube')
|
|
expect(webSpeechPlatformLabel('radio')).toBe('Rádio')
|
|
})
|
|
|
|
// C216 — the pill of a row without a known platform is honest, not blank.
|
|
it('falls back for a missing or unknown platform (C216)', () => {
|
|
expect(webSpeechPlatformLabel(null)).toBe('Outra plataforma')
|
|
expect(webSpeechPlatformLabel(undefined)).toBe('Outra plataforma')
|
|
})
|
|
})
|
|
|
|
// C216 (closes the C215 S4 defer) — the download name of the mirrored file.
|
|
describe('webSpeechDownloadFilename', () => {
|
|
it('keeps the stored extension and the title in the download name', () => {
|
|
expect(
|
|
webSpeechDownloadFilename({
|
|
title: 'Entrevista sobre a saúde pública',
|
|
storedFilename: 'source.mp4',
|
|
}),
|
|
).toBe('Entrevista sobre a saúde pública.mp4')
|
|
})
|
|
|
|
it('strips control characters and path separators', () => {
|
|
expect(
|
|
webSpeechDownloadFilename({
|
|
title: 'Fala "sobre"\n o SUS/educação',
|
|
storedFilename: 'source.mp3',
|
|
}),
|
|
).toBe('Fala sobre o SUS educação.mp3')
|
|
})
|
|
|
|
it('caps the length and never leaves a trailing dot', () => {
|
|
const filename = webSpeechDownloadFilename({
|
|
title: `${'a'.repeat(300)}.`,
|
|
storedFilename: 'source.mp4',
|
|
})
|
|
expect(filename).not.toBeNull()
|
|
expect(filename?.endsWith('.mp4')).toBe(true)
|
|
expect(filename?.length).toBeLessThanOrEqual(120)
|
|
})
|
|
|
|
it('falls back to the stored filename when the title leaves nothing readable', () => {
|
|
expect(webSpeechDownloadFilename({ title: ' ', storedFilename: 'source.mp3' })).toBeNull()
|
|
expect(webSpeechDownloadFilename({ title: 'Fala', storedFilename: null })).toBeNull()
|
|
})
|
|
})
|
|
|
|
// C217 — the pure web-speech rules the cut shares with the detail/list VMs.
|
|
describe('webSpeechMediaKind', () => {
|
|
it('reads the mirrored mime: audio gets the native audio control', () => {
|
|
expect(webSpeechMediaKind('audio/mpeg')).toBe('audio')
|
|
expect(webSpeechMediaKind('audio/mp4')).toBe('audio')
|
|
expect(webSpeechMediaKind('video/mp4')).toBe('video')
|
|
expect(webSpeechMediaKind(null)).toBe('video')
|
|
expect(webSpeechMediaKind(undefined)).toBe('video')
|
|
})
|
|
})
|
|
|
|
describe('webSpeechDisplayTitle', () => {
|
|
it('prefers the platform title and degrades to the row id', () => {
|
|
expect(webSpeechDisplayTitle({ id: 9, title: ' Entrevista ' })).toBe('Entrevista')
|
|
expect(webSpeechDisplayTitle({ id: 9, title: ' ' })).toBe('Fala da internet #9')
|
|
expect(webSpeechDisplayTitle({ id: 9 })).toBe('Fala da internet #9')
|
|
})
|
|
})
|
|
|
|
describe('webSpeechCreditLabel', () => {
|
|
it('composes the platform with the channel and never claims a missing source', () => {
|
|
expect(webSpeechCreditLabel({ platform: 'youtube', channel: 'Canal do Solla' })).toBe(
|
|
'YouTube · Canal do Solla',
|
|
)
|
|
expect(webSpeechCreditLabel({ platform: 'radio', channel: ' ' })).toBe('Rádio')
|
|
expect(webSpeechCreditLabel({ platform: null, channel: 'Canal' })).toBeNull()
|
|
expect(webSpeechCreditLabel({})).toBeNull()
|
|
})
|
|
})
|