Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

10349 Bug: Idle Logout Issues (with automated tests) #5176

Merged
merged 51 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
92ad46e
10349-bug: separate idle logout path from user logout path, fixing th…
Mwindo Jul 23, 2024
999ae68
10349-bug: fix race condition bugs, and clear the idle status timer o…
Mwindo Jul 23, 2024
139c908
10349-bug: add some unit tests
Mwindo Jul 23, 2024
8189d1e
10349-bug: pull out constants
Mwindo Jul 23, 2024
dcc55bb
10349-bug: better constants
Mwindo Jul 23, 2024
4d56e6a
10349-bug: remove unnecessary action
Mwindo Jul 23, 2024
661e406
10349-bug: undoing routing behavior of idle logout to preserve curren…
Mwindo Jul 24, 2024
4d92595
add user signed in check for idle-logout route
Mwindo Jul 24, 2024
2383922
10349-bug: get multi-tab test working
Mwindo Jul 26, 2024
52e7a8d
10349-bug: try adding a wait to fix GA failing test
Mwindo Jul 26, 2024
1d50fab
10349-bug: try a hack to see if test passes
Mwindo Jul 26, 2024
ef1e4d8
10349-bug: temporarily disable failing test to see if other test pass…
Mwindo Jul 26, 2024
bef73fd
10349-bug: re-add test, and temporarily set constants in getConstants…
Mwindo Jul 26, 2024
ac2c799
10349-bug: dismiss modals in older tabs to prevent surprise logouts i…
Mwindo Jul 26, 2024
d6c52e4
10349-bug: add some temporary logging to debug CI test failure since …
Mwindo Jul 26, 2024
1652f9e
10349-bug: undo logging, try removing render check in app.tsx to see …
Mwindo Jul 26, 2024
a7306da
10349-bug: try to get tests to not fail spectacularly
Mwindo Jul 26, 2024
5e7c342
Merge branch 'staging' into 10349-bug
Mwindo Jul 26, 2024
1745a0d
fix merge issue
Mwindo Jul 26, 2024
4ac3c96
10349-bug: add AppContext and AppInstanceManagerWrapper to allow us t…
Mwindo Jul 27, 2024
ffe5955
10349-bug: revert getConstants temp test hack
Mwindo Jul 27, 2024
e4f9575
10349-bug: fix unit test, try defaulting to true in appContext to see…
Mwindo Jul 27, 2024
029b57e
10349-bug: remove code that turned out to be unnecessary, focus on fa…
Mwindo Jul 27, 2024
6699bf5
10349-bug: forgot to remove ci check :/
Mwindo Jul 27, 2024
f293899
10349-bug: try using cypress baseUrl rather than localhost
Mwindo Jul 29, 2024
7959c0f
10349-bug: do some more logging, try reloading page (since it seems, …
Mwindo Jul 29, 2024
740cc5b
10349-bug: last attempt at logging the puppeteer CI failure
Mwindo Jul 29, 2024
d57373a
10349-bug: extend timeout
Mwindo Jul 29, 2024
1713615
10349-bug: one final *final* attempt, as I believe puppeteer page is …
Mwindo Jul 29, 2024
ae47b67
10349-bug: hail mary attempt before just giving up
Mwindo Jul 29, 2024
9c6fbfd
10349-bug: last try, for real
Mwindo Jul 29, 2024
e72877e
10349-bug: just had an idea about what the issue might be
Mwindo Jul 29, 2024
79dd8ee
10349-bug: clean up in case this actually works
Mwindo Jul 29, 2024
3e7f612
10349-bug: remove package that was causing a failure, and fix unit test
Mwindo Jul 29, 2024
eb3fcd2
10349-bug: update broadcastIdleStatusActiveAction.test.ts, remove con…
Mwindo Jul 29, 2024
2cd2b76
Merge remote-tracking branch 'ustc/staging' into 10349-bug
Mwindo Jul 29, 2024
8d37302
10349-bug: add missing test
Mwindo Jul 29, 2024
2b6f6ef
10349-bug: incorporate reviewer feedback
Mwindo Jul 30, 2024
e0988bb
10349-bug: remove unused import :/
Mwindo Jul 30, 2024
bb01bbe
10349-bug: expose cerebral in test and add better typing for sequences
Mwindo Aug 1, 2024
27a79a8
10349-bug: rename LOGOUT_BROADCAST_MESSAGES to BROADCAST_MESSAGE; add…
Mwindo Aug 9, 2024
f0039f5
Merge branch 'staging' into 10349-bug
Mwindo Aug 9, 2024
24760f6
10349-bug: stop tracking idle time if dawson has been updated, update…
Mwindo Aug 9, 2024
a0b540f
10349-bug: rename, and extract actions into a new sequence
Mwindo Aug 9, 2024
2eba0e4
10349-bug: update handleIdleLogoutAction
Mwindo Aug 12, 2024
ef4eed9
10349-bug: fix handleIdleLogoutAction.test.ts
Mwindo Aug 12, 2024
6fd5684
10349-bug: ensure idle logoutat is cleared on signing out
Mwindo Aug 15, 2024
d7d35b5
Merge branch 'staging' into 10349-bug
Mwindo Aug 15, 2024
1160002
Merge branch 'staging' into 10349-bug
Mwindo Aug 16, 2024
31a1f66
10349-bug: minor updates to tests
Mwindo Aug 16, 2024
8944515
Merge branch '10349-bug' of github.com:flexion/ef-cms into 10349-bug
Mwindo Aug 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions shared/src/business/entities/EntityConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1648,3 +1648,23 @@ export type CreatedCaseType = {
name: string;
};
};

