mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-23 15:47:53 -05:00
Add tests for all studio-member-actions
This commit is contained in:
parent
a8ed7410a4
commit
c9ba9d443c
2 changed files with 379 additions and 1 deletions
|
@ -14,7 +14,8 @@ const Errors = keyMirror({
|
|||
USER_MUTED: null,
|
||||
UNKNOWN_USERNAME: null,
|
||||
RATE_LIMIT: null,
|
||||
MANAGER_LIMIT: null
|
||||
MANAGER_LIMIT: null,
|
||||
UNHANDLED: null
|
||||
});
|
||||
|
||||
const normalizeError = (err, body, res) => {
|
||||
|
|
377
test/unit/redux/studio-member-actions.test.js
Normal file
377
test/unit/redux/studio-member-actions.test.js
Normal file
|
@ -0,0 +1,377 @@
|
|||
import {selectStudioManagerCount} from '../../../src/redux/studio';
|
||||
import {
|
||||
Errors,
|
||||
removeManager,
|
||||
loadManagers,
|
||||
loadCurators,
|
||||
removeCurator,
|
||||
inviteCurator,
|
||||
promoteCurator,
|
||||
acceptInvitation
|
||||
} from '../../../src/views/studio/lib/studio-member-actions';
|
||||
import {managers, curators} from '../../../src/views/studio/lib/redux-modules';
|
||||
import {reducers, initialState} from '../../../src/views/studio/studio-redux';
|
||||
import configureStore from '../../../src/lib/configure-store';
|
||||
|
||||
jest.mock('../../../src/lib/api');
|
||||
import api from '../../../src/lib/api';
|
||||
|
||||
let store;
|
||||
|
||||
beforeEach(() => {
|
||||
api.mockClear();
|
||||
});
|
||||
|
||||
describe('loadManagers', () => {
|
||||
test('it populates the managers list', () => {
|
||||
store = configureStore(reducers, {
|
||||
...initialState,
|
||||
studio: {id: 123123}
|
||||
});
|
||||
api.mockImplementation((opts, callback) => {
|
||||
const body = [{username: 'user1'}, {username: 'user2'}, {username: 'user3'}];
|
||||
callback(null, body, {statusCode: 200});
|
||||
});
|
||||
store.dispatch(loadManagers());
|
||||
let items = managers.selector(store.getState()).items;
|
||||
expect(api.mock.calls[0][0].uri).toBe('/studios/123123/managers/');
|
||||
expect(api.mock.calls[0][0].params.offset).toBe(0);
|
||||
expect(items.length).toBe(3);
|
||||
expect(items[0].username).toBe('user1');
|
||||
|
||||
// Include the new offset next time it is called
|
||||
store.dispatch(loadManagers());
|
||||
expect(api.mock.calls[1][0].params.offset).toBe(3);
|
||||
items = managers.selector(store.getState()).items;
|
||||
expect(items.length).toBe(6);
|
||||
});
|
||||
|
||||
test('it correctly uses the admin route when possible', () => {
|
||||
store = configureStore(reducers, {
|
||||
...initialState,
|
||||
studio: {id: 123123},
|
||||
session: {
|
||||
session: {
|
||||
user: {token: 'a-token'},
|
||||
permissions: {admin: true}
|
||||
}
|
||||
}
|
||||
});
|
||||
api.mockImplementation((opts, callback) => {
|
||||
expect(opts.uri).toBe('/admin/studios/123123/managers/');
|
||||
expect(opts.authentication).toBe('a-token');
|
||||
});
|
||||
store.dispatch(loadManagers());
|
||||
});
|
||||
|
||||
test('errors are set on the managers state', () => {
|
||||
store = configureStore(reducers, {
|
||||
...initialState,
|
||||
studio: {id: 123123}
|
||||
});
|
||||
api.mockImplementation((opts, callback) => {
|
||||
callback(null, null, {statusCode: 500});
|
||||
});
|
||||
store.dispatch(loadManagers());
|
||||
expect(managers.selector(store.getState()).error).toBe(Errors.SERVER);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('loadCurators', () => {
|
||||
test('it populates the curators list', () => {
|
||||
store = configureStore(reducers, {
|
||||
...initialState,
|
||||
studio: {id: 123123}
|
||||
});
|
||||
api.mockImplementation((opts, callback) => {
|
||||
const body = [{username: 'user1'}, {username: 'user2'}, {username: 'user3'}];
|
||||
callback(null, body, {statusCode: 200});
|
||||
});
|
||||
store.dispatch(loadCurators());
|
||||
let items = curators.selector(store.getState()).items;
|
||||
expect(api.mock.calls[0][0].uri).toBe('/studios/123123/curators/');
|
||||
expect(api.mock.calls[0][0].params.offset).toBe(0);
|
||||
expect(items.length).toBe(3);
|
||||
expect(items[0].username).toBe('user1');
|
||||
|
||||
// Include the new offset next time it is called
|
||||
store.dispatch(loadCurators());
|
||||
expect(api.mock.calls[1][0].params.offset).toBe(3);
|
||||
items = curators.selector(store.getState()).items;
|
||||
expect(items.length).toBe(6);
|
||||
});
|
||||
|
||||
test('it correctly uses the admin route when possible', () => {
|
||||
store = configureStore(reducers, {
|
||||
...initialState,
|
||||
studio: {id: 123123},
|
||||
session: {
|
||||
session: {
|
||||
user: {token: 'a-token'},
|
||||
permissions: {admin: true}
|
||||
}
|
||||
}
|
||||
});
|
||||
api.mockImplementation((opts, callback) => {
|
||||
expect(opts.uri).toBe('/admin/studios/123123/curators/');
|
||||
expect(opts.authentication).toBe('a-token');
|
||||
});
|
||||
store.dispatch(loadCurators());
|
||||
});
|
||||
|
||||
test('errors are set on the curators state', () => {
|
||||
store = configureStore(reducers, {
|
||||
...initialState,
|
||||
studio: {id: 123123}
|
||||
});
|
||||
api.mockImplementation((opts, callback) => {
|
||||
callback(null, null, {statusCode: 500});
|
||||
});
|
||||
store.dispatch(loadCurators());
|
||||
expect(curators.selector(store.getState()).error).toBe(Errors.SERVER);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeManager', () => {
|
||||
beforeEach(() => {
|
||||
store = configureStore(reducers, {
|
||||
...initialState,
|
||||
studio: {
|
||||
id: 123123,
|
||||
managers: 3,
|
||||
manager: true
|
||||
},
|
||||
managers: {
|
||||
items: [
|
||||
{username: 'user1'},
|
||||
{username: 'user2'},
|
||||
{username: 'user3'}
|
||||
]
|
||||
},
|
||||
session: {
|
||||
session: {
|
||||
user: {username: 'user2'}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('removes the manager by username and decrements the count', async () => {
|
||||
api.mockImplementation((opts, callback) => {
|
||||
expect(opts.uri).toBe('/site-api/users/curators-in/123123/remove/');
|
||||
callback(null, {}, {statusCode: 200});
|
||||
});
|
||||
|
||||
await store.dispatch(removeManager('user2'));
|
||||
const state = store.getState();
|
||||
|
||||
// Ensure it removes the correct manager (index=1)
|
||||
expect(selectStudioManagerCount(state)).toBe(2);
|
||||
expect(managers.selector(state).items[0].username).toBe('user1');
|
||||
expect(managers.selector(state).items[1].username).toBe('user3');
|
||||
|
||||
// Ensure roles change if you are removing yourself
|
||||
expect(state.studio.manager).toBe(false);
|
||||
});
|
||||
|
||||
test('on error, promise rejects without any changing count or list', async () => {
|
||||
api.mockImplementation((opts, callback) => {
|
||||
callback(null, {}, {statusCode: 403});
|
||||
});
|
||||
|
||||
await expect(store.dispatch(removeManager('user2')))
|
||||
.rejects.toBe(Errors.PERMISSION);
|
||||
|
||||
const state = store.getState();
|
||||
const {items} = managers.selector(state);
|
||||
expect(selectStudioManagerCount(state)).toBe(3);
|
||||
expect(items.length).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeCurator', () => {
|
||||
beforeEach(() => {
|
||||
store = configureStore(reducers, {
|
||||
...initialState,
|
||||
studio: {id: 123123},
|
||||
curators: {
|
||||
items: [
|
||||
{username: 'user1'},
|
||||
{username: 'user2'},
|
||||
{username: 'user3'}
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('removes the curator by username', async () => {
|
||||
api.mockImplementation((opts, callback) => {
|
||||
expect(opts.uri).toBe('/site-api/users/curators-in/123123/remove/');
|
||||
callback(null, {}, {statusCode: 200});
|
||||
});
|
||||
|
||||
await store.dispatch(removeCurator('user2'));
|
||||
const state = store.getState();
|
||||
|
||||
// Ensure it removes the correct curator (index=1)
|
||||
expect(curators.selector(state).items[0].username).toBe('user1');
|
||||
expect(curators.selector(state).items[1].username).toBe('user3');
|
||||
});
|
||||
|
||||
test('on error, promise rejects without changing anything', async () => {
|
||||
api.mockImplementation((opts, callback) => {
|
||||
callback(null, {}, {statusCode: 500});
|
||||
});
|
||||
|
||||
await expect(store.dispatch(removeCurator('user2')))
|
||||
.rejects.toBe(Errors.SERVER);
|
||||
|
||||
const state = store.getState();
|
||||
const {items} = curators.selector(state);
|
||||
expect(items.length).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('inviteCurator', () => {
|
||||
beforeEach(() => {
|
||||
store = configureStore(reducers, {
|
||||
...initialState,
|
||||
studio: {id: 123123}
|
||||
});
|
||||
});
|
||||
|
||||
test('invites the curator on success', async () => {
|
||||
api.mockImplementation((opts, callback) => {
|
||||
callback(null, {}, {statusCode: 200});
|
||||
});
|
||||
|
||||
const result = await store.dispatch(inviteCurator('user2'));
|
||||
expect(result).toBe('user2');
|
||||
expect(api.mock.calls[0][0].uri).toBe('/site-api/users/curators-in/123123/invite_curator/');
|
||||
expect(api.mock.calls[0][0].params.usernames).toBe('user2');
|
||||
});
|
||||
|
||||
test('error because of unknown user', async () => {
|
||||
api.mockImplementation((opts, callback) => {
|
||||
callback(null, {}, {statusCode: 404});
|
||||
});
|
||||
await expect(store.dispatch(inviteCurator('user2')))
|
||||
.rejects.toBe(Errors.UNKNOWN_USERNAME);
|
||||
});
|
||||
test('error because of duplicate curator', async () => {
|
||||
api.mockImplementation((opts, callback) => {
|
||||
callback(null, {status: 'error', message: 'already a curator'}, {statusCode: 200});
|
||||
});
|
||||
await expect(store.dispatch(inviteCurator('user2')))
|
||||
.rejects.toBe(Errors.DUPLICATE);
|
||||
});
|
||||
test('unhandled error response', async () => {
|
||||
api.mockImplementation((opts, callback) => {
|
||||
callback(null, {status: 'error', message: 'xyz'}, {statusCode: 200});
|
||||
});
|
||||
await expect(store.dispatch(inviteCurator('user2')))
|
||||
.rejects.toBe(Errors.UNHANDLED);
|
||||
});
|
||||
});
|
||||
|
||||
describe('promoteCurator', () => {
|
||||
beforeEach(() => {
|
||||
store = configureStore(reducers, {
|
||||
...initialState,
|
||||
studio: {id: 123123, managers: 0},
|
||||
curators: {
|
||||
items: [{username: 'curatorName'}]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('promotes the curator on success', async () => {
|
||||
api.mockImplementation((opts, callback) => {
|
||||
callback(null, {}, {statusCode: 200});
|
||||
});
|
||||
|
||||
await store.dispatch(promoteCurator('curatorName'));
|
||||
const state = store.getState();
|
||||
const {items: curatorList} = curators.selector(state);
|
||||
const {items: managerList} = managers.selector(state);
|
||||
|
||||
expect(api.mock.calls[0][0].uri).toBe('/site-api/users/curators-in/123123/promote/');
|
||||
expect(api.mock.calls[0][0].params.usernames).toBe('curatorName');
|
||||
expect(managerList.length).toBe(1);
|
||||
expect(managerList[0].username).toBe('curatorName');
|
||||
expect(curatorList.length).toBe(0);
|
||||
expect(selectStudioManagerCount(state)).toBe(1);
|
||||
});
|
||||
|
||||
test('on error, promise rejects and nothing is modified', async () => {
|
||||
api.mockImplementation((opts, callback) => {
|
||||
callback(null, {}, {statusCode: 403});
|
||||
});
|
||||
await expect(store.dispatch(promoteCurator('curatorName')))
|
||||
.rejects.toBe(Errors.PERMISSION);
|
||||
const state = store.getState();
|
||||
const {items: curatorList} = curators.selector(state);
|
||||
const {items: managerList} = managers.selector(state);
|
||||
expect(managerList.length).toBe(0);
|
||||
expect(curatorList.length).toBe(1);
|
||||
expect(selectStudioManagerCount(state)).toBe(0);
|
||||
});
|
||||
|
||||
test('error because of exceeding manager limit', async () => {
|
||||
api.mockImplementation((opts, callback) => {
|
||||
callback(null, {message: 'too many owners'}, {statusCode: 400});
|
||||
});
|
||||
await expect(store.dispatch(promoteCurator('curatorName')))
|
||||
.rejects.toBe(Errors.MANAGER_LIMIT);
|
||||
});
|
||||
});
|
||||
|
||||
describe('acceptInvitation', () => {
|
||||
beforeEach(() => {
|
||||
store = configureStore(reducers, {
|
||||
...initialState,
|
||||
studio: {id: 123123, invited: true, curator: false},
|
||||
session: {
|
||||
session: {
|
||||
user: {
|
||||
username: 'me'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('accepts the invitation on success', async () => {
|
||||
api.mockImplementation((opts, callback) => {
|
||||
callback(null, {username: 'me'}, {statusCode: 200});
|
||||
});
|
||||
jest.useFakeTimers();
|
||||
await store.dispatch(acceptInvitation());
|
||||
let state = store.getState();
|
||||
const {items: curatorList} = curators.selector(state);
|
||||
expect(api.mock.calls[0][0].uri).toBe('/site-api/users/curators-in/123123/add/');
|
||||
expect(api.mock.calls[0][0].params.usernames).toBe('me');
|
||||
expect(curatorList.length).toBe(1);
|
||||
expect(curatorList[0].username).toBe('me');
|
||||
expect(state.studio.invited).toBe(true); // Should remain true until timers run
|
||||
jest.runAllTimers(); // delay to show success alert before toggling invited back to false
|
||||
state = store.getState();
|
||||
expect(state.studio.invited).toBe(false);
|
||||
expect(state.studio.curator).toBe(true);
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
test('on error, promise rejects and nothing is modified', async () => {
|
||||
api.mockImplementation((opts, callback) => {
|
||||
callback(null, {}, {statusCode: 403});
|
||||
});
|
||||
await expect(store.dispatch(acceptInvitation()))
|
||||
.rejects.toBe(Errors.PERMISSION);
|
||||
const state = store.getState();
|
||||
const {items: curatorList} = curators.selector(state);
|
||||
expect(curatorList.length).toBe(0);
|
||||
expect(state.studio.invited).toBe(true);
|
||||
expect(state.studio.curator).toBe(false);
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue