Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add storage route (MAPCO-2932) #60

Merged
merged 11 commits into from
Mar 20, 2023
36 changes: 36 additions & 0 deletions openapi3.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,31 @@ info:
name: MIT
url: https://opensource.org/licenses/MIT
paths:
/storage:
get:
tags:
- storage
summary: Get free, in-use and total storage size for exporting (in bytes)
ronenkapelian marked this conversation as resolved.
Show resolved Hide resolved
operationId: getStorage
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/StorageResponse'
'404':
asafMasa marked this conversation as resolved.
Show resolved Hide resolved
description: Could not find requested path
content:
application/json:
schema:
$ref: '#/components/schemas/error'
'500':
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/internalError'
/create:
post:
tags:
Expand Down Expand Up @@ -151,6 +176,17 @@ components:
schema:
$ref: '#/components/schemas/exportFromFeatures'
schemas:
StorageResponse:
type: object
description: Response for storage request (in bytes)
required:
- free
- size
properties:
free:
type: number
size:
type: number
CommonResponse:
type: object
required:
Expand Down
2 changes: 2 additions & 0 deletions src/containerConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { createPackageRouterFactory, CREATE_PACKAGE_ROUTER_SYMBOL } from './crea
import { InjectionObject, registerDependencies } from './common/dependencyRegistration';
import { tasksRouterFactory, TASKS_ROUTER_SYMBOL } from './tasks/routes/tasksRouter';
import { PollingManager, POLLING_MANGER_SYMBOL } from './pollingManager';
import { storageRouterFactory, STORAGE_ROUTER_SYMBOL } from './storage/routes/storageRouter';

export interface RegisterOptions {
override?: InjectionObject<unknown>[];
Expand All @@ -33,6 +34,7 @@ export const registerExternalValues = (options?: RegisterOptions): DependencyCon
{ token: SERVICES.LOGGER, provider: { useValue: logger } },
{ token: SERVICES.TRACER, provider: { useValue: tracer } },
{ token: SERVICES.METER, provider: { useValue: meter } },
{ token: STORAGE_ROUTER_SYMBOL, provider: { useFactory: storageRouterFactory } },
{ token: CREATE_PACKAGE_ROUTER_SYMBOL, provider: { useFactory: createPackageRouterFactory } },
{ token: TASKS_ROUTER_SYMBOL, provider: { useFactory: tasksRouterFactory } },
{ token: POLLING_MANGER_SYMBOL, provider: { useClass: PollingManager } },
Expand Down
3 changes: 3 additions & 0 deletions src/serverBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Logger } from '@map-colonies/js-logger';
import httpLogger from '@map-colonies/express-access-log-middleware';
import { SERVICES } from './common/constants';
import { IConfig } from './common/interfaces';
import { STORAGE_ROUTER_SYMBOL } from './storage/routes/storageRouter';
import { CREATE_PACKAGE_ROUTER_SYMBOL } from './createPackage/routes/createPackageRouter';
import { TASKS_ROUTER_SYMBOL } from './tasks/routes/tasksRouter';

Expand All @@ -19,6 +20,7 @@ export class ServerBuilder {
public constructor(
@inject(SERVICES.CONFIG) private readonly config: IConfig,
@inject(SERVICES.LOGGER) private readonly logger: Logger,
@inject(STORAGE_ROUTER_SYMBOL) private readonly createStorageRouter: Router,
@inject(CREATE_PACKAGE_ROUTER_SYMBOL) private readonly createPackageRouter: Router,
@inject(TASKS_ROUTER_SYMBOL) private readonly tasksRouter: Router
) {
Expand All @@ -40,6 +42,7 @@ export class ServerBuilder {
}

private buildRoutes(): void {
this.serverInstance.use('/storage', this.createStorageRouter);
this.serverInstance.use('/create', this.createPackageRouter);
this.serverInstance.use('/', this.tasksRouter);
this.buildDocsRoutes();
Expand Down
23 changes: 23 additions & 0 deletions src/storage/controllers/storageController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Logger } from '@map-colonies/js-logger';
import { RequestHandler } from 'express';
ronenkapelian marked this conversation as resolved.
Show resolved Hide resolved
import httpStatus from 'http-status-codes';
import { injectable, inject } from 'tsyringe';
import { SERVICES } from '../../common/constants';
import { StorageManager } from '../models/storageManager';
import { IStorageStatusResponse } from '../../common/interfaces'

type GetTaskByJobIdHandler = RequestHandler<{ jobId: string }, IStorageStatusResponse, string>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the name of the type is confusing, mabye i missing something but I think the name should be relative to the issue of storage

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My mistake, renamed.


@injectable()
export class StorageController {
public constructor(@inject(SERVICES.LOGGER) private readonly logger: Logger, @inject(StorageManager) private readonly storageManager: StorageManager) {}

public getStorage: GetTaskByJobIdHandler = async (req, res, next) => {
try {
const storageStatus = await this.storageManager.getStorage();
return res.status(httpStatus.OK).json(storageStatus);
} catch (err) {
next(err);
}
};
}
27 changes: 27 additions & 0 deletions src/storage/models/storageManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Logger } from '@map-colonies/js-logger';
import { inject, injectable } from 'tsyringe';
import config from 'config';
import { SERVICES } from '../../common/constants';
import { IStorageStatusResponse } from '../../common/interfaces';
import { getStorageStatus } from '../../common/utils';

@injectable()
export class StorageManager {
private readonly gpkgsLocation: string;

public constructor(
@inject(SERVICES.LOGGER) private readonly logger: Logger
) {
this.gpkgsLocation = config.get<string>('gpkgsLocation');
}

public async getStorage(): Promise<IStorageStatusResponse> {
const storageStatus: IStorageStatusResponse = await getStorageStatus(this.gpkgsLocation);
this.logger.debug(storageStatus, `Current storage free and total space for gpkgs location`);
ronenkapelian marked this conversation as resolved.
Show resolved Hide resolved
asafMasa marked this conversation as resolved.
Show resolved Hide resolved

return {
free: storageStatus.free,
size: storageStatus.size
};
}
}
16 changes: 16 additions & 0 deletions src/storage/routes/storageRouter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Router } from 'express';
import { FactoryFunction } from 'tsyringe';
import { StorageController } from '../controllers/storageController';

