azure-mock 4.2.0 → 4.3.3
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/README.md +1 -1
- package/dist/index.d.ts +1108 -0
- package/dist/index.js +1265 -0
- package/package.json +6 -12
package/dist/index.js
ADDED
|
@@ -0,0 +1,1265 @@
|
|
|
1
|
+
import { ID_SEPARATOR, InvalidOperationError, Operation, checkIsDeepEqual, chunk, exhaustiveGuard, getDecodedUriComponent, getOrCreate, getResult, html, noop, streamToText, takeOne } from "@esposter/shared";
|
|
2
|
+
import { toHttpHeadersLike } from "@azure/core-http-compat";
|
|
3
|
+
import { RestError, createHttpHeaders, createPipelineRequest } from "@azure/core-rest-pipeline";
|
|
4
|
+
import { AnonymousCredential } from "@azure/storage-blob";
|
|
5
|
+
import { Readable } from "node:stream";
|
|
6
|
+
import { AZURE_MAX_PAGE_SIZE, AZURE_MAX_QUEUE_VISIBILITY_TIMEOUT_MS, BinaryOperator, deserializeClause, deserializeKey, getTableNullClause, serializeClauses } from "@esposter/azure";
|
|
7
|
+
// src/constants.ts
|
|
8
|
+
const BLOB_NOT_FOUND_ERROR_CODE = "BlobNotFound";
|
|
9
|
+
const BLOB_NOT_FOUND_MESSAGE = "The specified blob does not exist.";
|
|
10
|
+
const CONDITION_NOT_MET_ERROR_CODE = "ConditionNotMet";
|
|
11
|
+
const CONDITION_NOT_MET_MESSAGE = "The condition specified using HTTP conditional header(s) is not met.";
|
|
12
|
+
const MOCK_BLOB_BASE_URL = "https://mockaccount.blob.core.windows.net";
|
|
13
|
+
const MOCK_QUEUE_BASE_URL = "https://mockaccount.queue.core.windows.net";
|
|
14
|
+
const MOCK_SEARCH_BASE_URL = "https://mockaccount.search.windows.net";
|
|
15
|
+
const MOCK_TABLE_BASE_URL = "https://mockaccount.table.core.windows.net";
|
|
16
|
+
// src/models/shared/NotImplementedError.ts
|
|
17
|
+
var NotImplementedError = class extends Error {
|
|
18
|
+
constructor(name) {
|
|
19
|
+
super(`${name} is not implemented in the mock`);
|
|
20
|
+
this.name = "NotImplementedError";
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
// src/services/container/getMockBlobKey.ts
|
|
24
|
+
const getMockBlobKey = (containerName, blobName) => `${encodeURIComponent(containerName)}${ID_SEPARATOR}${encodeURIComponent(blobName)}`;
|
|
25
|
+
// src/store/MockContainerBlobDatesDatabase.ts
|
|
26
|
+
const MockContainerBlobDatesDatabase = /* @__PURE__ */ new Map();
|
|
27
|
+
// src/store/MockContainerBlobMetadataDatabase.ts
|
|
28
|
+
const MockContainerBlobMetadataDatabase = /* @__PURE__ */ new Map();
|
|
29
|
+
// src/store/MockContainerDatabase.ts
|
|
30
|
+
var MockContainerDatabaseMap = class extends Map {
|
|
31
|
+
clear() {
|
|
32
|
+
super.clear();
|
|
33
|
+
MockContainerBlobDatesDatabase.clear();
|
|
34
|
+
MockContainerBlobMetadataDatabase.clear();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const MockContainerDatabase = new MockContainerDatabaseMap();
|
|
38
|
+
// src/services/container/getMockContainer.ts
|
|
39
|
+
const getMockContainer = (containerName) => getOrCreate(MockContainerDatabase, containerName, () => /* @__PURE__ */ new Map());
|
|
40
|
+
// src/services/container/storeMockBlobMetadata.ts
|
|
41
|
+
const storeMockBlobMetadata = (containerName, blobName, metadata) => {
|
|
42
|
+
const key = getMockBlobKey(containerName, blobName);
|
|
43
|
+
if (metadata) MockContainerBlobMetadataDatabase.set(key, metadata);
|
|
44
|
+
else MockContainerBlobMetadataDatabase.delete(key);
|
|
45
|
+
};
|
|
46
|
+
// src/services/container/deleteMockBlob.ts
|
|
47
|
+
const deleteMockBlob = (containerName, blobName) => {
|
|
48
|
+
if (!getMockContainer(containerName).delete(blobName)) return false;
|
|
49
|
+
MockContainerBlobDatesDatabase.delete(getMockBlobKey(containerName, blobName));
|
|
50
|
+
storeMockBlobMetadata(containerName, blobName, void 0);
|
|
51
|
+
return true;
|
|
52
|
+
};
|
|
53
|
+
// src/services/container/getAzureErrorXml.ts
|
|
54
|
+
/**
|
|
55
|
+
* Generates a standard Azure Storage error XML response body.
|
|
56
|
+
* @param errorCode The official Azure error code (e.g., "BlobNotFound").
|
|
57
|
+
* @param errorMessage The user-friendly error message.
|
|
58
|
+
* @returns A formatted XML string.
|
|
59
|
+
*/
|
|
60
|
+
const getAzureErrorXml = (errorCode, errorMessage) => html`<?xml version="1.0" encoding="utf-8"?>
|
|
61
|
+
<Error>
|
|
62
|
+
<code>${errorCode}</code>
|
|
63
|
+
<Message>${errorMessage}</Message>
|
|
64
|
+
</Error>`;
|
|
65
|
+
// src/services/container/getBlobUrlParts.ts
|
|
66
|
+
const decodeSegment = (segment) => getDecodedUriComponent(segment, segment);
|
|
67
|
+
const getBlobUrlParts = (url) => {
|
|
68
|
+
const parsedUrl = getResult(() => new URL(url)).unwrapOr(void 0);
|
|
69
|
+
if (!parsedUrl) return void 0;
|
|
70
|
+
const pathSegments = parsedUrl.pathname.split("/").filter(Boolean);
|
|
71
|
+
if (pathSegments.length < 2) return void 0;
|
|
72
|
+
return {
|
|
73
|
+
blobName: decodeSegment(pathSegments.slice(1).join("/")),
|
|
74
|
+
containerName: decodeSegment(takeOne(pathSegments))
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
// src/services/container/constants.ts
|
|
78
|
+
const MOCK_BLOB_SEEDED_DATE = /* @__PURE__ */ new Date(-864e13);
|
|
79
|
+
const MOCK_BLOB_SEEDED_PROPERTIES = {
|
|
80
|
+
createdOn: MOCK_BLOB_SEEDED_DATE,
|
|
81
|
+
etag: "\"seeded\"",
|
|
82
|
+
lastModified: MOCK_BLOB_SEEDED_DATE
|
|
83
|
+
};
|
|
84
|
+
// src/services/container/readMockBlobDates.ts
|
|
85
|
+
const readMockBlobDates = (containerName, blobName) => MockContainerBlobDatesDatabase.get(getMockBlobKey(containerName, blobName)) ?? MOCK_BLOB_SEEDED_PROPERTIES;
|
|
86
|
+
// src/services/container/toWebResourceLike.ts
|
|
87
|
+
const toWebResourceLike = (request) => ({
|
|
88
|
+
abortSignal: request.abortSignal,
|
|
89
|
+
agent: request.agent,
|
|
90
|
+
body: request.body,
|
|
91
|
+
clone: () => {
|
|
92
|
+
throw new NotImplementedError(`${toWebResourceLike.name}(…).clone`);
|
|
93
|
+
},
|
|
94
|
+
formData: request.formData,
|
|
95
|
+
headers: toHttpHeadersLike(request.headers),
|
|
96
|
+
keepAlive: request.disableKeepAlive,
|
|
97
|
+
method: request.method,
|
|
98
|
+
onDownloadProgress: request.onDownloadProgress,
|
|
99
|
+
onUploadProgress: request.onUploadProgress,
|
|
100
|
+
prepare: () => {
|
|
101
|
+
throw new NotImplementedError(`${toWebResourceLike.name}(…).prepare`);
|
|
102
|
+
},
|
|
103
|
+
proxySettings: request.proxySettings,
|
|
104
|
+
requestId: request.headers.get("x-ms-client-request-id") ?? request.requestId,
|
|
105
|
+
requestOverrides: request.requestOverrides,
|
|
106
|
+
streamResponseStatusCodes: request.streamResponseStatusCodes,
|
|
107
|
+
timeout: request.timeout,
|
|
108
|
+
url: request.url,
|
|
109
|
+
validateRequestProperties: noop,
|
|
110
|
+
withCredentials: request.withCredentials
|
|
111
|
+
});
|
|
112
|
+
// src/services/shared/createMockResponse.ts
|
|
113
|
+
const createMockResponse = (status, url = "") => ({
|
|
114
|
+
headers: toHttpHeadersLike(createHttpHeaders()),
|
|
115
|
+
parsedHeaders: {},
|
|
116
|
+
request: toWebResourceLike(createPipelineRequest({ url })),
|
|
117
|
+
status
|
|
118
|
+
});
|
|
119
|
+
// src/models/container/MockBlobBatchClient.ts
|
|
120
|
+
var MockBlobBatchClient = class {
|
|
121
|
+
url;
|
|
122
|
+
constructor(url) {
|
|
123
|
+
this.url = url;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Simulates the deletion of multiple blobs in a single batch request.
|
|
127
|
+
* It iterates through the requested deletions, removes existing blobs from the
|
|
128
|
+
* underlying MockContainerDatabase, and builds a response object that reports
|
|
129
|
+
* which deletions succeeded and which failed (e.g. for blobs that didn't exist).
|
|
130
|
+
*/
|
|
131
|
+
deleteBlobs(urls, credential, options) {
|
|
132
|
+
const { ifUnmodifiedSince, ...unsupportedConditions } = options?.conditions ?? {};
|
|
133
|
+
const unsupportedConditionName = Object.entries(unsupportedConditions).find(([, value]) => value !== void 0)?.[0];
|
|
134
|
+
if (unsupportedConditionName !== void 0) return Promise.reject(new NotImplementedError(`deleteBlobs with ${unsupportedConditionName}`));
|
|
135
|
+
const subResponses = [];
|
|
136
|
+
let subResponsesSucceededCount = 0;
|
|
137
|
+
let subResponsesFailedCount = 0;
|
|
138
|
+
for (const url of urls) {
|
|
139
|
+
const urlParts = getBlobUrlParts(url);
|
|
140
|
+
if (!urlParts) {
|
|
141
|
+
subResponses.push(this.#createFailedSubResponse(credential, 400, "InvalidUri", "Invalid blob URL format."));
|
|
142
|
+
subResponsesFailedCount++;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const { blobName, containerName } = urlParts;
|
|
146
|
+
if (this.#checkIsModifiedSince(containerName, blobName, ifUnmodifiedSince)) {
|
|
147
|
+
subResponses.push(this.#createFailedSubResponse(credential, 412, CONDITION_NOT_MET_ERROR_CODE, CONDITION_NOT_MET_MESSAGE));
|
|
148
|
+
subResponsesFailedCount++;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (deleteMockBlob(containerName, blobName)) {
|
|
152
|
+
subResponses.push({
|
|
153
|
+
_request: {
|
|
154
|
+
credential,
|
|
155
|
+
url: this.url
|
|
156
|
+
},
|
|
157
|
+
headers: toHttpHeadersLike(createHttpHeaders()),
|
|
158
|
+
status: 202,
|
|
159
|
+
statusMessage: "Accepted"
|
|
160
|
+
});
|
|
161
|
+
subResponsesSucceededCount++;
|
|
162
|
+
} else {
|
|
163
|
+
subResponses.push(this.#createFailedSubResponse(credential, 404, BLOB_NOT_FOUND_ERROR_CODE, BLOB_NOT_FOUND_MESSAGE));
|
|
164
|
+
subResponsesFailedCount++;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return Promise.resolve({
|
|
168
|
+
_response: {
|
|
169
|
+
...createMockResponse(202, this.url),
|
|
170
|
+
headers: toHttpHeadersLike(createHttpHeaders({
|
|
171
|
+
"content-type": "multipart/mixed",
|
|
172
|
+
"x-ms-request-id": crypto.randomUUID()
|
|
173
|
+
}))
|
|
174
|
+
},
|
|
175
|
+
subResponses,
|
|
176
|
+
subResponsesFailedCount,
|
|
177
|
+
subResponsesSucceededCount
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
getContainer(containerName) {
|
|
181
|
+
return getMockContainer(containerName);
|
|
182
|
+
}
|
|
183
|
+
#checkIsModifiedSince(containerName, blobName, ifUnmodifiedSince) {
|
|
184
|
+
return ifUnmodifiedSince !== void 0 && getMockContainer(containerName).has(blobName) && readMockBlobDates(containerName, blobName).lastModified > ifUnmodifiedSince;
|
|
185
|
+
}
|
|
186
|
+
#createFailedSubResponse(credential, status, errorCode, statusMessage) {
|
|
187
|
+
return {
|
|
188
|
+
_request: {
|
|
189
|
+
credential,
|
|
190
|
+
url: this.url
|
|
191
|
+
},
|
|
192
|
+
bodyAsText: getAzureErrorXml(errorCode, statusMessage),
|
|
193
|
+
errorCode,
|
|
194
|
+
headers: toHttpHeadersLike(createHttpHeaders()),
|
|
195
|
+
status,
|
|
196
|
+
statusMessage
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
// src/models/shared/MockRestError.ts
|
|
201
|
+
var MockRestError = class extends RestError {
|
|
202
|
+
constructor(message, statusCode) {
|
|
203
|
+
super(message, { statusCode });
|
|
204
|
+
this.name = "MockRestError";
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
// src/services/container/getBlobUrl.ts
|
|
208
|
+
const getBlobUrl = (containerName, blobName) => `${MOCK_BLOB_BASE_URL}/${containerName}/${blobName.split("/").map((segment) => encodeURIComponent(segment)).join("/")}`;
|
|
209
|
+
// src/services/container/storeMockBlobWrite.ts
|
|
210
|
+
const storeMockBlobWrite = (containerName, blobName, isExisting) => {
|
|
211
|
+
const key = getMockBlobKey(containerName, blobName);
|
|
212
|
+
const lastModified = /* @__PURE__ */ new Date();
|
|
213
|
+
MockContainerBlobDatesDatabase.set(key, {
|
|
214
|
+
createdOn: MockContainerBlobDatesDatabase.get(key)?.createdOn ?? (isExisting ? MOCK_BLOB_SEEDED_DATE : lastModified),
|
|
215
|
+
etag: `"${crypto.randomUUID()}"`,
|
|
216
|
+
lastModified
|
|
217
|
+
});
|
|
218
|
+
};
|
|
219
|
+
// src/services/shared/getMockSasUrl.ts
|
|
220
|
+
const getMockSasUrl = (url, permissions, resourceType) => `${url}?sv=2025-11-05&${resourceType ? `sr=${resourceType}&` : ""}sig=mock-signature&st=1970-01-01T00:00:00Z&se=2099-12-31T23:59:59Z&sp=${permissions?.toString() ?? "r"}`;
|
|
221
|
+
// src/models/container/MockBlobClient.ts
|
|
222
|
+
var MockBlobClient = class {
|
|
223
|
+
connectionString;
|
|
224
|
+
containerName;
|
|
225
|
+
credential = new AnonymousCredential();
|
|
226
|
+
name;
|
|
227
|
+
url;
|
|
228
|
+
get container() {
|
|
229
|
+
return getMockContainer(this.containerName);
|
|
230
|
+
}
|
|
231
|
+
constructor(connectionString, containerName, blobName) {
|
|
232
|
+
this.connectionString = connectionString;
|
|
233
|
+
this.containerName = containerName;
|
|
234
|
+
this.name = blobName;
|
|
235
|
+
this.url = getBlobUrl(this.containerName, this.name);
|
|
236
|
+
}
|
|
237
|
+
abortCopyFromURL() {
|
|
238
|
+
throw new NotImplementedError(this.abortCopyFromURL.name);
|
|
239
|
+
}
|
|
240
|
+
beginCopyFromURL(copySource) {
|
|
241
|
+
const sourceParts = getBlobUrlParts(copySource);
|
|
242
|
+
if (!sourceParts) throw new MockRestError("Invalid copy source URL format", 400);
|
|
243
|
+
const { blobName: sourceBlobName, containerName: sourceContainerName } = sourceParts;
|
|
244
|
+
const sourceContainer = MockContainerDatabase.get(sourceContainerName);
|
|
245
|
+
if (!sourceContainer) throw new MockRestError("Source container not found", 404);
|
|
246
|
+
const sourceData = sourceContainer.get(sourceBlobName);
|
|
247
|
+
if (!sourceData) throw new MockRestError("Source blob not found", 404);
|
|
248
|
+
storeMockBlobWrite(this.containerName, this.name, this.container.has(this.name));
|
|
249
|
+
this.container.set(this.name, Buffer.from(sourceData));
|
|
250
|
+
const response = { _response: createMockResponse(202, `${this.url}?comp=copy`) };
|
|
251
|
+
return Promise.resolve({
|
|
252
|
+
cancelOperation: () => Promise.resolve(),
|
|
253
|
+
getOperationState: () => ({
|
|
254
|
+
isCompleted: true,
|
|
255
|
+
result: response
|
|
256
|
+
}),
|
|
257
|
+
getResult: () => response,
|
|
258
|
+
isDone: () => true,
|
|
259
|
+
isStopped: () => false,
|
|
260
|
+
onProgress: () => noop,
|
|
261
|
+
poll: () => Promise.resolve(),
|
|
262
|
+
pollUntilDone: () => Promise.resolve(response),
|
|
263
|
+
stopPolling: noop
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
createSnapshot() {
|
|
267
|
+
throw new NotImplementedError(this.createSnapshot.name);
|
|
268
|
+
}
|
|
269
|
+
delete() {
|
|
270
|
+
if (!deleteMockBlob(this.containerName, this.name)) throw new MockRestError(BLOB_NOT_FOUND_MESSAGE, 404);
|
|
271
|
+
return Promise.resolve({ _response: createMockResponse(200) });
|
|
272
|
+
}
|
|
273
|
+
deleteIfExists() {
|
|
274
|
+
const succeeded = deleteMockBlob(this.containerName, this.name);
|
|
275
|
+
return Promise.resolve({
|
|
276
|
+
_response: createMockResponse(succeeded ? 200 : 404),
|
|
277
|
+
succeeded
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
deleteImmutabilityPolicy() {
|
|
281
|
+
throw new NotImplementedError(this.deleteImmutabilityPolicy.name);
|
|
282
|
+
}
|
|
283
|
+
download() {
|
|
284
|
+
const buffer = this.container.get(this.name);
|
|
285
|
+
if (!buffer) throw new MockRestError(BLOB_NOT_FOUND_MESSAGE, 404);
|
|
286
|
+
return Promise.resolve({
|
|
287
|
+
_response: createMockResponse(200),
|
|
288
|
+
etag: readMockBlobDates(this.containerName, this.name).etag,
|
|
289
|
+
readableStreamBody: Readable.from(buffer)
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
downloadToBuffer() {
|
|
293
|
+
const data = this.container.get(this.name);
|
|
294
|
+
if (!data) throw new MockRestError(BLOB_NOT_FOUND_MESSAGE, 404);
|
|
295
|
+
return Promise.resolve(Buffer.from(data));
|
|
296
|
+
}
|
|
297
|
+
downloadToFile() {
|
|
298
|
+
throw new NotImplementedError(this.downloadToFile.name);
|
|
299
|
+
}
|
|
300
|
+
exists() {
|
|
301
|
+
return Promise.resolve(this.container.has(this.name));
|
|
302
|
+
}
|
|
303
|
+
generateSasStringToSign() {
|
|
304
|
+
throw new NotImplementedError(this.generateSasStringToSign.name);
|
|
305
|
+
}
|
|
306
|
+
generateSasUrl(options) {
|
|
307
|
+
return Promise.resolve(getMockSasUrl(this.url, options.permissions, "b"));
|
|
308
|
+
}
|
|
309
|
+
generateUserDelegationSasStringToSign() {
|
|
310
|
+
throw new NotImplementedError(this.generateUserDelegationSasStringToSign.name);
|
|
311
|
+
}
|
|
312
|
+
generateUserDelegationSasUrl() {
|
|
313
|
+
throw new NotImplementedError(this.generateUserDelegationSasUrl.name);
|
|
314
|
+
}
|
|
315
|
+
getAccountInfo() {
|
|
316
|
+
throw new NotImplementedError(this.getAccountInfo.name);
|
|
317
|
+
}
|
|
318
|
+
getAppendBlobClient() {
|
|
319
|
+
throw new NotImplementedError(this.getAppendBlobClient.name);
|
|
320
|
+
}
|
|
321
|
+
getBlobLeaseClient() {
|
|
322
|
+
throw new NotImplementedError(this.getBlobLeaseClient.name);
|
|
323
|
+
}
|
|
324
|
+
getBlockBlobClient() {
|
|
325
|
+
throw new NotImplementedError(this.getBlockBlobClient.name);
|
|
326
|
+
}
|
|
327
|
+
getPageBlobClient() {
|
|
328
|
+
throw new NotImplementedError(this.getPageBlobClient.name);
|
|
329
|
+
}
|
|
330
|
+
getProperties() {
|
|
331
|
+
const blob = this.container.get(this.name);
|
|
332
|
+
if (!blob) throw new MockRestError(BLOB_NOT_FOUND_MESSAGE, 404);
|
|
333
|
+
return Promise.resolve({
|
|
334
|
+
_response: createMockResponse(200),
|
|
335
|
+
contentLength: blob.byteLength,
|
|
336
|
+
...readMockBlobDates(this.containerName, this.name)
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
getTags() {
|
|
340
|
+
throw new NotImplementedError(this.getTags.name);
|
|
341
|
+
}
|
|
342
|
+
setAccessTier() {
|
|
343
|
+
throw new NotImplementedError(this.setAccessTier.name);
|
|
344
|
+
}
|
|
345
|
+
setHTTPHeaders() {
|
|
346
|
+
throw new NotImplementedError(this.setHTTPHeaders.name);
|
|
347
|
+
}
|
|
348
|
+
setImmutabilityPolicy() {
|
|
349
|
+
throw new NotImplementedError(this.setImmutabilityPolicy.name);
|
|
350
|
+
}
|
|
351
|
+
setLegalHold() {
|
|
352
|
+
throw new NotImplementedError(this.setLegalHold.name);
|
|
353
|
+
}
|
|
354
|
+
setMetadata(metadata) {
|
|
355
|
+
if (!this.container.has(this.name)) throw new MockRestError(BLOB_NOT_FOUND_MESSAGE, 404);
|
|
356
|
+
MockContainerBlobMetadataDatabase.set(getMockBlobKey(this.containerName, this.name), metadata ?? {});
|
|
357
|
+
storeMockBlobWrite(this.containerName, this.name, true);
|
|
358
|
+
return Promise.resolve({
|
|
359
|
+
_response: createMockResponse(200),
|
|
360
|
+
...readMockBlobDates(this.containerName, this.name)
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
setTags() {
|
|
364
|
+
throw new NotImplementedError(this.setTags.name);
|
|
365
|
+
}
|
|
366
|
+
syncCopyFromURL() {
|
|
367
|
+
throw new NotImplementedError(this.syncCopyFromURL.name);
|
|
368
|
+
}
|
|
369
|
+
undelete() {
|
|
370
|
+
throw new NotImplementedError(this.undelete.name);
|
|
371
|
+
}
|
|
372
|
+
withSnapshot() {
|
|
373
|
+
throw new NotImplementedError(this.withSnapshot.name);
|
|
374
|
+
}
|
|
375
|
+
withVersion() {
|
|
376
|
+
throw new NotImplementedError(this.withVersion.name);
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
// src/services/container/checkIsReadableStream.ts
|
|
380
|
+
const checkIsReadableStream = (value) => value instanceof Readable;
|
|
381
|
+
// src/services/container/bodyToBuffer.ts
|
|
382
|
+
const bodyToBuffer = async (body) => {
|
|
383
|
+
if (body === null) return Buffer.alloc(0);
|
|
384
|
+
else if (typeof body === "string") return Buffer.from(body);
|
|
385
|
+
else if (body instanceof ArrayBuffer) return Buffer.from(body);
|
|
386
|
+
else if (ArrayBuffer.isView(body)) return Buffer.from(body.buffer, body.byteOffset, body.byteLength);
|
|
387
|
+
else if (typeof body === "function") {
|
|
388
|
+
const streamOrBlob = body();
|
|
389
|
+
return bodyToBuffer(streamOrBlob);
|
|
390
|
+
} else if (body instanceof Blob) {
|
|
391
|
+
const arrayBuffer = await body.arrayBuffer();
|
|
392
|
+
return Buffer.from(arrayBuffer);
|
|
393
|
+
} else if (checkIsReadableStream(body)) return Buffer.from(await streamToText(body));
|
|
394
|
+
else if (body instanceof ReadableStream) {
|
|
395
|
+
const reader = body.getReader();
|
|
396
|
+
const chunks = [];
|
|
397
|
+
while (true) {
|
|
398
|
+
const { done, value } = await reader.read();
|
|
399
|
+
if (done) break;
|
|
400
|
+
else chunks.push(value);
|
|
401
|
+
}
|
|
402
|
+
return Buffer.concat(chunks);
|
|
403
|
+
} else if (body instanceof FormData) throw new NotImplementedError(`${bodyToBuffer.name}(FormData)`);
|
|
404
|
+
else {
|
|
405
|
+
exhaustiveGuard(body);
|
|
406
|
+
return Buffer.alloc(0);
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
// src/models/container/MockBlockBlobClient.ts
|
|
410
|
+
var MockBlockBlobClient = class extends MockBlobClient {
|
|
411
|
+
commitBlockList() {
|
|
412
|
+
throw new NotImplementedError(this.commitBlockList.name);
|
|
413
|
+
}
|
|
414
|
+
getBlockList() {
|
|
415
|
+
throw new NotImplementedError(this.getBlockList.name);
|
|
416
|
+
}
|
|
417
|
+
query() {
|
|
418
|
+
throw new NotImplementedError(this.query.name);
|
|
419
|
+
}
|
|
420
|
+
stageBlock() {
|
|
421
|
+
throw new NotImplementedError(this.stageBlock.name);
|
|
422
|
+
}
|
|
423
|
+
stageBlockFromURL() {
|
|
424
|
+
throw new NotImplementedError(this.stageBlockFromURL.name);
|
|
425
|
+
}
|
|
426
|
+
syncUploadFromURL() {
|
|
427
|
+
throw new NotImplementedError(this.syncUploadFromURL.name);
|
|
428
|
+
}
|
|
429
|
+
async upload(body, _contentLength, options) {
|
|
430
|
+
const buffer = await bodyToBuffer(body);
|
|
431
|
+
if (options?.conditions?.ifNoneMatch === "*" && this.container.has(this.name)) throw new MockRestError("The specified blob already exists.", 409);
|
|
432
|
+
if (options?.conditions?.ifMatch !== void 0 && options.conditions.ifMatch !== (this.container.has(this.name) ? readMockBlobDates(this.containerName, this.name).etag : void 0)) throw new MockRestError("The condition specified using HTTP conditional header(s) is not met.", 412);
|
|
433
|
+
storeMockBlobWrite(this.containerName, this.name, this.container.has(this.name));
|
|
434
|
+
storeMockBlobMetadata(this.containerName, this.name, options?.metadata);
|
|
435
|
+
this.container.set(this.name, buffer);
|
|
436
|
+
const { etag } = readMockBlobDates(this.containerName, this.name);
|
|
437
|
+
return {
|
|
438
|
+
_response: createMockResponse(201),
|
|
439
|
+
etag
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
uploadBrowserData() {
|
|
443
|
+
throw new NotImplementedError(this.uploadBrowserData.name);
|
|
444
|
+
}
|
|
445
|
+
uploadData() {
|
|
446
|
+
throw new NotImplementedError(this.uploadData.name);
|
|
447
|
+
}
|
|
448
|
+
uploadFile() {
|
|
449
|
+
throw new NotImplementedError(this.uploadFile.name);
|
|
450
|
+
}
|
|
451
|
+
uploadStream() {
|
|
452
|
+
throw new NotImplementedError(this.uploadStream.name);
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
// src/services/container/getBlobItemXml.ts
|
|
456
|
+
const getBlobItemXml = ({ name, properties }) => html`<Blob>
|
|
457
|
+
<Name>${name}</Name>
|
|
458
|
+
<Properties>
|
|
459
|
+
<Last-Modified>${properties.lastModified.toUTCString()}</Last-Modified>
|
|
460
|
+
<Etag>${properties.etag}</Etag>
|
|
461
|
+
<Content-Length>${properties.contentLength}</Content-Length>
|
|
462
|
+
<Content-Type>${properties.contentType}</Content-Type>
|
|
463
|
+
<BlobType>BlockBlob</BlobType>
|
|
464
|
+
<LeaseStatus>unlocked</LeaseStatus>
|
|
465
|
+
<LeaseState>available</LeaseState>
|
|
466
|
+
</Properties>
|
|
467
|
+
</Blob>`;
|
|
468
|
+
// src/services/container/getBlobPrefixXml.ts
|
|
469
|
+
const getBlobPrefixXml = (name) => html`<BlobPrefix><Name>${name}</Name></BlobPrefix>`;
|
|
470
|
+
// src/services/container/getListBlobsXml.ts
|
|
471
|
+
/**
|
|
472
|
+
* Generates a standard Azure Storage list blobs XML response body.
|
|
473
|
+
* @param containerName The container name.
|
|
474
|
+
* @param blobsXml The blobs xml data.
|
|
475
|
+
* @returns A formatted XML string.
|
|
476
|
+
*/
|
|
477
|
+
const getListBlobsXml = (containerName, blobsXml) => html`<?xml version="1.0" encoding="utf8"?>
|
|
478
|
+
<EnumerationResults ServiceEndpoint="" ContainerName="${containerName}">
|
|
479
|
+
<Blobs>${blobsXml}</Blobs>
|
|
480
|
+
<NextMarker />
|
|
481
|
+
</EnumerationResults>`;
|
|
482
|
+
// src/services/container/getListBlobsSegmentResponse.ts
|
|
483
|
+
const getListBlobsSegmentResponse = (containerName, prefix, segment, blobsXml) => {
|
|
484
|
+
const segmentResponse = {
|
|
485
|
+
containerName,
|
|
486
|
+
marker: "",
|
|
487
|
+
prefix,
|
|
488
|
+
segment,
|
|
489
|
+
serviceEndpoint: ""
|
|
490
|
+
};
|
|
491
|
+
return {
|
|
492
|
+
_response: {
|
|
493
|
+
...createMockResponse(200),
|
|
494
|
+
bodyAsText: getListBlobsXml(containerName, blobsXml),
|
|
495
|
+
parsedBody: segmentResponse
|
|
496
|
+
},
|
|
497
|
+
...segmentResponse
|
|
498
|
+
};
|
|
499
|
+
};
|
|
500
|
+
// src/services/container/readMockBlobMetadata.ts
|
|
501
|
+
const readMockBlobMetadata = (containerName, blobName) => MockContainerBlobMetadataDatabase.get(getMockBlobKey(containerName, blobName));
|
|
502
|
+
// src/models/container/MockContainerClient.ts
|
|
503
|
+
/**
|
|
504
|
+
* An in-memory mock of the Azure ContainerClient — no emulator and no network.
|
|
505
|
+
*
|
|
506
|
+
* @example
|
|
507
|
+
* const mockContainerClient = new MockContainerClient("", "hello world");
|
|
508
|
+
* const blockBlobClient = mockContainerClient.getBlockBlobClient("hello world.txt");
|
|
509
|
+
* await blockBlobClient.upload("hello world", 11);
|
|
510
|
+
* const content = await blockBlobClient.downloadToBuffer();
|
|
511
|
+
*/
|
|
512
|
+
var MockContainerClient = class {
|
|
513
|
+
connectionString;
|
|
514
|
+
containerName;
|
|
515
|
+
credential = new AnonymousCredential();
|
|
516
|
+
url;
|
|
517
|
+
get container() {
|
|
518
|
+
return getMockContainer(this.containerName);
|
|
519
|
+
}
|
|
520
|
+
constructor(connectionString, containerName) {
|
|
521
|
+
this.connectionString = connectionString;
|
|
522
|
+
this.containerName = containerName;
|
|
523
|
+
this.url = `${MOCK_BLOB_BASE_URL}/${this.containerName}`;
|
|
524
|
+
}
|
|
525
|
+
create() {
|
|
526
|
+
throw new NotImplementedError(this.create.name);
|
|
527
|
+
}
|
|
528
|
+
createIfNotExists() {
|
|
529
|
+
throw new NotImplementedError(this.createIfNotExists.name);
|
|
530
|
+
}
|
|
531
|
+
delete() {
|
|
532
|
+
throw new NotImplementedError(this.delete.name);
|
|
533
|
+
}
|
|
534
|
+
deleteBlob(blobName) {
|
|
535
|
+
if (!deleteMockBlob(this.containerName, blobName)) throw new MockRestError(BLOB_NOT_FOUND_MESSAGE, 404);
|
|
536
|
+
return Promise.resolve({ _response: createMockResponse(200, getBlobUrl(this.containerName, blobName)) });
|
|
537
|
+
}
|
|
538
|
+
deleteIfExists() {
|
|
539
|
+
throw new NotImplementedError(this.deleteIfExists.name);
|
|
540
|
+
}
|
|
541
|
+
exists() {
|
|
542
|
+
throw new NotImplementedError(this.exists.name);
|
|
543
|
+
}
|
|
544
|
+
findBlobsByTags() {
|
|
545
|
+
throw new NotImplementedError(this.findBlobsByTags.name);
|
|
546
|
+
}
|
|
547
|
+
generateSasStringToSign() {
|
|
548
|
+
throw new NotImplementedError(this.generateSasStringToSign.name);
|
|
549
|
+
}
|
|
550
|
+
generateSasUrl(options) {
|
|
551
|
+
return Promise.resolve(getMockSasUrl(this.url, options.permissions, "c"));
|
|
552
|
+
}
|
|
553
|
+
generateUserDelegationSasStringToSign() {
|
|
554
|
+
throw new NotImplementedError(this.generateUserDelegationSasStringToSign.name);
|
|
555
|
+
}
|
|
556
|
+
generateUserDelegationSasUrl() {
|
|
557
|
+
throw new NotImplementedError(this.generateUserDelegationSasUrl.name);
|
|
558
|
+
}
|
|
559
|
+
getAccessPolicy() {
|
|
560
|
+
throw new NotImplementedError(this.getAccessPolicy.name);
|
|
561
|
+
}
|
|
562
|
+
getAccountInfo() {
|
|
563
|
+
throw new NotImplementedError(this.getAccountInfo.name);
|
|
564
|
+
}
|
|
565
|
+
getAppendBlobClient() {
|
|
566
|
+
throw new NotImplementedError(this.getAppendBlobClient.name);
|
|
567
|
+
}
|
|
568
|
+
getBlobBatchClient() {
|
|
569
|
+
return new MockBlobBatchClient(this.url);
|
|
570
|
+
}
|
|
571
|
+
getBlobClient(blobName) {
|
|
572
|
+
return new MockBlockBlobClient(this.connectionString, this.containerName, blobName);
|
|
573
|
+
}
|
|
574
|
+
getBlobLeaseClient() {
|
|
575
|
+
throw new NotImplementedError(this.getBlobLeaseClient.name);
|
|
576
|
+
}
|
|
577
|
+
getBlockBlobClient(blobName) {
|
|
578
|
+
return new MockBlockBlobClient(this.connectionString, this.containerName, blobName);
|
|
579
|
+
}
|
|
580
|
+
getPageBlobClient() {
|
|
581
|
+
throw new NotImplementedError(this.getPageBlobClient.name);
|
|
582
|
+
}
|
|
583
|
+
getProperties() {
|
|
584
|
+
throw new NotImplementedError(this.getProperties.name);
|
|
585
|
+
}
|
|
586
|
+
listBlobsByHierarchy(delimiter, options) {
|
|
587
|
+
const blobHierarchyItemIterator = this.#getBlobHierarchyItemIterator(delimiter, options);
|
|
588
|
+
return {
|
|
589
|
+
byPage: () => async function* () {
|
|
590
|
+
const allBlobItems = [];
|
|
591
|
+
const allBlobItemXml = [];
|
|
592
|
+
const allBlobPrefixes = [];
|
|
593
|
+
const allBlobPrefixXml = [];
|
|
594
|
+
for await (const blobHierarchyItem of blobHierarchyItemIterator) if (blobHierarchyItem.kind === "blob") {
|
|
595
|
+
allBlobItems.push(blobHierarchyItem);
|
|
596
|
+
allBlobItemXml.push(getBlobItemXml(blobHierarchyItem));
|
|
597
|
+
} else {
|
|
598
|
+
allBlobPrefixes.push({ name: blobHierarchyItem.name });
|
|
599
|
+
allBlobPrefixXml.push(getBlobPrefixXml(blobHierarchyItem.name));
|
|
600
|
+
}
|
|
601
|
+
if (allBlobItems.length > 0 || allBlobPrefixes.length > 0) yield await Promise.resolve(getListBlobsSegmentResponse(this.containerName, options?.prefix ?? "", {
|
|
602
|
+
blobItems: allBlobItems,
|
|
603
|
+
blobPrefixes: allBlobPrefixes
|
|
604
|
+
}, `${allBlobItemXml.join("")}${allBlobPrefixXml.join("")}`));
|
|
605
|
+
}.bind(this)(),
|
|
606
|
+
next: blobHierarchyItemIterator.next.bind(blobHierarchyItemIterator),
|
|
607
|
+
[Symbol.asyncIterator]() {
|
|
608
|
+
return this;
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
listBlobsFlat(options) {
|
|
613
|
+
const blobItemIterator = this.#getBlobItemIterator(options);
|
|
614
|
+
return {
|
|
615
|
+
byPage: () => async function* () {
|
|
616
|
+
const allBlobItems = [];
|
|
617
|
+
const allBlobItemXml = [];
|
|
618
|
+
for await (const blobItem of blobItemIterator) {
|
|
619
|
+
allBlobItems.push(blobItem);
|
|
620
|
+
allBlobItemXml.push(getBlobItemXml(blobItem));
|
|
621
|
+
}
|
|
622
|
+
if (allBlobItems.length > 0) yield await Promise.resolve(getListBlobsSegmentResponse(this.containerName, options?.prefix ?? "", { blobItems: allBlobItems }, allBlobItemXml.join("")));
|
|
623
|
+
}.bind(this)(),
|
|
624
|
+
next: blobItemIterator.next.bind(blobItemIterator),
|
|
625
|
+
[Symbol.asyncIterator]() {
|
|
626
|
+
return this;
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
setAccessPolicy() {
|
|
631
|
+
throw new NotImplementedError(this.setAccessPolicy.name);
|
|
632
|
+
}
|
|
633
|
+
setMetadata() {
|
|
634
|
+
throw new NotImplementedError(this.setMetadata.name);
|
|
635
|
+
}
|
|
636
|
+
async uploadBlockBlob(blobName, body, contentLength) {
|
|
637
|
+
const blockBlobClient = this.getBlockBlobClient(blobName);
|
|
638
|
+
return {
|
|
639
|
+
blockBlobClient,
|
|
640
|
+
response: await blockBlobClient.upload(body, contentLength)
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
async *#getBlobHierarchyItemIterator(delimiter, options) {
|
|
644
|
+
const prefix = options?.prefix ?? "";
|
|
645
|
+
const uniqueSubprefixes = /* @__PURE__ */ new Set();
|
|
646
|
+
const blobsInCurrentLevel = [];
|
|
647
|
+
for (const [name, buffer] of this.container.entries()) {
|
|
648
|
+
if (!name.startsWith(prefix)) continue;
|
|
649
|
+
const nameAfterPrefix = name.slice(prefix.length);
|
|
650
|
+
const delimiterIndex = nameAfterPrefix.indexOf(delimiter);
|
|
651
|
+
if (delimiterIndex === -1) blobsInCurrentLevel.push(this.#getBlobItem(name, buffer, options?.includeMetadata));
|
|
652
|
+
else {
|
|
653
|
+
const subprefix = `${prefix}${nameAfterPrefix.slice(0, delimiterIndex + delimiter.length)}`;
|
|
654
|
+
uniqueSubprefixes.add(subprefix);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
for (const prefixName of [...uniqueSubprefixes].toSorted()) yield await Promise.resolve({
|
|
658
|
+
kind: "prefix",
|
|
659
|
+
name: prefixName
|
|
660
|
+
});
|
|
661
|
+
for (const blobItem of blobsInCurrentLevel) yield await Promise.resolve({
|
|
662
|
+
kind: "blob",
|
|
663
|
+
...blobItem
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
#getBlobItem(name, buffer, isMetadataIncluded) {
|
|
667
|
+
const { createdOn, etag, lastModified } = readMockBlobDates(this.containerName, name);
|
|
668
|
+
return {
|
|
669
|
+
deleted: false,
|
|
670
|
+
...isMetadataIncluded && { metadata: readMockBlobMetadata(this.containerName, name) },
|
|
671
|
+
name,
|
|
672
|
+
properties: {
|
|
673
|
+
blobType: "BlockBlob",
|
|
674
|
+
contentLength: buffer.length,
|
|
675
|
+
contentType: "application/octet-stream",
|
|
676
|
+
createdOn,
|
|
677
|
+
etag,
|
|
678
|
+
lastModified,
|
|
679
|
+
leaseState: "available",
|
|
680
|
+
leaseStatus: "unlocked"
|
|
681
|
+
},
|
|
682
|
+
snapshot: ""
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
async *#getBlobItemIterator(options) {
|
|
686
|
+
const prefix = options?.prefix ?? "";
|
|
687
|
+
for (const [name, buffer] of this.container.entries()) {
|
|
688
|
+
if (!name.startsWith(prefix)) continue;
|
|
689
|
+
yield await Promise.resolve(this.#getBlobItem(name, buffer, options?.includeMetadata));
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
// src/store/MockEventGridDatabase.ts
|
|
694
|
+
const MockEventGridDatabase = /* @__PURE__ */ new Map();
|
|
695
|
+
// src/models/eventGrid/MockEventGridPublisherClient.ts
|
|
696
|
+
/**
|
|
697
|
+
* An in-memory mock of the Azure EventGridPublisherClient — no emulator and no network.
|
|
698
|
+
* `client` is excluded because it is a private member of the real class and cannot be satisfied structurally.
|
|
699
|
+
*/
|
|
700
|
+
var MockEventGridPublisherClient = class {
|
|
701
|
+
endpointUrl;
|
|
702
|
+
inputSchema;
|
|
703
|
+
topicType;
|
|
704
|
+
constructor(endpoint, topicType) {
|
|
705
|
+
this.endpointUrl = endpoint;
|
|
706
|
+
this.topicType = topicType;
|
|
707
|
+
this.inputSchema = topicType;
|
|
708
|
+
}
|
|
709
|
+
send(newEvents) {
|
|
710
|
+
getOrCreate(MockEventGridDatabase, this.endpointUrl, () => []).push(...newEvents);
|
|
711
|
+
return Promise.resolve();
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
// src/services/queue/getMockQueueMessageItem.ts
|
|
715
|
+
const getMockQueueMessageItem = (messageText) => {
|
|
716
|
+
const insertedOn = /* @__PURE__ */ new Date();
|
|
717
|
+
return {
|
|
718
|
+
expiresOn: new Date(insertedOn.getTime() + AZURE_MAX_QUEUE_VISIBILITY_TIMEOUT_MS),
|
|
719
|
+
insertedOn,
|
|
720
|
+
messageId: crypto.randomUUID(),
|
|
721
|
+
messageText
|
|
722
|
+
};
|
|
723
|
+
};
|
|
724
|
+
// src/store/MockQueueDatabase.ts
|
|
725
|
+
const MockQueueDatabase = /* @__PURE__ */ new Map();
|
|
726
|
+
// src/models/queue/MockQueueClient.ts
|
|
727
|
+
/**
|
|
728
|
+
* An in-memory mock of the Azure QueueClient — no emulator and no network.
|
|
729
|
+
*
|
|
730
|
+
* @example
|
|
731
|
+
* const mockQueueClient = new MockQueueClient("", "hello world");
|
|
732
|
+
* await mockQueueClient.createIfNotExists();
|
|
733
|
+
* await mockQueueClient.sendMessage("hello world");
|
|
734
|
+
* const messages = await mockQueueClient.peekMessages();
|
|
735
|
+
*/
|
|
736
|
+
var MockQueueClient = class {
|
|
737
|
+
connectionString;
|
|
738
|
+
name;
|
|
739
|
+
url;
|
|
740
|
+
get queue() {
|
|
741
|
+
return getOrCreate(MockQueueDatabase, this.name, () => []);
|
|
742
|
+
}
|
|
743
|
+
constructor(connectionString, queueName) {
|
|
744
|
+
this.connectionString = connectionString;
|
|
745
|
+
this.name = queueName;
|
|
746
|
+
this.url = `${MOCK_QUEUE_BASE_URL}/${this.name}`;
|
|
747
|
+
}
|
|
748
|
+
clearMessages() {
|
|
749
|
+
throw new NotImplementedError(this.clearMessages.name);
|
|
750
|
+
}
|
|
751
|
+
create(_options) {
|
|
752
|
+
throw new NotImplementedError(this.create.name);
|
|
753
|
+
}
|
|
754
|
+
createIfNotExists() {
|
|
755
|
+
throw new NotImplementedError(this.createIfNotExists.name);
|
|
756
|
+
}
|
|
757
|
+
delete() {
|
|
758
|
+
throw new NotImplementedError(this.delete.name);
|
|
759
|
+
}
|
|
760
|
+
deleteIfExists() {
|
|
761
|
+
throw new NotImplementedError(this.deleteIfExists.name);
|
|
762
|
+
}
|
|
763
|
+
deleteMessage() {
|
|
764
|
+
throw new NotImplementedError(this.deleteMessage.name);
|
|
765
|
+
}
|
|
766
|
+
exists() {
|
|
767
|
+
throw new NotImplementedError(this.exists.name);
|
|
768
|
+
}
|
|
769
|
+
generateSasStringToSign() {
|
|
770
|
+
throw new NotImplementedError(this.generateSasStringToSign.name);
|
|
771
|
+
}
|
|
772
|
+
generateSasUrl(options) {
|
|
773
|
+
return getMockSasUrl(this.url, options.permissions);
|
|
774
|
+
}
|
|
775
|
+
generateUserDelegationSasUrl() {
|
|
776
|
+
throw new NotImplementedError(this.generateUserDelegationSasUrl.name);
|
|
777
|
+
}
|
|
778
|
+
generateUserDelegationStringToSign() {
|
|
779
|
+
throw new NotImplementedError(this.generateUserDelegationStringToSign.name);
|
|
780
|
+
}
|
|
781
|
+
getAccessPolicy() {
|
|
782
|
+
throw new NotImplementedError(this.getAccessPolicy.name);
|
|
783
|
+
}
|
|
784
|
+
getProperties() {
|
|
785
|
+
throw new NotImplementedError(this.getProperties.name);
|
|
786
|
+
}
|
|
787
|
+
getServiceProperties() {
|
|
788
|
+
throw new NotImplementedError(this.getServiceProperties.name);
|
|
789
|
+
}
|
|
790
|
+
listQueues() {
|
|
791
|
+
throw new NotImplementedError(this.listQueues.name);
|
|
792
|
+
}
|
|
793
|
+
peekMessages(_options) {
|
|
794
|
+
const peekedMessageItems = this.queue.map((text) => ({
|
|
795
|
+
dequeueCount: 0,
|
|
796
|
+
...getMockQueueMessageItem(text)
|
|
797
|
+
}));
|
|
798
|
+
return Promise.resolve({
|
|
799
|
+
_response: {
|
|
800
|
+
...createMockResponse(200, this.url),
|
|
801
|
+
bodyAsText: "",
|
|
802
|
+
parsedBody: peekedMessageItems
|
|
803
|
+
},
|
|
804
|
+
peekedMessageItems
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
receiveMessages(_options) {
|
|
808
|
+
const queuedTexts = this.queue;
|
|
809
|
+
MockQueueDatabase.set(this.name, []);
|
|
810
|
+
const receivedMessageItems = queuedTexts.map((text) => Object.assign(getMockQueueMessageItem(text), {
|
|
811
|
+
dequeueCount: 1,
|
|
812
|
+
nextVisibleOn: /* @__PURE__ */ new Date(),
|
|
813
|
+
popReceipt: crypto.randomUUID()
|
|
814
|
+
}));
|
|
815
|
+
return Promise.resolve({
|
|
816
|
+
_response: {
|
|
817
|
+
...createMockResponse(200, this.url),
|
|
818
|
+
bodyAsText: "",
|
|
819
|
+
parsedBody: receivedMessageItems
|
|
820
|
+
},
|
|
821
|
+
receivedMessageItems
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
sendMessage(messageText, _options) {
|
|
825
|
+
this.queue.push(messageText);
|
|
826
|
+
const { expiresOn, insertedOn, messageId } = getMockQueueMessageItem(messageText);
|
|
827
|
+
const enqueuedMessage = {
|
|
828
|
+
expiresOn,
|
|
829
|
+
insertedOn,
|
|
830
|
+
messageId,
|
|
831
|
+
nextVisibleOn: insertedOn,
|
|
832
|
+
popReceipt: crypto.randomUUID()
|
|
833
|
+
};
|
|
834
|
+
const enqueuedMessages = [enqueuedMessage];
|
|
835
|
+
return Promise.resolve({
|
|
836
|
+
_response: {
|
|
837
|
+
...createMockResponse(200, this.url),
|
|
838
|
+
bodyAsText: "",
|
|
839
|
+
parsedBody: enqueuedMessages
|
|
840
|
+
},
|
|
841
|
+
...enqueuedMessage
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
setAccessPolicy(_identifiers) {
|
|
845
|
+
throw new NotImplementedError(this.setAccessPolicy.name);
|
|
846
|
+
}
|
|
847
|
+
setMetadata() {
|
|
848
|
+
throw new NotImplementedError(this.setMetadata.name);
|
|
849
|
+
}
|
|
850
|
+
updateMessage() {
|
|
851
|
+
throw new NotImplementedError(this.updateMessage.name);
|
|
852
|
+
}
|
|
853
|
+
};
|
|
854
|
+
// src/services/filter/checkIsNullClause.ts
|
|
855
|
+
const checkIsNullClause = (clause) => {
|
|
856
|
+
const tableNullClause = getTableNullClause(clause.key);
|
|
857
|
+
return checkIsDeepEqual(clause, tableNullClause);
|
|
858
|
+
};
|
|
859
|
+
// src/services/filter/compare.ts
|
|
860
|
+
const compare = (operator, leftHandSide, rightHandSide) => {
|
|
861
|
+
if (rightHandSide === null || rightHandSide === void 0) {
|
|
862
|
+
if (operator !== BinaryOperator.Eq) throw new InvalidOperationError(Operation.Read, compare.name, JSON.stringify({
|
|
863
|
+
leftHandSide,
|
|
864
|
+
operator,
|
|
865
|
+
rightHandSide
|
|
866
|
+
}));
|
|
867
|
+
return leftHandSide === null || leftHandSide === void 0;
|
|
868
|
+
}
|
|
869
|
+
switch (operator) {
|
|
870
|
+
case BinaryOperator.Eq: return leftHandSide === rightHandSide;
|
|
871
|
+
case BinaryOperator.Ge: return leftHandSide >= rightHandSide;
|
|
872
|
+
case BinaryOperator.Gt: return leftHandSide > rightHandSide;
|
|
873
|
+
case BinaryOperator.Le: return leftHandSide <= rightHandSide;
|
|
874
|
+
case BinaryOperator.Lt: return leftHandSide < rightHandSide;
|
|
875
|
+
case BinaryOperator.Ne: return leftHandSide !== rightHandSide;
|
|
876
|
+
default: return exhaustiveGuard(operator);
|
|
877
|
+
}
|
|
878
|
+
};
|
|
879
|
+
// src/services/filter/createFilterPredicate.ts
|
|
880
|
+
const createFilterPredicate = (filter) => {
|
|
881
|
+
const orGroups = filter.replaceAll(String.raw`(`, " ").replaceAll(String.raw`)`, "").split(/\s+and\s+/iu).filter(Boolean).map((group) => group.split(/\s+or\s+/iu).filter(Boolean).map((clauseString) => {
|
|
882
|
+
const clause = deserializeClause(clauseString);
|
|
883
|
+
return {
|
|
884
|
+
clause,
|
|
885
|
+
isNull: checkIsNullClause(clause)
|
|
886
|
+
};
|
|
887
|
+
}));
|
|
888
|
+
return (document) => {
|
|
889
|
+
for (const orGroup of orGroups) {
|
|
890
|
+
let isGroupMatched = false;
|
|
891
|
+
for (const { clause, isNull } of orGroup) {
|
|
892
|
+
const value = takeOne(document, clause.key);
|
|
893
|
+
let isMatched;
|
|
894
|
+
if (isNull) isMatched = compare(BinaryOperator.Eq, value, null);
|
|
895
|
+
else {
|
|
896
|
+
const comparisonResult = compare(clause.operator, value, clause.value);
|
|
897
|
+
isMatched = clause.not ? !comparisonResult : comparisonResult;
|
|
898
|
+
}
|
|
899
|
+
if (isMatched) {
|
|
900
|
+
isGroupMatched = true;
|
|
901
|
+
break;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
if (!isGroupMatched) return false;
|
|
905
|
+
}
|
|
906
|
+
return true;
|
|
907
|
+
};
|
|
908
|
+
};
|
|
909
|
+
// src/store/MockSearchDatabase.ts
|
|
910
|
+
const MockSearchDatabase = /* @__PURE__ */ new Map();
|
|
911
|
+
// src/models/search/MockSearchClient.ts
|
|
912
|
+
const toComparable = (value) => {
|
|
913
|
+
if (value instanceof Date) return value.getTime();
|
|
914
|
+
else if (typeof value === "number" || typeof value === "string") return value;
|
|
915
|
+
else return String(value);
|
|
916
|
+
};
|
|
917
|
+
const compareValues = (firstValue, secondValue) => {
|
|
918
|
+
const firstComparable = toComparable(firstValue);
|
|
919
|
+
const secondComparable = toComparable(secondValue);
|
|
920
|
+
if (typeof firstComparable === "number" && typeof secondComparable === "number") return firstComparable - secondComparable;
|
|
921
|
+
else return String(firstComparable).localeCompare(String(secondComparable));
|
|
922
|
+
};
|
|
923
|
+
const sortDocuments = (documents, orderBy) => {
|
|
924
|
+
const parsedOrderBy = orderBy.map((clause) => {
|
|
925
|
+
const [field = "", direction = "asc"] = clause.split(/\s+/u);
|
|
926
|
+
return {
|
|
927
|
+
direction,
|
|
928
|
+
key: deserializeKey(field)
|
|
929
|
+
};
|
|
930
|
+
});
|
|
931
|
+
return documents.toSorted((firstDocument, secondDocument) => {
|
|
932
|
+
for (const { direction, key } of parsedOrderBy) {
|
|
933
|
+
const comparison = compareValues(firstDocument[key], secondDocument[key]);
|
|
934
|
+
if (comparison !== 0) return direction === "desc" ? -comparison : comparison;
|
|
935
|
+
}
|
|
936
|
+
return 0;
|
|
937
|
+
});
|
|
938
|
+
};
|
|
939
|
+
const getSearchFieldValues = (value, pathSegments) => {
|
|
940
|
+
if (pathSegments.length === 0) return [value];
|
|
941
|
+
else if (Array.isArray(value)) return value.flatMap((item) => getSearchFieldValues(item, pathSegments));
|
|
942
|
+
else if (typeof value === "object" && value !== null) {
|
|
943
|
+
const [field = "", ...remainingPathSegments] = pathSegments;
|
|
944
|
+
return getSearchFieldValues(value[deserializeKey(field)], remainingPathSegments);
|
|
945
|
+
} else return [];
|
|
946
|
+
};
|
|
947
|
+
const searchDocuments = (documents, searchText, searchFields) => {
|
|
948
|
+
if (!searchText || searchText === "*") return documents;
|
|
949
|
+
const normalizedSearchText = searchText.toLocaleLowerCase();
|
|
950
|
+
const searchFieldPaths = searchFields?.map((searchField) => searchField.split("/"));
|
|
951
|
+
return documents.filter((document) => (searchFieldPaths ?? Object.keys(document).map((field) => field.split("/"))).some((pathSegments) => getSearchFieldValues(document, pathSegments).some((value) => typeof value === "string" && value.toLocaleLowerCase().includes(normalizedSearchText))));
|
|
952
|
+
};
|
|
953
|
+
/**
|
|
954
|
+
* An in-memory mock of the Azure SearchClient — no emulator and no network. It applies the same OData filtering
|
|
955
|
+
* as the other mock clients, so a query behaves the same here as it does against the service.
|
|
956
|
+
*
|
|
957
|
+
* @example
|
|
958
|
+
* const mockSearchClient = new MockSearchClient(SearchIndex.Messages);
|
|
959
|
+
* await mockSearchClient.uploadDocuments([{ ... }]);
|
|
960
|
+
* const { count, results } = await mockSearchClient.search("*", { filter, includeTotalCount: true });
|
|
961
|
+
*/
|
|
962
|
+
var MockSearchClient = class {
|
|
963
|
+
apiVersion = "";
|
|
964
|
+
endpoint = MOCK_SEARCH_BASE_URL;
|
|
965
|
+
indexName;
|
|
966
|
+
serviceVersion = "";
|
|
967
|
+
get documents() {
|
|
968
|
+
return getOrCreate(MockSearchDatabase, this.indexName, () => []);
|
|
969
|
+
}
|
|
970
|
+
constructor(indexName) {
|
|
971
|
+
this.indexName = indexName;
|
|
972
|
+
}
|
|
973
|
+
autocomplete() {
|
|
974
|
+
throw new NotImplementedError(this.autocomplete.name);
|
|
975
|
+
}
|
|
976
|
+
deleteDocuments() {
|
|
977
|
+
throw new NotImplementedError(this.deleteDocuments.name);
|
|
978
|
+
}
|
|
979
|
+
getDocument(_key, _options) {
|
|
980
|
+
throw new NotImplementedError(this.getDocument.name);
|
|
981
|
+
}
|
|
982
|
+
getDocumentsCount(_options) {
|
|
983
|
+
return Promise.resolve(this.documents.length);
|
|
984
|
+
}
|
|
985
|
+
indexDocuments(_batch, _options) {
|
|
986
|
+
throw new NotImplementedError(this.indexDocuments.name);
|
|
987
|
+
}
|
|
988
|
+
mergeDocuments(_documents, _options) {
|
|
989
|
+
throw new NotImplementedError(this.mergeDocuments.name);
|
|
990
|
+
}
|
|
991
|
+
mergeOrUploadDocuments(_documents, _options) {
|
|
992
|
+
throw new NotImplementedError(this.mergeOrUploadDocuments.name);
|
|
993
|
+
}
|
|
994
|
+
search(searchText, options) {
|
|
995
|
+
const { filter, includeTotalCount, orderBy, searchFields, skip = 0, top } = options ?? {};
|
|
996
|
+
let documents = searchDocuments(this.documents, searchText, searchFields);
|
|
997
|
+
if (filter) {
|
|
998
|
+
const predicate = createFilterPredicate(filter);
|
|
999
|
+
documents = documents.filter((document) => predicate(document));
|
|
1000
|
+
}
|
|
1001
|
+
if (orderBy) documents = sortDocuments(documents, orderBy);
|
|
1002
|
+
const count = documents.length;
|
|
1003
|
+
const paginatedDocuments = documents.slice(skip, top === void 0 ? void 0 : skip + top);
|
|
1004
|
+
const results = { async *[Symbol.asyncIterator]() {
|
|
1005
|
+
for (const document of paginatedDocuments) yield { document };
|
|
1006
|
+
} };
|
|
1007
|
+
return Promise.resolve({
|
|
1008
|
+
...includeTotalCount ? { count } : {},
|
|
1009
|
+
results
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
suggest(_searchText, _suggesterName, _options) {
|
|
1013
|
+
throw new NotImplementedError(this.suggest.name);
|
|
1014
|
+
}
|
|
1015
|
+
uploadDocuments(documents, _options) {
|
|
1016
|
+
this.documents.push(...documents);
|
|
1017
|
+
return Promise.resolve({ results: [] });
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
// src/store/MockServiceBusDatabase.ts
|
|
1021
|
+
const MockServiceBusDatabase = /* @__PURE__ */ new Map();
|
|
1022
|
+
// src/models/serviceBus/MockServiceBusSender.ts
|
|
1023
|
+
/**
|
|
1024
|
+
* An in-memory mock of the Azure ServiceBusSender — no emulator and no network.
|
|
1025
|
+
*
|
|
1026
|
+
* @example
|
|
1027
|
+
* const mockServiceBusSender = new MockServiceBusSender("hello world");
|
|
1028
|
+
* await mockServiceBusSender.scheduleMessages({ body: "hello world" }, new Date());
|
|
1029
|
+
*/
|
|
1030
|
+
var MockServiceBusSender = class {
|
|
1031
|
+
entityPath;
|
|
1032
|
+
identifier;
|
|
1033
|
+
isClosed = false;
|
|
1034
|
+
get queue() {
|
|
1035
|
+
return getOrCreate(MockServiceBusDatabase, this.entityPath, () => []);
|
|
1036
|
+
}
|
|
1037
|
+
constructor(queueName) {
|
|
1038
|
+
this.entityPath = queueName;
|
|
1039
|
+
this.identifier = queueName;
|
|
1040
|
+
}
|
|
1041
|
+
cancelScheduledMessages() {
|
|
1042
|
+
throw new NotImplementedError(this.cancelScheduledMessages.name);
|
|
1043
|
+
}
|
|
1044
|
+
close() {
|
|
1045
|
+
this.isClosed = true;
|
|
1046
|
+
return Promise.resolve();
|
|
1047
|
+
}
|
|
1048
|
+
createMessageBatch() {
|
|
1049
|
+
throw new NotImplementedError(this.createMessageBatch.name);
|
|
1050
|
+
}
|
|
1051
|
+
scheduleMessages(messages, scheduledEnqueueTimeUtc) {
|
|
1052
|
+
for (const message of Array.isArray(messages) ? messages : [messages]) this.queue.push({
|
|
1053
|
+
...message,
|
|
1054
|
+
scheduledEnqueueTimeUtc
|
|
1055
|
+
});
|
|
1056
|
+
return Promise.resolve([]);
|
|
1057
|
+
}
|
|
1058
|
+
sendMessages(messages) {
|
|
1059
|
+
if (!Array.isArray(messages) && "tryAddMessage" in messages) throw new NotImplementedError(`${this.sendMessages.name}(ServiceBusMessageBatch)`);
|
|
1060
|
+
this.queue.push(...Array.isArray(messages) ? messages : [messages]);
|
|
1061
|
+
return Promise.resolve();
|
|
1062
|
+
}
|
|
1063
|
+
};
|
|
1064
|
+
// src/services/table/compareByCompositeKey.ts
|
|
1065
|
+
const compareByCompositeKey = (firstEntity, secondEntity) => {
|
|
1066
|
+
if (firstEntity.partitionKey !== secondEntity.partitionKey) return firstEntity.partitionKey < secondEntity.partitionKey ? -1 : 1;
|
|
1067
|
+
else if (firstEntity.rowKey === secondEntity.rowKey) return 0;
|
|
1068
|
+
else return firstEntity.rowKey < secondEntity.rowKey ? -1 : 1;
|
|
1069
|
+
};
|
|
1070
|
+
// src/store/MockTableDatabase.ts
|
|
1071
|
+
const MockTableDatabase = /* @__PURE__ */ new Map();
|
|
1072
|
+
// src/models/table/MockTableClient.ts
|
|
1073
|
+
/**
|
|
1074
|
+
* An in-memory mock of the Azure TableClient — no emulator and no network.
|
|
1075
|
+
*
|
|
1076
|
+
* @example
|
|
1077
|
+
* const mockTableClient = new MockTableClient("", "hello world");
|
|
1078
|
+
* await mockTableClient.createEntity({ partitionKey: "partitionKey", rowKey: "rowKey" });
|
|
1079
|
+
* const entity = await mockTableClient.getEntity("partitionKey", "rowKey");
|
|
1080
|
+
*/
|
|
1081
|
+
var MockTableClient = class {
|
|
1082
|
+
tableName;
|
|
1083
|
+
url;
|
|
1084
|
+
get table() {
|
|
1085
|
+
return getOrCreate(MockTableDatabase, this.tableName, () => /* @__PURE__ */ new Map());
|
|
1086
|
+
}
|
|
1087
|
+
constructor(_url, tableName) {
|
|
1088
|
+
this.tableName = tableName;
|
|
1089
|
+
this.url = `${MOCK_TABLE_BASE_URL}/${this.tableName}`;
|
|
1090
|
+
}
|
|
1091
|
+
createEntity(entity) {
|
|
1092
|
+
this.#applyCreate(entity);
|
|
1093
|
+
return Promise.resolve({
|
|
1094
|
+
date: /* @__PURE__ */ new Date(),
|
|
1095
|
+
etag: this.#getEtag()
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
createTable() {
|
|
1099
|
+
throw new NotImplementedError(this.createTable.name);
|
|
1100
|
+
}
|
|
1101
|
+
deleteEntity(partitionKey, rowKey, options) {
|
|
1102
|
+
this.#applyDelete(partitionKey, rowKey, options?.etag);
|
|
1103
|
+
return Promise.resolve({});
|
|
1104
|
+
}
|
|
1105
|
+
deleteTable() {
|
|
1106
|
+
throw new NotImplementedError(this.deleteTable.name);
|
|
1107
|
+
}
|
|
1108
|
+
getAccessPolicy() {
|
|
1109
|
+
throw new NotImplementedError(this.getAccessPolicy.name);
|
|
1110
|
+
}
|
|
1111
|
+
getEntity(partitionKey, rowKey) {
|
|
1112
|
+
const key = this.#getCompositeKey(partitionKey, rowKey);
|
|
1113
|
+
const entity = this.table.get(key);
|
|
1114
|
+
if (!entity) throw new MockRestError("The specified resource does not exist.", 404);
|
|
1115
|
+
return Promise.resolve({
|
|
1116
|
+
...entity,
|
|
1117
|
+
etag: entity.etag ?? this.#getEtag()
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
listEntities(options) {
|
|
1121
|
+
const withMetadata = this.#withMetadata.bind(this);
|
|
1122
|
+
const filter = options?.queryOptions?.filter;
|
|
1123
|
+
const tableEntities = [...this.table.values()];
|
|
1124
|
+
const predicate = filter ? createFilterPredicate(filter) : void 0;
|
|
1125
|
+
const resultTableEntities = (predicate ? tableEntities.filter((entity) => predicate(entity)) : tableEntities).toSorted(compareByCompositeKey);
|
|
1126
|
+
const entityIterator = (async function* (entities) {
|
|
1127
|
+
for (const entity of entities) yield await Promise.resolve(withMetadata(entity));
|
|
1128
|
+
})(resultTableEntities);
|
|
1129
|
+
return {
|
|
1130
|
+
byPage: ({ maxPageSize } = {}) => (async function* (entities) {
|
|
1131
|
+
if (maxPageSize !== void 0 && maxPageSize <= 0) throw new RangeError("maxPageSize must be greater than 0.");
|
|
1132
|
+
else if (maxPageSize !== void 0 && maxPageSize > AZURE_MAX_PAGE_SIZE) throw new MockRestError("One of the request inputs is not valid.", 400);
|
|
1133
|
+
if (entities.length === 0) return;
|
|
1134
|
+
else if (!maxPageSize) {
|
|
1135
|
+
yield await Promise.resolve(entities.map((entity) => withMetadata(entity)));
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
for (const page of chunk(entities, maxPageSize)) yield await Promise.resolve(page.map((entity) => withMetadata(entity)));
|
|
1139
|
+
})(resultTableEntities),
|
|
1140
|
+
next: () => entityIterator.next(),
|
|
1141
|
+
[Symbol.asyncIterator]() {
|
|
1142
|
+
return this;
|
|
1143
|
+
}
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
setAccessPolicy() {
|
|
1147
|
+
throw new NotImplementedError(this.setAccessPolicy.name);
|
|
1148
|
+
}
|
|
1149
|
+
submitTransaction(actions) {
|
|
1150
|
+
let partitionKey;
|
|
1151
|
+
for (const [, entity] of actions) if (partitionKey === void 0) partitionKey = entity.partitionKey;
|
|
1152
|
+
else if (entity.partitionKey !== partitionKey) throw new MockRestError("All transaction actions must target the same partitionKey.", 400);
|
|
1153
|
+
const snapshot = new Map(this.table);
|
|
1154
|
+
getResult(() => {
|
|
1155
|
+
for (const [type, entity, updateMode] of actions) switch (type) {
|
|
1156
|
+
case "create":
|
|
1157
|
+
this.#applyCreate(entity);
|
|
1158
|
+
break;
|
|
1159
|
+
case "delete":
|
|
1160
|
+
this.#applyDelete(entity.partitionKey, entity.rowKey);
|
|
1161
|
+
break;
|
|
1162
|
+
case "update":
|
|
1163
|
+
this.#applyUpdate(entity, updateMode);
|
|
1164
|
+
break;
|
|
1165
|
+
case "upsert":
|
|
1166
|
+
this.#applyUpsert(entity, updateMode);
|
|
1167
|
+
break;
|
|
1168
|
+
default: exhaustiveGuard(type);
|
|
1169
|
+
}
|
|
1170
|
+
}).orTee(() => {
|
|
1171
|
+
this.table.clear();
|
|
1172
|
+
for (const [key, value] of snapshot) this.table.set(key, value);
|
|
1173
|
+
}).match(noop, (error) => {
|
|
1174
|
+
throw error;
|
|
1175
|
+
});
|
|
1176
|
+
return Promise.resolve({
|
|
1177
|
+
getResponseForEntity: () => void 0,
|
|
1178
|
+
status: 202,
|
|
1179
|
+
subResponses: []
|
|
1180
|
+
});
|
|
1181
|
+
}
|
|
1182
|
+
updateEntity(entity, mode = "Merge", options) {
|
|
1183
|
+
this.#applyUpdate(entity, mode, options?.etag);
|
|
1184
|
+
return Promise.resolve({
|
|
1185
|
+
date: /* @__PURE__ */ new Date(),
|
|
1186
|
+
etag: this.#getEtag()
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
upsertEntity(entity, mode = "Merge") {
|
|
1190
|
+
this.#applyUpsert(entity, mode);
|
|
1191
|
+
return Promise.resolve({
|
|
1192
|
+
date: /* @__PURE__ */ new Date(),
|
|
1193
|
+
etag: this.#getEtag()
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
#applyCreate(entity) {
|
|
1197
|
+
const key = this.#getCompositeKey(entity.partitionKey, entity.rowKey);
|
|
1198
|
+
if (this.table.has(key)) throw new MockRestError("The specified entity already exists.", 409);
|
|
1199
|
+
this.table.set(key, {
|
|
1200
|
+
...entity,
|
|
1201
|
+
etag: this.#getEtag()
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
#applyDelete(partitionKey, rowKey, etag) {
|
|
1205
|
+
const key = this.#getCompositeKey(partitionKey, rowKey);
|
|
1206
|
+
const existingEntity = this.table.get(key);
|
|
1207
|
+
if (!existingEntity) throw new MockRestError("The specified resource does not exist.", 404);
|
|
1208
|
+
this.#assertEtag(existingEntity.etag, etag);
|
|
1209
|
+
this.table.delete(key);
|
|
1210
|
+
}
|
|
1211
|
+
#applyUpdate(entity, mode = "Merge", etag) {
|
|
1212
|
+
const key = this.#getCompositeKey(entity.partitionKey, entity.rowKey);
|
|
1213
|
+
const existingEntity = this.table.get(key);
|
|
1214
|
+
if (!existingEntity) throw new MockRestError("The specified resource does not exist.", 404);
|
|
1215
|
+
this.#assertEtag(existingEntity.etag, etag);
|
|
1216
|
+
this.#writeEntity(key, entity, mode, existingEntity);
|
|
1217
|
+
}
|
|
1218
|
+
#applyUpsert(entity, mode = "Merge") {
|
|
1219
|
+
const key = this.#getCompositeKey(entity.partitionKey, entity.rowKey);
|
|
1220
|
+
const existingEntity = this.table.get(key);
|
|
1221
|
+
this.#writeEntity(key, entity, mode, existingEntity);
|
|
1222
|
+
}
|
|
1223
|
+
#assertEtag(existingEtag, etag) {
|
|
1224
|
+
if (etag !== void 0 && etag !== "*" && etag !== existingEtag) throw new MockRestError("The update condition specified in the request was not satisfied.", 412);
|
|
1225
|
+
}
|
|
1226
|
+
#getCompositeKey(partitionKey, rowKey) {
|
|
1227
|
+
return `${encodeURIComponent(partitionKey)}${ID_SEPARATOR}${encodeURIComponent(rowKey)}`;
|
|
1228
|
+
}
|
|
1229
|
+
#getEtag() {
|
|
1230
|
+
return `W/"${crypto.randomUUID()}"`;
|
|
1231
|
+
}
|
|
1232
|
+
#withMetadata(entity) {
|
|
1233
|
+
return {
|
|
1234
|
+
...entity,
|
|
1235
|
+
etag: entity.etag ?? this.#getEtag()
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
#writeEntity(key, entity, mode, existingEntity) {
|
|
1239
|
+
if (existingEntity && mode === "Merge") this.table.set(key, {
|
|
1240
|
+
...existingEntity,
|
|
1241
|
+
...entity,
|
|
1242
|
+
etag: this.#getEtag()
|
|
1243
|
+
});
|
|
1244
|
+
else this.table.set(key, {
|
|
1245
|
+
...entity,
|
|
1246
|
+
etag: this.#getEtag()
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
};
|
|
1250
|
+
// src/models/webPubSub/MockWebPubSubServiceClient.ts
|
|
1251
|
+
/**
|
|
1252
|
+
* A minimal mock of the Azure WebPubSubServiceClient for testing purposes.
|
|
1253
|
+
* Only implements `group().sendToAll` — the only method used in azure-functions tests.
|
|
1254
|
+
*/
|
|
1255
|
+
var MockWebPubSubServiceClient = class {
|
|
1256
|
+
group(_groupName) {
|
|
1257
|
+
return { sendToAll: () => Promise.resolve() };
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
// src/services/filter/applyFilter.ts
|
|
1261
|
+
const applyFilter = (documents, clauses) => {
|
|
1262
|
+
const predicate = createFilterPredicate(serializeClauses(clauses));
|
|
1263
|
+
return documents.filter((document) => predicate(document));
|
|
1264
|
+
};
|
|
1265
|
+
export { BLOB_NOT_FOUND_ERROR_CODE, BLOB_NOT_FOUND_MESSAGE, CONDITION_NOT_MET_ERROR_CODE, CONDITION_NOT_MET_MESSAGE, MOCK_BLOB_BASE_URL, MOCK_BLOB_SEEDED_DATE, MOCK_BLOB_SEEDED_PROPERTIES, MOCK_QUEUE_BASE_URL, MOCK_SEARCH_BASE_URL, MOCK_TABLE_BASE_URL, MockBlobBatchClient, MockBlobClient, MockBlockBlobClient, MockContainerBlobDatesDatabase, MockContainerBlobMetadataDatabase, MockContainerClient, MockContainerDatabase, MockEventGridDatabase, MockEventGridPublisherClient, MockQueueClient, MockQueueDatabase, MockRestError, MockSearchClient, MockSearchDatabase, MockServiceBusDatabase, MockServiceBusSender, MockTableClient, MockTableDatabase, MockWebPubSubServiceClient, NotImplementedError, applyFilter, bodyToBuffer, checkIsNullClause, checkIsReadableStream, compare, compareByCompositeKey, createFilterPredicate, createMockResponse, deleteMockBlob, getAzureErrorXml, getBlobItemXml, getBlobPrefixXml, getBlobUrl, getBlobUrlParts, getListBlobsSegmentResponse, getListBlobsXml, getMockBlobKey, getMockContainer, getMockQueueMessageItem, getMockSasUrl, readMockBlobDates, readMockBlobMetadata, storeMockBlobMetadata, storeMockBlobWrite, toWebResourceLike };
|