Skip to content

Commit

Permalink
e2e test for backup restore
Browse files Browse the repository at this point in the history
  • Loading branch information
oznu committed Aug 31, 2020
1 parent fc0137c commit 8c9972b
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 12 deletions.
19 changes: 15 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/modules/backup/backup.controller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Controller, Get, Post, Put, UseGuards, Res, Req, InternalServerErrorException } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { ApiTags, ApiBearerAuth, ApiOperation, ApiBody, ApiConsumes } from '@nestjs/swagger';

import { BackupService } from './backup.service';
import { AdminGuard } from '../../core/auth/guards/admin.guard';
import { Logger } from '../../core/logger/logger.service';
Expand Down
10 changes: 6 additions & 4 deletions src/modules/backup/backup.gateway.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as color from 'bash-color';
import { EventEmitter } from 'events';
import { UseGuards } from '@nestjs/common';
import { WebSocketGateway, SubscribeMessage, WsException } from '@nestjs/websockets';

import { Logger } from '../../core/logger/logger.service';
import { WsAdminGuard } from '../../core/auth/guards/ws-admin-guard';
import { BackupService } from './backup.service';
Expand All @@ -14,9 +16,9 @@ export class BackupGateway {
) { }

@SubscribeMessage('do-restore')
async doRestore(client, payload) {
async doRestore(client: EventEmitter) {
try {
return await this.backupService.restoreFromBackup(payload, client);
return await this.backupService.restoreFromBackup(client);
} catch (e) {
this.logger.error(e);
client.emit('stdout', '\n\r' + color.red(e.toString()) + '\n\r');
Expand All @@ -25,9 +27,9 @@ export class BackupGateway {
}

@SubscribeMessage('do-restore-hbfx')
async doRestoreHbfx(client, payload) {
async doRestoreHbfx(client: EventEmitter) {
try {
return await this.backupService.restoreHbfxBackup(payload, client);
return await this.backupService.restoreHbfxBackup(client);
} catch (e) {
this.logger.error(e);
client.emit('stdout', '\n\r' + color.red(e.toString()) + '\n\r');
Expand Down
1 change: 1 addition & 0 deletions src/modules/backup/backup.module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';

import { ConfigModule } from '../../core/config/config.module';
import { LoggerModule } from '../../core/logger/logger.module';
import { BackupService } from './backup.service';
Expand Down
8 changes: 6 additions & 2 deletions src/modules/backup/backup.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import * as path from 'path';
import * as fs from 'fs-extra';
import * as color from 'bash-color';
import * as unzipper from 'unzipper';
import { EventEmitter } from 'events';
import * as child_process from 'child_process';
import { Injectable, BadRequestException } from '@nestjs/common';

import { PluginsService } from '../plugins/plugins.service';
import { ConfigService, HomebridgeConfig } from '../../core/config/config.service';
import { Logger } from '../../core/logger/logger.service';
Expand Down Expand Up @@ -133,11 +135,13 @@ export class BackupService {
/**
* Restores the uploaded backup
*/
async restoreFromBackup(payload, client) {
async restoreFromBackup(client: EventEmitter) {
if (!this.restoreDirectory) {
throw new BadRequestException();
}

console.log(this.restoreDirectory);

// check info.json exists
if (!await fs.pathExists(path.resolve(this.restoreDirectory, 'info.json'))) {
await this.removeRestoreDirectory();
Expand Down Expand Up @@ -267,7 +271,7 @@ export class BackupService {
/**
* Restore .hbfx backup file
*/
async restoreHbfxBackup(payload, client) {
async restoreHbfxBackup(client: EventEmitter) {
if (!this.restoreDirectory) {
throw new BadRequestException();
}
Expand Down
87 changes: 86 additions & 1 deletion test/e2e/backup.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,40 @@
import * as path from 'path';
import * as fs from 'fs-extra';
import { EventEmitter } from 'events';
import { ValidationPipe } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { FastifyAdapter, NestFastifyApplication, } from '@nestjs/platform-fastify';
import * as fastifyMultipart from 'fastify-multipart';
import * as FormData from 'form-data';

import { AuthModule } from '../../src/core/auth/auth.module';
import { BackupModule } from '../../src/modules/backup/backup.module';
import { BackupService } from '../../src/modules/backup/backup.service';
import { BackupGateway } from '../../src/modules/backup/backup.gateway';
import { PluginsService } from '../../src/modules/plugins/plugins.service';

describe('BackupController (e2e)', () => {
let app: NestFastifyApplication;

let authFilePath: string;
let secretsFilePath: string;
let authorization: string;
let tempBackupPath: string;

let backupService: BackupService;
let backupGateway: BackupGateway;
let pluginsService: PluginsService;
let postBackupRestoreRestartFn;

beforeAll(async () => {
process.env.UIX_BASE_PATH = path.resolve(__dirname, '../../');
process.env.UIX_STORAGE_PATH = path.resolve(__dirname, '../', '.homebridge');
process.env.UIX_CONFIG_PATH = path.resolve(process.env.UIX_STORAGE_PATH, 'config.json');
process.env.UIX_CUSTOM_PLUGIN_PATH = path.resolve(process.env.UIX_STORAGE_PATH, 'plugins/node_modules');

authFilePath = path.resolve(process.env.UIX_STORAGE_PATH, 'auth.json');
secretsFilePath = path.resolve(process.env.UIX_STORAGE_PATH, '.uix-secrets');
tempBackupPath = path.resolve(process.env.UIX_STORAGE_PATH, 'backup.tar.gz');

// setup test config
await fs.copy(path.resolve(__dirname, '../mocks', 'config.json'), process.env.UIX_CONFIG_PATH);
Expand All @@ -36,7 +47,15 @@ describe('BackupController (e2e)', () => {
imports: [BackupModule, AuthModule],
}).compile();

app = moduleFixture.createNestApplication<NestFastifyApplication>(new FastifyAdapter());
const fAdapter = new FastifyAdapter();

fAdapter.register(fastifyMultipart, {
limits: {
files: 1,
},
});

app = moduleFixture.createNestApplication<NestFastifyApplication>(fAdapter);

app.useGlobalPipes(new ValidationPipe({
whitelist: true,
Expand All @@ -47,6 +66,8 @@ describe('BackupController (e2e)', () => {
await app.getHttpAdapter().getInstance().ready();

backupService = app.get(BackupService);
backupGateway = app.get(BackupGateway);
pluginsService = app.get(PluginsService);
});

beforeEach(async () => {
Expand Down Expand Up @@ -78,6 +99,70 @@ describe('BackupController (e2e)', () => {
expect(res.headers['content-type']).toEqual('application/octet-stream');
});

it('POST /backup/restore', async () => {
// get a new backup
const downloadBackup = await app.inject({
method: 'GET',
path: '/backup/download',
headers: {
authorization,
}
});

// save the backup to disk
await fs.writeFile(tempBackupPath, downloadBackup.rawPayload);

// create multi-part form
const payload = new FormData();
payload.append('backup.tar.gz', await fs.readFile(tempBackupPath));

const headers = payload.getHeaders();
headers.authorization = authorization;

const res = await app.inject({
method: 'POST',
path: '/backup/restore',
headers,
payload,
});

expect(res.statusCode).toEqual(200);

await new Promise((resolve) => setTimeout(resolve, 100));

// check the backup contains the required files
const restoreDirectory = (backupService as any).restoreDirectory;
const pluginsJson = path.join(restoreDirectory, 'plugins.json');
const infoJson = path.join(restoreDirectory, 'info.json');

expect(await fs.pathExists(pluginsJson)).toEqual(true);
expect(await fs.pathExists(infoJson)).toEqual(true);

// mark the "homebridge-mock-plugin" dummy plugin as public so we can test the mock install
const installedPlugins = (await fs.readJson(pluginsJson)).map(x => {
x.publicPackage = true;
return x;
});
await fs.writeJson(pluginsJson, installedPlugins);

// create some mocks
const client = new EventEmitter();

jest.spyOn(client, 'emit');

jest.spyOn(pluginsService, 'installPlugin')
.mockImplementation(async () => {
return true;
});

// start restore
await backupGateway.doRestore(client);

expect(client.emit).toBeCalledWith('stdout', expect.stringContaining('Restoring backup'));
expect(client.emit).toBeCalledWith('stdout', expect.stringContaining('Restore Complete'));
expect(pluginsService.installPlugin).toBeCalledWith('homebridge-mock-plugin', client);
});

it('GET /backup/restart', async () => {
const res = await app.inject({
method: 'PUT',
Expand Down
1 change: 0 additions & 1 deletion test/jest-e2e.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverage": true,
"collectCoverageFrom": [
"**/*.ts"
],
Expand Down

0 comments on commit 8c9972b

Please sign in to comment.