Skip to content

Commit 5b11f6b

Browse files
authored
chore(atlas-service, settings): add tests for settings section and sign out (#4750)
1 parent 8f853c2 commit 5b11f6b

File tree

11 files changed

+359
-30
lines changed

11 files changed

+359
-30
lines changed

configs/mocha-config-compass/register/compass-preferences-register.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
let setupPreferencesPromise;
2+
13
module.exports = {
24
mochaHooks: {
35
async beforeAll() {
@@ -11,8 +13,12 @@ module.exports = {
1113
// preferences used by a lot of packages in the monorepo
1214
process.env.COMPASS_TEST_USE_PREFERENCES_SANDBOX =
1315
process.env.COMPASS_TEST_USE_PREFERENCES_SANDBOX ?? 'true';
16+
// Make sure we only do this once so that --watch mode doesn't try to set
17+
// up preferences again on re-run
18+
setupPreferencesPromise ??=
19+
require('compass-preferences-model').setupPreferences();
1420
// NB: Not adding this as a dep in package.json to avoid circular dependency
15-
await require('compass-preferences-model').setupPreferences();
21+
await setupPreferencesPromise;
1622
},
1723
},
1824
};

packages/atlas-service/src/main.spec.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Sinon from 'sinon';
22
import { expect } from 'chai';
33
import { AtlasService, throwIfNotOk } from './main';
44
import { promisify } from 'util';
5-
import type { EventEmitter } from 'events';
5+
import { EventEmitter } from 'events';
66
import { once } from 'events';
77
import preferencesAccess from 'compass-preferences-model';
88

@@ -612,9 +612,10 @@ describe('AtlasServiceMain', function () {
612612
});
613613

614614
describe('with networkTraffic turned off', function () {
615-
const networkTraffic = preferencesAccess.getPreferences().networkTraffic;
615+
let networkTraffic: boolean;
616616

617617
before(async function () {
618+
networkTraffic = preferencesAccess.getPreferences().networkTraffic;
618619
await preferencesAccess.savePreferences({ networkTraffic: false });
619620
});
620621

@@ -644,4 +645,33 @@ describe('AtlasServiceMain', function () {
644645
});
645646
}
646647
});
648+
649+
describe('signOut', function () {
650+
it('should reset service state, revoke tokens, and destroy plugin', async function () {
651+
const logger = new EventEmitter();
652+
const plugin = {
653+
destroy: sandbox.stub().resolves(),
654+
};
655+
AtlasService['openExternal'] = sandbox.stub().resolves();
656+
AtlasService['token'] = { accessToken: '1234' };
657+
AtlasService['createMongoDBOIDCPlugin'] = (() => {
658+
return plugin;
659+
}) as any;
660+
AtlasService['fetch'] = sandbox.stub().resolves({ ok: true }) as any;
661+
AtlasService['oidcPluginLogger'] = logger;
662+
663+
await AtlasService.init();
664+
expect(getListenerCount(logger)).to.eq(25);
665+
666+
await AtlasService.signOut();
667+
expect(getListenerCount(logger)).to.eq(0);
668+
expect(logger).to.not.eq(AtlasService['oidcPluginLogger']);
669+
expect(plugin.destroy).to.have.been.calledOnce;
670+
expect(AtlasService['fetch']).to.have.been.calledOnceWith(
671+
'http://example.com/v1/revoke?client_id=1234abcd'
672+
);
673+
expect(AtlasService['token']).to.eq(null);
674+
expect(AtlasService['openExternal']).to.have.been.calledOnce;
675+
});
676+
});
647677
});

packages/atlas-service/src/main.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,10 @@ export class AtlasService {
253253
return apiBaseUrl;
254254
}
255255

