Skip to content

Commit cf16519

Browse files
authored
Merge pull request #2476 from anth-volk/feat/multi-year-call
Add function to make sequential API requests
2 parents 164ed73 + 0030237 commit cf16519

4 files changed

Lines changed: 408 additions & 2 deletions

File tree

package-lock.json

Lines changed: 44 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@
6060
"workbox-range-requests": "^7.3.0",
6161
"workbox-routing": "^7.3.0",
6262
"workbox-strategies": "^7.0.0",
63-
"workbox-streams": "^7.3.0"
63+
"workbox-streams": "^7.3.0",
64+
"yup": "^1.6.1"
6465
},
6566
"scripts": {
6667
"start": "react-scripts start",
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
import { makeSequentialRequests } from "../../api/makeSequentialRequests";
2+
import * as callModule from "../../api/call";
3+
4+
jest.mock("../../api/call", () => ({
5+
...jest.requireActual("../../api/call"),
6+
countryApiCall: jest.fn(),
7+
}));
8+
9+
describe("makeSequentialRequests", () => {
10+
beforeEach(() => {
11+
jest.clearAllMocks();
12+
console.error = jest.fn(); // Prevent console error output during tests
13+
});
14+
15+
describe("Given a list of successful requests", () => {
16+
test("it should process all requests sequentially and return successful results", async () => {
17+
const requests = [
18+
{ countryId: "us", path: "/api/users", method: "GET" },
19+
{ countryId: "ca", path: "/api/posts", method: "GET" },
20+
{ countryId: "uk", path: "/api/comments", method: "GET" },
21+
];
22+
23+
const mockResponses = [
24+
{ data: "users data" },
25+
{ data: "posts data" },
26+
{ data: "comments data" },
27+
];
28+
29+
// Mock successful responses
30+
callModule.countryApiCall
31+
.mockResolvedValueOnce(mockResponses[0])
32+
.mockResolvedValueOnce(mockResponses[1])
33+
.mockResolvedValueOnce(mockResponses[2]);
34+
35+
const result = await makeSequentialRequests(requests);
36+
37+
expect(callModule.countryApiCall).toHaveBeenCalledTimes(3);
38+
expect(callModule.countryApiCall).toHaveBeenNthCalledWith(
39+
1,
40+
"us",
41+
"/api/users",
42+
undefined,
43+
"GET",
44+
undefined,
45+
undefined,
46+
);
47+
expect(callModule.countryApiCall).toHaveBeenNthCalledWith(
48+
2,
49+
"ca",
50+
"/api/posts",
51+
undefined,
52+
"GET",
53+
undefined,
54+
undefined,
55+
);
56+
expect(callModule.countryApiCall).toHaveBeenNthCalledWith(
57+
3,
58+
"uk",
59+
"/api/comments",
60+
undefined,
61+
"GET",
62+
undefined,
63+
undefined,
64+
);
65+
66+
expect(result).toEqual({
67+
results: [
68+
{
69+
status: "success",
70+
requestIndex: 0,
71+
requestSetup: requests[0],
72+
response: mockResponses[0],
73+
error: null,
74+
},
75+
{
76+
status: "success",
77+
requestIndex: 1,
78+
requestSetup: requests[1],
79+
response: mockResponses[1],
80+
error: null,
81+
},
82+
{
83+
status: "success",
84+
requestIndex: 2,
85+
requestSetup: requests[2],
86+
response: mockResponses[2],
87+
error: null,
88+
},
89+
],
90+
summary: {
91+
total: 3,
92+
successes: 3,
93+
errors: 0,
94+
},
95+
});
96+
});
97+
98+
test("it should call onComplete callback after each request", async () => {
99+
const requests = [
100+
{ countryId: "us", path: "/api/users", method: "GET" },
101+
{ countryId: "ca", path: "/api/posts", method: "GET" },
102+
];
103+
104+
callModule.countryApiCall
105+
.mockResolvedValueOnce({ data: "users data" })
106+
.mockResolvedValueOnce({ data: "posts data" });
107+
108+
const onComplete = jest.fn();
109+
110+
await makeSequentialRequests(requests, onComplete);
111+
112+
expect(onComplete).toHaveBeenCalledTimes(2);
113+
expect(onComplete).toHaveBeenNthCalledWith(1, {
114+
current: 0,
115+
total: 2,
116+
successCount: 1,
117+
errorCount: 0,
118+
});
119+
expect(onComplete).toHaveBeenNthCalledWith(2, {
120+
current: 1,
121+
total: 2,
122+
successCount: 2,
123+
errorCount: 0,
124+
});
125+
});
126+
});
127+
128+
describe("Given a list with mixed successful and failed requests", () => {
129+
test("it should continue processing after failures and track error counts", async () => {
130+
const requests = [
131+
{ countryId: "us", path: "/api/users", method: "GET" },
132+
{ countryId: "ca", path: "/api/posts", method: "GET" },
133+
{ countryId: "uk", path: "/api/comments", method: "GET" },
134+
];
135+
136+
const mockError = new Error("API Error");
137+
mockError.response = { status: 404, data: "Not Found" };
138+
139+
// First request succeeds, second fails, third succeeds
140+
callModule.countryApiCall
141+
.mockResolvedValueOnce({ data: "users data" })
142+
.mockRejectedValueOnce(mockError)
143+
.mockResolvedValueOnce({ data: "comments data" });
144+
145+
const result = await makeSequentialRequests(requests);
146+
147+
expect(callModule.countryApiCall).toHaveBeenCalledTimes(3);
148+
expect(result.results[0].status).toBe("success");
149+
expect(result.results[1].status).toBe("error");
150+
expect(result.results[1].error).toEqual({
151+
message: "API Error",
152+
statusCode: 404,
153+
data: "Not Found",
154+
});
155+
expect(result.results[2].status).toBe("success");
156+
expect(result.summary).toEqual({
157+
total: 3,
158+
successes: 2,
159+
errors: 1,
160+
});
161+
expect(console.error).toHaveBeenCalledWith(
162+
"Request 2 failed:",
163+
"API Error",
164+
);
165+
});
166+
});
167+
168+
describe("Given a request with secondAttempt flag", () => {
169+
test("it should pass the secondAttempt parameter to the API call", async () => {
170+
const requests = [
171+
{
172+
countryId: "us",
173+
path: "/api/users",
174+
method: "GET",
175+
secondAttempt: true,
176+
},
177+
];
178+
179+
callModule.countryApiCall.mockResolvedValueOnce({ data: "users data" });
180+
181+
await makeSequentialRequests(requests);
182+
183+
expect(callModule.countryApiCall).toHaveBeenCalledWith(
184+
"us",
185+
"/api/users",
186+
undefined,
187+
"GET",
188+
true,
189+
undefined,
190+
);
191+
});
192+
});
193+
194+
describe("Given a POST request with body", () => {
195+
test("it should pass the body to the API call", async () => {
196+
const requestBody = { name: "John", age: 30 };
197+
const requests = [
198+
{
199+
countryId: "us",
200+
path: "/api/users",
201+
method: "POST",
202+
body: requestBody,
203+
},
204+
];
205+
206+
callModule.countryApiCall.mockResolvedValueOnce({ data: "created" });
207+
208+
await makeSequentialRequests(requests);
209+
210+
expect(callModule.countryApiCall).toHaveBeenCalledWith(
211+
"us",
212+
"/api/users",
213+
requestBody,
214+
"POST",
215+
undefined,
216+
undefined,
217+
);
218+
});
219+
});
220+
221+
describe("Given an empty requests array", () => {
222+
test("it should return empty results with zero counts", async () => {
223+
const requests = [];
224+
225+
const result = await makeSequentialRequests(requests);
226+
227+
expect(callModule.countryApiCall).not.toHaveBeenCalled();
228+
expect(result).toEqual({
229+
results: [],
230+
summary: {
231+
total: 0,
232+
successes: 0,
233+
errors: 0,
234+
},
235+
});
236+
});
237+
});
238+
239+
describe("Given an unexpected error in the main function", () => {
240+
test("it should throw the error", async () => {
241+
// Force an error in the main function by setting requests to null
242+
const requests = null;
243+
244+
await expect(makeSequentialRequests(requests)).rejects.toThrow();
245+
expect(console.error).toHaveBeenCalledWith(
246+
"Sequential requests failed:",
247+
expect.any(Error),
248+
);
249+
});
250+
});
251+
});

0 commit comments

Comments
 (0)