mirror of
https://github.com/whyour/qinglong.git
synced 2026-10-17 07:20:51 +08:00
* fix: address six verified legacy issue regressions * fix: limit issue patch to mirror URLs and CRLF imports * fix: preserve APT formats and third-party repositories
177 lines
6.1 KiB
JavaScript
177 lines
6.1 KiB
JavaScript
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}\nVERSION_CODENAME=bookworm\n`
|
|
: 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/);
|
|
},
|
|
);
|
|
}
|
|
}
|