Spaces:
Running
Running
Download test_dashboard.js from Reoo88/Prayer-Time-Bot: direct link, hf CLI and curl.
- Browser
- Download file 7.55 kB
-
https://huggingface.co/spaces/Reoo88/Prayer-Time-Bot/resolve/main/test_dashboard.js
- Command line
-
hf download hf://spaces/Reoo88/Prayer-Time-Bot/test_dashboard.js
-
curl -L -o test_dashboard.js https://huggingface.co/spaces/Reoo88/Prayer-Time-Bot/resolve/main/test_dashboard.js
7.55 kB
| ; | |
| // Execute the real inline dashboard script with a small DOM/fetch harness. No live API. | |
| const assert = require('node:assert/strict'); | |
| const fs = require('node:fs'); | |
| const path = require('node:path'); | |
| const vm = require('node:vm'); | |
| const root = process.argv[2] || __dirname; | |
| const html = fs.readFileSync(path.join(root, 'index.html'), 'utf8'); | |
| const script = html.match(/<script>([\s\S]*?)<\/script>/)[1]; | |
| const ids = [...html.matchAll(/id="([^"]+)"/g)].map(m => m[1]); | |
| const OLD = {id: 'OLD_CHANNEL', title: 'Old channel'}; | |
| const NEW = {id: 'NEW_CHANNEL', title: 'New channel'}; | |
| const clone = o => JSON.parse(JSON.stringify(o)); | |
| let passed = 0; | |
| function harness() { | |
| const elements = new Map(ids.map(id => [id, { | |
| hidden: false, disabled: false, value: '', textContent: '', innerHTML: '', focus() {} | |
| }])); | |
| const state = {active: clone(OLD), authorized: clone(NEW), automatic: false, events: [], | |
| requests: [], prompts: [], alerts: [], accept: true, blockCheck: null, failCheck: false}; | |
| const storage = new Map(); | |
| const sandbox = { | |
| console, TextEncoder, Date, JSON, String, Number, Math, Promise, | |
| btoa: s => Buffer.from(s, 'binary').toString('base64'), | |
| sessionStorage: {getItem: k => storage.get(k) || null, setItem: (k,v) => storage.set(k,v), removeItem: k => storage.delete(k)}, | |
| document: {hidden: false, activeElement: null, | |
| getElementById(id) {assert(elements.has(id), 'missing DOM element: ' + id);return elements.get(id)}, | |
| addEventListener() {}}, | |
| setInterval(fn) {state.timer = fn;return 1}, | |
| alert(text) {state.alerts.push(text)}, | |
| confirm(text) {state.prompts.push(text);return state.accept}, | |
| async fetch(url, opts) { | |
| const body = opts.body ? JSON.parse(opts.body) : null; | |
| state.requests.push({url, method: opts.method, body, headers: opts.headers}); | |
| let data, status = 200; | |
| if (url === '/api/youtube') data = {channel: clone(state.active), automatic: state.automatic, | |
| configured: true, events: clone(state.events), settings: {privacy:'public', title:'title', description:'description'}}; | |
| else if (url === '/api/state') data = {tasks:[], logs:[], monitor_running:true, check_interval:20}; | |
| else if (url === '/api/youtube/check') { | |
| if (state.blockCheck) await state.blockCheck; | |
| if (state.failCheck) {status=400;data={detail:'OAuth check failed'}} | |
| else data={channel:Object.assign(clone(state.authorized), | |
| state.active.id!==state.authorized.id ? {previous:clone(state.active)} : {})}; | |
| } else if (url === '/api/youtube/start' || url === '/api/youtube/channel') { | |
| assert.equal(body.channel_id, state.authorized.id, 'must send the freshly checked channel id'); | |
| state.active=clone(state.authorized);state.automatic=url.endsWith('/start'); | |
| data={ok:true, channel:clone(state.active)}; | |
| } else if (url === '/api/youtube/stop') {state.automatic=false;data={ok:true}} | |
| else throw Error('Unexpected route: '+url); | |
| return {ok:status<400,status,async json(){return clone(data)}}; | |
| } | |
| }; | |
| const context=vm.createContext(sandbox); | |
| vm.runInContext(script, context); | |
| vm.runInContext("authHeader='Basic dGVzdDp0ZXN0'", context); | |
| return {state, elements, context, | |
| async click(id){await vm.runInContext("$('"+id+"').onclick()",context)}, | |
| async refresh(){await vm.runInContext('refresh()',context)}, | |
| read(expr){return vm.runInContext(expr,context)}}; | |
| } | |
| function pass(name){passed++;console.log('PASS '+name)} | |
| (async()=>{ | |
| let h=harness(); | |
| h.state.events=[{username:'muhajer_iraqi',title:'Live event',phase:'live',youtube_status:'live', | |
| source:'hd 720p',send_mode:'copy',pending_send_mode:'matchgop',output_seconds:60}]; | |
| await h.refresh(); | |
| assert.match(h.elements.get('events').innerHTML,/وضع الإرسال الفعلي: copy/); | |
| assert.match(h.elements.get('events').innerHTML,/الترميز الثابت ينتظر خانة: matchgop/); | |
| pass('Per-event sender mode is shown instead of hiding an account-specific fallback behind the global preference'); | |
| await h.click('ytcheck'); | |
| assert.equal(h.read('checkedChannel.id'),NEW.id); | |
| assert.equal(h.state.active.id,OLD.id); | |
| await h.refresh();await h.state.timer(); | |
| assert.equal(h.read('checkedChannel.id'),NEW.id); | |
| assert.equal(h.read('lastY.channel.id'),OLD.id); | |
| assert.equal(h.elements.get('ytadopt').hidden,false); | |
| assert.match(h.elements.get('ytchannelnotice').textContent,/New channel/); | |
| pass('OAuth check → refresh → timer retains NEW, not the journal OLD (production regression)'); | |
| await h.click('ytstart'); | |
| const posts=h.state.requests.filter(r=>r.method==='POST'); | |
| assert.equal(posts.at(-2).url,'/api/youtube/check'); | |
| assert.equal(posts.at(-1).url,'/api/youtube/start'); | |
| assert.equal(posts.at(-1).body.channel_id,NEW.id); | |
| assert.match(h.state.prompts.at(-1),/Old channel/); | |
| assert.match(h.state.prompts.at(-1),/New channel/); | |
| assert.equal(h.state.automatic,true); | |
| assert.equal(h.elements.get('ytadopt').hidden,true); | |
| pass('Start rechecks OAuth and explicitly confirms both channel names before NEW is enabled'); | |
| h=harness();await h.click('ytcheck');await h.click('ytadopt'); | |
| assert.equal(h.state.active.id,NEW.id); | |
| assert.equal(h.state.automatic,false); | |
| assert(!h.state.requests.some(r=>r.url==='/api/youtube/start')); | |
| assert.equal(h.elements.get('ytadopt').hidden,true); | |
| pass('Adopt-only confirms the new channel without starting a broadcast'); | |
| h=harness();await h.click('ytcheck');h.state.accept=false;await h.click('ytstart'); | |
| assert.equal(h.state.active.id,OLD.id); | |
| assert(!h.state.requests.some(r=>r.url==='/api/youtube/start')); | |
| pass('Cancelled start never sends a start request or adopts the channel'); | |
| h=harness();await h.click('ytcheck');h.state.accept=false;await h.click('ytadopt'); | |
| assert(!h.state.requests.some(r=>r.url==='/api/youtube/channel')); | |
| assert.equal(h.state.active.id,OLD.id); | |
| pass('Cancelled adoption leaves the previous channel unchanged'); | |
| h=harness();await h.click('ytcheck'); | |
| h.state.authorized={id:'THIRD_CHANNEL',title:'Rotated channel'}; | |
| await h.click('ytstart'); | |
| assert.equal(h.state.active.id,'THIRD_CHANNEL'); | |
| assert.match(h.state.prompts.at(-1),/Rotated channel/); | |
| pass('Secrets rotated after check are freshly discovered, not silently confirmed with a stale id'); | |
| h=harness();h.state.failCheck=true;await h.click('ytstart'); | |
| assert(!h.state.requests.some(r=>r.url==='/api/youtube/start')); | |
| assert.match(h.elements.get('error').textContent,/OAuth check failed/); | |
| assert.equal(h.elements.get('ytcheck').disabled,false); | |
| pass('Failed OAuth check cannot start and restores the controls'); | |
| h=harness();let release; | |
| h.state.blockCheck=new Promise(resolve=>{release=resolve}); | |
| const first=h.click('ytstart');await Promise.resolve();await h.click('ytstart');release();await first; | |
| assert.equal(h.state.requests.filter(r=>r.url==='/api/youtube/start').length,1); | |
| assert.equal(h.state.requests.filter(r=>r.url==='/api/youtube/check').length,1); | |
| pass('Double-click while OAuth is pending sends only one check and one start'); | |
| h=harness();await h.click('ytcheck');await h.click('logout'); | |
| assert.equal(h.read('checkedChannel'),null); | |
| assert.equal(h.read('authHeader'),''); | |
| assert.equal(h.elements.get('ytadopt').hidden,true); | |
| pass('Logout clears the checked channel and credentials'); | |
| console.log('\n'+passed+' dashboard regressions passed'); | |
| })().catch(err=>{console.error(err);process.exitCode=1}); | |