azure-mock 2.35.0 → 2.37.2

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 CHANGED
@@ -5,7 +5,7 @@
5
5
  [![NPM downloads][badge-npm-downloads]][url-npm]
6
6
  [![NPM Unpacked Size (with version)][badge-npm-unpacked-size]][url-npm]
7
7
 
8
- Mock Azure service classes for local development and testing. Provides in-memory implementations of Azure Table Storage, Blob Storage, Queue Storage, Service Bus, and EventGrid clients that mirror the real Azure SDK interfaces.
8
+ Mock Azure service classes for local development and testing. Provides in-memory implementations of Azure Table Storage, Blob Storage, Service Bus, and EventGrid clients that mirror the real Azure SDK interfaces.
9
9
 
10
10
  ## Table of Contents
11
11
 
@@ -18,22 +18,29 @@ Mock Azure service classes for local development and testing. Provides in-memory
18
18
  ## <a name="getting-started">🚀 Getting Started</a>
19
19
 
20
20
  ```bash
21
- pnpm i -D azure-mock @azure/core-http-compat @azure/core-rest-pipeline @azure/data-tables @azure/eventgrid @azure/service-bus @azure/storage-blob @azure/storage-queue
21
+ pnpm i -D azure-mock @azure/core-http-compat @azure/core-rest-pipeline @azure/data-tables @azure/eventgrid @azure/service-bus @azure/storage-blob
22
22
  ```
23
23
 
24
24
  ## <a name="documentation">📖 Documentation</a>
25
25
 
