diff --git a/src/redux/studio.js b/src/redux/studio.js index 97d325bb5..42a157da1 100644 --- a/src/redux/studio.js +++ b/src/redux/studio.js @@ -3,6 +3,9 @@ const keyMirror = require('keymirror'); const api = require('../lib/api'); const log = require('../lib/log'); +const {selectUserId} = require('./session'); +const {selectIsAdmin, selectIsSocial} = require('./permissions'); + const Status = keyMirror({ FETCHED: null, NOT_FETCHED: null, @@ -18,6 +21,7 @@ const getInitialState = () => ({ commentingAllowed: false, thumbnail: '', followers: 0, + owner: null, rolesStatus: Status.NOT_FETCHED, manager: false, @@ -55,6 +59,8 @@ const studioReducer = (state, action) => { } }; +// Action Creators + const setFetchStatus = (fetchType, fetchStatus, error) => ({ type: 'SET_FETCH_STATUS', fetchType, @@ -72,6 +78,8 @@ const setRoles = roles => ({ roles: roles }); +// Thunks + const getInfo = studioId => (dispatch => { dispatch(setFetchStatus('infoStatus', Status.FETCHING)); api({uri: `/studios/${studioId}`}, (err, body, res) => { @@ -86,7 +94,8 @@ const getInfo = studioId => (dispatch => { openToAll: body.open_to_all, commentingAllowed: body.commenting_allowed, updated: new Date(body.history.modified), - followers: body.stats.followers + followers: body.stats.followers, + owner: body.owner })); }); }); @@ -111,10 +120,32 @@ const getRoles = (studioId, username, token) => (dispatch => { }); }); +// Selectors + +// Fine-grain selector helpers - not exported, use the higher level selectors below +const isCreator = state => + state.studio.owner !== null && // Never try matching if owner has not been set + selectUserId(state) === state.studio.owner; +const isCurator = state => state.studio.curator; +const isManager = state => state.studio.manager || isCreator(state); + +// Action-based permissions selectors +const selectCanEditInfo = state => selectIsAdmin(state) || isManager(state); +const selectCanAddProjects = state => + isManager(state) || + isCurator(state) || + (selectIsSocial(state) && state.studio.openToAll); + module.exports = { getInitialState, studioReducer, Status, + + // Thunks getInfo, - getRoles + getRoles, + + // Selectors + selectCanEditInfo, + selectCanAddProjects }; diff --git a/test/unit/redux/studio.test.js b/test/unit/redux/studio.test.js new file mode 100644 index 000000000..6a9c22ca7 --- /dev/null +++ b/test/unit/redux/studio.test.js @@ -0,0 +1,98 @@ +import { + getInitialState as getInitialStudioState, + selectCanEditInfo, + selectCanAddProjects +} from '../../../src/redux/studio'; + +import { + getInitialState as getInitialSessionState +} from '../../../src/redux/session'; + +const fixtures = { + permissions: { + isAdmin: {admin: true}, + isSocial: {social: true} + }, + studio: { + isManager: {manager: true}, + isCurator: {curator: true}, + creator1: {owner: 1}, + openToAll: {openToAll: true} + }, + session: { + user1: { + session: {user: {id: 1}} + } + } +}; + +describe('studio selectors', () => { + let state; + + beforeEach(() => { + state = { + permissions: {}, + session: getInitialSessionState(), + studio: getInitialStudioState() + }; + }); + + describe('studio info', () => { + test('is editable by admin', () => { + state.permissions = fixtures.permissions.isAdmin; + expect(selectCanEditInfo(state)).toBe(true); + }); + test('is editable by managers and studio creator', () => { + state.studio = fixtures.studio.isManager; + expect(selectCanEditInfo(state)).toBe(true); + + state.studio = fixtures.studio.creator1; + state.session = fixtures.session.user1; + expect(selectCanEditInfo(state)).toBe(true); + }); + test('is not editable by curators', () => { + state.studio = fixtures.studio.isCurator; + state.session = fixtures.session.user1; + expect(selectCanEditInfo(state)).toBe(false); + }); + test('is not editable by other logged in users', () => { + state.session = fixtures.session.user1; + expect(selectCanEditInfo(state)).toBe(false); + }); + test('is not editable by logged out users', () => { + expect(selectCanEditInfo(state)).toBe(false); + }); + }); + + describe('studio projects', () => { + test('cannot be added by admin', () => { + state.permissions = fixtures.permissions.isAdmin; + state.session = fixtures.session.user1; + expect(selectCanAddProjects(state)).toBe(false); + }); + test('can be added by managers and studio creator', () => { + state.studio = fixtures.studio.isManager; + expect(selectCanAddProjects(state)).toBe(true); + + state.studio = fixtures.studio.creator1; + state.session = fixtures.session.user1; + expect(selectCanAddProjects(state)).toBe(true); + }); + test('can be added by curators', () => { + state.studio = fixtures.studio.isCurator; + state.session = fixtures.session.user1; + expect(selectCanAddProjects(state)).toBe(true); + }); + test('can be added by social users if studio is openToAll', () => { + state.studio = fixtures.studio.openToAll; + state.permissions = fixtures.permissions.isSocial; + state.session = fixtures.session.user1; + expect(selectCanAddProjects(state)).toBe(true); + }); + test('cannot be added by social users if not openToAll', () => { + state.permissions = fixtures.permissions.isSocial; + state.session = fixtures.session.user1; + expect(selectCanAddProjects(state)).toBe(false); + }); + }); +});