Skip to content

Commit

Permalink
Merge branch 'develop' into HUM-61-dm-filters-on-mobile
Browse files Browse the repository at this point in the history
  • Loading branch information
KacperKoza343 committed Oct 28, 2024
2 parents d2027d0 + ca513a6 commit cda0969
Show file tree
Hide file tree
Showing 172 changed files with 4,841 additions and 1,043 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,18 @@ import { CronJobModule } from './modules/cron-job/cron-job.module';
import { HealthModule } from './modules/health/health.module';
import { EnvConfigModule } from './common/config/config.module';
import { ScheduleModule } from '@nestjs/schedule';
import { TransformEnumInterceptor } from './common/interceptors/transform-enum.interceptor';

@Module({
providers: [
{
provide: APP_INTERCEPTOR,
useClass: SnakeCaseInterceptor,
},
{
provide: APP_INTERCEPTOR,
useClass: TransformEnumInterceptor,
},
JwtHttpStrategy,
],
imports: [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {
registerDecorator,
ValidationOptions,
ValidationArguments,
} from 'class-validator';
import 'reflect-metadata';

export function IsEnumCaseInsensitive(
enumType: any,
validationOptions?: ValidationOptions,
) {
// eslint-disable-next-line @typescript-eslint/ban-types
return function (object: Object, propertyName: string) {
// Attach enum metadata to the property
Reflect.defineMetadata('custom:enum', enumType, object, propertyName);

// Register the validation logic using class-validator
registerDecorator({
name: 'isEnumWithMetadata',
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: {
validate(value: any, args: ValidationArguments) {
// Retrieve enum type from metadata
const enumType = Reflect.getMetadata(
'custom:enum',
args.object,
args.property,
);
if (!enumType) {
return false; // If no enum metadata is found, validation fails
}

// Validate value is part of the enum
const enumValues = Object.values(enumType);
return enumValues.includes(value);
},
defaultMessage(args: ValidationArguments) {
// Default message if validation fails
const enumType = Reflect.getMetadata(
'custom:enum',
args.object,
args.property,
);
const enumValues = Object.values(enumType).join(', ');
return `${args.property} must be a valid enum value. Valid values: [${enumValues}]`;
},
},
});
};
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './public';
export * from './enums';
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export enum JobStatus {
ACTIVE = 'ACTIVE',
COMPLETED = 'COMPLETED',
CANCELED = 'CANCELED',
ACTIVE = 'active',
COMPLETED = 'completed',
CANCELED = 'canceled',
}

export enum JobSortField {
Expand All @@ -22,12 +22,12 @@ export enum JobFieldName {
}

export enum AssignmentStatus {
ACTIVE = 'ACTIVE',
VALIDATION = 'VALIDATION',
COMPLETED = 'COMPLETED',
EXPIRED = 'EXPIRED',
CANCELED = 'CANCELED',
REJECTED = 'REJECTED',
ACTIVE = 'active',
VALIDATION = 'validation',
COMPLETED = 'completed',
EXPIRED = 'expired',
CANCELED = 'canceled',
REJECTED = 'rejected',
}

export enum AssignmentSortField {
Expand All @@ -40,5 +40,5 @@ export enum AssignmentSortField {
}

export enum JobType {
FORTUNE = 'FORTUNE',
FORTUNE = 'fortune',
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ export enum AuthSignatureRole {
}

export enum Role {
Worker = 'WORKER',
HumanApp = 'HUMAN_APP',
Worker = 'worker',
HumanApp = 'human_app',
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export enum EventType {
}

export enum WebhookStatus {
PENDING = 'PENDING',
COMPLETED = 'COMPLETED',
FAILED = 'FAILED',
PENDING = 'pending',
COMPLETED = 'completed',
FAILED = 'failed',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { TransformEnumInterceptor } from './transform-enum.interceptor';
import {
ExecutionContext,
CallHandler,
BadRequestException,
} from '@nestjs/common';
import { of } from 'rxjs';
import { IsNumber, IsString, Min } from 'class-validator';
import { JobType } from '../../common/enums/job';
import { ApiProperty } from '@nestjs/swagger';
import { IsEnumCaseInsensitive } from '../decorators/enums';

export class MockDto {
@ApiProperty({
enum: JobType,
})
@IsEnumCaseInsensitive(JobType)
public jobType: JobType;

@ApiProperty()
@IsNumber()
@Min(0.5)
public amount: number;

@ApiProperty()
@IsString()
public address: string;
}

describe('TransformEnumInterceptor', () => {
let interceptor: TransformEnumInterceptor;
let executionContext: ExecutionContext;
let callHandler: CallHandler;

beforeEach(() => {
interceptor = new TransformEnumInterceptor();

// Mocking ExecutionContext and CallHandler
executionContext = {
switchToHttp: jest.fn().mockReturnValue({
getRequest: jest.fn().mockReturnValue({
body: {
jobType: 'FORTUNE',
amount: 5,
address: '0xCf88b3f1992458C2f5a229573c768D0E9F70C44e',
},
}),
}),
getHandler: jest.fn().mockReturnValue({
name: 'create', // Assume the handler is named 'create'
}),
getClass: jest.fn().mockReturnValue({
prototype: {},
}),
} as unknown as ExecutionContext;

callHandler = {
handle: jest.fn().mockReturnValue(of({})),
};

// Mock Reflect.getMetadata to return DTO and Enum types
Reflect.getMetadata = jest.fn((metadataKey, target, propertyKey) => {
// Mock design:paramtypes to return MockDto as the parameter type
if (metadataKey === 'design:paramtypes') {
return [MockDto];
}

// Mock custom:enum to return the corresponding enum for each property
if (metadataKey === 'custom:enum' && propertyKey === 'jobType') {
return JobType;
}
return undefined; // For non-enum properties, return undefined
}) as any;
});

it('should transform enum values to lowercase', async () => {
// Run the interceptor
await interceptor.intercept(executionContext, callHandler).toPromise();

// Access the modified request body
const request = executionContext.switchToHttp().getRequest();

// Expectations
expect(request.body.jobType).toBe('fortune'); // Should be transformed to lowercase
expect(request.body).toEqual({
jobType: 'fortune',
amount: 5,
address: '0xCf88b3f1992458C2f5a229573c768D0E9F70C44e',
});
expect(callHandler.handle).toBeCalled(); // Ensure the handler is called
});

it('should throw an error if the value is not a valid enum', async () => {
// Modify the request body to have an invalid enum value for jobType
executionContext.switchToHttp = jest.fn().mockReturnValue({
getRequest: jest.fn().mockReturnValue({
body: {
jobType: 'invalidEnum', // Invalid enum value for jobType
amount: 5,
address: '0xCf88b3f1992458C2f5a229573c768D0E9F70C44e',
},
}),
});

try {
// Run the interceptor
await interceptor.intercept(executionContext, callHandler).toPromise();
} catch (err) {
// Expect an error to be thrown
expect(err).toBeInstanceOf(BadRequestException);
expect(err.response.statusCode).toBe(400);
expect(err.response.message).toContain('Validation failed');
}
});

it('should not transform non-enum properties', async () => {
// Run the interceptor with a non-enum property (amount and address)
await interceptor.intercept(executionContext, callHandler).toPromise();

// Access the modified request body
const request = executionContext.switchToHttp().getRequest();

// Expectations
expect(request.body.amount).toBe(5); // Non-enum property should remain unchanged
expect(request.body.address).toBe(
'0xCf88b3f1992458C2f5a229573c768D0E9F70C44e',
); // Non-enum string should remain unchanged
expect(callHandler.handle).toBeCalled();
});

it('should handle nested objects with enums', async () => {
// Modify the request body to have a nested object with enum value
executionContext.switchToHttp = jest.fn().mockReturnValue({
getRequest: jest.fn().mockReturnValue({
body: {
transaction: {
jobType: 'FORTUNE',
address: '0xCf88b3f1992458C2f5a229573c768D0E9F70C44e',
},
amount: 5,
address: '0xCf88b3f1992458C2f5a229573c768D0E9F70C44e',
},
}),
});

// Run the interceptor
await interceptor.intercept(executionContext, callHandler).toPromise();

// Access the modified request body
const request = executionContext.switchToHttp().getRequest();

// Expectations
expect(request.body.transaction.jobType).toBe('fortune');
expect(request.body).toEqual({
transaction: {
jobType: 'fortune',
address: '0xCf88b3f1992458C2f5a229573c768D0E9F70C44e',
},
amount: 5,
address: '0xCf88b3f1992458C2f5a229573c768D0E9F70C44e',
});
expect(callHandler.handle).toHaveBeenCalled();
});
});
Loading

0 comments on commit cda0969

Please sign in to comment.