fix-tests-cleanup

This commit is contained in:
Tour
2025-12-08 07:52:54 +01:00
parent be65f4a5e6
commit 3cc0d40fa3
14 changed files with 70 additions and 3410 deletions

View File

@@ -1 +1 @@
jvmArguments=--add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED jvmArguments=-Djava.util.logging.manager=org.jboss.logmanager.LogManager --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED

View File

@@ -14,8 +14,8 @@ services:
- AUCTION_IMAGES_PATH=/mnt/okcomputer/output/images - AUCTION_IMAGES_PATH=/mnt/okcomputer/output/images
# Notification configuration # Notification configuration
- AUCTION_NOTIFICATION_CONFIG=desktop # - AUCTION_NOTIFICATION_CONFIG=desktop
- AUCTION_NOTIFICATION_CONFIG=smtp:michael.bakker1986@gmail.com:agrepolhlnvhipkv:michael.bakker1986@gmail.com
# Quarkus configuration # Quarkus configuration
- QUARKUS_HTTP_PORT=8081 - QUARKUS_HTTP_PORT=8081
- QUARKUS_HTTP_HOST=0.0.0.0 - QUARKUS_HTTP_HOST=0.0.0.0

View File

@@ -440,6 +440,8 @@
<!-- Enable ByteBuddy experimental mode for Java 25 support --> <!-- Enable ByteBuddy experimental mode for Java 25 support -->
<!-- Mockito requires this for Java 24+ --> <!-- Mockito requires this for Java 24+ -->
<argLine> <argLine>
--enable-native-access=ALL-UNNAMED
--add-opens java.base/sun.misc=ALL-UNNAMED
-Dnet.bytebuddy.experimental=true -Dnet.bytebuddy.experimental=true
--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,13 @@
package auctiora; package auctiora;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.config.inject.ConfigProperty;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import nu.pattern.OpenCV;
import org.opencv.core.Core;
import org.opencv.core.Mat; import org.opencv.core.Mat;
import org.opencv.core.Scalar; import org.opencv.core.Scalar;
import org.opencv.core.Size; import org.opencv.core.Size;
@@ -23,7 +26,7 @@ import static org.opencv.dnn.Dnn.DNN_TARGET_CPU;
/** /**
* Service for performing object detection on images using OpenCV's DNN * Service for performing object detection on images using OpenCV's DNN
* module. The DNN module can load pretrained models from several * module. The DNN module can load pretrained models from several
* frameworks (Darknet, TensorFlow, ONNX, etc.)【784097309529506†L209-L233】. Here * frameworks (Darknet, TensorFlow, ONNX, etc.). Here
* we load a YOLO model (Darknet) by specifying the configuration and * we load a YOLO model (Darknet) by specifying the configuration and
* weights files. For each image we run a forward pass and return a * weights files. For each image we run a forward pass and return a
* list of detected class labels. * list of detected class labels.
@@ -34,13 +37,44 @@ import static org.opencv.dnn.Dnn.DNN_TARGET_CPU;
@Slf4j @Slf4j
public class ObjectDetectionService { public class ObjectDetectionService {
private final Net net; private Net net;
private final List<String> classNames; private List<String> classNames;
private final boolean enabled; private boolean enabled;
private int warnCount = 0; private int warnCount = 0;
private static final int MAX_WARNINGS = 5; private static final int MAX_WARNINGS = 5;
private static boolean openCvLoaded = false;
private final String cfgPath;
private final String weightsPath;
private final String classNamesPath;
ObjectDetectionService(String cfgPath, String weightsPath, String classNamesPath) throws IOException { ObjectDetectionService(String cfgPath, String weightsPath, String classNamesPath) throws IOException {
this.cfgPath = cfgPath;
this.weightsPath = weightsPath;
this.classNamesPath = classNamesPath;
}
@PostConstruct
void init() {
// Load OpenCV native libraries first
if (!openCvLoaded) {
try {
OpenCV.loadLocally();
openCvLoaded = true;
log.info("✓ OpenCV {} loaded successfully", Core.VERSION);
} catch (Exception e) {
log.warn("⚠️ Object detection disabled: OpenCV native libraries not loaded");
enabled = false;
net = null;
classNames = new ArrayList<>();
return;
}
}
initializeModel();
}
private void initializeModel() {
// Check if model files exist // Check if model files exist
var cfgFile = Paths.get(cfgPath); var cfgFile = Paths.get(cfgPath);
var weightsFile = Paths.get(weightsPath); var weightsFile = Paths.get(weightsPath);
@@ -86,11 +120,15 @@ public class ObjectDetectionService {
classNames = Files.readAllLines(classNamesFile); classNames = Files.readAllLines(classNamesFile);
enabled = true; enabled = true;
} catch (UnsatisfiedLinkError e) { } catch (UnsatisfiedLinkError e) {
System.err.println("⚠️ Object detection disabled: OpenCV native libraries not loaded"); log.error("⚠️ Object detection disabled: OpenCV native libraries not loaded", e);
throw new IOException("Failed to initialize object detection: OpenCV native libraries not loaded", e); enabled = false;
net = null;
classNames = new ArrayList<>();
} catch (Exception e) { } catch (Exception e) {
System.err.println("⚠️ Object detection disabled: " + e.getMessage()); log.error("⚠️ Object detection disabled: " + e.getMessage(), e);
throw new IOException("Failed to initialize object detection", e); enabled = false;
net = null;
classNames = new ArrayList<>();
} }
} }
/** /**

View File

@@ -65,10 +65,10 @@ public class StatusResource {
private String getOpenCvVersion() { private String getOpenCvVersion() {
try { try {
OpenCV.loadLocally(); // OpenCV is already loaded by AuctionMonitorProducer
return Core.VERSION; return Core.VERSION;
} catch (Exception e) { } catch (Exception e) {
return "4.9.0 (default)"; return "Not loaded";
} }
} }
} }

View File

@@ -119,7 +119,7 @@ public class TroostwijkMonitor {
allLots.size(), imageCount); allLots.size(), imageCount);
if (!allLots.isEmpty()) { if (!allLots.isEmpty()) {
var sum = allLots.stream().mapToDouble(Lot::currentBid).sum(); var sum = allLots.stream().mapToDouble(Lot::currentBid).sum();
log.info("Total current bids: €{:.2f}", sum); log.info("Total current bids: €{}", String.format("%.2f", sum));
} }
} catch (Exception e) { } catch (Exception e) {
log.warn("Could not retrieve database stats", e); log.warn("Could not retrieve database stats", e);

View File

@@ -46,7 +46,10 @@ quarkus.http.root-path=/
# Auction Monitor Configuration # Auction Monitor Configuration
auction.database.path=/mnt/okcomputer/output/cache.db auction.database.path=/mnt/okcomputer/output/cache.db
auction.images.path=/mnt/okcomputer/output/images auction.images.path=/mnt/okcomputer/output/images
auction.notification.config=desktop # auction.notification.config=desktop
# Format: smtp:username:password:recipient_email
auction.notification.config=smtp:michael.bakker1986@gmail.com:agrepolhlnvhipkv:michael.bakker1986@gmail.com
auction.yolo.config=/mnt/okcomputer/output/models/yolov4.cfg auction.yolo.config=/mnt/okcomputer/output/models/yolov4.cfg
auction.yolo.weights=/mnt/okcomputer/output/models/yolov4.weights auction.yolo.weights=/mnt/okcomputer/output/models/yolov4.weights
auction.yolo.classes=/mnt/okcomputer/output/models/coco.names auction.yolo.classes=/mnt/okcomputer/output/models/coco.names
@@ -69,3 +72,4 @@ auction.http.timeout-seconds=30
# Health Check Configuration # Health Check Configuration
quarkus.smallrye-health.root-path=/health quarkus.smallrye-health.root-path=/health

View File

@@ -1,115 +0,0 @@
package auctiora;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
/**
* Test auction parsing logic using saved HTML from test.html
* Tests the markup data extraction for each auction found
*/
@Slf4j
public class AuctionParsingTest {
private static String testHtml;
@BeforeAll
public static void loadTestHtml() throws IOException {
// Load the test HTML file
testHtml = Files.readString(Paths.get("src/test/resources/test_auctions.html"));
log.info("Loaded test HTML ({} characters)", testHtml.length());
}
@Test
public void testLocationPatternMatching() {
log.info("\n=== Location Pattern Tests ===");
// Test different location formats
var testCases = new String[]{
"<p>Amsterdam, NL</p>",
"<p class=\"flex truncate\"><span class=\"w-full truncate\">Sofia,<!-- --> </span>BG</p>",
"<p>Berlin, DE</p>",
"<span>Brussels,</span>BE"
};
for (var testHtml : testCases) {
var doc = Jsoup.parse(testHtml);
var elem = doc.select("p, span").first();
if (elem != null) {
var text = elem.text();
log.info("\nTest: {}", testHtml);
log.info("Text: {}", text);
// Test regex pattern
if (text.matches(".*[A-Z]{2}$")) {
var countryCode = text.substring(text.length() - 2);
var cityPart = text.substring(0, text.length() - 2).trim().replaceAll("[,\\s]+$", "");
log.info("→ Extracted: {}, {}", cityPart, countryCode);
} else {
log.info("→ No match");
}
}
}
}
@Test
public void testFullTextPatternMatching() {
log.info("\n=== Full Text Pattern Tests ===");
// Test the complete auction text format
var testCases = new String[]{
"woensdag om 18:00 1 Vrachtwagens voor bedrijfsvoertuigen Loßburg, DE",
"maandag om 14:30 5 Industriële machines Amsterdam, NL",
"vrijdag om 10:00 12 Landbouwmachines Antwerpen, BE"
};
for (var testText : testCases) {
log.info("\nParsing: \"{}\"", testText);
// Simulated extraction
var remaining = testText;
// Extract time
var timePattern = java.util.regex.Pattern.compile("(\\w+)\\s+om\\s+(\\d{1,2}:\\d{2})");
var timeMatcher = timePattern.matcher(remaining);
if (timeMatcher.find()) {
log.info(" Time: {} om {}", timeMatcher.group(1), timeMatcher.group(2));
remaining = remaining.substring(timeMatcher.end()).trim();
}
// Extract location
var locPattern = java.util.regex.Pattern.compile(
"([A-ZÀ-ÿa-z][A-ZÀ-ÿa-z\\s\\-'öäüßàèéêëïôùûç]+?),\\s*([A-Z]{2})\\s*$"
);
var locMatcher = locPattern.matcher(remaining);
if (locMatcher.find()) {
log.info(" Location: {}, {}", locMatcher.group(1), locMatcher.group(2));
remaining = remaining.substring(0, locMatcher.start()).trim();
}
// Extract lot count
var lotPattern = java.util.regex.Pattern.compile("^(\\d+)\\s+");
var lotMatcher = lotPattern.matcher(remaining);
if (lotMatcher.find()) {
log.info(" Lot count: {}", lotMatcher.group(1));
remaining = remaining.substring(lotMatcher.end()).trim();
}
// What remains is title
log.info(" Title: {}", remaining);
}
}
}

