emberflow 1.5.0 → 2.0.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.
Files changed (59) hide show
  1. package/README.md +231 -4
  2. package/lib/index-utils.d.ts +18 -7
  3. package/lib/index-utils.js +71 -76
  4. package/lib/index-utils.js.map +1 -1
  5. package/lib/index.d.ts +22 -6
  6. package/lib/index.js +51 -36
  7. package/lib/index.js.map +1 -1
  8. package/lib/logics/view-logics.d.ts +0 -2
  9. package/lib/logics/view-logics.js +1 -19
  10. package/lib/logics/view-logics.js.map +1 -1
  11. package/lib/sample-custom/cleanup-configs.d.ts +2 -0
  12. package/lib/sample-custom/cleanup-configs.js +17 -0
  13. package/lib/sample-custom/cleanup-configs.js.map +1 -0
  14. package/lib/sample-custom/index.js +14 -1
  15. package/lib/sample-custom/index.js.map +1 -1
  16. package/lib/sample-custom/one-time-patches.d.ts +2 -0
  17. package/lib/sample-custom/one-time-patches.js +14 -0
  18. package/lib/sample-custom/one-time-patches.js.map +1 -0
  19. package/lib/tests/index-utils.test.js +349 -88
  20. package/lib/tests/index-utils.test.js.map +1 -1
  21. package/lib/tests/index.test.js +68 -4
  22. package/lib/tests/index.test.js.map +1 -1
  23. package/lib/tests/logics/patch-logics.test.js +50 -5
  24. package/lib/tests/logics/patch-logics.test.js.map +1 -1
  25. package/lib/tests/logics/view-logics.test.js +22 -4
  26. package/lib/tests/logics/view-logics.test.js.map +1 -1
  27. package/lib/tests/utils/cleanup.test.d.ts +1 -0
  28. package/lib/tests/utils/cleanup.test.js +243 -0
  29. package/lib/tests/utils/cleanup.test.js.map +1 -0
  30. package/lib/tests/utils/distribution.test.js +256 -1
  31. package/lib/tests/utils/distribution.test.js.map +1 -1
  32. package/lib/tests/utils/forms.test.js +10 -86
  33. package/lib/tests/utils/forms.test.js.map +1 -1
  34. package/lib/tests/utils/misc.test.js +96 -2
  35. package/lib/tests/utils/misc.test.js.map +1 -1
  36. package/lib/tests/utils/paths.test.js +20 -2
  37. package/lib/tests/utils/paths.test.js.map +1 -1
  38. package/lib/tests/utils/pubsub.test.js +10 -31
  39. package/lib/tests/utils/pubsub.test.js.map +1 -1
  40. package/lib/types.d.ts +51 -2
  41. package/lib/utils/cleanup.d.ts +5 -0
  42. package/lib/utils/cleanup.js +99 -0
  43. package/lib/utils/cleanup.js.map +1 -0
  44. package/lib/utils/distribution.d.ts +17 -3
  45. package/lib/utils/distribution.js +61 -23
  46. package/lib/utils/distribution.js.map +1 -1
  47. package/lib/utils/forms.d.ts +0 -2
  48. package/lib/utils/forms.js +1 -35
  49. package/lib/utils/forms.js.map +1 -1
  50. package/lib/utils/misc.d.ts +1 -0
  51. package/lib/utils/misc.js +32 -1
  52. package/lib/utils/misc.js.map +1 -1
  53. package/lib/utils/pubsub.d.ts +0 -2
  54. package/lib/utils/pubsub.js +1 -16
  55. package/lib/utils/pubsub.js.map +1 -1
  56. package/package.json +1 -1
  57. package/src/sample-custom/cleanup-configs.ts +15 -0
  58. package/src/sample-custom/index.ts +15 -11
  59. package/src/sample-custom/one-time-patches.ts +13 -0
@@ -36,9 +36,9 @@ const paths_1 = require("../utils/paths");
36
36
  const batch_1 = require("../utils/batch");
37
37
  const distribution = __importStar(require("../utils/distribution"));
38
38
  const forms = __importStar(require("../utils/forms"));
39
- const misc = __importStar(require("../utils/misc"));
40
39
  const viewLogics = __importStar(require("../logics/view-logics"));
41
40
  const view_logics_1 = require("../logics/view-logics");
41
+ const patchLogics = __importStar(require("../logics/patch-logics"));
42
42
  const patch_logics_1 = require("../sample-custom/patch-logics");
43
43
  var Timestamp = firebase_admin_1.firestore.Timestamp;
44
44
  // should mock when using initializeEmberFlow and testing db.doc() calls count
@@ -262,7 +262,7 @@ describe("distributeDoc", () => {
262
262
  expect(transactionSetMock).toHaveBeenNthCalledWith(1, dstDocRef, expectedInstructions, { merge: true });
263
263
  expect(transactionSetMock).toHaveBeenNthCalledWith(2, dstDocRef, expectedData, { merge: true });
264
264
  });
