Skip to content

Commit 1b20762

Browse files
committed
chore: handle exception on load
1 parent 8491743 commit 1b20762

File tree

4 files changed

+95
-4
lines changed

4 files changed

+95
-4
lines changed

src/ibmcloud-extension.spec.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@
1818

1919
import type { ExtensionContext } from '@podman-desktop/api';
2020
import { beforeEach, expect, test, vi } from 'vitest';
21-
import type { Container } from 'inversify';
21+
import type { Container, ServiceIdentifier } from 'inversify';
2222
import { IBMCloudExtension } from './ibmcloud-extension';
2323
import { AuthenticationProviderManager } from './manager/authentication-provider-manager';
24+
import { InversifyBinding } from './inject/inversify-binding';
2425

2526
let extensionContextMock: ExtensionContext;
2627
let ibmCloudExtension: TestIBMCloudExtension;
@@ -98,3 +99,25 @@ test('should log error if deferActivate throws', async () => {
9899

99100
spyConsoleError.mockRestore();
100101
});
102+
103+
test('should log error if getAsync for AuthenticationProviderManager throws', async () => {
104+
expect.assertions(2);
105+
106+
const error = new Error('getAsync failure');
107+
const spyConsoleError = vi.spyOn(console, 'error').mockImplementation(() => {});
108+
109+
// Mock the initBindings to return a container with a throwing getAsync
110+
const fakeContainer = {
111+
getAsync: vi.fn<(serviceIdentifier: ServiceIdentifier) => Promise<unknown>>().mockRejectedValueOnce(error),
112+
} as unknown as Container;
113+
const initBindingsMock = vi.fn<() => Promise<Container>>().mockResolvedValue(fakeContainer);
114+
const spyInitBindings = vi.spyOn(InversifyBinding.prototype, 'initBindings');
115+
spyInitBindings.mockImplementation(initBindingsMock);
116+
117+
await ibmCloudExtension.activate();
118+
119+
expect(fakeContainer.getAsync).toHaveBeenCalledWith(AuthenticationProviderManager);
120+
expect(spyConsoleError).toHaveBeenCalledWith('Error while creating the authentication provider manager', error);
121+
122+
spyConsoleError.mockRestore();
123+
});

src/ibmcloud-extension.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,12 @@ export class IBMCloudExtension {
3939
this.#inversifyBinding = new InversifyBinding(this.#extensionContext, telemetryLogger);
4040
this.#container = await this.#inversifyBinding.initBindings();
4141

42-
this.#authenticationProviderManager = await this.#container.getAsync(AuthenticationProviderManager);
42+
try {
43+
this.#authenticationProviderManager = await this.getContainer()?.getAsync(AuthenticationProviderManager);
44+
} catch (e) {
45+
console.error('Error while creating the authentication provider manager', e);
46+
return;
47+
}
4348

4449
// Perform the registration after the startup to not hold up the startup
4550
this.deferActivate().catch((e: unknown) => {

src/manager/authentication-provider-manager.spec.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,22 @@ describe('init/post construct', () => {
158158
expect(console.error).toHaveBeenCalledWith('Error monitoring tokens', expect.any(Error));
159159
});
160160
});
161+
162+
it('should handle error during initial monitorTokens in init()', async () => {
163+
expect.assertions(2);
164+
165+
// Spy and force monitorTokens to throw on first call
166+
const monitorTokensSpy = vi
167+
.spyOn(authenticationProviderManager, 'monitorTokens')
168+
.mockRejectedValueOnce(new Error('Initial monitor failure'));
169+
170+
// Call postConstruct
171+
await authenticationProviderManager.postConstruct();
172+
173+
// Should call monitorTokens and handle the error
174+
expect(monitorTokensSpy).toHaveBeenCalledTimes(1);
175+
expect(console.error).toHaveBeenCalledWith('Error while the initial monitoring of tokens', expect.any(Error));
176+
});
161177
});
162178

163179
test('destroy', async () => {
@@ -360,4 +376,33 @@ describe('monitorTokens', () => {
360376
expect(authenticationProviderManager.getOnDidChangeSessions().fire).not.toHaveBeenCalled();
361377
expect(PersistentSessionHelper.prototype.save).toHaveBeenCalledWith([freshSession]);
362378
});
379+
380+
it('should remove session and fire removed event on refresh error', async () => {
381+
expect.assertions(4);
382+
383+
const now = Math.round(Date.now() / 1000);
384+
const expiringSession: IAMSession = { session_id: 'fail-session', expiration: now + 10 } as unknown as IAMSession;
385+
const convertedSession = { id: 'fail-session' } as unknown as AuthenticationSession;
386+
387+
authenticationProviderManager.getIamSessions().push(expiringSession);
388+
389+
vi.mocked(IamSessionRefreshTokenHelper.prototype.refreshToken).mockRejectedValue(new Error('refresh fail'));
390+
vi.mocked(IamSessionConverterHelper.prototype.convertToAuthenticationSession).mockReturnValue(convertedSession);
391+
392+
await authenticationProviderManager.monitorTokens();
393+
394+
// Should have removed session
395+
expect(authenticationProviderManager.getIamSessions()).toHaveLength(0);
396+
397+
// Should have fired removed event
398+
expect(authenticationProviderManager.getOnDidChangeSessions().fire).toHaveBeenCalledWith({
399+
removed: [convertedSession],
400+
});
401+
402+
// Should have saved sessions (empty)
403+
expect(PersistentSessionHelper.prototype.save).toHaveBeenCalledWith([]);
404+
405+
// Should log error
406+
expect(console.error).toHaveBeenCalledWith('Error refreshing token', expect.any(Error));
407+
});
363408
});

src/manager/authentication-provider-manager.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,11 @@ export class AuthenticationProviderManager {
7070
this.iamSessions = await this.persistentSessionHelper.restoreSessions();
7171

7272
// Monitor the token expiration
73-
await this.monitorTokens();
73+
try {
74+
await this.monitorTokens();
75+
} catch (error: unknown) {
76+
console.error('Error while the initial monitoring of tokens', error);
77+
}
7478

7579
this.checkTimeout = setInterval(() => {
7680
this.monitorTokens().catch((error: unknown) => {
@@ -97,7 +101,21 @@ export class AuthenticationProviderManager {
97101
for (const session of this.iamSessions) {
98102
const delta = session.expiration - now;
99103
if (delta < 60) {
100-
const updatedSession = await this.iamSessionRefreshTokenHelper.refreshToken(session);
104+
let updatedSession: IAMSession;
105+
try {
106+
updatedSession = await this.iamSessionRefreshTokenHelper.refreshToken(session);
107+
} catch (error: unknown) {
108+
console.error('Error refreshing token', error);
109+
// Remove this session from the list
110+
const sessionIndex = this.iamSessions.findIndex(s => s.session_id === session.session_id);
111+
if (sessionIndex !== -1) {
112+
this.iamSessions.splice(sessionIndex, 1);
113+
}
114+
// Fire the event to notify the change
115+
const authenticationSession = this.iamSessionConverterHelper.convertToAuthenticationSession(session);
116+
this._onDidChangeSessions.fire({ removed: [authenticationSession] });
117+
continue;
118+
}
101119
// Update the session in the list
102120
const sessionIndex = this.iamSessions.findIndex(s => s.session_id === session.session_id);
103121
if (sessionIndex !== -1) {

0 commit comments

Comments
 (0)