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

Moved login/logout logic into the useUser composable and updated references #12915

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
88 changes: 0 additions & 88 deletions kolibri/core/assets/src/state/modules/core/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,94 +67,6 @@ export function handleApiError(store, { error, reloadOnReconnect = false } = {})
throw error;
}

export function setSession(store, { session, clientNow }) {
const serverTime = session.server_time;
if (clientNow) {
setServerTime(serverTime, clientNow);
}
session = pick(session, Object.keys(baseSessionState));
store.commit('CORE_SET_SESSION', session);
}

/**
* Sets a password that is currently not specified
* due to an account that was created while passwords
* were not required.
*
* @param {object} store The store.
* @param {object} sessionPayload The session payload.
*/
export function kolibriSetUnspecifiedPassword(store, { username, password, facility }) {
const data = {
username,
password,
facility,
};
return client({
url: urls['kolibri:core:setnonspecifiedpassword'](),
data,
method: 'post',
});
}

/**
* Signs in user.
*
* @param {object} store The store.
* @param {object} sessionPayload The session payload.
*/
export function kolibriLogin(store, sessionPayload) {
Lockr.set(UPDATE_MODAL_DISMISSED, false);
return client({
data: {
...sessionPayload,
active: true,
browser,
os,
},
url: urls['kolibri:core:session_list'](),
method: 'post',
})
.then(() => {
// check redirect is disabled:
if (!sessionPayload.disableRedirect)
if (sessionPayload.next) {
// OIDC redirect
redirectBrowser(sessionPayload.next);
}
// Normal redirect on login
else {
redirectBrowser();
}
})
.catch(error => {
const errorsCaught = CatchErrors(error, [
ERROR_CONSTANTS.INVALID_CREDENTIALS,
ERROR_CONSTANTS.MISSING_PASSWORD,
ERROR_CONSTANTS.PASSWORD_NOT_SPECIFIED,
ERROR_CONSTANTS.NOT_FOUND,
]);
if (errorsCaught) {
if (errorsCaught.includes(ERROR_CONSTANTS.INVALID_CREDENTIALS)) {
return LoginErrors.INVALID_CREDENTIALS;
} else if (errorsCaught.includes(ERROR_CONSTANTS.MISSING_PASSWORD)) {
return LoginErrors.PASSWORD_MISSING;
} else if (errorsCaught.includes(ERROR_CONSTANTS.PASSWORD_NOT_SPECIFIED)) {
return LoginErrors.PASSWORD_NOT_SPECIFIED;
} else if (errorsCaught.includes(ERROR_CONSTANTS.NOT_FOUND)) {
return LoginErrors.USER_NOT_FOUND;
}
} else {
store.dispatch('handleApiError', { error });
}
});
}

export function kolibriLogout() {
// Use the logout backend URL to initiate logout
redirectBrowser(urls['kolibri:core:logout']());
}

const _setPageVisibility = debounce((store, visibility) => {
store.commit('CORE_SET_PAGE_VISIBILITY', visibility);
}, 500);
Expand Down
24 changes: 15 additions & 9 deletions packages/kolibri/__tests__/heartbeat.spec.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
import mock from 'xhr-mock';
import coreStore from 'kolibri/store';
import redirectBrowser from 'kolibri/utils/redirectBrowser';
import * as serverClock from 'kolibri/utils/serverClock';
import { get, set } from '@vueuse/core';
import useSnackbar, { useSnackbarMock } from 'kolibri/composables/useSnackbar'; // eslint-disable-line
import { ref } from '@vue/composition-api';
import { DisconnectionErrorCodes } from 'kolibri/constants';
import useUser, { useUserMock } from 'kolibri/composables/useUser'; // eslint-disable-line
import { HeartBeat } from '../heartbeat.js';
import { trs } from '../internal/disconnection';
import coreModule from '../../../kolibri/core/assets/src/state/modules/core';
import { stubWindowLocation } from 'testUtils'; // eslint-disable-line

jest.mock('kolibri/utils/redirectBrowser');
jest.mock('kolibri/urls');
jest.mock('lockr');
jest.mock('kolibri/composables/useSnackbar');

