diff --git a/src/main/java/me/itzg/helpers/modrinth/ModrinthApiClient.java b/src/main/java/me/itzg/helpers/modrinth/ModrinthApiClient.java index 224a7c0e..f455bd04 100644 --- a/src/main/java/me/itzg/helpers/modrinth/ModrinthApiClient.java +++ b/src/main/java/me/itzg/helpers/modrinth/ModrinthApiClient.java @@ -7,6 +7,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -181,31 +182,56 @@ public Mono downloadMrPack(VersionFile versionFile) { public Mono> getVersionsForProject(String projectIdOrSlug, @Nullable Loader loader, String gameVersion ) { - return sharedFetch.fetch( - uriBuilder.resolve("/v2/project/{id|slug}/version", - queryParameters() - .addStringArray("loaders", expandCompatibleLoaders(loader)) - .addStringArray("game_versions", gameVersion), - projectIdOrSlug + return getJustVersionsForProject(projectIdOrSlug, gameVersion, + loader != null ? Collections.singletonList(loader.toString()) : null, + false + ) + .switchIfEmpty( + getJustVersionsForProject(projectIdOrSlug, gameVersion, + expandCompatibleLoaders(loader), + true ) ) - .toObjectList(Version.class) - .assemble() - .flatMap(versions -> - versions.isEmpty() ? - getProject(projectIdOrSlug) - .flatMap(project -> Mono.error(new NoFilesAvailableException(project, loader, gameVersion))) - : Mono.just(versions) - ); + .switchIfEmpty( + getProject(projectIdOrSlug) + .flatMap(project -> Mono.error(new NoFilesAvailableException(project, loader, gameVersion))) + ); + } + + /** + * @return the non-empty list of versions or an empty mono + */ + private Mono> getJustVersionsForProject( + String projectIdOrSlug, String gameVersion, + Collection loaderNames, + boolean skipEmptyLoaders + ) { + return + skipEmptyLoaders && (loaderNames == null || loaderNames.isEmpty()) ? + Mono.empty() : + sharedFetch.fetch( + uriBuilder.resolve("/v2/project/{id|slug}/version", + queryParameters() + .addStringArray("loaders", loaderNames) + .addStringArray("game_versions", gameVersion), + projectIdOrSlug + ) + ) + .toObjectList(Version.class) + .assemble() + .filter(versions -> !versions.isEmpty()); } + /** + * @param loader the target loader + * @return a list of the compatible loaders + */ private List expandCompatibleLoaders(@Nullable Loader loader) { if (loader == null) { return null; } final ArrayList expanded = new ArrayList<>(); - expanded.add(loader.toString()); Loader compatibleWith = loader; while ((compatibleWith = compatibleWith.getCompatibleWith()) != null) { expanded.add(compatibleWith.toString()); diff --git a/src/test/java/me/itzg/helpers/modrinth/ModrinthApiClientTest.java b/src/test/java/me/itzg/helpers/modrinth/ModrinthApiClientTest.java index f0c9177c..869928ea 100644 --- a/src/test/java/me/itzg/helpers/modrinth/ModrinthApiClientTest.java +++ b/src/test/java/me/itzg/helpers/modrinth/ModrinthApiClientTest.java @@ -45,33 +45,75 @@ void getBulkProjectsWithUnknownServerSide(WireMockRuntimeInfo wmInfo) { } } - @Test - void getVersionsForProject(WireMockRuntimeInfo wmInfo) { - - stubFor(get(urlPathMatching("/v2/project/(BITzwT7B|clickvillagers)/version")) - .withQueryParam("loaders", equalTo("[\"purpur\",\"paper\",\"spigot\"]")) - .withQueryParam("game_versions", equalTo("[\"1.20.1\"]")) - .willReturn(aResponse() - .withHeader("Content-Type", "application/json") - .withBodyFile("modrinth/project-BITzwT7B-version-resp.json") - ) - ); + @Nested + class getVersionsForProject { + @Test + void exactLoader(WireMockRuntimeInfo wmInfo) { - try (ModrinthApiClient client = new ModrinthApiClient(wmInfo.getHttpBaseUrl(), "modrinth", Options.builder().build())) { - final List result = client.getVersionsForProject("BITzwT7B", Loader.purpur, "1.20.1") - .block(); + stubFor(get(urlPathMatching("/v2/project/(BITzwT7B|clickvillagers)/version")) + .withQueryParam("loaders", equalTo("[\"purpur\"]")) + .withQueryParam("game_versions", equalTo("[\"1.20.1\"]")) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json") + .withBodyFile("modrinth/project-BITzwT7B-version-resp.json") + ) + ); + + try (ModrinthApiClient client = new ModrinthApiClient(wmInfo.getHttpBaseUrl(), "modrinth", Options.builder().build())) { + final List result = client.getVersionsForProject("BITzwT7B", Loader.purpur, "1.20.1") + .block(); + + assertThat(result) + .hasSize(3) + .extracting(Version::getId) + .containsExactly( + "O9nndrTu", + "DfUyEmsH", + "oUJMLDhz" + ); + } + } + + @Test + void fallbackToCompatibleLoader(WireMockRuntimeInfo wmInfo) { + stubFor(get(urlPathMatching("/v2/project/entityculling/version")) + .withQueryParam("loaders", equalTo("[\"neoforge\"]")) + .withQueryParam("game_versions", equalTo("[\"1.12.2\"]")) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json") + .withBodyFile("modrinth/versions-entityculling-neoforge-not-forge.json") + ) + ); + stubFor(get(urlPathMatching("/v2/project/entityculling/version")) + .withQueryParam("loaders", equalTo("[\"forge\"]")) + .withQueryParam("game_versions", equalTo("[\"1.12.2\"]")) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json") + .withBodyFile("modrinth/versions-entityculling-forge.json") + ) + ); - assertThat(result) - .hasSize(3) - .extracting(Version::getId) - .containsExactly( - "O9nndrTu", - "DfUyEmsH", - "oUJMLDhz" - ); + try (ModrinthApiClient client = new ModrinthApiClient(wmInfo.getHttpBaseUrl(), "modrinth", + Options.builder().build() + )) { + final List result = client.getVersionsForProject( + "entityculling", + Loader.neoforge, + "1.12.2" + ) + .block(); + + assertThat(result) + .extracting(Version::getId) + .containsExactly( + "knltv3Vh" + ); + } } } + + @Nested class resolveProjectVersion { @@ -142,7 +184,7 @@ void noFiles(WireMockRuntimeInfo wmInfo) { @Test void noApplicableVersionsOfType(WireMockRuntimeInfo wmInfo) { stubFor(get(urlPathMatching("/v2/project/(3wmN97b8|multiverse-core)/version")) - .withQueryParam("loaders", equalTo("[\"purpur\",\"paper\",\"spigot\"]")) + .withQueryParam("loaders", equalTo("[\"purpur\"]")) .withQueryParam("game_versions", equalTo("[\"1.21.1\"]")) .willReturn(aResponse() .withHeader("Content-Type", "application/json") diff --git a/src/test/java/me/itzg/helpers/modrinth/ModrinthCommandTest.java b/src/test/java/me/itzg/helpers/modrinth/ModrinthCommandTest.java index 48ea9a08..8e495f4b 100644 --- a/src/test/java/me/itzg/helpers/modrinth/ModrinthCommandTest.java +++ b/src/test/java/me/itzg/helpers/modrinth/ModrinthCommandTest.java @@ -14,8 +14,8 @@ import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer; import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder; -import java.nio.file.Path; import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; import java.util.function.Consumer; import me.itzg.helpers.LatchingExecutionExceptionHandler; @@ -167,7 +167,8 @@ void failsWhenNoDependenciesForModLoader(@TempDir Path tempDir) throws JsonProce .put("project_id", requiredDepProjectId) .put("dependency_type", "required"); }); - stubVersionRequestEmptyResponse(requiredDepProjectId); + stubVersionRequestEmptyResponse(requiredDepProjectId, "paper"); + stubVersionRequestEmptyResponse(requiredDepProjectId, "spigot"); stubGetProject(requiredDepProjectId, new Project().setProjectType(ProjectType.resourcepack)); stubDownload(); @@ -462,7 +463,7 @@ private void stubVersionRequest(String projectId, String versionId, Consumer