diff --git a/ads.js b/ads.js index 48c95c0..a132a92 100644 Binary files a/ads.js and b/ads.js differ diff --git a/public/my.html b/public/my.html index 81d49f6..8274de8 100644 --- a/public/my.html +++ b/public/my.html @@ -1037,7 +1037,7 @@ - + diff --git a/qa/login-slot.mjs b/qa/login-slot.mjs new file mode 100644 index 0000000..49ddd84 --- /dev/null +++ b/qa/login-slot.mjs @@ -0,0 +1,42 @@ +// Login ads are a flat 30-day slot, not a daily meter. +// +// The old model charged 100 credits for every day a campaign was shown, with no end date. +// That produced 74-day queues, and every new campaign added 100/day of charges while adding +// no inventory — on 18 Sep, 15 campaigns were charged 1,500 credits against a platform that +// had produced 367 login impressions in its whole life. +import { createRequire } from 'node:module'; +const require = createRequire(import.meta.url); +const ads = require('../ads.js'); + +const ok = [], bad = []; +const t = (n, c, extra) => { (c ? ok : bad).push(n + (c || !extra ? '' : ' -> ' + extra)); }; + +const r = ads.rates(); +t('a slot price is configured', Number(r.loginSlotCredits) > 0, String(r.loginSlotCredits)); +t('a slot length is configured', Number(r.loginSlotDays) > 0, String(r.loginSlotDays)); +t('the slot is 3,000 credits for 30 days', r.loginSlotCredits === 3000 && r.loginSlotDays === 30, + r.loginSlotCredits + ' / ' + r.loginSlotDays); + +// the price matches what 30 days used to cost, so nobody pays more than before +t('same total as 30 days at the old daily rate', + r.loginSlotCredits === 30 * r.loginCreditsPerDay, + r.loginSlotCredits + ' vs ' + (30 * r.loginCreditsPerDay)); + +// the source guarantees that matter, checked directly — these are the behaviours that stop +// a slot draining against days when the gate produced nothing +const src = require('fs').readFileSync(new URL('../ads.js', import.meta.url), 'utf8'); + +t('buying a slot charges the whole price up front', + /Login-ad slot \(/.test(src) && /chargeCredits\(campaign\.id, v\.c\.budget, 'Login-ad slot/.test(src)); +t('a slot gets an end date at purchase', + /out\.expires = Date\.now\(\) \+ days \* 86400000/.test(src)); +t('the MySQL daily meter skips slot campaigns', + /expires IS NULL AND \(last_day_charged/.test(src)); +t('the JSON daily meter skips slot campaigns', + /if \(c\.expires\) continue; \/\/ slot buy/.test(src)); +t('a short budget is refused with the slot price named', + /A login-ad slot is ' \+ cost \+ ' credits for ' \+ days \+ ' days/.test(src)); + +console.log('PASS ' + ok.length); +for (const b of bad) console.log('FAIL ' + b); +process.exit(bad.length ? 1 : 0);