1import { readdirSync, readFileSync } from 'node:fs'; 2import { describe, expect, it } from 'vitest'; 3import { codeOnly, SCREEN_CRITERIA, screenRequest, verdictOf, whyNotCode } from './screen'; 4import { whyNot } from './relay'; 5 6const SONGS = new URL('../../songs/', import.meta.url); 7const siteSongs = () => 8 readdirSync(SONGS, { withFileTypes: true }) 9 .filter((d) => d.isDirectory()) 10 .flatMap((t) => 11 readdirSync(new URL(`${t.name}/`, SONGS), { withFileTypes: true }) 12 .filter((d) => d.isDirectory()) 13 .map((d) => `${t.name}/${d.name}`), 14 ) 15 .flatMap((id) => { 16 try { 17 return [{ id, code: readFileSync(new URL(`${id}/song.js`, SONGS), 'utf8') }]; 18 } catch { 19 return []; 20 } 21 }); 22 23describe('the screening question', () => { 24 it('is a choice the relay would forward, with fine, spam and abuse', () => { 25 const body = screenRequest({ kind: 'comment', body: 'love the drop', song: 'jev/dial-up' }); 26 expect(whyNot(body)).toBeNull(); 27 expect(Object.keys(body.questions.verdict.criteria)).toEqual(['fine', 'spam', 'abuse']); 28 expect(body.state).toEqual({ item: 'a comment on a song', body: 'love the drop', song: 'jev/dial-up' }); 29 }); 30 31 it('reads only a verdict that is one of its options', () => { 32 expect(verdictOf({ verdict: { choice: 'spam', confidence: 0.9 } })).toEqual({ verdict: 'spam', confidence: 0.9 }); 33 expect(verdictOf({ verdict: { choice: 'fine' } })).toEqual({ verdict: 'fine', confidence: null }); 34 for (const bad of [null, {}, { verdict: {} }, { verdict: { choice: 'toString' } }, { verdict: { choice: 'ok' } }]) { 35 expect(verdictOf(bad)).toBeNull(); 36 } 37 expect(Object.keys(SCREEN_CRITERIA)).toEqual(['fine', 'spam', 'abuse']); 38 }); 39}); 40 41describe('whyNotCode: a song only makes music', () => { 42 it("passes every one of the site's own songs", () => { 43 const songs = siteSongs(); 44 expect(songs.length).toBeGreaterThan(5); 45 for (const { id, code } of songs) expect(whyNotCode(code), id).toBeNull(); 46 }); 47 48 it('refuses code that reaches the network, storage, the document or the global object', () => { 49 for (const code of [ 50 'fetch("/jev/listeners/pitches", { method: "POST" })', 51 's("bd").gain(1); window.x = 1', 52 'document.cookie', 53 "globalThis['fe' + 'tch']", 54 '[].at.constructor("return 1")()', 55 'self.fetch', 56 'top["location"]', 57 '(function () { return this })()', 58 'setTimeout("x()", 1)', 59 'import("https://example.com/x.js")', 60 'note(`c3 ${eval("1")}`)', 61 ]) { 62 expect(whyNotCode(code), code).toMatch(/only make music/); 63 } 64 }); 65 66 it('reads what the code does, not what its comments and strings say', () => { 67 expect(whyNotCode('// open the window\nnote("c3") // fetch me a beer')).toBeNull(); 68 expect(whyNotCode('const lyric = \'Take this. Close the window.\'; s("bd")')).toBeNull(); 69 expect(whyNotCode('const top = 3; n(top)')).toBeNull(); 70 expect(codeOnly('a("x" + `y${b}z`) /* c */ // d')).toBe('a("" + `${b}`) '); 71 }); 72});