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
Former-commit-id: e216a763ac
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
|
* Updates the labels field for an image after object detection
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
package auctiora;
|
package auctiora;
|
||||||
|
|
||||||
import org.junit.jupiter.api.*;
|
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.sql.SQLException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -13,205 +10,156 @@ import static org.mockito.Mockito.*;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Test cases for ImageProcessingService.
|
* 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 {
|
class ImageProcessingServiceTest {
|
||||||
|
|
||||||
private DatabaseService mockDb;
|
private DatabaseService mockDb;
|
||||||
private ObjectDetectionService mockDetector;
|
private ObjectDetectionService mockDetector;
|
||||||
private RateLimitedHttpClient mockHttpClient;
|
|
||||||
private ImageProcessingService service;
|
private ImageProcessingService service;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
mockDb = mock(DatabaseService.class);
|
mockDb = mock(DatabaseService.class);
|
||||||
mockDetector = mock(ObjectDetectionService.class);
|
mockDetector = mock(ObjectDetectionService.class);
|
||||||
mockHttpClient = mock(RateLimitedHttpClient.class);
|
service = new ImageProcessingService(mockDb, mockDetector);
|
||||||
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
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("Should process images for lot with object detection")
|
@DisplayName("Should process single image and update labels")
|
||||||
void testProcessImagesForLot() throws SQLException {
|
void testProcessImage() throws SQLException {
|
||||||
when(mockDetector.detectObjects(anyString()))
|
// Mock object detection
|
||||||
|
when(mockDetector.detectObjects("/path/to/image.jpg"))
|
||||||
.thenReturn(List.of("car", "vehicle"));
|
.thenReturn(List.of("car", "vehicle"));
|
||||||
|
|
||||||
// Note: This test uses mock URLs and won't actually download
|
// Process image
|
||||||
// In a real scenario, you'd use a test HTTP server
|
boolean result = service.processImage(1, "/path/to/image.jpg", 12345);
|
||||||
var imageUrls = List.of("https://example.com/test1.jpg");
|
|
||||||
|
|
||||||
// Mock successful processing
|
// Verify success
|
||||||
doNothing().when(mockDb).insertImage(anyInt(), anyString(), anyString(), anyList());
|
assertTrue(result);
|
||||||
|
verify(mockDetector).detectObjects("/path/to/image.jpg");
|
||||||
// Process images
|
verify(mockDb).updateImageLabels(1, List.of("car", "vehicle"));
|
||||||
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"))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("Should handle empty detection results")
|
@DisplayName("Should handle empty detection results")
|
||||||
void testEmptyDetectionResults() throws SQLException {
|
void testProcessImageWithNoDetections() throws SQLException {
|
||||||
when(mockDetector.detectObjects(anyString()))
|
when(mockDetector.detectObjects(anyString()))
|
||||||
.thenReturn(List.of());
|
.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
|
assertTrue(result);
|
||||||
// (In real scenario with actual download)
|
verify(mockDb).updateImageLabels(2, List.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("Should handle lots with no existing images")
|
@DisplayName("Should handle database error gracefully")
|
||||||
void testLotsWithNoImages() throws SQLException {
|
void testProcessImageDatabaseError() throws SQLException {
|
||||||
when(mockDb.getAllLots()).thenReturn(List.of(
|
when(mockDetector.detectObjects(anyString()))
|
||||||
new Lot(555, 666, "New Lot", "Desc", "", "", 0, "Cat",
|
.thenReturn(List.of("object"));
|
||||||
100.0, "EUR", "https://example.com", null, false)
|
|
||||||
|
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();
|
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"
|
"non_existent.txt"
|
||||||
);
|
);
|
||||||
|
|
||||||
var httpClient = new RateLimitedHttpClient();
|
imageProcessor = new ImageProcessingService(db, detector);
|
||||||
imageProcessor = new ImageProcessingService(db, detector, httpClient);
|
|
||||||
|
|
||||||
monitor = new TroostwijkMonitor(
|
monitor = new TroostwijkMonitor(
|
||||||
testDbPath,
|
testDbPath,
|
||||||
|
|||||||
Reference in New Issue
Block a user