CI / Build & Quality Checks (pull_request) Successful in 1m54s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Failing after 13s
CI / Playwright smoke (e2e) (pull_request) Successful in 12m47s
The pacman / apt update command installed the downloaded package with no integrity check. Download it with the release's SHA256SUMS.txt and .minisig into a fresh temp dir, verify the signature with the updater key (minisign), check the hash, then install. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
101 lines
4.1 KiB
TypeScript
101 lines
4.1 KiB
TypeScript
import { test } from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import {
|
||
describeUpdateError,
|
||
manualDownloadUrl,
|
||
MANUAL_DOWNLOAD_URL,
|
||
parseUpdateError,
|
||
} from './updateErrors';
|
||
|
||
test('parseUpdateError reads the native phase prefix', () => {
|
||
assert.deepEqual(parseUpdateError('download: error sending request for url (x)', 'check'), {
|
||
phase: 'download',
|
||
message: 'error sending request for url (x)',
|
||
});
|
||
assert.deepEqual(parseUpdateError('install: access denied', 'download'), {
|
||
phase: 'install',
|
||
message: 'access denied',
|
||
});
|
||
});
|
||
|
||
test('parseUpdateError falls back to the caller phase for unprefixed errors', () => {
|
||
assert.deepEqual(parseUpdateError('error sending request', 'check'), {
|
||
phase: 'check',
|
||
message: 'error sending request',
|
||
});
|
||
});
|
||
|
||
test('the reported download failure reads as a busy server, not a failed check', () => {
|
||
const { phase, message } = parseUpdateError(
|
||
'download: error sending request for url (https://code.lotusguild.org/LotusGuild/cinny-desktop/releases/download/latest/LotusChat-x86_64-setup.nsis.zip)',
|
||
'check',
|
||
);
|
||
const text = describeUpdateError(phase, message);
|
||
assert.match(text, /didn’t finish downloading/);
|
||
assert.match(text, /download the installer yourself/);
|
||
});
|
||
|
||
test('only signature failures are reported as a failed safety check', () => {
|
||
assert.match(
|
||
describeUpdateError('download', 'The signature X could not be decoded'),
|
||
/safety check/,
|
||
);
|
||
assert.doesNotMatch(describeUpdateError('download', 'No space left on device'), /safety/);
|
||
});
|
||
|
||
test('check failures never suggest the app is broken', () => {
|
||
assert.match(describeUpdateError('check', 'operation timed out'), /Lotus Chat still works/);
|
||
assert.match(describeUpdateError('check', 'weird'), /Lotus Chat still works/);
|
||
});
|
||
|
||
test('manual download link: Windows gets the installer, others the release page', () => {
|
||
assert.equal(
|
||
manualDownloadUrl('Mozilla/5.0 (Windows NT 10.0; Win64; x64) Edg/140'),
|
||
MANUAL_DOWNLOAD_URL.windows,
|
||
);
|
||
assert.equal(manualDownloadUrl('Mozilla/5.0 (X11; Linux x86_64)'), MANUAL_DOWNLOAD_URL.other);
|
||
});
|
||
|
||
test('install kinds: anything unknown keeps the in-app updater', async () => {
|
||
const { toInstallKind } = await import('./updateErrors');
|
||
assert.equal(toInstallKind('pacman'), 'pacman');
|
||
assert.equal(toInstallKind('deb'), 'deb');
|
||
assert.equal(toInstallKind('manual'), 'manual');
|
||
assert.equal(toInstallKind('in-app'), 'in-app');
|
||
assert.equal(toInstallKind(undefined), 'in-app', 'older desktop build without the command');
|
||
assert.equal(toInstallKind('rpm'), 'in-app');
|
||
});
|
||
|
||
test('package installs get their package manager’s command, not an Install button', async () => {
|
||
const { packageUpdateHelp, isPackageManagedError } = await import('./updateErrors');
|
||
const pacman = packageUpdateHelp('pacman')!;
|
||
// [cinny-desktop #36] signature, then checksum, then install, in that order;
|
||
// a local `pacman -U ./file` (not `<url>`, which also wants `<url>.sig`).
|
||
const steps = pacman.command!.split(' && ');
|
||
assert.equal(steps[0], 'cd "$(mktemp -d)"');
|
||
assert.match(
|
||
steps[1],
|
||
/-fLO \S+\/LotusChat-x86_64\.pkg\.tar\.zst -fLO \S+\/SHA256SUMS\.txt -fLO \S+\/SHA256SUMS\.txt\.minisig$/,
|
||
);
|
||
assert.equal(
|
||
steps[2],
|
||
'minisign -Vm SHA256SUMS.txt -P RWRZZdyii05/NUcz337A5SAbiZK+NPVDWuiL2mM5Jk1p/LfRmNfiZ/6l',
|
||
);
|
||
assert.equal(steps[3], 'sha256sum -c --ignore-missing SHA256SUMS.txt');
|
||
assert.equal(steps[4], 'sudo pacman -U ./LotusChat-x86_64.pkg.tar.zst');
|
||
assert.match(pacman.url, /LotusChat-x86_64\.pkg\.tar\.zst$/);
|
||
const deb = packageUpdateHelp('deb')!;
|
||
assert.match(deb.command!, /minisign -Vm SHA256SUMS\.txt/);
|
||
assert.match(deb.command!, /sudo apt install \.\/LotusChat-x86_64\.deb$/);
|
||
assert.equal(packageUpdateHelp('manual')?.command, undefined);
|
||
assert.equal(packageUpdateHelp('in-app'), undefined);
|
||
assert.equal(
|
||
isPackageManagedError('package-managed (pacman): update Lotus Chat with your package manager'),
|
||
true,
|
||
);
|
||
assert.equal(
|
||
isPackageManagedError('Permission denied (os error 13) at path "/usr/bin/tauri_current_app"'),
|
||
false,
|
||
);
|
||
});
|