emberflow 1.5.1 → 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 +70 -75
  4. package/lib/index-utils.js.map +1 -1
  5. package/lib/index.d.ts +22 -6
  6. package/lib/index.js +43 -34
  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 +345 -85
  20. package/lib/tests/index-utils.test.js.map +1 -1
  21. package/lib/tests/index.test.js +33 -5
  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
@@ -489,7 +489,16 @@ describe("distribute", () => {
489
489
  dstPath: "/users/test-user-id/documents/test-doc-id",
490
490
  }],
491
491
  ]]);
492
- (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
+ });
493
502
  await indexUtils.distributeFnNonTransactional(userDocsByDstPath, appVersion);
494
503
  expect(admin.firestore().doc).toHaveBeenCalledTimes(1);
495
504
  expect(admin.firestore().doc).toHaveBeenCalledWith("/users/test-user-id/documents/test-doc-id");
@@ -526,7 +535,16 @@ describe("distribute", () => {
526
535
  describe("distributeLater", () => {
527
536
  let queueForDistributionLaterSpy;
528
537
  beforeEach(() => {
529
- (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
+ });
530
548
  queueForDistributionLaterSpy = jest.spyOn(distribution, "queueForDistributionLater").mockResolvedValue();
531
549
  });
532
550
  afterEach(() => {
@@ -558,7 +576,16 @@ describe("distributeLater", () => {
558
576
  });
559
577
  describe("validateForm", () => {
560
578
  beforeEach(() => {
561
- (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
+ });
562
589
  });
563
590
  const targetVersion = "2.5";
564
591
  const entity = "user";
@@ -595,7 +622,16 @@ describe("validateForm", () => {
595
622
  version: "02.04.06",
596
623
  },
597
624
  ];
598
- (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
+ });
599
635
  const document = {
600
636
  name: "John Doe",
601
637
  email: "johndoe@example.com",
@@ -643,7 +679,16 @@ describe("validateForm", () => {
643
679
  version: "2.0.0",
644
680
  },
645
681
  ];
646
- (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
+ });
647
692
  const document = {
648
693
  name: "John Doe",
649
694
  };
@@ -691,7 +736,16 @@ describe("getSecurityFn", () => {
691
736
  version: "02.04.06",
692
737
  },
693
738
  ];
694
- (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
+ });
695
749
  const result = await indexUtils.getSecurityFn(entity, targetVersion);
696
750
  expect(result).toEqual(userSecurityFnV2);
697
751
  });
@@ -710,7 +764,16 @@ describe("getSecurityFn", () => {
710
764
  version: "2.0.0",
711
765
  },
712
766
  ];
713
- (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
+ });
714
777
  const entity = "user";
715
778
  // targetVersion is 1.0.0, even if app is 2.0.0
716
779
  const result = await indexUtils.getSecurityFn(entity, "1.0.0");
@@ -769,7 +832,16 @@ describe("delayFormSubmissionAndCheckIfCancelled", () => {
769
832
  });
770
833
  });
771
834
  describe("groupDocsByTargetDocPath", () => {
772
- (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
+ });
773
845
  const docsByDstPath = new Map([
774
846
  ["users/user123/document1", [{ action: "merge", priority: "normal", dstPath: "users/user123/document1", doc: { field1: "value1", field2: "value2" } }]],
775
847
  ["users/user123/document1/threads/thread1", [{ action: "merge", priority: "normal", dstPath: "users/user123/document1/threads/thread1", doc: { field3: "value3", field6: "value6" } }]],
@@ -812,7 +884,7 @@ describe("runBusinessLogics", () => {
812
884
  docId: "document123",
813
885
  formId: "form123",
814
886
  docPath: "users/user123",
815
- entity: entity,
887
+ entity,
816
888
  },
817
889
  actionType,
818
890
  document: {
@@ -920,7 +992,16 @@ describe("runBusinessLogics", () => {
920
992
  obsoleteStartingFromVersion: "2", // Obsolete after appVersion
921
993
  },
922
994
  ];
923
- (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
+ });
924
1005
  const txnGet = { get: jest.fn() };
925
1006
  const { logicResults } = await indexUtils.runBusinessLogics(txnGet, action, appVersion);
926
1007
  expect(logicResults.length).toBe(2);
@@ -944,7 +1025,16 @@ describe("runBusinessLogics", () => {
944
1025
  obsoleteStartingFromVersion: "1", // Is obsolete
945
1026
  },
946
1027
  ];
947
- (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
+ });
948
1038
  const txnGet = { get: jest.fn() };
949
1039
  const result = await indexUtils.runBusinessLogics(txnGet, action, appVersion);
950
1040
  expect(result.logicResults.length).toBe(0);
@@ -976,7 +1066,16 @@ describe("runBusinessLogics", () => {
976
1066
  version: "1",
977
1067
  },
978
1068
  ];
979
- (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
+ });
980
1079
  const runStatus = await indexUtils.runBusinessLogics(txnGet, action, "2");
981
1080
  expect(logicFn1).toHaveBeenCalledWith(txnGet, action, new Map());
982
1081
  expect(logicFn2).toHaveBeenCalledWith(txnGet, action, new Map());
@@ -1046,7 +1145,16 @@ describe("runBusinessLogics", () => {
1046
1145
  version: "1",
1047
1146
  },
1048
1147
  ];
1049
- (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
+ });
1050
1158
  const newAction = Object.assign(Object.assign({}, action), { modifiedFields: {
1051
1159
  shouldRun: true,
1052
1160
  } });
@@ -1153,7 +1261,16 @@ describe("runBusinessLogics", () => {
1153
1261
  obsoleteAfterVersion: "2.4.9",
1154
1262
  },
1155
1263
  ];
1156
- (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
+ });
1157
1274
  const newAction = Object.assign(Object.assign({}, action), { modifiedFields: {
1158
1275
  field1: true,
1159
1276
  } });
