@crawlcheck/sdk 1.0.9 → 1.1.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/dist/index.d.ts +69 -10
- package/dist/index.js +151 -13
- package/dist/mcp.js +2 -1
- package/package.json +3 -2
- package/test/fixtures/guard-acceptance.json +4694 -0
- package/test/guard-acceptance.test.js +54 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// The guard() acceptance suite (https://crawlcheck.io/spec/guard) against this SDK.
|
|
2
|
+
// CC_SDK=<path or package name> runs it against another build (CI uses the copy installed from npm).
|
|
3
|
+
import { test } from "node:test";
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
6
|
+
const here = new URL(".", import.meta.url);
|
|
7
|
+
const CASES = process.env.CC_GUARD_CASES || [new URL("../../spec/guard-acceptance/cases.json", here), new URL("./fixtures/guard-acceptance.json", here)].find((u) => existsSync(u));
|
|
8
|
+
const suite = JSON.parse(readFileSync(CASES, "utf8"));
|
|
9
|
+
const { CrawlCheck } = await import(process.env.CC_SDK || "../dist/index.js");
|
|
10
|
+
const BASE = "https://cc.test";
|
|
11
|
+
|
|
12
|
+
function fakeFetch(c, log) {
|
|
13
|
+
return async (url, init = {}) => {
|
|
14
|
+
url = String(url);
|
|
15
|
+
if (url.startsWith(BASE)) {
|
|
16
|
+
const p = new URL(url).pathname, cc = c.crawlcheck;
|
|
17
|
+
if (cc.unreachable) throw new TypeError("fetch failed");
|
|
18
|
+
if (p === "/.well-known/http-message-signatures-directory") return new Response(JSON.stringify(suite.directory), { headers: { "content-type": "application/json" } });
|
|
19
|
+
if (p === "/api/v1/preflight") {
|
|
20
|
+
const b = JSON.parse(init.body || "{}"); log.asked.push(b.domain);
|
|
21
|
+
if (cc.by_domain) { const r = cc.by_domain[b.domain]; return r ? new Response(JSON.stringify(r), { headers: { "content-type": "application/json" } }) : new Response("{\"error\":\"none\"}", { status: 404 }); }
|
|
22
|
+
return new Response(cc.text !== undefined ? cc.text : JSON.stringify(cc.body), { status: cc.status || 200, headers: { "content-type": "application/json" } });
|
|
23
|
+
}
|
|
24
|
+
return new Response("{}", { status: 404 });
|
|
25
|
+
}
|
|
26
|
+
log.fetched.push(url);
|
|
27
|
+
const h = new Headers(init.headers || {}); if (h.get("crawlcheck-receipt")) log.header_to.push(new URL(url).hostname);
|
|
28
|
+
const s = (c.sites || {})[url];
|
|
29
|
+
if (!s) return new Response("not in fixture", { status: 404 });
|
|
30
|
+
return new Response(s.body ?? null, { status: s.status, headers: s.location ? { location: s.location } : {} });
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const c of suite.cases) {
|
|
35
|
+
test(c.id + ": " + c.title, async () => {
|
|
36
|
+
const log = { fetched: [], header_to: [], asked: [] };
|
|
37
|
+
const cc = new CrawlCheck({ base: BASE, fetch: fakeFetch(c, log) });
|
|
38
|
+
const q = c.request, opts = { nonce: q.nonce, now: Date.parse(q.now), allowWarn: q.allow_warn };
|
|
39
|
+
if (q.confirm !== null) opts.confirm = async () => q.confirm;
|
|
40
|
+
if (q.url) {
|
|
41
|
+
assert.equal(typeof cc.guardedFetch, "function", "this SDK has no guarded fetch, so nothing re-checks redirects");
|
|
42
|
+
const r = await cc.guardedFetch(q.url, Object.assign(opts, { action: q.action, maxHops: q.max_hops }));
|
|
43
|
+
assert.equal(r.proceed, c.expect.proceed, "proceed; why: " + r.why.join(" | "));
|
|
44
|
+
if (c.expect.fetched) assert.deepEqual(log.fetched, c.expect.fetched);
|
|
45
|
+
if (c.expect.fetched_count !== undefined) assert.equal(log.fetched.length, c.expect.fetched_count);
|
|
46
|
+
if (c.expect.receipt_header_to) assert.deepEqual(log.header_to, c.expect.receipt_header_to);
|
|
47
|
+
} else {
|
|
48
|
+
let r; try { r = await cc.guard(q.domain, q.action, opts); } catch (e) { r = { proceed: false, header: null, threw: String(e && e.message || e) }; } // a throw is a refusal: the caller cannot act on it
|
|
49
|
+
assert.equal(r.proceed, c.expect.proceed, "proceed; why: " + (r.threw || (r.why || []).join(" | ")));
|
|
50
|
+
assert.equal(!!r.header, c.expect.proceed, "the receipt header is given only when it proceeds");
|
|
51
|
+
if (!r.proceed) assert.ok(r.threw || (r.why && r.why.length > 0), "a refusal says why");
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|