forked from Shared/esg
feat: add Persian error translations and comprehensive test suite
Implement normalized error handling with Persian translations across all exception types, replace legacy NestJS exceptions with AppException, and add unit, integration, and smoke tests. - Add error catalog with gateway-owned codes and Persian messages - Introduce AppException wrapping normalized error envelopes - Add translateError helper for automatic messageFa population - Remove claims module and update provider error normalization - Add unit tests for error contracts and helper functions - Add integration tests for admin and inquiry endpoints - Add smoke tests for real provider connectivity - Add test support utilities (auth mocks, assertions, app factory)
This commit is contained in:
@@ -1,9 +1,5 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
ConflictException,
|
||||
ForbiddenException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { InjectModel } from '@nestjs/mongoose';
|
||||
import { Model, Types } from 'mongoose';
|
||||
@@ -19,6 +15,7 @@ import { UpdateUserDto } from './dto/update-user.dto';
|
||||
import { UserMapper } from './mappers/user.mapper';
|
||||
import { User, UserDocument } from './schemas/user.schema';
|
||||
import { UserResponseDto } from './dto/user-response.dto';
|
||||
import { AppException } from '../common/exceptions/app-exception';
|
||||
|
||||
export interface RequestContext {
|
||||
ip?: string;
|
||||
@@ -36,7 +33,7 @@ export class UsersService {
|
||||
async findById(id: string): Promise<UserDocument> {
|
||||
const user = await this.userModel.findById(id);
|
||||
if (!user) {
|
||||
throw new NotFoundException('User not found');
|
||||
throw new AppException('USER_NOT_FOUND');
|
||||
}
|
||||
return user;
|
||||
}
|
||||
@@ -68,7 +65,7 @@ export class UsersService {
|
||||
$or: [{ username }, { email }],
|
||||
});
|
||||
if (existing) {
|
||||
throw new ConflictException('Username or email already exists');
|
||||
throw new AppException('USERNAME_OR_EMAIL_EXISTS');
|
||||
}
|
||||
|
||||
const passwordHash = await this.passwordService.hash(dto.password);
|
||||
@@ -117,7 +114,7 @@ export class UsersService {
|
||||
const email = dto.email.toLowerCase().trim();
|
||||
const conflict = await this.userModel.findOne({ email, _id: { $ne: id } });
|
||||
if (conflict) {
|
||||
throw new ConflictException('Email already in use');
|
||||
throw new AppException('EMAIL_ALREADY_IN_USE');
|
||||
}
|
||||
user.email = email;
|
||||
}
|
||||
@@ -149,7 +146,7 @@ export class UsersService {
|
||||
): Promise<UserResponseDto> {
|
||||
const user = await this.findById(id);
|
||||
if (user._id.toString() === actor.id) {
|
||||
throw new BadRequestException('Cannot block your own account');
|
||||
throw new AppException('CANNOT_BLOCK_SELF');
|
||||
}
|
||||
user.isBlocked = true;
|
||||
user.refreshToken = undefined;
|
||||
@@ -194,7 +191,7 @@ export class UsersService {
|
||||
): Promise<void> {
|
||||
const user = await this.userModel.findById(id).select('+password +refreshToken');
|
||||
if (!user) {
|
||||
throw new NotFoundException('User not found');
|
||||
throw new AppException('USER_NOT_FOUND');
|
||||
}
|
||||
|
||||
user.password = await this.passwordService.hash(dto.newPassword);
|
||||
@@ -238,10 +235,10 @@ export class UsersService {
|
||||
|
||||
private assertCanAssignRole(actorRole: Role, targetRole: Role): void {
|
||||
if (targetRole === Role.SUPER_ADMIN && actorRole !== Role.SUPER_ADMIN) {
|
||||
throw new ForbiddenException('Only SUPER_ADMIN can assign SUPER_ADMIN role');
|
||||
throw new AppException('SUPER_ADMIN_ROLE_REQUIRED');
|
||||
}
|
||||
if (!ADMIN_ROLES.includes(actorRole)) {
|
||||
throw new ForbiddenException('Insufficient permissions to manage users');
|
||||
throw new AppException('INSUFFICIENT_USER_MANAGEMENT_PERMISSION');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user