Skip to content
This repository has been archived by the owner on Aug 17, 2024. It is now read-only.

Commit

Permalink
Refresh Sbanken token when expired for accounts
Browse files Browse the repository at this point in the history
  • Loading branch information
Wedvich committed Jan 29, 2023
1 parent e280ec9 commit fd3ca5d
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 29 deletions.
78 changes: 53 additions & 25 deletions src/services/sbanken.api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import { sbankenGetTransactionsApi } from './sbanken.api';
import { produce } from 'immer';
import { sbankenApiBaseUrl, sbankenIdentityServerUrl } from '../config';
import { createStore, RootState, store } from '.';
import { sbankenAccountsAdapter, SbankenCredential, sbankenCredentialsAdapter } from './sbanken';
import {
fetchSbankenAccounts,
sbankenAccountsAdapter,
SbankenCredential,
sbankenCredentialsAdapter,
} from './sbanken';
import type { SbankenAccountWithClientId } from './sbanken.types';

const mockAgent = new undici.MockAgent();
Expand All @@ -26,7 +31,19 @@ apiPool
.reply(200, {
availableItems: 0,
items: [],
});
})
.times(Number.MAX_SAFE_INTEGER);

apiPool
.intercept({
path: `${apiUrl.pathname}/Accounts`,
method: 'get',
})
.reply(200, {
availableItems: 0,
items: [],
})
.times(Number.MAX_SAFE_INTEGER);

const identityUrl = new URL(sbankenIdentityServerUrl);
const identityPool = mockAgent.get(identityUrl.origin);
Expand All @@ -49,31 +66,28 @@ identityPool
.reply(200, {
access_token: mockRefreshedToken,
expires_in: 3600,
});

describe('getTransactions', () => {
const initialState = produce(store.getState(), (draft: RootState) => {
draft.sbanken.accounts = sbankenAccountsAdapter.setOne(draft.sbanken.accounts, {
accountId: mockAccountId,
clientId: 'def',
} as SbankenAccountWithClientId);
draft.sbanken.credentials = sbankenCredentialsAdapter.setOne(draft.sbanken.credentials, {
clientId: 'def',
clientSecret: 'ghi',
token: {
expires: exp,
notBefore: nbf,
value: '.eyX.',
},
} as SbankenCredential);
});
})
.times(Number.MAX_SAFE_INTEGER);

beforeEach(() => {
vitest.setSystemTime(exp + 1);
const initialState = produce(store.getState(), (draft: RootState) => {
draft.sbanken.accounts = sbankenAccountsAdapter.setOne(draft.sbanken.accounts, {
accountId: mockAccountId,
clientId: 'def',
} as SbankenAccountWithClientId);
draft.sbanken.credentials = sbankenCredentialsAdapter.setOne(draft.sbanken.credentials, {
clientId: 'def',
clientSecret: 'ghi',
token: {
expires: exp,
notBefore: nbf,
value: '.eyX.',
},
} as SbankenCredential);
});

return () => {
vitest.setSystemTime(vitest.getRealSystemTime());
};
describe('getTransactions', () => {
afterEach(() => {
vitest.setSystemTime(vitest.getRealSystemTime());
});

it('refreshes token if expired before request is sent', async () => {
Expand All @@ -89,3 +103,17 @@ describe('getTransactions', () => {
expect(isSuccess).toBe(true);
});
});

describe('fetchSbankenAccounts', () => {
it('refreshes token if expired before request is sent', async () => {
vitest.setSystemTime(exp + 1);
const testStore = createStore(true, initialState as any);
const credential = sbankenCredentialsAdapter
.getSelectors()
.selectById(testStore.getState().sbanken.credentials, 'def')!;

const { meta } = await testStore.dispatch(fetchSbankenAccounts(credential));

expect(meta.requestStatus).toBe('fulfilled');
});
});
14 changes: 11 additions & 3 deletions src/services/sbanken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,15 +226,23 @@ export const fetchSbankenToken = createAsyncThunk<SbankenToken, SbankenCredentia
export const fetchSbankenAccounts = createAsyncThunk<
Array<SbankenAccountWithClientId>,
SbankenCredential
>(`${sbankenSlice.name}/fetchAccountsForClient`, async (credential) => {
>(`${sbankenSlice.name}/fetchSbankenAccounts`, async (credential, { dispatch }) => {
if (!validateSbankenToken(credential.token)) {
return Promise.reject('invalid token');
const result = await dispatch(fetchSbankenToken(credential));
if (fetchSbankenToken.rejected.match(result) || !validateSbankenToken(result.payload)) {
throw new Error(`Unable to refresh token for credential with ID ${credential.clientId}`);
}

credential = {
...credential,
token: result.payload,
};
}

const response = await fetch(`${sbankenApiBaseUrl}/Accounts`, {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${credential.token.value}`,
Authorization: `Bearer ${credential.token!.value}`,
},
});

Expand Down
2 changes: 1 addition & 1 deletion src/services/ynab.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ describe('clearServerKnowledge', () => {
});
});

describe('adjustServerKnowledge', () => {
describe('adjustAccountBalance', () => {
const dispatch = vi.fn();
let state: YnabState;
const getState = () => ({ [ynabSlice.name]: state } as RootState);
Expand Down

0 comments on commit fd3ca5d

Please sign in to comment.