diff --git a/packages/app/obojobo-express/__tests__/express_validators.test.js b/packages/app/obojobo-express/__tests__/express_validators.test.js
index 1bed32b95a..23afbe2fc2 100644
--- a/packages/app/obojobo-express/__tests__/express_validators.test.js
+++ b/packages/app/obojobo-express/__tests__/express_validators.test.js
@@ -519,6 +519,28 @@ describe('current user middleware', () => {
})
})
+ // requireCanViewAdminPage
+
+ test('requireCanViewAdminPage calls next and has no validation errors', () => {
+ mockUser.hasPermission = perm => perm === 'canViewAdminPage'
+ mockReq.requireCurrentUser = jest.fn().mockResolvedValue(mockUser)
+ return Validators.requireCanViewAdminPage(mockReq, mockRes, mockNext).then(() => {
+ expect(mockNext).toHaveBeenCalledTimes(1)
+ expect(mockRes.notAuthorized).toHaveBeenCalledTimes(0)
+ expect(mockReq._validationErrors).toBeUndefined()
+ })
+ })
+
+ test('requireCanViewAdminPage doesnt call next and has errors', () => {
+ mockUser.hasPermission = () => false
+ mockReq.requireCurrentUser = jest.fn().mockResolvedValue(mockUser)
+ return Validators.requireCanViewAdminPage(mockReq, mockRes, mockNext).then(() => {
+ expect(mockNext).toHaveBeenCalledTimes(0)
+ expect(mockRes.notAuthorized).toHaveBeenCalledTimes(1)
+ expect(mockReq._validationErrors).toBeUndefined()
+ })
+ })
+
// checkValidationRules
test('checkValidationRules calls next with no errors', () => {
diff --git a/packages/app/obojobo-express/server/models/user.js b/packages/app/obojobo-express/server/models/user.js
index 204c950fd7..e2c420667a 100644
--- a/packages/app/obojobo-express/server/models/user.js
+++ b/packages/app/obojobo-express/server/models/user.js
@@ -102,22 +102,6 @@ class User {
})
}
- static getAll() {
- return db
- .manyOrNone(
- `SELECT
- *
- FROM users
- ORDER BY first_name, last_name
- LIMIT 25`
- )
- .then(results => results.map(r => User.dbResultToModel(r)))
- .catch(error => {
- logger.logError('Error getAll', error)
- throw Error('Error fetching all users.')
- })
- }
-
saveOrCreate() {
return db
.one(
diff --git a/packages/app/obojobo-repository/server/models/admin_interface.js b/packages/app/obojobo-repository/server/models/admin_interface.js
index 6ac4ca6275..ba1c7677df 100644
--- a/packages/app/obojobo-repository/server/models/admin_interface.js
+++ b/packages/app/obojobo-repository/server/models/admin_interface.js
@@ -3,72 +3,94 @@ const logger = require('obojobo-express/server/logger')
const User = require('obojobo-express/server/models/user')
+const { PERMS_PER_ROLE } = require('../../shared/util/implicit-perms')
+
class AdminInterface {
static addPermission(userId, permission) {
return new Promise((resolve, reject) => {
- // First fetch user
User.fetchById(userId)
- .then(u => {
- let perms = u.perms
- if (perms.includes(permission)) return
- perms = [...perms, permission]
-
- // Then add new permission (if it is new)
- this._updateUserPerms(userId, perms)
- .then(id => resolve(id))
+ .then(u => {
+ let perms = u.perms
+ if (perms.includes(permission)) return resolve(u)
+
+ perms = [...u.perms, permission]
+
+ const allRolePerms = new Set()
+
+ u.roles.forEach(role => {
+ PERMS_PER_ROLE[role].forEach(perm => allRolePerms.add(perm))
+ })
+
+ perms = perms.filter(p => !allRolePerms.has(p))
+
+ const dedupedPerms = new Set([...u.perms, ...perms])
+ u.perms = [...dedupedPerms]
+
+ // Then add new permission (if it is new)
+ this._updateUserPerms(userId, perms)
+ .then(() => resolve(u))
+ .catch(() => {
+ reject(logger.logError(`AdminInterface error adding permission`))
+ })
+ })
.catch(() => {
- reject()
- throw logger.logError(`AdminInterface error adding permission`)
+ reject(logger.logError(`AdminInterface error finding user with id ${userId}`))
})
- })
- .catch(err => {
- reject()
- throw logger.logError(`AdminInterface error finding user with id ${userId}`)
- })
})
}
static removePermission(userId, permission) {
return new Promise((resolve, reject) => {
- // First fetch user
User.fetchById(userId)
- .then(u => {
- const perms = u.perms
+ .then(u => {
+ const allRolePerms = new Set()
+
+ u.roles.forEach(role => {
+ PERMS_PER_ROLE[role].forEach(perm => allRolePerms.add(perm))
+ })
+
+ const perms = u.perms.filter(p => !allRolePerms.has(p))
+
+ const ix = perms.indexOf(permission)
+ if (ix === -1) return resolve(u)
+ perms.splice(ix, 1)
- const ix = perms.indexOf(permission)
- if (ix === -1) return
- perms.splice(ix, 1)
+ // add any remaining perms to the 'allRolePerms' set so we can use it to set the user's new combined perms
+ perms.forEach(p => allRolePerms.add(p))
- // Then remove permission (if present)
- this._updateUserPerms(userId, perms)
- .then(id => resolve(id))
+ u.perms = [...allRolePerms]
+
+ // Then remove permission (if present)
+ this._updateUserPerms(userId, perms)
+ .then(() => resolve(u))
+ .catch(() => {
+ reject(logger.logError(`AdminInterface error removing permission`))
+ })
+ })
.catch(() => {
- reject()
- throw logger.logError(`AdminInterface error removing permission`)
+ reject(logger.logError(`AdminInterface error finding user with id ${userId}`))
})
- })
- .catch(() => {
- reject()
- throw logger.logError(`AdminInterface Error finding user with id ${userId}`)
- })
})
}
static _updateUserPerms(userId, perms) {
return new Promise((resolve, reject) => {
- db.oneOrNone(`
- UPDATE user_perms
- SET perms = $[perms]
- WHERE user_id = $[userId]
+ db.oneOrNone(
+ `
+ INSERT INTO user_perms (user_id, perms)
+ VALUES ($[userId], $[perms])
+ ON CONFLICT (user_id)
+ DO UPDATE SET perms = $[perms]
+ WHERE user_perms.user_id = $[userId]
`,
- { userId, perms })
- .then(() => resolve(userId))
- .catch(error => {
- reject()
- throw logger.logError('AdminInterface _updateUserPerms Error', error)
- })
+ { userId, perms }
+ )
+ .then(() => resolve(userId))
+ .catch(error => {
+ reject(logger.logError('AdminInterface _updateUserPerms error', error))
+ })
})
}
}
-module.exports = AdminInterface
\ No newline at end of file
+module.exports = AdminInterface
diff --git a/packages/app/obojobo-repository/server/models/admin_interface.test.js b/packages/app/obojobo-repository/server/models/admin_interface.test.js
index d6b7ff67d8..01341aa48e 100644
--- a/packages/app/obojobo-repository/server/models/admin_interface.test.js
+++ b/packages/app/obojobo-repository/server/models/admin_interface.test.js
@@ -1,67 +1,183 @@
+jest.mock('obojobo-express/server/db')
+jest.mock('obojobo-express/server/logger')
+jest.mock('obojobo-express/server/models/user')
+jest.mock('../../shared/util/implicit-perms', () => ({
+ PERMS_PER_ROLE: {
+ mockRole: ['mockExistingPermission']
+ }
+}))
+
+const db = require('obojobo-express/server/db')
+const logger = require('obojobo-express/server/logger')
+
+const User = require('obojobo-express/server/models/user')
+
+const AdminInterface = require('./admin_interface')
+
describe('AdminInterface Model', () => {
- jest.mock('obojobo-express/server/db')
- jest.mock('obojobo-express/server/logger')
- let db
- let logger
- let AdminInterface
+ let expectedResponseUser
- beforeEach(() => {
+ beforeEach(() => {
jest.resetModules()
jest.resetAllMocks()
- db = require('obojobo-express/server/db')
- logger = require('obojobo-express/server/logger')
- AdminInterface = require('./admin_interface')
+ expectedResponseUser = {
+ perms: [],
+ roles: []
+ }
+
+ // provide this by defualt, override in individual tests if necessary
+ // if every test ends up overriding this, just remove this one
+ User.fetchById = jest.fn().mockResolvedValueOnce(expectedResponseUser)
+ })
+
+ test('addPermission does nothing if trying to add a permission the given user already has', () => {
+ expect.assertions(2)
+
+ // set the user's permissions such that they already have the one we're trying to give them
+ // ideally we could check implicit and explicit perms separately, but they're added to a single
+ // array inside the User model so our only option is to check the one location
+ expectedResponseUser.perms = ['someExistingPermission']
+ User.fetchById = jest.fn().mockResolvedValueOnce(expectedResponseUser)
+
+ return AdminInterface.addPermission(5, 'someExistingPermission').then(u => {
+ expect(u).toEqual(expectedResponseUser)
+ expect(db.oneOrNone).not.toHaveBeenCalled()
+ })
+ })
+
+ test('addPermission only saves explicitly granted permissions', () => {
+ expect.assertions(3)
+
+ db.oneOrNone.mockResolvedValueOnce(5)
+
+ // 'mockRole' will account for the 'mockExistingPermission' perm below
+ expectedResponseUser.roles = ['mockRole']
+ expectedResponseUser.perms = ['someExistingPermission', 'mockExistingPermission']
+ User.fetchById = jest.fn().mockResolvedValueOnce(expectedResponseUser)
+
+ return AdminInterface.addPermission(5, 'someNewPermission').then(u => {
+ expect(u).toEqual({
+ ...expectedResponseUser,
+ perms: ['someExistingPermission', 'mockExistingPermission', 'someNewPermission']
+ })
+ expect(db.oneOrNone).toHaveBeenCalledTimes(1)
+ // first argument to db function is the query string, no need to check that
+ expect(db.oneOrNone.mock.calls[0][1]).toEqual({
+ userId: 5,
+ // since 'mockExistingPermission' is a perm-based/implicit perm,
+ // it should not have been saved explicitly
+ perms: ['someExistingPermission', 'someNewPermission']
+ })
+ })
+ })
+
+ test('addPermission catches error when fetching user with invalid id', () => {
+ User.fetchById = jest.fn().mockRejectedValueOnce('mock-error')
+
+ expect.hasAssertions()
+
+ return AdminInterface.addPermission(123456, 'someNewPermission').catch(() => {
+ expect(logger.logError).toHaveBeenCalledWith(
+ 'AdminInterface error finding user with id 123456'
+ )
+ })
+ })
+
+ test('addPermission catches error when updating user perms', () => {
+ expect.hasAssertions()
+
+ db.oneOrNone.mockRejectedValueOnce('mock-error')
+
+ return AdminInterface.addPermission(5, 'someNewPermission').catch(() => {
+ expect(logger.logError).toHaveBeenCalledTimes(2)
+ expect(logger.logError).toHaveBeenCalledWith(
+ 'AdminInterface _updateUserPerms error',
+ 'mock-error'
+ )
+ expect(logger.logError).toHaveBeenCalledWith('AdminInterface error adding permission')
+ })
+ })
+
+ test('removePermission does nothing if trying to remove a permission the given user does not have', () => {
+ expect.assertions(2)
+
+ // set the user's permissions such that they already have the one we're trying to give them
+ // ideally we could check implicit and explicit perms separately, but they're added to a single
+ // array inside the User model so our only option is to check the one location
+ expectedResponseUser.perms = ['someOtherPermission']
+ User.fetchById = jest.fn().mockResolvedValueOnce(expectedResponseUser)
+
+ return AdminInterface.removePermission(5, 'someExistingPermission').then(u => {
+ expect(u).toEqual(expectedResponseUser)
+ expect(db.oneOrNone).not.toHaveBeenCalled()
+ })
+ })
+
+ test('removePermission does nothing if trying to remove a permission the given user has implicitly', () => {
+ expect.assertions(2)
+
+ expectedResponseUser.roles = ['mockRole']
+ expectedResponseUser.perms = ['someExistingPermission', 'mockExistingPermission']
+ User.fetchById = jest.fn().mockResolvedValueOnce(expectedResponseUser)
+
+ return AdminInterface.removePermission(5, 'mockExistingPermission').then(u => {
+ expect(u).toEqual(expectedResponseUser)
+ expect(db.oneOrNone).not.toHaveBeenCalled()
+ })
})
- afterEach(() => {})
-
- test('addPermission correctly fetches id and adds new permission (if any)', () => {
- db.one.mockResolvedValueOnce({
- id: 5,
- created_at: 'mocked-date',
- email: 'guest@obojobo.ucf.edu',
- first_name: 'Guest',
- last_name: 'Guest',
- roles: [],
- username: 'guest'
+
+ test('removePermission saves explicit permissions after removing one from the given user', () => {
+ db.oneOrNone.mockResolvedValueOnce(5)
+
+ // 'mockRole' will account for the 'mockExistingPermission' perm below
+ expectedResponseUser.roles = ['mockRole']
+ expectedResponseUser.perms = [
+ 'someExistingPermission',
+ 'someOtherExistingPermission',
+ 'mockExistingPermission'
+ ]
+ User.fetchById = jest.fn().mockResolvedValueOnce(expectedResponseUser)
+
+ return AdminInterface.removePermission(5, 'someOtherExistingPermission').then(u => {
+ expect(u).toEqual({
+ ...expectedResponseUser,
+ perms: ['mockExistingPermission', 'someExistingPermission']
+ })
+ expect(db.oneOrNone).toHaveBeenCalledTimes(1)
+ expect(db.oneOrNone.mock.calls[0][1]).toEqual({
+ userId: 5,
+ perms: ['someExistingPermission']
+ })
})
+ })
+
+ test('removePermission catches error when fetching user with invalid id', () => {
+ User.fetchById = jest.fn().mockRejectedValueOnce('mock-error')
+
+ expect.hasAssertions()
- db.oneOrNone.mockResolvedValueOnce({})
-
- return AdminInterface.addPermission(5, 'canViewAdminPage')
- .then(ret => expect(ret).toBe(5))
- })
-
- test('addPermission catches error when fetching user with invalid id', () => {
- logger.logError = jest.fn()
-
- db.one.mockRejectedValueOnce('mock-error')
-
- return AdminInterface.addPermission(123456, 'canViewAdminPage')
- .then(ret => {
- console.log("ret:")
- console.log(ret)
- })
- .catch(() => {
- console.log("here")
- expect(logger.logError).toHaveBeenCalledWith('AdminInterface error finding user with id 123456')
- })
- })
-
- test('removePermission correctly fetches id and removes permission', () => {
- db.one.mockResolvedValueOnce({
- id: 5,
- created_at: 'mocked-date',
- email: 'guest@obojobo.ucf.edu',
- first_name: 'Guest',
- last_name: 'Guest',
- perms: ['canViewAdminPage'],
- username: 'guest'
+ return AdminInterface.removePermission(123456, 'someExistingPermission').catch(() => {
+ expect(logger.logError).toHaveBeenCalledWith(
+ 'AdminInterface error finding user with id 123456'
+ )
})
+ })
+
+ test('removePermission catches error when updating user perms', () => {
+ expect.hasAssertions()
+
+ db.oneOrNone.mockRejectedValueOnce('mock-error')
- db.oneOrNone.mockResolvedValueOnce({})
-
- return AdminInterface.removePermission(5, 'canViewAdminPage')
- .then(ret => expect(ret).toBe(5))
- })
-})
\ No newline at end of file
+ expectedResponseUser.perms = ['someExistingPermission']
+
+ return AdminInterface.removePermission(5, 'someExistingPermission').catch(() => {
+ expect(logger.logError).toHaveBeenCalledTimes(2)
+ expect(logger.logError).toHaveBeenCalledWith(
+ 'AdminInterface _updateUserPerms error',
+ 'mock-error'
+ )
+ expect(logger.logError).toHaveBeenCalledWith('AdminInterface error removing permission')
+ })
+ })
+})
diff --git a/packages/app/obojobo-repository/server/routes/api.js b/packages/app/obojobo-repository/server/routes/api.js
index b16b87438c..b912aa8688 100644
--- a/packages/app/obojobo-repository/server/routes/api.js
+++ b/packages/app/obojobo-repository/server/routes/api.js
@@ -16,7 +16,7 @@ const {
requireCanDeleteDrafts,
check,
requireCanViewStatsPage,
- requireCanViewAdminPage,
+ requireCanViewAdminPage
} = require('obojobo-express/server/express_validators')
const UserModel = require('obojobo-express/server/models/user')
const { searchForUserByString } = require('../services/search')
@@ -172,47 +172,34 @@ router
})
router
- .route('/users/all')
- .get([requireCanViewAdminPage])
- .get(async (req, res) => {
+ .route('/permissions/add')
+ .post([requireCanViewAdminPage])
+ .post(async (req, res) => {
+ const userId = req.body.userId
+ const perm = req.body.perm
+
try {
- let users = await UserModel.getAll()
- users = users.map(u => u.toJSON())
- res.success(users)
+ const modifiedUser = await AdminInterface.addPermission(userId, perm)
+ res.success(modifiedUser)
} catch (error) {
res.unexpected(error)
}
})
router
-.route('/permissions/add')
-.post([requireCanViewAdminPage])
-.post(async (req, res) => {
- const userId = req.body.userId
- const perm = req.body.perm
-
- try {
- await AdminInterface.addPermission(userId, perm)
- res.success()
- } catch (error) {
- res.unexpected(error)
- }
-})
+ .route('/permissions/remove')
+ .post([requireCanViewAdminPage])
+ .post(async (req, res) => {
+ const userId = req.body.userId
+ const perm = req.body.perm
-router
-.route('/permissions/remove')
-.post([requireCanViewAdminPage])
-.post(async (req, res) => {
- const userId = req.body.userId
- const perm = req.body.perm
-
- try {
- await AdminInterface.removePermission(userId, perm)
- res.success()
- } catch (error) {
- res.unexpected(error)
- }
-})
+ try {
+ const modifiedUser = await AdminInterface.removePermission(userId, perm)
+ res.success(modifiedUser)
+ } catch (error) {
+ res.unexpected(error)
+ }
+ })
// Copy a draft to the current user
// mounted as /api/drafts/:draftId/copy
@@ -541,5 +528,4 @@ router
}
})
-
module.exports = router
diff --git a/packages/app/obojobo-repository/server/routes/api.test.js b/packages/app/obojobo-repository/server/routes/api.test.js
index db541d935e..538d94debd 100644
--- a/packages/app/obojobo-repository/server/routes/api.test.js
+++ b/packages/app/obojobo-repository/server/routes/api.test.js
@@ -10,6 +10,7 @@ jest.mock('../services/collections')
jest.mock('../services/count')
jest.mock('obojobo-express/server/models/user')
jest.mock('obojobo-express/server/insert_event')
+jest.mock('../models/admin_interface')
jest.unmock('fs') // need fs working for view rendering
jest.unmock('express') // we'll use supertest + express for this
@@ -24,6 +25,7 @@ let CountServices
let UserModel
let insertEvent
let DraftPermissions
+let AdminInterface
// override requireCurrentUser for tests to provide our own user
let mockCurrentUser
@@ -98,6 +100,7 @@ describe('repository api route', () => {
CountServices = require('../services/count')
DraftPermissions = require('../models/draft_permissions')
UserModel = require('obojobo-express/server/models/user')
+ AdminInterface = require('../models/admin_interface')
insertEvent = require('obojobo-express/server/insert_event')
})
@@ -494,6 +497,64 @@ describe('repository api route', () => {
})
})
+ test('post /permissions/add returns the expected response', () => {
+ expect.hasAssertions()
+
+ // this should be a user object but for testing purposes it doesn't matter
+ AdminInterface.addPermission.mockResolvedValueOnce(true)
+
+ return request(app)
+ .post('/permissions/add')
+ .send({ userId: 5, perm: 'someNewPerm' })
+ .then(response => {
+ expect(AdminInterface.addPermission).toHaveBeenCalledTimes(1)
+ expect(AdminInterface.addPermission).toHaveBeenCalledWith(5, 'someNewPerm')
+ expect(response.statusCode).toBe(200)
+ })
+ })
+ test('post /permissions/add handles thrown errors', () => {
+ expect.hasAssertions()
+
+ AdminInterface.addPermission.mockRejectedValueOnce('database error')
+
+ return request(app)
+ .post('/permissions/add')
+ .send({ userId: 5, perm: 'someNewPerm' })
+ .then(response => {
+ expect(AdminInterface.addPermission).toHaveBeenCalledTimes(1)
+ expect(AdminInterface.addPermission).toHaveBeenCalledWith(5, 'someNewPerm')
+ expect(response.statusCode).toBe(500)
+ })
+ })
+ test('post /permissions/remove returns the expected response', () => {
+ expect.hasAssertions()
+
+ AdminInterface.removePermission.mockResolvedValueOnce(true)
+
+ return request(app)
+ .post('/permissions/remove')
+ .send({ userId: 5, perm: 'someExistingPerm' })
+ .then(response => {
+ expect(AdminInterface.removePermission).toHaveBeenCalledTimes(1)
+ expect(AdminInterface.removePermission).toHaveBeenCalledWith(5, 'someExistingPerm')
+ expect(response.statusCode).toBe(200)
+ })
+ })
+ test('post /permissions/remove handles thrown errors', () => {
+ expect.hasAssertions()
+
+ AdminInterface.removePermission.mockRejectedValueOnce('database error')
+
+ return request(app)
+ .post('/permissions/remove')
+ .send({ userId: 5, perm: 'someExistingPerm' })
+ .then(response => {
+ expect(AdminInterface.removePermission).toHaveBeenCalledTimes(1)
+ expect(AdminInterface.removePermission).toHaveBeenCalledWith(5, 'someExistingPerm')
+ expect(response.statusCode).toBe(500)
+ })
+ })
+
test('post /drafts/:draftId/copy returns the expected response when user can copy draft', () => {
expect.hasAssertions()
diff --git a/packages/app/obojobo-repository/server/services/search.js b/packages/app/obojobo-repository/server/services/search.js
index 19dd0c5080..1ba978780a 100644
--- a/packages/app/obojobo-repository/server/services/search.js
+++ b/packages/app/obojobo-repository/server/services/search.js
@@ -6,18 +6,21 @@ const searchForUserByString = async searchString => {
return db
.manyOrNone(
`SELECT
- id,
- first_name AS "firstName",
- last_name AS "lastName",
- email,
- username,
- created_at AS "createdAt",
- roles
+ users.id,
+ users.first_name AS "firstName",
+ users.last_name AS "lastName",
+ users.email,
+ users.username,
+ users.created_at AS "createdAt",
+ users.roles,
+ user_perms.perms
FROM users
- WHERE obo_immutable_concat_ws(' ', first_name, last_name) ILIKE $[search]
- OR email ILIKE $[search]
- OR username ILIKE $[search]
- ORDER BY first_name, last_name
+ LEFT JOIN user_perms
+ ON users.id = user_perms.user_id
+ WHERE obo_immutable_concat_ws(' ', users.first_name, users.last_name) ILIKE $[search]
+ OR users.email ILIKE $[search]
+ OR users.username ILIKE $[search]
+ ORDER BY users.first_name, users.last_name
LIMIT 25`,
{ search: `%${searchString}%` }
)
diff --git a/packages/app/obojobo-repository/server/services/search.test.js b/packages/app/obojobo-repository/server/services/search.test.js
index 789f406285..61c748db44 100644
--- a/packages/app/obojobo-repository/server/services/search.test.js
+++ b/packages/app/obojobo-repository/server/services/search.test.js
@@ -36,18 +36,21 @@ describe('Search Services', () => {
expect.hasAssertions()
const manyOrNoneQueryString = `SELECT
- id,
- first_name AS "firstName",
- last_name AS "lastName",
- email,
- username,
- created_at AS "createdAt",
- roles
+ users.id,
+ users.first_name AS "firstName",
+ users.last_name AS "lastName",
+ users.email,
+ users.username,
+ users.created_at AS "createdAt",
+ users.roles,
+ user_perms.perms
FROM users
- WHERE obo_immutable_concat_ws(' ', first_name, last_name) ILIKE $[search]
- OR email ILIKE $[search]
- OR username ILIKE $[search]
- ORDER BY first_name, last_name
+ LEFT JOIN user_perms
+ ON users.id = user_perms.user_id
+ WHERE obo_immutable_concat_ws(' ', users.first_name, users.last_name) ILIKE $[search]
+ OR users.email ILIKE $[search]
+ OR users.username ILIKE $[search]
+ ORDER BY users.first_name, users.last_name
LIMIT 25`
return SearchServices.searchForUserByString('searchString').then(response => {
diff --git a/packages/app/obojobo-repository/shared/actions/admin-actions.js b/packages/app/obojobo-repository/shared/actions/admin-actions.js
index 393aea53c9..ab594a955b 100644
--- a/packages/app/obojobo-repository/shared/actions/admin-actions.js
+++ b/packages/app/obojobo-repository/shared/actions/admin-actions.js
@@ -1,31 +1,44 @@
-// =================== API =======================
+const debouncePromise = require('debounce-promise')
-const apiGetModules = () => {
- const JSON_MIME_TYPE = 'application/json'
- const fetchOptions = {
- method: 'GET',
- credentials: 'include',
- headers: {
- Accept: JSON_MIME_TYPE,
- 'Content-Type': JSON_MIME_TYPE
- }
+const JSON_MIME_TYPE = 'application/json'
+const defaultOptions = () => ({
+ method: 'GET',
+ credentials: 'include',
+ headers: {
+ Accept: JSON_MIME_TYPE,
+ 'Content-Type': JSON_MIME_TYPE
}
- return fetch('/api/drafts', fetchOptions).then(res => res.json())
+})
+
+const throwIfNotOk = res => {
+ if (!res.ok) throw Error(`Error requesting ${res.url}, status code: ${res.status}`)
+ return res
}
-const apiGetUsers = () => {
- const JSON_MIME_TYPE = 'application/json'
- const fetchOptions = {
- method: 'GET',
- credentials: 'include',
- headers: {
- Accept: JSON_MIME_TYPE,
- 'Content-Type': JSON_MIME_TYPE
- }
- }
- return fetch('/api/users/all', fetchOptions).then(res => res.json())
+const apiSearchForUser = searchString => {
+ return fetch(`/api/users/search?q=${searchString}`, defaultOptions())
+ .then(throwIfNotOk)
+ .then(res => res.json())
}
+const apiSearchForUserDebounced = debouncePromise(apiSearchForUser, 300)
+
+// =================== API =======================
+
+// not using this yet
+// const apiGetModules = () => {
+// const JSON_MIME_TYPE = 'application/json'
+// const fetchOptions = {
+// method: 'GET',
+// credentials: 'include',
+// headers: {
+// Accept: JSON_MIME_TYPE,
+// 'Content-Type': JSON_MIME_TYPE
+// }
+// }
+// return fetch('/api/drafts', fetchOptions).then(res => res.json())
+// }
+
const apiAddUserPermission = (userId, perm) => {
const JSON_MIME_TYPE = 'application/json'
const fetchOptions = {
@@ -58,16 +71,17 @@ const apiRemoveUserPermission = (userId, perm) => {
// ================== ACTIONS ===================
-const LOAD_ALL_MODULES = 'LOAD_ALL_MODULES'
-const loadModuleList = () => ({
- type: LOAD_ALL_MODULES,
- promise: apiGetModules()
-})
+// const LOAD_ALL_MODULES = 'LOAD_ALL_MODULES'
+// const loadModuleList = () => ({
+// type: LOAD_ALL_MODULES,
+// promise: apiGetModules()
+// })
-const LOAD_ALL_USERS = 'LOAD_ALL_USERS'
-const loadUserList = () => ({
- type: LOAD_ALL_USERS,
- promise: apiGetUsers()
+const LOAD_USER_SEARCH = 'LOAD_USER_SEARCH'
+const searchForUser = searchString => ({
+ type: LOAD_USER_SEARCH,
+ meta: { searchString },
+ promise: apiSearchForUserDebounced(searchString)
})
const ADD_USER_PERMISSION = 'ADD_USER_PERMISSION'
@@ -82,16 +96,22 @@ const removeUserPermission = (userId, perm) => ({
promise: apiRemoveUserPermission(userId, perm)
})
-module.exports = {
- LOAD_ALL_MODULES,
- loadModuleList,
+const CLEAR_PEOPLE_SEARCH_RESULTS = 'CLEAR_PEOPLE_SEARCH_RESULTS'
+const clearPeopleSearchResults = () => ({ type: CLEAR_PEOPLE_SEARCH_RESULTS })
- LOAD_ALL_USERS,
- loadUserList,
+module.exports = {
+ // LOAD_ALL_MODULES,
+ // loadModuleList,
ADD_USER_PERMISSION,
addUserPermission,
REMOVE_USER_PERMISSION,
removeUserPermission,
-}
\ No newline at end of file
+
+ LOAD_USER_SEARCH,
+ searchForUser,
+
+ CLEAR_PEOPLE_SEARCH_RESULTS,
+ clearPeopleSearchResults
+}
diff --git a/packages/app/obojobo-repository/shared/actions/admin-actions.test.js b/packages/app/obojobo-repository/shared/actions/admin-actions.test.js
index e69de29bb2..07b0fc4491 100644
--- a/packages/app/obojobo-repository/shared/actions/admin-actions.test.js
+++ b/packages/app/obojobo-repository/shared/actions/admin-actions.test.js
@@ -0,0 +1,163 @@
+const dayjs = require('dayjs')
+const advancedFormat = require('dayjs/plugin/advancedFormat')
+
+jest.mock('./shared-api-methods', () => ({
+ apiGetAssessmentDetailsForDraft: () => Promise.resolve()
+}))
+
+dayjs.extend(advancedFormat)
+
+describe('Admin Actions', () => {
+ let standardFetchResponse
+
+ let AdminActions
+
+ // this is lifted straight out of admin-actions, for ease of comparison
+ // barring any better ways of using it
+ const defaultFetchOptions = {
+ method: 'GET',
+ credentials: 'include',
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json'
+ }
+ }
+
+ const originalFetch = global.fetch
+
+ beforeAll(() => {
+ global.fetch = jest.fn()
+ })
+
+ beforeEach(() => {
+ jest.resetModules()
+ jest.resetAllMocks()
+ jest.useFakeTimers()
+
+ global.alert = jest.fn()
+
+ delete window.location
+ window.location = {
+ reload: jest.fn()
+ }
+
+ standardFetchResponse = {
+ ok: true,
+ json: jest.fn(() => ({ value: 'mockVal' }))
+ }
+
+ AdminActions = require('./admin-actions')
+ })
+
+ afterAll(() => {
+ global.fetch = originalFetch
+ })
+
+ test('searchForUser returns the expected output and calls other functions correctly - server ok', () => {
+ global.fetch.mockResolvedValueOnce({ ...standardFetchResponse, ok: true })
+
+ const actionReply = AdminActions.searchForUser('mockSearchString')
+ jest.runAllTimers()
+
+ expect(global.fetch).toHaveBeenCalledWith(
+ '/api/users/search?q=mockSearchString',
+ defaultFetchOptions
+ )
+
+ expect(actionReply).toEqual({
+ type: AdminActions.LOAD_USER_SEARCH,
+ meta: {
+ searchString: 'mockSearchString'
+ },
+ promise: expect.any(Object)
+ })
+
+ return actionReply.promise.then(() => {
+ expect(standardFetchResponse.json).toHaveBeenCalled()
+ })
+ })
+ test('searchForUser returns the expected output and calls other functions correctly - server error', () => {
+ const mockFetchUrl = '/api/users/search?q=mockSearchString'
+ global.fetch.mockResolvedValueOnce({
+ ok: false,
+ url: mockFetchUrl,
+ status: 500
+ })
+
+ const actionReply = AdminActions.searchForUser('mockSearchString')
+
+ expect(actionReply).toEqual({
+ type: AdminActions.LOAD_USER_SEARCH,
+ meta: {
+ searchString: 'mockSearchString'
+ },
+ promise: expect.any(Object)
+ })
+
+ jest.runAllTimers()
+ return actionReply.promise.catch(error => {
+ expect(error).toBeInstanceOf(Error)
+ expect(error.message).toBe(
+ 'Error requesting /api/users/search?q=mockSearchString, status code: 500'
+ )
+
+ expect(global.fetch).toHaveBeenCalledWith(
+ '/api/users/search?q=mockSearchString',
+ defaultFetchOptions
+ )
+ })
+ })
+
+ test('addUserPermission returns the expected output and calls other functions', () => {
+ global.fetch.mockResolvedValueOnce({ ...standardFetchResponse, ok: true })
+
+ const actionReply = AdminActions.addUserPermission(5, 'someNewPermission')
+ jest.runAllTimers()
+
+ expect(global.fetch).toHaveBeenCalledWith('/api/permissions/add', {
+ ...defaultFetchOptions,
+ method: 'POST',
+ body: JSON.stringify({ userId: 5, perm: 'someNewPermission' })
+ })
+
+ expect(actionReply).toEqual({
+ type: AdminActions.ADD_USER_PERMISSION,
+ promise: expect.any(Object)
+ })
+
+ return actionReply.promise.then(() => {
+ expect(standardFetchResponse.json).toHaveBeenCalled()
+ })
+ })
+
+ test('removeUserPermission returns the expected output and calls other functions', () => {
+ global.fetch.mockResolvedValueOnce({ ...standardFetchResponse, ok: true })
+
+ const actionReply = AdminActions.removeUserPermission(5, 'someExistingPermission')
+ jest.runAllTimers()
+
+ expect(global.fetch).toHaveBeenCalledWith('/api/permissions/remove', {
+ ...defaultFetchOptions,
+ method: 'POST',
+ body: JSON.stringify({ userId: 5, perm: 'someExistingPermission' })
+ })
+
+ expect(actionReply).toEqual({
+ type: AdminActions.REMOVE_USER_PERMISSION,
+ promise: expect.any(Object)
+ })
+
+ return actionReply.promise.then(() => {
+ expect(standardFetchResponse.json).toHaveBeenCalled()
+ })
+ })
+
+ test('clearPeopleSearchResults returns the expected output', () => {
+ const actionReply = AdminActions.clearPeopleSearchResults()
+
+ expect(global.fetch).not.toHaveBeenCalled()
+ expect(actionReply).toEqual({
+ type: AdminActions.CLEAR_PEOPLE_SEARCH_RESULTS
+ })
+ })
+})
diff --git a/packages/app/obojobo-repository/shared/components/__snapshots__/admin.test.js.snap b/packages/app/obojobo-repository/shared/components/__snapshots__/admin.test.js.snap
index a08ef7b7ea..485364a0c2 100644
--- a/packages/app/obojobo-repository/shared/components/__snapshots__/admin.test.js.snap
+++ b/packages/app/obojobo-repository/shared/components/__snapshots__/admin.test.js.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`Admin Renders "tools loaded" state correctly 1`] = `
+exports[`Admin renders default view correctly 1`] = `
@@ -100,9 +100,23 @@ exports[`Admin Renders "tools loaded" state correctly 1`] = `
- Loading...
-
+ Find Users to Manage
+
+
Managing:
+Manage User Permissions
+Select permission:
+ {renderPermissionSelectList()} +Find Users to Manage
+Manage User Permissions
-Select user:
- {renderUserSelectList()} -Select permission:
- {renderPermissionSelectList()} -Loading...
: getTools()} -