// Child accounts: the daily limit counts time from pings, bedtime locks, locked accounts are refused (except for what they need to ask), parents give more time and are asked. const http = require('http'), assert = require('assert'), fs = require('fs'), os = require('os'), path = require('path'); process.env.DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'nehub-kid-')); process.env.HUB_TZ = 'UTC'; const users = require('../users'), kid = require('../kidlimits'), chat = require('../chat'); const ann = users.create({ name: 'Ann', role: 'admin', handle: 'YQ' }), sam = users.create({ name: 'Sam', role: 'user', tabs: ['music'], handle: 'Yg' }), pat = users.create({ name: 'Pat', role: 'user', tabs: ['music'], handle: 'Yw' }); const asAdmin = (u, fn) => users.runAs(u, fn); assert.throws(() => users.update(ann.id, { kid: { on: true } }), /admin cannot be a child/, 'an admin cannot be a child account'); users.update(sam.id, { kid: { on: true, limitMin: 3, bedtime: { on: false, from: '20:30', to: '07:00' } } }); const s0 = users.get(sam.id).kid; assert.deepEqual(s0, { on: true, limitMin: 15, bedtime: { on: false, from: '20:30', to: '07:00' }, safe: true }, 'a limit is at least 15 minutes; safe search is on unless turned off'); assert.equal(users.publicView(users.get(sam.id)).kid.limitMin, 15); assert.equal(users.publicView(users.get(pat.id)).kid, undefined, 'only child accounts carry the settings'); assert.deepEqual(kid.status(users.get(pat.id)), { on: false }); assert.equal(kid.lockedFor(users.get(pat.id), '/api/music/x'), null); // time counts from pings (one a minute while the app is open) const real = Date.now; let t = real(); Date.now = () => t; let st; for (let i = 0; i < 14; i++) { st = kid.ping(users.get(sam.id)); t += 60e3; } assert.equal(st.used, 13, 'the first ping counts half a minute, the rest a minute each'); assert.equal(st.left, 2); assert.equal(st.locked, null); st = kid.ping(users.get(sam.id)); t += 60e3; st = kid.ping(users.get(sam.id)); t += 60e3; assert.equal(st.locked, 'time', 'time is up'); assert.ok(kid.lockedFor(users.get(sam.id), '/api/music/x'), 'so requests are refused'); assert.equal(kid.lockedFor(users.get(sam.id), '/api/kid/status'), null, 'except to see the status'); assert.equal(kid.lockedFor(users.get(sam.id), '/api/me'), null); const used = kid.ping(users.get(sam.id)).used; t += 60e3; assert.equal(kid.ping(users.get(sam.id)).used, used, 'a locked account stops counting'); assert.throws(() => kid.give(pat.id, 15), /not a child/); st = kid.give(sam.id, 20); assert.equal(st.locked, null, 'more time unlocks it'); assert.equal(st.limit, 35); // bedtime users.update(sam.id, { kid: { on: true, limitMin: 0, bedtime: { on: true, from: '00:00', to: '23:59' } } }); assert.equal(kid.status(users.get(sam.id)).locked, 'bedtime'); users.update(sam.id, { kid: { on: true, limitMin: 0, bedtime: { on: false } } }); assert.equal(kid.status(users.get(sam.id)).locked, null, 'no limit, no bedtime: never locked'); assert.equal(kid.status(users.get(sam.id)).left, null); Date.now = real; // over HTTP: who may do what, and asking a parent const hub = http.createServer((req, res) => { const who = { sam, ann, pat }[req.headers['x-user'] || 'sam']; users.runAs(users.get(who.id), () => kid.handle(req, res, users.get(who.id), { askParents: chat.askParents })); }).listen(0, async () => { const B = 'http://127.0.0.1:' + hub.address().port + '/api/kid', call = (p, o = {}, who) => fetch(B + p, { ...o, headers: { ...(who ? { 'x-user': who } : {}) } }), post = (p, b, who) => call(p, { method: 'POST', body: JSON.stringify(b) }, who), j = r => r.json(); try { assert.equal((await j(await call('/status'))).on, true); assert.equal((await post('/ping', {})).status, 200); assert.equal((await post('/extra', { user: sam.id, minutes: 10 }, 'sam')).status, 403, 'a child cannot give themselves time'); assert.equal((await post('/extra', { user: sam.id, minutes: 10 }, 'ann')).status, 200); assert.equal((await call('/usage?user=' + sam.id, {}, 'sam')).status, 403); assert.equal((await j(await call('/usage?user=' + sam.id, {}, 'ann'))).on, true); assert.equal((await post('/ask', { text: 'More time please?' }, 'pat')).status, 400, 'only child accounts ask'); const r = await j(await post('/ask', { text: 'Can I have 15 more minutes? I am nearly done.' })); assert.equal(r.told, 1, 'the one admin is told'); const dm = 'd:' + [sam.id, ann.id].sort().join(':'); const conv = await asAdmin(users.get(ann.id), async () => { const out = {}; await chat.handle({ url: '/api/chat/conv/' + encodeURIComponent(dm), method: 'GET', headers: {} }, { writeHead() {}, end(b) { out.j = JSON.parse(b); }, headersSent: false }); return out.j; }); assert.equal(conv.msgs[0].text, 'Can I have 15 more minutes? I am nearly done.'); assert.equal(conv.msgs[0].name, 'Sam', 'it arrives as a chat message from the child'); console.log('kidlimits ok'); } catch (e) { console.error(e); process.exitCode = 1; } hub.close(); setTimeout(() => process.exit(process.exitCode || 0), 50); });