|
@@ -1,13 +1,12 @@
|
|
import AppsService from '../apps.service';
|
|
import AppsService from '../apps.service';
|
|
import fs from 'fs-extra';
|
|
import fs from 'fs-extra';
|
|
-import childProcess from 'child_process';
|
|
|
|
import { AppInfo, AppStatusEnum } from '../apps.types';
|
|
import { AppInfo, AppStatusEnum } from '../apps.types';
|
|
import App from '../app.entity';
|
|
import App from '../app.entity';
|
|
import { createApp } from './apps.factory';
|
|
import { createApp } from './apps.factory';
|
|
import { setupConnection, teardownConnection } from '../../../test/connection';
|
|
import { setupConnection, teardownConnection } from '../../../test/connection';
|
|
import { DataSource } from 'typeorm';
|
|
import { DataSource } from 'typeorm';
|
|
import { getEnvMap } from '../apps.helpers';
|
|
import { getEnvMap } from '../apps.helpers';
|
|
-import { getConfig } from '../../../core/config/TipiConfig';
|
|
|
|
|
|
+import EventDispatcher, { eventDispatcher, EventTypes } from '../../../core/config/EventDispatcher';
|
|
|
|
|
|
jest.mock('fs-extra');
|
|
jest.mock('fs-extra');
|
|
jest.mock('child_process');
|
|
jest.mock('child_process');
|
|
@@ -23,6 +22,7 @@ beforeEach(async () => {
|
|
jest.resetModules();
|
|
jest.resetModules();
|
|
jest.resetAllMocks();
|
|
jest.resetAllMocks();
|
|
jest.restoreAllMocks();
|
|
jest.restoreAllMocks();
|
|
|
|
+ EventDispatcher.prototype.dispatchEventAsync = jest.fn().mockResolvedValue({ success: true });
|
|
await App.clear();
|
|
await App.clear();
|
|
});
|
|
});
|
|
|
|
|
|
@@ -42,6 +42,7 @@ describe('Install app', () => {
|
|
});
|
|
});
|
|
|
|
|
|
it('Should correctly generate env file for app', async () => {
|
|
it('Should correctly generate env file for app', async () => {
|
|
|
|
+ // EventDispatcher.prototype.dispatchEventAsync = jest.fn().mockResolvedValueOnce({ success: true });
|
|
await AppsService.installApp(app1.id, { TEST_FIELD: 'test' });
|
|
await AppsService.installApp(app1.id, { TEST_FIELD: 'test' });
|
|
const envFile = fs.readFileSync(`/app/storage/app-data/${app1.id}/app.env`).toString();
|
|
const envFile = fs.readFileSync(`/app/storage/app-data/${app1.id}/app.env`).toString();
|
|
|
|
|
|
@@ -59,39 +60,28 @@ describe('Install app', () => {
|
|
expect(app!.status).toBe(AppStatusEnum.RUNNING);
|
|
expect(app!.status).toBe(AppStatusEnum.RUNNING);
|
|
});
|
|
});
|
|
|
|
|
|
- it('Should correctly run app script', async () => {
|
|
|
|
- const spy = jest.spyOn(childProcess, 'execFile');
|
|
|
|
- await AppsService.installApp(app1.id, { TEST_FIELD: 'test' });
|
|
|
|
-
|
|
|
|
- expect(spy.mock.lastCall).toEqual([`${getConfig().rootFolder}/scripts/app.sh`, ['install', app1.id], {}, expect.any(Function)]);
|
|
|
|
- spy.mockRestore();
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
it('Should start app if already installed', async () => {
|
|
it('Should start app if already installed', async () => {
|
|
- const spy = jest.spyOn(childProcess, 'execFile');
|
|
|
|
|
|
+ const spy = jest.spyOn(eventDispatcher, 'dispatchEventAsync');
|
|
|
|
|
|
await AppsService.installApp(app1.id, { TEST_FIELD: 'test' });
|
|
await AppsService.installApp(app1.id, { TEST_FIELD: 'test' });
|
|
await AppsService.installApp(app1.id, { TEST_FIELD: 'test' });
|
|
await AppsService.installApp(app1.id, { TEST_FIELD: 'test' });
|
|
|
|
|
|
expect(spy.mock.calls.length).toBe(2);
|
|
expect(spy.mock.calls.length).toBe(2);
|
|
- expect(spy.mock.calls[0]).toEqual(['/runtipi/scripts/app.sh', ['install', app1.id], {}, expect.any(Function)]);
|
|
|
|
- expect(spy.mock.calls[1]).toEqual(['/runtipi/scripts/app.sh', ['start', app1.id], {}, expect.any(Function)]);
|
|
|
|
|
|
+ expect(spy.mock.calls[0]).toEqual([EventTypes.APP, ['install', app1.id]]);
|
|
|
|
+ expect(spy.mock.calls[1]).toEqual([EventTypes.APP, ['start', app1.id]]);
|
|
|
|
|
|
spy.mockRestore();
|
|
spy.mockRestore();
|
|
});
|
|
});
|
|
|
|
|
|
it('Should delete app if install script fails', async () => {
|
|
it('Should delete app if install script fails', async () => {
|
|
- const spy = jest.spyOn(childProcess, 'execFile');
|
|
|
|
- spy.mockImplementation(() => {
|
|
|
|
- throw new Error('Test error');
|
|
|
|
- });
|
|
|
|
|
|
+ // Arrange
|
|
|
|
+ EventDispatcher.prototype.dispatchEventAsync = jest.fn().mockResolvedValueOnce({ success: false, stdout: 'error' });
|
|
|
|
|
|
- await expect(AppsService.installApp(app1.id, { TEST_FIELD: 'test' })).rejects.toThrow('Test error');
|
|
|
|
|
|
+ await expect(AppsService.installApp(app1.id, { TEST_FIELD: 'test' })).rejects.toThrow(`App ${app1.id} failed to install\nstdout: error`);
|
|
|
|
|
|
const app = await App.findOne({ where: { id: app1.id } });
|
|
const app = await App.findOne({ where: { id: app1.id } });
|
|
|
|
|
|
expect(app).toBeNull();
|
|
expect(app).toBeNull();
|
|
- spy.mockRestore();
|
|
|
|
});
|
|
});
|
|
|
|
|
|
it('Should throw if required form fields are missing', async () => {
|
|
it('Should throw if required form fields are missing', async () => {
|
|
@@ -175,56 +165,51 @@ describe('Uninstall app', () => {
|
|
});
|
|
});
|
|
|
|
|
|
it('App should be installed by default', async () => {
|
|
it('App should be installed by default', async () => {
|
|
|
|
+ // Act
|
|
const app = await App.findOne({ where: { id: app1.id } });
|
|
const app = await App.findOne({ where: { id: app1.id } });
|
|
|
|
+
|
|
|
|
+ // Assert
|
|
expect(app).toBeDefined();
|
|
expect(app).toBeDefined();
|
|
expect(app!.id).toBe(app1.id);
|
|
expect(app!.id).toBe(app1.id);
|
|
expect(app!.status).toBe(AppStatusEnum.RUNNING);
|
|
expect(app!.status).toBe(AppStatusEnum.RUNNING);
|
|
});
|
|
});
|
|
|
|
|
|
it('Should correctly remove app from database', async () => {
|
|
it('Should correctly remove app from database', async () => {
|
|
|
|
+ // Act
|
|
await AppsService.uninstallApp(app1.id);
|
|
await AppsService.uninstallApp(app1.id);
|
|
-
|
|
|
|
const app = await App.findOne({ where: { id: app1.id } });
|
|
const app = await App.findOne({ where: { id: app1.id } });
|
|
|
|
|
|
|
|
+ // Assert
|
|
expect(app).toBeNull();
|
|
expect(app).toBeNull();
|
|
});
|
|
});
|
|
|
|
|
|
- it('Should correctly run app script', async () => {
|
|
|
|
- const spy = jest.spyOn(childProcess, 'execFile');
|
|
|
|
-
|
|
|
|
- await AppsService.uninstallApp(app1.id);
|
|
|
|
-
|
|
|
|
- expect(spy.mock.lastCall).toEqual([`${getConfig().rootFolder}/scripts/app.sh`, ['uninstall', app1.id], {}, expect.any(Function)]);
|
|
|
|
-
|
|
|
|
- spy.mockRestore();
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
it('Should stop app if it is running', async () => {
|
|
it('Should stop app if it is running', async () => {
|
|
- const spy = jest.spyOn(childProcess, 'execFile');
|
|
|
|
|
|
+ // Arrange
|
|
|
|
+ const spy = jest.spyOn(eventDispatcher, 'dispatchEventAsync');
|
|
|
|
|
|
|
|
+ // Act
|
|
await AppsService.uninstallApp(app1.id);
|
|
await AppsService.uninstallApp(app1.id);
|
|
|
|
|
|
|
|
+ // Assert
|
|
expect(spy.mock.calls.length).toBe(2);
|
|
expect(spy.mock.calls.length).toBe(2);
|
|
- expect(spy.mock.calls[0]).toEqual([`${getConfig().rootFolder}/scripts/app.sh`, ['stop', app1.id], {}, expect.any(Function)]);
|
|
|
|
- expect(spy.mock.calls[1]).toEqual([`${getConfig().rootFolder}/scripts/app.sh`, ['uninstall', app1.id], {}, expect.any(Function)]);
|
|
|
|
|
|
+ expect(spy.mock.calls[0]).toEqual([EventTypes.APP, ['stop', app1.id]]);
|
|
|
|
+ expect(spy.mock.calls[1]).toEqual([EventTypes.APP, ['uninstall', app1.id]]);
|
|
|
|
|
|
spy.mockRestore();
|
|
spy.mockRestore();
|
|
});
|
|
});
|
|
|
|
|
|
it('Should throw if app is not installed', async () => {
|
|
it('Should throw if app is not installed', async () => {
|
|
|
|
+ // Act & Assert
|
|
await expect(AppsService.uninstallApp('any')).rejects.toThrowError('App any not found');
|
|
await expect(AppsService.uninstallApp('any')).rejects.toThrowError('App any not found');
|
|
});
|
|
});
|
|
|
|
|
|
it('Should throw if uninstall script fails', async () => {
|
|
it('Should throw if uninstall script fails', async () => {
|
|
- // Update app
|
|
|
|
|
|
+ // Arrange
|
|
|
|
+ EventDispatcher.prototype.dispatchEventAsync = jest.fn().mockResolvedValueOnce({ success: false, stdout: 'test' });
|
|
await App.update({ id: app1.id }, { status: AppStatusEnum.UPDATING });
|
|
await App.update({ id: app1.id }, { status: AppStatusEnum.UPDATING });
|
|
|
|
|
|
- const spy = jest.spyOn(childProcess, 'execFile');
|
|
|
|
- spy.mockImplementation(() => {
|
|
|
|
- throw new Error('Test error');
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- await expect(AppsService.uninstallApp(app1.id)).rejects.toThrow('Test error');
|
|
|
|
|
|
+ // Act & Assert
|
|
|
|
+ await expect(AppsService.uninstallApp(app1.id)).rejects.toThrow(`App ${app1.id} failed to uninstall\nstdout: test`);
|
|
const app = await App.findOne({ where: { id: app1.id } });
|
|
const app = await App.findOne({ where: { id: app1.id } });
|
|
expect(app!.status).toBe(AppStatusEnum.STOPPED);
|
|
expect(app!.status).toBe(AppStatusEnum.STOPPED);
|
|
});
|
|
});
|
|
@@ -240,12 +225,12 @@ describe('Start app', () => {
|
|
fs.__createMockFiles(Object.assign(app1create.MockFiles));
|
|
fs.__createMockFiles(Object.assign(app1create.MockFiles));
|
|
});
|
|
});
|
|
|
|
|
|
- it('Should correctly run app script', async () => {
|
|
|
|
- const spy = jest.spyOn(childProcess, 'execFile');
|
|
|
|
|
|
+ it('Should correctly dispatch event', async () => {
|
|
|
|
+ const spy = jest.spyOn(eventDispatcher, 'dispatchEventAsync');
|
|
|
|
|
|
await AppsService.startApp(app1.id);
|
|
await AppsService.startApp(app1.id);
|
|
|
|
|
|
- expect(spy.mock.lastCall).toEqual([`${getConfig().rootFolder}/scripts/app.sh`, ['start', app1.id], {}, expect.any(Function)]);
|
|
|
|
|
|
+ expect(spy.mock.lastCall).toEqual([EventTypes.APP, ['start', app1.id]]);
|
|
|
|
|
|
spy.mockRestore();
|
|
spy.mockRestore();
|
|
});
|
|
});
|
|
@@ -255,7 +240,7 @@ describe('Start app', () => {
|
|
});
|
|
});
|
|
|
|
|
|
it('Should restart if app is already running', async () => {
|
|
it('Should restart if app is already running', async () => {
|
|
- const spy = jest.spyOn(childProcess, 'execFile');
|
|
|
|
|
|
+ const spy = jest.spyOn(eventDispatcher, 'dispatchEventAsync');
|
|
|
|
|
|
await AppsService.startApp(app1.id);
|
|
await AppsService.startApp(app1.id);
|
|
expect(spy.mock.calls.length).toBe(1);
|
|
expect(spy.mock.calls.length).toBe(1);
|
|
@@ -276,12 +261,11 @@ describe('Start app', () => {
|
|
});
|
|
});
|
|
|
|
|
|
it('Should throw if start script fails', async () => {
|
|
it('Should throw if start script fails', async () => {
|
|
- const spy = jest.spyOn(childProcess, 'execFile');
|
|
|
|
- spy.mockImplementation(() => {
|
|
|
|
- throw new Error('Test error');
|
|
|
|
- });
|
|
|
|
|
|
+ // Arrange
|
|
|
|
+ EventDispatcher.prototype.dispatchEventAsync = jest.fn().mockResolvedValueOnce({ success: false, stdout: 'test' });
|
|
|
|
|
|
- await expect(AppsService.startApp(app1.id)).rejects.toThrow('Test error');
|
|
|
|
|
|
+ // Act & Assert
|
|
|
|
+ await expect(AppsService.startApp(app1.id)).rejects.toThrow(`App ${app1.id} failed to start\nstdout: test`);
|
|
const app = await App.findOne({ where: { id: app1.id } });
|
|
const app = await App.findOne({ where: { id: app1.id } });
|
|
expect(app!.status).toBe(AppStatusEnum.STOPPED);
|
|
expect(app!.status).toBe(AppStatusEnum.STOPPED);
|
|
});
|
|
});
|
|
@@ -297,12 +281,12 @@ describe('Stop app', () => {
|
|
fs.__createMockFiles(Object.assign(app1create.MockFiles));
|
|
fs.__createMockFiles(Object.assign(app1create.MockFiles));
|
|
});
|
|
});
|
|
|
|
|
|
- it('Should correctly run app script', async () => {
|
|
|
|
- const spy = jest.spyOn(childProcess, 'execFile');
|
|
|
|
|
|
+ it('Should correctly dispatch stop event', async () => {
|
|
|
|
+ const spy = jest.spyOn(eventDispatcher, 'dispatchEventAsync');
|
|
|
|
|
|
await AppsService.stopApp(app1.id);
|
|
await AppsService.stopApp(app1.id);
|
|
|
|
|
|
- expect(spy.mock.lastCall).toEqual([`${getConfig().rootFolder}/scripts/app.sh`, ['stop', app1.id], {}, expect.any(Function)]);
|
|
|
|
|
|
+ expect(spy.mock.lastCall).toEqual([EventTypes.APP, ['stop', app1.id]]);
|
|
});
|
|
});
|
|
|
|
|
|
it('Should throw if app is not installed', async () => {
|
|
it('Should throw if app is not installed', async () => {
|
|
@@ -310,12 +294,11 @@ describe('Stop app', () => {
|
|
});
|
|
});
|
|
|
|
|
|
it('Should throw if stop script fails', async () => {
|
|
it('Should throw if stop script fails', async () => {
|
|
- const spy = jest.spyOn(childProcess, 'execFile');
|
|
|
|
- spy.mockImplementation(() => {
|
|
|
|
- throw new Error('Test error');
|
|
|
|
- });
|
|
|
|
|
|
+ // Arrange
|
|
|
|
+ EventDispatcher.prototype.dispatchEventAsync = jest.fn().mockResolvedValueOnce({ success: false, stdout: 'test' });
|
|
|
|
|
|
- await expect(AppsService.stopApp(app1.id)).rejects.toThrow('Test error');
|
|
|
|
|
|
+ // Act & Assert
|
|
|
|
+ await expect(AppsService.stopApp(app1.id)).rejects.toThrow(`App ${app1.id} failed to stop\nstdout: test`);
|
|
const app = await App.findOne({ where: { id: app1.id } });
|
|
const app = await App.findOne({ where: { id: app1.id } });
|
|
expect(app!.status).toBe(AppStatusEnum.RUNNING);
|
|
expect(app!.status).toBe(AppStatusEnum.RUNNING);
|
|
});
|
|
});
|
|
@@ -464,19 +447,19 @@ describe('Start all apps', () => {
|
|
});
|
|
});
|
|
|
|
|
|
it('Should correctly start all apps', async () => {
|
|
it('Should correctly start all apps', async () => {
|
|
- const spy = jest.spyOn(childProcess, 'execFile');
|
|
|
|
|
|
+ const spy = jest.spyOn(eventDispatcher, 'dispatchEventAsync');
|
|
|
|
|
|
await AppsService.startAllApps();
|
|
await AppsService.startAllApps();
|
|
|
|
|
|
expect(spy.mock.calls.length).toBe(2);
|
|
expect(spy.mock.calls.length).toBe(2);
|
|
expect(spy.mock.calls).toEqual([
|
|
expect(spy.mock.calls).toEqual([
|
|
- [`${getConfig().rootFolder}/scripts/app.sh`, ['start', app1.id], {}, expect.any(Function)],
|
|
|
|
- [`${getConfig().rootFolder}/scripts/app.sh`, ['start', app2.id], {}, expect.any(Function)],
|
|
|
|
|
|
+ [EventTypes.APP, ['start', app1.id]],
|
|
|
|
+ [EventTypes.APP, ['start', app2.id]],
|
|
]);
|
|
]);
|
|
});
|
|
});
|
|
|
|
|
|
it('Should not start app which has not status RUNNING', async () => {
|
|
it('Should not start app which has not status RUNNING', async () => {
|
|
- const spy = jest.spyOn(childProcess, 'execFile');
|
|
|
|
|
|
+ const spy = jest.spyOn(eventDispatcher, 'dispatchEventAsync');
|
|
await createApp({ installed: true, status: AppStatusEnum.STOPPED });
|
|
await createApp({ installed: true, status: AppStatusEnum.STOPPED });
|
|
|
|
|
|
await AppsService.startAllApps();
|
|
await AppsService.startAllApps();
|
|
@@ -487,16 +470,14 @@ describe('Start all apps', () => {
|
|
});
|
|
});
|
|
|
|
|
|
it('Should put app status to STOPPED if start script fails', async () => {
|
|
it('Should put app status to STOPPED if start script fails', async () => {
|
|
- const spy = jest.spyOn(childProcess, 'execFile');
|
|
|
|
- spy.mockImplementation(() => {
|
|
|
|
- throw new Error('Test error');
|
|
|
|
- });
|
|
|
|
|
|
+ // Arrange
|
|
|
|
+ EventDispatcher.prototype.dispatchEventAsync = jest.fn().mockResolvedValueOnce({ success: false, stdout: 'error' });
|
|
|
|
|
|
|
|
+ // Act
|
|
await AppsService.startAllApps();
|
|
await AppsService.startAllApps();
|
|
-
|
|
|
|
const apps = await App.find();
|
|
const apps = await App.find();
|
|
|
|
|
|
- expect(spy.mock.calls.length).toBe(2);
|
|
|
|
|
|
+ // Assert
|
|
expect(apps.length).toBe(2);
|
|
expect(apps.length).toBe(2);
|
|
expect(apps[0].status).toBe(AppStatusEnum.STOPPED);
|
|
expect(apps[0].status).toBe(AppStatusEnum.STOPPED);
|
|
expect(apps[1].status).toBe(AppStatusEnum.STOPPED);
|
|
expect(apps[1].status).toBe(AppStatusEnum.STOPPED);
|
|
@@ -529,12 +510,10 @@ describe('Update app', () => {
|
|
});
|
|
});
|
|
|
|
|
|
it('Should throw if update script fails', async () => {
|
|
it('Should throw if update script fails', async () => {
|
|
- const spy = jest.spyOn(childProcess, 'execFile');
|
|
|
|
- spy.mockImplementation(() => {
|
|
|
|
- throw new Error('Test error');
|
|
|
|
- });
|
|
|
|
|
|
+ // Arrange
|
|
|
|
+ EventDispatcher.prototype.dispatchEventAsync = jest.fn().mockResolvedValueOnce({ success: false, stdout: 'error' });
|
|
|
|
|
|
- await expect(AppsService.updateApp(app1.id)).rejects.toThrow('Test error');
|
|
|
|
|
|
+ await expect(AppsService.updateApp(app1.id)).rejects.toThrow(`App ${app1.id} failed to update\nstdout: error`);
|
|
const app = await App.findOne({ where: { id: app1.id } });
|
|
const app = await App.findOne({ where: { id: app1.id } });
|
|
expect(app!.status).toBe(AppStatusEnum.STOPPED);
|
|
expect(app!.status).toBe(AppStatusEnum.STOPPED);
|
|
});
|
|
});
|