Fix mock tests

This commit is contained in:
Tour
2025-12-04 20:14:28 +01:00
parent 2ff6fcca17
commit ff8f5f2c1a
7 changed files with 408 additions and 366 deletions

View File

@@ -24,7 +24,7 @@ public class AuctionMonitorProducer {
@ConfigProperty(name = "auction.database.path") String dbPath) throws SQLException { @ConfigProperty(name = "auction.database.path") String dbPath) throws SQLException {
LOG.infof("Initializing DatabaseService with path: %s", dbPath); LOG.infof("Initializing DatabaseService with path: %s", dbPath);
DatabaseService db = new DatabaseService(dbPath); var db = new DatabaseService(dbPath);
db.ensureSchema(); db.ensureSchema();
return db; return db;
} }

View File

@@ -20,343 +20,343 @@ import java.util.Map;
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
public class AuctionMonitorResource { public class AuctionMonitorResource {
private static final Logger LOG = Logger.getLogger(AuctionMonitorResource.class); private static final Logger LOG = Logger.getLogger(AuctionMonitorResource.class);
@Inject @Inject
DatabaseService db; DatabaseService db;
@Inject @Inject
QuarkusWorkflowScheduler scheduler; QuarkusWorkflowScheduler scheduler;
@Inject @Inject
NotificationService notifier; NotificationService notifier;
@Inject @Inject
RateLimitedHttpClient httpClient; RateLimitedHttpClient httpClient;
/** /**
* GET /api/monitor/status * GET /api/monitor/status
* Returns current monitoring status * Returns current monitoring status
*/ */
@GET @GET
@Path("/status") @Path("/status")
public Response getStatus() { public Response getStatus() {
try { try {
Map<String, Object> status = new HashMap<>(); Map<String, Object> status = new HashMap<>();
status.put("running", true); status.put("running", true);
status.put("auctions", db.getAllAuctions().size()); status.put("auctions", db.getAllAuctions().size());
status.put("lots", db.getAllLots().size()); status.put("lots", db.getAllLots().size());
status.put("images", db.getImageCount()); status.put("images", db.getImageCount());
// Count closing soon // Count closing soon
int closingSoon = 0; var closingSoon = 0;
for (var lot : db.getAllLots()) { for (var lot : db.getAllLots()) {
if (lot.closingTime() != null && lot.minutesUntilClose() < 30) { if (lot.closingTime() != null && lot.minutesUntilClose() < 30) {
closingSoon++; closingSoon++;
}
} }
status.put("closingSoon", closingSoon); }
status.put("closingSoon", closingSoon);
return Response.ok(status).build();
return Response.ok(status).build();
} catch (Exception e) {
LOG.error("Failed to get status", e); } catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR) LOG.error("Failed to get status", e);
.entity(Map.of("error", e.getMessage())) return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.build(); .entity(Map.of("error", e.getMessage()))
} .build();
} }
}
/**
* GET /api/monitor/statistics /**
* Returns detailed statistics * GET /api/monitor/statistics
*/ * Returns detailed statistics
@GET */
@Path("/statistics") @GET
public Response getStatistics() { @Path("/statistics")
try { public Response getStatistics() {
Map<String, Object> stats = new HashMap<>(); try {
Map<String, Object> stats = new HashMap<>();
var auctions = db.getAllAuctions();
var lots = db.getAllLots(); var auctions = db.getAllAuctions();
var lots = db.getAllLots();
stats.put("totalAuctions", auctions.size());
stats.put("totalLots", lots.size()); stats.put("totalAuctions", auctions.size());
stats.put("totalImages", db.getImageCount()); stats.put("totalLots", lots.size());
stats.put("totalImages", db.getImageCount());
// Lot statistics
int activeLots = 0; // Lot statistics
int lotsWithBids = 0; var activeLots = 0;
double totalBids = 0; var lotsWithBids = 0;
double totalBids = 0;
for (var lot : lots) {
if (lot.closingTime() != null && lot.minutesUntilClose() > 0) { for (var lot : lots) {
activeLots++; if (lot.closingTime() != null && lot.minutesUntilClose() > 0) {
} activeLots++;
if (lot.currentBid() > 0) {
lotsWithBids++;
totalBids += lot.currentBid();
}
} }
if (lot.currentBid() > 0) {
stats.put("activeLots", activeLots); lotsWithBids++;
stats.put("lotsWithBids", lotsWithBids); totalBids += lot.currentBid();
stats.put("totalBidValue", String.format("€%.2f", totalBids)); }
stats.put("averageBid", lotsWithBids > 0 ? String.format("€%.2f", totalBids / lotsWithBids) : "€0.00"); }
return Response.ok(stats).build(); stats.put("activeLots", activeLots);
stats.put("lotsWithBids", lotsWithBids);
} catch (Exception e) { stats.put("totalBidValue", String.format("€%.2f", totalBids));
LOG.error("Failed to get statistics", e); stats.put("averageBid", lotsWithBids > 0 ? String.format("€%.2f", totalBids / lotsWithBids) : "€0.00");
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", e.getMessage())) return Response.ok(stats).build();
.build();
} } catch (Exception e) {
} LOG.error("Failed to get statistics", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
/** .entity(Map.of("error", e.getMessage()))
* POST /api/monitor/trigger/scraper-import .build();
* Manually trigger scraper import workflow }
*/ }
@POST
@Path("/trigger/scraper-import") /**
public Response triggerScraperImport() { * POST /api/monitor/trigger/scraper-import
try { * Manually trigger scraper import workflow
scheduler.importScraperData(); */
return Response.ok(Map.of("message", "Scraper import triggered successfully")).build(); @POST
} catch (Exception e) { @Path("/trigger/scraper-import")
LOG.error("Failed to trigger scraper import", e); public Response triggerScraperImport() {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR) try {
.entity(Map.of("error", e.getMessage())) scheduler.importScraperData();
.build(); return Response.ok(Map.of("message", "Scraper import triggered successfully")).build();
} } catch (Exception e) {
} LOG.error("Failed to trigger scraper import", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
/** .entity(Map.of("error", e.getMessage()))
* POST /api/monitor/trigger/image-processing .build();
* Manually trigger image processing workflow }
*/ }
@POST
@Path("/trigger/image-processing") /**
public Response triggerImageProcessing() { * POST /api/monitor/trigger/image-processing
try { * Manually trigger image processing workflow
scheduler.processImages(); */
return Response.ok(Map.of("message", "Image processing triggered successfully")).build(); @POST
} catch (Exception e) { @Path("/trigger/image-processing")
LOG.error("Failed to trigger image processing", e); public Response triggerImageProcessing() {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR) try {
.entity(Map.of("error", e.getMessage())) scheduler.processImages();
.build(); return Response.ok(Map.of("message", "Image processing triggered successfully")).build();
} } catch (Exception e) {
} LOG.error("Failed to trigger image processing", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
/** .entity(Map.of("error", e.getMessage()))
* POST /api/monitor/trigger/bid-monitoring .build();
* Manually trigger bid monitoring workflow }
*/ }
@POST
@Path("/trigger/bid-monitoring") /**
public Response triggerBidMonitoring() { * POST /api/monitor/trigger/bid-monitoring
try { * Manually trigger bid monitoring workflow
scheduler.monitorBids(); */
return Response.ok(Map.of("message", "Bid monitoring triggered successfully")).build(); @POST
} catch (Exception e) { @Path("/trigger/bid-monitoring")
LOG.error("Failed to trigger bid monitoring", e); public Response triggerBidMonitoring() {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR) try {
.entity(Map.of("error", e.getMessage())) scheduler.monitorBids();
.build(); return Response.ok(Map.of("message", "Bid monitoring triggered successfully")).build();
} } catch (Exception e) {
} LOG.error("Failed to trigger bid monitoring", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
/** .entity(Map.of("error", e.getMessage()))
* POST /api/monitor/trigger/closing-alerts .build();
* Manually trigger closing alerts workflow }
*/ }
@POST
@Path("/trigger/closing-alerts") /**
public Response triggerClosingAlerts() { * POST /api/monitor/trigger/closing-alerts
try { * Manually trigger closing alerts workflow
scheduler.checkClosingTimes(); */
return Response.ok(Map.of("message", "Closing alerts triggered successfully")).build(); @POST
} catch (Exception e) { @Path("/trigger/closing-alerts")
LOG.error("Failed to trigger closing alerts", e); public Response triggerClosingAlerts() {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR) try {
.entity(Map.of("error", e.getMessage())) scheduler.checkClosingTimes();
.build(); return Response.ok(Map.of("message", "Closing alerts triggered successfully")).build();
} } catch (Exception e) {
} LOG.error("Failed to trigger closing alerts", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
/** .entity(Map.of("error", e.getMessage()))
* GET /api/monitor/auctions .build();
* Returns list of all auctions }
*/ }
@GET
@Path("/auctions") /**
public Response getAuctions(@QueryParam("country") String country) { * GET /api/monitor/auctions
try { * Returns list of all auctions
var auctions = country != null && !country.isEmpty() */
? db.getAuctionsByCountry(country) @GET
: db.getAllAuctions(); @Path("/auctions")
public Response getAuctions(@QueryParam("country") String country) {
return Response.ok(auctions).build(); try {
} catch (Exception e) { var auctions = country != null && !country.isEmpty()
LOG.error("Failed to get auctions", e); ? db.getAuctionsByCountry(country)
return Response.status(Response.Status.INTERNAL_SERVER_ERROR) : db.getAllAuctions();
.entity(Map.of("error", e.getMessage()))
.build(); return Response.ok(auctions).build();
} } catch (Exception e) {
} LOG.error("Failed to get auctions", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
/** .entity(Map.of("error", e.getMessage()))
* GET /api/monitor/lots .build();
* Returns list of active lots }
*/ }
@GET
@Path("/lots") /**
public Response getActiveLots() { * GET /api/monitor/lots
try { * Returns list of active lots
var lots = db.getActiveLots(); */
return Response.ok(lots).build(); @GET
} catch (Exception e) { @Path("/lots")
LOG.error("Failed to get lots", e); public Response getActiveLots() {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR) try {
.entity(Map.of("error", e.getMessage())) var lots = db.getActiveLots();
.build(); return Response.ok(lots).build();
} } catch (Exception e) {
} LOG.error("Failed to get lots", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
/** .entity(Map.of("error", e.getMessage()))
* GET /api/monitor/lots/closing-soon .build();
* Returns lots closing within specified minutes (default 30) }
*/ }
@GET
@Path("/lots/closing-soon") /**
public Response getLotsClosingSoon(@QueryParam("minutes") @DefaultValue("30") int minutes) { * GET /api/monitor/lots/closing-soon
try { * Returns lots closing within specified minutes (default 30)
var allLots = db.getActiveLots(); */
var closingSoon = allLots.stream() @GET
.filter(lot -> lot.closingTime() != null && lot.minutesUntilClose() < minutes) @Path("/lots/closing-soon")
.toList(); public Response getLotsClosingSoon(@QueryParam("minutes") @DefaultValue("30") int minutes) {
try {
return Response.ok(closingSoon).build(); var allLots = db.getActiveLots();
} catch (Exception e) { var closingSoon = allLots.stream()
LOG.error("Failed to get closing lots", e); .filter(lot -> lot.closingTime() != null && lot.minutesUntilClose() < minutes)
return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .toList();
.entity(Map.of("error", e.getMessage()))
.build(); return Response.ok(closingSoon).build();
} } catch (Exception e) {
} LOG.error("Failed to get closing lots", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
/** .entity(Map.of("error", e.getMessage()))
* GET /api/monitor/lots/{lotId}/images .build();
* Returns images for a specific lot }
*/ }
@GET
@Path("/lots/{lotId}/images") /**
public Response getLotImages(@PathParam("lotId") int lotId) { * GET /api/monitor/lots/{lotId}/images
try { * Returns images for a specific lot
var images = db.getImagesForLot(lotId); */
return Response.ok(images).build(); @GET
} catch (Exception e) { @Path("/lots/{lotId}/images")
LOG.error("Failed to get lot images", e); public Response getLotImages(@PathParam("lotId") int lotId) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR) try {
.entity(Map.of("error", e.getMessage())) var images = db.getImagesForLot(lotId);
.build(); return Response.ok(images).build();
} } catch (Exception e) {
} LOG.error("Failed to get lot images", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
/** .entity(Map.of("error", e.getMessage()))
* POST /api/monitor/test-notification .build();
* Send a test notification }
*/ }
@POST
@Path("/test-notification") /**
public Response sendTestNotification(Map<String, String> request) { * POST /api/monitor/test-notification
try { * Send a test notification
String message = request.getOrDefault("message", "Test notification from Auction Monitor"); */
String title = request.getOrDefault("title", "Test Notification"); @POST
int priority = Integer.parseInt(request.getOrDefault("priority", "0")); @Path("/test-notification")
public Response sendTestNotification(Map<String, String> request) {
notifier.sendNotification(message, title, priority); try {
var message = request.getOrDefault("message", "Test notification from Auction Monitor");
return Response.ok(Map.of("message", "Test notification sent successfully")).build(); var title = request.getOrDefault("title", "Test Notification");
} catch (Exception e) { var priority = Integer.parseInt(request.getOrDefault("priority", "0"));
LOG.error("Failed to send test notification", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR) notifier.sendNotification(message, title, priority);
.entity(Map.of("error", e.getMessage()))
.build(); return Response.ok(Map.of("message", "Test notification sent successfully")).build();
} } catch (Exception e) {
} LOG.error("Failed to send test notification", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
/** .entity(Map.of("error", e.getMessage()))
* GET /api/monitor/rate-limit/stats .build();
* Returns HTTP rate limiting statistics for all hosts }
*/ }
@GET
@Path("/rate-limit/stats") /**
public Response getRateLimitStats() { * GET /api/monitor/rate-limit/stats
try { * Returns HTTP rate limiting statistics for all hosts
var stats = httpClient.getAllStats(); */
@GET
Map<String, Object> response = new HashMap<>(); @Path("/rate-limit/stats")
response.put("hosts", stats.size()); public Response getRateLimitStats() {
try {
Map<String, Object> hostStats = new HashMap<>(); var stats = httpClient.getAllStats();
for (var entry : stats.entrySet()) {
var stat = entry.getValue(); Map<String, Object> response = new HashMap<>();
hostStats.put(entry.getKey(), Map.of( response.put("hosts", stats.size());
Map<String, Object> hostStats = new HashMap<>();
for (var entry : stats.entrySet()) {
var stat = entry.getValue();
hostStats.put(entry.getKey(), Map.of(
"totalRequests", stat.getTotalRequests(), "totalRequests", stat.getTotalRequests(),
"successfulRequests", stat.getSuccessfulRequests(), "successfulRequests", stat.getSuccessfulRequests(),
"failedRequests", stat.getFailedRequests(), "failedRequests", stat.getFailedRequests(),
"rateLimitedRequests", stat.getRateLimitedRequests(), "rateLimitedRequests", stat.getRateLimitedRequests(),
"averageDurationMs", stat.getAverageDurationMs() "averageDurationMs", stat.getAverageDurationMs()
)); ));
} }
response.put("statistics", hostStats); response.put("statistics", hostStats);
return Response.ok(response).build(); return Response.ok(response).build();
} catch (Exception e) { } catch (Exception e) {
LOG.error("Failed to get rate limit stats", e); LOG.error("Failed to get rate limit stats", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR) return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", e.getMessage())) .entity(Map.of("error", e.getMessage()))
.build(); .build();
} }
} }
/** /**
* GET /api/monitor/rate-limit/stats/{host} * GET /api/monitor/rate-limit/stats/{host}
* Returns HTTP rate limiting statistics for a specific host * Returns HTTP rate limiting statistics for a specific host
*/ */
@GET @GET
@Path("/rate-limit/stats/{host}") @Path("/rate-limit/stats/{host}")
public Response getRateLimitStatsForHost(@PathParam("host") String host) { public Response getRateLimitStatsForHost(@PathParam("host") String host) {
try { try {
var stat = httpClient.getStats(host); var stat = httpClient.getStats(host);
if (stat == null) { if (stat == null) {
return Response.status(Response.Status.NOT_FOUND) return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", "No statistics found for host: " + host)) .entity(Map.of("error", "No statistics found for host: " + host))
.build(); .build();
} }
Map<String, Object> response = Map.of( Map<String, Object> response = Map.of(
"host", stat.getHost(), "host", stat.getHost(),
"totalRequests", stat.getTotalRequests(), "totalRequests", stat.getTotalRequests(),
"successfulRequests", stat.getSuccessfulRequests(), "successfulRequests", stat.getSuccessfulRequests(),
"failedRequests", stat.getFailedRequests(), "failedRequests", stat.getFailedRequests(),
"rateLimitedRequests", stat.getRateLimitedRequests(), "rateLimitedRequests", stat.getRateLimitedRequests(),
"averageDurationMs", stat.getAverageDurationMs() "averageDurationMs", stat.getAverageDurationMs()
); );
return Response.ok(response).build(); return Response.ok(response).build();
} catch (Exception e) { } catch (Exception e) {
LOG.error("Failed to get rate limit stats for host", e); LOG.error("Failed to get rate limit stats for host", e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR) return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(Map.of("error", e.getMessage())) .entity(Map.of("error", e.getMessage()))
.build(); .build();
} }
} }
} }

