fix(webrequest): complete rewrite to v4: migrate API to fetch-compatible function and new WebrequestClient, update tests and docs, and bump dependencies

This commit is contained in:
2026-03-02 13:49:27 +00:00
parent 1bed95d96e
commit 9972272f00
8 changed files with 5771 additions and 4063 deletions

View File

@@ -1,96 +1,88 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { webrequest, WebrequestClient } from '../ts/index.js';
import * as typedserver from '@api.global/typedserver';
import { TypedServer } from '@api.global/typedserver';
let testServer: typedserver.servertools.Server;
let testServer: TypedServer;
// Setup test server
tap.test('setup test server for v4 tests', async () => {
testServer = new typedserver.servertools.Server({
testServer = new TypedServer({
cors: false,
forceSsl: false,
port: 2346,
});
// Route that returns JSON with cache headers
testServer.addRoute(
'/cached',
new typedserver.servertools.Handler('GET', (req, res) => {
res.setHeader('Cache-Control', 'max-age=60');
res.setHeader('ETag', '"12345"');
res.status(200);
res.send({ data: 'cached response', timestamp: Date.now() });
}),
);
testServer.addRoute('/cached', 'GET', async (ctx) => {
return new Response(JSON.stringify({ data: 'cached response', timestamp: Date.now() }), {
status: 200,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'max-age=60',
'ETag': '"12345"',
},
});
});
// Route that returns different data each time
testServer.addRoute(
'/dynamic',
new typedserver.servertools.Handler('GET', (req, res) => {
res.status(200);
res.send({ data: 'dynamic response', timestamp: Date.now() });
}),
);
testServer.addRoute('/dynamic', 'GET', async (ctx) => {
return new Response(JSON.stringify({ data: 'dynamic response', timestamp: Date.now() }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
});
// Route that sometimes fails (for retry testing)
let requestCount = 0;
testServer.addRoute(
'/flaky',
new typedserver.servertools.Handler('GET', (req, res) => {
requestCount++;
if (requestCount < 3) {
res.status(500);
res.end();
} else {
res.status(200);
res.send({ success: true, attempts: requestCount });
}
}),
);
testServer.addRoute('/flaky', 'GET', async (ctx) => {
requestCount++;
if (requestCount < 3) {
return new Response(null, { status: 500 });
} else {
return new Response(JSON.stringify({ success: true, attempts: requestCount }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
});
// Route that always fails with 500 (for fallback testing)
testServer.addRoute(
'/always-fails',
new typedserver.servertools.Handler('GET', (req, res) => {
res.status(500);
res.end();
}),
);
testServer.addRoute('/always-fails', 'GET', async (ctx) => {
return new Response(null, { status: 500 });
});
// Route that takes a long time to respond (for timeout testing)
testServer.addRoute(
'/slow',
new typedserver.servertools.Handler('GET', async (req, res) => {
await new Promise((resolve) => setTimeout(resolve, 5000));
res.status(200);
res.send({ data: 'slow response' });
}),
);
testServer.addRoute('/slow', 'GET', async (ctx) => {
await new Promise((resolve) => setTimeout(resolve, 5000));
return new Response(JSON.stringify({ data: 'slow response' }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
});
// Route that returns 304 when ETag matches
testServer.addRoute(
'/conditional',
new typedserver.servertools.Handler('GET', (req, res) => {
const ifNoneMatch = req.headers['if-none-match'];
if (ifNoneMatch === '"67890"') {
res.status(304);
res.end();
} else {
res.setHeader('ETag', '"67890"');
res.status(200);
res.send({ data: 'conditional response' });
}
}),
);
testServer.addRoute('/conditional', 'GET', async (ctx) => {
const ifNoneMatch = ctx.request.headers.get('if-none-match');
if (ifNoneMatch === '"67890"') {
return new Response(null, { status: 304 });
} else {
return new Response(JSON.stringify({ data: 'conditional response' }), {
status: 200,
headers: {
'Content-Type': 'application/json',
'ETag': '"67890"',
},
});
}
});
// POST route for testing
testServer.addRoute(
'/post',
new typedserver.servertools.Handler('POST', (req, res) => {
res.status(200);
res.send({ received: true });
}),
);
testServer.addRoute('/post', 'POST', async (ctx) => {
return new Response(JSON.stringify({ received: true }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
});
await testServer.start();
});
@@ -247,8 +239,6 @@ tap.test('should support global interceptors', async () => {
// Test 11: Request deduplication
tap.test('should deduplicate simultaneous requests', async () => {
const start = Date.now();
// Make 3 identical requests simultaneously
const [res1, res2, res3] = await Promise.all([
webrequest('http://localhost:2346/dynamic', { deduplicate: true }),