mirror of
https://github.com/orangecoding/fredy.git
synced 2026-06-16 12:31:07 +00:00
ability to start jobs individually
This commit is contained in:
124
test/services/jobs/jobExecutionService.test.js
Normal file
124
test/services/jobs/jobExecutionService.test.js
Normal file
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright (c) 2025 by Christian Kellner.
|
||||
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
||||
*/
|
||||
|
||||
import { expect } from 'chai';
|
||||
import esmock from 'esmock';
|
||||
import { EventEmitter } from 'node:events';
|
||||
|
||||
describe('services/jobs/jobExecutionService', () => {
|
||||
/** @type {EventEmitter} */
|
||||
let bus;
|
||||
let calls;
|
||||
let state;
|
||||
|
||||
async function initService() {
|
||||
const root = (await import('node:path')).resolve('.');
|
||||
const svcPath = root + '/lib/services/jobs/jobExecutionService.js';
|
||||
const busPath = root + '/lib/services/events/event-bus.js';
|
||||
const jobStoragePath = root + '/lib/services/storage/jobStorage.js';
|
||||
const userStoragePath = root + '/lib/services/storage/userStorage.js';
|
||||
const brokerPath = root + '/lib/services/sse/sse-broker.js';
|
||||
const utilsPath = root + '/lib/utils.js';
|
||||
const loggerPath = root + '/lib/services/logger.js';
|
||||
|
||||
// esmock the service with all its collaborators
|
||||
const mod = await esmock(
|
||||
svcPath,
|
||||
{},
|
||||
{
|
||||
[busPath]: { bus },
|
||||
[jobStoragePath]: {
|
||||
getJob: (id) => state.jobsById[id] || null,
|
||||
getJobs: () => state.jobsList.slice(),
|
||||
},
|
||||
[userStoragePath]: {
|
||||
getUsers: () => state.users.slice(),
|
||||
getUser: (id) => state.users.find((u) => u.id === id) || null,
|
||||
},
|
||||
[brokerPath]: {
|
||||
sendToUsers: (...args) => calls.sent.push(args),
|
||||
},
|
||||
[utilsPath]: {
|
||||
duringWorkingHoursOrNotSet: () => false, // avoid startup run
|
||||
},
|
||||
[loggerPath]: {
|
||||
debug: () => {},
|
||||
info: () => {},
|
||||
warn: () => {},
|
||||
error: () => {},
|
||||
},
|
||||
[root + '/lib/services/jobs/run-state.js']: {
|
||||
isRunning: () => false,
|
||||
markRunning: (id) => {
|
||||
calls.markRunning.push(id);
|
||||
return true;
|
||||
},
|
||||
markFinished: () => {},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// call initializer with minimal deps
|
||||
mod.initJobExecutionService({ providers: [], settings: { demoMode: false }, intervalMs: 0 });
|
||||
return mod;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
bus = new EventEmitter();
|
||||
calls = { sent: [], markRunning: [] };
|
||||
state = {
|
||||
jobsById: {},
|
||||
jobsList: [],
|
||||
users: [],
|
||||
};
|
||||
});
|
||||
|
||||
it('forwards SSE jobStatus to owner, shared users and admins', async () => {
|
||||
state.jobsById['j1'] = { id: 'j1', userId: 'owner1', shared_with_user: ['u2'] };
|
||||
state.users = [
|
||||
{ id: 'a1', isAdmin: true },
|
||||
{ id: 'owner1', isAdmin: false },
|
||||
{ id: 'u2', isAdmin: false },
|
||||
];
|
||||
|
||||
await initService();
|
||||
|
||||
bus.emit('jobs:status', { jobId: 'j1', running: true });
|
||||
|
||||
expect(calls.sent.length).to.equal(1, 'sendToUsers should be called once');
|
||||
const [recipients, event, data] = calls.sent[0];
|
||||
expect(event).to.equal('jobStatus');
|
||||
expect(data).to.deep.equal({ jobId: 'j1', running: true });
|
||||
const got = new Set(recipients);
|
||||
const expected = new Set(['owner1', 'u2', 'a1']);
|
||||
expect(got).to.deep.equal(expected);
|
||||
});
|
||||
|
||||
it('runs all jobs for admin; only own jobs for regular user', async () => {
|
||||
state.jobsList = [
|
||||
{ id: 'j1', enabled: true, userId: 'u1', provider: [] },
|
||||
{ id: 'j2', enabled: true, userId: 'u2', provider: [] },
|
||||
];
|
||||
state.users = [
|
||||
{ id: 'u1', isAdmin: false },
|
||||
{ id: 'u2', isAdmin: false },
|
||||
{ id: 'admin', isAdmin: true },
|
||||
];
|
||||
|
||||
await initService();
|
||||
|
||||
// Non-admin: only own jobs
|
||||
bus.emit('jobs:runAll', { userId: 'u1' });
|
||||
// allow microtasks to flush
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
expect(new Set(calls.markRunning)).to.deep.equal(new Set(['j1']));
|
||||
|
||||
// Admin: all jobs
|
||||
calls.markRunning = [];
|
||||
bus.emit('jobs:runAll', { userId: 'admin' });
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
expect(new Set(calls.markRunning)).to.deep.equal(new Set(['j1', 'j2']));
|
||||
});
|
||||
});
|
||||
@@ -11,7 +11,7 @@ import { send } from './mocks/mockNotification.js';
|
||||
export const providerConfig = JSON.parse(await readFile(new URL('./provider/testProvider.json', import.meta.url)));
|
||||
|
||||
export const mockFredy = async () => {
|
||||
return await esmock('../lib/FredyPipeline', {
|
||||
return await esmock('../lib/FredyPipelineExecutioner', {
|
||||
'../lib/services/storage/listingsStorage.js': {
|
||||
...mockStore,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user