diff --git a/scm-ui-components/packages/ui-types/src/Role.js b/scm-ui-components/packages/ui-types/src/Role.js new file mode 100644 index 0000000000..385b498d38 --- /dev/null +++ b/scm-ui-components/packages/ui-types/src/Role.js @@ -0,0 +1,9 @@ +//@flow + +export type Role = { + name: string, + creationDate: number | null, + lastModified: number | null, + type: string, + verb: string[] +}; diff --git a/scm-ui-components/packages/ui-types/src/index.js b/scm-ui-components/packages/ui-types/src/index.js index c57a3c6792..59c423c5d5 100644 --- a/scm-ui-components/packages/ui-types/src/index.js +++ b/scm-ui-components/packages/ui-types/src/index.js @@ -16,6 +16,7 @@ export type { Changeset } from "./Changesets"; export type { Tag } from "./Tags"; export type { Config } from "./Config"; +export type { Role } from "./Role"; export type { IndexResources } from "./IndexResources"; diff --git a/scm-ui/src/config/components/table/RoleTable.js b/scm-ui/src/config/components/table/RoleTable.js new file mode 100644 index 0000000000..ed0c298b0e --- /dev/null +++ b/scm-ui/src/config/components/table/RoleTable.js @@ -0,0 +1,34 @@ +// @flow +import React from "react"; +import { translate } from "react-i18next"; +import type { Role } from "@scm-manager/ui-types"; + +type Props = { + t: string => string, + roles: Role[] +}; + +class RoleTable extends React.Component { + render() { + const { roles, t } = this.props; + return ( + + + + + + + + + + + {roles.map((role, index) => { + return

role

