@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,426 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ const _testing = require("@nestjs/testing");
6
+ const _modules = require("@flusys/nestjs-shared/modules");
7
+ const _repositorymock = require("@test-utils/mocks/repository.mock");
8
+ const _entities = require("../entities");
9
+ const _translationservice = require("./translation.service");
10
+ const _localizationdatasourceprovider = require("./localization-datasource.provider");
11
+ function buildLanguage(overrides = {}) {
12
+ return {
13
+ id: 'lang-en',
14
+ code: 'en',
15
+ name: 'English',
16
+ nativeName: 'English',
17
+ direction: 'ltr',
18
+ isActive: true,
19
+ isDefault: true,
20
+ serial: 1,
21
+ isReadonly: false,
22
+ createdAt: new Date(),
23
+ updatedAt: new Date(),
24
+ deletedAt: null,
25
+ createdById: null,
26
+ updatedById: null,
27
+ deletedById: null,
28
+ ...overrides
29
+ };
30
+ }
31
+ function buildTranslationKey(overrides = {}) {
32
+ return {
33
+ id: 'tkey-1',
34
+ module: 'auth',
35
+ key: 'auth.login.success',
36
+ defaultMessage: 'Login successful',
37
+ description: null,
38
+ variables: null,
39
+ isActive: true,
40
+ isReadonly: false,
41
+ serial: 1,
42
+ createdAt: new Date(),
43
+ updatedAt: new Date(),
44
+ deletedAt: null,
45
+ createdById: null,
46
+ updatedById: null,
47
+ deletedById: null,
48
+ ...overrides
49
+ };
50
+ }
51
+ function buildTranslation(overrides = {}) {
52
+ return {
53
+ id: 'translation-1',
54
+ languageId: 'lang-en',
55
+ translationKeyId: 'tkey-1',
56
+ value: 'Login successful',
57
+ isVerified: false,
58
+ isActive: true,
59
+ createdAt: new Date(),
60
+ updatedAt: new Date(),
61
+ deletedAt: null,
62
+ createdById: null,
63
+ updatedById: null,
64
+ deletedById: null,
65
+ ...overrides
66
+ };
67
+ }
68
+ describe('TranslationService', ()=>{
69
+ let service;
70
+ let mockRepo;
71
+ // `TranslationService` uses joins/aggregate query-builder methods (innerJoin, innerJoinAndSelect,
72
+ // groupBy, addSelect, getRawMany) that aren't part of the shared MockQueryBuilder — extend loosely.
73
+ let qb;
74
+ let languageRepo;
75
+ let translationKeyRepo;
76
+ let fakeDataSource;
77
+ beforeEach(async ()=>{
78
+ mockRepo = (0, _repositorymock.createMockRepository)();
79
+ qb = mockRepo.createQueryBuilder();
80
+ qb.innerJoin = jest.fn().mockReturnThis();
81
+ qb.innerJoinAndSelect = jest.fn().mockReturnThis();
82
+ qb.groupBy = jest.fn().mockReturnThis();
83
+ qb.addSelect = jest.fn().mockReturnThis();
84
+ qb.getRawMany = jest.fn().mockResolvedValue([]);
85
+ const mockDataSourceProvider = (0, _repositorymock.createMockDataSourceProvider)(mockRepo);
86
+ languageRepo = {
87
+ findOne: jest.fn()
88
+ };
89
+ translationKeyRepo = {
90
+ find: jest.fn()
91
+ };
92
+ fakeDataSource = {
93
+ getRepository: jest.fn((entity)=>{
94
+ if (entity === _entities.Language) return languageRepo;
95
+ if (entity === _entities.TranslationKey) return translationKeyRepo;
96
+ return mockRepo;
97
+ }),
98
+ createQueryRunner: jest.fn()
99
+ };
100
+ mockDataSourceProvider.getDataSource.mockResolvedValue(fakeDataSource);
101
+ const mockUtilsService = {
102
+ clearCache: jest.fn(),
103
+ getCacheKey: jest.fn().mockReturnValue('cache-key'),
104
+ trackCacheKey: jest.fn()
105
+ };
106
+ const module = await _testing.Test.createTestingModule({
107
+ providers: [
108
+ _translationservice.TranslationService,
109
+ {
110
+ provide: 'CACHE_INSTANCE',
111
+ useValue: {}
112
+ },
113
+ {
114
+ provide: _modules.UtilsService,
115
+ useValue: mockUtilsService
116
+ },
117
+ {
118
+ provide: _localizationdatasourceprovider.LocalizationDataSourceProvider,
119
+ useValue: mockDataSourceProvider
120
+ }
121
+ ]
122
+ }).compile();
123
+ service = await module.resolve(_translationservice.TranslationService);
124
+ });
125
+ describe('getByLanguageCode', ()=>{
126
+ it('returns an empty translations map when the language code is unknown', async ()=>{
127
+ languageRepo.findOne.mockResolvedValue(null);
128
+ const result = await service.getByLanguageCode('xx');
129
+ expect(result).toEqual({});
130
+ expect(qb.getMany).not.toHaveBeenCalled();
131
+ });
132
+ it('builds a translations map keyed by translation key, scoped to the active language', async ()=>{
133
+ languageRepo.findOne.mockResolvedValue(buildLanguage());
134
+ const translations = [
135
+ {
136
+ ...buildTranslation({
137
+ value: 'Login successful'
138
+ }),
139
+ translationKey: buildTranslationKey({
140
+ key: 'auth.login.success'
141
+ })
142
+ },
143
+ {
144
+ ...buildTranslation({
145
+ id: 'translation-2',
146
+ value: 'Logout successful'
147
+ }),
148
+ translationKey: buildTranslationKey({
149
+ id: 'tkey-2',
150
+ key: 'auth.logout.success'
151
+ })
152
+ }
153
+ ];
154
+ qb.getMany.mockResolvedValue(translations);
155
+ const result = await service.getByLanguageCode('en');
156
+ expect(languageRepo.findOne).toHaveBeenCalledWith({
157
+ where: {
158
+ code: 'en',
159
+ isActive: true,
160
+ deletedAt: expect.anything()
161
+ }
162
+ });
163
+ expect(result).toEqual({
164
+ 'auth.login.success': 'Login successful',
165
+ 'auth.logout.success': 'Logout successful'
166
+ });
167
+ });
168
+ it('scopes translations by module when modules are supplied (module-based lazy loading)', async ()=>{
169
+ languageRepo.findOne.mockResolvedValue(buildLanguage());
170
+ qb.getMany.mockResolvedValue([]);
171
+ await service.getByLanguageCode('en', [
172
+ 'auth',
173
+ 'shared'
174
+ ]);
175
+ expect(qb.andWhere).toHaveBeenCalledWith('tk.module IN (:...modules)', {
176
+ modules: [
177
+ 'auth',
178
+ 'shared'
179
+ ]
180
+ });
181
+ });
182
+ it('does not apply a module filter when the modules array is empty', async ()=>{
183
+ languageRepo.findOne.mockResolvedValue(buildLanguage());
184
+ qb.getMany.mockResolvedValue([]);
185
+ await service.getByLanguageCode('en', []);
186
+ expect(qb.andWhere).not.toHaveBeenCalledWith(expect.stringContaining('tk.module IN'), expect.anything());
187
+ });
188
+ it('silently drops translations whose translationKey relation failed to join (fallback: key omitted, no throw)', async ()=>{
189
+ languageRepo.findOne.mockResolvedValue(buildLanguage());
190
+ qb.getMany.mockResolvedValue([
191
+ {
192
+ ...buildTranslation(),
193
+ translationKey: undefined
194
+ }
195
+ ]);
196
+ const result = await service.getByLanguageCode('en');
197
+ expect(result).toEqual({});
198
+ });
199
+ });
200
+ describe('getByModuleAndLanguage', ()=>{
201
+ it('returns an empty map for an unknown language code', async ()=>{
202
+ languageRepo.findOne.mockResolvedValue(null);
203
+ const result = await service.getByModuleAndLanguage('auth', 'xx');
204
+ expect(result).toEqual({});
205
+ });
206
+ it('filters translations to the requested module only', async ()=>{
207
+ languageRepo.findOne.mockResolvedValue(buildLanguage());
208
+ qb.getMany.mockResolvedValue([
209
+ {
210
+ ...buildTranslation(),
211
+ translationKey: buildTranslationKey({
212
+ key: 'auth.login.success'
213
+ })
214
+ }
215
+ ]);
216
+ const result = await service.getByModuleAndLanguage('auth', 'en');
217
+ expect(qb.andWhere).toHaveBeenCalledWith('tk.module = :module', {
218
+ module: 'auth'
219
+ });
220
+ expect(result).toEqual({
221
+ 'auth.login.success': 'Login successful'
222
+ });
223
+ });
224
+ });
225
+ describe('bulkUpdate', ()=>{
226
+ function buildQueryRunner() {
227
+ return {
228
+ connect: jest.fn(),
229
+ startTransaction: jest.fn(),
230
+ commitTransaction: jest.fn(),
231
+ rollbackTransaction: jest.fn(),
232
+ release: jest.fn(),
233
+ manager: {
234
+ update: jest.fn(),
235
+ create: jest.fn((_entity, data)=>({
236
+ id: 'new-translation-id',
237
+ ...data
238
+ })),
239
+ save: jest.fn(async (entity)=>entity)
240
+ }
241
+ };
242
+ }
243
+ it('returns an empty array without opening a transaction when there are no updates', async ()=>{
244
+ const result = await service.bulkUpdate([]);
245
+ expect(result).toEqual([]);
246
+ expect(fakeDataSource.createQueryRunner).not.toHaveBeenCalled();
247
+ });
248
+ it('updates existing translations by id and defaults isVerified to false', async ()=>{
249
+ const queryRunner = buildQueryRunner();
250
+ fakeDataSource.createQueryRunner.mockReturnValue(queryRunner);
251
+ mockRepo.find.mockResolvedValue([
252
+ buildTranslation({
253
+ id: 'translation-1'
254
+ })
255
+ ]);
256
+ const result = await service.bulkUpdate([
257
+ {
258
+ id: 'translation-1',
259
+ languageId: 'lang-en',
260
+ translationKeyId: 'tkey-1',
261
+ value: 'Updated'
262
+ }
263
+ ]);
264
+ expect(queryRunner.manager.update).toHaveBeenCalledWith(_entities.Translation, {
265
+ id: 'translation-1'
266
+ }, {
267
+ value: 'Updated',
268
+ isVerified: false
269
+ });
270
+ expect(queryRunner.commitTransaction).toHaveBeenCalled();
271
+ expect(mockRepo.find).toHaveBeenCalledWith({
272
+ where: {
273
+ id: expect.anything()
274
+ }
275
+ });
276
+ expect(result).toHaveLength(1);
277
+ });
278
+ it('creates new translations when no id is provided', async ()=>{
279
+ const queryRunner = buildQueryRunner();
280
+ fakeDataSource.createQueryRunner.mockReturnValue(queryRunner);
281
+ mockRepo.find.mockResolvedValue([
282
+ buildTranslation({
283
+ id: 'new-translation-id'
284
+ })
285
+ ]);
286
+ await service.bulkUpdate([
287
+ {
288
+ languageId: 'lang-en',
289
+ translationKeyId: 'tkey-1',
290
+ value: 'Brand new',
291
+ isVerified: true
292
+ }
293
+ ]);
294
+ expect(queryRunner.manager.create).toHaveBeenCalledWith(_entities.Translation, {
295
+ languageId: 'lang-en',
296
+ translationKeyId: 'tkey-1',
297
+ value: 'Brand new',
298
+ isVerified: true,
299
+ isActive: true
300
+ });
301
+ expect(queryRunner.manager.save).toHaveBeenCalled();
302
+ expect(queryRunner.commitTransaction).toHaveBeenCalled();
303
+ });
304
+ it('rolls back the transaction and rethrows the original error when a write fails', async ()=>{
305
+ const queryRunner = buildQueryRunner();
306
+ queryRunner.manager.update.mockRejectedValue(new Error('db write failed'));
307
+ fakeDataSource.createQueryRunner.mockReturnValue(queryRunner);
308
+ await expect(service.bulkUpdate([
309
+ {
310
+ id: 'translation-1',
311
+ languageId: 'lang-en',
312
+ translationKeyId: 'tkey-1',
313
+ value: 'x'
314
+ }
315
+ ])).rejects.toThrow('db write failed');
316
+ expect(queryRunner.rollbackTransaction).toHaveBeenCalled();
317
+ expect(queryRunner.release).toHaveBeenCalled();
318
+ expect(queryRunner.commitTransaction).not.toHaveBeenCalled();
319
+ });
320
+ });
321
+ describe('getMissingTranslations', ()=>{
322
+ it('returns an empty array for an unknown language code', async ()=>{
323
+ languageRepo.findOne.mockResolvedValue(null);
324
+ const result = await service.getMissingTranslations('xx');
325
+ expect(result).toEqual([]);
326
+ expect(translationKeyRepo.find).not.toHaveBeenCalled();
327
+ });
328
+ it('returns translation keys that have no translation row yet for the language', async ()=>{
329
+ languageRepo.findOne.mockResolvedValue(buildLanguage());
330
+ const allKeys = [
331
+ buildTranslationKey({
332
+ id: 'tkey-1',
333
+ key: 'auth.login.success'
334
+ }),
335
+ buildTranslationKey({
336
+ id: 'tkey-2',
337
+ key: 'auth.logout.success'
338
+ })
339
+ ];
340
+ translationKeyRepo.find.mockResolvedValue(allKeys);
341
+ mockRepo.find.mockResolvedValue([
342
+ {
343
+ translationKeyId: 'tkey-1'
344
+ }
345
+ ]);
346
+ const result = await service.getMissingTranslations('en');
347
+ expect(result).toEqual([
348
+ allKeys[1]
349
+ ]);
350
+ });
351
+ it('returns an empty array when every translation key already has a translation', async ()=>{
352
+ languageRepo.findOne.mockResolvedValue(buildLanguage());
353
+ const allKeys = [
354
+ buildTranslationKey({
355
+ id: 'tkey-1'
356
+ })
357
+ ];
358
+ translationKeyRepo.find.mockResolvedValue(allKeys);
359
+ mockRepo.find.mockResolvedValue([
360
+ {
361
+ translationKeyId: 'tkey-1'
362
+ }
363
+ ]);
364
+ const result = await service.getMissingTranslations('en');
365
+ expect(result).toEqual([]);
366
+ });
367
+ });
368
+ describe('getTranslationCounts', ()=>{
369
+ it('returns a map of translationKeyId -> translation count', async ()=>{
370
+ qb.getRawMany = jest.fn().mockResolvedValue([
371
+ {
372
+ keyId: 'tkey-1',
373
+ count: '3'
374
+ },
375
+ {
376
+ keyId: 'tkey-2',
377
+ count: '1'
378
+ }
379
+ ]);
380
+ const result = await service.getTranslationCounts();
381
+ expect(result).toEqual({
382
+ 'tkey-1': 3,
383
+ 'tkey-2': 1
384
+ });
385
+ });
386
+ it('returns an empty object when there are no translations', async ()=>{
387
+ qb.getRawMany = jest.fn().mockResolvedValue([]);
388
+ const result = await service.getTranslationCounts();
389
+ expect(result).toEqual({});
390
+ });
391
+ });
392
+ describe('getFilterQuery (module/language/key filtering)', ()=>{
393
+ it('joins translation_key and filters by module when filter.module is provided', async ()=>{
394
+ const { query, isRaw } = await service.getFilterQuery(qb, {
395
+ module: 'auth'
396
+ }, null);
397
+ expect(qb.innerJoin).toHaveBeenCalledWith('translation.translationKey', 'tk');
398
+ expect(qb.andWhere).toHaveBeenCalledWith('tk.module = :module', {
399
+ module: 'auth'
400
+ });
401
+ expect(isRaw).toBe(false);
402
+ expect(query).toBe(qb);
403
+ });
404
+ it('filters by languageId without requiring a join', async ()=>{
405
+ await service.getFilterQuery(qb, {
406
+ languageId: 'lang-en'
407
+ }, null);
408
+ expect(qb.andWhere).toHaveBeenCalledWith('translation.languageId = :languageId', {
409
+ languageId: 'lang-en'
410
+ });
411
+ });
412
+ it('filters by translationKeyId', async ()=>{
413
+ await service.getFilterQuery(qb, {
414
+ translationKeyId: 'tkey-1'
415
+ }, null);
416
+ expect(qb.andWhere).toHaveBeenCalledWith('translation.translationKeyId = :translationKeyId', {
417
+ translationKeyId: 'tkey-1'
418
+ });
419
+ });
420
+ it('applies no filters when the filter object is empty', async ()=>{
421
+ await service.getFilterQuery(qb, {}, null);
422
+ expect(qb.innerJoin).not.toHaveBeenCalled();
423
+ expect(qb.andWhere).not.toHaveBeenCalled();
424
+ });
425
+ });
426
+ });
@@ -0,0 +1,150 @@
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 { LanguageController } from './language.controller';
5
+ import { LanguageService } from '../services';
6
+ function buildLanguage(overrides = {}) {
7
+ return {
8
+ id: 'lang-en',
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('LanguageController', ()=>{
27
+ let controller;
28
+ let mockService;
29
+ beforeEach(async ()=>{
30
+ mockService = {
31
+ insert: jest.fn(),
32
+ findById: jest.fn(),
33
+ getAll: jest.fn(),
34
+ update: jest.fn(),
35
+ delete: jest.fn(),
36
+ getActiveLanguages: jest.fn(),
37
+ getDefaultLanguage: jest.fn(),
38
+ setAsDefault: jest.fn()
39
+ };
40
+ const module = await Test.createTestingModule({
41
+ controllers: [
42
+ LanguageController
43
+ ],
44
+ providers: [
45
+ {
46
+ provide: LanguageService,
47
+ useValue: mockService
48
+ },
49
+ // The base ApiController wires the Slug interceptor (needs UtilsService) on several
50
+ // inherited endpoints, so it must be resolvable even though these tests only hit custom routes.
51
+ {
52
+ provide: UtilsService,
53
+ useValue: {}
54
+ }
55
+ ]
56
+ }).compile();
57
+ controller = module.get(LanguageController);
58
+ });
59
+ describe('getActive', ()=>{
60
+ it('returns all active languages with a full-length meta block', async ()=>{
61
+ const languages = [
62
+ buildLanguage({
63
+ code: 'en'
64
+ }),
65
+ buildLanguage({
66
+ id: 'lang-bn',
67
+ code: 'bn'
68
+ })
69
+ ];
70
+ mockService.getActiveLanguages.mockResolvedValue(languages);
71
+ const result = await controller.getActive();
72
+ expect(result).toEqual({
73
+ success: true,
74
+ message: 'Active languages retrieved successfully',
75
+ messageKey: 'language.active.success',
76
+ data: languages,
77
+ meta: {
78
+ total: 2,
79
+ page: 0,
80
+ pageSize: 2,
81
+ count: 2
82
+ }
83
+ });
84
+ });
85
+ it('returns an empty list with zeroed meta when no languages are active', async ()=>{
86
+ mockService.getActiveLanguages.mockResolvedValue([]);
87
+ const result = await controller.getActive();
88
+ expect(result.data).toEqual([]);
89
+ expect(result.meta).toEqual({
90
+ total: 0,
91
+ page: 0,
92
+ pageSize: 0,
93
+ count: 0
94
+ });
95
+ });
96
+ });
97
+ describe('getDefault', ()=>{
98
+ it('returns the default language', async ()=>{
99
+ const defaultLanguage = buildLanguage({
100
+ isDefault: true
101
+ });
102
+ mockService.getDefaultLanguage.mockResolvedValue(defaultLanguage);
103
+ const result = await controller.getDefault();
104
+ expect(result).toEqual({
105
+ success: true,
106
+ message: 'Default language retrieved successfully',
107
+ messageKey: 'language.get.default.success',
108
+ data: defaultLanguage
109
+ });
110
+ });
111
+ it('falls back to undefined data when no default language is configured', async ()=>{
112
+ mockService.getDefaultLanguage.mockResolvedValue(null);
113
+ const result = await controller.getDefault();
114
+ expect(result.data).toBeUndefined();
115
+ expect(result.success).toBe(true);
116
+ });
117
+ });
118
+ describe('setDefault', ()=>{
119
+ it('sets the given language id as default and returns it', async ()=>{
120
+ const updated = buildLanguage({
121
+ id: 'lang-bn',
122
+ isDefault: true
123
+ });
124
+ mockService.setAsDefault.mockResolvedValue(updated);
125
+ const result = await controller.setDefault({
126
+ id: 'lang-bn'
127
+ }, buildMockUser());
128
+ expect(mockService.setAsDefault).toHaveBeenCalledWith('lang-bn');
129
+ expect(result).toEqual({
130
+ success: true,
131
+ message: 'Default language updated successfully',
132
+ messageKey: 'language.set.default.success',
133
+ data: updated
134
+ });
135
+ });
136
+ it('returns undefined data when the id does not match any language', async ()=>{
137
+ mockService.setAsDefault.mockResolvedValue(null);
138
+ const result = await controller.setDefault({
139
+ id: 'unknown-id'
140
+ }, buildMockUser());
141
+ expect(result.data).toBeUndefined();
142
+ });
143
+ it('propagates errors raised by the service unchanged', async ()=>{
144
+ mockService.setAsDefault.mockRejectedValue(new Error('database unavailable'));
145
+ await expect(controller.setDefault({
146
+ id: 'lang-en'
147
+ }, buildMockUser())).rejects.toThrow('database unavailable');
148
+ });
149
+ });
150
+ });
@@ -0,0 +1,66 @@
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 { TranslationKeyController } from './translation-key.controller';
5
+ import { TranslationKeyService } from '../services';
6
+ describe('TranslationKeyController', ()=>{
7
+ let controller;
8
+ let mockService;
9
+ beforeEach(async ()=>{
10
+ mockService = {
11
+ insert: jest.fn(),
12
+ findById: jest.fn(),
13
+ getAll: jest.fn(),
14
+ update: jest.fn(),
15
+ delete: jest.fn(),
16
+ getModules: jest.fn(),
17
+ findByModule: jest.fn()
18
+ };
19
+ const module = await Test.createTestingModule({
20
+ controllers: [
21
+ TranslationKeyController
22
+ ],
23
+ providers: [
24
+ {
25
+ provide: TranslationKeyService,
26
+ useValue: mockService
27
+ },
28
+ {
29
+ provide: UtilsService,
30
+ useValue: {}
31
+ }
32
+ ]
33
+ }).compile();
34
+ controller = module.get(TranslationKeyController);
35
+ });
36
+ describe('getModules', ()=>{
37
+ it('returns the distinct module names from the service', async ()=>{
38
+ mockService.getModules.mockResolvedValue([
39
+ 'auth',
40
+ 'shared',
41
+ 'storage'
42
+ ]);
43
+ const result = await controller.getModules(buildMockUser());
44
+ expect(result).toEqual({
45
+ success: true,
46
+ message: 'Modules retrieved successfully',
47
+ messageKey: 'translation.key.modules.success',
48
+ data: [
49
+ 'auth',
50
+ 'shared',
51
+ 'storage'
52
+ ]
53
+ });
54
+ });
55
+ it('returns an empty array when no translation keys have been created yet', async ()=>{
56
+ mockService.getModules.mockResolvedValue([]);
57
+ const result = await controller.getModules(buildMockUser());
58
+ expect(result.data).toEqual([]);
59
+ expect(result.success).toBe(true);
60
+ });
61
+ it('propagates unexpected errors from the service', async ()=>{
62
+ mockService.getModules.mockRejectedValue(new Error('query failed'));
63
+ await expect(controller.getModules(buildMockUser())).rejects.toThrow('query failed');
64
+ });
65
+ });
66
+ });