Implement the GRPC controller to update ads
This commit is contained in:
		
							parent
							
								
									7a84bff260
								
							
						
					
					
						commit
						659c1baea8
					
				| 
						 | 
				
			
			@ -33,12 +33,14 @@ import { DeleteAdGrpcController } from './interface/grpc-controllers/delete-ad.g
 | 
			
		|||
import { FindAdByIdGrpcController } from './interface/grpc-controllers/find-ad-by-id.grpc.controller';
 | 
			
		||||
import { FindAdsByIdsGrpcController } from './interface/grpc-controllers/find-ads-by-ids.grpc.controller';
 | 
			
		||||
import { FindAdsByUserIdGrpcController } from './interface/grpc-controllers/find-ads-by-user-id.grpc.controller';
 | 
			
		||||
import { UpdateAdGrpcController } from './interface/grpc-controllers/update-ad.grpc.controller';
 | 
			
		||||
import { MatcherAdCreatedMessageHandler } from './interface/message-handlers/matcher-ad-created.message-handler';
 | 
			
		||||
import { MatcherAdCreationFailedMessageHandler } from './interface/message-handlers/matcher-ad-creation-failed.message-handler';
 | 
			
		||||
import { UserDeletedMessageHandler } from './interface/message-handlers/user-deleted.message-handler';
 | 
			
		||||
 | 
			
		||||
