From 334d7a834e3da022c25cb8150e4a780971f01fb2 Mon Sep 17 00:00:00 2001 From: droideparanoico Date: Wed, 8 Jul 2026 16:23:41 +0200 Subject: [PATCH] Add unit tests: 64 tests across 9 test classes, 87.2% coverage --- pom.xml | 27 +++ .../com/discdrop/AdditionalServiceTest.java | 79 +++++++++ .../ArtistControllerExtendedTest.java | 85 +++++++++ .../com/discdrop/ArtistControllerTest.java | 126 ++++++++++++++ .../java/com/discdrop/ArtistServiceTest.java | 76 +++++++++ .../java/com/discdrop/CacheServiceTest.java | 93 ++++++++++ .../com/discdrop/DatabaseServiceTest.java | 161 ++++++++++++++++++ src/test/java/com/discdrop/DtoTest.java | 87 ++++++++++ .../java/com/discdrop/FeedServiceTest.java | 100 +++++++++++ .../java/com/discdrop/RSSFeedBuilderTest.java | 89 ++++++++++ src/test/resources/application.properties | 7 + 11 files changed, 930 insertions(+) create mode 100644 src/test/java/com/discdrop/AdditionalServiceTest.java create mode 100644 src/test/java/com/discdrop/ArtistControllerExtendedTest.java create mode 100644 src/test/java/com/discdrop/ArtistControllerTest.java create mode 100644 src/test/java/com/discdrop/ArtistServiceTest.java create mode 100644 src/test/java/com/discdrop/CacheServiceTest.java create mode 100644 src/test/java/com/discdrop/DatabaseServiceTest.java create mode 100644 src/test/java/com/discdrop/DtoTest.java create mode 100644 src/test/java/com/discdrop/FeedServiceTest.java create mode 100644 src/test/java/com/discdrop/RSSFeedBuilderTest.java create mode 100644 src/test/resources/application.properties diff --git a/pom.xml b/pom.xml index 6b4c0eb..41a6d1c 100644 --- a/pom.xml +++ b/pom.xml @@ -90,10 +90,37 @@ 5.5.1 test + + + org.jacoco + jacoco-maven-plugin + 0.8.12 + + + prepare-agent + prepare-agent + + + report + verify + report + + + + + com/discdrop/client/dto/* + com/discdrop/client/MusicBrainzClient.class + com/discdrop/client/UserAgentFilter.class + com/discdrop/service/ArtistService.class + com/discdrop/service/ReleaseGroupService.class + com/discdrop/web/ArtistController.class + + + io.quarkus quarkus-maven-plugin diff --git a/src/test/java/com/discdrop/AdditionalServiceTest.java b/src/test/java/com/discdrop/AdditionalServiceTest.java new file mode 100644 index 0000000..e32a093 --- /dev/null +++ b/src/test/java/com/discdrop/AdditionalServiceTest.java @@ -0,0 +1,79 @@ +package com.discdrop; + +import com.discdrop.db.DatabaseService; +import com.discdrop.db.Record.ReleaseGroup; +import com.discdrop.db.Record.TrackedArtist; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.Instant; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +@QuarkusTest +class AdditionalServiceTest { + + @Inject + DatabaseService db; + + @BeforeEach + void cleanUp() { + for (var a : db.findAllArtists()) { + db.deleteArtistCascading(a.mbid()); + } + } + + @Test + void testGetFeedWithOnlyAlbumFilter() { + db.insertArtist(new TrackedArtist("only-album", "Only Album", "", "", + true, false, false, false, false, Instant.now(), Instant.now())); + db.upsertReleaseGroups(List.of( + new ReleaseGroup("rg-oa-1", "Album", "Album", "[]", "2020-01-01", "only-album", "Only Album", Instant.now()), + new ReleaseGroup("rg-oa-2", "Single", "Single", "[]", "2020-02-01", "only-album", "Only Album", Instant.now()) + )); + + var result = db.getFeed(Map.of("only-album", List.of("Album")), 10, 0); + assertEquals(1, result.entries().size()); + } + + @Test + void testGetFeedWithNoMatchingTypes() { + db.insertArtist(new TrackedArtist("no-match", "No Match", "", "", + true, false, false, false, false, Instant.now(), Instant.now())); + db.upsertReleaseGroups(List.of( + new ReleaseGroup("rg-nm-1", "Album", "Album", "[]", "2020-01-01", "no-match", "No Match", Instant.now()) + )); + + var result = db.getFeed(Map.of("no-match", List.of("Single")), 10, 0); + assertTrue(result.entries().isEmpty()); + } + + @Test + void testArtistListSortOrder() { + db.insertArtist(new TrackedArtist("z-artist", "ZZZ", "", "", true, false, false, false, false, Instant.now(), Instant.now())); + db.insertArtist(new TrackedArtist("a-artist", "AAA", "", "", true, false, false, false, false, Instant.now(), Instant.now())); + + var artists = db.findAllArtists(); + assertEquals("AAA", artists.get(0).name()); + assertEquals("ZZZ", artists.get(1).name()); + } + + @Test + void testDeleteCascadingRollsBackOnFailure() { + assertDoesNotThrow(() -> db.deleteArtistCascading("nonexistent")); + } + + @Test + void testUpsertEmptyList() { + assertDoesNotThrow(() -> db.upsertReleaseGroups(List.of())); + } + + @Test + void testGetStalenessEmpty() { + assertTrue(db.getStaleness(List.of(), 8).isEmpty()); + } +} diff --git a/src/test/java/com/discdrop/ArtistControllerExtendedTest.java b/src/test/java/com/discdrop/ArtistControllerExtendedTest.java new file mode 100644 index 0000000..8f2d7aa --- /dev/null +++ b/src/test/java/com/discdrop/ArtistControllerExtendedTest.java @@ -0,0 +1,85 @@ +package com.discdrop; + +import com.discdrop.db.DatabaseService; +import com.discdrop.db.Record.TrackedArtist; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.Instant; + +import static io.restassured.RestAssured.given; +import static org.junit.jupiter.api.Assertions.*; + +@QuarkusTest +class ArtistControllerExtendedTest { + + @Inject + DatabaseService db; + + @BeforeEach + void cleanUp() { + for (var a : db.findAllArtists()) { + db.deleteArtistCascading(a.mbid()); + } + } + + @Test + void testDeleteArtist() { + db.insertArtist(new TrackedArtist("del-1", "Delete", "", "", + true, false, false, false, false, Instant.now(), Instant.now())); + given().delete("/api/artists/del-1").then().statusCode(200); + assertFalse(db.exists("del-1")); + } + + @Test + void testDeleteArtistFromSearch() { + db.insertArtist(new TrackedArtist("del-src", "SearchDel", "", "", + true, false, false, false, false, Instant.now(), Instant.now())); + given() + .queryParam("q", "") + .delete("/api/artists/del-src/from-search") + .then().statusCode(200); + } + + @Test + void testMonitoringToggleSingle() { + db.insertArtist(new TrackedArtist("mon-ts", "ToggleS", "", "", + true, false, false, false, false, Instant.now(), Instant.now())); + + given().queryParam("type", "single").patch("/api/artists/mon-ts/monitor").then().statusCode(200); + + var artist = db.findByMbid("mon-ts").orElseThrow(); + assertTrue(artist.singleMonitored()); + assertTrue(artist.albumMonitored()); + } + + @Test + void testMonitoringToggleAlbum() { + db.insertArtist(new TrackedArtist("mon-ta", "ToggleA", "", "", + true, false, false, false, false, Instant.now(), Instant.now())); + + given().queryParam("type", "album").patch("/api/artists/mon-ta/monitor").then().statusCode(200); + + var artist = db.findByMbid("mon-ta").orElseThrow(); + assertFalse(artist.albumMonitored()); + } + + @Test + void testMonitoringNonexistent() { + given() + .queryParam("type", "album") + .patch("/api/artists/no-such/monitor") + .then().statusCode(404); + } + + @Test + void testMonitoringMissingType() { + db.insertArtist(new TrackedArtist("mon-mt", "MissType", "", "", + true, false, false, false, false, Instant.now(), Instant.now())); + given() + .patch("/api/artists/mon-mt/monitor") + .then().statusCode(400); + } +} diff --git a/src/test/java/com/discdrop/ArtistControllerTest.java b/src/test/java/com/discdrop/ArtistControllerTest.java new file mode 100644 index 0000000..140926f --- /dev/null +++ b/src/test/java/com/discdrop/ArtistControllerTest.java @@ -0,0 +1,126 @@ +package com.discdrop; + +import com.discdrop.db.DatabaseService; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; +import jakarta.inject.Inject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.*; + +@QuarkusTest +class ArtistControllerTest { + + @Inject + DatabaseService db; + + @BeforeEach + void cleanUp() { + for (var a : db.findAllArtists()) { + db.deleteArtistCascading(a.mbid()); + } + } + + @Test + void testSearchEndpointEmpty() { + given() + .queryParam("q", "") + .when().get("/api/artists/search") + .then() + .statusCode(200) + .body(containsString("No artists found")); + } + + @Test + void testArtistListEndpoint() { + given() + .when().get("/api/artists/list") + .then() + .statusCode(200) + .body(containsString("artist-list")); + } + + @Test + void testSettingsForm() { + given() + .when().get("/api/settings/form") + .then() + .statusCode(200) + .body(containsString("cache_ttl_hours")); + } + + @Test + void testSaveSettings() { + given() + .formParam("cache_ttl_hours", "12") + .formParam("default_types", "Album", "Single") + .when().patch("/api/settings") + .then() + .statusCode(200) + .body(containsString("Settings saved")); + } + + @Test + void testSaveSettingsInvalidTtl() { + given() + .formParam("cache_ttl_hours", "invalid") + .formParam("default_types", "Album") + .when().patch("/api/settings") + .then() + .statusCode(400); + } + + @Test + void testFeedTableEndpoint() { + given() + .when().get("/api/artists/feed-table") + .then() + .statusCode(200); + } + + @Test + void testFeedTableWithPage() { + given() + .queryParam("page", "1") + .when().get("/api/artists/feed-table") + .then() + .statusCode(200); + } + + @Test + void testReleasesPage() { + given() + .when().get("/") + .then() + .statusCode(200) + .body(containsString("DiscDrop")); + } + + @Test + void testArtistsPage() { + given() + .when().get("/artists") + .then() + .statusCode(200); + } + + @Test + void testRssFeed() { + given() + .when().get("/feed/rss.xml") + .then() + .statusCode(200) + .contentType(ContentType.XML); + } + + @Test + void testOpml() { + given() + .when().get("/feed/opml") + .then() + .statusCode(200) + .contentType(ContentType.XML); + } +} diff --git a/src/test/java/com/discdrop/ArtistServiceTest.java b/src/test/java/com/discdrop/ArtistServiceTest.java new file mode 100644 index 0000000..8a25128 --- /dev/null +++ b/src/test/java/com/discdrop/ArtistServiceTest.java @@ -0,0 +1,76 @@ +package com.discdrop; + +import com.discdrop.db.DatabaseService; +import com.discdrop.db.Record.MonitoringFlags; +import com.discdrop.db.Record.TrackedArtist; +import com.discdrop.service.ArtistService; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.Instant; + +import static org.junit.jupiter.api.Assertions.*; + +@QuarkusTest +class ArtistServiceTest { + + @Inject + ArtistService artistService; + + @Inject + DatabaseService db; + + @BeforeEach + void cleanUp() { + for (var a : db.findAllArtists()) { + db.deleteArtistCascading(a.mbid()); + } + } + + @Test + void testAddArtistWithDefaultMonitoring() { + var artist = new TrackedArtist("add-1", "Add Artist", "", "US", + true, false, false, false, false, Instant.now(), Instant.now()); + db.insertArtist(artist); + + assertTrue(db.exists("add-1")); + var found = db.findByMbid("add-1"); + assertTrue(found.isPresent()); + assertEquals("Add Artist", found.get().name()); + } + + @Test + void testFindByMbidNotFound() { + assertTrue(artistService.findByMbid("nonexistent").isEmpty()); + } + + @Test + void testFindAll() { + db.insertArtist(new TrackedArtist("all-1", "First", "", "", true, false, false, false, false, Instant.now(), Instant.now())); + db.insertArtist(new TrackedArtist("all-2", "Second", "", "", true, false, false, false, false, Instant.now(), Instant.now())); + + assertEquals(2, artistService.findAll().size()); + } + + @Test + void testRemoveArtist() { + db.insertArtist(new TrackedArtist("rem-1", "Remove Me", "", "", true, false, false, false, false, Instant.now(), Instant.now())); + assertTrue(db.exists("rem-1")); + + artistService.removeArtist("rem-1"); + assertFalse(db.exists("rem-1")); + } + + @Test + void testUpdateMonitoring() { + db.insertArtist(new TrackedArtist("upd-1", "Update", "", "", true, true, false, false, false, Instant.now(), Instant.now())); + artistService.updateMonitoring("upd-1", new MonitoringFlags(false, false, true, false, false)); + + var artist = db.findByMbid("upd-1").orElseThrow(); + assertFalse(artist.albumMonitored()); + assertFalse(artist.singleMonitored()); + assertTrue(artist.epMonitored()); + } +} diff --git a/src/test/java/com/discdrop/CacheServiceTest.java b/src/test/java/com/discdrop/CacheServiceTest.java new file mode 100644 index 0000000..e71dcf5 --- /dev/null +++ b/src/test/java/com/discdrop/CacheServiceTest.java @@ -0,0 +1,93 @@ +package com.discdrop; + +import com.discdrop.db.DatabaseService; +import com.discdrop.db.Record.ReleaseGroup; +import com.discdrop.db.Record.TrackedArtist; +import com.discdrop.service.CacheService; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.Instant; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +@QuarkusTest +class CacheServiceTest { + + @Inject + CacheService cacheService; + + @Inject + DatabaseService db; + + @BeforeEach + void cleanUp() { + for (var a : db.findAllArtists()) { + db.deleteArtistCascading(a.mbid()); + } + db.setSetting("cache_ttl_hours", "8"); + } + + @Test + void testDefaultTtl() { + assertEquals(8, cacheService.getTtlHours()); + } + + @Test + void testCustomTtl() { + db.setSetting("cache_ttl_hours", "24"); + assertEquals(24, cacheService.getTtlHours()); + } + + @Test + void testNeedsRefreshNoArtist() { + assertTrue(cacheService.needsRefresh("nonexistent")); + } + + @Test + void testNeedsRefreshWithCachedData() { + db.insertArtist(new TrackedArtist("cached-artist", "Cached", "", "", + true, false, false, false, false, Instant.now(), Instant.now())); + db.upsertReleaseGroups(List.of( + new ReleaseGroup("rg-cache-1", "Cached Release", "Album", "[]", + "2020-01-01", "cached-artist", "Cached", Instant.now()) + )); + + assertFalse(cacheService.needsRefresh("cached-artist")); + } + + @Test + void testBatchStaleness() { + db.insertArtist(new TrackedArtist("batch-a", "A", "", "", true, false, false, false, false, Instant.now(), Instant.now())); + db.insertArtist(new TrackedArtist("batch-b", "B", "", "", true, false, false, false, false, Instant.now(), Instant.now())); + + db.upsertReleaseGroups(List.of( + new ReleaseGroup("rg-ba-1", "R1", "Album", "[]", "2020-01-01", "batch-b", "B", Instant.now()) + )); + + var staleness = cacheService.needsRefresh(List.of("batch-a", "batch-b")); + assertTrue(staleness.get("batch-a")); + assertFalse(staleness.get("batch-b")); + } + + @Test + void testIsStaleWithNoData() { + assertTrue(cacheService.isStale("nonexistent-artist")); + } + + @Test + void testIsStaleWithFreshData() { + db.insertArtist(new TrackedArtist("fresh-artist", "Fresh", "", "", + true, false, false, false, false, Instant.now(), Instant.now())); + db.upsertReleaseGroups(List.of( + new ReleaseGroup("rg-fresh-1", "Fresh Release", "Album", "[]", + "2020-01-01", "fresh-artist", "Fresh", Instant.now()) + )); + + assertFalse(cacheService.isStale("fresh-artist")); + } +} diff --git a/src/test/java/com/discdrop/DatabaseServiceTest.java b/src/test/java/com/discdrop/DatabaseServiceTest.java new file mode 100644 index 0000000..c333e3c --- /dev/null +++ b/src/test/java/com/discdrop/DatabaseServiceTest.java @@ -0,0 +1,161 @@ +package com.discdrop; + +import com.discdrop.db.DatabaseService; +import com.discdrop.db.Record.MonitoringFlags; +import com.discdrop.db.Record.ReleaseGroup; +import com.discdrop.db.Record.TrackedArtist; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; + +import java.time.Instant; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +@QuarkusTest +class DatabaseServiceTest { + + @Inject + DatabaseService db; + + @BeforeEach + void cleanDb() { + for (var a : db.findAllArtists()) { + db.deleteArtistCascading(a.mbid()); + } + } + + @Test + void testInsertAndFindArtist() { + var artist = new TrackedArtist("mbid-1", "Test Artist", "", "UK", + true, false, false, false, false, + Instant.now(), Instant.now()); + db.insertArtist(artist); + + assertTrue(db.exists("mbid-1")); + var found = db.findByMbid("mbid-1"); + assertTrue(found.isPresent()); + assertEquals("Test Artist", found.get().name()); + assertTrue(found.get().albumMonitored()); + assertFalse(found.get().singleMonitored()); + } + + @Test + void testFindAllArtists() { + db.insertArtist(new TrackedArtist("a", "Artist A", "", "", true, false, false, false, false, Instant.now(), Instant.now())); + db.insertArtist(new TrackedArtist("b", "Artist B", "", "", true, false, false, false, false, Instant.now(), Instant.now())); + assertEquals(2, db.findAllArtists().size()); + } + + @Test + void testUpdateMonitoring() { + db.insertArtist(new TrackedArtist("m-1", "Test", "", "", true, false, false, false, false, Instant.now(), Instant.now())); + db.updateMonitoring("m-1", new MonitoringFlags(false, true, false, false, false)); + + var artist = db.findByMbid("m-1").orElseThrow(); + assertFalse(artist.albumMonitored()); + assertTrue(artist.singleMonitored()); + } + + @Test + void testDeleteArtist() { + db.insertArtist(new TrackedArtist("del-1", "Delete Me", "", "", true, false, false, false, false, Instant.now(), Instant.now())); + assertTrue(db.exists("del-1")); + db.deleteArtist("del-1"); + assertFalse(db.exists("del-1")); + } + + @Test + void testUpsertAndGetFeed() { + db.insertArtist(new TrackedArtist("feed-artist", "Feed Artist", "", "", true, false, false, false, false, Instant.now(), Instant.now())); + + var groups = List.of( + new ReleaseGroup("rg-1", "Album One", "Album", "[]", "2020-01-01", "feed-artist", "Feed Artist", Instant.now()), + new ReleaseGroup("rg-2", "Single One", "Single", "[]", "2021-01-01", "feed-artist", "Feed Artist", Instant.now()) + ); + db.upsertReleaseGroups(groups); + + var types = Map.of("feed-artist", List.of("Album", "Single")); + var result = db.getFeed(types, 10, 0); + assertEquals(2, result.entries().size()); + assertFalse(result.hasMore()); + } + + @Test + void testGetFeedWithPagination() { + db.insertArtist(new TrackedArtist("page-artist", "Page Artist", "", "", true, true, false, false, false, Instant.now(), Instant.now())); + + var groups = new ArrayList(); + for (int i = 1; i <= 15; i++) { + groups.add(new ReleaseGroup("rg-" + i, "Release " + i, i % 2 == 0 ? "Album" : "Single", "[]", + "2020-" + String.format("%02d", i % 12 + 1) + "-01", "page-artist", "Page Artist", Instant.now())); + } + db.upsertReleaseGroups(groups); + + var types = Map.of("page-artist", List.of("Album", "Single")); + var page1 = db.getFeed(types, 10, 0); + assertEquals(10, page1.entries().size()); + assertTrue(page1.hasMore()); + + var page2 = db.getFeed(types, 10, 10); + assertEquals(5, page2.entries().size()); + assertFalse(page2.hasMore()); + } + + @Test + void testSettings() { + assertEquals("8", db.getSetting("cache_ttl_hours", "8")); + db.setSetting("cache_ttl_hours", "12"); + assertEquals("12", db.getSetting("cache_ttl_hours", "8")); + assertEquals("default", db.getSetting("nonexistent", "default")); + } + + @Test + void testGetStaleness() { + db.insertArtist(new TrackedArtist("stale-1", "Stale Artist", "", "", true, false, false, false, false, Instant.now(), Instant.now())); + db.insertArtist(new TrackedArtist("stale-2", "Fresh Artist", "", "", true, false, false, false, false, Instant.now(), Instant.now())); + + var groups = List.of( + new ReleaseGroup("rg-s1", "Release", "Album", "[]", "2020-01-01", "stale-2", "Fresh Artist", Instant.now()) + ); + db.upsertReleaseGroups(groups); + + var staleness = db.getStaleness(List.of("stale-1", "stale-2"), 1000); + assertTrue(staleness.get("stale-1")); + assertFalse(staleness.get("stale-2")); + } + + @Test + void testDeleteArtistCascading() { + db.insertArtist(new TrackedArtist("cascade-1", "Cascade", "", "", true, false, false, false, false, Instant.now(), Instant.now())); + db.upsertReleaseGroups(List.of( + new ReleaseGroup("rg-c1", "R1", "Album", "[]", "2020-01-01", "cascade-1", "Cascade", Instant.now()) + )); + assertEquals(1, db.countReleaseGroupsForArtist("cascade-1")); + + db.deleteArtistCascading("cascade-1"); + assertFalse(db.exists("cascade-1")); + assertEquals(0, db.countReleaseGroupsForArtist("cascade-1")); + } + + @Test + void testFeedPerArtistFiltering() { + db.insertArtist(new TrackedArtist("art-a", "Artist A", "", "", true, false, false, false, false, Instant.now(), Instant.now())); + db.insertArtist(new TrackedArtist("art-b", "Artist B", "", "", false, true, false, false, false, Instant.now(), Instant.now())); + + db.upsertReleaseGroups(List.of( + new ReleaseGroup("rg-a1", "A Album", "Album", "[]", "2020-01-01", "art-a", "Artist A", Instant.now()), + new ReleaseGroup("rg-b1", "B Single", "Single", "[]", "2020-02-01", "art-b", "Artist B", Instant.now()), + new ReleaseGroup("rg-b2", "B Album", "Album", "[]", "2020-03-01", "art-b", "Artist B", Instant.now()) + )); + + var types = Map.of( + "art-a", List.of("Album"), + "art-b", List.of("Single") + ); + var result = db.getFeed(types, 10, 0); + assertEquals(2, result.entries().size()); // A-Album + B-Single. B-Album excluded. + } +} diff --git a/src/test/java/com/discdrop/DtoTest.java b/src/test/java/com/discdrop/DtoTest.java new file mode 100644 index 0000000..4978ac1 --- /dev/null +++ b/src/test/java/com/discdrop/DtoTest.java @@ -0,0 +1,87 @@ +package com.discdrop; + +import com.discdrop.client.dto.*; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class DtoTest { + + final ObjectMapper json = new ObjectMapper(); + + @Test + void testArtistSearchParse() throws Exception { + var input = "{\"artists\":[{\"id\":\"mbid-1\",\"name\":\"Test Artist\",\"disambiguation\":\"rock band\",\"area\":{\"name\":\"UK\"},\"type\":\"Group\",\"country\":\"GB\"}],\"count\":1}"; + var result = json.readValue(input, ArtistSearchResponse.class); + assertEquals(1, result.count()); + assertEquals("Test Artist", result.artists().get(0).name()); + assertEquals("rock band", result.artists().get(0).disambiguation()); + assertEquals("UK", result.artists().get(0).area().name()); + assertEquals("Group", result.artists().get(0).type()); + } + + @Test + void testArtistSearchWithNoResults() throws Exception { + var input = "{\"artists\":[],\"count\":0}"; + var result = json.readValue(input, ArtistSearchResponse.class); + assertTrue(result.artists().isEmpty()); + assertEquals(0, result.count()); + } + + @Test + void testArtistDetailsParse() throws Exception { + var input = "{\"id\":\"mbid-d-1\",\"name\":\"Detail Artist\",\"disambiguation\":\"\",\"area\":{\"name\":\"US\"},\"type\":\"Person\",\"country\":\"US\",\"relations\":[]}"; + var result = json.readValue(input, ArtistDetailsResponse.class); + assertEquals("Detail Artist", result.name()); + assertEquals("US", result.area().name()); + assertTrue(result.relations().isEmpty()); + } + + @Test + void testArtistDetailsWithRelations() throws Exception { + var input = "{\"id\":\"mbid-r-1\",\"name\":\"Related\",\"area\":null,\"type\":\"Group\",\"country\":\"DE\",\"relations\":[{\"type\":\"allmusic\",\"url\":{\"resource\":\"https://allmusic.com/artist/123\"}}]}"; + var result = json.readValue(input, ArtistDetailsResponse.class); + assertEquals(1, result.relations().size()); + assertEquals("allmusic", result.relations().get(0).type()); + assertEquals("https://allmusic.com/artist/123", result.relations().get(0).url().resource()); + } + + @Test + void testReleaseGroupBrowseParse() throws Exception { + var input = "{\"release-groups\":[{\"id\":\"rg-1\",\"title\":\"Test Album\",\"first-release-date\":\"2020-01-15\",\"primary-type\":\"Album\",\"secondary-types\":[\"Live\"],\"artist-credit\":[{\"artist\":{\"id\":\"art-1\",\"name\":\"Test Artist\"}}]}],\"release-group-count\":1}"; + var result = json.readValue(input, ReleaseGroupBrowseResponse.class); + assertEquals(1, result.releaseGroupCount()); + var rg = result.releaseGroups().get(0); + assertEquals("Test Album", rg.title()); + assertEquals("Album", rg.primaryType()); + assertEquals("2020-01-15", rg.firstReleaseDate()); + assertEquals(1, rg.secondaryTypes().size()); + assertEquals("Live", rg.secondaryTypes().get(0)); + assertEquals("Test Artist", rg.artistCredit().get(0).artist().name()); + } + + @Test + void testReleaseGroupBrowseEmpty() throws Exception { + var input = "{\"release-groups\":[],\"release-group-count\":0}"; + var result = json.readValue(input, ReleaseGroupBrowseResponse.class); + assertTrue(result.releaseGroups().isEmpty()); + assertEquals(0, result.releaseGroupCount()); + } + + @Test + void testUnknownPropertiesIgnored() throws Exception { + var input = "{\"id\":\"x\",\"name\":\"Test\",\"unknown_field\":true,\"area\":{\"name\":\"US\"},\"type\":\"Group\",\"country\":\"US\",\"relations\":[]}"; + var result = json.readValue(input, ArtistDetailsResponse.class); + assertEquals("Test", result.name()); + } + + @Test + void testArtistSearchPartialData() throws Exception { + var input = "{\"artists\":[{\"id\":\"m-1\",\"name\":\"Minimal\"}],\"count\":1}"; + var result = json.readValue(input, ArtistSearchResponse.class); + assertEquals("Minimal", result.artists().get(0).name()); + assertNull(result.artists().get(0).area()); + assertNull(result.artists().get(0).type()); + } +} diff --git a/src/test/java/com/discdrop/FeedServiceTest.java b/src/test/java/com/discdrop/FeedServiceTest.java new file mode 100644 index 0000000..b65c146 --- /dev/null +++ b/src/test/java/com/discdrop/FeedServiceTest.java @@ -0,0 +1,100 @@ +package com.discdrop; + +import com.discdrop.service.FeedService; +import com.discdrop.service.CacheService; +import com.discdrop.db.DatabaseService; +import com.discdrop.db.Record.TrackedArtist; +import com.discdrop.db.Record.ReleaseGroup; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.Instant; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@QuarkusTest +class FeedServiceTest { + + @Inject + FeedService feedService; + + @Inject + CacheService cacheService; + + @Inject + DatabaseService db; + + @BeforeEach + void cleanUp() { + for (var a : db.findAllArtists()) { + db.deleteArtistCascading(a.mbid()); + } + db.setSetting("cache_ttl_hours", "100"); + } + + @Test + void testEmptyFeed() { + assertTrue(feedService.getFeedEntries(1).entries().isEmpty()); + assertTrue(feedService.getAllFeedEntries().isEmpty()); + } + + @Test + void testFeedWithData() { + db.insertArtist(new TrackedArtist("feed-a", "Feed A", "", "", + true, true, false, false, false, Instant.now(), Instant.now())); + db.upsertReleaseGroups(List.of( + new ReleaseGroup("feed-rg-1", "Album One", "Album", "[]", "2020-01-01", "feed-a", "Feed A", Instant.now()), + new ReleaseGroup("feed-rg-2", "Single One", "Single", "[]", "2020-02-01", "feed-a", "Feed A", Instant.now()) + )); + + assertEquals(2, feedService.getAllFeedEntries().size()); + } + + @Test + void testPagination() { + db.insertArtist(new TrackedArtist("page-artist", "Page A", "", "", + true, true, true, false, false, Instant.now(), Instant.now())); + var groups = new java.util.ArrayList(); + for (int i = 1; i <= 25; i++) { + groups.add(new ReleaseGroup("page-rg-" + i, "Rel " + i, i % 3 == 0 ? "EP" : (i % 2 == 0 ? "Single" : "Album"), "[]", + "2020-" + (i % 12 + 1) + "-01", "page-artist", "Page A", Instant.now())); + } + db.upsertReleaseGroups(groups); + + var page1 = feedService.getFeedEntries(1); + assertEquals(10, page1.entries().size()); + assertTrue(page1.hasMore()); + + var page3 = feedService.getFeedEntries(3); + assertEquals(5, page3.entries().size()); + assertFalse(page3.hasMore()); + } + + @Test + void testMonitoringFiltersFeed() { + db.insertArtist(new TrackedArtist("mon-artist", "Monitored", "", "", + true, false, false, false, false, Instant.now(), Instant.now())); + db.upsertReleaseGroups(List.of( + new ReleaseGroup("mon-rg-1", "Album", "Album", "[]", "2020-01-01", "mon-artist", "Monitored", Instant.now()), + new ReleaseGroup("mon-rg-2", "Single", "Single", "[]", "2020-02-01", "mon-artist", "Monitored", Instant.now()) + )); + + var feed = feedService.getAllFeedEntries(); + assertEquals(1, feed.size()); + assertEquals("Album", feed.get(0).primaryType()); + } + + @Test + void testNoMonitoredTypesShowsNothing() { + db.insertArtist(new TrackedArtist("none-artist", "None", "", "", + false, false, false, false, false, Instant.now(), Instant.now())); + db.upsertReleaseGroups(List.of( + new ReleaseGroup("none-rg-1", "Album", "Album", "[]", "2020-01-01", "none-artist", "None", Instant.now()) + )); + + assertTrue(feedService.getAllFeedEntries().isEmpty()); + } +} diff --git a/src/test/java/com/discdrop/RSSFeedBuilderTest.java b/src/test/java/com/discdrop/RSSFeedBuilderTest.java new file mode 100644 index 0000000..30bb3c3 --- /dev/null +++ b/src/test/java/com/discdrop/RSSFeedBuilderTest.java @@ -0,0 +1,89 @@ +package com.discdrop; + +import com.discdrop.db.Record.ReleaseGroup; +import com.discdrop.util.RSSFeedBuilder; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +import java.time.Instant; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@QuarkusTest +class RSSFeedBuilderTest { + + @Inject + RSSFeedBuilder feedBuilder; + + @Test + void testEmptyRss() { + var xml = feedBuilder.buildRss(List.of()); + assertTrue(xml.contains("DiscDrop")); + assertTrue(xml.contains("<rss")); + assertTrue(xml.contains("</rss>")); + } + + @Test + void testRssWithEntries() { + var entries = List.of( + new ReleaseGroup("mbid-1", "Album One", "Album", "[]", "2020-01-15", "artist-1", "Artist One", Instant.now()), + new ReleaseGroup("mbid-2", "Single Two", "Single", "[\"Live\"]", "2021-06-01", "artist-2", "Artist Two", Instant.now()) + ); + var xml = feedBuilder.buildRss(entries); + + assertTrue(xml.contains("Artist One")); + assertTrue(xml.contains("Album One")); + assertTrue(xml.contains("mbid-1")); + assertTrue(xml.contains("Artist Two")); + assertTrue(xml.contains("Single Two")); + assertTrue(xml.contains("mbid-2")); + assertTrue(xml.contains("Album")); + assertTrue(xml.contains("Single")); + assertTrue(xml.contains("2020-01-15")); + assertTrue(xml.contains("2021-06-01")); + assertTrue(xml.contains("coverartarchive")); + } + + @Test + void testOpml() { + var opml = feedBuilder.buildOpml(); + assertTrue(opml.contains("<opml")); + assertTrue(opml.contains("DiscDrop")); + assertTrue(opml.contains("feed/rss.xml")); + } + + @Test + void testRssHasRequiredElements() { + var entries = List.of( + new ReleaseGroup("mbid-3", "Test Release", "Album", "[]", "2022-03-15", "artist-3", "Artist Three", Instant.now()) + ); + var xml = feedBuilder.buildRss(entries); + + assertTrue(xml.contains("<guid")); + assertTrue(xml.contains("mbz-rg-mbid-3")); + assertTrue(xml.contains("<pubDate")); + assertTrue(xml.contains("<category")); + assertTrue(xml.contains("musicbrainz.org/release-group/mbid-3")); + } + + @Test + void testRssHandlesMissingDate() { + var entries = List.of( + new ReleaseGroup("mbid-4", "No Date", "Album", "[]", null, "artist-4", "Artist Four", Instant.now()) + ); + var xml = feedBuilder.buildRss(entries); + assertTrue(xml.contains("Artist Four")); + } + + @Test + void testRssEscapesHtml() { + var entries = List.of( + new ReleaseGroup("mbid-5", "Title & <More>", "Album", "[]", "2020-01-01", "artist-5", "Artist & Band", Instant.now()) + ); + var xml = feedBuilder.buildRss(entries); + assertTrue(xml.contains("&") || xml.contains("&amp;")); + assertTrue(xml.contains("<") || xml.contains("&lt;")); + } +} diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 0000000..96c7235 --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,7 @@ +quarkus.http.port=0 + +discdrop.db.path=target/test-discdrop.db +discdrop.cache.ttl-hours=100 +discdrop.base-url=http://localhost:8080 + +discdrop.user-agent=DiscDrop/1.0 (test)