diff --git a/apps/server/test/api/routes/v1/plugin/plguin.stop.test.ts b/apps/server/test/api/routes/v1/plugin/plguin.stop.test.ts new file mode 100644 index 00000000..c98962cc --- /dev/null +++ b/apps/server/test/api/routes/v1/plugin/plguin.stop.test.ts @@ -0,0 +1,75 @@ +import Docker from '@friday-ai/docker'; +import { expect } from 'chai'; +import { Container } from 'dockerode'; +import Plugin from '../../../../../src/core/plugin/plugin'; +import { admin, guest, habitant } from '../../../../utils/apiToken'; +import server from '../../../../utils/request'; + +let plugin: Plugin; +let docker: Docker; +let container: Container; + +describe('PATCH /api/v1/plugin/stop/:id', () => { + // Create a fake container and save docker id on plugin + before(async function before() { + plugin = global.FRIDAY.plugin; + docker = global.FRIDAY.docker; + + container = await docker.createContainer({ + Image: 'alpine', + AttachStdin: false, + AttachStdout: true, + AttachStderr: true, + Tty: true, + OpenStdin: false, + StdinOnce: false, + }); + }); + + // Only update container id before each hook to prevent multiple useless container created + beforeEach(async function beforeEach() { + await container.start(); + await plugin.update('33ddf1e2-3c51-4426-93af-3b0453ac0c1e', { + dockerId: container.id, + }); + }); + + after(async function after() { + this.timeout(15000); + await container.remove({ force: true }); + }); + + it('should stop a plugin', async function stop() { + this.timeout(15000); + await server + .patch('/api/v1/plugin/stop/33ddf1e2-3c51-4426-93af-3b0453ac0c1e') + .expect(200) + .then((res) => { + expect(res.body.success).to.equal(true); + }); + }); + + it('admin should have access to stop a plugin', async function stop() { + this.timeout(15000); + await server + .patch('/api/v1/plugin/stop/33ddf1e2-3c51-4426-93af-3b0453ac0c1e', admin) + .expect(200) + .then((res) => { + expect(res.body.success).to.equal(true); + }); + }); +}); + +describe('PATCH /api/v1/plugin/stop/:id', () => { + it("habitant should't have access to stop a plugin", async () => { + await server.patch('/api/v1/plugin/stop/33ddf1e2-3c51-4426-93af-3b0453ac0c1e', habitant).expect(403); + }); + + it("guest should't have access to stop a plugin", async () => { + await server.patch('/api/v1/plugin/stop/33ddf1e2-3c51-4426-93af-3b0453ac0c1e', guest).expect(403); + }); + + it('should not found plugin to stop', async () => { + await server.patch('/api/v1/plugin/stop/33ddf1e2-3c51-4426-93af-3b0453ac0333').expect(404); + }); +}); diff --git a/apps/server/test/api/routes/v1/plugin/plugin.install.test.ts b/apps/server/test/api/routes/v1/plugin/plugin.install.test.ts new file mode 100644 index 00000000..8583ab89 --- /dev/null +++ b/apps/server/test/api/routes/v1/plugin/plugin.install.test.ts @@ -0,0 +1,66 @@ +import { PluginInstallAttributes } from '@friday-ai/shared'; +import { expect } from 'chai'; +import { admin, guest, habitant } from '../../../../utils/apiToken'; +import server from '../../../../utils/request'; + +const testPlugin: PluginInstallAttributes = { + name: 'Sample-plugin', + version: 'v1.2.0', + repo: 'alpine', + satelliteId: 'a7ef5f08-2bad-4489-95bf-b73fcf894d8f', + variables: [], +}; + +let containerId = ''; + +describe('POST /api/v1/plugin', () => { + afterEach(async function afterEach() { + this.timeout(15000); + if (containerId !== '') { + const container = await global.FRIDAY.docker.getContainer(containerId); + await container.remove({ force: true }); + containerId = ''; + } + }); + + it('should install a plugin', async function install() { + this.timeout(15000); + await server + .post('/api/v1/plugin') + .send(testPlugin) + .expect(201) + .then((res) => { + const { body } = res; + expect(body).to.be.an('object'); + expect(res.body.name).to.equal('Sample-plugin'); + containerId = res.body.dockerId; + }); + }); + + it('admin should have access to install a plugin', async function install() { + this.timeout(15000); + await server + .post('/api/v1/plugin', admin) + .send(testPlugin) + .expect(201) + .then((res) => { + const { body } = res; + expect(body).to.be.an('object'); + expect(res.body.name).to.equal('Sample-plugin'); + containerId = res.body.dockerId; + }); + }); + + it("habitant should't have access to install a plugin", async () => { + await server.post('/api/v1/plugin', habitant).send(testPlugin).expect(403); + }); + + it("guest should't have access to install a plugin", async () => { + await server.post('/api/v1/plugin', guest).send(testPlugin).expect(403); + }); + + it('should not install a plugin with wrong repo', async () => { + testPlugin.repo = 'fake-plugin:friday'; + await server.post('/api/v1/plugin').send(testPlugin).expect(404); + }); +}); diff --git a/apps/server/test/api/routes/v1/plugin/plugin.restart.test.ts b/apps/server/test/api/routes/v1/plugin/plugin.restart.test.ts new file mode 100644 index 00000000..f259913d --- /dev/null +++ b/apps/server/test/api/routes/v1/plugin/plugin.restart.test.ts @@ -0,0 +1,76 @@ +import Docker from '@friday-ai/docker'; +import { expect } from 'chai'; +import { Container } from 'dockerode'; +import Plugin from '../../../../../src/core/plugin/plugin'; +import { admin, guest, habitant } from '../../../../utils/apiToken'; +import server from '../../../../utils/request'; + +let plugin: Plugin; +let docker: Docker; +let container: Container; + +describe('PATCH /api/v1/plugin/restart/:id', () => { + // Create a fake container and save docker id on plugin + before(async function before() { + plugin = global.FRIDAY.plugin; + docker = global.FRIDAY.docker; + + container = await docker.createContainer({ + Image: 'alpine', + AttachStdin: false, + AttachStdout: true, + AttachStderr: true, + Tty: true, + OpenStdin: false, + StdinOnce: false, + }); + + await container.start(); + }); + + // Only update container id before each hook to prevent multiple useless container created + beforeEach(async function beforeEach() { + await plugin.update('33ddf1e2-3c51-4426-93af-3b0453ac0c1e', { + dockerId: container.id, + }); + }); + + after(async function after() { + this.timeout(15000); + await container.remove({ force: true }); + }); + + it('should restart a plugin', async function restart() { + this.timeout(15000); + await server + .patch('/api/v1/plugin/restart/33ddf1e2-3c51-4426-93af-3b0453ac0c1e') + .expect(200) + .then((res) => { + expect(res.body.success).to.equal(true); + }); + }); + + it('admin should have access to restart a plugin', async function restart() { + this.timeout(15000); + await server + .patch('/api/v1/plugin/restart/33ddf1e2-3c51-4426-93af-3b0453ac0c1e', admin) + .expect(200) + .then((res) => { + expect(res.body.success).to.equal(true); + }); + }); +}); + +describe('PATCH /api/v1/plugin/restart/:id', () => { + it("habitant should't have access to restart a plugin", async () => { + await server.patch('/api/v1/plugin/restart/33ddf1e2-3c51-4426-93af-3b0453ac0c1e', habitant).expect(403); + }); + + it("guest should't have access to restart a plugin", async () => { + await server.patch('/api/v1/plugin/restart/33ddf1e2-3c51-4426-93af-3b0453ac0c1e', guest).expect(403); + }); + + it('should not found plugin to restart', async () => { + await server.patch('/api/v1/plugin/restart/33ddf1e2-3c51-4426-93af-3b0453ac0333').expect(404); + }); +}); diff --git a/apps/server/test/core/plugin/plugin.checkState.test.ts b/apps/server/test/core/plugin/plugin.checkState.test.ts new file mode 100644 index 00000000..3e5cfb2f --- /dev/null +++ b/apps/server/test/core/plugin/plugin.checkState.test.ts @@ -0,0 +1,62 @@ +import { AvailableState, EventsType } from '@friday-ai/shared'; +import { assert, expect } from 'chai'; +import Dockerode, { Container } from 'dockerode'; +import sinon from 'sinon'; +import Plugin from '../../../src/core/plugin/plugin'; +import { NotFoundError } from '../../../src/utils/decorators/error'; +import wait from '../../utils/timer'; + +let plugin: Plugin; +let container: Container; + +describe('Plugin.checkstate', () => { + before(async () => { + plugin = global.FRIDAY.plugin; + // Override object for tests + global.FRIDAY.docker.dockerode = new Dockerode(); + + // Create a fake container and save docker id on plugin + container = await global.FRIDAY.docker.createContainer({ + Image: 'alpine', + AttachStdin: false, + AttachStdout: true, + AttachStderr: true, + Tty: true, + OpenStdin: false, + StdinOnce: false, + }); + + await container.start(); + }); + + // Only update container id before each hook to prevent multiple useless container created + beforeEach(async function beforeEach() { + await plugin.update('33ddf1e2-3c51-4426-93af-3b0453ac0c1e', { + dockerId: container.id, + }); + }); + + after(async function after() { + this.timeout(15000); + await container.remove({ force: true }); + }); + + it('should return state "running" of a plugin', async function stop() { + this.timeout(15000); + + const listener = sinon.spy(); + global.FRIDAY.event.on(EventsType.WEBSOCKET_SEND_ALL, listener); + + const result = await plugin.checkState('33ddf1e2-3c51-4426-93af-3b0453ac0c1e'); + + await wait(80); + expect(result).equal('running'); + expect(listener.called).equal(true); + expect(listener.args[0][0].type).to.equal(AvailableState.PLUGIN_RUNNING); + }); + + it('should not found a plugin to check', async () => { + const promise = plugin.stop('580efda9-6fa1-4bef-865f-d4ef04ea57d6'); + await assert.isRejected(promise, NotFoundError); + }); +}); diff --git a/apps/server/test/core/plugin/plugin.restart.test.ts b/apps/server/test/core/plugin/plugin.restart.test.ts new file mode 100644 index 00000000..399affc8 --- /dev/null +++ b/apps/server/test/core/plugin/plugin.restart.test.ts @@ -0,0 +1,74 @@ +import { AvailableState, EventsType } from '@friday-ai/shared'; +import { assert, expect } from 'chai'; +import Dockerode, { Container } from 'dockerode'; +import sinon from 'sinon'; +import Plugin from '../../../src/core/plugin/plugin'; +import { NotFoundError } from '../../../src/utils/decorators/error'; +import wait from '../../utils/timer'; + +let plugin: Plugin; +let container: Container; + +describe('Plugin.restart', () => { + before(async () => { + plugin = global.FRIDAY.plugin; + // Override object for tests + global.FRIDAY.docker.dockerode = new Dockerode(); + + // Create a fake container and save docker id on plugin + container = await global.FRIDAY.docker.createContainer({ + Image: 'alpine', + AttachStdin: false, + AttachStdout: true, + AttachStderr: true, + Tty: true, + OpenStdin: false, + StdinOnce: false, + }); + + await container.start(); + }); + + // Only update container id before each hook to prevent multiple useless container created + beforeEach(async function beforeEach() { + await plugin.update('33ddf1e2-3c51-4426-93af-3b0453ac0c1e', { + dockerId: container.id, + }); + }); + + after(async function after() { + this.timeout(15000); + await container.remove({ force: true }); + }); + + it('should restart a plugin', async function stop() { + this.timeout(15000); + + const listener = sinon.spy(); + global.FRIDAY.event.on(EventsType.WEBSOCKET_SEND_ALL, listener); + + const result = await plugin.restart('33ddf1e2-3c51-4426-93af-3b0453ac0c1e'); + + await wait(80); + expect(result).equal(true); + expect(listener.called).equal(true); + expect(listener.args[0][0].type).to.equal(AvailableState.PLUGIN_STOPPED); + }); + + it('should not found a plugin to stop', async () => { + const promise = plugin.stop('580efda9-6fa1-4bef-865f-d4ef04ea57d6'); + await assert.isRejected(promise, NotFoundError); + }); + + it('should not restart a plugin with wrong docker id', async function stop() { + const listener = sinon.spy(); + global.FRIDAY.event.on(EventsType.WEBSOCKET_SEND_ALL, listener); + + const result = await plugin.restart('88b48273-15e6-4729-9199-0682677475f4'); + + await wait(80); + expect(result).equal(false); + expect(listener.called).equal(true); + expect(listener.args[0][0].type).to.equal(AvailableState.PLUGIN_ERRORED); + }); +}); diff --git a/apps/server/test/core/plugin/plugin.stop.test.ts b/apps/server/test/core/plugin/plugin.stop.test.ts index 028d24b0..0cec4f4e 100644 --- a/apps/server/test/core/plugin/plugin.stop.test.ts +++ b/apps/server/test/core/plugin/plugin.stop.test.ts @@ -1,7 +1,10 @@ +import { AvailableState, EventsType } from '@friday-ai/shared'; import { assert, expect } from 'chai'; import Dockerode, { Container } from 'dockerode'; -import { NotFoundError } from '../../../src/utils/decorators/error'; +import sinon from 'sinon'; import Plugin from '../../../src/core/plugin/plugin'; +import { NotFoundError } from '../../../src/utils/decorators/error'; +import wait from '../../utils/timer'; let plugin: Plugin; let container: Container; @@ -11,11 +14,8 @@ describe('Plugin.stop', () => { plugin = global.FRIDAY.plugin; // Override object for tests global.FRIDAY.docker.dockerode = new Dockerode(); - }); - // Create a fake container and save docker id on plugin - beforeEach(async function beforeEach() { - this.timeout(15000); + // Create a fake container and save docker id on plugin container = await global.FRIDAY.docker.createContainer({ Image: 'alpine', AttachStdin: false, @@ -26,28 +26,62 @@ describe('Plugin.stop', () => { StdinOnce: false, }); + await container.start(); + }); + + // Only update container id before each hook to prevent multiple useless container created + beforeEach(async function beforeEach() { await plugin.update('33ddf1e2-3c51-4426-93af-3b0453ac0c1e', { dockerId: container.id, }); - - // Start container for test - await container.start(); }); after(async function after() { this.timeout(15000); - await container.stop(); - await container.remove(); + await container.remove({ force: true }); }); it('should stop a plugin', async function stop() { this.timeout(15000); + + const listener = sinon.spy(); + global.FRIDAY.event.on(EventsType.WEBSOCKET_SEND_ALL, listener); + + const result = await plugin.stop('33ddf1e2-3c51-4426-93af-3b0453ac0c1e'); + + await wait(80); + expect(result).equal(true); + expect(listener.called).equal(true); + expect(listener.args[0][0].type).to.equal(AvailableState.PLUGIN_STOPPED); + }); + + it('should not stop a plugin already stopped', async function stop() { + this.timeout(15000); + const listener = sinon.spy(); + global.FRIDAY.event.on(EventsType.WEBSOCKET_SEND_ALL, listener); + const result = await plugin.stop('33ddf1e2-3c51-4426-93af-3b0453ac0c1e'); - expect(result).to.equal(true); + + await wait(80); + expect(result).equal(true); + expect(listener.called).equal(true); + expect(listener.args[0][0].type).to.equal(AvailableState.PLUGIN_STOPPED); }); it('should not found a plugin to stop', async () => { const promise = plugin.stop('580efda9-6fa1-4bef-865f-d4ef04ea57d6'); await assert.isRejected(promise, NotFoundError); }); + + it('should not stop a plugin with wrong docker id', async function stop() { + const listener = sinon.spy(); + global.FRIDAY.event.on(EventsType.WEBSOCKET_SEND_ALL, listener); + + const result = await plugin.stop('88b48273-15e6-4729-9199-0682677475f4'); + + await wait(80); + expect(result).equal(false); + expect(listener.called).equal(true); + expect(listener.args[0][0].type).to.equal(AvailableState.PLUGIN_ERRORED); + }); }); diff --git a/apps/server/test/core/plugin/plugin.uninstall.test.ts b/apps/server/test/core/plugin/plugin.uninstall.test.ts index bc032b4d..56797924 100644 --- a/apps/server/test/core/plugin/plugin.uninstall.test.ts +++ b/apps/server/test/core/plugin/plugin.uninstall.test.ts @@ -22,7 +22,10 @@ describe('Plugin.uninstall', () => { OpenStdin: false, StdinOnce: false, }); + }); + // Only update container id before each hook to prevent multiple useless container created + beforeEach(async () => { await plugin.update('33ddf1e2-3c51-4426-93af-3b0453ac0c1e', { dockerId: container.id, satelliteId: 'a7ef5f08-2bad-4489-95bf-b73fcf894d8f', @@ -31,15 +34,15 @@ describe('Plugin.uninstall', () => { it('should uninstall a plugin', async function destroy() { this.timeout(30000); - await plugin.destroy('33ddf1e2-3c51-4426-93af-3b0453ac0c1e'); + await plugin.uninstall('33ddf1e2-3c51-4426-93af-3b0453ac0c1e'); }); it('should uninstall a plugin even if it not found container', async function destroy() { - await plugin.destroy('88b48273-15e6-4729-9199-0682677475f4'); + await plugin.uninstall('88b48273-15e6-4729-9199-0682677475f4'); }); it('should not found a plugin to uninstall', async () => { - const promise = plugin.destroy('a58c31cc-61d2-4c18-b9f6-b8ba8609d12e'); + const promise = plugin.uninstall('a58c31cc-61d2-4c18-b9f6-b8ba8609d12e'); await assert.isRejected(promise, NotFoundError); }); });