const grpcControllers = [
 | 
			
		||||
  CreateAdGrpcController,
 | 
			
		||||
  UpdateAdGrpcController,
 | 
			
		||||
  DeleteAdGrpcController,
 | 
			
		||||
  FindAdByIdGrpcController,
 | 
			
		||||
  FindAdsByIdsGrpcController,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,7 @@ service AdService {
 | 
			
		|||
  rpc FindAllByIds(AdsById) returns (Ads);
 | 
			
		||||
  rpc FindAllByUserId(UserById) returns (Ads);
 | 
			
		||||
  rpc Create(Ad) returns (AdById);
 | 
			
		||||
  rpc Update(Ad) returns (Ad);
 | 
			
		||||
  rpc Update(Ad) returns (Empty);
 | 
			
		||||
  rpc Delete(AdById) returns (Empty);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
import { IsUUID } from 'class-validator';
 | 
			
		||||
import { CreateAdRequestDto } from './create-ad.request.dto';
 | 
			
		||||
 | 
			
		||||
export class UpdateAdRequestDto extends CreateAdRequestDto {
 | 
			
		||||
  @IsUUID(4)
 | 
			
		||||
  id: string;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,43 @@
 | 
			
		|||
import {
 | 
			
		||||
  NotFoundException,
 | 
			
		||||
  RpcExceptionCode,
 | 
			
		||||
  RpcValidationPipe,
 | 
			
		||||
} from '@mobicoop/ddd-library';
 | 
			
		||||
import { UpdateAdCommand } from '@modules/ad/core/application/commands/update-ad/update-ad.command';
 | 
			
		||||
import { Controller, UsePipes } from '@nestjs/common';
 | 
			
		||||
import { CommandBus } from '@nestjs/cqrs';
 | 
			
		||||
import { GrpcMethod, RpcException } from '@nestjs/microservices';
 | 
			
		||||
import { GRPC_SERVICE_NAME } from '@src/app.constants';
 | 
			
		||||
import { UpdateAdRequestDto } from './dtos/update-ad.request.dto';
 | 
			
		||||
 | 
			
		||||
@UsePipes(
 | 
			
		||||
  new RpcValidationPipe({
 | 
			
		||||
    whitelist: false,
 | 
			
		||||
    forbidUnknownValues: false,
 | 
			
		||||
  }),
 | 
			
		||||
)
 | 
			
		||||
@Controller()
 | 
			
		||||
export class UpdateAdGrpcController {
 | 
			
		||||
  constructor(private readonly commandBus: CommandBus) {}
 | 
			
		||||
 | 
			
		||||
  @GrpcMethod(GRPC_SERVICE_NAME, 'Update')
 | 
			
		||||
  async update(data: UpdateAdRequestDto): Promise<void> {
 | 
			
		||||
    try {
 | 
			
		||||
      const cmdProps = {
 | 
			
		||||
        adId: data.id,
 | 
			
		||||
        ...data,
 | 
			
		||||
      };
 | 
			
		||||
      delete (cmdProps as { id?: string }).id;
 | 
			
		||||
 | 
			
		||||
      await this.commandBus.execute(new UpdateAdCommand(cmdProps));
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      if (error instanceof NotFoundException) {
 | 
			
		||||
        throw new RpcException({
 | 
			
		||||
          code: RpcExceptionCode.NOT_FOUND,
 | 
			
		||||
          message: error.message,
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
      throw error;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,43 @@
 | 
			
		|||
import { Frequency } from '@modules/ad/core/domain/ad.types';
 | 
			
		||||
import { CreateAdRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/create-ad.request.dto';
 | 
			
		||||
import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto';
 | 
			
		||||
 | 
			
		||||
const originWaypoint: WaypointDto = {
 | 
			
		||||
  position: 0,
 | 
			
		||||
  lat: 48.689445,
 | 
			
		||||
  lon: 6.17651,
 | 
			
		||||
  houseNumber: '5',
 | 
			
		||||
  street: 'Avenue Foch',
 | 
			
		||||
  locality: 'Nancy',
 | 
			
		||||
  postalCode: '54000',
 | 
			
		||||
  country: 'France',
 | 
			
		||||
};
 | 
			
		||||
const destinationWaypoint: WaypointDto = {
 | 
			
		||||
  position: 1,
 | 
			
		||||
  lat: 48.8566,
 | 
			
		||||
  lon: 2.3522,
 | 
			
		||||
  locality: 'Paris',
 | 
			
		||||
  postalCode: '75000',
 | 
			
		||||
  country: 'France',
 | 
			
		||||
};
 | 
			
		||||
export function punctualCreateAdRequest(): CreateAdRequestDto {
 | 
			
		||||
  return {
 | 
			
		||||
    userId: '4eb6a6af-ecfd-41c3-9118-473a507014d4',
 | 
			
		||||
    fromDate: '2023-12-21',
 | 
			
		||||
    toDate: '2023-12-21',
 | 
			
		||||
    schedule: [
 | 
			
		||||
      {
 | 
			
		||||
        time: '08:15',
 | 
			
		||||
        day: 4,
 | 
			
		||||
        margin: 600,
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
    driver: false,
 | 
			
		||||
    passenger: true,
 | 
			
		||||
    seatsRequested: 1,
 | 
			
		||||
    seatsProposed: 3,
 | 
			
		||||
    strict: false,
 | 
			
		||||
    frequency: Frequency.PUNCTUAL,
 | 
			
		||||
    waypoints: [originWaypoint, destinationWaypoint],
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,51 +1,10 @@
 | 
			
		|||
import { IdResponse } from '@mobicoop/ddd-library';
 | 
			
		||||
import { RpcExceptionCode } from '@mobicoop/ddd-library';
 | 
			
		||||
import { IdResponse, RpcExceptionCode } from '@mobicoop/ddd-library';
 | 
			
		||||
import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
 | 
			
		||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
 | 
			
		||||
import { CreateAdGrpcController } from '@modules/ad/interface/grpc-controllers/create-ad.grpc.controller';
 | 
			
		||||
import { CreateAdRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/create-ad.request.dto';
 | 
			
		||||
import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto';
 | 
			
		||||
import { CommandBus } from '@nestjs/cqrs';
 | 
			
		||||
import { RpcException } from '@nestjs/microservices';
 | 
			
		||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
			
		||||
 | 
			
		||||
const originWaypoint: WaypointDto = {
 | 
			
		||||
  position: 0,
 | 
			
		||||
  lat: 48.689445,
 | 
			
		||||
  lon: 6.17651,
 | 
			
		||||
  houseNumber: '5',
 | 
			
		||||
  street: 'Avenue Foch',
 | 
			
		||||
  locality: 'Nancy',
 | 
			
		||||
  postalCode: '54000',
 | 
			
		||||
  country: 'France',
 | 
			
		||||
};
 | 
			
		||||
const destinationWaypoint: WaypointDto = {
 | 
			
		||||
  position: 1,
 | 
			
		||||
  lat: 48.8566,
 | 
			
		||||
  lon: 2.3522,
 | 
			
		||||
  locality: 'Paris',
 | 
			
		||||
  postalCode: '75000',
 | 
			
		||||
  country: 'France',
 | 
			
		||||
};
 | 
			
		||||
const punctualCreateAdRequest: CreateAdRequestDto = {
 | 
			
		||||
  userId: '4eb6a6af-ecfd-41c3-9118-473a507014d4',
 | 
			
		||||
  fromDate: '2023-12-21',
 | 
			
		||||
  toDate: '2023-12-21',
 | 
			
		||||
  schedule: [
 | 
			
		||||
    {
 | 
			
		||||
      time: '08:15',
 | 
			
		||||
      day: 4,
 | 
			
		||||
      margin: 600,
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  driver: false,
 | 
			
		||||
  passenger: true,
 | 
			
		||||
  seatsRequested: 1,
 | 
			
		||||
  seatsProposed: 3,
 | 
			
		||||
  strict: false,
 | 
			
		||||
  frequency: Frequency.PUNCTUAL,
 | 
			
		||||
  waypoints: [originWaypoint, destinationWaypoint],
 | 
			
		||||
};
 | 
			
		||||
import { punctualCreateAdRequest } from './ad.fixtures';
 | 
			
		||||
 | 
			
		||||
const mockCommandBus = {
 | 
			
		||||
  execute: jest
 | 
			
		||||
| 
						 | 
				
			
			@ -89,7 +48,7 @@ describe('Create Ad Grpc Controller', () => {
 | 
			
		|||
  it('should create a new ad', async () => {
 | 
			
		||||
    jest.spyOn(mockCommandBus, 'execute');
 | 
			
		||||
    const result: IdResponse = await createAdGrpcController.create(
 | 
			
		||||
      punctualCreateAdRequest,
 | 
			
		||||
      punctualCreateAdRequest(),
 | 
			
		||||
    );
 | 
			
		||||
    expect(result).toBeInstanceOf(IdResponse);
 | 
			
		||||
    expect(result.id).toBe('200d61a8-d878-4378-a609-c19ea71633d2');
 | 
			
		||||
| 
						 | 
				
			
			@ -100,7 +59,7 @@ describe('Create Ad Grpc Controller', () => {
 | 
			
		|||
    jest.spyOn(mockCommandBus, 'execute');
 | 
			
		||||
    expect.assertions(3);
 | 
			
		||||
    try {
 | 
			
		||||
      await createAdGrpcController.create(punctualCreateAdRequest);
 | 
			
		||||
      await createAdGrpcController.create(punctualCreateAdRequest());
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
      expect(e).toBeInstanceOf(RpcException);
 | 
			
		||||
      expect(e.error.code).toBe(RpcExceptionCode.ALREADY_EXISTS);
 | 
			
		||||
| 
						 | 
				
			
			@ -112,7 +71,7 @@ describe('Create Ad Grpc Controller', () => {
 | 
			
		|||
    jest.spyOn(mockCommandBus, 'execute');
 | 
			
		||||
    expect.assertions(3);
 | 
			
		||||
    try {
 | 
			
		||||
      await createAdGrpcController.create(punctualCreateAdRequest);
 | 
			
		||||
      await createAdGrpcController.create(punctualCreateAdRequest());
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
      expect(e).toBeInstanceOf(RpcException);
 | 
			
		||||
      expect(e.error.code).toBe(RpcExceptionCode.UNKNOWN);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,76 @@
 | 
			
		|||
import { NotFoundException, RpcExceptionCode } from '@mobicoop/ddd-library';
 | 
			
		||||
import { UpdateAdGrpcController } from '@modules/ad/interface/grpc-controllers/update-ad.grpc.controller';
 | 
			
		||||
import { CommandBus } from '@nestjs/cqrs';
 | 
			
		||||
import { RpcException } from '@nestjs/microservices';
 | 
			
		||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
			
		||||
import { punctualCreateAdRequest } from './ad.fixtures';
 | 
			
		||||
 | 
			
		||||
const validAdId = '200d61a8-d878-4378-a609-c19ea71633d2';
 | 
			
		||||
const mockCommandBus = {
 | 
			
		||||
  execute: jest.fn().mockImplementation(async (command) => {
 | 
			
		||||
    if (command.adId === '') throw 'Ad id is empty';
 | 
			
		||||
    if (command.adId != validAdId) throw new NotFoundException();
 | 
			
		||||
  }),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe('Update Ad GRPC Controller', () => {
 | 
			
		||||
  let updateAdGrpcController: UpdateAdGrpcController;
 | 
			
		||||
 | 
			
		||||
  beforeAll(async () => {
 | 
			
		||||
    const module: TestingModule = await Test.createTestingModule({
 | 
			
		||||
      providers: [
 | 
			
		||||
        {
 | 
			
		||||
          provide: CommandBus,
 | 
			
		||||
          useValue: mockCommandBus,
 | 
			
		||||
        },
 | 
			
		||||
        UpdateAdGrpcController,
 | 
			
		||||
      ],
 | 
			
		||||
    }).compile();
 | 
			
		||||
    updateAdGrpcController = module.get<UpdateAdGrpcController>(
 | 
			
		||||
      UpdateAdGrpcController,
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  afterEach(async () => {
 | 
			
		||||
    jest.clearAllMocks();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should be defined', () => {
 | 
			
		||||
    expect(updateAdGrpcController).toBeDefined();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should execute the update ad command', async () => {
 | 
			
		||||
    await updateAdGrpcController.update({
 | 
			
		||||
      id: validAdId,
 | 
			
		||||
      ...punctualCreateAdRequest(),
 | 
			
		||||
    });
 | 
			
		||||
    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should throw a dedicated RpcException if ad is not found', async () => {
 | 
			
		||||
    expect.assertions(3);
 | 
			
		||||
    try {
 | 
			
		||||
      await updateAdGrpcController.update({
 | 
			
		||||
        id: 'ac85f5f4-41cd-4c5d-9aee-0a1acb176fb8',
 | 
			
		||||
        ...punctualCreateAdRequest(),
 | 
			
		||||
      });
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
      expect(e).toBeInstanceOf(RpcException);
 | 
			
		||||
      expect(e.error.code).toBe(RpcExceptionCode.NOT_FOUND);
 | 
			
		||||
    }
 | 
			
		||||
    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should rethrow any other exceptions', async () => {
 | 
			
		||||
    expect.assertions(2);
 | 
			
		||||
    try {
 | 
			
		||||
      await updateAdGrpcController.update({
 | 
			
		||||
        id: '',
 | 
			
		||||
        ...punctualCreateAdRequest(),
 | 
			
		||||
      });
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
      expect(e).toBe('Ad id is empty');
 | 
			
		||||
    }
 | 
			
		||||
    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
		Loading…
	
		Reference in New Issue