mirror of
https://github.com/Athou/commafeed.git
synced 2026-03-21 21:37:29 +00:00
refactor
This commit is contained in:
@@ -6,6 +6,7 @@ import java.io.OutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.net.NoRouteToHostException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
@@ -56,6 +57,7 @@ class HttpGetterTest {
|
||||
|
||||
private CommaFeedConfiguration config;
|
||||
|
||||
private HttpClientFactory provider;
|
||||
private HttpGetter getter;
|
||||
|
||||
@BeforeEach
|
||||
@@ -78,7 +80,8 @@ class HttpGetterTest {
|
||||
Mockito.when(config.httpClient().cache().expiration()).thenReturn(Duration.ofMinutes(1));
|
||||
Mockito.when(config.feedRefresh().httpThreads()).thenReturn(3);
|
||||
|
||||
this.getter = new HttpGetter(config, () -> NOW, Mockito.mock(CommaFeedVersion.class), Mockito.mock(MetricRegistry.class));
|
||||
this.provider = new HttpClientFactory(config, Mockito.mock(CommaFeedVersion.class));
|
||||
this.getter = new HttpGetter(config, () -> NOW, provider, Mockito.mock(MetricRegistry.class));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@@ -172,7 +175,7 @@ class HttpGetterTest {
|
||||
@Test
|
||||
void dataTimeout() {
|
||||
Mockito.when(config.httpClient().responseTimeout()).thenReturn(Duration.ofMillis(500));
|
||||
this.getter = new HttpGetter(config, () -> NOW, Mockito.mock(CommaFeedVersion.class), Mockito.mock(MetricRegistry.class));
|
||||
this.getter = new HttpGetter(config, () -> NOW, provider, Mockito.mock(MetricRegistry.class));
|
||||
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET"))
|
||||
.respond(HttpResponse.response().withDelay(Delay.milliseconds(1000)));
|
||||
@@ -183,7 +186,7 @@ class HttpGetterTest {
|
||||
@Test
|
||||
void connectTimeout() {
|
||||
Mockito.when(config.httpClient().connectTimeout()).thenReturn(Duration.ofMillis(500));
|
||||
this.getter = new HttpGetter(config, () -> NOW, Mockito.mock(CommaFeedVersion.class), Mockito.mock(MetricRegistry.class));
|
||||
this.getter = new HttpGetter(config, () -> NOW, provider, Mockito.mock(MetricRegistry.class));
|
||||
// try to connect to a non-routable address
|
||||
// https://stackoverflow.com/a/904609
|
||||
Exception e = Assertions.assertThrows(Exception.class, () -> getter.get("http://10.255.255.1"));
|
||||
@@ -367,44 +370,44 @@ class HttpGetterTest {
|
||||
@BeforeEach
|
||||
void init() {
|
||||
Mockito.when(config.httpClient().blockLocalAddresses()).thenReturn(true);
|
||||
getter = new HttpGetter(config, () -> NOW, Mockito.mock(CommaFeedVersion.class), Mockito.mock(MetricRegistry.class));
|
||||
getter = new HttpGetter(config, () -> NOW, provider, Mockito.mock(MetricRegistry.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void localhost() {
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://localhost"));
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://127.0.0.1"));
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://2130706433"));
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://0x7F.0x00.0x00.0X01"));
|
||||
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://localhost"));
|
||||
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://127.0.0.1"));
|
||||
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://2130706433"));
|
||||
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://0x7F.0x00.0x00.0X01"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void zero() {
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://0.0.0.0"));
|
||||
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://0.0.0.0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void linkLocal() {
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://169.254.12.34"));
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://169.254.169.254"));
|
||||
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://169.254.12.34"));
|
||||
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://169.254.169.254"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void multicast() {
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://224.2.3.4"));
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://239.255.255.254"));
|
||||
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://224.2.3.4"));
|
||||
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://239.255.255.254"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void privateIpv4Ranges() {
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://10.0.0.1"));
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://172.16.0.1"));
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://192.168.0.1"));
|
||||
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://10.0.0.1"));
|
||||
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://172.16.0.1"));
|
||||
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://192.168.0.1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void privateIpv6Ranges() {
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://fd12:3456:789a:1::1"));
|
||||
Assertions.assertThrows(UnknownHostException.class, () -> getter.get("http://[fe80::215:5dff:fe15:102]"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
package com.commafeed.backend.feed;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
||||
import com.commafeed.backend.dao.UnitOfWork;
|
||||
import com.commafeed.backend.dao.UserSettingsDAO;
|
||||
import com.commafeed.backend.feed.parser.FeedParserResult.Content;
|
||||
import com.commafeed.backend.feed.parser.FeedParserResult.Entry;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.commafeed.backend.model.FeedEntry;
|
||||
import com.commafeed.backend.model.FeedSubscription;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.model.UserSettings;
|
||||
import com.commafeed.backend.service.FeedEntryService;
|
||||
import com.commafeed.backend.service.FeedService;
|
||||
import com.commafeed.backend.service.NotificationService;
|
||||
import com.commafeed.frontend.ws.WebSocketSessions;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class FeedRefreshUpdaterTest {
|
||||
|
||||
@Mock
|
||||
private UnitOfWork unitOfWork;
|
||||
|
||||
@Mock
|
||||
private FeedService feedService;
|
||||
|
||||
@Mock
|
||||
private FeedEntryService feedEntryService;
|
||||
|
||||
@Mock
|
||||
private FeedSubscriptionDAO feedSubscriptionDAO;
|
||||
|
||||
@Mock
|
||||
private UserSettingsDAO userSettingsDAO;
|
||||
|
||||
@Mock
|
||||
private WebSocketSessions webSocketSessions;
|
||||
|
||||
@Mock
|
||||
private NotificationService notificationService;
|
||||
|
||||
private FeedRefreshUpdater updater;
|
||||
|
||||
private Feed feed;
|
||||
private User user;
|
||||
private FeedSubscription subscription;
|
||||
private Entry entry;
|
||||
private FeedEntry feedEntry;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
MetricRegistry metrics = new MetricRegistry();
|
||||
updater = new FeedRefreshUpdater(unitOfWork, feedService, feedEntryService, metrics, feedSubscriptionDAO, userSettingsDAO,
|
||||
webSocketSessions, notificationService);
|
||||
|
||||
// UnitOfWork passthrough: execute callables and runnables directly
|
||||
Mockito.when(unitOfWork.call(Mockito.any())).thenAnswer(inv -> inv.getArgument(0, Callable.class).call());
|
||||
Mockito.doAnswer(inv -> {
|
||||
inv.getArgument(0, Runnable.class).run();
|
||||
return null;
|
||||
}).when(unitOfWork).run(Mockito.any());
|
||||
|
||||
user = new User();
|
||||
user.setId(1L);
|
||||
|
||||
feed = new Feed();
|
||||
feed.setId(1L);
|
||||
feed.setUrl("https://example.com/feed.xml");
|
||||
|
||||
subscription = new FeedSubscription();
|
||||
subscription.setId(1L);
|
||||
subscription.setTitle("My Feed");
|
||||
subscription.setUser(user);
|
||||
subscription.setNotifyOnNewEntries(true);
|
||||
|
||||
Content content = new Content("Article Title", "content", "author", null, null, null);
|
||||
entry = new Entry("guid-1", "https://example.com/article", Instant.now(), content);
|
||||
|
||||
feedEntry = new FeedEntry();
|
||||
feedEntry.setUrl("https://example.com/article");
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateSendsNotificationsForNewEntries() {
|
||||
Mockito.when(feedSubscriptionDAO.findByFeed(feed)).thenReturn(List.of(subscription));
|
||||
Mockito.when(feedEntryService.find(feed, entry)).thenReturn(null);
|
||||
Mockito.when(feedEntryService.create(feed, entry)).thenReturn(feedEntry);
|
||||
Mockito.when(feedEntryService.applyFilter(subscription, feedEntry)).thenReturn(true);
|
||||
|
||||
UserSettings settings = new UserSettings();
|
||||
settings.setNotificationEnabled(true);
|
||||
Mockito.when(userSettingsDAO.findByUser(user)).thenReturn(settings);
|
||||
|
||||
updater.update(feed, List.of(entry));
|
||||
|
||||
Mockito.verify(notificationService).notify(settings, subscription, feedEntry);
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateDoesNotNotifyWhenSubscriptionNotifyDisabled() {
|
||||
subscription.setNotifyOnNewEntries(false);
|
||||
|
||||
Mockito.when(feedSubscriptionDAO.findByFeed(feed)).thenReturn(List.of(subscription));
|
||||
Mockito.when(feedEntryService.find(feed, entry)).thenReturn(null);
|
||||
Mockito.when(feedEntryService.create(feed, entry)).thenReturn(feedEntry);
|
||||
Mockito.when(feedEntryService.applyFilter(subscription, feedEntry)).thenReturn(true);
|
||||
|
||||
updater.update(feed, List.of(entry));
|
||||
|
||||
Mockito.verify(notificationService, Mockito.never()).notify(Mockito.any(), Mockito.any(), Mockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateDoesNotNotifyWhenUserNotificationsDisabled() {
|
||||
Mockito.when(feedSubscriptionDAO.findByFeed(feed)).thenReturn(List.of(subscription));
|
||||
Mockito.when(feedEntryService.find(feed, entry)).thenReturn(null);
|
||||
Mockito.when(feedEntryService.create(feed, entry)).thenReturn(feedEntry);
|
||||
Mockito.when(feedEntryService.applyFilter(subscription, feedEntry)).thenReturn(true);
|
||||
|
||||
UserSettings settings = new UserSettings();
|
||||
settings.setNotificationEnabled(false);
|
||||
Mockito.when(userSettingsDAO.findByUser(user)).thenReturn(settings);
|
||||
|
||||
updater.update(feed, List.of(entry));
|
||||
|
||||
Mockito.verify(notificationService, Mockito.never()).notify(Mockito.any(), Mockito.any(), Mockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateDoesNotNotifyWhenNoUserSettings() {
|
||||
Mockito.when(feedSubscriptionDAO.findByFeed(feed)).thenReturn(List.of(subscription));
|
||||
Mockito.when(feedEntryService.find(feed, entry)).thenReturn(null);
|
||||
Mockito.when(feedEntryService.create(feed, entry)).thenReturn(feedEntry);
|
||||
Mockito.when(feedEntryService.applyFilter(subscription, feedEntry)).thenReturn(true);
|
||||
|
||||
Mockito.when(userSettingsDAO.findByUser(user)).thenReturn(null);
|
||||
|
||||
updater.update(feed, List.of(entry));
|
||||
|
||||
Mockito.verify(notificationService, Mockito.never()).notify(Mockito.any(), Mockito.any(), Mockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateDoesNotNotifyForExistingEntries() {
|
||||
Mockito.when(feedSubscriptionDAO.findByFeed(feed)).thenReturn(List.of(subscription));
|
||||
Mockito.when(feedEntryService.find(feed, entry)).thenReturn(feedEntry);
|
||||
|
||||
updater.update(feed, List.of(entry));
|
||||
|
||||
Mockito.verify(notificationService, Mockito.never()).notify(Mockito.any(), Mockito.any(), Mockito.any());
|
||||
}
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
package com.commafeed.backend.service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.net.http.HttpResponse.BodyHandler;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import com.commafeed.backend.model.FeedEntry;
|
||||
import com.commafeed.backend.model.FeedEntryContent;
|
||||
import com.commafeed.backend.model.FeedSubscription;
|
||||
import com.commafeed.backend.model.UserSettings;
|
||||
import com.commafeed.backend.model.UserSettings.NotificationType;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class NotificationServiceTest {
|
||||
|
||||
@Mock
|
||||
private HttpClient httpClient;
|
||||
|
||||
@Mock
|
||||
private HttpResponse<String> httpResponse;
|
||||
|
||||
private NotificationService notificationService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
notificationService = new NotificationService(httpClient);
|
||||
}
|
||||
|
||||
private void stubHttpClient() throws Exception {
|
||||
Mockito.when(httpResponse.statusCode()).thenReturn(200);
|
||||
Mockito.when(httpClient.send(Mockito.any(HttpRequest.class), Mockito.<BodyHandler<String>> any())).thenReturn(httpResponse);
|
||||
}
|
||||
|
||||
private HttpRequest captureRequest() throws Exception {
|
||||
ArgumentCaptor<HttpRequest> captor = ArgumentCaptor.forClass(HttpRequest.class);
|
||||
Mockito.verify(httpClient).send(captor.capture(), Mockito.<BodyHandler<String>> any());
|
||||
return captor.getValue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendNtfyBuildsCorrectRequest() throws Exception {
|
||||
stubHttpClient();
|
||||
|
||||
UserSettings settings = newSettings(NotificationType.NTFY);
|
||||
settings.setNotificationServerUrl("https://ntfy.example.com");
|
||||
settings.setNotificationTopic("my-topic");
|
||||
settings.setNotificationToken("my-token");
|
||||
|
||||
FeedSubscription sub = newSubscription("My Feed");
|
||||
FeedEntry entry = newEntry("New Article", "https://example.com/article");
|
||||
|
||||
notificationService.notify(settings, sub, entry);
|
||||
|
||||
HttpRequest request = captureRequest();
|
||||
Assertions.assertEquals("https://ntfy.example.com/my-topic", request.uri().toString());
|
||||
Assertions.assertEquals("My Feed: New Article", request.headers().firstValue("Title").orElse(null));
|
||||
Assertions.assertEquals("https://example.com/article", request.headers().firstValue("Click").orElse(null));
|
||||
Assertions.assertEquals("Bearer my-token", request.headers().firstValue("Authorization").orElse(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendNtfyOmitsOptionalHeaders() throws Exception {
|
||||
stubHttpClient();
|
||||
|
||||
UserSettings settings = newSettings(NotificationType.NTFY);
|
||||
settings.setNotificationServerUrl("https://ntfy.example.com");
|
||||
settings.setNotificationTopic("my-topic");
|
||||
|
||||
FeedSubscription sub = newSubscription("My Feed");
|
||||
FeedEntry entry = newEntry("Title", "");
|
||||
|
||||
notificationService.notify(settings, sub, entry);
|
||||
|
||||
HttpRequest request = captureRequest();
|
||||
Assertions.assertTrue(request.headers().firstValue("Click").isEmpty());
|
||||
Assertions.assertTrue(request.headers().firstValue("Authorization").isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendNtfySkipsWhenMissingConfig() throws Exception {
|
||||
UserSettings settings = newSettings(NotificationType.NTFY);
|
||||
settings.setNotificationTopic("topic");
|
||||
notificationService.notify(settings, newSubscription("F"), newEntry("T", "U"));
|
||||
Mockito.verify(httpClient, Mockito.never()).send(Mockito.any(), Mockito.any());
|
||||
|
||||
UserSettings settings2 = newSettings(NotificationType.NTFY);
|
||||
settings2.setNotificationServerUrl("https://ntfy.example.com");
|
||||
notificationService.notify(settings2, newSubscription("F"), newEntry("T", "U"));
|
||||
Mockito.verify(httpClient, Mockito.never()).send(Mockito.any(), Mockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendGotifyBuildsCorrectRequest() throws Exception {
|
||||
stubHttpClient();
|
||||
|
||||
UserSettings settings = newSettings(NotificationType.GOTIFY);
|
||||
settings.setNotificationServerUrl("https://gotify.example.com/");
|
||||
settings.setNotificationToken("app-token");
|
||||
|
||||
FeedSubscription sub = newSubscription("My Feed");
|
||||
FeedEntry entry = newEntry("New Article", "https://example.com/article");
|
||||
|
||||
notificationService.notify(settings, sub, entry);
|
||||
|
||||
HttpRequest request = captureRequest();
|
||||
Assertions.assertEquals("https://gotify.example.com/message", request.uri().toString());
|
||||
Assertions.assertEquals("app-token", request.headers().firstValue("X-Gotify-Key").orElse(null));
|
||||
Assertions.assertEquals("application/json", request.headers().firstValue("Content-Type").orElse(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendGotifySkipsWhenMissingConfig() throws Exception {
|
||||
UserSettings settings = newSettings(NotificationType.GOTIFY);
|
||||
settings.setNotificationToken("token");
|
||||
notificationService.notify(settings, newSubscription("F"), newEntry("T", "U"));
|
||||
Mockito.verify(httpClient, Mockito.never()).send(Mockito.any(), Mockito.any());
|
||||
|
||||
UserSettings settings2 = newSettings(NotificationType.GOTIFY);
|
||||
settings2.setNotificationServerUrl("https://gotify.example.com");
|
||||
notificationService.notify(settings2, newSubscription("F"), newEntry("T", "U"));
|
||||
Mockito.verify(httpClient, Mockito.never()).send(Mockito.any(), Mockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendPushoverBuildsCorrectRequest() throws Exception {
|
||||
stubHttpClient();
|
||||
|
||||
UserSettings settings = newSettings(NotificationType.PUSHOVER);
|
||||
settings.setNotificationToken("po-token");
|
||||
settings.setNotificationUserKey("po-user");
|
||||
|
||||
FeedSubscription sub = newSubscription("My Feed");
|
||||
FeedEntry entry = newEntry("New Article", "https://example.com/article");
|
||||
|
||||
notificationService.notify(settings, sub, entry);
|
||||
|
||||
HttpRequest request = captureRequest();
|
||||
Assertions.assertEquals("https://api.pushover.net/1/messages.json", request.uri().toString());
|
||||
Assertions.assertEquals("application/x-www-form-urlencoded", request.headers().firstValue("Content-Type").orElse(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendPushoverOmitsUrlWhenBlank() throws Exception {
|
||||
stubHttpClient();
|
||||
|
||||
UserSettings settings = newSettings(NotificationType.PUSHOVER);
|
||||
settings.setNotificationToken("po-token");
|
||||
settings.setNotificationUserKey("po-user");
|
||||
|
||||
FeedSubscription sub = newSubscription("My Feed");
|
||||
FeedEntry entry = newEntry("Title", "");
|
||||
|
||||
notificationService.notify(settings, sub, entry);
|
||||
|
||||
Mockito.verify(httpClient).send(Mockito.any(HttpRequest.class), Mockito.<BodyHandler<String>> any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendPushoverSkipsWhenMissingConfig() throws Exception {
|
||||
UserSettings settings = newSettings(NotificationType.PUSHOVER);
|
||||
settings.setNotificationUserKey("user");
|
||||
notificationService.notify(settings, newSubscription("F"), newEntry("T", "U"));
|
||||
Mockito.verify(httpClient, Mockito.never()).send(Mockito.any(), Mockito.any());
|
||||
|
||||
UserSettings settings2 = newSettings(NotificationType.PUSHOVER);
|
||||
settings2.setNotificationToken("token");
|
||||
notificationService.notify(settings2, newSubscription("F"), newEntry("T", "U"));
|
||||
Mockito.verify(httpClient, Mockito.never()).send(Mockito.any(), Mockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void notifyDoesNotPropagateExceptions() throws Exception {
|
||||
Mockito.when(httpClient.send(Mockito.any(HttpRequest.class), Mockito.<BodyHandler<String>> any()))
|
||||
.thenThrow(new IOException("connection failed"));
|
||||
|
||||
UserSettings settings = newSettings(NotificationType.NTFY);
|
||||
settings.setNotificationServerUrl("https://ntfy.example.com");
|
||||
settings.setNotificationTopic("topic");
|
||||
|
||||
Assertions.assertDoesNotThrow(() -> notificationService.notify(settings, newSubscription("Feed"), newEntry("Title", "url")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void notifyUsesNewEntryAsFallbackTitle() throws Exception {
|
||||
stubHttpClient();
|
||||
|
||||
UserSettings settings = newSettings(NotificationType.NTFY);
|
||||
settings.setNotificationServerUrl("https://ntfy.example.com");
|
||||
settings.setNotificationTopic("topic");
|
||||
|
||||
FeedSubscription sub = newSubscription("Feed");
|
||||
|
||||
FeedEntry entryNoContent = new FeedEntry();
|
||||
entryNoContent.setUrl("https://example.com");
|
||||
notificationService.notify(settings, sub, entryNoContent);
|
||||
|
||||
HttpRequest request = captureRequest();
|
||||
Assertions.assertEquals("Feed: New entry", request.headers().firstValue("Title").orElse(null));
|
||||
}
|
||||
|
||||
private UserSettings newSettings(NotificationType type) {
|
||||
UserSettings settings = new UserSettings();
|
||||
settings.setNotificationEnabled(true);
|
||||
settings.setNotificationType(type);
|
||||
return settings;
|
||||
}
|
||||
|
||||
private FeedSubscription newSubscription(String title) {
|
||||
FeedSubscription sub = new FeedSubscription();
|
||||
sub.setTitle(title);
|
||||
return sub;
|
||||
}
|
||||
|
||||
private FeedEntry newEntry(String title, String url) {
|
||||
FeedEntryContent content = new FeedEntryContent();
|
||||
content.setTitle(title);
|
||||
FeedEntry entry = new FeedEntry();
|
||||
entry.setContent(content);
|
||||
entry.setUrl(url);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package com.commafeed.backend.service;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockserver.client.MockServerClient;
|
||||
import org.mockserver.junit.jupiter.MockServerExtension;
|
||||
import org.mockserver.model.HttpRequest;
|
||||
import org.mockserver.model.HttpResponse;
|
||||
import org.mockserver.model.JsonBody;
|
||||
import org.mockserver.model.MediaType;
|
||||
|
||||
import com.codahale.metrics.Meter;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.HttpClientFactory;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.commafeed.backend.model.FeedEntry;
|
||||
import com.commafeed.backend.model.FeedEntryContent;
|
||||
import com.commafeed.backend.model.FeedSubscription;
|
||||
import com.commafeed.backend.model.UserSettings;
|
||||
import com.commafeed.backend.model.UserSettings.PushNotificationType;
|
||||
|
||||
@ExtendWith(MockServerExtension.class)
|
||||
class PushNotificationServiceTest {
|
||||
|
||||
private MockServerClient mockServerClient;
|
||||
private PushNotificationService pushNotificationService;
|
||||
private CommaFeedConfiguration config;
|
||||
private UserSettings userSettings;
|
||||
private FeedSubscription subscription;
|
||||
private FeedEntry entry;
|
||||
|
||||
@BeforeEach
|
||||
void init(MockServerClient mockServerClient) {
|
||||
this.mockServerClient = mockServerClient;
|
||||
this.mockServerClient.reset();
|
||||
|
||||
this.config = Mockito.mock(CommaFeedConfiguration.class, Mockito.RETURNS_DEEP_STUBS);
|
||||
Mockito.when(config.pushNotifications().enabled()).thenReturn(true);
|
||||
Mockito.when(config.pushNotifications().threads()).thenReturn(1);
|
||||
Mockito.when(config.httpClient().responseTimeout()).thenReturn(Duration.ofSeconds(30));
|
||||
|
||||
HttpClientFactory httpClientFactory = new HttpClientFactory(config, Mockito.mock(com.commafeed.CommaFeedVersion.class));
|
||||
MetricRegistry metricRegistry = Mockito.mock(MetricRegistry.class);
|
||||
Mockito.when(metricRegistry.meter(Mockito.anyString())).thenReturn(Mockito.mock(Meter.class));
|
||||
|
||||
this.pushNotificationService = new PushNotificationService(httpClientFactory, metricRegistry, config);
|
||||
|
||||
this.userSettings = new UserSettings();
|
||||
|
||||
this.subscription = createSubscription("Test Feed");
|
||||
this.entry = createEntry("Test Entry", "http://example.com/entry");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNtfyNotification() {
|
||||
userSettings.setPushNotificationType(PushNotificationType.NTFY);
|
||||
userSettings.setPushNotificationServerUrl("http://localhost:" + mockServerClient.getPort());
|
||||
userSettings.setPushNotificationTopic("test-topic");
|
||||
userSettings.setPushNotificationUserSecret("test-secret");
|
||||
|
||||
mockServerClient.when(HttpRequest.request()
|
||||
.withMethod("POST")
|
||||
.withPath("/test-topic")
|
||||
.withHeader("Title", "Test Feed")
|
||||
.withHeader("Click", "http://example.com/entry")
|
||||
.withHeader("Authorization", "Bearer test-secret")
|
||||
.withBody("Test Entry")).respond(HttpResponse.response().withStatusCode(200));
|
||||
|
||||
Assertions.assertDoesNotThrow(() -> pushNotificationService.notify(userSettings, subscription, entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGotifyNotification() {
|
||||
userSettings.setPushNotificationType(PushNotificationType.GOTIFY);
|
||||
userSettings.setPushNotificationServerUrl("http://localhost:" + mockServerClient.getPort());
|
||||
userSettings.setPushNotificationUserSecret("gotify-token");
|
||||
|
||||
mockServerClient.when(HttpRequest.request()
|
||||
.withMethod("POST")
|
||||
.withPath("/message")
|
||||
.withHeader("X-Gotify-Key", "gotify-token")
|
||||
.withContentType(MediaType.APPLICATION_JSON_UTF_8)
|
||||
.withBody(JsonBody.json("""
|
||||
{
|
||||
"title": "Test Feed",
|
||||
"message": "Test Entry",
|
||||
"priority": 5,
|
||||
"extras": {
|
||||
"client::notification": {
|
||||
"click": {
|
||||
"url": "http://example.com/entry"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""))).respond(HttpResponse.response().withStatusCode(200));
|
||||
|
||||
Assertions.assertDoesNotThrow(() -> pushNotificationService.notify(userSettings, subscription, entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPushNotificationDisabled() {
|
||||
Mockito.when(config.pushNotifications().enabled()).thenReturn(false);
|
||||
userSettings.setPushNotificationType(PushNotificationType.NTFY);
|
||||
userSettings.setPushNotificationServerUrl("http://localhost:" + mockServerClient.getPort());
|
||||
userSettings.setPushNotificationTopic("test-topic");
|
||||
|
||||
Assertions.assertDoesNotThrow(() -> pushNotificationService.notify(userSettings, subscription, entry));
|
||||
mockServerClient.verifyZeroInteractions();
|
||||
}
|
||||
|
||||
private static FeedSubscription createSubscription(String title) {
|
||||
FeedSubscription subscription = new FeedSubscription();
|
||||
subscription.setTitle(title);
|
||||
subscription.setFeed(new Feed());
|
||||
|
||||
return subscription;
|
||||
}
|
||||
|
||||
private static FeedEntry createEntry(String title, String url) {
|
||||
FeedEntry entry = new FeedEntry();
|
||||
|
||||
FeedEntryContent content = new FeedEntryContent();
|
||||
content.setTitle(title);
|
||||
|
||||
entry.setContent(content);
|
||||
entry.setUrl(url);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ public abstract class BaseIT {
|
||||
|
||||
private static final HttpRequest FEED_REQUEST = HttpRequest.request().withMethod("GET").withPath("/");
|
||||
|
||||
@Getter
|
||||
private MockServerClient mockServerClient;
|
||||
private Client client;
|
||||
private String feedUrl;
|
||||
@@ -122,10 +123,15 @@ public abstract class BaseIT {
|
||||
}
|
||||
|
||||
protected Long subscribe(String feedUrl, String categoryId) {
|
||||
return subscribe(feedUrl, categoryId, false);
|
||||
}
|
||||
|
||||
protected Long subscribe(String feedUrl, String categoryId, boolean pushNotificationsEnabled) {
|
||||
SubscribeRequest subscribeRequest = new SubscribeRequest();
|
||||
subscribeRequest.setUrl(feedUrl);
|
||||
subscribeRequest.setTitle("my title for this feed");
|
||||
subscribeRequest.setCategoryId(categoryId);
|
||||
subscribeRequest.setPushNotificationsEnabled(pushNotificationsEnabled);
|
||||
return RestAssured.given()
|
||||
.body(subscribeRequest)
|
||||
.contentType(ContentType.JSON)
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.commafeed.integration;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.awaitility.Awaitility;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockserver.model.HttpRequest;
|
||||
import org.mockserver.model.HttpResponse;
|
||||
import org.mockserver.verify.VerificationTimes;
|
||||
|
||||
import com.commafeed.TestConstants;
|
||||
import com.commafeed.backend.model.UserSettings.PushNotificationType;
|
||||
import com.commafeed.frontend.model.Settings;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.restassured.RestAssured;
|
||||
import io.restassured.http.ContentType;
|
||||
|
||||
@QuarkusTest
|
||||
class PushNotificationIT extends BaseIT {
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
initialSetup(TestConstants.ADMIN_USERNAME, TestConstants.ADMIN_PASSWORD);
|
||||
RestAssured.authentication = RestAssured.preemptive().basic(TestConstants.ADMIN_USERNAME, TestConstants.ADMIN_PASSWORD);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
RestAssured.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
void receivedPushNotifications() {
|
||||
// mock ntfy server
|
||||
HttpRequest ntfyPost = HttpRequest.request().withMethod("POST").withPath("/ntfy/integration-test");
|
||||
getMockServerClient().when(ntfyPost).respond(HttpResponse.response().withStatusCode(200));
|
||||
|
||||
// enable push notifications
|
||||
Settings settings = RestAssured.given().get("rest/user/settings").then().extract().as(Settings.class);
|
||||
settings.getPushNotificationSettings().setType(PushNotificationType.NTFY);
|
||||
settings.getPushNotificationSettings().setServerUrl("http://localhost:" + getMockServerClient().getPort() + "/ntfy");
|
||||
settings.getPushNotificationSettings().setTopic("integration-test");
|
||||
RestAssured.given().body(settings).contentType(ContentType.JSON).post("rest/user/settings").then().statusCode(200);
|
||||
|
||||
// subscribe with push notifications enabled
|
||||
subscribe(getFeedUrl(), null, true);
|
||||
|
||||
// await push notification for the two entries in the feed
|
||||
Awaitility.await()
|
||||
.atMost(Duration.ofSeconds(20))
|
||||
.untilAsserted(() -> getMockServerClient().verify(ntfyPost, VerificationTimes.exactly(2)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,6 +23,7 @@ class ServerIT extends BaseIT {
|
||||
Assertions.assertEquals(30000, serverInfos.getTreeReloadInterval());
|
||||
Assertions.assertEquals(60000, serverInfos.getForceRefreshCooldownDuration());
|
||||
Assertions.assertEquals(4, serverInfos.getMinimumPasswordLength());
|
||||
Assertions.assertTrue(serverInfos.isPushNotificationsEnabled());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user