View File

@@ -80,13 +80,13 @@ class ClosingTimeCalculationTest {
@DisplayName("Should identify lots closing within 30 minutes (dashboard threshold)") @DisplayName("Should identify lots closing within 30 minutes (dashboard threshold)")
void testDashboardClosingThreshold() { void testDashboardClosingThreshold() {
var closing20Min = createLot(LocalDateTime.now().plusMinutes(20)); var closing20Min = createLot(LocalDateTime.now().plusMinutes(20));
var closing30Min = createLot(LocalDateTime.now().plusMinutes(30)); var closing31Min = createLot(LocalDateTime.now().plusMinutes(31)); // Use 31 to avoid boundary timing issue
var closing40Min = createLot(LocalDateTime.now().plusMinutes(40)); var closing40Min = createLot(LocalDateTime.now().plusMinutes(40));
assertTrue(closing20Min.minutesUntilClose() < 30, assertTrue(closing20Min.minutesUntilClose() < 30,
"Lot closing in 20 min should be < 30 minutes"); "Lot closing in 20 min should be < 30 minutes");
assertTrue(closing30Min.minutesUntilClose() >= 30, assertTrue(closing31Min.minutesUntilClose() >= 30,
"Lot closing in 30 min should be >= 30 minutes"); "Lot closing in 31 min should be >= 30 minutes");
assertTrue(closing40Min.minutesUntilClose() > 30, assertTrue(closing40Min.minutesUntilClose() > 30,
"Lot closing in 40 min should be > 30 minutes"); "Lot closing in 40 min should be > 30 minutes");
} }

View File

@@ -81,7 +81,7 @@ class ObjectDetectionServiceTest {
} }
@Test @Test
@DisplayName("Should throw IOException when model files exist but OpenCV fails to load") @DisplayName("Should gracefully handle when model files exist but OpenCV fails to load")
void testInitializeWithValidModels() throws IOException { void testInitializeWithValidModels() throws IOException {
var cfgPath = Paths.get(TEST_CFG); var cfgPath = Paths.get(TEST_CFG);
var weightsPath = Paths.get(TEST_WEIGHTS); var weightsPath = Paths.get(TEST_WEIGHTS);
@@ -93,10 +93,11 @@ class ObjectDetectionServiceTest {
Files.writeString(classesPath, "person\ncar\ntruck\n"); Files.writeString(classesPath, "person\ncar\ntruck\n");
// When files exist but OpenCV native library isn't loaded, // When files exist but OpenCV native library isn't loaded,
// constructor should throw IOException wrapping the UnsatisfiedLinkError // service should construct successfully but be disabled (handled in @PostConstruct)
assertThrows(IOException.class, () -> { var service = new ObjectDetectionService(TEST_CFG, TEST_WEIGHTS, TEST_CLASSES);
new ObjectDetectionService(TEST_CFG, TEST_WEIGHTS, TEST_CLASSES); // Service is created, but init() handles failures gracefully
}); // detectObjects should return empty list when disabled
assertNotNull(service);
} finally { } finally {
Files.deleteIfExists(cfgPath); Files.deleteIfExists(cfgPath);
Files.deleteIfExists(weightsPath); Files.deleteIfExists(weightsPath);

File diff suppressed because one or more lines are too long

View File

@@ -1,26 +0,0 @@
Configure your devices to use the Pi-hole as their DNS server │
│ using: │
│ │
│ IPv4: 192.168.1.159 │
│ IPv6: fdc5:59a6:9ac1:f11f:2c86:25d3:6282:37ef │
│ If you have not done so already, the above IP should be set to │
│ static. │
│ View the web interface at http://pi.hole:80/admin or │
│ http://192.168.1.159:80/admin │
│ │
│ Your Admin Webpage login password is gYj7Enh- │
│ │
│ │
│ To allow your user to use all CLI functions without │
│ authentication, │
│ refer to https://docs.pi-hole.net/main/post-install/ │
├─────────────────────────────────────────────────────────────
127.0.0.1
192.168.1.159
::1
fdc5:59a6:9ac1:f11f:2c86:25d3:6282:37ef
fdc5:59a6:9ac1:f11f:bd8c:6e87:65f0:243c
fe80::a05b:bbc6:d47f:3002%enp9s0
2IXD-XJN9-C337-1K4Y-BBEO-HV1F-3BVI

File diff suppressed because one or more lines are too long