// shouldCapture() decision table — written before the implementation. // // The rule (docs/05 §2): capture when ANY positive signal holds AND NONE of the vetoes // do. Too eager and we hijack page navigations; too shy and we are not a download // manager. Every row here is a case that has to stay pinned. import { describe, expect, it } from 'vitest'; import type { CaptureRules, Headers } from '../../src/shared/protocol/index.js'; import { shouldCapture, type CaptureCandidate } from '../../src/background/capture/rules.js'; const RULES: CaptureRules = { enabled: true, monitoredExtensions: ['zip', 'iso', 'mp4', 'dmg', 'pkg'], monitoredMimeTypes: ['application/octet-stream', 'application/x-iso9660-image', 'video/mp4'], minSizeBytes: 1024 * 1024, // 1 MiB excludedHosts: ['mail.example.com', '*.internal.example.org'], bypassModifier: 'alt', rulesVersion: 1, }; function candidate(over: Partial = {}): CaptureCandidate { return { url: 'https://cdn.example.com/files/report.bin', method: 'GET', statusCode: 200, type: 'other', responseHeaders: {}, requestHeaders: {}, documentUrl: 'https://example.com/downloads', bypassHeld: false, ...over, }; } const h = (o: Record): Headers => o; interface Row { name: string; candidate: CaptureCandidate; capture: boolean; reason: string; rules?: CaptureRules; } const TABLE: Row[] = [ // --- positives --------------------------------------------------------------------- { name: 'Content-Disposition: attachment', candidate: candidate({ url: 'https://cdn.example.com/generate?id=9', responseHeaders: h({ 'content-disposition': 'attachment; filename="q3.csv"', 'content-type': 'text/csv' }), }), capture: true, reason: 'content_disposition', }, { name: 'attachment on a main_frame is still a download, not a navigation', candidate: candidate({ type: 'main_frame', responseHeaders: h({ 'content-disposition': 'attachment; filename="page.html"', 'content-type': 'text/html' }), }), capture: true, reason: 'content_disposition', }, { name: 'monitored file extension', candidate: candidate({ url: 'https://cdn.example.com/a/ubuntu-26.04.iso?sig=abc' }), capture: true, reason: 'monitored_extension', }, { name: 'monitored MIME type (not text/html)', candidate: candidate({ responseHeaders: h({ 'content-type': 'application/octet-stream' }) }), capture: true, reason: 'monitored_mime', }, { name: 'over the size threshold and not a renderable type', candidate: candidate({ responseHeaders: h({ 'content-type': 'application/x-tar', 'content-length': String(8 * 1024 * 1024) }), }), capture: true, reason: 'over_min_size', }, // --- vetoes ---------------------------------------------------------------------- { name: 'capture disabled in rules', candidate: candidate({ url: 'https://cdn.example.com/a.zip' }), rules: { ...RULES, enabled: false }, capture: false, reason: 'capture_disabled', }, { name: 'excluded host (exact)', candidate: candidate({ url: 'https://mail.example.com/attach/a.zip' }), capture: false, reason: 'excluded_host', }, { name: 'excluded host (wildcard)', candidate: candidate({ url: 'https://build07.internal.example.org/artifacts/out.zip' }), capture: false, reason: 'excluded_host', }, { name: 'HTML page navigation', candidate: candidate({ type: 'main_frame', responseHeaders: h({ 'content-type': 'text/html; charset=utf-8', 'content-length': String(4 * 1024 * 1024) }), }), capture: false, reason: 'html_navigation', }, { name: 'blob: document origin', candidate: candidate({ url: 'https://cdn.example.com/a.zip', documentUrl: 'blob:https://example.com/uuid' }), capture: false, reason: 'blob_or_data_origin', }, { name: 'the download URL itself is a blob:', candidate: candidate({ url: 'blob:https://example.com/2b7f-...' }), capture: false, reason: 'blob_or_data_origin', }, { name: 'bypass modifier held', candidate: candidate({ url: 'https://cdn.example.com/a.zip', bypassHeld: true }), capture: false, reason: 'bypass_modifier', }, { name: 'streaming media — HLS manifest MIME', candidate: candidate({ url: 'https://v.example.com/live/index.m3u8', responseHeaders: h({ 'content-type': 'application/vnd.apple.mpegurl' }), }), capture: false, reason: 'streaming_media', }, { name: 'streaming media —