View File

@@ -1,6 +1,10 @@
package auctiora; package auctiora;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import java.io.Console; import java.io.Console;
import java.sql.DriverManager; import java.sql.DriverManager;
import java.sql.SQLException; import java.sql.SQLException;
@@ -16,9 +20,9 @@ import java.util.List;
*/ */
@Slf4j @Slf4j
public class DatabaseService { public class DatabaseService {
private final String url; private final String url;
DatabaseService(String dbPath) { DatabaseService(String dbPath) {
this.url = "jdbc:sqlite:" + dbPath; this.url = "jdbc:sqlite:" + dbPath;
} }
@@ -43,7 +47,7 @@ public class DatabaseService {
closing_time TEXT, closing_time TEXT,
discovered_at INTEGER discovered_at INTEGER
)"""); )""");
// Lots table (populated by external scraper) // Lots table (populated by external scraper)
stmt.execute(""" stmt.execute("""
CREATE TABLE IF NOT EXISTS lots ( CREATE TABLE IF NOT EXISTS lots (
@@ -62,7 +66,7 @@ public class DatabaseService {
closing_notified INTEGER DEFAULT 0, closing_notified INTEGER DEFAULT 0,
FOREIGN KEY (sale_id) REFERENCES auctions(auction_id) FOREIGN KEY (sale_id) REFERENCES auctions(auction_id)
)"""); )""");
// Images table (populated by this process) // Images table (populated by this process)
stmt.execute(""" stmt.execute("""
CREATE TABLE IF NOT EXISTS images ( CREATE TABLE IF NOT EXISTS images (
@@ -74,13 +78,40 @@ public class DatabaseService {
processed_at INTEGER, processed_at INTEGER,
FOREIGN KEY (lot_id) REFERENCES lots(lot_id) FOREIGN KEY (lot_id) REFERENCES lots(lot_id)
)"""); )""");
// Migrate existing tables to add missing columns
migrateSchema(stmt);
// Indexes for performance // Indexes for performance
stmt.execute("CREATE INDEX IF NOT EXISTS idx_auctions_country ON auctions(country)"); stmt.execute("CREATE INDEX IF NOT EXISTS idx_auctions_country ON auctions(country)");
stmt.execute("CREATE INDEX IF NOT EXISTS idx_lots_sale_id ON lots(sale_id)"); stmt.execute("CREATE INDEX IF NOT EXISTS idx_lots_sale_id ON lots(sale_id)");
stmt.execute("CREATE INDEX IF NOT EXISTS idx_images_lot_id ON images(lot_id)"); stmt.execute("CREATE INDEX IF NOT EXISTS idx_images_lot_id ON images(lot_id)");
} }
} }
/**
* Migrates existing database schema to add new columns.
* SQLite doesn't support DROP COLUMN, so we add columns with ALTER TABLE ADD COLUMN.
*/
private void migrateSchema(java.sql.Statement stmt) throws SQLException {
// Check if country column exists in auctions table
try (var rs = stmt.executeQuery("PRAGMA table_info(auctions)")) {
boolean hasCountry = false;
while (rs.next()) {
if ("country".equals(rs.getString("name"))) {
hasCountry = true;
break;
}
}
if (!hasCountry) {
log.info("Migrating schema: Adding 'country' column to auctions table");
stmt.execute("ALTER TABLE auctions ADD COLUMN country TEXT");
}
} catch (SQLException e) {
// Table might not exist yet, which is fine
log.debug("Could not check auctions table schema: " + e.getMessage());
}
}
/** /**
* Inserts or updates an auction record (typically called by external scraper) * Inserts or updates an auction record (typically called by external scraper)
@@ -355,7 +386,7 @@ public class DatabaseService {
} }
} catch (SQLException e) { } catch (SQLException e) {
// Table might not exist in scraper format - that's ok // Table might not exist in scraper format - that's ok
IO.println(" Scraper auction table not found or incompatible schema"); log.info(" Scraper auction table not found or incompatible schema");
} }
return imported; return imported;

View File

@@ -1,5 +1,8 @@
package auctiora; package auctiora;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
@@ -16,11 +19,11 @@ import java.util.List;
*/ */
@Slf4j @Slf4j
class ImageProcessingService { class ImageProcessingService {
private final RateLimitedHttpClient httpClient; private final RateLimitedHttpClient httpClient;
private final DatabaseService db; private final DatabaseService db;
private final ObjectDetectionService detector; private final ObjectDetectionService detector;
ImageProcessingService(DatabaseService db, ObjectDetectionService detector, RateLimitedHttpClient httpClient) { ImageProcessingService(DatabaseService db, ObjectDetectionService detector, RateLimitedHttpClient httpClient) {
this.httpClient = httpClient; this.httpClient = httpClient;
this.db = db; this.db = db;

View File

@@ -1,5 +1,9 @@
package auctiora; package auctiora;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import javax.mail.*; import javax.mail.*;
import javax.mail.internet.*; import javax.mail.internet.*;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -9,9 +13,9 @@ import java.util.Properties;
@Slf4j @Slf4j
public class NotificationService { public class NotificationService {
private final Config config; private final Config config;
public NotificationService(String cfg) { public NotificationService(String cfg) {
this.config = Config.parse(cfg); this.config = Config.parse(cfg);
} }

View File

@@ -1,5 +1,9 @@
package auctiora; package auctiora;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.opencv.core.Mat; import org.opencv.core.Mat;
import org.opencv.core.Scalar; import org.opencv.core.Scalar;
@@ -29,11 +33,11 @@ import static org.opencv.dnn.Dnn.DNN_TARGET_CPU;
*/ */
@Slf4j @Slf4j
public class ObjectDetectionService { public class ObjectDetectionService {
private final Net net; private final Net net;
private final List<String> classNames; private final List<String> classNames;
private final boolean enabled; private final boolean enabled;
ObjectDetectionService(String cfgPath, String weightsPath, String classNamesPath) throws IOException { ObjectDetectionService(String cfgPath, String weightsPath, String classNamesPath) throws IOException {
// Check if model files exist // Check if model files exist
var cfgFile = Paths.get(cfgPath); var cfgFile = Paths.get(cfgPath);

View File

@@ -47,8 +47,8 @@ class IntegrationTest {
"non_existent.weights", "non_existent.weights",
"non_existent.txt" "non_existent.txt"
); );
RateLimitedHttpClient httpClient = new RateLimitedHttpClient(); var httpClient = new RateLimitedHttpClient();
imageProcessor = new ImageProcessingService(db, detector, httpClient); imageProcessor = new ImageProcessingService(db, detector, httpClient);
monitor = new TroostwijkMonitor( monitor = new TroostwijkMonitor(
@@ -170,8 +170,8 @@ class IntegrationTest {
db.updateLotCurrentBid(updatedLot); db.updateLotCurrentBid(updatedLot);
// Send notification // Send notification
String message = String.format("Nieuw bod op kavel %d: €%.2f (was €%.2f)", var message = String.format("Nieuw bod op kavel %d: €%.2f (was €%.2f)",
lot.lotId(), 2000.00, 1500.00); lot.lotId(), 2000.00, 1500.00);
assertDoesNotThrow(() -> assertDoesNotThrow(() ->
notifier.sendNotification(message, "Kavel bieding update", 0) notifier.sendNotification(message, "Kavel bieding update", 0)
@@ -212,7 +212,7 @@ class IntegrationTest {
assertTrue(closingSoon.minutesUntilClose() < 5); assertTrue(closingSoon.minutesUntilClose() < 5);
// Send high-priority notification // Send high-priority notification
String message = "Kavel " + closingSoon.lotId() + " sluit binnen 5 min."; var message = "Kavel " + closingSoon.lotId() + " sluit binnen 5 min.";
assertDoesNotThrow(() -> assertDoesNotThrow(() ->
notifier.sendNotification(message, "Lot nearing closure", 1) notifier.sendNotification(message, "Lot nearing closure", 1)
); );
@@ -281,7 +281,7 @@ class IntegrationTest {
assertDoesNotThrow(() -> monitor.processPendingImages()); assertDoesNotThrow(() -> monitor.processPendingImages());
// Verify database integrity // Verify database integrity
int imageCount = db.getImageCount(); var imageCount = db.getImageCount();
assertTrue(imageCount >= 0); assertTrue(imageCount >= 0);
} }
@@ -348,11 +348,11 @@ class IntegrationTest {
assertTrue(allLabels.contains("excavator") || allLabels.contains("machinery")); assertTrue(allLabels.contains("excavator") || allLabels.contains("machinery"));
// Simulate value estimation notification // Simulate value estimation notification
String message = String.format( var message = String.format(
"Lot contains: %s\nEstimated value: €%,.2f", "Lot contains: %s\nEstimated value: €%,.2f",
String.join(", ", allLabels), String.join(", ", allLabels),
lot.currentBid() lot.currentBid()
); );
assertDoesNotThrow(() -> assertDoesNotThrow(() ->
notifier.sendNotification(message, "Object Detected", 0) notifier.sendNotification(message, "Object Detected", 0)
@@ -363,9 +363,9 @@ class IntegrationTest {
@Order(9) @Order(9)
@DisplayName("Integration: Handle rapid concurrent updates") @DisplayName("Integration: Handle rapid concurrent updates")
void testConcurrentOperations() throws InterruptedException, SQLException { void testConcurrentOperations() throws InterruptedException, SQLException {
Thread auctionThread = new Thread(() -> { var auctionThread = new Thread(() -> {
try { try {
for (int i = 0; i < 10; i++) { for (var i = 0; i < 10; i++) {
db.upsertAuction(new AuctionInfo( db.upsertAuction(new AuctionInfo(
60000 + i, "Concurrent Auction " + i, "Test, NL", "Test", "NL", 60000 + i, "Concurrent Auction " + i, "Test, NL", "Test", "NL",
"https://example.com/60" + i, "A1", 5, null "https://example.com/60" + i, "A1", 5, null
@@ -375,10 +375,10 @@ class IntegrationTest {
fail("Auction thread failed: " + e.getMessage()); fail("Auction thread failed: " + e.getMessage());
} }
}); });
Thread lotThread = new Thread(() -> { var lotThread = new Thread(() -> {
try { try {
for (int i = 0; i < 10; i++) { for (var i = 0; i < 10; i++) {
db.upsertLot(new Lot( db.upsertLot(new Lot(
60000 + i, 70000 + i, "Concurrent Lot " + i, "Desc", "", "", 0, "Cat", 60000 + i, 70000 + i, "Concurrent Lot " + i, "Desc", "", "", 0, "Cat",
100.0 * i, "EUR", "https://example.com/70" + i, null, false 100.0 * i, "EUR", "https://example.com/70" + i, null, false
@@ -397,14 +397,14 @@ class IntegrationTest {
// Verify all were inserted // Verify all were inserted
var auctions = db.getAllAuctions(); var auctions = db.getAllAuctions();
var lots = db.getAllLots(); var lots = db.getAllLots();
long auctionCount = auctions.stream() var auctionCount = auctions.stream()
.filter(a -> a.auctionId() >= 60000 && a.auctionId() < 60010) .filter(a -> a.auctionId() >= 60000 && a.auctionId() < 60010)
.count(); .count();
long lotCount = lots.stream() var lotCount = lots.stream()
.filter(l -> l.lotId() >= 70000 && l.lotId() < 70010) .filter(l -> l.lotId() >= 70000 && l.lotId() < 70010)
.count(); .count();
assertEquals(10, auctionCount); assertEquals(10, auctionCount);
assertEquals(10, lotCount); assertEquals(10, lotCount);