find ads by user id query, dto and controller
This commit is contained in:
		
							parent
							
								
									909ef04e69
								
							
						
					
					
						commit
						bbb96cfd36
					
				| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
{
 | 
			
		||||
  "typescript.tsdk": "node_modules/typescript/lib"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,25 @@
 | 
			
		|||
import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
 | 
			
		||||
import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
 | 
			
		||||
import { AdRepositoryPort } from '../../ports/ad.repository.port';
 | 
			
		||||
import { Inject } from '@nestjs/common';
 | 
			
		||||
import { AdEntity } from '../../../domain/ad.entity';
 | 
			
		||||
import { FindAdsByUserIdQuery } from './find-ads-by-user-id.query';
 | 
			
		||||
 | 
			
		||||
@QueryHandler(FindAdsByUserIdQuery)
 | 
			
		||||
export class FindAdsByUserIdQueryHandler implements IQueryHandler {
 | 
			
		||||
  constructor(
 | 
			
		||||
    @Inject(AD_REPOSITORY)
 | 
			
		||||
    private readonly repository: AdRepositoryPort,
 | 
			
		||||
  ) {}
 | 
			
		||||
  async execute(query: FindAdsByUserIdQuery): Promise<AdEntity[]> {
 | 
			
		||||
    return await this.repository.findAll(
 | 
			
		||||
      {
 | 
			
		||||
        userUuid: query.userId,
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        waypoints: true,
 | 
			
		||||
        schedule: true,
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
import { QueryBase } from '@mobicoop/ddd-library';
 | 
			
		||||
 | 
			
		||||
export class FindAdsByUserIdQuery extends QueryBase {
 | 
			
		||||
  readonly userId: string;
 | 
			
		||||
 | 
			
		||||
  constructor(userId: string) {
 | 
			
		||||
    super();
 | 
			
		||||
    this.userId = userId;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ package ad;
 | 
			
		|||
service AdService {
 | 
			
		||||
  rpc FindOneById(AdById) returns (Ad);
 | 
			
		||||
  rpc FindAllByIds(AdsById) returns (Ads);
 | 
			
		||||
  rpc FindAllByUserId(UserById) returns (Ads);
 | 
			
		||||
  rpc Create(Ad) returns (AdById);
 | 
			
		||||
  rpc Update(Ad) returns (Ad);
 | 
			
		||||
  rpc Delete(AdById) returns (Empty);
 | 
			
		||||
| 
						 | 
				
			
			@ -14,6 +15,10 @@ message AdById {
 | 
			
		|||
  string id = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message UserById {
 | 
			
		||||
  string id = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message AdsById {
 | 
			
		||||
  repeated string ids = 1;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
import { IsString } from 'class-validator';
 | 
			
		||||
 | 
			
		||||
export class FindAdsByUserIdRequestDto {
 | 
			
		||||
  @IsString()
 | 
			
		||||
  id: string;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,44 @@
 | 
			
		|||
import { Controller, UsePipes } from '@nestjs/common';
 | 
			
		||||
import { QueryBus } from '@nestjs/cqrs';
 | 
			
		||||
import { GrpcMethod, RpcException } from '@nestjs/microservices';
 | 
			
		||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
 | 
			
		||||
import { AdMapper } from '@modules/ad/ad.mapper';
 | 
			
		||||
import { RpcExceptionCode } from '@mobicoop/ddd-library';
 | 
			
		||||
import { RpcValidationPipe } from '@mobicoop/ddd-library';
 | 
			
		||||
import { GRPC_SERVICE_NAME } from '@src/app.constants';
 | 
			
		||||
import { AdsResponseDto } from '../dtos/ads.response.dto';
 | 
			
		||||
import { FindAdsByUserIdQuery } from '@modules/ad/core/application/queries/find-ads-by-user-id/find-ads-by-user-id.query';
 | 
			
		||||
import { FindAdsByUserIdRequestDto } from './dtos/find-ads-by-user-id.request.dto';
 | 
			
		||||
 | 
			
		||||
@UsePipes(
 | 
			
		||||
  new RpcValidationPipe({
 | 
			
		||||
    whitelist: false,
 | 
			
		||||
    forbidUnknownValues: false,
 | 
			
		||||
  }),
 | 
			
		||||
)
 | 
			
		||||
@Controller()
 | 
			
		||||
export class FindAdsByUserIdGrpcController {
 | 
			
		||||
  constructor(
 | 
			
		||||
    protected readonly mapper: AdMapper,
 | 
			
		||||
    private readonly queryBus: QueryBus,
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  @GrpcMethod(GRPC_SERVICE_NAME, 'FindAllByUserId')
 | 
			
		||||
  async findAllByUserId(
 | 
			
		||||
    data: FindAdsByUserIdRequestDto,
 | 
			
		||||
  ): Promise<AdsResponseDto> {
 | 
			
		||||
    try {
 | 
			
		||||
      const ads: AdEntity[] = await this.queryBus.execute(
 | 
			
		||||
        new FindAdsByUserIdQuery(data.id),
 | 
			
		||||
      );
 | 
			
		||||
      return {
 | 
			
		||||
        ads: ads.map((ad: AdEntity) => this.mapper.toResponse(ad)),
 | 
			
		||||
      };
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      throw new RpcException({
 | 
			
		||||
        code: RpcExceptionCode.UNKNOWN,
 | 
			
		||||
        message: e.message,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,103 @@
 | 
			
		|||
import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
 | 
			
		||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
 | 
			
		||||
import { CreateAdProps, Frequency } from '@modules/ad/core/domain/ad.types';
 | 
			
		||||
import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
 | 
			
		||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
			
		||||
import { FindAdsByUserIdQueryHandler } from '@modules/ad/core/application/queries/find-ads-by-user-id/find-ads-by-user-id.query-handler';
 | 
			
		||||
import { FindAdsByUserIdQuery } from '@modules/ad/core/application/queries/find-ads-by-user-id/find-ads-by-user-id.query';
 | 
			
		||||
 | 
			
		||||
const originWaypointProps: WaypointProps = {
 | 
			
		||||
  position: 0,
 | 
			
		||||
  address: {
 | 
			
		||||
    houseNumber: '5',
 | 
			
		||||
    street: 'Avenue Foch',
 | 
			
		||||
    locality: 'Nancy',
 | 
			
		||||
    postalCode: '54000',
 | 
			
		||||
    country: 'France',
 | 
			
		||||
    coordinates: {
 | 
			
		||||
      lat: 48.689445,
 | 
			
		||||
      lon: 6.17651,
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
const destinationWaypointProps: WaypointProps = {
 | 
			
		||||
  position: 1,
 | 
			
		||||
  address: {
 | 
			
		||||
    locality: 'Paris',
 | 
			
		||||
    postalCode: '75000',
 | 
			
		||||
    country: 'France',
 | 
			
		||||
    coordinates: {
 | 
			
		||||
      lat: 48.8566,
 | 
			
		||||
      lon: 2.3522,
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
const baseCreateAdProps = {
 | 
			
		||||
  userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36',
 | 
			
		||||
  seatsProposed: 3,
 | 
			
		||||
  seatsRequested: 1,
 | 
			
		||||
  strict: false,
 | 
			
		||||
  waypoints: [originWaypointProps, destinationWaypointProps],
 | 
			
		||||
};
 | 
			
		||||
const punctualCreateAdProps = {
 | 
			
		||||
  fromDate: '2023-06-22',
 | 
			
		||||
  toDate: '2023-06-22',
 | 
			
		||||
  schedule: [
 | 
			
		||||
    {
 | 
			
		||||
      time: '08:30',
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  frequency: Frequency.PUNCTUAL,
 | 
			
		||||
};
 | 
			
		||||
const punctualPassengerCreateAdProps: CreateAdProps = {
 | 
			
		||||
  ...baseCreateAdProps,
 | 
			
		||||
  ...punctualCreateAdProps,
 | 
			
		||||
  driver: false,
 | 
			
		||||
  passenger: true,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const ads: AdEntity[] = [
 | 
			
		||||
  AdEntity.create(punctualPassengerCreateAdProps),
 | 
			
		||||
  AdEntity.create(punctualPassengerCreateAdProps),
 | 
			
		||||
  AdEntity.create(punctualPassengerCreateAdProps),
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const mockAdRepository = {
 | 
			
		||||
  findAll: jest.fn().mockImplementation(() => ads),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe('Find Ads By User Id Query Handler', () => {
 | 
			
		||||
  let findAdsByUserIdQueryHandler: FindAdsByUserIdQueryHandler;
 | 
			
		||||
 | 
			
		||||
  beforeAll(async () => {
 | 
			
		||||
    const module: TestingModule = await Test.createTestingModule({
 | 
			
		||||
      providers: [
 | 
			
		||||
        {
 | 
			
		||||
          provide: AD_REPOSITORY,
 | 
			
		||||
          useValue: mockAdRepository,
 | 
			
		||||
        },
 | 
			
		||||
        FindAdsByUserIdQueryHandler,
 | 
			
		||||
      ],
 | 
			
		||||
    }).compile();
 | 
			
		||||
 | 
			
		||||
    findAdsByUserIdQueryHandler = module.get<FindAdsByUserIdQueryHandler>(
 | 
			
		||||
      FindAdsByUserIdQueryHandler,
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should be defined', () => {
 | 
			
		||||
    expect(findAdsByUserIdQueryHandler).toBeDefined();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('execution', () => {
 | 
			
		||||
    it('should return an ad', async () => {
 | 
			
		||||
      const findAdsByIdsQuery = new FindAdsByUserIdQuery(
 | 
			
		||||
        'e8fe64b1-4c33-49e1-9f69-4db48b21df36',
 | 
			
		||||
      );
 | 
			
		||||
      const ads: AdEntity[] =
 | 
			
		||||
        await findAdsByUserIdQueryHandler.execute(findAdsByIdsQuery);
 | 
			
		||||
      expect(ads).toHaveLength(3);
 | 
			
		||||
      expect(ads[1].getProps().fromDate).toBe('2023-06-22');
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,124 @@
 | 
			
		|||
import { RpcExceptionCode } from '@mobicoop/ddd-library';
 | 
			
		||||
import { AdMapper } from '@modules/ad/ad.mapper';
 | 
			
		||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
 | 
			
		||||
import { FindAdsByUserIdGrpcController } from '@modules/ad/interface/grpc-controllers/find-ads-by-user-id.grpc.controller';
 | 
			
		||||
import { QueryBus } from '@nestjs/cqrs';
 | 
			
		||||
import { RpcException } from '@nestjs/microservices';
 | 
			
		||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
			
		||||
 | 
			
		||||
const mockQueryBus = {
 | 
			
		||||
  execute: jest
 | 
			
		||||
    .fn()
 | 
			
		||||
    .mockImplementationOnce(() => [
 | 
			
		||||
      '200d61a8-d878-4378-a609-c19ea71633d2',
 | 
			
		||||
      '200d61a8-d878-4378-a609-c19ea71633d3',
 | 
			
		||||
      '200d61a8-d878-4378-a609-c19ea71633d4',
 | 
			
		||||
    ])
 | 
			
		||||
    .mockImplementationOnce(() => {
 | 
			
		||||
      throw new Error();
 | 
			
		||||
    }),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const mockAdMapper = {
 | 
			
		||||
  toResponse: jest.fn().mockImplementationOnce(() => ({
 | 
			
		||||
    userId: '8cc90d1a-4a59-4289-a7d8-078f9db7857f',
 | 
			
		||||
    driver: true,
 | 
			
		||||
    passenger: true,
 | 
			
		||||
    frequency: Frequency.PUNCTUAL,
 | 
			
		||||
    fromDate: '2023-06-27',
 | 
			
		||||
    toDate: '2023-06-27',
 | 
			
		||||
    schedule: {
 | 
			
		||||
      tue: '07:15',
 | 
			
		||||
    },
 | 
			
		||||
    marginDurations: {
 | 
			
		||||
      mon: 900,
 | 
			
		||||
      tue: 900,
 | 
			
		||||
      wed: 900,
 | 
			
		||||
      thu: 900,
 | 
			
		||||
      fri: 900,
 | 
			
		||||
      sat: 900,
 | 
			
		||||
      sun: 900,
 | 
			
		||||
    },
 | 
			
		||||
    seatsProposed: 3,
 | 
			
		||||
    seatsRequested: 1,
 | 
			
		||||
    waypoints: [
 | 
			
		||||
      {
 | 
			
		||||
        position: 0,
 | 
			
		||||
        lat: 48.689445,
 | 
			
		||||
        lon: 6.17651,
 | 
			
		||||
        houseNumber: '5',
 | 
			
		||||
        street: 'Avenue Foch',
 | 
			
		||||
        locality: 'Nancy',
 | 
			
		||||
        postalCode: '54000',
 | 
			
		||||
        country: 'France',
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        position: 1,
 | 
			
		||||
        lat: 48.8566,
 | 
			
		||||
        lon: 2.3522,
 | 
			
		||||
        locality: 'Paris',
 | 
			
		||||
        postalCode: '75000',
 | 
			
		||||
        country: 'France',
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
  })),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe('Find Ads By User Id Grpc Controller', () => {
 | 
			
		||||
  let findAdsByUserIdGrpcController: FindAdsByUserIdGrpcController;
 | 
			
		||||
 | 
			
		||||
  beforeAll(async () => {
 | 
			
		||||
    const module: TestingModule = await Test.createTestingModule({
 | 
			
		||||
      providers: [
 | 
			
		||||
        {
 | 
			
		||||
          provide: QueryBus,
 | 
			
		||||
          useValue: mockQueryBus,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          provide: AdMapper,
 | 
			
		||||
          useValue: mockAdMapper,
 | 
			
		||||
        },
 | 
			
		||||
        FindAdsByUserIdGrpcController,
 | 
			
		||||
      ],
 | 
			
		||||
    }).compile();
 | 
			
		||||
 | 
			
		||||
    findAdsByUserIdGrpcController = module.get<FindAdsByUserIdGrpcController>(
 | 
			
		||||
      FindAdsByUserIdGrpcController,
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  afterEach(async () => {
 | 
			
		||||
    jest.clearAllMocks();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should be defined', () => {
 | 
			
		||||
    expect(findAdsByUserIdGrpcController).toBeDefined();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should return ads', async () => {
 | 
			
		||||
    jest.spyOn(mockQueryBus, 'execute');
 | 
			
		||||
    jest.spyOn(mockAdMapper, 'toResponse');
 | 
			
		||||
    const response = await findAdsByUserIdGrpcController.findAllByUserId({
 | 
			
		||||
      id: '8cc90d1a-4a59-4289-a7d8-078f9db7857f',
 | 
			
		||||
    });
 | 
			
		||||
    expect(response.ads).toHaveLength(3);
 | 
			
		||||
    expect(mockQueryBus.execute).toHaveBeenCalledTimes(1);
 | 
			
		||||
    expect(mockAdMapper.toResponse).toHaveBeenCalledTimes(3);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should throw a generic RpcException', async () => {
 | 
			
		||||
    jest.spyOn(mockQueryBus, 'execute');
 | 
			
		||||
    jest.spyOn(mockAdMapper, 'toResponse');
 | 
			
		||||
    expect.assertions(4);
 | 
			
		||||
    try {
 | 
			
		||||
      await findAdsByUserIdGrpcController.findAllByUserId({
 | 
			
		||||
        id: '8cc90d1a-4a59-4289-a7d8-078f9db7857f',
 | 
			
		||||
      });
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
      expect(e).toBeInstanceOf(RpcException);
 | 
			
		||||
      expect(e.error.code).toBe(RpcExceptionCode.UNKNOWN);
 | 
			
		||||
    }
 | 
			
		||||
    expect(mockQueryBus.execute).toHaveBeenCalledTimes(1);
 | 
			
		||||
    expect(mockAdMapper.toResponse).toHaveBeenCalledTimes(0);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
		Loading…
	
		Reference in New Issue