256+
private static openExternal(...args: Parameters<typeof shell.openExternal>) {
257+
return shell?.openExternal(...args);
258+
}
259+
256260
// We use `allowedFlows` plugin option to control whether or not plugin is
257261
// allowed to start sign in flow or just try refresh and fail if refresh is
258262
// not possible.
@@ -287,8 +291,8 @@ export class AtlasService {
287291

288292
redirectRequestHandler(data);
289293
},
290-
async openBrowser({ url }) {
291-
await shell.openExternal(url);
294+
openBrowser: async ({ url }) => {
295+
await this.openExternal(url);
292296
},
293297
allowedFlows: this.getAllowedAuthFlows.bind(this),
294298
logger: this.oidcPluginLogger,
@@ -617,7 +621,7 @@ export class AtlasService {
617621
this.oidcPluginSyncedFromLoggerState = 'unauthenticated';
618622
this.oidcPluginLogger.emit('atlas-service-signed-out');
619623
// Open Atlas sign out page to end the browser session created for sign in
620-
void shell.openExternal(
624+
void this.openExternal(
621625
'https://account.mongodb.com/account/login?signedOut=true'
622626
);
623627
}

packages/atlas-service/src/store/atlas-signin-reducer.spec.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
AttemptStateMap,
88
signInWithModalPrompt,
99
closeSignInModal,
10+
signInWithoutPrompt,
1011
} from './atlas-signin-reducer';
1112
import { expect } from 'chai';
1213
import { configureStore } from './atlas-signin-store';
@@ -278,4 +279,63 @@ describe('atlasSignInReducer', function () {
278279
expect(store.getState()).to.have.property('state', 'canceled');
279280
});
280281
});
282+
283+
describe('signInWithoutPrompt', function () {
284+
it('should resolve when sign in flow finishes', async function () {
285+
const mockAtlasService = {
286+
isAuthenticated: sandbox.stub().resolves(false),
287+
signIn: sandbox.stub().resolves(),
288+
getUserInfo: sandbox.stub().resolves({}),
289+
emit: sandbox.stub(),
290+
};
291+
const store = configureStore({
292+
atlasService: mockAtlasService as any,
293+
});
294+
await store.dispatch(signInWithoutPrompt());
295+
expect(store.getState()).to.have.property('state', 'success');
296+
});
297+
298+
it('should reject if sign in fails', async function () {
299+
const mockAtlasService = {
300+
isAuthenticated: sandbox.stub().resolves(false),
301+
signIn: sandbox.stub().rejects(new Error('Sign in failed')),
302+
getUserInfo: sandbox.stub().resolves({}),
303+
emit: sandbox.stub(),
304+
};
305+
const store = configureStore({
306+
atlasService: mockAtlasService as any,
307+
});
308+
try {
309+
await store.dispatch(signInWithoutPrompt());
310+
expect.fail('Expected signInWithoutPrompt action to throw');
311+
} catch (err) {
312+
expect(err).to.have.property('message', 'Sign in failed');
313+
}
314+
expect(store.getState()).to.have.property('state', 'error');
315+
});
316+
317+
it('should reject if provided signal was aborted', async function () {
318+
const mockAtlasService = {
319+
isAuthenticated: sandbox.stub().resolves(false),
320+
signIn: sandbox.stub().resolves(),
321+
getUserInfo: sandbox.stub().resolves({}),
322+
emit: sandbox.stub(),
323+
};
324+
const store = configureStore({
325+
atlasService: mockAtlasService as any,
326+
});
327+
const c = new AbortController();
328+
const signInPromise = store.dispatch(
329+
signInWithoutPrompt({ signal: c.signal })
330+
);
331+
c.abort(new Error('Aborted from outside'));
332+
try {
333+
await signInPromise;
334+
throw new Error('Expected signInPromise to throw');
335+
} catch (err) {
336+
expect(err).to.have.property('message', 'Aborted from outside');
337+
}
338+
expect(store.getState()).to.have.property('state', 'canceled');
339+
});
340+
});
281341
});

packages/atlas-service/src/store/atlas-signin-reducer.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { AnyAction, Reducer } from 'redux';
22
import type { ThunkAction } from 'redux-thunk';
33
import { openToast } from '@mongodb-js/compass-components';
44
import type { AtlasService } from '../renderer';
5+
import { throwIfAborted } from '@mongodb-js/compass-utils';
56

67
export function isAction<A extends AnyAction>(
78
action: AnyAction,
@@ -315,7 +316,9 @@ const startAttempt = (fn: () => void): AtlasSignInThunkAction<AttemptState> => {
315316
// noop for the promise created by `finally`, original promise rejection
316317
// should be handled by the service user
317318
});
318-
fn();
319+
setImmediate(function () {
320+
fn();
321+
});
319322
return attempt;
320323
};
321324
};
@@ -376,6 +379,7 @@ export const signIn = (): AtlasSignInThunkAction<Promise<void>> => {
376379
} = getAttempt(getState().currentAttemptId);
377380
dispatch({ type: AtlasSignInActions.Start });
378381
try {
382+
throwIfAborted(signal);
379383
if ((await atlasService.isAuthenticated({ signal })) === false) {
380384
await atlasService.signIn({ signal });
381385
}

packages/compass-settings/src/components/modal.spec.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import React from 'react';
22
import type { ComponentProps } from 'react';
3-
import { render, screen, within, waitFor } from '@testing-library/react';
3+
import {
4+
render,
5+
screen,
6+
within,
7+
waitFor,
8+
cleanup,
9+
} from '@testing-library/react';
410
import { spy, stub } from 'sinon';
511
import type { SinonSpy } from 'sinon';
612
import { expect } from 'chai';
@@ -42,6 +48,10 @@ describe('SettingsModal', function () {
4248
};
4349
});
4450

51+
afterEach(function () {
52+
cleanup();
53+
});
54+
4555
it('renders nothing until it is open and loaded', function () {
4656
renderSettingsModal({ isOpen: false });
4757

0 commit comments

Comments
 (0)