From 431eba86e7448c4bf00e3c114aaa8985ff5c1ff7 Mon Sep 17 00:00:00 2001 From: Bastian Wagner Date: Sat, 1 Aug 2026 11:20:47 +0200 Subject: [PATCH] fix: align admin profile request validation --- .../src/users/admin-users.controller.spec.ts | 16 +++++++++++++++ .../src/users/dto/admin-user.dto.ts | 9 +++++---- .../src/app/core/users/users-api.spec.ts | 20 +++++++++++++++++-- .../src/app/models/user-directory.model.ts | 4 ++-- 4 files changed, 41 insertions(+), 8 deletions(-) diff --git a/myteamwallet_backend/src/users/admin-users.controller.spec.ts b/myteamwallet_backend/src/users/admin-users.controller.spec.ts index 7cfd986..d335167 100644 --- a/myteamwallet_backend/src/users/admin-users.controller.spec.ts +++ b/myteamwallet_backend/src/users/admin-users.controller.spec.ts @@ -69,6 +69,22 @@ describe('AdminUsersController', () => { }); describe('admin user DTOs', () => { + it('accepts omitted and string profile names but rejects explicit nulls', async () => { + const omitted = plainToInstance(AdminUserProfileDto, {}); + const names = plainToInstance(AdminUserProfileDto, { + firstName: 'Ada', + lastName: 'Admin', + }); + const nulls = plainToInstance(AdminUserProfileDto, { + firstName: null, + lastName: null, + }); + + expect(await validate(omitted)).toEqual([]); + expect(await validate(names)).toEqual([]); + expect(await validate(nulls)).not.toEqual([]); + }); + it('accepts only profile names in the profile DTO', async () => { const dto = plainToInstance(AdminUserProfileDto, { firstName: 'Ada', diff --git a/myteamwallet_backend/src/users/dto/admin-user.dto.ts b/myteamwallet_backend/src/users/dto/admin-user.dto.ts index 07b654d..ed67c16 100644 --- a/myteamwallet_backend/src/users/dto/admin-user.dto.ts +++ b/myteamwallet_backend/src/users/dto/admin-user.dto.ts @@ -7,20 +7,21 @@ import { Max, MaxLength, Min, + ValidateIf, } from 'class-validator'; import { RoleEnum } from '../../roles/roles.enum'; import { StatusEnum } from '../../statuses/statuses.enum'; export class AdminUserProfileDto { - @IsOptional() + @ValidateIf((_, value) => value !== undefined) @IsString() @MaxLength(255) - firstName?: string | null; + firstName?: string; - @IsOptional() + @ValidateIf((_, value) => value !== undefined) @IsString() @MaxLength(255) - lastName?: string | null; + lastName?: string; } export class AdminUserRoleDto { diff --git a/myteamwallet_frontend_modern/src/app/core/users/users-api.spec.ts b/myteamwallet_frontend_modern/src/app/core/users/users-api.spec.ts index e838ae5..42e7db3 100644 --- a/myteamwallet_frontend_modern/src/app/core/users/users-api.spec.ts +++ b/myteamwallet_frontend_modern/src/app/core/users/users-api.spec.ts @@ -3,6 +3,7 @@ import { TestBed } from '@angular/core/testing'; import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { environment } from '../../../environments/environment'; import { AdminUsersApi } from './admin-users-api'; +import { AdminUserProfileRequest } from '../../models/user-directory.model'; import { UsersApi } from './users-api'; describe('UsersApi', () => { @@ -32,6 +33,21 @@ describe('AdminUsersApi', () => { let service: AdminUsersApi; let httpMock: HttpTestingController; + it('accepts profile requests with omitted or string names only', () => { + const omitted: AdminUserProfileRequest = {}; + const names: AdminUserProfileRequest = { + firstName: 'Ada', + lastName: 'Admin', + }; + + // @ts-expect-error Profile names cannot be explicitly cleared to null. + const nullName: AdminUserProfileRequest = { firstName: null }; + + expect(omitted).toEqual({}); + expect(names).toEqual({ firstName: 'Ada', lastName: 'Admin' }); + expect(nullName).toEqual({ firstName: null }); + }); + beforeEach(() => { TestBed.configureTestingModule({ providers: [provideHttpClient(), provideHttpClientTesting()] }); service = TestBed.inject(AdminUsersApi); @@ -41,10 +57,10 @@ describe('AdminUsersApi', () => { afterEach(() => httpMock.verify()); it('uses the dedicated admin profile endpoint and payload', () => { - service.updateProfile(7, { firstName: 'Alex', lastName: null }).subscribe(); + service.updateProfile(7, { firstName: 'Alex' }).subscribe(); const request = httpMock.expectOne(`${environment.apiUrl}admin/users/7/profile`); expect(request.request.method).toBe('PATCH'); - expect(request.request.body).toEqual({ firstName: 'Alex', lastName: null }); + expect(request.request.body).toEqual({ firstName: 'Alex' }); request.flush({}); }); diff --git a/myteamwallet_frontend_modern/src/app/models/user-directory.model.ts b/myteamwallet_frontend_modern/src/app/models/user-directory.model.ts index 0a91db4..f7ac630 100644 --- a/myteamwallet_frontend_modern/src/app/models/user-directory.model.ts +++ b/myteamwallet_frontend_modern/src/app/models/user-directory.model.ts @@ -48,8 +48,8 @@ export interface UserDirectoryFilters { } export interface AdminUserProfileRequest { - firstName?: string | null; - lastName?: string | null; + firstName?: string; + lastName?: string; } export type AdminUserRoleId = 1 | 2;