result = vacatureResource.getAllVacatures();
+
+ assertEquals(1, result.size());
+ assertEquals(expectedVacatures, result);
+ verify(vacatureService).getAllVacatures();
+ }
+
+ @Test
+ void cleanVacatures_CallsCleanVacaturesOnService() {
+ vacatureResource.cleanVacatures();
+
+ verify(vacatureService).cleanVacatures();
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/nl/veenm/jobfindr/scrapers/ScraperServiceTest.java b/src/test/java/nl/veenm/jobfindr/scrapers/ScraperServiceTest.java
new file mode 100644
index 0000000..9474c08
--- /dev/null
+++ b/src/test/java/nl/veenm/jobfindr/scrapers/ScraperServiceTest.java
@@ -0,0 +1,103 @@
+package nl.veenm.jobfindr.scrapers;
+
+import nl.veenm.jobfindr.domain.Vacature;
+import org.jsoup.Connection;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.io.IOException;
+import java.time.LocalDate;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class ScraperServiceTest {
+
+ @InjectMocks
+ ScraperService scraperService;
+
+ @Test
+ void getVacatureDetails_HaaltBeschrijvingEnDatumOp() throws IOException {
+ String detailHtml = "" +
+ "0,8 FTE
" +
+ "Sluitingsdatum: 15-04-2026
" +
+ "";
+ Document mockDocument = Jsoup.parse(detailHtml);
+
+ Connection mockConnection = mock(Connection.class);
+ when(mockConnection.get()).thenReturn(mockDocument);
+
+ Vacature vacature = new Vacature("Test", "https://fake-url.com", "Apeldoorn");
+
+ try (MockedStatic mockedJsoup = mockStatic(Jsoup.class)) {
+ mockedJsoup.when(() -> Jsoup.connect("https://fake-url.com")).thenReturn(mockConnection);
+
+ scraperService.getVacatureDetails(vacature);
+
+ assertEquals("0,8 FTE
Sluitingsdatum: 15-04-2026
", vacature.getDescription());
+ assertEquals(LocalDate.of(2026, 4, 15), vacature.getClosingDate());
+ }
+ }
+
+ @Test
+ void scrapeVacatures_HaaltLijstOpEnFiltertOnbekendeLocaties() throws IOException {
+ String listHtml = "" +
+ " " +
+ "
" +
+ "
Docent Geschiedenis
" +
+ "
Link" +
+ "
" +
+ "
Apeldoorn
" +
+ "
" +
+ " " +
+ "
" +
+ "
Docent Wiskunde
" +
+ "
Link" +
+ "
" +
+ "
" +
+ "";
+
+ String detailHtml = "Sluitingsdatum: 01-05-2026
";
+ String mainUrl = "https://www.meesterbaan.nl/zoeken";
+
+ // FIX: We parsen de HTML nu hier, buiten het bereik van de mock!
+ Document listDocument = Jsoup.parse(listHtml);
+ Document detailDocument = Jsoup.parse(detailHtml);
+
+ try (MockedStatic mockedJsoup = mockStatic(Jsoup.class)) {
+ mockedJsoup.when(() -> Jsoup.connect(anyString())).thenAnswer(invocation -> {
+ String url = invocation.getArgument(0);
+ Connection connection = mock(Connection.class);
+
+ // We geven nu direct de kant-en-klare Document objecten terug
+ if (url.equals(mainUrl)) {
+ when(connection.get()).thenReturn(listDocument);
+ } else {
+ when(connection.get()).thenReturn(detailDocument);
+ }
+ return connection;
+ });
+
+ // Act
+ List results = scraperService.scrapeVacatures(mainUrl);
+
+ // Assert
+ assertEquals(1, results.size());
+
+ Vacature v = results.getFirst();
+ assertEquals("Docent Geschiedenis", v.getTitel());
+ assertEquals("https://www.meesterbaan.nl/vacature/123", v.getUrl());
+ assertEquals("Apeldoorn", v.getLocatie());
+ assertEquals("Sluitingsdatum: 01-05-2026
", v.getDescription());
+ assertEquals(LocalDate.of(2026, 5, 1), v.getClosingDate());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/nl/veenm/jobfindr/services/EmailServiceTest.java b/src/test/java/nl/veenm/jobfindr/services/EmailServiceTest.java
new file mode 100644
index 0000000..ca2e551
--- /dev/null
+++ b/src/test/java/nl/veenm/jobfindr/services/EmailServiceTest.java
@@ -0,0 +1,78 @@
+package nl.veenm.jobfindr.services;
+
+import io.quarkus.mailer.Mail;
+import io.quarkus.mailer.Mailer;
+import nl.veenm.jobfindr.domain.Vacature;
+import nl.veenm.jobfindr.util.EventService;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class EmailServiceTest {
+
+ @Mock
+ EventService eventService;
+
+ @Mock
+ Mailer mailer;
+
+ @InjectMocks
+ EmailService emailService;
+
+ @Captor
+ ArgumentCaptor mailCaptor;
+
+ @Test
+ void stuurVacatureEmail_VerstuurtEmailEnLogtSucces() {
+ Vacature vacature = mock(Vacature.class);
+ when(vacature.getTitel()).thenReturn("Java Developer");
+ when(vacature.getDescription()).thenReturn("Mooie Quarkus rol");
+ when(vacature.getUrl()).thenReturn("https://example.com/vacature/1");
+
+ emailService.stuurVacatureEmail("henk@example.com", List.of(vacature));
+
+ verify(eventService).logInfo("EmailService", "stuurVacatureEmail", "Sending email to henk@example.com");
+
+ verify(mailer).send(mailCaptor.capture());
+ Mail capturedMail = mailCaptor.getValue();
+
+ assertEquals(1, capturedMail.getTo().size());
+ assertEquals("henk@example.com", capturedMail.getTo().getFirst());
+
+ LocalDateTime now = LocalDateTime.now();
+ String expectedDate = now.getDayOfMonth() + "-" + now.getMonthValue() + "-" + now.getYear();
+ assertEquals("[" + expectedDate + "] Nieuwe vacatures gevonden", capturedMail.getSubject());
+
+ String htmlBody = capturedMail.getHtml();
+ assertTrue(htmlBody.contains("Lieve Danthe"));
+ assertTrue(htmlBody.contains("Java Developer"));
+ assertTrue(htmlBody.contains("Mooie Quarkus rol"));
+ assertTrue(htmlBody.contains("https://example.com/vacature/1"));
+
+ verify(eventService).logSucces("EmailService", "stuurVacatureEmail", "Email sent");
+ }
+
+ @Test
+ void stuurVacatureEmail_LogtErrorBijException() {
+ RuntimeException exception = new RuntimeException("Mail server down");
+ doThrow(exception).when(mailer).send(any(Mail.class));
+
+ emailService.stuurVacatureEmail("henk@example.com", List.of());
+
+ verify(eventService).logInfo("EmailService", "stuurVacatureEmail", "Sending email to henk@example.com");
+ verify(eventService).logError("EmailService", "stuurVacatureEmail", "Sending email failed", exception);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/nl/veenm/jobfindr/services/VacatureServiceTest.java b/src/test/java/nl/veenm/jobfindr/services/VacatureServiceTest.java
new file mode 100644
index 0000000..d1bc2a7
--- /dev/null
+++ b/src/test/java/nl/veenm/jobfindr/services/VacatureServiceTest.java
@@ -0,0 +1,189 @@
+package nl.veenm.jobfindr.services;
+
+import io.quarkus.hibernate.orm.panache.PanacheQuery;
+import nl.veenm.jobfindr.domain.Vacature;
+import nl.veenm.jobfindr.domain.VacatureDetail;
+import nl.veenm.jobfindr.repository.VacatureDetailRepository;
+import nl.veenm.jobfindr.repository.VacatureRepository;
+import nl.veenm.jobfindr.scrapers.ScraperService;
+import nl.veenm.jobfindr.util.EventService;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.io.IOException;
+import java.time.LocalDate;
+import java.util.List;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class VacatureServiceTest {
+
+ @Mock
+ ScraperService scraperService;
+
+ @Mock
+ EventService eventService;
+
+ @Mock
+ EmailService emailService;
+
+ @Mock
+ VacatureRepository vacatureRepository;
+
+ @Mock
+ VacatureDetailRepository vacatureDetailRepository;
+
+ @InjectMocks
+ VacatureService vacatureService;
+
+ @Captor
+ ArgumentCaptor emailCaptor;
+
+ @Captor
+ ArgumentCaptor> listCaptor;
+
+ @Test
+ void getNewVacatures() throws IOException {
+ Vacature v1 = new Vacature();
+ when(scraperService.scrapeVacatures(anyString())).thenReturn(List.of(v1));
+
+ List result = vacatureService.getNewVacatures();
+
+ assertEquals(2, result.size());
+
+ // Geen eq() nodig omdat we exacte waarden doorgeven zonder matchers
+ verify(eventService).logInfo(VacatureService.class.getName(), "getServices", "getServices aangeroepen");
+
+ // Afvangen in plaats van eq()
+ verify(emailService).stuurVacatureEmail(emailCaptor.capture(), listCaptor.capture());
+ assertEquals("vanveenmel11@gmail.com", emailCaptor.getValue());
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ void checkAndSendNewVacatures_WithNewVacatures() throws IOException {
+ Vacature newVacature = new Vacature();
+ newVacature.setUrl("url1");
+
+ Vacature existingVacature = new Vacature();
+ existingVacature.setUrl("url2");
+
+ when(scraperService.scrapeVacatures(anyString())).thenReturn(List.of(newVacature, existingVacature));
+
+ PanacheQuery query = mock(PanacheQuery.class);
+ when(vacatureRepository.findAll()).thenReturn(query);
+ when(query.stream()).thenReturn(Stream.of(existingVacature));
+
+ vacatureService.checkAndSendNewVacatures();
+
+ verify(scraperService, times(4)).getVacatureDetails(any(Vacature.class));
+ verify(vacatureRepository, times(2)).persist(any(Vacature.class));
+
+ verify(emailService, times(2)).stuurVacatureEmail(emailCaptor.capture(), listCaptor.capture());
+ assertEquals("vanveenmel11@gmail.com", emailCaptor.getValue());
+
+ verify(eventService).logSucces("VacatureService", "checkAndSendNewVacatures", "Nieuwe vacatures verstuurd en opgeslagen.");
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ void checkAndSendNewVacatures_NoNewVacatures() throws IOException {
+ Vacature existingVacature = new Vacature();
+ existingVacature.setUrl("url1");
+
+ when(scraperService.scrapeVacatures(anyString())).thenReturn(List.of(existingVacature));
+
+ PanacheQuery query = mock(PanacheQuery.class);
+ when(vacatureRepository.findAll()).thenReturn(query);
+ when(query.stream()).thenReturn(Stream.of(existingVacature));
+
+ vacatureService.checkAndSendNewVacatures();
+
+ verify(vacatureRepository, never()).persist(any(Vacature.class));
+ // Omdat het nooit wordt aangeroepen, kunnen we hier wel any() en anyString() gebruiken
+ verify(emailService, never()).stuurVacatureEmail(anyString(), any());
+
+ verify(eventService).logInfo("VacatureService", "checkAndSendNewVacatures", "Geen nieuwe vacatures gevonden.");
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ void checkAndSendNewVacatures_HandlesIOException() throws IOException {
+ Vacature vacature = new Vacature();
+ vacature.setUrl("url1");
+
+ when(scraperService.scrapeVacatures(anyString())).thenReturn(List.of(vacature));
+
+ IOException exception = new IOException("Scrape failed");
+ doThrow(exception).when(scraperService).getVacatureDetails(any(Vacature.class));
+
+ PanacheQuery query = mock(PanacheQuery.class);
+ when(vacatureRepository.findAll()).thenReturn(query);
+ when(query.stream()).thenReturn(Stream.empty());
+
+ vacatureService.checkAndSendNewVacatures();
+
+ // Pass the actual exception object directly
+ verify(eventService, times(2)).logError("VacatureService", "checkAndSendNewVacatures", "checkAndSendNewVacatures failed", exception);
+ }
+
+ @Test
+ void getAllVacatures() {
+ List vacatures = List.of(new Vacature());
+ when(vacatureRepository.listAll()).thenReturn(vacatures);
+
+ List result = vacatureService.getAllVacatures();
+
+ assertEquals(1, result.size());
+ verify(eventService).logInfo("VacatureService", "getAllVacatures", "fetching all vacatures");
+ }
+
+ @Test
+ void cleanVacatures() {
+ Vacature expired = new Vacature();
+ expired.setTitel("Oude Vacature");
+ expired.setClosingDate(LocalDate.now().minusDays(1));
+ VacatureDetail detail = mock(VacatureDetail.class);
+ expired.setDetail(detail);
+
+ Vacature valid = new Vacature();
+ valid.setClosingDate(LocalDate.now().plusDays(1));
+
+ when(vacatureRepository.listAll()).thenReturn(List.of(expired, valid));
+
+ vacatureService.cleanVacatures();
+
+ verify(vacatureDetailRepository).delete(detail);
+ verify(vacatureRepository).delete(expired);
+ verify(vacatureRepository, never()).delete(valid);
+
+ verify(eventService).logSucces("VacatureService", "cleanVacatures", "Cleaning vacatures done");
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ void dagelijksControleerEnVerstuur() {
+ PanacheQuery query = mock(PanacheQuery.class);
+ when(vacatureRepository.findAll()).thenReturn(query);
+ when(query.stream()).thenReturn(Stream.empty());
+
+ vacatureService.dagelijksControleerEnVerstuur();
+
+ verify(eventService).logInfo("VacatureService", "checkAndSendNewVacatures", "Checking for new vacatures");
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/nl/veenm/jobfindr/util/EventServiceTest.java b/src/test/java/nl/veenm/jobfindr/util/EventServiceTest.java
new file mode 100644
index 0000000..9e33aec
--- /dev/null
+++ b/src/test/java/nl/veenm/jobfindr/util/EventServiceTest.java
@@ -0,0 +1,80 @@
+package nl.veenm.jobfindr.util;
+
+import nl.veenm.jobfindr.domain.Event;
+import nl.veenm.jobfindr.repository.EventRepository;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+class EventServiceTest {
+
+ @Mock
+ EventRepository eventRepository;
+
+ @InjectMocks
+ EventService eventService;
+
+ @Captor
+ ArgumentCaptor eventCaptor;
+
+ @Test
+ void testLogInfo_SavesInfoEvent() {
+ eventService.logInfo("MyClass", "myMethod", "Dit is een info bericht");
+
+ verify(eventRepository).persist(eventCaptor.capture());
+ Event capturedEvent = eventCaptor.getValue();
+
+ assertNotNull(capturedEvent.getTimestamp());
+ assertEquals("MyClass", capturedEvent.getClassName());
+ assertEquals("myMethod", capturedEvent.getMethodName());
+ assertEquals("Dit is een info bericht", capturedEvent.getMessage());
+ assertEquals("INFO", capturedEvent.getLevel());
+ }
+
+ @Test
+ void testLogSucces_SavesSuccessEvent() {
+ eventService.logSucces("MyClass", "myMethod", "Dit is gelukt");
+
+ verify(eventRepository).persist(eventCaptor.capture());
+ Event capturedEvent = eventCaptor.getValue();
+
+ assertNotNull(capturedEvent.getTimestamp());
+ assertEquals("MyClass", capturedEvent.getClassName());
+ assertEquals("myMethod", capturedEvent.getMethodName());
+ assertEquals("Dit is gelukt", capturedEvent.getMessage());
+ assertEquals("SUCCESS", capturedEvent.getLevel());
+ }
+
+ @Test
+ void testLogError_SavesErrorEventWithExceptionMessage() {
+ Exception dummyException = new RuntimeException("Database connectie verbroken");
+
+ eventService.logError("MyClass", "myMethod", "Er ging iets mis", dummyException);
+
+ verify(eventRepository).persist(eventCaptor.capture());
+ Event capturedEvent = eventCaptor.getValue();
+
+ assertNotNull(capturedEvent.getTimestamp());
+ assertEquals("MyClass", capturedEvent.getClassName());
+ assertEquals("myMethod", capturedEvent.getMethodName());
+ assertEquals("Er ging iets mis", capturedEvent.getMessage());
+ assertEquals("ERROR", capturedEvent.getLevel());
+ assertEquals("Database connectie verbroken", capturedEvent.getError());
+ }
+
+ @Test
+ void testCleanEvents_CallsDeleteAll() {
+ eventService.cleanEvents();
+
+ verify(eventRepository).deleteAll();
+ }
+}
\ No newline at end of file