Files
qinglong/test/back/script-notification-rpc.test.cjs
T

115 lines
3.9 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const os = require('node:os');
const { promisify } = require('node:util');
const { execFile } = require('node:child_process');
const grpc = require('@grpc/grpc-js');
const loader = require('@grpc/proto-loader');
const load = require('../helpers/load-security-module.cjs');
const run = promisify(execFile);
test('JS and Python script reports reach the same authenticated RPC with bounded failure', async (t) => {
const data = fs.mkdtempSync(path.join(os.tmpdir(), 'ql-audit-rpc-'));
t.after(() => fs.rmSync(data, { recursive: true, force: true }));
fs.mkdirSync(path.join(data, 'config'));
const certs = await load('back/config/grpcCerts.ts', {
'./index': { configPath: path.join(data, 'config') },
'./util': { fileExist: async (p) => fs.existsSync(p) },
'../loaders/logger': { info() {}, error() {} },
}).initGrpcCerts();
const definition = loader.loadSync(path.resolve('back/protos/api.proto'), {
keepCase: true,
defaults: true,
});
const Api = grpc.loadPackageDefinition(definition).com.ql.api.Api;
const received = [];
const server = new grpc.Server();
server.addService(Api.service, {
RecordNotifyLog: (call, callback) => {
received.push(call.request);
callback(null, { code: 200 });
},
});
const credentials = grpc.ServerCredentials.createSsl(
Buffer.from(certs.caCert),
[
{
private_key: Buffer.from(certs.serverKey),
cert_chain: Buffer.from(certs.serverCert),
},
],
true,
);
const port = await new Promise((resolve, reject) =>
server.bindAsync('127.0.0.1:0', credentials, (error, port) =>
error ? reject(error) : resolve(port),
),
);
t.after(() => server.forceShutdown());
const env = {
...process.env,
QL_DIR: process.cwd(),
QL_DATA_DIR: data,
GRPC_PORT: String(port),
ID: '9',
HITOKOTO: 'false',
};
await run(
process.execPath,
[
'-e',
`require('./shell/preload/notificationAudit.js')({title:'JS task',content:'body',success:true,notifyType:'smtp',scriptName:'task.js',taskId:process.env.ID})`,
],
{ env, timeout: 8000 },
);
const python = `import importlib.util,sys,types,smtplib,os
sys.modules['requests']=types.SimpleNamespace()
spec=importlib.util.spec_from_file_location('notify',os.path.join(os.environ['QL_DIR'],'sample/notify.py'))
n=importlib.util.module_from_spec(spec);spec.loader.exec_module(n)
class Mail:
def __init__(self,*a,**kw): pass
def login(self,*a): pass
def sendmail(self,*a): return {}
def close(self): pass
smtplib.SMTP_SSL=Mail
n.push_config={'SMTP_SERVER':'fixture','SMTP_SSL':'true','SMTP_EMAIL':'sender@example.test','SMTP_PASSWORD':'private-password','SMTP_NAME':'fixture','HITOKOTO':False}
n.send('Python success','body')
def rejected(*a,**kw): raise RuntimeError('private-password')
smtplib.SMTP_SSL=rejected
n.send('Python failure','body')
`;
await run('python3', ['-c', python], { env, timeout: 12000 });
assert.deepEqual(
received.map((x) => [x.title, x.success]),
[
['JS task', true],
['Python success', true],
['Python failure', false],
],
);
assert.ok(received.every((x) => x.taskId === '9'));
assert.ok(!JSON.stringify(received).includes('private-password'));
// Missing certificates and an older/unavailable backend must not kill scripts.
await run(
process.execPath,
[
'-e',
`require('./shell/preload/notificationAudit.js')({title:'offline'}).then(()=>console.log('done'))`,
],
{ env: { ...env, QL_DATA_DIR: path.join(data, 'missing') }, timeout: 8000 },
);
server.forceShutdown();
const started = Date.now();
await run(
process.execPath,
[
'-e',
`require('./shell/preload/notificationAudit.js')({title:'offline'})`,
],
{ env, timeout: 8000 },
);
assert.ok(Date.now() - started < 5000);
});