Chunk 012: Demand-Radar
tests/databaseMigrations.test.js (lines 1–38)
- Repository
- Demand-Radar
- Path
tests/databaseMigrations.test.js
const test = require('node:test');
const assert = require('node:assert/strict');
const Database = require('better-sqlite3');
const { runMigrations } = require('../src/db/migrations');
test('all migrations can be applied to a fresh in-memory SQLite database', () => {
const db = new Database(':memory:');
db.pragma('foreign_keys = ON');
try {
runMigrations(db);
const tables = db
.prepare("SELECT name FROM sqlite_master WHERE type = 'table'")
.all()
.map(row => row.name);
assert.ok(tables.includes('projects'));
assert.ok(tables.includes('query_plans'));
assert.ok(tables.includes('meta_ads'));
assert.ok(tables.includes('project_ad_relevance'));
assert.ok(tables.includes('offer_families'));
} finally {
db.close();
}
});
test('running migrations twice is idempotent', () => {
const db = new Database(':memory:');
try {
runMigrations(db);
assert.doesNotThrow(() => runMigrations(db));
} finally {
db.close();
}
});
tests/fixtures/discovery-plan.valid.json (lines 1–69)
- Repository
- Demand-Radar
- Path
tests/fixtures/discovery-plan.valid.json
{
"schemaVersion": "1.1",
"capabilityUnderstanding": "Twórca potrafi prowadzić szkolenia online i pracować z zespołami.",
"marketBreadth": {
"country": "PL",
"language": "pl",
"notes": "Przykładowy fixture testowy."
},
"seedQueries": [
{
"id": "seed-1",
"query": "komunikacja w zespole szkolenie",
"rationale": "Bezpośrednio łączy kompetencję z formatem produktu.",
"priority": "high",
"enabled": true,
"source": "LLM_INITIAL"
}
],
"candidateProductTypes": [
{
"id": "type-1",
"label": "Szkolenie online",
"description": "Format zgodny z deklarowanymi możliwościami twórcy."
}
],
"initialTaxonomy": [
{
"id": "tax-1",
"label": "Komunikacja w pracy",
"description": "Obszar startowy do organizowania znalezionych sygnałów."
}
],
"discoveryStrategies": [
{
"id": "strategy-1",
"source": "META_AD_LIBRARY",
"mode": "KEYWORD_SEARCH",
"label": "Keyword search",
"description": "Wyszukiwanie reklam po zatwierdzonych frazach."
}
],
"queryExpansionPolicy": {
"whenToBroaden": "Gdy wyniki są zbyt nieliczne lub zbyt jednorodne.",
"whenToNarrow": "Gdy wyniki są liczne, ale słabo związane z badanym obszarem.",
"maxExpansionRounds": 2
},
"openUnknowns": [
{
"id": "unknown-1",
"question": "Które segmenty odbiorców wykazują najsilniejsze sygnały trakcji?"
}
],
"constraints": [],
"discoveryLimits": {
"maxQueriesPerRun": 10,
"maxAdsPerQuery": 20,
"maxScrollsPerQuery": 5,
"stopAfterScrollsWithoutNewAds": 2,
"maxRuntimeMinutes": 30
},
"saturationCriteria": [
{
"id": "sat-1",
"description": "Zakończ rundę, gdy kolejne zapytania nie przynoszą nowych istotnych ofert."
}
],
"changeSummary": "",
"warnings": []
}
tests/lifecycleClassifier.test.js (lines 1–57)
- Repository
- Demand-Radar
- Path
tests/lifecycleClassifier.test.js
const test = require('node:test');
const assert = require('node:assert/strict');
const {
LONGEVITY_CATEGORIES,
classifyAdLifecycle,
calculateRuntimeDays,
} = require('../src/meta/lifecycleClassifier');
test('calculateRuntimeDays uses now for an active ad', () => {
const runtimeDays = calculateRuntimeDays({
startDate: '2026-01-01T00:00:00.000Z',
endDate: null,
isActive: true,
now: new Date('2026-01-11T00:00:00.000Z'),
});
assert.equal(runtimeDays, 10);
});
test('lifecycle classification respects all configured boundaries', () => {
const now = new Date('2026-08-30T00:00:00.000Z');
const cases = [
[7, LONGEVITY_CATEGORIES.BALON_PROBNY],
[8, LONGEVITY_CATEGORIES.TEST_W_TOKU],
[30, LONGEVITY_CATEGORIES.TEST_W_TOKU],
[31, LONGEVITY_CATEGORIES.ROKUJACA],
[90, LONGEVITY_CATEGORIES.ROKUJACA],
[91, LONGEVITY_CATEGORIES.MOCNA],
[180, LONGEVITY_CATEGORIES.MOCNA],
[181, LONGEVITY_CATEGORIES.EVERGREEN],
];
for (const [days, expected] of cases) {
const start = new Date(now.getTime() - days * 24 * 60 * 60 * 1000);
const result = classifyAdLifecycle({
startDate: start.toISOString(),
isActive: true,
now,
});
assert.equal(result.runtimeDays, days);
assert.equal(result.lifecycleClass, expected);
}
});
test('invalid start date falls back to conservative classification', () => {
const result = classifyAdLifecycle({
startDate: 'not-a-date',
isActive: true,
now: new Date('2026-08-30T00:00:00.000Z'),
});
assert.equal(result.runtimeDays, null);
assert.equal(result.lifecycleClass, LONGEVITY_CATEGORIES.BALON_PROBNY);
assert.equal(result.lifecycleConfidence, 45);
});
tests/offerFamilyClassifier.test.js (lines 1–69)
- Repository
- Demand-Radar
- Path
tests/offerFamilyClassifier.test.js
const test = require('node:test');
const assert = require('node:assert/strict');
const {
canonicalizeUrl,
jaccardSimilarity,
buildOfferFamilies,
} = require('../src/meta/offerFamilyClassifier');
test('canonicalizeUrl removes tracking params but keeps business params', () => {
const result = canonicalizeUrl(
'https://www.example.com/oferta/?utm_source=meta&fbclid=abc&variant=pro#sekcja',
);
assert.equal(result, 'https://example.com/oferta?variant=pro');
});
test('canonicalizeUrl produces stable ordering of query parameters', () => {
const left = canonicalizeUrl('https://example.com/product?b=2&a=1&utm_campaign=x');
const right = canonicalizeUrl('https://www.example.com/product?a=1&b=2');
assert.equal(left, right);
});
test('jaccardSimilarity is deterministic for repeated terms and punctuation', () => {
const score = jaccardSimilarity(
'Webinar: regulacja emocji dla rodziców',
'Regulacja emocji dla rodziców — webinar!',
);
assert.equal(score, 1);
});
test('ads from the same advertiser and canonical destination form one family', () => {
const ads = [
{
metaAdId: 'ad-1',
pageId: 'page-1',
title: 'Kurs regulacji emocji',
bodyText: 'Program online dla rodziców',
destinationUrl: 'https://example.com/kurs?utm_source=meta',
startDate: '2026-01-01',
endDate: '2026-01-10',
isActive: false,
},
{
metaAdId: 'ad-2',
pageId: 'page-1',
title: 'Kurs regulacji emocji',
bodyText: 'Program online dla rodziców',
destinationUrl: 'https://www.example.com/kurs?fbclid=123',
startDate: '2026-01-11',
endDate: null,
isActive: true,
},
];
const result = buildOfferFamilies('project-test', ads, {
now: new Date('2026-02-01T00:00:00.000Z'),
classifierVersion: 'test-v1',
});
assert.equal(result.families.length, 1);
assert.equal(result.families[0].adsCount, 2);
assert.deepEqual(
Object.values(result.canonicalDestinationByAdId),
['https://example.com/kurs', 'https://example.com/kurs'],
);
});