const storageRouterFactory: FactoryFunction<Router> = (dependencyContainer) => {
const router = Router();
const controller = dependencyContainer.resolve(StorageController);

router.get('/', controller.getStorage);

return router;
};

export const STORAGE_ROUTER_SYMBOL = Symbol('storageFactory');

export { storageRouterFactory };
9 changes: 9 additions & 0 deletions tests/integration/storage/helpers/storageSender.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as supertest from 'supertest';

export class StorageSender {
public constructor(private readonly app: Express.Application) {}

public async getStorage(): Promise<supertest.Response> {
return supertest.agent(this.app).get(`/storage`).set('Content-Type', 'application/json').send();
}
}
42 changes: 42 additions & 0 deletions tests/integration/storage/storage.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import httpStatusCodes from 'http-status-codes';
import { getApp } from '../../../src/app';
import { getContainerConfig, resetContainer } from '../testContainerConfig';
import { IStorageStatusResponse } from '../../../src/common/interfaces';
import * as utils from '../../../src/common/utils';
import { StorageSender } from './helpers/storageSender';

describe('storage', function () {
let requestSender: StorageSender;
let getStorageSpy: jest.SpyInstance;

beforeEach(function () {
const app = getApp({
override: [...getContainerConfig()],
useChild: true,
});
requestSender = new StorageSender(app);
getStorageSpy = jest.spyOn(utils, 'getStorageStatus');
});

afterEach(function () {
resetContainer();
jest.resetAllMocks();
});

describe('Happy Path', function () {
it('should return 200 status code and the storage details', async function () {
const storageStatusResponse: IStorageStatusResponse = {
free: 1000,
size: 1000
}

getStorageSpy.mockResolvedValue(storageStatusResponse);

const resposne = await requestSender.getStorage();
expect(resposne).toSatisfyApiSpec();
expect(JSON.stringify(resposne.body)).toBe(JSON.stringify(storageStatusResponse));
expect(getStorageSpy).toHaveBeenCalledTimes(1);
expect(resposne.status).toBe(httpStatusCodes.OK);
});
});
});
45 changes: 45 additions & 0 deletions tests/unit/storage/models/storageModel.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import jsLogger from '@map-colonies/js-logger';
import { StorageManager } from '../../../../src/storage/models/storageManager';
import {
IStorageStatusResponse
} from '../../../../src/common/interfaces';
import { registerDefaultConfig } from '../../../mocks/config';
import * as utils from '../../../../src/common/utils';

let storageManager: StorageManager;

describe('Storage', () => {
beforeEach(() => {
const logger = jsLogger({ enabled: false });
registerDefaultConfig();
storageManager = new StorageManager(logger);
});

afterEach(() => {
jest.resetAllMocks();
jest.restoreAllMocks();
});

describe('GetStorage', () => {
/**
* @deprecated GetMap API - will be deprecated on future
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why deprecated notation? if it was accidently copies from reference tests, remove the notation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

*/
describe('#getStorage', () => {
it.only('should return storage status', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

const storageStatusResponse: IStorageStatusResponse = {
free: 1000,
size: 1000
}

const getStorageSpy = jest.spyOn(utils, 'getStorageStatus');
getStorageSpy.mockResolvedValue(storageStatusResponse);

const storageStatus = await storageManager.getStorage();

expect(storageStatus.free).toBe(1000);
expect(storageStatus.size).toBe(1000);
expect(getStorageSpy).toHaveBeenCalledTimes(1);
});
});
});
});