@spacefast/wpcloud-sdk 0.0.23 → 0.0.26
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/generated/sdk.gen.d.ts +2 -2
- package/dist/generated/sdk.gen.js +2 -2
- package/dist/generated/types.gen.d.ts +13 -5
- package/dist/generated/types.gen.d.ts.map +1 -1
- package/dist/runtime.d.ts +1 -7
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +26 -315
- package/package.json +1 -6
- package/dist/fake/index.d.ts +0 -2
- package/dist/fake/index.d.ts.map +0 -1
- package/dist/fake/index.js +0 -1
- package/dist/fake/router.d.ts +0 -13
- package/dist/fake/router.d.ts.map +0 -1
- package/dist/fake/router.js +0 -329
- package/dist/fake/server.d.ts +0 -28
- package/dist/fake/server.d.ts.map +0 -1
- package/dist/fake/server.js +0 -108
- package/dist/fake/store.d.ts +0 -157
- package/dist/fake/store.d.ts.map +0 -1
- package/dist/fake/store.js +0 -306
- package/dist/fake/test-fixtures/isolation-target.d.ts +0 -2
- package/dist/fake/test-fixtures/isolation-target.d.ts.map +0 -1
- package/dist/fake/test-fixtures/isolation-target.js +0 -1
package/dist/fake/router.js
DELETED
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Routes a parsed wp.cloud request to the in-memory store and returns the `{message, data}` envelope
|
|
3
|
-
* the real API uses. Errors are `{message, data: []}` + an HTTP status with NO machine code — exactly
|
|
4
|
-
* what the captured fixtures show (the control plane classifies on status + message text).
|
|
5
|
-
*/
|
|
6
|
-
import { createHash } from "node:crypto";
|
|
7
|
-
import { FakeAtomicStore } from "./store.js";
|
|
8
|
-
const ok = (data) => ({ status: 200, body: { message: "OK", data } });
|
|
9
|
-
const fail = (status, message) => ({
|
|
10
|
-
status,
|
|
11
|
-
body: { message, data: [] },
|
|
12
|
-
});
|
|
13
|
-
const RESTRICTED_DOMAINS = new Set(["wordpress.com", "wp.com", "automattic.com"]);
|
|
14
|
-
function asString(value, fallback = "") {
|
|
15
|
-
return typeof value === "string" ? value : fallback;
|
|
16
|
-
}
|
|
17
|
-
function asStringMap(value) {
|
|
18
|
-
const out = {};
|
|
19
|
-
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
20
|
-
for (const [k, v] of Object.entries(value))
|
|
21
|
-
out[k] = String(v);
|
|
22
|
-
}
|
|
23
|
-
return out;
|
|
24
|
-
}
|
|
25
|
-
/** The `data` form field of `POST /site-persist-data/{site}` — a bag of per-key
|
|
26
|
-
* set/delete operations, matching `FakeAtomicStore#applyPersistentData`. */
|
|
27
|
-
function isPersistDataOperations(value) {
|
|
28
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
29
|
-
}
|
|
30
|
-
const READABLE_CLIENT_META_KEYS = new Set([
|
|
31
|
-
"webhook_url",
|
|
32
|
-
"client_ssh_default_from",
|
|
33
|
-
"client_ssh_force_from",
|
|
34
|
-
"client_ssh_firewall",
|
|
35
|
-
"default_privacy_model",
|
|
36
|
-
"max_space_quota",
|
|
37
|
-
"skip_force_backup_db",
|
|
38
|
-
]);
|
|
39
|
-
const WRITABLE_CLIENT_META_KEYS = new Set([
|
|
40
|
-
"webhook_url",
|
|
41
|
-
"webhook_secret_key",
|
|
42
|
-
"client_ssh_default_from",
|
|
43
|
-
"client_ssh_force_from",
|
|
44
|
-
"client_ssh_firewall",
|
|
45
|
-
"default_privacy_model",
|
|
46
|
-
"webhook_hmac_key",
|
|
47
|
-
"skip_force_backup_db",
|
|
48
|
-
]);
|
|
49
|
-
/**
|
|
50
|
-
* @param path the path after `/api/v1.0/`, e.g. `create-site/minipage` or `site-meta/151/_data/get`
|
|
51
|
-
* (segments still URL-encoded; this fn decodes the ones it needs)
|
|
52
|
-
* @param body the nested-decoded request body (form-urlencoded → object)
|
|
53
|
-
*/
|
|
54
|
-
export function routeFakeRequest(store, method, path, body) {
|
|
55
|
-
const seg = path.split("/").filter(Boolean);
|
|
56
|
-
const head = seg[0] ?? "";
|
|
57
|
-
// --- static reads ---------------------------------------------------------------------------
|
|
58
|
-
if (method === "GET" && head === "get-available-datacenters")
|
|
59
|
-
return ok(store.datacenters());
|
|
60
|
-
if (method === "GET" && head === "get-php-versions")
|
|
61
|
-
return ok(store.phpVersions());
|
|
62
|
-
if (method === "GET" && head === "check-can-host-domain") {
|
|
63
|
-
const domain = decodeURIComponent(seg[2] ?? "");
|
|
64
|
-
if (RESTRICTED_DOMAINS.has(domain))
|
|
65
|
-
return fail(400, `Restricted domain [${domain}]`);
|
|
66
|
-
if (domain.endsWith("example.com")) {
|
|
67
|
-
return fail(400, `Domain name already used [${domain}]. TXT record verification is required to bypass this check.`);
|
|
68
|
-
}
|
|
69
|
-
return ok([]); // claimable
|
|
70
|
-
}
|
|
71
|
-
// GET /get-domain-verification-code/{client}/{domain} — `data` is the bare code string
|
|
72
|
-
// (docs/wpcloud/openapi.json). Deterministic per domain so repeated DNS refreshes hand
|
|
73
|
-
// out the same TXT value, like the real API's stable per-client+domain code.
|
|
74
|
-
if (method === "GET" && head === "get-domain-verification-code") {
|
|
75
|
-
const domain = decodeURIComponent(seg[2] ?? "");
|
|
76
|
-
if (!domain)
|
|
77
|
-
return fail(400, "Missing or invalid domain name");
|
|
78
|
-
return ok(`atomic-domain-${createHash("sha256").update(domain).digest("hex")}`);
|
|
79
|
-
}
|
|
80
|
-
// GET /get-ips/{client}[/{domain}] — client IPs/CIDRs; `suggested` is only present when a
|
|
81
|
-
// domain is supplied (docs/wpcloud/openapi.json example values).
|
|
82
|
-
if (method === "GET" && head === "get-ips") {
|
|
83
|
-
const domain = decodeURIComponent(seg[2] ?? "");
|
|
84
|
-
return ok({
|
|
85
|
-
ips: ["192.0.78.128/25"],
|
|
86
|
-
...(domain ? { suggested: ["192.0.78.150", "192.0.78.200"] } : {}),
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
// --- sites lifecycle ------------------------------------------------------------------------
|
|
90
|
-
// GET /get-sites/{client}[/_data]?limit=… — only return requested meta,
|
|
91
|
-
// matching Atomic's slash-delimited additional-meta contract.
|
|
92
|
-
if (method === "GET" && head === "get-sites") {
|
|
93
|
-
const includeData = seg.slice(2).includes("_data");
|
|
94
|
-
return ok(store.list().map((site) => store.listEntry(site, { includeData })));
|
|
95
|
-
}
|
|
96
|
-
// POST /create-site/{client}
|
|
97
|
-
if (method === "POST" && head === "create-site") {
|
|
98
|
-
const meta = asStringMap(body.meta);
|
|
99
|
-
const domainName = asString(body.domain_name, `fake-${store.nextJobId()}.wpcomstaging.com`);
|
|
100
|
-
return ok(store.createSite({ domainName, meta }));
|
|
101
|
-
}
|
|
102
|
-
// POST /delete-site/{service}/{identifier}
|
|
103
|
-
if (method === "POST" && head === "delete-site") {
|
|
104
|
-
return ok(store.delete(seg[2] ?? ""));
|
|
105
|
-
}
|
|
106
|
-
// GET /get-site/{site}[/extra] — `{site}` is a site id OR any hostname the site
|
|
107
|
-
// answers to: its own domain, or one of its aliases (see `FakeAtomicStore#resolve`).
|
|
108
|
-
if (method === "GET" && head === "get-site") {
|
|
109
|
-
const site = store.resolve(decodeURIComponent(seg[1] ?? ""));
|
|
110
|
-
return site ? ok(store.siteExtra(site)) : fail(404, "Site not found.");
|
|
111
|
-
}
|
|
112
|
-
// GET /job-completion/{id} , GET /job-status/{id} — jobs settle immediately in the fake
|
|
113
|
-
if (method === "GET" && head === "job-completion")
|
|
114
|
-
return ok("success");
|
|
115
|
-
if (method === "GET" && head === "job-status")
|
|
116
|
-
return ok({ status: "success" });
|
|
117
|
-
// POST /site-persist-data/{site} — the only endpoint in the API that documents
|
|
118
|
-
// a 429: "Site data lock could not be acquired; retry after the Retry-After
|
|
119
|
-
// header value (10 seconds)." Contention is a per-site write lock, so the
|
|
120
|
-
// refusal comes before the write is applied.
|
|
121
|
-
if (method === "POST" && head === "site-persist-data") {
|
|
122
|
-
const retryAfterSeconds = store.takePersistDataLockRefusal();
|
|
123
|
-
if (retryAfterSeconds !== null) {
|
|
124
|
-
return {
|
|
125
|
-
...fail(429, "Site data lock could not be acquired."),
|
|
126
|
-
headers: { "retry-after": String(retryAfterSeconds) },
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
const data = isPersistDataOperations(body.data)
|
|
130
|
-
? store.applyPersistentData(seg[1] ?? "", body.data)
|
|
131
|
-
: null;
|
|
132
|
-
if (!data)
|
|
133
|
-
return fail(404, "Site not found.");
|
|
134
|
-
return ok({
|
|
135
|
-
// "response_ticket_id — Response ticket ID to check on the status of this
|
|
136
|
-
// request." Not marked required, so a test can ask for it to be absent.
|
|
137
|
-
// `job_id` rides along exactly as the real endpoint sends it, and stays
|
|
138
|
-
// documented as "Deprecated. Internal job ID; unreliable indicator of
|
|
139
|
-
// success."
|
|
140
|
-
...(store.persistDataOmitsResponseTicket()
|
|
141
|
-
? {}
|
|
142
|
-
: { response_ticket_id: store.openResponseTicket() }),
|
|
143
|
-
job_id: store.nextJobId(),
|
|
144
|
-
data,
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
// POST /response-ticket/get/summary — "Retrieve metadata about the response
|
|
148
|
-
// ticket." Body carries `response-ticket-id`; 200 returns
|
|
149
|
-
// {created, created_ts, response_count, status}, 202 means "Ticket has not yet
|
|
150
|
-
// received a response; retry after 1 second.", 404 "Ticket not found (and old
|
|
151
|
-
// enough to have been deleted)."
|
|
152
|
-
if (method === "POST" && head === "response-ticket" && seg[1] === "get" && seg[2] === "summary") {
|
|
153
|
-
const poll = store.pollResponseTicket(asString(body["response-ticket-id"]));
|
|
154
|
-
if (poll === null)
|
|
155
|
-
return fail(404, "Ticket not found.");
|
|
156
|
-
if (poll === "forbidden")
|
|
157
|
-
return fail(403, "Access denied.");
|
|
158
|
-
if (poll === "accepted") {
|
|
159
|
-
// The 202 is the one success-family body the spec declares OUTSIDE the
|
|
160
|
-
// `{message, data}` envelope: its schema is
|
|
161
|
-
// {"type":"object","properties":{"Accepted":{"type":"string",
|
|
162
|
-
// "description":"Ticket has not yet received a response; retry after 1
|
|
163
|
-
// second."}}}. Serve it exactly that way — a caller that assumes the
|
|
164
|
-
// envelope here breaks on the first poll of a pending ticket.
|
|
165
|
-
return {
|
|
166
|
-
status: 202,
|
|
167
|
-
body: { Accepted: "Ticket has not yet received a response; retry after 1 second." },
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
return ok({
|
|
171
|
-
created: new Date().toISOString().replace("T", " ").slice(0, 19),
|
|
172
|
-
created_ts: Math.floor(Date.now() / 1000),
|
|
173
|
-
response_count: poll === "running" ? 0 : 1,
|
|
174
|
-
status: poll,
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
// /site-meta/{site}/{key}/{action}
|
|
178
|
-
if (head === "site-meta") {
|
|
179
|
-
const [, id, key, action] = seg;
|
|
180
|
-
if (method === "GET" && action === "get") {
|
|
181
|
-
// envelope `data` is the raw value string (or null) — faithful to the captured _data/get
|
|
182
|
-
return ok(store.getMeta(id ?? "", key ?? "") ?? null);
|
|
183
|
-
}
|
|
184
|
-
if (method === "POST" && (action === "update" || action === "add")) {
|
|
185
|
-
store.setMeta(id ?? "", key ?? "", asString(body.value));
|
|
186
|
-
return ok([]);
|
|
187
|
-
}
|
|
188
|
-
if (method === "POST" && action === "remove") {
|
|
189
|
-
store.removeMeta(id ?? "", key ?? "");
|
|
190
|
-
return ok([]);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
// /site-alias/{client}/{id}/{op}[/{domain}]
|
|
194
|
-
if (method === "GET" && head === "site-alias") {
|
|
195
|
-
const id = seg[2] ?? "";
|
|
196
|
-
const op = seg[3];
|
|
197
|
-
const domain = decodeURIComponent(seg[4] ?? "");
|
|
198
|
-
if (op === "list")
|
|
199
|
-
return ok({ domains: store.aliases(id) });
|
|
200
|
-
if (op === "add") {
|
|
201
|
-
store.addAlias(id, domain);
|
|
202
|
-
return ok({ domains: store.aliases(id) });
|
|
203
|
-
}
|
|
204
|
-
if (op === "remove") {
|
|
205
|
-
store.removeAlias(id, domain);
|
|
206
|
-
return ok([]);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
// /crontab/{site}/add|list|update/{cron_id}|remove — matches the documented
|
|
210
|
-
// envelope exactly (docs/wpcloud/openapi.json): add/update return
|
|
211
|
-
// {cron_id}, list returns the array of entries verbatim, remove returns [].
|
|
212
|
-
if (head === "crontab") {
|
|
213
|
-
const id = seg[1] ?? "";
|
|
214
|
-
const action = seg[2];
|
|
215
|
-
if (method === "POST" && action === "add") {
|
|
216
|
-
const schedule = asString(body.schedule);
|
|
217
|
-
const command = asString(body.command);
|
|
218
|
-
if (!schedule || !command)
|
|
219
|
-
return fail(400, "Invalid or empty schedule or command.");
|
|
220
|
-
const cron = store.addCron(id, { schedule, command });
|
|
221
|
-
if (!cron)
|
|
222
|
-
return fail(404, "Site not found.");
|
|
223
|
-
return ok({ cron_id: cron.cronId });
|
|
224
|
-
}
|
|
225
|
-
if (method === "GET" && action === "list") {
|
|
226
|
-
if (!store.get(id))
|
|
227
|
-
return fail(404, "Site not found.");
|
|
228
|
-
return ok(store.listCrons(id).map((cron) => ({
|
|
229
|
-
cron_id: cron.cronId,
|
|
230
|
-
schedule: cron.schedule,
|
|
231
|
-
requested_schedule: cron.requestedSchedule,
|
|
232
|
-
command: cron.command,
|
|
233
|
-
})));
|
|
234
|
-
}
|
|
235
|
-
if (method === "POST" && action === "update") {
|
|
236
|
-
const cronId = Number(seg[3] ?? "");
|
|
237
|
-
const command = asString(body.command);
|
|
238
|
-
if (!command)
|
|
239
|
-
return fail(400, "Invalid or empty command.");
|
|
240
|
-
return store.updateCron(id, cronId, command)
|
|
241
|
-
? ok({ cron_id: cronId })
|
|
242
|
-
: fail(404, "Cron entry not found.");
|
|
243
|
-
}
|
|
244
|
-
if (method === "POST" && action === "remove") {
|
|
245
|
-
const cronId = Number(body.cron_id ?? "");
|
|
246
|
-
return store.removeCron(id, cronId) ? ok([]) : fail(404, "Cron entry not found.");
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
// /edge-cache/{id}/{op}/{host} — Atomic falls back to the primary domain
|
|
250
|
-
// when `host` is not attached, so callers must avoid issuing that request.
|
|
251
|
-
if (head === "edge-cache") {
|
|
252
|
-
const op = seg[2];
|
|
253
|
-
if (op === "on" || op === "off" || op === "purge")
|
|
254
|
-
return ok([]);
|
|
255
|
-
if (op === "get")
|
|
256
|
-
return ok({ status: 1, status_name: "Enabled", ddos_until: 0 });
|
|
257
|
-
}
|
|
258
|
-
// /ssl-info/{host} — lean: not provisioned (loadWpCloudSslInfo tolerates null). SSL = direct suites.
|
|
259
|
-
if (head === "ssl-info")
|
|
260
|
-
return fail(404, "Site not found.");
|
|
261
|
-
// POST /site-phpmyadmin/{site} — a time-limited sign-in URL for the site's
|
|
262
|
-
// own MySQL (docs/wpcloud/openapi.json: `{ url }`, 404 when the site is
|
|
263
|
-
// unknown). The token is opaque and single-use upstream, so the fake mints a
|
|
264
|
-
// fresh one per call rather than a stable value a test could match on.
|
|
265
|
-
if (method === "POST" && head === "site-phpmyadmin") {
|
|
266
|
-
const site = store.resolve(decodeURIComponent(seg[1] ?? ""));
|
|
267
|
-
if (!site)
|
|
268
|
-
return fail(404, "Site not found.");
|
|
269
|
-
const token = createHash("sha256")
|
|
270
|
-
.update(`${site.atomicSiteId}:${store.nextJobId()}`)
|
|
271
|
-
.digest("hex");
|
|
272
|
-
return ok({ url: `https://phpmyadmin.wpcloud.test/?token=${token}` });
|
|
273
|
-
}
|
|
274
|
-
// POST /site-logs/{site} — enough provider shape for the control plane's
|
|
275
|
-
// access-log normalization and hostname filtering to run in full-lane tests.
|
|
276
|
-
if (method === "POST" && head === "site-logs") {
|
|
277
|
-
const logs = store.accessLogs(seg[1] ?? "");
|
|
278
|
-
return logs ? ok({ logs }) : fail(404, "Site not found.");
|
|
279
|
-
}
|
|
280
|
-
// /client-authorized-keys/{client}/{op}[/{id}] — deploy-key registration with the
|
|
281
|
-
// platform. The full lane's local-atomic node already trusts the test deploy key via
|
|
282
|
-
// its authorized_keys, so ack add/list/remove and let real SSH carry the deploy.
|
|
283
|
-
if (head === "client-authorized-keys") {
|
|
284
|
-
const op = seg[2];
|
|
285
|
-
if (op === "list")
|
|
286
|
-
return ok({});
|
|
287
|
-
return ok([]); // add / remove
|
|
288
|
-
}
|
|
289
|
-
// /client-meta/{client}/{key}/{action}
|
|
290
|
-
if (head === "client-meta") {
|
|
291
|
-
const [, , key, action] = seg.map((part) => decodeURIComponent(part));
|
|
292
|
-
if (!key)
|
|
293
|
-
return fail(400, "Invalid key");
|
|
294
|
-
if (method === "GET" && action === "get") {
|
|
295
|
-
if (!READABLE_CLIENT_META_KEYS.has(key))
|
|
296
|
-
return fail(400, "Invalid key");
|
|
297
|
-
const value = store.getClientMeta(key);
|
|
298
|
-
return value === undefined ? fail(404, "Not Found") : ok(value);
|
|
299
|
-
}
|
|
300
|
-
if (method === "GET" && action === "remove") {
|
|
301
|
-
if (!WRITABLE_CLIENT_META_KEYS.has(key))
|
|
302
|
-
return fail(400, "Invalid key");
|
|
303
|
-
store.deleteClientMeta(key);
|
|
304
|
-
return ok([]);
|
|
305
|
-
}
|
|
306
|
-
if (method === "POST" && (action === "update" || action === "add")) {
|
|
307
|
-
if (!WRITABLE_CLIENT_META_KEYS.has(key))
|
|
308
|
-
return fail(400, "Invalid key");
|
|
309
|
-
if (body.value === undefined || body.value === null || body.value === "") {
|
|
310
|
-
return fail(400, "A value must be provided");
|
|
311
|
-
}
|
|
312
|
-
if (action === "update" && store.getClientMeta(key) === undefined) {
|
|
313
|
-
return fail(404, "Not Found");
|
|
314
|
-
}
|
|
315
|
-
store.setClientMeta(key, asString(body.value));
|
|
316
|
-
return ok([]);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
// POST /metrics/{type}/{key}[/summarize]
|
|
320
|
-
if (method === "POST" && head === "metrics") {
|
|
321
|
-
const type = decodeURIComponent(seg[1] ?? "");
|
|
322
|
-
const key = decodeURIComponent(seg[2] ?? "");
|
|
323
|
-
const summarize = seg[3] === "summarize";
|
|
324
|
-
store.recordMetricsRequest({ type, key, summarize, body });
|
|
325
|
-
return ok(store.metricsResponse(type, key));
|
|
326
|
-
}
|
|
327
|
-
// Anything the control plane calls that we don't model: surface it loudly, don't silently pass.
|
|
328
|
-
return fail(404, `fake_endpoint_not_implemented: ${method} /${path}`);
|
|
329
|
-
}
|
package/dist/fake/server.d.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { WpCloudFakeFetch } from "../runtime.js";
|
|
2
|
-
import { FakeAtomicStore } from "./store.js";
|
|
3
|
-
/**
|
|
4
|
-
* Same fake as `makeFakeAtomic()`, but also hands back the underlying store
|
|
5
|
-
* so a test can assert on recorded state directly (house rule: assert the
|
|
6
|
-
* effect, not that a mock was called) — e.g. crontab registration, aliases,
|
|
7
|
-
* persistent data.
|
|
8
|
-
*/
|
|
9
|
-
export declare function makeFakeAtomicWithStore(options?: {
|
|
10
|
-
siteSequenceStart?: number;
|
|
11
|
-
}): {
|
|
12
|
-
fetch: WpCloudFakeFetch;
|
|
13
|
-
store: FakeAtomicStore;
|
|
14
|
-
};
|
|
15
|
-
export declare function makeFakeAtomic(options?: {
|
|
16
|
-
siteSequenceStart?: number;
|
|
17
|
-
}): WpCloudFakeFetch;
|
|
18
|
-
/**
|
|
19
|
-
* Process-global fake for `WP_CLOUD_PROVIDER_MODE=fake` (no per-suite override
|
|
20
|
-
* installed). The site sequence starts at a per-process random offset: several
|
|
21
|
-
* processes can share one database in fake mode (the e2e seed script on the
|
|
22
|
-
* host, the control-plane container) and each runs its own store — with a
|
|
23
|
-
* fixed base every process mints atomic_site_id 151000001 first and collides
|
|
24
|
-
* on the DB's unique provider_ref index. Per-suite `makeFakeAtomic()` stores
|
|
25
|
-
* keep the deterministic default.
|
|
26
|
-
*/
|
|
27
|
-
export declare const defaultFakeFetch: WpCloudFakeFetch;
|
|
28
|
-
//# sourceMappingURL=server.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/fake/server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AA+D7C;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,GAAE;IAAE,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG;IACrF,KAAK,EAAE,gBAAgB,CAAC;IACxB,KAAK,EAAE,eAAe,CAAC;CACxB,CAoBA;AAED,wBAAgB,cAAc,CAAC,OAAO,GAAE;IAAE,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,gBAAgB,CAE7F;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,EAAE,gBAE7B,CAAC"}
|
package/dist/fake/server.js
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The lean wp.cloud fake exposed as a `fetch`-shaped function — the same seam `wpCloudRawFetch` uses.
|
|
3
|
-
* `makeFakeAtomic()` gives a fresh, isolated store (per-suite use via `setWpCloudFakeFetch`);
|
|
4
|
-
* `defaultFakeFetch` is a process-global instance for env-driven `WP_CLOUD_PROVIDER_MODE=fake`.
|
|
5
|
-
*/
|
|
6
|
-
import { createHash, randomInt } from "node:crypto";
|
|
7
|
-
import { routeFakeRequest } from "./router.js";
|
|
8
|
-
import { FakeAtomicStore } from "./store.js";
|
|
9
|
-
let fakeStoreOrdinal = 0;
|
|
10
|
-
function defaultSiteSequenceStart() {
|
|
11
|
-
// Bun's isolated test worker updates `process.argv[1]` to the active test
|
|
12
|
-
// file. Hash that real file identity plus the process and store ordinal so
|
|
13
|
-
// DB-backed files sharing Postgres never restart at the same provider ref.
|
|
14
|
-
// This is deterministic input, not random test data; explicit starts remain
|
|
15
|
-
// available for tests that assert exact captured provider IDs.
|
|
16
|
-
const identity = [
|
|
17
|
-
process.env.GITHUB_RUN_ID ?? "local",
|
|
18
|
-
process.env.GITHUB_RUN_ATTEMPT ?? "0",
|
|
19
|
-
process.ppid,
|
|
20
|
-
process.pid,
|
|
21
|
-
process.argv[1] ?? "unknown",
|
|
22
|
-
fakeStoreOrdinal++,
|
|
23
|
-
].join("\0");
|
|
24
|
-
const digest = createHash("sha256").update(identity).digest();
|
|
25
|
-
return 1_000_000_000_000 + digest.readUIntBE(0, 6);
|
|
26
|
-
}
|
|
27
|
-
/** Decode an `application/x-www-form-urlencoded` body, un-nesting `key[child]` / `key[]` keys. */
|
|
28
|
-
function parseNestedForm(raw) {
|
|
29
|
-
const out = {};
|
|
30
|
-
for (const [rawKey, value] of new URLSearchParams(raw)) {
|
|
31
|
-
const segs = rawKey.replace(/\]/g, "").split("[");
|
|
32
|
-
let node = out;
|
|
33
|
-
for (let i = 0; i < segs.length; i++) {
|
|
34
|
-
const key = segs[i];
|
|
35
|
-
const last = i === segs.length - 1;
|
|
36
|
-
if (last) {
|
|
37
|
-
if (key === "" && Array.isArray(node))
|
|
38
|
-
node.push(value);
|
|
39
|
-
else
|
|
40
|
-
node[key] = value;
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
const container = node;
|
|
44
|
-
const nextIsArray = segs[i + 1] === "";
|
|
45
|
-
container[key] ??= nextIsArray ? [] : {};
|
|
46
|
-
node = container[key];
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return out;
|
|
51
|
-
}
|
|
52
|
-
async function readBody(input, init) {
|
|
53
|
-
let raw = "";
|
|
54
|
-
const body = init?.body;
|
|
55
|
-
if (typeof body === "string")
|
|
56
|
-
raw = body;
|
|
57
|
-
else if (body instanceof URLSearchParams)
|
|
58
|
-
raw = body.toString();
|
|
59
|
-
else if (input instanceof Request) {
|
|
60
|
-
try {
|
|
61
|
-
raw = await input.text();
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
raw = "";
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return raw ? parseNestedForm(raw) : {};
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Same fake as `makeFakeAtomic()`, but also hands back the underlying store
|
|
71
|
-
* so a test can assert on recorded state directly (house rule: assert the
|
|
72
|
-
* effect, not that a mock was called) — e.g. crontab registration, aliases,
|
|
73
|
-
* persistent data.
|
|
74
|
-
*/
|
|
75
|
-
export function makeFakeAtomicWithStore(options = {}) {
|
|
76
|
-
const store = new FakeAtomicStore({
|
|
77
|
-
siteSequenceStart: options.siteSequenceStart ?? defaultSiteSequenceStart(),
|
|
78
|
-
});
|
|
79
|
-
const fetch = async (input, init) => {
|
|
80
|
-
const rawUrl = input instanceof Request ? input.url : String(input);
|
|
81
|
-
const url = new URL(rawUrl);
|
|
82
|
-
// path after /api/v1.0/, trailing slash trimmed; segments stay encoded (router decodes its own)
|
|
83
|
-
const path = url.pathname.replace(/^.*\/api\/v1\.0\//, "").replace(/\/+$/, "");
|
|
84
|
-
const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
|
|
85
|
-
const body = await readBody(input, init);
|
|
86
|
-
const { status, body: payload, headers } = routeFakeRequest(store, method, path, body);
|
|
87
|
-
return new Response(JSON.stringify(payload), {
|
|
88
|
-
status,
|
|
89
|
-
headers: { "content-type": "application/json", ...headers },
|
|
90
|
-
});
|
|
91
|
-
};
|
|
92
|
-
return { fetch, store };
|
|
93
|
-
}
|
|
94
|
-
export function makeFakeAtomic(options = {}) {
|
|
95
|
-
return makeFakeAtomicWithStore(options).fetch;
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Process-global fake for `WP_CLOUD_PROVIDER_MODE=fake` (no per-suite override
|
|
99
|
-
* installed). The site sequence starts at a per-process random offset: several
|
|
100
|
-
* processes can share one database in fake mode (the e2e seed script on the
|
|
101
|
-
* host, the control-plane container) and each runs its own store — with a
|
|
102
|
-
* fixed base every process mints atomic_site_id 151000001 first and collides
|
|
103
|
-
* on the DB's unique provider_ref index. Per-suite `makeFakeAtomic()` stores
|
|
104
|
-
* keep the deterministic default.
|
|
105
|
-
*/
|
|
106
|
-
export const defaultFakeFetch = makeFakeAtomic({
|
|
107
|
-
siteSequenceStart: 151_000_000 + randomInt(0, 800_000) * 1_000,
|
|
108
|
-
});
|
package/dist/fake/store.d.ts
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
export type FakeCronEntry = {
|
|
2
|
-
cronId: number;
|
|
3
|
-
schedule: string;
|
|
4
|
-
requestedSchedule: string;
|
|
5
|
-
command: string;
|
|
6
|
-
};
|
|
7
|
-
export type FakeSite = {
|
|
8
|
-
atomicSiteId: string;
|
|
9
|
-
domainName: string;
|
|
10
|
-
meta: Record<string, string>;
|
|
11
|
-
persistentData: Record<string, string>;
|
|
12
|
-
persistentDataEnv: Record<string, boolean>;
|
|
13
|
-
aliases: Set<string>;
|
|
14
|
-
crons: FakeCronEntry[];
|
|
15
|
-
};
|
|
16
|
-
export type FakeSiteSnapshot = {
|
|
17
|
-
atomicSiteId: string;
|
|
18
|
-
domainName: string;
|
|
19
|
-
meta: Record<string, string>;
|
|
20
|
-
persistentData: Record<string, string>;
|
|
21
|
-
persistentDataEnv: Record<string, boolean>;
|
|
22
|
-
aliases: string[];
|
|
23
|
-
};
|
|
24
|
-
export type FakeMetricsRequest = {
|
|
25
|
-
type: string;
|
|
26
|
-
key: string;
|
|
27
|
-
summarize: boolean;
|
|
28
|
-
body: Record<string, unknown>;
|
|
29
|
-
};
|
|
30
|
-
/**
|
|
31
|
-
* One outcome of a `POST /response-ticket/get/summary` poll.
|
|
32
|
-
* `accepted` is the 202 — docs: "Ticket has not yet received a response; retry
|
|
33
|
-
* after 1 second." The next four are the ticket `status` strings the API
|
|
34
|
-
* enumerates on `POST /response-ticket/multi-status`: "Object mapping each
|
|
35
|
-
* requested ticket ID to its status string ("success", "failure", "running",
|
|
36
|
-
* or "unknown")." `forbidden` is the API-wide auth refusal (403 "Access
|
|
37
|
-
* denied."), which the ticket endpoints do not document either way — it is the
|
|
38
|
-
* unverified-authorization case the audit flagged.
|
|
39
|
-
*/
|
|
40
|
-
export type FakeResponseTicketPoll = "accepted" | "running" | "unknown" | "success" | "failure" | "forbidden";
|
|
41
|
-
export type FakePersistDataBehavior = {
|
|
42
|
-
/**
|
|
43
|
-
* Outcomes served to successive polls of every response ticket minted from
|
|
44
|
-
* here on; the last entry repeats. Default: the write is already applied when
|
|
45
|
-
* the fake answers, so its ticket reports success on the first poll.
|
|
46
|
-
*/
|
|
47
|
-
ticketPolls: FakeResponseTicketPoll[];
|
|
48
|
-
/** Answer without `response_ticket_id` — the docs do not mark it required. */
|
|
49
|
-
omitResponseTicket: boolean;
|
|
50
|
-
/** Consecutive documented 429s to serve before accepting the write. */
|
|
51
|
-
lockRefusals: number;
|
|
52
|
-
/** `Retry-After` on those 429s. Docs say the provider asks for 10 seconds. */
|
|
53
|
-
retryAfterSeconds: number;
|
|
54
|
-
};
|
|
55
|
-
export declare class FakeAtomicStore {
|
|
56
|
-
private readonly sites;
|
|
57
|
-
private readonly clientMeta;
|
|
58
|
-
private readonly metricsResponses;
|
|
59
|
-
private readonly metricRequestLog;
|
|
60
|
-
private readonly responseTickets;
|
|
61
|
-
private persistDataBehavior;
|
|
62
|
-
private siteSeq;
|
|
63
|
-
private jobSeq;
|
|
64
|
-
private cronSeq;
|
|
65
|
-
private ticketSeq;
|
|
66
|
-
constructor(options?: {
|
|
67
|
-
siteSequenceStart?: number;
|
|
68
|
-
});
|
|
69
|
-
datacenters(): string[];
|
|
70
|
-
phpVersions(): string[];
|
|
71
|
-
nextJobId(): number;
|
|
72
|
-
createSite(input: {
|
|
73
|
-
domainName: string;
|
|
74
|
-
meta: Record<string, string>;
|
|
75
|
-
}): {
|
|
76
|
-
job_id: number;
|
|
77
|
-
atomic_site_id: number;
|
|
78
|
-
domain_name: string;
|
|
79
|
-
};
|
|
80
|
-
ensureSite(input: {
|
|
81
|
-
atomicSiteId: string;
|
|
82
|
-
domainName: string;
|
|
83
|
-
meta?: Record<string, string>;
|
|
84
|
-
persistentData?: Record<string, string>;
|
|
85
|
-
persistentDataEnv?: Record<string, boolean>;
|
|
86
|
-
aliases?: Iterable<string>;
|
|
87
|
-
}): FakeSite;
|
|
88
|
-
snapshot(): FakeSiteSnapshot[];
|
|
89
|
-
get(id: string): FakeSite | undefined;
|
|
90
|
-
/**
|
|
91
|
-
* Atomic's `get_site_by_site_id_or_domain`: an argument containing a dot is
|
|
92
|
-
* matched against `atomic_site.domain_name` first, then against the alias
|
|
93
|
-
* table (`atomic_domain` joined onto `atomic_site`); anything else is a site
|
|
94
|
-
* id. This is what lets one `get-site` call answer "who holds this hostname?".
|
|
95
|
-
*/
|
|
96
|
-
resolve(idOrDomain: string): FakeSite | undefined;
|
|
97
|
-
delete(id: string): {
|
|
98
|
-
job_id: number;
|
|
99
|
-
};
|
|
100
|
-
list(): FakeSite[];
|
|
101
|
-
setMeta(id: string, key: string, value: string): void;
|
|
102
|
-
getMeta(id: string, key: string): string | undefined;
|
|
103
|
-
removeMeta(id: string, key: string): void;
|
|
104
|
-
addAlias(id: string, domain: string): void;
|
|
105
|
-
removeAlias(id: string, domain: string): void;
|
|
106
|
-
aliases(id: string): string[];
|
|
107
|
-
addCron(id: string, input: {
|
|
108
|
-
schedule: string;
|
|
109
|
-
command: string;
|
|
110
|
-
}): FakeCronEntry | null;
|
|
111
|
-
listCrons(id: string): FakeCronEntry[];
|
|
112
|
-
updateCron(id: string, cronId: number, command: string): boolean;
|
|
113
|
-
removeCron(id: string, cronId: number): boolean;
|
|
114
|
-
accessLogs(id: string): Array<Record<string, unknown>> | null;
|
|
115
|
-
setClientMeta(key: string, value: string): void;
|
|
116
|
-
getClientMeta(key: string): string | undefined;
|
|
117
|
-
deleteClientMeta(key: string): boolean;
|
|
118
|
-
setMetricsResponse(type: string, key: string, response: Record<string, unknown>): void;
|
|
119
|
-
metricsResponse(type: string, key: string): Record<string, unknown>;
|
|
120
|
-
recordMetricsRequest(request: FakeMetricsRequest): void;
|
|
121
|
-
metricsRequests(): FakeMetricsRequest[];
|
|
122
|
-
applyPersistentData(id: string, operations: Record<string, unknown>): Record<string, string> | null;
|
|
123
|
-
/** Shape the next `POST /site-persist-data/{site}` answers a test needs. */
|
|
124
|
-
setPersistDataBehavior(input: Partial<FakePersistDataBehavior>): void;
|
|
125
|
-
/**
|
|
126
|
-
* Consumes one pending lock refusal. Returns the `Retry-After` seconds to
|
|
127
|
-
* send with the documented 429, or null when the write should be accepted.
|
|
128
|
-
* Docs, `POST /site-persist-data/{site}` 429: "Site data lock could not be
|
|
129
|
-
* acquired; retry after the Retry-After header value (10 seconds)."
|
|
130
|
-
*/
|
|
131
|
-
takePersistDataLockRefusal(): number | null;
|
|
132
|
-
persistDataOmitsResponseTicket(): boolean;
|
|
133
|
-
/**
|
|
134
|
-
* Mints a ticket id in the shape of the real capture at
|
|
135
|
-
* `e2e-tests/fixtures/wpcloud-snapshots/site-lifecycle/4a94e336931c035047b5c3b39e2832c5258c0095623100933c5345a812c4b9a8.json`,
|
|
136
|
-
* whose persist-data response carried
|
|
137
|
-
* `"response_ticket_id": "6a3aeb00aec214efed259911342.54f874b098adb09f.1"` —
|
|
138
|
-
* three dot-separated segments, not the two-segment `67a3f000.abc123` the
|
|
139
|
-
* OpenAPI example shows.
|
|
140
|
-
*/
|
|
141
|
-
openResponseTicket(): string;
|
|
142
|
-
/**
|
|
143
|
-
* One `POST /response-ticket/get/summary` poll: consumes the head outcome,
|
|
144
|
-
* the last one repeating forever. null means the provider has no such ticket
|
|
145
|
-
* (the documented 404).
|
|
146
|
-
*/
|
|
147
|
-
pollResponseTicket(id: string): FakeResponseTicketPoll | null;
|
|
148
|
-
persistentData(id: string): Record<string, string> | null;
|
|
149
|
-
/** `GET /get-site/{id}/extra` shape — note `_data` is intentionally ABSENT (faithful to the real
|
|
150
|
-
* endpoint, which omits it; the marker lives only on the list + site-meta endpoints). */
|
|
151
|
-
siteExtra(site: FakeSite): Record<string, unknown>;
|
|
152
|
-
/** `GET /get-sites/{client}/_data/` entry shape. */
|
|
153
|
-
listEntry(site: FakeSite, options: {
|
|
154
|
-
includeData: boolean;
|
|
155
|
-
}): Record<string, unknown>;
|
|
156
|
-
}
|
|
157
|
-
//# sourceMappingURL=store.d.ts.map
|
package/dist/fake/store.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/fake/store.ts"],"names":[],"mappings":"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"}
|