265
- it("should not allow submit-form logicResultDoc in transaction", async () => {
265
+ it("should allow submit-form logicResultDoc in transaction (handled later)", async () => {
266
266
  const logicResultDoc = {
267
267
  action: "submit-form",
268
268
  priority: "normal",
@@ -272,10 +272,11 @@ describe("distributeDoc", () => {
272
272
  },
273
273
  dstPath: "/users/test-user-id/documents/test-doc-id",
274
274
  };
275
- const errorSpy = jest.spyOn(console, "error").mockImplementation();
275
+ const debugSpy = jest.spyOn(console, "debug").mockImplementation();
276
276
  await indexUtils.distributeDoc(logicResultDoc, appVersion, undefined, transactionMock);
277
- expect(errorSpy).toHaveBeenCalledWith("Submit-form is not supported in transactional logic result");
277
+ expect(debugSpy).toHaveBeenCalledWith("Submit-form in transactional logic result will be handled after transaction success");
278
278
  expect(transactionSetMock).toHaveBeenCalledTimes(0);
279
+ debugSpy.mockRestore();
279
280
  });
280
281
  });
281
282
  describe("batched", () => {
@@ -488,7 +489,16 @@ describe("distribute", () => {
488
489
  dstPath: "/users/test-user-id/documents/test-doc-id",
489
490
  }],
490
491
  ]]);
491
- (0, index_1.initializeEmberFlow)(projectConfig, admin, db_structure_1.dbStructure, db_structure_1.Entity, security_1.securityConfigs, validators_1.validatorConfigs, [], patch_logics_1.patchLogicConfigs);
492
+ (0, index_1.initializeEmberFlow)({
493
+ projectConfig,
494
+ admin,
495
+ dbStructure: db_structure_1.dbStructure,
496
+ Entity: db_structure_1.Entity,
497
+ securityConfigs: security_1.securityConfigs,
498
+ validatorConfigs: validators_1.validatorConfigs,
499
+ logicConfigs: [],
500
+ patchLogicConfigs: patch_logics_1.patchLogicConfigs,
501
+ });
492
502
  await indexUtils.distributeFnNonTransactional(userDocsByDstPath, appVersion);
493
503
  expect(admin.firestore().doc).toHaveBeenCalledTimes(1);
494
504
  expect(admin.firestore().doc).toHaveBeenCalledWith("/users/test-user-id/documents/test-doc-id");
