@flusys/nestjs-localization 6.0.2 → 6.1.1

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.
@@ -0,0 +1,165 @@
1
+ import { Test } from '@nestjs/testing';
2
+ import { UtilsService } from '@flusys/nestjs-shared/modules';
3
+ import { buildMockUser } from '@test-utils/mocks/logged-user.mock';
4
+ import { TranslationController } from './translation.controller';
5
+ import { TranslationService } from '../services';
6
+ function buildTranslation(overrides = {}) {
7
+ return {
8
+ id: 'translation-1',
9
+ languageId: 'lang-en',
10
+ translationKeyId: 'tkey-1',
11
+ value: 'Login successful',
12
+ isVerified: false,
13
+ isActive: true,
14
+ createdAt: new Date(),
15
+ updatedAt: new Date(),
16
+ deletedAt: null,
17
+ createdById: null,
18
+ updatedById: null,
19
+ deletedById: null,
20
+ ...overrides
21
+ };
22
+ }
23
+ describe('TranslationController', ()=>{
24
+ let controller;
25
+ let mockService;
26
+ beforeEach(async ()=>{
27
+ mockService = {
28
+ insert: jest.fn(),
29
+ findById: jest.fn(),
30
+ getAll: jest.fn(),
31
+ update: jest.fn(),
32
+ delete: jest.fn(),
33
+ getByLanguageCode: jest.fn(),
34
+ bulkUpdate: jest.fn(),
35
+ getTranslationCounts: jest.fn()
36
+ };
37
+ const module = await Test.createTestingModule({
38
+ controllers: [
39
+ TranslationController
40
+ ],
41
+ providers: [
42
+ {
43
+ provide: TranslationService,
44
+ useValue: mockService
45
+ },
46
+ {
47
+ provide: UtilsService,
48
+ useValue: {}
49
+ }
50
+ ]
51
+ }).compile();
52
+ controller = module.get(TranslationController);
53
+ });
54
+ describe('getByLanguage', ()=>{
55
+ it('returns the translations map for the requested language and modules', async ()=>{
56
+ mockService.getByLanguageCode.mockResolvedValue({
57
+ 'auth.login.success': 'Login successful'
58
+ });
59
+ const result = await controller.getByLanguage({
60
+ languageCode: 'en',
61
+ modules: [
62
+ 'auth'
63
+ ]
64
+ });
65
+ expect(mockService.getByLanguageCode).toHaveBeenCalledWith('en', [
66
+ 'auth'
67
+ ]);
68
+ expect(result).toEqual({
69
+ success: true,
70
+ message: 'Translations retrieved successfully',
71
+ messageKey: 'translation.by.language.success',
72
+ data: {
73
+ 'auth.login.success': 'Login successful'
74
+ }
75
+ });
76
+ });
77
+ it('returns an empty map (fallback) for an unknown language code without throwing', async ()=>{
78
+ mockService.getByLanguageCode.mockResolvedValue({});
79
+ const result = await controller.getByLanguage({
80
+ languageCode: 'xx'
81
+ });
82
+ expect(result.success).toBe(true);
83
+ expect(result.data).toEqual({});
84
+ });
85
+ it('requests translations for all modules when modules is omitted', async ()=>{
86
+ mockService.getByLanguageCode.mockResolvedValue({});
87
+ await controller.getByLanguage({
88
+ languageCode: 'en'
89
+ });
90
+ expect(mockService.getByLanguageCode).toHaveBeenCalledWith('en', undefined);
91
+ });
92
+ });
93
+ describe('updateManyValues', ()=>{
94
+ it('bulk updates translations and reports the updated count', async ()=>{
95
+ const updated = [
96
+ buildTranslation(),
97
+ buildTranslation({
98
+ id: 'translation-2'
99
+ })
100
+ ];
101
+ mockService.bulkUpdate.mockResolvedValue(updated);
102
+ const result = await controller.updateManyValues({
103
+ translations: [
104
+ {
105
+ languageId: 'lang-en',
106
+ translationKeyId: 'tkey-1',
107
+ value: 'Login successful'
108
+ }
109
+ ]
110
+ }, buildMockUser());
111
+ expect(result).toEqual({
112
+ success: true,
113
+ message: '2 translations updated',
114
+ messageKey: 'translation.update.many.success',
115
+ messageVariables: {
116
+ count: 2
117
+ },
118
+ data: updated,
119
+ meta: {
120
+ count: 2
121
+ }
122
+ });
123
+ });
124
+ it('reports zero updates for an empty batch without calling the service twice', async ()=>{
125
+ mockService.bulkUpdate.mockResolvedValue([]);
126
+ const result = await controller.updateManyValues({
127
+ translations: []
128
+ }, buildMockUser());
129
+ expect(mockService.bulkUpdate).toHaveBeenCalledTimes(1);
130
+ expect(result.meta).toEqual({
131
+ count: 0
132
+ });
133
+ expect(result.message).toBe('0 translations updated');
134
+ });
135
+ it('propagates errors raised during the bulk transaction (e.g. rollback surfaced as a rejection)', async ()=>{
136
+ mockService.bulkUpdate.mockRejectedValue(new Error('transaction rolled back'));
137
+ await expect(controller.updateManyValues({
138
+ translations: []
139
+ }, buildMockUser())).rejects.toThrow('transaction rolled back');
140
+ });
141
+ });
142
+ describe('getCounts', ()=>{
143
+ it('returns translation counts keyed by translation key id', async ()=>{
144
+ mockService.getTranslationCounts.mockResolvedValue({
145
+ 'tkey-1': 3,
146
+ 'tkey-2': 1
147
+ });
148
+ const result = await controller.getCounts(buildMockUser());
149
+ expect(result).toEqual({
150
+ success: true,
151
+ message: 'Translation counts retrieved successfully',
152
+ messageKey: 'translation.get.all.success',
153
+ data: {
154
+ 'tkey-1': 3,
155
+ 'tkey-2': 1
156
+ }
157
+ });
158
+ });
159
+ it('returns an empty object when there are no translations yet', async ()=>{
160
+ mockService.getTranslationCounts.mockResolvedValue({});
161
+ const result = await controller.getCounts(buildMockUser());
162
+ expect(result.data).toEqual({});
163
+ });
164
+ });
165
+ });
@@ -0,0 +1,26 @@
1
+ import { localizationSwaggerConfig } from './localization-swagger.config';
2
+ describe('localizationSwaggerConfig', ()=>{
3
+ it('should build a static config with no multi-tenant note by default', ()=>{
4
+ const config = localizationSwaggerConfig();
5
+ expect(config.title).toBe('Localization API');
6
+ expect(config.path).toBe('api/docs/localization');
7
+ expect(config.bearerAuth).toBe(true);
8
+ expect(config.description).not.toContain('Multi-Tenant Mode');
9
+ });
10
+ it('should include the multi-tenant note only for multi-tenant database mode', ()=>{
11
+ const multiTenant = localizationSwaggerConfig({
12
+ databaseMode: 'multi-tenant'
13
+ });
14
+ const single = localizationSwaggerConfig({
15
+ databaseMode: 'single'
16
+ });
17
+ expect(multiTenant.description).toContain('Multi-Tenant Mode');
18
+ expect(single.description).not.toContain('Multi-Tenant Mode');
19
+ });
20
+ it('should treat any non-"multi-tenant" value as single-database mode', ()=>{
21
+ const config = localizationSwaggerConfig({
22
+ databaseMode: 'unexpected-value'
23
+ });
24
+ expect(config.description).not.toContain('Multi-Tenant Mode');
25
+ });
26
+ });
@@ -0,0 +1,14 @@
1
+ import { getLocalizationEntities, LocalizationEntities } from './index';
2
+ import { Language } from './language.entity';
3
+ import { TranslationKey } from './translation-key.entity';
4
+ import { Translation } from './translation.entity';
5
+ describe('getLocalizationEntities', ()=>{
6
+ it('returns the fixed entity list (no company/core split for localization)', ()=>{
7
+ expect(getLocalizationEntities()).toBe(LocalizationEntities);
8
+ expect(getLocalizationEntities()).toEqual([
9
+ Language,
10
+ TranslationKey,
11
+ Translation
12
+ ]);
13
+ });
14
+ });
@@ -0,0 +1,128 @@
1
+ function _define_property(obj, key, value) {
2
+ if (key in obj) {
3
+ Object.defineProperty(obj, key, {
4
+ value: value,
5
+ enumerable: true,
6
+ configurable: true,
7
+ writable: true
8
+ });
9
+ } else {
10
+ obj[key] = value;
11
+ }
12
+ return obj;
13
+ }
14
+ import { LocalizationModule } from './localization.module';
15
+ import { LOCALIZATION_MODULE_OPTIONS } from '../config/localization.constants';
16
+ import { LanguageController, TranslationKeyController, TranslationController } from '../controllers';
17
+ function providerTokens(providers) {
18
+ return providers.map((p)=>typeof p === 'function' ? p : p.provide);
19
+ }
20
+ describe('LocalizationModule', ()=>{
21
+ describe('forRoot', ()=>{
22
+ it('should default to a non-global module with all controllers registered', ()=>{
23
+ const dynamicModule = LocalizationModule.forRoot({});
24
+ expect(dynamicModule.module).toBe(LocalizationModule);
25
+ expect(dynamicModule.global).toBe(false);
26
+ expect(dynamicModule.controllers).toEqual([
27
+ LanguageController,
28
+ TranslationKeyController,
29
+ TranslationController
30
+ ]);
31
+ });
32
+ it('should respect global: true', ()=>{
33
+ expect(LocalizationModule.forRoot({
34
+ global: true
35
+ }).global).toBe(true);
36
+ });
37
+ it('should register no controllers when includeController is false', ()=>{
38
+ expect(LocalizationModule.forRoot({
39
+ includeController: false
40
+ }).controllers).toEqual([]);
41
+ });
42
+ it('should register a LOCALIZATION_MODULE_OPTIONS provider carrying the given options', ()=>{
43
+ const options = {
44
+ bootstrapAppConfig: {}
45
+ };
46
+ const dynamicModule = LocalizationModule.forRoot(options);
47
+ const provider = dynamicModule.providers.find((p)=>p.provide === LOCALIZATION_MODULE_OPTIONS);
48
+ expect(provider.useValue).toBe(options);
49
+ });
50
+ it('should always export the same fixed set of services', ()=>{
51
+ expect(LocalizationModule.forRoot({}).exports).toHaveLength(5);
52
+ });
53
+ });
54
+ describe('forRootAsync', ()=>{
55
+ it('should forward external imports alongside CacheModule/UtilsModule', ()=>{
56
+ let FakeImportedModule = class FakeImportedModule {
57
+ };
58
+ const dynamicModule = LocalizationModule.forRootAsync({
59
+ imports: [
60
+ FakeImportedModule
61
+ ]
62
+ });
63
+ expect(dynamicModule.imports).toEqual(expect.arrayContaining([
64
+ FakeImportedModule
65
+ ]));
66
+ });
67
+ it('useFactory: builds a LOCALIZATION_MODULE_OPTIONS provider merging resolved config', async ()=>{
68
+ const useFactory = jest.fn().mockResolvedValue({
69
+ foo: 'bar'
70
+ });
71
+ const dynamicModule = LocalizationModule.forRootAsync({
72
+ useFactory,
73
+ inject: [
74
+ 'SOME_TOKEN'
75
+ ]
76
+ });
77
+ const provider = dynamicModule.providers.find((p)=>p.provide === LOCALIZATION_MODULE_OPTIONS);
78
+ expect(provider.inject).toEqual([
79
+ 'SOME_TOKEN'
80
+ ]);
81
+ const resolved = await provider.useFactory('injected');
82
+ expect(useFactory).toHaveBeenCalledWith('injected');
83
+ expect(resolved.config).toEqual({
84
+ foo: 'bar'
85
+ });
86
+ });
87
+ it('useClass: registers both the class provider and an options provider calling createLocalizationOptions()', async ()=>{
88
+ let MyOptionsFactory = class MyOptionsFactory {
89
+ constructor(){
90
+ _define_property(this, "createLocalizationOptions", jest.fn().mockResolvedValue({
91
+ foo: 'from-class'
92
+ }));
93
+ }
94
+ };
95
+ const dynamicModule = LocalizationModule.forRootAsync({
96
+ useClass: MyOptionsFactory
97
+ });
98
+ const classProvider = dynamicModule.providers.find((p)=>p.provide === MyOptionsFactory);
99
+ expect(classProvider.useClass).toBe(MyOptionsFactory);
100
+ const optionsProvider = dynamicModule.providers.find((p)=>p.provide === LOCALIZATION_MODULE_OPTIONS);
101
+ const instance = new MyOptionsFactory();
102
+ const resolved = await optionsProvider.useFactory(instance);
103
+ expect(instance.createLocalizationOptions).toHaveBeenCalled();
104
+ expect(resolved.config).toEqual({
105
+ foo: 'from-class'
106
+ });
107
+ });
108
+ it('useExisting: registers a single options provider without a duplicate class registration', ()=>{
109
+ let MyOptionsFactory = class MyOptionsFactory {
110
+ constructor(){
111
+ _define_property(this, "createLocalizationOptions", jest.fn());
112
+ }
113
+ };
114
+ const dynamicModule = LocalizationModule.forRootAsync({
115
+ useExisting: MyOptionsFactory
116
+ });
117
+ expect(dynamicModule.providers.filter((p)=>p.provide === MyOptionsFactory)).toHaveLength(0);
118
+ const optionsProvider = dynamicModule.providers.find((p)=>p.provide === LOCALIZATION_MODULE_OPTIONS);
119
+ expect(optionsProvider.inject).toEqual([
120
+ MyOptionsFactory
121
+ ]);
122
+ });
123
+ it('should register no LOCALIZATION_MODULE_OPTIONS provider when neither useFactory, useClass, nor useExisting is given', ()=>{
124
+ const dynamicModule = LocalizationModule.forRootAsync({});
125
+ expect(providerTokens(dynamicModule.providers)).not.toContain(LOCALIZATION_MODULE_OPTIONS);
126
+ });
127
+ });
128
+ });
@@ -0,0 +1,147 @@
1
+ import { Test } from '@nestjs/testing';
2
+ import { UtilsService } from '@flusys/nestjs-shared/modules';
3
+ import { createMockDataSourceProvider, createMockRepository } from '@test-utils/mocks/repository.mock';
4
+ import { LanguageService } from './language.service';
5
+ import { LocalizationDataSourceProvider } from './localization-datasource.provider';
6
+ function buildLanguage(overrides = {}) {
7
+ return {
8
+ id: 'language-uuid-1',
9
+ code: 'en',
10
+ name: 'English',
11
+ nativeName: 'English',
12
+ direction: 'ltr',
13
+ isActive: true,
14
+ isDefault: false,
15
+ serial: 1,
16
+ isReadonly: false,
17
+ createdAt: new Date(),
18
+ updatedAt: new Date(),
19
+ deletedAt: null,
20
+ createdById: null,
21
+ updatedById: null,
22
+ deletedById: null,
23
+ ...overrides
24
+ };
25
+ }
26
+ describe('LanguageService', ()=>{
27
+ let service;
28
+ let mockRepo;
29
+ beforeEach(async ()=>{
30
+ mockRepo = createMockRepository();
31
+ const mockDataSourceProvider = createMockDataSourceProvider(mockRepo);
32
+ const module = await Test.createTestingModule({
33
+ providers: [
34
+ LanguageService,
35
+ {
36
+ provide: 'CACHE_INSTANCE',
37
+ useValue: {}
38
+ },
39
+ {
40
+ provide: UtilsService,
41
+ useValue: {}
42
+ },
43
+ {
44
+ provide: LocalizationDataSourceProvider,
45
+ useValue: mockDataSourceProvider
46
+ }
47
+ ]
48
+ }).compile();
49
+ service = await module.resolve(LanguageService);
50
+ });
51
+ describe('getDefaultLanguage', ()=>{
52
+ it('returns the language flagged as default', async ()=>{
53
+ const defaultLanguage = buildLanguage({
54
+ id: 'default-lang',
55
+ isDefault: true,
56
+ code: 'en'
57
+ });
58
+ mockRepo.findOne.mockResolvedValue(defaultLanguage);
59
+ const result = await service.getDefaultLanguage();
60
+ expect(mockRepo.findOne).toHaveBeenCalledWith({
61
+ where: {
62
+ isDefault: true,
63
+ deletedAt: expect.anything()
64
+ }
65
+ });
66
+ expect(result).toEqual(defaultLanguage);
67
+ });
68
+ it('returns null when no language is marked default', async ()=>{
69
+ mockRepo.findOne.mockResolvedValue(null);
70
+ const result = await service.getDefaultLanguage();
71
+ expect(result).toBeNull();
72
+ });
73
+ });
74
+ describe('getActiveLanguages', ()=>{
75
+ it('returns active, non-deleted languages ordered by serial then name', async ()=>{
76
+ const languages = [
77
+ buildLanguage({
78
+ code: 'en',
79
+ serial: 1
80
+ }),
81
+ buildLanguage({
82
+ code: 'bn',
83
+ serial: 2
84
+ })
85
+ ];
86
+ mockRepo.find.mockResolvedValue(languages);
87
+ const result = await service.getActiveLanguages();
88
+ expect(mockRepo.find).toHaveBeenCalledWith({
89
+ where: {
90
+ isActive: true,
91
+ deletedAt: expect.anything()
92
+ },
93
+ order: {
94
+ serial: 'ASC',
95
+ name: 'ASC'
96
+ }
97
+ });
98
+ expect(result).toHaveLength(2);
99
+ });
100
+ it('returns an empty array when no languages are active', async ()=>{
101
+ mockRepo.find.mockResolvedValue([]);
102
+ const result = await service.getActiveLanguages();
103
+ expect(result).toEqual([]);
104
+ });
105
+ });
106
+ describe('setAsDefault', ()=>{
107
+ it('unsets the previous default and sets the requested language as default', async ()=>{
108
+ const newDefault = buildLanguage({
109
+ id: 'lang-2',
110
+ isDefault: true,
111
+ code: 'bn'
112
+ });
113
+ mockRepo.findOne.mockResolvedValue(newDefault);
114
+ const result = await service.setAsDefault('lang-2');
115
+ expect(mockRepo.update).toHaveBeenNthCalledWith(1, {
116
+ isDefault: true
117
+ }, {
118
+ isDefault: false
119
+ });
120
+ expect(mockRepo.update).toHaveBeenNthCalledWith(2, {
121
+ id: 'lang-2'
122
+ }, {
123
+ isDefault: true
124
+ });
125
+ expect(mockRepo.findOne).toHaveBeenCalledWith({
126
+ where: {
127
+ id: 'lang-2'
128
+ }
129
+ });
130
+ expect(result).toEqual(newDefault);
131
+ });
132
+ it('returns null when the id does not correspond to any language after the update', async ()=>{
133
+ mockRepo.findOne.mockResolvedValue(null);
134
+ const result = await service.setAsDefault('non-existent-id');
135
+ expect(result).toBeNull();
136
+ });
137
+ it('still unsets other defaults first even when the target id is unknown (no exception thrown)', async ()=>{
138
+ mockRepo.findOne.mockResolvedValue(null);
139
+ await service.setAsDefault('unknown-id');
140
+ expect(mockRepo.update).toHaveBeenCalledWith({
141
+ isDefault: true
142
+ }, {
143
+ isDefault: false
144
+ });
145
+ });
146
+ });
147
+ });
@@ -0,0 +1,76 @@
1
+ import { LocalizationConfigService } from './localization-config.service';
2
+ function buildOptions(overrides = {}) {
3
+ return {
4
+ bootstrapAppConfig: undefined,
5
+ config: undefined,
6
+ ...overrides
7
+ };
8
+ }
9
+ describe('LocalizationConfigService', ()=>{
10
+ describe('isCompanyFeatureEnabled', ()=>{
11
+ it('always returns false — localization module has no company scoping', ()=>{
12
+ const service = new LocalizationConfigService(buildOptions());
13
+ expect(service.isCompanyFeatureEnabled()).toBe(false);
14
+ });
15
+ it('returns false even when bootstrapAppConfig enables the company feature elsewhere', ()=>{
16
+ const service = new LocalizationConfigService(buildOptions({
17
+ bootstrapAppConfig: {
18
+ databaseMode: 'single',
19
+ enableCompanyFeature: true
20
+ }
21
+ }));
22
+ expect(service.isCompanyFeatureEnabled()).toBe(false);
23
+ });
24
+ });
25
+ describe('getDatabaseMode', ()=>{
26
+ it('returns the configured database mode', ()=>{
27
+ const service = new LocalizationConfigService(buildOptions({
28
+ bootstrapAppConfig: {
29
+ databaseMode: 'multi-tenant'
30
+ }
31
+ }));
32
+ expect(service.getDatabaseMode()).toBe('multi-tenant');
33
+ });
34
+ it('defaults to "single" when bootstrapAppConfig is missing', ()=>{
35
+ const service = new LocalizationConfigService(buildOptions({
36
+ bootstrapAppConfig: undefined
37
+ }));
38
+ expect(service.getDatabaseMode()).toBe('single');
39
+ });
40
+ it('defaults to "single" when databaseMode is not set on bootstrapAppConfig', ()=>{
41
+ const service = new LocalizationConfigService(buildOptions({
42
+ bootstrapAppConfig: {}
43
+ }));
44
+ expect(service.getDatabaseMode()).toBe('single');
45
+ });
46
+ });
47
+ describe('isMultiTenant', ()=>{
48
+ it('returns true when database mode is multi-tenant', ()=>{
49
+ const service = new LocalizationConfigService(buildOptions({
50
+ bootstrapAppConfig: {
51
+ databaseMode: 'multi-tenant'
52
+ }
53
+ }));
54
+ expect(service.isMultiTenant()).toBe(true);
55
+ });
56
+ it('returns false when database mode is single', ()=>{
57
+ const service = new LocalizationConfigService(buildOptions({
58
+ bootstrapAppConfig: {
59
+ databaseMode: 'single'
60
+ }
61
+ }));
62
+ expect(service.isMultiTenant()).toBe(false);
63
+ });
64
+ });
65
+ describe('getOptions', ()=>{
66
+ it('returns the exact options object it was constructed with', ()=>{
67
+ const options = buildOptions({
68
+ config: {
69
+ defaultLanguageCode: 'en'
70
+ }
71
+ });
72
+ const service = new LocalizationConfigService(options);
73
+ expect(service.getOptions()).toBe(options);
74
+ });
75
+ });
76
+ });
@@ -0,0 +1,81 @@
1
+ import { LocalizationDataSourceProvider } from './localization-datasource.provider';
2
+ import { Language, Translation, TranslationKey } from '../entities';
3
+ function buildConfigService(overrides = {}) {
4
+ const options = {
5
+ bootstrapAppConfig: {
6
+ databaseMode: 'single'
7
+ },
8
+ config: {
9
+ defaultDatabaseConfig: {
10
+ type: 'mysql',
11
+ host: 'localhost',
12
+ port: 3306,
13
+ username: 'root',
14
+ password: 'password',
15
+ database: 'flusys_test'
16
+ },
17
+ tenantDefaultDatabaseConfig: undefined,
18
+ tenants: []
19
+ },
20
+ ...overrides
21
+ };
22
+ return {
23
+ getOptions: jest.fn().mockReturnValue(options)
24
+ };
25
+ }
26
+ describe('LocalizationDataSourceProvider', ()=>{
27
+ afterEach(()=>{
28
+ // Reset the isolated static caches this provider owns so tests don't leak state between runs.
29
+ LocalizationDataSourceProvider.tenantConnections = new Map();
30
+ LocalizationDataSourceProvider.singleDataSource = null;
31
+ LocalizationDataSourceProvider.tenantsRegistry = new Map();
32
+ LocalizationDataSourceProvider.initialized = false;
33
+ LocalizationDataSourceProvider.connectionLocks = new Map();
34
+ LocalizationDataSourceProvider.singleConnectionLock = null;
35
+ });
36
+ it('constructs and forwards bootstrap/database config to the base class', ()=>{
37
+ const configService = buildConfigService();
38
+ const provider = new LocalizationDataSourceProvider(configService, undefined);
39
+ expect(configService.getOptions).toHaveBeenCalled();
40
+ expect(provider.getDatabaseMode()).toBe('single');
41
+ expect(provider.isMultiTenant()).toBe(false);
42
+ });
43
+ it('reflects multi-tenant mode from bootstrapAppConfig', ()=>{
44
+ const configService = buildConfigService({
45
+ bootstrapAppConfig: {
46
+ databaseMode: 'multi-tenant'
47
+ }
48
+ });
49
+ const provider = new LocalizationDataSourceProvider(configService, undefined);
50
+ expect(provider.isMultiTenant()).toBe(true);
51
+ });
52
+ it('owns isolated static caches, separate from the base MultiTenantDataSourceService', ()=>{
53
+ // Each DataSource Provider must override BOTH static cache properties and the
54
+ // methods that read them — otherwise modules would share connections (see FLUSYS_NEST/CLAUDE.md).
55
+ expect(LocalizationDataSourceProvider.tenantConnections).not.toBe(Object.getPrototypeOf(LocalizationDataSourceProvider).tenantConnections);
56
+ });
57
+ it('createDataSourceFromConfig builds a DataSource using all localization entities', async ()=>{
58
+ const configService = buildConfigService();
59
+ const provider = new LocalizationDataSourceProvider(configService, undefined);
60
+ const fakeDataSource = {
61
+ isInitialized: true
62
+ };
63
+ const superSpy = jest.spyOn(Object.getPrototypeOf(Object.getPrototypeOf(provider)), 'createDataSourceFromConfig').mockResolvedValue(fakeDataSource);
64
+ const config = {
65
+ type: 'mysql',
66
+ host: 'localhost',
67
+ port: 3306,
68
+ username: 'root',
69
+ password: 'password',
70
+ database: 'flusys_test'
71
+ };
72
+ const result = await provider.createDataSourceFromConfig(config);
73
+ expect(superSpy).toHaveBeenCalledWith(config, [
74
+ Language,
75
+ TranslationKey,
76
+ Translation
77
+ ]);
78
+ expect(result).toBe(fakeDataSource);
79
+ superSpy.mockRestore();
80
+ });
81
+ });