Files
instantadpay/qa/dripoffers-bridge.mjs
T
martbost d4e2f59f9b Member campaigns now go out as click offers on DripOffers
Third syndication rail. The first two send impressions; this one sends
clicks — a real person picks the offer off the offerwall, goes to the
member's link, and has to stay the dwell time before anyone is paid.

Two things it does that the other rails can't:

- It carries geo-targeted campaigns. DripOffers filters on an explicit
  country list, so a campaign aimed at Tier 1 finally reaches an outside
  audience instead of staying on our own site. Tier-3-only campaigns are
  skipped rather than quietly sent worldwide, because an inclusion list
  can't express "everywhere except the other tiers" and widening it would
  deliver exactly the traffic the owner chose to exclude.
- Delivery is counted, not derived. It reads rows from the platform's
  permanent click ledger. It never computes delivery as ordered minus
  remaining, and pausing never zeroes remaining — that pair is what
  charged members for impressions that never ran on Network Ad Space.

Also fixes two live bugs found while wiring it: the AdRevLinks pause was
nested inside the Network Ad Space check in both the end sweep and
setStatus, so with NAS switched off an ended or paused campaign kept
running on AdRevLinks. Each rail is now checked on its own.

Caps: 1,000 credits minimum, 10,000 clicks maximum per campaign. Not for
cost — Marty owns the platform and these placements are free — but because
the whole site delivers around 2,700 clicks a day, and one campaign
booking 50,000 would sit in the list for weeks.

Inert unless DRIPOFFERS_BRIDGE_URL and _KEY are set. 26 checks green
against the live endpoint; member walk clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 06:27:33 -05:00

92 lines
5.5 KiB
JavaScript

// DripOffers paid-per-click syndication, driven against the REAL bridge on dripoffers.com.
//
// Creates a paused test campaign, reads its delivery back, pauses/resumes it, proves a retry
// cannot double-book, then deletes it. Nothing is left behind and nothing is ever activated,
// so no publisher is ever paid for a test click.
//
// DRIPOFFERS_BRIDGE_URL=... DRIPOFFERS_BRIDGE_KEY=... node qa/dripoffers-bridge.mjs
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const drip = require('../dripoffers.js');
const ok = [], bad = [];
const t = (n, c, extra) => { (c ? ok : bad).push(n + (c || !extra ? '' : ' -> ' + extra)); };
// ---- pure logic first: these run with or without a configured bridge ------
const tiers = { t1: new Set(['US', 'CA', 'GB']), t2: new Set(['BR', 'PL']) };
t('no targeting means worldwide', drip.countriesFor('', tiers) === '*', drip.countriesFor('', tiers));
t('all three tiers collapse to worldwide', drip.countriesFor('1,2,3', tiers) === '*', drip.countriesFor('1,2,3', tiers));
t('tier 1 becomes a country list', drip.countriesFor('1', tiers) === 'US,CA,GB', drip.countriesFor('1', tiers));
t('tier 1+2 merges both lists', drip.countriesFor('1,2', tiers) === 'US,CA,GB,BR,PL', drip.countriesFor('1,2', tiers));
// tier 3 is "everywhere the others are not" — an inclusion list cannot say that, and sending it
// worldwide would deliver the Tier 1 traffic its owner deliberately excluded
t('tier 3 alone is skipped, not silently widened', drip.countriesFor('3', tiers) === null, String(drip.countriesFor('3', tiers)));
t('credits convert to clicks one for one', drip.clicksFor(4000) === 4000, String(drip.clicksFor(4000)));
t('one campaign cannot swallow the platform', drip.clicksFor(500000) === drip.MAX_CLICKS, String(drip.clicksFor(500000)));
t('a click format is accepted', drip.kindOk('banner') && drip.kindOk('visits') && drip.kindOk('text'));
t('video and solo are not click formats', !drip.kindOk('video') && !drip.kindOk('solo') && !drip.kindOk('featured'));
if (!drip.enabled()) {
console.log('PASS ' + ok.length + ' (pure logic only)');
for (const b of bad) console.log('FAIL ' + b);
console.log('SKIP: DRIPOFFERS_BRIDGE_URL / _KEY not set — live bridge checks not run');
process.exit(bad.length ? 1 : 0);
}
// ---- live bridge ---------------------------------------------------------
const ID = 999000 + Math.floor(Math.random() * 900);
const camp = { id: ID, name: 'QA bridge campaign', title: 'A test offer that is never activated',
targetUrl: 'https://instantadpay.com/', budget: 12000, spent: 0, accrued: 0, type: 'banner' };
// a campaign below the credit floor is skipped quietly rather than taking a listing
const small = await drip.push({ ...camp, id: ID + 1, budget: 200 }, { paused: true, tiers });
t('a campaign under the credit floor is not syndicated', small === null, JSON.stringify(small));
// a format that is not a click offer never goes out
const wrongKind = await drip.push({ ...camp, id: ID + 2, type: 'video' }, { paused: true, tiers });
t('a video campaign is not syndicated as a click offer', wrongKind === null, JSON.stringify(wrongKind));
// the real push
const r = await drip.push(camp, { paused: true, tiers });
t('campaign syndicated', !!(r && r.campaign_id), JSON.stringify(r));
t('clicks are capped at the per-campaign ceiling', r && r.clicks_booked === drip.MAX_CLICKS, r && String(r.clicks_booked));
t('untargeted campaign goes worldwide', r && r.countries === '*', r && String(r.countries));
t('created paused, so no publisher is paid by the test', r && r.status === 0, r && String(r.status));
t('the funding account was actually debited', r && r.charged === true, r && String(r.charged));
// idempotency: the guard that stops a retry double-booking
const again = await drip.push(camp, { paused: true, tiers });
t('a retry returns the SAME campaign, never a second', again && again.campaign_id === r.campaign_id && again.duplicate === true,
JSON.stringify(again));
// delivery is counted from the click ledger, never derived from remaining_visits
const st = await drip.readServed(ID);
t('delivery reads back from the platform', !!(st && st.ok), JSON.stringify(st).slice(0, 140));
t('ordered clicks match what was booked', st && st.ordered_clicks === drip.MAX_CLICKS, st && String(st.ordered_clicks));
t('delivered starts at zero and is a counted figure', st && st.delivered_clicks === 0, st && String(st.delivered_clicks));
// pause / resume mirror — and the reason this matters: a pause must NOT read back as delivered
await drip.resume(ID);
let s2 = await drip.readServed(ID);
t('resume activates it', s2 && s2.status === 1, s2 && String(s2.status));
await drip.pause(ID);
s2 = await drip.readServed(ID);
t('pause deactivates it', s2 && s2.status === 0, s2 && String(s2.status));
t('pausing does not fake delivery', s2 && s2.delivered_clicks === 0 && s2.remaining_visits === drip.MAX_CLICKS,
s2 && (s2.delivered_clicks + '/' + s2.remaining_visits));
// cleanup must actually work, or tests litter a production platform
const del = await drip.remove(ID);
t('the test campaign is deleted', !!(del && del.ok), JSON.stringify(del));
const gone = await drip.readServed(ID);
t('and it is really gone', gone === null, JSON.stringify(gone));
// an unknown campaign reads as null rather than throwing
const none = await drip.readServed(ID + 12345);
t('an unknown campaign reads as null', none === null, JSON.stringify(none));
console.log('PASS ' + ok.length);
for (const b of bad) console.log('FAIL ' + b);
process.exit(bad.length ? 1 : 0);