@@ -525,7 +535,16 @@ describe("distribute", () => {
525
535
  describe("distributeLater", () => {
526
536
  let queueForDistributionLaterSpy;
527
537
  beforeEach(() => {
528
- (0, index_1.initializeEmberFlow)(projectConfig, admin, db_structure_1.dbStructure, db_structure_1.Entity, security_1.securityConfigs, validators_1.validatorConfigs, [], patch_logics_1.patchLogicConfigs);
538
+ (0, index_1.initializeEmberFlow)({
539
+ projectConfig,
540
+ admin,
541
+ dbStructure: db_structure_1.dbStructure,
542
+ Entity: db_structure_1.Entity,
543
+ securityConfigs: security_1.securityConfigs,
544
+ validatorConfigs: validators_1.validatorConfigs,
545
+ logicConfigs: [],
546
+ patchLogicConfigs: patch_logics_1.patchLogicConfigs,
547
+ });
529
548
  queueForDistributionLaterSpy = jest.spyOn(distribution, "queueForDistributionLater").mockResolvedValue();
530
549
  });
531
550
  afterEach(() => {
@@ -557,7 +576,16 @@ describe("distributeLater", () => {
557
576
  });
558
577
  describe("validateForm", () => {
559
578
  beforeEach(() => {
560
- (0, index_1.initializeEmberFlow)(projectConfig, admin, db_structure_1.dbStructure, db_structure_1.Entity, security_1.securityConfigs, validators_1.validatorConfigs, [], patch_logics_1.patchLogicConfigs);
579
+ (0, index_1.initializeEmberFlow)({
580
+ projectConfig,
581
+ admin,
582
+ dbStructure: db_structure_1.dbStructure,
583
+ Entity: db_structure_1.Entity,
584
+ securityConfigs: security_1.securityConfigs,
585
+ validatorConfigs: validators_1.validatorConfigs,
586
+ logicConfigs: [],
587
+ patchLogicConfigs: patch_logics_1.patchLogicConfigs,
588
+ });
561
589
  });
562
590
  const targetVersion = "2.5";
563
591
  const entity = "user";
@@ -594,7 +622,16 @@ describe("validateForm", () => {
594
622
  version: "02.04.06",
595
623
  },
596
624
  ];
597
- (0, index_1.initializeEmberFlow)(projectConfig, admin, db_structure_1.dbStructure, db_structure_1.Entity, security_1.securityConfigs, validatorConfigs, [], patch_logics_1.patchLogicConfigs);
625
+ (0, index_1.initializeEmberFlow)({
626
+ projectConfig,
627
+ admin,
628
+ dbStructure: db_structure_1.dbStructure,
629
+ Entity: db_structure_1.Entity,
630
+ securityConfigs: security_1.securityConfigs,
631
+ validatorConfigs,
632
+ logicConfigs: [],
633
+ patchLogicConfigs: patch_logics_1.patchLogicConfigs,
634
+ });
598
635
  const document = {
599
636
  name: "John Doe",
600
637
  email: "johndoe@example.com",
@@ -642,7 +679,16 @@ describe("validateForm", () => {
642
679
  version: "2.0.0",
643
680
  },
644
681
  ];
645
- (0, index_1.initializeEmberFlow)(projectConfig, admin, db_structure_1.dbStructure, db_structure_1.Entity, security_1.securityConfigs, validatorConfigs, [], patch_logics_1.patchLogicConfigs);
682
+ (0, index_1.initializeEmberFlow)({
683
+ projectConfig,
684
+ admin,
685
+ dbStructure: db_structure_1.dbStructure,
686
+ Entity: db_structure_1.Entity,
687
+ securityConfigs: security_1.securityConfigs,
688
+ validatorConfigs,
689
+ logicConfigs: [],
690
+ patchLogicConfigs: patch_logics_1.patchLogicConfigs,
691
+ });
646
692
  const document = {
647
693
  name: "John Doe",
648
694
  };
@@ -690,7 +736,16 @@ describe("getSecurityFn", () => {
690
736
  version: "02.04.06",
691
737
  },
692
738
  ];
693
- (0, index_1.initializeEmberFlow)(projectConfig, admin, db_structure_1.dbStructure, db_structure_1.Entity, securityConfigs, validators_1.validatorConfigs, [], patch_logics_1.patchLogicConfigs);
739
+ (0, index_1.initializeEmberFlow)({
740
+ projectConfig,
741
+ admin,
742
+ dbStructure: db_structure_1.dbStructure,
743
+ Entity: db_structure_1.Entity,
744
+ securityConfigs,
745
+ validatorConfigs: validators_1.validatorConfigs,
746
+ logicConfigs: [],
747
+ patchLogicConfigs: patch_logics_1.patchLogicConfigs,
748
+ });
694
749
  const result = await indexUtils.getSecurityFn(entity, targetVersion);
695
750
  expect(result).toEqual(userSecurityFnV2);
696
751
  });
@@ -709,7 +764,16 @@ describe("getSecurityFn", () => {
709
764
  version: "2.0.0",
710
765
  },
711
766
  ];
712
- (0, index_1.initializeEmberFlow)(projectConfig, admin, db_structure_1.dbStructure, db_structure_1.Entity, securityConfigs, validators_1.validatorConfigs, [], patch_logics_1.patchLogicConfigs);
767
+ (0, index_1.initializeEmberFlow)({
768
+ projectConfig,
769
+ admin,
770
+ dbStructure: db_structure_1.dbStructure,
771
+ Entity: db_structure_1.Entity,
772
+ securityConfigs,
773
+ validatorConfigs: validators_1.validatorConfigs,
774
+ logicConfigs: [],
775
+ patchLogicConfigs: patch_logics_1.patchLogicConfigs,
776
+ });
713
777
  const entity = "user";
714
778
  // targetVersion is 1.0.0, even if app is 2.0.0
715
779
  const result = await indexUtils.getSecurityFn(entity, "1.0.0");
@@ -768,7 +832,16 @@ describe("delayFormSubmissionAndCheckIfCancelled", () => {
768
832
  });
769
833
  });
770
834
  describe("groupDocsByTargetDocPath", () => {
771
- (0, index_1.initializeEmberFlow)(projectConfig, admin, db_structure_1.dbStructure, db_structure_1.Entity, [], [], [], []);
835
+ (0, index_1.initializeEmberFlow)({
836
+ projectConfig,
837
+ admin,
838
+ dbStructure: db_structure_1.dbStructure,
839
+ Entity: db_structure_1.Entity,
840
+ securityConfigs: [],
841
+ validatorConfigs: [],
842
+ logicConfigs: [],
843
+ patchLogicConfigs: [],
844
+ });
772
845
  const docsByDstPath = new Map([
773
846
  ["users/user123/document1", [{ action: "merge", priority: "normal", dstPath: "users/user123/document1", doc: { field1: "value1", field2: "value2" } }]],
774
847
  ["users/user123/document1/threads/thread1", [{ action: "merge", priority: "normal", dstPath: "users/user123/document1/threads/thread1", doc: { field3: "value3", field6: "value6" } }]],
@@ -811,7 +884,7 @@ describe("runBusinessLogics", () => {
811
884
  docId: "document123",
812
885
  formId: "form123",
813
886
  docPath: "users/user123",
814
- entity: entity,
887
+ entity,
815
888
  },
816
889
  actionType,
817
890
  document: {
@@ -919,7 +992,16 @@ describe("runBusinessLogics", () => {
919
992
  obsoleteStartingFromVersion: "2", // Obsolete after appVersion
920
993
  },
921
994
  ];
922
- (0, index_1.initializeEmberFlow)(projectConfig, admin, db_structure_1.dbStructure, db_structure_1.Entity, security_1.securityConfigs, validators_1.validatorConfigs, logics, patch_logics_1.patchLogicConfigs);
995
+ (0, index_1.initializeEmberFlow)({
996
+ projectConfig,
997
+ admin,
998
+ dbStructure: db_structure_1.dbStructure,
999
+ Entity: db_structure_1.Entity,
1000
+ securityConfigs: security_1.securityConfigs,
1001
+ validatorConfigs: validators_1.validatorConfigs,
1002
+ logicConfigs: logics,
1003
+ patchLogicConfigs: patch_logics_1.patchLogicConfigs,
1004
+ });
923
1005
  const txnGet = { get: jest.fn() };
924
1006
  const { logicResults } = await indexUtils.runBusinessLogics(txnGet, action, appVersion);
925
1007
  expect(logicResults.length).toBe(2);
@@ -943,7 +1025,16 @@ describe("runBusinessLogics", () => {
943
1025
  obsoleteStartingFromVersion: "1", // Is obsolete
944
1026
  },
945
1027
  ];
946
- (0, index_1.initializeEmberFlow)(projectConfig, admin, db_structure_1.dbStructure, db_structure_1.Entity, security_1.securityConfigs, validators_1.validatorConfigs, logics, patch_logics_1.patchLogicConfigs);
1028
+ (0, index_1.initializeEmberFlow)({
1029
+ projectConfig,
1030
+ admin,
1031
+ dbStructure: db_structure_1.dbStructure,
1032
+ Entity: db_structure_1.Entity,
1033
+ securityConfigs: security_1.securityConfigs,
1034
+ validatorConfigs: validators_1.validatorConfigs,
1035
+ logicConfigs: logics,
1036
+ patchLogicConfigs: patch_logics_1.patchLogicConfigs,
1037
+ });
947
1038
  const txnGet = { get: jest.fn() };
948
1039
  const result = await indexUtils.runBusinessLogics(txnGet, action, appVersion);
949
1040
  expect(result.logicResults.length).toBe(0);
@@ -975,7 +1066,16 @@ describe("runBusinessLogics", () => {
975
1066
  version: "1",
976
1067
  },
977
1068
  ];
978
- (0, index_1.initializeEmberFlow)(projectConfig, admin, db_structure_1.dbStructure, db_structure_1.Entity, security_1.securityConfigs, validators_1.validatorConfigs, logics, patch_logics_1.patchLogicConfigs);
1069
+ (0, index_1.initializeEmberFlow)({
1070
+ projectConfig,
1071
+ admin,
1072
+ dbStructure: db_structure_1.dbStructure,
1073
+ Entity: db_structure_1.Entity,
1074
+ securityConfigs: security_1.securityConfigs,
1075
+ validatorConfigs: validators_1.validatorConfigs,
1076
+ logicConfigs: logics,
1077
+ patchLogicConfigs: patch_logics_1.patchLogicConfigs,
1078
+ });
979
1079
  const runStatus = await indexUtils.runBusinessLogics(txnGet, action, "2");
980
1080
  expect(logicFn1).toHaveBeenCalledWith(txnGet, action, new Map());
981
1081
  expect(logicFn2).toHaveBeenCalledWith(txnGet, action, new Map());
@@ -1045,7 +1145,16 @@ describe("runBusinessLogics", () => {
1045
1145
  version: "1",
1046
1146
  },
1047
1147
  ];
1048
- (0, index_1.initializeEmberFlow)(projectConfig, admin, db_structure_1.dbStructure, db_structure_1.Entity, security_1.securityConfigs, validators_1.validatorConfigs, logics, []);
1148
+ (0, index_1.initializeEmberFlow)({
1149
+ projectConfig,
1150
+ admin,
1151
+ dbStructure: db_structure_1.dbStructure,
1152
+ Entity: db_structure_1.Entity,
1153
+ securityConfigs: security_1.securityConfigs,
1154
+ validatorConfigs: validators_1.validatorConfigs,
1155
+ logicConfigs: logics,
1156
+ patchLogicConfigs: [],
1157
+ });
1049
1158
  const newAction = Object.assign(Object.assign({}, action), { modifiedFields: {
1050
1159
  shouldRun: true,
1051
1160
  } });
@@ -1152,7 +1261,16 @@ describe("runBusinessLogics", () => {
1152
1261
  obsoleteAfterVersion: "2.4.9",
1153
1262
  },
1154
1263
  ];
1155
- (0, index_1.initializeEmberFlow)(projectConfig, admin, db_structure_1.dbStructure, db_structure_1.Entity, security_1.securityConfigs, validators_1.validatorConfigs, logics, []);
1264
+ (0, index_1.initializeEmberFlow)({
1265
+ projectConfig,
1266
+ admin,
1267
+ dbStructure: db_structure_1.dbStructure,
1268
+ Entity: db_structure_1.Entity,
1269
+ securityConfigs: security_1.securityConfigs,
1270
+ validatorConfigs: validators_1.validatorConfigs,
1271
+ logicConfigs: logics,
1272
+ patchLogicConfigs: [],
1273
+ });
1156
1274
  const newAction = Object.assign(Object.assign({}, action), { modifiedFields: {
1157
1275
  field1: true,
1158
1276
  } });
@@ -1218,7 +1336,16 @@ describe("runBusinessLogics", () => {
1218
1336
  version: "1",
1219
1337
  },
1220
1338
  ];
1221
- (0, index_1.initializeEmberFlow)(projectConfig, admin, db_structure_1.dbStructure, db_structure_1.Entity, security_1.securityConfigs, validators_1.validatorConfigs, logics, []);
1339
+ (0, index_1.initializeEmberFlow)({
1340
+ projectConfig,
1341
+ admin,
1342
+ dbStructure: db_structure_1.dbStructure,
1343
+ Entity: db_structure_1.Entity,
1344
+ securityConfigs: security_1.securityConfigs,
1345
+ validatorConfigs: validators_1.validatorConfigs,
1346
+ logicConfigs: logics,
1347
+ patchLogicConfigs: [],
1348
+ });
1222
1349
  const runStatus = await indexUtils.runBusinessLogics(txnGet, action, "1");
1223
1350
  expect(logicFn1).not.toHaveBeenCalled();
1224
1351
  expect(logicFn2).not.toHaveBeenCalled();
@@ -1272,7 +1399,16 @@ describe("runBusinessLogics", () => {
1272
1399
  version: "1",
1273
1400
  },
1274
1401
  ];
1275
- (0, index_1.initializeEmberFlow)(projectConfig, admin, db_structure_1.dbStructure, db_structure_1.Entity, security_1.securityConfigs, validators_1.validatorConfigs, logics, []);
1402
+ (0, index_1.initializeEmberFlow)({
1403
+ projectConfig,
1404
+ admin,
1405
+ dbStructure: db_structure_1.dbStructure,
1406
+ Entity: db_structure_1.Entity,
1407
+ securityConfigs: security_1.securityConfigs,
1408
+ validatorConfigs: validators_1.validatorConfigs,
1409
+ logicConfigs: logics,
1410
+ patchLogicConfigs: [],
1411
+ });
1276
1412
  const runStatus = await indexUtils.runBusinessLogics(txnGet, action, "1");
1277
1413
  expect(logicFn1).toHaveBeenCalledWith(txnGet, action, expectedSharedMap);
1278
1414
  expect(logicFn2).toHaveBeenCalledWith(txnGet, action, expectedSharedMap);
@@ -1634,46 +1770,6 @@ describe("createMetricExecution", () => {
1634
1770
  });
1635
1771
  });
1636
1772
  });
1637
- describe("cleanMetricExecutions", () => {
1638
- let colGetMock;
1639
- let deleteCollectionSpy;
1640
- beforeEach(() => {
1641
- colGetMock = jest.fn().mockResolvedValue({
1642
- docs: [
1643
- {
1644
- ref: {
1645
- collection: jest.fn().mockReturnValue({
1646
- where: jest.fn().mockReturnValue({}),
1647
- }),
1648
- },
1649
- },
1650
- ],
1651
- });
1652
- jest.spyOn(admin.firestore(), "collection").mockReturnValue({
1653
- get: colGetMock,
1654
- });
1655
- deleteCollectionSpy = jest.spyOn(misc, "deleteCollection")
1656
- .mockImplementation(async (query, callback) => {
1657
- if (callback) {
1658
- await callback({ size: 1 });
1659
- }
1660
- return Promise.resolve();
1661
- });
1662
- });
1663
- afterEach(() => {
1664
- jest.restoreAllMocks();
1665
- });
1666
- it("should clean metric executions", async () => {
1667
- jest.spyOn(console, "info").mockImplementation();
1668
- await (0, index_utils_1.cleanMetricExecutions)({});
1669
- expect(console.info).toHaveBeenCalledWith("Running cleanMetricExecutions");
1670
- expect(admin.firestore().collection).toHaveBeenCalledTimes(1);
1671
- expect(admin.firestore().collection).toHaveBeenCalledWith("@metrics");
1672
- expect(colGetMock).toHaveBeenCalledTimes(1);
1673
- expect(deleteCollectionSpy).toHaveBeenCalled();
1674
- expect(console.info).toHaveBeenCalledWith("Cleaned 1 logic metric executions");
1675
- });
1676
- });
1677
1773
  describe("createMetricComputation", () => {
1678
1774
  let colGetMock;
1679
1775
  let setMock;
@@ -1786,44 +1882,209 @@ describe("createMetricComputation", () => {
1786
1882
  });
1787
1883
  });
1788
1884
  });
1789
- describe("cleanMetricComputations", () => {
1885
+ describe("patchGroupDocs", () => {
1886
+ const collectionPath = "/users/user1/feeds";
1887
+ let docGetMock;
1888
+ let docSetMock;
1790
1889
  let colGetMock;
1791
- let deleteCollectionSpy;
1890
+ let limitMock;
1891
+ let startAfterMock;
1892
+ let orderByMock;
1893
+ let batchUpdateMock;
1894
+ let batchCommitMock;
1895
+ let queueGroupPatchSpy;
1896
+ let runPatchLogicsSpy;
1897
+ function makeDoc(id, data = {}) {
1898
+ return {
1899
+ id,
1900
+ data: () => data,
1901
+ ref: {
1902
+ path: `${collectionPath.slice(1)}/${id}`,
1903
+ update: jest.fn(),
1904
+ },
1905
+ };
1906
+ }
1792
1907
  beforeEach(() => {
1793
- colGetMock = jest.fn().mockResolvedValue({
1794
- docs: [
1795
- {
1796
- ref: {
1797
- collection: jest.fn().mockReturnValue({
1798
- where: jest.fn().mockReturnValue({}),
1799
- }),
1800
- },
1801
- },
1802
- ],
1908
+ (0, index_1.initializeEmberFlow)({
1909
+ projectConfig,
1910
+ admin,
1911
+ dbStructure: db_structure_1.dbStructure,
1912
+ Entity: db_structure_1.Entity,
1913
+ securityConfigs: security_1.securityConfigs,
1914
+ validatorConfigs: validators_1.validatorConfigs,
1915
+ logicConfigs: [],
1916
+ patchLogicConfigs: [],
1803
1917
  });
1918
+ docGetMock = jest.fn().mockResolvedValue({ exists: false, data: () => undefined });
1919
+ docSetMock = jest.fn().mockResolvedValue({});
1920
+ jest.spyOn(admin.firestore(), "doc").mockReturnValue({
1921
+ get: docGetMock,
1922
+ set: docSetMock,
1923
+ });
1924
+ colGetMock = jest.fn();
1925
+ limitMock = jest.fn().mockReturnValue({ get: colGetMock });
1926
+ startAfterMock = jest.fn().mockReturnValue({ limit: limitMock });
1927
+ orderByMock = jest.fn().mockReturnValue({ limit: limitMock, startAfter: startAfterMock });
1804
1928
  jest.spyOn(admin.firestore(), "collection").mockReturnValue({
1805
- get: colGetMock,
1929
+ orderBy: orderByMock,
1806
1930
  });
1807
- deleteCollectionSpy = jest.spyOn(misc, "deleteCollection")
1808
- .mockImplementation(async (query, callback) => {
1809
- if (callback) {
1810
- await callback({ size: 1 });
1811
- }
1812
- return Promise.resolve();
1931
+ batchUpdateMock = jest.fn();
1932
+ batchCommitMock = jest.fn().mockResolvedValue({});
1933
+ jest.spyOn(admin.firestore(), "batch").mockReturnValue({
1934
+ update: batchUpdateMock,
1935
+ commit: batchCommitMock,
1813
1936
  });
1937
+ queueGroupPatchSpy = jest.spyOn(distribution, "queueGroupPatch").mockResolvedValue();
1938
+ runPatchLogicsSpy = jest.spyOn(patchLogics, "runPatchLogics").mockResolvedValue();
1814
1939
  });
1815
1940
  afterEach(() => {
1816
1941
  jest.restoreAllMocks();
1817
1942
  });
1818
- it("should clean metric computations", async () => {
1819
- jest.spyOn(console, "info").mockImplementation();
1820
- await (0, index_utils_1.cleanMetricComputations)({});
1821
- expect(console.info).toHaveBeenCalledWith("Running cleanMetricComputations");
1822
- expect(admin.firestore().collection).toHaveBeenCalledTimes(1);
1823
- expect(admin.firestore().collection).toHaveBeenCalledWith("@metrics");
1824
- expect(colGetMock).toHaveBeenCalledTimes(1);
1825
- expect(deleteCollectionSpy).toHaveBeenCalled();
1826
- expect(console.info).toHaveBeenCalledWith("Cleaned 1 logic metric computations");
1943
+ it("marks the patch as completed and does not reschedule when there are no more docs", async () => {
1944
+ colGetMock.mockResolvedValue({ empty: true, docs: [] });
1945
+ await indexUtils.patchGroupDocs({ collectionPath, patchType: "back-fill", backFillPatchName: "ancestor-ids" });
1946
+ expect(docSetMock).toHaveBeenCalledWith(expect.objectContaining({ status: "completed" }), { merge: true });
1947
+ expect(queueGroupPatchSpy).not.toHaveBeenCalled();
1948
+ });
1949
+ it("skips processing entirely when the status is already completed", async () => {
1950
+ docGetMock.mockResolvedValue({ exists: true, data: () => ({ status: "completed" }) });
1951
+ await indexUtils.patchGroupDocs({ collectionPath, patchType: "back-fill", backFillPatchName: "ancestor-ids" });
1952
+ expect(admin.firestore().collection).not.toHaveBeenCalled();
1953
+ expect(queueGroupPatchSpy).not.toHaveBeenCalled();
1954
+ });
1955
+ it("runs the built-in ancestor-ids back-fill unchanged: bulk single-batch commit, only undefined keys patched", async () => {
1956
+ const doc1 = makeDoc("feed1");
1957
+ colGetMock.mockResolvedValue({ empty: false, docs: [doc1] });
1958
+ await indexUtils.patchGroupDocs({ collectionPath, patchType: "back-fill", backFillPatchName: "ancestor-ids" });
1959
+ expect(admin.firestore().batch).toHaveBeenCalledTimes(1);
1960
+ expect(batchUpdateMock).toHaveBeenCalledWith(doc1.ref, { "@entity": "feed", "@user": "user1" });
1961
+ expect(batchCommitMock).toHaveBeenCalledTimes(1);
1962
+ expect(docSetMock).toHaveBeenCalledWith(expect.objectContaining({ status: "running", count: 1, lastPatchedId: "feed1" }), { merge: true });
1963
+ expect(queueGroupPatchSpy).toHaveBeenCalledWith({
1964
+ path: collectionPath,
1965
+ patchType: "back-fill",
1966
+ backFillPatchName: "ancestor-ids",
1967
+ appVersion: undefined,
1968
+ lastPatchedId: "feed1",
1969
+ });
1970
+ });
1971
+ it("does not patch keys that are already defined on the document", async () => {
1972
+ const doc1 = makeDoc("feed1", { "@entity": "feed", "@user": "user1" });
1973
+ colGetMock.mockResolvedValue({ empty: false, docs: [doc1] });
1974
+ await indexUtils.patchGroupDocs({ collectionPath, patchType: "back-fill", backFillPatchName: "ancestor-ids" });
1975
+ expect(batchUpdateMock).not.toHaveBeenCalled();
1976
+ expect(batchCommitMock).not.toHaveBeenCalled();
1977
+ });
1978
+ it("dispatches to a custom back-fill config registered via initializeEmberFlow options", async () => {
1979
+ const customPatchFn = jest.fn().mockResolvedValue(undefined);
1980
+ (0, index_1.initializeEmberFlow)({
1981
+ projectConfig,
1982
+ admin,
1983
+ dbStructure: db_structure_1.dbStructure,
1984
+ Entity: db_structure_1.Entity,
1985
+ securityConfigs: security_1.securityConfigs,
1986
+ validatorConfigs: validators_1.validatorConfigs,
1987
+ logicConfigs: [],
1988
+ patchLogicConfigs: [],
1989
+ backFillPatchConfigs: [{ name: "custom-back-fill", patchFn: customPatchFn }],
1990
+ });
1991
+ const doc1 = makeDoc("feed1");
1992
+ colGetMock.mockResolvedValue({ empty: false, docs: [doc1] });
1993
+ await indexUtils.patchGroupDocs({ collectionPath, patchType: "back-fill", backFillPatchName: "custom-back-fill" });
1994
+ expect(customPatchFn).toHaveBeenCalledWith(collectionPath, [doc1]);
1995
+ expect(queueGroupPatchSpy).toHaveBeenCalledWith(expect.objectContaining({ backFillPatchName: "custom-back-fill" }));
1996
+ });
1997
+ it("dispatches to runPatchLogics per doc when patchType is patch-logics", async () => {
1998
+ const doc1 = makeDoc("feed1");
1999
+ const doc2 = makeDoc("feed2");
2000
+ colGetMock.mockResolvedValue({ empty: false, docs: [doc1, doc2] });
2001
+ await indexUtils.patchGroupDocs({ collectionPath, patchType: "patch-logics", appVersion: "1.2.3" });
2002
+ expect(runPatchLogicsSpy).toHaveBeenCalledTimes(2);
2003
+ expect(runPatchLogicsSpy).toHaveBeenNthCalledWith(1, "1.2.3", `/${doc1.ref.path}`);
2004
+ expect(runPatchLogicsSpy).toHaveBeenNthCalledWith(2, "1.2.3", `/${doc2.ref.path}`);
2005
+ expect(queueGroupPatchSpy).toHaveBeenCalledWith(expect.objectContaining({
2006
+ patchType: "patch-logics",
2007
+ appVersion: "1.2.3",
2008
+ lastPatchedId: "feed2",
2009
+ }));
2010
+ });
2011
+ it("marks the run as error (no silent default) when appVersion is missing for patch-logics", async () => {
2012
+ jest.spyOn(console, "error").mockImplementation();
2013
+ const doc1 = makeDoc("feed1");
2014
+ colGetMock.mockResolvedValue({ empty: false, docs: [doc1] });
2015
+ await expect(indexUtils.patchGroupDocs({ collectionPath, patchType: "patch-logics" })).rejects.toThrow();
2016
+ expect(docSetMock).toHaveBeenCalledWith(expect.objectContaining({ status: "error" }), { merge: true });
2017
+ expect(runPatchLogicsSpy).not.toHaveBeenCalled();
2018
+ expect(queueGroupPatchSpy).not.toHaveBeenCalled();
2019
+ });
2020
+ it("marks the run as error (no silent default) when the back-fill name is unresolved", async () => {
2021
+ jest.spyOn(console, "error").mockImplementation();
2022
+ const doc1 = makeDoc("feed1");
2023
+ colGetMock.mockResolvedValue({ empty: false, docs: [doc1] });
2024
+ await expect(indexUtils.patchGroupDocs({
2025
+ collectionPath,
2026
+ patchType: "back-fill",
2027
+ backFillPatchName: "does-not-exist",
2028
+ })).rejects.toThrow();
2029
+ expect(docSetMock).toHaveBeenCalledWith(expect.objectContaining({ status: "error", error: expect.stringContaining("does-not-exist") }), { merge: true });
2030
+ expect(queueGroupPatchSpy).not.toHaveBeenCalled();
2031
+ });
2032
+ it("marks the run as error (no silent default) for an unknown patchType", async () => {
2033
+ jest.spyOn(console, "error").mockImplementation();
2034
+ const doc1 = makeDoc("feed1");
2035
+ colGetMock.mockResolvedValue({ empty: false, docs: [doc1] });
2036
+ await expect(indexUtils.patchGroupDocs({
2037
+ collectionPath,
2038
+ patchType: "unknown-type",
2039
+ })).rejects.toThrow();
2040
+ expect(docSetMock).toHaveBeenCalledWith(expect.objectContaining({ status: "error" }), { merge: true });
2041
+ expect(queueGroupPatchSpy).not.toHaveBeenCalled();
2042
+ });
2043
+ });
2044
+ describe("ancestorIdsPatchConfig", () => {
2045
+ it("is auto-registered as a built-in BackFillPatchConfig named \"ancestor-ids\"", () => {
2046
+ (0, index_1.initializeEmberFlow)({
2047
+ projectConfig,
2048
+ admin,
2049
+ dbStructure: db_structure_1.dbStructure,
2050
+ Entity: db_structure_1.Entity,
2051
+ securityConfigs: security_1.securityConfigs,
2052
+ validatorConfigs: validators_1.validatorConfigs,
2053
+ logicConfigs: [],
2054
+ patchLogicConfigs: [],
2055
+ });
2056
+ const registered = indexUtils._mockable.getOneTimePatchConfigs()
2057
+ .find((config) => config.name === "ancestor-ids");
2058
+ expect(registered).toBe(indexUtils.ancestorIdsPatchConfig);
2059
+ });
2060
+ it("throws when a custom BackFillPatchConfig reuses the built-in \"ancestor-ids\" name", () => {
2061
+ expect(() => (0, index_1.initializeEmberFlow)({
2062
+ projectConfig,
2063
+ admin,
2064
+ dbStructure: db_structure_1.dbStructure,
2065
+ Entity: db_structure_1.Entity,
2066
+ securityConfigs: security_1.securityConfigs,
2067
+ validatorConfigs: validators_1.validatorConfigs,
2068
+ logicConfigs: [],
2069
+ patchLogicConfigs: [],
2070
+ backFillPatchConfigs: [{ name: "ancestor-ids", patchFn: jest.fn() }],
2071
+ })).toThrow();
2072
+ });
2073
+ it("throws when two custom BackFillPatchConfigs share the same name", () => {
2074
+ expect(() => (0, index_1.initializeEmberFlow)({
2075
+ projectConfig,
2076
+ admin,
2077
+ dbStructure: db_structure_1.dbStructure,
2078
+ Entity: db_structure_1.Entity,
2079
+ securityConfigs: security_1.securityConfigs,
2080
+ validatorConfigs: validators_1.validatorConfigs,
2081
+ logicConfigs: [],
2082
+ patchLogicConfigs: [],
2083
+ backFillPatchConfigs: [
2084
+ { name: "dup", patchFn: jest.fn() },
2085
+ { name: "dup", patchFn: jest.fn() },
2086
+ ],
2087
+ })).toThrow();
1827
2088
  });
1828
2089
  });
1829
2090
  //# sourceMappingURL=index-utils.test.js.map