Skip to content

Commit

Permalink
feat(hooks): handle refresh of connection
Browse files Browse the repository at this point in the history
  • Loading branch information
g-ongenae committed Jun 28, 2021
1 parent c4601ec commit b8834d0
Show file tree
Hide file tree
Showing 8 changed files with 371 additions and 40 deletions.
13 changes: 11 additions & 2 deletions src/aggregator/interfaces/bridge-mock.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import {
AuthenticationResponse,
BridgeAccount,
BridgeAccountStatus,
BridgeAccountType,
BridgeRefreshStatus,
BridgeTransaction,
UserResponse,
AuthenticationResponse,
BridgeUserInformation,
UserResponse,
} from './bridge.interface';

export const mockAccount: BridgeAccount = {
Expand Down Expand Up @@ -116,3 +117,11 @@ export const mockPersonalInformation: BridgeUserInformation[] = [
nb_kids: null, // eslint-disable-line no-null/no-null
},
];

export const mockRefreshStatus: BridgeRefreshStatus = {
status: 'finished',
refresh_at: new Date().toISOString(),
mfa: null, // eslint-disable-line no-null/no-null
refresh_accounts_count: 0,
total_accounts_count: 0,
};
12 changes: 12 additions & 0 deletions src/aggregator/interfaces/bridge.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,15 @@ export interface BridgeUserInformation {
is_owner?: boolean | null;
nb_kids?: number | null;
}

/**
* Status of a refresh
* https://docs.bridgeapi.io/reference#get-a-refresh-status
*/
export interface BridgeRefreshStatus {
status: string;
refresh_at: Date | string;
mfa?: Record<string, unknown> | null;
refresh_accounts_count?: number | null;
total_accounts_count?: number | null;
}
29 changes: 26 additions & 3 deletions src/aggregator/services/aggregator.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { CacheModule, HttpModule, HttpStatus } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { createHmac } from 'crypto';
import { customerMock } from '../../algoan/dto/customer.objects.mock';

import { AlgoanModule } from '../../algoan/algoan.module';
import { customerMock } from '../../algoan/dto/customer.objects.mock';
import { AppModule } from '../../app.module';
import { mockAccount, mockAuthResponse, mockPersonalInformation, mockTransaction } from '../interfaces/bridge-mock';
import {
mockAccount,
mockAuthResponse,
mockPersonalInformation,
mockRefreshStatus,
mockTransaction,
} from '../interfaces/bridge-mock';
import { AggregatorService } from './aggregator.service';
import { BridgeClient } from './bridge/bridge.client';

Expand Down Expand Up @@ -182,6 +187,24 @@ describe('AggregatorService', () => {
});
});

it('should refresh an item', async () => {
const spy = jest.spyOn(client, 'refreshItem').mockReturnValue(Promise.resolve());
const itemId = 'mockItemId';
const token = 'mockToken';
await service.refresh(itemId, token);

expect(spy).toBeCalledWith(itemId, token, undefined);
});

it('should refresh an item', async () => {
const spy = jest.spyOn(client, 'getRefreshStatus').mockReturnValue(Promise.resolve(mockRefreshStatus));
const itemId = 'mockItemId';
const token = 'mockToken';
await service.getRefreshStatus(itemId, token);

expect(spy).toBeCalledWith(itemId, token, undefined);
});

