test: event dispatcher

This commit is contained in:
Nicolas Meienberger 2022-10-05 22:16:44 +02:00
parent a024b03508
commit b6e41bbfb6
10 changed files with 232 additions and 36 deletions

View file

@ -55,7 +55,7 @@ services:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ${PWD}/apps:/runtipi/apps:ro
- ${PWD}/repos:/runtipi/repos:ro
- ${PWD}/state:/runtipi/state:ro
- ${PWD}/state:/runtipi/state
- ${PWD}/packages/system-api/src:/api/src
- ${PWD}/logs:/app/logs
- ${STORAGE_PATH}:/app/storage

View file

@ -48,7 +48,7 @@ services:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ${PWD}/apps:/runtipi/apps:ro
- ${PWD}/repos:/runtipi/repos:ro
- ${PWD}/state:/runtipi/state:ro
- ${PWD}/state:/runtipi/state
- ${PWD}/logs:/app/logs
- ${STORAGE_PATH}:/app/storage
environment:

View file

@ -48,7 +48,7 @@ services:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ${PWD}/apps:/runtipi/apps:ro
- ${PWD}/repos:/runtipi/repos:ro
- ${PWD}/state:/runtipi/state:ro
- ${PWD}/state:/runtipi/state
- ${PWD}/logs:/app/logs
- ${STORAGE_PATH}:/app/storage
environment:

View file

