content: package com.example.auction.service;
  
  import com.example.auction.dto.AuctionCreateDTO;
  import com.example.auction.dto.BidCreateDTO;
  import com.example.auction.exception.AuctionException;
  import com.example.auction.exception.AuctionNotFoundException;
  import com.example.auction.model.Auction;
  import com.example.auction.model.AuctionStatus;
  import com.example.auction.model.Bid;
  import com.example.auction.repository.AuctionRepository;
  import com.example.auction.repository.BidRepository;
  import org.junit.jupiter.api.BeforeEach;
  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.data.domain.PageRequest;
  import org.springframework.data.domain.Pageable;
  
  import java.math.BigDecimal;
  import java.time.LocalDateTime;
  import java.util.Arrays;
  import java.util.List;
  import java.util.Optional;
  
  import static org.junit.jupiter.api.Assertions.*;
  import static org.mockito.ArgumentMatchers.*;
  import static org.mockito.Mockito.*;
  
  @ExtendWith(MockitoExtension.class)
  class AuctionServiceTest {
  
      @Mock
      private AuctionRepository auctionRepository;
  
      @Mock
      private BidRepository bidRepository;
  
      @InjectMocks
      private AuctionService auctionService;
  
      private AuctionCreateDTO createDTO;
      private Auction testAuction;
  
      @BeforeEach
      void setUp() {
          createDTO = new AuctionCreateDTO();
          createDTO.setTitle("Test Auction");
          createDTO.setDescription("Test Description");
          createDTO.setStartingPrice(new BigDecimal("100.00"));
          createDTO.setMinimumIncrement(new BigDecimal("10.00"));
          createDTO.setDurationMinutes(60);
  
          testAuction = new Auction();
          testAuction.setId("test-id");
          testAuction.setTitle("Test Auction");
          testAuction.setDescription("Test Description");
          testAuction.setStartingPrice(new BigDecimal("100.00"));
          testAuction.setCurrentPrice(new BigDecimal("100.00"));
          testAuction.setMinimumIncrement(new BigDecimal("10.00"));
          testAuction.setStatus(AuctionStatus.OPEN);
          testAuction.setCreatedAt(LocalDateTime.now());
          testAuction.setEndsAt(LocalDateTime.now().plusMinutes(60));
      }
  
      @Test
      void testCreateAuction_Success() {
          when(auctionRepository.existsByStatus(AuctionStatus.OPEN)).thenReturn(false);
          when(auctionRepository.save(any(Auction.class))).thenAnswer(invocation -> {
              Auction auction = invocation.getArgument(0);
              auction.setId("generated-id");
              return auction;
          });
  
          Auction result = auctionService.createAuction(createDTO);
  
          assertNotNull(result);
          assertEquals("Test Auction", result.getTitle());
          assertEquals("Test Description", result.getDescription());
          assertEquals(new BigDecimal("100.00"), result.getStartingPrice());
          assertEquals(new BigDecimal("100.00"), result.getCurrentPrice());
          assertEquals(AuctionStatus.OPEN, result.getStatus());
          
          verify(auctionRepository, times(1)).existsByStatus(AuctionStatus.OPEN);
          verify(auctionRepository, times(1)).save(any(Auction.class));
      }
  
      @Test
      void testCreateAuction_FailsWhenActiveAuctionExists() {
          when(auctionRepository.existsByStatus(AuctionStatus.OPEN)).thenReturn(true);
  
          AuctionException exception = assertThrows(AuctionException.class, 
              () -> auctionService.createAuction(createDTO));
          
          assertEquals("Cannot create auction: An active auction already exists", exception.getMessage());
          verify(auctionRepository, never()).save(any(Auction.class));
      }
  
      @Test
      void testGetAuctions_WithStatusFilter() {
          List<Auction> openAuctions = Arrays.asList(testAuction);
          when(auctionRepository.findExpiredAuctions(any(LocalDateTime.class))).thenReturn(Arrays.asList());
          when(auctionRepository.findByStatus(AuctionStatus.OPEN)).thenReturn(openAuctions);
  
          List<Auction> result = auctionService.getAuctions("OPEN", null);
  
          assertEquals(1, result.size());
          assertEquals("test-id", result.get(0).getId());
          verify(auctionRepository, times(1)).findByStatus(AuctionStatus.OPEN);
      }
  
      @Test
      void testGetAuctions_WithInvalidStatus() {
          when(auctionRepository.findExpiredAuctions(any(LocalDateTime.class))).thenReturn(Arrays.asList());
  
          AuctionException exception = assertThrows(AuctionException.class,
              () -> auctionService.getAuctions("INVALID", null));
          
          assertEquals("Invalid status: INVALID", exception.getMessage());
      }
  
      @Test
      void testGetAuctions_SortByNewest() {
          List<Auction> auctions = Arrays.asList(testAuction);
          when(auctionRepository.findExpiredAuctions(any(LocalDateTime.class))).thenReturn(Arrays.asList());
          when(auctionRepository.findAllByOrderByCreatedAtDesc()).thenReturn(auctions);
  
          List<Auction> result = auctionService.getAuctions(null, "newest");
  
          assertEquals(1, result.size());
          verify(auctionRepository, times(1)).findAllByOrderByCreatedAtDesc();
      }
  
      @Test
      void testGetAuctions_SortByEndingSoon() {
          List<Auction> auctions = Arrays.asList(testAuction);
          when(auctionRepository.findExpiredAuctions(any(LocalDateTime.class))).thenReturn(Arrays.asList());
          when(auctionRepository.findByStatusOrderByEndsAtAsc(AuctionStatus.OPEN)).thenReturn(auctions);
  
          List<Auction> result = auctionService.getAuctions(null, "ending_soon");
  
          assertEquals(1, result.size());
          verify(auctionRepository, times(1)).findByStatusOrderByEndsAtAsc(AuctionStatus.OPEN);
      }
  
      @Test
      void testGetAuction_Success() {
          when(auctionRepository.findExpiredAuctions(any(LocalDateTime.class))).thenReturn(Arrays.asList());
          when(auctionRepository.findById("test-id")).thenReturn(Optional.of(testAuction));
  
          Auction result = auctionService.getAuction("test-id");
  
          assertNotNull(result);
          assertEquals("test-id", result.getId());
          verify(auctionRepository, times(1)).findById("test-id");
      }
  
      @Test
      void testGetAuction_NotFound() {
          when(auctionRepository.findExpiredAuctions(any(LocalDateTime.class))).thenReturn(Arrays.asList());
          when(auctionRepository.findById("non-existent")).thenReturn(Optional.empty());
  
          AuctionNotFoundException exception = assertThrows(AuctionNotFoundException.class,
              () -> auctionService.getAuction("non-existent"));
          
          assertEquals("Auction not found: non-existent", exception.getMessage());
      }
  
      @Test
      void testPlaceBid_ValidBid() {
          BidCreateDTO bidDTO = new BidCreateDTO();
          bidDTO.setBidderId("bidder-1");
          bidDTO.setBidderName("John Doe");
          bidDTO.setAmount(new BigDecimal("110.00"));
  
          when(auctionRepository.findById("test-id")).thenReturn(Optional.of(testAuction));
          when(bidRepository.save(any(Bid.class))).thenAnswer(invocation -> {
              Bid bid = invocation.getArgument(0);
              bid.setId("bid-id");
              return bid;
          });
          when(auctionRepository.save(any(Auction.class))).thenReturn(testAuction);
  
          Bid result = auctionService.placeBid("test-id", bidDTO);
  
          assertNotNull(result);
          assertEquals("bidder-1", result.getBidderId());
          assertEquals("John Doe", result.getBidderName());
          assertEquals(new BigDecimal("110.00"), result.getAmount());
          assertTrue(result.getIsValid());
          assertNull(result.getValidationError());
          
          assertEquals(new BigDecimal("110.00"), testAuction.getCurrentPrice());
          assertEquals("bidder-1", testAuction.getHighestBidderId());
          assertEquals("John Doe", testAuction.getHighestBidderName());
          
          verify(bidRepository, times(1)).save(any(Bid.class));
          verify(auctionRepository, times(1)).save(any(Auction.class));
      }
  
      @Test
      void testPlaceBid_BidTooLow() {
          BidCreateDTO bidDTO = new BidCreateDTO();
          bidDTO.setBidderId("bidder-1");
          bidDTO.setBidderName("John Doe");
          bidDTO.setAmount(new BigDecimal("105.00")); // Less than required 110.00
  
          when(auctionRepository.findById("test-id")).thenReturn(Optional.of(testAuction));
          when(bidRepository.save(any(Bid.class))).thenAnswer(invocation -> invocation.getArgument(0));
  
          AuctionException exception = assertThrows(AuctionException.class,
              () -> auctionService.placeBid("test-id", bidDTO));
          
          assertTrue(exception.getMessage().contains("Bid must be at least $110.00"));
          verify(bidRepository, times(1)).save(any(Bid.class));
          verify(auctionRepository, never()).save(any(Auction.class));
      }
  
      @Test
      void testPlaceBid_AuctionClosed() {
          testAuction.setStatus(AuctionStatus.CLOSED);
          
          BidCreateDTO bidDTO = new BidCreateDTO();
          bidDTO.setBidderId("bidder-1");
          bidDTO.setBidderName("John Doe");
          bidDTO.setAmount(new BigDecimal("110.00"));
  
          when(auctionRepository.findById("test-id")).thenReturn(Optional.of(testAuction));
          when(bidRepository.save(any(Bid.class))).thenAnswer(invocation -> invocation.getArgument(0));
  
          AuctionException exception = assertThrows(AuctionException.class,
              () -> auctionService.placeBid("test-id", bidDTO));
          
          assertTrue(exception.getMessage().contains("Auction is closed"));
          verify(bidRepository, times(1)).save(any(Bid.class));
          verify(auctionRepository, never()).save(any(Auction.class));
      }
  
      @Test
      void testPlaceBid_AuctionExpired() {
          testAuction.setEndsAt(LocalDateTime.now().minusMinutes(10)); // Expired
          
          BidCreateDTO bidDTO = new BidCreateDTO();
          bidDTO.setBidderId("bidder-1");
          bidDTO.setBidderName("John Doe");
          bidDTO.setAmount(new BigDecimal("110.00"));
  
          when(auctionRepository.findById("test-id")).thenReturn(Optional.of(testAuction));
          when(bidRepository.save(any(Bid.class))).thenAnswer(invocation -> invocation.getArgument(0));
  
          AuctionException exception = assertThrows(AuctionException.class,
              () -> auctionService.placeBid("test-id", bidDTO));
          
          assertTrue(exception.getMessage().contains("Auction has ended"));
          verify(bidRepository, times(1)).save(any(Bid.class));
      }
  
      @Test
      void testGetBids_Success() {
          Bid bid1 = new Bid();
          bid1.setId("bid-1");
          bid1.setBidderId("bidder-1");
          bid1.setAmount(new BigDecimal("110.00"));
          
          Bid bid2 = new Bid();
          bid2.setId("bid-2");
          bid2.setBidderId("bidder-2");
          bid2.setAmount(new BigDecimal("120.00"));
          
          List<Bid> bids = Arrays.asList(bid2, bid1);
          
          when(auctionRepository.existsById("test-id")).thenReturn(true);
          when(bidRepository.findByAuctionIdOrderByTimestampDesc(eq("test-id"), any(Pageable.class)))
              .thenReturn(bids);
  
          List<Bid> result = auctionService.getBids("test-id", 10);
  
          assertEquals(2, result.size());
          assertEquals("bid-2", result.get(0).getId());
          assertEquals("bid-1", result.get(1).getId());
      }
  
      @Test
      void testGetBids_AuctionNotFound() {
          when(auctionRepository.existsById("non-existent")).thenReturn(false);
  
          AuctionNotFoundException exception = assertThrows(AuctionNotFoundException.class,
              () -> auctionService.getBids("non-existent", 10));
          
          assertEquals("Auction not found: non-existent", exception.getMessage());
      }
  
      @Test
      void testCloseAuction_Success() {
          when(auctionRepository.findById("test-id")).thenReturn(Optional.of(testAuction));
          when(auctionRepository.save(any(Auction.class))).thenReturn(testAuction);
  
          Auction result = auctionService.closeAuction("test-id");
  
          assertNotNull(result);
          assertEquals(AuctionStatus.CLOSED, result.getStatus());
          verify(auctionRepository, times(1)).save(any(Auction.class));
      }
  
      @Test
      void testCloseAuction_AlreadyClosed() {
          testAuction.setStatus(AuctionStatus.CLOSED);
          when(auctionRepository.findById("test-id")).thenReturn(Optional.of(testAuction));
  
          AuctionException exception = assertThrows(AuctionException.class,
              () -> auctionService.closeAuction("test-id"));
          
          assertEquals("Auction is already closed", exception.getMessage());
          verify(auctionRepository, never()).save(any(Auction.class));
      }
  
      @Test
      void testAutoCloseExpiredAuctions() {
          Auction expiredAuction = new Auction();
          expiredAuction.setId("expired-id");
          expiredAuction.setStatus(AuctionStatus.OPEN);
          expiredAuction.setEndsAt(LocalDateTime.now().minusMinutes(10));
          
          List<Auction> expiredAuctions = Arrays.asList(expiredAuction);
          
          when(auctionRepository.findExpiredAuctions(any(LocalDateTime.class))).thenReturn(expiredAuctions);
          when(auctionRepository.findAllByOrderByCreatedAtDesc()).thenReturn(Arrays.asList(testAuction));
  
          auctionService.getAuctions(null, null);
  
          assertEquals(AuctionStatus.CLOSED, expiredAuction.getStatus());
          verify(auctionRepository, times(1)).save(expiredAuction);
      }
  
      @Test
      void testResetDatabase() {
          auctionService.resetDatabase();
  
          verify(bidRepository, times(1)).deleteAll();
          verify(auctionRepository, times(1)).deleteAll();
      }
  }