Files
hub/tools/kidsearch-test.js
T
nmemmertandClaude Sonnet 5.5 301e8017d9
build-container / build (push) Successful in 3m52s
Parent controls for children, name-tile sign-in, restricted Learn search, simple mode
- Sign-in page shows name tiles on remembered devices; tap a name, then PIN or passkey
- Music and Movies are separate on the server; Plex can be locked to a child's managed profile
- Activity log, weekly summary and alerts to parents; per-area and weekend limits
- Requests from children (Ask button) answered in Settings; bonus screen time from rewards
- Restricted Learn search (Simple English Wikipedia, blocked words) replaces web search for children
- Simple mode (big-button home) for young children or grandparents
- Child usage kept in memory instead of read from disk on every request
- Tests and README updated

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 16:49:48 -04:00

19 lines
3.3 KiB
JavaScript

// The restricted search for children: blocked words (built in and added by a parent), results that mention them are hidden, and nothing but Simple English Wikipedia is asked.
const assert = require('assert'), fs = require('fs'), os = require('os'), path = require('path'), { Readable } = require('stream');
process.env.DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'nehub-kidsearch-'));
const users = require('../users'), ks = require('../kidsearch'), inbox = require('../inbox');
const ann = users.create({ name: 'Ann', role: 'admin', handle: 'YQ' }), kai = users.create({ name: 'Kai', role: 'user', tabs: ['search'], handle: 'Yg' }); users.update(kai.id, { kid: { on: true } });
const asked = []; global.fetch = async (url) => { asked.push(String(url)); const u = new URL(url), q = u.searchParams.get('gsrsearch'), t = u.searchParams.get('titles');
const pages = q ? { 1: { title: 'Dinosaur', index: 1, extract: 'Dinosaurs lived long ago.' }, 2: { title: 'Casino', index: 2, extract: 'A place for gambling.' }, 3: { title: 'Tyrannosaurus', index: 3, extract: 'A big dinosaur with a secret word: zebra.' } } : { 9: { title: t, extract: 'Long text about ' + t } };
return { ok: true, json: async () => ({ query: { pages } }) }; };
const call = (who, url, method = 'GET', body) => new Promise(ok => { const q = Readable.from(body ? [Buffer.from(JSON.stringify(body))] : []); q.url = url; q.method = method; q.headers = {}; users.runAs(who, () => ks.handle(q, { writeHead(c) { this.c = c; }, end(d) { ok({ status: this.c, json: JSON.parse(d) }); } })); });
(async () => {
assert.ok(ks.blocked('free porn') && ks.blocked('To Kill a Mockingbird') === false && ks.blocked('sexual stuff') && !ks.blocked('Essex county') && !ks.blocked('a sextant and a class'), 'whole words only, any case');
let r = await call(users.get(kai.id), '/api/kidsearch?q=dinosaurs'); assert.equal(r.status, 200); assert.deepEqual(r.json.results.map(x => x.title), ['Dinosaur', 'Tyrannosaurus'], 'a result that mentions a blocked word (gambling) is hidden'); assert.ok(asked.every(u => u.startsWith('https://simple.wikipedia.org/')), 'only Simple English Wikipedia is asked');
r = await call(users.get(kai.id), '/api/kidsearch?q=porn'); assert.ok(r.json.blocked && r.json.results.length === 0, 'a blocked search is not made'); assert.equal(asked.filter(u => /porn/.test(u)).length, 0); assert.ok(inbox.load(ann.id).some(i => /searched for something blocked/.test(i.body)), 'the parent is told');
assert.equal((await call(users.get(kai.id), '/api/kidsearch/words', 'POST', { words: 'zebra' })).status, 403, 'a child cannot change the list'); assert.deepEqual((await call(ann, '/api/kidsearch/words', 'POST', { words: 'Zebra, unicorn\nZEBRA' })).json.words, ['zebra', 'unicorn'], 'a parent adds words');
r = await call(users.get(kai.id), '/api/kidsearch?q=dinosaurs'); assert.deepEqual(r.json.results.map(x => x.title), ['Dinosaur'], 'and results with those words go too'); assert.ok((await call(users.get(kai.id), '/api/kidsearch?q=a unicorn')).json.blocked);
r = await call(users.get(kai.id), '/api/kidsearch/page?title=Dinosaur'); assert.equal(r.json.title, 'Dinosaur'); assert.equal((await call(users.get(kai.id), '/api/kidsearch/page?title=Casino')).status, 403, 'a blocked page cannot be opened either');
console.log('kidsearch ok'); process.exit(0);
})().catch(e => { console.error(e); process.exit(1); });