← back to Rentv Adintel
test/export-filter.test.js
104 lines
'use strict';
/**
* Tests for rights-aware export filtering (spec §28, §6.14, §6.18).
* The module is being written concurrently by a teammate; import it
* defensively and t.skip() every case if it isn't present yet so
* `node --test` never hard-crashes.
*
* Rule under test: exports must honor suppression, do-not-contact, private
* notes, and source rights — those rows/fields must be filtered OUT of any
* exported dataset (§28 "Exports must honor suppression, do-not-contact,
* private notes, source rights").
*/
const test = require('node:test');
const assert = require('node:assert');
let rights;
try {
rights = require('../src/export/rights');
} catch (_e) {
rights = null;
}
test('applyExportRights excludes do_not_contact contacts', (t) => {
if (!rights || !rights.applyExportRights) return t.skip('export/rights not present yet');
const rows = [
{ id: 1, value: 'ok@acme.com', do_not_contact: false, export_allowed: true },
{ id: 2, value: 'stop@acme.com', do_not_contact: true, export_allowed: true },
];
const out = rights.applyExportRights(rows, 'contacts');
const ids = out.map((r) => r.id);
assert.ok(ids.includes(1));
assert.ok(!ids.includes(2), 'do_not_contact contact must be excluded');
});
test('applyExportRights excludes export_allowed=false contacts', (t) => {
if (!rights || !rights.applyExportRights) return t.skip('export/rights not present yet');
const rows = [
{ id: 1, value: 'ok@acme.com', do_not_contact: false, export_allowed: true },
{ id: 3, value: 'internal@acme.com', do_not_contact: false, export_allowed: false },
];
const out = rights.applyExportRights(rows, 'contacts');
const ids = out.map((r) => r.id);
assert.ok(ids.includes(1));
assert.ok(!ids.includes(3), 'export_allowed=false contact must be excluded');
});
test('applyExportRights excludes private notes', (t) => {
if (!rights || !rights.applyExportRights) return t.skip('export/rights not present yet');
const rows = [
{ id: 10, body: 'public note', is_private: false },
{ id: 11, body: 'private note', is_private: true },
];
const out = rights.applyExportRights(rows, 'notes');
const ids = out.map((r) => r.id);
assert.ok(ids.includes(10));
assert.ok(!ids.includes(11), 'is_private note must be excluded from export');
});
test('applyExportRights excludes rows for a suppressed organization', (t) => {
if (!rights || !rights.applyExportRights) return t.skip('export/rights not present yet');
const rows = [
{ id: 'org-keep', name: 'Keep Co' },
{ id: 'org-suppressed', name: 'Suppressed Co' },
];
// Suppression is driven by a suppression-request set passed in ctx (§6.14).
// filterOrganizations matches the org's own id against suppressedOrgIds.
const ctx = { suppressedOrgIds: new Set(['org-suppressed']) };
const out = rights.applyExportRights(rows, 'organizations', ctx);
const ids = out.map((r) => r.id);
assert.ok(ids.includes('org-keep'));
assert.ok(!ids.includes('org-suppressed'), 'suppressed organization must be excluded from export');
});
test('applyExportRights classifies internal-only creatives as link-only (bytes omitted)', (t) => {
if (!rights || !rights.applyExportRights) return t.skip('export/rights not present yet');
const rows = [
{ id: 30, organization_id: 'a', file_name: 'ad.png', rights_status: 'EXPORT_ALLOWED' },
{ id: 31, organization_id: 'a', file_name: 'internal.png', rights_status: 'INTERNAL_EVIDENCE_ONLY' },
];
const out = rights.applyExportRights(rows, 'creative_assets', {});
const byId = Object.fromEntries(out.map((r) => [r.id, r]));
// Export-allowed → file bytes bundled; internal-only → metadata kept but bytes omitted.
assert.strictEqual(byId[30]._exportClass, 'include');
assert.strictEqual(byId[31]._exportClass, 'link_only');
assert.notStrictEqual(byId[31]._exportClass, 'include', 'internal-only bytes must not be exported');
});
test('classifyAssetForExport never exports UNKNOWN-rights image bytes', (t) => {
if (!rights || !rights.classifyAssetForExport) return t.skip('classifyAssetForExport not present yet');
assert.strictEqual(rights.classifyAssetForExport({ rights_status: 'EXPORT_ALLOWED' }), 'include');
assert.notStrictEqual(rights.classifyAssetForExport({ rights_status: 'UNKNOWN' }), 'include');
assert.notStrictEqual(rights.classifyAssetForExport({ rights_status: 'INTERNAL_EVIDENCE_ONLY' }), 'include');
});
test('applyExportRights returns an array and does not mutate the input', (t) => {
if (!rights || !rights.applyExportRights) return t.skip('export/rights not present yet');
const rows = [{ id: 1, do_not_contact: false, export_allowed: true }];
const copy = JSON.parse(JSON.stringify(rows));
const out = rights.applyExportRights(rows, 'contacts', {});
assert.ok(Array.isArray(out));
assert.deepStrictEqual(rows, copy, 'input rows must not be mutated');
});