@@ -1219,7 +1336,16 @@ describe("runBusinessLogics", () => {
1219
1336
  version: "1",
1220
1337
  },
1221
1338
  ];
1222
- (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
+ });
1223
1349
  const runStatus = await indexUtils.runBusinessLogics(txnGet, action, "1");
1224
1350
  expect(logicFn1).not.toHaveBeenCalled();
1225
1351
  expect(logicFn2).not.toHaveBeenCalled();
@@ -1273,7 +1399,16 @@ describe("runBusinessLogics", () => {
1273
1399
  version: "1",
1274
1400
  },
1275
1401
  ];
1276
- (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
+ });
1277
1412
  const runStatus = await indexUtils.runBusinessLogics(txnGet, action, "1");
1278
1413
  expect(logicFn1).toHaveBeenCalledWith(txnGet, action, expectedSharedMap);
1279
1414
  expect(logicFn2).toHaveBeenCalledWith(txnGet, action, expectedSharedMap);
@@ -1635,46 +1770,6 @@ describe("createMetricExecution", () => {
1635
1770
  });
1636
1771
  });
1637
1772
  });
1638
- describe("cleanMetricExecutions", () => {
1639
- let colGetMock;
1640
- let deleteCollectionSpy;
1641
- beforeEach(() => {
1642
- colGetMock = jest.fn().mockResolvedValue({
1643
- docs: [
1644
- {
1645
- ref: {
1646
- collection: jest.fn().mockReturnValue({
1647
- where: jest.fn().mockReturnValue({}),
1648
- }),
1649
- },
1650
- },
1651
- ],
1652
- });
1653
- jest.spyOn(admin.firestore(), "collection").mockReturnValue({
1654
- get: colGetMock,
1655
- });
1656
- deleteCollectionSpy = jest.spyOn(misc, "deleteCollection")
1657
- .mockImplementation(async (query, callback) => {
1658
- if (callback) {
1659
- await callback({ size: 1 });
1660
- }
1661
- return Promise.resolve();
1662
- });
1663
- });
1664
- afterEach(() => {
1665
- jest.restoreAllMocks();
1666
- });
1667
- it("should clean metric executions", async () => {
1668
- jest.spyOn(console, "info").mockImplementation();
1669
- await (0, index_utils_1.cleanMetricExecutions)({});
1670
- expect(console.info).toHaveBeenCalledWith("Running cleanMetricExecutions");
1671
- expect(admin.firestore().collection).toHaveBeenCalledTimes(1);
1672
- expect(admin.firestore().collection).toHaveBeenCalledWith("@metrics");
1673
- expect(colGetMock).toHaveBeenCalledTimes(1);
1674
- expect(deleteCollectionSpy).toHaveBeenCalled();
1675
- expect(console.info).toHaveBeenCalledWith("Cleaned 1 logic metric executions");
1676
- });
1677
- });
1678
1773
  describe("createMetricComputation", () => {
1679
1774
  let colGetMock;
1680
1775
  let setMock;
@@ -1787,44 +1882,209 @@ describe("createMetricComputation", () => {
1787
1882
  });
1788
1883
  });
1789
1884
  });
1790
- describe("cleanMetricComputations", () => {
1885
+ describe("patchGroupDocs", () => {
1886
+ const collectionPath = "/users/user1/feeds";
1887
+ let docGetMock;
1888
+ let docSetMock;
1791
1889
  let colGetMock;
1792
- 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
+ }
1793
1907
  beforeEach(() => {
1794
- colGetMock = jest.fn().mockResolvedValue({
1795
- docs: [
1796
- {
1797
- ref: {
1798
- collection: jest.fn().mockReturnValue({
1799
- where: jest.fn().mockReturnValue({}),
1800
- }),
1801
- },
1802
- },
1803
- ],
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: [],
1804
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 });
1805
1928
  jest.spyOn(admin.firestore(), "collection").mockReturnValue({
1806
- get: colGetMock,
1929
+ orderBy: orderByMock,
1807
1930
  });
1808
- deleteCollectionSpy = jest.spyOn(misc, "deleteCollection")
1809
- .mockImplementation(async (query, callback) => {
1810
- if (callback) {
1811
- await callback({ size: 1 });
1812
- }
1813
- 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,
1814
1936
  });
1937
+ queueGroupPatchSpy = jest.spyOn(distribution, "queueGroupPatch").mockResolvedValue();
1938
+ runPatchLogicsSpy = jest.spyOn(patchLogics, "runPatchLogics").mockResolvedValue();
1815
1939
  });
1816
1940
  afterEach(() => {
1817
1941
  jest.restoreAllMocks();
1818
1942
  });
1819
- it("should clean metric computations", async () => {
1820
- jest.spyOn(console, "info").mockImplementation();
1821
- await (0, index_utils_1.cleanMetricComputations)({});
1822
- expect(console.info).toHaveBeenCalledWith("Running cleanMetricComputations");
1823
- expect(admin.firestore().collection).toHaveBeenCalledTimes(1);
1824
- expect(admin.firestore().collection).toHaveBeenCalledWith("@metrics");
1825
- expect(colGetMock).toHaveBeenCalledTimes(1);
1826
- expect(deleteCollectionSpy).toHaveBeenCalled();
1827
- 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();
1828
2088
  });
1829
2089
  });
1830
2090
  //# sourceMappingURL=index-utils.test.js.map