mirror of
https://github.com/whyour/qinglong.git
synced 2026-10-17 07:20:51 +08:00
fix: resolve Debian regression findings (#3087)
* fix: resolve Debian regression findings * fix: reject failed APT index refreshes and simplify SSH home lookup
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const test = require('node:test');
|
||||
const { spawn, spawnSync } = require('node:child_process');
|
||||
const fs = require('node:fs/promises');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const load = require('../helpers/load-security-module.cjs');
|
||||
|
||||
for (const scenario of [
|
||||
{
|
||||
name: 'stderr warning with exit zero',
|
||||
code: 'process.stderr.write("sudo: warning\\n")',
|
||||
saved: true,
|
||||
},
|
||||
{
|
||||
name: 'nonzero exit without stderr',
|
||||
code: 'process.exitCode = 7',
|
||||
saved: false,
|
||||
},
|
||||
{
|
||||
name: 'signal termination',
|
||||
code: 'process.kill(process.pid, "SIGTERM")',
|
||||
saved: false,
|
||||
},
|
||||
{ name: 'spawn failure', shell: '/nonexistent-ql-test-shell', saved: false },
|
||||
]) {
|
||||
test(`Linux mirror persistence follows the process result: ${scenario.name}`, async () => {
|
||||
const logger = { info() {}, error() {} };
|
||||
const ScheduleService = load('back/services/schedule.ts', {
|
||||
'../shared/cronScheduler': {},
|
||||
'../shared/pLimit': { runWithSystemLimit: async (_params, fn) => fn() },
|
||||
'cross-spawn': {
|
||||
spawn: () =>
|
||||
spawn(
|
||||
process.execPath,
|
||||
['-e', scenario.code || ''],
|
||||
scenario.shell ? { shell: scenario.shell } : {},
|
||||
),
|
||||
},
|
||||
}).default;
|
||||
const schedule = new ScheduleService(logger);
|
||||
let completion;
|
||||
const runTask = schedule.runTask.bind(schedule);
|
||||
schedule.runTask = (...args) => (completion = runTask(...args));
|
||||
const messages = [],
|
||||
writes = [];
|
||||
const SystemService = load('back/services/system.ts', {
|
||||
os: { platform: () => 'linux' },
|
||||
'../config': {},
|
||||
'../config/const': {},
|
||||
'../config/util': { updateLinuxMirrorFile: async () => 'fixture' },
|
||||
'../data/dependence': {},
|
||||
'../data/notify': {},
|
||||
'../data/system': {},
|
||||
'../schedule/client': {},
|
||||
'../shared/pLimit': {},
|
||||
'../shared/i18n': {},
|
||||
'../shared/schedulerMutationLock': {},
|
||||
'./notify': class {},
|
||||
'./schedule': ScheduleService,
|
||||
'./sock': class {},
|
||||
}).default;
|
||||
const service = new SystemService(logger, schedule, {
|
||||
sendMessage: (message) => messages.push(message),
|
||||
});
|
||||
service.getSystemConfig = async () => ({ id: 1, info: { lang: 'en-US' } });
|
||||
service.updateAuthDb = async (doc) => writes.push(doc);
|
||||
let ended = 0;
|
||||
await service.updateLinuxMirror(
|
||||
{ linuxMirror: 'https://mirror.example' },
|
||||
undefined,
|
||||
() => ended++,
|
||||
);
|
||||
const result = await completion;
|
||||
assert.equal(ended, 1);
|
||||
assert.equal(writes.length, scenario.saved ? 1 : 0);
|
||||
assert.ok(messages.some((message) => message.status === 'completed'));
|
||||
if (scenario.saved) {
|
||||
assert.equal(result.code, 0);
|
||||
assert.ok(
|
||||
messages.some((message) => message.message.includes('sudo: warning')),
|
||||
);
|
||||
assert.deepEqual(writes[0].info, {
|
||||
lang: 'en-US',
|
||||
linuxMirror: 'https://mirror.example',
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const hasApt = spawnSync('apt-get', ['--version']).status === 0;
|
||||
for (const distribution of ['Debian', 'Ubuntu']) {
|
||||
for (const existingMirror of [false, true]) {
|
||||
test(
|
||||
`${distribution} mirror update rejects failed index downloads with ${
|
||||
existingMirror ? 'an existing' : 'an empty'
|
||||
} source`,
|
||||
{ skip: !hasApt },
|
||||
async (t) => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'ql-apt-result-'));
|
||||
t.after(() => fs.rm(root, { recursive: true, force: true }));
|
||||
const parts = path.join(root, 'parts');
|
||||
await fs.mkdir(parts);
|
||||
await fs.mkdir(path.join(root, 'lists', 'partial'), {
|
||||
recursive: true,
|
||||
});
|
||||
const source = path.join(root, 'sources.list');
|
||||
await fs.writeFile(
|
||||
source,
|
||||
'deb http://127.0.0.1:1/debian bookworm main\n',
|
||||
);
|
||||
// Load only fixture configuration, including all writable APT paths.
|
||||
const aptConfig = path.join(root, 'apt.conf');
|
||||
await fs.writeFile(
|
||||
aptConfig,
|
||||
`Dir::Etc::main "/dev/null";
|
||||
Dir::Etc::parts "${parts}";
|
||||
Dir::Etc::sourcelist "${source}";
|
||||
Dir::Etc::sourceparts "${parts}";
|
||||
Dir::State::lists "${path.join(root, 'lists')}";
|
||||
Dir::Cache "${path.join(root, 'cache')}";
|
||||
Dir::Log "${path.join(root, 'logs')}";
|
||||
Acquire::Retries "0";
|
||||
Acquire::http::Proxy "DIRECT";
|
||||
Acquire::http::Timeout "1";
|
||||
`,
|
||||
);
|
||||
const util = load('back/config/util.ts', {
|
||||
'fs/promises': {
|
||||
readFile: async (file) =>
|
||||
file === '/etc/os-release'
|
||||
? distribution
|
||||
: existingMirror
|
||||
? 'URIs: http://old.example/debian\n'
|
||||
: '',
|
||||
},
|
||||
os: { platform: () => 'linux' },
|
||||
'./index': {},
|
||||
'./const': {},
|
||||
'./share': {},
|
||||
'./container': { isInContainer: () => false },
|
||||
'../loaders/logger': {},
|
||||
'../shared/utils': {},
|
||||
'../shared/fileAccess': {},
|
||||
'../data/dependence': {},
|
||||
});
|
||||
const previousOS = process.env.QL_OS_TYPE;
|
||||
let command;
|
||||
try {
|
||||
process.env.QL_OS_TYPE = distribution.toLowerCase();
|
||||
command = await util.updateLinuxMirrorFile('http://127.0.0.1:1');
|
||||
} finally {
|
||||
if (previousOS === undefined) delete process.env.QL_OS_TYPE;
|
||||
else process.env.QL_OS_TYPE = previousOS;
|
||||
}
|
||||
// Exercise the generated updater without modifying host source files.
|
||||
const [program, ...args] = command
|
||||
.slice(command.lastIndexOf('&&') + 2)
|
||||
.trim()
|
||||
.split(/\s+/);
|
||||
assert.equal(program, 'apt-get');
|
||||
const options = {
|
||||
encoding: 'utf8',
|
||||
timeout: 10000,
|
||||
env: { ...process.env, APT_CONFIG: aptConfig },
|
||||
};
|
||||
const baseline = spawnSync('apt-get', ['update'], options);
|
||||
assert.equal(baseline.status, 0, baseline.stderr);
|
||||
assert.match(baseline.stderr, /Failed to fetch/);
|
||||
const result = spawnSync(program, args, options);
|
||||
assert.equal(result.status, 100, result.stderr);
|
||||
assert.match(result.stderr, /Failed to fetch/);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user