diff --git a/backend/src/main/java/com/angel/autonow/driver/DriverController.java b/backend/src/main/java/com/angel/autonow/driver/DriverController.java new file mode 100644 index 0000000..50b5e76 --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/driver/DriverController.java @@ -0,0 +1,49 @@ +package com.angel.autonow.driver; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/drivers") +public class DriverController { + + private final DriverService driverService; + + @PostMapping + @PreAuthorize("hasRole('ADMIN')") + public ResponseEntity createDriver(@Valid @RequestBody DriverRequestDTO request) { + return driverService.createDriver(request) + .map(driver -> ResponseEntity.status(HttpStatus.CREATED).body(driver)) + .orElse(ResponseEntity.badRequest().build()); + } + + @GetMapping("/{id}") + @PreAuthorize("hasAnyRole('ADMIN', 'CUSTOMER', 'DRIVER')") + public DriverResponseDTO getDriverById(@PathVariable Long id) { + return driverService.getDriverById(id).orElse(null); + } + + @GetMapping("/license/{licenseNumber}") + @PreAuthorize("hasAnyRole('ADMIN', 'DRIVER')") + public DriverResponseDTO getDriverByLicenseNumber(@PathVariable String licenseNumber) { + return driverService.getDriverByLicenseNumber(licenseNumber).orElse(null); + } + + @GetMapping + @PreAuthorize("hasAnyRole('ADMIN', 'CUSTOMER', 'DRIVER')") + public List getAllDrivers() { + return driverService.getAllDrivers(); + } +} diff --git a/backend/src/main/java/com/angel/autonow/driver/DriverMapper.java b/backend/src/main/java/com/angel/autonow/driver/DriverMapper.java new file mode 100644 index 0000000..0c92aaf --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/driver/DriverMapper.java @@ -0,0 +1,32 @@ +package com.angel.autonow.driver; + +import com.angel.autonow.vehicle.VehicleEntity; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; + +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; + +@Mapper(componentModel = "spring") +public interface DriverMapper { + + @Mapping(source = "vehicles", target = "vehicleIds", qualifiedByName = "vehiclesToIds") + DriverResponseDTO toDTO(DriverEntity driver); + + @Mapping(target = "id", ignore = true) + @Mapping(target = "vehicles", ignore = true) + DriverEntity toEntity(DriverRequestDTO request); + + @Named("vehiclesToIds") + default Set vehiclesToIds(Set vehicles) { + if (vehicles == null) { + return Collections.emptySet(); + } + + return vehicles.stream() + .map(VehicleEntity::getId) + .collect(Collectors.toSet()); + } +} diff --git a/backend/src/main/java/com/angel/autonow/driver/DriverRepository.java b/backend/src/main/java/com/angel/autonow/driver/DriverRepository.java new file mode 100644 index 0000000..3dd6d62 --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/driver/DriverRepository.java @@ -0,0 +1,12 @@ +package com.angel.autonow.driver; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface DriverRepository extends JpaRepository { + + Optional findByLicenseNumber(String licenseNumber); +} diff --git a/backend/src/main/java/com/angel/autonow/driver/DriverRequestDTO.java b/backend/src/main/java/com/angel/autonow/driver/DriverRequestDTO.java new file mode 100644 index 0000000..f290b98 --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/driver/DriverRequestDTO.java @@ -0,0 +1,31 @@ +package com.angel.autonow.driver; + +import com.angel.autonow.expertise.ExpertiseType; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; + +public record DriverRequestDTO( + + @NotBlank(message = "First name is required") + String firstName, + + @NotBlank(message = "Last name is required") + String lastName, + + @NotBlank(message = "Phone number is required") + @Pattern(regexp = "^\\+?[0-9]{10,15}$", message = "Phone number must be valid") + String phoneNumber, + + @NotBlank(message = "License number is required") + String licenseNumber, + + @NotNull(message = "Expertise type is required") + ExpertiseType expertiseType, + + boolean available, + + String imageUrl +) { + +} diff --git a/backend/src/main/java/com/angel/autonow/driver/DriverResponseDTO.java b/backend/src/main/java/com/angel/autonow/driver/DriverResponseDTO.java new file mode 100644 index 0000000..960d055 --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/driver/DriverResponseDTO.java @@ -0,0 +1,21 @@ +package com.angel.autonow.driver; + +import com.angel.autonow.expertise.ExpertiseType; +import lombok.Builder; + +import java.util.Set; + +@Builder +public record DriverResponseDTO( + Long id, + String firstName, + String lastName, + String phoneNumber, + String licenseNumber, + ExpertiseType expertiseType, + boolean available, + String imageUrl, + Set vehicleIds +) { + +} diff --git a/backend/src/main/java/com/angel/autonow/driver/DriverService.java b/backend/src/main/java/com/angel/autonow/driver/DriverService.java new file mode 100644 index 0000000..76c0243 --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/driver/DriverService.java @@ -0,0 +1,35 @@ +package com.angel.autonow.driver; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class DriverService { + + private final DriverRepository driverRepository; + private final DriverMapper driverMapper; + + public Optional createDriver(DriverRequestDTO request) { + DriverEntity driver = driverMapper.toEntity(request); + DriverEntity saved = driverRepository.save(driver); + return Optional.of(driverMapper.toDTO(saved)); + } + + public Optional getDriverById(Long id) { + return driverRepository.findById(id).map(driverMapper::toDTO); + } + + public Optional getDriverByLicenseNumber(String licenseNumber) { + return driverRepository.findByLicenseNumber(licenseNumber).map(driverMapper::toDTO); + } + + public List getAllDrivers() { + return driverRepository.findAll().stream() + .map(driverMapper::toDTO) + .toList(); + } +} diff --git a/backend/src/main/java/com/angel/autonow/exception/GlobalControllerExceptionHandler.java b/backend/src/main/java/com/angel/autonow/exception/GlobalControllerExceptionHandler.java index 32b070c..8ab8db2 100644 --- a/backend/src/main/java/com/angel/autonow/exception/GlobalControllerExceptionHandler.java +++ b/backend/src/main/java/com/angel/autonow/exception/GlobalControllerExceptionHandler.java @@ -5,11 +5,15 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ProblemDetail; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.security.authorization.AuthorizationDeniedException; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.servlet.resource.NoResourceFoundException; import java.util.HashMap; import java.util.Map; @@ -68,6 +72,13 @@ public ProblemDetail handleUserException(UserException e) { return handle(e, HttpStatus.BAD_REQUEST); } + @ResponseStatus(HttpStatus.FORBIDDEN) + @ExceptionHandler(AuthorizationDeniedException.class) + public ProblemDetail handleAuthorizationDeniedException(AuthorizationDeniedException e) { + log.warn(e.getMessage(), HttpStatus.FORBIDDEN, e); + return handle(e, HttpStatus.FORBIDDEN); + } + @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(IllegalArgumentException.class) public ProblemDetail handleIllegalArgumentException(IllegalArgumentException e) { @@ -75,6 +86,26 @@ public ProblemDetail handleIllegalArgumentException(IllegalArgumentException e) return handle(e, HttpStatus.BAD_REQUEST); } + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(HttpMessageNotReadableException.class) + public ProblemDetail handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { + log.warn(e.getMessage(), HttpStatus.BAD_REQUEST, e); + return handle(e, HttpStatus.BAD_REQUEST); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public ProblemDetail handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) { + log.warn(e.getMessage(), HttpStatus.BAD_REQUEST, e); + return handle(e, HttpStatus.BAD_REQUEST); + } + + @ResponseStatus(HttpStatus.NOT_FOUND) + @ExceptionHandler(NoResourceFoundException.class) + public ProblemDetail handleNoResourceFoundException(NoResourceFoundException e) { + return handle(e, HttpStatus.NOT_FOUND); + } + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ExceptionHandler(Exception.class) public ProblemDetail handleException(Exception e) { diff --git a/backend/src/main/java/com/angel/autonow/order/OrderController.java b/backend/src/main/java/com/angel/autonow/order/OrderController.java new file mode 100644 index 0000000..ebd7c40 --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/order/OrderController.java @@ -0,0 +1,49 @@ +package com.angel.autonow.order; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/orders") +public class OrderController { + + private final OrderService orderService; + + @PostMapping + @PreAuthorize("hasAnyRole('ADMIN', 'CUSTOMER')") + public ResponseEntity createOrder(@Valid @RequestBody OrderRequestDTO request) { + return orderService.createOrder(request) + .map(order -> ResponseEntity.status(HttpStatus.CREATED).body(order)) + .orElse(ResponseEntity.badRequest().build()); + } + + @GetMapping("/{id}") + @PreAuthorize("hasAnyRole('ADMIN', 'CUSTOMER', 'DRIVER')") + public OrderResponseDTO getOrderById(@PathVariable Long id) { + return orderService.getOrderById(id).orElse(null); + } + + @GetMapping("/user/{userId}") + @PreAuthorize("hasAnyRole('ADMIN', 'CUSTOMER')") + public List getOrdersByUserId(@PathVariable Long userId) { + return orderService.getOrdersByUserId(userId); + } + + @GetMapping + @PreAuthorize("hasRole('ADMIN')") + public List getAllOrders() { + return orderService.getAllOrders(); + } +} diff --git a/backend/src/main/java/com/angel/autonow/order/OrderMapper.java b/backend/src/main/java/com/angel/autonow/order/OrderMapper.java new file mode 100644 index 0000000..916604f --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/order/OrderMapper.java @@ -0,0 +1,24 @@ +package com.angel.autonow.order; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper(componentModel = "spring") +public interface OrderMapper { + + @Mapping(source = "user.id", target = "userId") + @Mapping(source = "driver.id", target = "driverId") + @Mapping(source = "vehicle.id", target = "vehicleId") + OrderResponseDTO toDTO(OrderEntity order); + + @Mapping(target = "id", ignore = true) + @Mapping(target = "user", ignore = true) + @Mapping(target = "driver", ignore = true) + @Mapping(target = "vehicle", ignore = true) + @Mapping(target = "status", ignore = true) + @Mapping(target = "finalPrice", ignore = true) + @Mapping(target = "cancellationReason", ignore = true) + @Mapping(target = "createdAt", ignore = true) + @Mapping(target = "updatedAt", ignore = true) + OrderEntity toEntity(OrderRequestDTO request); +} diff --git a/backend/src/main/java/com/angel/autonow/order/OrderRepository.java b/backend/src/main/java/com/angel/autonow/order/OrderRepository.java new file mode 100644 index 0000000..21973b5 --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/order/OrderRepository.java @@ -0,0 +1,12 @@ +package com.angel.autonow.order; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface OrderRepository extends JpaRepository { + + List findByUserId(Long userId); +} diff --git a/backend/src/main/java/com/angel/autonow/order/OrderRequestDTO.java b/backend/src/main/java/com/angel/autonow/order/OrderRequestDTO.java new file mode 100644 index 0000000..5f6d4ad --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/order/OrderRequestDTO.java @@ -0,0 +1,50 @@ +package com.angel.autonow.order; + +import com.angel.autonow.vehicle.VehicleType; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; + +public record OrderRequestDTO( + + @NotNull(message = "User ID is required") + Long userId, + + Long driverId, + + Long vehicleId, + + @NotNull(message = "Vehicle type is required") + VehicleType vehicleType, + + @NotBlank(message = "Pickup address is required") + String pickupAddress, + + @NotNull(message = "Pickup latitude is required") + Double pickupLatitude, + + @NotNull(message = "Pickup longitude is required") + Double pickupLongitude, + + @NotBlank(message = "Dropoff address is required") + String dropoffAddress, + + @NotNull(message = "Dropoff latitude is required") + Double dropoffLatitude, + + @NotNull(message = "Dropoff longitude is required") + Double dropoffLongitude, + + @Positive(message = "Estimated price must be positive") + Double estimatedPrice, + + @Positive(message = "Distance must be positive") + Double distanceKm, + + @Positive(message = "Estimated duration must be positive") + Integer estimatedDurationMinutes, + + String specialRequirements +) { + +} diff --git a/backend/src/main/java/com/angel/autonow/order/OrderResponseDTO.java b/backend/src/main/java/com/angel/autonow/order/OrderResponseDTO.java new file mode 100644 index 0000000..ea1c6c8 --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/order/OrderResponseDTO.java @@ -0,0 +1,32 @@ +package com.angel.autonow.order; + +import com.angel.autonow.vehicle.VehicleType; +import lombok.Builder; + +import java.time.LocalDateTime; + +@Builder +public record OrderResponseDTO( + Long id, + Long userId, + Long driverId, + Long vehicleId, + VehicleType vehicleType, + String pickupAddress, + Double pickupLatitude, + Double pickupLongitude, + String dropoffAddress, + Double dropoffLatitude, + Double dropoffLongitude, + OrderStatus status, + Double estimatedPrice, + Double finalPrice, + Double distanceKm, + Integer estimatedDurationMinutes, + String specialRequirements, + String cancellationReason, + LocalDateTime createdAt, + LocalDateTime updatedAt +) { + +} diff --git a/backend/src/main/java/com/angel/autonow/order/OrderService.java b/backend/src/main/java/com/angel/autonow/order/OrderService.java new file mode 100644 index 0000000..437a6bf --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/order/OrderService.java @@ -0,0 +1,75 @@ +package com.angel.autonow.order; + +import com.angel.autonow.driver.DriverRepository; +import com.angel.autonow.user.UserEntity; +import com.angel.autonow.user.UserRepository; +import com.angel.autonow.vehicle.VehicleRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class OrderService { + + private final OrderRepository orderRepository; + private final OrderMapper orderMapper; + private final UserRepository userRepository; + private final DriverRepository driverRepository; + private final VehicleRepository vehicleRepository; + + @Transactional + public Optional createOrder(OrderRequestDTO request) { + Optional user = userRepository.findById(request.userId()); + + if (user.isEmpty()) { + return Optional.empty(); + } + + OrderEntity order = orderMapper.toEntity(request); + order.setUser(user.get()); + + if (request.driverId() != null) { + var driver = driverRepository.findById(request.driverId()); + + if (driver.isEmpty()) { + return Optional.empty(); + } + + order.setDriver(driver.get()); + } + + if (request.vehicleId() != null) { + var vehicle = vehicleRepository.findById(request.vehicleId()); + + if (vehicle.isEmpty()) { + return Optional.empty(); + } + + order.setVehicle(vehicle.get()); + } + + OrderEntity saved = orderRepository.save(order); + + return Optional.of(orderMapper.toDTO(saved)); + } + + public Optional getOrderById(Long id) { + return orderRepository.findById(id).map(orderMapper::toDTO); + } + + public List getOrdersByUserId(Long userId) { + return orderRepository.findByUserId(userId).stream() + .map(orderMapper::toDTO) + .toList(); + } + + public List getAllOrders() { + return orderRepository.findAll().stream() + .map(orderMapper::toDTO) + .toList(); + } +} diff --git a/backend/src/main/java/com/angel/autonow/payment/PaymentController.java b/backend/src/main/java/com/angel/autonow/payment/PaymentController.java new file mode 100644 index 0000000..1eaddea --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/payment/PaymentController.java @@ -0,0 +1,49 @@ +package com.angel.autonow.payment; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/payments") +public class PaymentController { + + private final PaymentService paymentService; + + @PostMapping + @PreAuthorize("hasAnyRole('ADMIN', 'CUSTOMER')") + public ResponseEntity createPayment(@Valid @RequestBody PaymentRequestDTO request) { + return paymentService.createPayment(request) + .map(payment -> ResponseEntity.status(HttpStatus.CREATED).body(payment)) + .orElse(ResponseEntity.badRequest().build()); + } + + @GetMapping("/{id}") + @PreAuthorize("hasAnyRole('ADMIN', 'CUSTOMER')") + public PaymentResponseDTO getPaymentById(@PathVariable Long id) { + return paymentService.getPaymentById(id).orElse(null); + } + + @GetMapping("/order/{orderId}") + @PreAuthorize("hasAnyRole('ADMIN', 'CUSTOMER')") + public PaymentResponseDTO getPaymentByOrderId(@PathVariable Long orderId) { + return paymentService.getPaymentByOrderId(orderId).orElse(null); + } + + @GetMapping + @PreAuthorize("hasRole('ADMIN')") + public List getAllPayments() { + return paymentService.getAllPayments(); + } +} diff --git a/backend/src/main/java/com/angel/autonow/payment/PaymentMapper.java b/backend/src/main/java/com/angel/autonow/payment/PaymentMapper.java new file mode 100644 index 0000000..76bdf3d --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/payment/PaymentMapper.java @@ -0,0 +1,18 @@ +package com.angel.autonow.payment; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper(componentModel = "spring") +public interface PaymentMapper { + + @Mapping(source = "order.id", target = "orderId") + PaymentResponseDTO toDTO(PaymentEntity payment); + + @Mapping(target = "id", ignore = true) + @Mapping(target = "order", ignore = true) + @Mapping(target = "status", ignore = true) + @Mapping(target = "createdAt", ignore = true) + @Mapping(target = "updatedAt", ignore = true) + PaymentEntity toEntity(PaymentRequestDTO request); +} diff --git a/backend/src/main/java/com/angel/autonow/payment/PaymentRepository.java b/backend/src/main/java/com/angel/autonow/payment/PaymentRepository.java new file mode 100644 index 0000000..ba35219 --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/payment/PaymentRepository.java @@ -0,0 +1,12 @@ +package com.angel.autonow.payment; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface PaymentRepository extends JpaRepository { + + Optional findByOrderId(Long orderId); +} diff --git a/backend/src/main/java/com/angel/autonow/payment/PaymentRequestDTO.java b/backend/src/main/java/com/angel/autonow/payment/PaymentRequestDTO.java new file mode 100644 index 0000000..6fb14ab --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/payment/PaymentRequestDTO.java @@ -0,0 +1,23 @@ +package com.angel.autonow.payment; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; + +public record PaymentRequestDTO( + + @NotNull(message = "Order ID is required") + Long orderId, + + @NotNull(message = "Amount is required") + @Positive(message = "Amount must be positive") + Double amount, + + @NotNull(message = "Payment method is required") + PaymentMethod paymentMethod, + + String transactionId, + + String currency +) { + +} diff --git a/backend/src/main/java/com/angel/autonow/payment/PaymentResponseDTO.java b/backend/src/main/java/com/angel/autonow/payment/PaymentResponseDTO.java new file mode 100644 index 0000000..fb634db --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/payment/PaymentResponseDTO.java @@ -0,0 +1,20 @@ +package com.angel.autonow.payment; + +import lombok.Builder; + +import java.time.LocalDateTime; + +@Builder +public record PaymentResponseDTO( + Long id, + Long orderId, + Double amount, + PaymentMethod paymentMethod, + PaymentStatus status, + String transactionId, + String currency, + LocalDateTime createdAt, + LocalDateTime updatedAt +) { + +} diff --git a/backend/src/main/java/com/angel/autonow/payment/PaymentService.java b/backend/src/main/java/com/angel/autonow/payment/PaymentService.java new file mode 100644 index 0000000..ebcd2ee --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/payment/PaymentService.java @@ -0,0 +1,54 @@ +package com.angel.autonow.payment; + +import com.angel.autonow.order.OrderEntity; +import com.angel.autonow.order.OrderRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class PaymentService { + + private final PaymentRepository paymentRepository; + private final PaymentMapper paymentMapper; + private final OrderRepository orderRepository; + + @Transactional + public Optional createPayment(PaymentRequestDTO request) { + Optional order = orderRepository.findById(request.orderId()); + + if (order.isEmpty()) { + return Optional.empty(); + } + + if (paymentRepository.findByOrderId(request.orderId()).isPresent()) { + return Optional.empty(); + } + + PaymentEntity payment = paymentMapper.toEntity(request); + payment.setOrder(order.get()); + PaymentEntity saved = paymentRepository.save(payment); + + return Optional.of(paymentMapper.toDTO(saved)); + } + + public Optional getPaymentById(Long id) { + return paymentRepository.findById(id) + .map(paymentMapper::toDTO); + } + + public Optional getPaymentByOrderId(Long orderId) { + return paymentRepository.findByOrderId(orderId) + .map(paymentMapper::toDTO); + } + + public List getAllPayments() { + return paymentRepository.findAll().stream() + .map(paymentMapper::toDTO) + .toList(); + } +} diff --git a/backend/src/main/java/com/angel/autonow/rating/RatingController.java b/backend/src/main/java/com/angel/autonow/rating/RatingController.java new file mode 100644 index 0000000..a9f31ef --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/rating/RatingController.java @@ -0,0 +1,49 @@ +package com.angel.autonow.rating; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/ratings") +public class RatingController { + + private final RatingService ratingService; + + @PostMapping + @PreAuthorize("hasAnyRole('ADMIN', 'CUSTOMER')") + public ResponseEntity createRating(@Valid @RequestBody RatingRequestDTO request) { + return ratingService.createRating(request) + .map(rating -> ResponseEntity.status(HttpStatus.CREATED).body(rating)) + .orElse(ResponseEntity.badRequest().build()); + } + + @GetMapping("/{id}") + @PreAuthorize("hasAnyRole('ADMIN', 'CUSTOMER', 'DRIVER')") + public RatingResponseDTO getRatingById(@PathVariable Long id) { + return ratingService.getRatingById(id).orElse(null); + } + + @GetMapping("/order/{orderId}") + @PreAuthorize("hasAnyRole('ADMIN', 'CUSTOMER', 'DRIVER')") + public RatingResponseDTO getRatingByOrderId(@PathVariable Long orderId) { + return ratingService.getRatingByOrderId(orderId).orElse(null); + } + + @GetMapping + @PreAuthorize("hasAnyRole('ADMIN', 'CUSTOMER', 'DRIVER')") + public List getAllRatings() { + return ratingService.getAllRatings(); + } +} diff --git a/backend/src/main/java/com/angel/autonow/rating/RatingMapper.java b/backend/src/main/java/com/angel/autonow/rating/RatingMapper.java new file mode 100644 index 0000000..a8e34df --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/rating/RatingMapper.java @@ -0,0 +1,16 @@ +package com.angel.autonow.rating; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper(componentModel = "spring") +public interface RatingMapper { + + @Mapping(source = "order.id", target = "orderId") + RatingResponseDTO toDTO(RatingEntity rating); + + @Mapping(target = "id", ignore = true) + @Mapping(target = "order", ignore = true) + @Mapping(target = "createdAt", ignore = true) + RatingEntity toEntity(RatingRequestDTO request); +} diff --git a/backend/src/main/java/com/angel/autonow/rating/RatingRepository.java b/backend/src/main/java/com/angel/autonow/rating/RatingRepository.java new file mode 100644 index 0000000..cde2d98 --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/rating/RatingRepository.java @@ -0,0 +1,12 @@ +package com.angel.autonow.rating; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface RatingRepository extends JpaRepository { + + Optional findByOrderId(Long orderId); +} diff --git a/backend/src/main/java/com/angel/autonow/rating/RatingRequestDTO.java b/backend/src/main/java/com/angel/autonow/rating/RatingRequestDTO.java new file mode 100644 index 0000000..3872d20 --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/rating/RatingRequestDTO.java @@ -0,0 +1,22 @@ +package com.angel.autonow.rating; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +public record RatingRequestDTO( + + @NotNull(message = "Order ID is required") + Long orderId, + + @NotNull(message = "Rating is required") + @Min(value = 1, message = "Rating must be at least 1") + @Max(value = 5, message = "Rating must be at most 5") + Integer rating, + + @Size(max = 1000, message = "Comment must not exceed 1000 characters") + String comment +) { + +} diff --git a/backend/src/main/java/com/angel/autonow/rating/RatingResponseDTO.java b/backend/src/main/java/com/angel/autonow/rating/RatingResponseDTO.java new file mode 100644 index 0000000..0cf1775 --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/rating/RatingResponseDTO.java @@ -0,0 +1,16 @@ +package com.angel.autonow.rating; + +import lombok.Builder; + +import java.time.LocalDateTime; + +@Builder +public record RatingResponseDTO( + Long id, + Long orderId, + Integer rating, + String comment, + LocalDateTime createdAt +) { + +} diff --git a/backend/src/main/java/com/angel/autonow/rating/RatingService.java b/backend/src/main/java/com/angel/autonow/rating/RatingService.java new file mode 100644 index 0000000..3e0838f --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/rating/RatingService.java @@ -0,0 +1,48 @@ +package com.angel.autonow.rating; + +import com.angel.autonow.order.OrderEntity; +import com.angel.autonow.order.OrderRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class RatingService { + + private final RatingRepository ratingRepository; + private final RatingMapper ratingMapper; + private final OrderRepository orderRepository; + + @Transactional + public Optional createRating(RatingRequestDTO request) { + Optional order = orderRepository.findById(request.orderId()); + + if (order.isEmpty()) { + return Optional.empty(); + } + + RatingEntity rating = ratingMapper.toEntity(request); + rating.setOrder(order.get()); + RatingEntity saved = ratingRepository.save(rating); + + return Optional.of(ratingMapper.toDTO(saved)); + } + + public Optional getRatingById(Long id) { + return ratingRepository.findById(id).map(ratingMapper::toDTO); + } + + public Optional getRatingByOrderId(Long orderId) { + return ratingRepository.findByOrderId(orderId).map(ratingMapper::toDTO); + } + + public List getAllRatings() { + return ratingRepository.findAll().stream() + .map(ratingMapper::toDTO) + .toList(); + } +} diff --git a/backend/src/main/java/com/angel/autonow/user/UserController.java b/backend/src/main/java/com/angel/autonow/user/UserController.java index 79591e7..16932cc 100644 --- a/backend/src/main/java/com/angel/autonow/user/UserController.java +++ b/backend/src/main/java/com/angel/autonow/user/UserController.java @@ -3,7 +3,6 @@ import com.angel.autonow.security.jwt.JwtResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -17,14 +16,14 @@ public class UserController { private final UserService userService; @PostMapping("/register") - public ResponseEntity register(@Valid @RequestBody UserRequestDTO request) { + public JwtResponse register(@Valid @RequestBody UserRequestDTO request) { String token = userService.register(request); - return ResponseEntity.ok(new JwtResponse(token)); + return new JwtResponse(token); } @PostMapping("/login") - public ResponseEntity login(@Valid @RequestBody UserRequestDTO request) { + public JwtResponse login(@Valid @RequestBody UserRequestDTO request) { String token = userService.login(request); - return ResponseEntity.ok(new JwtResponse(token)); + return new JwtResponse(token); } } diff --git a/backend/src/main/java/com/angel/autonow/user/UserRequestDTO.java b/backend/src/main/java/com/angel/autonow/user/UserRequestDTO.java index 7262220..1dc7a6d 100644 --- a/backend/src/main/java/com/angel/autonow/user/UserRequestDTO.java +++ b/backend/src/main/java/com/angel/autonow/user/UserRequestDTO.java @@ -2,18 +2,15 @@ import jakarta.validation.constraints.Email; import jakarta.validation.constraints.Pattern; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -@Getter -@RequiredArgsConstructor -public class UserRequestDTO { +public record UserRequestDTO( - private static final String PASSWORD_REGEX = "^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,}$"; + @Email(message = "Email must be valid") + String email, - @Email - private final String email; + @Pattern(regexp = "^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,}$", + message = "Password must have at least 8 characters, one digit, one lowercase and one uppercase letter") + String password +) { - @Pattern(regexp = PASSWORD_REGEX) - private final String password; } diff --git a/backend/src/main/java/com/angel/autonow/user/UserService.java b/backend/src/main/java/com/angel/autonow/user/UserService.java index c4b73f5..648e424 100644 --- a/backend/src/main/java/com/angel/autonow/user/UserService.java +++ b/backend/src/main/java/com/angel/autonow/user/UserService.java @@ -1,6 +1,7 @@ package com.angel.autonow.user; import com.angel.autonow.security.jwt.JwtService; +import com.angel.autonow.user.role.Role; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.crypto.password.PasswordEncoder; @@ -13,17 +14,15 @@ @RequiredArgsConstructor public class UserService { - private static final String USER_READ_AUTHORITY = "user.read"; - private final UserRepository userRepository; private final JwtService jwtService; private final PasswordEncoder passwordEncoder; - private final Set defaultAuthorities = Set.of(USER_READ_AUTHORITY); + private final Set defaultAuthorities = Set.of(Role.CUSTOMER.getAuthority()); public String register(UserRequestDTO request) { - String email = request.getEmail(); - String password = request.getPassword(); + String email = request.email(); + String password = request.password(); if (userRepository.findByEmail(email).isPresent()) { throw new UserException("Account with this email already exists."); @@ -41,8 +40,8 @@ public String register(UserRequestDTO request) { } public String login(UserRequestDTO request) { - String email = request.getEmail(); - String password = request.getPassword(); + String email = request.email(); + String password = request.password(); UserEntity user = userRepository.findByEmail(email).orElseThrow(() -> new UserException("Invalid credentials")); diff --git a/backend/src/main/java/com/angel/autonow/user/role/Role.java b/backend/src/main/java/com/angel/autonow/user/role/Role.java index d38b638..b2c23b2 100644 --- a/backend/src/main/java/com/angel/autonow/user/role/Role.java +++ b/backend/src/main/java/com/angel/autonow/user/role/Role.java @@ -4,5 +4,11 @@ public enum Role { ADMIN, DRIVER, CUSTOMER, - GUEST + GUEST; + + private static final String ROLE_PREFIX = "ROLE_"; + + public String getAuthority() { + return ROLE_PREFIX + name(); + } } diff --git a/backend/src/main/java/com/angel/autonow/vehicle/VehicleController.java b/backend/src/main/java/com/angel/autonow/vehicle/VehicleController.java index d8508d1..8dd4986 100644 --- a/backend/src/main/java/com/angel/autonow/vehicle/VehicleController.java +++ b/backend/src/main/java/com/angel/autonow/vehicle/VehicleController.java @@ -3,23 +3,40 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + @RestController @RequiredArgsConstructor -@RequestMapping("/api/vehicle") +@RequestMapping("/api/vehicles") public class VehicleController { private final VehicleService vehicleService; - @PostMapping("/create") - public ResponseEntity createVehicle(@RequestBody @Valid VehicleDTO vehicleDTO) { - return vehicleService.createVehicle(vehicleDTO) - .map(vehicle -> ResponseEntity.status(HttpStatus.CREATED).body(vehicle)) - .orElse(ResponseEntity.badRequest().build()); + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + @PreAuthorize("hasRole('ADMIN')") + public VehicleResponseDTO createVehicle(@Valid @RequestBody VehicleRequestDTO request) { + return vehicleService.createVehicle(request); + } + + @GetMapping("/{id}") + @PreAuthorize("hasAnyRole('ADMIN', 'CUSTOMER', 'DRIVER', 'GUEST')") + public VehicleResponseDTO getVehicleById(@PathVariable Long id) { + return vehicleService.getVehicleById(id).orElse(null); + } + + @GetMapping + @PreAuthorize("hasAnyRole('ADMIN', 'CUSTOMER', 'DRIVER', 'GUEST')") + public List getAllVehicles() { + return vehicleService.getAllVehicles(); } } diff --git a/backend/src/main/java/com/angel/autonow/vehicle/VehicleDTO.java b/backend/src/main/java/com/angel/autonow/vehicle/VehicleDTO.java deleted file mode 100644 index 29283b3..0000000 --- a/backend/src/main/java/com/angel/autonow/vehicle/VehicleDTO.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.angel.autonow.vehicle; - -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Positive; - -public record VehicleDTO( - - @NotBlank(message = "Brand is mandatory") - @NotNull(message = "Invalid brand: brand is null") - String brand, - - @NotBlank(message = "Model is mandatory") - @NotNull(message = "Invalid model: model is null") - String model, - - String imageURL, - - boolean airConditioning, - - @Positive - Integer numberOfSeats, - - @Positive - double trunkCapacity, - - @Enumerated(EnumType.STRING) - VehicleType vehicleType -) { - -} diff --git a/backend/src/main/java/com/angel/autonow/vehicle/VehicleMapper.java b/backend/src/main/java/com/angel/autonow/vehicle/VehicleMapper.java index f5ff064..f20a3f6 100644 --- a/backend/src/main/java/com/angel/autonow/vehicle/VehicleMapper.java +++ b/backend/src/main/java/com/angel/autonow/vehicle/VehicleMapper.java @@ -1,10 +1,13 @@ package com.angel.autonow.vehicle; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; @Mapper(componentModel = "spring") public interface VehicleMapper { - VehicleDTO toDTO(VehicleEntity vehicle); - VehicleEntity toEntity(VehicleDTO vehicleDTO); + VehicleResponseDTO toDTO(VehicleEntity vehicle); + + @Mapping(target = "id", ignore = true) + VehicleEntity toEntity(VehicleRequestDTO request); } diff --git a/backend/src/main/java/com/angel/autonow/vehicle/VehicleRequestDTO.java b/backend/src/main/java/com/angel/autonow/vehicle/VehicleRequestDTO.java new file mode 100644 index 0000000..65b32bc --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/vehicle/VehicleRequestDTO.java @@ -0,0 +1,29 @@ +package com.angel.autonow.vehicle; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; + +public record VehicleRequestDTO( + + @NotBlank(message = "Brand is required") + String brand, + + @NotBlank(message = "Model is required") + String model, + + String imageURL, + + boolean airConditioning, + + @Positive(message = "Number of seats must be positive") + Integer numberOfSeats, + + @Positive(message = "Trunk capacity must be positive") + Double trunkCapacity, + + @NotNull(message = "Vehicle type is required") + VehicleType vehicleType +) { + +} diff --git a/backend/src/main/java/com/angel/autonow/vehicle/VehicleResponseDTO.java b/backend/src/main/java/com/angel/autonow/vehicle/VehicleResponseDTO.java new file mode 100644 index 0000000..32ced6c --- /dev/null +++ b/backend/src/main/java/com/angel/autonow/vehicle/VehicleResponseDTO.java @@ -0,0 +1,17 @@ +package com.angel.autonow.vehicle; + +import lombok.Builder; + +@Builder +public record VehicleResponseDTO( + Long id, + String brand, + String model, + String imageURL, + boolean airConditioning, + Integer numberOfSeats, + Double trunkCapacity, + VehicleType vehicleType +) { + +} diff --git a/backend/src/main/java/com/angel/autonow/vehicle/VehicleService.java b/backend/src/main/java/com/angel/autonow/vehicle/VehicleService.java index 1406fa0..f19215e 100644 --- a/backend/src/main/java/com/angel/autonow/vehicle/VehicleService.java +++ b/backend/src/main/java/com/angel/autonow/vehicle/VehicleService.java @@ -3,6 +3,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.util.List; import java.util.Optional; @Service @@ -12,14 +13,20 @@ public class VehicleService { private final VehicleRepository vehicleRepository; private final VehicleMapper vehicleMapper; - public Optional createVehicle(VehicleDTO vehicleDTO) { - if (vehicleDTO == null) { - throw new IllegalArgumentException("Vehicle cannot be null"); - } + public VehicleResponseDTO createVehicle(VehicleRequestDTO request) { + VehicleEntity vehicle = vehicleMapper.toEntity(request); + VehicleEntity saved = vehicleRepository.save(vehicle); + return vehicleMapper.toDTO(saved); + } - VehicleEntity vehicle = vehicleMapper.toEntity(vehicleDTO); - vehicleRepository.save(vehicle); + public Optional getVehicleById(Long id) { + return vehicleRepository.findById(id) + .map(vehicleMapper::toDTO); + } - return Optional.of(vehicleMapper.toDTO(vehicle)); + public List getAllVehicles() { + return vehicleRepository.findAll().stream() + .map(vehicleMapper::toDTO) + .toList(); } } diff --git a/backend/src/main/resources/data.sql b/backend/src/main/resources/data.sql index 83873ba..ce4da7e 100644 --- a/backend/src/main/resources/data.sql +++ b/backend/src/main/resources/data.sql @@ -8,11 +8,11 @@ INSERT INTO users (email, password) VALUES INSERT INTO user_authorities (user_id, authorities) SELECT id, 'ROLE_ADMIN' FROM users WHERE email = 'admin@autonow.com'; INSERT INTO user_authorities (user_id, authorities) -SELECT id, 'ROLE_USER' FROM users WHERE email = 'admin@autonow.com'; +SELECT id, 'ROLE_CUSTOMER' FROM users WHERE email = 'admin@autonow.com'; INSERT INTO user_authorities (user_id, authorities) -SELECT id, 'ROLE_USER' FROM users WHERE email = 'john.doe@example.com'; +SELECT id, 'ROLE_CUSTOMER' FROM users WHERE email = 'john.doe@example.com'; INSERT INTO user_authorities (user_id, authorities) -SELECT id, 'ROLE_USER' FROM users WHERE email = 'jane.smith@example.com'; +SELECT id, 'ROLE_CUSTOMER' FROM users WHERE email = 'jane.smith@example.com'; -- Vehicles INSERT INTO vehicle (brand, model, image_url, air_conditioning, number_of_seats, trunk_capacity, vehicle_type) VALUES diff --git a/backend/src/test/java/com/angel/autonow/data/TestData.java b/backend/src/test/java/com/angel/autonow/data/TestData.java new file mode 100644 index 0000000..92cf052 --- /dev/null +++ b/backend/src/test/java/com/angel/autonow/data/TestData.java @@ -0,0 +1,231 @@ +package com.angel.autonow.data; + +import com.angel.autonow.driver.DriverEntity; +import com.angel.autonow.driver.DriverRequestDTO; +import com.angel.autonow.driver.DriverResponseDTO; +import com.angel.autonow.expertise.ExpertiseType; +import com.angel.autonow.order.OrderEntity; +import com.angel.autonow.order.OrderRequestDTO; +import com.angel.autonow.order.OrderResponseDTO; +import com.angel.autonow.order.OrderStatus; +import com.angel.autonow.payment.PaymentEntity; +import com.angel.autonow.payment.PaymentMethod; +import com.angel.autonow.payment.PaymentRequestDTO; +import com.angel.autonow.payment.PaymentResponseDTO; +import com.angel.autonow.payment.PaymentStatus; +import com.angel.autonow.rating.RatingEntity; +import com.angel.autonow.rating.RatingRequestDTO; +import com.angel.autonow.rating.RatingResponseDTO; +import com.angel.autonow.user.UserEntity; +import com.angel.autonow.user.role.Role; +import com.angel.autonow.vehicle.VehicleEntity; +import com.angel.autonow.vehicle.VehicleRequestDTO; +import com.angel.autonow.vehicle.VehicleResponseDTO; +import com.angel.autonow.vehicle.VehicleType; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.test.web.servlet.request.RequestPostProcessor; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.Set; + +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt; + +public final class TestData { + + public static final long NON_EXISTENT_ID = 999L; + public static final String DEFAULT_CURRENCY = "EUR"; + public static final double DEFAULT_AMOUNT = 16.00; + public static final String DEFAULT_PICKUP_ADDRESS = "123 Main St"; + public static final String DEFAULT_DROPOFF_ADDRESS = "456 Oak Ave"; + public static final double DEFAULT_PICKUP_LAT = 42.6977; + public static final double DEFAULT_PICKUP_LNG = 23.3219; + public static final double DEFAULT_DROPOFF_LAT = 42.7105; + public static final double DEFAULT_DROPOFF_LNG = 23.3238; + + private TestData() { + } + + public static RequestPostProcessor customerJwt() { + return jwt().authorities(new SimpleGrantedAuthority(Role.CUSTOMER.getAuthority())); + } + + public static RequestPostProcessor adminJwt() { + return jwt().authorities(new SimpleGrantedAuthority(Role.ADMIN.getAuthority())); + } + + public static RequestPostProcessor driverJwt() { + return jwt().authorities(new SimpleGrantedAuthority(Role.DRIVER.getAuthority())); + } + + public static RequestPostProcessor guestJwt() { + return jwt().authorities(new SimpleGrantedAuthority(Role.GUEST.getAuthority())); + } + + public static UserEntity createUserEntity() { + return UserEntity.builder() + .email("test@example.com") + .password("encodedPassword") + .authorities(Set.of(Role.CUSTOMER.getAuthority())) + .build(); + } + + public static OrderEntity createOrderEntity(UserEntity user) { + return OrderEntity.builder() + .user(user) + .vehicleType(VehicleType.TAXI) + .pickupAddress(DEFAULT_PICKUP_ADDRESS) + .pickupLatitude(DEFAULT_PICKUP_LAT) + .pickupLongitude(DEFAULT_PICKUP_LNG) + .dropoffAddress(DEFAULT_DROPOFF_ADDRESS) + .dropoffLatitude(DEFAULT_DROPOFF_LAT) + .dropoffLongitude(DEFAULT_DROPOFF_LNG) + .status(OrderStatus.COMPLETED) + .build(); + } + + public static OrderRequestDTO createOrderRequest(Long userId) { + return new OrderRequestDTO( + userId, + null, + null, + VehicleType.TAXI, + DEFAULT_PICKUP_ADDRESS, + DEFAULT_PICKUP_LAT, + DEFAULT_PICKUP_LNG, + DEFAULT_DROPOFF_ADDRESS, + DEFAULT_DROPOFF_LAT, + DEFAULT_DROPOFF_LNG, + 15.50, + 5.2, + 15, + null + ); + } + + public static OrderResponseDTO createOrderResponse(Long id, Long userId, OrderStatus status, LocalDateTime createdAt) { + return OrderResponseDTO.builder() + .id(id) + .userId(userId) + .vehicleType(VehicleType.TAXI) + .pickupAddress(DEFAULT_PICKUP_ADDRESS) + .pickupLatitude(DEFAULT_PICKUP_LAT) + .pickupLongitude(DEFAULT_PICKUP_LNG) + .dropoffAddress(DEFAULT_DROPOFF_ADDRESS) + .dropoffLatitude(DEFAULT_DROPOFF_LAT) + .dropoffLongitude(DEFAULT_DROPOFF_LNG) + .status(status) + .estimatedPrice(15.50) + .distanceKm(5.2) + .estimatedDurationMinutes(15) + .createdAt(createdAt) + .build(); + } + + public static VehicleRequestDTO createVehicleRequest() { + return new VehicleRequestDTO("Toyota", "Camry", null, true, 5, 450.0, VehicleType.TAXI); + } + + public static VehicleEntity createVehicleEntity() { + return VehicleEntity.builder() + .brand("Toyota") + .model("Camry") + .airConditioning(true) + .numberOfSeats(5) + .trunkCapacity(450.0) + .vehicleType(VehicleType.TAXI) + .build(); + } + + public static VehicleResponseDTO createVehicleResponse(Long id) { + return VehicleResponseDTO.builder() + .id(id) + .brand("Toyota") + .model("Camry") + .airConditioning(true) + .numberOfSeats(5) + .trunkCapacity(450.0) + .vehicleType(VehicleType.TAXI) + .build(); + } + + public static DriverRequestDTO createDriverRequest() { + return new DriverRequestDTO("Michael", "Johnson", "+1234567890", "DL-TEST-001", ExpertiseType.B, true, null); + } + + public static DriverEntity createDriverEntity() { + return DriverEntity.builder() + .firstName("Michael") + .lastName("Johnson") + .phoneNumber("+1234567890") + .licenseNumber("DL-TEST-001") + .expertiseType(ExpertiseType.B) + .available(true) + .build(); + } + + public static DriverResponseDTO createDriverResponse(Long id) { + return DriverResponseDTO.builder() + .id(id) + .firstName("Michael") + .lastName("Johnson") + .phoneNumber("+1234567890") + .licenseNumber("DL-TEST-001") + .expertiseType(ExpertiseType.B) + .available(true) + .vehicleIds(Collections.emptySet()) + .build(); + } + + public static PaymentRequestDTO createPaymentRequest(Long orderId) { + return new PaymentRequestDTO(orderId, DEFAULT_AMOUNT, PaymentMethod.CREDIT_CARD, "TXN-TEST-001", DEFAULT_CURRENCY); + } + + public static PaymentEntity createPaymentEntity(OrderEntity order, double amount, PaymentMethod method, PaymentStatus status) { + return PaymentEntity.builder() + .order(order) + .amount(amount) + .paymentMethod(method) + .status(status) + .currency(DEFAULT_CURRENCY) + .build(); + } + + public static PaymentResponseDTO createPaymentResponse(Long id, Long orderId, double amount, PaymentMethod method, PaymentStatus status, LocalDateTime createdAt) { + return PaymentResponseDTO.builder() + .id(id) + .orderId(orderId) + .amount(amount) + .paymentMethod(method) + .status(status) + .currency(DEFAULT_CURRENCY) + .createdAt(createdAt) + .build(); + } + + public static RatingEntity createRatingEntity(OrderEntity order, int rating, String comment) { + return RatingEntity.builder() + .order(order) + .rating(rating) + .comment(comment) + .build(); + } + + public static RatingRequestDTO createRatingRequest(Long orderId) { + return new RatingRequestDTO(orderId, 5, "Excellent service!"); + } + + public static RatingRequestDTO createRatingRequest(Long orderId, int rating, String comment) { + return new RatingRequestDTO(orderId, rating, comment); + } + + public static RatingResponseDTO createRatingResponse(Long id, Long orderId, int rating, String comment, LocalDateTime createdAt) { + return RatingResponseDTO.builder() + .id(id) + .orderId(orderId) + .rating(rating) + .comment(comment) + .createdAt(createdAt) + .build(); + } +} diff --git a/backend/src/test/java/com/angel/autonow/driver/DriverControllerIT.java b/backend/src/test/java/com/angel/autonow/driver/DriverControllerIT.java new file mode 100644 index 0000000..e0f7fcb --- /dev/null +++ b/backend/src/test/java/com/angel/autonow/driver/DriverControllerIT.java @@ -0,0 +1,146 @@ +package com.angel.autonow.driver; + +import com.angel.autonow.data.TestData; +import tools.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import static com.angel.autonow.data.TestData.NON_EXISTENT_ID; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@Transactional +@SpringBootTest +@AutoConfigureMockMvc +@TestPropertySource(locations = "classpath:application-test.properties") +class DriverControllerIT { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private DriverRepository driverRepository; + + @Test + void createDriver_asAdmin() throws Exception { + var request = TestData.createDriverRequest(); + + mockMvc.perform(post("/api/drivers") + .with(TestData.adminJwt()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isCreated()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.id").exists()) + .andExpect(jsonPath("$.firstName").value("Michael")) + .andExpect(jsonPath("$.lastName").value("Johnson")) + .andExpect(jsonPath("$.licenseNumber").value("DL-TEST-001")) + .andExpect(jsonPath("$.expertiseType").value("B")); + } + + @Test + void createDriver_asCustomer_returnsForbidden() throws Exception { + var request = TestData.createDriverRequest(); + + mockMvc.perform(post("/api/drivers") + .with(TestData.customerJwt()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isForbidden()); + } + + @Test + void createDriver_invalidInput_returnsBadRequest() throws Exception { + var invalidRequest = new DriverRequestDTO(null, null, null, null, null, false, null); + + mockMvc.perform(post("/api/drivers") + .with(TestData.adminJwt()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(invalidRequest))) + .andExpect(status().isBadRequest()); + } + + @Test + void createDriver_withoutAuth_returnsUnauthorized() throws Exception { + var request = TestData.createDriverRequest(); + + mockMvc.perform(post("/api/drivers") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isUnauthorized()); + } + + @Test + void getDriverById() throws Exception { + var driver = TestData.createDriverEntity(); + driverRepository.save(driver); + + mockMvc.perform(get("/api/drivers/{id}", driver.getId()) + .with(TestData.customerJwt())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.firstName").value("Michael")); + } + + @Test + void getDriverById_notFound_returnsOkEmpty() throws Exception { + mockMvc.perform(get("/api/drivers/{id}", NON_EXISTENT_ID) + .with(TestData.customerJwt())) + .andExpect(status().isOk()) + .andExpect(content().string("")); + } + + @Test + void getDriverByLicenseNumber() throws Exception { + var driver = TestData.createDriverEntity(); + driverRepository.save(driver); + + mockMvc.perform(get("/api/drivers/license/{licenseNumber}", "DL-TEST-001") + .with(TestData.adminJwt())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.licenseNumber").value("DL-TEST-001")); + } + + @Test + void getDriverByLicenseNumber_asCustomer_returnsForbidden() throws Exception { + mockMvc.perform(get("/api/drivers/license/{licenseNumber}", "DL-TEST-001") + .with(TestData.customerJwt())) + .andExpect(status().isForbidden()); + } + + @Test + void getAllDrivers() throws Exception { + var driver = TestData.createDriverEntity(); + driverRepository.save(driver); + + mockMvc.perform(get("/api/drivers") + .with(TestData.customerJwt())) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.length()").value(1)); + } + + @Test + void getAllDrivers_noEntries_returnsEmptyList() throws Exception { + mockMvc.perform(get("/api/drivers") + .with(TestData.customerJwt())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$").isEmpty()); + } + + @Test + void getAllDrivers_withoutAuth_returnsUnauthorized() throws Exception { + mockMvc.perform(get("/api/drivers")) + .andExpect(status().isUnauthorized()); + } +} diff --git a/backend/src/test/java/com/angel/autonow/driver/DriverServiceTest.java b/backend/src/test/java/com/angel/autonow/driver/DriverServiceTest.java new file mode 100644 index 0000000..cfcc385 --- /dev/null +++ b/backend/src/test/java/com/angel/autonow/driver/DriverServiceTest.java @@ -0,0 +1,127 @@ +package com.angel.autonow.driver; + +import com.angel.autonow.data.TestData; +import com.angel.autonow.expertise.ExpertiseType; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; +import java.util.Optional; + +import static com.angel.autonow.data.TestData.NON_EXISTENT_ID; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class DriverServiceTest { + + @Mock + private DriverRepository driverRepository; + + @Mock + private DriverMapper driverMapper; + + @InjectMocks + private DriverService driverService; + + @Test + void createDriver_returnDriverResponse() { + DriverRequestDTO request = TestData.createDriverRequest(); + + DriverEntity entity = DriverEntity.builder() + .firstName("Michael") + .lastName("Johnson") + .build(); + + DriverEntity saved = DriverEntity.builder() + .id(1L) + .firstName("Michael") + .lastName("Johnson") + .build(); + + DriverResponseDTO response = TestData.createDriverResponse(1L); + + when(driverMapper.toEntity(request)).thenReturn(entity); + when(driverRepository.save(entity)).thenReturn(saved); + when(driverMapper.toDTO(saved)).thenReturn(response); + + var result = driverService.createDriver(request); + + assertTrue(result.isPresent()); + assertEquals(1L, result.get().id()); + assertEquals("Michael", result.get().firstName()); + } + + @Test + void getDriverById_returnDriverResponse() { + DriverEntity entity = DriverEntity.builder().id(1L).firstName("Michael").build(); + DriverResponseDTO response = TestData.createDriverResponse(1L); + + when(driverRepository.findById(1L)).thenReturn(Optional.of(entity)); + when(driverMapper.toDTO(entity)).thenReturn(response); + + var result = driverService.getDriverById(1L); + + assertTrue(result.isPresent()); + assertEquals("Michael", result.get().firstName()); + } + + @Test + void getDriverById_notFound_returnsEmpty() { + when(driverRepository.findById(NON_EXISTENT_ID)).thenReturn(Optional.empty()); + var result = driverService.getDriverById(NON_EXISTENT_ID); + assertTrue(result.isEmpty()); + } + + @Test + void getDriverByLicenseNumber_returnDriverResponse() { + DriverEntity entity = DriverEntity.builder().id(1L).licenseNumber("DL-TEST-001").build(); + DriverResponseDTO response = TestData.createDriverResponse(1L); + + when(driverRepository.findByLicenseNumber("DL-TEST-001")).thenReturn(Optional.of(entity)); + when(driverMapper.toDTO(entity)).thenReturn(response); + + var result = driverService.getDriverByLicenseNumber("DL-TEST-001"); + + assertTrue(result.isPresent()); + assertEquals("DL-TEST-001", result.get().licenseNumber()); + } + + @Test + void getDriverByLicenseNumber_notFound_returnsEmpty() { + when(driverRepository.findByLicenseNumber("INVALID")).thenReturn(Optional.empty()); + var result = driverService.getDriverByLicenseNumber("INVALID"); + assertTrue(result.isEmpty()); + } + + @Test + void getAllDrivers_returnList() { + DriverEntity firstDriver = DriverEntity.builder().id(1L).firstName("Michael").build(); + DriverEntity secondDriver = DriverEntity.builder().id(2L).firstName("Jane").build(); + DriverResponseDTO firstResponse = TestData.createDriverResponse(1L); + DriverResponseDTO secondResponse = DriverResponseDTO.builder() + .id(2L).firstName("Jane").lastName("Smith") + .phoneNumber("+1234567891").licenseNumber("DL-002") + .expertiseType(ExpertiseType.C).available(true) + .vehicleIds(java.util.Collections.emptySet()) + .build(); + + when(driverRepository.findAll()).thenReturn(List.of(firstDriver, secondDriver)); + when(driverMapper.toDTO(firstDriver)).thenReturn(firstResponse); + when(driverMapper.toDTO(secondDriver)).thenReturn(secondResponse); + + var result = driverService.getAllDrivers(); + + assertEquals(2, result.size()); + } + + @Test + void getAllDrivers_emptyList() { + when(driverRepository.findAll()).thenReturn(List.of()); + var result = driverService.getAllDrivers(); + assertTrue(result.isEmpty()); + } +} diff --git a/backend/src/test/java/com/angel/autonow/order/OrderControllerIT.java b/backend/src/test/java/com/angel/autonow/order/OrderControllerIT.java new file mode 100644 index 0000000..909cb31 --- /dev/null +++ b/backend/src/test/java/com/angel/autonow/order/OrderControllerIT.java @@ -0,0 +1,172 @@ +package com.angel.autonow.order; + +import com.angel.autonow.data.TestData; +import com.angel.autonow.user.UserEntity; +import com.angel.autonow.user.UserRepository; +import tools.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import static com.angel.autonow.data.TestData.NON_EXISTENT_ID; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@Transactional +@SpringBootTest +@AutoConfigureMockMvc +@TestPropertySource(locations = "classpath:application-test.properties") +class OrderControllerIT { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private OrderRepository orderRepository; + + @Autowired + private UserRepository userRepository; + + private UserEntity user; + + @BeforeEach + void setUp() { + user = TestData.createUserEntity(); + userRepository.save(user); + } + + @Test + void createOrder() throws Exception { + var request = TestData.createOrderRequest(user.getId()); + + mockMvc.perform(post("/api/orders") + .with(TestData.customerJwt()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isCreated()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.id").exists()) + .andExpect(jsonPath("$.userId").value(user.getId())) + .andExpect(jsonPath("$.vehicleType").value("TAXI")) + .andExpect(jsonPath("$.status").value("CREATED")) + .andExpect(jsonPath("$.pickupAddress").value("123 Main St")); + } + + @Test + void createOrder_userNotFound_returnsBadRequest() throws Exception { + var request = TestData.createOrderRequest(NON_EXISTENT_ID); + + mockMvc.perform(post("/api/orders") + .with(TestData.customerJwt()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + } + + @Test + void createOrder_invalidInput_returnsBadRequest() throws Exception { + var invalidRequest = new OrderRequestDTO(null, null, null, null, null, null, null, null, null, null, null, null, null, null); + + mockMvc.perform(post("/api/orders") + .with(TestData.customerJwt()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(invalidRequest))) + .andExpect(status().isBadRequest()); + } + + @Test + void createOrder_asDriver_returnsForbidden() throws Exception { + var request = TestData.createOrderRequest(user.getId()); + + mockMvc.perform(post("/api/orders") + .with(TestData.driverJwt()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isForbidden()); + } + + @Test + void createOrder_withoutAuth_returnsUnauthorized() throws Exception { + var request = TestData.createOrderRequest(user.getId()); + + mockMvc.perform(post("/api/orders") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isUnauthorized()); + } + + @Test + void getOrderById() throws Exception { + var order = TestData.createOrderEntity(user); + orderRepository.save(order); + + mockMvc.perform(get("/api/orders/{id}", order.getId()) + .with(TestData.customerJwt())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.pickupAddress").value("123 Main St")); + } + + @Test + void getOrderById_notFound_returnsOkEmpty() throws Exception { + mockMvc.perform(get("/api/orders/{id}", NON_EXISTENT_ID) + .with(TestData.customerJwt())) + .andExpect(status().isOk()) + .andExpect(content().string("")); + } + + @Test + void getOrdersByUserId() throws Exception { + var order = TestData.createOrderEntity(user); + orderRepository.save(order); + + mockMvc.perform(get("/api/orders/user/{userId}", user.getId()) + .with(TestData.customerJwt())) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.length()").value(1)); + } + + @Test + void getOrdersByUserId_noOrders_returnsEmptyList() throws Exception { + mockMvc.perform(get("/api/orders/user/{userId}", user.getId()) + .with(TestData.customerJwt())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$").isEmpty()); + } + + @Test + void getAllOrders_asAdmin() throws Exception { + var order = TestData.createOrderEntity(user); + orderRepository.save(order); + + mockMvc.perform(get("/api/orders") + .with(TestData.adminJwt())) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.length()").value(1)); + } + + @Test + void getAllOrders_asCustomer_returnsForbidden() throws Exception { + mockMvc.perform(get("/api/orders") + .with(TestData.customerJwt())) + .andExpect(status().isForbidden()); + } + + @Test + void getAllOrders_withoutAuth_returnsUnauthorized() throws Exception { + mockMvc.perform(get("/api/orders")) + .andExpect(status().isUnauthorized()); + } +} diff --git a/backend/src/test/java/com/angel/autonow/order/OrderServiceTest.java b/backend/src/test/java/com/angel/autonow/order/OrderServiceTest.java new file mode 100644 index 0000000..bd2d8c5 --- /dev/null +++ b/backend/src/test/java/com/angel/autonow/order/OrderServiceTest.java @@ -0,0 +1,213 @@ +package com.angel.autonow.order; + +import com.angel.autonow.data.TestData; +import com.angel.autonow.driver.DriverEntity; +import com.angel.autonow.driver.DriverRepository; +import com.angel.autonow.user.UserEntity; +import com.angel.autonow.user.UserRepository; +import com.angel.autonow.vehicle.VehicleEntity; +import com.angel.autonow.vehicle.VehicleRepository; +import com.angel.autonow.vehicle.VehicleType; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static com.angel.autonow.data.TestData.NON_EXISTENT_ID; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class OrderServiceTest { + + private static final LocalDateTime NOW = LocalDateTime.now(); + + @Mock + private OrderRepository orderRepository; + + @Mock + private OrderMapper orderMapper; + + @Mock + private UserRepository userRepository; + + @Mock + private DriverRepository driverRepository; + + @Mock + private VehicleRepository vehicleRepository; + + @InjectMocks + private OrderService orderService; + + @Test + void createOrder_returnOrderResponse() { + OrderRequestDTO request = TestData.createOrderRequest(1L); + UserEntity user = UserEntity.builder().id(1L).build(); + OrderEntity entity = OrderEntity.builder().vehicleType(VehicleType.TAXI).build(); + OrderEntity saved = OrderEntity.builder().id(1L).user(user).vehicleType(VehicleType.TAXI).status(OrderStatus.CREATED).createdAt(NOW).build(); + OrderResponseDTO response = TestData.createOrderResponse(1L, 1L, OrderStatus.CREATED, NOW); + + when(userRepository.findById(1L)).thenReturn(Optional.of(user)); + when(orderMapper.toEntity(request)).thenReturn(entity); + when(orderRepository.save(entity)).thenReturn(saved); + when(orderMapper.toDTO(saved)).thenReturn(response); + + var result = orderService.createOrder(request); + + assertTrue(result.isPresent()); + assertEquals(1L, result.get().id()); + assertEquals(OrderStatus.CREATED, result.get().status()); + verify(orderRepository).save(entity); + } + + @Test + void createOrder_userNotFound_returnsEmpty() { + OrderRequestDTO request = TestData.createOrderRequest(NON_EXISTENT_ID); + + when(userRepository.findById(NON_EXISTENT_ID)).thenReturn(Optional.empty()); + + var result = orderService.createOrder(request); + + assertTrue(result.isEmpty()); + verify(orderRepository, never()).save(any()); + } + + @Test + void createOrder_withDriverAndVehicle() { + OrderRequestDTO request = new OrderRequestDTO(1L, 2L, 3L, VehicleType.TAXI, + TestData.DEFAULT_PICKUP_ADDRESS, TestData.DEFAULT_PICKUP_LAT, TestData.DEFAULT_PICKUP_LNG, + TestData.DEFAULT_DROPOFF_ADDRESS, TestData.DEFAULT_DROPOFF_LAT, TestData.DEFAULT_DROPOFF_LNG, + 15.50, 5.2, 15, null); + UserEntity user = UserEntity.builder().id(1L).build(); + DriverEntity driver = DriverEntity.builder().id(2L).build(); + VehicleEntity vehicle = VehicleEntity.builder().id(3L).build(); + OrderEntity entity = OrderEntity.builder().vehicleType(VehicleType.TAXI).build(); + OrderEntity saved = OrderEntity.builder().id(1L).user(user).driver(driver).vehicle(vehicle) + .vehicleType(VehicleType.TAXI).status(OrderStatus.CREATED).createdAt(NOW).build(); + OrderResponseDTO response = OrderResponseDTO.builder() + .id(1L).userId(1L).driverId(2L).vehicleId(3L) + .vehicleType(VehicleType.TAXI).status(OrderStatus.CREATED) + .estimatedPrice(15.50).distanceKm(5.2).estimatedDurationMinutes(15) + .createdAt(NOW).build(); + + when(userRepository.findById(1L)).thenReturn(Optional.of(user)); + when(driverRepository.findById(2L)).thenReturn(Optional.of(driver)); + when(vehicleRepository.findById(3L)).thenReturn(Optional.of(vehicle)); + when(orderMapper.toEntity(request)).thenReturn(entity); + when(orderRepository.save(entity)).thenReturn(saved); + when(orderMapper.toDTO(saved)).thenReturn(response); + + var result = orderService.createOrder(request); + + assertTrue(result.isPresent()); + assertEquals(2L, result.get().driverId()); + assertEquals(3L, result.get().vehicleId()); + } + + @Test + void createOrder_driverNotFound_returnsEmpty() { + OrderRequestDTO request = new OrderRequestDTO(1L, NON_EXISTENT_ID, null, VehicleType.TAXI, + TestData.DEFAULT_PICKUP_ADDRESS, TestData.DEFAULT_PICKUP_LAT, TestData.DEFAULT_PICKUP_LNG, + TestData.DEFAULT_DROPOFF_ADDRESS, TestData.DEFAULT_DROPOFF_LAT, TestData.DEFAULT_DROPOFF_LNG, + 15.50, 5.2, 15, null); + UserEntity user = UserEntity.builder().id(1L).build(); + + when(userRepository.findById(1L)).thenReturn(Optional.of(user)); + when(orderMapper.toEntity(request)).thenReturn(OrderEntity.builder().vehicleType(VehicleType.TAXI).build()); + when(driverRepository.findById(NON_EXISTENT_ID)).thenReturn(Optional.empty()); + + var result = orderService.createOrder(request); + + assertTrue(result.isEmpty()); + verify(orderRepository, never()).save(any()); + } + + @Test + void createOrder_vehicleNotFound_returnsEmpty() { + OrderRequestDTO request = new OrderRequestDTO(1L, null, NON_EXISTENT_ID, VehicleType.TAXI, + TestData.DEFAULT_PICKUP_ADDRESS, TestData.DEFAULT_PICKUP_LAT, TestData.DEFAULT_PICKUP_LNG, + TestData.DEFAULT_DROPOFF_ADDRESS, TestData.DEFAULT_DROPOFF_LAT, TestData.DEFAULT_DROPOFF_LNG, + 15.50, 5.2, 15, null); + UserEntity user = UserEntity.builder().id(1L).build(); + + when(userRepository.findById(1L)).thenReturn(Optional.of(user)); + when(orderMapper.toEntity(request)).thenReturn(OrderEntity.builder().vehicleType(VehicleType.TAXI).build()); + when(vehicleRepository.findById(NON_EXISTENT_ID)).thenReturn(Optional.empty()); + + var result = orderService.createOrder(request); + + assertTrue(result.isEmpty()); + verify(orderRepository, never()).save(any()); + } + + @Test + void getOrderById_returnOrderResponse() { + UserEntity user = UserEntity.builder().id(1L).build(); + OrderEntity entity = OrderEntity.builder().id(1L).user(user).status(OrderStatus.COMPLETED).createdAt(NOW).build(); + OrderResponseDTO response = TestData.createOrderResponse(1L, 1L, OrderStatus.COMPLETED, NOW); + + when(orderRepository.findById(1L)).thenReturn(Optional.of(entity)); + when(orderMapper.toDTO(entity)).thenReturn(response); + + var result = orderService.getOrderById(1L); + + assertTrue(result.isPresent()); + assertEquals(OrderStatus.COMPLETED, result.get().status()); + } + + @Test + void getOrderById_notFound_returnsEmpty() { + when(orderRepository.findById(NON_EXISTENT_ID)).thenReturn(Optional.empty()); + + var result = orderService.getOrderById(NON_EXISTENT_ID); + + assertTrue(result.isEmpty()); + } + + @Test + void getOrdersByUserId_returnList() { + UserEntity user = UserEntity.builder().id(1L).build(); + OrderEntity orderEntity = OrderEntity.builder().id(1L).user(user).createdAt(NOW).build(); + OrderResponseDTO orderResponse = TestData.createOrderResponse(1L, 1L, OrderStatus.CREATED, NOW); + + when(orderRepository.findByUserId(1L)).thenReturn(List.of(orderEntity)); + when(orderMapper.toDTO(orderEntity)).thenReturn(orderResponse); + + var result = orderService.getOrdersByUserId(1L); + + assertEquals(1, result.size()); + } + + @Test + void getAllOrders_returnList() { + UserEntity user = UserEntity.builder().id(1L).build(); + OrderEntity firstOrder = OrderEntity.builder().id(1L).user(user).createdAt(NOW).build(); + OrderEntity secondOrder = OrderEntity.builder().id(2L).user(user).createdAt(NOW).build(); + OrderResponseDTO firstResponse = TestData.createOrderResponse(1L, 1L, OrderStatus.CREATED, NOW); + OrderResponseDTO secondResponse = TestData.createOrderResponse(2L, 1L, OrderStatus.COMPLETED, NOW); + + when(orderRepository.findAll()).thenReturn(List.of(firstOrder, secondOrder)); + when(orderMapper.toDTO(firstOrder)).thenReturn(firstResponse); + when(orderMapper.toDTO(secondOrder)).thenReturn(secondResponse); + + var result = orderService.getAllOrders(); + + assertEquals(2, result.size()); + } + + @Test + void getAllOrders_emptyList() { + when(orderRepository.findAll()).thenReturn(List.of()); + + var result = orderService.getAllOrders(); + + assertTrue(result.isEmpty()); + } +} diff --git a/backend/src/test/java/com/angel/autonow/payment/PaymentControllerIT.java b/backend/src/test/java/com/angel/autonow/payment/PaymentControllerIT.java new file mode 100644 index 0000000..1edc906 --- /dev/null +++ b/backend/src/test/java/com/angel/autonow/payment/PaymentControllerIT.java @@ -0,0 +1,172 @@ +package com.angel.autonow.payment; + +import com.angel.autonow.data.TestData; +import com.angel.autonow.order.OrderEntity; +import com.angel.autonow.order.OrderRepository; +import com.angel.autonow.user.UserEntity; +import com.angel.autonow.user.UserRepository; +import tools.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import static com.angel.autonow.data.TestData.NON_EXISTENT_ID; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@Transactional +@SpringBootTest +@AutoConfigureMockMvc +@TestPropertySource(locations = "classpath:application-test.properties") +class PaymentControllerIT { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private PaymentRepository paymentRepository; + + @Autowired + private OrderRepository orderRepository; + + @Autowired + private UserRepository userRepository; + + private OrderEntity order; + + @BeforeEach + void setUp() { + UserEntity user = TestData.createUserEntity(); + userRepository.save(user); + + order = TestData.createOrderEntity(user); + orderRepository.save(order); + } + + @Test + void createPayment() throws Exception { + var request = TestData.createPaymentRequest(order.getId()); + + mockMvc.perform(post("/api/payments") + .with(TestData.customerJwt()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isCreated()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.id").exists()) + .andExpect(jsonPath("$.orderId").value(order.getId())) + .andExpect(jsonPath("$.amount").value(16.00)) + .andExpect(jsonPath("$.paymentMethod").value("CREDIT_CARD")) + .andExpect(jsonPath("$.status").value("PENDING")) + .andExpect(jsonPath("$.currency").value("EUR")); + } + + @Test + void createPayment_orderNotFound_returnsBadRequest() throws Exception { + var request = TestData.createPaymentRequest(NON_EXISTENT_ID); + + mockMvc.perform(post("/api/payments") + .with(TestData.customerJwt()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + } + + @Test + void createPayment_invalidInput_returnsBadRequest() throws Exception { + var invalidRequest = new PaymentRequestDTO(null, null, null, null, null); + + mockMvc.perform(post("/api/payments") + .with(TestData.customerJwt()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(invalidRequest))) + .andExpect(status().isBadRequest()); + } + + @Test + void createPayment_asDriver_returnsForbidden() throws Exception { + var request = TestData.createPaymentRequest(order.getId()); + + mockMvc.perform(post("/api/payments") + .with(TestData.driverJwt()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isForbidden()); + } + + @Test + void createPayment_withoutAuth_returnsUnauthorized() throws Exception { + var request = TestData.createPaymentRequest(order.getId()); + + mockMvc.perform(post("/api/payments") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isUnauthorized()); + } + + @Test + void getPaymentById() throws Exception { + PaymentEntity payment = TestData.createPaymentEntity(order, 16.00, PaymentMethod.CREDIT_CARD, PaymentStatus.COMPLETED); + paymentRepository.save(payment); + + mockMvc.perform(get("/api/payments/{id}", payment.getId()) + .with(TestData.customerJwt())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.amount").value(16.00)) + .andExpect(jsonPath("$.paymentMethod").value("CREDIT_CARD")); + } + + @Test + void getPaymentById_notFound_returnsOkEmpty() throws Exception { + mockMvc.perform(get("/api/payments/{id}", NON_EXISTENT_ID) + .with(TestData.customerJwt())) + .andExpect(status().isOk()) + .andExpect(content().string("")); + } + + @Test + void getPaymentByOrderId() throws Exception { + PaymentEntity payment = TestData.createPaymentEntity(order, 38.50, PaymentMethod.DEBIT_CARD, PaymentStatus.COMPLETED); + paymentRepository.save(payment); + + mockMvc.perform(get("/api/payments/order/{orderId}", order.getId()) + .with(TestData.customerJwt())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.amount").value(38.50)); + } + + @Test + void getAllPayments_asAdmin() throws Exception { + PaymentEntity payment = TestData.createPaymentEntity(order, 16.00, PaymentMethod.CASH, PaymentStatus.PENDING); + paymentRepository.save(payment); + + mockMvc.perform(get("/api/payments") + .with(TestData.adminJwt())) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.length()").value(1)); + } + + @Test + void getAllPayments_asCustomer_returnsForbidden() throws Exception { + mockMvc.perform(get("/api/payments") + .with(TestData.customerJwt())) + .andExpect(status().isForbidden()); + } + + @Test + void getAllPayments_withoutAuth_returnsUnauthorized() throws Exception { + mockMvc.perform(get("/api/payments")) + .andExpect(status().isUnauthorized()); + } +} diff --git a/backend/src/test/java/com/angel/autonow/payment/PaymentServiceTest.java b/backend/src/test/java/com/angel/autonow/payment/PaymentServiceTest.java new file mode 100644 index 0000000..f2930e6 --- /dev/null +++ b/backend/src/test/java/com/angel/autonow/payment/PaymentServiceTest.java @@ -0,0 +1,163 @@ +package com.angel.autonow.payment; + +import com.angel.autonow.data.TestData; +import com.angel.autonow.order.OrderEntity; +import com.angel.autonow.order.OrderRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static com.angel.autonow.data.TestData.NON_EXISTENT_ID; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class PaymentServiceTest { + + private static final LocalDateTime NOW = LocalDateTime.now(); + + @Mock + private PaymentRepository paymentRepository; + + @Mock + private PaymentMapper paymentMapper; + + @Mock + private OrderRepository orderRepository; + + @InjectMocks + private PaymentService paymentService; + + @Test + void createPayment_returnPaymentResponse() { + PaymentRequestDTO request = TestData.createPaymentRequest(1L); + OrderEntity order = OrderEntity.builder().id(1L).build(); + PaymentEntity entity = PaymentEntity.builder().amount(16.00).paymentMethod(PaymentMethod.CREDIT_CARD).build(); + PaymentEntity saved = PaymentEntity.builder() + .id(1L).order(order).amount(16.00).paymentMethod(PaymentMethod.CREDIT_CARD) + .status(PaymentStatus.PENDING).currency("EUR").createdAt(NOW).updatedAt(NOW) + .build(); + PaymentResponseDTO response = TestData.createPaymentResponse(1L, 1L, 16.00, PaymentMethod.CREDIT_CARD, PaymentStatus.PENDING, NOW); + + when(orderRepository.findById(1L)).thenReturn(Optional.of(order)); + when(paymentMapper.toEntity(request)).thenReturn(entity); + when(paymentRepository.save(entity)).thenReturn(saved); + when(paymentMapper.toDTO(saved)).thenReturn(response); + + var result = paymentService.createPayment(request); + + assertTrue(result.isPresent()); + assertEquals(1L, result.get().id()); + assertEquals(16.00, result.get().amount()); + verify(paymentRepository).save(entity); + } + + @Test + void createPayment_orderNotFound_returnsEmpty() { + PaymentRequestDTO request = new PaymentRequestDTO(NON_EXISTENT_ID, 16.00, PaymentMethod.CASH, null, "EUR"); + + when(orderRepository.findById(NON_EXISTENT_ID)).thenReturn(Optional.empty()); + + var result = paymentService.createPayment(request); + + assertTrue(result.isEmpty()); + verify(paymentRepository, never()).save(any()); + } + + @Test + void createPayment_duplicateForOrder_returnsEmpty() { + OrderEntity order = OrderEntity.builder().id(1L).build(); + PaymentEntity existing = PaymentEntity.builder().id(1L).order(order).build(); + PaymentRequestDTO request = TestData.createPaymentRequest(1L); + + when(orderRepository.findById(1L)).thenReturn(Optional.of(order)); + when(paymentRepository.findByOrderId(1L)).thenReturn(Optional.of(existing)); + + var result = paymentService.createPayment(request); + + assertTrue(result.isEmpty()); + verify(paymentRepository, never()).save(any()); + } + + @Test + void getPaymentById_returnPaymentResponse() { + OrderEntity order = OrderEntity.builder().id(1L).build(); + PaymentEntity entity = PaymentEntity.builder().id(1L).order(order).amount(16.00).createdAt(NOW).build(); + PaymentResponseDTO response = TestData.createPaymentResponse(1L, 1L, 16.00, PaymentMethod.CREDIT_CARD, PaymentStatus.COMPLETED, NOW); + + when(paymentRepository.findById(1L)).thenReturn(Optional.of(entity)); + when(paymentMapper.toDTO(entity)).thenReturn(response); + + var result = paymentService.getPaymentById(1L); + + assertTrue(result.isPresent()); + assertEquals(16.00, result.get().amount()); + } + + @Test + void getPaymentById_notFound_returnsEmpty() { + when(paymentRepository.findById(NON_EXISTENT_ID)).thenReturn(Optional.empty()); + + var result = paymentService.getPaymentById(NON_EXISTENT_ID); + + assertTrue(result.isEmpty()); + } + + @Test + void getPaymentByOrderId_returnPaymentResponse() { + OrderEntity order = OrderEntity.builder().id(1L).build(); + PaymentEntity entity = PaymentEntity.builder().id(1L).order(order).amount(38.50).createdAt(NOW).build(); + PaymentResponseDTO response = TestData.createPaymentResponse(1L, 1L, 38.50, PaymentMethod.DEBIT_CARD, PaymentStatus.COMPLETED, NOW); + + when(paymentRepository.findByOrderId(1L)).thenReturn(Optional.of(entity)); + when(paymentMapper.toDTO(entity)).thenReturn(response); + + var result = paymentService.getPaymentByOrderId(1L); + + assertTrue(result.isPresent()); + assertEquals(38.50, result.get().amount()); + } + + @Test + void getPaymentByOrderId_notFound_returnsEmpty() { + when(paymentRepository.findByOrderId(NON_EXISTENT_ID)).thenReturn(Optional.empty()); + + var result = paymentService.getPaymentByOrderId(NON_EXISTENT_ID); + + assertTrue(result.isEmpty()); + } + + @Test + void getAllPayments_returnList() { + OrderEntity firstOrder = OrderEntity.builder().id(1L).build(); + OrderEntity secondOrder = OrderEntity.builder().id(2L).build(); + PaymentEntity firstPayment = PaymentEntity.builder().id(1L).order(firstOrder).amount(16.00).createdAt(NOW).build(); + PaymentEntity secondPayment = PaymentEntity.builder().id(2L).order(secondOrder).amount(38.50).createdAt(NOW).build(); + PaymentResponseDTO firstResponse = TestData.createPaymentResponse(1L, 1L, 16.00, PaymentMethod.CASH, PaymentStatus.COMPLETED, NOW); + PaymentResponseDTO secondResponse = TestData.createPaymentResponse(2L, 2L, 38.50, PaymentMethod.CREDIT_CARD, PaymentStatus.PENDING, NOW); + + when(paymentRepository.findAll()).thenReturn(List.of(firstPayment, secondPayment)); + when(paymentMapper.toDTO(firstPayment)).thenReturn(firstResponse); + when(paymentMapper.toDTO(secondPayment)).thenReturn(secondResponse); + + var result = paymentService.getAllPayments(); + + assertEquals(2, result.size()); + } + + @Test + void getAllPayments_emptyList() { + when(paymentRepository.findAll()).thenReturn(List.of()); + + var result = paymentService.getAllPayments(); + + assertTrue(result.isEmpty()); + } +} diff --git a/backend/src/test/java/com/angel/autonow/rating/RatingControllerIT.java b/backend/src/test/java/com/angel/autonow/rating/RatingControllerIT.java new file mode 100644 index 0000000..5a6f328 --- /dev/null +++ b/backend/src/test/java/com/angel/autonow/rating/RatingControllerIT.java @@ -0,0 +1,225 @@ +package com.angel.autonow.rating; + +import com.angel.autonow.data.TestData; +import com.angel.autonow.order.OrderEntity; +import com.angel.autonow.order.OrderRepository; +import com.angel.autonow.user.UserEntity; +import com.angel.autonow.user.UserRepository; +import tools.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import static com.angel.autonow.data.TestData.NON_EXISTENT_ID; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@Transactional +@SpringBootTest +@AutoConfigureMockMvc +@TestPropertySource(locations = "classpath:application-test.properties") +class RatingControllerIT { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private RatingRepository ratingRepository; + + @Autowired + private OrderRepository orderRepository; + + @Autowired + private UserRepository userRepository; + + private OrderEntity order; + + @BeforeEach + void setUp() { + UserEntity user = TestData.createUserEntity(); + userRepository.save(user); + + order = TestData.createOrderEntity(user); + orderRepository.save(order); + } + + @Test + void createRating() throws Exception { + var request = TestData.createRatingRequest(order.getId()); + + mockMvc.perform(post("/api/ratings") + .with(TestData.customerJwt()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isCreated()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.id").exists()) + .andExpect(jsonPath("$.orderId").value(order.getId())) + .andExpect(jsonPath("$.rating").value(5)) + .andExpect(jsonPath("$.comment").value("Excellent service!")) + .andExpect(jsonPath("$.createdAt").exists()); + } + + @Test + void createRating_asAdmin() throws Exception { + var request = TestData.createRatingRequest(order.getId()); + + mockMvc.perform(post("/api/ratings") + .with(TestData.adminJwt()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isCreated()); + } + + @Test + void createRating_asDriver_returnsForbidden() throws Exception { + var request = TestData.createRatingRequest(order.getId()); + + mockMvc.perform(post("/api/ratings") + .with(TestData.driverJwt()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isForbidden()); + } + + @Test + void createRating_asGuest_returnsForbidden() throws Exception { + var request = TestData.createRatingRequest(order.getId()); + + mockMvc.perform(post("/api/ratings") + .with(TestData.guestJwt()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isForbidden()); + } + + @Test + void createRating_invalidInput_returnsBadRequest() throws Exception { + var invalidRequest = new RatingRequestDTO(null, null, null); + + mockMvc.perform(post("/api/ratings") + .with(TestData.customerJwt()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(invalidRequest))) + .andExpect(status().isBadRequest()); + } + + @Test + void createRating_ratingOutOfRange_returnsBadRequest() throws Exception { + var invalidRequest = TestData.createRatingRequest(order.getId(), 6, null); + + mockMvc.perform(post("/api/ratings") + .with(TestData.customerJwt()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(invalidRequest))) + .andExpect(status().isBadRequest()); + } + + @Test + void createRating_orderNotFound_returnsBadRequest() throws Exception { + var request = TestData.createRatingRequest(NON_EXISTENT_ID); + + mockMvc.perform(post("/api/ratings") + .with(TestData.customerJwt()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + } + + @Test + void createRating_withoutAuth_returnsUnauthorized() throws Exception { + var request = TestData.createRatingRequest(order.getId()); + + mockMvc.perform(post("/api/ratings") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isUnauthorized()); + } + + @Test + void getRatingById() throws Exception { + var rating = TestData.createRatingEntity(order, 4, "Good ride"); + ratingRepository.save(rating); + + mockMvc.perform(get("/api/ratings/{id}", rating.getId()) + .with(TestData.customerJwt())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.rating").value(4)) + .andExpect(jsonPath("$.comment").value("Good ride")); + } + + @Test + void getRatingById_asGuest_returnsForbidden() throws Exception { + var rating = TestData.createRatingEntity(order, 4, "Good ride"); + ratingRepository.save(rating); + + mockMvc.perform(get("/api/ratings/{id}", rating.getId()) + .with(TestData.guestJwt())) + .andExpect(status().isForbidden()); + } + + @Test + void getRatingById_notFound_returnsOkEmpty() throws Exception { + mockMvc.perform(get("/api/ratings/{id}", NON_EXISTENT_ID) + .with(TestData.customerJwt())) + .andExpect(status().isOk()) + .andExpect(content().string("")); + } + + @Test + void getRatingByOrderId() throws Exception { + var rating = TestData.createRatingEntity(order, 3, "OK"); + ratingRepository.save(rating); + + mockMvc.perform(get("/api/ratings/order/{orderId}", order.getId()) + .with(TestData.customerJwt())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.rating").value(3)) + .andExpect(jsonPath("$.comment").value("OK")); + } + + @Test + void getRatingByOrderId_notFound_returnsOkEmpty() throws Exception { + mockMvc.perform(get("/api/ratings/order/{orderId}", NON_EXISTENT_ID) + .with(TestData.customerJwt())) + .andExpect(status().isOk()) + .andExpect(content().string("")); + } + + @Test + void getAllRatings() throws Exception { + var rating = TestData.createRatingEntity(order, 5, null); + ratingRepository.save(rating); + + mockMvc.perform(get("/api/ratings") + .with(TestData.customerJwt())) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.length()").value(1)); + } + + @Test + void getAllRatings_noEntries_returnsEmptyList() throws Exception { + mockMvc.perform(get("/api/ratings") + .with(TestData.customerJwt())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$").isEmpty()); + } + + @Test + void getAllRatings_withoutAuth_returnsUnauthorized() throws Exception { + mockMvc.perform(get("/api/ratings")) + .andExpect(status().isUnauthorized()); + } +} diff --git a/backend/src/test/java/com/angel/autonow/rating/RatingServiceTest.java b/backend/src/test/java/com/angel/autonow/rating/RatingServiceTest.java new file mode 100644 index 0000000..7c353ac --- /dev/null +++ b/backend/src/test/java/com/angel/autonow/rating/RatingServiceTest.java @@ -0,0 +1,145 @@ +package com.angel.autonow.rating; + +import com.angel.autonow.data.TestData; +import com.angel.autonow.order.OrderEntity; +import com.angel.autonow.order.OrderRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static com.angel.autonow.data.TestData.NON_EXISTENT_ID; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class RatingServiceTest { + + private static final LocalDateTime NOW = LocalDateTime.now(); + + @Mock + private RatingRepository ratingRepository; + + @Mock + private RatingMapper ratingMapper; + + @Mock + private OrderRepository orderRepository; + + @InjectMocks + private RatingService ratingService; + + @Test + void createRating_returnRatingResponse() { + RatingRequestDTO request = TestData.createRatingRequest(1L); + OrderEntity order = OrderEntity.builder().id(1L).build(); + RatingEntity entity = RatingEntity.builder().rating(5).comment("Excellent service!").build(); + RatingEntity saved = RatingEntity.builder().id(1L).order(order).rating(5).comment("Excellent service!").createdAt(NOW).build(); + RatingResponseDTO response = TestData.createRatingResponse(1L, 1L, 5, "Excellent service!", NOW); + + when(orderRepository.findById(1L)).thenReturn(Optional.of(order)); + when(ratingMapper.toEntity(request)).thenReturn(entity); + when(ratingRepository.save(entity)).thenReturn(saved); + when(ratingMapper.toDTO(saved)).thenReturn(response); + + var result = ratingService.createRating(request); + + assertTrue(result.isPresent()); + assertEquals(1L, result.get().id()); + assertEquals(5, result.get().rating()); + assertEquals("Excellent service!", result.get().comment()); + } + + @Test + void createRating_orderNotFound_returnsEmpty() { + RatingRequestDTO request = TestData.createRatingRequest(NON_EXISTENT_ID); + + when(orderRepository.findById(NON_EXISTENT_ID)).thenReturn(Optional.empty()); + + var result = ratingService.createRating(request); + + assertTrue(result.isEmpty()); + verify(ratingRepository, never()).save(any()); + } + + @Test + void getRatingById_returnRatingResponse() { + OrderEntity order = OrderEntity.builder().id(1L).build(); + RatingEntity entity = RatingEntity.builder().id(1L).order(order).rating(4).comment("Good").createdAt(NOW).build(); + RatingResponseDTO response = TestData.createRatingResponse(1L, 1L, 4, "Good", NOW); + + when(ratingRepository.findById(1L)).thenReturn(Optional.of(entity)); + when(ratingMapper.toDTO(entity)).thenReturn(response); + + var result = ratingService.getRatingById(1L); + + assertTrue(result.isPresent()); + assertEquals(4, result.get().rating()); + } + + @Test + void getRatingById_notFound_returnsEmpty() { + when(ratingRepository.findById(NON_EXISTENT_ID)).thenReturn(Optional.empty()); + + var result = ratingService.getRatingById(NON_EXISTENT_ID); + + assertTrue(result.isEmpty()); + } + + @Test + void getRatingByOrderId_returnRatingResponse() { + OrderEntity order = OrderEntity.builder().id(1L).build(); + RatingEntity entity = RatingEntity.builder().id(1L).order(order).rating(3).comment("OK").createdAt(NOW).build(); + RatingResponseDTO response = TestData.createRatingResponse(1L, 1L, 3, "OK", NOW); + + when(ratingRepository.findByOrderId(1L)).thenReturn(Optional.of(entity)); + when(ratingMapper.toDTO(entity)).thenReturn(response); + + var result = ratingService.getRatingByOrderId(1L); + + assertTrue(result.isPresent()); + assertEquals(3, result.get().rating()); + } + + @Test + void getRatingByOrderId_notFound_returnsEmpty() { + when(ratingRepository.findByOrderId(NON_EXISTENT_ID)).thenReturn(Optional.empty()); + + var result = ratingService.getRatingByOrderId(NON_EXISTENT_ID); + + assertTrue(result.isEmpty()); + } + + @Test + void getAllRatings_returnList() { + OrderEntity firstOrder = OrderEntity.builder().id(1L).build(); + OrderEntity secondOrder = OrderEntity.builder().id(2L).build(); + RatingEntity firstRating = RatingEntity.builder().id(1L).order(firstOrder).rating(5).createdAt(NOW).build(); + RatingEntity secondRating = RatingEntity.builder().id(2L).order(secondOrder).rating(3).createdAt(NOW).build(); + RatingResponseDTO firstResponse = TestData.createRatingResponse(1L, 1L, 5, null, NOW); + RatingResponseDTO secondResponse = TestData.createRatingResponse(2L, 2L, 3, null, NOW); + + when(ratingRepository.findAll()).thenReturn(List.of(firstRating, secondRating)); + when(ratingMapper.toDTO(firstRating)).thenReturn(firstResponse); + when(ratingMapper.toDTO(secondRating)).thenReturn(secondResponse); + + var result = ratingService.getAllRatings(); + + assertEquals(2, result.size()); + } + + @Test + void getAllRatings_emptyList() { + when(ratingRepository.findAll()).thenReturn(List.of()); + + var result = ratingService.getAllRatings(); + + assertTrue(result.isEmpty()); + } +} diff --git a/backend/src/test/java/com/angel/autonow/user/UserControllerIT.java b/backend/src/test/java/com/angel/autonow/user/UserControllerIT.java new file mode 100644 index 0000000..3f91cdb --- /dev/null +++ b/backend/src/test/java/com/angel/autonow/user/UserControllerIT.java @@ -0,0 +1,118 @@ +package com.angel.autonow.user; + +import tools.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@Transactional +@SpringBootTest +@AutoConfigureMockMvc +@TestPropertySource(locations = "classpath:application-test.properties") +class UserControllerIT { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Test + void register() throws Exception { + var request = new UserRequestDTO("newuser@example.com", "Password1"); + + mockMvc.perform(post("/api/auth/register") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.token").exists()); + } + + @Test + void register_duplicateEmail_returnsBadRequest() throws Exception { + var request = new UserRequestDTO("duplicate@example.com", "Password1"); + + mockMvc.perform(post("/api/auth/register") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()); + + mockMvc.perform(post("/api/auth/register") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + } + + @Test + void register_invalidEmail_returnsBadRequest() throws Exception { + var request = new UserRequestDTO("not-an-email", "Password1"); + + mockMvc.perform(post("/api/auth/register") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + } + + @Test + void register_weakPassword_returnsBadRequest() throws Exception { + var request = new UserRequestDTO("test@example.com", "weak"); + + mockMvc.perform(post("/api/auth/register") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + } + + @Test + void login() throws Exception { + var registerRequest = new UserRequestDTO("login@example.com", "Password1"); + + mockMvc.perform(post("/api/auth/register") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(registerRequest))) + .andExpect(status().isOk()); + + mockMvc.perform(post("/api/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(registerRequest))) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.token").exists()); + } + + @Test + void login_wrongPassword_returnsBadRequest() throws Exception { + var registerRequest = new UserRequestDTO("wrongpw@example.com", "Password1"); + + mockMvc.perform(post("/api/auth/register") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(registerRequest))) + .andExpect(status().isOk()); + + var loginRequest = new UserRequestDTO("wrongpw@example.com", "WrongPass1"); + + mockMvc.perform(post("/api/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(loginRequest))) + .andExpect(status().isBadRequest()); + } + + @Test + void login_userNotFound_returnsBadRequest() throws Exception { + var request = new UserRequestDTO("nonexistent@example.com", "Password1"); + + mockMvc.perform(post("/api/auth/login") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + } +} diff --git a/backend/src/test/java/com/angel/autonow/user/UserServiceTest.java b/backend/src/test/java/com/angel/autonow/user/UserServiceTest.java new file mode 100644 index 0000000..f3fba51 --- /dev/null +++ b/backend/src/test/java/com/angel/autonow/user/UserServiceTest.java @@ -0,0 +1,106 @@ +package com.angel.autonow.user; + +import com.angel.autonow.security.jwt.JwtService; +import com.angel.autonow.user.role.Role; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.util.Optional; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class UserServiceTest { + + private static final String TEST_EMAIL = "test@example.com"; + private static final String TEST_PASSWORD = "Password1"; + private static final String ENCODED_PASSWORD = "encodedPassword"; + private static final String TEST_TOKEN = "jwt-token"; + + @Mock + private UserRepository userRepository; + + @Mock + private JwtService jwtService; + + @Mock + private PasswordEncoder passwordEncoder; + + @InjectMocks + private UserService userService; + + @Test + void register_returnToken() { + UserRequestDTO request = new UserRequestDTO(TEST_EMAIL, TEST_PASSWORD); + + when(userRepository.findByEmail(TEST_EMAIL)).thenReturn(Optional.empty()); + when(passwordEncoder.encode(TEST_PASSWORD)).thenReturn(ENCODED_PASSWORD); + when(jwtService.generateToken(eq(TEST_EMAIL), any())).thenReturn(TEST_TOKEN); + + var result = userService.register(request); + + assertEquals(TEST_TOKEN, result); + verify(userRepository).save(any(UserEntity.class)); + } + + @Test + void register_duplicateEmail_throws() { + UserRequestDTO request = new UserRequestDTO(TEST_EMAIL, TEST_PASSWORD); + UserEntity existing = UserEntity.builder().email(TEST_EMAIL).build(); + + when(userRepository.findByEmail(TEST_EMAIL)).thenReturn(Optional.of(existing)); + + assertThrows(UserException.class, () -> userService.register(request)); + verify(userRepository, never()).save(any()); + } + + @Test + void login_returnToken() { + UserRequestDTO request = new UserRequestDTO(TEST_EMAIL, TEST_PASSWORD); + Set authorities = Set.of(Role.CUSTOMER.getAuthority()); + UserEntity user = UserEntity.builder() + .email(TEST_EMAIL) + .password(ENCODED_PASSWORD) + .authorities(authorities) + .build(); + + when(userRepository.findByEmail(TEST_EMAIL)).thenReturn(Optional.of(user)); + when(passwordEncoder.matches(TEST_PASSWORD, ENCODED_PASSWORD)).thenReturn(true); + when(jwtService.generateToken(TEST_EMAIL, authorities)).thenReturn(TEST_TOKEN); + + var result = userService.login(request); + + assertEquals(TEST_TOKEN, result); + } + + @Test + void login_wrongPassword_throws() { + UserRequestDTO request = new UserRequestDTO(TEST_EMAIL, TEST_PASSWORD); + UserEntity user = UserEntity.builder() + .email(TEST_EMAIL) + .password(ENCODED_PASSWORD) + .build(); + + when(userRepository.findByEmail(TEST_EMAIL)).thenReturn(Optional.of(user)); + when(passwordEncoder.matches(TEST_PASSWORD, ENCODED_PASSWORD)).thenReturn(false); + + assertThrows(UserException.class, () -> userService.login(request)); + } + + @Test + void login_userNotFound_throws() { + UserRequestDTO request = new UserRequestDTO(TEST_EMAIL, TEST_PASSWORD); + + when(userRepository.findByEmail(TEST_EMAIL)).thenReturn(Optional.empty()); + + assertThrows(UserException.class, () -> userService.login(request)); + } +} diff --git a/backend/src/test/java/com/angel/autonow/vehicle/VehicleControllerIT.java b/backend/src/test/java/com/angel/autonow/vehicle/VehicleControllerIT.java new file mode 100644 index 0000000..b4529b1 --- /dev/null +++ b/backend/src/test/java/com/angel/autonow/vehicle/VehicleControllerIT.java @@ -0,0 +1,127 @@ +package com.angel.autonow.vehicle; + +import com.angel.autonow.data.TestData; +import tools.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import static com.angel.autonow.data.TestData.NON_EXISTENT_ID; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@Transactional +@SpringBootTest +@AutoConfigureMockMvc +@TestPropertySource(locations = "classpath:application-test.properties") +class VehicleControllerIT { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private VehicleRepository vehicleRepository; + + @Test + void createVehicle_asAdmin() throws Exception { + var request = TestData.createVehicleRequest(); + + mockMvc.perform(post("/api/vehicles") + .with(TestData.adminJwt()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isCreated()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.id").exists()) + .andExpect(jsonPath("$.brand").value("Toyota")) + .andExpect(jsonPath("$.model").value("Camry")) + .andExpect(jsonPath("$.vehicleType").value("TAXI")); + } + + @Test + void createVehicle_asCustomer_returnsForbidden() throws Exception { + var request = TestData.createVehicleRequest(); + + mockMvc.perform(post("/api/vehicles") + .with(TestData.customerJwt()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isForbidden()); + } + + @Test + void createVehicle_invalidInput_returnsBadRequest() throws Exception { + var invalidRequest = new VehicleRequestDTO(null, null, null, false, null, null, null); + + mockMvc.perform(post("/api/vehicles") + .with(TestData.adminJwt()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(invalidRequest))) + .andExpect(status().isBadRequest()); + } + + @Test + void createVehicle_withoutAuth_returnsUnauthorized() throws Exception { + var request = TestData.createVehicleRequest(); + + mockMvc.perform(post("/api/vehicles") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isUnauthorized()); + } + + @Test + void getVehicleById() throws Exception { + var vehicle = TestData.createVehicleEntity(); + vehicleRepository.save(vehicle); + + mockMvc.perform(get("/api/vehicles/{id}", vehicle.getId()) + .with(TestData.guestJwt())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.brand").value("Toyota")); + } + + @Test + void getVehicleById_notFound_returnsOkEmpty() throws Exception { + mockMvc.perform(get("/api/vehicles/{id}", NON_EXISTENT_ID) + .with(TestData.guestJwt())) + .andExpect(status().isOk()) + .andExpect(content().string("")); + } + + @Test + void getAllVehicles() throws Exception { + var vehicle = TestData.createVehicleEntity(); + vehicleRepository.save(vehicle); + + mockMvc.perform(get("/api/vehicles") + .with(TestData.guestJwt())) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.length()").value(1)); + } + + @Test + void getAllVehicles_noEntries_returnsEmptyList() throws Exception { + mockMvc.perform(get("/api/vehicles") + .with(TestData.guestJwt())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$").isEmpty()); + } + + @Test + void getAllVehicles_withoutAuth_returnsUnauthorized() throws Exception { + mockMvc.perform(get("/api/vehicles")) + .andExpect(status().isUnauthorized()); + } +} diff --git a/backend/src/test/java/com/angel/autonow/vehicle/VehicleServiceTest.java b/backend/src/test/java/com/angel/autonow/vehicle/VehicleServiceTest.java new file mode 100644 index 0000000..52c2eb0 --- /dev/null +++ b/backend/src/test/java/com/angel/autonow/vehicle/VehicleServiceTest.java @@ -0,0 +1,97 @@ +package com.angel.autonow.vehicle; + +import com.angel.autonow.data.TestData; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; +import java.util.Optional; + +import static com.angel.autonow.data.TestData.NON_EXISTENT_ID; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class VehicleServiceTest { + + @Mock + private VehicleRepository vehicleRepository; + + @Mock + private VehicleMapper vehicleMapper; + + @InjectMocks + private VehicleService vehicleService; + + @Test + void createVehicle_returnVehicleResponse() { + VehicleRequestDTO request = TestData.createVehicleRequest(); + VehicleEntity entity = VehicleEntity.builder().brand("Toyota").model("Camry").build(); + VehicleEntity saved = VehicleEntity.builder().id(1L).brand("Toyota").model("Camry").build(); + VehicleResponseDTO response = TestData.createVehicleResponse(1L); + + when(vehicleMapper.toEntity(request)).thenReturn(entity); + when(vehicleRepository.save(entity)).thenReturn(saved); + when(vehicleMapper.toDTO(saved)).thenReturn(response); + + var result = vehicleService.createVehicle(request); + + assertEquals(1L, result.id()); + assertEquals("Toyota", result.brand()); + verify(vehicleRepository).save(entity); + } + + @Test + void getVehicleById_returnVehicleResponse() { + VehicleEntity entity = VehicleEntity.builder().id(1L).brand("Toyota").build(); + VehicleResponseDTO response = TestData.createVehicleResponse(1L); + + when(vehicleRepository.findById(1L)).thenReturn(Optional.of(entity)); + when(vehicleMapper.toDTO(entity)).thenReturn(response); + + var result = vehicleService.getVehicleById(1L); + + assertTrue(result.isPresent()); + assertEquals("Toyota", result.get().brand()); + } + + @Test + void getVehicleById_notFound_returnsEmpty() { + when(vehicleRepository.findById(NON_EXISTENT_ID)).thenReturn(Optional.empty()); + + var result = vehicleService.getVehicleById(NON_EXISTENT_ID); + + assertTrue(result.isEmpty()); + } + + @Test + void getAllVehicles_returnList() { + VehicleEntity firstVehicle = VehicleEntity.builder().id(1L).brand("Toyota").build(); + VehicleEntity secondVehicle = VehicleEntity.builder().id(2L).brand("Honda").build(); + VehicleResponseDTO firstResponse = TestData.createVehicleResponse(1L); + VehicleResponseDTO secondResponse = VehicleResponseDTO.builder() + .id(2L).brand("Honda").model("CR-V") + .vehicleType(VehicleType.TAXI) + .build(); + + when(vehicleRepository.findAll()).thenReturn(List.of(firstVehicle, secondVehicle)); + when(vehicleMapper.toDTO(firstVehicle)).thenReturn(firstResponse); + when(vehicleMapper.toDTO(secondVehicle)).thenReturn(secondResponse); + + var result = vehicleService.getAllVehicles(); + + assertEquals(2, result.size()); + } + + @Test + void getAllVehicles_emptyList() { + when(vehicleRepository.findAll()).thenReturn(List.of()); + + var result = vehicleService.getAllVehicles(); + + assertTrue(result.isEmpty()); + } +} diff --git a/backend/src/test/resources/application-test.properties b/backend/src/test/resources/application-test.properties index 85647ba..c40d01a 100644 --- a/backend/src/test/resources/application-test.properties +++ b/backend/src/test/resources/application-test.properties @@ -6,6 +6,9 @@ spring.datasource.password= spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.show-sql=false +spring.sql.init.mode=never +spring.docker.compose.enabled=false + jwt.secret=0123456789ABCDEF0123456789ABCDEF jwt.algorithm=HmacSHA256 jwt.expiration=3600000