Skip to content

Commit

Permalink
Merge pull request #53 from mollie/feature/MOL-401
Browse files Browse the repository at this point in the history
Feature/mol 401
  • Loading branch information
Tung-Huynh-Shopmacher authored Sep 10, 2024
2 parents 95adf7f + 0ba2e48 commit 18de5c9
Show file tree
Hide file tree
Showing 15 changed files with 487 additions and 24 deletions.
3 changes: 3 additions & 0 deletions processor/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,8 @@
"import/no-duplicates": "error",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-var-requires": "off"
},
"settings": {
"tabWidth": 2
}
}
12 changes: 2 additions & 10 deletions processor/src/connector/post-deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@ import dotenv from 'dotenv';
dotenv.config();

import { assertError, assertString } from '../utils/assert.utils';
import {
createCustomPaymentInterfaceInteractionType,
createCustomPaymentTransactionCancelReasonType,
createCustomPaymentType,
} from '../commercetools/customFields.commercetools';
import { createPaymentExtension } from '../commercetools/extensions.commercetools';
import { createExtensionAndCustomFields } from '../service/connector.service';

const CONNECT_APPLICATION_URL_KEY = 'CONNECT_SERVICE_URL';

Expand All @@ -16,10 +11,7 @@ async function postDeploy(properties: Map<string, unknown>): Promise<void> {

assertString(applicationUrl, CONNECT_APPLICATION_URL_KEY);

await createPaymentExtension(applicationUrl);
await createCustomPaymentType();
await createCustomPaymentInterfaceInteractionType();
await createCustomPaymentTransactionCancelReasonType();
await createExtensionAndCustomFields(applicationUrl);
}

