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
+29
-10
@@ -23,11 +23,30 @@ const GITHUB_RELEASES_API =
|
||||
'https://api.github.com/repos/billowliu2/OpenTerminal/releases?per_page=10'
|
||||
const GITHUB_PROBE_URL =
|
||||
'https://api.github.com/repos/billowliu2/OpenTerminal/releases/latest'
|
||||
const GITHUB_PROBE_TIMEOUT_MS = 20_000
|
||||
/** Overall budget for ONE check attempt — see withTimeout. */
|
||||
const CHECK_TIMEOUT_MS = 30_000
|
||||
/** Changelog / releases-API fetches: a stalled response must not hang the About tab. */
|
||||
const FETCH_TIMEOUT_MS = 15_000
|
||||
|
||||
/**
|
||||
* Time budgets, kept in one mutable object so the offline harness
|
||||
* (tests/updater-fallback.mjs) can drive the timeout and fallback paths without
|
||||
* waiting out the production values. Nothing in the app calls
|
||||
* `setUpdateTimeouts`; the numbers below are the shipping ones.
|
||||
*
|
||||
* - `probe`: GitHub connectivity probe. Short enough that a proxy-less user
|
||||
* falls back to Gitea instead of hanging on a dead proxy/DNS.
|
||||
* - `check`: overall budget for ONE check attempt — see withTimeout, which
|
||||
* exists because electron-updater's own socket idle timeout only fires on
|
||||
* silence, so a slow trickling response can hold an attempt open forever.
|
||||
* - `fetch`: changelog / releases-API fetches; a stalled response must not
|
||||
* hang the About tab.
|
||||
*/
|
||||
const budgets = {
|
||||
probe: 20_000,
|
||||
check: 30_000,
|
||||
fetch: 15_000
|
||||
}
|
||||
|
||||
export function setUpdateTimeouts(patch: Partial<typeof budgets>): void {
|
||||
Object.assign(budgets, patch)
|
||||
}
|
||||
|
||||
let state: UpdateState = { status: 'idle', currentVersion: app.getVersion() }
|
||||
let activeFeed: 'gitea' | 'github' = 'gitea'
|
||||
@@ -113,7 +132,7 @@ async function checkWithFallback(): Promise<void> {
|
||||
if (await probeGithub()) {
|
||||
useFeed('github')
|
||||
try {
|
||||
await withTimeout(autoUpdater.checkForUpdates(), CHECK_TIMEOUT_MS)
|
||||
await withTimeout(autoUpdater.checkForUpdates(), budgets.check)
|
||||
return
|
||||
} catch (err) {
|
||||
console.warn('[updater] github feed failed, falling back to gitea:', err)
|
||||
@@ -126,7 +145,7 @@ async function checkWithFallback(): Promise<void> {
|
||||
try {
|
||||
// Bounded as well: a still-stuck GitHub check is handed back to us here, and
|
||||
// it must not leave the state at "checking" forever.
|
||||
await withTimeout(autoUpdater.checkForUpdates(), CHECK_TIMEOUT_MS)
|
||||
await withTimeout(autoUpdater.checkForUpdates(), budgets.check)
|
||||
} catch (err) {
|
||||
if (githubErr === undefined) throw err
|
||||
const giteaErr = err instanceof Error ? err.message : String(err)
|
||||
@@ -166,7 +185,7 @@ async function directFetch(url: string): Promise<Response> {
|
||||
// to the releases APIs instead of leaving the view spinning.
|
||||
return s.fetch(url, {
|
||||
headers: { 'User-Agent': 'OpenTerminal' },
|
||||
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
||||
signal: AbortSignal.timeout(budgets.fetch)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -181,7 +200,7 @@ async function probeGithub(): Promise<boolean> {
|
||||
await s.setProxy({ mode: 'system' })
|
||||
const resp = await s.fetch(GITHUB_PROBE_URL, {
|
||||
headers: { 'User-Agent': 'OpenTerminal' },
|
||||
signal: AbortSignal.timeout(GITHUB_PROBE_TIMEOUT_MS)
|
||||
signal: AbortSignal.timeout(budgets.probe)
|
||||
})
|
||||
return resp.ok
|
||||
} catch {
|
||||
@@ -215,7 +234,7 @@ async function fetchChangelog(): Promise<ReleaseNote[]> {
|
||||
try {
|
||||
const resp = await net.fetch(url, {
|
||||
headers: { 'User-Agent': 'OpenTerminal' },
|
||||
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
||||
signal: AbortSignal.timeout(budgets.fetch)
|
||||
})
|
||||
if (!resp.ok) continue
|
||||
const data = (await resp.json()) as Array<{
|
||||
|
||||
Reference in new issue
Block a user