Fix build: Update tests for image download refactor
- Remove RateLimitedHttpClient from ImageProcessingService constructor - Rewrite ImageProcessingServiceTest to test new object detection workflow - Fix IntegrationTest constructor call - Add insertImage() back to DatabaseService for test compatibility - Tests now focus on object detection rather than image downloading
This commit is contained in:
@@ -469,6 +469,22 @@ public class DatabaseService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a complete image record (for testing/legacy compatibility).
|
||||
* In production, scraper inserts with local_path, monitor updates labels via updateImageLabels.
|
||||
*/
|
||||
synchronized void insertImage(long lotId, String url, String filePath, List<String> labels) throws SQLException {
|
||||
var sql = "INSERT INTO images (lot_id, url, local_path, labels, processed_at, downloaded) VALUES (?, ?, ?, ?, ?, 1)";
|
||||
try (var conn = DriverManager.getConnection(this.url); var ps = conn.prepareStatement(sql)) {
|
||||
ps.setLong(1, lotId);
|
||||
ps.setString(2, url);
|
||||
ps.setString(3, filePath);
|
||||
ps.setString(4, String.join(",", labels));
|
||||
ps.setLong(5, Instant.now().getEpochSecond());
|
||||
ps.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the labels field for an image after object detection
|
||||
*/
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package auctiora;
|
||||
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
@@ -13,205 +10,156 @@ import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Test cases for ImageProcessingService.
|
||||
* Tests image downloading, object detection integration, and database updates.
|
||||
* Tests object detection integration and database label updates.
|
||||
*
|
||||
* NOTE: Image downloading is now handled by the scraper, so these tests
|
||||
* focus only on object detection and label storage.
|
||||
*/
|
||||
class ImageProcessingServiceTest {
|
||||
|
||||
private DatabaseService mockDb;
|
||||
private ObjectDetectionService mockDetector;
|
||||
private RateLimitedHttpClient mockHttpClient;
|
||||
private ImageProcessingService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
mockDb = mock(DatabaseService.class);
|
||||
mockDetector = mock(ObjectDetectionService.class);
|
||||
mockHttpClient = mock(RateLimitedHttpClient.class);
|
||||
service = new ImageProcessingService(mockDb, mockDetector, mockHttpClient);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws Exception {
|
||||
// Clean up any test image directories
|
||||
var testDir = Paths.get("C:", "mnt", "okcomputer", "output", "images", "999");
|
||||
if (Files.exists(testDir)) {
|
||||
Files.walk(testDir)
|
||||
.sorted((a, b) -> b.compareTo(a)) // Reverse order for deletion
|
||||
.forEach(path -> {
|
||||
try {
|
||||
Files.deleteIfExists(path);
|
||||
} catch (Exception e) {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
});
|
||||
}
|
||||
service = new ImageProcessingService(mockDb, mockDetector);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should process images for lot with object detection")
|
||||
void testProcessImagesForLot() throws SQLException {
|
||||
when(mockDetector.detectObjects(anyString()))
|
||||
@DisplayName("Should process single image and update labels")
|
||||
void testProcessImage() throws SQLException {
|
||||
// Mock object detection
|
||||
when(mockDetector.detectObjects("/path/to/image.jpg"))
|
||||
.thenReturn(List.of("car", "vehicle"));
|
||||
|
||||
// Note: This test uses mock URLs and won't actually download
|
||||
// In a real scenario, you'd use a test HTTP server
|
||||
var imageUrls = List.of("https://example.com/test1.jpg");
|
||||
// Process image
|
||||
boolean result = service.processImage(1, "/path/to/image.jpg", 12345);
|
||||
|
||||
// Mock successful processing
|
||||
doNothing().when(mockDb).insertImage(anyInt(), anyString(), anyString(), anyList());
|
||||
|
||||
// Process images
|
||||
service.processImagesForLot(12345, 99999, imageUrls);
|
||||
|
||||
// Verify detection was called (may not be called if download fails)
|
||||
// In a full integration test, you'd verify the complete flow
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle image download failure gracefully")
|
||||
void testDownloadImageFailure() throws Exception {
|
||||
// Mock HTTP client to throw exception
|
||||
when(mockHttpClient.sendGetBytes(anyString()))
|
||||
.thenThrow(new java.io.IOException("Connection failed"));
|
||||
|
||||
// Should return null on failure
|
||||
String result = service.downloadImage("http://example.com/image.jpg", 123, 456);
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should create directory structure for images")
|
||||
void testDirectoryCreation() {
|
||||
// Create test directory structure
|
||||
var testDir = Paths.get("C:", "mnt", "okcomputer", "output", "images", "999", "888");
|
||||
|
||||
// Ensure parent exists for test
|
||||
try {
|
||||
Files.createDirectories(testDir.getParent());
|
||||
assertTrue(Files.exists(testDir.getParent()));
|
||||
} catch (Exception e) {
|
||||
// Skip test if cannot create directories (permissions issue)
|
||||
Assumptions.assumeTrue(false, "Cannot create test directories");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should save detected objects to database")
|
||||
void testSaveDetectedObjects() throws SQLException {
|
||||
// Capture what's saved to database
|
||||
ArgumentCaptor<Integer> lotIdCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||
ArgumentCaptor<String> urlCaptor = ArgumentCaptor.forClass(String.class);
|
||||
ArgumentCaptor<String> filePathCaptor = ArgumentCaptor.forClass(String.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<List<String>> labelsCaptor = ArgumentCaptor.forClass(List.class);
|
||||
|
||||
when(mockDetector.detectObjects(anyString()))
|
||||
.thenReturn(List.of("truck", "machinery"));
|
||||
|
||||
doNothing().when(mockDb).insertImage(
|
||||
lotIdCaptor.capture(),
|
||||
urlCaptor.capture(),
|
||||
filePathCaptor.capture(),
|
||||
labelsCaptor.capture()
|
||||
);
|
||||
|
||||
// Simulate processing (won't actually download without real server)
|
||||
// In real test, you'd mock the HTTP client
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle empty image list")
|
||||
void testProcessEmptyImageList() throws SQLException {
|
||||
service.processImagesForLot(123, 456, List.of());
|
||||
|
||||
// Should not call database insert
|
||||
verify(mockDb, never()).insertImage(anyInt(), anyString(), anyString(), anyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should process pending images from database")
|
||||
void testProcessPendingImages() throws SQLException {
|
||||
when(mockDb.getAllLots()).thenReturn(List.of(
|
||||
new Lot(111, 222, "Test Lot 1", "Desc", "", "", 0, "Cat",
|
||||
100.0, "EUR", "https://example.com", null, false),
|
||||
new Lot(333, 444, "Test Lot 2", "Desc", "", "", 0, "Cat",
|
||||
200.0, "EUR", "https://example.com", null, false)
|
||||
));
|
||||
|
||||
when(mockDb.getImagesForLot(anyLong())).thenReturn(List.of());
|
||||
|
||||
service.processPendingImages();
|
||||
|
||||
// Verify lots were queried
|
||||
verify(mockDb, times(1)).getAllLots();
|
||||
verify(mockDb, times(2)).getImagesForLot(anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should skip lots that already have images")
|
||||
void testSkipLotsWithExistingImages() throws SQLException {
|
||||
when(mockDb.getAllLots()).thenReturn(List.of(
|
||||
new Lot(111, 222, "Test Lot", "Desc", "", "", 0, "Cat",
|
||||
100.0, "EUR", "https://example.com", null, false)
|
||||
));
|
||||
|
||||
// Return existing images
|
||||
when(mockDb.getImagesForLot(222)).thenReturn(List.of(
|
||||
new DatabaseService.ImageRecord(1, 222, "http://example.com/img.jpg",
|
||||
"C:/images/img.jpg", "car,vehicle")
|
||||
));
|
||||
|
||||
service.processPendingImages();
|
||||
|
||||
verify(mockDb).getImagesForLot(222);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle database errors during image save")
|
||||
void testDatabaseErrorHandling() throws Exception {
|
||||
// Mock successful HTTP download
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
var mockResponse = (java.net.http.HttpResponse<byte[]>) mock(java.net.http.HttpResponse.class);
|
||||
when(mockResponse.statusCode()).thenReturn(200);
|
||||
when(mockResponse.body()).thenReturn(new byte[]{1, 2, 3});
|
||||
when(mockHttpClient.sendGetBytes(anyString())).thenReturn(mockResponse);
|
||||
|
||||
when(mockDetector.detectObjects(anyString()))
|
||||
.thenReturn(List.of("object"));
|
||||
|
||||
doThrow(new SQLException("Database error"))
|
||||
.when(mockDb).insertImage(anyInt(), anyString(), anyString(), anyList());
|
||||
|
||||
// Should not throw exception, but handle error
|
||||
assertDoesNotThrow(() ->
|
||||
service.processImagesForLot(123, 456, List.of("https://example.com/test.jpg"))
|
||||
);
|
||||
// Verify success
|
||||
assertTrue(result);
|
||||
verify(mockDetector).detectObjects("/path/to/image.jpg");
|
||||
verify(mockDb).updateImageLabels(1, List.of("car", "vehicle"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle empty detection results")
|
||||
void testEmptyDetectionResults() throws SQLException {
|
||||
void testProcessImageWithNoDetections() throws SQLException {
|
||||
when(mockDetector.detectObjects(anyString()))
|
||||
.thenReturn(List.of());
|
||||
|
||||
doNothing().when(mockDb).insertImage(anyInt(), anyString(), anyString(), anyList());
|
||||
boolean result = service.processImage(2, "/path/to/image2.jpg", 12346);
|
||||
|
||||
// Should still save to database with empty labels
|
||||
// (In real scenario with actual download)
|
||||
assertTrue(result);
|
||||
verify(mockDb).updateImageLabels(2, List.of());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle lots with no existing images")
|
||||
void testLotsWithNoImages() throws SQLException {
|
||||
when(mockDb.getAllLots()).thenReturn(List.of(
|
||||
new Lot(555, 666, "New Lot", "Desc", "", "", 0, "Cat",
|
||||
100.0, "EUR", "https://example.com", null, false)
|
||||
@DisplayName("Should handle database error gracefully")
|
||||
void testProcessImageDatabaseError() throws SQLException {
|
||||
when(mockDetector.detectObjects(anyString()))
|
||||
.thenReturn(List.of("object"));
|
||||
|
||||
doThrow(new SQLException("Database error"))
|
||||
.when(mockDb).updateImageLabels(anyInt(), anyList());
|
||||
|
||||
// Should return false on error
|
||||
boolean result = service.processImage(3, "/path/to/image3.jpg", 12347);
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle object detection error gracefully")
|
||||
void testProcessImageDetectionError() {
|
||||
when(mockDetector.detectObjects(anyString()))
|
||||
.thenThrow(new RuntimeException("Detection failed"));
|
||||
|
||||
// Should return false on error
|
||||
boolean result = service.processImage(4, "/path/to/image4.jpg", 12348);
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should process pending images batch")
|
||||
void testProcessPendingImages() throws SQLException {
|
||||
// Mock pending images from database
|
||||
when(mockDb.getImagesNeedingDetection()).thenReturn(List.of(
|
||||
new DatabaseService.ImageDetectionRecord(1, 100L, "/images/100/001.jpg"),
|
||||
new DatabaseService.ImageDetectionRecord(2, 101L, "/images/101/001.jpg")
|
||||
));
|
||||
|
||||
when(mockDb.getImagesForLot(666)).thenReturn(List.of());
|
||||
when(mockDetector.detectObjects(anyString()))
|
||||
.thenReturn(List.of("item1"))
|
||||
.thenReturn(List.of("item2"));
|
||||
|
||||
when(mockDb.getImageLabels(anyInt()))
|
||||
.thenReturn(List.of("item1"))
|
||||
.thenReturn(List.of("item2"));
|
||||
|
||||
// Process batch
|
||||
service.processPendingImages();
|
||||
|
||||
// Verify all images were processed
|
||||
verify(mockDb).getImagesNeedingDetection();
|
||||
verify(mockDetector, times(2)).detectObjects(anyString());
|
||||
verify(mockDb, times(2)).updateImageLabels(anyInt(), anyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle empty pending images list")
|
||||
void testProcessPendingImagesEmpty() throws SQLException {
|
||||
when(mockDb.getImagesNeedingDetection()).thenReturn(List.of());
|
||||
|
||||
service.processPendingImages();
|
||||
|
||||
verify(mockDb).getImagesForLot(666);
|
||||
verify(mockDb).getImagesNeedingDetection();
|
||||
verify(mockDetector, never()).detectObjects(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should continue processing after single image failure")
|
||||
void testProcessPendingImagesWithFailure() throws SQLException {
|
||||
when(mockDb.getImagesNeedingDetection()).thenReturn(List.of(
|
||||
new DatabaseService.ImageDetectionRecord(1, 100L, "/images/100/001.jpg"),
|
||||
new DatabaseService.ImageDetectionRecord(2, 101L, "/images/101/001.jpg")
|
||||
));
|
||||
|
||||
// First image fails, second succeeds
|
||||
when(mockDetector.detectObjects("/images/100/001.jpg"))
|
||||
.thenThrow(new RuntimeException("Detection error"));
|
||||
when(mockDetector.detectObjects("/images/101/001.jpg"))
|
||||
.thenReturn(List.of("item"));
|
||||
|
||||
when(mockDb.getImageLabels(2))
|
||||
.thenReturn(List.of("item"));
|
||||
|
||||
service.processPendingImages();
|
||||
|
||||
// Verify second image was still processed
|
||||
verify(mockDetector, times(2)).detectObjects(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle database query error in batch processing")
|
||||
void testProcessPendingImagesDatabaseError() throws SQLException {
|
||||
when(mockDb.getImagesNeedingDetection())
|
||||
.thenThrow(new SQLException("Database connection failed"));
|
||||
|
||||
// Should not throw exception
|
||||
assertDoesNotThrow(() -> service.processPendingImages());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should process images with multiple detected objects")
|
||||
void testProcessImageMultipleDetections() throws SQLException {
|
||||
when(mockDetector.detectObjects(anyString()))
|
||||
.thenReturn(List.of("car", "truck", "vehicle", "road"));
|
||||
|
||||
boolean result = service.processImage(5, "/path/to/image5.jpg", 12349);
|
||||
|
||||
assertTrue(result);
|
||||
verify(mockDb).updateImageLabels(5, List.of("car", "truck", "vehicle", "road"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,8 +48,7 @@ class IntegrationTest {
|
||||
"non_existent.txt"
|
||||
);
|
||||
|
||||
var httpClient = new RateLimitedHttpClient();
|
||||
imageProcessor = new ImageProcessingService(db, detector, httpClient);
|
||||
imageProcessor = new ImageProcessingService(db, detector);
|
||||
|
||||
monitor = new TroostwijkMonitor(
|
||||
testDbPath,
|
||||
|
||||
Reference in New Issue
Block a user