SPB Git forge

spb/admin-ka

Public
41commits 1branches 0releases
172.9 MBsize
maindefault branch
20 days agolast push
JavaScript 65.5% Python 17.8% CSS 13% HTML 3.7%
6.9 KB · 129 lines javascript
Raw Blame History
1// Banc d'essai analytics v2 — valide les scénarios de classification et les2// invariants des métriques. À lancer depuis une COPIE du repo (il écrit dans3// data/analytics.sqlite) : `node server/test-analytics.mjs`4import fs from 'node:fs';5import path from 'node:path';6import { fileURLToPath } from 'node:url';7import { DatabaseSync } from 'node:sqlite';89const __dirname = path.dirname(fileURLToPath(import.meta.url));10const DATA = path.join(__dirname, '..', 'data');11const DB = path.join(DATA, 'analytics.sqlite');12fs.mkdirSync(DATA, { recursive: true });13try { fs.unlinkSync(DB); } catch {}14try { fs.unlinkSync(DB + '-wal'); } catch {}15try { fs.unlinkSync(DB + '-shm'); } catch {}16if (!fs.existsSync(path.join(DATA, 'config.json'))) {17  fs.writeFileSync(path.join(DATA, 'config.json'), JSON.stringify({ salt: '00', passwordHash: '00' }));18}1920const { initAnalytics, recordHit, analyticsSummary, analyticsRealtime, analyticsInspect, setAnalyticsConfig } =21  await import('./analytics.js');2223initAnalytics();24setAnalyticsConfig({ activeSec: 2 }); // fenêtre « en ligne » courte pour tester l'expiration2526const CHROME = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36';27const GOOGLEBOT = 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)';28const UPTIME = 'Mozilla/5.0 (compatible; UptimeRobot/2.0; http://www.uptimerobot.com/)';29const CURL = 'curl/8.6.0';3031function hit(params, ua, ip) {32  const q = new URLSearchParams(params);33  recordHit(q, { headers: { 'user-agent': ua, 'x-forwarded-for': ip }, socket: { remoteAddress: ip } });34}35const SITES = [{ app: 'lou-ka', label: 'Lou·Ka' }];36const sum = () => analyticsSummary(SITES).sites[0];3738let pass = 0, fail = 0;39function check(name, cond, detail) {40  if (cond) { pass++; console.log('  ✓ ' + name); }41  else { fail++; console.log('  ✗ ' + name + (detail !== undefined ? ' — obtenu : ' + JSON.stringify(detail) : '')); }42}4344// IP AWS pré-classifiée hosting (simule le cache ip-api)45const db = new DatabaseSync(DB);46db.prepare('INSERT OR REPLACE INTO ipinfo (ip,asn,org,isp,cc,country,region,city,hosting,proxy,mobile,ts) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)')47  .run('54.230.1.9', 16509, 'Amazon.com, Inc.', 'Amazon Technologies', 'US', 'United States', 'Virginia', 'Ashburn', 1, 0, 0, Date.now());4849console.log('\nCas 1 — humain ouvre Lou-KA (beacon v2)');50hit({ site: 'lou-ka', vid: 'hum1', sid: 'sess1', ev: 'pv', p: '/', d: 'd' }, CHROME, '24.48.100.10');51let s = sum();52check('+1 visiteur humain', s.today === 1, s.today);53check('+1 session', s.sessions === 1, s.sessions);54check('+1 page vue', s.pv === 1, s.pv);55check('+1 en ligne', s.online === 1, s.online);5657console.log('\nCas 2 — même humain rafraîchit la page');58hit({ site: 'lou-ka', vid: 'hum1', sid: 'sess1', ev: 'pv', p: '/', d: 'd' }, CHROME, '24.48.100.10');59s = sum();60check('même visiteur', s.today === 1, s.today);61check('même session', s.sessions === 1, s.sessions);62check('+1 page vue (2)', s.pv === 2, s.pv);6364console.log('\nCas 3 — même humain navigue (SPA) + heartbeat');65hit({ site: 'lou-ka', vid: 'hum1', sid: 'sess1', ev: 'pv', p: '/logement/abc', d: 'd' }, CHROME, '24.48.100.10');66hit({ site: 'lou-ka', vid: 'hum1', sid: 'sess1', ev: 'hb', p: '/logement/abc', d: 'd' }, CHROME, '24.48.100.10');67s = sum();68check('toujours 1 visiteur / 1 session', s.today === 1 && s.sessions === 1, [s.today, s.sessions]);69check('+1 page vue (3) — heartbeat non compté', s.pv === 3, s.pv);7071console.log('\nCas 4 — Googlebot rend 300 URLs (JS exécuté, beacon v1, vid changeant)');72for (let i = 0; i < 300; i++) hit({ site: 'lou-ka', vid: 'gb' + i, p: '/page' + i }, GOOGLEBOT, '66.249.66.' + (i % 250));73s = sum();74check('0 visiteur humain ajouté', s.today === 1, s.today);75check('300 hits crawler', s.crawler === 300, s.crawler);76check('0 page vue humaine ajoutée', s.pv === 3, s.pv);7778console.log('\nCas 5 — uptime monitor ping (UptimeRobot)');79for (let i = 0; i < 20; i++) hit({ site: 'lou-ka', vid: 'up1', p: '/' }, UPTIME, '69.162.124.1');80s = sum();81check('0 humain ajouté, visible en crawler', s.today === 1 && s.crawler === 320, [s.today, s.crawler]);8283console.log('\nCas 6 — IP AWS (headless Chrome, UA navigateur) fait 100 requêtes');84for (let i = 0; i < 100; i++) hit({ site: 'lou-ka', vid: 'aws' + i, sid: 'as' + i, ev: 'pv', p: '/x' + i, d: 'd' }, CHROME, '54.230.1.9');85s = sum();86check('0 humain ajouté (datacenter)', s.today === 1, s.today);87check('100 hits datacenter', s.datacenter === 100, s.datacenter);8889console.log('\nCas 6b — script curl → automation ; trafic interne → internal');90hit({ site: 'lou-ka', vid: 'cu1', p: '/' }, CURL, '203.0.113.5');91hit({ site: 'lou-ka', vid: 'in1', p: '/' }, CHROME, '192.168.2.40');92s = sum();93check('1 hit automation', s.automation === 1, s.automation);94check('1 hit interne', s.internal === 1, s.internal);95check('toujours 1 seul humain', s.today === 1, s.today);9697console.log('\nCas 7 — beacon v1 : dédup des pings 60 s côté serveur');98const now = Date.now();99hit({ site: 'lou-ka', vid: 'v1user', p: '/appartement' }, CHROME, '70.80.1.2'); // pv (nouvelle session)100// simule un ping d'intervalle : on triche sur l'horloge de sessState en réinsérant à ~60 s101// (ici on vérifie surtout : 2e ping immédiat même page = reload = pv, page différente = pv)102hit({ site: 'lou-ka', vid: 'v1user', p: '/appartement' }, CHROME, '70.80.1.2'); // reload rapide → pv103hit({ site: 'lou-ka', vid: 'v1user', p: '/contact' }, CHROME, '70.80.1.2');     // nouvelle page → pv104s = sum();105check('v1 : 1 visiteur, 1 session', s.today === 2 && s.sessions === 2, [s.today, s.sessions]);106check('v1 : 3 pv (pas de hb en rafale)', s.pv === 6, s.pv);107108console.log('\nInspecteur — raisons de classification');109const insp = analyticsInspect({ ip: '54.230.1.9' });110check('classe stockée datacenter', insp.storedClass === 'datacenter', insp.storedClass);111check('raison mentionne le hosting/Amazon', (insp.reasons || []).join(' ').toLowerCase().includes('hosting') || (insp.reasons || []).join(' ').includes('Amazon'), insp.reasons);112113console.log('\nInvariants');114s = sum();115check('uniques <= sessions', s.today <= s.sessions, [s.today, s.sessions]);116check('sessions <= pages vues', s.sessions <= s.pv, [s.sessions, s.pv]);117check('en ligne <= uniques du jour', s.online <= s.today, [s.online, s.today]);118119console.log('\nTemps réel + expiration du « en ligne » (fenêtre 2 s)');120let rt = analyticsRealtime(SITES);121check('humains actifs présents', rt.active.length >= 1, rt.active.length);122check('bots absents du temps réel', rt.active.every(a => !a.vid.startsWith('gb') && !a.vid.startsWith('aws')), rt.active.map(a => a.vid));123await new Promise(r => setTimeout(r, 2600));124s = sum(); rt = analyticsRealtime(SITES);125check('en ligne retombe à 0 (expiration auto)', s.online === 0 && rt.active.length === 0, [s.online, rt.active.length]);126127console.log(`\n${pass} réussis, ${fail} échoués`);128process.exit(fail ? 1 : 0);129