Skip to content

Commit

Permalink
bug: fix metadata-token-service.ts
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexey Zorkaltsev authored and Alexey Zorkaltsev committed Jul 25, 2023
1 parent 7ac6ff1 commit f9df471
Show file tree
Hide file tree
Showing 2 changed files with 203 additions and 14 deletions.
190 changes: 190 additions & 0 deletions src/token-service/metadata-token-service.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import axios from 'axios'
import {MetadataTokenService} from './metadata-token-service'
import Mock = jest.Mock;

describe('metadata-token-service', () => {

let oldGet = axios.get;

beforeEach(() => {
axios.get = jest.fn();
jest.useFakeTimers();
});

afterEach(() => {
axios.get = oldGet;
jest.useRealTimers();
});

it('simple scenario', async () => {

let metadataTokenService = new MetadataTokenService();

// set token
(axios.get as Mock).mockReturnValue({
status: 200,
data: {
access_token: '123'
}
});

// first time
const t1 = await metadataTokenService.getToken();

expect(t1).toBe('123');
expect((axios.get as Mock).mock.calls).toHaveLength(1);

// second time - no extra token request
const t2 = await metadataTokenService.getToken();

expect(t2).toBe('123');
expect((axios.get as Mock).mock.calls).toHaveLength(1);
});

it('provider works over few hours, so token expected to get updated every hour', async () => {

let metadataTokenService = new MetadataTokenService();

// set token
(axios.get as Mock).mockReturnValue({
status: 200,
data: {
access_token: '123'
}
});

// first time
let t = await metadataTokenService.getToken();
expect(t).toBe('123');
expect((axios.get as Mock).mock.calls).toHaveLength(1);

// change token
(axios.get as Mock).mockReturnValue({
status: 200,
data: {
access_token: '456'
}
});

// after 30 minutes
jest.advanceTimersByTime(30 * 60 * 1000);

t = await metadataTokenService.getToken();
expect(t).toBe('123');
expect((axios.get as Mock).mock.calls).toHaveLength(1);

// after 1 hour
jest.advanceTimersByTime(30 * 60 * 1000);

t = await metadataTokenService.getToken();
expect(t).toBe('456');
expect((axios.get as Mock).mock.calls).toHaveLength(2);

// change token
(axios.get as Mock).mockReturnValue({
status: 200,
data: {
access_token: '789'
}
});

// after 1 hour 30 minutes
jest.advanceTimersByTime(30 * 60 * 1000);

t = await metadataTokenService.getToken();
expect(t).toBe('456');
expect((axios.get as Mock).mock.calls).toHaveLength(2);

// after 2 hours
jest.advanceTimersByTime(30 * 60 * 1000);

t = await metadataTokenService.getToken();
expect(t).toBe('789');
expect((axios.get as Mock).mock.calls).toHaveLength(3);

// after 2 hours 30 minutes
jest.advanceTimersByTime(30 * 60 * 1000);

t = await metadataTokenService.getToken();
expect(t).toBe('789');
expect((axios.get as Mock).mock.calls).toHaveLength(3);

// after 2 hours 59 minutes
jest.advanceTimersByTime(29 * 60 * 1000);

t = await metadataTokenService.getToken();
expect(t).toBe('789');
expect((axios.get as Mock).mock.calls).toHaveLength(3);
});

it('Iam always returns an error', async () => {

let metadataTokenService = new MetadataTokenService();

// return an error
(axios.get as Mock).mockReturnValue({
status: 400,
});

await expect(() => metadataTokenService.getToken()).rejects.toThrow();
});

it('Iam occasionally returns an error', async () => {

let metadataTokenService = new MetadataTokenService();

// return token on 4th attempt - tests initialize()
const nextResp = (function*() {

for (let i = 0; i < 3; i++) {
yield {
status: 400,
};
}

return {
status: 200,
data: {
access_token: '123'
}
};
})();

(axios.get as Mock).mockImplementation(() => nextResp.next().value);

// first time - return token, even if it was returned only on 4th attempt
let t = await metadataTokenService.getToken();
expect(t).toBe('123');
expect((axios.get as Mock).mock.calls).toHaveLength(4);

// after 1 hour, return on an error use old token and make only one attempt to get token
(axios.get as Mock).mockReturnValue({
status: 400,
});

// return an error
(axios.get as Mock).mockReturnValue({
status: 400,
});

// after 1 hour,
jest.advanceTimersByTime(60 * 60 * 1000);

// Iam returns an error on 1st attempt, so we use the old token
t = await metadataTokenService.getToken();
expect(t).toBe('123');
expect((axios.get as Mock).mock.calls).toHaveLength(5);

// on next attempt we receive new token, and use this one
(axios.get as Mock).mockReturnValue({
status: 200,
data: {
access_token: '456'
}
});

t = await metadataTokenService.getToken();
expect(t).toBe('456');
expect((axios.get as Mock).mock.calls).toHaveLength(6);
});
});
27 changes: 13 additions & 14 deletions src/token-service/metadata-token-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,32 @@ const DEFAULT_OPTIONS: Options = {
},
};

const TOKEN_UPDATE_PERIOD_MS = 60 * 60 * 1000; // 1 hour ~ 10% of 12 hours as recommended in Iam documentation

export class MetadataTokenService implements TokenService {
private readonly url: string;
private readonly opts: Options;
private token?: string;
private lastFetch = 0;

constructor(url: string = DEFAULT_URL, options: Options = DEFAULT_OPTIONS) {
this.url = url;
this.opts = options;
}

async getToken(): Promise<string> {
if (!this.token) {
await this.initialize();

if (!this.token) {
throw new Error('Token is empty after MetadataTokenService.initialize');
if (!this.token) {
await this.initialize(); // may throw error, so there is no need to check for !this.token
} else if ((Date.now() - this.lastFetch) >= TOKEN_UPDATE_PERIOD_MS) { // then time to update token
try {
this.token = await this.fetchToken();
} catch {
// nothing - use old token
}

return this.token;
}

return this.token;
return this.token as string;
}

private async fetchToken(): Promise<string> {
Expand All @@ -42,6 +46,8 @@ export class MetadataTokenService implements TokenService {
throw new Error(`failed to fetch token from metadata service: ${res.status} ${res.statusText}`);
}

this.lastFetch = Date.now();

return res.data.access_token;
}

Expand All @@ -67,12 +73,5 @@ export class MetadataTokenService implements TokenService {
`failed to fetch token from metadata service: ${lastError}`,
);
}
setTimeout(async () => {
try {
this.token = await this.fetchToken();
} catch {
// TBD
}
}, 30_000);
}
}

0 comments on commit f9df471

Please sign in to comment.