@spacefast/wpcloud-sdk 0.0.21 → 0.0.24

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.
@@ -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
- }
@@ -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"}
@@ -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
- });
@@ -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
@@ -1 +0,0 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/fake/store.ts"],"names":[],"mappings":"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"}