it('should get the accounts', async () => {
const spy = jest.spyOn(client, 'getAccounts').mockReturnValue(Promise.resolve([mockAccount]));
const token = 'token';
Expand Down
24 changes: 23 additions & 1 deletion src/aggregator/services/aggregator.service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { createHmac } from 'crypto';
import { HttpStatus, Injectable } from '@nestjs/common';
import { config } from 'node-config-ts';

import {
AuthenticationResponse,
BridgeAccount,
BridgeRefreshStatus,
BridgeTransaction,
BridgeUserInformation,
UserAccount,
Expand All @@ -18,6 +18,28 @@ import { BridgeClient, ClientConfig } from './bridge/bridge.client';
export class AggregatorService {
constructor(private readonly bridgeClient: BridgeClient) {}

/**
* Refresh a connection
* @param id id of the connection
* @param accessToken access token of the connection
*/
public async refresh(id: string | number, accessToken: string, clientConfig?: ClientConfig): Promise<void> {
return this.bridgeClient.refreshItem(id, accessToken, clientConfig);
}

/**
* Get the status of the refresh of a connection
* @param id id of the connection
* @param accessToken access token of the connection
*/
public async getRefreshStatus(
id: string | number,
accessToken: string,
clientConfig?: ClientConfig,
): Promise<BridgeRefreshStatus> {
return this.bridgeClient.getRefreshStatus(id, accessToken, clientConfig);
}

/**
* Create the Bridge Webview url base on the client and it's callbackUrl
*
Expand Down
66 changes: 59 additions & 7 deletions src/aggregator/services/bridge/bridge.client.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import { CacheModule, CACHE_MANAGER, HttpModule, HttpService } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { AxiosResponse } from 'axios';
import { config } from 'node-config-ts';
import { of } from 'rxjs';
import { v4 as uuidV4 } from 'uuid';
import { AlgoanModule } from '../../../algoan/algoan.module';
import { AppModule } from '../../../app.module';
import { ConfigModule } from '../../../config/config.module';
import { mockAuthResponse, mockPersonalInformation, mockUserResponse } from '../../interfaces/bridge-mock';
import {
mockAuthResponse,
mockPersonalInformation,
mockRefreshStatus,
mockUserResponse,
} from '../../interfaces/bridge-mock';
import {
BridgeAccount,
BridgeAccountType,
Expand All @@ -11,12 +22,6 @@ import {
ListResponse,
} from '../../interfaces/bridge.interface';
import { BridgeClient } from './bridge.client';
import { CACHE_MANAGER, CacheModule, HttpModule, HttpService } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { AxiosResponse } from 'axios';
import { config } from 'node-config-ts';
import { of } from 'rxjs';
import { v4 as uuidV4 } from 'uuid';

describe('BridgeClient', () => {
let service: BridgeClient;
Expand All @@ -38,6 +43,53 @@ describe('BridgeClient', () => {
expect(service).toBeDefined();
});

it('can refresh an item', async () => {
const result: AxiosResponse = {
data: {},
status: 202,
statusText: '',
headers: {},
config: {},
};

const spy = jest.spyOn(httpService, 'post').mockImplementationOnce(() => of(result));

await service.refreshItem('mockItemId', 'secret-access-token');

expect(spy).toHaveBeenCalledWith('https://sync.bankin.com/v2/items/mockItemId/refresh', {
headers: {
Authorization: 'Bearer secret-access-token',
'Client-Id': config.bridge.clientId,
'Client-Secret': config.bridge.clientSecret,
'Bankin-Version': config.bridge.bankinVersion,
},
});
});

it('can get the status of a refresh of an item', async () => {
const result: AxiosResponse = {
data: mockRefreshStatus,
status: 200,
statusText: '',
headers: {},
config: {},
};

const spy = jest.spyOn(httpService, 'get').mockImplementationOnce(() => of(result));

const resp = await service.getRefreshStatus('mockItemId', 'secret-access-token');
expect(resp).toBe(mockRefreshStatus);

expect(spy).toHaveBeenCalledWith('https://sync.bankin.com/v2/items/mockItemId/refresh/status', {
headers: {
Authorization: 'Bearer secret-access-token',
'Client-Id': config.bridge.clientId,
'Client-Secret': config.bridge.clientSecret,
'Bankin-Version': config.bridge.bankinVersion,
},
});
});

it('can create a user', async () => {
const result: AxiosResponse = {
data: mockUserResponse,
Expand Down
43 changes: 35 additions & 8 deletions src/aggregator/services/bridge/bridge.client.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { CACHE_MANAGER, Inject, HttpService, Injectable, Logger } from '@nestjs/common';
import { Cache } from 'cache-manager';
import { config } from 'node-config-ts';
import { CACHE_MANAGER, HttpService, Inject, Injectable, Logger } from '@nestjs/common';
import { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
import { Cache } from 'cache-manager';
import { isNil } from 'lodash';
import { config } from 'node-config-ts';
import {
UserResponse,
UserAccount,
AuthenticationResponse,
ConnectItemResponse,
ListResponse,
BridgeAccount,
BridgeTransaction,
BridgeBank,
BridgeCategory,
BridgeRefreshStatus,
BridgeTransaction,
BridgeUserInformation,
ConnectItemResponse,
ListResponse,
UserAccount,
UserResponse,
} from '../../interfaces/bridge.interface';

/**
Expand Down Expand Up @@ -116,6 +117,32 @@ export class BridgeClient {
return resp.data;
}

/**
* Refresh an item
*/
public async refreshItem(id: string | number, accessToken: string, clientConfig?: ClientConfig): Promise<void> {
const url: string = `${config.bridge.baseUrl}/v2/items/${id}/refresh`;
await this.httpService
.post(url, { headers: { Authorization: `Bearer ${accessToken}`, ...BridgeClient.getHeaders(clientConfig) } })
.toPromise();
}

/**
* Get status of a refresh
*/
public async getRefreshStatus(
id: string | number,
accessToken: string,
clientConfig?: ClientConfig,
): Promise<BridgeRefreshStatus> {
const url: string = `${config.bridge.baseUrl}/v2/items/${id}/refresh/status`;
const resp: AxiosResponse<BridgeRefreshStatus> = await this.httpService
.get(url, { headers: { Authorization: `Bearer ${accessToken}`, ...BridgeClient.getHeaders(clientConfig) } })
.toPromise();

return resp.data;
}

/**
* Get a bridge user's accounts
*/
Expand Down
Loading

0 comments on commit b8834d0

Please sign in to comment.