26
- We highly recommend you take a look at the [documentation](https://esposter.com/docs/modules/azure-mock.html) to level up.
26
+ We highly recommend you take a look at the [documentation](https://esposter.com/docs/api/modules/azure-mock.html) to level up.
27
27
 
28
28
  ### Available Mocks
29
29
 
30
- | Class | Azure Equivalent | Description |
31
- | ------------------------------ | -------------------------- | ----------------------------- |
32
- | `MockTableClient` | `TableClient` | In-memory Azure Table Storage |
33
- | `MockBlobServiceClient` | `BlobServiceClient` | In-memory Azure Blob Storage |
34
- | `MockQueueClient` | `QueueClient` | In-memory Azure Queue Storage |
35
- | `MockServiceBusSender` | `ServiceBusSender` | In-memory Azure Service Bus |
36
- | `MockEventGridPublisherClient` | `EventGridPublisherClient` | No-op EventGrid publisher |
30
+ | Class | Azure Equivalent | Description |
31
+ | ------------------------------ | -------------------------- | -------------------------------------------- |
32
+ | `MockTableClient` | `TableClient` | In-memory Azure Table Storage |
33
+ | `MockContainerClient` | `ContainerClient` | In-memory blob container, listing and paging |
34
+ | `MockBlobClient` | `BlobClient` | In-memory blob read, copy and delete |
35
+ | `MockBlockBlobClient` | `BlockBlobClient` | In-memory block blob upload and download |
36
+ | `MockBlobBatchClient` | `BlobBatchClient` | In-memory batched blob deletion |
37
+ | `MockSearchClient` | `SearchClient` | In-memory Azure AI Search index |
38
+ | `MockQueueClient` | `QueueClient` | In-memory Azure Storage Queue |
39
+ | `MockServiceBusSender` | `ServiceBusSender` | In-memory Azure Service Bus |
40
+ | `MockWebPubSubServiceClient` | `WebPubSubServiceClient` | In-memory Azure Web PubSub |
41
+ | `MockEventGridPublisherClient` | `EventGridPublisherClient` | In-memory EventGrid publisher |
42
+
43
+ Each client's state lives in an exported `Mock*Database` map (`MockTableDatabase`, `MockContainerDatabase`, …), so a test resets by clearing the one it seeded.
37
44
 
38
45
  ### Usage
39
46
 
package/dist/index.d.ts CHANGED
@@ -1,31 +1,32 @@
1
+ import { PipelineRequest, RestError } from "@azure/core-rest-pipeline";
1
2
  import { HttpHeadersLike, WebResourceLike } from "@azure/core-http-compat";
2
3
  import { MapValue } from "@esposter/shared";
3
- import { PipelineRequest } from "@azure/core-rest-pipeline";
4
- import { AnonymousCredential, AppendBlobClient, BlobAbortCopyFromURLResponse, BlobBatchClient, BlobBatchDeleteBlobsResponse, BlobBeginCopyFromURLResponse, BlobClient, BlobCopyFromURLResponse, BlobCreateSnapshotResponse, BlobDeleteIfExistsResponse, BlobDeleteImmutabilityPolicyResponse, BlobDeleteOptions, BlobDeleteResponse, BlobDownloadResponseModel, BlobDownloadResponseParsed, BlobGenerateSasUrlOptions, BlobGetAccountInfoResponse, BlobGetPropertiesResponse, BlobGetTagsResponse, BlobItem, BlobLeaseClient, BlobPrefix, BlobSetHTTPHeadersResponse, BlobSetImmutabilityPolicyResponse, BlobSetLegalHoldResponse, BlobSetMetadataResponse, BlobSetTagsResponse, BlobSetTierResponse, BlobUndeleteResponse, BlobUploadCommonResponse, BlockBlobClient, BlockBlobCommitBlockListResponse, BlockBlobGetBlockListResponse, BlockBlobPutBlobFromUrlResponse, BlockBlobStageBlockFromURLResponse, BlockBlobStageBlockResponse, BlockBlobUploadResponse, ContainerClient, ContainerCreateIfNotExistsResponse, ContainerCreateResponse, ContainerDeleteIfExistsResponse, ContainerDeleteResponse, ContainerFindBlobsByTagsSegmentResponse, ContainerGenerateSasUrlOptions, ContainerGetAccessPolicyResponse, ContainerGetAccountInfoResponse, ContainerGetPropertiesResponse, ContainerListBlobFlatSegmentResponse, ContainerListBlobHierarchySegmentResponse, ContainerListBlobsOptions, ContainerSetAccessPolicyResponse, ContainerSetMetadataResponse, FilterBlobItem, HttpRequestBody, PageBlobClient, PollOperationState, PollerLikeWithCancellation, StorageSharedKeyCredential } from "@azure/storage-blob";
4
+ import { AnonymousCredential, AppendBlobClient, BlobAbortCopyFromURLResponse, BlobBatchClient, BlobBatchDeleteBlobsResponse, BlobBeginCopyFromURLResponse, BlobClient, BlobCopyFromURLResponse, BlobCreateSnapshotResponse, BlobDeleteIfExistsResponse, BlobDeleteImmutabilityPolicyResponse, BlobDeleteOptions, BlobDeleteResponse, BlobDownloadResponseModel, BlobDownloadResponseParsed, BlobGenerateSasUrlOptions, BlobGetAccountInfoResponse, BlobGetPropertiesResponse, BlobGetTagsResponse, BlobHierarchyListSegment, BlobItem, BlobLeaseClient, BlobPrefix, BlobSetHTTPHeadersResponse, BlobSetImmutabilityPolicyResponse, BlobSetLegalHoldResponse, BlobSetMetadataResponse, BlobSetTagsResponse, BlobSetTierResponse, BlobUndeleteResponse, BlobUploadCommonResponse, BlockBlobClient, BlockBlobCommitBlockListResponse, BlockBlobGetBlockListResponse, BlockBlobPutBlobFromUrlResponse, BlockBlobStageBlockFromURLResponse, BlockBlobStageBlockResponse, BlockBlobUploadOptions, BlockBlobUploadResponse, ContainerClient, ContainerCreateIfNotExistsResponse, ContainerCreateResponse, ContainerDeleteIfExistsResponse, ContainerDeleteResponse, ContainerFindBlobsByTagsSegmentResponse, ContainerGenerateSasUrlOptions, ContainerGetAccessPolicyResponse, ContainerGetAccountInfoResponse, ContainerGetPropertiesResponse, ContainerListBlobFlatSegmentResponse, ContainerListBlobHierarchySegmentResponse, ContainerListBlobsOptions, ContainerSetAccessPolicyResponse, ContainerSetMetadataResponse, FilterBlobItem, HttpRequestBody, PageBlobClient, PollOperationState, PollerLikeWithCancellation, StorageSharedKeyCredential } from "@azure/storage-blob";
5
5
  import { BinaryOperator, Clause } from "@esposter/db-schema";
6
6
  import { EventGridEvent, EventGridPublisherClient } from "@azure/eventgrid";
7
7
  import { ServiceBusMessage, ServiceBusMessageBatch, ServiceBusSender } from "@azure/service-bus";
8
8
  import { CreateTableEntityResponse, GetAccessPolicyResponse, GetTableEntityResponse, ListTableEntitiesOptions, TableClient, TableDeleteEntityHeaders, TableEntity, TableEntityResult, TableEntityResultPage, TableMergeEntityHeaders, TableSetAccessPolicyHeaders, TableTransactionResponse, UpdateMode } from "@azure/data-tables";
9
9
  import { QueueClearMessagesResponse, QueueClient, QueueCreateIfNotExistsResponse, QueueCreateOptions, QueueCreateResponse, QueueDeleteIfExistsResponse, QueueDeleteMessageResponse, QueueDeleteResponse, QueueGenerateSasUrlOptions, QueueGetAccessPolicyResponse, QueueGetPropertiesResponse, QueueItem, QueuePeekMessagesOptions, QueuePeekMessagesResponse, QueueReceiveMessageOptions, QueueReceiveMessageResponse, QueueSendMessageOptions, QueueSendMessageResponse, QueueServiceProperties, QueueSetAccessPolicyResponse, QueueSetMetadataResponse, QueueUpdateMessageResponse, SignedIdentifier } from "@azure/storage-queue";
10
10
  import { AutocompleteResult, CountDocumentsOptions, DeleteDocumentsOptions, GetDocumentOptions, IndexDocumentsBatch, IndexDocumentsOptions, IndexDocumentsResult, MergeDocumentsOptions, MergeOrUploadDocumentsOptions, NarrowedModel, SearchClient, SearchDocumentsResult, SearchOptions, SelectFields, SuggestDocumentsResult, SuggestOptions, UploadDocumentsOptions } from "@azure/search-documents";
11
- declare const MOCK_BLOB_BASE_URL = "https://mockaccount.blob.core.windows.net";
12
- declare const MOCK_QUEUE_BASE_URL = "https://mockaccount.queue.core.windows.net";
13
- declare const MOCK_SEARCH_BASE_URL = "https://mockaccount.search.windows.net";
14
- declare const MOCK_TABLE_BASE_URL = "https://mockaccount.table.core.windows.net";
15
- interface MockResponse {
11
+ export declare const BLOB_NOT_FOUND_ERROR_CODE = "BlobNotFound";
12
+ export declare const BLOB_NOT_FOUND_MESSAGE = "The specified blob does not exist.";
13
+ export declare const MOCK_BLOB_BASE_URL = "https://mockaccount.blob.core.windows.net";
14
+ export declare const MOCK_QUEUE_BASE_URL = "https://mockaccount.queue.core.windows.net";
15
+ export declare const MOCK_SEARCH_BASE_URL = "https://mockaccount.search.windows.net";
16
+ export declare const MOCK_TABLE_BASE_URL = "https://mockaccount.table.core.windows.net";
17
+ export interface MockResponse {
16
18
  headers: HttpHeadersLike;
17
19
  parsedHeaders: Record<string, never>;
18
20
  request: WebResourceLike;
19
21
  status: number;
20
22
  }
21
- declare class MockRestError extends Error {
22
- statusCode: number;
23
+ export declare class MockRestError extends RestError {
23
24
  constructor(message: string, statusCode: number);
24
25
  }
25
26
  /**
26
27
  * An interface that tracks the settings for paged iteration
27
28
  */
28
- interface PageSettings {
29
+ export interface PageSettings {
29
30
  /**
30
31
  * The token that keeps track of where to continue the iterator
31
32
  */
@@ -38,7 +39,7 @@ interface PageSettings {
38
39
  /**
39
40
  * An interface that allows async iterable iteration both to completion and by page.
40
41
  */
41
- interface PagedAsyncIterableIterator<TElement, TPage = TElement[], TPageSettings = PageSettings> {
42
+ export interface PagedAsyncIterableIterator<TElement, TPage = TElement[], TPageSettings = PageSettings> {
42
43
  /**
43
44
  * The connection to the async iterator, part of the iteration protocol
44
45
  */
@@ -52,22 +53,26 @@ interface PagedAsyncIterableIterator<TElement, TPage = TElement[], TPageSettings
52
53
  */
53
54
  next: () => Promise<IteratorResult<TElement>>;
54
55
  }
55
- declare const createMockResponse: (status: number, url?: string) => MockResponse;
56
- declare const getMockSasUrl: (url: string, permissions: undefined | {
56
+ export declare const createMockResponse: (status: number, url?: string) => MockResponse;
57
+ export declare const getMockSasUrl: (url: string, permissions: undefined | {
57
58
  toString: () => string;
58
59
  }, resourceType?: string) => string;
59
- declare const MockContainerDatabase: Map<string, Map<string, Buffer>>;
60
- declare const MockEventGridDatabase: Map<string, EventGridEvent<unknown>[]>;
61
- declare const MockQueueDatabase: Map<string, string[]>;
62
- declare const MockSearchDatabase: Map<string, object[]>;
63
- declare const MockServiceBusDatabase: Map<string, ServiceBusMessage[]>;
64
- declare const MockTableDatabase: Map<string, Map<string, TableEntity>>;
65
- type BlobHierarchyItem = (BlobItem & {
60
+ export declare const MockContainerCreatedOnDatabase: Map<string, Date>;
61
+ export declare const MOCK_BLOB_SEEDED_CREATED_ON: Date;
62
+ export declare const getMockContainerCreatedOnKey: (containerName: string, blobName: string) => string;
63
+ export declare const MockContainerDatabase: Map<string, Map<string, Buffer>>;
64
+ export declare const MockEventGridDatabase: Map<string, EventGridEvent<unknown>[]>;
65
+ export declare const MockQueueDatabase: Map<string, string[]>;
66
+ export declare const MockSearchDatabase: Map<string, object[]>;
67
+ export declare const MockServiceBusDatabase: Map<string, ServiceBusMessage[]>;
68
+ export declare const MockTableDatabase: Map<string, Map<string, TableEntity>>;
69
+ export type BlobHierarchyItem = (BlobItem & {
66
70
  kind: "blob";
67
71
  }) | (BlobPrefix & {
68
72
  kind: "prefix";
69
73
  });
70
- declare class MockBlobBatchClient implements BlobBatchClient {
74
+ export declare class MockBlobBatchClient implements BlobBatchClient {
75
+ #private;
71
76
  url: string;
72
77
  constructor(url: string);
73
78
  /**
@@ -812,7 +817,7 @@ type PostPayloadFixed = Except<UserData, 'email'>;
812
817
  */
813
818
  type Except<ObjectType, KeysType extends keyof ObjectType, Options extends ExceptOptions = {}> = _Except<ObjectType, KeysType, ApplyDefaultOptions<ExceptOptions, DefaultExceptOptions, Options>>;
814
819
  type _Except<ObjectType, KeysType extends keyof ObjectType, Options extends Required<ExceptOptions>> = { [KeyType in keyof ObjectType as Filter<KeyType, KeysType>]: ObjectType[KeyType]; } & (Options['requireExactProps'] extends true ? Partial<Record<KeysType, never>> : {});
815
- declare class MockBlobClient implements Except<BlobClient, "accountName"> {
820
+ export declare class MockBlobClient implements Except<BlobClient, "accountName"> {
816
821
  connectionString: string;
817
822
  containerName: string;
818
823
  credential: AnonymousCredential;
@@ -852,14 +857,14 @@ declare class MockBlobClient implements Except<BlobClient, "accountName"> {
852
857
  withSnapshot(): BlockBlobClient;
853
858
  withVersion(): BlobClient;
854
859
  }
855
- declare class MockBlockBlobClient extends MockBlobClient implements Except<BlockBlobClient, "accountName"> {
860
+ export declare class MockBlockBlobClient extends MockBlobClient implements Except<BlockBlobClient, "accountName"> {
856
861
  commitBlockList(): Promise<BlockBlobCommitBlockListResponse>;
857
862
  getBlockList(): Promise<BlockBlobGetBlockListResponse>;
858
863
  query(): Promise<BlobDownloadResponseModel>;
859
864
  stageBlock(): Promise<BlockBlobStageBlockResponse>;
860
865
  stageBlockFromURL(): Promise<BlockBlobStageBlockFromURLResponse>;
861
866
  syncUploadFromURL(): Promise<BlockBlobPutBlobFromUrlResponse>;
862
- upload(body: HttpRequestBody, _contentLength: number): Promise<BlockBlobUploadResponse>;
867
+ upload(body: HttpRequestBody, _contentLength: number, options?: BlockBlobUploadOptions): Promise<BlockBlobUploadResponse>;
863
868
  uploadBrowserData(): Promise<BlobUploadCommonResponse>;
864
869
  uploadData(): Promise<BlobUploadCommonResponse>;
865
870
  uploadFile(): Promise<BlobUploadCommonResponse>;
@@ -875,7 +880,7 @@ declare class MockBlockBlobClient extends MockBlobClient implements Except<Block
875
880
  * await blockBlobClient.upload("hello world", 11);
876
881
  * const content = await blockBlobClient.downloadToBuffer();
877
882
  */
878
- declare class MockContainerClient implements Except<ContainerClient, "accountName"> {
883
+ export declare class MockContainerClient implements Except<ContainerClient, "accountName"> {
879
884
  #private;
880
885
  connectionString: string;
881
886
  containerName: string;
@@ -904,7 +909,7 @@ declare class MockContainerClient implements Except<ContainerClient, "accountNam
904
909
  getPageBlobClient(): PageBlobClient;
905
910
  getProperties(): Promise<ContainerGetPropertiesResponse>;
906
911
  listBlobsByHierarchy(delimiter: string, options?: ContainerListBlobsOptions): PagedAsyncIterableIterator<BlobHierarchyItem, ContainerListBlobHierarchySegmentResponse>;
907
- listBlobsFlat(): PagedAsyncIterableIterator<BlobItem, ContainerListBlobFlatSegmentResponse>;
912
+ listBlobsFlat(options?: ContainerListBlobsOptions): PagedAsyncIterableIterator<BlobItem, ContainerListBlobFlatSegmentResponse>;
908
913
  setAccessPolicy(): Promise<ContainerSetAccessPolicyResponse>;
909
914
  setMetadata(): Promise<ContainerSetMetadataResponse>;
910
915
  uploadBlockBlob(blobName: string, body: HttpRequestBody, contentLength: number): Promise<{
@@ -917,7 +922,7 @@ declare class MockContainerClient implements Except<ContainerClient, "accountNam
917
922
  * It uses a Map to simulate event grid storage and correctly implements the EventGridPublisherClient interface.
918
923
  * `client` is excluded because it is a private member of the real class and cannot be satisfied structurally.
919
924
  */
920
- declare class MockEventGridPublisherClient implements Except<EventGridPublisherClient<"EventGrid">, "apiVersion"> {
925
+ export declare class MockEventGridPublisherClient implements Except<EventGridPublisherClient<"EventGrid">, "apiVersion"> {
921
926
  readonly endpointUrl: string;
922
927
  readonly inputSchema: "EventGrid";
923
928
  readonly topicType: "EventGrid";
@@ -934,7 +939,7 @@ declare class MockEventGridPublisherClient implements Except<EventGridPublisherC
934
939
  * await mockQueueClient.sendMessage("hello world");
935
940
  * const messages = await mockQueueClient.peekMessages();
936
941
  */
937
- declare class MockQueueClient implements Except<QueueClient, "accountName"> {
942
+ export declare class MockQueueClient implements Except<QueueClient, "accountName"> {
938
943
  connectionString: string;
939
944
  name: string;
940
945
  url: string;
@@ -971,7 +976,7 @@ declare class MockQueueClient implements Except<QueueClient, "accountName"> {
971
976
  * await mockSearchClient.uploadDocuments([{ ... }]);
972
977
  * const { count, results } = await mockSearchClient.search("*", { filter, includeTotalCount: true });
973
978
  */
974
- declare class MockSearchClient<TModel extends object = Record<string, unknown>> implements Except<SearchClient<TModel>, "pipeline"> {
979
+ export declare class MockSearchClient<TModel extends object = Record<string, unknown>> implements Except<SearchClient<TModel>, "pipeline"> {
975
980
  readonly apiVersion: string;
976
981
  readonly endpoint: string;
977
982
  readonly indexName: string;
@@ -998,7 +1003,7 @@ declare class MockSearchClient<TModel extends object = Record<string, unknown>>
998
1003
  * const mockServiceBusSender = new MockServiceBusSender("hello world");
999
1004
  * await mockServiceBusSender.scheduleMessages({ body: "hello world" }, new Date());
1000
1005
  */
1001
- declare class MockServiceBusSender implements ServiceBusSender {
1006
+ export declare class MockServiceBusSender implements ServiceBusSender {
1002
1007
  entityPath: string;
1003
1008
  identifier: string;
1004
1009
  isClosed: boolean;
@@ -1019,7 +1024,7 @@ declare class MockServiceBusSender implements ServiceBusSender {
1019
1024
  * await mockTableClient.createEntity({ partitionKey: "partitionKey", rowKey: "rowKey" });
1020
1025
  * const entity = await mockTableClient.getEntity("partitionKey", "rowKey");
1021
1026
  */
1022
- declare class MockTableClient<TEntity extends TableEntity = TableEntity> implements Except<TableClient, "pipeline"> {
1027
+ export declare class MockTableClient<TEntity extends TableEntity = TableEntity> implements Except<TableClient, "pipeline"> {
1023
1028
  #private;
1024
1029
  entityType: TEntity;
1025
1030
  tableName: string;
@@ -1035,39 +1040,54 @@ declare class MockTableClient<TEntity extends TableEntity = TableEntity> impleme
1035
1040
  listEntities<T extends object>(options?: ListTableEntitiesOptions): PagedAsyncIterableIterator<TableEntityResult<T>, TableEntityResultPage<T>>;
1036
1041
  setAccessPolicy(): Promise<TableSetAccessPolicyHeaders>;
1037
1042
  submitTransaction(actions: Parameters<TableClient["submitTransaction"]>[0]): Promise<TableTransactionResponse>;
1038
- updateEntity<T extends object>(entity: TableEntity<T>, mode?: UpdateMode): Promise<TableMergeEntityHeaders>;
1043
+ updateEntity<T extends object>(entity: TableEntity<T>, mode?: UpdateMode, options?: {
1044
+ etag?: string;
1045
+ }): Promise<TableMergeEntityHeaders>;
1039
1046
  upsertEntity<T extends object>(entity: TableEntity<T>, mode?: UpdateMode): Promise<TableMergeEntityHeaders>;
1040
1047
  }
1041
1048
  /**
1042
1049
  * A minimal mock of the Azure WebPubSubServiceClient for testing purposes.
1043
1050
  * Only implements `group().sendToAll` — the only method used in azure-functions tests.
1044
1051
  */
1045
- declare class MockWebPubSubServiceClient {
1052
+ export declare class MockWebPubSubServiceClient {
1046
1053
  group(_groupName: string): {
1047
1054
  sendToAll: () => Promise<void>;
1048
1055
  };
1049
1056
  }
1050
- declare const bodyToBuffer: (body: HttpRequestBody) => Promise<Buffer>;
1057
+ export declare const bodyToBuffer: (body: HttpRequestBody) => Promise<Buffer>;
1051
1058
  /**
1052
1059
  * Generates a standard Azure Storage error XML response body.
1053
1060
  * @param errorCode The official Azure error code (e.g., "BlobNotFound").
1054
1061
  * @param errorMessage The user-friendly error message.
1055
1062
  * @returns A formatted XML string.
1056
1063
  */
1057
- declare const getAzureErrorXml: (errorCode: string, errorMessage: string) => string;
1058
- declare const getBlobItemXml: ({ name, properties }: BlobItem) => string;
1059
- declare const getBlobPrefixXml: (name: string) => string;
1064
+ export declare const getAzureErrorXml: (errorCode: string, errorMessage: string) => string;
1065
+ export declare const getBlobItemXml: ({ name, properties }: BlobItem) => string;
1066
+ export declare const getBlobPrefixXml: (name: string) => string;
1067
+ export declare const getBlobUrl: (containerName: string, blobName: string) => string;
1068
+ export declare const getBlobUrlParts: (url: string) => undefined | {
1069
+ blobName: string;
1070
+ containerName: string;
1071
+ };
1072
+ export declare const getListBlobsSegmentResponse: (containerName: string, prefix: string, segment: BlobHierarchyListSegment, blobsXml: string) => ContainerListBlobHierarchySegmentResponse;
1060
1073
  /**
1061
1074
  * Generates a standard Azure Storage list blobs XML response body.
1062
1075
  * @param containerName The container name.
1063
1076
  * @param blobsXml The blobs xml data.
1064
1077
  * @returns A formatted XML string.
1065
1078
  */
1066
- declare const getListBlobsXml: (containerName: string, blobsXml: string) => string;
1067
- declare const isReadableStream: (value: unknown) => value is NodeJS.ReadableStream;
1068
- declare const toWebResourceLike: (request: PipelineRequest) => WebResourceLike;
1069
- declare const applyFilter: <T extends Record<string, unknown>>(documents: T[], clauses: Clause<T>[]) => T[];
1070
- declare const compare: <T>(operator: BinaryOperator, leftHandSide: T, rightHandSide: null | T) => boolean;
1071
- declare const createFilterPredicate: (filter: string) => ((document: Record<string, unknown>) => boolean);
1072
- declare const isNullClause: (clause: Clause<Record<string, unknown>>) => boolean;
1073
- export { BlobHierarchyItem, MOCK_BLOB_BASE_URL, MOCK_QUEUE_BASE_URL, MOCK_SEARCH_BASE_URL, MOCK_TABLE_BASE_URL, MockBlobBatchClient, MockBlobClient, MockBlockBlobClient, MockContainerClient, MockContainerDatabase, MockEventGridDatabase, MockEventGridPublisherClient, MockQueueClient, MockQueueDatabase, MockResponse, MockRestError, MockSearchClient, MockSearchDatabase, MockServiceBusDatabase, MockServiceBusSender, MockTableClient, MockTableDatabase, MockWebPubSubServiceClient, PageSettings, PagedAsyncIterableIterator, applyFilter, bodyToBuffer, compare, createFilterPredicate, createMockResponse, getAzureErrorXml, getBlobItemXml, getBlobPrefixXml, getListBlobsXml, getMockSasUrl, isNullClause, isReadableStream, toWebResourceLike };
1079
+ export declare const getListBlobsXml: (containerName: string, blobsXml: string) => string;
1080
+ export declare const getMockContainer: (containerName: string) => MapValue<typeof MockContainerDatabase>;
1081
+ export declare const isReadableStream: (value: unknown) => value is NodeJS.ReadableStream;
1082
+ export declare const toWebResourceLike: (request: PipelineRequest) => WebResourceLike;
1083
+ export declare const applyFilter: <T extends Record<string, unknown>>(documents: T[], clauses: Clause<T>[]) => T[];
1084
+ export declare const compare: <T>(operator: BinaryOperator, leftHandSide: T, rightHandSide: null | T) => boolean;
1085
+ export declare const createFilterPredicate: (filter: string) => ((document: Record<string, unknown>) => boolean);
1086
+ export declare const isNullClause: (clause: Clause<Record<string, unknown>>) => boolean;
1087
+ export declare const getMockQueueMessageItem: (messageText: string) => {
1088
+ expiresOn: Date;
1089
+ insertedOn: Date;
1090
+ messageId: string;
1091
+ messageText: string;
1092
+ };
1093
+ export declare const compareByCompositeKey: (a: TableEntity, b: TableEntity) => number;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
+ import { RestError, createHttpHeaders, createPipelineRequest } from "@azure/core-rest-pipeline";
1
2
  import { toHttpHeadersLike } from "@azure/core-http-compat";
2
- import { ID_SEPARATOR, InvalidOperationError, Operation, exhaustiveGuard, getOrCreate, getResultAsync, html, noop, streamToText, takeOne } from "@esposter/shared";
3
- import { createHttpHeaders, createPipelineRequest } from "@azure/core-rest-pipeline";
3
+ import { ID_SEPARATOR, InvalidOperationError, Operation, chunk, exhaustiveGuard, getDecodedUriComponent, getOrCreate, getResult, html, noop, streamToText, takeOne } from "@esposter/shared";
4
4
  import { AnonymousCredential } from "@azure/storage-blob";
5
5
  import { Readable } from "node:stream";
6
6
  import { MAX_QUEUE_VISIBILITY_TIMEOUT_MS, deserializeClause, deserializeKey, getTableNullClause, serializeClauses } from "@esposter/db";
@@ -22,20 +22,20 @@ var __copyProps = (to, from, except, desc) => {
22
22
  }
23
23
  return to;
24
24
  };
25
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
25
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
26
26
  value: mod,
27
27
  enumerable: true
28
28
  }) : target, mod));
29
+ const BLOB_NOT_FOUND_ERROR_CODE = "BlobNotFound";
30
+ const BLOB_NOT_FOUND_MESSAGE = "The specified blob does not exist.";
29
31
  const MOCK_BLOB_BASE_URL = "https://mockaccount.blob.core.windows.net";
30
32
  const MOCK_QUEUE_BASE_URL = "https://mockaccount.queue.core.windows.net";
31
33
  const MOCK_SEARCH_BASE_URL = "https://mockaccount.search.windows.net";
32
34
  const MOCK_TABLE_BASE_URL = "https://mockaccount.table.core.windows.net";
33
- var MockRestError = class extends Error {
34
- statusCode;
35
+ var MockRestError = class extends RestError {
35
36
  constructor(message, statusCode) {
36
- super(message);
37
+ super(message, { statusCode });
37
38
  this.name = "MockRestError";
38
- this.statusCode = statusCode;
39
39
  }
40
40
  };
41
41
  const toWebResourceLike = (request) => ({
@@ -70,7 +70,16 @@ const createMockResponse = (status, url = "") => ({
70
70
  status
71
71
  });
72
72
  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"}`;
73
- const MockContainerDatabase = /* @__PURE__ */ new Map();
73
+ const MockContainerCreatedOnDatabase = /* @__PURE__ */ new Map();
74
+ const MOCK_BLOB_SEEDED_CREATED_ON = /* @__PURE__ */ new Date(-864e13);
75
+ const getMockContainerCreatedOnKey = (containerName, blobName) => `${containerName}/${blobName}`;
76
+ var MockContainerDatabaseMap = class extends Map {
77
+ clear() {
78
+ super.clear();
79
+ MockContainerCreatedOnDatabase.clear();
80
+ }
81
+ };
82
+ const MockContainerDatabase = new MockContainerDatabaseMap();
74
83
  const MockEventGridDatabase = /* @__PURE__ */ new Map();
75
84
  const MockQueueDatabase = /* @__PURE__ */ new Map();
76
85
  const MockSearchDatabase = /* @__PURE__ */ new Map();
@@ -87,6 +96,18 @@ const getAzureErrorXml = (errorCode, errorMessage) => html`<?xml version="1.0" e
87
96
  <code>${errorCode}</code>
88
97
  <Message>${errorMessage}</Message>
89
98
  </Error>`;
99
+ const decodeSegment = (segment) => getDecodedUriComponent(segment, segment);
100
+ const getBlobUrlParts = (url) => {
101
+ const parsedUrl = getResult(() => new URL(url)).unwrapOr(void 0);
102
+ if (!parsedUrl) return void 0;
103
+ const pathSegments = parsedUrl.pathname.split("/").filter(Boolean);
104
+ if (pathSegments.length < 2) return void 0;
105
+ return {
106
+ blobName: decodeSegment(pathSegments.slice(1).join("/")),
107
+ containerName: decodeSegment(takeOne(pathSegments))
108
+ };
109
+ };
110
+ const getMockContainer = (containerName) => getOrCreate(MockContainerDatabase, containerName, () => /* @__PURE__ */ new Map());
90
111
  var MockBlobBatchClient = class {
91
112
  url;
92
113
  constructor(url) {
@@ -103,29 +124,17 @@ var MockBlobBatchClient = class {
103
124
  let subResponsesSucceededCount = 0;
104
125
  let subResponsesFailedCount = 0;
105
126
  for (const url of urls) {
106
- const pathSegments = new URL(url).pathname.split("/").filter(Boolean);
107
- if (pathSegments.length < 2) {
108
- const errorCode = "InvalidUri";
109
- const statusMessage = "Invalid blob URL format.";
110
- subResponses.push({
111
- _request: {
112
- credential,
113
- url: this.url
114
- },
115
- bodyAsText: getAzureErrorXml(errorCode, statusMessage),
116
- errorCode,
117
- headers: toHttpHeadersLike(createHttpHeaders()),
118
- status: 400,
119
- statusMessage
120
- });
127
+ const urlParts = getBlobUrlParts(url);
128
+ if (!urlParts) {
129
+ subResponses.push(this.#createFailedSubResponse(credential, 400, "InvalidUri", "Invalid blob URL format."));
121
130
  subResponsesFailedCount++;
122
131
  continue;
123
132
  }
124
- const containerName = takeOne(pathSegments);
125
- const blobName = pathSegments.slice(1).join("/");
133
+ const { blobName, containerName } = urlParts;
126
134
  const container = this.getContainer(containerName);
127
135
  if (container.has(blobName)) {
128
136
  container.delete(blobName);
137
+ MockContainerCreatedOnDatabase.delete(getMockContainerCreatedOnKey(containerName, blobName));
129
138
  subResponses.push({
130
139
  _request: {
131
140
  credential,
@@ -137,19 +146,7 @@ var MockBlobBatchClient = class {
137
146
  });
138
147
  subResponsesSucceededCount++;
139
148
  } else {
140
- const errorCode = "BlobNotFound";
141
- const statusMessage = "The specified blob does not exist.";
142
- subResponses.push({
143
- _request: {
144
- credential,
145
- url: this.url
146
- },
147
- bodyAsText: getAzureErrorXml(errorCode, statusMessage),
148
- errorCode,
149
- headers: toHttpHeadersLike(createHttpHeaders()),
150
- status: 404,
151
- statusMessage
152
- });
149
+ subResponses.push(this.#createFailedSubResponse(credential, 404, BLOB_NOT_FOUND_ERROR_CODE, BLOB_NOT_FOUND_MESSAGE));
153
150
  subResponsesFailedCount++;
154
151
  }
155
152
  }
@@ -167,9 +164,23 @@ var MockBlobBatchClient = class {
167
164
  });
168
165
  }
169
166
  getContainer(containerName) {
170
- return getOrCreate(MockContainerDatabase, containerName, () => /* @__PURE__ */ new Map());
167
+ return getMockContainer(containerName);
168
+ }
169
+ #createFailedSubResponse(credential, status, errorCode, statusMessage) {
170
+ return {
171
+ _request: {
172
+ credential,
173
+ url: this.url
174
+ },
175
+ bodyAsText: getAzureErrorXml(errorCode, statusMessage),
176
+ errorCode,
177
+ headers: toHttpHeadersLike(createHttpHeaders()),
178
+ status,
179
+ statusMessage
180
+ };
171
181
  }
172
182
  };
183
+ const getBlobUrl = (containerName, blobName) => `${MOCK_BLOB_BASE_URL}/${containerName}/${blobName.split("/").map((segment) => encodeURIComponent(segment)).join("/")}`;
173
184
  var MockBlobClient = class {
174
185
  connectionString;
175
186
  containerName;
@@ -177,27 +188,27 @@ var MockBlobClient = class {
177
188
  name;
178
189
  url;
179
190
  get container() {
180
- return getOrCreate(MockContainerDatabase, this.containerName, () => /* @__PURE__ */ new Map());
191
+ return getMockContainer(this.containerName);
181
192
  }
182
193
  constructor(connectionString, containerName, blobName) {
183
194
  this.connectionString = connectionString;
184
195
  this.containerName = containerName;
185
196
  this.name = blobName;
186
- this.url = `${MOCK_BLOB_BASE_URL}/${this.containerName}/${this.name}`;
197
+ this.url = getBlobUrl(this.containerName, this.name);
187
198
  }
188
199
  abortCopyFromURL() {
189
200
  throw new Error("Method not implemented.");
190
201
  }
191
202
  beginCopyFromURL(copySource) {
192
- const pathSegments = new URL(copySource).pathname.split("/").filter(Boolean);
193
- if (pathSegments.length < 2) throw new MockRestError("Invalid copy source URL format", 400);
194
- const sourceContainerName = takeOne(pathSegments);
195
- const sourceBlobName = pathSegments.slice(1).join("/");
203
+ const sourceParts = getBlobUrlParts(copySource);
204
+ if (!sourceParts) throw new MockRestError("Invalid copy source URL format", 400);
205
+ const { blobName: sourceBlobName, containerName: sourceContainerName } = sourceParts;
196
206
  const sourceContainer = MockContainerDatabase.get(sourceContainerName);
197
207
  if (!sourceContainer) throw new MockRestError("Source container not found", 404);
198
208
  const sourceData = sourceContainer.get(sourceBlobName);
199
209
  if (!sourceData) throw new MockRestError("Source blob not found", 404);
200
210
  this.container.set(this.name, Buffer.from(sourceData));
211
+ MockContainerCreatedOnDatabase.set(getMockContainerCreatedOnKey(this.containerName, this.name), /* @__PURE__ */ new Date());
201
212
  const response = { _response: createMockResponse(202, `${this.url}?comp=copy`) };
202
213
  return Promise.resolve({
203
214
  cancelOperation: () => Promise.resolve(),
@@ -218,13 +229,17 @@ var MockBlobClient = class {
218
229
  throw new Error("Method not implemented.");
219
230
  }
220
231
  delete() {
221
- if (!this.container.has(this.name)) throw new MockRestError("The specified blob does not exist.", 404);
232
+ if (!this.container.has(this.name)) throw new MockRestError(BLOB_NOT_FOUND_MESSAGE, 404);
222
233
  this.container.delete(this.name);
234
+ MockContainerCreatedOnDatabase.delete(getMockContainerCreatedOnKey(this.containerName, this.name));
223
235
  return Promise.resolve({ _response: createMockResponse(200) });
224
236
  }
225
237
  deleteIfExists() {
226
238
  const succeeded = this.container.has(this.name);
227
- if (succeeded) this.container.delete(this.name);
239
+ if (succeeded) {
240
+ this.container.delete(this.name);
241
+ MockContainerCreatedOnDatabase.delete(getMockContainerCreatedOnKey(this.containerName, this.name));
242
+ }
228
243
  return Promise.resolve({
229
244
  _response: createMockResponse(succeeded ? 200 : 404),
230
245
  succeeded
@@ -242,14 +257,14 @@ var MockBlobClient = class {
242
257
  }
243
258
  downloadToBuffer() {
244
259
  const data = this.container.get(this.name);
245
- if (!data) throw new MockRestError("The specified blob does not exist.", 404);
260
+ if (!data) throw new MockRestError(BLOB_NOT_FOUND_MESSAGE, 404);
246
261
  return Promise.resolve(Buffer.from(data));
247
262
  }
248
263
  downloadToFile() {
249
264
  throw new Error("Method not implemented.");
250
265
  }
251
266
  exists() {
252
- throw new Error("Method not implemented.");
267
+ return Promise.resolve(this.container.has(this.name));
253
268
  }
254
269
  generateSasStringToSign() {
255
270
  throw new Error("Method not implemented.");
@@ -362,8 +377,11 @@ var MockBlockBlobClient = class extends MockBlobClient {
362
377
  syncUploadFromURL() {
363
378
  throw new Error("Method not implemented.");
364
379
  }
365
- async upload(body, _contentLength) {
366
- this.container.set(this.name, await bodyToBuffer(body));
380
+ async upload(body, _contentLength, options) {
381
+ const buffer = await bodyToBuffer(body);
382
+ if (options?.conditions?.ifNoneMatch === "*" && this.container.has(this.name)) throw new MockRestError("The specified blob already exists.", 409);
383
+ this.container.set(this.name, buffer);
384
+ MockContainerCreatedOnDatabase.set(getMockContainerCreatedOnKey(this.containerName, this.name), /* @__PURE__ */ new Date());
367
385
  return { _response: createMockResponse(201) };
368
386
  }
369
387
  uploadBrowserData() {
@@ -403,6 +421,23 @@ const getListBlobsXml = (containerName, blobsXml) => html`<?xml version="1.0" en
403
421
  <Blobs>${blobsXml}</Blobs>
404
422
  <NextMarker />
405
423
  </EnumerationResults>`;
424
+ const getListBlobsSegmentResponse = (containerName, prefix, segment, blobsXml) => {
425
+ const segmentResponse = {
426
+ containerName,
427
+ marker: "",
428
+ prefix,
429
+ segment,
430
+ serviceEndpoint: ""
431
+ };
432
+ return {
433
+ _response: {
434
+ ...createMockResponse(200),
435
+ bodyAsText: getListBlobsXml(containerName, blobsXml),
436
+ parsedBody: segmentResponse
437
+ },
438
+ ...segmentResponse
439
+ };
440
+ };
406
441
  /**
407
442
  * An in-memory mock of the Azure ContainerClient.
408
443
  * It uses a Map to simulate blob storage.
@@ -419,7 +454,7 @@ var MockContainerClient = class {
419
454
  credential = new AnonymousCredential();
420
455
  url;
421
456
  get container() {
422
- return getOrCreate(MockContainerDatabase, this.containerName, () => /* @__PURE__ */ new Map());
457
+ return getMockContainer(this.containerName);
423
458
  }
424
459
  constructor(connectionString, containerName) {
425
460
  this.connectionString = connectionString;
@@ -436,9 +471,10 @@ var MockContainerClient = class {
436
471
  throw new Error("Method not implemented.");
437
472
  }
438
473
  deleteBlob(blobName) {
439
- if (!this.container.has(blobName)) throw new MockRestError("The specified blob does not exist.", 404);
474
+ if (!this.container.has(blobName)) throw new MockRestError(BLOB_NOT_FOUND_MESSAGE, 404);
440
475
  this.container.delete(blobName);
441
- return Promise.resolve({ _response: createMockResponse(200, `${this.url}/${blobName}`) });
476
+ MockContainerCreatedOnDatabase.delete(getMockContainerCreatedOnKey(this.containerName, blobName));
477
+ return Promise.resolve({ _response: createMockResponse(200, getBlobUrl(this.containerName, blobName)) });
442
478
  }
443
479
  deleteIfExists() {
444
480
  throw new Error("Method not implemented.");
@@ -503,24 +539,10 @@ var MockContainerClient = class {
503
539
  allBlobPrefixes.push({ name: blobHierarchyItem.name });
504
540
  allBlobPrefixXml.push(getBlobPrefixXml(blobHierarchyItem.name));
505
541
  }
506
- if (allBlobItems.length > 0 || allBlobPrefixes.length > 0) yield await Promise.resolve({
507
- _response: {
508
- ...createMockResponse(200),
509
- bodyAsText: getListBlobsXml(this.containerName, `${allBlobItemXml.join("")}${allBlobPrefixXml.join("")}`),
510
- parsedBody: {
511
- containerName: this.containerName,
512
- marker: "",
513
- prefix: options?.prefix ?? "",
514
- segment: { blobItems: allBlobItems },
515
- serviceEndpoint: ""
516
- }
517
- },
518
- containerName: this.containerName,
519
- marker: "",
520
- prefix: options?.prefix ?? "",
521
- segment: { blobItems: allBlobItems },
522
- serviceEndpoint: ""
523
- });
542
+ if (allBlobItems.length > 0 || allBlobPrefixes.length > 0) yield await Promise.resolve(getListBlobsSegmentResponse(this.containerName, options?.prefix ?? "", {
543
+ blobItems: allBlobItems,
544
+ blobPrefixes: allBlobPrefixes
545
+ }, `${allBlobItemXml.join("")}${allBlobPrefixXml.join("")}`));
524
546
  }.bind(this)(),
525
547
  next: blobHierarchyItemIterator.next.bind(blobHierarchyItemIterator),
526
548
  [Symbol.asyncIterator]() {
@@ -528,8 +550,8 @@ var MockContainerClient = class {
528
550
  }
529
551
  };
530
552
  }
531
- listBlobsFlat() {
532
- const blobItemIterator = this.#getBlobItemIterator();
553
+ listBlobsFlat(options) {
554
+ const blobItemIterator = this.#getBlobItemIterator(options);
533
555
  return {
534
556
  byPage: () => async function* () {
535
557
  const allBlobItems = [];
@@ -538,24 +560,7 @@ var MockContainerClient = class {
538
560
  allBlobItems.push(blobItem);
539
561
  allBlobItemXml.push(getBlobItemXml(blobItem));
540
562
  }
541
- if (allBlobItems.length > 0) yield await Promise.resolve({
542
- _response: {
543
- ...createMockResponse(200),
544
- bodyAsText: getListBlobsXml(this.containerName, allBlobItemXml.join("")),
545
- parsedBody: {
546
- containerName: this.containerName,
547
- marker: "",
548
- prefix: "",
549
- segment: { blobItems: allBlobItems },
550
- serviceEndpoint: ""
551
- }
552
- },
553
- containerName: this.containerName,
554
- marker: "",
555
- prefix: "",
556
- segment: { blobItems: allBlobItems },
557
- serviceEndpoint: ""
558
- });
563
+ if (allBlobItems.length > 0) yield await Promise.resolve(getListBlobsSegmentResponse(this.containerName, options?.prefix ?? "", { blobItems: allBlobItems }, allBlobItemXml.join("")));
559
564
  }.bind(this)(),
560
565
  next: blobItemIterator.next.bind(blobItemIterator),
561
566
  [Symbol.asyncIterator]() {
@@ -584,20 +589,7 @@ var MockContainerClient = class {
584
589
  if (!name.startsWith(prefix)) continue;
585
590
  const nameAfterPrefix = name.slice(prefix.length);
586
591
  const delimiterIndex = nameAfterPrefix.indexOf(delimiter);
587
- if (delimiterIndex === -1) blobsInCurrentLevel.push({
588
- deleted: false,
589
- name,
590
- properties: {
591
- blobType: "BlockBlob",
592
- contentLength: buffer.length,
593
- contentType: "application/octet-stream",
594
- etag: `"${crypto.randomUUID()}"`,
595
- lastModified: /* @__PURE__ */ new Date(),
596
- leaseState: "available",
597
- leaseStatus: "unlocked"
598
- },
599
- snapshot: ""
600
- });
592
+ if (delimiterIndex === -1) blobsInCurrentLevel.push(this.#getBlobItem(name, buffer));
601
593
  else {
602
594
  const subprefix = `${prefix}${nameAfterPrefix.slice(0, delimiterIndex + delimiter.length)}`;
603
595
  uniqueSubprefixes.add(subprefix);
@@ -612,21 +604,29 @@ var MockContainerClient = class {
612
604
  ...blobItem
613
605
  });
614
606
  }
615
- async *#getBlobItemIterator() {
616
- for (const [name, buffer] of this.container.entries()) yield await Promise.resolve({
607
+ #getBlobItem(name, buffer) {
608
+ return {
617
609
  deleted: false,
618
610
  name,
619
611
  properties: {
620
612
  blobType: "BlockBlob",
621
613
  contentLength: buffer.length,
622
614
  contentType: "application/octet-stream",
615
+ createdOn: MockContainerCreatedOnDatabase.get(getMockContainerCreatedOnKey(this.containerName, name)) ?? MOCK_BLOB_SEEDED_CREATED_ON,
623
616
  etag: `"${crypto.randomUUID()}"`,
624
617
  lastModified: /* @__PURE__ */ new Date(),
625
618
  leaseState: "available",
626
619
  leaseStatus: "unlocked"
627
620
  },
628
621
  snapshot: ""
629
- });
622
+ };
623
+ }
624
+ async *#getBlobItemIterator(options) {
625
+ const prefix = options?.prefix ?? "";
626
+ for (const [name, buffer] of this.container.entries()) {
627
+ if (!name.startsWith(prefix)) continue;
628
+ yield await Promise.resolve(this.#getBlobItem(name, buffer));
629
+ }
630
630
  }
631
631
  };
632
632
  /**
@@ -648,6 +648,15 @@ var MockEventGridPublisherClient = class {
648
648
  return Promise.resolve();
649
649
  }
650
650
  };
651
+ const getMockQueueMessageItem = (messageText) => {
652
+ const insertedOn = /* @__PURE__ */ new Date();
653
+ return {
654
+ expiresOn: new Date(insertedOn.getTime() + MAX_QUEUE_VISIBILITY_TIMEOUT_MS),
655
+ insertedOn,
656
+ messageId: crypto.randomUUID(),
657
+ messageText
658
+ };
659
+ };
651
660
  /**
652
661
  * An in-memory mock of the Azure QueueClient.
653
662
  * It uses a Map to simulate queue storage and correctly implements the QueueClient interface.
@@ -718,10 +727,7 @@ var MockQueueClient = class {
718
727
  peekMessages(_options) {
719
728
  const peekedMessageItems = this.queue.map((text) => ({
720
729
  dequeueCount: 0,
721
- expiresOn: new Date(Date.now() + MAX_QUEUE_VISIBILITY_TIMEOUT_MS),
722
- insertedOn: /* @__PURE__ */ new Date(),
723
- messageId: crypto.randomUUID(),
724
- messageText: text
730
+ ...getMockQueueMessageItem(text)
725
731
  }));
726
732
  return Promise.resolve({
727
733
  _response: {
@@ -733,12 +739,8 @@ var MockQueueClient = class {
733
739
  });
734
740
  }
735
741
  receiveMessages(_options) {
736
- const receivedMessageItems = this.queue.splice(0).map((text) => ({
742
+ const receivedMessageItems = this.queue.splice(0).map((text) => Object.assign(getMockQueueMessageItem(text), {
737
743
  dequeueCount: 1,
738
- expiresOn: new Date(Date.now() + MAX_QUEUE_VISIBILITY_TIMEOUT_MS),
739
- insertedOn: /* @__PURE__ */ new Date(),
740
- messageId: crypto.randomUUID(),
741
- messageText: text,
742
744
  nextVisibleOn: /* @__PURE__ */ new Date(),
743
745
  popReceipt: crypto.randomUUID()
744
746
  }));
@@ -753,11 +755,8 @@ var MockQueueClient = class {
753
755
  }
754
756
  sendMessage(messageText, _options) {
755
757
  this.queue.push(messageText);
756
- const now = /* @__PURE__ */ new Date();
757
- const expiresOn = new Date(now.getTime() + MAX_QUEUE_VISIBILITY_TIMEOUT_MS);
758
- const insertedOn = now;
759
- const messageId = crypto.randomUUID();
760
- const nextVisibleOn = now;
758
+ const { expiresOn, insertedOn, messageId } = getMockQueueMessageItem(messageText);
759
+ const nextVisibleOn = insertedOn;
761
760
  const popReceipt = crypto.randomUUID();
762
761
  const enqueuedMessages = [{
763
762
  expiresOn,
@@ -837,18 +836,24 @@ var import_fast_deep_equal = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJS
837
836
  };
838
837
  })))(), 1);
839
838
  const isNullClause = (clause) => {
840
- return (0, import_fast_deep_equal.default)(clause, getTableNullClause(clause.key));
839
+ const tableNullClause = getTableNullClause(clause.key);
840
+ return (0, import_fast_deep_equal.default)(clause, tableNullClause);
841
841
  };
842
842
  const createFilterPredicate = (filter) => {
843
- 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));
843
+ 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) => {
844
+ const clause = deserializeClause(clauseString);
845
+ return {
846
+ clause,
847
+ isNull: isNullClause(clause)
848
+ };
849
+ }));
844
850
  return (document) => {
845
851
  for (const orGroup of orGroups) {
846
852
  let isGroupMatched = false;
847
- for (const group of orGroup) {
848
- const clause = deserializeClause(group);
853
+ for (const { clause, isNull } of orGroup) {
849
854
  const value = takeOne(document, clause.key);
850
855
  let isMatched;
851
- if (isNullClause(clause)) isMatched = compare(BinaryOperator.eq, value, null);
856
+ if (isNull) isMatched = compare(BinaryOperator.eq, value, null);
852
857
  else {
853
858
  const comparisonResult = compare(clause.operator, value, clause.value);
854
859
  isMatched = clause.not ? !comparisonResult : comparisonResult;
@@ -874,15 +879,22 @@ const compareValues = (leftHandSide, rightHandSide) => {
874
879
  if (typeof left === "number" && typeof right === "number") return left - right;
875
880
  return String(left).localeCompare(String(right));
876
881
  };
877
- const sortDocuments = (documents, orderBy) => [...documents].toSorted((leftHandSide, rightHandSide) => {
878
- for (const clause of orderBy) {
882
+ const sortDocuments = (documents, orderBy) => {
883
+ const parsedOrderBy = orderBy.map((clause) => {
879
884
  const [field = "", direction = "asc"] = clause.split(/\s+/u);
880
- const key = deserializeKey(field);
881
- const comparison = compareValues(leftHandSide[key], rightHandSide[key]);
882
- if (comparison !== 0) return direction === "desc" ? -comparison : comparison;
883
- }
884
- return 0;
885
- });
885
+ return {
886
+ direction,
887
+ key: deserializeKey(field)
888
+ };
889
+ });
890
+ return documents.toSorted((leftHandSide, rightHandSide) => {
891
+ for (const { direction, key } of parsedOrderBy) {
892
+ const comparison = compareValues(leftHandSide[key], rightHandSide[key]);
893
+ if (comparison !== 0) return direction === "desc" ? -comparison : comparison;
894
+ }
895
+ return 0;
896
+ });
897
+ };
886
898
  const getSearchFieldValues = (value, pathSegments) => {
887
899
  if (pathSegments.length === 0) return [value];
888
900
  if (Array.isArray(value)) return value.flatMap((item) => getSearchFieldValues(item, pathSegments));
@@ -893,7 +905,8 @@ const getSearchFieldValues = (value, pathSegments) => {
893
905
  const searchDocuments = (documents, searchText, searchFields) => {
894
906
  if (!searchText || searchText === "*") return documents;
895
907
  const normalizedSearchText = searchText.toLocaleLowerCase();
896
- return documents.filter((document) => (searchFields ?? Object.keys(document)).some((searchField) => getSearchFieldValues(document, searchField.split("/")).some((value) => value !== null && value !== void 0 && String(value).toLocaleLowerCase().includes(normalizedSearchText))));
908
+ const searchFieldPaths = searchFields?.map((searchField) => searchField.split("/"));
909
+ 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))));
897
910
  };
898
911
  /**
899
912
  * An in-memory mock of the Azure SearchClient.
@@ -1004,6 +1017,11 @@ var MockServiceBusSender = class {
1004
1017
  return Promise.resolve();
1005
1018
  }
1006
1019
  };
1020
+ const compareByCompositeKey = (a, b) => {
1021
+ if (a.partitionKey !== b.partitionKey) return a.partitionKey < b.partitionKey ? -1 : 1;
1022
+ else if (a.rowKey === b.rowKey) return 0;
1023
+ else return a.rowKey < b.rowKey ? -1 : 1;
1024
+ };
1007
1025
  /**
1008
1026
  * An in-memory mock of the Azure TableClient.
1009
1027
  * It uses a Map to simulate table storage and correctly implements the TableClient interface.
@@ -1024,9 +1042,7 @@ var MockTableClient = class {
1024
1042
  this.url = `${MOCK_TABLE_BASE_URL}/${this.tableName}`;
1025
1043
  }
1026
1044
  createEntity(entity) {
1027
- const key = this.#getCompositeKey(entity.partitionKey, entity.rowKey);
1028
- if (this.table.has(key)) throw new MockRestError("The specified entity already exists.", 409);
1029
- this.table.set(key, entity);
1045
+ this.#applyCreate(entity);
1030
1046
  return Promise.resolve({
1031
1047
  date: /* @__PURE__ */ new Date(),
1032
1048
  etag: this.#getEtag()
@@ -1036,9 +1052,7 @@ var MockTableClient = class {
1036
1052
  throw new Error("Method not implemented.");
1037
1053
  }
1038
1054
  deleteEntity(partitionKey, rowKey) {
1039
- const key = this.#getCompositeKey(partitionKey, rowKey);
1040
- if (!this.table.has(key)) throw new MockRestError("The specified resource does not exist.", 404);
1041
- this.table.delete(key);
1055
+ this.#applyDelete(partitionKey, rowKey);
1042
1056
  return Promise.resolve({});
1043
1057
  }
1044
1058
  deleteTable() {
@@ -1053,7 +1067,7 @@ var MockTableClient = class {
1053
1067
  if (!entity) throw new MockRestError("The specified resource does not exist.", 404);
1054
1068
  return Promise.resolve({
1055
1069
  ...entity,
1056
- etag: this.#getEtag()
1070
+ etag: entity.etag ?? this.#getEtag()
1057
1071
  });
1058
1072
  }
1059
1073
  listEntities(options) {
@@ -1061,22 +1075,22 @@ var MockTableClient = class {
1061
1075
  const filter = options?.queryOptions?.filter;
1062
1076
  const tableEntities = [...this.table.values()];
1063
1077
  const predicate = filter ? createFilterPredicate(filter) : void 0;
1064
- const resultTableEntities = predicate ? tableEntities.filter((e) => predicate(e)) : tableEntities;
1078
+ const resultTableEntities = (predicate ? tableEntities.filter((e) => predicate(e)) : tableEntities).toSorted(compareByCompositeKey);
1079
+ const entityIterator = (async function* (entities) {
1080
+ for (const entity of entities) yield await Promise.resolve(withMetadata(entity));
1081
+ })(resultTableEntities);
1065
1082
  return {
1066
1083
  byPage: ({ maxPageSize } = {}) => (async function* (entities) {
1067
1084
  if (maxPageSize !== void 0 && maxPageSize <= 0) throw new RangeError("maxPageSize must be greater than 0.");
1068
1085
  else if (maxPageSize !== void 0 && maxPageSize > AZURE_MAX_PAGE_SIZE) throw new MockRestError("One of the request inputs is not valid.", 400);
1069
- const allEntitiesWithMetadata = entities.map((e) => withMetadata(e));
1070
- if (allEntitiesWithMetadata.length === 0) return;
1086
+ if (entities.length === 0) return;
1071
1087
  else if (!maxPageSize) {
1072
- yield await Promise.resolve(allEntitiesWithMetadata);
1088
+ yield await Promise.resolve(entities.map((e) => withMetadata(e)));
1073
1089
  return;
1074
1090
  }
1075
- for (let i = 0; i < allEntitiesWithMetadata.length; i += maxPageSize) yield await Promise.resolve(allEntitiesWithMetadata.slice(i, i + maxPageSize));
1091
+ for (const page of chunk(entities, maxPageSize)) yield await Promise.resolve(page.map((e) => withMetadata(e)));
1076
1092
  })(resultTableEntities),
1077
- next: () => (async function* (entities) {
1078
- for (const entity of entities) yield await Promise.resolve(withMetadata(entity));
1079
- })(resultTableEntities).next(),
1093
+ next: () => entityIterator.next(),
1080
1094
  [Symbol.asyncIterator]() {
1081
1095
  return this;
1082
1096
  }
@@ -1085,24 +1099,24 @@ var MockTableClient = class {
1085
1099
  setAccessPolicy() {
1086
1100
  throw new Error("Method not implemented.");
1087
1101
  }
1088
- async submitTransaction(actions) {
1102
+ submitTransaction(actions) {
1089
1103
  let partitionKey;
1090
1104
  for (const [, entity] of actions) if (partitionKey === void 0) partitionKey = entity.partitionKey;
1091
1105
  else if (entity.partitionKey !== partitionKey) throw new MockRestError("All transaction actions must target the same partitionKey.", 400);
1092
1106
  const snapshot = new Map(this.table);
1093
- await getResultAsync(async () => {
1107
+ getResult(() => {
1094
1108
  for (const [type, entity, updateMode] of actions) switch (type) {
1095
1109
  case "create":
1096
- await this.createEntity(entity);
1110
+ this.#applyCreate(entity);
1097
1111
  break;
1098
1112
  case "delete":
1099
- await this.deleteEntity(entity.partitionKey, entity.rowKey);
1113
+ this.#applyDelete(entity.partitionKey, entity.rowKey);
1100
1114
  break;
1101
1115
  case "update":
1102
- await this.updateEntity(entity, updateMode);
1116
+ this.#applyUpdate(entity, updateMode);
1103
1117
  break;
1104
1118
  case "upsert":
1105
- await this.upsertEntity(entity, updateMode);
1119
+ this.#applyUpsert(entity, updateMode);
1106
1120
  break;
1107
1121
  default: exhaustiveGuard(type);
1108
1122
  }
@@ -1112,57 +1126,77 @@ var MockTableClient = class {
1112
1126
  }).match(noop, (error) => {
1113
1127
  throw error;
1114
1128
  });
1115
- return {
1116
- getResponseForEntity: noop,
1129
+ return Promise.resolve({
1130
+ getResponseForEntity: () => void 0,
1117
1131
  status: 202,
1118
1132
  subResponses: []
1119
- };
1133
+ });
1120
1134
  }
1121
- updateEntity(entity, mode = "Merge") {
1135
+ updateEntity(entity, mode = "Merge", options) {
1136
+ this.#applyUpdate(entity, mode, options?.etag);
1137
+ return Promise.resolve({
1138
+ date: /* @__PURE__ */ new Date(),
1139
+ etag: this.#getEtag()
1140
+ });
1141
+ }
1142
+ upsertEntity(entity, mode = "Merge") {
1143
+ this.#applyUpsert(entity, mode);
1144
+ return Promise.resolve({
1145
+ date: /* @__PURE__ */ new Date(),
1146
+ etag: this.#getEtag()
1147
+ });
1148
+ }
1149
+ #applyCreate(entity) {
1150
+ const key = this.#getCompositeKey(entity.partitionKey, entity.rowKey);
1151
+ if (this.table.has(key)) throw new MockRestError("The specified entity already exists.", 409);
1152
+ this.table.set(key, {
1153
+ ...entity,
1154
+ etag: this.#getEtag()
1155
+ });
1156
+ }
1157
+ #applyDelete(partitionKey, rowKey) {
1158
+ const key = this.#getCompositeKey(partitionKey, rowKey);
1159
+ if (!this.table.has(key)) throw new MockRestError("The specified resource does not exist.", 404);
1160
+ this.table.delete(key);
1161
+ }
1162
+ #applyUpdate(entity, mode = "Merge", etag) {
1122
1163
  const key = this.#getCompositeKey(entity.partitionKey, entity.rowKey);
1123
1164
  const existingEntity = this.table.get(key);
1124
1165
  if (!existingEntity) throw new MockRestError("The specified resource does not exist.", 404);
1125
- else if (mode === "Merge") return this.#mergeEntity(key, existingEntity, entity);
1126
- else {
1127
- this.table.set(key, entity);
1128
- return Promise.resolve({
1129
- date: /* @__PURE__ */ new Date(),
1130
- etag: this.#getEtag()
1131
- });
1132
- }
1166
+ else if (etag !== void 0 && etag !== "*" && etag !== existingEntity.etag) throw new MockRestError("The update condition specified in the request was not satisfied.", 412);
1167
+ else if (mode === "Merge") this.table.set(key, {
1168
+ ...existingEntity,
1169
+ ...entity,
1170
+ etag: this.#getEtag()
1171
+ });
1172
+ else this.table.set(key, {
1173
+ ...entity,
1174
+ etag: this.#getEtag()
1175
+ });
1133
1176
  }
1134
- upsertEntity(entity, mode = "Merge") {
1177
+ #applyUpsert(entity, mode = "Merge") {
1135
1178
  const key = this.#getCompositeKey(entity.partitionKey, entity.rowKey);
1136
1179
  const existingEntity = this.table.get(key);
1137
- if (existingEntity && mode === "Merge") return this.#mergeEntity(key, existingEntity, entity);
1138
- else {
1139
- this.table.set(key, entity);
1140
- return Promise.resolve({
1141
- date: /* @__PURE__ */ new Date(),
1142
- etag: this.#getEtag()
1143
- });
1144
- }
1180
+ if (existingEntity && mode === "Merge") this.table.set(key, {
1181
+ ...existingEntity,
1182
+ ...entity,
1183
+ etag: this.#getEtag()
1184
+ });
1185
+ else this.table.set(key, {
1186
+ ...entity,
1187
+ etag: this.#getEtag()
1188
+ });
1145
1189
  }
1146
1190
  #getCompositeKey(partitionKey, rowKey) {
1147
1191
  return `${partitionKey}${ID_SEPARATOR}${rowKey}`;
1148
1192
  }
1149
1193
  #getEtag() {
1150
- return `W/"datetime'${(/* @__PURE__ */ new Date()).toISOString()}'"`;
1151
- }
1152
- #mergeEntity(key, entity, entityToMerge) {
1153
- this.table.set(key, {
1154
- ...entity,
1155
- ...entityToMerge
1156
- });
1157
- return Promise.resolve({
1158
- date: /* @__PURE__ */ new Date(),
1159
- etag: this.#getEtag()
1160
- });
1194
+ return `W/"${crypto.randomUUID()}"`;
1161
1195
  }
1162
1196
  #withMetadata(entity) {
1163
1197
  return {
1164
1198
  ...entity,
1165
- etag: this.#getEtag()
1199
+ etag: entity.etag ?? this.#getEtag()
1166
1200
  };
1167
1201
  }
1168
1202
  };
@@ -1179,4 +1213,4 @@ const applyFilter = (documents, clauses) => {
1179
1213
  const predicate = createFilterPredicate(serializeClauses(clauses));
1180
1214
  return documents.filter((document) => predicate(document));
1181
1215
  };
1182
- export { MOCK_BLOB_BASE_URL, MOCK_QUEUE_BASE_URL, MOCK_SEARCH_BASE_URL, MOCK_TABLE_BASE_URL, MockBlobBatchClient, MockBlobClient, MockBlockBlobClient, MockContainerClient, MockContainerDatabase, MockEventGridDatabase, MockEventGridPublisherClient, MockQueueClient, MockQueueDatabase, MockRestError, MockSearchClient, MockSearchDatabase, MockServiceBusDatabase, MockServiceBusSender, MockTableClient, MockTableDatabase, MockWebPubSubServiceClient, applyFilter, bodyToBuffer, compare, createFilterPredicate, createMockResponse, getAzureErrorXml, getBlobItemXml, getBlobPrefixXml, getListBlobsXml, getMockSasUrl, isNullClause, isReadableStream, toWebResourceLike };
1216
+ export { BLOB_NOT_FOUND_ERROR_CODE, BLOB_NOT_FOUND_MESSAGE, MOCK_BLOB_BASE_URL, MOCK_BLOB_SEEDED_CREATED_ON, MOCK_QUEUE_BASE_URL, MOCK_SEARCH_BASE_URL, MOCK_TABLE_BASE_URL, MockBlobBatchClient, MockBlobClient, MockBlockBlobClient, MockContainerClient, MockContainerCreatedOnDatabase, MockContainerDatabase, MockEventGridDatabase, MockEventGridPublisherClient, MockQueueClient, MockQueueDatabase, MockRestError, MockSearchClient, MockSearchDatabase, MockServiceBusDatabase, MockServiceBusSender, MockTableClient, MockTableDatabase, MockWebPubSubServiceClient, applyFilter, bodyToBuffer, compare, compareByCompositeKey, createFilterPredicate, createMockResponse, getAzureErrorXml, getBlobItemXml, getBlobPrefixXml, getBlobUrl, getBlobUrlParts, getListBlobsSegmentResponse, getListBlobsXml, getMockContainer, getMockContainerCreatedOnKey, getMockQueueMessageItem, getMockSasUrl, isNullClause, isReadableStream, toWebResourceLike };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "azure-mock",
3
- "version": "2.35.0",
3
+ "version": "2.37.2",
4
4
  "description": "A library that contains azure mock classes.",
5
5
  "keywords": [
6
6
  "azure",
@@ -47,15 +47,15 @@
47
47
  "typecheck": "tsgo"
48
48
  },
49
49
  "dependencies": {
50
- "@esposter/db": "2.35.0",
51
- "@esposter/db-schema": "2.35.0",
52
- "@esposter/shared": "2.35.0",
50
+ "@esposter/db": "2.37.2",
51
+ "@esposter/db-schema": "2.37.2",
52
+ "@esposter/shared": "2.37.2",
53
53
  "fast-deep-equal": "^3.1.3"
54
54
  },
55
55
  "devDependencies": {
56
- "@esposter/configuration": "2.35.0",
57
- "ctix": "^2.8.1",
58
- "rolldown": "^1.1.5",
56
+ "@esposter/configuration": "2.37.2",
57
+ "ctix": "^2.8.2",
58
+ "rolldown": "^1.2.4",
59
59
  "type-fest": "^5.8.0",
60
60
  "typedoc": "^0.28.20",
61
61
  "vitest": "^4.1.10"
@@ -70,5 +70,5 @@
70
70
  "@azure/storage-blob": "^12.33.0",
71
71
  "@azure/storage-queue": "^12.31.0"
72
72
  },
73
- "gitHead": "3cd4a45d586379867ceba91c23052d755d95ff8f"
73
+ "gitHead": "4d775dbd8289e6caaee48f08038b65fddb4babbc"
74
74
  }