@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.
@@ -1,306 +0,0 @@
1
- /**
2
- * In-memory state for the lean wp.cloud fake. Models only the lifecycle we captured real shapes for
3
- * (sites, jobs, aliases, meta, crontab) — see internal-docs/testing-system-plan.html §4. Edge-cache /
4
- * SSL / domain-claim are answered leniently by the router but not modelled here; their real behavior
5
- * lives in the gated `direct/` suites.
6
- */
7
- import { createHash } from "node:crypto";
8
- const DEFAULT_PERSIST_DATA_BEHAVIOR = {
9
- ticketPolls: ["success"],
10
- omitResponseTicket: false,
11
- lockRefusals: 0,
12
- retryAfterSeconds: 10,
13
- };
14
- const DATACENTERS = ["ams", "bur", "dca", "dfw"];
15
- const PHP_VERSIONS = ["8.2", "8.3", "8.4", "8.5"];
16
- function formFlag(value) {
17
- return value === true || value === 1 || value === "1" || value === "true" || value === "on";
18
- }
19
- export class FakeAtomicStore {
20
- sites = new Map();
21
- clientMeta = new Map();
22
- metricsResponses = new Map();
23
- metricRequestLog = [];
24
- responseTickets = new Map();
25
- persistDataBehavior = { ...DEFAULT_PERSIST_DATA_BEHAVIOR };
26
- siteSeq;
27
- jobSeq = 154_000_000;
28
- cronSeq = 900_000;
29
- ticketSeq = 0;
30
- constructor(options = {}) {
31
- this.siteSeq = options.siteSequenceStart ?? 151_000_000;
32
- }
33
- datacenters() {
34
- return [...DATACENTERS];
35
- }
36
- phpVersions() {
37
- return [...PHP_VERSIONS];
38
- }
39
- nextJobId() {
40
- return ++this.jobSeq;
41
- }
42
- createSite(input) {
43
- const atomicSiteId = String(++this.siteSeq);
44
- this.sites.set(atomicSiteId, {
45
- atomicSiteId,
46
- domainName: input.domainName,
47
- meta: { ...input.meta },
48
- persistentData: {},
49
- persistentDataEnv: {},
50
- aliases: new Set(),
51
- crons: [],
52
- });
53
- return {
54
- job_id: this.nextJobId(),
55
- atomic_site_id: Number(atomicSiteId),
56
- domain_name: input.domainName,
57
- };
58
- }
59
- ensureSite(input) {
60
- const existing = this.sites.get(input.atomicSiteId);
61
- if (existing)
62
- return existing;
63
- this.siteSeq = Math.max(this.siteSeq, Number(input.atomicSiteId) || this.siteSeq);
64
- const site = {
65
- atomicSiteId: input.atomicSiteId,
66
- domainName: input.domainName,
67
- meta: input.meta ? { ...input.meta } : {},
68
- persistentData: input.persistentData ? { ...input.persistentData } : {},
69
- persistentDataEnv: input.persistentDataEnv ? { ...input.persistentDataEnv } : {},
70
- aliases: new Set(input.aliases ?? []),
71
- crons: [],
72
- };
73
- this.sites.set(input.atomicSiteId, site);
74
- return site;
75
- }
76
- snapshot() {
77
- return [...this.sites.values()].map((site) => ({
78
- atomicSiteId: site.atomicSiteId,
79
- domainName: site.domainName,
80
- meta: { ...site.meta },
81
- persistentData: { ...site.persistentData },
82
- persistentDataEnv: { ...site.persistentDataEnv },
83
- aliases: [...site.aliases],
84
- }));
85
- }
86
- get(id) {
87
- return this.sites.get(id);
88
- }
89
- /**
90
- * Atomic's `get_site_by_site_id_or_domain`: an argument containing a dot is
91
- * matched against `atomic_site.domain_name` first, then against the alias
92
- * table (`atomic_domain` joined onto `atomic_site`); anything else is a site
93
- * id. This is what lets one `get-site` call answer "who holds this hostname?".
94
- */
95
- resolve(idOrDomain) {
96
- if (!idOrDomain.includes(".")) {
97
- return this.sites.get(idOrDomain);
98
- }
99
- const sites = [...this.sites.values()];
100
- return (sites.find((site) => site.domainName === idOrDomain) ??
101
- sites.find((site) => site.aliases.has(idOrDomain)));
102
- }
103
- delete(id) {
104
- this.sites.delete(id);
105
- return { job_id: this.nextJobId() };
106
- }
107
- list() {
108
- return [...this.sites.values()];
109
- }
110
- setMeta(id, key, value) {
111
- const site = this.sites.get(id);
112
- if (site)
113
- site.meta[key] = value;
114
- }
115
- getMeta(id, key) {
116
- return this.sites.get(id)?.meta[key];
117
- }
118
- removeMeta(id, key) {
119
- const site = this.sites.get(id);
120
- if (site)
121
- delete site.meta[key];
122
- }
123
- addAlias(id, domain) {
124
- this.sites.get(id)?.aliases.add(domain);
125
- }
126
- removeAlias(id, domain) {
127
- this.sites.get(id)?.aliases.delete(domain);
128
- }
129
- aliases(id) {
130
- return [...(this.sites.get(id)?.aliases ?? [])];
131
- }
132
- addCron(id, input) {
133
- const site = this.sites.get(id);
134
- if (!site)
135
- return null;
136
- const cron = {
137
- cronId: ++this.cronSeq,
138
- schedule: input.schedule,
139
- requestedSchedule: input.schedule,
140
- command: input.command,
141
- };
142
- site.crons.push(cron);
143
- return cron;
144
- }
145
- listCrons(id) {
146
- return [...(this.sites.get(id)?.crons ?? [])];
147
- }
148
- updateCron(id, cronId, command) {
149
- const cron = this.sites.get(id)?.crons.find((entry) => entry.cronId === cronId);
150
- if (!cron)
151
- return false;
152
- cron.command = command;
153
- return true;
154
- }
155
- removeCron(id, cronId) {
156
- const site = this.sites.get(id);
157
- if (!site)
158
- return false;
159
- const before = site.crons.length;
160
- site.crons = site.crons.filter((entry) => entry.cronId !== cronId);
161
- return site.crons.length < before;
162
- }
163
- accessLogs(id) {
164
- const site = this.sites.get(id);
165
- if (!site)
166
- return null;
167
- const host = [...site.aliases][0] ?? site.domainName;
168
- return [
169
- {
170
- timestamp: Math.floor(Date.now() / 1000),
171
- http_host: host,
172
- request_type: "GET",
173
- request_url: "/",
174
- status: 200,
175
- response_bytes: 1024,
176
- },
177
- ];
178
- }
179
- setClientMeta(key, value) {
180
- this.clientMeta.set(key, value);
181
- }
182
- getClientMeta(key) {
183
- return this.clientMeta.get(key);
184
- }
185
- deleteClientMeta(key) {
186
- return this.clientMeta.delete(key);
187
- }
188
- setMetricsResponse(type, key, response) {
189
- this.metricsResponses.set(`${type}:${key}`, response);
190
- }
191
- metricsResponse(type, key) {
192
- return this.metricsResponses.get(`${type}:${key}`) ?? { periods: [] };
193
- }
194
- recordMetricsRequest(request) {
195
- this.metricRequestLog.push(request);
196
- }
197
- metricsRequests() {
198
- return [...this.metricRequestLog];
199
- }
200
- applyPersistentData(id, operations) {
201
- const site = this.sites.get(id);
202
- if (!site)
203
- return null;
204
- for (const [key, operation] of Object.entries(operations)) {
205
- if (!operation || typeof operation !== "object" || Array.isArray(operation))
206
- continue;
207
- const record = operation;
208
- if (formFlag(record.delete)) {
209
- delete site.persistentData[key];
210
- delete site.persistentDataEnv[key];
211
- continue;
212
- }
213
- // The wire form is `application/x-www-form-urlencoded`, so a scalar value
214
- // always decodes to a string; anything else was never a persistable value.
215
- site.persistentData[key] = typeof record.value === "string" ? record.value : "";
216
- site.persistentDataEnv[key] = formFlag(record.env);
217
- }
218
- return this.persistentData(id);
219
- }
220
- /** Shape the next `POST /site-persist-data/{site}` answers a test needs. */
221
- setPersistDataBehavior(input) {
222
- this.persistDataBehavior = { ...this.persistDataBehavior, ...input };
223
- }
224
- /**
225
- * Consumes one pending lock refusal. Returns the `Retry-After` seconds to
226
- * send with the documented 429, or null when the write should be accepted.
227
- * Docs, `POST /site-persist-data/{site}` 429: "Site data lock could not be
228
- * acquired; retry after the Retry-After header value (10 seconds)."
229
- */
230
- takePersistDataLockRefusal() {
231
- if (this.persistDataBehavior.lockRefusals <= 0)
232
- return null;
233
- this.persistDataBehavior.lockRefusals -= 1;
234
- return this.persistDataBehavior.retryAfterSeconds;
235
- }
236
- persistDataOmitsResponseTicket() {
237
- return this.persistDataBehavior.omitResponseTicket;
238
- }
239
- /**
240
- * Mints a ticket id in the shape of the real capture at
241
- * `e2e-tests/fixtures/wpcloud-snapshots/site-lifecycle/4a94e336931c035047b5c3b39e2832c5258c0095623100933c5345a812c4b9a8.json`,
242
- * whose persist-data response carried
243
- * `"response_ticket_id": "6a3aeb00aec214efed259911342.54f874b098adb09f.1"` —
244
- * three dot-separated segments, not the two-segment `67a3f000.abc123` the
245
- * OpenAPI example shows.
246
- */
247
- openResponseTicket() {
248
- const seq = ++this.ticketSeq;
249
- const digest = createHash("sha256").update(`fake-response-ticket:${seq}`).digest("hex");
250
- const id = `${digest.slice(0, 27)}.${digest.slice(27, 43)}.${seq}`;
251
- this.responseTickets.set(id, [...this.persistDataBehavior.ticketPolls]);
252
- return id;
253
- }
254
- /**
255
- * One `POST /response-ticket/get/summary` poll: consumes the head outcome,
256
- * the last one repeating forever. null means the provider has no such ticket
257
- * (the documented 404).
258
- */
259
- pollResponseTicket(id) {
260
- const polls = this.responseTickets.get(id);
261
- if (!polls || polls.length === 0)
262
- return null;
263
- return polls.length === 1 ? polls[0] : polls.shift();
264
- }
265
- persistentData(id) {
266
- const site = this.sites.get(id);
267
- if (!site)
268
- return null;
269
- return { ...site.persistentData };
270
- }
271
- /** `GET /get-site/{id}/extra` shape — note `_data` is intentionally ABSENT (faithful to the real
272
- * endpoint, which omits it; the marker lives only on the list + site-meta endpoints). */
273
- siteExtra(site) {
274
- return {
275
- atomic_site_id: site.atomicSiteId,
276
- wpcom_blog_id: null,
277
- domain_name: site.domainName,
278
- server_pool_id: "146",
279
- atomic_client_id: "168",
280
- chroot_path: "/client-chroot/generic/web",
281
- chroot_ssh_path: "/client-chroot/generic/ssh",
282
- burst_php_conns: Number(site.meta.burst_php_conns ?? 0),
283
- cache_prefix: "fake00000000000000000000000000000",
284
- canonicalize_aliases: site.meta.canonicalize_aliases ?? "false",
285
- db_charset: "utf8mb4",
286
- db_collate: "utf8mb4_general_ci",
287
- db_pass: "[REDACTED]",
288
- default_php_conns: Number(site.meta.default_php_conns ?? 10),
289
- php_fs_permissions: site.meta.php_fs_permissions ?? "RW",
290
- php_memory_limit: Number(site.meta.php_memory_limit ?? 512),
291
- php_version: site.meta.php_version ?? "8.4",
292
- site_api_key: "[REDACTED]",
293
- static_file_404: site.meta.static_file_404 ?? "wordpress",
294
- wp_admin_user: `stattic-${site.atomicSiteId}`.slice(0, 32),
295
- wp_version: "latest",
296
- };
297
- }
298
- /** `GET /get-sites/{client}/_data/` entry shape. */
299
- listEntry(site, options) {
300
- return {
301
- atomic_site_id: Number(site.atomicSiteId),
302
- domain_name: site.domainName,
303
- ...(options.includeData ? { _data: site.meta._data ?? null } : {}),
304
- };
305
- }
306
- }
@@ -1,2 +0,0 @@
1
- export declare const value = "real";
2
- //# sourceMappingURL=isolation-target.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"isolation-target.d.ts","sourceRoot":"","sources":["../../../src/fake/test-fixtures/isolation-target.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,KAAK,SAAS,CAAC"}
@@ -1 +0,0 @@
1
- export const value = "real";