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:
Tour
2025-12-06 21:34:29 +01:00
parent 6091b7180f
commit e216a763ac
3 changed files with 135 additions and 172 deletions

View File

@@ -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
*/

View File

@@ -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"));
}
}

View File

@@ -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,