async function run(): Promise<void> {
Expand Down
7 changes: 3 additions & 4 deletions processor/src/connector/pre-undeploy.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { assertError } from '../utils/assert.utils';
import { removeExtension } from './../service/connector.service';
import dotenv from 'dotenv';
dotenv.config();

import { assertError } from '../utils/assert.utils';
import { deletePaymentExtension } from '../commercetools/extensions.commercetools';

async function preUndeploy(): Promise<void> {
await deletePaymentExtension();
await removeExtension();
}

async function run(): Promise<void> {
Expand Down
67 changes: 67 additions & 0 deletions processor/src/controllers/connector.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { logger } from '../utils/logger.utils';
import { Request, Response } from 'express';
import CustomError from '../errors/custom.error';
import { apiError } from '../api/error.api';
import { formatErrorResponse } from '../errors/mollie.error';
import { createExtensionAndCustomFields, removeExtension } from '../service/connector.service';
import { getProfile } from '../mollie/profile.mollie';

export const healthCheck = async (request: Request, response: Response) => {
try {
logger.debug('SCTM - healthCheck - The connector is running healthily.');
return response.status(200).json('The connector is running healthily.');
} catch (error) {
logger.error('SCTM - healthCheck - Unexpected error occurred when processing request', error);
return apiError(response, formatErrorResponse(error).errors);
}
};

export const mollieStatus = async (request: Request, response: Response) => {
try {
logger.debug('SCTM - mollieStatus - Checking Mollie API status.');
const profile = await getProfile();
logger.debug('SCTM - mollieStatus - Mollie API status is functioning.');
return response.status(200).json({
mode: profile.mode,
name: profile.name,
website: profile.website,
status: profile.status,
});
} catch (error) {
logger.error('SCTM - healthCheck - Unexpected error occurred when processing request', error);
return response.status(400).json(error).send();
}
};

export const install = async (request: Request, response: Response) => {
const { extensionUrl } = request.body;

if (!extensionUrl) {
logger.debug('SCTM - install - Missing body parameters {extensionUrl}.');
return apiError(response, formatErrorResponse(new CustomError(400, 'Missing body parameters.')).errors);
}

try {
await createExtensionAndCustomFields(extensionUrl);
logger.debug(
'SCTM - install - The connector was installed successfully with required extensions and custom fields.',
);
return response
.status(200)
.json('The connector was installed successfully with required extensions and custom fields.');
} catch (error) {
logger.error('SCTM - install - Unexpected error occurred when processing request', error);
return apiError(response, formatErrorResponse(error).errors);
}
};

export const uninstall = async (request: Request, response: Response) => {
try {
await removeExtension();
logger.debug('SCTM - uninstall - The connector was uninstalled successfully.');
return response.status(200).json('The connector was uninstalled successfully.');
} catch (error) {
logger.error('SCTM - uninstallation - Unexpected error occurred when processing request', error);
return apiError(response, formatErrorResponse(error).errors);
}
};
7 changes: 4 additions & 3 deletions processor/src/controllers/processor.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,24 @@ export const post = async (request: Request, response: Response) => {
data = await paymentController(action, resource);
logger.debug('SCTM - Processor - Finish processing payment requests');
} else {
logger.debug(`SCTM - Processor - Internal Server Error - Resource not recognized. Allowed values are 'payment'.`);
throw new CustomError(500, `Internal Server Error - Resource not recognized. Allowed values are 'payment'.`);
}

return apiSuccess(200, response, data?.actions);
} catch (error) {
if (error instanceof SkipError) {
logger.debug('Skip action', error.message);
logger.debug('SCTM - Processor - Skip action', error.message);

return apiSuccess(200, response, []);
}
if (error instanceof CustomError) {
logger.debug('Error occurred when processing request', error);
logger.debug('SCTM - Processor - Error occurred when processing request', error);

return apiError(response, error.errors);
}

logger.debug('Unexpected error occurred when processing request', error);
logger.debug('SCTM - Processor - Unexpected error occurred when processing request', error);

return apiError(response, formatErrorResponse(error).errors);
}
Expand Down
22 changes: 22 additions & 0 deletions processor/src/mollie/profile.mollie.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { initMollieClient } from '../client/mollie.client';
import { MollieApiError, Profile } from '@mollie/api-client';
import { logger } from '../utils/logger.utils';
import CustomError from '../errors/custom.error';

export const getProfile = async (): Promise<Profile> => {
try {
return await initMollieClient().profiles.getCurrent();
} catch (error) {
let errorMessage;
if (error instanceof MollieApiError) {
errorMessage = `SCTM - getProfile - error: ${error.message}, field: ${error.field}`;
} else {
errorMessage = `SCTM - getProfile - Failed to get Mollie profile with unknown errors`;
}

logger.error(errorMessage, {
error,
});
throw new CustomError(400, errorMessage);
}
};
13 changes: 9 additions & 4 deletions processor/src/routes/processor.route.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { Router } from 'express';
import { post } from '../controllers/processor.controller';
import { install, healthCheck, uninstall, mollieStatus } from '../controllers/connector.controller';

const serviceRouter = Router();

serviceRouter.get('/health-check', async (req, res) => {
res.status(200).send('Mollie processor is successfully running');
});

serviceRouter.post('/', post);

serviceRouter.get('/health-check', healthCheck);

serviceRouter.get('/mollie/status', mollieStatus);

serviceRouter.post('/install', install);

serviceRouter.post('/uninstall', uninstall);

export default serviceRouter;
16 changes: 16 additions & 0 deletions processor/src/service/connector.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { createPaymentExtension, deletePaymentExtension } from '../commercetools/extensions.commercetools';
import {
createCustomPaymentType,
createCustomPaymentInterfaceInteractionType,
createCustomPaymentTransactionCancelReasonType,
} from '../commercetools/customFields.commercetools';
export const createExtensionAndCustomFields = async (extensionUrl: string): Promise<void> => {
await createPaymentExtension(extensionUrl);
await createCustomPaymentType();
await createCustomPaymentInterfaceInteractionType();
await createCustomPaymentTransactionCancelReasonType();
};

export const removeExtension = async (): Promise<void> => {
await deletePaymentExtension();
};
1 change: 1 addition & 0 deletions processor/tests/api/success.api.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { describe, it, jest, expect, beforeEach } from '@jest/globals';
import { UpdateAction } from '@commercetools/sdk-client-v2';
import { Response } from 'express';
import { apiSuccess } from '../../src/api/success.api';
Expand Down
42 changes: 42 additions & 0 deletions processor/tests/client/build.client.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { describe, it, jest, expect } from '@jest/globals';
import { createClient } from '../../src/client/build.client';
import { ClientBuilder } from '@commercetools/sdk-client-v2';
import { readConfiguration } from '../../src/utils/config.utils';

jest.mock('@commercetools/sdk-client-v2', () => ({
ClientBuilder: jest.fn().mockImplementation(() => ({
withProjectKey: jest.fn().mockReturnThis(),
withClientCredentialsFlow: jest.fn().mockReturnThis(),
withHttpMiddleware: jest.fn().mockReturnThis(),
withUserAgentMiddleware: jest.fn().mockReturnThis(),
build: jest.fn(),
})),
}));

jest.mock('../../src/utils/config.utils', () => ({
readConfiguration: jest.fn(() => {
return {
commerceTools: {
projectKey: 'test-project-key',
region: 'europe-west1.gcp',
clientId: 'test-client-id',
clientSecret: 'test-client-secret',
},
};
}),
}));

describe('createClient', () => {
it('should create a client with the correct configuration', () => {
(readConfiguration as jest.Mock).mockReturnValueOnce({
commerceTools: {
projectKey: 'test-project-key',
region: 'europe-west1.gcp',
clientId: 'test-client-id',
clientSecret: 'test-client-secret',
},
});
createClient();
expect(ClientBuilder).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { describe, it, jest, expect, afterEach, beforeAll } from '@jest/globals';
import { createClient } from '../../src/client/build.client';
import { createApiBuilderFromCtpClient } from '@commercetools/platform-sdk';
import { readConfiguration } from '../../src/utils/config.utils';
Expand All @@ -12,7 +13,7 @@ jest.mock('../../src/utils/config.utils');
const mockApiBuilder = {
withProjectKey: jest.fn().mockReturnThis(),
get: jest.fn().mockReturnThis(),
execute: jest.fn().mockResolvedValue('mock-response'),
execute: jest.fn().mockReturnValueOnce('mock-response'),
};

describe('createApiRoot', () => {
Expand Down
24 changes: 22 additions & 2 deletions processor/tests/client/mollie.client.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getApiKey } from '../../src/utils/config.utils';
import { initMollieClient } from '../../src/client/mollie.client';
import { getApiKey, readConfiguration } from '../../src/utils/config.utils';
import { initMollieClient, initMollieClientForApplePaySession } from '../../src/client/mollie.client';
import createMollieClient, { MollieClient } from '@mollie/api-client';
import { describe, jest, expect, it } from '@jest/globals';
import { MOLLIE_VERSION_STRINGS } from '../../src/utils/constant.utils';
Expand All @@ -14,11 +14,13 @@ jest.mock('@mollie/api-client', () => ({

jest.mock('../../src/utils/config.utils', () => ({
getApiKey: jest.fn(),
readConfiguration: jest.fn(),
}));

describe('Test mollie.client.ts', () => {
const mockCreateMollieClient = createMollieClient as jest.Mock;
const mockGetApiKey = getApiKey as jest.Mock;
const mockReadConfiguration = readConfiguration as jest.Mock;
let mockMollieClient: MollieClient;

it('should initialize and return a MollieClient instance with the correct API key', () => {
Expand All @@ -34,4 +36,22 @@ describe('Test mollie.client.ts', () => {
});
expect(client).toBe(mockMollieClient);
});

it('should initialize and return a MollieClientForApplePaySession instance with the correct API key', () => {
mockCreateMollieClient.mockReturnValue(mockMollieClient);
mockReadConfiguration.mockReturnValue({
mollie: {
liveApiKey: 'test-api-key',
},
});

const client = initMollieClientForApplePaySession();

expect(mockReadConfiguration).toHaveBeenCalled();
expect(mockCreateMollieClient).toHaveBeenCalledWith({
apiKey: 'test-api-key',
versionStrings: MOLLIE_VERSION_STRINGS,
});
expect(client).toBe(mockMollieClient);
});
});
69 changes: 69 additions & 0 deletions processor/tests/controllers/connector.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { describe, jest, beforeEach, afterEach, it, expect } from '@jest/globals';
import { Request, Response } from 'express';
import { healthCheck, install, uninstall } from '../../src/controllers/connector.controller';
import { logger } from '../../src/utils/logger.utils';

jest.mock('../../src/service/connector.service', () => ({
createExtensionAndCustomFields: jest.fn(),
removeExtension: jest.fn(),
}));

jest.mock('../../src/utils/logger.utils');

describe('Test connector.controller.ts', () => {
let req: Partial<Request>;
let res: Partial<Response>;

beforeEach(() => {
res = {
// @ts-expect-error: ignore type error
status: jest.fn().mockReturnThis(),
// @ts-expect-error: ignore type error
send: jest.fn(),
// @ts-expect-error: ignore type error
json: jest.fn(),
};
});

afterEach(() => {
jest.clearAllMocks();
});

it('should return status code 200 with a successful health check response', async () => {
req = {};
await healthCheck(req as Request, res as Response);
expect(logger.debug).toBeCalledTimes(1);
expect(logger.debug).toHaveBeenCalledWith('SCTM - healthCheck - The connector is running healthily.');
expect(res.status).toBeCalledWith(200);
});

it('should return status code 200 with a successful install response', async () => {
req = {
body: { extensionUrl: 'https://example.com/extensionUrl' },
};
await install(req as Request, res as Response);
expect(logger.debug).toBeCalledTimes(1);
expect(logger.debug).toHaveBeenCalledWith(
'SCTM - install - The connector was installed successfully with required extensions and custom fields.',
);
expect(res.status).toBeCalledWith(200);
});

it('should return status code 400 when extensionUrl is missing during install', async () => {
req = {
body: { extensionUrl: '' },
};
await install(req as Request, res as Response);
expect(logger.debug).toBeCalledTimes(1);
expect(logger.debug).toHaveBeenCalledWith('SCTM - install - Missing body parameters {extensionUrl}.');
expect(res.status).toBeCalledWith(400);
});

it('should return status code 200 with a successful uninstall response', async () => {
req = {};
await uninstall(req as Request, res as Response);
expect(logger.debug).toBeCalledTimes(1);
expect(logger.debug).toHaveBeenCalledWith('SCTM - uninstall - The connector was uninstalled successfully.');
expect(res.status).toBeCalledWith(200);
});
});
Loading

0 comments on commit 18de5c9

Please sign in to comment.