azure-mock 2.27.0 → 2.29.0

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
@@ -47,6 +47,17 @@ const entity = await mockTableClient.getEntity("pk", "rk");
47
47
 
48
48
  Replace real Azure clients with their mock equivalents by swapping them in your test setup or local `.env` configuration.
49
49
 
50
+ ### Commands
51
+
52
+ Run from `packages/azure-mock/`:
53
+
54
+ ```bash
55
+ pnpm build # compile to dist/
56
+ pnpm test # vitest watch mode (coverage is run from the repo root)
57
+ pnpm lint:fix # auto-fix lint
58
+ pnpm typecheck # type check
59
+ ```
60
+
50
61
  ## <a name="license">⚖️ License</a>
51
62
 
52
63
  This project is licensed under the [Apache-2.0 license](https://github.com/Esposter/Esposter/blob/main/LICENSE).
package/dist/index.d.ts CHANGED
@@ -6,10 +6,12 @@ import { BinaryOperator, Clause } from "@esposter/db-schema";
6
6
  import { EventGridEvent, EventGridPublisherClient } from "@azure/eventgrid";
7
7
  import { CreateTableEntityResponse, GetAccessPolicyResponse, GetTableEntityResponse, ListTableEntitiesOptions, TableClient, TableDeleteEntityHeaders, TableEntity, TableEntityResult, TableEntityResultPage, TableMergeEntityHeaders, TableSetAccessPolicyHeaders, TableTransactionResponse, UpdateMode } from "@azure/data-tables";
8
8
  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";
9
+ import { AutocompleteResult, CountDocumentsOptions, DeleteDocumentsOptions, GetDocumentOptions, IndexDocumentsBatch, IndexDocumentsOptions, IndexDocumentsResult, MergeDocumentsOptions, MergeOrUploadDocumentsOptions, NarrowedModel, SearchClient, SearchDocumentsResult, SearchOptions, SelectFields, SuggestDocumentsResult, SuggestOptions, UploadDocumentsOptions } from "@azure/search-documents";
9
10
 
10
11
  //#region src/constants.d.ts
11
12
  declare const MOCK_BLOB_BASE_URL = "https://mockaccount.blob.core.windows.net";
12
13
  declare const MOCK_QUEUE_BASE_URL = "https://mockaccount.queue.core.windows.net";
14
+ declare const MOCK_SEARCH_BASE_URL = "https://mockaccount.search.windows.net";
13
15
  declare const MOCK_TABLE_BASE_URL = "https://mockaccount.table.core.windows.net";
14
16
  //#endregion
15
17
  //#region src/models/MockRestError.d.ts
@@ -61,6 +63,9 @@ declare const MockEventGridDatabase: Map<string, EventGridEvent<unknown>[]>;
61
63
  //#region src/store/MockQueueDatabase.d.ts
62
64
  declare const MockQueueDatabase: Map<string, string[]>;
63
65
  //#endregion
66
+ //#region src/store/MockSearchDatabase.d.ts
67
+ declare const MockSearchDatabase: Map<string, object[]>;
68
+ //#endregion
64
69
  //#region src/store/MockTableDatabase.d.ts
65
70
  declare const MockTableDatabase: Map<string, Map<string, TableEntity>>;
66
71
  //#endregion
@@ -939,6 +944,7 @@ declare class MockBlockBlobClient extends MockBlobClient implements Except<Block
939
944
  * const content = await blockBlobClient.downloadToBuffer();
940
945
  */
941
946
  declare class MockContainerClient implements Except<ContainerClient, "accountName"> {
947
+ #private;
942
948
  connectionString: string;
943
949
  containerName: string;
944
950
  credential: AnonymousCredential;
@@ -973,18 +979,18 @@ declare class MockContainerClient implements Except<ContainerClient, "accountNam
973
979
  blockBlobClient: BlockBlobClient;
974
980
  response: BlockBlobUploadResponse;
975
981
  }>;
976
- private getBlobHierarchyItemIterator;
977
- private getBlobItemIterator;
978
982
  }
979
983
  //#endregion
980
984
  //#region src/models/eventGrid/MockEventGridPublisherClient.d.ts
981
985
  /**
982
986
  * An in-memory mock of the Azure EventGridPublisherClient.
983
987
  * It uses a Map to simulate event grid storage and correctly implements the EventGridPublisherClient interface.
988
+ * `client` is excluded because it is a private member of the real class and cannot be satisfied structurally.
984
989
  */
