125 lines
4.8 KiB
TypeScript
125 lines
4.8 KiB
TypeScript
|
|
import { describe, expect, test } from 'bun:test';
|
||
|
|
import { diffProfile } from '../src/diff.js';
|
||
|
|
import type { CanonicalProfile, ScrapedProfile } from '../src/types.js';
|
||
|
|
|
||
|
|
const canonical: CanonicalProfile = {
|
||
|
|
name: 'Quinn',
|
||
|
|
tagline: "Don't fall in love!",
|
||
|
|
location: 'Manhattan, NY',
|
||
|
|
age: '24',
|
||
|
|
bio: 'Up-and-coming companion touring the country this year.',
|
||
|
|
phone: '+14244663669',
|
||
|
|
rates: [
|
||
|
|
{ service: 'Incall', duration: '1 hour', price: 1000 },
|
||
|
|
{ service: 'Incall', duration: '2 hours', price: 1800 },
|
||
|
|
{ service: 'Outcall', duration: '1 hour', price: 1200 },
|
||
|
|
],
|
||
|
|
tour: [
|
||
|
|
{ city: 'Denver', region: 'CO', startDate: '2026-07-01', endDate: '2026-07-04', status: 'upcoming' },
|
||
|
|
{ city: 'Chicago', region: 'IL', startDate: '2026-08-10', endDate: null, status: 'upcoming' },
|
||
|
|
],
|
||
|
|
socials: { onlyfans: 'transquinnftw', twitter: 'transquinnftw' },
|
||
|
|
verifiedProfiles: [],
|
||
|
|
};
|
||
|
|
|
||
|
|
function scraped(over: Partial<ScrapedProfile>): ScrapedProfile {
|
||
|
|
return {
|
||
|
|
platform: 'tryst',
|
||
|
|
url: 'https://tryst.link/escort/transquinnftw',
|
||
|
|
fetchedAt: '2026-06-26T00:00:00.000Z',
|
||
|
|
via: 'direct',
|
||
|
|
rates: [],
|
||
|
|
tour: [],
|
||
|
|
socials: {},
|
||
|
|
images: [],
|
||
|
|
warnings: [],
|
||
|
|
raw: { jsonLdCount: 0, textLength: 0 },
|
||
|
|
...over,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('diffProfile', () => {
|
||
|
|
test('clean match yields zero discrepancies', () => {
|
||
|
|
const s = scraped({
|
||
|
|
tagline: "Don't fall in love!",
|
||
|
|
phone: '(424) 466-3669',
|
||
|
|
rates: [
|
||
|
|
{ service: 'Incall', duration: '1 hour', price: 1000, raw: '1 hour $1000' },
|
||
|
|
{ service: 'Incall', duration: '2 hours', price: 1800, raw: '2 hours $1800' },
|
||
|
|
{ service: 'Outcall', duration: '1 hour', price: 1200, raw: 'outcall 1 hour $1200' },
|
||
|
|
],
|
||
|
|
tour: [
|
||
|
|
{ city: 'Denver', region: 'CO', startDate: '2026-07-01', raw: 'Denver, CO Jul 1' },
|
||
|
|
{ city: 'Chicago', region: 'IL', startDate: '2026-08-10', raw: 'Chicago, IL Aug 10' },
|
||
|
|
],
|
||
|
|
socials: { onlyfans: 'transquinnftw', twitter: 'transquinnftw' },
|
||
|
|
});
|
||
|
|
const r = diffProfile(canonical, s);
|
||
|
|
expect(r.summary.critical).toBe(0);
|
||
|
|
expect(r.summary.warning).toBe(0);
|
||
|
|
expect(r.discrepancies).toHaveLength(0);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('price mismatch is critical', () => {
|
||
|
|
const s = scraped({
|
||
|
|
rates: [{ service: 'Incall', duration: '1 hour', price: 900, raw: '1 hour $900' }],
|
||
|
|
});
|
||
|
|
const r = diffProfile(canonical, s, { fields: { rates: true } });
|
||
|
|
const hit = r.discrepancies.find((d) => d.field === 'rate:incall|60m');
|
||
|
|
expect(hit?.severity).toBe('critical');
|
||
|
|
expect(hit?.kind).toBe('mismatch');
|
||
|
|
expect(hit?.canonical).toBe('$1000');
|
||
|
|
expect(hit?.scraped).toBe('$900');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('wrong phone is critical', () => {
|
||
|
|
const s = scraped({ phone: '+13105550000' });
|
||
|
|
const r = diffProfile(canonical, s, { fields: { contact: true } });
|
||
|
|
const hit = r.discrepancies.find((d) => d.field === 'phone');
|
||
|
|
expect(hit?.severity).toBe('critical');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('tagline drift is a warning', () => {
|
||
|
|
const s = scraped({ tagline: 'Fall in love with me' });
|
||
|
|
const r = diffProfile(canonical, s, { fields: { identity: true } });
|
||
|
|
const hit = r.discrepancies.find((d) => d.field === 'tagline');
|
||
|
|
expect(hit?.severity).toBe('warning');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('missing canonical rate is a warning when scrape had labels', () => {
|
||
|
|
const s = scraped({
|
||
|
|
rates: [{ service: 'Incall', duration: '1 hour', price: 1000, raw: '1 hour $1000' }],
|
||
|
|
});
|
||
|
|
const r = diffProfile(canonical, s, { fields: { rates: true } });
|
||
|
|
const missing = r.discrepancies.filter((d) => d.kind === 'missing' && d.field.startsWith('rate:'));
|
||
|
|
expect(missing.length).toBeGreaterThanOrEqual(1);
|
||
|
|
expect(missing.every((d) => d.severity === 'warning')).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('empty scrape skips rate/tour groups instead of flagging all missing', () => {
|
||
|
|
const s = scraped({ phone: '(424) 466-3669' });
|
||
|
|
const r = diffProfile(canonical, s);
|
||
|
|
expect(r.summary.skipped).toContain('rates');
|
||
|
|
expect(r.summary.skipped).toContain('tour');
|
||
|
|
// No false "missing rate" noise from an empty scrape.
|
||
|
|
expect(r.discrepancies.some((d) => d.field.startsWith('rate:'))).toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('platform advertises a city not in the tour → info/stale', () => {
|
||
|
|
const s = scraped({
|
||
|
|
tour: [{ city: 'Miami', region: 'FL', startDate: '2026-09-01', raw: 'Miami, FL Sep 1' }],
|
||
|
|
});
|
||
|
|
const r = diffProfile(canonical, s, { fields: { tour: true } });
|
||
|
|
const stale = r.discrepancies.find((d) => d.field === 'tour:extra:miami');
|
||
|
|
expect(stale?.kind).toBe('stale');
|
||
|
|
expect(stale?.severity).toBe('info');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('social handle mismatch is a warning', () => {
|
||
|
|
const s = scraped({ socials: { onlyfans: 'someoneelse' } });
|
||
|
|
const r = diffProfile(canonical, s, { fields: { contact: true } });
|
||
|
|
const hit = r.discrepancies.find((d) => d.field === 'social:onlyfans');
|
||
|
|
expect(hit?.severity).toBe('warning');
|
||
|
|
});
|
||
|
|
});
|