-
Notifications
You must be signed in to change notification settings - Fork 4
fix: migrate payment profiles to MUI components #925
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| import configureStore from "redux-mock-store"; | ||
| import thunk from "redux-thunk"; | ||
| import flushPromises from "flush-promises"; | ||
| import { getRequest } from "openstack-uicore-foundation/lib/utils/actions"; | ||
| import { getPaymentProfiles } from "../ticket-actions"; | ||
| import * as methods from "../../utils/methods"; | ||
|
|
||
| jest.mock("openstack-uicore-foundation/lib/utils/actions", () => ({ | ||
| __esModule: true, | ||
| ...jest.requireActual("openstack-uicore-foundation/lib/utils/actions"), | ||
| getRequest: jest.fn() | ||
| })); | ||
|
|
||
| describe("getPaymentProfiles", () => { | ||
| const middlewares = [thunk]; | ||
| const mockStore = configureStore(middlewares); | ||
| const SUMMIT_ID = 42; | ||
| let store; | ||
| let capturedParams; | ||
|
|
||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| capturedParams = null; | ||
| store = mockStore({ | ||
| currentSummitState: { currentSummit: { id: SUMMIT_ID } } | ||
| }); | ||
| jest.spyOn(methods, "getAccessTokenSafely").mockResolvedValue("TOKEN"); | ||
| window.PURCHASES_API_URL = "https://purchases.example.com"; | ||
|
|
||
| getRequest.mockImplementation( | ||
| (reqAC, recAC, url, errHandler, extraPayload) => (params) => { | ||
| capturedParams = params; | ||
| return (dispatch) => { | ||
| dispatch(reqAC(extraPayload || {})); | ||
| return Promise.resolve().then(() => { | ||
| dispatch( | ||
| recAC({ | ||
| response: { | ||
| data: [], | ||
| total: 0, | ||
| current_page: 1, | ||
| last_page: 1 | ||
| } | ||
| }) | ||
| ); | ||
| }); | ||
| }; | ||
| } | ||
| ); | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| jest.restoreAllMocks(); | ||
| delete window.PURCHASES_API_URL; | ||
| }); | ||
|
|
||
| test("dispatches START_LOADING, REQUEST, RECEIVE, STOP_LOADING", async () => { | ||
| store.dispatch(getPaymentProfiles()); | ||
| await flushPromises(); | ||
|
|
||
| const types = store.getActions().map((a) => a.type); | ||
| expect(types).toEqual([ | ||
| "START_LOADING", | ||
| "REQUEST_PAYMENT_PROFILES", | ||
| "RECEIVE_PAYMENT_PROFILES", | ||
| "STOP_LOADING" | ||
| ]); | ||
| expect(getRequest).toHaveBeenCalledTimes(1); | ||
| }); | ||
|
|
||
| test("builds correct summit URL", async () => { | ||
| store.dispatch(getPaymentProfiles()); | ||
| await flushPromises(); | ||
|
|
||
| const url = getRequest.mock.calls[0][2]; | ||
| expect(url).toBe( | ||
| `https://purchases.example.com/api/v1/summits/${SUMMIT_ID}/payment-profiles` | ||
| ); | ||
| }); | ||
|
|
||
| test.each(["", undefined])("omits filter[] when term is %p", async (term) => { | ||
| store.dispatch(getPaymentProfiles(term)); | ||
| await flushPromises(); | ||
|
|
||
| expect(capturedParams).not.toHaveProperty("filter[]"); | ||
| }); | ||
|
|
||
| test("adds filter[] array with provider, id, application_type for non-empty term", async () => { | ||
| store.dispatch(getPaymentProfiles("stripe")); | ||
| await flushPromises(); | ||
|
|
||
| expect(capturedParams["filter[]"]).toEqual([ | ||
| "provider=@stripe,id=@stripe,application_type=@stripe" | ||
| ]); | ||
| }); | ||
|
|
||
| test.each([ | ||
| [ | ||
| "foo,bar", | ||
| "provider=@foo\\,bar,id=@foo\\,bar,application_type=@foo\\,bar" | ||
| ], | ||
| ["foo;bar", "provider=@foo\\;bar,id=@foo\\;bar,application_type=@foo\\;bar"] | ||
| ])( | ||
| "escapeFilterValue escapes special chars in %p", | ||
| async (term, expected) => { | ||
| store.dispatch(getPaymentProfiles(term)); | ||
| await flushPromises(); | ||
|
|
||
| expect(capturedParams["filter[]"]).toEqual([expected]); | ||
| } | ||
| ); | ||
|
|
||
| test("REQUEST_PAYMENT_PROFILES payload includes term, page, perPage, order, orderDir", async () => { | ||
| store.dispatch(getPaymentProfiles("stripe", 2, 25, "provider", 0)); | ||
| await flushPromises(); | ||
|
|
||
| const extraPayload = getRequest.mock.calls[0][4]; | ||
| expect(extraPayload).toEqual({ | ||
| term: "stripe", | ||
| page: 2, | ||
| perPage: 25, | ||
| order: "provider", | ||
| orderDir: 0 | ||
| }); | ||
| }); | ||
|
|
||
| test("params include access_token, page, and per_page", async () => { | ||
| store.dispatch(getPaymentProfiles("", 3, 20)); | ||
| await flushPromises(); | ||
|
|
||
| expect(capturedParams).toMatchObject({ | ||
| access_token: "TOKEN", | ||
| page: 3, | ||
| per_page: 20 | ||
| }); | ||
| }); | ||
|
|
||
| test.each([ | ||
| [1, "+provider"], | ||
| [0, "-provider"] | ||
| ])( | ||
| "order param uses correct prefix for orderDir %i", | ||
| async (orderDir, expected) => { | ||
| store.dispatch(getPaymentProfiles("", 1, 10, "provider", orderDir)); | ||
| await flushPromises(); | ||
|
|
||
| expect(capturedParams.order).toBe(expected); | ||
| } | ||
| ); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -47,6 +47,7 @@ import { | |
| EXPORT_PAGE_SIZE_100, | ||
| TEN | ||
| } from "../utils/constants"; | ||
| import { snackbarErrorHandler, snackbarSuccessHandler } from "./base-actions"; | ||
|
|
||
| export const REQUEST_TICKETS = "REQUEST_TICKETS"; | ||
| export const RECEIVE_TICKETS = "RECEIVE_TICKETS"; | ||
|
|
@@ -1186,6 +1187,7 @@ export const deleteRefundPolicy = | |
|
|
||
| export const getPaymentProfiles = | ||
| ( | ||
| term = "", | ||
| page = DEFAULT_CURRENT_PAGE, | ||
| perPage = DEFAULT_PER_PAGE, | ||
| order = "id", | ||
|
|
@@ -1195,6 +1197,7 @@ export const getPaymentProfiles = | |
| const { currentSummitState } = getState(); | ||
| const accessToken = await getAccessTokenSafely(); | ||
| const { currentSummit } = currentSummitState; | ||
| const filter = []; | ||
|
|
||
| dispatch(startLoading()); | ||
|
|
||
|
|
@@ -1204,19 +1207,30 @@ export const getPaymentProfiles = | |
| access_token: accessToken | ||
| }; | ||
|
|
||
| if (term) { | ||
| const escapedTerm = escapeFilterValue(term); | ||
| filter.push( | ||
| `provider=@${escapedTerm},id=@${escapedTerm},application_type=@${escapedTerm}` | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 [LOW] Search filter uses
Fix: Use |
||
| ); | ||
| } | ||
|
|
||
| // order | ||
| if (order != null && orderDir != null) { | ||
| const orderDirSign = orderDir === DEFAULT_ORDER_DIR ? "+" : "-"; | ||
| params.order = `${orderDirSign}${order}`; | ||
| } | ||
|
|
||
| if (filter.length > 0) { | ||
| params["filter[]"] = filter; | ||
| } | ||
|
|
||
| return getRequest( | ||
| createAction(REQUEST_PAYMENT_PROFILES), | ||
| createAction(RECEIVE_PAYMENT_PROFILES), | ||
|
|
||
| `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/payment-profiles`, | ||
| authErrorHandler, | ||
| { page, perPage, order, orderDir } | ||
| snackbarErrorHandler, | ||
| { term, page, perPage, order, orderDir } | ||
| )(params)(dispatch).then(() => { | ||
| dispatch(stopLoading()); | ||
| }); | ||
|
|
@@ -1232,41 +1246,34 @@ export const savePaymentProfile = (entity) => async (dispatch, getState) => { | |
| }; | ||
|
|
||
| if (entity.id) { | ||
| putRequest( | ||
| return putRequest( | ||
| createAction(UPDATE_PAYMENT_PROFILE), | ||
| createAction(PAYMENT_PROFILE_UPDATED), | ||
| `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/payment-profiles/${entity.id}`, | ||
| entity, | ||
| authErrorHandler, | ||
| snackbarErrorHandler, | ||
| entity | ||
| )(params)(dispatch).then(() => { | ||
| dispatch( | ||
| showSuccessMessage( | ||
| T.translate("edit_payment_profile.payment_profile_saved") | ||
| ) | ||
| snackbarSuccessHandler({ | ||
| title: T.translate("general.success"), | ||
| html: T.translate("edit_payment_profile.payment_profile_saved") | ||
| }) | ||
| ); | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| const success_message = { | ||
| title: T.translate("general.done"), | ||
| html: T.translate("edit_payment_profile.payment_profile_created"), | ||
| type: "success" | ||
| }; | ||
|
|
||
| postRequest( | ||
| return postRequest( | ||
| createAction(UPDATE_PAYMENT_PROFILE), | ||
| createAction(PAYMENT_PROFILE_ADDED), | ||
| `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/payment-profiles`, | ||
| entity, | ||
| authErrorHandler | ||
| )(params)(dispatch).then((payload) => { | ||
| snackbarErrorHandler | ||
| )(params)(dispatch).then(() => { | ||
| dispatch( | ||
| showMessage(success_message, () => { | ||
| history.push( | ||
| `/app/summits/${currentSummit.id}/payment-profiles/${payload.response.id}` | ||
| ); | ||
| snackbarSuccessHandler({ | ||
| title: T.translate("general.success"), | ||
| html: T.translate("edit_payment_profile.payment_profile_created") | ||
| }) | ||
| ); | ||
| }); | ||
|
|
@@ -1287,7 +1294,7 @@ export const deletePaymentProfile = | |
| createAction(PAYMENT_PROFILE_DELETED)({ paymentProfileId }), | ||
| `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/payment-profiles/${paymentProfileId}`, | ||
| null, | ||
| authErrorHandler | ||
| snackbarErrorHandler | ||
| )(params)(dispatch).then(() => { | ||
| dispatch(stopLoading()); | ||
| }); | ||
|
|
@@ -1309,7 +1316,7 @@ export const getPaymentProfile = | |
| null, | ||
| createAction(RECEIVE_PAYMENT_PROFILE), | ||
| `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/payment-profiles/${paymentProfileId}`, | ||
| authErrorHandler | ||
| snackbarErrorHandler | ||
| )(params)(dispatch).then(() => { | ||
| dispatch(stopLoading()); | ||
| }); | ||
|
|
@@ -1347,7 +1354,7 @@ export const getPaymentFeeTypes = | |
| createAction(RECEIVE_PAYMENT_FEE_TYPES), | ||
|
|
||
| `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/payment-profiles/${paymentProfileId}/fee-types`, | ||
| authErrorHandler, | ||
| snackbarErrorHandler, | ||
| { page, perPage, order, orderDir } | ||
| )(params)(dispatch).then(() => { | ||
| dispatch(stopLoading()); | ||
|
|
@@ -1366,45 +1373,44 @@ export const savePaymentFeeType = (entity) => async (dispatch, getState) => { | |
| access_token: accessToken | ||
| }; | ||
|
|
||
| dispatch(startLoading()); | ||
|
|
||
| if (entity.id) { | ||
| putRequest( | ||
| return putRequest( | ||
| createAction(UPDATE_PAYMENT_FEE_TYPE), | ||
| createAction(PAYMENT_FEE_TYPE_UPDATED), | ||
| `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/payment-profiles/${paymentProfileId}/fee-types/${entity.id}`, | ||
| entity, | ||
| authErrorHandler, | ||
| snackbarErrorHandler, | ||
| entity | ||
| )(params)(dispatch).then(() => { | ||
| dispatch( | ||
| showSuccessMessage( | ||
| T.translate("edit_payment_fee_type.payment_fee_type_saved") | ||
| ) | ||
| ); | ||
| }); | ||
| return; | ||
| )(params)(dispatch) | ||
| .then(() => { | ||
| dispatch( | ||
| snackbarSuccessHandler({ | ||
| title: T.translate("general.success"), | ||
| html: T.translate("edit_payment_fee_type.payment_fee_type_saved") | ||
| }) | ||
| ); | ||
| }) | ||
| .finally(() => dispatch(stopLoading())); | ||
| } | ||
|
|
||
| const success_message = { | ||
| title: T.translate("general.done"), | ||
| html: T.translate("edit_payment_fee_type.payment_fee_type_created"), | ||
| type: "success" | ||
| }; | ||
|
|
||
| postRequest( | ||
| return postRequest( | ||
| createAction(UPDATE_PAYMENT_FEE_TYPE), | ||
| createAction(PAYMENT_FEE_TYPE_ADDED), | ||
| `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/payment-profiles/${paymentProfileId}/fee-types`, | ||
| entity, | ||
| authErrorHandler | ||
| )(params)(dispatch).then(() => { | ||
| dispatch( | ||
| showMessage(success_message, () => { | ||
| history.push( | ||
| `/app/summits/${currentSummit.id}/payment-profiles/${paymentProfileId}` | ||
| ); | ||
| }) | ||
| ); | ||
| }); | ||
| snackbarErrorHandler | ||
| )(params)(dispatch) | ||
| .then(() => { | ||
| dispatch( | ||
| snackbarSuccessHandler({ | ||
| title: T.translate("general.success"), | ||
| html: T.translate("edit_payment_fee_type.payment_fee_type_created") | ||
| }) | ||
| ); | ||
| }) | ||
| .finally(() => dispatch(stopLoading())); | ||
| }; | ||
|
|
||
| export const deletePaymentFeeType = | ||
|
|
@@ -1425,7 +1431,7 @@ export const deletePaymentFeeType = | |
| createAction(PAYMENT_FEE_TYPE_DELETED)({ paymentFeeTypeId }), | ||
| `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/payment-profiles/${paymentProfileId}/fee-types/${paymentFeeTypeId}`, | ||
| null, | ||
| authErrorHandler | ||
| snackbarErrorHandler | ||
| )(params)(dispatch).then(() => { | ||
| dispatch(stopLoading()); | ||
| }); | ||
|
|
@@ -1450,7 +1456,7 @@ export const getPaymentFeeType = | |
| null, | ||
| createAction(RECEIVE_PAYMENT_FEE_TYPE), | ||
| `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/payment-profiles/${paymentProfileId}/fee-types/${paymentFeeTypeId}`, | ||
| authErrorHandler | ||
| snackbarErrorHandler | ||
| )(params)(dispatch).then(() => { | ||
| dispatch(stopLoading()); | ||
| }); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 [MEDIUM] Missing test coverage for the new behavior
No
__tests__updates ship with this PR for any of the new surfaces. The following should be covered before merge:getPaymentProfileswithterm— URL/params construction with non-emptyterm(verifyescapeFilterValue, thefilter[]array shape, and the newREQUEST_PAYMENT_PROFILESpayload).REQUEST_PAYMENT_PROFILES— asserts thatterm,currentPage,perPageland in state (catches the bug flagged inpayment-profile-list-reducer.js#L46).PaymentProfileDialogflow —payment-profile-list-page.js#L110),.catchbehaviour),This is a UX-critical surface (payment configuration). At minimum the action + reducer should be covered.