@mbc-cqrs-serverless/master 0.1.70-beta.0 → 0.1.72-beta.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.
@@ -8,32 +8,34 @@ const master_setting_service_1 = require("./master-setting.service");
8
8
  const constants_1 = require("../constants");
9
9
  const master_module_definition_1 = require("../master.module-definition");
10
10
  const task_1 = require("@mbc-cqrs-serverless/task");
11
- const optionsMock = {
12
- invokeContext: {
13
- event: {
14
- requestContext: {
15
- accountId: '1',
16
- http: {
17
- protocol: 'HTTP/1.1',
18
- sourceIp: '127.0.0.1',
19
- userAgent: 'PostmanRuntime/7.28.4',
20
- },
21
- requestId: '81bf1821-34b0-4dc5-a2ce-685d37d22f8c',
22
- authorizer: {
23
- jwt: {
24
- claims: {
25
- sub: 'abc',
26
- 'custom:roles': '[{"tenant":"MBC","role":"admin"}]',
27
- },
11
+ const common_1 = require("@nestjs/common");
12
+ const mockInvokeContext = {
13
+ event: {
14
+ requestContext: {
15
+ accountId: '1',
16
+ http: {
17
+ protocol: 'HTTP/1.1',
18
+ sourceIp: '127.0.0.1',
19
+ userAgent: 'PostmanRuntime/7.28.4',
20
+ },
21
+ requestId: '81bf1821-34b0-4dc5-a2ce-685d37d22f8c',
22
+ authorizer: {
23
+ jwt: {
24
+ claims: {
25
+ sub: 'abc',
26
+ 'custom:roles': '[{"tenant":"MBC","role":"admin"}]',
28
27
  },
29
28
  },
30
29
  },
31
30
  },
32
- context: {
33
- awsRequestId: '81bf1821-34b0-4dc5-a2ce-685d37d22f8c',
34
- },
31
+ },
32
+ context: {
33
+ awsRequestId: '81bf1821-34b0-4dc5-a2ce-685d37d22f8c',
35
34
  },
36
35
  };
36
+ const optionsMock = {
37
+ invokeContext: mockInvokeContext,
38
+ };
37
39
  describe('SettingService', () => {
38
40
  let service;
39
41
  let dataService;
@@ -55,12 +57,7 @@ describe('SettingService', () => {
55
57
  provide: core_1.CommandService,
56
58
  useValue: {
57
59
  publishAsync: jest.fn(),
58
- },
59
- },
60
- {
61
- provide: core_1.CommandService,
62
- useValue: {
63
- publishAsync: jest.fn(),
60
+ publishPartialUpdateAsync: jest.fn(),
64
61
  },
65
62
  },
66
63
  {
@@ -82,6 +79,7 @@ describe('SettingService', () => {
82
79
  service = module.get(master_setting_service_1.MasterSettingService);
83
80
  dataService = module.get(core_1.DataService);
84
81
  commandService = module.get(core_1.CommandService);
82
+ dynamoDbService = module.get(core_1.DynamoDbService);
85
83
  });
86
84
  //
87
85
  it('should be defined', () => {
@@ -673,6 +671,730 @@ describe('SettingService', () => {
673
671
  expect(result).toEqual(mockResponse);
674
672
  });
675
673
  });
676
- describe('deleteSetting', () => { });
674
+ describe('deleteSetting', () => {
675
+ /**
676
+ * Test Overview: Tests deleteSetting method functionality for MasterSettingService
677
+ * Purpose: Ensures settings can be properly soft deleted with correct version handling
678
+ * Details: Verifies that settings are marked as deleted rather than physically removed
679
+ */
680
+ it('should soft delete existing setting', async () => {
681
+ const key = { pk: 'MASTER#TEST_TENANT', sk: 'SETTING#TEST_CODE' };
682
+ const existingData = {
683
+ id: 'test-id',
684
+ pk: 'MASTER#TEST_TENANT',
685
+ sk: 'SETTING#TEST_CODE',
686
+ code: 'TEST_CODE',
687
+ name: 'Test Setting',
688
+ version: 1,
689
+ type: 'MASTER',
690
+ tenantCode: 'TEST_TENANT',
691
+ isDeleted: false,
692
+ createdAt: new Date(),
693
+ updatedAt: new Date(),
694
+ };
695
+ dataService.getItem.mockResolvedValue(existingData);
696
+ const mockDeleteResult = {
697
+ ...existingData,
698
+ isDeleted: true,
699
+ version: 1,
700
+ updatedAt: new Date(),
701
+ };
702
+ commandService.publishPartialUpdateAsync.mockResolvedValue(mockDeleteResult);
703
+ const result = await service.deleteSetting(key, { invokeContext: mockInvokeContext });
704
+ expect(result).toBeInstanceOf(Object);
705
+ expect(commandService.publishPartialUpdateAsync).toHaveBeenCalledWith(expect.objectContaining({
706
+ pk: key.pk,
707
+ sk: key.sk,
708
+ version: existingData.version,
709
+ isDeleted: true,
710
+ }), { invokeContext: mockInvokeContext });
711
+ });
712
+ it('should throw BadRequestException when setting does not exist', async () => {
713
+ const key = { pk: 'MASTER#TEST_TENANT', sk: 'SETTING#NONEXISTENT' };
714
+ dataService.getItem.mockResolvedValue(null);
715
+ await expect(service.deleteSetting(key, { invokeContext: mockInvokeContext }))
716
+ .rejects.toThrow(common_1.BadRequestException);
717
+ expect(commandService.publishPartialUpdateAsync).not.toHaveBeenCalled();
718
+ });
719
+ });
720
+ /**
721
+ * Test Overview: Tests comprehensive error handling scenarios for MasterSettingService operations
722
+ * Purpose: Ensures the service properly handles database failures, validation errors, and edge cases
723
+ * Details: Verifies error handling for DynamoDB failures, CommandService errors, and hierarchical processing
724
+ */
725
+ describe('Error Handling Scenarios', () => {
726
+ describe('getSetting - Hierarchical Error Handling', () => {
727
+ it('should handle DynamoDB tenant data access failures gracefully', async () => {
728
+ const tenantCode = 'TEST_TENANT';
729
+ const code = 'TEST_SETTING';
730
+ const dbError = new Error('DynamoDB access failed');
731
+ dbError.name = 'ResourceNotFoundException';
732
+ dynamoDbService.getItem.mockRejectedValue(dbError);
733
+ await expect(service.getSetting({ code }, { invokeContext: mockInvokeContext }))
734
+ .rejects.toThrow(common_1.BadRequestException);
735
+ });
736
+ it('should handle malformed tenant data in DynamoDB', async () => {
737
+ const tenantCode = 'TEST_TENANT';
738
+ const code = 'TEST_SETTING';
739
+ dynamoDbService.getItem.mockResolvedValue({
740
+ attributes: {
741
+ malformedData: 'invalid-json-structure'
742
+ }
743
+ });
744
+ await expect(service.getSetting({ code }, { invokeContext: mockInvokeContext }))
745
+ .rejects.toThrow(common_1.BadRequestException);
746
+ });
747
+ it('should handle timeout errors during hierarchical setting retrieval', async () => {
748
+ const tenantCode = 'TEST_TENANT';
749
+ const code = 'TEST_SETTING';
750
+ const timeoutError = new Error('Request timeout');
751
+ timeoutError.name = 'TimeoutError';
752
+ dataService.getItem.mockRejectedValue(timeoutError);
753
+ await expect(service.getSetting({ code }, { invokeContext: mockInvokeContext }))
754
+ .rejects.toThrow('Request timeout');
755
+ });
756
+ });
757
+ describe('createCommonTenantSetting - Error Handling', () => {
758
+ it('should handle CommandService publish failures', async () => {
759
+ const dto = {
760
+ code: 'TEST_SETTING',
761
+ name: 'Test Setting',
762
+ settingValue: { key: 'value' },
763
+ };
764
+ dataService.getItem.mockResolvedValue(null);
765
+ const publishError = new Error('Command publish failed');
766
+ commandService.publishAsync.mockRejectedValue(publishError);
767
+ await expect(service.createCommonTenantSetting(dto, { invokeContext: mockInvokeContext }))
768
+ .rejects.toThrow('Command publish failed');
769
+ });
770
+ it('should handle database connection errors during existence check', async () => {
771
+ const dto = {
772
+ code: 'TEST_SETTING',
773
+ name: 'Test Setting',
774
+ settingValue: { key: 'value' },
775
+ };
776
+ const dbError = new Error('Database connection failed');
777
+ dbError.name = 'NetworkingError';
778
+ dataService.getItem.mockRejectedValue(dbError);
779
+ await expect(service.createCommonTenantSetting(dto, { invokeContext: mockInvokeContext }))
780
+ .rejects.toThrow('Database connection failed');
781
+ });
782
+ });
783
+ describe('updateSetting - Error Handling', () => {
784
+ it('should handle version conflict errors during update', async () => {
785
+ const key = { pk: 'MASTER#TEST_TENANT', sk: 'SETTING#TEST_CODE' };
786
+ const existingData = {
787
+ id: 'test-id',
788
+ pk: 'MASTER#TEST_TENANT',
789
+ sk: 'SETTING#TEST_CODE',
790
+ code: 'TEST_CODE',
791
+ name: 'Test Setting',
792
+ version: 1,
793
+ type: 'MASTER',
794
+ tenantCode: 'TEST_TENANT',
795
+ isDeleted: false,
796
+ createdAt: new Date(),
797
+ updatedAt: new Date(),
798
+ };
799
+ dataService.getItem.mockResolvedValue(existingData);
800
+ const versionError = new Error('Version conflict');
801
+ versionError.name = 'ConditionalCheckFailedException';
802
+ commandService.publishPartialUpdateAsync.mockRejectedValue(versionError);
803
+ await expect(service.updateSetting(key, { settingValue: { updated: true } }, { invokeContext: mockInvokeContext }))
804
+ .rejects.toThrow('Version conflict');
805
+ });
806
+ it('should handle concurrent update attempts', async () => {
807
+ const key = { pk: 'MASTER#TEST_TENANT', sk: 'SETTING#TEST_CODE' };
808
+ const existingData = {
809
+ id: 'test-id',
810
+ pk: 'MASTER#TEST_TENANT',
811
+ sk: 'SETTING#TEST_CODE',
812
+ code: 'TEST_CODE',
813
+ name: 'Test Setting',
814
+ version: 1,
815
+ type: 'MASTER',
816
+ tenantCode: 'TEST_TENANT',
817
+ isDeleted: false,
818
+ createdAt: new Date(),
819
+ updatedAt: new Date(),
820
+ };
821
+ dataService.getItem.mockResolvedValue(existingData);
822
+ const concurrencyError = new Error('Concurrent modification');
823
+ commandService.publishPartialUpdateAsync.mockRejectedValue(concurrencyError);
824
+ await expect(service.updateSetting(key, { settingValue: { updated: true } }, { invokeContext: mockInvokeContext }))
825
+ .rejects.toThrow('Concurrent modification');
826
+ });
827
+ });
828
+ });
829
+ /**
830
+ * Test Overview: Tests edge cases and boundary conditions for MasterSettingService operations
831
+ * Purpose: Ensures the service handles unusual inputs and boundary conditions properly
832
+ * Details: Verifies behavior with empty values, special characters, and malformed data
833
+ */
834
+ describe('Edge Cases and Boundary Conditions', () => {
835
+ it('should handle empty setting code', async () => {
836
+ await expect(service.getSetting({ code: 'TEST_SETTING' }, { invokeContext: mockInvokeContext }))
837
+ .rejects.toThrow(common_1.BadRequestException);
838
+ });
839
+ it('should handle null setting code', async () => {
840
+ await expect(service.getSetting({ code: null }, { invokeContext: mockInvokeContext }))
841
+ .rejects.toThrow(common_1.BadRequestException);
842
+ });
843
+ it('should handle special characters in setting codes', async () => {
844
+ const specialCode = 'SETTING_特殊文字@#$%';
845
+ const mockCommonSetting = {
846
+ id: 'test-id',
847
+ pk: 'SETTING#COMMON',
848
+ sk: `SETTING#${specialCode}`,
849
+ code: specialCode,
850
+ name: 'Special Setting',
851
+ version: 1,
852
+ type: 'MASTER',
853
+ tenantCode: 'COMMON',
854
+ isDeleted: false,
855
+ attributes: { special: true },
856
+ createdAt: new Date(),
857
+ updatedAt: new Date(),
858
+ };
859
+ dataService.getItem.mockResolvedValue(mockCommonSetting);
860
+ const result = await service.getSetting({ code: specialCode }, { invokeContext: mockInvokeContext });
861
+ expect(result).toBeDefined();
862
+ expect(result).toBeInstanceOf(entities_1.MasterSettingEntity);
863
+ });
864
+ it('should handle missing user context in invoke context', async () => {
865
+ const tenantCode = 'TEST_TENANT';
866
+ const code = 'TEST_SETTING';
867
+ const emptyInvokeContext = {};
868
+ dataService.getItem.mockResolvedValue(null);
869
+ await expect(service.getSetting({ code }, { invokeContext: mockInvokeContext }))
870
+ .rejects.toThrow(common_1.BadRequestException);
871
+ });
872
+ });
873
+ describe('createTenantSetting - Edge Cases', () => {
874
+ it('should handle empty setting values', async () => {
875
+ const dto = {
876
+ tenantCode: 'TEST_TENANT',
877
+ code: 'TEST_SETTING',
878
+ name: 'Test Setting',
879
+ settingValue: {},
880
+ };
881
+ dataService.getItem.mockResolvedValue(null);
882
+ const mockResponse = {
883
+ id: 'test-id',
884
+ pk: 'SETTING#TEST_TENANT',
885
+ sk: 'SETTING#TEST_SETTING',
886
+ version: 1,
887
+ type: 'MASTER',
888
+ tenantCode: 'TEST_TENANT',
889
+ name: 'TEST_SETTING',
890
+ code: 'TEST_SETTING',
891
+ isDeleted: false,
892
+ attributes: {},
893
+ createdAt: new Date(),
894
+ updatedAt: new Date(),
895
+ };
896
+ commandService.publishAsync.mockResolvedValue(mockResponse);
897
+ const result = await service.createTenantSetting(dto, { invokeContext: mockInvokeContext });
898
+ expect(result).toBeInstanceOf(Object);
899
+ expect(result.attributes).toEqual({});
900
+ });
901
+ it('should handle very large setting values', async () => {
902
+ const largeSettingValue = {};
903
+ for (let i = 0; i < 1000; i++) {
904
+ largeSettingValue[`key${i}`] = `value${i}`.repeat(50);
905
+ }
906
+ const dto = {
907
+ tenantCode: 'TEST_TENANT',
908
+ code: 'LARGE_SETTING',
909
+ name: 'Large Setting',
910
+ settingValue: largeSettingValue,
911
+ };
912
+ dataService.getItem.mockResolvedValue(null);
913
+ const mockResponse = {
914
+ id: 'test-id',
915
+ pk: 'SETTING#TEST_TENANT',
916
+ sk: 'SETTING#LARGE_SETTING',
917
+ version: 1,
918
+ type: 'MASTER',
919
+ tenantCode: 'TEST_TENANT',
920
+ name: 'LARGE_SETTING',
921
+ code: 'LARGE_SETTING',
922
+ isDeleted: false,
923
+ attributes: largeSettingValue,
924
+ createdAt: new Date(),
925
+ updatedAt: new Date(),
926
+ };
927
+ commandService.publishAsync.mockResolvedValue(mockResponse);
928
+ const result = await service.createTenantSetting(dto, { invokeContext: mockInvokeContext });
929
+ expect(result).toBeInstanceOf(Object);
930
+ expect(Object.keys(result.attributes)).toHaveLength(1000);
931
+ });
932
+ });
933
+ /**
934
+ * Test Overview: Tests comprehensive deleted setting re-addition scenarios for MasterSettingService
935
+ * Purpose: Ensures deleted settings can be properly recreated for all setting types with correct version handling
936
+ * Details: Verifies version increment, attribute preservation, and state transitions for common, tenant, group, and user settings
937
+ */
938
+ describe('Deleted Setting Re-addition Scenarios', () => {
939
+ describe('createCommonTenantSetting - Deleted Setting Recreation', () => {
940
+ it('should recreate deleted common setting with incremented version', async () => {
941
+ const dto = {
942
+ code: 'RECREATED_SETTING',
943
+ name: 'Recreated Setting',
944
+ settingValue: { recreated: true, version: 2 },
945
+ };
946
+ const existingDeletedSetting = {
947
+ id: 'existing-id',
948
+ pk: 'SETTING#COMMON',
949
+ sk: 'SETTING#RECREATED_SETTING',
950
+ code: 'RECREATED_SETTING',
951
+ name: 'RECREATED_SETTING',
952
+ version: 3,
953
+ type: 'MASTER',
954
+ tenantCode: 'COMMON',
955
+ isDeleted: true,
956
+ attributes: { original: true, version: 1 },
957
+ createdAt: new Date('2023-01-01'),
958
+ updatedAt: new Date('2023-01-02'),
959
+ };
960
+ dataService.getItem.mockResolvedValue(existingDeletedSetting);
961
+ const mockResponse = {
962
+ id: 'existing-id',
963
+ pk: 'SETTING#COMMON',
964
+ sk: 'SETTING#RECREATED_SETTING',
965
+ version: 3,
966
+ type: 'MASTER',
967
+ tenantCode: 'COMMON',
968
+ name: 'RECREATED_SETTING',
969
+ code: 'RECREATED_SETTING',
970
+ isDeleted: false,
971
+ attributes: dto.settingValue,
972
+ createdAt: new Date('2023-01-01'),
973
+ updatedAt: new Date(),
974
+ };
975
+ commandService.publishAsync.mockResolvedValue(mockResponse);
976
+ const result = await service.createCommonTenantSetting(dto, { invokeContext: mockInvokeContext });
977
+ expect(result).toBeInstanceOf(Object);
978
+ expect(result.version).toBe(3);
979
+ expect(result.isDeleted).toBe(false);
980
+ expect(result.attributes).toEqual({ recreated: true, version: 2 });
981
+ expect(commandService.publishAsync).toHaveBeenCalledWith(expect.objectContaining({
982
+ version: 3,
983
+ isDeleted: false,
984
+ attributes: { recreated: true, version: 2 },
985
+ }), { invokeContext: mockInvokeContext });
986
+ });
987
+ it('should handle multiple deletion and recreation cycles for common settings', async () => {
988
+ const dto = {
989
+ code: 'CYCLED_SETTING',
990
+ name: 'Cycled Setting',
991
+ settingValue: { cycle: 4, final: true },
992
+ };
993
+ const existingDeletedSetting = {
994
+ id: 'existing-id',
995
+ pk: 'SETTING#COMMON',
996
+ sk: 'SETTING#CYCLED_SETTING',
997
+ code: 'CYCLED_SETTING',
998
+ name: 'CYCLED_SETTING',
999
+ version: 7,
1000
+ type: 'MASTER',
1001
+ tenantCode: 'COMMON',
1002
+ isDeleted: true,
1003
+ attributes: { cycle: 3, previous: true },
1004
+ createdAt: new Date('2023-01-01'),
1005
+ updatedAt: new Date('2023-01-04'),
1006
+ };
1007
+ dataService.getItem.mockResolvedValue(existingDeletedSetting);
1008
+ const mockResponse = {
1009
+ id: 'existing-id',
1010
+ pk: 'SETTING#COMMON',
1011
+ sk: 'SETTING#CYCLED_SETTING',
1012
+ version: 7,
1013
+ type: 'MASTER',
1014
+ tenantCode: 'COMMON',
1015
+ name: 'CYCLED_SETTING',
1016
+ code: 'CYCLED_SETTING',
1017
+ isDeleted: false,
1018
+ attributes: dto.settingValue,
1019
+ createdAt: new Date('2023-01-01'),
1020
+ updatedAt: new Date(),
1021
+ };
1022
+ commandService.publishAsync.mockResolvedValue(mockResponse);
1023
+ const result = await service.createCommonTenantSetting(dto, { invokeContext: mockInvokeContext });
1024
+ expect(result).toBeInstanceOf(Object);
1025
+ expect(result.version).toBe(7);
1026
+ expect(result.isDeleted).toBe(false);
1027
+ expect(result.attributes).toEqual({ cycle: 4, final: true });
1028
+ });
1029
+ });
1030
+ describe('createTenantSetting - Deleted Setting Recreation', () => {
1031
+ it('should recreate deleted tenant setting with proper tenant isolation', async () => {
1032
+ const dto = {
1033
+ tenantCode: 'TENANT_A',
1034
+ code: 'TENANT_SETTING',
1035
+ name: 'Tenant Setting',
1036
+ settingValue: { tenant: 'A', recreated: true },
1037
+ };
1038
+ const existingDeletedSetting = {
1039
+ id: 'existing-id',
1040
+ pk: 'SETTING#TENANT_A',
1041
+ sk: 'SETTING#TENANT_SETTING',
1042
+ code: 'TENANT_SETTING',
1043
+ name: 'TENANT_SETTING',
1044
+ version: 2,
1045
+ type: 'MASTER',
1046
+ tenantCode: 'TENANT_A',
1047
+ isDeleted: true,
1048
+ attributes: { tenant: 'A', original: true },
1049
+ createdAt: new Date('2023-01-01'),
1050
+ updatedAt: new Date('2023-01-02'),
1051
+ };
1052
+ dataService.getItem.mockResolvedValue(existingDeletedSetting);
1053
+ const mockResponse = {
1054
+ id: 'existing-id',
1055
+ pk: 'SETTING#TENANT_A',
1056
+ sk: 'SETTING#TENANT_SETTING',
1057
+ version: 2,
1058
+ type: 'MASTER',
1059
+ tenantCode: 'TENANT_A',
1060
+ name: 'TENANT_SETTING',
1061
+ code: 'TENANT_SETTING',
1062
+ isDeleted: false,
1063
+ attributes: dto.settingValue,
1064
+ createdAt: new Date('2023-01-01'),
1065
+ updatedAt: new Date(),
1066
+ };
1067
+ commandService.publishAsync.mockResolvedValue(mockResponse);
1068
+ const result = await service.createTenantSetting(dto, { invokeContext: mockInvokeContext });
1069
+ expect(result).toBeInstanceOf(Object);
1070
+ expect(result.version).toBe(2);
1071
+ expect(result.tenantCode).toBe('TENANT_A');
1072
+ expect(result.isDeleted).toBe(false);
1073
+ expect(result.attributes).toEqual({ tenant: 'A', recreated: true });
1074
+ });
1075
+ it('should handle tenant setting recreation with different tenant codes', async () => {
1076
+ const dto = {
1077
+ tenantCode: 'TENANT_B',
1078
+ code: 'SHARED_SETTING',
1079
+ name: 'Shared Setting',
1080
+ settingValue: { tenant: 'B', value: 'new' },
1081
+ };
1082
+ dataService.getItem.mockResolvedValue(null);
1083
+ const mockResponse = {
1084
+ id: 'new-id',
1085
+ pk: 'SETTING#TENANT_B',
1086
+ sk: 'SETTING#SHARED_SETTING',
1087
+ version: 1,
1088
+ type: 'MASTER',
1089
+ tenantCode: 'TENANT_B',
1090
+ name: 'SHARED_SETTING',
1091
+ code: 'SHARED_SETTING',
1092
+ isDeleted: false,
1093
+ attributes: dto.settingValue,
1094
+ createdAt: new Date(),
1095
+ updatedAt: new Date(),
1096
+ };
1097
+ commandService.publishAsync.mockResolvedValue(mockResponse);
1098
+ const result = await service.createTenantSetting(dto, { invokeContext: mockInvokeContext });
1099
+ expect(result).toBeInstanceOf(Object);
1100
+ expect(result.version).toBe(1);
1101
+ expect(result.tenantCode).toBe('TENANT_B');
1102
+ expect(result.isDeleted).toBe(false);
1103
+ });
1104
+ });
1105
+ describe('createGroupSetting - Deleted Setting Recreation', () => {
1106
+ it('should recreate deleted group setting with group hierarchy validation', async () => {
1107
+ const dto = {
1108
+ tenantCode: 'TEST_TENANT',
1109
+ groupId: 'GROUP_123',
1110
+ code: 'GROUP_SETTING',
1111
+ name: 'Group Setting',
1112
+ settingValue: { group: 'GROUP_123', recreated: true },
1113
+ };
1114
+ const existingDeletedSetting = {
1115
+ id: 'existing-id',
1116
+ pk: 'SETTING#TEST_TENANT',
1117
+ sk: 'SETTING#GROUP#GROUP_123#GROUP_SETTING',
1118
+ code: 'GROUP_SETTING',
1119
+ name: 'GROUP_SETTING',
1120
+ version: 4,
1121
+ type: 'MASTER',
1122
+ tenantCode: 'TEST_TENANT',
1123
+ isDeleted: true,
1124
+ attributes: { group: 'GROUP_123', original: true },
1125
+ createdAt: new Date('2023-01-01'),
1126
+ updatedAt: new Date('2023-01-03'),
1127
+ };
1128
+ dataService.getItem.mockResolvedValue(existingDeletedSetting);
1129
+ const mockResponse = {
1130
+ id: 'existing-id',
1131
+ pk: 'SETTING#TEST_TENANT',
1132
+ sk: 'SETTING#GROUP#GROUP_123#GROUP_SETTING',
1133
+ version: 4,
1134
+ type: 'MASTER',
1135
+ tenantCode: 'TEST_TENANT',
1136
+ name: 'GROUP_SETTING',
1137
+ code: 'GROUP_SETTING',
1138
+ isDeleted: false,
1139
+ attributes: dto.settingValue,
1140
+ createdAt: new Date('2023-01-01'),
1141
+ updatedAt: new Date(),
1142
+ };
1143
+ commandService.publishAsync.mockResolvedValue(mockResponse);
1144
+ const result = await service.createGroupSetting(dto, { invokeContext: mockInvokeContext });
1145
+ expect(result).toBeInstanceOf(Object);
1146
+ expect(result.version).toBe(4);
1147
+ expect(result.tenantCode).toBe('TEST_TENANT');
1148
+ expect(result.isDeleted).toBe(false);
1149
+ expect(result.attributes).toEqual({ group: 'GROUP_123', recreated: true });
1150
+ });
1151
+ it('should handle group setting recreation across different groups', async () => {
1152
+ const dto = {
1153
+ tenantCode: 'TEST_TENANT',
1154
+ groupId: 'GROUP_456',
1155
+ code: 'SHARED_GROUP_SETTING',
1156
+ name: 'Shared Group Setting',
1157
+ settingValue: { group: 'GROUP_456', value: 'different' },
1158
+ };
1159
+ dataService.getItem.mockResolvedValue(null);
1160
+ const mockResponse = {
1161
+ id: 'new-id',
1162
+ pk: 'SETTING#TEST_TENANT',
1163
+ sk: 'SETTING#GROUP#GROUP_456#SHARED_GROUP_SETTING',
1164
+ version: 1,
1165
+ type: 'MASTER',
1166
+ tenantCode: 'TEST_TENANT',
1167
+ name: 'SHARED_GROUP_SETTING',
1168
+ code: 'SHARED_GROUP_SETTING',
1169
+ isDeleted: false,
1170
+ attributes: dto.settingValue,
1171
+ createdAt: new Date(),
1172
+ updatedAt: new Date(),
1173
+ };
1174
+ commandService.publishAsync.mockResolvedValue(mockResponse);
1175
+ const result = await service.createGroupSetting(dto, { invokeContext: mockInvokeContext });
1176
+ expect(result).toBeInstanceOf(Object);
1177
+ expect(result.version).toBe(1);
1178
+ expect(result.isDeleted).toBe(false);
1179
+ expect(result.attributes).toEqual({ group: 'GROUP_456', value: 'different' });
1180
+ });
1181
+ });
1182
+ describe('createUserSetting - Deleted Setting Recreation', () => {
1183
+ it('should recreate deleted user setting with user context validation', async () => {
1184
+ const dto = {
1185
+ tenantCode: 'TEST_TENANT',
1186
+ userId: 'USER_789',
1187
+ code: 'USER_SETTING',
1188
+ name: 'User Setting',
1189
+ settingValue: { user: 'USER_789', recreated: true },
1190
+ };
1191
+ const existingDeletedSetting = {
1192
+ id: 'existing-id',
1193
+ pk: 'SETTING#TEST_TENANT',
1194
+ sk: 'SETTING#USER#USER_789#USER_SETTING',
1195
+ code: 'USER_SETTING',
1196
+ name: 'USER_SETTING',
1197
+ version: 6,
1198
+ type: 'MASTER',
1199
+ tenantCode: 'TEST_TENANT',
1200
+ isDeleted: true,
1201
+ attributes: { user: 'USER_789', original: true },
1202
+ createdAt: new Date('2023-01-01'),
1203
+ updatedAt: new Date('2023-01-05'),
1204
+ };
1205
+ dataService.getItem.mockResolvedValue(existingDeletedSetting);
1206
+ const mockResponse = {
1207
+ id: 'existing-id',
1208
+ pk: 'SETTING#TEST_TENANT',
1209
+ sk: 'SETTING#USER#USER_789#USER_SETTING',
1210
+ version: 6,
1211
+ type: 'MASTER',
1212
+ tenantCode: 'TEST_TENANT',
1213
+ name: 'USER_SETTING',
1214
+ code: 'USER_SETTING',
1215
+ isDeleted: false,
1216
+ attributes: dto.settingValue,
1217
+ createdAt: new Date('2023-01-01'),
1218
+ updatedAt: new Date(),
1219
+ };
1220
+ commandService.publishAsync.mockResolvedValue(mockResponse);
1221
+ const result = await service.createUserSetting(dto, { invokeContext: mockInvokeContext });
1222
+ expect(result).toBeInstanceOf(Object);
1223
+ expect(result.version).toBe(6);
1224
+ expect(result.tenantCode).toBe('TEST_TENANT');
1225
+ expect(result.isDeleted).toBe(false);
1226
+ expect(result.attributes).toEqual({ user: 'USER_789', recreated: true });
1227
+ });
1228
+ it('should preserve original creation timestamp when recreating user setting', async () => {
1229
+ const originalCreatedAt = new Date('2023-01-01T08:00:00Z');
1230
+ const dto = {
1231
+ tenantCode: 'TEST_TENANT',
1232
+ userId: 'USER_ABC',
1233
+ code: 'TIMESTAMP_SETTING',
1234
+ name: 'Timestamp Setting',
1235
+ settingValue: { preserveTimestamp: true },
1236
+ };
1237
+ const existingDeletedSetting = {
1238
+ id: 'existing-id',
1239
+ pk: 'SETTING#TEST_TENANT',
1240
+ sk: 'SETTING#USER#USER_ABC#TIMESTAMP_SETTING',
1241
+ code: 'TIMESTAMP_SETTING',
1242
+ name: 'TIMESTAMP_SETTING',
1243
+ version: 3,
1244
+ type: 'MASTER',
1245
+ tenantCode: 'TEST_TENANT',
1246
+ isDeleted: true,
1247
+ attributes: { original: true },
1248
+ createdAt: originalCreatedAt,
1249
+ updatedAt: new Date('2023-01-02'),
1250
+ };
1251
+ dataService.getItem.mockResolvedValue(existingDeletedSetting);
1252
+ const mockResponse = {
1253
+ id: 'existing-id',
1254
+ pk: 'SETTING#TEST_TENANT',
1255
+ sk: 'SETTING#USER#USER_ABC#TIMESTAMP_SETTING',
1256
+ version: 3,
1257
+ type: 'MASTER',
1258
+ tenantCode: 'TEST_TENANT',
1259
+ name: 'TIMESTAMP_SETTING',
1260
+ code: 'TIMESTAMP_SETTING',
1261
+ isDeleted: false,
1262
+ attributes: dto.settingValue,
1263
+ createdAt: originalCreatedAt,
1264
+ updatedAt: new Date(),
1265
+ };
1266
+ commandService.publishAsync.mockResolvedValue(mockResponse);
1267
+ const result = await service.createUserSetting(dto, { invokeContext: mockInvokeContext });
1268
+ expect(result).toBeInstanceOf(Object);
1269
+ expect(result.createdAt).toEqual(originalCreatedAt);
1270
+ expect(result.isDeleted).toBe(false);
1271
+ expect(result.attributes).toEqual({ preserveTimestamp: true });
1272
+ });
1273
+ });
1274
+ });
1275
+ /**
1276
+ * Test Overview: Tests concurrent operation scenarios for MasterSettingService
1277
+ * Purpose: Ensures the service handles simultaneous operations correctly with proper version control
1278
+ * Details: Verifies race condition handling, version conflicts, and concurrent hierarchical setting operations
1279
+ */
1280
+ describe('Concurrent Operation Scenarios', () => {
1281
+ describe('getSetting - Concurrent Hierarchical Access', () => {
1282
+ it('should handle concurrent hierarchical setting access correctly', async () => {
1283
+ const tenantCode = 'TEST_TENANT';
1284
+ const code = 'CONCURRENT_SETTING';
1285
+ const mockCommonSetting = {
1286
+ id: 'test-id',
1287
+ pk: 'SETTING#COMMON',
1288
+ sk: 'SETTING#CONCURRENT_SETTING',
1289
+ code: 'CONCURRENT_SETTING',
1290
+ name: 'Concurrent Setting',
1291
+ version: 1,
1292
+ type: 'MASTER',
1293
+ tenantCode: 'COMMON',
1294
+ isDeleted: false,
1295
+ attributes: { concurrent: true },
1296
+ createdAt: new Date(),
1297
+ updatedAt: new Date(),
1298
+ };
1299
+ dataService.getItem.mockResolvedValue(mockCommonSetting);
1300
+ const promises = Array.from({ length: 5 }, () => service.getSetting({ code }, { invokeContext: mockInvokeContext }));
1301
+ const results = await Promise.all(promises);
1302
+ results.forEach(result => {
1303
+ expect(result).toBeInstanceOf(entities_1.MasterSettingEntity);
1304
+ expect(result).toBeDefined();
1305
+ });
1306
+ });
1307
+ it('should handle mixed success and failure in concurrent hierarchical access', async () => {
1308
+ const tenantCode = 'TEST_TENANT';
1309
+ const code = 'MIXED_SETTING';
1310
+ dataService.getItem
1311
+ .mockResolvedValueOnce(null)
1312
+ .mockRejectedValueOnce(new Error('User fetch failed'))
1313
+ .mockResolvedValueOnce(null);
1314
+ const results = await Promise.allSettled([
1315
+ service.getSetting({ code }, { invokeContext: mockInvokeContext }),
1316
+ service.getSetting({ code }, { invokeContext: mockInvokeContext }),
1317
+ service.getSetting({ code }, { invokeContext: mockInvokeContext })
1318
+ ]);
1319
+ expect(results[0].status).toBe('rejected');
1320
+ expect(results[1].status).toBe('rejected');
1321
+ expect(results[2].status).toBe('rejected');
1322
+ });
1323
+ });
1324
+ describe('createTenantSetting - Concurrent Operations', () => {
1325
+ it('should handle concurrent tenant setting creation with version conflicts', async () => {
1326
+ const dto1 = {
1327
+ tenantCode: 'TEST_TENANT',
1328
+ code: 'CONCURRENT_SETTING',
1329
+ name: 'Concurrent Setting 1',
1330
+ settingValue: { first: true },
1331
+ };
1332
+ const dto2 = {
1333
+ tenantCode: 'TEST_TENANT',
1334
+ code: 'CONCURRENT_SETTING',
1335
+ name: 'Concurrent Setting 2',
1336
+ settingValue: { second: true },
1337
+ };
1338
+ dataService.getItem.mockResolvedValue(null);
1339
+ const mockResponse1 = {
1340
+ id: 'test-id-1',
1341
+ pk: 'SETTING#TEST_TENANT',
1342
+ sk: 'SETTING#CONCURRENT_SETTING',
1343
+ version: 1,
1344
+ type: 'MASTER',
1345
+ tenantCode: 'TEST_TENANT',
1346
+ name: 'CONCURRENT_SETTING',
1347
+ code: 'CONCURRENT_SETTING',
1348
+ isDeleted: false,
1349
+ attributes: dto1.settingValue,
1350
+ createdAt: new Date(),
1351
+ updatedAt: new Date(),
1352
+ };
1353
+ const versionConflictError = new Error('Version conflict');
1354
+ versionConflictError.name = 'ConditionalCheckFailedException';
1355
+ commandService.publishAsync
1356
+ .mockResolvedValueOnce(mockResponse1)
1357
+ .mockRejectedValueOnce(versionConflictError);
1358
+ const result1 = await service.createTenantSetting(dto1, { invokeContext: mockInvokeContext });
1359
+ await expect(service.createTenantSetting(dto2, { invokeContext: mockInvokeContext }))
1360
+ .rejects.toThrow('Version conflict');
1361
+ expect(result1).toBeInstanceOf(Object);
1362
+ expect(result1.attributes).toEqual({ first: true });
1363
+ });
1364
+ });
1365
+ describe('updateSetting - Concurrent Operations', () => {
1366
+ it('should handle concurrent setting updates with proper version control', async () => {
1367
+ const key = { pk: 'SETTING#TEST_TENANT', sk: 'SETTING#UPDATE_TEST' };
1368
+ const existingData = {
1369
+ id: 'test-id',
1370
+ pk: 'SETTING#TEST_TENANT',
1371
+ sk: 'SETTING#UPDATE_TEST',
1372
+ code: 'UPDATE_TEST',
1373
+ name: 'Update Test',
1374
+ version: 1,
1375
+ type: 'MASTER',
1376
+ tenantCode: 'TEST_TENANT',
1377
+ isDeleted: false,
1378
+ createdAt: new Date(),
1379
+ updatedAt: new Date(),
1380
+ };
1381
+ dataService.getItem.mockResolvedValue(existingData);
1382
+ const mockUpdateResult1 = {
1383
+ ...existingData,
1384
+ version: 2,
1385
+ updatedAt: new Date(),
1386
+ };
1387
+ const versionConflictError = new Error('Version conflict');
1388
+ versionConflictError.name = 'ConditionalCheckFailedException';
1389
+ commandService.publishPartialUpdateAsync
1390
+ .mockResolvedValueOnce(mockUpdateResult1)
1391
+ .mockRejectedValueOnce(versionConflictError);
1392
+ const result1 = await service.updateSetting(key, { settingValue: { updated: true } }, { invokeContext: mockInvokeContext });
1393
+ await expect(service.updateSetting(key, { settingValue: { updated: true } }, { invokeContext: mockInvokeContext }))
1394
+ .rejects.toThrow('Version conflict');
1395
+ expect(result1.version).toBe(2);
1396
+ });
1397
+ });
1398
+ });
677
1399
  });
678
1400
  //# sourceMappingURL=master-setting.service.spec.js.map