mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-09 06:55:47 +01:00
use reflow to migrate from flow to typescript
This commit is contained in:
@@ -1,369 +0,0 @@
|
||||
import reducer, {
|
||||
FETCH_ME,
|
||||
FETCH_ME_FAILURE,
|
||||
FETCH_ME_PENDING,
|
||||
FETCH_ME_SUCCESS,
|
||||
FETCH_ME_UNAUTHORIZED,
|
||||
fetchMe,
|
||||
fetchMeSuccess,
|
||||
fetchMeUnauthenticated,
|
||||
getFetchMeFailure,
|
||||
getLoginFailure,
|
||||
getLogoutFailure,
|
||||
getMe,
|
||||
isAuthenticated,
|
||||
isFetchMePending,
|
||||
isLoginPending,
|
||||
isLogoutPending,
|
||||
isRedirecting,
|
||||
login,
|
||||
LOGIN,
|
||||
LOGIN_FAILURE,
|
||||
LOGIN_PENDING,
|
||||
LOGIN_SUCCESS,
|
||||
loginSuccess,
|
||||
logout,
|
||||
LOGOUT,
|
||||
LOGOUT_FAILURE,
|
||||
LOGOUT_PENDING,
|
||||
LOGOUT_REDIRECT,
|
||||
LOGOUT_SUCCESS,
|
||||
logoutSuccess,
|
||||
redirectAfterLogout
|
||||
} from "./auth";
|
||||
|
||||
import configureMockStore from "redux-mock-store";
|
||||
import thunk from "redux-thunk";
|
||||
import fetchMock from "fetch-mock";
|
||||
import {
|
||||
FETCH_INDEXRESOURCES_PENDING,
|
||||
FETCH_INDEXRESOURCES_SUCCESS
|
||||
} from "./indexResource";
|
||||
|
||||
const me = {
|
||||
name: "tricia",
|
||||
displayName: "Tricia McMillian",
|
||||
mail: "trillian@heartofgold.universe"
|
||||
};
|
||||
|
||||
describe("auth reducer", () => {
|
||||
it("should set me and login on successful fetch of me", () => {
|
||||
const state = reducer(undefined, fetchMeSuccess(me));
|
||||
expect(state.me).toBe(me);
|
||||
});
|
||||
|
||||
it("should set authenticated to false", () => {
|
||||
const initialState = {
|
||||
me
|
||||
};
|
||||
const state = reducer(initialState, fetchMeUnauthenticated());
|
||||
expect(state.me.name).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should reset the state after logout", () => {
|
||||
const initialState = {
|
||||
me
|
||||
};
|
||||
const state = reducer(initialState, logoutSuccess());
|
||||
expect(state.me).toBeUndefined();
|
||||
expect(state.authenticated).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should keep state and set redirecting to true", () => {
|
||||
const initialState = {
|
||||
me
|
||||
};
|
||||
const state = reducer(initialState, redirectAfterLogout());
|
||||
expect(state.me).toBe(initialState.me);
|
||||
expect(state.redirecting).toBe(true);
|
||||
});
|
||||
|
||||
it("should set state authenticated and me after login", () => {
|
||||
const state = reducer(undefined, loginSuccess(me));
|
||||
expect(state.me).toBe(me);
|
||||
});
|
||||
});
|
||||
|
||||
describe("auth actions", () => {
|
||||
const mockStore = configureMockStore([thunk]);
|
||||
|
||||
afterEach(() => {
|
||||
fetchMock.reset();
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
it("should dispatch login success and dispatch fetch me", () => {
|
||||
fetchMock.postOnce("/api/v2/auth/access_token", {
|
||||
body: {
|
||||
cookie: true,
|
||||
grant_type: "password",
|
||||
username: "tricia",
|
||||
password: "secret123"
|
||||
},
|
||||
headers: { "content-type": "application/json" }
|
||||
});
|
||||
|
||||
fetchMock.getOnce("/api/v2/me", {
|
||||
body: me,
|
||||
headers: { "content-type": "application/json" }
|
||||
});
|
||||
|
||||
const meLink = {
|
||||
me: {
|
||||
href: "/me"
|
||||
}
|
||||
};
|
||||
|
||||
fetchMock.getOnce("/api/v2/", {
|
||||
_links: meLink
|
||||
});
|
||||
|
||||
const expectedActions = [
|
||||
{ type: LOGIN_PENDING },
|
||||
{ type: FETCH_INDEXRESOURCES_PENDING },
|
||||
{ type: FETCH_INDEXRESOURCES_SUCCESS, payload: { _links: meLink } },
|
||||
{ type: LOGIN_SUCCESS, payload: me }
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
return store
|
||||
.dispatch(login("/auth/access_token", "tricia", "secret123"))
|
||||
.then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch login failure", () => {
|
||||
fetchMock.postOnce("/api/v2/auth/access_token", {
|
||||
status: 400
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(login("/auth/access_token", "tricia", "secret123"))
|
||||
.then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(LOGIN_PENDING);
|
||||
expect(actions[1].type).toEqual(LOGIN_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch fetch me success", () => {
|
||||
fetchMock.getOnce("/api/v2/me", {
|
||||
body: me,
|
||||
headers: { "content-type": "application/json" }
|
||||
});
|
||||
|
||||
const expectedActions = [
|
||||
{ type: FETCH_ME_PENDING },
|
||||
{
|
||||
type: FETCH_ME_SUCCESS,
|
||||
payload: me
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
return store.dispatch(fetchMe("me")).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch fetch me failure", () => {
|
||||
fetchMock.getOnce("/api/v2/me", {
|
||||
status: 500
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchMe("me")).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(FETCH_ME_PENDING);
|
||||
expect(actions[1].type).toEqual(FETCH_ME_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch fetch me unauthorized", () => {
|
||||
fetchMock.getOnce("/api/v2/me", 401);
|
||||
|
||||
const expectedActions = [
|
||||
{ type: FETCH_ME_PENDING },
|
||||
{ type: FETCH_ME_UNAUTHORIZED, resetPending: true }
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
return store.dispatch(fetchMe("me")).then(() => {
|
||||
// return of async actions
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch logout success", () => {
|
||||
fetchMock.deleteOnce("/api/v2/auth/access_token", {
|
||||
status: 204
|
||||
});
|
||||
|
||||
fetchMock.getOnce("/api/v2/me", {
|
||||
status: 401
|
||||
});
|
||||
|
||||
fetchMock.getOnce("/api/v2/", {
|
||||
_links: {
|
||||
login: {
|
||||
login: "/login"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const expectedActions = [
|
||||
{ type: LOGOUT_PENDING },
|
||||
{ type: LOGOUT_SUCCESS },
|
||||
{ type: FETCH_INDEXRESOURCES_PENDING }
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
return store.dispatch(logout("/auth/access_token")).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch logout success and redirect", () => {
|
||||
fetchMock.deleteOnce("/api/v2/auth/access_token", {
|
||||
status: 200,
|
||||
body: { logoutRedirect: "http://example.com/cas/logout" }
|
||||
});
|
||||
|
||||
fetchMock.getOnce("/api/v2/me", {
|
||||
status: 401
|
||||
});
|
||||
|
||||
fetchMock.getOnce("/api/v2/", {
|
||||
_links: {
|
||||
login: {
|
||||
login: "/login"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.location.assign = jest.fn();
|
||||
|
||||
const expectedActions = [
|
||||
{ type: LOGOUT_PENDING },
|
||||
{ type: LOGOUT_REDIRECT }
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
return store.dispatch(logout("/auth/access_token")).then(() => {
|
||||
expect(window.location.assign.mock.calls[0][0]).toBe(
|
||||
"http://example.com/cas/logout"
|
||||
);
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch logout failure", () => {
|
||||
fetchMock.deleteOnce("/api/v2/auth/access_token", {
|
||||
status: 500
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(logout("/auth/access_token")).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(LOGOUT_PENDING);
|
||||
expect(actions[1].type).toEqual(LOGOUT_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("auth selectors", () => {
|
||||
const error = new Error("yo it failed");
|
||||
|
||||
it("should return true if me exist and login Link does not exist", () => {
|
||||
expect(
|
||||
isAuthenticated({ auth: { me }, indexResources: { links: {} } })
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false if me exist and login Link does exist", () => {
|
||||
expect(
|
||||
isAuthenticated({
|
||||
auth: { me },
|
||||
indexResources: { links: { login: { href: "login.href" } } }
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("should return me", () => {
|
||||
expect(getMe({ auth: { me } })).toBe(me);
|
||||
});
|
||||
|
||||
it("should return undefined, if me is not set", () => {
|
||||
expect(getMe({})).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should return true, if FETCH_ME is pending", () => {
|
||||
expect(isFetchMePending({ pending: { [FETCH_ME]: true } })).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false, if FETCH_ME is not in pending state", () => {
|
||||
expect(isFetchMePending({ pending: {} })).toBe(false);
|
||||
});
|
||||
|
||||
it("should return true, if LOGIN is pending", () => {
|
||||
expect(isLoginPending({ pending: { [LOGIN]: true } })).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false, if LOGIN is not in pending state", () => {
|
||||
expect(isLoginPending({ pending: {} })).toBe(false);
|
||||
});
|
||||
|
||||
it("should return true, if LOGOUT is pending", () => {
|
||||
expect(isLogoutPending({ pending: { [LOGOUT]: true } })).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false, if LOGOUT is not in pending state", () => {
|
||||
expect(isLogoutPending({ pending: {} })).toBe(false);
|
||||
});
|
||||
|
||||
it("should return the error, if failure state is set for FETCH_ME", () => {
|
||||
expect(getFetchMeFailure({ failure: { [FETCH_ME]: error } })).toBe(error);
|
||||
});
|
||||
|
||||
it("should return unknown, if failure state is not set for FETCH_ME", () => {
|
||||
expect(getFetchMeFailure({})).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should return the error, if failure state is set for LOGIN", () => {
|
||||
expect(getLoginFailure({ failure: { [LOGIN]: error } })).toBe(error);
|
||||
});
|
||||
|
||||
it("should return unknown, if failure state is not set for LOGIN", () => {
|
||||
expect(getLoginFailure({})).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should return the error, if failure state is set for LOGOUT", () => {
|
||||
expect(getLogoutFailure({ failure: { [LOGOUT]: error } })).toBe(error);
|
||||
});
|
||||
|
||||
it("should return unknown, if failure state is not set for LOGOUT", () => {
|
||||
expect(getLogoutFailure({})).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should return false, if redirecting is not set", () => {
|
||||
expect(isRedirecting({})).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false, if redirecting is false", () => {
|
||||
expect(isRedirecting({ auth: { redirecting: false } })).toBe(false);
|
||||
});
|
||||
|
||||
it("should return true, if redirecting is true", () => {
|
||||
expect(isRedirecting({ auth: { redirecting: true } })).toBe(true);
|
||||
});
|
||||
});
|
||||
487
scm-ui/ui-webapp/src/modules/auth.test.ts
Normal file
487
scm-ui/ui-webapp/src/modules/auth.test.ts
Normal file
@@ -0,0 +1,487 @@
|
||||
import reducer, {
|
||||
FETCH_ME,
|
||||
FETCH_ME_FAILURE,
|
||||
FETCH_ME_PENDING,
|
||||
FETCH_ME_SUCCESS,
|
||||
FETCH_ME_UNAUTHORIZED,
|
||||
fetchMe,
|
||||
fetchMeSuccess,
|
||||
fetchMeUnauthenticated,
|
||||
getFetchMeFailure,
|
||||
getLoginFailure,
|
||||
getLogoutFailure,
|
||||
getMe,
|
||||
isAuthenticated,
|
||||
isFetchMePending,
|
||||
isLoginPending,
|
||||
isLogoutPending,
|
||||
isRedirecting,
|
||||
login,
|
||||
LOGIN,
|
||||
LOGIN_FAILURE,
|
||||
LOGIN_PENDING,
|
||||
LOGIN_SUCCESS,
|
||||
loginSuccess,
|
||||
logout,
|
||||
LOGOUT,
|
||||
LOGOUT_FAILURE,
|
||||
LOGOUT_PENDING,
|
||||
LOGOUT_REDIRECT,
|
||||
LOGOUT_SUCCESS,
|
||||
logoutSuccess,
|
||||
redirectAfterLogout,
|
||||
} from './auth';
|
||||
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import {
|
||||
FETCH_INDEXRESOURCES_PENDING,
|
||||
FETCH_INDEXRESOURCES_SUCCESS,
|
||||
} from './indexResource';
|
||||
|
||||
const me = {
|
||||
name: 'tricia',
|
||||
displayName: 'Tricia McMillian',
|
||||
mail: 'trillian@heartofgold.universe',
|
||||
};
|
||||
|
||||
describe('auth reducer', () => {
|
||||
it('should set me and login on successful fetch of me', () => {
|
||||
const state = reducer(undefined, fetchMeSuccess(me));
|
||||
expect(state.me).toBe(me);
|
||||
});
|
||||
|
||||
it('should set authenticated to false', () => {
|
||||
const initialState = {
|
||||
me,
|
||||
};
|
||||
const state = reducer(initialState, fetchMeUnauthenticated());
|
||||
expect(state.me.name).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should reset the state after logout', () => {
|
||||
const initialState = {
|
||||
me,
|
||||
};
|
||||
const state = reducer(initialState, logoutSuccess());
|
||||
expect(state.me).toBeUndefined();
|
||||
expect(state.authenticated).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should keep state and set redirecting to true', () => {
|
||||
const initialState = {
|
||||
me,
|
||||
};
|
||||
const state = reducer(initialState, redirectAfterLogout());
|
||||
expect(state.me).toBe(initialState.me);
|
||||
expect(state.redirecting).toBe(true);
|
||||
});
|
||||
|
||||
it('should set state authenticated and me after login', () => {
|
||||
const state = reducer(undefined, loginSuccess(me));
|
||||
expect(state.me).toBe(me);
|
||||
});
|
||||
});
|
||||
|
||||
describe('auth actions', () => {
|
||||
const mockStore = configureMockStore([thunk]);
|
||||
|
||||
afterEach(() => {
|
||||
fetchMock.reset();
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
it('should dispatch login success and dispatch fetch me', () => {
|
||||
fetchMock.postOnce('/api/v2/auth/access_token', {
|
||||
body: {
|
||||
cookie: true,
|
||||
grant_type: 'password',
|
||||
username: 'tricia',
|
||||
password: 'secret123',
|
||||
},
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
fetchMock.getOnce('/api/v2/me', {
|
||||
body: me,
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
const meLink = {
|
||||
me: {
|
||||
href: '/me',
|
||||
},
|
||||
};
|
||||
|
||||
fetchMock.getOnce('/api/v2/', {
|
||||
_links: meLink,
|
||||
});
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: LOGIN_PENDING,
|
||||
},
|
||||
{
|
||||
type: FETCH_INDEXRESOURCES_PENDING,
|
||||
},
|
||||
{
|
||||
type: FETCH_INDEXRESOURCES_SUCCESS,
|
||||
payload: {
|
||||
_links: meLink,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: LOGIN_SUCCESS,
|
||||
payload: me,
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
return store
|
||||
.dispatch(login('/auth/access_token', 'tricia', 'secret123'))
|
||||
.then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it('should dispatch login failure', () => {
|
||||
fetchMock.postOnce('/api/v2/auth/access_token', {
|
||||
status: 400,
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store
|
||||
.dispatch(login('/auth/access_token', 'tricia', 'secret123'))
|
||||
.then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(LOGIN_PENDING);
|
||||
expect(actions[1].type).toEqual(LOGIN_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should dispatch fetch me success', () => {
|
||||
fetchMock.getOnce('/api/v2/me', {
|
||||
body: me,
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_ME_PENDING,
|
||||
},
|
||||
{
|
||||
type: FETCH_ME_SUCCESS,
|
||||
payload: me,
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
return store.dispatch(fetchMe('me')).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it('should dispatch fetch me failure', () => {
|
||||
fetchMock.getOnce('/api/v2/me', {
|
||||
status: 500,
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchMe('me')).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(FETCH_ME_PENDING);
|
||||
expect(actions[1].type).toEqual(FETCH_ME_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should dispatch fetch me unauthorized', () => {
|
||||
fetchMock.getOnce('/api/v2/me', 401);
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_ME_PENDING,
|
||||
},
|
||||
{
|
||||
type: FETCH_ME_UNAUTHORIZED,
|
||||
resetPending: true,
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
return store.dispatch(fetchMe('me')).then(() => {
|
||||
// return of async actions
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it('should dispatch logout success', () => {
|
||||
fetchMock.deleteOnce('/api/v2/auth/access_token', {
|
||||
status: 204,
|
||||
});
|
||||
|
||||
fetchMock.getOnce('/api/v2/me', {
|
||||
status: 401,
|
||||
});
|
||||
|
||||
fetchMock.getOnce('/api/v2/', {
|
||||
_links: {
|
||||
login: {
|
||||
login: '/login',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: LOGOUT_PENDING,
|
||||
},
|
||||
{
|
||||
type: LOGOUT_SUCCESS,
|
||||
},
|
||||
{
|
||||
type: FETCH_INDEXRESOURCES_PENDING,
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
return store.dispatch(logout('/auth/access_token')).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it('should dispatch logout success and redirect', () => {
|
||||
fetchMock.deleteOnce('/api/v2/auth/access_token', {
|
||||
status: 200,
|
||||
body: {
|
||||
logoutRedirect: 'http://example.com/cas/logout',
|
||||
},
|
||||
});
|
||||
|
||||
fetchMock.getOnce('/api/v2/me', {
|
||||
status: 401,
|
||||
});
|
||||
|
||||
fetchMock.getOnce('/api/v2/', {
|
||||
_links: {
|
||||
login: {
|
||||
login: '/login',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
window.location.assign = jest.fn();
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: LOGOUT_PENDING,
|
||||
},
|
||||
{
|
||||
type: LOGOUT_REDIRECT,
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
|
||||
return store.dispatch(logout('/auth/access_token')).then(() => {
|
||||
expect(window.location.assign.mock.calls[0][0]).toBe(
|
||||
'http://example.com/cas/logout',
|
||||
);
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it('should dispatch logout failure', () => {
|
||||
fetchMock.deleteOnce('/api/v2/auth/access_token', {
|
||||
status: 500,
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(logout('/auth/access_token')).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(LOGOUT_PENDING);
|
||||
expect(actions[1].type).toEqual(LOGOUT_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('auth selectors', () => {
|
||||
const error = new Error('yo it failed');
|
||||
|
||||
it('should return true if me exist and login Link does not exist', () => {
|
||||
expect(
|
||||
isAuthenticated({
|
||||
auth: {
|
||||
me,
|
||||
},
|
||||
indexResources: {
|
||||
links: {},
|
||||
},
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if me exist and login Link does exist', () => {
|
||||
expect(
|
||||
isAuthenticated({
|
||||
auth: {
|
||||
me,
|
||||
},
|
||||
indexResources: {
|
||||
links: {
|
||||
login: {
|
||||
href: 'login.href',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return me', () => {
|
||||
expect(
|
||||
getMe({
|
||||
auth: {
|
||||
me,
|
||||
},
|
||||
}),
|
||||
).toBe(me);
|
||||
});
|
||||
|
||||
it('should return undefined, if me is not set', () => {
|
||||
expect(getMe({})).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return true, if FETCH_ME is pending', () => {
|
||||
expect(
|
||||
isFetchMePending({
|
||||
pending: {
|
||||
[FETCH_ME]: true,
|
||||
},
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false, if FETCH_ME is not in pending state', () => {
|
||||
expect(
|
||||
isFetchMePending({
|
||||
pending: {},
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true, if LOGIN is pending', () => {
|
||||
expect(
|
||||
isLoginPending({
|
||||
pending: {
|
||||
[LOGIN]: true,
|
||||
},
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false, if LOGIN is not in pending state', () => {
|
||||
expect(
|
||||
isLoginPending({
|
||||
pending: {},
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true, if LOGOUT is pending', () => {
|
||||
expect(
|
||||
isLogoutPending({
|
||||
pending: {
|
||||
[LOGOUT]: true,
|
||||
},
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false, if LOGOUT is not in pending state', () => {
|
||||
expect(
|
||||
isLogoutPending({
|
||||
pending: {},
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return the error, if failure state is set for FETCH_ME', () => {
|
||||
expect(
|
||||
getFetchMeFailure({
|
||||
failure: {
|
||||
[FETCH_ME]: error,
|
||||
},
|
||||
}),
|
||||
).toBe(error);
|
||||
});
|
||||
|
||||
it('should return unknown, if failure state is not set for FETCH_ME', () => {
|
||||
expect(getFetchMeFailure({})).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return the error, if failure state is set for LOGIN', () => {
|
||||
expect(
|
||||
getLoginFailure({
|
||||
failure: {
|
||||
[LOGIN]: error,
|
||||
},
|
||||
}),
|
||||
).toBe(error);
|
||||
});
|
||||
|
||||
it('should return unknown, if failure state is not set for LOGIN', () => {
|
||||
expect(getLoginFailure({})).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return the error, if failure state is set for LOGOUT', () => {
|
||||
expect(
|
||||
getLogoutFailure({
|
||||
failure: {
|
||||
[LOGOUT]: error,
|
||||
},
|
||||
}),
|
||||
).toBe(error);
|
||||
});
|
||||
|
||||
it('should return unknown, if failure state is not set for LOGOUT', () => {
|
||||
expect(getLogoutFailure({})).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return false, if redirecting is not set', () => {
|
||||
expect(isRedirecting({})).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false, if redirecting is false', () => {
|
||||
expect(
|
||||
isRedirecting({
|
||||
auth: {
|
||||
redirecting: false,
|
||||
},
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true, if redirecting is true', () => {
|
||||
expect(
|
||||
isRedirecting({
|
||||
auth: {
|
||||
redirecting: true,
|
||||
},
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,32 +1,31 @@
|
||||
// @flow
|
||||
import type { Me } from "@scm-manager/ui-types";
|
||||
import * as types from "./types";
|
||||
import { Me } from '@scm-manager/ui-types';
|
||||
import * as types from './types';
|
||||
|
||||
import { apiClient, UnauthorizedError } from "@scm-manager/ui-components";
|
||||
import { isPending } from "./pending";
|
||||
import { getFailure } from "./failure";
|
||||
import { apiClient, UnauthorizedError } from '@scm-manager/ui-components';
|
||||
import { isPending } from './pending';
|
||||
import { getFailure } from './failure';
|
||||
import {
|
||||
callFetchIndexResources,
|
||||
fetchIndexResources,
|
||||
fetchIndexResourcesPending,
|
||||
fetchIndexResourcesSuccess,
|
||||
getLoginLink
|
||||
} from "./indexResource";
|
||||
getLoginLink,
|
||||
} from './indexResource';
|
||||
|
||||
// Action
|
||||
|
||||
export const LOGIN = "scm/auth/LOGIN";
|
||||
export const LOGIN = 'scm/auth/LOGIN';
|
||||
export const LOGIN_PENDING = `${LOGIN}_${types.PENDING_SUFFIX}`;
|
||||
export const LOGIN_SUCCESS = `${LOGIN}_${types.SUCCESS_SUFFIX}`;
|
||||
export const LOGIN_FAILURE = `${LOGIN}_${types.FAILURE_SUFFIX}`;
|
||||
|
||||
export const FETCH_ME = "scm/auth/FETCH_ME";
|
||||
export const FETCH_ME = 'scm/auth/FETCH_ME';
|
||||
export const FETCH_ME_PENDING = `${FETCH_ME}_${types.PENDING_SUFFIX}`;
|
||||
export const FETCH_ME_SUCCESS = `${FETCH_ME}_${types.SUCCESS_SUFFIX}`;
|
||||
export const FETCH_ME_FAILURE = `${FETCH_ME}_${types.FAILURE_SUFFIX}`;
|
||||
export const FETCH_ME_UNAUTHORIZED = `${FETCH_ME}_UNAUTHORIZED`;
|
||||
|
||||
export const LOGOUT = "scm/auth/LOGOUT";
|
||||
export const LOGOUT = 'scm/auth/LOGOUT';
|
||||
export const LOGOUT_PENDING = `${LOGOUT}_${types.PENDING_SUFFIX}`;
|
||||
export const LOGOUT_SUCCESS = `${LOGOUT}_${types.SUCCESS_SUFFIX}`;
|
||||
export const LOGOUT_FAILURE = `${LOGOUT}_${types.FAILURE_SUFFIX}`;
|
||||
@@ -37,19 +36,21 @@ export const LOGOUT_REDIRECT = `${LOGOUT}_REDIRECT`;
|
||||
const initialState = {};
|
||||
|
||||
export default function reducer(
|
||||
state: Object = initialState,
|
||||
action: Object = { type: "UNKNOWN" }
|
||||
state: object = initialState,
|
||||
action: object = {
|
||||
type: 'UNKNOWN',
|
||||
},
|
||||
) {
|
||||
switch (action.type) {
|
||||
case LOGIN_SUCCESS:
|
||||
case FETCH_ME_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
me: action.payload
|
||||
me: action.payload,
|
||||
};
|
||||
case FETCH_ME_UNAUTHORIZED:
|
||||
return {
|
||||
me: {}
|
||||
me: {},
|
||||
};
|
||||
case LOGOUT_SUCCESS:
|
||||
return initialState;
|
||||
@@ -58,7 +59,7 @@ export default function reducer(
|
||||
// we keep the current state until we are redirected to the new page
|
||||
return {
|
||||
...state,
|
||||
redirecting: true
|
||||
redirecting: true,
|
||||
};
|
||||
}
|
||||
default:
|
||||
@@ -70,73 +71,73 @@ export default function reducer(
|
||||
|
||||
export const loginPending = () => {
|
||||
return {
|
||||
type: LOGIN_PENDING
|
||||
type: LOGIN_PENDING,
|
||||
};
|
||||
};
|
||||
|
||||
export const loginSuccess = (me: Me) => {
|
||||
return {
|
||||
type: LOGIN_SUCCESS,
|
||||
payload: me
|
||||
payload: me,
|
||||
};
|
||||
};
|
||||
|
||||
export const loginFailure = (error: Error) => {
|
||||
return {
|
||||
type: LOGIN_FAILURE,
|
||||
payload: error
|
||||
payload: error,
|
||||
};
|
||||
};
|
||||
|
||||
export const logoutPending = () => {
|
||||
return {
|
||||
type: LOGOUT_PENDING
|
||||
type: LOGOUT_PENDING,
|
||||
};
|
||||
};
|
||||
|
||||
export const logoutSuccess = () => {
|
||||
return {
|
||||
type: LOGOUT_SUCCESS
|
||||
type: LOGOUT_SUCCESS,
|
||||
};
|
||||
};
|
||||
|
||||
export const redirectAfterLogout = () => {
|
||||
return {
|
||||
type: LOGOUT_REDIRECT
|
||||
type: LOGOUT_REDIRECT,
|
||||
};
|
||||
};
|
||||
|
||||
export const logoutFailure = (error: Error) => {
|
||||
return {
|
||||
type: LOGOUT_FAILURE,
|
||||
payload: error
|
||||
payload: error,
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchMePending = () => {
|
||||
return {
|
||||
type: FETCH_ME_PENDING
|
||||
type: FETCH_ME_PENDING,
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchMeSuccess = (me: Me) => {
|
||||
return {
|
||||
type: FETCH_ME_SUCCESS,
|
||||
payload: me
|
||||
payload: me,
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchMeUnauthenticated = () => {
|
||||
return {
|
||||
type: FETCH_ME_UNAUTHORIZED,
|
||||
resetPending: true
|
||||
resetPending: true,
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchMeFailure = (error: Error) => {
|
||||
return {
|
||||
type: FETCH_ME_FAILURE,
|
||||
payload: error
|
||||
payload: error,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -151,13 +152,13 @@ const callFetchMe = (link: string): Promise<Me> => {
|
||||
export const login = (
|
||||
loginLink: string,
|
||||
username: string,
|
||||
password: string
|
||||
password: string,
|
||||
) => {
|
||||
const login_data = {
|
||||
cookie: true,
|
||||
grant_type: "password",
|
||||
grant_type: 'password',
|
||||
username,
|
||||
password
|
||||
password,
|
||||
};
|
||||
return function(dispatch: any) {
|
||||
dispatch(loginPending());
|
||||
@@ -234,45 +235,45 @@ export const logout = (link: string) => {
|
||||
|
||||
// selectors
|
||||
|
||||
const stateAuth = (state: Object): Object => {
|
||||
const stateAuth = (state: object): object => {
|
||||
return state.auth || {};
|
||||
};
|
||||
|
||||
export const isAuthenticated = (state: Object) => {
|
||||
export const isAuthenticated = (state: object) => {
|
||||
if (state.auth.me && !getLoginLink(state)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const getMe = (state: Object): Me => {
|
||||
export const getMe = (state: object): Me => {
|
||||
return stateAuth(state).me;
|
||||
};
|
||||
|
||||
export const isFetchMePending = (state: Object) => {
|
||||
export const isFetchMePending = (state: object) => {
|
||||
return isPending(state, FETCH_ME);
|
||||
};
|
||||
|
||||
export const getFetchMeFailure = (state: Object) => {
|
||||
export const getFetchMeFailure = (state: object) => {
|
||||
return getFailure(state, FETCH_ME);
|
||||
};
|
||||
|
||||
export const isLoginPending = (state: Object) => {
|
||||
export const isLoginPending = (state: object) => {
|
||||
return isPending(state, LOGIN);
|
||||
};
|
||||
|
||||
export const getLoginFailure = (state: Object) => {
|
||||
export const getLoginFailure = (state: object) => {
|
||||
return getFailure(state, LOGIN);
|
||||
};
|
||||
|
||||
export const isLogoutPending = (state: Object) => {
|
||||
export const isLogoutPending = (state: object) => {
|
||||
return isPending(state, LOGOUT);
|
||||
};
|
||||
|
||||
export const getLogoutFailure = (state: Object) => {
|
||||
export const getLogoutFailure = (state: object) => {
|
||||
return getFailure(state, LOGOUT);
|
||||
};
|
||||
|
||||
export const isRedirecting = (state: Object) => {
|
||||
export const isRedirecting = (state: object) => {
|
||||
return !!stateAuth(state).redirecting;
|
||||
};
|
||||
@@ -1,16 +0,0 @@
|
||||
// @flow
|
||||
import { apiClient } from "@scm-manager/ui-components";
|
||||
|
||||
export const CONTENT_TYPE_PASSWORD_CHANGE =
|
||||
"application/vnd.scmm-passwordChange+json;v=2";
|
||||
export function changePassword(
|
||||
url: string,
|
||||
oldPassword: string,
|
||||
newPassword: string
|
||||
) {
|
||||
return apiClient
|
||||
.put(url, { oldPassword, newPassword }, CONTENT_TYPE_PASSWORD_CHANGE)
|
||||
.then(response => {
|
||||
return response;
|
||||
});
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import fetchMock from "fetch-mock";
|
||||
import { changePassword, CONTENT_TYPE_PASSWORD_CHANGE } from "./changePassword";
|
||||
|
||||
describe("change password", () => {
|
||||
const CHANGE_PASSWORD_URL = "/me/password";
|
||||
const oldPassword = "old";
|
||||
const newPassword = "new";
|
||||
|
||||
afterEach(() => {
|
||||
fetchMock.reset();
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
it("should update password", done => {
|
||||
fetchMock.put("/api/v2" + CHANGE_PASSWORD_URL, 204, {
|
||||
headers: { "content-type": CONTENT_TYPE_PASSWORD_CHANGE }
|
||||
});
|
||||
|
||||
changePassword(CHANGE_PASSWORD_URL, oldPassword, newPassword).then(
|
||||
content => {
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
27
scm-ui/ui-webapp/src/modules/changePassword.test.ts
Normal file
27
scm-ui/ui-webapp/src/modules/changePassword.test.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { changePassword, CONTENT_TYPE_PASSWORD_CHANGE } from './changePassword';
|
||||
|
||||
describe('change password', () => {
|
||||
const CHANGE_PASSWORD_URL = '/me/password';
|
||||
const oldPassword = 'old';
|
||||
const newPassword = 'new';
|
||||
|
||||
afterEach(() => {
|
||||
fetchMock.reset();
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
it('should update password', done => {
|
||||
fetchMock.put('/api/v2' + CHANGE_PASSWORD_URL, 204, {
|
||||
headers: {
|
||||
'content-type': CONTENT_TYPE_PASSWORD_CHANGE,
|
||||
},
|
||||
});
|
||||
|
||||
changePassword(CHANGE_PASSWORD_URL, oldPassword, newPassword).then(
|
||||
content => {
|
||||
done();
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
22
scm-ui/ui-webapp/src/modules/changePassword.ts
Normal file
22
scm-ui/ui-webapp/src/modules/changePassword.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { apiClient } from '@scm-manager/ui-components';
|
||||
|
||||
export const CONTENT_TYPE_PASSWORD_CHANGE =
|
||||
'application/vnd.scmm-passwordChange+json;v=2';
|
||||
export function changePassword(
|
||||
url: string,
|
||||
oldPassword: string,
|
||||
newPassword: string,
|
||||
) {
|
||||
return apiClient
|
||||
.put(
|
||||
url,
|
||||
{
|
||||
oldPassword,
|
||||
newPassword,
|
||||
},
|
||||
CONTENT_TYPE_PASSWORD_CHANGE,
|
||||
)
|
||||
.then(response => {
|
||||
return response;
|
||||
});
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
// @flow
|
||||
import reducer, { getFailure } from "./failure";
|
||||
|
||||
const err = new Error("something failed");
|
||||
const otherErr = new Error("something else failed");
|
||||
|
||||
describe("failure reducer", () => {
|
||||
it("should set the error for FETCH_ITEMS", () => {
|
||||
const newState = reducer({}, { type: "FETCH_ITEMS_FAILURE", payload: err });
|
||||
expect(newState["FETCH_ITEMS"]).toBe(err);
|
||||
});
|
||||
|
||||
it("should do nothing for unknown action types", () => {
|
||||
const state = {};
|
||||
const newState = reducer(state, { type: "UNKNOWN" });
|
||||
expect(newState).toBe(state);
|
||||
});
|
||||
|
||||
it("should set the error for FETCH_ITEMS, if payload has multiple values", () => {
|
||||
const newState = reducer(
|
||||
{},
|
||||
{
|
||||
type: "FETCH_ITEMS_FAILURE",
|
||||
payload: { something: "something", error: err }
|
||||
}
|
||||
);
|
||||
expect(newState["FETCH_ITEMS"]).toBe(err);
|
||||
});
|
||||
|
||||
it("should set the error for FETCH_ITEMS, but should not affect others", () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
FETCH_USERS: otherErr
|
||||
},
|
||||
{ type: "FETCH_ITEMS_FAILURE", payload: err }
|
||||
);
|
||||
expect(newState["FETCH_ITEMS"]).toBe(err);
|
||||
expect(newState["FETCH_USERS"]).toBe(otherErr);
|
||||
});
|
||||
|
||||
it("should reset FETCH_ITEMS after FETCH_ITEMS_SUCCESS", () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
FETCH_ITEMS: err
|
||||
},
|
||||
{ type: "FETCH_ITEMS_SUCCESS" }
|
||||
);
|
||||
expect(newState["FETCH_ITEMS"]).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should reset FETCH_ITEMS after FETCH_ITEMS_RESET", () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
FETCH_ITEMS: err
|
||||
},
|
||||
{ type: "FETCH_ITEMS_RESET" }
|
||||
);
|
||||
expect(newState["FETCH_ITEMS"]).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should reset FETCH_ITEMS after FETCH_ITEMS_RESET, but should not affect others", () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
FETCH_ITEMS: err,
|
||||
FETCH_USERS: err
|
||||
},
|
||||
{ type: "FETCH_ITEMS_RESET" }
|
||||
);
|
||||
expect(newState["FETCH_ITEMS"]).toBeFalsy();
|
||||
expect(newState["FETCH_USERS"]).toBe(err);
|
||||
});
|
||||
|
||||
it("should set the error for a single item of FETCH_ITEM", () => {
|
||||
const newState = reducer(
|
||||
{},
|
||||
{ type: "FETCH_ITEM_FAILURE", payload: err, itemId: 42 }
|
||||
);
|
||||
expect(newState["FETCH_ITEM/42"]).toBe(err);
|
||||
});
|
||||
|
||||
it("should reset error for a single item of FETCH_ITEM", () => {
|
||||
const newState = reducer(
|
||||
{ "FETCH_ITEM/42": err },
|
||||
{ type: "FETCH_ITEM_SUCCESS", payload: err, itemId: 42 }
|
||||
);
|
||||
expect(newState["FETCH_ITEM/42"]).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("failure selector", () => {
|
||||
it("should return failure, if FETCH_ITEMS failure exists", () => {
|
||||
const failure = getFailure(
|
||||
{
|
||||
failure: {
|
||||
FETCH_ITEMS: err
|
||||
}
|
||||
},
|
||||
"FETCH_ITEMS"
|
||||
);
|
||||
expect(failure).toBe(err);
|
||||
});
|
||||
|
||||
it("should return undefined, if state has no failure", () => {
|
||||
const failure = getFailure({}, "FETCH_ITEMS");
|
||||
expect(failure).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should return undefined, if FETCH_ITEMS is not defined", () => {
|
||||
const failure = getFailure(
|
||||
{
|
||||
failure: {}
|
||||
},
|
||||
"FETCH_ITEMS"
|
||||
);
|
||||
expect(failure).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should return failure, if FETCH_ITEM 42 failure exists", () => {
|
||||
const failure = getFailure(
|
||||
{
|
||||
failure: {
|
||||
"FETCH_ITEM/42": err
|
||||
}
|
||||
},
|
||||
"FETCH_ITEM",
|
||||
42
|
||||
);
|
||||
expect(failure).toBe(err);
|
||||
});
|
||||
});
|
||||
159
scm-ui/ui-webapp/src/modules/failure.test.ts
Normal file
159
scm-ui/ui-webapp/src/modules/failure.test.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import reducer, { getFailure } from './failure';
|
||||
|
||||
const err = new Error('something failed');
|
||||
const otherErr = new Error('something else failed');
|
||||
|
||||
describe('failure reducer', () => {
|
||||
it('should set the error for FETCH_ITEMS', () => {
|
||||
const newState = reducer(
|
||||
{},
|
||||
{
|
||||
type: 'FETCH_ITEMS_FAILURE',
|
||||
payload: err,
|
||||
},
|
||||
);
|
||||
expect(newState['FETCH_ITEMS']).toBe(err);
|
||||
});
|
||||
|
||||
it('should do nothing for unknown action types', () => {
|
||||
const state = {};
|
||||
const newState = reducer(state, {
|
||||
type: 'UNKNOWN',
|
||||
});
|
||||
expect(newState).toBe(state);
|
||||
});
|
||||
|
||||
it('should set the error for FETCH_ITEMS, if payload has multiple values', () => {
|
||||
const newState = reducer(
|
||||
{},
|
||||
{
|
||||
type: 'FETCH_ITEMS_FAILURE',
|
||||
payload: {
|
||||
something: 'something',
|
||||
error: err,
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(newState['FETCH_ITEMS']).toBe(err);
|
||||
});
|
||||
|
||||
it('should set the error for FETCH_ITEMS, but should not affect others', () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
FETCH_USERS: otherErr,
|
||||
},
|
||||
{
|
||||
type: 'FETCH_ITEMS_FAILURE',
|
||||
payload: err,
|
||||
},
|
||||
);
|
||||
expect(newState['FETCH_ITEMS']).toBe(err);
|
||||
expect(newState['FETCH_USERS']).toBe(otherErr);
|
||||
});
|
||||
|
||||
it('should reset FETCH_ITEMS after FETCH_ITEMS_SUCCESS', () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
FETCH_ITEMS: err,
|
||||
},
|
||||
{
|
||||
type: 'FETCH_ITEMS_SUCCESS',
|
||||
},
|
||||
);
|
||||
expect(newState['FETCH_ITEMS']).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should reset FETCH_ITEMS after FETCH_ITEMS_RESET', () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
FETCH_ITEMS: err,
|
||||
},
|
||||
{
|
||||
type: 'FETCH_ITEMS_RESET',
|
||||
},
|
||||
);
|
||||
expect(newState['FETCH_ITEMS']).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should reset FETCH_ITEMS after FETCH_ITEMS_RESET, but should not affect others', () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
FETCH_ITEMS: err,
|
||||
FETCH_USERS: err,
|
||||
},
|
||||
{
|
||||
type: 'FETCH_ITEMS_RESET',
|
||||
},
|
||||
);
|
||||
expect(newState['FETCH_ITEMS']).toBeFalsy();
|
||||
expect(newState['FETCH_USERS']).toBe(err);
|
||||
});
|
||||
|
||||
it('should set the error for a single item of FETCH_ITEM', () => {
|
||||
const newState = reducer(
|
||||
{},
|
||||
{
|
||||
type: 'FETCH_ITEM_FAILURE',
|
||||
payload: err,
|
||||
itemId: 42,
|
||||
},
|
||||
);
|
||||
expect(newState['FETCH_ITEM/42']).toBe(err);
|
||||
});
|
||||
|
||||
it('should reset error for a single item of FETCH_ITEM', () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
'FETCH_ITEM/42': err,
|
||||
},
|
||||
{
|
||||
type: 'FETCH_ITEM_SUCCESS',
|
||||
payload: err,
|
||||
itemId: 42,
|
||||
},
|
||||
);
|
||||
expect(newState['FETCH_ITEM/42']).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('failure selector', () => {
|
||||
it('should return failure, if FETCH_ITEMS failure exists', () => {
|
||||
const failure = getFailure(
|
||||
{
|
||||
failure: {
|
||||
FETCH_ITEMS: err,
|
||||
},
|
||||
},
|
||||
'FETCH_ITEMS',
|
||||
);
|
||||
expect(failure).toBe(err);
|
||||
});
|
||||
|
||||
it('should return undefined, if state has no failure', () => {
|
||||
const failure = getFailure({}, 'FETCH_ITEMS');
|
||||
expect(failure).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined, if FETCH_ITEMS is not defined', () => {
|
||||
const failure = getFailure(
|
||||
{
|
||||
failure: {},
|
||||
},
|
||||
'FETCH_ITEMS',
|
||||
);
|
||||
expect(failure).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return failure, if FETCH_ITEM 42 failure exists', () => {
|
||||
const failure = getFailure(
|
||||
{
|
||||
failure: {
|
||||
'FETCH_ITEM/42': err,
|
||||
},
|
||||
},
|
||||
'FETCH_ITEM',
|
||||
42,
|
||||
);
|
||||
expect(failure).toBe(err);
|
||||
});
|
||||
});
|
||||
@@ -1,22 +1,21 @@
|
||||
// @flow
|
||||
import type { Action } from "@scm-manager/ui-types";
|
||||
import { Action } from '@scm-manager/ui-types';
|
||||
|
||||
const FAILURE_SUFFIX = "_FAILURE";
|
||||
const FAILURE_SUFFIX = '_FAILURE';
|
||||
const RESET_PATTERN = /^(.*)_(SUCCESS|RESET)$/;
|
||||
|
||||
function extractIdentifierFromFailure(action: Action) {
|
||||
const type = action.type;
|
||||
let identifier = type.substring(0, type.length - FAILURE_SUFFIX.length);
|
||||
if (action.itemId) {
|
||||
identifier += "/" + action.itemId;
|
||||
identifier += '/' + action.itemId;
|
||||
}
|
||||
return identifier;
|
||||
}
|
||||
|
||||
function removeAllEntriesOfIdentifierFromState(
|
||||
state: Object,
|
||||
state: object,
|
||||
payload: any,
|
||||
identifier: string
|
||||
identifier: string,
|
||||
) {
|
||||
const newState = {};
|
||||
for (let failureType in state) {
|
||||
@@ -27,7 +26,7 @@ function removeAllEntriesOfIdentifierFromState(
|
||||
return newState;
|
||||
}
|
||||
|
||||
function removeFromState(state: Object, identifier: string) {
|
||||
function removeFromState(state: object, identifier: string) {
|
||||
const newState = {};
|
||||
for (let failureType in state) {
|
||||
if (failureType !== identifier) {
|
||||
@@ -38,9 +37,11 @@ function removeFromState(state: Object, identifier: string) {
|
||||
}
|
||||
|
||||
export default function reducer(
|
||||
state: Object = {},
|
||||
action: Action = { type: "UNKNOWN" }
|
||||
): Object {
|
||||
state: object = {},
|
||||
action: Action = {
|
||||
type: 'UNKNOWN',
|
||||
},
|
||||
): object {
|
||||
const type = action.type;
|
||||
if (type.endsWith(FAILURE_SUFFIX)) {
|
||||
const identifier = extractIdentifierFromFailure(action);
|
||||
@@ -52,17 +53,21 @@ export default function reducer(
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
[identifier]: payload
|
||||
[identifier]: payload,
|
||||
};
|
||||
} else {
|
||||
const match = RESET_PATTERN.exec(type);
|
||||
if (match) {
|
||||
let identifier = match[1];
|
||||
if (action.itemId) {
|
||||
identifier += "/" + action.itemId;
|
||||
identifier += '/' + action.itemId;
|
||||
}
|
||||
if (action.payload)
|
||||
return removeAllEntriesOfIdentifierFromState(state, action.payload, identifier);
|
||||
return removeAllEntriesOfIdentifierFromState(
|
||||
state,
|
||||
action.payload,
|
||||
identifier,
|
||||
);
|
||||
else return removeFromState(state, identifier);
|
||||
}
|
||||
}
|
||||
@@ -70,14 +75,14 @@ export default function reducer(
|
||||
}
|
||||
|
||||
export function getFailure(
|
||||
state: Object,
|
||||
state: object,
|
||||
actionType: string,
|
||||
itemId?: string | number
|
||||
itemId?: string | number,
|
||||
) {
|
||||
if (state.failure) {
|
||||
let identifier = actionType;
|
||||
if (itemId) {
|
||||
identifier += "/" + itemId;
|
||||
identifier += '/' + itemId;
|
||||
}
|
||||
return state.failure[identifier];
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
// @flow
|
||||
import * as types from "./types";
|
||||
|
||||
import { apiClient } from "@scm-manager/ui-components";
|
||||
import type { Action, IndexResources, Link } from "@scm-manager/ui-types";
|
||||
import { isPending } from "./pending";
|
||||
import { getFailure } from "./failure";
|
||||
|
||||
// Action
|
||||
|
||||
export const FETCH_INDEXRESOURCES = "scm/INDEXRESOURCES";
|
||||
export const FETCH_INDEXRESOURCES_PENDING = `${FETCH_INDEXRESOURCES}_${
|
||||
types.PENDING_SUFFIX
|
||||
}`;
|
||||
export const FETCH_INDEXRESOURCES_SUCCESS = `${FETCH_INDEXRESOURCES}_${
|
||||
types.SUCCESS_SUFFIX
|
||||
}`;
|
||||
export const FETCH_INDEXRESOURCES_FAILURE = `${FETCH_INDEXRESOURCES}_${
|
||||
types.FAILURE_SUFFIX
|
||||
}`;
|
||||
|
||||
const INDEX_RESOURCES_LINK = "/";
|
||||
|
||||
export const callFetchIndexResources = (): Promise<IndexResources> => {
|
||||
return apiClient.get(INDEX_RESOURCES_LINK).then(response => {
|
||||
return response.json();
|
||||
});
|
||||
};
|
||||
|
||||
export function fetchIndexResources() {
|
||||
return function(dispatch: any) {
|
||||
dispatch(fetchIndexResourcesPending());
|
||||
return callFetchIndexResources()
|
||||
.then(resources => {
|
||||
dispatch(fetchIndexResourcesSuccess(resources));
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch(fetchIndexResourcesFailure(err));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchIndexResourcesPending(): Action {
|
||||
return {
|
||||
type: FETCH_INDEXRESOURCES_PENDING
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchIndexResourcesSuccess(resources: IndexResources): Action {
|
||||
return {
|
||||
type: FETCH_INDEXRESOURCES_SUCCESS,
|
||||
payload: resources
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchIndexResourcesFailure(err: Error): Action {
|
||||
return {
|
||||
type: FETCH_INDEXRESOURCES_FAILURE,
|
||||
payload: err
|
||||
};
|
||||
}
|
||||
|
||||
// reducer
|
||||
export default function reducer(
|
||||
state: Object = {},
|
||||
action: Action = { type: "UNKNOWN" }
|
||||
): Object {
|
||||
if (!action.payload) {
|
||||
return state;
|
||||
}
|
||||
|
||||
switch (action.type) {
|
||||
case FETCH_INDEXRESOURCES_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
version: action.payload.version,
|
||||
links: action.payload._links
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
// selectors
|
||||
|
||||
export function isFetchIndexResourcesPending(state: Object) {
|
||||
return isPending(state, FETCH_INDEXRESOURCES);
|
||||
}
|
||||
|
||||
export function getFetchIndexResourcesFailure(state: Object) {
|
||||
return getFailure(state, FETCH_INDEXRESOURCES);
|
||||
}
|
||||
|
||||
export function getLinks(state: Object) {
|
||||
return state.indexResources.links;
|
||||
}
|
||||
|
||||
export function getLink(state: Object, name: string) {
|
||||
if (state.indexResources.links && state.indexResources.links[name]) {
|
||||
return state.indexResources.links[name].href;
|
||||
}
|
||||
}
|
||||
|
||||
export function getLinkCollection(state: Object, name: string): Link[] {
|
||||
if (state.indexResources.links && state.indexResources.links[name]) {
|
||||
return state.indexResources.links[name];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function getAppVersion(state: Object) {
|
||||
return state.indexResources.version;
|
||||
}
|
||||
|
||||
export function getUiPluginsLink(state: Object) {
|
||||
return getLink(state, "uiPlugins");
|
||||
}
|
||||
|
||||
export function getAvailablePluginsLink(state: Object) {
|
||||
return getLink(state, "availablePlugins");
|
||||
}
|
||||
|
||||
export function getInstalledPluginsLink(state: Object) {
|
||||
return getLink(state, "installedPlugins");
|
||||
}
|
||||
|
||||
export function getPendingPluginsLink(state: Object) {
|
||||
return getLink(state, "pendingPlugins");
|
||||
}
|
||||
|
||||
export function getMeLink(state: Object) {
|
||||
return getLink(state, "me");
|
||||
}
|
||||
|
||||
export function getLogoutLink(state: Object) {
|
||||
return getLink(state, "logout");
|
||||
}
|
||||
|
||||
export function getLoginLink(state: Object) {
|
||||
return getLink(state, "login");
|
||||
}
|
||||
|
||||
export function getUsersLink(state: Object) {
|
||||
return getLink(state, "users");
|
||||
}
|
||||
|
||||
export function getRepositoryRolesLink(state: Object) {
|
||||
return getLink(state, "repositoryRoles");
|
||||
}
|
||||
|
||||
export function getRepositoryVerbsLink(state: Object) {
|
||||
return getLink(state, "repositoryVerbs");
|
||||
}
|
||||
|
||||
export function getGroupsLink(state: Object) {
|
||||
return getLink(state, "groups");
|
||||
}
|
||||
|
||||
export function getConfigLink(state: Object) {
|
||||
return getLink(state, "config");
|
||||
}
|
||||
|
||||
export function getRepositoriesLink(state: Object) {
|
||||
return getLink(state, "repositories");
|
||||
}
|
||||
|
||||
export function getHgConfigLink(state: Object) {
|
||||
return getLink(state, "hgConfig");
|
||||
}
|
||||
|
||||
export function getGitConfigLink(state: Object) {
|
||||
return getLink(state, "gitConfig");
|
||||
}
|
||||
|
||||
export function getSvnConfigLink(state: Object) {
|
||||
return getLink(state, "svnConfig");
|
||||
}
|
||||
|
||||
export function getLoginInfoLink(state: Object) {
|
||||
return getLink(state, "loginInfo");
|
||||
}
|
||||
|
||||
export function getUserAutoCompleteLink(state: Object): string {
|
||||
const link = getLinkCollection(state, "autocomplete").find(
|
||||
i => i.name === "users"
|
||||
);
|
||||
if (link) {
|
||||
return link.href;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export function getGroupAutoCompleteLink(state: Object): string {
|
||||
const link = getLinkCollection(state, "autocomplete").find(
|
||||
i => i.name === "groups"
|
||||
);
|
||||
if (link) {
|
||||
return link.href;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@@ -1,480 +0,0 @@
|
||||
import configureMockStore from "redux-mock-store";
|
||||
import thunk from "redux-thunk";
|
||||
import fetchMock from "fetch-mock";
|
||||
import reducer, {
|
||||
FETCH_INDEXRESOURCES_PENDING,
|
||||
FETCH_INDEXRESOURCES_SUCCESS,
|
||||
FETCH_INDEXRESOURCES_FAILURE,
|
||||
fetchIndexResources,
|
||||
fetchIndexResourcesSuccess,
|
||||
FETCH_INDEXRESOURCES,
|
||||
isFetchIndexResourcesPending,
|
||||
getFetchIndexResourcesFailure,
|
||||
getUiPluginsLink,
|
||||
getMeLink,
|
||||
getLogoutLink,
|
||||
getLoginLink,
|
||||
getUsersLink,
|
||||
getConfigLink,
|
||||
getRepositoriesLink,
|
||||
getHgConfigLink,
|
||||
getGitConfigLink,
|
||||
getSvnConfigLink,
|
||||
getLinks,
|
||||
getGroupsLink,
|
||||
getLinkCollection,
|
||||
getUserAutoCompleteLink,
|
||||
getGroupAutoCompleteLink
|
||||
} from "./indexResource";
|
||||
|
||||
const indexResourcesUnauthenticated = {
|
||||
version: "2.0.0-SNAPSHOT",
|
||||
_links: {
|
||||
self: {
|
||||
href: "http://localhost:8081/scm/api/v2/"
|
||||
},
|
||||
uiPlugins: {
|
||||
href: "http://localhost:8081/scm/api/v2/ui/plugins"
|
||||
},
|
||||
login: {
|
||||
href: "http://localhost:8081/scm/api/v2/auth/access_token"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const indexResourcesAuthenticated = {
|
||||
version: "2.0.0-SNAPSHOT",
|
||||
_links: {
|
||||
self: {
|
||||
href: "http://localhost:8081/scm/api/v2/"
|
||||
},
|
||||
uiPlugins: {
|
||||
href: "http://localhost:8081/scm/api/v2/ui/plugins"
|
||||
},
|
||||
me: {
|
||||
href: "http://localhost:8081/scm/api/v2/me/"
|
||||
},
|
||||
logout: {
|
||||
href: "http://localhost:8081/scm/api/v2/auth/access_token"
|
||||
},
|
||||
users: {
|
||||
href: "http://localhost:8081/scm/api/v2/users/"
|
||||
},
|
||||
groups: {
|
||||
href: "http://localhost:8081/scm/api/v2/groups/"
|
||||
},
|
||||
config: {
|
||||
href: "http://localhost:8081/scm/api/v2/config"
|
||||
},
|
||||
repositories: {
|
||||
href: "http://localhost:8081/scm/api/v2/repositories/"
|
||||
},
|
||||
hgConfig: {
|
||||
href: "http://localhost:8081/scm/api/v2/config/hg"
|
||||
},
|
||||
gitConfig: {
|
||||
href: "http://localhost:8081/scm/api/v2/config/git"
|
||||
},
|
||||
svnConfig: {
|
||||
href: "http://localhost:8081/scm/api/v2/config/svn"
|
||||
},
|
||||
autocomplete: [
|
||||
{
|
||||
href: "http://localhost:8081/scm/api/v2/autocomplete/users",
|
||||
name: "users"
|
||||
},
|
||||
{
|
||||
href: "http://localhost:8081/scm/api/v2/autocomplete/groups",
|
||||
name: "groups"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
describe("index resource", () => {
|
||||
describe("fetch index resource", () => {
|
||||
const index_url = "/api/v2/";
|
||||
const mockStore = configureMockStore([thunk]);
|
||||
|
||||
afterEach(() => {
|
||||
fetchMock.reset();
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
it("should successfully fetch index resources when unauthenticated", () => {
|
||||
fetchMock.getOnce(index_url, indexResourcesUnauthenticated);
|
||||
|
||||
const expectedActions = [
|
||||
{ type: FETCH_INDEXRESOURCES_PENDING },
|
||||
{
|
||||
type: FETCH_INDEXRESOURCES_SUCCESS,
|
||||
payload: indexResourcesUnauthenticated
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchIndexResources()).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it("should successfully fetch index resources when authenticated", () => {
|
||||
fetchMock.getOnce(index_url, indexResourcesAuthenticated);
|
||||
|
||||
const expectedActions = [
|
||||
{ type: FETCH_INDEXRESOURCES_PENDING },
|
||||
{
|
||||
type: FETCH_INDEXRESOURCES_SUCCESS,
|
||||
payload: indexResourcesAuthenticated
|
||||
}
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchIndexResources()).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch FETCH_INDEX_RESOURCES_FAILURE if request fails", () => {
|
||||
fetchMock.getOnce(index_url, {
|
||||
status: 500
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchIndexResources()).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(FETCH_INDEXRESOURCES_PENDING);
|
||||
expect(actions[1].type).toEqual(FETCH_INDEXRESOURCES_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("index resources reducer", () => {
|
||||
it("should return empty object, if state and action is undefined", () => {
|
||||
expect(reducer()).toEqual({});
|
||||
});
|
||||
|
||||
it("should return the same state, if the action is undefined", () => {
|
||||
const state = { x: true };
|
||||
expect(reducer(state)).toBe(state);
|
||||
});
|
||||
|
||||
it("should return the same state, if the action is unknown to the reducer", () => {
|
||||
const state = { x: true };
|
||||
expect(reducer(state, { type: "EL_SPECIALE" })).toBe(state);
|
||||
});
|
||||
|
||||
it("should store the index resources on FETCH_INDEXRESOURCES_SUCCESS", () => {
|
||||
const newState = reducer(
|
||||
{},
|
||||
fetchIndexResourcesSuccess(indexResourcesAuthenticated)
|
||||
);
|
||||
expect(newState.links).toBe(indexResourcesAuthenticated._links);
|
||||
});
|
||||
});
|
||||
|
||||
describe("index resources selectors", () => {
|
||||
const error = new Error("something goes wrong");
|
||||
|
||||
it("should return true, when fetch index resources is pending", () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[FETCH_INDEXRESOURCES]: true
|
||||
}
|
||||
};
|
||||
expect(isFetchIndexResourcesPending(state)).toEqual(true);
|
||||
});
|
||||
|
||||
it("should return false, when fetch index resources is not pending", () => {
|
||||
expect(isFetchIndexResourcesPending({})).toEqual(false);
|
||||
});
|
||||
|
||||
it("should return error when fetch index resources did fail", () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[FETCH_INDEXRESOURCES]: error
|
||||
}
|
||||
};
|
||||
expect(getFetchIndexResourcesFailure(state)).toEqual(error);
|
||||
});
|
||||
|
||||
it("should return undefined when fetch index resources did not fail", () => {
|
||||
expect(getFetchIndexResourcesFailure({})).toBe(undefined);
|
||||
});
|
||||
|
||||
it("should return all links", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getLinks(state)).toBe(indexResourcesAuthenticated._links);
|
||||
});
|
||||
|
||||
// ui plugins link
|
||||
it("should return ui plugins link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getUiPluginsLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/ui/plugins"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return ui plugins links when unauthenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getUiPluginsLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/ui/plugins"
|
||||
);
|
||||
});
|
||||
|
||||
// me link
|
||||
it("should return me link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getMeLink(state)).toBe("http://localhost:8081/scm/api/v2/me/");
|
||||
});
|
||||
|
||||
it("should return undefined for me link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getMeLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// logout link
|
||||
it("should return logout link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getLogoutLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/auth/access_token"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return undefined for logout link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getLogoutLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// login link
|
||||
it("should return login link when unauthenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getLoginLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/auth/access_token"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return undefined for login link when authenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getLoginLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// users link
|
||||
it("should return users link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getUsersLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/users/"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return undefined for users link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getUsersLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// groups link
|
||||
it("should return groups link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getGroupsLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/groups/"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return undefined for groups link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getGroupsLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// config link
|
||||
it("should return config link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getConfigLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/config"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return undefined for config link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getConfigLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// repositories link
|
||||
it("should return repositories link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getRepositoriesLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/repositories/"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return config for repositories link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getRepositoriesLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// hgConfig link
|
||||
it("should return hgConfig link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getHgConfigLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/config/hg"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return config for hgConfig link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getHgConfigLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// gitConfig link
|
||||
it("should return gitConfig link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getGitConfigLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/config/git"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return config for gitConfig link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getGitConfigLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// svnConfig link
|
||||
it("should return svnConfig link when authenticated and has permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getSvnConfigLink(state)).toBe(
|
||||
"http://localhost:8081/scm/api/v2/config/svn"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return config for svnConfig link when unauthenticated or has not permission to see it", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getSvnConfigLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// Autocomplete links
|
||||
it("should return link collection", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getLinkCollection(state, "autocomplete")).toEqual(
|
||||
indexResourcesAuthenticated._links.autocomplete
|
||||
);
|
||||
});
|
||||
|
||||
it("should return user autocomplete link", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getUserAutoCompleteLink(state)).toEqual(
|
||||
"http://localhost:8081/scm/api/v2/autocomplete/users"
|
||||
);
|
||||
});
|
||||
|
||||
it("should return group autocomplete link", () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links
|
||||
}
|
||||
};
|
||||
expect(getGroupAutoCompleteLink(state)).toEqual(
|
||||
"http://localhost:8081/scm/api/v2/autocomplete/groups"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
492
scm-ui/ui-webapp/src/modules/indexResource.test.ts
Normal file
492
scm-ui/ui-webapp/src/modules/indexResource.test.ts
Normal file
@@ -0,0 +1,492 @@
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import reducer, {
|
||||
FETCH_INDEXRESOURCES_PENDING,
|
||||
FETCH_INDEXRESOURCES_SUCCESS,
|
||||
FETCH_INDEXRESOURCES_FAILURE,
|
||||
fetchIndexResources,
|
||||
fetchIndexResourcesSuccess,
|
||||
FETCH_INDEXRESOURCES,
|
||||
isFetchIndexResourcesPending,
|
||||
getFetchIndexResourcesFailure,
|
||||
getUiPluginsLink,
|
||||
getMeLink,
|
||||
getLogoutLink,
|
||||
getLoginLink,
|
||||
getUsersLink,
|
||||
getConfigLink,
|
||||
getRepositoriesLink,
|
||||
getHgConfigLink,
|
||||
getGitConfigLink,
|
||||
getSvnConfigLink,
|
||||
getLinks,
|
||||
getGroupsLink,
|
||||
getLinkCollection,
|
||||
getUserAutoCompleteLink,
|
||||
getGroupAutoCompleteLink,
|
||||
} from './indexResource';
|
||||
|
||||
const indexResourcesUnauthenticated = {
|
||||
version: '2.0.0-SNAPSHOT',
|
||||
_links: {
|
||||
self: {
|
||||
href: 'http://localhost:8081/scm/api/v2/',
|
||||
},
|
||||
uiPlugins: {
|
||||
href: 'http://localhost:8081/scm/api/v2/ui/plugins',
|
||||
},
|
||||
login: {
|
||||
href: 'http://localhost:8081/scm/api/v2/auth/access_token',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const indexResourcesAuthenticated = {
|
||||
version: '2.0.0-SNAPSHOT',
|
||||
_links: {
|
||||
self: {
|
||||
href: 'http://localhost:8081/scm/api/v2/',
|
||||
},
|
||||
uiPlugins: {
|
||||
href: 'http://localhost:8081/scm/api/v2/ui/plugins',
|
||||
},
|
||||
me: {
|
||||
href: 'http://localhost:8081/scm/api/v2/me/',
|
||||
},
|
||||
logout: {
|
||||
href: 'http://localhost:8081/scm/api/v2/auth/access_token',
|
||||
},
|
||||
users: {
|
||||
href: 'http://localhost:8081/scm/api/v2/users/',
|
||||
},
|
||||
groups: {
|
||||
href: 'http://localhost:8081/scm/api/v2/groups/',
|
||||
},
|
||||
config: {
|
||||
href: 'http://localhost:8081/scm/api/v2/config',
|
||||
},
|
||||
repositories: {
|
||||
href: 'http://localhost:8081/scm/api/v2/repositories/',
|
||||
},
|
||||
hgConfig: {
|
||||
href: 'http://localhost:8081/scm/api/v2/config/hg',
|
||||
},
|
||||
gitConfig: {
|
||||
href: 'http://localhost:8081/scm/api/v2/config/git',
|
||||
},
|
||||
svnConfig: {
|
||||
href: 'http://localhost:8081/scm/api/v2/config/svn',
|
||||
},
|
||||
autocomplete: [
|
||||
{
|
||||
href: 'http://localhost:8081/scm/api/v2/autocomplete/users',
|
||||
name: 'users',
|
||||
},
|
||||
{
|
||||
href: 'http://localhost:8081/scm/api/v2/autocomplete/groups',
|
||||
name: 'groups',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
describe('index resource', () => {
|
||||
describe('fetch index resource', () => {
|
||||
const index_url = '/api/v2/';
|
||||
const mockStore = configureMockStore([thunk]);
|
||||
|
||||
afterEach(() => {
|
||||
fetchMock.reset();
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
it('should successfully fetch index resources when unauthenticated', () => {
|
||||
fetchMock.getOnce(index_url, indexResourcesUnauthenticated);
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_INDEXRESOURCES_PENDING,
|
||||
},
|
||||
{
|
||||
type: FETCH_INDEXRESOURCES_SUCCESS,
|
||||
payload: indexResourcesUnauthenticated,
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchIndexResources()).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully fetch index resources when authenticated', () => {
|
||||
fetchMock.getOnce(index_url, indexResourcesAuthenticated);
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: FETCH_INDEXRESOURCES_PENDING,
|
||||
},
|
||||
{
|
||||
type: FETCH_INDEXRESOURCES_SUCCESS,
|
||||
payload: indexResourcesAuthenticated,
|
||||
},
|
||||
];
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchIndexResources()).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
it('should dispatch FETCH_INDEX_RESOURCES_FAILURE if request fails', () => {
|
||||
fetchMock.getOnce(index_url, {
|
||||
status: 500,
|
||||
});
|
||||
|
||||
const store = mockStore({});
|
||||
return store.dispatch(fetchIndexResources()).then(() => {
|
||||
const actions = store.getActions();
|
||||
expect(actions[0].type).toEqual(FETCH_INDEXRESOURCES_PENDING);
|
||||
expect(actions[1].type).toEqual(FETCH_INDEXRESOURCES_FAILURE);
|
||||
expect(actions[1].payload).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('index resources reducer', () => {
|
||||
it('should return empty object, if state and action is undefined', () => {
|
||||
expect(reducer()).toEqual({});
|
||||
});
|
||||
|
||||
it('should return the same state, if the action is undefined', () => {
|
||||
const state = {
|
||||
x: true,
|
||||
};
|
||||
expect(reducer(state)).toBe(state);
|
||||
});
|
||||
|
||||
it('should return the same state, if the action is unknown to the reducer', () => {
|
||||
const state = {
|
||||
x: true,
|
||||
};
|
||||
expect(
|
||||
reducer(state, {
|
||||
type: 'EL_SPECIALE',
|
||||
}),
|
||||
).toBe(state);
|
||||
});
|
||||
|
||||
it('should store the index resources on FETCH_INDEXRESOURCES_SUCCESS', () => {
|
||||
const newState = reducer(
|
||||
{},
|
||||
fetchIndexResourcesSuccess(indexResourcesAuthenticated),
|
||||
);
|
||||
expect(newState.links).toBe(indexResourcesAuthenticated._links);
|
||||
});
|
||||
});
|
||||
|
||||
describe('index resources selectors', () => {
|
||||
const error = new Error('something goes wrong');
|
||||
|
||||
it('should return true, when fetch index resources is pending', () => {
|
||||
const state = {
|
||||
pending: {
|
||||
[FETCH_INDEXRESOURCES]: true,
|
||||
},
|
||||
};
|
||||
expect(isFetchIndexResourcesPending(state)).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false, when fetch index resources is not pending', () => {
|
||||
expect(isFetchIndexResourcesPending({})).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return error when fetch index resources did fail', () => {
|
||||
const state = {
|
||||
failure: {
|
||||
[FETCH_INDEXRESOURCES]: error,
|
||||
},
|
||||
};
|
||||
expect(getFetchIndexResourcesFailure(state)).toEqual(error);
|
||||
});
|
||||
|
||||
it('should return undefined when fetch index resources did not fail', () => {
|
||||
expect(getFetchIndexResourcesFailure({})).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should return all links', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getLinks(state)).toBe(indexResourcesAuthenticated._links);
|
||||
});
|
||||
|
||||
// ui plugins link
|
||||
it('should return ui plugins link when authenticated and has permission to see it', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getUiPluginsLink(state)).toBe(
|
||||
'http://localhost:8081/scm/api/v2/ui/plugins',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return ui plugins links when unauthenticated and has permission to see it', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getUiPluginsLink(state)).toBe(
|
||||
'http://localhost:8081/scm/api/v2/ui/plugins',
|
||||
);
|
||||
});
|
||||
|
||||
// me link
|
||||
it('should return me link when authenticated and has permission to see it', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getMeLink(state)).toBe('http://localhost:8081/scm/api/v2/me/');
|
||||
});
|
||||
|
||||
it('should return undefined for me link when unauthenticated or has not permission to see it', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getMeLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// logout link
|
||||
it('should return logout link when authenticated and has permission to see it', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getLogoutLink(state)).toBe(
|
||||
'http://localhost:8081/scm/api/v2/auth/access_token',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return undefined for logout link when unauthenticated or has not permission to see it', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getLogoutLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// login link
|
||||
it('should return login link when unauthenticated and has permission to see it', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getLoginLink(state)).toBe(
|
||||
'http://localhost:8081/scm/api/v2/auth/access_token',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return undefined for login link when authenticated or has not permission to see it', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getLoginLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// users link
|
||||
it('should return users link when authenticated and has permission to see it', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getUsersLink(state)).toBe(
|
||||
'http://localhost:8081/scm/api/v2/users/',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return undefined for users link when unauthenticated or has not permission to see it', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getUsersLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// groups link
|
||||
it('should return groups link when authenticated and has permission to see it', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getGroupsLink(state)).toBe(
|
||||
'http://localhost:8081/scm/api/v2/groups/',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return undefined for groups link when unauthenticated or has not permission to see it', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getGroupsLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// config link
|
||||
it('should return config link when authenticated and has permission to see it', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getConfigLink(state)).toBe(
|
||||
'http://localhost:8081/scm/api/v2/config',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return undefined for config link when unauthenticated or has not permission to see it', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getConfigLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// repositories link
|
||||
it('should return repositories link when authenticated and has permission to see it', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getRepositoriesLink(state)).toBe(
|
||||
'http://localhost:8081/scm/api/v2/repositories/',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return config for repositories link when unauthenticated or has not permission to see it', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getRepositoriesLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// hgConfig link
|
||||
it('should return hgConfig link when authenticated and has permission to see it', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getHgConfigLink(state)).toBe(
|
||||
'http://localhost:8081/scm/api/v2/config/hg',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return config for hgConfig link when unauthenticated or has not permission to see it', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getHgConfigLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// gitConfig link
|
||||
it('should return gitConfig link when authenticated and has permission to see it', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getGitConfigLink(state)).toBe(
|
||||
'http://localhost:8081/scm/api/v2/config/git',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return config for gitConfig link when unauthenticated or has not permission to see it', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getGitConfigLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// svnConfig link
|
||||
it('should return svnConfig link when authenticated and has permission to see it', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getSvnConfigLink(state)).toBe(
|
||||
'http://localhost:8081/scm/api/v2/config/svn',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return config for svnConfig link when unauthenticated or has not permission to see it', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesUnauthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getSvnConfigLink(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
// Autocomplete links
|
||||
it('should return link collection', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getLinkCollection(state, 'autocomplete')).toEqual(
|
||||
indexResourcesAuthenticated._links.autocomplete,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return user autocomplete link', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getUserAutoCompleteLink(state)).toEqual(
|
||||
'http://localhost:8081/scm/api/v2/autocomplete/users',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return group autocomplete link', () => {
|
||||
const state = {
|
||||
indexResources: {
|
||||
links: indexResourcesAuthenticated._links,
|
||||
},
|
||||
};
|
||||
expect(getGroupAutoCompleteLink(state)).toEqual(
|
||||
'http://localhost:8081/scm/api/v2/autocomplete/groups',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
196
scm-ui/ui-webapp/src/modules/indexResource.ts
Normal file
196
scm-ui/ui-webapp/src/modules/indexResource.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
import * as types from './types';
|
||||
|
||||
import { apiClient } from '@scm-manager/ui-components';
|
||||
import { Action, IndexResources, Link } from '@scm-manager/ui-types';
|
||||
import { isPending } from './pending';
|
||||
import { getFailure } from './failure';
|
||||
|
||||
// Action
|
||||
|
||||
export const FETCH_INDEXRESOURCES = 'scm/INDEXRESOURCES';
|
||||
export const FETCH_INDEXRESOURCES_PENDING = `${FETCH_INDEXRESOURCES}_${types.PENDING_SUFFIX}`;
|
||||
export const FETCH_INDEXRESOURCES_SUCCESS = `${FETCH_INDEXRESOURCES}_${types.SUCCESS_SUFFIX}`;
|
||||
export const FETCH_INDEXRESOURCES_FAILURE = `${FETCH_INDEXRESOURCES}_${types.FAILURE_SUFFIX}`;
|
||||
|
||||
const INDEX_RESOURCES_LINK = '/';
|
||||
|
||||
export const callFetchIndexResources = (): Promise<IndexResources> => {
|
||||
return apiClient.get(INDEX_RESOURCES_LINK).then(response => {
|
||||
return response.json();
|
||||
});
|
||||
};
|
||||
|
||||
export function fetchIndexResources() {
|
||||
return function(dispatch: any) {
|
||||
dispatch(fetchIndexResourcesPending());
|
||||
return callFetchIndexResources()
|
||||
.then(resources => {
|
||||
dispatch(fetchIndexResourcesSuccess(resources));
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch(fetchIndexResourcesFailure(err));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchIndexResourcesPending(): Action {
|
||||
return {
|
||||
type: FETCH_INDEXRESOURCES_PENDING,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchIndexResourcesSuccess(resources: IndexResources): Action {
|
||||
return {
|
||||
type: FETCH_INDEXRESOURCES_SUCCESS,
|
||||
payload: resources,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchIndexResourcesFailure(err: Error): Action {
|
||||
return {
|
||||
type: FETCH_INDEXRESOURCES_FAILURE,
|
||||
payload: err,
|
||||
};
|
||||
}
|
||||
|
||||
// reducer
|
||||
export default function reducer(
|
||||
state: object = {},
|
||||
action: Action = {
|
||||
type: 'UNKNOWN',
|
||||
},
|
||||
): object {
|
||||
if (!action.payload) {
|
||||
return state;
|
||||
}
|
||||
|
||||
switch (action.type) {
|
||||
case FETCH_INDEXRESOURCES_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
version: action.payload.version,
|
||||
links: action.payload._links,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
// selectors
|
||||
|
||||
export function isFetchIndexResourcesPending(state: object) {
|
||||
return isPending(state, FETCH_INDEXRESOURCES);
|
||||
}
|
||||
|
||||
export function getFetchIndexResourcesFailure(state: object) {
|
||||
return getFailure(state, FETCH_INDEXRESOURCES);
|
||||
}
|
||||
|
||||
export function getLinks(state: object) {
|
||||
return state.indexResources.links;
|
||||
}
|
||||
|
||||
export function getLink(state: object, name: string) {
|
||||
if (state.indexResources.links && state.indexResources.links[name]) {
|
||||
return state.indexResources.links[name].href;
|
||||
}
|
||||
}
|
||||
|
||||
export function getLinkCollection(state: object, name: string): Link[] {
|
||||
if (state.indexResources.links && state.indexResources.links[name]) {
|
||||
return state.indexResources.links[name];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function getAppVersion(state: object) {
|
||||
return state.indexResources.version;
|
||||
}
|
||||
|
||||
export function getUiPluginsLink(state: object) {
|
||||
return getLink(state, 'uiPlugins');
|
||||
}
|
||||
|
||||
export function getAvailablePluginsLink(state: object) {
|
||||
return getLink(state, 'availablePlugins');
|
||||
}
|
||||
|
||||
export function getInstalledPluginsLink(state: object) {
|
||||
return getLink(state, 'installedPlugins');
|
||||
}
|
||||
|
||||
export function getPendingPluginsLink(state: object) {
|
||||
return getLink(state, 'pendingPlugins');
|
||||
}
|
||||
|
||||
export function getMeLink(state: object) {
|
||||
return getLink(state, 'me');
|
||||
}
|
||||
|
||||
export function getLogoutLink(state: object) {
|
||||
return getLink(state, 'logout');
|
||||
}
|
||||
|
||||
export function getLoginLink(state: object) {
|
||||
return getLink(state, 'login');
|
||||
}
|
||||
|
||||
export function getUsersLink(state: object) {
|
||||
return getLink(state, 'users');
|
||||
}
|
||||
|
||||
export function getRepositoryRolesLink(state: object) {
|
||||
return getLink(state, 'repositoryRoles');
|
||||
}
|
||||
|
||||
export function getRepositoryVerbsLink(state: object) {
|
||||
return getLink(state, 'repositoryVerbs');
|
||||
}
|
||||
|
||||
export function getGroupsLink(state: object) {
|
||||
return getLink(state, 'groups');
|
||||
}
|
||||
|
||||
export function getConfigLink(state: object) {
|
||||
return getLink(state, 'config');
|
||||
}
|
||||
|
||||
export function getRepositoriesLink(state: object) {
|
||||
return getLink(state, 'repositories');
|
||||
}
|
||||
|
||||
export function getHgConfigLink(state: object) {
|
||||
return getLink(state, 'hgConfig');
|
||||
}
|
||||
|
||||
export function getGitConfigLink(state: object) {
|
||||
return getLink(state, 'gitConfig');
|
||||
}
|
||||
|
||||
export function getSvnConfigLink(state: object) {
|
||||
return getLink(state, 'svnConfig');
|
||||
}
|
||||
|
||||
export function getLoginInfoLink(state: object) {
|
||||
return getLink(state, 'loginInfo');
|
||||
}
|
||||
|
||||
export function getUserAutoCompleteLink(state: object): string {
|
||||
const link = getLinkCollection(state, 'autocomplete').find(
|
||||
i => i.name === 'users',
|
||||
);
|
||||
if (link) {
|
||||
return link.href;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
export function getGroupAutoCompleteLink(state: object): string {
|
||||
const link = getLinkCollection(state, 'autocomplete').find(
|
||||
i => i.name === 'groups',
|
||||
);
|
||||
if (link) {
|
||||
return link.href;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
import reducer, { isPending } from "./pending";
|
||||
|
||||
describe("pending reducer", () => {
|
||||
it("should set pending for FETCH_ITEMS to true", () => {
|
||||
const newState = reducer({}, { type: "FETCH_ITEMS_PENDING" });
|
||||
expect(newState["FETCH_ITEMS"]).toBe(true);
|
||||
});
|
||||
|
||||
it("should do nothing for unknown action types", () => {
|
||||
const state = {};
|
||||
const newState = reducer(state, { type: "UNKNOWN" });
|
||||
expect(newState).toBe(state);
|
||||
});
|
||||
|
||||
it("should set pending for FETCH_ITEMS to true, but should not affect others", () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
FETCH_USERS: true
|
||||
},
|
||||
{ type: "FETCH_ITEMS_PENDING" }
|
||||
);
|
||||
expect(newState["FETCH_ITEMS"]).toBe(true);
|
||||
expect(newState["FETCH_USERS"]).toBe(true);
|
||||
});
|
||||
|
||||
it("should reset pending state for FETCH_ITEMS after FETCH_ITEMS_SUCCESS", () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
FETCH_ITEMS: true
|
||||
},
|
||||
{ type: "FETCH_ITEMS_SUCCESS" }
|
||||
);
|
||||
expect(newState["FETCH_ITEMS"]).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should reset pending state for FETCH_ITEMS after FETCH_ITEMS_FAILURE", () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
FETCH_ITEMS: true
|
||||
},
|
||||
{ type: "FETCH_ITEMS_FAILURE" }
|
||||
);
|
||||
expect(newState["FETCH_ITEMS"]).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should reset pending state for FETCH_ITEMS after FETCH_ITEMS_RESET", () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
FETCH_ITEMS: true
|
||||
},
|
||||
{ type: "FETCH_ITEMS_RESET" }
|
||||
);
|
||||
expect(newState["FETCH_ITEMS"]).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should reset pending state for FETCH_ITEMS, if resetPending prop is available", () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
FETCH_ITEMS: true
|
||||
},
|
||||
{ type: "FETCH_ITEMS_SOMETHING", resetPending: true }
|
||||
);
|
||||
expect(newState["FETCH_ITEMS"]).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should reset pending state for FETCH_ITEMS after FETCH_ITEMS_SUCCESS, but should not affect others", () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
FETCH_USERS: true,
|
||||
FETCH_ITEMS: true
|
||||
},
|
||||
{ type: "FETCH_ITEMS_SUCCESS" }
|
||||
);
|
||||
expect(newState["FETCH_ITEMS"]).toBeFalsy();
|
||||
expect(newState["FETCH_USERS"]).toBe(true);
|
||||
});
|
||||
|
||||
it("should set pending for a single item", () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
"FETCH_USER/42": false
|
||||
},
|
||||
{ type: "FETCH_USER_PENDING", itemId: 21 }
|
||||
);
|
||||
expect(newState["FETCH_USER/21"]).toBe(true);
|
||||
expect(newState["FETCH_USER/42"]).toBe(false);
|
||||
});
|
||||
|
||||
it("should reset pending for a single item", () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
"FETCH_USER/42": true
|
||||
},
|
||||
{ type: "FETCH_USER_SUCCESS", itemId: 42 }
|
||||
);
|
||||
expect(newState["FETCH_USER/42"]).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("pending selectors", () => {
|
||||
it("should return true, while FETCH_ITEMS is pending", () => {
|
||||
const result = isPending(
|
||||
{
|
||||
pending: {
|
||||
FETCH_ITEMS: true
|
||||
}
|
||||
},
|
||||
"FETCH_ITEMS"
|
||||
);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false, if pending is not defined", () => {
|
||||
const result = isPending({}, "FETCH_ITEMS");
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should return true, while FETCH_ITEM 42 is pending", () => {
|
||||
const result = isPending(
|
||||
{
|
||||
pending: {
|
||||
"FETCH_ITEM/42": true
|
||||
}
|
||||
},
|
||||
"FETCH_ITEM",
|
||||
42
|
||||
);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true, while FETCH_ITEM 42 is undefined", () => {
|
||||
const result = isPending(
|
||||
{
|
||||
pending: {
|
||||
"FETCH_ITEM/21": true
|
||||
}
|
||||
},
|
||||
"FETCH_ITEM",
|
||||
42
|
||||
);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
169
scm-ui/ui-webapp/src/modules/pending.test.ts
Normal file
169
scm-ui/ui-webapp/src/modules/pending.test.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import reducer, { isPending } from './pending';
|
||||
|
||||
describe('pending reducer', () => {
|
||||
it('should set pending for FETCH_ITEMS to true', () => {
|
||||
const newState = reducer(
|
||||
{},
|
||||
{
|
||||
type: 'FETCH_ITEMS_PENDING',
|
||||
},
|
||||
);
|
||||
expect(newState['FETCH_ITEMS']).toBe(true);
|
||||
});
|
||||
|
||||
it('should do nothing for unknown action types', () => {
|
||||
const state = {};
|
||||
const newState = reducer(state, {
|
||||
type: 'UNKNOWN',
|
||||
});
|
||||
expect(newState).toBe(state);
|
||||
});
|
||||
|
||||
it('should set pending for FETCH_ITEMS to true, but should not affect others', () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
FETCH_USERS: true,
|
||||
},
|
||||
{
|
||||
type: 'FETCH_ITEMS_PENDING',
|
||||
},
|
||||
);
|
||||
expect(newState['FETCH_ITEMS']).toBe(true);
|
||||
expect(newState['FETCH_USERS']).toBe(true);
|
||||
});
|
||||
|
||||
it('should reset pending state for FETCH_ITEMS after FETCH_ITEMS_SUCCESS', () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
FETCH_ITEMS: true,
|
||||
},
|
||||
{
|
||||
type: 'FETCH_ITEMS_SUCCESS',
|
||||
},
|
||||
);
|
||||
expect(newState['FETCH_ITEMS']).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should reset pending state for FETCH_ITEMS after FETCH_ITEMS_FAILURE', () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
FETCH_ITEMS: true,
|
||||
},
|
||||
{
|
||||
type: 'FETCH_ITEMS_FAILURE',
|
||||
},
|
||||
);
|
||||
expect(newState['FETCH_ITEMS']).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should reset pending state for FETCH_ITEMS after FETCH_ITEMS_RESET', () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
FETCH_ITEMS: true,
|
||||
},
|
||||
{
|
||||
type: 'FETCH_ITEMS_RESET',
|
||||
},
|
||||
);
|
||||
expect(newState['FETCH_ITEMS']).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should reset pending state for FETCH_ITEMS, if resetPending prop is available', () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
FETCH_ITEMS: true,
|
||||
},
|
||||
{
|
||||
type: 'FETCH_ITEMS_SOMETHING',
|
||||
resetPending: true,
|
||||
},
|
||||
);
|
||||
expect(newState['FETCH_ITEMS']).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should reset pending state for FETCH_ITEMS after FETCH_ITEMS_SUCCESS, but should not affect others', () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
FETCH_USERS: true,
|
||||
FETCH_ITEMS: true,
|
||||
},
|
||||
{
|
||||
type: 'FETCH_ITEMS_SUCCESS',
|
||||
},
|
||||
);
|
||||
expect(newState['FETCH_ITEMS']).toBeFalsy();
|
||||
expect(newState['FETCH_USERS']).toBe(true);
|
||||
});
|
||||
|
||||
it('should set pending for a single item', () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
'FETCH_USER/42': false,
|
||||
},
|
||||
{
|
||||
type: 'FETCH_USER_PENDING',
|
||||
itemId: 21,
|
||||
},
|
||||
);
|
||||
expect(newState['FETCH_USER/21']).toBe(true);
|
||||
expect(newState['FETCH_USER/42']).toBe(false);
|
||||
});
|
||||
|
||||
it('should reset pending for a single item', () => {
|
||||
const newState = reducer(
|
||||
{
|
||||
'FETCH_USER/42': true,
|
||||
},
|
||||
{
|
||||
type: 'FETCH_USER_SUCCESS',
|
||||
itemId: 42,
|
||||
},
|
||||
);
|
||||
expect(newState['FETCH_USER/42']).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('pending selectors', () => {
|
||||
it('should return true, while FETCH_ITEMS is pending', () => {
|
||||
const result = isPending(
|
||||
{
|
||||
pending: {
|
||||
FETCH_ITEMS: true,
|
||||
},
|
||||
},
|
||||
'FETCH_ITEMS',
|
||||
);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false, if pending is not defined', () => {
|
||||
const result = isPending({}, 'FETCH_ITEMS');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true, while FETCH_ITEM 42 is pending', () => {
|
||||
const result = isPending(
|
||||
{
|
||||
pending: {
|
||||
'FETCH_ITEM/42': true,
|
||||
},
|
||||
},
|
||||
'FETCH_ITEM',
|
||||
42,
|
||||
);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true, while FETCH_ITEM 42 is undefined', () => {
|
||||
const result = isPending(
|
||||
{
|
||||
pending: {
|
||||
'FETCH_ITEM/21': true,
|
||||
},
|
||||
},
|
||||
'FETCH_ITEM',
|
||||
42,
|
||||
);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,15 +1,14 @@
|
||||
// @flow
|
||||
import type { Action } from "@scm-manager/ui-types";
|
||||
import * as types from "./types";
|
||||
import { Action } from '@scm-manager/ui-types';
|
||||
import * as types from './types';
|
||||
|
||||
const PENDING_SUFFIX = "_" + types.PENDING_SUFFIX;
|
||||
const PENDING_SUFFIX = '_' + types.PENDING_SUFFIX;
|
||||
const RESET_ACTIONTYPES = [
|
||||
types.SUCCESS_SUFFIX,
|
||||
types.FAILURE_SUFFIX,
|
||||
types.RESET_SUFFIX
|
||||
types.RESET_SUFFIX,
|
||||
];
|
||||
|
||||
function removeFromState(state: Object, identifier: string) {
|
||||
function removeFromState(state: object, identifier: string) {
|
||||
let newState = {};
|
||||
for (let childType in state) {
|
||||
if (childType !== identifier) {
|
||||
@@ -20,9 +19,9 @@ function removeFromState(state: Object, identifier: string) {
|
||||
}
|
||||
|
||||
function removeAllEntriesOfIdentifierFromState(
|
||||
state: Object,
|
||||
state: object,
|
||||
payload: any,
|
||||
identifier: string
|
||||
identifier: string,
|
||||
) {
|
||||
const newState = {};
|
||||
for (let childType in state) {
|
||||
@@ -37,35 +36,40 @@ function extractIdentifierFromPending(action: Action) {
|
||||
const type = action.type;
|
||||
let identifier = type.substring(0, type.length - PENDING_SUFFIX.length);
|
||||
if (action.itemId) {
|
||||
identifier += "/" + action.itemId;
|
||||
identifier += '/' + action.itemId;
|
||||
}
|
||||
return identifier;
|
||||
}
|
||||
|
||||
export default function reducer(
|
||||
state: Object = {},
|
||||
action: Action = { type: "UNKNOWN" }
|
||||
): Object {
|
||||
state: object = {},
|
||||
action: Action = {
|
||||
type: 'UNKNOWN',
|
||||
},
|
||||
): object {
|
||||
const type = action.type;
|
||||
if (type.endsWith(PENDING_SUFFIX)) {
|
||||
const identifier = extractIdentifierFromPending(action);
|
||||
return {
|
||||
...state,
|
||||
[identifier]: true
|
||||
[identifier]: true,
|
||||
};
|
||||
} else {
|
||||
const index = type.lastIndexOf("_");
|
||||
const index = type.lastIndexOf('_');
|
||||
if (index > 0) {
|
||||
const actionType = type.substring(index + 1);
|
||||
if (RESET_ACTIONTYPES.indexOf(actionType) >= 0 || action.resetPending) {
|
||||
let identifier = type.substring(0, index);
|
||||
if (action.itemId) {
|
||||
identifier += "/" + action.itemId;
|
||||
identifier += '/' + action.itemId;
|
||||
}
|
||||
if (action.payload)
|
||||
return removeAllEntriesOfIdentifierFromState(state, action.payload, identifier);
|
||||
else
|
||||
return removeFromState(state, identifier);
|
||||
return removeAllEntriesOfIdentifierFromState(
|
||||
state,
|
||||
action.payload,
|
||||
identifier,
|
||||
);
|
||||
else return removeFromState(state, identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,13 +77,13 @@ export default function reducer(
|
||||
}
|
||||
|
||||
export function isPending(
|
||||
state: Object,
|
||||
state: object,
|
||||
actionType: string,
|
||||
itemId?: string | number
|
||||
itemId?: string | number,
|
||||
) {
|
||||
let type = actionType;
|
||||
if (itemId) {
|
||||
type += "/" + itemId;
|
||||
type += '/' + itemId;
|
||||
}
|
||||
if (state.pending && state.pending[type]) {
|
||||
return true;
|
||||
@@ -1,4 +0,0 @@
|
||||
export const PENDING_SUFFIX = "PENDING";
|
||||
export const SUCCESS_SUFFIX = "SUCCESS";
|
||||
export const FAILURE_SUFFIX = "FAILURE";
|
||||
export const RESET_SUFFIX = "RESET";
|
||||
4
scm-ui/ui-webapp/src/modules/types.ts
Normal file
4
scm-ui/ui-webapp/src/modules/types.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export const PENDING_SUFFIX = 'PENDING';
|
||||
export const SUCCESS_SUFFIX = 'SUCCESS';
|
||||
export const FAILURE_SUFFIX = 'FAILURE';
|
||||
export const RESET_SUFFIX = 'RESET';
|
||||
Reference in New Issue
Block a user