; + })} + +
{t("user.name")}{t("user.displayName")}{t("user.mail")}{t("user.active")}
+ ); + } +} + +export default translate("config")(RoleTable); diff --git a/scm-ui/src/config/containers/Config.js b/scm-ui/src/config/containers/Config.js index 04de525c95..ee6a6d4f59 100644 --- a/scm-ui/src/config/containers/Config.js +++ b/scm-ui/src/config/containers/Config.js @@ -7,6 +7,7 @@ import { ExtensionPoint } from "@scm-manager/ui-extensions"; import type { Links } from "@scm-manager/ui-types"; import { Page, Navigation, NavLink, Section } from "@scm-manager/ui-components"; import GlobalConfig from "./GlobalConfig"; +import GlobalPermissionRoles from "./GlobalPermissionRoles"; import type { History } from "history"; import { connect } from "react-redux"; import { compose } from "redux"; @@ -47,6 +48,11 @@ class Config extends React.Component {
+ { to={`${url}`} label={t("config.globalConfigurationNavLink")} /> + string, + history: History, + location: any, + + // dispatch functions + fetchRolesByPage: (link: string, page: number, filter?: string) => void +}; + +class GlobalPermissionRoles extends React.Component { + componentDidMount() { + const { fetchRolesByPage, rolesLink, page, location } = this.props; + fetchRolesByPage( + rolesLink, + page, + urls.getQueryStringFromLocation(location) + ); + } + + render() { + const { t, loading } = this.props; + + if (loading) { + return ; + } + + return ( +
+ + {this.renderPermissionsTable()} + {this.renderCreateButton()} + </div> + ); + } + + renderPermissionsTable() { + const { roles, list, page, location, t } = this.props; + if (roles && roles.length > 0) { + return ( + <> + <RoleTable roles={roles} /> + <LinkPaginator + collection={list} + page={page} + filter={urls.getQueryStringFromLocation(location)} + /> + </> + ); + } + return <Notification type="info">{t("config.roles.noPermissionRoles")}</Notification>; + } + + renderCreateButton() { + const { canAddRoles, t } = this.props; + if (canAddRoles) { + return ( + <CreateButton label={t("config.permissions.createButton")} link="/create" /> + ); + } + return null; + } +} + +const mapStateToProps = (state, ownProps) => { + const { match } = ownProps; + const roles = getRolesFromState(state); + const loading = isFetchRolesPending(state); + const error = getFetchRolesFailure(state); + const page = urls.getPageFromMatch(match); + const canAddRoles = isPermittedToCreateRoles(state); + const list = selectListAsCollection(state); + const rolesLink = getRolesLink(state); + + return { + roles, + loading, + error, + canAddRoles, + list, + page, + rolesLink + }; +}; + +const mapDispatchToProps = dispatch => { + return { + fetchRolesByPage: (link: string, page: number, filter?: string) => { + dispatch(fetchRolesByPage(link, page, filter)); + } + }; +}; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(translate("config")(GlobalPermissionRoles)); diff --git a/scm-ui/src/config/modules/roles.js b/scm-ui/src/config/modules/roles.js new file mode 100644 index 0000000000..823fbb7aa7 --- /dev/null +++ b/scm-ui/src/config/modules/roles.js @@ -0,0 +1,477 @@ +// @flow +import { apiClient } from "@scm-manager/ui-components"; +import { isPending } from "../../modules/pending"; +import { getFailure } from "../../modules/failure"; +import * as types from "../../modules/types"; +import { combineReducers, Dispatch } from "redux"; +import type {Action, PagedCollection, Role} from "@scm-manager/ui-types"; + +export const FETCH_ROLES = "scm/roles/FETCH_ROLES"; +export const FETCH_ROLES_PENDING = `${FETCH_ROLES}_${types.PENDING_SUFFIX}`; +export const FETCH_ROLES_SUCCESS = `${FETCH_ROLES}_${types.SUCCESS_SUFFIX}`; +export const FETCH_ROLES_FAILURE = `${FETCH_ROLES}_${types.FAILURE_SUFFIX}`; + +export const FETCH_ROLE = "scm/roles/FETCH_ROLE"; +export const FETCH_ROLE_PENDING = `${FETCH_ROLE}_${types.PENDING_SUFFIX}`; +export const FETCH_ROLE_SUCCESS = `${FETCH_ROLE}_${types.SUCCESS_SUFFIX}`; +export const FETCH_ROLE_FAILURE = `${FETCH_ROLE}_${types.FAILURE_SUFFIX}`; + +export const CREATE_ROLE = "scm/roles/CREATE_ROLE"; +export const CREATE_ROLE_PENDING = `${CREATE_ROLE}_${types.PENDING_SUFFIX}`; +export const CREATE_ROLE_SUCCESS = `${CREATE_ROLE}_${types.SUCCESS_SUFFIX}`; +export const CREATE_ROLE_FAILURE = `${CREATE_ROLE}_${types.FAILURE_SUFFIX}`; +export const CREATE_ROLE_RESET = `${CREATE_ROLE}_${types.RESET_SUFFIX}`; + +export const MODIFY_ROLE = "scm/roles/MODIFY_ROLE"; +export const MODIFY_ROLE_PENDING = `${MODIFY_ROLE}_${types.PENDING_SUFFIX}`; +export const MODIFY_ROLE_SUCCESS = `${MODIFY_ROLE}_${types.SUCCESS_SUFFIX}`; +export const MODIFY_ROLE_FAILURE = `${MODIFY_ROLE}_${types.FAILURE_SUFFIX}`; +export const MODIFY_ROLE_RESET = `${MODIFY_ROLE}_${types.RESET_SUFFIX}`; + +export const DELETE_ROLE = "scm/roles/DELETE_ROLE"; +export const DELETE_ROLE_PENDING = `${DELETE_ROLE}_${types.PENDING_SUFFIX}`; +export const DELETE_ROLE_SUCCESS = `${DELETE_ROLE}_${types.SUCCESS_SUFFIX}`; +export const DELETE_ROLE_FAILURE = `${DELETE_ROLE}_${types.FAILURE_SUFFIX}`; + +const CONTENT_TYPE_ROLE = "application/vnd.scmm-role+json;v=2"; + +// fetch roles +export function fetchRolesPending(): Action { + return { + type: FETCH_ROLES_PENDING + }; +} + +export function fetchRolesSuccess(roles: any): Action { + return { + type: FETCH_ROLES_SUCCESS, + payload: roles + }; +} + +export function fetchRolesFailure(url: string, error: Error): Action { + return { + type: FETCH_ROLES_FAILURE, + payload: { + error, + url + } + }; +} + +export function fetchRolesByLink(link: string) { + return function(dispatch: any) { + dispatch(fetchRolesPending()); + return apiClient + .get(link) + .then(response => response.json()) + .then(data => { + dispatch(fetchRolesSuccess(data)); + }) + .catch(error => { + dispatch(fetchRolesFailure(link, error)); + }); + }; +} + +export function fetchRoles(link: string) { + return fetchRolesByLink(link); +} + +export function fetchRolesByPage(link: string, page: number, filter?: string) { + // backend start counting by 0 + if (filter) { + return fetchRolesByLink( + `${link}?page=${page - 1}&q=${decodeURIComponent(filter)}` + ); + } + return fetchRolesByLink(`${link}?page=${page - 1}`); +} + +// fetch role +export function fetchRolePending(name: string): Action { + return { + type: FETCH_ROLE_PENDING, + payload: name, + itemId: name + }; +} + +export function fetchRoleSuccess(role: any): Action { + return { + type: FETCH_ROLE_SUCCESS, + payload: role, + itemId: role.name + }; +} + +export function fetchRoleFailure(name: string, error: Error): Action { + return { + type: FETCH_ROLE_FAILURE, + payload: { + name, + error + }, + itemId: name + }; +} + +function fetchRole(link: string, name: string) { + return function(dispatch: any) { + dispatch(fetchRolePending(name)); + return apiClient + .get(link) + .then(response => { + return response.json(); + }) + .then(data => { + dispatch(fetchRoleSuccess(data)); + }) + .catch(error => { + dispatch(fetchRoleFailure(name, error)); + }); + }; +} + +export function fetchRoleByName(link: string, name: string) { + const roleUrl = link.endsWith("/") ? link + name : link + "/" + name; + return fetchRole(roleUrl, name); +} + +export function fetchRoleByLink(role: Role) { + return fetchRole(role._links.self.href, role.name); +} + +// create role +export function createRolePending(role: Role): Action { + return { + type: CREATE_ROLE_PENDING, + role + }; +} + +export function createRoleSuccess(): Action { + return { + type: CREATE_ROLE_SUCCESS + }; +} + +export function createRoleFailure(error: Error): Action { + return { + type: CREATE_ROLE_FAILURE, + payload: error + }; +} + +export function createRoleReset() { + return { + type: CREATE_ROLE_RESET + }; +} + +export function createRole(link: string, role: Role, callback?: () => void) { + return function(dispatch: Dispatch) { + dispatch(createRolePending(role)); + return apiClient + .post(link, role, CONTENT_TYPE_ROLE) + .then(() => { + dispatch(createRoleSuccess()); + if (callback) { + callback(); + } + }) + .catch(error => dispatch(createRoleFailure(error))); + }; +} + +// modify group +export function modifyRolePending(role: Role): Action { + return { + type: MODIFY_ROLE_PENDING, + payload: role, + itemId: role.name + }; +} + +export function modifyRoleSuccess(role: Role): Action { + return { + type: MODIFY_ROLE_SUCCESS, + payload: role, + itemId: role.name + }; +} + +export function modifyRoleFailure(role: Role, error: Error): Action { + return { + type: MODIFY_ROLE_FAILURE, + payload: { + error, + role + }, + itemId: role.name + }; +} + +export function modifyRoleReset(role: Role): Action { + return { + type: MODIFY_ROLE_RESET, + itemId: role.name + }; +} + +export function modifyRole(role: Role, callback?: () => void) { + return function(dispatch: Dispatch) { + dispatch(modifyRolePending(role)); + return apiClient + .put(role._links.update.href, role, CONTENT_TYPE_ROLE) + .then(() => { + dispatch(modifyRoleSuccess(role)); + if (callback) { + callback(); + } + }) + .then(() => { + dispatch(fetchRoleByLink(role)); + }) + .catch(err => { + dispatch(modifyRoleFailure(role, err)); + }); + }; +} + +// delete role +export function deleteRolePending(role: Role): Action { + return { + type: DELETE_ROLE_PENDING, + payload: role, + itemId: role.name + }; +} + +export function deleteRoleSuccess(role: Role): Action { + return { + type: DELETE_ROLE_SUCCESS, + payload: role, + itemId: role.name + }; +} + +export function deleteRoleFailure(role: Role, error: Error): Action { + return { + type: DELETE_ROLE_FAILURE, + payload: { + error, + role + }, + itemId: role.name + }; +} + +export function deleteRole(role: Role, callback?: () => void) { + return function(dispatch: any) { + dispatch(deleteRolePending(role)); + return apiClient + .delete(role._links.delete.href) + .then(() => { + dispatch(deleteRoleSuccess(role)); + if (callback) { + callback(); + } + }) + .catch(error => { + dispatch(deleteRoleFailure(role, error)); + }); + }; +} + +function extractRolesByNames( + roles: Role[], + roleNames: string[], + oldRolesByNames: Object +) { + const rolesByNames = {}; + + for (let role of roles) { + rolesByNames[role.name] = role; + } + + for (let roleName in oldRolesByNames) { + rolesByNames[roleName] = oldRolesByNames[roleName]; + } + return rolesByNames; +} + +function deleteRoleInRolesByNames(roles: {}, roleName: string) { + let newRoles = {}; + for (let rolename in roles) { + if (rolename !== roleName) newRoles[rolename] = roles[rolename]; + } + return newRoles; +} + +function deleteRoleInEntries(roles: [], roleName: string) { + let newRoles = []; + for (let role of roles) { + if (role !== roleName) newRoles.push(role); + } + return newRoles; +} + +const reducerByName = (state: any, rolename: string, newRoleState: any) => { + const newRolesByNames = { + ...state, + [rolename]: newRoleState + }; + + return newRolesByNames; +}; + +function listReducer(state: any = {}, action: any = {}) { + switch (action.type) { + case FETCH_ROLES_SUCCESS: + const roles = action.payload._embedded.roles; + const roleNames = roles.map(role => role.name); + return { + ...state, + entries: roleNames, + entry: { + roleCreatePermission: action.payload._links.create ? true : false, + page: action.payload.page, + pageTotal: action.payload.pageTotal, + _links: action.payload._links + } + }; + + // Delete single role actions + case DELETE_ROLE_SUCCESS: + const newRoleEntries = deleteRoleInEntries( + state.entries, + action.payload.name + ); + return { + ...state, + entries: newRoleEntries + }; + default: + return state; + } +} + +function byNamesReducer(state: any = {}, action: any = {}) { + switch (action.type) { + // Fetch all roles actions + case FETCH_ROLES_SUCCESS: + const roles = action.payload._embedded.roles; + const roleNames = roles.map(role => role.name); + const byNames = extractRolesByNames(roles, roleNames, state.byNames); + return { + ...byNames + }; + + // Fetch single role actions + case FETCH_ROLE_SUCCESS: + return reducerByName(state, action.payload.name, action.payload); + + case DELETE_ROLE_SUCCESS: + return deleteRoleInRolesByNames( + state, + action.payload.name + ); + + default: + return state; + } +} + +export default combineReducers({ + list: listReducer, + byNames: byNamesReducer +}); + +// selectors +const selectList = (state: Object) => { + if (state.roles && state.roles.list) { + return state.roles.list; + } + return {}; +}; + +const selectListEntry = (state: Object): Object => { + const list = selectList(state); + if (list.entry) { + return list.entry; + } + return {}; +}; + +export const selectListAsCollection = (state: Object): PagedCollection => { + return selectListEntry(state); +}; + +export const isPermittedToCreateRoles = (state: Object): boolean => { + const permission = selectListEntry(state).roleCreatePermission; + if (permission) { + return true; + } + return false; +}; + +export function getRolesFromState(state: Object) { + const roleNames = selectList(state).entries; + if (!roleNames) { + return null; + } + const roleEntries: Role[] = []; + + for (let roleName of roleNames) { + roleEntries.push(state.roles.byNames[roleName]); + } + + return roleEntries; +} + +export function isFetchRolesPending(state: Object) { + return isPending(state, FETCH_ROLES); +} + +export function getFetchRolesFailure(state: Object) { + return getFailure(state, FETCH_ROLES); +} + +export function isCreateRolePending(state: Object) { + return isPending(state, CREATE_ROLE); +} + +export function getCreateRoleFailure(state: Object) { + return getFailure(state, CREATE_ROLE); +} + +export function getRoleByName(state: Object, name: string) { + if (state.roles && state.roles.byNames) { + return state.roles.byNames[name]; + } +} + +export function isFetchRolePending(state: Object, name: string) { + return isPending(state, FETCH_ROLE, name); +} + +export function getFetchRoleFailure(state: Object, name: string) { + return getFailure(state, FETCH_ROLE, name); +} + +export function isModifyRolePending(state: Object, name: string) { + return isPending(state, MODIFY_ROLE, name); +} + +export function getModifyRoleFailure(state: Object, name: string) { + return getFailure(state, MODIFY_ROLE, name); +} + +export function isDeleteRolePending(state: Object, name: string) { + return isPending(state, DELETE_ROLE, name); +} + +export function getDeleteRoleFailure(state: Object, name: string) { + return getFailure(state, DELETE_ROLE, name); +} diff --git a/scm-ui/src/config/modules/roles.test.js b/scm-ui/src/config/modules/roles.test.js new file mode 100644 index 0000000000..7bb6bdcc60 --- /dev/null +++ b/scm-ui/src/config/modules/roles.test.js @@ -0,0 +1,552 @@ +// @flow +import configureMockStore from "redux-mock-store"; +import thunk from "redux-thunk"; +import fetchMock from "fetch-mock"; + +import reducer, { + FETCH_ROLES, + FETCH_ROLES_PENDING, + FETCH_ROLES_SUCCESS, + FETCH_ROLES_FAILURE, + FETCH_ROLE, + FETCH_ROLE_PENDING, + FETCH_ROLE_SUCCESS, + FETCH_ROLE_FAILURE, + CREATE_ROLE, + CREATE_ROLE_PENDING, + CREATE_ROLE_SUCCESS, + CREATE_ROLE_FAILURE, + MODIFY_ROLE, + MODIFY_ROLE_PENDING, + MODIFY_ROLE_SUCCESS, + MODIFY_ROLE_FAILURE, + DELETE_ROLE, + DELETE_ROLE_PENDING, + DELETE_ROLE_SUCCESS, + DELETE_ROLE_FAILURE, + fetchRoles, + getFetchRolesFailure, + getRolesFromState, + isFetchRolesPending, + fetchRolesSuccess, + fetchRoleByLink, + fetchRoleByName, + fetchRoleSuccess, + isFetchRolePending, + getFetchRoleFailure, + createRole, + isCreateRolePending, + getCreateRoleFailure, + getRoleByName, + modifyRole, + isModifyRolePending, + getModifyRoleFailure, + deleteRole, + isDeleteRolePending, + deleteRoleSuccess, + getDeleteRoleFailure, + selectListAsCollection, + isPermittedToCreateRoles +} from "./roles"; + +const URL = "roles"; +const ROLES_URL = "api/v2/repositoryPermissions"; + +const error = new Error("FEHLER!"); + +const verbs = [ + "createPullRequest", + "readPullRequest", + "commentPullRequest", + "modifyPullRequest", + "mergePullRequest", + "git", + "hg", + "read", + "modify", + "delete", + "pull", + "push", + "permissionRead", + "permissionWrite", + "*" +]; + +const repositoryRoles = { + availableVerbs: verbs, + availableRoles: [ + { + name: "READ", + creationDate: null, + lastModified: null, + type: "system", + verb: ["read", "pull", "readPullRequest"] + }, + { + name: "WRITE", + creationDate: null, + lastModified: null, + type: "system", + verb: [ + "read", + "pull", + "push", + "createPullRequest", + "readPullRequest", + "commentPullRequest", + "mergePullRequest" + ] + }, + { + name: "OWNER", + creationDate: null, + lastModified: null, + type: "system", + verb: ["*"] + } + ], + _links: { + self: { + href: "http://localhost:8081/scm/api/v2/repositoryPermissions/" + } + } +}; + +const responseBody = { + entries: repositoryRoles, + rolesUpdatePermission: false +}; + +const response = { + headers: { "content-type": "application/json" }, + responseBody +}; + +describe("repository roles fetch()", () => { + const mockStore = configureMockStore([thunk]); + afterEach(() => { + fetchMock.reset(); + fetchMock.restore(); + }); + + it("should successfully fetch repository roles", () => { + fetchMock.getOnce(ROLES_URL, response); + + const expectedActions = [ + { type: FETCH_ROLES_PENDING }, + { + type: FETCH_ROLES_SUCCESS, + payload: response + } + ]; + + const store = mockStore({}); + + return store.dispatch(fetchRoles(URL)).then(() => { + expect(store.getActions()).toEqual(expectedActions); + }); + }); + + it("should fail getting repository roles on HTTP 500", () => { + fetchMock.getOnce(ROLES_URL, { + status: 500 + }); + + const store = mockStore({}); + + return store.dispatch(fetchRoles(URL)).then(() => { + const actions = store.getActions(); + expect(actions[0].type).toEqual(FETCH_ROLES_PENDING); + expect(actions[1].type).toEqual(FETCH_ROLES_FAILURE); + expect(actions[1].payload).toBeDefined(); + }); + }); + + it("should add a role successfully", () => { + // unmatched + fetchMock.postOnce(ROLES_URL, { + status: 204 + }); + + // after create, the roles are fetched again + fetchMock.getOnce(ROLES_URL, response); + + const store = mockStore({}); + + return store.dispatch(createRole(URL, role1)).then(() => { + const actions = store.getActions(); + expect(actions[0].type).toEqual(CREATE_ROLE_PENDING); + expect(actions[1].type).toEqual(CREATE_ROLE_SUCCESS); + }); + }); + + it("should fail adding a role on HTTP 500", () => { + fetchMock.postOnce(ROLES_URL, { + status: 500 + }); + + const store = mockStore({}); + + return store.dispatch(createRole(URL, role1)).then(() => { + const actions = store.getActions(); + expect(actions[0].type).toEqual(CREATE_ROLE_PENDING); + expect(actions[1].type).toEqual(CREATE_ROLE_FAILURE); + expect(actions[1].payload).toBeDefined(); + }); + }); + + it("should call the callback after role successfully created", () => { + // unmatched + fetchMock.postOnce(ROLES_URL, { + status: 204 + }); + + let callMe = "not yet"; + + const callback = () => { + callMe = "yeah"; + }; + + const store = mockStore({}); + return store.dispatch(createRole(URL, role1, callback)).then(() => { + expect(callMe).toBe("yeah"); + }); + }); + + it("successfully update role", () => { + fetchMock.putOnce(ROLE1_URL, { + status: 204 + }); + fetchMock.getOnce(ROLE1_URL, role1); + + const store = mockStore({}); + return store.dispatch(modifyRole(role1)).then(() => { + const actions = store.getActions(); + expect(actions.length).toBe(3); + expect(actions[0].type).toEqual(MODIFY_ROLE_PENDING); + expect(actions[1].type).toEqual(MODIFY_ROLE_SUCCESS); + expect(actions[2].type).toEqual(FETCH_ROLE_PENDING); + }); + }); + + it("should call callback, after successful modified role", () => { + fetchMock.putOnce(ROLE1_URL, { + status: 204 + }); + fetchMock.getOnce(ROLE1_URL, role1); + + let called = false; + const callMe = () => { + called = true; + }; + + const store = mockStore({}); + return store.dispatch(modifyRole(role1, callMe)).then(() => { + expect(called).toBeTruthy(); + }); + }); + + + + + + it("should fail updating role on HTTP 500", () => { + fetchMock.putOnce(ROLE1_URL, { + status: 500 + }); + + const store = mockStore({}); + return store.dispatch(modifyRole(role1)).then(() => { + const actions = store.getActions(); + expect(actions[0].type).toEqual(MODIFY_ROLE_PENDING); + expect(actions[1].type).toEqual(MODIFY_ROLE_FAILURE); + expect(actions[1].payload).toBeDefined(); + }); + }); + + it("should delete successfully role1", () => { + fetchMock.deleteOnce(ROLE1_URL, { + status: 204 + }); + + const store = mockStore({}); + return store.dispatch(deleteRole(role1)).then(() => { + const actions = store.getActions(); + expect(actions.length).toBe(2); + expect(actions[0].type).toEqual(DELETE_ROLE_PENDING); + expect(actions[0].payload).toBe(role1); + expect(actions[1].type).toEqual(DELETE_ROLE_SUCCESS); + }); + }); + + it("should call the callback, after successful delete", () => { + fetchMock.deleteOnce(ROLE1_URL, { + status: 204 + }); + + let called = false; + const callMe = () => { + called = true; + }; + + const store = mockStore({}); + return store.dispatch(deleteRole(role1, callMe)).then(() => { + expect(called).toBeTruthy(); + }); + }); + + it("should fail to delete role1", () => { + fetchMock.deleteOnce(ROLE1_URL, { + status: 500 + }); + + const store = mockStore({}); + return store.dispatch(deleteRole(role1)).then(() => { + const actions = store.getActions(); + expect(actions[0].type).toEqual(DELETE_ROLE_PENDING); + expect(actions[0].payload).toBe(role1); + expect(actions[1].type).toEqual(DELETE_ROLE_FAILURE); + expect(actions[1].payload).toBeDefined(); + }); + }); +}); + +describe("repository roles reducer", () => { + it("should update state correctly according to FETCH_ROLES_SUCCESS action", () => { + + }); + + it("should set roleCreatePermission to true if update link is present", () => { + + }); + + it("should not replace whole byNames map when fetching roles", () => { + + }); + + it("should remove role from state when delete succeeds", () => { + + }); + + it("should set roleCreatePermission to true if create link is present", () => { + + }); + + it("should update state according to FETCH_ROLE_SUCCESS action", () => { + + }); + + it("should affect roles state nor the state of other roles", () => { + + }); +}); + +describe("selector tests", () => { + it("should return an empty object", () => { + expect(selectListAsCollection({})).toEqual({}); + expect(selectListAsCollection({ roles: { a: "a" } })).toEqual({}); + }); + + it("should return a state slice collection", () => { + const collection = { + page: 3, + totalPages: 42 + }; + + const state = { + roles: { + list: { + entry: collection + } + } + }; + expect(selectListAsCollection(state)).toBe(collection); + }); + + it("should return false", () => { + expect(isPermittedToCreateRoles({})).toBe(false); + expect(isPermittedToCreateRoles({ roles: { list: { entry: {} } } })).toBe( + false + ); + expect( + isPermittedToCreateRoles({ + roles: { list: { entry: { roleCreatePermission: false } } } + }) + ).toBe(false); + }); + + it("should return true", () => { + const state = { + roles: { + list: { + entry: { + roleCreatePermission: true + } + } + } + }; + expect(isPermittedToCreateRoles(state)).toBe(true); + }); + + it("should get roles from state", () => { + const state = { + roles: { + list: { + entries: ["a", "b"] + }, + byNames: { + a: { name: "a" }, + b: { name: "b" } + } + } + }; + expect(getRolesFromState(state)).toEqual([{ name: "a" }, { name: "b" }]); + }); + + it("should return true, when fetch roles is pending", () => { + const state = { + pending: { + [FETCH_ROLES]: true + } + }; + expect(isFetchRolesPending(state)).toEqual(true); + }); + + it("should return false, when fetch roles is not pending", () => { + expect(isFetchRolesPending({})).toEqual(false); + }); + + it("should return error when fetch roles did fail", () => { + const state = { + failure: { + [FETCH_ROLES]: error + } + }; + expect(getFetchRolesFailure(state)).toEqual(error); + }); + + it("should return undefined when fetch roles did not fail", () => { + expect(getFetchRolesFailure({})).toBe(undefined); + }); + + it("should return true if create role is pending", () => { + const state = { + pending: { + [CREATE_ROLE]: true + } + }; + expect(isCreateRolePending(state)).toBe(true); + }); + + it("should return false if create role is not pending", () => { + const state = { + pending: { + [CREATE_ROLE]: false + } + }; + expect(isCreateRolePending(state)).toBe(false); + }); + + it("should return error when create role did fail", () => { + const state = { + failure: { + [CREATE_ROLE]: error + } + }; + expect(getCreateRoleFailure(state)).toEqual(error); + }); + + it("should return undefined when create role did not fail", () => { + expect(getCreateRoleFailure({})).toBe(undefined); + }); + + it("should return role1", () => { + const state = { + roles: { + byNames: { + role1: role1 + } + } + }; + expect(getRoleByName(state, "role1")).toEqual(role1); + }); + + it("should return true, when fetch role2 is pending", () => { + const state = { + pending: { + [FETCH_ROLE + "/role2"]: true + } + }; + expect(isFetchRolePending(state, "role2")).toEqual(true); + }); + + it("should return false, when fetch role2 is not pending", () => { + expect(isFetchRolePending({}, "role2")).toEqual(false); + }); + + it("should return error when fetch role2 did fail", () => { + const state = { + failure: { + [FETCH_ROLE + "/role2"]: error + } + }; + expect(getFetchRoleFailure(state, "role2")).toEqual(error); + }); + + it("should return undefined when fetch role2 did not fail", () => { + expect(getFetchRoleFailure({}, "role2")).toBe(undefined); + }); + + it("should return true, when modify role1 is pending", () => { + const state = { + pending: { + [MODIFY_ROLE + "/role1"]: true + } + }; + expect(isModifyRolePending(state, "role1")).toEqual(true); + }); + + it("should return false, when modify role1 is not pending", () => { + expect(isModifyRolePending({}, "role1")).toEqual(false); + }); + + it("should return error when modify role1 did fail", () => { + const state = { + failure: { + [MODIFY_ROLE + "/role1"]: error + } + }; + expect(getModifyRoleFailure(state, "role1")).toEqual(error); + }); + + it("should return undefined when modify role1 did not fail", () => { + expect(getModifyRoleFailure({}, "role1")).toBe(undefined); + }); + + it("should return true, when delete role2 is pending", () => { + const state = { + pending: { + [DELETE_ROLE + "/role2"]: true + } + }; + expect(isDeleteRolePending(state, "role2")).toEqual(true); + }); + + it("should return false, when delete role2 is not pending", () => { + expect(isDeleteRolePending({}, "role2")).toEqual(false); + }); + + it("should return error when delete role2 did fail", () => { + const state = { + failure: { + [DELETE_ROLE + "/role2"]: error + } + }; + expect(getDeleteRoleFailure(state, "role2")).toEqual(error); + }); + + it("should return undefined when delete role2 did not fail", () => { + expect(getDeleteRoleFailure({}, "role2")).toBe(undefined); + }); +}); + diff --git a/scm-ui/src/groups/modules/groups.js b/scm-ui/src/groups/modules/groups.js index cb3c24aa0f..6884a8817f 100644 --- a/scm-ui/src/groups/modules/groups.js +++ b/scm-ui/src/groups/modules/groups.js @@ -28,7 +28,7 @@ export const MODIFY_GROUP_SUCCESS = `${MODIFY_GROUP}_${types.SUCCESS_SUFFIX}`; export const MODIFY_GROUP_FAILURE = `${MODIFY_GROUP}_${types.FAILURE_SUFFIX}`; export const MODIFY_GROUP_RESET = `${MODIFY_GROUP}_${types.RESET_SUFFIX}`; -export const DELETE_GROUP = "scm/groups/DELETE"; +export const DELETE_GROUP = "scm/groups/DELETE_GROUP"; export const DELETE_GROUP_PENDING = `${DELETE_GROUP}_${types.PENDING_SUFFIX}`; export const DELETE_GROUP_SUCCESS = `${DELETE_GROUP}_${types.SUCCESS_SUFFIX}`; export const DELETE_GROUP_FAILURE = `${DELETE_GROUP}_${types.FAILURE_SUFFIX}`; diff --git a/scm-ui/src/modules/indexResource.js b/scm-ui/src/modules/indexResource.js index df55c63756..909d356752 100644 --- a/scm-ui/src/modules/indexResource.js +++ b/scm-ui/src/modules/indexResource.js @@ -151,6 +151,11 @@ export function getSvnConfigLink(state: Object) { return getLink(state, "svnConfig"); } +export function getRolesLink(state: Object) { + //return getLink(state, "availableRepositoryPermissions"); + return "http://localhost:8081/scm/api/v2/repository-roles"; // TODO +} + export function getUserAutoCompleteLink(state: Object): string { const link = getLinkCollection(state, "autocomplete").find( i => i.name === "users" diff --git a/scm-ui/src/users/modules/users.js b/scm-ui/src/users/modules/users.js index c83a4d9b94..71235c689c 100644 --- a/scm-ui/src/users/modules/users.js +++ b/scm-ui/src/users/modules/users.js @@ -28,7 +28,7 @@ export const MODIFY_USER_SUCCESS = `${MODIFY_USER}_${types.SUCCESS_SUFFIX}`; export const MODIFY_USER_FAILURE = `${MODIFY_USER}_${types.FAILURE_SUFFIX}`; export const MODIFY_USER_RESET = `${MODIFY_USER}_${types.RESET_SUFFIX}`; -export const DELETE_USER = "scm/users/DELETE"; +export const DELETE_USER = "scm/users/DELETE_USER"; export const DELETE_USER_PENDING = `${DELETE_USER}_${types.PENDING_SUFFIX}`; export const DELETE_USER_SUCCESS = `${DELETE_USER}_${types.SUCCESS_SUFFIX}`; export const DELETE_USER_FAILURE = `${DELETE_USER}_${types.FAILURE_SUFFIX}`; diff --git a/scm-ui/src/users/modules/users.test.js b/scm-ui/src/users/modules/users.test.js index ac4d1c97a9..e046443f3b 100644 --- a/scm-ui/src/users/modules/users.test.js +++ b/scm-ui/src/users/modules/users.test.js @@ -4,49 +4,49 @@ import thunk from "redux-thunk"; import fetchMock from "fetch-mock"; import reducer, { - CREATE_USER_FAILURE, - CREATE_USER_PENDING, - CREATE_USER_SUCCESS, - createUser, - DELETE_USER_FAILURE, - DELETE_USER_PENDING, - DELETE_USER_SUCCESS, - deleteUser, - deleteUserSuccess, - FETCH_USER_FAILURE, - FETCH_USER_PENDING, - isFetchUserPending, - FETCH_USER_SUCCESS, - FETCH_USERS_FAILURE, + FETCH_USERS, FETCH_USERS_PENDING, FETCH_USERS_SUCCESS, + FETCH_USERS_FAILURE, + FETCH_USER, + FETCH_USER_PENDING, + FETCH_USER_SUCCESS, + FETCH_USER_FAILURE, + CREATE_USER, + CREATE_USER_PENDING, + CREATE_USER_SUCCESS, + CREATE_USER_FAILURE, + MODIFY_USER, + MODIFY_USER_PENDING, + MODIFY_USER_SUCCESS, + MODIFY_USER_FAILURE, + DELETE_USER, + DELETE_USER_PENDING, + DELETE_USER_SUCCESS, + DELETE_USER_FAILURE, + fetchUsers, + getFetchUsersFailure, + getUsersFromState, + isFetchUsersPending, + fetchUsersSuccess, fetchUserByLink, fetchUserByName, fetchUserSuccess, + isFetchUserPending, getFetchUserFailure, - fetchUsers, - fetchUsersSuccess, - isFetchUsersPending, - selectListAsCollection, - isPermittedToCreateUsers, - MODIFY_USER, - MODIFY_USER_FAILURE, - MODIFY_USER_PENDING, - MODIFY_USER_SUCCESS, - modifyUser, - getUsersFromState, - FETCH_USERS, - getFetchUsersFailure, - FETCH_USER, - CREATE_USER, + createUser, isCreateUserPending, getCreateUserFailure, getUserByName, + modifyUser, isModifyUserPending, getModifyUserFailure, - DELETE_USER, + deleteUser, isDeleteUserPending, - getDeleteUserFailure + deleteUserSuccess, + getDeleteUserFailure, + selectListAsCollection, + isPermittedToCreateUsers } from "./users"; const userZaphod = { @@ -302,7 +302,7 @@ describe("users fetch()", () => { }); it("should fail updating user on HTTP 500", () => { - fetchMock.putOnce("http://localhost:8081/api/v2/users/zaphod", { + fetchMock.putOnce(USER_ZAPHOD_URL, { status: 500 }); @@ -316,7 +316,7 @@ describe("users fetch()", () => { }); it("should delete successfully user zaphod", () => { - fetchMock.deleteOnce("http://localhost:8081/api/v2/users/zaphod", { + fetchMock.deleteOnce(USER_ZAPHOD_URL, { status: 204 }); @@ -331,7 +331,7 @@ describe("users fetch()", () => { }); it("should call the callback, after successful delete", () => { - fetchMock.deleteOnce("http://localhost:8081/api/v2/users/zaphod", { + fetchMock.deleteOnce(USER_ZAPHOD_URL, { status: 204 }); @@ -347,7 +347,7 @@ describe("users fetch()", () => { }); it("should fail to delete user zaphod", () => { - fetchMock.deleteOnce("http://localhost:8081/api/v2/users/zaphod", { + fetchMock.deleteOnce(USER_ZAPHOD_URL, { status: 500 });