test(main): cover updater fallback, ipc sender guard, log sanitizer, sftp timeouts
- updater-fallback.mjs (82 assertions): GitHub probe fallback to Gitea, timeout budgets, in-flight check never stuck in 'checking'; updater.ts gains a setUpdateTimeouts test seam, electron-updater aliased to a stub - ipc-guard.mjs (43): trusted-frame guard exercised through real registerIpc handlers with forged senderFrames; electron-stub now records registrations via globalThis so bundle and test share one instance - log-sanitizer.mjs (71): CSI/OSC/charset state machine, alt-screen fold, byte-split fuzz equal to whole-chunk output; fixes a wrong comment - sftp-timeout.mjs (39): per-op timeouts (metadata 30s, transfer chunk 60s, open 10s) evict half-dead channels with one retry, slow-but-progressing transfers untouched, late rejections never unhandled - reservedAccelerators.ts: single pure isReservedAccelerator shared by the settings recorder and applyGlobalShortcut (the two tables had drifted — main now also refuses Ctrl+=/-/0/PgUp/PgDn legacy values); 56 assertions - ssh-loopback.mjs loads the real ssh.ts via a bundle (50 assertions): TOFU pinning, fail-closed stores, auth gate, connect budget Offline suite grows 13 -> 17. Renderer test framework evaluated: not introducing vitest/jsdom; pure logic keeps being extracted and tested through the existing bundle harness.
This commit is contained in:
1 parent
5ff311ea0f
commit
a0be827650
21 files changed
+2370
-174
No files matched your search
+73
-8
@@ -2,24 +2,76 @@
|
||||
// (tests/ssh-session-e2e.mjs, tests/commands-store.mjs). Only what the bundled
|
||||
// modules touch.
|
||||
//
|
||||
// `app.getPath('userData')` is configurable via `__setUserData` so a test can
|
||||
// point the settings store at a temp dir and exercise settings-driven behavior.
|
||||
let userDataPath = process.env.OT_STUB_USERDATA || ''
|
||||
// Every mutable piece of state lives on `globalThis.__otElectronStub` rather
|
||||
// than in this module's scope: the bundles INLINE this file (esbuild aliases
|
||||
// `electron` to it), so a test requiring both `./electron-stub.cjs` and a
|
||||
// bundle would otherwise get two independent copies, and registrations made by
|
||||
// the bundled code would be invisible to the test.
|
||||
//
|
||||
// `app.getPath('userData')` defaults to `OT_STUB_USERDATA` and is also settable
|
||||
// via `__setUserData`, so a test can point the settings store at a temp dir and
|
||||
// exercise settings-driven behavior.
|
||||
const state = (globalThis.__otElectronStub ??= {
|
||||
handlers: new Map(),
|
||||
userDataPath: process.env.OT_STUB_USERDATA || '',
|
||||
isPackaged: false,
|
||||
sessions: new Map()
|
||||
})
|
||||
|
||||
/** Fetch double for the updater bundle: `net.fetch` and every session's fetch. */
|
||||
const noFetch = async () => ({
|
||||
ok: false,
|
||||
status: 404,
|
||||
text: async () => '',
|
||||
json: async () => []
|
||||
})
|
||||
const callFetch = (url, init) =>
|
||||
globalThis.__otFetch ? globalThis.__otFetch(url, init) : noFetch()
|
||||
|
||||
const fakeSession = (name) => {
|
||||
let s = state.sessions.get(name)
|
||||
if (!s) {
|
||||
s = {
|
||||
name,
|
||||
proxyCalls: [],
|
||||
setProxy: async (cfg) => {
|
||||
s.proxyCalls.push(cfg)
|
||||
},
|
||||
fetch: callFetch
|
||||
}
|
||||
state.sessions.set(name, s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
BrowserWindow: {
|
||||
getAllWindows: () => []
|
||||
},
|
||||
app: {
|
||||
getPath: (name) => {
|
||||
if (name === 'userData' && userDataPath) return userDataPath
|
||||
if (name === 'userData' && state.userDataPath) return state.userDataPath
|
||||
throw new Error(`electron stub: app.getPath(${name}) is not configured`)
|
||||
},
|
||||
getVersion: () => '0.0.0-stub',
|
||||
setLoginItemSettings: () => {},
|
||||
isPackaged: false
|
||||
get isPackaged() {
|
||||
return state.isPackaged
|
||||
}
|
||||
},
|
||||
ipcMain: {
|
||||
handle: () => {},
|
||||
on: () => {}
|
||||
handle: (channel, listener) => {
|
||||
state.handlers.set(channel, listener)
|
||||
},
|
||||
on: (channel, listener) => {
|
||||
state.handlers.set(`on:${channel}`, listener)
|
||||
}
|
||||
},
|
||||
session: {
|
||||
fromPartition: (name) => fakeSession(name)
|
||||
},
|
||||
net: {
|
||||
fetch: callFetch
|
||||
},
|
||||
globalShortcut: {
|
||||
register: () => true,
|
||||
@@ -39,6 +91,9 @@ module.exports = {
|
||||
shell: {
|
||||
openPath: async () => ''
|
||||
},
|
||||
dialog: {
|
||||
showOpenDialog: async () => ({ canceled: true, filePaths: [] })
|
||||
},
|
||||
safeStorage: {
|
||||
isEncryptionAvailable: () => false
|
||||
},
|
||||
@@ -63,6 +118,16 @@ module.exports = {
|
||||
createFromPath: () => ({ isEmpty: () => true, resize: () => ({}) })
|
||||
},
|
||||
__setUserData: (p) => {
|
||||
userDataPath = p
|
||||
state.userDataPath = p
|
||||
},
|
||||
__setPackaged: (v) => {
|
||||
state.isPackaged = v
|
||||
},
|
||||
/** channel -> listener (invoke), `on:<channel>` -> listener (send). */
|
||||
get __handlers() {
|
||||
return state.handlers
|
||||
},
|
||||
get __sessions() {
|
||||
return state.sessions
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user