Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ export class AuthService {
// A 6-digit verification code will be sent to their email
const user = await this.userService.create({
...dto,
lastName: dto.lastName ?? null,
email: dto.email,
role: role.id,
status: {
Expand Down
93 changes: 93 additions & 0 deletions src/auth/dto/auth-register-login.dto.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { validate } from 'class-validator';
import { plainToInstance } from 'class-transformer';
import { AuthRegisterLoginDto } from './auth-register-login.dto';

describe('AuthRegisterLoginDto', () => {
describe('validation', () => {
it('should validate successfully with valid data', async () => {
const dto = plainToInstance(AuthRegisterLoginDto, {
email: '[email protected]',
password: 'secret123',
firstName: 'John',
lastName: 'Doe',
});

const errors = await validate(dto);
expect(errors).toHaveLength(0);
});

it('should fail when email is invalid', async () => {
const dto = plainToInstance(AuthRegisterLoginDto, {
email: 'not-an-email',
password: 'secret123',
firstName: 'John',
lastName: 'Doe',
});

const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
expect(errors[0].property).toBe('email');
});

it('should fail when password is too short', async () => {
const dto = plainToInstance(AuthRegisterLoginDto, {
email: '[email protected]',
password: '12345', // less than 6 characters
firstName: 'John',
lastName: 'Doe',
});

const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
expect(errors[0].property).toBe('password');
});

it('should fail when firstName is empty', async () => {
const dto = plainToInstance(AuthRegisterLoginDto, {
email: '[email protected]',
password: 'secret123',
firstName: '',
lastName: 'Doe',
});

const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
const firstNameError = errors.find((e) => e.property === 'firstName');
expect(firstNameError).toBeDefined();
});

it('should allow empty lastName for single-name users', async () => {
const dto = plainToInstance(AuthRegisterLoginDto, {
email: '[email protected]',
password: 'secret123',
firstName: 'Cher',
lastName: '',
});

const errors = await validate(dto);
expect(errors).toHaveLength(0);
});

it('should allow missing lastName for single-name users', async () => {
const dto = plainToInstance(AuthRegisterLoginDto, {
email: '[email protected]',
password: 'secret123',
firstName: 'Madonna',
});

const errors = await validate(dto);
expect(errors).toHaveLength(0);
});

it('should transform email to lowercase', () => {
const dto = plainToInstance(AuthRegisterLoginDto, {
email: '[email protected]',
password: 'secret123',
firstName: 'John',
lastName: 'Doe',
});

expect(dto.email).toBe('[email protected]');
});
});
});
8 changes: 4 additions & 4 deletions src/auth/dto/auth-register-login.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsNotEmpty, MinLength } from 'class-validator';
import { IsEmail, IsNotEmpty, IsOptional, MinLength } from 'class-validator';
import { Transform } from 'class-transformer';
import { lowerCaseTransformer } from '../../utils/transformers/lower-case.transformer';

Expand All @@ -17,7 +17,7 @@ export class AuthRegisterLoginDto {
@IsNotEmpty()
firstName: string;

@ApiProperty({ example: 'Doe' })
@IsNotEmpty()
lastName: string;
@ApiProperty({ example: 'Doe', required: false })
@IsOptional()
lastName?: string;
}
3 changes: 1 addition & 2 deletions src/auth/dto/auth-update.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ export class AuthUpdateDto {

@ApiPropertyOptional({ example: 'Doe' })
@IsOptional()
@IsNotEmpty({ message: 'Please enter your last name' })
lastName?: string;
lastName?: string | null;

@ApiPropertyOptional({ example: '[email protected]' })
@IsOptional()
Expand Down
7 changes: 4 additions & 3 deletions src/core/constants/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,12 @@ export enum PostgisSrid {
export const DEFAULT_RADIUS = 200; // default radius in Miles for location searching

// Slug validation constants
// Slug must be 3-100 characters, start and end with alphanumeric, can contain hyphens
// Slug must be 3-100 characters, start with alphanumeric, can contain hyphens and underscores
// Note: Accepts both lower and uppercase - will be normalized to lowercase by the service
export const SLUG_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9-]{1,98}[a-zA-Z0-9]$/;
// Note: generateShortCode() uses nanoid's urlAlphabet which includes underscores
export const SLUG_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9_-]{1,98}[a-zA-Z0-9_]$/;
export const SLUG_VALIDATION_MESSAGE =
'Slug must be 3-100 characters, alphanumeric with hyphens, and cannot start or end with a hyphen';
'Slug must be 3-100 characters, start with alphanumeric, and can contain hyphens or underscores';

export interface TenantConfig {
id: string; // tenant id, ex: asdf2jkl
Expand Down
6 changes: 3 additions & 3 deletions src/user/dto/create-user.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ export class CreateUserDto {
@IsNotEmpty()
firstName: string | null;

@ApiProperty({ example: 'Doe', type: String })
@IsNotEmpty()
lastName: string | null;
@ApiPropertyOptional({ example: 'Doe', type: String })
@IsOptional()
lastName?: string | null;

@ApiPropertyOptional({ type: () => FileDto })
@IsOptional()
Expand Down