Quinn resolved the two source contradictions (2026-06-27): - price: one rate everywhere = $1000 (FACT_SHEET). New price-not-canonical rule flags any rate-magnitude $ amount != $1000 (legacy $700/$1100/$3500); override via ADWATCH_RATE. Verified: tryst.txt flags $3,500/$5,000, not $1,000. - domain: prefer the long transquinnftw.com; tsquinn.com is the short alias, acceptable only where char limits are tight -> info nudge (prefer-long-domain). Rule model gains an optional detect() for parse-based rules (price). CONTRADICTIONS now empty. dedup listAdCopyPlatforms (.txt+.html). 61 tests pass; typecheck clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
59 lines
3 KiB
TypeScript
59 lines
3 KiB
TypeScript
import { describe, expect, test } from 'bun:test';
|
|
import { checkCompliance } from '../src/compliance.js';
|
|
|
|
const has = (text: string, platform: string | undefined, ruleId: string): boolean =>
|
|
checkCompliance(text, { platform }).some((v) => v.rule === ruleId);
|
|
|
|
describe('checkCompliance', () => {
|
|
test('flags "nerd", not "geek"', () => {
|
|
expect(has('total nerd behind closed doors', undefined, 'geek-not-nerd')).toBe(true);
|
|
expect(has('total geek behind closed doors', undefined, 'geek-not-nerd')).toBe(false);
|
|
});
|
|
|
|
test('flags the banned phrase', () => {
|
|
expect(has('Williamsburg, where I like to stay', undefined, 'banned-phrase-where-i-like-to-stay')).toBe(true);
|
|
});
|
|
|
|
test('flags twitter/x links but not a bare @handle (OF/IG handle is fine)', () => {
|
|
expect(has('follow https://twitter.com/TransQuinnFTW', undefined, 'suspended-twitter-link')).toBe(true);
|
|
expect(has('x.com/transquinnftw', undefined, 'suspended-twitter-link')).toBe(true);
|
|
expect(has('OnlyFans @transquinnftw', undefined, 'suspended-twitter-link')).toBe(false);
|
|
});
|
|
|
|
test('flags Bay Area / old-location geo, not Brooklyn', () => {
|
|
expect(has('based in San Francisco and Napa', undefined, 'bay-area-geo')).toBe(true);
|
|
expect(has('moved from the UWS', undefined, 'bay-area-geo')).toBe(true);
|
|
expect(has('Williamsburg, Brooklyn — NYC', undefined, 'bay-area-geo')).toBe(false);
|
|
});
|
|
|
|
test('Eros emoji rule applies only to eros', () => {
|
|
expect(has('Upscale companionship 💋', 'eros', 'eros-no-emoji')).toBe(true);
|
|
expect(has('Upscale companionship 💋', 'tryst', 'eros-no-emoji')).toBe(false);
|
|
expect(has('Upscale companionship, never rushed.', 'eros', 'eros-no-emoji')).toBe(false);
|
|
});
|
|
|
|
test('clean compliant text yields no violations', () => {
|
|
const clean = 'Williamsburg, Brooklyn — NYC. Total geek behind closed doors. OnlyFans @transquinnftw.';
|
|
expect(checkCompliance(clean, { platform: 'eros' })).toHaveLength(0);
|
|
});
|
|
|
|
test('flags rates that are not $1000, not the canonical rate', () => {
|
|
expect(has('1 hour $700', undefined, 'price-not-canonical')).toBe(true);
|
|
expect(has('overnight $3,500', undefined, 'price-not-canonical')).toBe(true);
|
|
expect(has('$1000/hr 💕', undefined, 'price-not-canonical')).toBe(false);
|
|
expect(has('$1,000 normal NYC pricing', undefined, 'price-not-canonical')).toBe(false);
|
|
// incidental small/large $ amounts are not treated as rates
|
|
expect(has('coffee is $5', undefined, 'price-not-canonical')).toBe(false);
|
|
});
|
|
|
|
test('nudges (info) toward the long domain', () => {
|
|
const v = checkCompliance('book at tsquinn.com', {}).find((x) => x.rule === 'prefer-long-domain');
|
|
expect(v?.severity).toBe('info');
|
|
expect(has('book at transquinnftw.com', undefined, 'prefer-long-domain')).toBe(false);
|
|
});
|
|
|
|
test('reports the offending matches', () => {
|
|
const v = checkCompliance('nerd and another nerd', {}).find((x) => x.rule === 'geek-not-nerd');
|
|
expect(v?.matches).toContain('nerd');
|
|
});
|
|
});
|