From f7ae2e6689d25c416747fc6157238f86d22b56c0 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 21 Jul 2025 07:58:59 +0200 Subject: [PATCH] add even more integration tests --- commafeed-client/src/app/client.ts | 2 +- .../favicon/YoutubeFaviconFetcher.java | 10 +- .../com/commafeed/backend/feed/FeedUtils.java | 5 +- .../backend/model/FeedEntryContent.java | 12 - .../com/commafeed/frontend/model/Entry.java | 2 +- .../frontend/resource/AdminREST.java | 4 +- .../tools/CommaFeedPropertiesGenerator.java | 29 +- .../src/main/resources/application.properties | 4 - .../favicon/FacebookFaviconFetcherTest.java | 107 ++++ .../favicon/YoutubeFaviconFetcherTest.java | 215 +++++++ .../backend/model/FeedEntryContentTest.java | 140 +++++ .../InPageReferenceFeedURLProviderTest.java | 91 +++ .../commafeed/frontend/model/EntryTest.java | 41 ++ .../com/commafeed/integration/BaseIT.java | 24 +- .../com/commafeed/integration/SecurityIT.java | 8 +- .../commafeed/integration/WebSocketIT.java | 4 +- .../commafeed/integration/rest/AdminIT.java | 51 +- .../integration/rest/CategoryIT.java | 125 +++- .../commafeed/integration/rest/FeedIT.java | 44 +- .../commafeed/integration/rest/FeverIT.java | 26 +- .../commafeed/integration/rest/UserIT.java | 22 +- .../integration/servlet/CustomCodeIT.java | 10 +- .../CommaFeedPropertiesGeneratorTest.java | 27 + .../src/test/resources/application.properties | 2 + .../resources/properties/output.properties | 150 +++++ .../properties/quarkus-config-javadoc.yaml | 186 ++++++ .../properties/quarkus-config-model.yaml | 538 ++++++++++++++++++ 27 files changed, 1772 insertions(+), 107 deletions(-) create mode 100644 commafeed-server/src/test/java/com/commafeed/backend/favicon/FacebookFaviconFetcherTest.java create mode 100644 commafeed-server/src/test/java/com/commafeed/backend/favicon/YoutubeFaviconFetcherTest.java create mode 100644 commafeed-server/src/test/java/com/commafeed/backend/model/FeedEntryContentTest.java create mode 100644 commafeed-server/src/test/java/com/commafeed/backend/urlprovider/InPageReferenceFeedURLProviderTest.java create mode 100644 commafeed-server/src/test/java/com/commafeed/frontend/model/EntryTest.java create mode 100644 commafeed-server/src/test/java/com/commafeed/tools/CommaFeedPropertiesGeneratorTest.java create mode 100644 commafeed-server/src/test/resources/application.properties create mode 100644 commafeed-server/src/test/resources/properties/output.properties create mode 100644 commafeed-server/src/test/resources/properties/quarkus-config-javadoc.yaml create mode 100644 commafeed-server/src/test/resources/properties/quarkus-config-model.yaml diff --git a/commafeed-client/src/app/client.ts b/commafeed-client/src/app/client.ts index e34759ed..f124c8f9 100644 --- a/commafeed-client/src/app/client.ts +++ b/commafeed-client/src/app/client.ts @@ -105,7 +105,7 @@ export const client = { }, admin: { getAllUsers: async () => await axiosInstance.get("admin/user/getAll"), - saveUser: async (req: AdminSaveUserRequest) => await axiosInstance.post("admin/user/save", req), + saveUser: async (req: AdminSaveUserRequest) => await axiosInstance.post("admin/user/save", req), deleteUser: async (req: IDRequest) => await axiosInstance.post("admin/user/delete", req), getMetrics: async () => await axiosInstance.get("admin/metrics"), }, diff --git a/commafeed-server/src/main/java/com/commafeed/backend/favicon/YoutubeFaviconFetcher.java b/commafeed-server/src/main/java/com/commafeed/backend/favicon/YoutubeFaviconFetcher.java index b8a256a1..4b834251 100644 --- a/commafeed-server/src/main/java/com/commafeed/backend/favicon/YoutubeFaviconFetcher.java +++ b/commafeed-server/src/main/java/com/commafeed/backend/favicon/YoutubeFaviconFetcher.java @@ -32,6 +32,8 @@ import lombok.extern.slf4j.Slf4j; @Singleton public class YoutubeFaviconFetcher extends AbstractFaviconFetcher { + private static final String PART_SNIPPET = "snippet"; + private static final JsonPointer CHANNEL_THUMBNAIL_URL = JsonPointer.compile("/items/0/snippet/thumbnails/default/url"); private static final JsonPointer PLAYLIST_CHANNEL_ID = JsonPointer.compile("/items/0/snippet/channelId"); @@ -86,7 +88,7 @@ public class YoutubeFaviconFetcher extends AbstractFaviconFetcher { bytes = iconResult.getContent(); contentType = iconResult.getContentType(); } catch (Exception e) { - log.error("Failed to retrieve YouTube icon", e); + log.debug("Failed to retrieve YouTube icon", e); } if (!isValidIconResponse(bytes, contentType)) { @@ -98,7 +100,7 @@ public class YoutubeFaviconFetcher extends AbstractFaviconFetcher { private byte[] fetchForUser(String googleAuthKey, String userId) throws IOException, NotModifiedException, TooManyRequestsException, HostNotAllowedException, SchemeNotAllowedException { URI uri = UriBuilder.fromUri("https://www.googleapis.com/youtube/v3/channels") - .queryParam("part", "snippet") + .queryParam("part", PART_SNIPPET) .queryParam("key", googleAuthKey) .queryParam("forUsername", userId) .build(); @@ -108,7 +110,7 @@ public class YoutubeFaviconFetcher extends AbstractFaviconFetcher { private byte[] fetchForChannel(String googleAuthKey, String channelId) throws IOException, NotModifiedException, TooManyRequestsException, HostNotAllowedException, SchemeNotAllowedException { URI uri = UriBuilder.fromUri("https://www.googleapis.com/youtube/v3/channels") - .queryParam("part", "snippet") + .queryParam("part", PART_SNIPPET) .queryParam("key", googleAuthKey) .queryParam("id", channelId) .build(); @@ -118,7 +120,7 @@ public class YoutubeFaviconFetcher extends AbstractFaviconFetcher { private byte[] fetchForPlaylist(String googleAuthKey, String playlistId) throws IOException, NotModifiedException, TooManyRequestsException, HostNotAllowedException, SchemeNotAllowedException { URI uri = UriBuilder.fromUri("https://www.googleapis.com/youtube/v3/playlists") - .queryParam("part", "snippet") + .queryParam("part", PART_SNIPPET) .queryParam("key", googleAuthKey) .queryParam("id", playlistId) .build(); diff --git a/commafeed-server/src/main/java/com/commafeed/backend/feed/FeedUtils.java b/commafeed-server/src/main/java/com/commafeed/backend/feed/FeedUtils.java index 31d3d6bc..b63d6609 100644 --- a/commafeed-server/src/main/java/com/commafeed/backend/feed/FeedUtils.java +++ b/commafeed-server/src/main/java/com/commafeed/backend/feed/FeedUtils.java @@ -26,10 +26,7 @@ import lombok.extern.slf4j.Slf4j; public class FeedUtils { public static String truncate(String string, int length) { - if (string != null) { - string = string.substring(0, Math.min(length, string.length())); - } - return string; + return StringUtils.truncate(string, length); } public static boolean isRTL(String title, String content) { diff --git a/commafeed-server/src/main/java/com/commafeed/backend/model/FeedEntryContent.java b/commafeed-server/src/main/java/com/commafeed/backend/model/FeedEntryContent.java index 220bcf2a..0440bf89 100644 --- a/commafeed-server/src/main/java/com/commafeed/backend/model/FeedEntryContent.java +++ b/commafeed-server/src/main/java/com/commafeed/backend/model/FeedEntryContent.java @@ -14,7 +14,6 @@ import jakarta.persistence.Table; import org.apache.commons.lang3.builder.EqualsBuilder; import org.hibernate.annotations.JdbcTypeCode; -import com.commafeed.backend.feed.FeedUtils; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; @@ -99,15 +98,4 @@ public class FeedEntryContent extends AbstractModel { .append(mediaThumbnailHeight, c.mediaThumbnailHeight) .build(); } - - public boolean isRTL() { - if (direction == Direction.RTL) { - return true; - } else if (direction == Direction.LTR) { - return false; - } else { - // detect on the fly for content that was inserted before the direction field was added - return FeedUtils.isRTL(title, content); - } - } } diff --git a/commafeed-server/src/main/java/com/commafeed/frontend/model/Entry.java b/commafeed-server/src/main/java/com/commafeed/frontend/model/Entry.java index 33762911..491be40a 100644 --- a/commafeed-server/src/main/java/com/commafeed/frontend/model/Entry.java +++ b/commafeed-server/src/main/java/com/commafeed/frontend/model/Entry.java @@ -128,7 +128,7 @@ public class Entry implements Serializable { entry.setTags(status.getTags().stream().map(FeedEntryTag::getName).toList()); if (content != null) { - entry.setRtl(content.isRTL()); + entry.setRtl(content.getDirection() == FeedEntryContent.Direction.RTL); entry.setTitle(content.getTitle()); entry.setContent(proxyImages ? FeedUtils.proxyImages(content.getContent()) : content.getContent()); entry.setAuthor(content.getAuthor()); diff --git a/commafeed-server/src/main/java/com/commafeed/frontend/resource/AdminREST.java b/commafeed-server/src/main/java/com/commafeed/frontend/resource/AdminREST.java index 18e86e7e..e403e217 100644 --- a/commafeed-server/src/main/java/com/commafeed/frontend/resource/AdminREST.java +++ b/commafeed-server/src/main/java/com/commafeed/frontend/resource/AdminREST.java @@ -80,7 +80,7 @@ public class AdminREST { roles.add(Role.ADMIN); } try { - userService.register(req.getName(), req.getPassword(), req.getEmail(), roles, true); + id = userService.register(req.getName(), req.getPassword(), req.getEmail(), roles, true).getId(); } catch (Exception e) { return Response.status(Status.CONFLICT).entity(e.getMessage()).build(); } @@ -113,7 +113,7 @@ public class AdminREST { } } - return Response.ok().build(); + return Response.ok(id).build(); } diff --git a/commafeed-server/src/main/java/com/commafeed/tools/CommaFeedPropertiesGenerator.java b/commafeed-server/src/main/java/com/commafeed/tools/CommaFeedPropertiesGenerator.java index c3d540c3..348c6d58 100644 --- a/commafeed-server/src/main/java/com/commafeed/tools/CommaFeedPropertiesGenerator.java +++ b/commafeed-server/src/main/java/com/commafeed/tools/CommaFeedPropertiesGenerator.java @@ -1,6 +1,9 @@ package com.commafeed.tools; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -8,6 +11,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import org.apache.commons.io.IOUtils; + import com.commafeed.CommaFeedConfiguration; import io.quarkus.annotation.processor.Outputs; @@ -31,16 +36,22 @@ public class CommaFeedPropertiesGenerator { private final List lines = new ArrayList<>(); public static void main(String[] args) throws Exception { - new CommaFeedPropertiesGenerator().generate(args); - } - - private void generate(String[] args) throws IOException { Path targetPath = Paths.get(args[0]); - ResolvedModel resolvedModel = JacksonMappers.yamlObjectReader() - .readValue(targetPath.resolve(Outputs.QUARKUS_CONFIG_DOC_MODEL).toFile(), ResolvedModel.class); - JavadocElements javadocElements = JacksonMappers.yamlObjectReader() - .readValue(targetPath.resolve(Outputs.QUARKUS_CONFIG_DOC_JAVADOC).toFile(), JavadocElements.class); + Path modelPath = targetPath.resolve(Outputs.QUARKUS_CONFIG_DOC_MODEL); + Path javadocPath = targetPath.resolve(Outputs.QUARKUS_CONFIG_DOC_JAVADOC); + Path outputPath = targetPath.resolve("quarkus-generated-doc").resolve("application.properties"); + + try (InputStream model = Files.newInputStream(modelPath); + InputStream javadoc = Files.newInputStream(javadocPath); + OutputStream output = Files.newOutputStream(outputPath)) { + new CommaFeedPropertiesGenerator().generate(model, javadoc, output); + } + } + + void generate(InputStream model, InputStream javadoc, OutputStream output) throws IOException { + ResolvedModel resolvedModel = JacksonMappers.yamlObjectReader().readValue(model, ResolvedModel.class); + JavadocElements javadocElements = JacksonMappers.yamlObjectReader().readValue(javadoc, JavadocElements.class); for (ConfigRoot configRoot : resolvedModel.getConfigRoots()) { for (AbstractConfigItem item : configRoot.getItems()) { @@ -48,7 +59,7 @@ public class CommaFeedPropertiesGenerator { } } - Files.writeString(targetPath.resolve("quarkus-generated-doc").resolve("application.properties"), String.join("\n", lines)); + IOUtils.write(String.join("\n", lines), output, StandardCharsets.UTF_8); } private void handleAbstractConfigItem(AbstractConfigItem item, JavadocElements javadocElements) { diff --git a/commafeed-server/src/main/resources/application.properties b/commafeed-server/src/main/resources/application.properties index fd2580b0..e28b20d5 100644 --- a/commafeed-server/src/main/resources/application.properties +++ b/commafeed-server/src/main/resources/application.properties @@ -41,10 +41,6 @@ quarkus.native.add-all-charsets=true # fix for https://github.com/Athou/commafeed/issues/1795 quarkus.native.additional-build-args=-H:PageSize=65536 -# jacoco -## we generate the report ourselves by aggregating the unit tests and integration tests jacoco.exec files -quarkus.jacoco.report=false - # dev profile overrides %dev.quarkus.http.port=8083 diff --git a/commafeed-server/src/test/java/com/commafeed/backend/favicon/FacebookFaviconFetcherTest.java b/commafeed-server/src/test/java/com/commafeed/backend/favicon/FacebookFaviconFetcherTest.java new file mode 100644 index 00000000..026d7416 --- /dev/null +++ b/commafeed-server/src/test/java/com/commafeed/backend/favicon/FacebookFaviconFetcherTest.java @@ -0,0 +1,107 @@ +package com.commafeed.backend.favicon; + +import java.time.Duration; + +import jakarta.ws.rs.core.MediaType; + +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.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.commafeed.backend.HttpGetter; +import com.commafeed.backend.HttpGetter.HttpResult; +import com.commafeed.backend.model.Feed; + +@ExtendWith(MockitoExtension.class) +class FacebookFaviconFetcherTest { + + @Mock + private HttpGetter httpGetter; + + private FacebookFaviconFetcher faviconFetcher; + + @BeforeEach + void init() { + faviconFetcher = new FacebookFaviconFetcher(httpGetter); + } + + @Test + void testFetchWithValidFacebookUrl() throws Exception { + Feed feed = new Feed(); + feed.setUrl("https://www.facebook.com/something?id=validUserId"); + + byte[] iconBytes = new byte[1000]; + String contentType = "image/png"; + + HttpResult httpResult = new HttpResult(iconBytes, contentType, null, null, null, Duration.ZERO); + Mockito.when(httpGetter.get("https://graph.facebook.com/validUserId/picture?type=square&height=16")).thenReturn(httpResult); + + Favicon result = faviconFetcher.fetch(feed); + + Assertions.assertNotNull(result); + Assertions.assertEquals(iconBytes, result.getIcon()); + Assertions.assertTrue(result.getMediaType().isCompatible(MediaType.valueOf(contentType))); + } + + @Test + void testFetchWithNonFacebookUrl() { + Feed feed = new Feed(); + feed.setUrl("https://example.com"); + + Assertions.assertNull(faviconFetcher.fetch(feed)); + Mockito.verifyNoInteractions(httpGetter); + } + + @Test + void testFetchWithFacebookUrlButNoUserId() { + Feed feed = new Feed(); + feed.setUrl("https://www.facebook.com/something"); + + Assertions.assertNull(faviconFetcher.fetch(feed)); + Mockito.verifyNoInteractions(httpGetter); + } + + @Test + void testFetchWithHttpGetterException() throws Exception { + Feed feed = new Feed(); + feed.setUrl("https://www.facebook.com/something?id=validUserId"); + + Mockito.when(httpGetter.get("https://graph.facebook.com/validUserId/picture?type=square&height=16")) + .thenThrow(new RuntimeException("Network error")); + + Assertions.assertNull(faviconFetcher.fetch(feed)); + } + + @Test + void testFetchWithInvalidIconResponse() throws Exception { + Feed feed = new Feed(); + feed.setUrl("https://www.facebook.com/something?id=validUserId"); + + // Create a byte array that's too small + byte[] iconBytes = new byte[50]; + String contentType = "image/png"; + + HttpResult httpResult = new HttpResult(iconBytes, contentType, null, null, null, Duration.ZERO); + Mockito.when(httpGetter.get("https://graph.facebook.com/validUserId/picture?type=square&height=16")).thenReturn(httpResult); + + Assertions.assertNull(faviconFetcher.fetch(feed)); + } + + @Test + void testFetchWithBlacklistedContentType() throws Exception { + Feed feed = new Feed(); + feed.setUrl("https://www.facebook.com/something?id=validUserId"); + + byte[] iconBytes = new byte[1000]; + String contentType = "application/xml"; // Blacklisted content type + + HttpResult httpResult = new HttpResult(iconBytes, contentType, null, null, null, Duration.ZERO); + Mockito.when(httpGetter.get("https://graph.facebook.com/validUserId/picture?type=square&height=16")).thenReturn(httpResult); + + Assertions.assertNull(faviconFetcher.fetch(feed)); + } +} \ No newline at end of file diff --git a/commafeed-server/src/test/java/com/commafeed/backend/favicon/YoutubeFaviconFetcherTest.java b/commafeed-server/src/test/java/com/commafeed/backend/favicon/YoutubeFaviconFetcherTest.java new file mode 100644 index 00000000..87814804 --- /dev/null +++ b/commafeed-server/src/test/java/com/commafeed/backend/favicon/YoutubeFaviconFetcherTest.java @@ -0,0 +1,215 @@ +package com.commafeed.backend.favicon; + +import java.io.IOException; +import java.time.Duration; +import java.util.Optional; + +import jakarta.ws.rs.core.MediaType; + +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.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.commafeed.CommaFeedConfiguration; +import com.commafeed.backend.HttpGetter; +import com.commafeed.backend.HttpGetter.HttpResult; +import com.commafeed.backend.model.Feed; +import com.fasterxml.jackson.core.JsonPointer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +@ExtendWith(MockitoExtension.class) +class YoutubeFaviconFetcherTest { + + @Mock + private HttpGetter httpGetter; + + @Mock + private CommaFeedConfiguration config; + + @Mock + private ObjectMapper objectMapper; + + private YoutubeFaviconFetcher faviconFetcher; + + @BeforeEach + void init() { + faviconFetcher = new YoutubeFaviconFetcher(httpGetter, config, objectMapper); + } + + @Test + void testFetchWithNonYoutubeUrl() { + Feed feed = new Feed(); + feed.setUrl("https://example.com/feed"); + + Assertions.assertNull(faviconFetcher.fetch(feed)); + Mockito.verifyNoInteractions(httpGetter, objectMapper); + } + + @Test + void testFetchWithNoGoogleAuthKey() { + Feed feed = new Feed(); + feed.setUrl("https://youtube.com/feeds/videos.xml?user=someUser"); + + Mockito.when(config.googleAuthKey()).thenReturn(Optional.empty()); + + Assertions.assertNull(faviconFetcher.fetch(feed)); + Mockito.verify(config).googleAuthKey(); + Mockito.verifyNoInteractions(httpGetter, objectMapper); + } + + @Test + void testFetchForUser() throws Exception { + Feed feed = new Feed(); + feed.setUrl("https://youtube.com/feeds/videos.xml?user=testUser"); + + Mockito.when(config.googleAuthKey()).thenReturn(Optional.of("test-api-key")); + + byte[] apiResponse = """ + {"items":[{"snippet":{"thumbnails":{"default":{"url":"https://example.com/icon.png"}}}}]}""".getBytes(); + HttpResult apiHttpResult = new HttpResult(apiResponse, "application/json", null, null, null, Duration.ZERO); + Mockito.when(httpGetter.get("https://www.googleapis.com/youtube/v3/channels?part=snippet&key=test-api-key&forUsername=testUser")) + .thenReturn(apiHttpResult); + + JsonNode jsonNode = new ObjectMapper().readTree(apiResponse); + Mockito.when(objectMapper.readTree(apiResponse)).thenReturn(jsonNode); + + byte[] iconBytes = new byte[1000]; + String contentType = "image/png"; + HttpResult iconHttpResult = new HttpResult(iconBytes, contentType, null, null, null, Duration.ZERO); + Mockito.when(httpGetter.get("https://example.com/icon.png")).thenReturn(iconHttpResult); + + Favicon result = faviconFetcher.fetch(feed); + + Assertions.assertNotNull(result); + Assertions.assertEquals(iconBytes, result.getIcon()); + Assertions.assertTrue(result.getMediaType().isCompatible(MediaType.valueOf(contentType))); + } + + @Test + void testFetchForChannel() throws Exception { + Feed feed = new Feed(); + feed.setUrl("https://youtube.com/feeds/videos.xml?channel_id=testChannelId"); + + Mockito.when(config.googleAuthKey()).thenReturn(Optional.of("test-api-key")); + + byte[] apiResponse = """ + {"items":[{"snippet":{"thumbnails":{"default":{"url":"https://example.com/icon.png"}}}}]}""".getBytes(); + HttpResult apiHttpResult = new HttpResult(apiResponse, "application/json", null, null, null, Duration.ZERO); + Mockito.when(httpGetter.get("https://www.googleapis.com/youtube/v3/channels?part=snippet&key=test-api-key&id=testChannelId")) + .thenReturn(apiHttpResult); + + JsonNode jsonNode = new ObjectMapper().readTree(apiResponse); + Mockito.when(objectMapper.readTree(apiResponse)).thenReturn(jsonNode); + + byte[] iconBytes = new byte[1000]; + String contentType = "image/png"; + HttpResult iconHttpResult = new HttpResult(iconBytes, contentType, null, null, null, Duration.ZERO); + Mockito.when(httpGetter.get("https://example.com/icon.png")).thenReturn(iconHttpResult); + + Favicon result = faviconFetcher.fetch(feed); + + Assertions.assertNotNull(result); + Assertions.assertEquals(iconBytes, result.getIcon()); + Assertions.assertTrue(result.getMediaType().isCompatible(MediaType.valueOf(contentType))); + } + + @Test + void testFetchForPlaylist() throws Exception { + Feed feed = new Feed(); + feed.setUrl("https://youtube.com/feeds/videos.xml?playlist_id=testPlaylistId"); + + Mockito.when(config.googleAuthKey()).thenReturn(Optional.of("test-api-key")); + + byte[] playlistResponse = """ + {"items":[{"snippet":{"channelId":"testChannelId"}}]}""".getBytes(); + HttpResult playlistHttpResult = new HttpResult(playlistResponse, "application/json", null, null, null, Duration.ZERO); + Mockito.when(httpGetter.get("https://www.googleapis.com/youtube/v3/playlists?part=snippet&key=test-api-key&id=testPlaylistId")) + .thenReturn(playlistHttpResult); + + JsonNode playlistJsonNode = new ObjectMapper().readTree(playlistResponse); + Mockito.when(objectMapper.readTree(playlistResponse)).thenReturn(playlistJsonNode); + + byte[] channelResponse = """ + {"items":[{"snippet":{"thumbnails":{"default":{"url":"https://example.com/icon.png"}}}}]}""".getBytes(); + HttpResult channelHttpResult = new HttpResult(channelResponse, "application/json", null, null, null, Duration.ZERO); + Mockito.when(httpGetter.get("https://www.googleapis.com/youtube/v3/channels?part=snippet&key=test-api-key&id=testChannelId")) + .thenReturn(channelHttpResult); + + JsonNode channelJsonNode = new ObjectMapper().readTree(channelResponse); + Mockito.when(objectMapper.readTree(channelResponse)).thenReturn(channelJsonNode); + + byte[] iconBytes = new byte[1000]; + String contentType = "image/png"; + HttpResult iconHttpResult = new HttpResult(iconBytes, contentType, null, null, null, Duration.ZERO); + Mockito.when(httpGetter.get("https://example.com/icon.png")).thenReturn(iconHttpResult); + + Favicon result = faviconFetcher.fetch(feed); + + Assertions.assertNotNull(result); + Assertions.assertEquals(iconBytes, result.getIcon()); + Assertions.assertTrue(result.getMediaType().isCompatible(MediaType.valueOf(contentType))); + } + + @Test + void testFetchWithHttpGetterException() throws Exception { + Feed feed = new Feed(); + feed.setUrl("https://youtube.com/feeds/videos.xml?user=testUser"); + + Mockito.when(config.googleAuthKey()).thenReturn(Optional.of("test-api-key")); + + Mockito.when(httpGetter.get("https://www.googleapis.com/youtube/v3/channels?part=snippet&key=test-api-key&forUsername=testUser")) + .thenThrow(new IOException("Network error")); + + Assertions.assertNull(faviconFetcher.fetch(feed)); + } + + @Test + void testFetchWithInvalidIconResponse() throws Exception { + Feed feed = new Feed(); + feed.setUrl("https://youtube.com/feeds/videos.xml?user=testUser"); + + Mockito.when(config.googleAuthKey()).thenReturn(Optional.of("test-api-key")); + + byte[] apiResponse = """ + {"items":[{"snippet":{"thumbnails":{"default":{"url":"https://example.com/icon.png"}}}}]}""".getBytes(); + HttpResult apiHttpResult = new HttpResult(apiResponse, "application/json", null, null, null, Duration.ZERO); + Mockito.when(httpGetter.get("https://www.googleapis.com/youtube/v3/channels?part=snippet&key=test-api-key&forUsername=testUser")) + .thenReturn(apiHttpResult); + + JsonNode jsonNode = new ObjectMapper().readTree(apiResponse); + Mockito.when(objectMapper.readTree(apiResponse)).thenReturn(jsonNode); + + // Create a byte array that's too small + byte[] iconBytes = new byte[50]; + String contentType = "image/png"; + HttpResult iconHttpResult = new HttpResult(iconBytes, contentType, null, null, null, Duration.ZERO); + Mockito.when(httpGetter.get("https://example.com/icon.png")).thenReturn(iconHttpResult); + + Assertions.assertNull(faviconFetcher.fetch(feed)); + } + + @Test + void testFetchWithEmptyApiResponse() throws Exception { + Feed feed = new Feed(); + feed.setUrl("https://youtube.com/feeds/videos.xml?user=testUser"); + + Mockito.when(config.googleAuthKey()).thenReturn(Optional.of("test-api-key")); + + byte[] apiResponse = "{}".getBytes(); + HttpResult apiHttpResult = new HttpResult(apiResponse, "application/json", null, null, null, Duration.ZERO); + Mockito.when(httpGetter.get("https://www.googleapis.com/youtube/v3/channels?part=snippet&key=test-api-key&forUsername=testUser")) + .thenReturn(apiHttpResult); + + JsonNode jsonNode = Mockito.mock(JsonNode.class); + Mockito.when(objectMapper.readTree(apiResponse)).thenReturn(jsonNode); + Mockito.when(jsonNode.at(Mockito.any(JsonPointer.class))).thenReturn(jsonNode); + Mockito.when(jsonNode.isMissingNode()).thenReturn(true); + + Assertions.assertNull(faviconFetcher.fetch(feed)); + } +} \ No newline at end of file diff --git a/commafeed-server/src/test/java/com/commafeed/backend/model/FeedEntryContentTest.java b/commafeed-server/src/test/java/com/commafeed/backend/model/FeedEntryContentTest.java new file mode 100644 index 00000000..c665cb69 --- /dev/null +++ b/commafeed-server/src/test/java/com/commafeed/backend/model/FeedEntryContentTest.java @@ -0,0 +1,140 @@ +package com.commafeed.backend.model; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class FeedEntryContentTest { + + @Nested + class EquivalentTo { + + @Test + void shouldReturnFalseWhenComparedWithNull() { + Assertions.assertFalse(new FeedEntryContent().equivalentTo(null)); + } + + @Test + void shouldReturnTrueWhenComparedWithIdenticalContent() { + FeedEntryContent content1 = createContent("title", "content", "author", "categories", "enclosureUrl", "enclosureType", + "mediaDescription", "mediaThumbnailUrl", 10, 20); + FeedEntryContent content2 = createContent("title", "content", "author", "categories", "enclosureUrl", "enclosureType", + "mediaDescription", "mediaThumbnailUrl", 10, 20); + Assertions.assertTrue(content1.equivalentTo(content2)); + } + + @Test + void shouldReturnFalseWhenTitleDiffers() { + FeedEntryContent content1 = createContent("title1", "content", "author", "categories", "enclosureUrl", "enclosureType", + "mediaDescription", "mediaThumbnailUrl", 10, 20); + FeedEntryContent content2 = createContent("title2", "content", "author", "categories", "enclosureUrl", "enclosureType", + "mediaDescription", "mediaThumbnailUrl", 10, 20); + Assertions.assertFalse(content1.equivalentTo(content2)); + } + + @Test + void shouldReturnFalseWhenContentDiffers() { + FeedEntryContent content1 = createContent("title", "content1", "author", "categories", "enclosureUrl", "enclosureType", + "mediaDescription", "mediaThumbnailUrl", 10, 20); + FeedEntryContent content2 = createContent("title", "content2", "author", "categories", "enclosureUrl", "enclosureType", + "mediaDescription", "mediaThumbnailUrl", 10, 20); + Assertions.assertFalse(content1.equivalentTo(content2)); + } + + @Test + void shouldReturnFalseWhenAuthorDiffers() { + FeedEntryContent content1 = createContent("title", "content", "author1", "categories", "enclosureUrl", "enclosureType", + "mediaDescription", "mediaThumbnailUrl", 10, 20); + FeedEntryContent content2 = createContent("title", "content", "author2", "categories", "enclosureUrl", "enclosureType", + "mediaDescription", "mediaThumbnailUrl", 10, 20); + Assertions.assertFalse(content1.equivalentTo(content2)); + } + + @Test + void shouldReturnFalseWhenCategoriesDiffer() { + FeedEntryContent content1 = createContent("title", "content", "author", "categories1", "enclosureUrl", "enclosureType", + "mediaDescription", "mediaThumbnailUrl", 10, 20); + FeedEntryContent content2 = createContent("title", "content", "author", "categories2", "enclosureUrl", "enclosureType", + "mediaDescription", "mediaThumbnailUrl", 10, 20); + Assertions.assertFalse(content1.equivalentTo(content2)); + } + + @Test + void shouldReturnFalseWhenEnclosureUrlDiffers() { + FeedEntryContent content1 = createContent("title", "content", "author", "categories", "enclosureUrl1", "enclosureType", + "mediaDescription", "mediaThumbnailUrl", 10, 20); + FeedEntryContent content2 = createContent("title", "content", "author", "categories", "enclosureUrl2", "enclosureType", + "mediaDescription", "mediaThumbnailUrl", 10, 20); + Assertions.assertFalse(content1.equivalentTo(content2)); + } + + @Test + void shouldReturnFalseWhenEnclosureTypeDiffers() { + FeedEntryContent content1 = createContent("title", "content", "author", "categories", "enclosureUrl", "enclosureType1", + "mediaDescription", "mediaThumbnailUrl", 10, 20); + FeedEntryContent content2 = createContent("title", "content", "author", "categories", "enclosureUrl", "enclosureType2", + "mediaDescription", "mediaThumbnailUrl", 10, 20); + Assertions.assertFalse(content1.equivalentTo(content2)); + } + + @Test + void shouldReturnFalseWhenMediaDescriptionDiffers() { + FeedEntryContent content1 = createContent("title", "content", "author", "categories", "enclosureUrl", "enclosureType", + "mediaDescription1", "mediaThumbnailUrl", 10, 20); + FeedEntryContent content2 = createContent("title", "content", "author", "categories", "enclosureUrl", "enclosureType", + "mediaDescription2", "mediaThumbnailUrl", 10, 20); + Assertions.assertFalse(content1.equivalentTo(content2)); + } + + @Test + void shouldReturnFalseWhenMediaThumbnailUrlDiffers() { + FeedEntryContent content1 = createContent("title", "content", "author", "categories", "enclosureUrl", "enclosureType", + "mediaDescription", "mediaThumbnailUrl1", 10, 20); + FeedEntryContent content2 = createContent("title", "content", "author", "categories", "enclosureUrl", "enclosureType", + "mediaDescription", "mediaThumbnailUrl2", 10, 20); + Assertions.assertFalse(content1.equivalentTo(content2)); + } + + @Test + void shouldReturnFalseWhenMediaThumbnailWidthDiffers() { + FeedEntryContent content1 = createContent("title", "content", "author", "categories", "enclosureUrl", "enclosureType", + "mediaDescription", "mediaThumbnailUrl", 10, 20); + FeedEntryContent content2 = createContent("title", "content", "author", "categories", "enclosureUrl", "enclosureType", + "mediaDescription", "mediaThumbnailUrl", 15, 20); + Assertions.assertFalse(content1.equivalentTo(content2)); + } + + @Test + void shouldReturnFalseWhenMediaThumbnailHeightDiffers() { + FeedEntryContent content1 = createContent("title", "content", "author", "categories", "enclosureUrl", "enclosureType", + "mediaDescription", "mediaThumbnailUrl", 10, 20); + FeedEntryContent content2 = createContent("title", "content", "author", "categories", "enclosureUrl", "enclosureType", + "mediaDescription", "mediaThumbnailUrl", 10, 25); + Assertions.assertFalse(content1.equivalentTo(content2)); + } + + @Test + void shouldReturnTrueWhenNullFieldsAreEqual() { + FeedEntryContent content1 = new FeedEntryContent(); + FeedEntryContent content2 = new FeedEntryContent(); + Assertions.assertTrue(content1.equivalentTo(content2)); + } + + private FeedEntryContent createContent(String title, String content, String author, String categories, String enclosureUrl, + String enclosureType, String mediaDescription, String mediaThumbnailUrl, Integer mediaThumbnailWidth, + Integer mediaThumbnailHeight) { + FeedEntryContent feedEntryContent = new FeedEntryContent(); + feedEntryContent.setTitle(title); + feedEntryContent.setContent(content); + feedEntryContent.setAuthor(author); + feedEntryContent.setCategories(categories); + feedEntryContent.setEnclosureUrl(enclosureUrl); + feedEntryContent.setEnclosureType(enclosureType); + feedEntryContent.setMediaDescription(mediaDescription); + feedEntryContent.setMediaThumbnailUrl(mediaThumbnailUrl); + feedEntryContent.setMediaThumbnailWidth(mediaThumbnailWidth); + feedEntryContent.setMediaThumbnailHeight(mediaThumbnailHeight); + return feedEntryContent; + } + } +} \ No newline at end of file diff --git a/commafeed-server/src/test/java/com/commafeed/backend/urlprovider/InPageReferenceFeedURLProviderTest.java b/commafeed-server/src/test/java/com/commafeed/backend/urlprovider/InPageReferenceFeedURLProviderTest.java new file mode 100644 index 00000000..f6169c30 --- /dev/null +++ b/commafeed-server/src/test/java/com/commafeed/backend/urlprovider/InPageReferenceFeedURLProviderTest.java @@ -0,0 +1,91 @@ +package com.commafeed.backend.urlprovider; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class InPageReferenceFeedURLProviderTest { + + private final InPageReferenceFeedURLProvider provider = new InPageReferenceFeedURLProvider(); + + @Test + void extractsAtomFeedURL() { + String url = "http://example.com"; + String html = """ + + + + + + + """; + + String result = provider.get(url, html); + + Assertions.assertEquals("http://example.com/feed.atom", result); + } + + @Test + void extractsRSSFeedURL() { + String url = "http://example.com"; + String html = """ + + + + + + + """; + + String result = provider.get(url, html); + + Assertions.assertEquals("http://example.com/feed.rss", result); + } + + @Test + void prefersAtomOverRSS() { + String url = "http://example.com"; + String html = """ + + + + + + + + """; + + String result = provider.get(url, html); + + Assertions.assertEquals("http://example.com/feed.atom", result); + } + + @Test + void returnsNullForNonHtmlContent() { + String url = "http://example.com"; + String content = """ + + + """; + + String result = provider.get(url, content); + + Assertions.assertNull(result); + } + + @Test + void returnsNullForHtmlWithoutFeedLinks() { + String url = "http://example.com"; + String html = """ + + + + + + + """; + + String result = provider.get(url, html); + + Assertions.assertNull(result); + } +} \ No newline at end of file diff --git a/commafeed-server/src/test/java/com/commafeed/frontend/model/EntryTest.java b/commafeed-server/src/test/java/com/commafeed/frontend/model/EntryTest.java new file mode 100644 index 00000000..54a17e8e --- /dev/null +++ b/commafeed-server/src/test/java/com/commafeed/frontend/model/EntryTest.java @@ -0,0 +1,41 @@ +package com.commafeed.frontend.model; + +import java.time.Instant; +import java.util.Date; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.rometools.rome.feed.synd.SyndEntry; + +class EntryTest { + + @Test + void asRss() { + Entry entry = new Entry(); + entry.setId("1"); + entry.setGuid("guid-1"); + entry.setTitle("Test Entry"); + entry.setContent("This is a test entry content."); + entry.setCategories("test,example"); + entry.setRtl(false); + entry.setAuthor("Author Name"); + entry.setEnclosureUrl("http://example.com/enclosure.mp3"); + entry.setEnclosureType("audio/mpeg"); + entry.setDate(Instant.ofEpochSecond(1)); + entry.setUrl("http://example.com/test-entry"); + + SyndEntry syndEntry = entry.asRss(); + Assertions.assertEquals("guid-1", syndEntry.getUri()); + Assertions.assertEquals("Test Entry", syndEntry.getTitle()); + Assertions.assertEquals("Author Name", syndEntry.getAuthor()); + Assertions.assertEquals(1, syndEntry.getContents().size()); + Assertions.assertEquals("This is a test entry content.", syndEntry.getContents().get(0).getValue()); + Assertions.assertEquals(1, syndEntry.getEnclosures().size()); + Assertions.assertEquals("http://example.com/enclosure.mp3", syndEntry.getEnclosures().get(0).getUrl()); + Assertions.assertEquals("audio/mpeg", syndEntry.getEnclosures().get(0).getType()); + Assertions.assertEquals("http://example.com/test-entry", syndEntry.getLink()); + Assertions.assertEquals(Date.from(Instant.ofEpochSecond(1)), syndEntry.getPublishedDate()); + } + +} \ No newline at end of file diff --git a/commafeed-server/src/test/java/com/commafeed/integration/BaseIT.java b/commafeed-server/src/test/java/com/commafeed/integration/BaseIT.java index 8f57141f..8f6dae18 100644 --- a/commafeed-server/src/test/java/com/commafeed/integration/BaseIT.java +++ b/commafeed-server/src/test/java/com/commafeed/integration/BaseIT.java @@ -10,7 +10,6 @@ import java.util.Objects; import jakarta.ws.rs.client.Client; import jakarta.ws.rs.core.HttpHeaders; -import jakarta.ws.rs.core.MediaType; import org.apache.commons.io.IOUtils; import org.apache.hc.core5.http.HttpStatus; @@ -29,6 +28,7 @@ import com.commafeed.frontend.model.request.AddCategoryRequest; import com.commafeed.frontend.model.request.SubscribeRequest; import io.restassured.RestAssured; +import io.restassured.http.ContentType; import io.restassured.http.Header; import lombok.Getter; @@ -95,7 +95,7 @@ public abstract class BaseIT { addCategoryRequest.setName(name); return RestAssured.given() .body(addCategoryRequest) - .contentType(MediaType.APPLICATION_JSON) + .contentType(ContentType.JSON) .post("rest/category/add") .then() .extract() @@ -117,7 +117,7 @@ public abstract class BaseIT { subscribeRequest.setCategoryId(categoryId); return RestAssured.given() .body(subscribeRequest) - .contentType(MediaType.APPLICATION_JSON) + .contentType(ContentType.JSON) .post("rest/feed/subscribe") .then() .statusCode(HttpStatus.SC_OK) @@ -162,6 +162,24 @@ public abstract class BaseIT { .as(Entries.class); } + protected Entries getCategoryEntries(String categoryId, String keywords) { + return RestAssured.given() + .get("rest/category/entries?id={id}&readType=all&keywords={keywords}", categoryId, keywords) + .then() + .statusCode(HttpStatus.SC_OK) + .extract() + .as(Entries.class); + } + + protected Entries getTaggedEntries(String tag) { + return RestAssured.given() + .get("rest/category/entries?id=all&readType=all&tag={tag}", tag) + .then() + .statusCode(HttpStatus.SC_OK) + .extract() + .as(Entries.class); + } + protected int forceRefreshAllFeeds() { return RestAssured.given().get("rest/feed/refreshAll").then().extract().statusCode(); } diff --git a/commafeed-server/src/test/java/com/commafeed/integration/SecurityIT.java b/commafeed-server/src/test/java/com/commafeed/integration/SecurityIT.java index b483fa18..5a12d810 100644 --- a/commafeed-server/src/test/java/com/commafeed/integration/SecurityIT.java +++ b/commafeed-server/src/test/java/com/commafeed/integration/SecurityIT.java @@ -5,7 +5,6 @@ import java.util.List; import java.util.stream.Collectors; import jakarta.ws.rs.core.HttpHeaders; -import jakarta.ws.rs.core.MediaType; import org.apache.hc.core5.http.HttpStatus; import org.junit.jupiter.api.Assertions; @@ -20,6 +19,7 @@ import com.commafeed.frontend.model.request.SubscribeRequest; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; +import io.restassured.http.ContentType; @QuarkusTest class SecurityIT extends BaseIT { @@ -79,7 +79,7 @@ class SecurityIT extends BaseIT { .preemptive() .basic("admin", "admin") .body(req) - .contentType(MediaType.APPLICATION_JSON) + .contentType(ContentType.JSON) .post("rest/user/profile") .then() .statusCode(HttpStatus.SC_OK); @@ -105,7 +105,7 @@ class SecurityIT extends BaseIT { .preemptive() .basic("admin", "admin") .body(subscribeRequest) - .contentType(MediaType.APPLICATION_JSON) + .contentType(ContentType.JSON) .post("rest/feed/subscribe") .then() .statusCode(HttpStatus.SC_OK) @@ -130,7 +130,7 @@ class SecurityIT extends BaseIT { markRequest.setRead(true); RestAssured.given() .body(markRequest) - .contentType(MediaType.APPLICATION_JSON) + .contentType(ContentType.JSON) .queryParam("apiKey", apiKey) .post("rest/entry/mark") .then() diff --git a/commafeed-server/src/test/java/com/commafeed/integration/WebSocketIT.java b/commafeed-server/src/test/java/com/commafeed/integration/WebSocketIT.java index 9c93e951..a1e84327 100644 --- a/commafeed-server/src/test/java/com/commafeed/integration/WebSocketIT.java +++ b/commafeed-server/src/test/java/com/commafeed/integration/WebSocketIT.java @@ -19,7 +19,6 @@ import jakarta.websocket.Endpoint; import jakarta.websocket.EndpointConfig; import jakarta.websocket.Session; import jakarta.ws.rs.core.HttpHeaders; -import jakarta.ws.rs.core.MediaType; import org.apache.hc.core5.http.HttpStatus; import org.awaitility.Awaitility; @@ -32,6 +31,7 @@ import com.commafeed.frontend.model.request.FeedModificationRequest; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; +import io.restassured.http.ContentType; import lombok.extern.slf4j.Slf4j; @QuarkusTest @@ -102,7 +102,7 @@ class WebSocketIT extends BaseIT { req.setId(subscriptionId); req.setName("feed-name"); req.setFilter("!title.contains('item 4')"); - RestAssured.given().body(req).contentType(MediaType.APPLICATION_JSON).post("rest/feed/modify").then().statusCode(HttpStatus.SC_OK); + RestAssured.given().body(req).contentType(ContentType.JSON).post("rest/feed/modify").then().statusCode(HttpStatus.SC_OK); AtomicBoolean connected = new AtomicBoolean(); AtomicReference messageRef = new AtomicReference<>(); diff --git a/commafeed-server/src/test/java/com/commafeed/integration/rest/AdminIT.java b/commafeed-server/src/test/java/com/commafeed/integration/rest/AdminIT.java index e852c340..e56d8b89 100644 --- a/commafeed-server/src/test/java/com/commafeed/integration/rest/AdminIT.java +++ b/commafeed-server/src/test/java/com/commafeed/integration/rest/AdminIT.java @@ -2,8 +2,6 @@ package com.commafeed.integration.rest; import java.util.List; -import jakarta.ws.rs.core.MediaType; - import org.apache.hc.core5.http.HttpStatus; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; @@ -18,6 +16,7 @@ import com.commafeed.integration.BaseIT; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; +import io.restassured.http.ContentType; @QuarkusTest class AdminIT extends BaseIT { @@ -38,42 +37,47 @@ class AdminIT extends BaseIT { void saveModifyAndDeleteNewUser() { List existingUsers = getAllUsers(); - createUser(); + long userId = createUser(); Assertions.assertEquals(existingUsers.size() + 1, getAllUsers().size()); - modifyUser(); + UserModel user = getUser(userId); + Assertions.assertEquals("test", user.getName()); + + modifyUser(user); Assertions.assertEquals(existingUsers.size() + 1, getAllUsers().size()); deleteUser(); Assertions.assertEquals(existingUsers.size(), getAllUsers().size()); } - private void createUser() { + private long createUser() { User user = new User(); user.setName("test"); user.setPassword("test".getBytes()); user.setEmail("test@test.com"); - RestAssured.given() + String response = RestAssured.given() .body(user) - .contentType(MediaType.APPLICATION_JSON) + .contentType(ContentType.JSON) .post("rest/admin/user/save") .then() - .statusCode(HttpStatus.SC_OK); + .statusCode(HttpStatus.SC_OK) + .extract() + .asString(); + return Long.parseLong(response); } - private void modifyUser() { - List existingUsers = getAllUsers(); - UserModel user = existingUsers.stream() - .filter(u -> u.getName().equals("test")) - .findFirst() - .orElseThrow(() -> new NullPointerException("User not found")); - user.setEmail("new-email@provider.com"); - RestAssured.given() - .body(user) - .contentType(MediaType.APPLICATION_JSON) - .post("rest/admin/user/save") + private UserModel getUser(long userId) { + return RestAssured.given() + .get("rest/admin/user/get/{id}", userId) .then() - .statusCode(HttpStatus.SC_OK); + .statusCode(HttpStatus.SC_OK) + .extract() + .as(UserModel.class); + } + + private void modifyUser(UserModel user) { + user.setEmail("new-email@provider.com"); + RestAssured.given().body(user).contentType(ContentType.JSON).post("rest/admin/user/save").then().statusCode(HttpStatus.SC_OK); } private void deleteUser() { @@ -85,12 +89,7 @@ class AdminIT extends BaseIT { IDRequest req = new IDRequest(); req.setId(user.getId()); - RestAssured.given() - .body(req) - .contentType(MediaType.APPLICATION_JSON) - .post("rest/admin/user/delete") - .then() - .statusCode(HttpStatus.SC_OK); + RestAssured.given().body(req).contentType(ContentType.JSON).post("rest/admin/user/delete").then().statusCode(HttpStatus.SC_OK); } private List getAllUsers() { diff --git a/commafeed-server/src/test/java/com/commafeed/integration/rest/CategoryIT.java b/commafeed-server/src/test/java/com/commafeed/integration/rest/CategoryIT.java index 5a63f3f6..cc8e2364 100644 --- a/commafeed-server/src/test/java/com/commafeed/integration/rest/CategoryIT.java +++ b/commafeed-server/src/test/java/com/commafeed/integration/rest/CategoryIT.java @@ -1,25 +1,36 @@ package com.commafeed.integration.rest; -import jakarta.ws.rs.core.MediaType; +import java.io.StringReader; +import java.util.List; +import org.apache.hc.core5.http.HttpStatus; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.xml.sax.InputSource; import com.commafeed.frontend.model.Category; import com.commafeed.frontend.model.Entries; import com.commafeed.frontend.model.Entry; +import com.commafeed.frontend.model.UnreadCount; import com.commafeed.frontend.model.request.CategoryModificationRequest; import com.commafeed.frontend.model.request.CollapseRequest; import com.commafeed.frontend.model.request.IDRequest; +import com.commafeed.frontend.model.request.MarkRequest; import com.commafeed.frontend.model.request.StarRequest; +import com.commafeed.frontend.model.request.TagRequest; import com.commafeed.frontend.resource.CategoryREST; import com.commafeed.integration.BaseIT; +import com.rometools.rome.feed.synd.SyndFeed; +import com.rometools.rome.io.FeedException; +import com.rometools.rome.io.SyndFeedInput; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; +import io.restassured.common.mapper.TypeRef; +import io.restassured.http.ContentType; @QuarkusTest class CategoryIT extends BaseIT { @@ -37,18 +48,30 @@ class CategoryIT extends BaseIT { void modifyCategory() { String category1Id = createCategory("test-category-1"); String category2Id = createCategory("test-category-2"); + String category3Id = createCategory("test-category-3"); CategoryModificationRequest request = new CategoryModificationRequest(); request.setId(Long.valueOf(category2Id)); - request.setName("modified-category"); + request.setName("modified-category-2"); request.setParentId(category1Id); - RestAssured.given().body(request).contentType(MediaType.APPLICATION_JSON).post("rest/category/modify").then().statusCode(200); + request.setPosition(2); + RestAssured.given().body(request).contentType(ContentType.JSON).post("rest/category/modify").then().statusCode(200); Category root = getRootCategory(); - Assertions.assertEquals(1, root.getChildren().size()); + Assertions.assertEquals(2, root.getChildren().size()); Assertions.assertEquals("test-category-1", root.getChildren().get(0).getName()); Assertions.assertEquals(1, root.getChildren().get(0).getChildren().size()); - Assertions.assertEquals("modified-category", root.getChildren().get(0).getChildren().get(0).getName()); + Assertions.assertEquals("modified-category-2", root.getChildren().get(0).getChildren().get(0).getName()); + + request = new CategoryModificationRequest(); + request.setId(Long.valueOf(category3Id)); + request.setPosition(0); + RestAssured.given().body(request).contentType(ContentType.JSON).post("rest/category/modify").then().statusCode(200); + + root = getRootCategory(); + Assertions.assertEquals(2, root.getChildren().size()); + Assertions.assertEquals("test-category-3", root.getChildren().get(0).getName()); + Assertions.assertEquals("test-category-1", root.getChildren().get(1).getName()); } @Test @@ -62,7 +85,7 @@ class CategoryIT extends BaseIT { CollapseRequest request = new CollapseRequest(); request.setId(Long.valueOf(categoryId)); request.setCollapse(true); - RestAssured.given().body(request).contentType(MediaType.APPLICATION_JSON).post("rest/category/collapse").then().statusCode(200); + RestAssured.given().body(request).contentType(ContentType.JSON).post("rest/category/collapse").then().statusCode(200); root = getRootCategory(); Assertions.assertEquals(1, root.getChildren().size()); @@ -76,10 +99,57 @@ class CategoryIT extends BaseIT { IDRequest request = new IDRequest(); request.setId(Long.valueOf(categoryId)); - RestAssured.given().body(request).contentType(MediaType.APPLICATION_JSON).post("rest/category/delete").then().statusCode(200); + RestAssured.given().body(request).contentType(ContentType.JSON).post("rest/category/delete").then().statusCode(200); Assertions.assertEquals(0, getRootCategory().getChildren().size()); } + @Test + void unreadCount() { + String categoryId = createCategory("test-category"); + Long subscriptionId = subscribeAndWaitForEntries(getFeedUrl(), categoryId); + Assertions.assertEquals(2, getCategoryEntries(categoryId).getEntries().size()); + + List counts = RestAssured.given() + .get("rest/category/unreadCount") + .then() + .statusCode(200) + .extract() + .as(new TypeRef>() { + }); + + Assertions.assertEquals(1, counts.size()); + Assertions.assertEquals(subscriptionId, counts.get(0).getFeedId()); + Assertions.assertEquals(2, counts.get(0).getUnreadCount()); + } + + @Nested + class MarkEntriesAsRead { + @Test + void all() { + subscribeAndWaitForEntries(getFeedUrl()); + Assertions.assertTrue(getCategoryEntries(CategoryREST.ALL).getEntries().stream().noneMatch(Entry::isRead)); + + MarkRequest request = new MarkRequest(); + request.setId(CategoryREST.ALL); + request.setRead(true); + RestAssured.given().body(request).contentType(ContentType.JSON).post("rest/category/mark").then().statusCode(200); + Assertions.assertTrue(getCategoryEntries(CategoryREST.ALL).getEntries().stream().allMatch(Entry::isRead)); + } + + @Test + void specificCategory() { + String categoryId = createCategory("test-category"); + subscribeAndWaitForEntries(getFeedUrl(), categoryId); + Assertions.assertTrue(getCategoryEntries(categoryId).getEntries().stream().noneMatch(Entry::isRead)); + + MarkRequest request = new MarkRequest(); + request.setId(categoryId); + request.setRead(true); + RestAssured.given().body(request).contentType(ContentType.JSON).post("rest/category/mark").then().statusCode(200); + Assertions.assertTrue(getCategoryEntries(categoryId).getEntries().stream().allMatch(Entry::isRead)); + } + } + @Nested class GetEntries { @Test @@ -89,6 +159,22 @@ class CategoryIT extends BaseIT { Assertions.assertEquals(2, entries.getEntries().size()); } + @Test + void allAsFeed() throws FeedException { + subscribeAndWaitForEntries(getFeedUrl()); + String xml = RestAssured.given() + .get("rest/category/entriesAsFeed?id=all") + .then() + .statusCode(HttpStatus.SC_OK) + .contentType(ContentType.XML) + .extract() + .asString(); + + InputSource source = new InputSource(new StringReader(xml)); + SyndFeed feed = new SyndFeedInput().build(source); + Assertions.assertEquals(2, feed.getEntries().size()); + } + @Test void starred() { Long subscriptionId = subscribeAndWaitForEntries(getFeedUrl()); @@ -100,13 +186,36 @@ class CategoryIT extends BaseIT { starRequest.setId(entry.getId()); starRequest.setFeedId(subscriptionId); starRequest.setStarred(true); - RestAssured.given().body(starRequest).contentType(MediaType.APPLICATION_JSON).post("rest/entry/star"); + RestAssured.given().body(starRequest).contentType(ContentType.JSON).post("rest/entry/star"); Entries starredEntries = getCategoryEntries(CategoryREST.STARRED); Assertions.assertEquals(1, starredEntries.getEntries().size()); Assertions.assertEquals(entry.getId(), starredEntries.getEntries().get(0).getId()); } + @Test + void tagged() { + Long subscriptionId = subscribeAndWaitForEntries(getFeedUrl()); + Assertions.assertEquals(0, getTaggedEntries("my-tag").getEntries().size()); + + Entry entry = getFeedEntries(subscriptionId).getEntries().get(0); + + TagRequest tagRequest = new TagRequest(); + tagRequest.setEntryId(Long.valueOf(entry.getId())); + tagRequest.setTags(List.of("my-tag")); + RestAssured.given().body(tagRequest).contentType(ContentType.JSON).post("rest/entry/tag"); + + Entries taggedEntries = getTaggedEntries("my-tag"); + Assertions.assertEquals(1, taggedEntries.getEntries().size()); + Assertions.assertEquals(entry.getId(), taggedEntries.getEntries().get(0).getId()); + } + + @Test + void keywords() { + subscribeAndWaitForEntries(getFeedUrl()); + Assertions.assertEquals(1, getCategoryEntries(CategoryREST.ALL, "Item 2 description").getEntries().size()); + } + @Test void specificCategory() { String categoryId = createCategory("test-category"); diff --git a/commafeed-server/src/test/java/com/commafeed/integration/rest/FeedIT.java b/commafeed-server/src/test/java/com/commafeed/integration/rest/FeedIT.java index e59b7cf0..cb85cf72 100644 --- a/commafeed-server/src/test/java/com/commafeed/integration/rest/FeedIT.java +++ b/commafeed-server/src/test/java/com/commafeed/integration/rest/FeedIT.java @@ -2,6 +2,7 @@ package com.commafeed.integration.rest; import java.io.IOException; import java.io.InputStream; +import java.io.StringReader; import java.time.Duration; import java.time.Instant; import java.time.LocalDate; @@ -19,6 +20,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.xml.sax.InputSource; import com.commafeed.frontend.model.Entry; import com.commafeed.frontend.model.FeedInfo; @@ -28,9 +30,13 @@ import com.commafeed.frontend.model.request.FeedModificationRequest; import com.commafeed.frontend.model.request.IDRequest; import com.commafeed.frontend.model.request.MarkRequest; import com.commafeed.integration.BaseIT; +import com.rometools.rome.feed.synd.SyndFeed; +import com.rometools.rome.io.FeedException; +import com.rometools.rome.io.SyndFeedInput; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; +import io.restassured.http.ContentType; @QuarkusTest class FeedIT extends BaseIT { @@ -54,7 +60,7 @@ class FeedIT extends BaseIT { FeedInfo feedInfo = RestAssured.given() .body(req) - .contentType(MediaType.APPLICATION_JSON) + .contentType(ContentType.JSON) .post("rest/feed/fetch") .then() .statusCode(HttpStatus.SC_OK) @@ -101,7 +107,7 @@ class FeedIT extends BaseIT { return RestAssured.given() .body(request) - .contentType(MediaType.APPLICATION_JSON) + .contentType(ContentType.JSON) .post("rest/feed/unsubscribe") .then() .extract() @@ -152,12 +158,7 @@ class FeedIT extends BaseIT { request.setOlderThan(olderThan == null ? null : olderThan.toEpochMilli()); request.setInsertedBefore(insertedBefore == null ? null : insertedBefore.toEpochMilli()); - RestAssured.given() - .body(request) - .contentType(MediaType.APPLICATION_JSON) - .post("rest/feed/mark") - .then() - .statusCode(HttpStatus.SC_OK); + RestAssured.given().body(request).contentType(ContentType.JSON).post("rest/feed/mark").then().statusCode(HttpStatus.SC_OK); } } @@ -180,6 +181,25 @@ class FeedIT extends BaseIT { } } + @Nested + class RSS { + @Test + void allAsFeed() throws FeedException { + Long subscriptionId = subscribeAndWaitForEntries(getFeedUrl()); + String xml = RestAssured.given() + .get("rest/feed/entriesAsFeed?id={id}", subscriptionId) + .then() + .statusCode(HttpStatus.SC_OK) + .contentType(ContentType.XML) + .extract() + .asString(); + + InputSource source = new InputSource(new StringReader(xml)); + SyndFeed feed = new SyndFeedInput().build(source); + Assertions.assertEquals(2, feed.getEntries().size()); + } + } + @Nested class Modify { @Test @@ -192,12 +212,8 @@ class FeedIT extends BaseIT { req.setId(subscriptionId); req.setName("new name"); req.setCategoryId(subscription.getCategoryId()); - RestAssured.given() - .body(req) - .contentType(MediaType.APPLICATION_JSON) - .post("rest/feed/modify") - .then() - .statusCode(HttpStatus.SC_OK); + req.setPosition(1); + RestAssured.given().body(req).contentType(ContentType.JSON).post("rest/feed/modify").then().statusCode(HttpStatus.SC_OK); subscription = getSubscription(subscriptionId); Assertions.assertEquals("new name", subscription.getName()); diff --git a/commafeed-server/src/test/java/com/commafeed/integration/rest/FeverIT.java b/commafeed-server/src/test/java/com/commafeed/integration/rest/FeverIT.java index d17a8850..fb3969f5 100644 --- a/commafeed-server/src/test/java/com/commafeed/integration/rest/FeverIT.java +++ b/commafeed-server/src/test/java/com/commafeed/integration/rest/FeverIT.java @@ -1,7 +1,5 @@ package com.commafeed.integration.rest; -import jakarta.ws.rs.core.MediaType; - import org.apache.hc.core5.http.HttpStatus; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; @@ -16,6 +14,7 @@ import com.commafeed.integration.BaseIT; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; +import io.restassured.http.ContentType; @QuarkusTest class FeverIT extends BaseIT { @@ -31,7 +30,7 @@ class FeverIT extends BaseIT { ProfileModificationRequest req = new ProfileModificationRequest(); req.setCurrentPassword("admin"); req.setNewApiKey(true); - RestAssured.given().body(req).contentType(MediaType.APPLICATION_JSON).post("rest/user/profile").then().statusCode(HttpStatus.SC_OK); + RestAssured.given().body(req).contentType(ContentType.JSON).post("rest/user/profile").then().statusCode(HttpStatus.SC_OK); // retrieve api key UserModel user = RestAssured.given().get("rest/user/profile").then().statusCode(HttpStatus.SC_OK).extract().as(UserModel.class); @@ -70,6 +69,27 @@ class FeverIT extends BaseIT { Assertions.assertEquals(2, feverResponse.getUnreadItemIds().size()); } + @Test + void entries() { + subscribeAndWaitForEntries(getFeedUrl()); + FeverResponse feverResponse = fetch("items"); + Assertions.assertEquals(2, feverResponse.getItems().size()); + } + + @Test + void groups() { + createCategory("category-1"); + FeverResponse feverResponse = fetch("groups"); + Assertions.assertEquals(1, feverResponse.getGroups().size()); + Assertions.assertEquals("category-1", feverResponse.getGroups().get(0).getTitle()); + } + + @Test + void links() { + FeverResponse feverResponse = fetch("links"); + Assertions.assertTrue(feverResponse.getLinks().isEmpty()); + } + private FeverResponse fetch(String what) { return fetch(what, apiKey); } diff --git a/commafeed-server/src/test/java/com/commafeed/integration/rest/UserIT.java b/commafeed-server/src/test/java/com/commafeed/integration/rest/UserIT.java index f3d12b2b..fa3b9b53 100644 --- a/commafeed-server/src/test/java/com/commafeed/integration/rest/UserIT.java +++ b/commafeed-server/src/test/java/com/commafeed/integration/rest/UserIT.java @@ -3,19 +3,22 @@ package com.commafeed.integration.rest; import java.util.List; import jakarta.inject.Inject; -import jakarta.ws.rs.core.MediaType; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Element; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import com.commafeed.frontend.model.Settings; import com.commafeed.frontend.model.request.PasswordResetRequest; import com.commafeed.integration.BaseIT; import io.quarkus.mailer.MockMailbox; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; +import io.restassured.http.ContentType; import io.vertx.ext.mail.MailMessage; @QuarkusTest @@ -40,7 +43,7 @@ class UserIT extends BaseIT { void resetPassword() { PasswordResetRequest req = new PasswordResetRequest(); req.setEmail("admin@commafeed.com"); - RestAssured.given().body(req).contentType(MediaType.APPLICATION_JSON).post("rest/user/passwordReset").then().statusCode(200); + RestAssured.given().body(req).contentType(ContentType.JSON).post("rest/user/passwordReset").then().statusCode(200); List mails = mailbox.getMailMessagesSentTo("admin@commafeed.com"); Assertions.assertEquals(1, mails.size()); @@ -49,5 +52,20 @@ class UserIT extends BaseIT { Assertions.assertEquals("CommaFeed - Password recovery", message.getSubject()); Assertions.assertTrue(message.getHtml().startsWith("You asked for password recovery for account 'admin'")); Assertions.assertEquals("admin@commafeed.com", message.getTo().get(0)); + + Element a = Jsoup.parse(message.getHtml()).select("a").get(0); + String link = a.attr("href"); + String newPasswordResponse = RestAssured.given().urlEncodingEnabled(false).get(link).then().statusCode(200).extract().asString(); + Assertions.assertTrue(newPasswordResponse.contains("Your new password is:")); + } + + @Test + void saveSettings() { + Settings settings = RestAssured.given().get("rest/user/settings").then().extract().as(Settings.class); + settings.setLanguage("test"); + RestAssured.given().body(settings).contentType(ContentType.JSON).post("rest/user/settings").then().statusCode(200); + + Settings updatedSettings = RestAssured.given().get("rest/user/settings").then().extract().as(Settings.class); + Assertions.assertEquals("test", updatedSettings.getLanguage()); } } diff --git a/commafeed-server/src/test/java/com/commafeed/integration/servlet/CustomCodeIT.java b/commafeed-server/src/test/java/com/commafeed/integration/servlet/CustomCodeIT.java index 4eebe625..d7220e05 100644 --- a/commafeed-server/src/test/java/com/commafeed/integration/servlet/CustomCodeIT.java +++ b/commafeed-server/src/test/java/com/commafeed/integration/servlet/CustomCodeIT.java @@ -1,7 +1,5 @@ package com.commafeed.integration.servlet; -import jakarta.ws.rs.core.MediaType; - import org.apache.hc.core5.http.HttpStatus; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.AfterEach; @@ -13,6 +11,7 @@ import com.commafeed.integration.BaseIT; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; +import io.restassured.http.ContentType; @QuarkusTest class CustomCodeIT extends BaseIT { @@ -35,12 +34,7 @@ class CustomCodeIT extends BaseIT { // update settings settings.setCustomJs("custom-js"); settings.setCustomCss("custom-css"); - RestAssured.given() - .body(settings) - .contentType(MediaType.APPLICATION_JSON) - .post("rest/user/settings") - .then() - .statusCode(HttpStatus.SC_OK); + RestAssured.given().body(settings).contentType(ContentType.JSON).post("rest/user/settings").then().statusCode(HttpStatus.SC_OK); // check custom code servlets RestAssured.given().get("custom_js.js").then().statusCode(HttpStatus.SC_OK).body(CoreMatchers.is("custom-js")); diff --git a/commafeed-server/src/test/java/com/commafeed/tools/CommaFeedPropertiesGeneratorTest.java b/commafeed-server/src/test/java/com/commafeed/tools/CommaFeedPropertiesGeneratorTest.java new file mode 100644 index 00000000..e2500224 --- /dev/null +++ b/commafeed-server/src/test/java/com/commafeed/tools/CommaFeedPropertiesGeneratorTest.java @@ -0,0 +1,27 @@ +package com.commafeed.tools; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.net.URL; +import java.nio.charset.StandardCharsets; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.google.common.io.Resources; + +class CommaFeedPropertiesGeneratorTest { + + @Test + void testGenerate() throws Exception { + InputStream model = getClass().getResourceAsStream("/properties/quarkus-config-model.yaml"); + InputStream javadoc = getClass().getResourceAsStream("/properties/quarkus-config-javadoc.yaml"); + URL output = getClass().getResource("/properties/output.properties"); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + new CommaFeedPropertiesGenerator().generate(model, javadoc, baos); + + Assertions.assertLinesMatch(Resources.readLines(output, StandardCharsets.UTF_8).stream(), + baos.toString(StandardCharsets.UTF_8).lines()); + } + +} \ No newline at end of file diff --git a/commafeed-server/src/test/resources/application.properties b/commafeed-server/src/test/resources/application.properties new file mode 100644 index 00000000..b236ff3a --- /dev/null +++ b/commafeed-server/src/test/resources/application.properties @@ -0,0 +1,2 @@ +# we generate the jacoco report ourselves by aggregating the unit tests and integration tests jacoco.exec files +quarkus.jacoco.report=false \ No newline at end of file diff --git a/commafeed-server/src/test/resources/properties/output.properties b/commafeed-server/src/test/resources/properties/output.properties new file mode 100644 index 00000000..3156d252 --- /dev/null +++ b/commafeed-server/src/test/resources/properties/output.properties @@ -0,0 +1,150 @@ +# Whether to expose a robots.txt file that disallows web crawlers and search engine indexers. +commafeed.hide-from-web-crawlers=true + +# If enabled, images in feed entries will be proxied through the server instead of accessed directly by the browser. +# +# This is useful if commafeed is accessed through a restricting proxy that blocks some feeds that are followed. +commafeed.image-proxy-enabled=false + +# Enable password recovery via email. +# +# Quarkus mailer will need to be configured. +commafeed.password-recovery-enabled=false + +# Message displayed in a notification at the bottom of the page. +commafeed.announcement= + +# Google Analytics tracking code. +commafeed.google-analytics-tracking-code= + +# Google Auth key for fetching Youtube channel favicons. +commafeed.google-auth-key= + +# User-Agent string that will be used by the http client, leave empty for the default one. +commafeed.http-client.user-agent= + +# Time to wait for a connection to be established. +commafeed.http-client.connect-timeout=5s + +# Time to wait for SSL handshake to complete. +commafeed.http-client.ssl-handshake-timeout=5s + +# Time to wait between two packets before timeout. +commafeed.http-client.socket-timeout=10s + +# Time to wait for the full response to be received. +commafeed.http-client.response-timeout=10s + +# Time to live for a connection in the pool. +commafeed.http-client.connection-time-to-live=30s + +# Time between eviction runs for idle connections. +commafeed.http-client.idle-connections-eviction-interval=1m + +# If a feed is larger than this, it will be discarded to prevent memory issues while parsing the feed. +commafeed.http-client.max-response-size=5m + +# Prevent access to local addresses to mitigate server-side request forgery (SSRF) attacks, which could potentially expose internal +# resources. +# +# You may want to disable this if you subscribe to feeds that are only available on your local network and you trust all users of +# your CommaFeed instance. +commafeed.http-client.block-local-addresses=true + +# Whether to enable the cache. This cache is used to avoid spamming feeds in short bursts (e.g. when subscribing to a feed for the +# first time or when clicking "fetch all my feeds now"). +commafeed.http-client.cache.enabled=true + +# Maximum amount of memory the cache can use. +commafeed.http-client.cache.maximum-memory-size=10m + +# Duration after which an entry is removed from the cache. +commafeed.http-client.cache.expiration=1m + +# Default amount of time CommaFeed will wait before refreshing a feed. +commafeed.feed-refresh.interval=5m + +# Maximum amount of time CommaFeed will wait before refreshing a feed. This is used as an upper bound when: +# +#
    +#
  • an error occurs while refreshing a feed and we're backing off exponentially
  • +#
  • we receive a Cache-Control header from the feed
  • +#
  • we receive a Retry-After header from the feed
  • +#
+commafeed.feed-refresh.max-interval=4h + +# If enabled, CommaFeed will calculate the next refresh time based on the feed's average time between entries and the time since +# the last entry was published. The interval will be sometimes between the default refresh interval +# (`commafeed.feed-refresh.interval`) and the maximum refresh interval (`commafeed.feed-refresh.max-interval`). +# +# See {@link FeedRefreshIntervalCalculator} for details. +commafeed.feed-refresh.interval-empirical=true + +# Number of retries before backoff is applied. +commafeed.feed-refresh.errors.retries-before-backoff=3 + +# Duration to wait before retrying after an error. Will be multiplied by the number of errors since the last successful fetch. +commafeed.feed-refresh.errors.backoff-interval=1h + +# Amount of http threads used to fetch feeds. +commafeed.feed-refresh.http-threads=3 + +# Amount of threads used to insert new entries in the database. +commafeed.feed-refresh.database-threads=1 + +# Duration after which a user is considered inactive. Feeds for inactive users are not refreshed until they log in again. +# +# 0 to disable. +commafeed.feed-refresh.user-inactivity-period=0s + +# Duration after which the evaluation of a filtering expresion to mark an entry as read is considered to have timed out. +commafeed.feed-refresh.filtering-expression-evaluation-timeout=500ms + +# Duration after which the "Fetch all my feeds now" action is available again after use to avoid spamming feeds. +commafeed.feed-refresh.force-refresh-cooldown-duration=0s + +# Timeout applied to all database queries. +# +# 0 to disable. +commafeed.database.query-timeout=0s + +# Maximum age of feed entries in the database. Older entries will be deleted. +# +# 0 to disable. +commafeed.database.cleanup.entries-max-age=365d + +# Maximum age of feed entry statuses (read/unread) in the database. Older statuses will be deleted. +# +# 0 to disable. +commafeed.database.cleanup.statuses-max-age=0s + +# Maximum number of entries per feed to keep in the database. +# +# 0 to disable. +commafeed.database.cleanup.max-feed-capacity=500 + +# Limit the number of feeds a user can subscribe to. +# +# 0 to disable. +commafeed.database.cleanup.max-feeds-per-user=0 + +# Rows to delete per query while cleaning up old entries. +commafeed.database.cleanup.batch-size=100 + +# Whether to let users create accounts for themselves. +commafeed.users.allow-registrations=false + +# Whether to enable strict password validation (1 uppercase char, 1 lowercase char, 1 digit, 1 special char). +commafeed.users.strict-password-policy=true + +# Whether to create a demo account the first time the app starts. +commafeed.users.create-demo-account=false + +# Enable websocket connection so the server can notify web clients that there are new entries for feeds. +commafeed.websocket.enabled=true + +# Interval at which the client will send a ping message on the websocket to keep the connection alive. +commafeed.websocket.ping-interval=15m + +# If the websocket connection is disabled or the connection is lost, the client will reload the feed tree at this interval. +commafeed.websocket.tree-reload-interval=30s \ No newline at end of file diff --git a/commafeed-server/src/test/resources/properties/quarkus-config-javadoc.yaml b/commafeed-server/src/test/resources/properties/quarkus-config-javadoc.yaml new file mode 100644 index 00000000..5c6c767e --- /dev/null +++ b/commafeed-server/src/test/resources/properties/quarkus-config-javadoc.yaml @@ -0,0 +1,186 @@ +--- +extension: + groupId: "com.commafeed" + artifactId: "commafeed-server" + name: "CommaFeed Server" + nameSource: "POM_XML" + detected: true +elements: + com.commafeed.CommaFeedConfiguration.HttpClient.maxResponseSize: + description: "If a feed is larger than this, it will be discarded to prevent memory\ + \ issues while parsing the feed." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.passwordRecoveryEnabled: + description: "Enable password recovery via email.\n\nQuarkus mailer will need\ + \ to be configured." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.imageProxyEnabled: + description: "If enabled, images in feed entries will be proxied through the server\ + \ instead of accessed directly by the browser.\n\nThis is useful if commafeed\ + \ is accessed through a restricting proxy that blocks some feeds that are followed." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.FeedRefresh.forceRefreshCooldownDuration: + description: "Duration after which the \"Fetch all my feeds now\" action is available\ + \ again after use to avoid spamming feeds." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.Database.queryTimeout: + description: "Timeout applied to all database queries.\n\n0 to disable." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.Database.Cleanup.statusesMaxAge: + description: "Maximum age of feed entry statuses (read/unread) in the database.\ + \ Older statuses will be deleted.\n\n0 to disable." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.FeedRefresh.filteringExpressionEvaluationTimeout: + description: "Duration after which the evaluation of a filtering expresion to\ + \ mark an entry as read is considered to have timed out." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.Database.Cleanup.maxFeedCapacity: + description: "Maximum number of entries per feed to keep in the database.\n\n\ + 0 to disable." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.FeedRefresh.maxInterval: + description: "Maximum amount of time CommaFeed will wait before refreshing a feed.\ + \ This is used as an upper bound when:\n\n
    \n
  • an error occurs while refreshing\ + \ a feed and we're backing off exponentially
  • \n
  • we receive a Cache-Control\ + \ header from the feed
  • \n
  • we receive a Retry-After header from the feed
  • \n\ +
" + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.Database.cleanup: + description: "Database cleanup settings" + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.FeedRefresh.databaseThreads: + description: "Amount of threads used to insert new entries in the database." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.HttpClient.connectTimeout: + description: "Time to wait for a connection to be established." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.Database.Cleanup.batchSize: + description: "Rows to delete per query while cleaning up old entries." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.HttpClient.responseTimeout: + description: "Time to wait for the full response to be received." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.Users.strictPasswordPolicy: + description: "Whether to enable strict password validation (1 uppercase char,\ + \ 1 lowercase char, 1 digit, 1 special char)." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.Database.Cleanup.maxFeedsPerUser: + description: "Limit the number of feeds a user can subscribe to.\n\n0 to disable." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.database: + description: "Database settings" + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.HttpClientCache.maximumMemorySize: + description: "Maximum amount of memory the cache can use." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.HttpClient.sslHandshakeTimeout: + description: "Time to wait for SSL handshake to complete." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.users: + description: "Users settings" + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.FeedRefresh.intervalEmpirical: + description: "If enabled, CommaFeed will calculate the next refresh time based\ + \ on the feed's average time between entries and the time since\nthe last entry\ + \ was published. The interval will be sometimes between the default refresh\ + \ interval\n(`commafeed.feed-refresh.interval`) and the maximum refresh interval\ + \ (`commafeed.feed-refresh.max-interval`).\n\nSee {@link FeedRefreshIntervalCalculator}\ + \ for details." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.feedRefresh: + description: "Feed refresh engine settings" + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.googleAuthKey: + description: "Google Auth key for fetching Youtube channel favicons." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.HttpClient.socketTimeout: + description: "Time to wait between two packets before timeout." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.FeedRefresh.httpThreads: + description: "Amount of http threads used to fetch feeds." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.HttpClient.connectionTimeToLive: + description: "Time to live for a connection in the pool." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.HttpClient.cache: + description: "HTTP client cache configuration" + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.websocket: + description: "Websocket settings" + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.googleAnalyticsTrackingCode: + description: "Google Analytics tracking code." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.Users.allowRegistrations: + description: "Whether to let users create accounts for themselves." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.Websocket.treeReloadInterval: + description: "If the websocket connection is disabled or the connection is lost,\ + \ the client will reload the feed tree at this interval." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.httpClient: + description: "HTTP client configuration" + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.FeedRefresh.errors: + description: "Feed refresh engine error handling settings" + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration: + description: "CommaFeed configuration" + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.HttpClient.blockLocalAddresses: + description: "Prevent access to local addresses to mitigate server-side request\ + \ forgery (SSRF) attacks, which could potentially expose internal\nresources.\n\ + \nYou may want to disable this if you subscribe to feeds that are only available\ + \ on your local network and you trust all users of\nyour CommaFeed instance." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.announcement: + description: "Message displayed in a notification at the bottom of the page." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.HttpClientCache.expiration: + description: "Duration after which an entry is removed from the cache." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.FeedRefreshErrorHandling.backoffInterval: + description: "Duration to wait before retrying after an error. Will be multiplied\ + \ by the number of errors since the last successful fetch." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.Websocket.pingInterval: + description: "Interval at which the client will send a ping message on the websocket\ + \ to keep the connection alive." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.FeedRefresh.interval: + description: "Default amount of time CommaFeed will wait before refreshing a feed." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.FeedRefreshErrorHandling.retriesBeforeBackoff: + description: "Number of retries before backoff is applied." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.Database.Cleanup.entriesMaxAge: + description: "Maximum age of feed entries in the database. Older entries will\ + \ be deleted.\n\n0 to disable." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.HttpClientCache.enabled: + description: "Whether to enable the cache. This cache is used to avoid spamming\ + \ feeds in short bursts (e.g. when subscribing to a feed for the\nfirst time\ + \ or when clicking \"fetch all my feeds now\")." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.HttpClient.idleConnectionsEvictionInterval: + description: "Time between eviction runs for idle connections." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.FeedRefresh.userInactivityPeriod: + description: "Duration after which a user is considered inactive. Feeds for inactive\ + \ users are not refreshed until they log in again.\n\n0 to disable." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.Users.createDemoAccount: + description: "Whether to create a demo account the first time the app starts." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.hideFromWebCrawlers: + description: "Whether to expose a robots.txt file that disallows web crawlers\ + \ and search engine indexers." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.Websocket.enabled: + description: "Enable websocket connection so the server can notify web clients\ + \ that there are new entries for feeds." + format: "JAVADOC" + com.commafeed.CommaFeedConfiguration.HttpClient.userAgent: + description: "User-Agent string that will be used by the http client, leave empty\ + \ for the default one." + format: "JAVADOC" diff --git a/commafeed-server/src/test/resources/properties/quarkus-config-model.yaml b/commafeed-server/src/test/resources/properties/quarkus-config-model.yaml new file mode 100644 index 00000000..39954060 --- /dev/null +++ b/commafeed-server/src/test/resources/properties/quarkus-config-model.yaml @@ -0,0 +1,538 @@ +--- +configRoots: +- extension: + groupId: "com.commafeed" + artifactId: "commafeed-server" + name: "CommaFeed Server" + nameSource: "POM_XML" + detected: true + prefix: "commafeed" + topLevelPrefix: "commafeed" + items: + - ! + sourceType: "com.commafeed.CommaFeedConfiguration" + sourceElementName: "hideFromWebCrawlers" + sourceElementType: "METHOD" + path: ! + property: "commafeed.hide-from-web-crawlers" + environmentVariable: "COMMAFEED_HIDE_FROM_WEB_CRAWLERS" + type: "boolean" + phase: "RUN_TIME" + typeDescription: "boolean" + defaultValue: "true" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration" + sourceElementName: "imageProxyEnabled" + sourceElementType: "METHOD" + path: ! + property: "commafeed.image-proxy-enabled" + environmentVariable: "COMMAFEED_IMAGE_PROXY_ENABLED" + type: "boolean" + phase: "RUN_TIME" + typeDescription: "boolean" + defaultValue: "false" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration" + sourceElementName: "passwordRecoveryEnabled" + sourceElementType: "METHOD" + path: ! + property: "commafeed.password-recovery-enabled" + environmentVariable: "COMMAFEED_PASSWORD_RECOVERY_ENABLED" + type: "boolean" + phase: "RUN_TIME" + typeDescription: "boolean" + defaultValue: "false" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration" + sourceElementName: "announcement" + sourceElementType: "METHOD" + path: ! + property: "commafeed.announcement" + environmentVariable: "COMMAFEED_ANNOUNCEMENT" + type: "java.lang.String" + phase: "RUN_TIME" + typeDescription: "string" + optional: true + - ! + sourceType: "com.commafeed.CommaFeedConfiguration" + sourceElementName: "googleAnalyticsTrackingCode" + sourceElementType: "METHOD" + path: ! + property: "commafeed.google-analytics-tracking-code" + environmentVariable: "COMMAFEED_GOOGLE_ANALYTICS_TRACKING_CODE" + type: "java.lang.String" + phase: "RUN_TIME" + typeDescription: "string" + optional: true + - ! + sourceType: "com.commafeed.CommaFeedConfiguration" + sourceElementName: "googleAuthKey" + sourceElementType: "METHOD" + path: ! + property: "commafeed.google-auth-key" + environmentVariable: "COMMAFEED_GOOGLE_AUTH_KEY" + type: "java.lang.String" + phase: "RUN_TIME" + typeDescription: "string" + optional: true + - ! + sourceType: "com.commafeed.CommaFeedConfiguration" + sourceElementName: "httpClient" + sourceElementType: "METHOD" + path: ! + property: "commafeed.http-client" + type: "com.commafeed.CommaFeedConfiguration.HttpClient" + items: + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.HttpClient" + sourceElementName: "userAgent" + sourceElementType: "METHOD" + path: ! + property: "commafeed.http-client.user-agent" + environmentVariable: "COMMAFEED_HTTP_CLIENT_USER_AGENT" + type: "java.lang.String" + phase: "RUN_TIME" + typeDescription: "string" + optional: true + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.HttpClient" + sourceElementName: "connectTimeout" + sourceElementType: "METHOD" + path: ! + property: "commafeed.http-client.connect-timeout" + environmentVariable: "COMMAFEED_HTTP_CLIENT_CONNECT_TIMEOUT" + type: "java.time.Duration" + phase: "RUN_TIME" + typeDescription: "Duration" + defaultValue: "5S" + javadocSiteLink: "https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.HttpClient" + sourceElementName: "sslHandshakeTimeout" + sourceElementType: "METHOD" + path: ! + property: "commafeed.http-client.ssl-handshake-timeout" + environmentVariable: "COMMAFEED_HTTP_CLIENT_SSL_HANDSHAKE_TIMEOUT" + type: "java.time.Duration" + phase: "RUN_TIME" + typeDescription: "Duration" + defaultValue: "5S" + javadocSiteLink: "https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.HttpClient" + sourceElementName: "socketTimeout" + sourceElementType: "METHOD" + path: ! + property: "commafeed.http-client.socket-timeout" + environmentVariable: "COMMAFEED_HTTP_CLIENT_SOCKET_TIMEOUT" + type: "java.time.Duration" + phase: "RUN_TIME" + typeDescription: "Duration" + defaultValue: "10S" + javadocSiteLink: "https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.HttpClient" + sourceElementName: "responseTimeout" + sourceElementType: "METHOD" + path: ! + property: "commafeed.http-client.response-timeout" + environmentVariable: "COMMAFEED_HTTP_CLIENT_RESPONSE_TIMEOUT" + type: "java.time.Duration" + phase: "RUN_TIME" + typeDescription: "Duration" + defaultValue: "10S" + javadocSiteLink: "https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.HttpClient" + sourceElementName: "connectionTimeToLive" + sourceElementType: "METHOD" + path: ! + property: "commafeed.http-client.connection-time-to-live" + environmentVariable: "COMMAFEED_HTTP_CLIENT_CONNECTION_TIME_TO_LIVE" + type: "java.time.Duration" + phase: "RUN_TIME" + typeDescription: "Duration" + defaultValue: "30S" + javadocSiteLink: "https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.HttpClient" + sourceElementName: "idleConnectionsEvictionInterval" + sourceElementType: "METHOD" + path: ! + property: "commafeed.http-client.idle-connections-eviction-interval" + environmentVariable: "COMMAFEED_HTTP_CLIENT_IDLE_CONNECTIONS_EVICTION_INTERVAL" + type: "java.time.Duration" + phase: "RUN_TIME" + typeDescription: "Duration" + defaultValue: "1M" + javadocSiteLink: "https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.HttpClient" + sourceElementName: "maxResponseSize" + sourceElementType: "METHOD" + path: ! + property: "commafeed.http-client.max-response-size" + environmentVariable: "COMMAFEED_HTTP_CLIENT_MAX_RESPONSE_SIZE" + type: "io.quarkus.runtime.configuration.MemorySize" + phase: "RUN_TIME" + typeDescription: "MemorySize" + defaultValue: "5M" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.HttpClient" + sourceElementName: "blockLocalAddresses" + sourceElementType: "METHOD" + path: ! + property: "commafeed.http-client.block-local-addresses" + environmentVariable: "COMMAFEED_HTTP_CLIENT_BLOCK_LOCAL_ADDRESSES" + type: "boolean" + phase: "RUN_TIME" + typeDescription: "boolean" + defaultValue: "true" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.HttpClient" + sourceElementName: "cache" + sourceElementType: "METHOD" + path: ! + property: "commafeed.http-client.cache" + type: "com.commafeed.CommaFeedConfiguration.HttpClientCache" + items: + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.HttpClientCache" + sourceElementName: "enabled" + sourceElementType: "METHOD" + path: ! + property: "commafeed.http-client.cache.enabled" + environmentVariable: "COMMAFEED_HTTP_CLIENT_CACHE_ENABLED" + type: "boolean" + phase: "RUN_TIME" + typeDescription: "boolean" + defaultValue: "true" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.HttpClientCache" + sourceElementName: "maximumMemorySize" + sourceElementType: "METHOD" + path: ! + property: "commafeed.http-client.cache.maximum-memory-size" + environmentVariable: "COMMAFEED_HTTP_CLIENT_CACHE_MAXIMUM_MEMORY_SIZE" + type: "io.quarkus.runtime.configuration.MemorySize" + phase: "RUN_TIME" + typeDescription: "MemorySize" + defaultValue: "10M" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.HttpClientCache" + sourceElementName: "expiration" + sourceElementType: "METHOD" + path: ! + property: "commafeed.http-client.cache.expiration" + environmentVariable: "COMMAFEED_HTTP_CLIENT_CACHE_EXPIRATION" + type: "java.time.Duration" + phase: "RUN_TIME" + typeDescription: "Duration" + defaultValue: "1M" + javadocSiteLink: "https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html" + level: 1 + - ! + sourceType: "com.commafeed.CommaFeedConfiguration" + sourceElementName: "feedRefresh" + sourceElementType: "METHOD" + path: ! + property: "commafeed.feed-refresh" + type: "com.commafeed.CommaFeedConfiguration.FeedRefresh" + items: + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.FeedRefresh" + sourceElementName: "interval" + sourceElementType: "METHOD" + path: ! + property: "commafeed.feed-refresh.interval" + environmentVariable: "COMMAFEED_FEED_REFRESH_INTERVAL" + type: "java.time.Duration" + phase: "RUN_TIME" + typeDescription: "Duration" + defaultValue: "5M" + javadocSiteLink: "https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.FeedRefresh" + sourceElementName: "maxInterval" + sourceElementType: "METHOD" + path: ! + property: "commafeed.feed-refresh.max-interval" + environmentVariable: "COMMAFEED_FEED_REFRESH_MAX_INTERVAL" + type: "java.time.Duration" + phase: "RUN_TIME" + typeDescription: "Duration" + defaultValue: "4H" + javadocSiteLink: "https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.FeedRefresh" + sourceElementName: "intervalEmpirical" + sourceElementType: "METHOD" + path: ! + property: "commafeed.feed-refresh.interval-empirical" + environmentVariable: "COMMAFEED_FEED_REFRESH_INTERVAL_EMPIRICAL" + type: "boolean" + phase: "RUN_TIME" + typeDescription: "boolean" + defaultValue: "true" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.FeedRefresh" + sourceElementName: "errors" + sourceElementType: "METHOD" + path: ! + property: "commafeed.feed-refresh.errors" + type: "com.commafeed.CommaFeedConfiguration.FeedRefreshErrorHandling" + items: + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.FeedRefreshErrorHandling" + sourceElementName: "retriesBeforeBackoff" + sourceElementType: "METHOD" + path: ! + property: "commafeed.feed-refresh.errors.retries-before-backoff" + environmentVariable: "COMMAFEED_FEED_REFRESH_ERRORS_RETRIES_BEFORE_BACKOFF" + type: "int" + phase: "RUN_TIME" + typeDescription: "int" + defaultValue: "3" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.FeedRefreshErrorHandling" + sourceElementName: "backoffInterval" + sourceElementType: "METHOD" + path: ! + property: "commafeed.feed-refresh.errors.backoff-interval" + environmentVariable: "COMMAFEED_FEED_REFRESH_ERRORS_BACKOFF_INTERVAL" + type: "java.time.Duration" + phase: "RUN_TIME" + typeDescription: "Duration" + defaultValue: "1H" + javadocSiteLink: "https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html" + level: 1 + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.FeedRefresh" + sourceElementName: "httpThreads" + sourceElementType: "METHOD" + path: ! + property: "commafeed.feed-refresh.http-threads" + environmentVariable: "COMMAFEED_FEED_REFRESH_HTTP_THREADS" + type: "int" + phase: "RUN_TIME" + typeDescription: "int" + defaultValue: "3" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.FeedRefresh" + sourceElementName: "databaseThreads" + sourceElementType: "METHOD" + path: ! + property: "commafeed.feed-refresh.database-threads" + environmentVariable: "COMMAFEED_FEED_REFRESH_DATABASE_THREADS" + type: "int" + phase: "RUN_TIME" + typeDescription: "int" + defaultValue: "1" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.FeedRefresh" + sourceElementName: "userInactivityPeriod" + sourceElementType: "METHOD" + path: ! + property: "commafeed.feed-refresh.user-inactivity-period" + environmentVariable: "COMMAFEED_FEED_REFRESH_USER_INACTIVITY_PERIOD" + type: "java.time.Duration" + phase: "RUN_TIME" + typeDescription: "Duration" + defaultValue: "0S" + javadocSiteLink: "https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.FeedRefresh" + sourceElementName: "filteringExpressionEvaluationTimeout" + sourceElementType: "METHOD" + path: ! + property: "commafeed.feed-refresh.filtering-expression-evaluation-timeout" + environmentVariable: "COMMAFEED_FEED_REFRESH_FILTERING_EXPRESSION_EVALUATION_TIMEOUT" + type: "java.time.Duration" + phase: "RUN_TIME" + typeDescription: "Duration" + defaultValue: "500MS" + javadocSiteLink: "https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.FeedRefresh" + sourceElementName: "forceRefreshCooldownDuration" + sourceElementType: "METHOD" + path: ! + property: "commafeed.feed-refresh.force-refresh-cooldown-duration" + environmentVariable: "COMMAFEED_FEED_REFRESH_FORCE_REFRESH_COOLDOWN_DURATION" + type: "java.time.Duration" + phase: "RUN_TIME" + typeDescription: "Duration" + defaultValue: "0S" + javadocSiteLink: "https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration" + sourceElementName: "database" + sourceElementType: "METHOD" + path: ! + property: "commafeed.database" + type: "com.commafeed.CommaFeedConfiguration.Database" + items: + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.Database" + sourceElementName: "queryTimeout" + sourceElementType: "METHOD" + path: ! + property: "commafeed.database.query-timeout" + environmentVariable: "COMMAFEED_DATABASE_QUERY_TIMEOUT" + type: "java.time.Duration" + phase: "RUN_TIME" + typeDescription: "Duration" + defaultValue: "0S" + javadocSiteLink: "https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.Database" + sourceElementName: "cleanup" + sourceElementType: "METHOD" + path: ! + property: "commafeed.database.cleanup" + type: "com.commafeed.CommaFeedConfiguration.Database.Cleanup" + items: + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.Database.Cleanup" + sourceElementName: "entriesMaxAge" + sourceElementType: "METHOD" + path: ! + property: "commafeed.database.cleanup.entries-max-age" + environmentVariable: "COMMAFEED_DATABASE_CLEANUP_ENTRIES_MAX_AGE" + type: "java.time.Duration" + phase: "RUN_TIME" + typeDescription: "Duration" + defaultValue: "365D" + javadocSiteLink: "https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.Database.Cleanup" + sourceElementName: "statusesMaxAge" + sourceElementType: "METHOD" + path: ! + property: "commafeed.database.cleanup.statuses-max-age" + environmentVariable: "COMMAFEED_DATABASE_CLEANUP_STATUSES_MAX_AGE" + type: "java.time.Duration" + phase: "RUN_TIME" + typeDescription: "Duration" + defaultValue: "0S" + javadocSiteLink: "https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.Database.Cleanup" + sourceElementName: "maxFeedCapacity" + sourceElementType: "METHOD" + path: ! + property: "commafeed.database.cleanup.max-feed-capacity" + environmentVariable: "COMMAFEED_DATABASE_CLEANUP_MAX_FEED_CAPACITY" + type: "int" + phase: "RUN_TIME" + typeDescription: "int" + defaultValue: "500" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.Database.Cleanup" + sourceElementName: "maxFeedsPerUser" + sourceElementType: "METHOD" + path: ! + property: "commafeed.database.cleanup.max-feeds-per-user" + environmentVariable: "COMMAFEED_DATABASE_CLEANUP_MAX_FEEDS_PER_USER" + type: "int" + phase: "RUN_TIME" + typeDescription: "int" + defaultValue: "0" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.Database.Cleanup" + sourceElementName: "batchSize" + sourceElementType: "METHOD" + path: ! + property: "commafeed.database.cleanup.batch-size" + environmentVariable: "COMMAFEED_DATABASE_CLEANUP_BATCH_SIZE" + type: "int" + phase: "RUN_TIME" + typeDescription: "int" + defaultValue: "100" + level: 1 + - ! + sourceType: "com.commafeed.CommaFeedConfiguration" + sourceElementName: "users" + sourceElementType: "METHOD" + path: ! + property: "commafeed.users" + type: "com.commafeed.CommaFeedConfiguration.Users" + items: + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.Users" + sourceElementName: "allowRegistrations" + sourceElementType: "METHOD" + path: ! + property: "commafeed.users.allow-registrations" + environmentVariable: "COMMAFEED_USERS_ALLOW_REGISTRATIONS" + type: "boolean" + phase: "RUN_TIME" + typeDescription: "boolean" + defaultValue: "false" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.Users" + sourceElementName: "strictPasswordPolicy" + sourceElementType: "METHOD" + path: ! + property: "commafeed.users.strict-password-policy" + environmentVariable: "COMMAFEED_USERS_STRICT_PASSWORD_POLICY" + type: "boolean" + phase: "RUN_TIME" + typeDescription: "boolean" + defaultValue: "true" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.Users" + sourceElementName: "createDemoAccount" + sourceElementType: "METHOD" + path: ! + property: "commafeed.users.create-demo-account" + environmentVariable: "COMMAFEED_USERS_CREATE_DEMO_ACCOUNT" + type: "boolean" + phase: "RUN_TIME" + typeDescription: "boolean" + defaultValue: "false" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration" + sourceElementName: "websocket" + sourceElementType: "METHOD" + path: ! + property: "commafeed.websocket" + type: "com.commafeed.CommaFeedConfiguration.Websocket" + items: + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.Websocket" + sourceElementName: "enabled" + sourceElementType: "METHOD" + path: ! + property: "commafeed.websocket.enabled" + environmentVariable: "COMMAFEED_WEBSOCKET_ENABLED" + type: "boolean" + phase: "RUN_TIME" + typeDescription: "boolean" + defaultValue: "true" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.Websocket" + sourceElementName: "pingInterval" + sourceElementType: "METHOD" + path: ! + property: "commafeed.websocket.ping-interval" + environmentVariable: "COMMAFEED_WEBSOCKET_PING_INTERVAL" + type: "java.time.Duration" + phase: "RUN_TIME" + typeDescription: "Duration" + defaultValue: "15M" + javadocSiteLink: "https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html" + - ! + sourceType: "com.commafeed.CommaFeedConfiguration.Websocket" + sourceElementName: "treeReloadInterval" + sourceElementType: "METHOD" + path: ! + property: "commafeed.websocket.tree-reload-interval" + environmentVariable: "COMMAFEED_WEBSOCKET_TREE_RELOAD_INTERVAL" + type: "java.time.Duration" + phase: "RUN_TIME" + typeDescription: "Duration" + defaultValue: "30S" + javadocSiteLink: "https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html" + qualifiedNames: + - "com.commafeed.CommaFeedConfiguration"