mirror of
https://github.com/whyour/qinglong.git
synced 2026-10-17 07:20:51 +08:00
153 lines
4.4 KiB
JavaScript
153 lines
4.4 KiB
JavaScript
const test = require('node:test'),
|
|
assert = require('node:assert/strict'),
|
|
fs = require('node:fs'),
|
|
path = require('node:path'),
|
|
vm = require('node:vm');
|
|
const load = require('../helpers/load-security-module.cjs');
|
|
|
|
function script(config = {}, response = {}, auditError = false) {
|
|
const logs = [],
|
|
source = fs.readFileSync('sample/notify.js', 'utf8');
|
|
const transport = async () => ({
|
|
statusCode: response.httpStatus || 200,
|
|
body: { text: async () => JSON.stringify(response) },
|
|
});
|
|
const context = {
|
|
module: { exports: {} },
|
|
console: { log() {}, info() {} },
|
|
process: {
|
|
env: { QL_DIR: '/fixture', HITOKOTO: 'false', ...config },
|
|
argv: ['node', '/fixture/scripts/test-job.js'],
|
|
},
|
|
Buffer,
|
|
URL,
|
|
setTimeout,
|
|
clearTimeout,
|
|
};
|
|
context.require = (name) => {
|
|
if (name === 'undici')
|
|
return { request: transport, ProxyAgent: class {}, FormData };
|
|
if (name === '/fixture/shell/preload/notificationAudit.js')
|
|
return async (x) => {
|
|
if (auditError) throw Error('offline');
|
|
logs.push(x);
|
|
};
|
|
if (name === 'nodemailer')
|
|
return {
|
|
createTransport: () => ({
|
|
sendMail: async () => {
|
|
if (response.smtpFailure) throw Error('auth rejected');
|
|
return { messageId: 'fixture' };
|
|
},
|
|
close() {},
|
|
}),
|
|
};
|
|
return require(name);
|
|
};
|
|
vm.runInNewContext(source, context, {
|
|
filename: path.resolve('sample/notify.js'),
|
|
});
|
|
return { send: context.module.exports.sendNotify, logs };
|
|
}
|
|
|
|
test('JS helper logs only configured channels, success/failure and script/task provenance', async () => {
|
|
for (const fail of [false, true]) {
|
|
const s = script(
|
|
{
|
|
SMTP_EMAIL: 'sender@example.test',
|
|
SMTP_PASSWORD: 'private-password',
|
|
SMTP_SERVICE: 'fixture',
|
|
ID: '17',
|
|
},
|
|
{ smtpFailure: fail },
|
|
);
|
|
await s.send('标题', '正文');
|
|
assert.equal(s.logs.length, 1);
|
|
assert.equal(s.logs[0].success, !fail);
|
|
assert.equal(s.logs[0].notifyType, 'smtp');
|
|
assert.equal(s.logs[0].scriptName, 'test-job.js');
|
|
assert.equal(s.logs[0].taskId, '17');
|
|
assert.ok(!JSON.stringify(s.logs).includes('private-password'));
|
|
}
|
|
const s = script();
|
|
await s.send('none', 'none');
|
|
assert.equal(s.logs.length, 0);
|
|
});
|
|
|
|
test('HTTP business rejection is audited as failure and logging errors do not change sends', async () => {
|
|
for (const [code, success] of [
|
|
[200, true],
|
|
[500, false],
|
|
]) {
|
|
const s = script({ BARK_PUSH: 'https://fixture.invalid' }, { code });
|
|
await s.send('title', 'body');
|
|
assert.equal(s.logs.length, 1);
|
|
assert.equal(s.logs[0].success, success);
|
|
}
|
|
const offline = script(
|
|
{
|
|
SMTP_EMAIL: 'sender@example.test',
|
|
SMTP_PASSWORD: 'fixture',
|
|
SMTP_SERVICE: 'fixture',
|
|
},
|
|
{},
|
|
true,
|
|
);
|
|
await offline.send('still sends', 'body');
|
|
});
|
|
|
|
test('multi-channel helpers write one result per actual channel', async () => {
|
|
const s = script(
|
|
{
|
|
GOTIFY_URL: 'https://fixture.invalid',
|
|
GOTIFY_TOKEN: 'private-token',
|
|
SMTP_EMAIL: 'sender@example.test',
|
|
SMTP_PASSWORD: 'fixture',
|
|
SMTP_SERVICE: 'fixture',
|
|
},
|
|
{ id: 123 },
|
|
);
|
|
await s.send('title', 'body');
|
|
assert.deepEqual(s.logs.map((x) => x.notifyType).sort(), ['gotify', 'smtp']);
|
|
assert.ok(s.logs.every((x) => x.success));
|
|
});
|
|
|
|
test('script audit protobuf round trips without notification credentials', () => {
|
|
const p = load('back/protos/api.ts');
|
|
const value = p.RecordNotifyLogRequest.create({
|
|
title: '中文',
|
|
content: '内容',
|
|
success: false,
|
|
notifyType: 'smtp',
|
|
scriptName: 'job.py',
|
|
taskId: '12',
|
|
});
|
|
assert.deepEqual(
|
|
p.RecordNotifyLogRequest.decode(
|
|
p.RecordNotifyLogRequest.encode(value).finish(),
|
|
),
|
|
value,
|
|
);
|
|
assert.ok(p.ApiService.recordNotifyLog);
|
|
});
|
|
|
|
test('multi-target Chronocat records one failure if any recipient fails', async () => {
|
|
let calls = 0;
|
|
const response = {};
|
|
Object.defineProperty(response, 'httpStatus', {
|
|
get: () => (++calls === 1 ? 200 : 500),
|
|
});
|
|
const s = script(
|
|
{
|
|
CHRONOCAT_URL: 'https://fixture.invalid',
|
|
CHRONOCAT_TOKEN: 'private-token',
|
|
CHRONOCAT_QQ: 'user_id=123&group_id=456',
|
|
},
|
|
response,
|
|
);
|
|
await s.send('title', 'body');
|
|
assert.equal(calls, 2);
|
|
assert.equal(s.logs.length, 1);
|
|
assert.equal(s.logs[0].success, false);
|
|
});
|