Skip to content
Draft
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
11 changes: 6 additions & 5 deletions e2e/gm-api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ async function startGMApiMockServer(): Promise<GMApiMockServer> {
res.end(
JSON.stringify({
url: `http://${req.headers.host}${url.pathname}`,
method: req.method,
args,
})
);
Expand Down Expand Up @@ -221,14 +222,14 @@ function patchGMApiTestCode(code: string, mockOrigin: string): string {
const mockHost = new URL(mockOrigin).host;
return code
.replace(/^\/\/\s*@connect\s+api\.github\.com$/gm, `// @connect ${MOCK_CONNECT_HOST}`)
.replace(/^\/\/\s*@connect\s+httpbun\.com$/gm, `// @connect ${MOCK_CONNECT_HOST}`)
.replace(/^\/\/\s*@connect\s+mockhttp\.org$/gm, `// @connect ${MOCK_CONNECT_HOST}`)
.replace(/https:\/\/api\.github\.com\/repos\/scriptscat\/scriptcat/g, `${mockOrigin}/repos/scriptscat/scriptcat`)
.replace(/https:\/\/httpbun\.com\/get/g, `${mockOrigin}/get`)
.replace(/https:\/\/httpbun\.com\/bytes\/64/g, `${mockOrigin}/bytes/64`)
.replace(/https:\/\/httpbun\.com\/delay\/5/g, `${mockOrigin}/delay/5`)
.replace(/https:\/\/mockhttp\.org\/get/g, `${mockOrigin}/get`)
.replace(/https:\/\/mockhttp\.org\/bytes\/64/g, `${mockOrigin}/bytes/64`)
.replace(/https:\/\/mockhttp\.org\/delay\/5/g, `${mockOrigin}/delay/5`)
.replace(/https:\/\/www\.tampermonkey\.net\/favicon\.ico/g, `${mockOrigin}/favicon.ico`)
.replace(/api\.github\.com\/repos\/scriptscat\/scriptcat/g, `${mockHost}/repos/scriptscat/scriptcat`)
.replace(/httpbun\.com\/get/g, `${mockHost}/get`);
.replace(/mockhttp\.org\/get/g, `${mockHost}/get`);
}

async function runTestScript(
Expand Down
8 changes: 4 additions & 4 deletions example/crontab/crontab.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,26 @@
// @crontab */15 14-16 * * *
// @grant GM_log
// @grant GM_xmlhttpRequest
// @connect httpbun.com
// @connect mockhttp.org
// ==/UserScript==

return new Promise((resolve, reject) => {
GM_log("下午两点至四点每十五分钟运行一次");

GM_xmlhttpRequest({
url: "https://httpbun.com/get",
url: "https://mockhttp.org/get",
method: "GET",
responseType: "json",
anonymous: true,

onload(resp) {
const data = resp.response;
if (resp.status !== 200 || data?.url !== "https://httpbun.com/get") {
if (resp.status !== 200 || resp.finalUrl !== "https://mockhttp.org/get" || data?.method !== "GET") {
reject("服务器回应错误。");
return;
}

GM_log(`定时请求成功:\n${data.url}`);
GM_log(`定时请求成功:\n${resp.finalUrl}`);
resolve();
},
onerror() {
Expand Down
11 changes: 6 additions & 5 deletions example/tests/gm_api_async_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
// @grant unsafeWindow
// @require https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js#sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8JMa7o7lXvnz3GFKzPxzJdPfGK
// @resource testCSS https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css#sha256=62f74b1cf824a89f03554c638e719594c309b4d8a627a758928c0516fa7890ab
// @connect httpbun.com
// @connect mockhttp.org
// @connect example.com
// @run-at document-start
// ==/UserScript==
Expand Down Expand Up @@ -192,16 +192,17 @@
return new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: "GET",
url: "https://httpbun.com/get",
url: "https://mockhttp.org/get",
timeout: 10000,
onload: (response) => {
try {
assert(200, response.status, `请求状态码应该是 200`);
assert(true, !!response.responseText, "响应内容不应为空");
const data = JSON.parse(response.responseText);
assert("object", typeof data, "应该返回有效的 JSON 对象");
assert("https://httpbun.com/get", data.url, "响应应该包含 url 字段");
console.log("httpbun 响应信息:", data.url);
assert("GET", data.method, "响应应该包含 method 字段");
assert(true, response.finalUrl.includes("mockhttp.org/get"), "finalUrl 应指向请求的地址");
console.log("mockhttp 响应信息:", response.finalUrl);
resolve();
} catch (error) {
reject(error);
Expand All @@ -220,7 +221,7 @@
await testAsync("GM.xmlHttpRequest - 返回控制对象", async () => {
const controller = GM.xmlHttpRequest({
method: "GET",
url: "https://httpbun.com/get",
url: "https://mockhttp.org/get",
timeout: 10000,
onload: () => {},
onerror: () => {},
Expand Down
9 changes: 5 additions & 4 deletions example/tests/gm_api_sync_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
// @grant unsafeWindow
// @require https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js#sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8JMa7o7lXvnz3GFKzPxzJdPfGK
// @resource testCSS https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css#sha256=62f74b1cf824a89f03554c638e719594c309b4d8a627a758928c0516fa7890ab
// @connect httpbun.com
// @connect mockhttp.org
// @connect example.com
// @run-at document-start
// ==/UserScript==
Expand Down Expand Up @@ -308,16 +308,17 @@
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: "https://httpbun.com/get",
url: "https://mockhttp.org/get",
timeout: 10000,
onload: (response) => {
try {
assert(200, response.status, `请求状态码应该是 200`);
assert(true, !!response.responseText, "响应内容不应为空");
const data = JSON.parse(response.responseText);
assert("object", typeof data, "应该返回有效的 JSON 对象");
assert("https://httpbun.com/get", data.url, "响应应该包含 url 字段");
console.log("httpbun 响应信息:", data.url);
assert("GET", data.method, "响应应该包含 method 字段");
assert(true, response.finalUrl.includes("mockhttp.org/get"), "finalUrl 应指向请求的地址");
console.log("mockhttp 响应信息:", response.finalUrl);
resolve();
} catch (error) {
reject(error);
Expand Down
8 changes: 4 additions & 4 deletions example/tests/gm_download_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_info
// @connect httpbun.com
// @connect mockhttp.org
// @connect raw.githubusercontent.com
// @connect cdn.jsdelivr.net
// @connect ipv4.download.thinkbroadband.com
Expand Down Expand Up @@ -136,8 +136,8 @@ const enableTool = true;
// File extends Blob; useful to verify the URL-object branch handles File too.
const TEXT_FILE = new File([TEXT_BLOB], "ignored-by-api.txt", { type: "text/plain" });

// httpbun deterministic endpoint — returns N random bytes.
const HB = "https://httpbun.com";
// mockhttp deterministic endpoint — returns N random bytes.
const HB = "https://mockhttp.org";

// ---------- Panel ----------
const panel = h(
Expand Down Expand Up @@ -697,7 +697,7 @@ const enableTool = true;
skip(`(visual check) you should see both uniquify-target.txt and uniquify-target (1).txt`);
});

// 13) headers honored in native mode — httpbun /headers echoes request headers
// 13) headers honored in native mode — mockhttp /headers echoes request headers
autoTest("headers passed to backend xhr (native mode)", async () => {
// We can't easily inspect what hit the server because the response is
// streamed to disk. So instead: ask /headers, which RESPONDS with the headers
Expand Down
37 changes: 26 additions & 11 deletions example/tests/gm_xhr_redirect_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// @author you
// @match *://*/*?GM_XHR_REDIRECT_TEST_SC
// @grant GM_xmlhttpRequest
// @connect httpbun.com
// @connect mockhttp.org
// @noframes
// ==/UserScript==

Expand Down Expand Up @@ -104,7 +104,7 @@ const enableTool = true;
});
}

const HB = "https://httpbun.com";
const HB = "https://mockhttp.org";

// ---------- Assertion utils ----------
function assertEq(a, b, msg) {
Expand All @@ -131,9 +131,9 @@ const enableTool = true;
async run(fetch) {
const { res } = await gmRequest({ method: "GET", url: `${HB}/get?testing=234&abc=567`, responseType: "json", fetch });
assertEq(res.status, 200, "status 200");
assertEq(res.response?.args?.testing, "234", "response ok");
assertEq(res.response?.args?.abc, "567", "response ok");
assertEq(res.response?.url, `${HB}/get?testing=234&abc=567`, "response ok");
assertEq(res.response?.queryParams?.testing, "234", "response ok");
assertEq(res.response?.queryParams?.abc, "567", "response ok");
assertEq(res.finalUrl, `${HB}/get?testing=234&abc=567`, "response ok");
assertEq(objectProps(res), "ok", "Object Props OK");
},
},
Expand All @@ -142,29 +142,35 @@ const enableTool = true;
async run(fetch) {
const { res } = await gmRequest({ method: "GET", url: `${HB}/get?abc=567&testing=234`, responseType: "json", fetch });
assertEq(res.status, 200, "status 200");
assertEq(res.response?.args?.testing, "234", "response ok");
assertEq(res.response?.args?.abc, "567", "response ok");
assertEq(res.response?.url, `${HB}/get?abc=567&testing=234`, "response ok");
assertEq(res.response?.queryParams?.testing, "234", "response ok");
assertEq(res.response?.queryParams?.abc, "567", "response ok");
assertEq(res.finalUrl, `${HB}/get?abc=567&testing=234`, "response ok");
assertEq(objectProps(res), "ok", "Object Props OK");
},
},
{
name: "Redirect handling (finalUrl changes) [default]",
async run(fetch) {
const target = `${HB}/get?z=92`;
const { res } = await gmRequest({ method: "GET", url: `${HB}/redirect-to?url=${encodeURIComponent(target)}`, fetch });
const { res } = await gmRequest({ method: "GET", url: `${HB}/redirect-to?url=${encodeURIComponent(target)}`, responseType: "json", fetch });
assertEq(res.status, 200, "status after redirect is 200");
assertEq(res.finalUrl, target, "finalUrl is redirected target");
// finalUrl alone only proves the client thinks it followed the
// redirect - confirm the server actually served /get's response.
assertEq(res.response?.method, "GET", "redirected request reached /get as GET");
assertEq(res.response?.queryParams?.z, "92", "redirected response echoes the target's query");
assertEq(objectProps(res), "ok", "Object Props OK");
},
},
{
name: "Redirect handling (finalUrl changes) [follow]",
async run(fetch) {
const target = `${HB}/get?z=94`;
const { res } = await gmRequest({ method: "GET", url: `${HB}/redirect-to?url=${encodeURIComponent(target)}`, redirect: "follow", fetch });
const { res } = await gmRequest({ method: "GET", url: `${HB}/redirect-to?url=${encodeURIComponent(target)}`, redirect: "follow", responseType: "json", fetch });
assertEq(res.status, 200, "status after redirect is 200");
assertEq(res.finalUrl, target, "finalUrl is redirected target");
assertEq(res.response?.method, "GET", "redirected request reached /get as GET");
assertEq(res.response?.queryParams?.z, "94", "redirected response echoes the target's query");
assertEq(objectProps(res), "ok", "Object Props OK");
},
},
Expand Down Expand Up @@ -194,7 +200,16 @@ const enableTool = true;
gmRequest({ method: "GET", url, redirect: "manual", fetch }),
new Promise(resolve => setTimeout(resolve, 4000)),
]);
assertEq(res?.status, 301, "status is 301");
// NOT actually testing the server's real redirect status here: with
// redirect: "manual", implementations of this option commonly route
// through fetch()'s opaqueredirect response, whose real status the
// Fetch spec makes unreadable from JS - so a manager typically falls
// back to a fixed status (often 301) rather than the server's actual
// code. Confirmed the live server here returns 302 (verified with
// curl), yet the reported status is 301 regardless. This assertion
// pins down that fixed fallback value, not mockhttp.org's behavior -
// if your manager reports a different fallback, adjust accordingly.
assertEq(res?.status, 301, "status is 301 (manager's fixed fallback for opaque manual redirects, not the server's real code)");
assertEq(res?.finalUrl, url, "finalUrl is original url");
assertEq(typeof res?.responseHeaders === "string" && res?.responseHeaders !== "", true, "responseHeaders ok");
assertEq(objectProps(res), "ok", "Object Props OK");
Expand Down
Loading
Loading