Unverified Commit 42a544e1 authored by Tamika Tannis's avatar Tamika Tannis Committed by GitHub

Redux unit tests: tableMetadata, user (#208)

* ducks unit tests: feedback, log, popularTables, search

* Lint fix

* Cleanup previous tests

* Syntax fixes

* Fix build errors

* Cleanup for unit tests

* Tests for user actions, reducer, & saga

* tests for user apis

* Initial ducks/tableMetadata tests (#207)

* Improve tableMaetadata tags & owners tests

* Cleanup tableMetadata tests

* Tests for tableMetadata api helpers

* Code cleanup

* Some code cleanup

* saga syntax + OwnerDict type cleanup
parent a0ed1805
...@@ -13,10 +13,10 @@ module.exports = { ...@@ -13,10 +13,10 @@ module.exports = {
statements: 50, // 75 statements: 50, // 75
}, },
'./js/ducks': { './js/ducks': {
branches: 50, // 75 branches: 75,
functions: 50, // 75 functions: 80,
lines: 55, // 75 lines: 80,
statements: 50, // 75 statements: 80,
}, },
'./js/fixtures': { './js/fixtures': {
branches: 100, branches: 100,
......
...@@ -10,7 +10,7 @@ import Flag from 'components/common/Flag'; ...@@ -10,7 +10,7 @@ import Flag from 'components/common/Flag';
import Tabs from 'components/common/Tabs'; import Tabs from 'components/common/Tabs';
import { GlobalState } from 'ducks/rootReducer'; import { GlobalState } from 'ducks/rootReducer';
import { getUserById, getUserOwn, getUserRead } from 'ducks/user/reducer'; import { getUser, getUserOwn, getUserRead } from 'ducks/user/reducer';
import { PeopleUser, Resource } from 'interfaces'; import { PeopleUser, Resource } from 'interfaces';
import { GetUserRequest, GetUserOwnRequest, GetUserReadRequest } from 'ducks/user/types'; import { GetUserRequest, GetUserOwnRequest, GetUserReadRequest } from 'ducks/user/types';
...@@ -216,7 +216,7 @@ export const mapStateToProps = (state: GlobalState) => { ...@@ -216,7 +216,7 @@ export const mapStateToProps = (state: GlobalState) => {
}; };
export const mapDispatchToProps = (dispatch) => { export const mapDispatchToProps = (dispatch) => {
return bindActionCreators({ getUserById, getUserOwn, getUserRead, getBookmarksForUser }, dispatch); return bindActionCreators({ getUserOwn, getUserRead, getBookmarksForUser, getUserById: getUser }, dispatch);
}; };
export default connect<StateFromProps, DispatchFromProps>(mapStateToProps, mapDispatchToProps)(withRouter(ProfilePage)); export default connect<StateFromProps, DispatchFromProps>(mapStateToProps, mapDispatchToProps)(withRouter(ProfilePage));
import { expectSaga, testSaga } from 'redux-saga-test-plan'; import { testSaga } from 'redux-saga-test-plan';
import * as matchers from 'redux-saga-test-plan/matchers';
import { throwError } from 'redux-saga-test-plan/providers';
import { SendingState } from 'interfaces'; import { SendingState } from 'interfaces';
......
import { expectSaga, testSaga } from 'redux-saga-test-plan'; import { testSaga } from 'redux-saga-test-plan';
import * as matchers from 'redux-saga-test-plan/matchers';
import { throwError } from 'redux-saga-test-plan/providers';
import { TableResource } from 'interfaces'; import { TableResource } from 'interfaces';
......
...@@ -118,8 +118,8 @@ export default function reducer(state: SearchReducerState = initialState, action ...@@ -118,8 +118,8 @@ export default function reducer(state: SearchReducerState = initialState, action
case SearchAll.FAILURE: case SearchAll.FAILURE:
case SearchResource.FAILURE: case SearchResource.FAILURE:
return { return {
...initialState, ...initialState,
isLoading: false, search_term: state.search_term,
}; };
default: default:
return state; return state;
......
import { SagaIterator } from 'redux-saga'; import { SagaIterator } from 'redux-saga';
import { all, call, put, takeEvery } from 'redux-saga/effects'; import { all, call, put, takeEvery } from 'redux-saga/effects';
import { ResourceType } from 'interfaces/Resources';
import { import {
SearchAll, SearchAll,
SearchAllRequest, SearchAllRequest,
...@@ -11,7 +13,6 @@ import { ...@@ -11,7 +13,6 @@ import {
import { import {
searchResource, searchResource,
} from './api/v0'; } from './api/v0';
import { ResourceType } from 'interfaces/Resources';
import { import {
searchAllSuccess, searchAllFailure, searchAllSuccess, searchAllFailure,
......
import { expectSaga, testSaga } from 'redux-saga-test-plan'; import { testSaga } from 'redux-saga-test-plan';
import * as matchers from 'redux-saga-test-plan/matchers';
import { throwError } from 'redux-saga-test-plan/providers';
import { ResourceType } from 'interfaces'; import { ResourceType } from 'interfaces';
import globalState from 'fixtures/globalState'; import globalState from 'fixtures/globalState';
import { searchResource as srchResource } from '../api/v0'; import { searchResource as srchResource } from '../api/v0';
import reducer, { import reducer, {
searchAll, searchAllSuccess, searchAllFailure, searchAll, searchAllSuccess, searchAllFailure,
searchResource, searchResourceSuccess, searchResourceFailure, searchResource, searchResourceSuccess, searchResourceFailure,
...@@ -112,7 +111,7 @@ describe('search ducks', () => { ...@@ -112,7 +111,7 @@ describe('search ducks', () => {
it('should handle SearchAll.FAILURE', () => { it('should handle SearchAll.FAILURE', () => {
expect(reducer(testState, searchAllFailure())).toEqual({ expect(reducer(testState, searchAllFailure())).toEqual({
...initialState, ...initialState,
isLoading: false, search_term: testState.search_term,
}); });
}); });
...@@ -138,7 +137,7 @@ describe('search ducks', () => { ...@@ -138,7 +137,7 @@ describe('search ducks', () => {
it('should handle SearchResource.FAILURE', () => { it('should handle SearchResource.FAILURE', () => {
expect(reducer(testState, searchResourceFailure())).toEqual({ expect(reducer(testState, searchResourceFailure())).toEqual({
...initialState, ...initialState,
isLoading: false, search_term: testState.search_term,
}); });
}); });
}); });
......
import { filterFromObj, sortTagsAlphabetical } from 'ducks/utilMethods'; import { filterFromObj, sortTagsAlphabetical } from 'ducks/utilMethods';
import { TableMetadata, Tag, User } from 'interfaces'; import { OwnerDict, TableMetadata, Tag, User } from 'interfaces';
import { TableDataAPI } from './v0'; import { TableDataAPI } from './v0';
/** /**
...@@ -20,7 +20,7 @@ export function getTableDataFromResponseData(responseData: TableDataAPI): TableM ...@@ -20,7 +20,7 @@ export function getTableDataFromResponseData(responseData: TableDataAPI): TableM
/** /**
* Parses the response for table metadata to return the array of table owners * Parses the response for table metadata to return the array of table owners
*/ */
export function getTableOwnersFromResponseData(responseData: TableDataAPI): { [id: string] : User } { export function getTableOwnersFromResponseData(responseData: TableDataAPI): OwnerDict {
// TODO: owner needs proper id, until then we have to remember that we are using display_name // TODO: owner needs proper id, until then we have to remember that we are using display_name
const ownerObj = responseData.tableData.owners.reduce((resultObj, currentOwner) => { const ownerObj = responseData.tableData.owners.reduce((resultObj, currentOwner) => {
resultObj[currentOwner.display_name] = currentOwner as User; resultObj[currentOwner.display_name] = currentOwner as User;
......
import axios from 'axios';
import * as Helpers from '../helpers';
import * as Utils from 'ducks/utilMethods';
import globalState from 'fixtures/globalState';
import { TableData, TableDataAPI } from '../v0';
const filterFromObjSpy = jest.spyOn(Utils, 'filterFromObj').mockImplementation(() => {});
jest.mock('axios');
describe('helpers', () => {
let mockResponseData: TableDataAPI;
let tableResponseData: TableData;
beforeAll(() => {
tableResponseData = {
...globalState.tableMetadata.tableData,
owners: [{display_name: 'test', profile_url: 'test.io', email: 'test@test.com', user_id: 'test'}],
tags: [{tag_count: 2, tag_name: 'zname'}, {tag_count: 1, tag_name: 'aname'}],
};
mockResponseData = {
tableData: tableResponseData,
msg: 'Success',
};
})
it('getTableQueryParams',() => {
const tableKey = 'testKey';
expect(Helpers.getTableQueryParams(tableKey)).toEqual(`key=${encodeURIComponent(tableKey)}`)
});
it('getTableDataFromResponseData',() => {
Helpers.getTableDataFromResponseData(mockResponseData);
expect(filterFromObjSpy).toHaveBeenCalledWith(tableResponseData, ['owners', 'tags']);
});
it('getTableOwnersFromResponseData',() => {
expect(Helpers.getTableOwnersFromResponseData(mockResponseData)).toEqual({
'test': {display_name: 'test', profile_url: 'test.io', email: 'test@test.com', user_id: 'test'}
});
});
it('getTableTagsFromResponseData',() => {
expect(Helpers.getTableTagsFromResponseData(mockResponseData)).toEqual([
{tag_count: 1, tag_name: 'aname'},
{tag_count: 2, tag_name: 'zname'},
]);
});
});
...@@ -11,7 +11,7 @@ const API_PATH = '/api/metadata/v0'; ...@@ -11,7 +11,7 @@ const API_PATH = '/api/metadata/v0';
// TODO: Consider created shared interfaces for ducks so we can reuse MessageAPI everywhere else // TODO: Consider created shared interfaces for ducks so we can reuse MessageAPI everywhere else
type MessageAPI = { msg: string }; type MessageAPI = { msg: string };
type TableData = TableMetadata & { export type TableData = TableMetadata & {
owners: User[]; owners: User[];
tags: Tag[]; tags: Tag[];
}; };
......
import { UpdateOwnerPayload, User } from 'interfaces'; import { OwnerDict, UpdateOwnerPayload } from 'interfaces';
import { import {
GetTableData, GetTableDataResponse, GetTableData, GetTableDataResponse,
...@@ -16,11 +16,27 @@ export function updateTableOwner(updateArray: UpdateOwnerPayload[], onSuccess?: ...@@ -16,11 +16,27 @@ export function updateTableOwner(updateArray: UpdateOwnerPayload[], onSuccess?:
type: UpdateTableOwner.REQUEST, type: UpdateTableOwner.REQUEST,
}; };
}; };
export function updateTableOwnerFailure(owners: OwnerDict): UpdateTableOwnerResponse {
return {
type: UpdateTableOwner.FAILURE,
payload: {
owners
}
};
};
export function updateTableOwnerSuccess(owners: OwnerDict): UpdateTableOwnerResponse {
return {
type: UpdateTableOwner.SUCCESS,
payload: {
owners
}
};
};
/* REDUCER */ /* REDUCER */
export interface TableOwnerReducerState { export interface TableOwnerReducerState {
isLoading: boolean; isLoading: boolean;
owners: { [id: string] : User }; owners: OwnerDict;
}; };
export const initialOwnersState: TableOwnerReducerState = { export const initialOwnersState: TableOwnerReducerState = {
......
import { SagaIterator } from 'redux-saga'; import { SagaIterator } from 'redux-saga';
import { all, call, put, select, takeEvery } from 'redux-saga/effects'; import { all, call, put, select, takeEvery } from 'redux-saga/effects';
import { UpdateTableOwner, UpdateTableOwnerRequest } from '../types';
import { metadataUpdateTableOwner, metadataTableOwners } from '../api/v0'; import { metadataUpdateTableOwner, metadataTableOwners } from '../api/v0';
import { updateTableOwnerFailure, updateTableOwnerSuccess } from './reducer';
import { UpdateTableOwner, UpdateTableOwnerRequest } from '../types';
export function* updateTableOwnerWorker(action: UpdateTableOwnerRequest): SagaIterator { export function* updateTableOwnerWorker(action: UpdateTableOwnerRequest): SagaIterator {
const { payload } = action; const { payload } = action;
const state = yield select(); const state = yield select();
...@@ -12,12 +14,12 @@ export function* updateTableOwnerWorker(action: UpdateTableOwnerRequest): SagaIt ...@@ -12,12 +14,12 @@ export function* updateTableOwnerWorker(action: UpdateTableOwnerRequest): SagaIt
try { try {
yield all(metadataUpdateTableOwner(payload.updateArray, tableData.key)); yield all(metadataUpdateTableOwner(payload.updateArray, tableData.key));
const newOwners = yield call(metadataTableOwners, tableData.key); const newOwners = yield call(metadataTableOwners, tableData.key);
yield put({ type: UpdateTableOwner.SUCCESS, payload: { owners: newOwners } }); yield put(updateTableOwnerSuccess(newOwners));
if (payload.onSuccess) { if (payload.onSuccess) {
yield call(payload.onSuccess); yield call(payload.onSuccess);
} }
} catch (e) { } catch (e) {
yield put({ type: UpdateTableOwner.FAILURE, payload: { owners: state.tableMetadata.tableOwners.owners } }); yield put(updateTableOwnerFailure(state.tableMetadata.tableOwners.owners));
if (payload.onFailure) { if (payload.onFailure) {
yield call(payload.onFailure); yield call(payload.onFailure);
} }
......
import { testSaga } from 'redux-saga-test-plan';
import { OwnerDict, UpdateMethod, UpdateOwnerPayload } from 'interfaces';
import globalState from 'fixtures/globalState';
import * as apis from '../../api/v0';
import reducer, {
updateTableOwner, updateTableOwnerFailure, updateTableOwnerSuccess,
initialOwnersState, TableOwnerReducerState
} from '../reducer';
import { getTableData, getTableDataFailure, getTableDataSuccess } from '../../reducer';
import { updateTableOwnerWorker, updateTableOwnerWatcher } from '../sagas';
import { GetTableData, UpdateTableOwner } from '../../types';
const metadataUpdateTableOwnerSpy = jest.spyOn(apis, 'metadataUpdateTableOwner').mockImplementation((payload, key) => []);
describe('tableMetadata:owners ducks', () => {
let expectedOwners: OwnerDict;
let updatePayload: UpdateOwnerPayload[];
let mockSuccess;
let mockFailure;
beforeAll(() => {
expectedOwners = {
'testId': {display_name: 'test', profile_url: 'test.io', email: 'test@test.com', user_id: 'testId'}
};
updatePayload = [{method: UpdateMethod.PUT, id: 'testId'}];
mockSuccess = jest.fn();
mockFailure = jest.fn();
});
describe('actions', () => {
it('updateTableOwner - returns the action to update table owners', () => {
const action = updateTableOwner(updatePayload, mockSuccess, mockFailure);
const { payload } = action;
expect(action.type).toBe(UpdateTableOwner.REQUEST);
expect(payload.updateArray).toBe(updatePayload);
expect(payload.onSuccess).toBe(mockSuccess);
expect(payload.onFailure).toBe(mockFailure);
});
it('updateTableOwnerFailure - returns the action to process failure', () => {
const action = updateTableOwnerFailure(expectedOwners);
const { payload } = action;
expect(action.type).toBe(UpdateTableOwner.FAILURE);
expect(payload.owners).toBe(expectedOwners);
});
it('updateTableOwnerSuccess - returns the action to process success', () => {
const action = updateTableOwnerSuccess(expectedOwners);
const { payload } = action;
expect(action.type).toBe(UpdateTableOwner.SUCCESS);
expect(payload.owners).toBe(expectedOwners);
});
});
describe('reducer', () => {
let testState: TableOwnerReducerState;
beforeAll(() => {
testState = initialOwnersState;
});
it('should return the existing state if action is not handled', () => {
expect(reducer(testState, { type: 'INVALID.ACTION' })).toEqual(testState);
});
it('should handle UpdateTableOwner.REQUEST', () => {
expect(reducer(testState, updateTableOwner(updatePayload, mockSuccess, mockFailure))).toEqual({
...testState,
isLoading: true,
});
});
it('should handle UpdateTableOwner.FAILURE', () => {
expect(reducer(testState, updateTableOwnerFailure(expectedOwners))).toEqual({
...testState,
isLoading: false,
owners: expectedOwners,
});
});
it('should handle UpdateTableOwner.SUCCESS', () => {
expect(reducer(testState, updateTableOwnerSuccess(expectedOwners))).toEqual({
...testState,
isLoading: false,
owners: expectedOwners,
});
});
it('should handle GetTableData.REQUEST', () => {
expect(reducer(testState, getTableData('testKey'))).toEqual({
...testState,
isLoading: true,
owners: {},
});
});
it('should handle GetTableData.FAILURE', () => {
const action = getTableDataFailure();
expect(reducer(testState, action)).toEqual({
...testState,
isLoading: false,
owners: action.payload.owners,
});
});
it('should handle GetTableData.SUCCESS', () => {
const mockTableData = globalState.tableMetadata.tableData;
expect(reducer(testState, getTableDataSuccess(mockTableData, expectedOwners, 200, []))).toEqual({
...testState,
isLoading: false,
owners: expectedOwners,
});
});
});
describe('sagas', () => {
describe('updateTableOwnerWatcher', () => {
it('takes every UpdateTableOwner.REQUEST with updateTableOwnerWorker', () => {
testSaga(updateTableOwnerWatcher)
.next().takeEvery(UpdateTableOwner.REQUEST, updateTableOwnerWorker);
});
});
describe('updateTableOwnerWorker', () => {
describe('executes flow for updating owners and returning up to date owner dict', () => {
let sagaTest;
beforeAll(() => {
sagaTest = (action) => {
return testSaga(updateTableOwnerWorker, action)
.next().select()
.next(globalState).all(apis.metadataUpdateTableOwner(updatePayload, globalState.tableMetadata.tableData.key))
.next().call(apis.metadataTableOwners, globalState.tableMetadata.tableData.key)
.next(expectedOwners).put(updateTableOwnerSuccess(expectedOwners));
};
});
it('without success callback', () => {
sagaTest(updateTableOwner(updatePayload))
.next().isDone();
});
it('with success callback', () => {
sagaTest(updateTableOwner(updatePayload, mockSuccess, mockFailure))
.next().call(mockSuccess)
.next().isDone();
});
});
describe('handles request error', () => {
let sagaTest;
beforeAll(() => {
sagaTest = (action) => {
return testSaga(updateTableOwnerWorker, action)
.next().select()
.next(globalState).throw(new Error()).put(updateTableOwnerFailure(globalState.tableMetadata.tableOwners.owners))
};
});
it('without failure callback', () => {
sagaTest(updateTableOwner(updatePayload))
.next().isDone();
});
it('with failure callback', () => {
sagaTest(updateTableOwner(updatePayload, mockSuccess, mockFailure))
.next().call(mockFailure)
.next().isDone();
});
});
});
});
});
import { PreviewData, PreviewQueryParams, TableMetadata } from 'interfaces'; import { OwnerDict, PreviewData, PreviewQueryParams, TableMetadata, Tag, User } from 'interfaces';
import { import {
GetTableData, GetTableDataRequest, GetTableDataResponse, GetTableData, GetTableDataRequest, GetTableDataResponse,
...@@ -27,6 +27,24 @@ export function getTableData(key: string, searchIndex: string = '', source: stri ...@@ -27,6 +27,24 @@ export function getTableData(key: string, searchIndex: string = '', source: stri
type: GetTableData.REQUEST, type: GetTableData.REQUEST,
}; };
}; };
export function getTableDataFailure(): GetTableDataResponse {
return {
type: GetTableData.FAILURE,
payload: { data: initialTableDataState, owners: {}, statusCode: 500, tags: [] }
}
}
export function getTableDataSuccess(data: TableMetadata, owners: OwnerDict, statusCode: number, tags: Tag[]): GetTableDataResponse {
return {
type: GetTableData.SUCCESS,
payload: {
data,
owners,
statusCode,
tags,
}
}
}
export function getTableDescription(onSuccess?: () => any, onFailure?: () => any): GetTableDescriptionRequest { export function getTableDescription(onSuccess?: () => any, onFailure?: () => any): GetTableDescriptionRequest {
return { return {
payload: { payload: {
...@@ -36,6 +54,24 @@ export function getTableDescription(onSuccess?: () => any, onFailure?: () => any ...@@ -36,6 +54,24 @@ export function getTableDescription(onSuccess?: () => any, onFailure?: () => any
type: GetTableDescription.REQUEST, type: GetTableDescription.REQUEST,
}; };
}; };
export function getTableDescriptionFailure(tableMetadata: TableMetadata): GetTableDescriptionResponse {
return {
type: GetTableDescription.FAILURE,
payload: {
tableMetadata
},
};
};
export function getTableDescriptionSuccess(tableMetadata: TableMetadata): GetTableDescriptionResponse {
return {
type: GetTableDescription.SUCCESS,
payload: {
tableMetadata
},
};
};
export function updateTableDescription(newValue: string, onSuccess?: () => any, onFailure?: () => any): UpdateTableDescriptionRequest { export function updateTableDescription(newValue: string, onSuccess?: () => any, onFailure?: () => any): UpdateTableDescriptionRequest {
return { return {
payload: { payload: {
...@@ -46,6 +82,7 @@ export function updateTableDescription(newValue: string, onSuccess?: () => any, ...@@ -46,6 +82,7 @@ export function updateTableDescription(newValue: string, onSuccess?: () => any,
type: UpdateTableDescription.REQUEST, type: UpdateTableDescription.REQUEST,
}; };
}; };
export function getColumnDescription(columnIndex: number, onSuccess?: () => any, onFailure?: () => any): GetColumnDescriptionRequest { export function getColumnDescription(columnIndex: number, onSuccess?: () => any, onFailure?: () => any): GetColumnDescriptionRequest {
return { return {
payload: { payload: {
...@@ -56,6 +93,23 @@ export function getColumnDescription(columnIndex: number, onSuccess?: () => any, ...@@ -56,6 +93,23 @@ export function getColumnDescription(columnIndex: number, onSuccess?: () => any,
type: GetColumnDescription.REQUEST, type: GetColumnDescription.REQUEST,
}; };
}; };
export function getColumnDescriptionFailure(tableMetadata: TableMetadata): GetColumnDescriptionResponse {
return {
type: GetColumnDescription.FAILURE,
payload: {
tableMetadata
},
};
};
export function getColumnDescriptionSuccess(tableMetadata: TableMetadata): GetColumnDescriptionResponse {
return {
type: GetColumnDescription.SUCCESS,
payload: {
tableMetadata
},
};
};
export function updateColumnDescription(newValue: string, columnIndex: number, onSuccess?: () => any, onFailure?: () => any): UpdateColumnDescriptionRequest { export function updateColumnDescription(newValue: string, columnIndex: number, onSuccess?: () => any, onFailure?: () => any): UpdateColumnDescriptionRequest {
return { return {
payload: { payload: {
...@@ -67,12 +121,43 @@ export function updateColumnDescription(newValue: string, columnIndex: number, o ...@@ -67,12 +121,43 @@ export function updateColumnDescription(newValue: string, columnIndex: number, o
type: UpdateColumnDescription.REQUEST, type: UpdateColumnDescription.REQUEST,
}; };
}; };
export function getLastIndexed(): GetLastIndexedRequest { export function getLastIndexed(): GetLastIndexedRequest {
return { type: GetLastIndexed.REQUEST }; return { type: GetLastIndexed.REQUEST };
}; };
export function getLastIndexedFailure(): GetLastIndexedResponse {
return { type: GetLastIndexed.FAILURE };
};
export function getLastIndexedSuccess(lastIndexedEpoch: number): GetLastIndexedResponse {
return {
type: GetLastIndexed.SUCCESS,
payload: {
lastIndexedEpoch,
}
};
};
export function getPreviewData(queryParams: PreviewQueryParams): GetPreviewDataRequest { export function getPreviewData(queryParams: PreviewQueryParams): GetPreviewDataRequest {
return { payload: { queryParams }, type: GetPreviewData.REQUEST }; return { payload: { queryParams }, type: GetPreviewData.REQUEST };
}; };
export function getPreviewDataFailure(data: PreviewData, status: number): GetPreviewDataResponse {
return {
type: GetPreviewData.FAILURE,
payload: {
data,
status,
},
};
};
export function getPreviewDataSuccess(data: PreviewData, status: number): GetPreviewDataResponse {
return {
type: GetPreviewData.SUCCESS,
payload: {
data,
status,
},
};
};
/* REDUCER */ /* REDUCER */
export interface TableMetadataReducerState { export interface TableMetadataReducerState {
...@@ -88,12 +173,12 @@ export interface TableMetadataReducerState { ...@@ -88,12 +173,12 @@ export interface TableMetadataReducerState {
tableTags: TableTagsReducerState; tableTags: TableTagsReducerState;
}; };
const initialPreviewState = { export const initialPreviewState = {
data: {}, data: {},
status: null, status: null,
}; };
const initialTableDataState: TableMetadata = { export const initialTableDataState: TableMetadata = {
cluster: '', cluster: '',
columns: [], columns: [],
database: '', database: '',
...@@ -110,7 +195,7 @@ const initialTableDataState: TableMetadata = { ...@@ -110,7 +195,7 @@ const initialTableDataState: TableMetadata = {
watermarks: [], watermarks: [],
}; };
const initialState: TableMetadataReducerState = { export const initialState: TableMetadataReducerState = {
isLoading: true, isLoading: true,
lastIndexed: null, lastIndexed: null,
preview: initialPreviewState, preview: initialPreviewState,
......
import { SagaIterator } from 'redux-saga'; import { SagaIterator } from 'redux-saga';
import { all, call, put, select, takeEvery } from 'redux-saga/effects'; import { all, call, put, select, takeEvery } from 'redux-saga/effects';
import {
GetLastIndexed, GetLastIndexedRequest,
GetPreviewData, GetPreviewDataRequest,
GetTableData, GetTableDataRequest,
GetColumnDescription, GetColumnDescriptionRequest,
GetTableDescription, GetTableDescriptionRequest,
UpdateColumnDescription, UpdateColumnDescriptionRequest,
UpdateTableDescription, UpdateTableDescriptionRequest,
} from './types';
import { import {
metadataGetLastIndexed, metadataGetLastIndexed,
metadataGetPreviewData, metadataGetPreviewData,
...@@ -21,13 +11,31 @@ import { ...@@ -21,13 +11,31 @@ import {
metadataUpdateTableDescription, metadataUpdateTableDescription,
} from './api/v0'; } from './api/v0';
import {
getTableDataFailure, getTableDataSuccess,
getTableDescriptionFailure, getTableDescriptionSuccess,
getColumnDescriptionFailure, getColumnDescriptionSuccess,
getLastIndexedFailure, getLastIndexedSuccess,
getPreviewDataFailure, getPreviewDataSuccess,
} from './reducer';
import {
GetLastIndexed, GetLastIndexedRequest,
GetPreviewData, GetPreviewDataRequest,
GetTableData, GetTableDataRequest,
GetColumnDescription, GetColumnDescriptionRequest,
GetTableDescription, GetTableDescriptionRequest,
UpdateColumnDescription, UpdateColumnDescriptionRequest,
UpdateTableDescription, UpdateTableDescriptionRequest,
} from './types';
export function* getTableDataWorker(action: GetTableDataRequest): SagaIterator { export function* getTableDataWorker(action: GetTableDataRequest): SagaIterator {
try { try {
const { key, searchIndex, source } = action.payload; const { key, searchIndex, source } = action.payload;
const { data, owners, statusCode, tags } = yield call(metadataGetTableData, key, searchIndex, source); const { data, owners, statusCode, tags } = yield call(metadataGetTableData, key, searchIndex, source);
yield put({ type: GetTableData.SUCCESS, payload: { data, owners, statusCode, tags } }); yield put(getTableDataSuccess(data, owners, statusCode, tags));
} catch (e) { } catch (e) {
yield put({ type: GetTableData.FAILURE, payload: { data: {}, owners: [], statusCode: 500, tags: [] } }); yield put(getTableDataFailure());
} }
}; };
export function* getTableDataWatcher(): SagaIterator { export function* getTableDataWatcher(): SagaIterator {
...@@ -37,15 +45,15 @@ export function* getTableDataWatcher(): SagaIterator { ...@@ -37,15 +45,15 @@ export function* getTableDataWatcher(): SagaIterator {
export function* getTableDescriptionWorker(action: GetTableDescriptionRequest): SagaIterator { export function* getTableDescriptionWorker(action: GetTableDescriptionRequest): SagaIterator {
const { payload } = action; const { payload } = action;
const state = yield select(); const state = yield select();
let tableData; let tableData = state.tableMetadata.tableData;
try { try {
tableData = yield call(metadataGetTableDescription, state.tableMetadata.tableData); tableData = yield call(metadataGetTableDescription, state.tableMetadata.tableData);
yield put({ type: GetTableDescription.SUCCESS, payload: { tableMetadata: tableData } }); yield put(getTableDescriptionSuccess(tableData));
if (payload.onSuccess) { if (payload.onSuccess) {
yield call(payload.onSuccess); yield call(payload.onSuccess);
} }
} catch (e) { } catch (e) {
yield put({ type: GetTableDescription.FAILURE, payload: { tableMetadata: tableData } }); yield put(getTableDescriptionFailure(tableData));
if (payload.onFailure) { if (payload.onFailure) {
yield call(payload.onFailure); yield call(payload.onFailure);
} }
...@@ -76,15 +84,15 @@ export function* updateTableDescriptionWatcher(): SagaIterator { ...@@ -76,15 +84,15 @@ export function* updateTableDescriptionWatcher(): SagaIterator {
export function* getColumnDescriptionWorker(action: GetColumnDescriptionRequest): SagaIterator { export function* getColumnDescriptionWorker(action: GetColumnDescriptionRequest): SagaIterator {
const { payload } = action; const { payload } = action;
const state = yield select(); const state = yield select();
let tableData; let tableData = state.tableMetadata.tableData;
try { try {
tableData = yield call(metadataGetColumnDescription, payload.columnIndex, state.tableMetadata.tableData); tableData = yield call(metadataGetColumnDescription, payload.columnIndex, state.tableMetadata.tableData);
yield put({ type: GetColumnDescription.SUCCESS, payload: { tableMetadata: tableData } }); yield put(getColumnDescriptionSuccess(tableData));
if (payload.onSuccess) { if (payload.onSuccess) {
yield call(payload.onSuccess); yield call(payload.onSuccess);
} }
} catch (e) { } catch (e) {
yield put({ type: GetColumnDescription.FAILURE, payload: { tableMetadata: tableData } }); yield put(getColumnDescriptionFailure(tableData));
if (payload.onFailure) { if (payload.onFailure) {
yield call(payload.onFailure); yield call(payload.onFailure);
} }
...@@ -115,9 +123,9 @@ export function* updateColumnDescriptionWatcher(): SagaIterator { ...@@ -115,9 +123,9 @@ export function* updateColumnDescriptionWatcher(): SagaIterator {
export function* getLastIndexedWorker(action: GetLastIndexedRequest): SagaIterator { export function* getLastIndexedWorker(action: GetLastIndexedRequest): SagaIterator {
try { try {
const lastIndexed = yield call(metadataGetLastIndexed); const lastIndexed = yield call(metadataGetLastIndexed);
yield put({ type: GetLastIndexed.SUCCESS, payload: { lastIndexedEpoch: lastIndexed } }); yield put(getLastIndexedSuccess(lastIndexed));
} catch (e) { } catch (e) {
yield put({ type: GetLastIndexed.FAILURE }); yield put(getLastIndexedFailure());
} }
}; };
export function* getLastIndexedWatcher(): SagaIterator { export function* getLastIndexedWatcher(): SagaIterator {
...@@ -128,11 +136,11 @@ export function* getPreviewDataWorker(action: GetPreviewDataRequest): SagaIterat ...@@ -128,11 +136,11 @@ export function* getPreviewDataWorker(action: GetPreviewDataRequest): SagaIterat
try { try {
const response = yield call(metadataGetPreviewData, action.payload.queryParams); const response = yield call(metadataGetPreviewData, action.payload.queryParams);
const { data, status } = response; const { data, status } = response;
yield put({ type: GetPreviewData.SUCCESS, payload: { data, status } }); yield put(getPreviewDataSuccess(data, status));
} catch (e) { } catch (e) {
const data = e.response ? e.response.data : {}; const data = e.response ? e.response.data : {};
const status = e.response ? e.response.status : null; const status = e.response ? e.response.status : null;
yield put({ type: GetPreviewData.FAILURE, payload: { data, status } }); yield put(getPreviewDataFailure(data, status));
} }
}; };
export function* getPreviewDataWatcher(): SagaIterator { export function* getPreviewDataWatcher(): SagaIterator {
......
...@@ -14,6 +14,22 @@ export function updateTags(tagArray: UpdateTagData[]): UpdateTagsRequest { ...@@ -14,6 +14,22 @@ export function updateTags(tagArray: UpdateTagData[]): UpdateTagsRequest {
type: UpdateTags.REQUEST, type: UpdateTags.REQUEST,
}; };
}; };
export function updateTagsFailure(): UpdateTagsResponse {
return {
type: UpdateTags.FAILURE,
payload: {
tags: [],
}
};
};
export function updateTagsSuccess(tags: Tag[]): UpdateTagsResponse {
return {
type: UpdateTags.SUCCESS,
payload: {
tags
}
};
};
/* REDUCER */ /* REDUCER */
export interface TableTagsReducerState { export interface TableTagsReducerState {
......
import { SagaIterator } from 'redux-saga'; import { SagaIterator } from 'redux-saga';
import { all, call, put, select, takeEvery } from 'redux-saga/effects'; import { all, call, put, select, takeEvery } from 'redux-saga/effects';
import { UpdateTags, UpdateTagsRequest } from '../types';
import { metadataUpdateTableTags, metadataTableTags } from '../api/v0'; import { metadataUpdateTableTags, metadataTableTags } from '../api/v0';
import { updateTagsFailure, updateTagsSuccess } from './reducer';
import { UpdateTags, UpdateTagsRequest } from '../types';
export function* updateTableTagsWorker(action: UpdateTagsRequest): SagaIterator { export function* updateTableTagsWorker(action: UpdateTagsRequest): SagaIterator {
const state = yield select(); const state = yield select();
const tableData = state.tableMetadata.tableData; const tableData = state.tableMetadata.tableData;
try { try {
yield all(metadataUpdateTableTags(action.payload.tagArray, tableData.key)); yield all(metadataUpdateTableTags(action.payload.tagArray, tableData.key));
const newTags = yield call(metadataTableTags, tableData.key); const newTags = yield call(metadataTableTags, tableData.key);
yield put({ type: UpdateTags.SUCCESS, payload: { tags: newTags } }); yield put(updateTagsSuccess(newTags));
} catch (e) { } catch (e) {
yield put({ type: UpdateTags.FAILURE, payload: { tags: [] } }); yield put(updateTagsFailure());
} }
}; };
export function* updateTableTagsWatcher(): SagaIterator { export function* updateTableTagsWatcher(): SagaIterator {
......
import { testSaga } from 'redux-saga-test-plan';
import { UpdateMethod, UpdateTagData, Tag } from 'interfaces';
import globalState from 'fixtures/globalState';
import * as apis from '../../api/v0';
import reducer, {
updateTags, updateTagsFailure, updateTagsSuccess,
initialTagsState, TableTagsReducerState,
} from '../reducer';
import { getTableData, getTableDataFailure, getTableDataSuccess } from '../../reducer';
import { updateTableTagsWorker, updateTableTagsWatcher } from '../sagas';
import { GetTableData, UpdateTags } from '../../types';
const metadataUpdateTableTagsSpy = jest.spyOn(apis, 'metadataUpdateTableTags').mockImplementation((payload, key) => []);
describe('tableMetadata:tags ducks', () => {
let expectedTags: Tag[];
let updatePayload: UpdateTagData[];
beforeAll(() => {
expectedTags = [{tag_count: 2, tag_name: 'test'}, {tag_count: 1, tag_name: 'test2'}];
updatePayload = [{methodName: UpdateMethod.PUT, tagName: 'test'}];
});
describe('actions', () => {
it('updateTags - returns the action to updateTags', () => {
const action = updateTags(updatePayload);
const { payload } = action;
expect(action.type).toBe(UpdateTags.REQUEST);
expect(payload.tagArray).toBe(updatePayload);
});
it('updateTagsFailure - returns the action to process failure', () => {
const action = updateTagsFailure();
const { payload } = action;
expect(action.type).toBe(UpdateTags.FAILURE);
expect(payload.tags).toEqual([]);
});
it('updateTagsSuccess - returns the action to process success', () => {
const action = updateTagsSuccess(expectedTags);
const { payload } = action;
expect(action.type).toBe(UpdateTags.SUCCESS);
expect(payload.tags).toBe(expectedTags);
});
});
describe('reducer', () => {
let testState: TableTagsReducerState;
beforeAll(() => {
testState = initialTagsState;
});
it('should return the existing state if action is not handled', () => {
expect(reducer(testState, { type: 'INVALID.ACTION' })).toEqual(testState);
});
it('should handle UpdateTags.REQUEST', () => {
expect(reducer(testState, updateTags(updatePayload))).toEqual({
...testState,
isLoading: true,
});
});
it('should handle UpdateTags.FAILURE', () => {
expect(reducer(testState, updateTagsFailure())).toEqual({
...testState,
isLoading: false,
});
});
it('should handle UpdateTags.SUCCESS', () => {
expect(reducer(testState, updateTagsSuccess(expectedTags))).toEqual({
...testState,
isLoading: false,
tags: expectedTags,
});
});
it('should handle GetTableData.REQUEST', () => {
expect(reducer(testState, getTableData('testKey'))).toEqual({
...testState,
isLoading: true,
tags: [],
});
});
it('should handle GetTableData.FAILURE', () => {
const action = getTableDataFailure();
expect(reducer(testState, action)).toEqual({
...testState,
isLoading: false,
tags: action.payload.tags,
});
});
it('should handle GetTableData.SUCCESS', () => {
const mockTableData = globalState.tableMetadata.tableData;
expect(reducer(testState, getTableDataSuccess(mockTableData, {}, 200, expectedTags))).toEqual({
...testState,
isLoading: false,
tags: expectedTags,
});
});
});
describe('sagas', () => {
describe('updateTableTagsWatcher', () => {
it('takes every UpdateTags.REQUEST with updateTableTagsWorker', () => {
testSaga(updateTableTagsWatcher)
.next().takeEvery(UpdateTags.REQUEST, updateTableTagsWorker);
});
});
describe('updateTableTagsWorker', () => {
it('executes flow for updating tags and returning up to date tag array', () => {
testSaga(updateTableTagsWorker, updateTags(updatePayload))
.next().select()
.next(globalState).all(apis.metadataUpdateTableTags(updatePayload, globalState.tableMetadata.tableData.key))
.next().call(apis.metadataTableTags, globalState.tableMetadata.tableData.key)
.next(expectedTags).put(updateTagsSuccess(expectedTags))
.next().isDone();
});
it('handles request error', () => {
testSaga(updateTableTagsWorker, updateTags(updatePayload))
.next().select()
.next(globalState).throw(new Error()).put(updateTagsFailure())
.next().isDone();
});
});
});
});
import { import {
OwnerDict,
PreviewData, PreviewData,
PreviewQueryParams, PreviewQueryParams,
TableMetadata, TableMetadata,
...@@ -26,7 +27,7 @@ export interface GetTableDataResponse { ...@@ -26,7 +27,7 @@ export interface GetTableDataResponse {
payload: { payload: {
statusCode: number; statusCode: number;
data: TableMetadata; data: TableMetadata;
owners: { [id: string] : User }; owners: OwnerDict;
tags: Tag[]; tags: Tag[];
} }
}; };
...@@ -155,7 +156,7 @@ export interface UpdateTableOwnerRequest { ...@@ -155,7 +156,7 @@ export interface UpdateTableOwnerRequest {
export interface UpdateTableOwnerResponse { export interface UpdateTableOwnerResponse {
type: UpdateTableOwner.SUCCESS | UpdateTableOwner.FAILURE; type: UpdateTableOwner.SUCCESS | UpdateTableOwner.FAILURE;
payload: { payload: {
owners: { [id: string] : User }; owners: OwnerDict;
}; };
}; };
......
import axios, { AxiosResponse } from 'axios';
import globalState from 'fixtures/globalState';
import { LoggedInUser, PeopleUser, Resource } from 'interfaces';
import {
loggedInUser, userById, userOwn, userRead,
LoggedInUserAPI, UserAPI, UserOwnAPI, UserReadAPI
} from '../v0';
jest.mock('axios');
describe('loggedInUser', () => {
let axiosMock;
let mockGetResponse: AxiosResponse<LoggedInUserAPI>;
let testUser: LoggedInUser;
beforeAll(() => {
testUser = globalState.user.loggedInUser;
mockGetResponse = {
data: {
user: testUser,
msg: 'Success'
},
status: 200,
statusText: '',
headers: {},
config: {}
};
axiosMock = jest.spyOn(axios, 'get').mockImplementation(() => Promise.resolve(mockGetResponse));
});
it('calls axios with correct parameters', async () => {
expect.assertions(1);
await loggedInUser().then(user => {
expect(axiosMock).toHaveBeenCalledWith(`/api/auth_user`);
});
});
it('returns user from response data', async () => {
expect.assertions(1);
await loggedInUser().then(user => {
expect(user).toBe(testUser);
});
});
afterAll(() => {
axiosMock.mockClear();
})
});
describe('userById', () => {
let axiosMock;
let mockGetResponse: AxiosResponse<UserAPI>;
let testId: string;
let testUser: PeopleUser;
beforeAll(() => {
testId = 'testId';
testUser = globalState.user.profile.user;
mockGetResponse = {
data: {
user: testUser,
msg: 'Success'
},
status: 200,
statusText: '',
headers: {},
config: {}
};
axiosMock = jest.spyOn(axios, 'get').mockImplementation(() => Promise.resolve(mockGetResponse));
});
it('calls axios with correct parameters', async () => {
expect.assertions(1);
await userById(testId).then(user => {
expect(axiosMock).toHaveBeenCalledWith(`/api/metadata/v0/user?user_id=${testId}`);
});
});
it('returns user from response data', async () => {
expect.assertions(1);
await userById(testId).then(user => {
expect(user).toBe(testUser);
});
});
afterAll(() => {
axiosMock.mockClear();
})
});
describe('userOwn', () => {
let axiosMock;
let mockGetResponse: AxiosResponse<UserOwnAPI>;
let testId: string;
let testResources: Resource[];
beforeAll(() => {
testId = 'testId';
testResources = globalState.user.profile.own;
mockGetResponse = {
data: {
own: testResources,
msg: 'Success'
},
status: 200,
statusText: '',
headers: {},
config: {}
};
axiosMock = jest.spyOn(axios, 'get').mockImplementation(() => Promise.resolve(mockGetResponse));
});
it('calls axios with correct parameters', async () => {
expect.assertions(1);
await userOwn(testId).then(data => {
expect(axiosMock).toHaveBeenCalledWith(`/api/metadata/v0/user/own?user_id=${testId}`);
});
});
it('returns response data with owned resources', async () => {
expect.assertions(1);
await userOwn(testId).then(data => {
expect(data.own).toBe(testResources);
});
});
afterAll(() => {
axiosMock.mockClear();
})
});
describe('userRead', () => {
let axiosMock;
let mockGetResponse: AxiosResponse<UserReadAPI>;
let testId: string;
let testResources: Resource[];
beforeAll(() => {
testId = 'testId';
testResources = globalState.user.profile.read;
mockGetResponse = {
data: {
read: testResources,
msg: 'Success'
},
status: 200,
statusText: '',
headers: {},
config: {}
};
axiosMock = jest.spyOn(axios, 'get').mockImplementation(() => Promise.resolve(mockGetResponse));
});
it('calls axios with correct parameters', async () => {
expect.assertions(1);
await userRead(testId).then(data => {
expect(axiosMock).toHaveBeenCalledWith(`/api/metadata/v0/user/read?user_id=${testId}`);
});
});
it('returns response data with frequently read resources', async () => {
expect.assertions(1);
await userRead(testId).then(data => {
expect(data.read).toBe(testResources);
});
});
afterAll(() => {
axiosMock.mockClear();
})
});
...@@ -4,31 +4,31 @@ import { LoggedInUser, PeopleUser, Resource } from 'interfaces'; ...@@ -4,31 +4,31 @@ import { LoggedInUser, PeopleUser, Resource } from 'interfaces';
export type LoggedInUserAPI = { user: LoggedInUser; msg: string; }; export type LoggedInUserAPI = { user: LoggedInUser; msg: string; };
export type UserAPI = { user: PeopleUser; msg: string; }; export type UserAPI = { user: PeopleUser; msg: string; };
export type UserOwnAPI= { own: Resource[], msg: string; }; export type UserOwnAPI = { own: Resource[], msg: string; };
export type UserReadAPI = { read: Resource[], msg: string; }; export type UserReadAPI = { read: Resource[], msg: string; };
export function getLoggedInUser() { export function loggedInUser() {
return axios.get(`/api/auth_user`) return axios.get(`/api/auth_user`)
.then((response: AxiosResponse<LoggedInUserAPI>) => { .then((response: AxiosResponse<LoggedInUserAPI>) => {
return response.data.user; return response.data.user;
}); });
} }
export function getUserById(userId: string) { export function userById(userId: string) {
return axios.get(`/api/metadata/v0/user?user_id=${userId}`) return axios.get(`/api/metadata/v0/user?user_id=${userId}`)
.then((response: AxiosResponse<UserAPI>) => { .then((response: AxiosResponse<UserAPI>) => {
return response.data.user; return response.data.user;
}); });
} }
export function getUserOwn(userId: string) { export function userOwn(userId: string) {
return axios.get(`/api/metadata/v0/user/own?user_id=${userId}`) return axios.get(`/api/metadata/v0/user/own?user_id=${userId}`)
.then((response: AxiosResponse<UserOwnAPI>) => { .then((response: AxiosResponse<UserOwnAPI>) => {
return response.data return response.data
}); });
} }
export function getUserRead(userId: string) { export function userRead(userId: string) {
return axios.get(`/api/metadata/v0/user/read?user_id=${userId}`) return axios.get(`/api/metadata/v0/user/read?user_id=${userId}`)
.then((response: AxiosResponse<UserReadAPI>) => { .then((response: AxiosResponse<UserReadAPI>) => {
return response.data return response.data
......
import { LoggedInUser, PeopleUser, Resource } from 'interfaces'; import { LoggedInUser, PeopleUser, Resource } from 'interfaces';
import { import {
GetLoggedInUser, GetLoggedInUser, GetLoggedInUserRequest, GetLoggedInUserResponse,
GetLoggedInUserRequest, GetUser, GetUserRequest, GetUserResponse,
GetLoggedInUserResponse,
GetUser,
GetUserRequest,
GetUserResponse,
GetUserOwn, GetUserOwnRequest, GetUserOwnResponse, GetUserOwn, GetUserOwnRequest, GetUserOwnResponse,
GetUserRead, GetUserReadRequest, GetUserReadResponse, GetUserRead, GetUserReadRequest, GetUserReadResponse,
} from './types'; } from './types';
type UserReducerAction =
GetLoggedInUserRequest | GetLoggedInUserResponse |
GetUserRequest | GetUserResponse |
GetUserOwnRequest | GetUserOwnResponse |
GetUserReadRequest | GetUserReadResponse;
/* ACTIONS */ /* ACTIONS */
export function getLoggedInUser(): GetLoggedInUserRequest { export function getLoggedInUser(): GetLoggedInUserRequest {
return { type: GetLoggedInUser.REQUEST }; return { type: GetLoggedInUser.REQUEST };
}; };
export function getUserById(userId: string): GetUserRequest { export function getLoggedInUserFailure(): GetLoggedInUserResponse {
return { payload: { userId }, type: GetUser.REQUEST }; return { type: GetLoggedInUser.FAILURE };
};
export function getLoggedInUserSuccess(user: LoggedInUser): GetLoggedInUserResponse {
return { type: GetLoggedInUser.SUCCESS, payload: { user } };
};
export function getUser(userId: string): GetUserRequest {
return { type: GetUser.REQUEST, payload: { userId } };
};
export function getUserFailure(): GetUserResponse {
return { type: GetUser.FAILURE };
};
export function getUserSuccess(user: PeopleUser): GetUserResponse {
return { type: GetUser.SUCCESS, payload: { user } };
}; };
export function getUserOwn(userId: string): GetUserOwnRequest { export function getUserOwn(userId: string): GetUserOwnRequest {
return { type: GetUserOwn.REQUEST, payload: { userId }}; return { type: GetUserOwn.REQUEST, payload: { userId }};
}; };
export function getUserOwnFailure(): GetUserOwnResponse {
return { type: GetUserOwn.FAILURE };
};
export function getUserOwnSuccess(own: Resource[]): GetUserOwnResponse {
return { type: GetUserOwn.SUCCESS, payload: { own } };
};
export function getUserRead(userId: string): GetUserReadRequest { export function getUserRead(userId: string): GetUserReadRequest {
return { type: GetUserRead.REQUEST, payload: { userId }}; return { type: GetUserRead.REQUEST, payload: { userId }};
}; };
export function getUserReadFailure(): GetUserReadResponse {
return { type: GetUserRead.FAILURE };
};
export function getUserReadSuccess(read: Resource[]): GetUserReadResponse {
return { type: GetUserRead.SUCCESS, payload: { read } };
};
/* REDUCER */ /* REDUCER */
export interface UserReducerState { export interface UserReducerState {
...@@ -43,7 +58,7 @@ export interface UserReducerState { ...@@ -43,7 +58,7 @@ export interface UserReducerState {
}; };
}; };
const defaultUser = { export const defaultUser = {
display_name: '', display_name: '',
email: '', email: '',
employee_type: '', employee_type: '',
...@@ -59,7 +74,7 @@ const defaultUser = { ...@@ -59,7 +74,7 @@ const defaultUser = {
team_name: '', team_name: '',
user_id: '', user_id: '',
}; };
const initialState: UserReducerState = { export const initialState: UserReducerState = {
loggedInUser: defaultUser, loggedInUser: defaultUser,
profile: { profile: {
own: [], own: [],
...@@ -68,12 +83,12 @@ const initialState: UserReducerState = { ...@@ -68,12 +83,12 @@ const initialState: UserReducerState = {
}, },
}; };
export default function reducer(state: UserReducerState = initialState, action: UserReducerAction): UserReducerState { export default function reducer(state: UserReducerState = initialState, action): UserReducerState {
switch (action.type) { switch (action.type) {
case GetLoggedInUser.SUCCESS: case GetLoggedInUser.SUCCESS:
return { return {
...state, ...state,
loggedInUser: action.payload.user, loggedInUser: (<GetLoggedInUserResponse>action).payload.user,
}; };
case GetUser.REQUEST: case GetUser.REQUEST:
case GetUser.FAILURE: case GetUser.FAILURE:
...@@ -89,7 +104,7 @@ export default function reducer(state: UserReducerState = initialState, action: ...@@ -89,7 +104,7 @@ export default function reducer(state: UserReducerState = initialState, action:
...state, ...state,
profile: { profile: {
...state.profile, ...state.profile,
user: action.payload.user, user: (<GetUserResponse>action).payload.user,
}, },
}; };
case GetUserOwn.REQUEST: case GetUserOwn.REQUEST:
...@@ -106,10 +121,9 @@ export default function reducer(state: UserReducerState = initialState, action: ...@@ -106,10 +121,9 @@ export default function reducer(state: UserReducerState = initialState, action:
...state, ...state,
profile: { profile: {
...state.profile, ...state.profile,
...action.payload, own: (<GetUserOwnResponse>action).payload.own,
} }
}; };
case GetUserRead.REQUEST: case GetUserRead.REQUEST:
case GetUserRead.FAILURE: case GetUserRead.FAILURE:
return { return {
...@@ -124,7 +138,7 @@ export default function reducer(state: UserReducerState = initialState, action: ...@@ -124,7 +138,7 @@ export default function reducer(state: UserReducerState = initialState, action:
...state, ...state,
profile: { profile: {
...state.profile, ...state.profile,
...action.payload, read: (<GetUserReadResponse>action).payload.read,
} }
}; };
default: default:
......
import { SagaIterator } from 'redux-saga'; import { SagaIterator } from 'redux-saga';
import { call, put, takeEvery } from 'redux-saga/effects'; import { call, put, takeEvery } from 'redux-saga/effects';
import { loggedInUser, userById, userOwn, userRead } from './api/v0';
import { import {
GetLoggedInUser, GetLoggedInUser,
GetUser, GetUser,
...@@ -10,14 +12,20 @@ import { ...@@ -10,14 +12,20 @@ import {
GetUserReadRequest, GetUserReadRequest,
GetUserRequest GetUserRequest
} from './types'; } from './types';
import { getLoggedInUser, getUserById, getUserOwn, getUserRead } from './api/v0';
import {
getLoggedInUserFailure, getLoggedInUserSuccess,
getUserFailure, getUserSuccess,
getUserOwnFailure, getUserOwnSuccess,
getUserReadFailure, getUserReadSuccess,
} from './reducer';
export function* getLoggedInUserWorker(): SagaIterator { export function* getLoggedInUserWorker(): SagaIterator {
try { try {
const user = yield call(getLoggedInUser); const user = yield call(loggedInUser);
yield put({ type: GetLoggedInUser.SUCCESS, payload: { user } }); yield put(getLoggedInUserSuccess(user));
} catch (e) { } catch (e) {
yield put({ type: GetLoggedInUser.FAILURE }); yield put(getLoggedInUserFailure());
} }
}; };
export function* getLoggedInUserWatcher(): SagaIterator { export function* getLoggedInUserWatcher(): SagaIterator {
...@@ -26,10 +34,10 @@ export function* getLoggedInUserWatcher(): SagaIterator { ...@@ -26,10 +34,10 @@ export function* getLoggedInUserWatcher(): SagaIterator {
export function* getUserWorker(action: GetUserRequest): SagaIterator { export function* getUserWorker(action: GetUserRequest): SagaIterator {
try { try {
const user = yield call(getUserById, action.payload.userId); const user = yield call(userById, action.payload.userId);
yield put({ type: GetUser.SUCCESS, payload: { user } }); yield put(getUserSuccess(user));
} catch (e) { } catch (e) {
yield put({ type: GetUser.FAILURE }); yield put(getUserFailure());
} }
}; };
export function* getUserWatcher(): SagaIterator { export function* getUserWatcher(): SagaIterator {
...@@ -38,10 +46,10 @@ export function* getUserWatcher(): SagaIterator { ...@@ -38,10 +46,10 @@ export function* getUserWatcher(): SagaIterator {
export function* getUserOwnWorker(action: GetUserOwnRequest): SagaIterator { export function* getUserOwnWorker(action: GetUserOwnRequest): SagaIterator {
try { try {
const userOwn = yield call(getUserOwn, action.payload.userId); const responseData = yield call(userOwn, action.payload.userId);
yield put({ type: GetUserOwn.SUCCESS, payload: { own: userOwn.own }}); yield put(getUserOwnSuccess(responseData.own));
} catch (e) { } catch (e) {
yield put({ type: GetUserOwn.FAILURE }) yield put(getUserOwnFailure())
} }
}; };
...@@ -51,10 +59,10 @@ export function* getUserOwnWatcher(): SagaIterator { ...@@ -51,10 +59,10 @@ export function* getUserOwnWatcher(): SagaIterator {
export function* getUserReadWorker(action: GetUserReadRequest): SagaIterator { export function* getUserReadWorker(action: GetUserReadRequest): SagaIterator {
try { try {
const userRead = yield call(getUserRead, action.payload.userId); const responseData = yield call(userRead, action.payload.userId);
yield put({ type: GetUserRead.SUCCESS, payload: { read: userRead.read }}); yield put(getUserReadSuccess(responseData.read));
} catch (e) { } catch (e) {
yield put({ type: GetUserRead.FAILURE }) yield put(getUserReadFailure())
} }
}; };
......
import { testSaga } from 'redux-saga-test-plan';
import { LoggedInUser, PeopleUser, Resource } from 'interfaces';
import globalState from 'fixtures/globalState';
import { loggedInUser, userById, userOwn, userRead } from '../api/v0';
import reducer, {
getLoggedInUser, getLoggedInUserFailure, getLoggedInUserSuccess,
getUser, getUserFailure, getUserSuccess,
getUserOwn, getUserOwnFailure, getUserOwnSuccess,
getUserRead, getUserReadFailure, getUserReadSuccess,
defaultUser, initialState, UserReducerState,
} from '../reducer';
import {
getLoggedInUserWorker, getLoggedInUserWatcher,
getUserWorker, getUserWatcher,
getUserOwnWorker, getUserOwnWatcher,
getUserReadWorker, getUserReadWatcher,
} from '../sagas';
import {
GetLoggedInUser,
GetUser,
GetUserOwn,
GetUserRead,
} from '../types';
describe('user ducks', () => {
let currentUser: LoggedInUser;
let otherUser: {
own: Resource[],
read: Resource[],
user: PeopleUser,
};
let userId: string;
beforeAll(() => {
currentUser = globalState.user.loggedInUser;
otherUser = globalState.user.profile;
userId = 'testId';
});
describe('actions', () => {
it('getLoggedInUser - returns the action to get the data for the current user', () => {
const action = getLoggedInUser();
expect(action.type).toBe(GetLoggedInUser.REQUEST);
});
it('getLoggedInUserSuccess - returns the action to process the success', () => {
const action = getLoggedInUserSuccess(currentUser);
const { payload } = action;
expect(action.type).toBe(GetLoggedInUser.SUCCESS);
expect(payload.user).toBe(currentUser);
});
it('getLoggedInUserFailure - returns the action to process the failure', () => {
const action = getLoggedInUserFailure();
expect(action.type).toBe(GetLoggedInUser.FAILURE);
});
it('getUser - returns the action to get the data for a user given an id', () => {
const action = getUser(userId);
const { payload } = action;
expect(action.type).toBe(GetUser.REQUEST);
expect(payload.userId).toBe(userId);
});
it('getUserSuccess - returns the action to process the success', () => {
const action = getUserSuccess(otherUser.user);
const { payload } = action;
expect(action.type).toBe(GetUser.SUCCESS);
expect(payload.user).toBe(otherUser.user);
});
it('getUserFailure - returns the action to process the failure', () => {
const action = getUserFailure();
expect(action.type).toBe(GetUser.FAILURE);
});
it('getUserOwn - returns the action to get the owned resources for a user given an id', () => {
const action = getUserOwn(userId);
const { payload } = action;
expect(action.type).toBe(GetUserOwn.REQUEST);
expect(payload.userId).toBe(userId);
});
it('getUserOwnSuccess - returns the action to process the success', () => {
const action = getUserOwnSuccess(otherUser.own);
const { payload } = action;
expect(action.type).toBe(GetUserOwn.SUCCESS);
expect(payload.own).toBe(otherUser.own);
});
it('getUserOwnFailure - returns the action to process the failure', () => {
const action = getUserOwnFailure();
expect(action.type).toBe(GetUserOwn.FAILURE);
});
it('getUserRead - returns the action to get the frequently used resources for a user given an id', () => {
const action = getUserRead(userId);
const { payload } = action;
expect(action.type).toBe(GetUserRead.REQUEST);
expect(payload.userId).toBe(userId);
});
it('getUserReadSuccess - returns the action to process the success', () => {
const action = getUserReadSuccess(otherUser.read);
const { payload } = action;
expect(action.type).toBe(GetUserRead.SUCCESS);
expect(payload.read).toBe(otherUser.read);
});
it('getUserReadFailure - returns the action to process the failure', () => {
const action = getUserReadFailure();
expect(action.type).toBe(GetUserRead.FAILURE);
});
});
describe('reducer', () => {
let testState: UserReducerState;
beforeAll(() => {
testState = initialState;
});
it('should return the existing state if action is not handled', () => {
expect(reducer(testState, { type: 'INVALID.ACTION' })).toEqual(testState);
});
it('should handle GetLoggedInUser.SUCCESS', () => {
expect(reducer(testState, getLoggedInUserSuccess(currentUser))).toEqual({
...testState,
loggedInUser: currentUser,
});
});
it('should handle GetUser.REQUEST', () => {
expect(reducer(testState, getUser(userId))).toEqual({
...testState,
profile: {
...testState.profile,
user: defaultUser,
},
});
});
it('should handle GetUser.FAILURE', () => {
expect(reducer(testState, getUserFailure())).toEqual({
...testState,
profile: {
...testState.profile,
user: defaultUser,
},
});
});
it('should handle GetUser.SUCCESS', () => {
expect(reducer(testState, getUserSuccess(otherUser.user))).toEqual({
...testState,
profile: {
...testState.profile,
user: otherUser.user,
},
});
});
it('should handle GetUserOwn.REQUEST', () => {
expect(reducer(testState, getUserOwn(userId))).toEqual({
...testState,
profile: {
...testState.profile,
own: [],
},
});
});
it('should handle GetUserOwn.FAILURE', () => {
expect(reducer(testState, getUserOwnFailure())).toEqual({
...testState,
profile: {
...testState.profile,
own: [],
},
});
});
it('should handle GetUserOwn.SUCCESS', () => {
expect(reducer(testState, getUserOwnSuccess(otherUser.own))).toEqual({
...testState,
profile: {
...testState.profile,
own: otherUser.own,
},
});
});
it('should handle GetUserRead.REQUEST', () => {
expect(reducer(testState, getUserRead(userId))).toEqual({
...testState,
profile: {
...testState.profile,
read: [],
},
});
});
it('should handle GetUserRead.FAILURE', () => {
expect(reducer(testState, getUserReadFailure())).toEqual({
...testState,
profile: {
...testState.profile,
read: [],
},
});
});
it('should handle GetUserRead.SUCCESS', () => {
expect(reducer(testState, getUserReadSuccess(otherUser.read))).toEqual({
...testState,
profile: {
...testState.profile,
read: otherUser.read,
},
});
});
});
describe('sagas', () => {
describe('getLoggedInUserWatcher', () => {
it('takes every GetLoggedInUser.REQUEST with getLoggedInUserWorker', () => {
testSaga(getLoggedInUserWatcher)
.next().takeEvery(GetLoggedInUser.REQUEST, getLoggedInUserWorker);
});
});
describe('getLoggedInUserWorker', () => {
it('executes flow for returning the currentUser', () => {
testSaga(getLoggedInUserWorker, getLoggedInUser())
.next().call(loggedInUser)
.next(currentUser).put(getLoggedInUserSuccess(currentUser))
.next().isDone();
});
it('handles request error', () => {
testSaga(getLoggedInUserWorker, getLoggedInUser())
.next().throw(new Error()).put(getLoggedInUserFailure())
.next().isDone();
});
});
describe('getUserWatcher', () => {
it('takes every GetUser.REQUEST with getUserWorker', () => {
testSaga(getUserWatcher)
.next().takeEvery(GetUser.REQUEST, getUserWorker);
});
});
describe('getUserWorker', () => {
it('executes flow for returning a user given an id', () => {
testSaga(getUserWorker, getUser(userId))
.next().call(userById, userId)
.next(otherUser.user).put(getUserSuccess(otherUser.user))
.next().isDone();
});
it('handles request error', () => {
testSaga(getUserWorker, getUser(userId))
.next().throw(new Error()).put(getUserFailure())
.next().isDone();
});
});
describe('getUserOwnWatcher', () => {
it('takes every GetUserOwn.REQUEST with getUserOwnWorker', () => {
testSaga(getUserOwnWatcher)
.next().takeEvery(GetUserOwn.REQUEST, getUserOwnWorker);
});
});
describe('getUserOwnWorker', () => {
it('executes flow for returning a users owned resources given an id', () => {
testSaga(getUserOwnWorker, getUserOwn(userId))
.next().call(userOwn, userId)
.next(otherUser).put(getUserOwnSuccess(otherUser.own))
.next().isDone();
});
it('handles request error', () => {
testSaga(getUserOwnWorker, getUserOwn(userId))
.next().throw(new Error()).put(getUserOwnFailure())
.next().isDone();
});
});
describe('getUserReadWatcher', () => {
it('takes every GetUserRead.REQUEST with getUserReadWorker', () => {
testSaga(getUserReadWatcher)
.next().takeEvery(GetUserRead.REQUEST, getUserReadWorker);
});
});
describe('getUserReadWorker', () => {
it('executes flow for returning a users frequently used resources given an id', () => {
testSaga(getUserReadWorker, getUserRead(userId))
.next().call(userRead, userId)
.next(otherUser).put(getUserReadSuccess(otherUser.read))
.next().isDone();
});
it('handles request error', () => {
testSaga(getUserReadWorker, getUserRead(userId))
.next().throw(new Error()).put(getUserReadFailure())
.next().isDone();
});
});
});
});
...@@ -34,21 +34,17 @@ export interface GetUserResponse { ...@@ -34,21 +34,17 @@ export interface GetUserResponse {
}; };
}; };
/* getUserOwn */
export enum GetUserOwn { export enum GetUserOwn {
REQUEST = 'amundsen/user/own/GET_REQUEST', REQUEST = 'amundsen/user/own/GET_REQUEST',
SUCCESS = 'amundsen/user/own/GET_SUCCESS', SUCCESS = 'amundsen/user/own/GET_SUCCESS',
FAILURE = 'amundsen/user/own/GET_FAILURE', FAILURE = 'amundsen/user/own/GET_FAILURE',
} }
export interface GetUserOwnRequest { export interface GetUserOwnRequest {
type: GetUserOwn.REQUEST; type: GetUserOwn.REQUEST;
payload: { payload: {
userId: string; userId: string;
}; };
}; };
export interface GetUserOwnResponse { export interface GetUserOwnResponse {
type: GetUserOwn.SUCCESS | GetUserOwn.FAILURE; type: GetUserOwn.SUCCESS | GetUserOwn.FAILURE;
payload?: { payload?: {
...@@ -56,20 +52,17 @@ export interface GetUserOwnResponse { ...@@ -56,20 +52,17 @@ export interface GetUserOwnResponse {
}; };
}; };
/* getUserRead */
export enum GetUserRead { export enum GetUserRead {
REQUEST = 'amundsen/user/read/GET_REQUEST', REQUEST = 'amundsen/user/read/GET_REQUEST',
SUCCESS = 'amundsen/user/read/GET_SUCCESS', SUCCESS = 'amundsen/user/read/GET_SUCCESS',
FAILURE = 'amundsen/user/read/GET_FAILURE', FAILURE = 'amundsen/user/read/GET_FAILURE',
} }
export interface GetUserReadRequest { export interface GetUserReadRequest {
type: GetUserRead.REQUEST; type: GetUserRead.REQUEST;
payload: { payload: {
userId: string; userId: string;
}; };
}; };
export interface GetUserReadResponse { export interface GetUserReadResponse {
type: GetUserRead.SUCCESS | GetUserRead.FAILURE; type: GetUserRead.SUCCESS | GetUserRead.FAILURE;
payload?: { payload?: {
......
...@@ -27,3 +27,5 @@ export interface PeopleUser { ...@@ -27,3 +27,5 @@ export interface PeopleUser {
}; };
export type LoggedInUser = PeopleUser & {}; export type LoggedInUser = PeopleUser & {};
export type OwnerDict = { [id: string] : User };
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment