Improve test coverage and fix various issues
This commit is contained in:
8 files changed
+484
-6
No files matched your search
+200
-1
@@ -1,11 +1,26 @@
|
||||
import { applyMiddleware, legacy_createStore as createStore } from 'redux';
|
||||
import { thunk } from 'redux-thunk';
|
||||
|
||||
import { requestLogin, requestUserData } from '@/api';
|
||||
import {
|
||||
requestDeleteUser,
|
||||
requestLogin,
|
||||
requestRegisterUser,
|
||||
requestStripePaymentSheet,
|
||||
requestUserData,
|
||||
} from '@/api';
|
||||
import rootReducer from '@/reducers';
|
||||
|
||||
const createTestStore = () => createStore(rootReducer, undefined, applyMiddleware(thunk));
|
||||
|
||||
const fetchMock = global.fetch as jest.Mock;
|
||||
|
||||
const mockResponseOnce = (json: unknown, ok = true) =>
|
||||
fetchMock.mockResolvedValueOnce({ ok, status: ok ? 200 : 400, json: async () => json });
|
||||
|
||||
beforeEach(() => {
|
||||
fetchMock.mockClear();
|
||||
});
|
||||
|
||||
describe('dev fake users (__DEV__ only)', () => {
|
||||
it.each([
|
||||
['nonmember', false],
|
||||
@@ -22,3 +37,187 @@ describe('dev fake users (__DEV__ only)', () => {
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('requestLogin', () => {
|
||||
it('posts credentials and stores the token on success', async () => {
|
||||
const store = createTestStore();
|
||||
mockResponseOnce({ token: 'token-123' });
|
||||
|
||||
await store.dispatch(requestLogin('grisen', 'hunter2'));
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
'https://dusken.example.test/auth/obtain-token/',
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ username: 'grisen', password: 'hunter2' }),
|
||||
}),
|
||||
);
|
||||
expect(store.getState()).toMatchObject({
|
||||
isAuthenticated: true,
|
||||
isLoggingIn: false,
|
||||
userToken: 'token-123',
|
||||
loginError: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('stores the API error body on a rejected login', async () => {
|
||||
jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||
const store = createTestStore();
|
||||
const apiError = { non_field_errors: ['Kan ikke logge inn med disse opplysningene'] };
|
||||
mockResponseOnce(apiError, false);
|
||||
|
||||
await store.dispatch(requestLogin('grisen', 'wrong-password'));
|
||||
|
||||
expect(store.getState()).toMatchObject({
|
||||
isAuthenticated: false,
|
||||
isLoggingIn: false,
|
||||
userToken: null,
|
||||
loginError: apiError,
|
||||
});
|
||||
});
|
||||
|
||||
it('falls back to a generic error message when the API returns an empty body', async () => {
|
||||
jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||
const store = createTestStore();
|
||||
mockResponseOnce(null, false);
|
||||
|
||||
await store.dispatch(requestLogin('grisen', 'hunter2'));
|
||||
|
||||
expect(store.getState().loginError).toEqual({ non_field_errors: ['Kunne ikke logge inn, prøv igjen'] });
|
||||
});
|
||||
});
|
||||
|
||||
describe('requestUserData', () => {
|
||||
it('fetches /api/me/ with the auth token and stores the user', async () => {
|
||||
const store = createTestStore();
|
||||
const user = { username: 'grisen', email: 'grisen@example.test', is_member: true };
|
||||
mockResponseOnce(user);
|
||||
|
||||
await store.dispatch(requestUserData('token-123'));
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
'https://dusken.example.test/api/me/',
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
headers: expect.objectContaining({ Authorization: 'Token token-123' }),
|
||||
}),
|
||||
);
|
||||
expect(store.getState()).toMatchObject({ isFetchingUserData: false, user });
|
||||
});
|
||||
|
||||
it('stores the API error body when fetching user data fails', async () => {
|
||||
const store = createTestStore();
|
||||
const apiError = { detail: 'Ugyldig token.' };
|
||||
mockResponseOnce(apiError, false);
|
||||
|
||||
await store.dispatch(requestUserData('expired-token'));
|
||||
|
||||
expect(store.getState()).toMatchObject({ isFetchingUserData: false, user: null, userError: apiError });
|
||||
});
|
||||
});
|
||||
|
||||
describe('requestRegisterUser', () => {
|
||||
const formValues = {
|
||||
first_name: 'Grisen',
|
||||
last_name: 'Testersen',
|
||||
email: 'grisen@example.test',
|
||||
phone_number: '+4712345678',
|
||||
password: 'hunter2',
|
||||
};
|
||||
|
||||
it('authenticates with the returned auth_token and stores the user without it', async () => {
|
||||
const store = createTestStore();
|
||||
mockResponseOnce({ username: 'grisen', email: 'grisen@example.test', auth_token: 'token-456' });
|
||||
|
||||
await store.dispatch(requestRegisterUser(formValues));
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
'https://dusken.example.test/api/user/register/',
|
||||
expect.objectContaining({ method: 'POST', body: JSON.stringify(formValues) }),
|
||||
);
|
||||
expect(store.getState()).toMatchObject({
|
||||
isRegisteringUser: false,
|
||||
isAuthenticated: true,
|
||||
userToken: 'token-456',
|
||||
user: { username: 'grisen', email: 'grisen@example.test' },
|
||||
});
|
||||
expect(store.getState().user).not.toHaveProperty('auth_token');
|
||||
});
|
||||
|
||||
it('stores field errors from the API when registration fails', async () => {
|
||||
const store = createTestStore();
|
||||
const apiError = { email: ['Denne e-postadressen er allerede i bruk'] };
|
||||
mockResponseOnce(apiError, false);
|
||||
|
||||
await store.dispatch(requestRegisterUser(formValues));
|
||||
|
||||
expect(store.getState()).toMatchObject({
|
||||
isRegisteringUser: false,
|
||||
isAuthenticated: false,
|
||||
registerError: apiError,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('requestDeleteUser', () => {
|
||||
it('resets the store to its initial state on success', async () => {
|
||||
const store = createTestStore();
|
||||
// Authenticate first (via the fake-user shortcut) so the reset is observable
|
||||
await store.dispatch(requestLogin('member', 'any-password'));
|
||||
expect(store.getState().isAuthenticated).toBe(true);
|
||||
|
||||
mockResponseOnce({});
|
||||
await store.dispatch(requestDeleteUser('token-123'));
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
'https://dusken.example.test/api/me/delete/',
|
||||
expect.objectContaining({
|
||||
method: 'DELETE',
|
||||
headers: expect.objectContaining({ Authorization: 'Token token-123' }),
|
||||
}),
|
||||
);
|
||||
expect(store.getState()).toMatchObject({ isAuthenticated: false, userToken: null, user: null });
|
||||
});
|
||||
|
||||
it('stores the API error body when deletion fails', async () => {
|
||||
const store = createTestStore();
|
||||
const apiError = { detail: 'Kan ikke slette bruker med aktivt medlemskap' };
|
||||
mockResponseOnce(apiError, false);
|
||||
|
||||
await store.dispatch(requestDeleteUser('token-123'));
|
||||
|
||||
expect(store.getState()).toMatchObject({ isDeletingUser: false, deleteUserError: apiError });
|
||||
});
|
||||
});
|
||||
|
||||
describe('requestStripePaymentSheet', () => {
|
||||
it('posts the membership type and returns the payment sheet parameters', async () => {
|
||||
const sheet = { paymentIntent: 'pi_secret', ephemeralKey: 'ek_secret', customer: 'cus_123' };
|
||||
mockResponseOnce(sheet);
|
||||
|
||||
await expect(requestStripePaymentSheet('token-123', 'standard')).resolves.toEqual(sheet);
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
'https://dusken.example.test/api/stripe/payment-sheet/',
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ membership_type: 'standard' }),
|
||||
headers: expect.objectContaining({ Authorization: 'Token token-123' }),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects with the API detail message on failure', async () => {
|
||||
mockResponseOnce({ detail: 'Ugyldig medlemskapstype' }, false);
|
||||
|
||||
await expect(requestStripePaymentSheet('token-123', 'bogus')).rejects.toThrow('Ugyldig medlemskapstype');
|
||||
});
|
||||
|
||||
it('rejects with a generic message when the API gives no detail', async () => {
|
||||
mockResponseOnce({}, false);
|
||||
|
||||
await expect(requestStripePaymentSheet('token-123', 'standard')).rejects.toThrow(
|
||||
'Kunne ikke belaste bankkortet, prøv igjen',
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user