mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 13:33:42 +08:00
d921cf6559
Render tool menus from the catalog and update navigation, guides, and tests
112 lines
5.4 KiB
JavaScript
112 lines
5.4 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import { readFileSync } from 'node:fs'
|
|
import { SentryEventsFeed, SentryEventsPage, validSentryEvents } from '../web/js/sentry.js'
|
|
import { CamerasPage } from '../web/js/cameras.js'
|
|
const cameraTools = JSON.parse(readFileSync(new URL('../web/data/catalog.json', import.meta.url), 'utf8'))
|
|
.tools.find(tool => tool.path === '/cameras').children
|
|
|
|
const flush = async () => { for (let i = 0; i < 8; i++) await Promise.resolve() }
|
|
const eventId = 'a'.repeat(32)
|
|
const inventory = { schemaVersion: 1, source: 'local', scanIncomplete: false, capacity: 512,
|
|
events: [{ eventId, kind: 'warning', systemTimeMs: 1_700_000_000_000 }] }
|
|
assert.equal(validSentryEvents(inventory), true)
|
|
assert.equal(validSentryEvents({ ...inventory, events: [{ ...inventory.events[0], images: ['wide', 'cabin'] }] }), true)
|
|
assert.equal(validSentryEvents({ ...inventory, events: [{ ...inventory.events[0], images: ['../../secret'] }] }), false)
|
|
assert.equal(validSentryEvents({ ...inventory, events: [{ ...inventory.events[0], kind: 'armed' }] }), false)
|
|
assert.equal(validSentryEvents({ ...inventory, events: [{ ...inventory.events[0], path: '/secret' }] }), false)
|
|
assert.equal(validSentryEvents({ ...inventory, events: [inventory.events[0], inventory.events[0]] }), false)
|
|
assert.equal(validSentryEvents({ ...inventory, source: 'remote' }), false)
|
|
assert.match(SentryEventsPage.template, /device clock and may be inaccurate/)
|
|
assert.match(SentryEventsPage.template, /<img[^>]*event.images[^>]*api\/sentry\/image\//)
|
|
assert.doesNotMatch(SentryEventsPage.template, /v-html|deleteEvent|Download/)
|
|
assert.match(CamerasPage.template, /MenuTile v-for="tool in tools"/)
|
|
assert.match(cameraTools.find(tool => tool.path === "/cameras/events").description, /View motion events/)
|
|
const app = readFileSync(new URL('../web/js/app.js', import.meta.url), 'utf8')
|
|
assert.match(app, /SentryEventsPage[^>]*\/cameras\/events[^>]*\/cameras\/sentry-settings/)
|
|
|
|
function fixture() {
|
|
const requests = [], states = [], timers = new Map()
|
|
let next = 0, revoked = 0
|
|
const feed = new SentryEventsFeed({ publish: (state) => states.push(state), unauthorized: () => revoked++,
|
|
fetcher: (url, options) => new Promise((resolve) => requests.push({ url, options, resolve })),
|
|
later: (fn, ms) => { timers.set(++next, { fn, ms }); return next }, cancelTimer: (id) => timers.delete(id) })
|
|
async function reply(index, body, code = 200) {
|
|
requests[index].resolve({ status: code, ok: code === 200, json: async () => body })
|
|
await flush()
|
|
}
|
|
return { feed, requests, states, timers, reply, get revoked() { return revoked } }
|
|
}
|
|
const normal = fixture()
|
|
normal.feed.start()
|
|
assert.equal(normal.requests[0].url, './api/sentry/events')
|
|
assert.equal(normal.requests[0].options.credentials, 'same-origin')
|
|
await normal.reply(0, inventory)
|
|
assert.equal(normal.feed.status, 'ready')
|
|
await flush()
|
|
assert.equal(normal.requests.length, 1, 'no repeated storage scan without manual refresh')
|
|
normal.feed.load()
|
|
await normal.reply(1, { ...inventory, events: [] })
|
|
assert.equal(normal.feed.data.events.length, 0)
|
|
normal.feed.stop()
|
|
assert.equal(normal.feed.data, null)
|
|
|
|
const auth = fixture()
|
|
auth.feed.start()
|
|
await auth.reply(0, { code: 'setup_required' }, 503)
|
|
assert.equal(auth.revoked, 1)
|
|
assert.equal(auth.feed.data, null)
|
|
const loggedOut = fixture()
|
|
loggedOut.feed.start()
|
|
await loggedOut.reply(0, {}, 401)
|
|
assert.equal(loggedOut.revoked, 1)
|
|
const unavailable = fixture()
|
|
unavailable.feed.start()
|
|
await unavailable.reply(0, { code: 'backend_unavailable' }, 503)
|
|
assert.equal(unavailable.revoked, 0)
|
|
assert.equal(unavailable.feed.status, 'unavailable')
|
|
|
|
const timedOut = fixture()
|
|
timedOut.feed.start()
|
|
assert.deepEqual([...timedOut.timers.values()].map((timer) => timer.ms), [10000, 4000])
|
|
const [timeoutId, timeout] = [...timedOut.timers.entries()].find(([, timer]) => timer.ms === 4000)
|
|
timedOut.timers.delete(timeoutId)
|
|
timeout.fn()
|
|
assert.equal(timedOut.feed.status, 'unavailable')
|
|
timedOut.feed.load()
|
|
assert.equal(timedOut.requests.length, 2)
|
|
await timedOut.reply(1, inventory)
|
|
await timedOut.reply(0, { ...inventory, events: [] })
|
|
assert.equal(timedOut.feed.data.events.length, 1)
|
|
|
|
const lateBody = fixture()
|
|
lateBody.feed.start()
|
|
let resolveBody
|
|
lateBody.requests[0].resolve({ status: 200, ok: true, json: () => new Promise((resolve) => { resolveBody = resolve }) })
|
|
await flush()
|
|
lateBody.feed.stop()
|
|
lateBody.feed.start()
|
|
await lateBody.reply(1, inventory)
|
|
resolveBody({ ...inventory, events: [] })
|
|
await flush()
|
|
assert.equal(lateBody.feed.data.events.length, 1)
|
|
|
|
const priorDocument = globalThis.document
|
|
const listeners = new Map()
|
|
globalThis.document = { hidden: true, addEventListener: (name, cb) => listeners.set(name, cb),
|
|
removeEventListener: (name, cb) => { if (listeners.get(name) === cb) listeners.delete(name) } }
|
|
const page = { mode: 'local', $data: { status: 'idle', data: null, error: '' }, unauthorized() { throw new Error('revoked') } }
|
|
SentryEventsPage.created.call(page)
|
|
const pending = []
|
|
page.feed.fetcher = (url, options) => new Promise((resolve) => pending.push({ url, options, resolve }))
|
|
SentryEventsPage.mounted.call(page)
|
|
assert.equal(pending.length, 0)
|
|
document.hidden = false
|
|
listeners.get('visibilitychange')()
|
|
assert.equal(pending.length, 1)
|
|
document.hidden = true
|
|
listeners.get('visibilitychange')()
|
|
assert.equal(page.$data.data, null)
|
|
SentryEventsPage.beforeUnmount.call(page)
|
|
assert.equal(listeners.size, 0)
|
|
globalThis.document = priorDocument
|