fastfetch-api-fetch-enhancer 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.idea/FastFetch-Smart-API-Fetcher.iml +12 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/LICENSE +21 -0
- package/README.md +101 -0
- package/__tests__/demo.test.ts +216 -0
- package/__tests__/test_database.json +5002 -0
- package/coverage/clover.xml +463 -0
- package/coverage/coverage-final.json +11 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/circuit-breaker.ts.html +547 -0
- package/coverage/lcov-report/client.ts.html +1858 -0
- package/coverage/lcov-report/errors.ts.html +415 -0
- package/coverage/lcov-report/fastFetch.ts.html +1045 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +251 -0
- package/coverage/lcov-report/index.ts.html +241 -0
- package/coverage/lcov-report/metrics.ts.html +685 -0
- package/coverage/lcov-report/middleware.ts.html +403 -0
- package/coverage/lcov-report/offline-queue.ts.html +535 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/queue.ts.html +421 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +196 -0
- package/coverage/lcov-report/streaming.ts.html +466 -0
- package/coverage/lcov.info +908 -0
- package/dist/circuit-breaker.d.ts +61 -0
- package/dist/circuit-breaker.d.ts.map +1 -0
- package/dist/circuit-breaker.js +106 -0
- package/dist/client.d.ts +215 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +391 -0
- package/dist/errors.d.ts +56 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +91 -0
- package/dist/fastFetch.d.ts +65 -0
- package/dist/fastFetch.d.ts.map +1 -0
- package/dist/fastFetch.js +209 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/metrics.d.ts +71 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +131 -0
- package/dist/middleware.d.ts +66 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +45 -0
- package/dist/offline-queue.d.ts +65 -0
- package/dist/offline-queue.d.ts.map +1 -0
- package/dist/offline-queue.js +120 -0
- package/dist/queue.d.ts +33 -0
- package/dist/queue.d.ts.map +1 -0
- package/dist/queue.js +76 -0
- package/dist/streaming.d.ts +40 -0
- package/dist/streaming.d.ts.map +1 -0
- package/dist/streaming.js +98 -0
- package/index.d.ts +167 -0
- package/jest.config.js +16 -0
- package/package.json +55 -0
- package/src/circuit-breaker.ts +154 -0
- package/src/client.ts +591 -0
- package/src/errors.ts +110 -0
- package/src/fastFetch.ts +320 -0
- package/src/index.ts +52 -0
- package/src/metrics.ts +200 -0
- package/src/middleware.ts +106 -0
- package/src/offline-queue.ts +150 -0
- package/src/queue.ts +112 -0
- package/src/streaming.ts +127 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<module type="WEB_MODULE" version="4">
|
|
3
|
+
<component name="NewModuleRootManager">
|
|
4
|
+
<content url="file://$MODULE_DIR$">
|
|
5
|
+
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
|
6
|
+
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
|
7
|
+
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
|
8
|
+
</content>
|
|
9
|
+
<orderEntry type="inheritedJdk" />
|
|
10
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
|
11
|
+
</component>
|
|
12
|
+
</module>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<project version="4">
|
|
3
|
+
<component name="ProjectModuleManager">
|
|
4
|
+
<modules>
|
|
5
|
+
<module fileurl="file://$PROJECT_DIR$/.idea/FastFetch-Smart-API-Fetcher.iml" filepath="$PROJECT_DIR$/.idea/FastFetch-Smart-API-Fetcher.iml" />
|
|
6
|
+
</modules>
|
|
7
|
+
</component>
|
|
8
|
+
</project>
|
package/.idea/vcs.xml
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Keshav-005
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# FastFetch API Enhancer
|
|
2
|
+
|
|
3
|
+
FastFetch is a production-grade, enterprise-ready HTTP client built around the native `fetch` API. It transforms simple HTTP requests into robust network architecture by introducing advanced resilience patterns, high-concurrency request management, and real-time streaming capabilities without excessive boilerplate.
|
|
4
|
+
|
|
5
|
+
## Architecture & Features
|
|
6
|
+
|
|
7
|
+
FastFetch v2 provides seven core execution pipelines directly into your fetch workflow:
|
|
8
|
+
|
|
9
|
+
- **Strict Typed Error Hierarchy:** Explicit errors for `TimeoutError`, `NetworkError`, `HttpError`, and `CircuitOpenError` enabling robust try/catch control flow.
|
|
10
|
+
- **Koa-Style Middleware Pipeline:** A powerful `.use(async (ctx, next) => {})` onion-model interceptor architecture for modifying request metadata or observing response execution duration.
|
|
11
|
+
- **Finite State Machine Circuit Breaker:** Stops cascading failures to downstream services by automatically failing fast when failure thresholds are breached, entering a `HALF_OPEN` state after the configured timeout.
|
|
12
|
+
- **Priority-Aware Concurrency Queue:** Protects API rate limits by enforcing a strict `maxConcurrent` ceiling across all ongoing requests while maintaining execution priority.
|
|
13
|
+
- **Offline Mutation Buffering:** Automatically buffers mutating requests (`POST`, `PUT`, `PATCH`, `DELETE`) internally if the browser detects an offline state, sequentially replaying them upon reconnection.
|
|
14
|
+
- **Real-Time Telemetry & Metrics:** Maintains rolling windows on p50, p95, and p99 latency percentiles alongside exact endpoint success and failure ratios.
|
|
15
|
+
- **Server-Sent Events (SSE) Native Streaming:** A built-in high-performance stream consumer utilizing `api.stream()` for interacting with AI models and event-driven backends natively over HTTP.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install fastfetch-api-fetch-enhancer
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Basic Usage
|
|
24
|
+
|
|
25
|
+
A minimal example utilizing the `createClient` factory method:
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { createClient } from 'fastfetch-api-fetch-enhancer';
|
|
29
|
+
|
|
30
|
+
// Instantiate your global HTTP client
|
|
31
|
+
const api = createClient({
|
|
32
|
+
baseURL: 'https://api.example.com/v1',
|
|
33
|
+
headers: { 'Authorization': 'Bearer <token>' },
|
|
34
|
+
timeout: 10000,
|
|
35
|
+
retries: 3,
|
|
36
|
+
retryDelay: 1000,
|
|
37
|
+
maxConcurrent: 50,
|
|
38
|
+
circuitBreaker: {
|
|
39
|
+
threshold: 5,
|
|
40
|
+
timeout: 30000
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Koa-style middleware injection
|
|
45
|
+
api.use(async (ctx, next) => {
|
|
46
|
+
ctx.init.headers['X-Request-Id'] = crypto.randomUUID();
|
|
47
|
+
await next();
|
|
48
|
+
console.log(`[HTTP ${ctx.response?.status}] ${ctx.method} ${ctx.url} - ${ctx.duration}ms`);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Start making requests
|
|
52
|
+
async function execute() {
|
|
53
|
+
try {
|
|
54
|
+
// Standard JSON shorthand
|
|
55
|
+
const user = await api.json('/users/me');
|
|
56
|
+
|
|
57
|
+
// Auto-serialization for payload delivery
|
|
58
|
+
await api.post('/events', { action: 'login', timestamp: Date.now() });
|
|
59
|
+
|
|
60
|
+
} catch (error) {
|
|
61
|
+
// Typed catch boundaries
|
|
62
|
+
if (error instanceof HttpError) {
|
|
63
|
+
console.error('API Rejected Request:', error.status);
|
|
64
|
+
} else if (error instanceof TimeoutError) {
|
|
65
|
+
console.error('Request timed out at:', error.timeout);
|
|
66
|
+
} else {
|
|
67
|
+
console.error('Catastrophic failure:', error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Advanced Streaming (SSE)
|
|
74
|
+
|
|
75
|
+
Consume AI generation pipelines directly over HTTP using the `stream` handler:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
const signal = new AbortController();
|
|
79
|
+
|
|
80
|
+
await api.stream(
|
|
81
|
+
'/ai/generate-text',
|
|
82
|
+
(event) => {
|
|
83
|
+
process.stdout.write(event.data);
|
|
84
|
+
},
|
|
85
|
+
{ signal: signal.signal }
|
|
86
|
+
);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Monitoring & Telemetry
|
|
90
|
+
|
|
91
|
+
Evaluate application stability at any time by accessing the built-in system telemetry map:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
const telemetry = api.metrics.snapshot();
|
|
95
|
+
console.table(telemetry.byEndpoint);
|
|
96
|
+
console.log('p99 global latency:', telemetry.globalLatencies.p99);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
FastFetch is open-source software licensed under the MIT License.
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fastFetch,
|
|
3
|
+
clearCache,
|
|
4
|
+
createClient,
|
|
5
|
+
HttpError,
|
|
6
|
+
TimeoutError,
|
|
7
|
+
CircuitOpenError,
|
|
8
|
+
} from "../src/index.js";
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Helpers
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
const POKE_BASE = "https://pokeapi.co/api/v2";
|
|
14
|
+
const DITTO_URL = `${POKE_BASE}/pokemon/ditto`;
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// 1. Core fastFetch & v1.x capabilities
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
describe("fastFetch — core & TTL Cache & Dedup", () => {
|
|
20
|
+
beforeEach(() => clearCache());
|
|
21
|
+
|
|
22
|
+
it("makes a basic GET and returns ok response", async () => {
|
|
23
|
+
const res = await fastFetch(DITTO_URL);
|
|
24
|
+
expect(res.ok).toBe(true);
|
|
25
|
+
const data = await res.json();
|
|
26
|
+
expect(data).toHaveProperty("name", "ditto");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("deduplicates identical in-flight requests", async () => {
|
|
30
|
+
const [res1, res2] = await Promise.all([
|
|
31
|
+
fastFetch(DITTO_URL),
|
|
32
|
+
fastFetch(DITTO_URL),
|
|
33
|
+
]);
|
|
34
|
+
const [d1, d2] = await Promise.all([res1.json(), res2.json()]);
|
|
35
|
+
expect(d1.name).toBe("ditto");
|
|
36
|
+
expect(d2.name).toBe("ditto");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("returns a cached response on second call", async () => {
|
|
40
|
+
const res1 = await fastFetch(DITTO_URL, { fastCache: true, cacheTTL: 60_000 });
|
|
41
|
+
expect(res1.ok).toBe(true);
|
|
42
|
+
|
|
43
|
+
const res2 = await fastFetch(DITTO_URL, { fastCache: true, cacheTTL: 60_000 });
|
|
44
|
+
expect(res2.ok).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// 2. Typed Error Hierarchy
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
describe("FastFetch v2.0 — Typed Errors", () => {
|
|
52
|
+
it("throws TimeoutError on abort", async () => {
|
|
53
|
+
let error: any;
|
|
54
|
+
try {
|
|
55
|
+
await fastFetch(DITTO_URL, { timeout: 1 });
|
|
56
|
+
} catch (e) {
|
|
57
|
+
error = e;
|
|
58
|
+
}
|
|
59
|
+
expect(error).toBeDefined();
|
|
60
|
+
expect(error).toBeInstanceOf(TimeoutError);
|
|
61
|
+
expect((error as TimeoutError).timeout).toBe(1);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("throws HttpError when throwOnError is enabled via Client", async () => {
|
|
65
|
+
const api = createClient({ baseURL: POKE_BASE, throwOnError: true, retries: 0 });
|
|
66
|
+
let error: any;
|
|
67
|
+
try {
|
|
68
|
+
await api.get("/pokemon/invalid_pokemon_xyz123");
|
|
69
|
+
} catch (e) {
|
|
70
|
+
error = e;
|
|
71
|
+
}
|
|
72
|
+
expect(error).toBeDefined();
|
|
73
|
+
expect(error).toBeInstanceOf(HttpError);
|
|
74
|
+
expect((error as HttpError).status).toBe(404);
|
|
75
|
+
expect((error as HttpError).response).toBeDefined();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// 3. Koa-style Middleware
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
describe("FastFetch v2.0 — Middleware Pipeline", () => {
|
|
83
|
+
it("executes in onion model order (down then up)", async () => {
|
|
84
|
+
const api = createClient({ baseURL: POKE_BASE });
|
|
85
|
+
const order: string[] = [];
|
|
86
|
+
|
|
87
|
+
api.use(async (ctx, next) => {
|
|
88
|
+
order.push("mw1_down");
|
|
89
|
+
ctx.init.headers = { ...ctx.init.headers, "X-Trace": "1" };
|
|
90
|
+
await next();
|
|
91
|
+
order.push("mw1_up");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
api.use(async (ctx, next) => {
|
|
95
|
+
order.push("mw2_down");
|
|
96
|
+
expect((ctx.init.headers as any)["X-Trace"]).toBe("1");
|
|
97
|
+
await next();
|
|
98
|
+
order.push("mw2_up");
|
|
99
|
+
expect(ctx.response?.status).toBe(200);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
await api.get("/pokemon/ditto");
|
|
103
|
+
|
|
104
|
+
expect(order).toEqual(["mw1_down", "mw2_down", "mw2_up", "mw1_up"]);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// 4. Circuit Breaker
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
describe("FastFetch v2.0 — Circuit Breaker", () => {
|
|
112
|
+
it("transitions CLOSED -> OPEN -> HALF_OPEN -> CLOSED", async () => {
|
|
113
|
+
const api = createClient({
|
|
114
|
+
baseURL: "http://localhost:1", // guaranteed network failure
|
|
115
|
+
circuitBreaker: { threshold: 2, timeout: 50 },
|
|
116
|
+
retries: 0,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const cb = api.circuitBreaker!;
|
|
120
|
+
expect(cb.state).toBe("CLOSED");
|
|
121
|
+
|
|
122
|
+
// Failure 1
|
|
123
|
+
await expect(api.get("/")).rejects.toThrow();
|
|
124
|
+
expect(cb.state).toBe("CLOSED");
|
|
125
|
+
expect(cb.failures).toBe(1);
|
|
126
|
+
|
|
127
|
+
// Failure 2 -> Opens circuit
|
|
128
|
+
await expect(api.get("/")).rejects.toThrow();
|
|
129
|
+
expect(cb.state).toBe("OPEN");
|
|
130
|
+
|
|
131
|
+
// Circuit is OPEN, requests immediately throw (no network delay)
|
|
132
|
+
let error: any;
|
|
133
|
+
try {
|
|
134
|
+
await api.get("/");
|
|
135
|
+
} catch (e) {
|
|
136
|
+
error = e;
|
|
137
|
+
}
|
|
138
|
+
expect(error).toBeDefined();
|
|
139
|
+
expect(error).toBeInstanceOf(CircuitOpenError);
|
|
140
|
+
|
|
141
|
+
// Wait for timeout -> HALF_OPEN
|
|
142
|
+
await new Promise(r => setTimeout(r, 60));
|
|
143
|
+
expect(cb.state).toBe("HALF_OPEN");
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
// 5. Concurrency Queue
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
describe("FastFetch v2.0 — Concurrency Queue", () => {
|
|
151
|
+
it("limits active in-flight requests", async () => {
|
|
152
|
+
const maxConcurrent = 2;
|
|
153
|
+
const api = createClient({ baseURL: POKE_BASE, maxConcurrent });
|
|
154
|
+
|
|
155
|
+
// Fire 6 requests simultaneously
|
|
156
|
+
const p1 = api.get("/pokemon/1");
|
|
157
|
+
const p2 = api.get("/pokemon/2");
|
|
158
|
+
const p3 = api.get("/pokemon/3");
|
|
159
|
+
const p4 = api.get("/pokemon/4");
|
|
160
|
+
const p5 = api.get("/pokemon/5");
|
|
161
|
+
const p6 = api.get("/pokemon/6");
|
|
162
|
+
|
|
163
|
+
// Immediately check queue stats (2 active, 4 pending)
|
|
164
|
+
expect(api.queue!.active).toBe(2);
|
|
165
|
+
expect(api.queue!.pending).toBe(4);
|
|
166
|
+
|
|
167
|
+
await Promise.all([p1, p2, p3, p4, p5, p6]);
|
|
168
|
+
|
|
169
|
+
// All done
|
|
170
|
+
expect(api.queue!.active).toBe(0);
|
|
171
|
+
expect(api.queue!.pending).toBe(0);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// 6. Metrics
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
describe("FastFetch v2.0 — Metrics", () => {
|
|
179
|
+
it("records success rates and normalises URLs", async () => {
|
|
180
|
+
const api = createClient({ baseURL: POKE_BASE });
|
|
181
|
+
|
|
182
|
+
await api.get("/pokemon/1");
|
|
183
|
+
await api.get("/pokemon/2");
|
|
184
|
+
|
|
185
|
+
const snap = api.metrics.snapshot();
|
|
186
|
+
expect(snap.totalRequests).toBe(2);
|
|
187
|
+
|
|
188
|
+
// Normalisation replaces numeric segments with /:id
|
|
189
|
+
const keys = Object.keys(snap.byEndpoint);
|
|
190
|
+
expect(keys[0]).toContain("/pokemon/:id");
|
|
191
|
+
expect(snap.byEndpoint[keys[0]].count).toBe(2);
|
|
192
|
+
expect(snap.byEndpoint[keys[0]].successRate).toBe(1);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
// 7. Auto Body Serialization
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
describe("FastFetch v2.0 — Auto Body Serialization", () => {
|
|
200
|
+
it("serialises plain objects to JSON automatically", async () => {
|
|
201
|
+
const api = createClient({ baseURL: POKE_BASE });
|
|
202
|
+
|
|
203
|
+
api.use(async (ctx, next) => {
|
|
204
|
+
// Intercept and assert the mutation before it goes out
|
|
205
|
+
expect(ctx.init.body).toBe('{"name":"bulbasaur"}');
|
|
206
|
+
expect((ctx.init.headers as any)["Content-Type"]).toBe("application/json");
|
|
207
|
+
|
|
208
|
+
// Stub the response so we don't actually POST to PokeAPI
|
|
209
|
+
ctx.response = new Response("ok", { status: 200 });
|
|
210
|
+
// do not await next() so we skip the network call
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const res = await api.post("/pokemon", { name: "bulbasaur" });
|
|
214
|
+
expect(res.status).toBe(200);
|
|
215
|
+
});
|
|
216
|
+
});
|