浏览代码

test: event dispatcher

Nicolas Meienberger 2 年之前
父节点
当前提交
b6e41bbfb6

+ 1 - 1
docker-compose.dev.yml

@@ -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

+ 1 - 1
docker-compose.rc.yml

@@ -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:

+ 1 - 1
docker-compose.yml

@@ -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:

+ 20 - 11
packages/system-api/src/core/config/EventDispatcher.ts

@@ -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, '');
   }

+ 198 - 0
packages/system-api/src/core/config/__tests__/EventDispatcher.test.ts

@@ -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();
+  });
+});

+ 0 - 5
packages/system-api/src/modules/fs/fs.helpers.ts

@@ -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;

+ 1 - 1
packages/system-api/src/test/jest-setup.ts

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

+ 0 - 8
scripts/system/restart.sh

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

+ 0 - 8
scripts/system/update.sh

@@ -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

+ 10 - 0
scripts/watcher.sh

@@ -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
 }