Skip to content
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

Fix selectAPICallMultiple & reducers #3

Merged
merged 1 commit into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions src/store/api/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,24 @@ const APIReducer = (state = INIT_STATE, action) => {
case API_CALL:
let api = getAPIFn(action.apiName);
let key = api.urlFunction(...(action.args || []));
let newCallState = state.calls[key] ? { ...state.calls[key] } : {};
if (newCallState.state !== "LOADED") newCallState.state = "LOADING";
if (action.retry !== undefined) newCallState.retries = action.retry;
state = { ...state, calls: { ...state.calls, [key]: newCallState } };
state = modifyNode(state, ["calls", key], (call) => {
if (call !== undefined && call.state === "LOADED" && call.retries === undefined) {
return call;
}
call = call || {};
call.state = call.state !== "LOADED" ? "LOADING" : call.state;
if (action.retry !== undefined) call.retries = action.retry;
return call;
});
break;

case API_CALL_SUCCESS:
state = modifyNode(state, ["call_metadata", action.call_key], (x) => {
return { ...(x || {}), timestamp: action.timestamp };
});
state = modifyNode(state, ["calls", action.call_key], () => {
state = modifyNode(state, ["calls", action.call_key], (call) => {
if (call !== undefined && call.state === "LOADED" && call.retries === undefined && call.value === action.value)
return call;
return { state: "LOADED", value: action.value, code: action.code };
});
break;
Expand Down
158 changes: 158 additions & 0 deletions src/store/api/selector.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import _ from "lodash";
import Big from "big.js";
import store from "../index.js";
import assert from "assert";
import * as apiRegistry from "../../helpers/apiRegistry";
import { selectAPICallMultiple, getCallKey } from "./selectors";
import MockAdapter from "axios-mock-adapter";
import * as api_calls from "../../utils/helpers/api_calls";
import * as mock_helper from "../../helpers/mock_helper";
import axios from "axios";

const sinon = require("sinon");

const baseUrl = "https://testapi.com/api";
const currencyAddress = "0x94a9D9AC8a22534E3FaCa9F4e7F2E2cf85d5E4C8";
let axiosMock;

beforeEach(() => {
axiosMock = new MockAdapter(axios);
mock_helper.setupRiskFieldGets(axiosMock, currencyAddress);

apiRegistry.registerAPI(
"apy",
(address, days = 7) => `${baseUrl}/etokens/${address}/apr/?days_from=${days}`,
(response) => api_calls.toDecimal(response.apy)
);

apiRegistry.registerAPI(
"activePremiums",
(address, daysFrom = 90, daysTo) =>
`${baseUrl}/riskmodules/${address}/active_premiums/` +
api_calls.makeQueryParams(api_calls.addDaysParams(daysFrom, daysTo)),
(response) => response.data
);
});

afterEach(() => {
store.dispatch({ type: "RESET_ALL" });
sinon.restore();
axiosMock.restore();
});

describe("selectAPICallMultiple with activePremiums and apy", () => {
test("should return call_key for apy", () => {
const expectedUrl = `${baseUrl}/etokens/${currencyAddress}/apr/?days_from=7`;
const apiFunction = apiRegistry.getAPI("apy");

const callKey = apiFunction.urlFunction(currencyAddress);

expect(callKey).toEqual(expectedUrl);
});

test("should return call_key for activePremiums", () => {
const expectedUrl = `${baseUrl}/riskmodules/${currencyAddress}/active_premiums/?days_from=90`;
const apiFunction = apiRegistry.getAPI("activePremiums");

const callKey = apiFunction.urlFunction(currencyAddress, 90);

expect(callKey).toEqual(expectedUrl);
});

test("should return the corresponding calls for apy when present with actions", async () => {
const call_key = getCallKey({ currentChain: { id: 1234, rpc: "https://foo-rpc.com/" } }, "apy", [currencyAddress]);
store.dispatch({ type: "API_CALL", apiName: "apy", args: [currencyAddress] });

let result = selectAPICallMultiple(store.getState().APIReducer, [{ apiName: "apy", args: [currencyAddress] }]);
expect(result).toEqual([{ state: "LOADING" }]);

store.dispatch({ type: "API_CALL_SUCCESS", call_key: call_key, value: 1000, timestamp: new Date().getTime() });
result = selectAPICallMultiple(store.getState().APIReducer, [{ apiName: "apy", args: [currencyAddress] }]);
expect(result).toEqual([{ state: "LOADED", value: 1000 }]);

let result2 = selectAPICallMultiple(store.getState().APIReducer, [{ apiName: "apy", args: [currencyAddress] }]);
assert.strictEqual(result[0], result2[0]);
});

test("should return the corresponding calls for activePremiums with different states", async () => {
const call_key = getCallKey({ currentChain: { id: 1234, rpc: "https://foo-rpc.com/" } }, "activePremiums", [
currencyAddress,
]);
store.dispatch({ type: "API_CALL", apiName: "activePremiums", args: [currencyAddress] });

let result = selectAPICallMultiple(store.getState().APIReducer, [
{ apiName: "activePremiums", args: [currencyAddress] },
]);
expect(result).toEqual([{ state: "LOADING" }]);

store.dispatch({
type: "API_CALL_SUCCESS",
call_key: call_key,
value: `ret${currencyAddress}activePremiums`,
timestamp: new Date().getTime(),
});

result = selectAPICallMultiple(store.getState().APIReducer, [
{ apiName: "activePremiums", args: [currencyAddress] },
]);

expect(result).toEqual([{ state: "LOADED", value: `ret${currencyAddress}activePremiums` }]);
});

test("should handle multiple calls with different states for apy and activePremiums", async () => {
const apyCallKey = getCallKey({ currentChain: { id: 1234, rpc: "https://foo-rpc.com/" } }, "apy", [
currencyAddress,
]);

const activePremiumsCallKey = getCallKey(
{ currentChain: { id: 1234, rpc: "https://foo-rpc.com/" } },
"activePremiums",
[currencyAddress]
);

store.dispatch({ type: "API_CALL", apiName: "apy", args: [currencyAddress] });
store.dispatch({
type: "API_CALL",
apiName: "activePremiums",
args: [currencyAddress],
});

let result = selectAPICallMultiple(store.getState().APIReducer, [
{ apiName: "apy", args: [currencyAddress] },
{ apiName: "activePremiums", args: [currencyAddress] },
]);

expect(result).toEqual([{ state: "LOADING" }, { state: "LOADING" }]);

store.dispatch({
type: "API_CALL_SUCCESS",
call_key: apyCallKey,
value: 1000,
timestamp: new Date().getTime(),
});

result = selectAPICallMultiple(store.getState().APIReducer, [
{ apiName: "apy", args: [currencyAddress] },
{ apiName: "activePremiums", args: [currencyAddress] },
]);

expect(result).toEqual([{ state: "LOADED", value: 1000 }, { state: "LOADING" }]);

store.dispatch({
type: "API_CALL_SUCCESS",
call_key: activePremiumsCallKey,
value: `ret${currencyAddress}activePremiums`,
timestamp: new Date().getTime(),
});

result = selectAPICallMultiple(store.getState().APIReducer, [
{ apiName: "apy", args: [currencyAddress] },
{ apiName: "activePremiums", args: [currencyAddress] },
]);

expect(result).toEqual([
{ state: "LOADED", value: 1000 },
{ state: "LOADED", value: `ret${currencyAddress}activePremiums` },
]);
});
});
6 changes: 4 additions & 2 deletions src/store/api/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import _ from "lodash";
import { createSelector } from "reselect";
import { getAPIFn } from "../../package-index";

const EMPTYSTATE = {};

const getCalls = (state) => state.calls;
const getCallMetadata = (state) => state.call_metadata;
const getCallKey = (__, apiName, args) => {
export const getCallKey = (__, apiName, args) => {
let api = getAPIFn(apiName);
return api.urlFunction(...(args || []));
};
Expand Down Expand Up @@ -37,6 +39,6 @@ export const selectAPICallState = createSelector(

export const selectAPICallMultiple = createSelector([getCalls, getCallKeys], (calls, callKeys) =>
_.map(callKeys, (callKey) => {
return calls[callKey] === undefined ? {} : { value: calls[callKey].value, state: calls[callKey].state };
return !calls || calls[callKey] === undefined ? EMPTYSTATE : calls[callKey];
})
);
Loading