@ -33,6 +33,8 @@ class EventDispatcher {
private interval: NodeJS.Timer;
private intervals: NodeJS.Timer[] = [];
constructor() {
const timer = this.pollQueue();
this.interval = timer;
@ -67,7 +69,6 @@ class EventDispatcher {
return;
}
console.log('Status: ', status, 'clearing');
this.clearEvent(this.lock.id);
this.lock = null;
}
@ -77,10 +78,17 @@ class EventDispatcher {
*/
private pollQueue() {
logger.info('EventDispatcher: Polling queue...');
return setInterval(() => {
this.runEvent();
this.collectLockStatusAndClean();
}, 1000);
if (!this.interval) {
const id = setInterval(() => {
this.runEvent();
this.collectLockStatusAndClean();
}, 1000);
this.intervals.push(id);
return id;
}
return this.interval;
}
/**
@ -122,7 +130,7 @@ class EventDispatcher {
}
const file = fs.readFileSync(WATCH_FILE, 'utf8');
const lines = file.split('\n') || [];
const lines = file?.split('\n') || [];
const line = lines.find((l) => l.startsWith(`${event.type} ${event.id}`));
if (!line) {
@ -131,10 +139,6 @@ class EventDispatcher {
const status = line.split(' ')[2] as EventStatusTypes;
if (status === 'error') {
console.error(lines);
}
return status;
}
@ -180,6 +184,7 @@ class EventDispatcher {
return new Promise((resolve) => {
const interval = setInterval(() => {
this.intervals.push(interval);
const status = this.getEventStatus(event.id);
let log = '';
@ -198,10 +203,14 @@ class EventDispatcher {
});
}
public clearInterval() {
clearInterval(this.interval);
this.intervals.forEach((i) => clearInterval(i));
}
public clear() {
this.queue = [];
this.lock = null;
clearInterval(this.interval);
EventDispatcher.instance = null;
fs.writeFileSync(WATCH_FILE, '');
}

View file

@ -0,0 +1,198 @@
import fs from 'fs-extra';
import { eventDispatcher, EventTypes } from '../EventDispatcher';
const WATCH_FILE = '/runtipi/state/events';
jest.mock('fs-extra');
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
beforeEach(() => {
eventDispatcher.clear();
fs.writeFileSync(WATCH_FILE, '');
fs.writeFileSync('/app/logs/123.log', 'test');
});
describe('EventDispatcher - dispatchEvent', () => {
it('should dispatch an event', () => {
const event = eventDispatcher.dispatchEvent(EventTypes.APP);
expect(event.id).toBeDefined();
});
it('should dispatch an event with args', () => {
const event = eventDispatcher.dispatchEvent(EventTypes.APP, ['--help']);
expect(event.id).toBeDefined();
});
it('Should put events into queue', async () => {
eventDispatcher.dispatchEvent(EventTypes.APP, ['--help']);
eventDispatcher.dispatchEvent(EventTypes.APP, ['--help']);
// @ts-ignore
const queue = eventDispatcher.queue;
expect(queue.length).toBe(2);
});
it('Should put first event into lock after 1 sec', async () => {
eventDispatcher.dispatchEvent(EventTypes.APP, ['--help']);
eventDispatcher.dispatchEvent(EventTypes.UPDATE, ['--help']);
// @ts-ignore
const queue = eventDispatcher.queue;
await wait(1050);
// @ts-ignore
const lock = eventDispatcher.lock;
expect(queue.length).toBe(2);
expect(lock).toBeDefined();
expect(lock?.type).toBe(EventTypes.APP);
});
it('Should clear event once its status is success', async () => {
// @ts-ignore
jest.spyOn(eventDispatcher, 'getEventStatus').mockReturnValueOnce('success');
eventDispatcher.dispatchEvent(EventTypes.APP, ['--help']);
await wait(1050);
// @ts-ignore
const queue = eventDispatcher.queue;
expect(queue.length).toBe(0);
});
it('Should clear event once its status is error', async () => {
// @ts-ignore
jest.spyOn(eventDispatcher, 'getEventStatus').mockReturnValueOnce('error');
eventDispatcher.dispatchEvent(EventTypes.APP, ['--help']);
await wait(1050);
// @ts-ignore
const queue = eventDispatcher.queue;
expect(queue.length).toBe(0);
});
});
describe('EventDispatcher - dispatchEventAsync', () => {
it('Should dispatch an event and wait for it to finish', async () => {
// @ts-ignore
jest.spyOn(eventDispatcher, 'getEventStatus').mockReturnValueOnce('success');
const { success } = await eventDispatcher.dispatchEventAsync(EventTypes.APP, ['--help']);
expect(success).toBe(true);
});
it('Should dispatch an event and wait for it to finish with error', async () => {
// @ts-ignore
jest.spyOn(eventDispatcher, 'getEventStatus').mockReturnValueOnce('error');
const { success } = await eventDispatcher.dispatchEventAsync(EventTypes.APP, ['--help']);
expect(success).toBe(false);
});
});
describe('EventDispatcher - runEvent', () => {
it('Should do nothing if there is a lock', async () => {
// @ts-ignore
eventDispatcher.lock = { id: '123', type: EventTypes.APP, args: [] };
// @ts-ignore
await eventDispatcher.runEvent();
// @ts-ignore
const file = fs.readFileSync(WATCH_FILE, 'utf8');
expect(file).toBe('');
});
it('Should do nothing if there is no event in queue', async () => {
// @ts-ignore
await eventDispatcher.runEvent();
// @ts-ignore
const file = fs.readFileSync(WATCH_FILE, 'utf8');
expect(file).toBe('');
});
});
describe('EventDispatcher - getEventStatus', () => {
it('Should return success if event is not in the queue', async () => {
// @ts-ignore
eventDispatcher.queue = [];
// @ts-ignore
const status = eventDispatcher.getEventStatus('123');
expect(status).toBe('success');
});
it('Should return error if event is expired', async () => {
const dateFiveMinutesAgo = new Date(new Date().getTime() - 5 * 60 * 10000);
// @ts-ignore
eventDispatcher.queue = [{ id: '123', type: EventTypes.APP, args: [], creationDate: dateFiveMinutesAgo }];
// @ts-ignore
const status = eventDispatcher.getEventStatus('123');
expect(status).toBe('error');
});
it('Should be waiting if line is not found in the file', async () => {
// @ts-ignore
eventDispatcher.queue = [{ id: '123', type: EventTypes.APP, args: [], creationDate: new Date() }];
// @ts-ignore
const status = eventDispatcher.getEventStatus('123');
expect(status).toBe('waiting');
});
});
describe('EventDispatcher - clearEvent', () => {
it('Should clear event', async () => {
// @ts-ignore
eventDispatcher.queue = [{ id: '123', type: EventTypes.APP, args: [], creationDate: new Date() }];
// @ts-ignore
eventDispatcher.clearEvent('123');
// @ts-ignore
const queue = eventDispatcher.queue;
expect(queue.length).toBe(0);
});
});
describe('EventDispatcher - pollQueue', () => {
it('Should not create a new interval if one already exists', async () => {
// @ts-ignore
eventDispatcher.interval = 123;
// @ts-ignore
const id = eventDispatcher.pollQueue();
// @ts-ignore
const interval = eventDispatcher.interval;
expect(interval).toBe(123);
expect(id).toBe(123);
clearInterval(interval);
clearInterval(id);
});
});
describe('EventDispatcher - collectLockStatusAndClean', () => {
it('Should do nothing if there is no lock', async () => {
// @ts-ignore
eventDispatcher.lock = null;
// @ts-ignore
eventDispatcher.collectLockStatusAndClean();
// @ts-ignore
const lock = eventDispatcher.lock;
expect(lock).toBeNull();
});
});

View file

@ -1,15 +1,10 @@
import fs from 'fs-extra';
import childProcess from 'child_process';
import { getConfig } from '../../core/config/TipiConfig';
export const readJsonFile = (path: string): any => {
try {
const rawFile = fs.readFileSync(path).toString();
if (!rawFile) {
return null;
}
return JSON.parse(rawFile);
} catch (e) {
return null;

View file

@ -7,5 +7,5 @@ jest.mock('../config/logger/logger', () => ({
}));
afterAll(() => {
eventDispatcher.clear();
eventDispatcher.clearInterval();
});

View file

@ -1,8 +0,0 @@
#!/usr/bin/env bash
echo "Restarting Tipi..."
scripts/stop.sh
scripts/start.sh
exit

View file

@ -1,8 +0,0 @@
#!/usr/bin/env bash
echo "Updating Tipi to latest version..."
scripts/stop.sh
git pull origin master
scripts/start.sh
exit

View file

@ -83,6 +83,16 @@ function select_command() {
return 0
fi
if [ "$command" = "update" ]; then
run_command "${ROOT_FOLDER}/scripts/system.sh" "$id" "update"
return 0
fi
if [ "$command" = "restart" ]; then
run_command "${ROOT_FOLDER}/scripts/system.sh" "$id" "restart"
return 0
fi
echo "Unknown command ${command}"
return 0
}