coreStore.registerModule('core', coreModule);
jest.mock('kolibri/composables/useUser');

describe('HeartBeat', function () {
stubWindowLocation(beforeAll, afterAll);
// replace the real XHR object with the mock XHR object before each test
beforeEach(() => mock.setup());

beforeEach(() => {
mock.setup();
useUser.mockImplementation(() => useUserMock());
});

// put the real XHR object back and clear the mocks after each test
afterEach(() => mock.teardown());
Expand Down Expand Up @@ -206,7 +208,8 @@ describe('HeartBeat', function () {
jest.spyOn(heartBeat, '_sessionUrl').mockReturnValue('url');
});
it('should sign out if an auto logout is detected', function () {
coreStore.commit('CORE_SET_SESSION', { user_id: 'test', id: 'current' });
const { setSession } = useUser();
setSession({ session: { user_id: 'test', id: 'current' } });
mock.put(/.*/, {
status: 200,
body: JSON.stringify({ user_id: null, id: 'current' }),
Expand All @@ -218,7 +221,8 @@ describe('HeartBeat', function () {
});
});
it('should redirect if a change in user is detected', function () {
coreStore.commit('CORE_SET_SESSION', { user_id: 'test', id: 'current' });
const { setSession } = useUser();
setSession({ session: { user_id: 'test', id: 'current' } });
redirectBrowser.mockReset();
mock.put(/.*/, {
status: 200,
Expand All @@ -230,7 +234,8 @@ describe('HeartBeat', function () {
});
});
it('should not sign out if user_id changes but session is being set for first time', function () {
coreStore.commit('CORE_SET_SESSION', { user_id: undefined, id: undefined });
const { setSession } = useUser();
setSession({ session: { user_id: undefined, id: undefined } });
mock.put(/.*/, {
status: 200,
body: JSON.stringify({ user_id: null, id: 'current' }),
Expand All @@ -242,7 +247,8 @@ describe('HeartBeat', function () {
});
});
it('should call setServerTime with a clientNow value that is between the start and finish of the poll', function () {
coreStore.commit('CORE_SET_SESSION', { user_id: 'test', id: 'current' });
const { setSession } = useUser();
setSession({ session: { user_id: 'test', id: 'current' } });
const serverTime = new Date().toJSON();
mock.put(/.*/, {
status: 200,
Expand Down
35 changes: 29 additions & 6 deletions packages/kolibri/composables/__mocks__/useUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@
* useUser.mockImplementation(() => useUserMock())
* ```
*/
import { computed } from '@vue/composition-api';
import { ref, computed } from '@vue/composition-api';
import { UserKinds } from 'kolibri/constants';
import { jest } from '@jest/globals'; // Ensure jest is imported for mocking functions

const session = {
const MOCK_DEFAULT_SESSION = {
app_context: false,
can_manage_content: false,
facility_id: undefined,
Expand Down Expand Up @@ -63,9 +64,9 @@ const MOCK_DEFAULTS = {
userFacilityId: undefined,
getUserKind: UserKinds.ANONYMOUS,
userHasPermissions: false,
session,
//state
...session,
session: { ...MOCK_DEFAULT_SESSION },
// Mock state
...MOCK_DEFAULT_SESSION,
};

export function useUserMock(overrides = {}) {
Expand All @@ -77,7 +78,29 @@ export function useUserMock(overrides = {}) {
for (const key in mocks) {
computedMocks[key] = computed(() => mocks[key]);
}
return computedMocks;

// Module-level state reference for actions
const session = ref({ ...mocks.session });

// Mock implementation of `useUser` methods
return {
...computedMocks,
session, // Make session mutable for test scenarios

// Actions
setSession: jest.fn(({ session: newSession, clientNow }) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the failing tests re: not calling serverTime w/ clientNow may be fixed by calling it here.

session.value = {
...MOCK_DEFAULT_SESSION,
...newSession,
};
}),

kolibriLogin: jest.fn(async () => Promise.resolve()),

kolibriLogout: jest.fn(() => {}),

kolibrisetUnspecifiedPassword: jest.fn(async () => Promise.resolve()),
};
}

export default jest.fn(() => useUserMock());
Loading
Loading