985
- declare class MockEventGridPublisherClient implements Except<EventGridPublisherClient<"EventGrid">, "apiVersion" | "endpointUrl"> {
986
- endpoint: string;
987
- topicType: "EventGrid";
990
+ declare class MockEventGridPublisherClient implements Except<EventGridPublisherClient<"EventGrid">, "apiVersion"> {
991
+ readonly endpointUrl: string;
992
+ readonly inputSchema: "EventGrid";
993
+ readonly topicType: "EventGrid";
988
994
  constructor(endpoint: string, topicType: "EventGrid");
989
995
  send(newEvents: EventGridEvent<unknown>[]): Promise<void>;
990
996
  }
@@ -1029,6 +1035,36 @@ declare class MockQueueClient implements Except<QueueClient, "accountName"> {
1029
1035
  updateMessage(): Promise<QueueUpdateMessageResponse>;
1030
1036
  }
1031
1037
  //#endregion
1038
+ //#region src/models/search/MockSearchClient.d.ts
1039
+ /**
1040
+ * An in-memory mock of the Azure SearchClient.
1041
+ * It uses a Map to simulate the search index and applies the same OData filtering as the other mock clients.
1042
+ *
1043
+ * @example
1044
+ * const mockSearchClient = new MockSearchClient(SearchIndex.Messages);
1045
+ * await mockSearchClient.uploadDocuments([{ ... }]);
1046
+ * const { count, results } = await mockSearchClient.search("*", { filter, includeTotalCount: true });
1047
+ */
1048
+ declare class MockSearchClient<TModel extends object = Record<string, unknown>> implements Except<SearchClient<TModel>, "pipeline"> {
1049
+ readonly apiVersion: string;
1050
+ readonly endpoint: string;
1051
+ readonly indexName: string;
1052
+ readonly serviceVersion: string;
1053
+ get documents(): MapValue<typeof MockSearchDatabase>;
1054
+ constructor(indexName: string);
1055
+ autocomplete(): Promise<AutocompleteResult>;
1056
+ deleteDocuments(documents: TModel[], options?: DeleteDocumentsOptions): Promise<IndexDocumentsResult>;
1057
+ deleteDocuments(keyName: keyof TModel, keyValues: string[], options?: DeleteDocumentsOptions): Promise<IndexDocumentsResult>;
1058
+ getDocument<TFields extends SelectFields<TModel>>(_key: string, _options?: GetDocumentOptions<TModel, TFields>): Promise<NarrowedModel<TModel, TFields>>;
1059
+ getDocumentsCount(_options?: CountDocumentsOptions): Promise<number>;
1060
+ indexDocuments(_batch: IndexDocumentsBatch<TModel>, _options?: IndexDocumentsOptions): Promise<IndexDocumentsResult>;
1061
+ mergeDocuments(_documents: TModel[], _options?: MergeDocumentsOptions): Promise<IndexDocumentsResult>;
1062
+ mergeOrUploadDocuments(_documents: TModel[], _options?: MergeOrUploadDocumentsOptions): Promise<IndexDocumentsResult>;
1063
+ search<TFields extends SelectFields<TModel>>(searchText?: string, options?: SearchOptions<TModel, TFields>): Promise<SearchDocumentsResult<TModel, TFields>>;
1064
+ suggest<TFields extends SelectFields<TModel> = never>(_searchText: string, _suggesterName: string, _options?: SuggestOptions<TModel, TFields>): Promise<SuggestDocumentsResult<TModel, TFields>>;
1065
+ uploadDocuments(documents: TModel[], _options?: UploadDocumentsOptions): Promise<IndexDocumentsResult>;
1066
+ }
1067
+ //#endregion
1032
1068
  //#region src/models/table/MockTableClient.d.ts
1033
1069
  /**
1034
1070
  * An in-memory mock of the Azure TableClient.
@@ -1039,7 +1075,9 @@ declare class MockQueueClient implements Except<QueueClient, "accountName"> {
1039
1075
  * await mockTableClient.createEntity({ partitionKey: "partitionKey", rowKey: "rowKey" });
1040
1076
  * const entity = await mockTableClient.getEntity("partitionKey", "rowKey");
1041
1077
  */
1042
- declare class MockTableClient implements Except<TableClient, "pipeline"> {
1078
+ declare class MockTableClient<TEntity extends TableEntity = TableEntity> implements Except<TableClient, "pipeline"> {
1079
+ #private;
1080
+ entityType: TEntity;
1043
1081
  tableName: string;
1044
1082
  url: string;
1045
1083
  get table(): MapValue<typeof MockTableDatabase>;
@@ -1055,10 +1093,17 @@ declare class MockTableClient implements Except<TableClient, "pipeline"> {
1055
1093
  submitTransaction(actions: Parameters<TableClient["submitTransaction"]>[0]): Promise<TableTransactionResponse>;
1056
1094
  updateEntity<T extends object>(entity: TableEntity<T>, mode?: UpdateMode): Promise<TableMergeEntityHeaders>;
1057
1095
  upsertEntity<T extends object>(entity: TableEntity<T>, mode?: UpdateMode): Promise<TableMergeEntityHeaders>;
1058
- private getCompositeKey;
1059
- private getEtag;
1060
- private mergeEntity;
1061
- private withMetadata;
1096
+ }
1097
+ //#endregion
1098
+ //#region src/models/webPubSub/MockWebPubSubServiceClient.d.ts
1099
+ /**
1100
+ * A minimal mock of the Azure WebPubSubServiceClient for testing purposes.
1101
+ * Only implements `group().sendToAll` — the only method used in azure-functions tests.
1102
+ */
1103
+ declare class MockWebPubSubServiceClient {
1104
+ group(_groupName: string): {
1105
+ sendToAll: () => Promise<void>;
1106
+ };
1062
1107
  }
1063
1108
  //#endregion
1064
1109
  //#region src/services/container/bodyToBuffer.d.ts
@@ -1097,16 +1142,16 @@ declare const isReadableStream: (value: unknown) => value is NodeJS.ReadableStre
1097
1142
  //#region src/services/container/toWebResourceLike.d.ts
1098
1143
  declare const toWebResourceLike: (request: PipelineRequest) => WebResourceLike;
1099
1144
  //#endregion
1100
- //#region src/services/table/applyTableFilter.d.ts
1101
- declare const applyTableFilter: <T extends Record<string, unknown>>(entities: TableEntity<T>[], clauses: Clause<T>[]) => TableEntity<T>[];
1145
+ //#region src/services/filter/applyFilter.d.ts
1146
+ declare const applyFilter: <T extends Record<string, unknown>>(documents: T[], clauses: Clause<T>[]) => T[];
1102
1147
  //#endregion
1103
- //#region src/services/table/compare.d.ts
1148
+ //#region src/services/filter/compare.d.ts
1104
1149
  declare const compare: <T>(operator: BinaryOperator, leftHandSide: T, rightHandSide: null | T) => boolean;
1105
1150
  //#endregion
1106
- //#region src/services/table/createTableFilterPredicate.d.ts
1107
- declare const createTableFilterPredicate: <T extends Record<string, unknown>>(filter: string) => ((entity: TableEntity<T>) => boolean);
1151
+ //#region src/services/filter/createFilterPredicate.d.ts
1152
+ declare const createFilterPredicate: (filter: string) => ((document: Record<string, unknown>) => boolean);
1108
1153
  //#endregion
1109
- //#region src/services/table/isTableNullClause.d.ts
1110
- declare const isTableNullClause: (clause: Clause<Record<string, unknown>>) => boolean;
1154
+ //#region src/services/filter/isNullClause.d.ts
1155
+ declare const isNullClause: (clause: Clause<Record<string, unknown>>) => boolean;
1111
1156
  //#endregion
1112
- export { BlobHierarchyItem, MOCK_BLOB_BASE_URL, MOCK_QUEUE_BASE_URL, MOCK_TABLE_BASE_URL, MockBlobBatchClient, MockBlobClient, MockBlockBlobClient, MockContainerClient, MockContainerDatabase, MockEventGridDatabase, MockEventGridPublisherClient, MockQueueClient, MockQueueDatabase, MockRestError, MockTableClient, MockTableDatabase, PageSettings, PagedAsyncIterableIterator, applyTableFilter, bodyToBuffer, compare, createTableFilterPredicate, getAzureErrorXml, getBlobItemXml, getBlobPrefixXml, getListBlobsXml, isReadableStream, isTableNullClause, toWebResourceLike };
1157
+ 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, MockRestError, MockSearchClient, MockSearchDatabase, MockTableClient, MockTableDatabase, MockWebPubSubServiceClient, PageSettings, PagedAsyncIterableIterator, applyFilter, bodyToBuffer, compare, createFilterPredicate, getAzureErrorXml, getBlobItemXml, getBlobPrefixXml, getListBlobsXml, isNullClause, isReadableStream, toWebResourceLike };
package/dist/index.js CHANGED
@@ -3,8 +3,8 @@ import { toHttpHeadersLike } from "@azure/core-http-compat";
3
3
  import { createHttpHeaders, createPipelineRequest } from "@azure/core-rest-pipeline";
4
4
  import { AnonymousCredential } from "@azure/storage-blob";
5
5
  import { Readable } from "node:stream";
6
+ import { MAX_QUEUE_VISIBILITY_TIMEOUT_MS, deserializeClause, deserializeKey, getTableNullClause, serializeClauses } from "@esposter/db";
6
7
  import { BinaryOperator } from "@esposter/db-schema";
7
- import { deserializeClause, getTableNullClause, serializeClauses } from "@esposter/db";
8
8
  //#region \0rolldown/runtime.js
9
9
  var __create = Object.create;
10
10
  var __defProp = Object.defineProperty;
@@ -31,6 +31,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
31
31
  //#region src/constants.ts
32
32
  const MOCK_BLOB_BASE_URL = "https://mockaccount.blob.core.windows.net";
33
33
  const MOCK_QUEUE_BASE_URL = "https://mockaccount.queue.core.windows.net";
34
+ const MOCK_SEARCH_BASE_URL = "https://mockaccount.search.windows.net";
34
35
  const MOCK_TABLE_BASE_URL = "https://mockaccount.table.core.windows.net";
35
36
  //#endregion
36
37
  //#region src/models/MockRestError.ts
@@ -52,6 +53,9 @@ const MockEventGridDatabase = /* @__PURE__ */ new Map();
52
53
  //#region src/store/MockQueueDatabase.ts
53
54
  const MockQueueDatabase = /* @__PURE__ */ new Map();
54
55
  //#endregion
56
+ //#region src/store/MockSearchDatabase.ts
57
+ const MockSearchDatabase = /* @__PURE__ */ new Map();
58
+ //#endregion
55
59
  //#region src/store/MockTableDatabase.ts
56
60
  const MockTableDatabase = /* @__PURE__ */ new Map();
57
61
  //#endregion
@@ -560,7 +564,7 @@ var MockContainerClient = class {
560
564
  throw new Error("Method not implemented.");
561
565
  }
562
566
  listBlobsByHierarchy(delimiter, options) {
563
- const blobHierarchyItemIterator = this.getBlobHierarchyItemIterator(delimiter, options);
567
+ const blobHierarchyItemIterator = this.#getBlobHierarchyItemIterator(delimiter, options);
564
568
  return {
565
569
  byPage: () => async function* () {
566
570
  const allBlobItems = [];
@@ -603,7 +607,7 @@ var MockContainerClient = class {
603
607
  };
604
608
  }
605
609
  listBlobsFlat() {
606
- const blobItemIterator = this.getBlobItemIterator();
610
+ const blobItemIterator = this.#getBlobItemIterator();
607
611
  return {
608
612
  byPage: () => async function* () {
609
613
  const allBlobItems = [];
@@ -653,7 +657,7 @@ var MockContainerClient = class {
653
657
  response: await blockBlobClient.upload(body, contentLength)
654
658
  };
655
659
  }
656
- async *getBlobHierarchyItemIterator(delimiter, options) {
660
+ async *#getBlobHierarchyItemIterator(delimiter, options) {
657
661
  const prefix = options?.prefix ?? "";
658
662
  const uniqueSubprefixes = /* @__PURE__ */ new Set();
659
663
  const blobsInCurrentLevel = [];
@@ -689,7 +693,7 @@ var MockContainerClient = class {
689
693
  ...blobItem
690
694
  });
691
695
  }
692
- async *getBlobItemIterator() {
696
+ async *#getBlobItemIterator() {
693
697
  for (const [name, buffer] of this.container.entries()) yield await Promise.resolve({
694
698
  deleted: false,
695
699
  name,
@@ -711,18 +715,21 @@ var MockContainerClient = class {
711
715
  /**
712
716
  * An in-memory mock of the Azure EventGridPublisherClient.
713
717
  * It uses a Map to simulate event grid storage and correctly implements the EventGridPublisherClient interface.
718
+ * `client` is excluded because it is a private member of the real class and cannot be satisfied structurally.
714
719
  */
715
720
  var MockEventGridPublisherClient = class {
716
- endpoint;
721
+ endpointUrl;
722
+ inputSchema;
717
723
  topicType;
718
724
  constructor(endpoint, topicType) {
719
- this.endpoint = endpoint;
725
+ this.endpointUrl = endpoint;
720
726
  this.topicType = topicType;
727
+ this.inputSchema = topicType;
721
728
  }
722
729
  send(newEvents) {
723
- const events = MockEventGridDatabase.get(this.endpoint) ?? [];
730
+ const events = MockEventGridDatabase.get(this.endpointUrl) ?? [];
724
731
  events.push(...newEvents);
725
- MockEventGridDatabase.set(this.endpoint, events);
732
+ MockEventGridDatabase.set(this.endpointUrl, events);
726
733
  return Promise.resolve();
727
734
  }
728
735
  };
@@ -804,7 +811,7 @@ var MockQueueClient = class {
804
811
  peekMessages(_options) {
805
812
  const peekedMessageItems = this.queue.map((text) => ({
806
813
  dequeueCount: 0,
807
- expiresOn: new Date(Date.now() + 10080 * 60 * 1e3),
814
+ expiresOn: new Date(Date.now() + MAX_QUEUE_VISIBILITY_TIMEOUT_MS),
808
815
  insertedOn: /* @__PURE__ */ new Date(),
809
816
  messageId: crypto.randomUUID(),
810
817
  messageText: text
@@ -824,7 +831,7 @@ var MockQueueClient = class {
824
831
  receiveMessages(_options) {
825
832
  const receivedMessageItems = this.queue.splice(0).map((text) => ({
826
833
  dequeueCount: 1,
827
- expiresOn: new Date(Date.now() + 10080 * 60 * 1e3),
834
+ expiresOn: new Date(Date.now() + MAX_QUEUE_VISIBILITY_TIMEOUT_MS),
828
835
  insertedOn: /* @__PURE__ */ new Date(),
829
836
  messageId: crypto.randomUUID(),
830
837
  messageText: text,
@@ -846,7 +853,7 @@ var MockQueueClient = class {
846
853
  sendMessage(messageText, _options) {
847
854
  this.queue.push(messageText);
848
855
  const now = /* @__PURE__ */ new Date();
849
- const expiresOn = new Date(now.getTime() + 10080 * 60 * 1e3);
856
+ const expiresOn = new Date(now.getTime() + MAX_QUEUE_VISIBILITY_TIMEOUT_MS);
850
857
  const insertedOn = now;
851
858
  const messageId = crypto.randomUUID();
852
859
  const nextVisibleOn = now;
@@ -885,7 +892,7 @@ var MockQueueClient = class {
885
892
  }
886
893
  };
887
894
  //#endregion
888
- //#region src/services/table/compare.ts
895
+ //#region src/services/filter/compare.ts
889
896
  const compare = (operator, leftHandSide, rightHandSide) => {
890
897
  if (rightHandSide === null || rightHandSide === void 0) {
891
898
  if (operator !== BinaryOperator.eq) throw new InvalidOperationError(Operation.Read, compare.name, JSON.stringify({
@@ -893,7 +900,7 @@ const compare = (operator, leftHandSide, rightHandSide) => {
893
900
  operator,
894
901
  rightHandSide
895
902
  }));
896
- return leftHandSide === rightHandSide;
903
+ return leftHandSide === null || leftHandSide === void 0;
897
904
  }
898
905
  switch (operator) {
899
906
  case BinaryOperator.eq: return leftHandSide === rightHandSide;
@@ -906,7 +913,7 @@ const compare = (operator, leftHandSide, rightHandSide) => {
906
913
  }
907
914
  };
908
915
  //#endregion
909
- //#region src/services/table/isTableNullClause.ts
916
+ //#region src/services/filter/isNullClause.ts
910
917
  var import_fast_deep_equal = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
911
918
  module.exports = function equal(a, b) {
912
919
  if (a === b) return true;
@@ -935,21 +942,21 @@ var import_fast_deep_equal = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJS
935
942
  return a !== a && b !== b;
936
943
  };
937
944
  })))(), 1);
938
- const isTableNullClause = (clause) => {
945
+ const isNullClause = (clause) => {
939
946
  return (0, import_fast_deep_equal.default)(clause, getTableNullClause(clause.key));
940
947
  };
941
948
  //#endregion
942
- //#region src/services/table/createTableFilterPredicate.ts
943
- const createTableFilterPredicate = (filter) => {
949
+ //#region src/services/filter/createFilterPredicate.ts
950
+ const createFilterPredicate = (filter) => {
944
951
  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));
945
- return (entity) => {
952
+ return (document) => {
946
953
  for (const orGroup of orGroups) {
947
954
  let isGroupMatched = false;
948
955
  for (const group of orGroup) {
949
956
  const clause = deserializeClause(group);
950
- const value = takeOne(entity, clause.key);
951
- let isMatched = false;
952
- if (isTableNullClause(clause)) isMatched = compare(BinaryOperator.eq, value, null);
957
+ const value = takeOne(document, clause.key);
958
+ let isMatched;
959
+ if (isNullClause(clause)) isMatched = compare(BinaryOperator.eq, value, null);
953
960
  else {
954
961
  const comparisonResult = compare(clause.operator, value, clause.value);
955
962
  isMatched = clause.not ? !comparisonResult : comparisonResult;
@@ -965,6 +972,112 @@ const createTableFilterPredicate = (filter) => {
965
972
  };
966
973
  };
967
974
  //#endregion
975
+ //#region src/models/search/MockSearchClient.ts
976
+ const toComparable = (value) => {
977
+ if (value instanceof Date) return value.getTime();
978
+ else if (typeof value === "number" || typeof value === "string") return value;
979
+ else return String(value);
980
+ };
981
+ const compareValues = (leftHandSide, rightHandSide) => {
982
+ const left = toComparable(leftHandSide);
983
+ const right = toComparable(rightHandSide);
984
+ if (typeof left === "number" && typeof right === "number") return left - right;
985
+ return String(left).localeCompare(String(right));
986
+ };
987
+ const sortDocuments = (documents, orderBy) => [...documents].toSorted((leftHandSide, rightHandSide) => {
988
+ for (const clause of orderBy) {
989
+ const [field = "", direction = "asc"] = clause.split(/\s+/u);
990
+ const key = deserializeKey(field);
991
+ const comparison = compareValues(leftHandSide[key], rightHandSide[key]);
992
+ if (comparison !== 0) return direction === "desc" ? -comparison : comparison;
993
+ }
994
+ return 0;
995
+ });
996
+ const getSearchFieldValues = (value, pathSegments) => {
997
+ if (pathSegments.length === 0) return [value];
998
+ if (Array.isArray(value)) return value.flatMap((item) => getSearchFieldValues(item, pathSegments));
999
+ if (typeof value !== "object" || value === null) return [];
1000
+ const [field = "", ...remainingPathSegments] = pathSegments;
1001
+ return getSearchFieldValues(value[deserializeKey(field)], remainingPathSegments);
1002
+ };
1003
+ const searchDocuments = (documents, searchText, searchFields) => {
1004
+ if (!searchText || searchText === "*") return documents;
1005
+ const normalizedSearchText = searchText.toLocaleLowerCase();
1006
+ 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))));
1007
+ };
1008
+ /**
1009
+ * An in-memory mock of the Azure SearchClient.
1010
+ * It uses a Map to simulate the search index and applies the same OData filtering as the other mock clients.
1011
+ *
1012
+ * @example
1013
+ * const mockSearchClient = new MockSearchClient(SearchIndex.Messages);
1014
+ * await mockSearchClient.uploadDocuments([{ ... }]);
1015
+ * const { count, results } = await mockSearchClient.search("*", { filter, includeTotalCount: true });
1016
+ */
1017
+ var MockSearchClient = class {
1018
+ apiVersion = "";
1019
+ endpoint = MOCK_SEARCH_BASE_URL;
1020
+ indexName;
1021
+ serviceVersion = "";
1022
+ get documents() {
1023
+ let documents = MockSearchDatabase.get(this.indexName);
1024
+ if (!documents) {
1025
+ documents = [];
1026
+ MockSearchDatabase.set(this.indexName, documents);
1027
+ }
1028
+ return documents;
1029
+ }
1030
+ constructor(indexName) {
1031
+ this.indexName = indexName;
1032
+ }
1033
+ autocomplete() {
1034
+ throw new Error("Method not implemented.");
1035
+ }
1036
+ deleteDocuments() {
1037
+ throw new Error("Method not implemented.");
1038
+ }
1039
+ getDocument(_key, _options) {
1040
+ throw new Error("Method not implemented.");
1041
+ }
1042
+ getDocumentsCount(_options) {
1043
+ return Promise.resolve(this.documents.length);
1044
+ }
1045
+ indexDocuments(_batch, _options) {
1046
+ throw new Error("Method not implemented.");
1047
+ }
1048
+ mergeDocuments(_documents, _options) {
1049
+ throw new Error("Method not implemented.");
1050
+ }
1051
+ mergeOrUploadDocuments(_documents, _options) {
1052
+ throw new Error("Method not implemented.");
1053
+ }
1054
+ search(searchText, options) {
1055
+ const { filter, includeTotalCount, orderBy, searchFields, skip = 0, top } = options ?? {};
1056
+ let documents = searchDocuments(this.documents, searchText, searchFields);
1057
+ if (filter) {
1058
+ const predicate = createFilterPredicate(filter);
1059
+ documents = documents.filter((document) => predicate(document));
1060
+ }
1061
+ if (orderBy) documents = sortDocuments(documents, orderBy);
1062
+ const count = documents.length;
1063
+ const paginatedDocuments = documents.slice(skip, top === void 0 ? void 0 : skip + top);
1064
+ const results = { async *[Symbol.asyncIterator]() {
1065
+ for (const document of paginatedDocuments) yield { document };
1066
+ } };
1067
+ return Promise.resolve({
1068
+ ...includeTotalCount ? { count } : {},
1069
+ results
1070
+ });
1071
+ }
1072
+ suggest(_searchText, _suggesterName, _options) {
1073
+ throw new Error("Method not implemented.");
1074
+ }
1075
+ uploadDocuments(documents, _options) {
1076
+ this.documents.push(...documents);
1077
+ return Promise.resolve({ results: [] });
1078
+ }
1079
+ };
1080
+ //#endregion
968
1081
  //#region src/models/table/MockTableClient.ts
969
1082
  /**
970
1083
  * An in-memory mock of the Azure TableClient.
@@ -991,19 +1104,19 @@ var MockTableClient = class {
991
1104
  this.url = `${MOCK_TABLE_BASE_URL}/${this.tableName}`;
992
1105
  }
993
1106
  createEntity(entity) {
994
- const key = this.getCompositeKey(entity.partitionKey, entity.rowKey);
1107
+ const key = this.#getCompositeKey(entity.partitionKey, entity.rowKey);
995
1108
  if (this.table.has(key)) throw new MockRestError("The specified entity already exists.", 409);
996
1109
  this.table.set(key, entity);
997
1110
  return Promise.resolve({
998
1111
  date: /* @__PURE__ */ new Date(),
999
- etag: this.getEtag()
1112
+ etag: this.#getEtag()
1000
1113
  });
1001
1114
  }
1002
1115
  createTable() {
1003
1116
  throw new Error("Method not implemented.");
1004
1117
  }
1005
1118
  deleteEntity(partitionKey, rowKey) {
1006
- const key = this.getCompositeKey(partitionKey, rowKey);
1119
+ const key = this.#getCompositeKey(partitionKey, rowKey);
1007
1120
  if (!this.table.has(key)) throw new MockRestError("The specified resource does not exist.", 404);
1008
1121
  this.table.delete(key);
1009
1122
  return Promise.resolve({});
@@ -1015,19 +1128,20 @@ var MockTableClient = class {
1015
1128
  throw new Error("Method not implemented.");
1016
1129
  }
1017
1130
  getEntity(partitionKey, rowKey) {
1018
- const key = this.getCompositeKey(partitionKey, rowKey);
1131
+ const key = this.#getCompositeKey(partitionKey, rowKey);
1019
1132
  const entity = this.table.get(key);
1020
1133
  if (!entity) throw new MockRestError("The specified resource does not exist.", 404);
1021
1134
  return Promise.resolve({
1022
1135
  ...entity,
1023
- etag: this.getEtag()
1136
+ etag: this.#getEtag()
1024
1137
  });
1025
1138
  }
1026
1139
  listEntities(options) {
1027
- const withMetadata = this.withMetadata.bind(this);
1140
+ const withMetadata = this.#withMetadata.bind(this);
1028
1141
  const filter = options?.queryOptions?.filter;
1029
1142
  const tableEntities = [...this.table.values()];
1030
- const resultTableEntities = filter ? tableEntities.filter((e) => createTableFilterPredicate(filter)(e)) : tableEntities;
1143
+ const predicate = filter ? createFilterPredicate(filter) : void 0;
1144
+ const resultTableEntities = predicate ? tableEntities.filter((e) => predicate(e)) : tableEntities;
1031
1145
  return {
1032
1146
  byPage: ({ maxPageSize } = {}) => (async function* (entities) {
1033
1147
  if (maxPageSize !== void 0 && maxPageSize <= 0) throw new RangeError("maxPageSize must be greater than 0.");
@@ -1084,58 +1198,69 @@ var MockTableClient = class {
1084
1198
  };
1085
1199
  }
1086
1200
  updateEntity(entity, mode = "Merge") {
1087
- const key = this.getCompositeKey(entity.partitionKey, entity.rowKey);
1201
+ const key = this.#getCompositeKey(entity.partitionKey, entity.rowKey);
1088
1202
  const existingEntity = this.table.get(key);
1089
1203
  if (!existingEntity) throw new MockRestError("The specified resource does not exist.", 404);
1090
- else if (mode === "Merge") return this.mergeEntity(key, existingEntity, entity);
1204
+ else if (mode === "Merge") return this.#mergeEntity(key, existingEntity, entity);
1091
1205
  else {
1092
1206
  this.table.set(key, entity);
1093
1207
  return Promise.resolve({
1094
1208
  date: /* @__PURE__ */ new Date(),
1095
- etag: this.getEtag()
1209
+ etag: this.#getEtag()
1096
1210
  });
1097
1211
  }
1098
1212
  }
1099
1213
  upsertEntity(entity, mode = "Merge") {
1100
- const key = this.getCompositeKey(entity.partitionKey, entity.rowKey);
1214
+ const key = this.#getCompositeKey(entity.partitionKey, entity.rowKey);
1101
1215
  const existingEntity = this.table.get(key);
1102
- if (existingEntity && mode === "Merge") return this.mergeEntity(key, existingEntity, entity);
1216
+ if (existingEntity && mode === "Merge") return this.#mergeEntity(key, existingEntity, entity);
1103
1217
  else {
1104
1218
  this.table.set(key, entity);
1105
1219
  return Promise.resolve({
1106
1220
  date: /* @__PURE__ */ new Date(),
1107
- etag: this.getEtag()
1221
+ etag: this.#getEtag()
1108
1222
  });
1109
1223
  }
1110
1224
  }
1111
- getCompositeKey(partitionKey, rowKey) {
1225
+ #getCompositeKey(partitionKey, rowKey) {
1112
1226
  return `${partitionKey}${ID_SEPARATOR}${rowKey}`;
1113
1227
  }
1114
- getEtag() {
1228
+ #getEtag() {
1115
1229
  return `W/"datetime'${(/* @__PURE__ */ new Date()).toISOString()}'"`;
1116
1230
  }
1117
- mergeEntity(key, entity, entityToMerge) {
1231
+ #mergeEntity(key, entity, entityToMerge) {
1118
1232
  this.table.set(key, {
1119
1233
  ...entity,
1120
1234
  ...entityToMerge
1121
1235
  });
1122
1236
  return Promise.resolve({
1123
1237
  date: /* @__PURE__ */ new Date(),
1124
- etag: this.getEtag()
1238
+ etag: this.#getEtag()
1125
1239
  });
1126
1240
  }
1127
- withMetadata(entity) {
1241
+ #withMetadata(entity) {
1128
1242
  return {
1129
1243
  ...entity,
1130
- etag: this.getEtag()
1244
+ etag: this.#getEtag()
1131
1245
  };
1132
1246
  }
1133
1247
  };
1134
1248
  //#endregion
1135
- //#region src/services/table/applyTableFilter.ts
1136
- const applyTableFilter = (entities, clauses) => {
1137
- const predicate = createTableFilterPredicate(serializeClauses(clauses));
1138
- return entities.filter((e) => predicate(e));
1249
+ //#region src/models/webPubSub/MockWebPubSubServiceClient.ts
1250
+ /**
1251
+ * A minimal mock of the Azure WebPubSubServiceClient for testing purposes.
1252
+ * Only implements `group().sendToAll` the only method used in azure-functions tests.
1253
+ */
1254
+ var MockWebPubSubServiceClient = class {
1255
+ group(_groupName) {
1256
+ return { sendToAll: () => Promise.resolve() };
1257
+ }
1258
+ };
1259
+ //#endregion
1260
+ //#region src/services/filter/applyFilter.ts
1261
+ const applyFilter = (documents, clauses) => {
1262
+ const predicate = createFilterPredicate(serializeClauses(clauses));
1263
+ return documents.filter((document) => predicate(document));
1139
1264
  };
1140
1265
  //#endregion
1141
- export { MOCK_BLOB_BASE_URL, MOCK_QUEUE_BASE_URL, MOCK_TABLE_BASE_URL, MockBlobBatchClient, MockBlobClient, MockBlockBlobClient, MockContainerClient, MockContainerDatabase, MockEventGridDatabase, MockEventGridPublisherClient, MockQueueClient, MockQueueDatabase, MockRestError, MockTableClient, MockTableDatabase, applyTableFilter, bodyToBuffer, compare, createTableFilterPredicate, getAzureErrorXml, getBlobItemXml, getBlobPrefixXml, getListBlobsXml, isReadableStream, isTableNullClause, toWebResourceLike };
1266
+ 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, MockTableClient, MockTableDatabase, MockWebPubSubServiceClient, applyFilter, bodyToBuffer, compare, createFilterPredicate, getAzureErrorXml, getBlobItemXml, getBlobPrefixXml, getListBlobsXml, isNullClause, isReadableStream, toWebResourceLike };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "azure-mock",
3
- "version": "2.27.0",
3
+ "version": "2.29.0",
4
4
  "description": "A library that contains azure mock classes.",
5
5
  "homepage": "https://github.com/Esposter/Esposter#readme",
6
6
  "bugs": {
@@ -23,7 +23,6 @@
23
23
  },
24
24
  "scripts": {
25
25
  "build": "pnpm export:gen && rolldown --config rolldown.config.ts",
26
- "coverage": "vitest run --coverage",
27
26
  "export:gen": "ctix build --config ../configuration/.ctirc-ts",
28
27
  "format": "oxfmt",
29
28
  "format:check": "oxfmt --check",
@@ -33,26 +32,27 @@
33
32
  "typecheck": "tsgo"
34
33
  },
35
34
  "dependencies": {
36
- "@esposter/db": "2.27.0",
37
- "@esposter/db-schema": "2.27.0",
38
- "@esposter/shared": "2.27.0",
35
+ "@esposter/db": "2.29.0",
36
+ "@esposter/db-schema": "2.29.0",
37
+ "@esposter/shared": "2.29.0",
39
38
  "fast-deep-equal": "^3.1.3"
40
39
  },
41
40
  "devDependencies": {
42
- "@esposter/configuration": "2.27.0",
43
- "@vitest/coverage-v8": "^4.1.8",
41
+ "@esposter/configuration": "2.29.0",
42
+ "@vitest/coverage-v8": "^4.1.9",
44
43
  "ctix": "^2.8.1",
45
- "rolldown": "^1.1.0",
44
+ "rolldown": "^1.1.2",
46
45
  "type-fest": "^5.7.0",
47
- "vitest": "^4.1.8"
46
+ "vitest": "^4.1.9"
48
47
  },
49
48
  "peerDependencies": {
50
49
  "@azure/core-http-compat": "^2.4.0",
51
- "@azure/core-rest-pipeline": "^1.23.0",
50
+ "@azure/core-rest-pipeline": "^1.24.0",
52
51
  "@azure/data-tables": "^13.3.2",
53
52
  "@azure/eventgrid": "^5.12.0",
53
+ "@azure/search-documents": "^13.0.0",
54
54
  "@azure/storage-blob": "^12.32.0",
55
- "@azure/storage-queue": "^12.29.0"
55
+ "@azure/storage-queue": "^12.30.0"
56
56
  },
57
- "gitHead": "910d6b733310e882713270963a6f3d30314b865e"
57
+ "gitHead": "0166c5a9bea8b089ce37499f66b8d8095f4b4b23"
58
58
  }