export const LOGOUT_BROADCAST_MESSAGES = {
userLogout: 'userLogout',
idleLogout: 'idleLogout',
idleStatusActive: 'idleStatusActive',
stayLoggedIn: 'stayLoggedIn',
};

export const IDLE_LOGOUT_STATES = {
INITIAL: 'INITIAL',
MONITORING: 'MONITORING',
COUNTDOWN: 'COUNTDOWN',
};

export type IdleLogoutStateType =
(typeof IDLE_LOGOUT_STATES)[keyof typeof IDLE_LOGOUT_STATES];

export type IdleLogoutType =
| (typeof LOGOUT_BROADCAST_MESSAGES)['idleLogout']
| (typeof LOGOUT_BROADCAST_MESSAGES)['userLogout'];
20 changes: 14 additions & 6 deletions web-client/src/AppInstanceManager.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { LOGOUT_BROADCAST_MESSAGES } from '@shared/business/entities/EntityConstants';
import { connect } from '@web-client/presenter/shared.cerebral';
import { sequences } from '@web-client/presenter/app.cerebral';
import { state } from '@web-client/presenter/app.cerebral';
Expand All @@ -18,26 +19,33 @@ export const AppInstanceManager = connect(
appInstanceManagerHelper: state.appInstanceManagerHelper,
confirmStayLoggedInSequence: sequences.confirmStayLoggedInSequence,
resetIdleTimerSequence: sequences.resetIdleTimerSequence,
signOutSequence: sequences.signOutSequence,
signOutIdleSequence: sequences.signOutIdleSequence,
signOutUserInitiatedSequence: sequences.signOutUserInitiatedSequence,
Mwindo marked this conversation as resolved.
Show resolved Hide resolved
},
function AppInstanceManager({
appInstanceManagerHelper,
confirmStayLoggedInSequence,
resetIdleTimerSequence,
signOutSequence,
signOutIdleSequence,
signOutUserInitiatedSequence,
}) {
const { channelHandle } = appInstanceManagerHelper;

channelHandle.onmessage = msg => {
switch (msg.subject) {
case 'idleStatusActive':
case LOGOUT_BROADCAST_MESSAGES.idleStatusActive:
resetIdleTimerSequence();
break;
case 'stayLoggedIn':
case LOGOUT_BROADCAST_MESSAGES.stayLoggedIn:
confirmStayLoggedInSequence();
break;
case 'logout':
signOutSequence({
case LOGOUT_BROADCAST_MESSAGES.idleLogout:
signOutIdleSequence({
skipBroadcast: true,
});
break;
case LOGOUT_BROADCAST_MESSAGES.userLogout:
signOutUserInitiatedSequence({
skipBroadcast: true,
});
break;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { LOGOUT_BROADCAST_MESSAGES } from '@shared/business/entities/EntityConstants';
import { applicationContextForClient as applicationContext } from '@web-client/test/createClientTestApplicationContext';
import { broadcastIdleStatusActiveAction } from './broadcastIdleStatusActiveAction';
import { presenter } from '../presenter-mock';
Expand All @@ -19,7 +20,7 @@ describe('broadcastIdleStatusActiveAction', () => {
expect(
applicationContext.getBroadcastGateway().postMessage.mock.calls[0][0],
).toMatchObject({
subject: 'idleStatusActive',
subject: LOGOUT_BROADCAST_MESSAGES.idleStatusActive,
});
});
});
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { LOGOUT_BROADCAST_MESSAGES } from '@shared/business/entities/EntityConstants';
import { state } from '@web-client/presenter/app.cerebral';

/**
Expand All @@ -12,5 +13,7 @@ export const broadcastIdleStatusActiveAction = async ({
}: ActionProps) => {
store.set(state.lastIdleAction, Date.now());
const broadcastChannel = applicationContext.getBroadcastGateway();
await broadcastChannel.postMessage({ subject: 'idleStatusActive' });
await broadcastChannel.postMessage({
subject: LOGOUT_BROADCAST_MESSAGES.idleStatusActive,
});
};
7 changes: 6 additions & 1 deletion web-client/src/presenter/actions/broadcastLogoutAction.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { state } from '@web-client/presenter/app.cerebral';

/**
* tells all open tabs to also logout
* @param {object} providers the providers object
Expand All @@ -6,11 +8,14 @@
*/
export const broadcastLogoutAction = async ({
applicationContext,
get,
props,
}: ActionProps) => {
// for some reason this causes jest integration tests to never finish, so don't run in CI
if (!process.env.CI && !props.skipBroadcast) {
const broadcastChannel = applicationContext.getBroadcastGateway();
await broadcastChannel.postMessage({ subject: 'logout' });
await broadcastChannel.postMessage({
subject: get(state.logoutType),
});
}
};
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { LOGOUT_BROADCAST_MESSAGES } from '@shared/business/entities/EntityConstants';
import { applicationContextForClient as applicationContext } from '@web-client/test/createClientTestApplicationContext';
import { broadcastStayLoggedInAction } from './broadcastStayLoggedInAction';
import { presenter } from '../presenter-mock';
Expand All @@ -19,7 +20,7 @@ describe('broadcastStayLoggedInAction', () => {
expect(
applicationContext.getBroadcastGateway().postMessage.mock.calls[0][0],
).toMatchObject({
subject: 'stayLoggedIn',
subject: LOGOUT_BROADCAST_MESSAGES.stayLoggedIn,
});
});
});
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { LOGOUT_BROADCAST_MESSAGES } from '@shared/business/entities/EntityConstants';

/**
* broadcasts a stay logged in message to all app instances
* @param {object} providers the providers object
Expand All @@ -9,5 +11,7 @@ export const broadcastStayLoggedInAction = async ({
}: ActionProps) => {
const broadcastChannel = applicationContext.getBroadcastGateway();

await broadcastChannel.postMessage({ subject: 'stayLoggedIn' });
await broadcastChannel.postMessage({
subject: LOGOUT_BROADCAST_MESSAGES.stayLoggedIn,
});
};
23 changes: 23 additions & 0 deletions web-client/src/presenter/actions/clearIdleTimerAction.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { IDLE_LOGOUT_STATES } from '@shared/business/entities/EntityConstants';
import { clearIdleTimerAction } from './clearIdleTimerAction';
import { runAction } from '@web-client/presenter/test.cerebral';

describe('clearLoginFormAction', () => {
it('should reset the idle timer state', async () => {
const result = await runAction(clearIdleTimerAction, {
state: {
idleLogoutState: {
logoutAt: Date.now(),
state: IDLE_LOGOUT_STATES.COUNTDOWN,
},
lastIdleAction: Date.now(),
},
});

expect(result.state.lastIdleAction).not.toBeDefined();
expect(result.state.idleLogoutState).toMatchObject({
logoutAt: undefined,
state: IDLE_LOGOUT_STATES.INITIAL,
});
});
});
10 changes: 10 additions & 0 deletions web-client/src/presenter/actions/clearIdleTimerAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { IDLE_LOGOUT_STATES } from '@shared/business/entities/EntityConstants';
import { state } from '@web-client/presenter/app.cerebral';

export const clearIdleTimerAction = ({ store }: ActionProps) => {
store.unset(state.lastIdleAction);
store.set(state.idleLogoutState, {
logoutAt: undefined,
state: IDLE_LOGOUT_STATES.INITIAL,
});
};
Mwindo marked this conversation as resolved.
Show resolved Hide resolved
15 changes: 15 additions & 0 deletions web-client/src/presenter/actions/clearLogoutTypeAction.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { LOGOUT_BROADCAST_MESSAGES } from '@shared/business/entities/EntityConstants';
import { clearLogoutTypeAction } from './clearLogoutTypeAction';
import { runAction } from '@web-client/presenter/test.cerebral';

describe('clearLogoutTypeAction', () => {
it('should clear the logout type', async () => {
const result = await runAction(clearLogoutTypeAction, {
state: {
logoutType: LOGOUT_BROADCAST_MESSAGES.userLogout,
},
});

expect(result.state.logoutType).toBe('');
});
});
5 changes: 5 additions & 0 deletions web-client/src/presenter/actions/clearLogoutTypeAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { state } from '@web-client/presenter/app.cerebral';

export const clearLogoutTypeAction = ({ store }: ActionProps) => {
store.set(state.logoutType, '');
};
21 changes: 11 additions & 10 deletions web-client/src/presenter/actions/handleIdleLogoutAction.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { IDLE_LOGOUT_STATES } from '@shared/business/entities/EntityConstants';
import { handleIdleLogoutAction } from './handleIdleLogoutAction';
import { runAction } from '@web-client/presenter/test.cerebral';

Expand Down Expand Up @@ -30,7 +31,7 @@ describe('handleIdleLogoutAction', () => {
},
idleLogoutState: {
logoutAt: undefined,
state: 'INITIAL',
state: IDLE_LOGOUT_STATES.INITIAL,
},
lastIdleAction: 0,
user: undefined,
Expand All @@ -39,7 +40,7 @@ describe('handleIdleLogoutAction', () => {

expect(result.state.idleLogoutState).toEqual({
logoutAt: undefined,
state: 'INITIAL',
state: IDLE_LOGOUT_STATES.INITIAL,
});
});

Expand All @@ -58,7 +59,7 @@ describe('handleIdleLogoutAction', () => {
},
idleLogoutState: {
logoutAt: undefined,
state: 'INITIAL',
state: IDLE_LOGOUT_STATES.INITIAL,
},
lastIdleAction: 0,
user: {},
Expand All @@ -67,7 +68,7 @@ describe('handleIdleLogoutAction', () => {

expect(result.state.idleLogoutState).toEqual({
logoutAt: undefined,
state: 'INITIAL',
state: IDLE_LOGOUT_STATES.INITIAL,
});
});

Expand All @@ -86,7 +87,7 @@ describe('handleIdleLogoutAction', () => {
},
idleLogoutState: {
logoutAt: undefined,
state: 'INITIAL',
state: IDLE_LOGOUT_STATES.INITIAL,
},
lastIdleAction: 0,
user: {},
Expand All @@ -95,7 +96,7 @@ describe('handleIdleLogoutAction', () => {

expect(result.state.idleLogoutState).toEqual({
logoutAt: expect.any(Number),
state: 'MONITORING',
state: IDLE_LOGOUT_STATES.MONITORING,
});
});

Expand All @@ -115,7 +116,7 @@ describe('handleIdleLogoutAction', () => {
},
idleLogoutState: {
logoutAt: undefined,
state: 'MONITORING',
state: IDLE_LOGOUT_STATES.MONITORING,
},
lastIdleAction: 0,
user: {},
Expand All @@ -124,7 +125,7 @@ describe('handleIdleLogoutAction', () => {

expect(result.state.idleLogoutState).toEqual({
logoutAt: expect.any(Number),
state: 'COUNTDOWN',
state: IDLE_LOGOUT_STATES.COUNTDOWN,
});
});

Expand All @@ -144,7 +145,7 @@ describe('handleIdleLogoutAction', () => {
},
idleLogoutState: {
logoutAt: 15000,
state: 'COUNTDOWN',
state: IDLE_LOGOUT_STATES.COUNTDOWN,
},
lastIdleAction: 0,
user: {},
Expand All @@ -153,7 +154,7 @@ describe('handleIdleLogoutAction', () => {

expect(result.state.idleLogoutState).toEqual({
logoutAt: undefined,
state: 'INITIAL',
state: IDLE_LOGOUT_STATES.INITIAL,
});
expect(presenter.providers.path.logout).toHaveBeenCalled();
});
Expand Down
17 changes: 11 additions & 6 deletions web-client/src/presenter/actions/handleIdleLogoutAction.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { IDLE_LOGOUT_STATES } from '@shared/business/entities/EntityConstants';
import { state } from '@web-client/presenter/app.cerebral';

export const handleIdleLogoutAction = ({ get, path, store }: ActionProps) => {
Expand All @@ -7,29 +8,33 @@ export const handleIdleLogoutAction = ({ get, path, store }: ActionProps) => {
const isUploading = get(state.fileUploadProgress.isUploading);
const user = get(state.user);

if (user && !isUploading && idleLogoutState.state === 'INITIAL') {
if (
user &&
!isUploading &&
idleLogoutState.state === IDLE_LOGOUT_STATES.INITIAL
) {
store.set(state.idleLogoutState, {
logoutAt:
Date.now() +
constants.SESSION_MODAL_TIMEOUT +
constants.SESSION_TIMEOUT,
state: 'MONITORING',
state: IDLE_LOGOUT_STATES.MONITORING,
});
} else if (idleLogoutState.state === 'MONITORING') {
} else if (idleLogoutState.state === IDLE_LOGOUT_STATES.MONITORING) {
if (Date.now() > lastIdleAction + constants.SESSION_TIMEOUT) {
store.set(state.idleLogoutState, {
logoutAt:
lastIdleAction +
constants.SESSION_MODAL_TIMEOUT +
constants.SESSION_TIMEOUT,
state: 'COUNTDOWN',
state: IDLE_LOGOUT_STATES.COUNTDOWN,
});
}
} else if (idleLogoutState.state === 'COUNTDOWN') {
} else if (idleLogoutState.state === IDLE_LOGOUT_STATES.COUNTDOWN) {
if (Date.now() > idleLogoutState.logoutAt) {
store.set(state.idleLogoutState, {
logoutAt: undefined,
state: 'INITIAL',
state: IDLE_LOGOUT_STATES.INITIAL,
});
return path.logout();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { IDLE_LOGOUT_STATES } from '@shared/business/entities/EntityConstants';
import { applicationContextForClient as applicationContext } from '@web-client/test/createClientTestApplicationContext';
import { handlePeerResetIdleTimerAction } from './handlePeerResetIdleTimerAction';
import { presenter } from '../presenter-mock';
Expand All @@ -15,12 +16,14 @@ describe('handlePeerResetIdleTimerAction', () => {
},
state: {
idleLogoutState: {
state: 'COUNTDOWN',
state: IDLE_LOGOUT_STATES.COUNTDOWN,
},
},
});

expect(result.state.idleLogoutState.state).toEqual('COUNTDOWN');
expect(result.state.idleLogoutState.state).toEqual(
IDLE_LOGOUT_STATES.COUNTDOWN,
);
});

it('should reset the idle logout state when not COUNTDOWN', async () => {
Expand All @@ -30,11 +33,13 @@ describe('handlePeerResetIdleTimerAction', () => {
},
state: {
idleLogoutState: {
state: 'MONITORING',
state: IDLE_LOGOUT_STATES.MONITORING,
},
},
});

expect(result.state.idleLogoutState.state).toEqual('INITIAL');
expect(result.state.idleLogoutState.state).toEqual(
IDLE_LOGOUT_STATES.INITIAL,
);
});
});
Loading
Loading