Merge pull request #491 from Hexastack/fix/i18n-and-translation-strict-null-check
Some checks failed
Build and Push Docker API Image / build-and-push (push) Has been cancelled
Build and Push Docker Base Image / build-and-push (push) Has been cancelled
Build and Push Docker NLU Image / build-and-push (push) Has been cancelled
Build and Push Docker UI Image / build-and-push (push) Has been cancelled

Fix: i18n and translation strict null check
This commit is contained in:
Med Marrouchi 2024-12-31 10:29:25 +01:00 committed by GitHub
commit 1683ce5ca0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 76 additions and 22 deletions

View File

@ -73,7 +73,7 @@ export const config: Config = {
grant3rdPartyCookie: true, grant3rdPartyCookie: true,
onlyAllowOrigins: process.env.FRONTEND_ORIGIN onlyAllowOrigins: process.env.FRONTEND_ORIGIN
? process.env.FRONTEND_ORIGIN.split(',').map((origin) => origin.trim()) ? process.env.FRONTEND_ORIGIN.split(',').map((origin) => origin.trim())
: [undefined], // ['http://example.com', 'https://example.com'], : [], // ['http://example.com', 'https://example.com'],
}, },
session: { session: {
secret: process.env.SESSION_SECRET || 'changeme', secret: process.env.SESSION_SECRET || 'changeme',
@ -91,7 +91,9 @@ export const config: Config = {
emails: { emails: {
isEnabled: process.env.EMAIL_SMTP_ENABLED === 'true' || false, isEnabled: process.env.EMAIL_SMTP_ENABLED === 'true' || false,
smtp: { smtp: {
port: parseInt(process.env.EMAIL_SMTP_PORT) || 25, port: process.env.EMAIL_SMTP_PORT
? parseInt(process.env.EMAIL_SMTP_PORT)
: 25,
host: process.env.EMAIL_SMTP_HOST || 'localhost', host: process.env.EMAIL_SMTP_HOST || 'localhost',
ignoreTLS: false, ignoreTLS: false,
secure: process.env.EMAIL_SMTP_SECURE === 'true' || false, secure: process.env.EMAIL_SMTP_SECURE === 'true' || false,

View File

@ -69,7 +69,7 @@ describe('LanguageController', () => {
}).compile(); }).compile();
languageService = module.get<LanguageService>(LanguageService); languageService = module.get<LanguageService>(LanguageService);
languageController = module.get<LanguageController>(LanguageController); languageController = module.get<LanguageController>(LanguageController);
language = await languageService.findOne({ code: 'en' }); language = (await languageService.findOne({ code: 'en' })) as Language;
}); });
afterEach(jest.clearAllMocks); afterEach(jest.clearAllMocks);
@ -92,7 +92,7 @@ describe('LanguageController', () => {
expect(languageService.findOne).toHaveBeenCalledWith(language.id); expect(languageService.findOne).toHaveBeenCalledWith(language.id);
expect(result).toEqualPayload( expect(result).toEqualPayload(
languageFixtures.find(({ code }) => code === language.code), languageFixtures.find(({ code }) => code === language.code) as Language,
); );
}); });
}); });
@ -142,7 +142,9 @@ describe('LanguageController', () => {
it('should mark a language as default', async () => { it('should mark a language as default', async () => {
jest.spyOn(languageService, 'updateOne'); jest.spyOn(languageService, 'updateOne');
const translationUpdateDto = { isDefault: true }; const translationUpdateDto = { isDefault: true };
const frLang = await languageService.findOne({ code: 'fr' }); const frLang = (await languageService.findOne({
code: 'fr',
})) as Language;
const result = await languageController.updateOne( const result = await languageController.updateOne(
frLang.id, frLang.id,
translationUpdateDto, translationUpdateDto,
@ -157,7 +159,9 @@ describe('LanguageController', () => {
...translationUpdateDto, ...translationUpdateDto,
}); });
const enLang = await languageService.findOne({ code: 'en' }); const enLang = (await languageService.findOne({
code: 'en',
})) as Language;
expect(enLang.isDefault).toBe(false); expect(enLang.isDefault).toBe(false);
}); });
@ -171,7 +175,9 @@ describe('LanguageController', () => {
describe('deleteOne', () => { describe('deleteOne', () => {
it('should throw when attempting to delete the default language', async () => { it('should throw when attempting to delete the default language', async () => {
const defaultLang = await languageService.findOne({ isDefault: true }); const defaultLang = (await languageService.findOne({
isDefault: true,
})) as Language;
await expect( await expect(
languageController.deleteOne(defaultLang.id), languageController.deleteOne(defaultLang.id),

View File

@ -141,7 +141,9 @@ describe('TranslationController', () => {
translationController = module.get<TranslationController>( translationController = module.get<TranslationController>(
TranslationController, TranslationController,
); );
translation = await translationService.findOne({ str: 'Welcome' }); translation = (await translationService.findOne({
str: 'Welcome',
})) as Translation;
}); });
afterEach(jest.clearAllMocks); afterEach(jest.clearAllMocks);
@ -164,7 +166,9 @@ describe('TranslationController', () => {
expect(translationService.findOne).toHaveBeenCalledWith(translation.id); expect(translationService.findOne).toHaveBeenCalledWith(translation.id);
expect(result).toEqualPayload( expect(result).toEqualPayload(
translationFixtures.find(({ str }) => str === translation.str), translationFixtures.find(
({ str }) => str === translation.str,
) as Translation,
); );
}); });
}); });

View File

@ -11,6 +11,7 @@ import {
forwardRef, forwardRef,
Global, Global,
Inject, Inject,
InternalServerErrorException,
Module, Module,
} from '@nestjs/common'; } from '@nestjs/common';
import { HttpAdapterHost } from '@nestjs/core'; import { HttpAdapterHost } from '@nestjs/core';
@ -54,6 +55,11 @@ export class I18nModule extends NativeI18nModule {
static forRoot(options: I18nOptions): DynamicModule { static forRoot(options: I18nOptions): DynamicModule {
const { imports, providers, controllers, exports } = super.forRoot(options); const { imports, providers, controllers, exports } = super.forRoot(options);
if (!providers || !exports) {
throw new InternalServerErrorException(
'I18n: Unable to find providers and/or exports forRoot()',
);
}
return { return {
module: I18nModule, module: I18nModule,
imports: (imports || []).concat([ imports: (imports || []).concat([

View File

@ -6,7 +6,7 @@
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/ */
import { Injectable } from '@nestjs/common'; import { Injectable, InternalServerErrorException } from '@nestjs/common';
import { import {
I18nService as NativeI18nService, I18nService as NativeI18nService,
Path, Path,
@ -34,6 +34,11 @@ export class I18nService<
...options, ...options,
}; };
let { lang } = options; let { lang } = options;
if (!lang) {
throw new InternalServerErrorException('I18nService: lang is undefined');
}
lang = this.resolveLanguage(lang); lang = this.resolveLanguage(lang);
// Translate block message, button text, ... // Translate block message, button text, ...

View File

@ -7,7 +7,11 @@
*/ */
import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Inject, Injectable } from '@nestjs/common'; import {
Inject,
Injectable,
InternalServerErrorException,
} from '@nestjs/common';
import { Cache } from 'cache-manager'; import { Cache } from 'cache-manager';
import { import {
@ -53,7 +57,13 @@ export class LanguageService extends BaseService<Language> {
*/ */
@Cacheable(DEFAULT_LANGUAGE_CACHE_KEY) @Cacheable(DEFAULT_LANGUAGE_CACHE_KEY)
async getDefaultLanguage() { async getDefaultLanguage() {
return await this.findOne({ isDefault: true }); const defaultLanguage = await this.findOne({ isDefault: true });
if (!defaultLanguage) {
throw new InternalServerErrorException(
'Default language not found: getDefaultLanguage()',
);
}
return defaultLanguage;
} }
/** /**

View File

@ -100,6 +100,7 @@ export class TranslationService extends BaseService<Translation> {
} }
// Add fallback messages // Add fallback messages
if ( if (
block.options &&
'fallback' in block.options && 'fallback' in block.options &&
block.options.fallback && block.options.fallback &&
'message' in block.options.fallback && 'message' in block.options.fallback &&

View File

@ -28,6 +28,7 @@ import { I18nService } from '@/i18n/services/i18n.service';
import { LanguageService } from '@/i18n/services/language.service'; import { LanguageService } from '@/i18n/services/language.service';
import { LoggerService } from '@/logger/logger.service'; import { LoggerService } from '@/logger/logger.service';
import { getRandom } from '@/utils/helpers/safeRandom'; import { getRandom } from '@/utils/helpers/safeRandom';
import { installLanguageFixtures } from '@/utils/test/fixtures/language';
import { installUserFixtures } from '@/utils/test/fixtures/user'; import { installUserFixtures } from '@/utils/test/fixtures/user';
import { import {
closeInMongodConnection, closeInMongodConnection,
@ -43,7 +44,7 @@ import { RoleRepository } from '../repositories/role.repository';
import { UserRepository } from '../repositories/user.repository'; import { UserRepository } from '../repositories/user.repository';
import { InvitationModel } from '../schemas/invitation.schema'; import { InvitationModel } from '../schemas/invitation.schema';
import { PermissionModel } from '../schemas/permission.schema'; import { PermissionModel } from '../schemas/permission.schema';
import { RoleModel, Role } from '../schemas/role.schema'; import { Role, RoleModel } from '../schemas/role.schema';
import { UserModel } from '../schemas/user.schema'; import { UserModel } from '../schemas/user.schema';
import { InvitationService } from '../services/invitation.service'; import { InvitationService } from '../services/invitation.service';
import { PermissionService } from '../services/permission.service'; import { PermissionService } from '../services/permission.service';
@ -66,7 +67,10 @@ describe('AuthController', () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
controllers: [LocalAuthController], controllers: [LocalAuthController],
imports: [ imports: [
rootMongooseTestModule(installUserFixtures), rootMongooseTestModule(async () => {
await installLanguageFixtures();
await installUserFixtures();
}),
MongooseModule.forFeature([ MongooseModule.forFeature([
UserModel, UserModel,
RoleModel, RoleModel,

View File

@ -25,6 +25,7 @@ import { I18nService } from '@/i18n/services/i18n.service';
import { LanguageService } from '@/i18n/services/language.service'; import { LanguageService } from '@/i18n/services/language.service';
import { LoggerService } from '@/logger/logger.service'; import { LoggerService } from '@/logger/logger.service';
import { IGNORED_TEST_FIELDS } from '@/utils/test/constants'; import { IGNORED_TEST_FIELDS } from '@/utils/test/constants';
import { installLanguageFixtures } from '@/utils/test/fixtures/language';
import { installPermissionFixtures } from '@/utils/test/fixtures/permission'; import { installPermissionFixtures } from '@/utils/test/fixtures/permission';
import { getUserFixtures, userFixtures } from '@/utils/test/fixtures/user'; import { getUserFixtures, userFixtures } from '@/utils/test/fixtures/user';
import { getPageQuery } from '@/utils/test/pagination'; import { getPageQuery } from '@/utils/test/pagination';
@ -45,8 +46,8 @@ import { RoleRepository } from '../repositories/role.repository';
import { UserRepository } from '../repositories/user.repository'; import { UserRepository } from '../repositories/user.repository';
import { InvitationModel } from '../schemas/invitation.schema'; import { InvitationModel } from '../schemas/invitation.schema';
import { PermissionModel } from '../schemas/permission.schema'; import { PermissionModel } from '../schemas/permission.schema';
import { RoleModel, Role } from '../schemas/role.schema'; import { Role, RoleModel } from '../schemas/role.schema';
import { UserModel, User } from '../schemas/user.schema'; import { User, UserModel } from '../schemas/user.schema';
import { PasswordResetService } from '../services/passwordReset.service'; import { PasswordResetService } from '../services/passwordReset.service';
import { PermissionService } from '../services/permission.service'; import { PermissionService } from '../services/permission.service';
import { RoleService } from '../services/role.service'; import { RoleService } from '../services/role.service';
@ -71,7 +72,10 @@ describe('UserController', () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
controllers: [ReadWriteUserController], controllers: [ReadWriteUserController],
imports: [ imports: [
rootMongooseTestModule(installPermissionFixtures), rootMongooseTestModule(async () => {
await installLanguageFixtures();
await installPermissionFixtures();
}),
MongooseModule.forFeature([ MongooseModule.forFeature([
UserModel, UserModel,
RoleModel, RoleModel,

View File

@ -25,6 +25,7 @@ import {
installInvitationFixtures, installInvitationFixtures,
invitationsFixtures, invitationsFixtures,
} from '@/utils/test/fixtures/invitation'; } from '@/utils/test/fixtures/invitation';
import { installLanguageFixtures } from '@/utils/test/fixtures/language';
import { import {
closeInMongodConnection, closeInMongodConnection,
rootMongooseTestModule, rootMongooseTestModule,
@ -53,7 +54,10 @@ describe('InvitationService', () => {
beforeAll(async () => { beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
imports: [ imports: [
rootMongooseTestModule(installInvitationFixtures), rootMongooseTestModule(async () => {
await installLanguageFixtures();
await installInvitationFixtures();
}),
MongooseModule.forFeature([ MongooseModule.forFeature([
RoleModel, RoleModel,
PermissionModel, PermissionModel,

View File

@ -25,6 +25,7 @@ import { LanguageModel } from '@/i18n/schemas/language.schema';
import { I18nService } from '@/i18n/services/i18n.service'; import { I18nService } from '@/i18n/services/i18n.service';
import { LanguageService } from '@/i18n/services/language.service'; import { LanguageService } from '@/i18n/services/language.service';
import { LoggerService } from '@/logger/logger.service'; import { LoggerService } from '@/logger/logger.service';
import { installLanguageFixtures } from '@/utils/test/fixtures/language';
import { installUserFixtures, users } from '@/utils/test/fixtures/user'; import { installUserFixtures, users } from '@/utils/test/fixtures/user';
import { import {
closeInMongodConnection, closeInMongodConnection,
@ -49,7 +50,10 @@ describe('PasswordResetService', () => {
beforeAll(async () => { beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
imports: [ imports: [
rootMongooseTestModule(installUserFixtures), rootMongooseTestModule(async () => {
await installLanguageFixtures();
await installUserFixtures();
}),
MongooseModule.forFeature([ MongooseModule.forFeature([
UserModel, UserModel,
RoleModel, RoleModel,

View File

@ -22,6 +22,7 @@ import { LanguageModel } from '@/i18n/schemas/language.schema';
import { I18nService } from '@/i18n/services/i18n.service'; import { I18nService } from '@/i18n/services/i18n.service';
import { LanguageService } from '@/i18n/services/language.service'; import { LanguageService } from '@/i18n/services/language.service';
import { LoggerService } from '@/logger/logger.service'; import { LoggerService } from '@/logger/logger.service';
import { installLanguageFixtures } from '@/utils/test/fixtures/language';
import { installUserFixtures, users } from '@/utils/test/fixtures/user'; import { installUserFixtures, users } from '@/utils/test/fixtures/user';
import { import {
closeInMongodConnection, closeInMongodConnection,
@ -44,7 +45,10 @@ describe('ValidateAccountService', () => {
beforeAll(async () => { beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
imports: [ imports: [
rootMongooseTestModule(installUserFixtures), rootMongooseTestModule(async () => {
await installLanguageFixtures();
await installUserFixtures();
}),
MongooseModule.forFeature([ MongooseModule.forFeature([
UserModel, UserModel,
RoleModel, RoleModel,

View File

@ -8,10 +8,10 @@
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import { LanguageUpdateDto } from '@/i18n/dto/language.dto'; import { LanguageCreateDto } from '@/i18n/dto/language.dto';
import { LanguageModel } from '@/i18n/schemas/language.schema'; import { LanguageModel } from '@/i18n/schemas/language.schema';
export const languageFixtures: LanguageUpdateDto[] = [ export const languageFixtures: LanguageCreateDto[] = [
{ {
title: 'English', title: 'English',
code: 'en', code: 'en',