diff --git a/build.gradle.kts b/build.gradle.kts index 81abda5..f321dda 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -85,12 +85,12 @@ tasks.clean { tasks { withType { - useJUnitPlatform() - testLogging { - events("standardOut", "standardError", "skipped", "failed") + useJUnitPlatform() + testLogging { + events("standardOut", "standardError", "skipped", "failed") + } + jvmArgs("-XX:+EnableDynamicAgentLoading", "-Duser.timezone=UTC") } - jvmArgs("-XX:+EnableDynamicAgentLoading") - } } spotless { @@ -103,6 +103,7 @@ spotless { val dropWizardVersion = "4.0.7" val feignVersion = "13.6" +val gsonVersion = "2.12.1" val jaxbVersion = "4.0.5" val junitVersion = "5.13.4" val mockitoVersion = "5.19.0" @@ -111,14 +112,13 @@ val tripleaVersion = "2.7.15281" dependencies { implementation("at.favre.lib:bcrypt:0.10.2") implementation("be.tomcools:dropwizard-websocket-jsr356-bundle:4.0.0") + implementation("com.google.code.gson:gson:$gsonVersion") implementation("com.sun.mail:jakarta.mail:2.0.2") implementation("com.sun.xml.bind:jaxb-core:$jaxbVersion") implementation("com.sun.xml.bind:jaxb-impl:$jaxbVersion") implementation("io.dropwizard:dropwizard-auth:$dropWizardVersion") implementation("io.dropwizard:dropwizard-core:$dropWizardVersion") implementation("io.dropwizard:dropwizard-jdbi3:$dropWizardVersion") - implementation("io.github.openfeign:feign-core:$feignVersion") - implementation("io.github.openfeign:feign-gson:$feignVersion") implementation("javax.activation:activation:1.1.1") implementation("javax.servlet:servlet-api:2.5") implementation("javax.xml.bind:jaxb-api:2.3.1") @@ -128,12 +128,15 @@ dependencies { implementation("org.jdbi:jdbi3-sqlobject:3.49.5") implementation("org.snakeyaml:snakeyaml-engine:2.10") implementation("triplea:domain-data:$tripleaVersion") - implementation("triplea:feign-common:$tripleaVersion") implementation("triplea:java-extras:$tripleaVersion") implementation("triplea:lobby-client:$tripleaVersion") implementation("triplea:websocket-client:$tripleaVersion") runtimeOnly("org.postgresql:postgresql:42.7.7") + testImplementation("io.github.openfeign:feign-core:$feignVersion") + testImplementation("io.github.openfeign:feign-gson:$feignVersion") + testImplementation("triplea:feign-common:$tripleaVersion") + testImplementation("com.github.database-rider:rider-junit5:1.44.0") testImplementation("com.github.npathai:hamcrest-optional:2.0.0") testImplementation("com.sun.mail:jakarta.mail:2.0.2") diff --git a/src/main/java/org/triplea/http/client/github/GithubApiClient.java b/src/main/java/org/triplea/http/client/github/GithubApiClient.java new file mode 100644 index 0000000..b103646 --- /dev/null +++ b/src/main/java/org/triplea/http/client/github/GithubApiClient.java @@ -0,0 +1,170 @@ +package org.triplea.http.client.github; + +import static org.slf4j.LoggerFactory.getLogger; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import java.io.IOException; +import java.lang.reflect.Type; +import java.net.URI; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import javax.annotation.Nonnull; +import org.slf4j.Logger; + +/** Can be used to interact with github's webservice API. */ +class GithubApiClient implements GithubClient { + + private static final Logger log = getLogger(GithubApiClient.class); + + private static final String LIST_REPOS_PATH = "/orgs/%s/repos?per_page=100&page=%d"; + private static final String BRANCHES_PATH = "/repos/%s/%s/branches/%s"; + private static final String ISSUES_PATH = "/repos/%s/%s/issues"; + private static final String LATEST_RELEASE_PATH = "/repos/%s/%s/releases/latest"; + + private static final Gson GSON = new Gson(); + + private final HttpClient httpClient; + private final URI baseUri; + private final String authToken; + private final String org; + + /** + * @param baseUri The base URI for the Github webservice API. + * @param authToken Auth token sent to Github for webservice calls. Can be empty, but if specified + * must be valid (no auth token still works, but rate limits will be more restrictive). + * @param org Name of the Github org to be queried. + */ + GithubApiClient(@Nonnull URI baseUri, String authToken, @Nonnull String org) { + this.httpClient = HttpClient.newHttpClient(); + this.baseUri = baseUri; + this.authToken = authToken; + this.org = org; + } + + /** + * Returns a listing of the repositories within a github organization. This call handles paging, + * it returns a complete list and may perform multiple calls to Github. + * + *

Example equivalent cUrl call: + * + *

curl https://api.github.com/orgs/triplea-maps/repos + */ + @Override + public Collection listRepositories() { + final Collection allRepos = new HashSet<>(); + int pageNumber = 1; + Collection repos = listRepositories(pageNumber); + while (!repos.isEmpty()) { + pageNumber++; + allRepos.addAll(repos); + repos = listRepositories(pageNumber); + } + return allRepos; + } + + private Collection listRepositories(int pageNumber) { + String path = String.format(LIST_REPOS_PATH, encodePath(org), pageNumber); + Type listType = new TypeToken>() {}.getType(); + return GSON.fromJson(sendGet(path), listType); + } + + @Override + public Instant getLatestCommitDate(String repoName, String branchName) { + String path = + String.format(BRANCHES_PATH, encodePath(org), encodePath(repoName), encodePath(branchName)); + return GSON.fromJson(sendGet(path), BranchInfoResponse.class).getLastCommitDate(); + } + + @Override + public BranchInfoResponse fetchBranchInfo(String repo, String branch) { + String path = + String.format(BRANCHES_PATH, encodePath(org), encodePath(repo), encodePath(branch)); + return GSON.fromJson(sendGet(path), BranchInfoResponse.class); + } + + @Override + public Optional fetchLatestVersion(String repo) { + String path = String.format(LATEST_RELEASE_PATH, encodePath(org), encodePath(repo)); + try { + return Optional.of(GSON.fromJson(sendGet(path), LatestReleaseResponse.class).getTagName()); + } catch (RuntimeException e) { + log.warn("No data received from server for latest engine version", e); + return Optional.empty(); + } + } + + @Override + public CreateIssueResponse newIssue(String repo, CreateIssueRequest createIssueRequest) { + String path = String.format(ISSUES_PATH, encodePath(org), encodePath(repo)); + return GSON.fromJson(sendPost(path, createIssueRequest), CreateIssueResponse.class); + } + + private static String encodePath(String segment) { + return URLEncoder.encode(segment, StandardCharsets.UTF_8).replace("+", "%20"); + } + + private String sendGet(String path) { + HttpRequest.Builder builder = HttpRequest.newBuilder().uri(baseUri.resolve(path)).GET(); + if (authToken != null && !authToken.isEmpty()) { + builder.header("Authorization", "token " + authToken); + } + try { + HttpResponse response = + httpClient.send(builder.build(), HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() < 200 || response.statusCode() >= 300) { + throw new RuntimeException( + "GitHub API request to " + + path + + " failed with status " + + response.statusCode() + + ": " + + response.body()); + } + return response.body(); + } catch (IOException e) { + throw new RuntimeException("GitHub API request failed: " + path, e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("GitHub API request interrupted: " + path, e); + } + } + + private String sendPost(String path, Object body) { + HttpRequest.Builder builder = + HttpRequest.newBuilder() + .uri(baseUri.resolve(path)) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(GSON.toJson(body))); + if (authToken != null && !authToken.isEmpty()) { + builder.header("Authorization", "token " + authToken); + } + try { + HttpResponse response = + httpClient.send(builder.build(), HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() < 200 || response.statusCode() >= 300) { + throw new RuntimeException( + "GitHub API request to " + + path + + " failed with status " + + response.statusCode() + + ": " + + response.body()); + } + return response.body(); + } catch (IOException e) { + throw new RuntimeException("GitHub API request failed: " + path, e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("GitHub API request interrupted: " + path, e); + } + } +} diff --git a/src/main/java/org/triplea/http/client/github/GithubClient.java b/src/main/java/org/triplea/http/client/github/GithubClient.java index 38f2211..97378cf 100644 --- a/src/main/java/org/triplea/http/client/github/GithubClient.java +++ b/src/main/java/org/triplea/http/client/github/GithubClient.java @@ -1,144 +1,28 @@ package org.triplea.http.client.github; -import static org.slf4j.LoggerFactory.getLogger; - -import com.google.common.base.Strings; -import feign.FeignException; -import feign.Param; -import feign.QueryMap; -import feign.RequestLine; import java.net.URI; import java.time.Instant; import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; import java.util.Optional; import javax.annotation.Nonnull; -import lombok.Builder; -import org.slf4j.Logger; -import org.triplea.http.client.HttpClient; - -/** Can be used to interact with github's webservice API. */ -public class GithubClient { - - private static final Logger log = getLogger(GithubClient.class); - - /** - * Inner implementation for the HTTP client, Feign interface, we can instantiate this with Feign. - */ - @SuppressWarnings("InterfaceNeverImplemented") - interface GithubApiFeignClient { - @RequestLine("POST /repos/{org}/{repo}/issues") - CreateIssueResponse newIssue( - @Param("org") String org, - @Param("repo") String repo, - CreateIssueRequest createIssueRequest); - - @RequestLine("GET /orgs/{org}/repos") - List listRepos( - @QueryMap Map queryParams, @Param("org") String org); - @RequestLine("GET /repos/{org}/{repo}/branches/{branch}") - BranchInfoResponse getBranchInfo( - @Param("org") String org, @Param("repo") String repo, @Param("branch") String branch); +public interface GithubClient { - @RequestLine("GET /repos/{org}/{repo}/releases/latest") - LatestReleaseResponse getLatestRelease(@Param("org") String org, @Param("repo") String repo); + static GithubClient build(String authToken, @Nonnull String org) { + return new GithubApiClient(URI.create("https://api.github.com"), authToken, org); } - private static final String GITHUB_HOST = "https://api.github.com"; - - private final GithubApiFeignClient githubApiFeignClient; - private final String org; - - /** - * @param authToken Auth token that will be sent to Github for webservice calls. Can be empty, but - * if specified must be valid (no auth token still works, but rate limits will be more - * restrictive). - * @param org Name of the github org to be queried. - */ - @Builder - public GithubClient(String authToken, @Nonnull String org) { - this(URI.create(GITHUB_HOST), authToken, org); + static GithubClient build(@Nonnull URI baseUri, String authToken, @Nonnull String org) { + return new GithubApiClient(baseUri, authToken, org); } - public GithubClient(@Nonnull URI uri, String authToken, @Nonnull String org) { - githubApiFeignClient = - HttpClient.newClient( - GithubApiFeignClient.class, - uri, - Strings.isNullOrEmpty(authToken) - ? Map.of() - : Map.of("Authorization", "token " + authToken)); - this.org = org; - } + Collection listRepositories(); - /** - * Returns a listing of the repositories within a github organization. This call handles paging, - * it returns a complete list and may perform multiple calls to Github. - * - *

Example equivalent cUrl call: - * - *

curl https://api.github.com/orgs/triplea-maps/repos - */ - public Collection listRepositories() { - final Collection allRepos = new HashSet<>(); - int pageNumber = 1; - Collection repos = listRepositories(pageNumber); - while (!repos.isEmpty()) { - pageNumber++; - allRepos.addAll(repos); - repos = listRepositories(pageNumber); - } - return allRepos; - } + Instant getLatestCommitDate(String repoName, String branchName); - private Collection listRepositories(int pageNumber) { - final Map queryParams = new HashMap<>(); - queryParams.put("per_page", "100"); - queryParams.put("page", String.valueOf(pageNumber)); + BranchInfoResponse fetchBranchInfo(String repo, String branch); - return githubApiFeignClient.listRepos(queryParams, org); - } - - public Instant getLatestCommitDate(String repoName, String branchName) { - return githubApiFeignClient.getBranchInfo(org, repoName, branchName).getLastCommitDate(); - } - - /** - * Invokes github web-API to create a github issue with the provided parameter data. - * - * @param createIssueRequest Upload data for creating the body and title of the github issue. - * @return Response from server containing link to the newly created issue. - * @throws feign.FeignException thrown on error or if non-2xx response is received - */ - public CreateIssueResponse newIssue(String repo, CreateIssueRequest createIssueRequest) { - return githubApiFeignClient.newIssue(org, repo, createIssueRequest); - } - - /** - * Fetches details of a specific branch on a specific repo. Useful for retrieving info about the - * last commit to the repo. Note, the repo listing contains a 'last_push' date, but this method - * should be used instead as the last_push date on a repo can be for any branch (even PRs). - * - *

Example equivalent cUrl: - * https://api.github.com/repos/triplea-maps/star_wars_galactic_war/branches/master - * - * @param branch Which branch to be queried. - * @return Payload response object representing the response from Github's web API. - */ - public BranchInfoResponse fetchBranchInfo(String repo, String branch) { - return githubApiFeignClient.getBranchInfo(org, repo, branch); - } + Optional fetchLatestVersion(String repo); - public Optional fetchLatestVersion(String repo) { - try { - return Optional.of(githubApiFeignClient.getLatestRelease(org, repo).getTagName()); - } catch (final FeignException e) { - log.warn("No data received from server for latest engine version", e); - return Optional.empty(); - } - } + CreateIssueResponse newIssue(String repo, CreateIssueRequest createIssueRequest); } diff --git a/src/main/java/org/triplea/server/SupportServerConfig.java b/src/main/java/org/triplea/server/SupportServerConfig.java index 2e24f60..b46cc04 100644 --- a/src/main/java/org/triplea/server/SupportServerConfig.java +++ b/src/main/java/org/triplea/server/SupportServerConfig.java @@ -50,10 +50,10 @@ public class SupportServerConfig extends Configuration { private boolean logSqlStatements; public GithubClient githubClientMaps() { - return GithubClient.builder().authToken(githubApiToken).org(githubMapsOrgName).build(); + return GithubClient.build(githubApiToken, githubMapsOrgName); } public GithubClient githubClientErrorReporting() { - return GithubClient.builder().authToken(githubApiToken).org(tripleaOrgName).build(); + return GithubClient.build(githubApiToken, tripleaOrgName); } } diff --git a/src/test/java/org/triplea/http/client/github/GithubClientTest.java b/src/test/java/org/triplea/http/client/github/GithubClientTest.java index 38e41ed..cedcf14 100644 --- a/src/test/java/org/triplea/http/client/github/GithubClientTest.java +++ b/src/test/java/org/triplea/http/client/github/GithubClientTest.java @@ -38,7 +38,7 @@ void repoListing(@WiremockResolver.Wiremock final WireMockServer server) { stubRepoListingResponse(3, server, "[]"); final Collection repos = - new GithubClient(URI.create(server.baseUrl()), "", "example-org").listRepositories(); + GithubClient.build(URI.create(server.baseUrl()), "", "example-org").listRepositories(); assertThat(repos, hasSize(3)); @@ -73,7 +73,7 @@ void branchListingResponseFetchLastCommitDate( .willReturn(aResponse().withStatus(200).withBody(exampleResponse))); final BranchInfoResponse branchInfoResponse = - new GithubClient(URI.create(server.baseUrl()), "test-token", "example-org") + GithubClient.build(URI.create(server.baseUrl()), "test-token", "example-org") .fetchBranchInfo("map-repo", "main"); final Instant expectedLastCommitDate = @@ -92,7 +92,7 @@ void getLatestRelease(@WiremockResolver.Wiremock final WireMockServer server) { .willReturn(aResponse().withStatus(200).withBody(exampleResponse))); final String latestVersion = - new GithubClient(URI.create(server.baseUrl()), "example-org", "test-token") + GithubClient.build(URI.create(server.baseUrl()), "example-org", "test-token") .fetchLatestVersion("map-repo") .orElseThrow(); diff --git a/src/testInteg/java/org/triplea/maps/indexing/IndexingEndToEndTest.java b/src/testInteg/java/org/triplea/maps/indexing/IndexingEndToEndTest.java index 445feb2..b4c645e 100644 --- a/src/testInteg/java/org/triplea/maps/indexing/IndexingEndToEndTest.java +++ b/src/testInteg/java/org/triplea/maps/indexing/IndexingEndToEndTest.java @@ -27,7 +27,7 @@ public class IndexingEndToEndTest { */ @Test void verifyIndexing() { - var githubClient = new GithubClient("", "triplea-maps"); + var githubClient = GithubClient.build("", "triplea-maps"); MapIndexDao dao = new MapIndexDao(jdbi); MapIndexingTaskRunner taskRunner = new MapIndexingTaskRunner(dao, githubClient, MapIndexer.build(githubClient), 0); diff --git a/src/testInteg/java/org/triplea/maps/listing/MapListingCharacterizationTest.java b/src/testInteg/java/org/triplea/maps/listing/MapListingCharacterizationTest.java new file mode 100644 index 0000000..046050b --- /dev/null +++ b/src/testInteg/java/org/triplea/maps/listing/MapListingCharacterizationTest.java @@ -0,0 +1,294 @@ +package org.triplea.maps.listing; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.github.database.rider.core.api.dataset.DataSet; +import com.github.database.rider.junit5.DBUnitExtension; +import java.net.URI; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.triplea.http.client.ClientIdentifiers; +import org.triplea.http.client.maps.listing.MapDownloadItem; +import org.triplea.http.client.maps.listing.MapsClient; +import org.triplea.maps.IntegTestExtension; + +/** + * Characterization test for the maps listing HTTP endpoint. Each entry in the fixture mirrors real + * production data (first 20 maps alphabetically as of April 2026), giving us a stable reference + * point. If the server's response ever diverges from this baseline, the test will catch it. + * + *

The fixture {@code map_index_prod_sample.yml} was hand-crafted from + * https://prod.triplea-game.org/support/maps/listing. When the fixture needs to be refreshed, fetch + * the endpoint again, pick the first 20 maps, and update both the YAML and the expected list below. + */ +@DataSet(value = "map_index_prod_sample.yml", useSequenceFiltering = false) +@ExtendWith(IntegTestExtension.class) +@ExtendWith(DBUnitExtension.class) +class MapListingCharacterizationTest { + + private final MapsClient mapsClient; + + MapListingCharacterizationTest(final URI serverUri) { + mapsClient = + MapsClient.newClient( + serverUri, + ClientIdentifiers.builder() + .applicationVersion("test") + .systemId("test") + .apiKey("") + .build()); + } + + @Test + void mapsListingMatchesProdSample() { + final List results = mapsClient.fetchMapListing(); + + assertThat(results) + .hasSize(20) + .containsExactlyInAnyOrder( + // @formatter:off + expectedMap( + "1914-cow-empires", + "https://github.com/triplea-maps/1914-cow-empires/archive/refs/heads/master.zip", + "https://github.com/triplea-maps/1914-cow-empires/blob/master/preview.png?raw=true", + 4287712L, + 1631771026000L, + "
Version 1.0, last update 2014.12.07 for engine 1.8.0.3\n
Game done by" + + " RogerCooper\n
Suggestions to RogerCoop@aol.com\n
\n
a.)" + + " Content:\n
1 Game\n
1914-COW-Empires\n
\n
b.) Rough" + + " overview:\n
A WW1 scenario using the Napoleonic Empires map and" + + " military/economic data from the Correlates of War database.\n
\n
c.)" + + " General tips:\n
Read game notes before playing.\n
\n"), + expectedMap( + "1939ARevised", + "https://github.com/triplea-maps/1939ARevised/archive/refs/heads/master.zip", + "https://github.com/triplea-maps/1939ARevised/blob/master/preview.png?raw=true", + 1301896L, + 1669561222000L, + "No description available for: https://github.com/triplea-maps/1939ARevised." + + " Contact the map author and request they add a 'description.html' file"), + expectedMap( + "1939_Pact_of_Steel_Big_World", + "https://github.com/triplea-maps/1939_Pact_of_Steel_Big_World/archive/refs/heads/master.zip", + "https://github.com/triplea-maps/1939_Pact_of_Steel_Big_World/blob/master/preview.png?raw=true", + 803364L, + 1662315151000L, + "1939 on the Big World Map with 7 powers"), + expectedMap( + "1941", + "https://github.com/triplea-maps/1941/archive/refs/heads/master.zip", + "https://github.com/triplea-maps/1941/blob/master/preview.png?raw=true", + 8553163L, + 1631771018000L, + "No description available for: https://github.com/triplea-maps/1941. Contact the" + + " map author and request they add a 'description.html' file"), + expectedMap( + "1941_global_command_decision", + "https://github.com/triplea-maps/1941_global_command_decision/archive/refs/heads/master.zip", + "https://github.com/triplea-maps/1941_global_command_decision/blob/master/preview.png?raw=true", + 94976745L, + 1773475665000L, + "
\n
            1941" + + " GLOBAL COMMAND DECISION\n
by Black Elk (graphics/testing) & TheDog" + + " (code/testing)\n
\n
Introduction\n
It is late 1941; Germany" + + " has launched a massive invasion against the USSR and Japan is ready to strike" + + " at the USA in the Pacific.\n
\n
FEATURES\n
\u2022 Massive map" + + " 16816x8085px=136Mpx, one of the biggest TripleA maps. Unit size 54px high," + + " this is with 4K screens in mind.\n
\u2022 Nearly 800 land & sea locations," + + " with terrain effects, Desert, Forest, Marsh, Tundra, Urban, impassable" + + " mountains, canals\n
\n
\u2022 Stacking limit is enforced to help the" + + " AI. Only 10 Air & 10 Sea units are allowed per Sea Zone/Territory. Only 20" + + " Land per Territory.\n
\u2022 AI friendly, no Objectives, or" + + " Politics/Technology phases, both are scripted. 79 territories marked to" + + " guide the AI for better play.\n
\n
\u2022 Sea Zones are worth" + + " 1-2pu/turn and for Britain and Japan are vital for their" + + " survival\n
\u2022 All land territories bordering Sea Zones can be" + + " blockaded. There are no convoy centres/route/zones.\n
\n
\u2022 Four" + + " Lend-Lease-Depots, 3 in USSR Archangelsk, Persian Corridor to Baku," + + " Vladivostok and Burma Road to Yunnan China\n
\u2022 Four types of HQ" + + " Commands, Air, Army, Fleet and Submarine, nations can have between none and" + + " five types of a HQ. \n
    These represent Rommel," + + " Montgomery, Patton, Cunningham, Donitz, Nimitz, US 8th Air Force HQs etc." + + " and their staff.\n
\u2022 Four different type of \u201cFactory\u201d\n
\n
\u2022" + + " As a minimum requires TripleA 2.7.14848+\n
\n
For the latest download" + + " and discussion, copy and paste the link below\n
\n
https://forums.triplea-game.org/topic/3326/1941-global-command-decision\n
\n
"), + expectedMap( + "2013Conflict", + "https://github.com/triplea-maps/2013Conflict/archive/refs/heads/master.zip", + "https://github.com/triplea-maps/2013Conflict/blob/master/preview.png?raw=true", + 9717791L, + 1689426554000L, + "Hypothetical World War 3 in 2013."), + expectedMap( + "2020", + "https://github.com/triplea-maps/2020/archive/refs/heads/master.zip", + "https://github.com/triplea-maps/2020/blob/master/preview.png?raw=true", + 2670316L, + 1634771469000L, + "No description available for: https://github.com/triplea-maps/2020. Contact the" + + " map author and request they add a 'description.html' file"), + expectedMap( + "270BC_Tertiered", + "https://github.com/triplea-maps/270BC_Variants/archive/refs/heads/master.zip", + "https://github.com/triplea-maps/270BC_Variants/blob/master/preview.png?raw=true", + 8052661L, + 1645052831000L, + "No description available for: https://github.com/triplea-maps/270BC_Variants." + + " Contact the map author and request they add a 'description.html' file"), + expectedMap( + "270bc", + "https://github.com/triplea-maps/270bc/archive/refs/heads/master.zip", + "https://github.com/triplea-maps/270bc/blob/master/preview.png?raw=true", + 8087243L, + 1631771025000L, + "
By Doctor Che\n
Updated by Redrum\n
\n
Ancient Era Map of the" + + " Mediterranean\n
\n
Make yourself an empire around the Mediterranean" + + " Sea (the known world),\n
in the era when Hellenes, Romans, and" + + " Phoenicians ruled. Choose from a arsenal of\n
legionaires, hoplites," + + " onagers, cataphracts, triremes, war elephants, and many more.\n
Territory" + + " names are based on cities at the time give or take 500 years.\n
\n"), + expectedMap( + "270bc_wars", + "https://github.com/triplea-maps/270bc_wars/archive/refs/heads/master.zip", + "https://github.com/triplea-maps/270bc_wars/blob/master/preview.png?raw=true", + 24085728L, + 1631771018000L, + "
By Cernel.\n
Original 270BC map and game by Doctor Che.\n
Original 270BC" + + " game modified by Veqryn, Cernel, redrum.\n
Original 270BC map details" + + " and decorations, \"TripleA Ancient\" image, units images, territory names" + + " images, territory values images, dice images by Hepps.\n
\n
Mistress of" + + " Italy, Rome, looking over the sea, discovers herself engaged in the" + + " inexorable struggle for survival against the might of Carthage, encroaching" + + " on Africa and Spain, and the islands nearby, as far as the columns of" + + " Hercules, the end of the World.\n
In Greece, the royal hegemony of" + + " Macedonia is bitterly challenged by a warring coalition of free cities and" + + " leagues, from Sicily, in the west, to Asia, in the" + + " east.\n
Across what remains of what was taken by Alexander, what had" + + " begun as a spate of civil conflicts conflated into a dynastic strife between" + + " the new realms of Egypt and Syria.\n
Beyond the Hellenistic world, the" + + " ascendancy of Parthia looms on the horizon, eager to champion the" + + " resurgence of Persian supremacy to her former greatness.\n
Meanwhile," + + " hailing from unknown lands, the tribes of Numidia are swarming out of the" + + " wilds of Libya, their savage want for slaves and violent desire for booty," + + " unquenchable.\n
'Tis a clash of civilizations: for either side, the other" + + " side is to be eliminated.\n
\n
Special thanks to Navalland, guerrilla_J" + + " and Aposteles for play-testing.\n"), + expectedMap( + "300BC", + "https://github.com/triplea-maps/300BC/archive/refs/heads/master.zip", + "https://github.com/triplea-maps/300BC/blob/master/preview.png?raw=true", + 1903629L, + 1673105855000L, + "The Mediterranean in 300BC."), + expectedMap( + "41_Oztea", + "https://github.com/triplea-maps/41_Oztea_Variants/archive/refs/heads/master.zip", + "https://github.com/triplea-maps/41_Oztea_Variants/blob/master/preview.png?raw=true", + 26833930L, + 1626574828000L, + "No description available for: https://github.com/triplea-maps/41_Oztea_Variants." + + " Contact the map author and request they add a 'description.html' file"), + expectedMap( + "6Kingdoms", + "https://github.com/triplea-maps/6Kingdoms/archive/refs/heads/master.zip", + "https://github.com/triplea-maps/6Kingdoms/blob/master/preview.png?raw=true", + 2541514L, + 1701900576000L, + "A war between two alliances of fantasy kingdoms."), + expectedMap( + "AA50-41-Maintenance", + "https://github.com/triplea-maps/AA50-41-Maintenance/archive/refs/heads/master.zip", + "https://github.com/triplea-maps/AA50-41-Maintenance/blob/master/preview.png?raw=true", + 9270272L, + 1707059237000L, + "World War 2 Version 3 with Maintenance costs."), + expectedMap( + "AA50-BuildCaps", + "https://github.com/triplea-maps/AA50-BuildCaps/archive/refs/heads/master.zip", + "https://github.com/triplea-maps/AA50-BuildCaps/blob/master/preview.png?raw=true", + 9458131L, + 1699320131000L, + "AA50/World World V3/Anniversary edition with production capped at what came in the" + + " box."), + expectedMap( + "AA50-Europe", + "https://github.com/triplea-maps/AA50-Europe/archive/refs/heads/master.zip", + "https://github.com/triplea-maps/AA50-Europe/blob/master/preview.png?raw=true", + 9611525L, + 1747346744000L, + "World Word 2 in Europe on the AA50 map"), + expectedMap( + "AA50-realistic", + "https://github.com/triplea-maps/AA50-realistic/archive/refs/heads/master.zip", + "https://github.com/triplea-maps/AA50-realistic/blob/master/preview.png?raw=true", + 10492327L, + 1720105302000L, + "World War II v3, 1942 with an modified setup."), + expectedMap( + "AAC-BuildCaps", + "https://github.com/triplea-maps/AAC-BuildCaps/archive/refs/heads/master.zip", + "https://github.com/triplea-maps/AAC-BuildCaps/blob/master/preview.png?raw=true", + 8788099L, + 1642552934000L, + "No description available for: https://github.com/triplea-maps/AAC-BuildCaps." + + " Contact the map author and request they add a 'description.html' file"), + expectedMap( + "AAR-BuildCaps", + "https://github.com/triplea-maps/AAR-BuildCaps/archive/refs/heads/master.zip", + "https://github.com/triplea-maps/AAR-BuildCaps/blob/master/preview.png?raw=true", + 3026055L, + 1735505177000L, + "World War II Revised with caps on how many units of each type that can be" + + " built.\n"), + expectedMap( + "AAZ", + "https://github.com/triplea-maps/AAZ/archive/refs/heads/master.zip", + "https://github.com/triplea-maps/AAZ/blob/master/preview.png?raw=true", + 1908091L, + 1720406059000L, + "Axis & Allies & Zombies\n\t\t\t\t\t
\n\t\t\t\t\t
1. No strategic" + + " bombing.\n\t\t\t\t\t
2. No AA Guns / Mechanized Infantry /" + + " Cruisers.\n\t\t\t\t\t
3. No shore" + + " bombardment.\n\t\t\t\t\t
4. Cannot build new" + + " factories.\n\t\t\t\t\t
5. Recruitment centers are like factories but only" + + " build infantry\n\t\t\t\t\t
6 For balance, 3 Zombies start in" + + " Germany\n\t\t\t\t\t
Victory Condition:\n\t\t\t\t\t
Take 1" + + " enemy capital and hold all your own\n\t\t\t\t\t
Zombies win if their" + + " income reaches 25\n
Set Game/User Notifications/Show" + + " Trigger/Condition Change Roll Failure to" + + " off\n\t\t\t\t\t\t
Based upon Axis & Allies &" + + " Zombies\n\t\t\t\t\t\t
Player nations are not allowed to invade neutrals," + + " but can move in once zombies have taken" + + " over.\n\t\t\t\t\t\t
Note that in this weIrd alternate history scenario," + + " the Zombies are restless dead (Draugr) and not contagious as in most modern" + + " zombie fiction. They are capable of using weaponry, but too impaired to" + + " handle sophisticated weaponry or coordinate their" + + " actions.\n\t\t\t\t\t\t
The dead were awakened uncontrallably by Nazi" + + " occultists\n\t\t\t\t\t\t
The big change from the original game is that" + + " Zombies are not spawned by killing infantry, as there was no way to handle" + + " that in TripleA. Instead every area has 1% chance per turn of spawning a" + + " horde of 10. In addition each area has a 10% chance each turn of spawning" + + " 1.\n\t\t\t\t\t
Created for TripleA by Roger Cooper") + // @formatter:on + ); + } + + private static MapDownloadItem expectedMap( + final String mapName, + final String downloadUrl, + final String previewImageUrl, + final long downloadSizeInBytes, + final long lastCommitDateEpochMilli, + final String description) { + return MapDownloadItem.builder() + .mapName(mapName) + .downloadUrl(downloadUrl) + .previewImageUrl(previewImageUrl) + .downloadSizeInBytes(downloadSizeInBytes) + .lastCommitDateEpochMilli(lastCommitDateEpochMilli) + .description(description) + .build(); + } +} diff --git a/src/testInteg/resources/datasets/map_index_prod_sample.yml b/src/testInteg/resources/datasets/map_index_prod_sample.yml new file mode 100644 index 0000000..16a157b --- /dev/null +++ b/src/testInteg/resources/datasets/map_index_prod_sample.yml @@ -0,0 +1,257 @@ +map_index: +- id: 1000 + map_name: 1914-cow-empires + repo_url: https://github.com/triplea-maps/1914-cow-empires + default_branch: master + description: '
Version 1.0, last update 2014.12.07 for engine 1.8.0.3 + +
Game done by RogerCooper + +
Suggestions to RogerCoop@aol.com + +
+ +
a.) Content: + +
1 Game + +
1914-COW-Empires + +
+ +
b.) Rough overview: + +
A WW1 scenario using the Napoleonic Empires map and military/economic data from the Correlates of War database. + +
+ +
c.) General tips: + +
Read game notes before playing. + +
+ + ' + download_url: https://github.com/triplea-maps/1914-cow-empires/archive/refs/heads/master.zip + preview_image_url: https://github.com/triplea-maps/1914-cow-empires/blob/master/preview.png?raw=true + download_size_bytes: 4287712 + last_commit_date: '2021-09-16 05:43:46.0' +- id: 1001 + map_name: 1939ARevised + repo_url: https://github.com/triplea-maps/1939ARevised + default_branch: master + description: 'No description available for: https://github.com/triplea-maps/1939ARevised. Contact the map author and request they add a ''description.html'' file' + download_url: https://github.com/triplea-maps/1939ARevised/archive/refs/heads/master.zip + preview_image_url: https://github.com/triplea-maps/1939ARevised/blob/master/preview.png?raw=true + download_size_bytes: 1301896 + last_commit_date: '2022-11-27 15:00:22.0' +- id: 1002 + map_name: 1939_Pact_of_Steel_Big_World + repo_url: https://github.com/triplea-maps/1939_Pact_of_Steel_Big_World + default_branch: master + description: 1939 on the Big World Map with 7 powers + download_url: https://github.com/triplea-maps/1939_Pact_of_Steel_Big_World/archive/refs/heads/master.zip + preview_image_url: https://github.com/triplea-maps/1939_Pact_of_Steel_Big_World/blob/master/preview.png?raw=true + download_size_bytes: 803364 + last_commit_date: '2022-09-04 18:12:31.0' +- id: 1003 + map_name: '1941' + repo_url: https://github.com/triplea-maps/1941 + default_branch: master + description: 'No description available for: https://github.com/triplea-maps/1941. Contact the map author and request they add a ''description.html'' file' + download_url: https://github.com/triplea-maps/1941/archive/refs/heads/master.zip + preview_image_url: https://github.com/triplea-maps/1941/blob/master/preview.png?raw=true + download_size_bytes: 8553163 + last_commit_date: '2021-09-16 05:43:38.0' +- id: 1004 + map_name: 1941_global_command_decision + repo_url: https://github.com/triplea-maps/1941_global_command_decision + default_branch: master + description: "
\n
            1941 GLOBAL COMMAND DECISION\n
by Black Elk (graphics/testing) & TheDog (code/testing)\n
\n
Introduction\n
It is late 1941; Germany has launched a massive invasion against the USSR and Japan is ready to strike at the USA in the Pacific.\n
\n
FEATURES\n
• Massive map 16816x8085px=136Mpx, one of the biggest TripleA maps. Unit size 54px high, this is with 4K screens in mind.\n
• Nearly 800 land & sea locations, with terrain effects, Desert, Forest, Marsh, Tundra, Urban, impassable mountains, canals\n
\n
• Stacking limit is enforced to help the AI. Only 10 Air & 10 Sea units are allowed per Sea Zone/Territory. Only 20 Land per Territory.\n
• AI friendly, no Objectives, or Politics/Technology phases, both are scripted. 79 territories marked to guide the AI for better play.\n
\n
• Sea Zones are worth 1-2pu/turn and for Britain and Japan are vital for their survival\n
• All land territories bordering Sea Zones can be blockaded. There are no convoy centres/route/zones.\n
\n
• Four Lend-Lease-Depots, 3 in USSR Archangelsk, Persian Corridor to Baku, Vladivostok and Burma Road to Yunnan China\n
• Four types of HQ Commands, Air, Army, Fleet and Submarine, nations can have between none and five types of a HQ. \n
    These represent Rommel, Montgomery, Patton, Cunningham, Donitz, Nimitz, US 8th Air Force HQs etc. and their staff.\n
• Four different type of “Factory”\n
\n
• As a minimum requires TripleA 2.7.14848+\n
\n
For the latest download and discussion, copy and paste the link below\n
\n
https://forums.triplea-game.org/topic/3326/1941-global-command-decision\n
\n
" + download_url: https://github.com/triplea-maps/1941_global_command_decision/archive/refs/heads/master.zip + preview_image_url: https://github.com/triplea-maps/1941_global_command_decision/blob/master/preview.png?raw=true + download_size_bytes: 94976745 + last_commit_date: '2026-03-14 08:07:45.0' +- id: 1005 + map_name: 2013Conflict + repo_url: https://github.com/triplea-maps/2013Conflict + default_branch: master + description: Hypothetical World War 3 in 2013. + download_url: https://github.com/triplea-maps/2013Conflict/archive/refs/heads/master.zip + preview_image_url: https://github.com/triplea-maps/2013Conflict/blob/master/preview.png?raw=true + download_size_bytes: 9717791 + last_commit_date: '2023-07-15 13:09:14.0' +- id: 1006 + map_name: '2020' + repo_url: https://github.com/triplea-maps/2020 + default_branch: master + description: 'No description available for: https://github.com/triplea-maps/2020. Contact the map author and request they add a ''description.html'' file' + download_url: https://github.com/triplea-maps/2020/archive/refs/heads/master.zip + preview_image_url: https://github.com/triplea-maps/2020/blob/master/preview.png?raw=true + download_size_bytes: 2670316 + last_commit_date: '2021-10-20 23:11:09.0' +- id: 1007 + map_name: 270BC_Tertiered + repo_url: https://github.com/triplea-maps/270BC_Variants + default_branch: master + description: 'No description available for: https://github.com/triplea-maps/270BC_Variants. Contact the map author and request they add a ''description.html'' file' + download_url: https://github.com/triplea-maps/270BC_Variants/archive/refs/heads/master.zip + preview_image_url: https://github.com/triplea-maps/270BC_Variants/blob/master/preview.png?raw=true + download_size_bytes: 8052661 + last_commit_date: '2022-02-16 23:07:11.0' +- id: 1008 + map_name: 270bc + repo_url: https://github.com/triplea-maps/270bc + default_branch: master + description: '
By Doctor Che + +
Updated by Redrum + +
+ +
Ancient Era Map of the Mediterranean + +
+ +
Make yourself an empire around the Mediterranean Sea (the known world), + +
in the era when Hellenes, Romans, and Phoenicians ruled. Choose from a arsenal of + +
legionaires, hoplites, onagers, cataphracts, triremes, war elephants, and many more. + +
Territory names are based on cities at the time give or take 500 years. + +
+ + ' + download_url: https://github.com/triplea-maps/270bc/archive/refs/heads/master.zip + preview_image_url: https://github.com/triplea-maps/270bc/blob/master/preview.png?raw=true + download_size_bytes: 8087243 + last_commit_date: '2021-09-16 05:43:45.0' +- id: 1009 + map_name: 270bc_wars + repo_url: https://github.com/triplea-maps/270bc_wars + default_branch: master + description: '
By Cernel. + +
Original 270BC map and game by Doctor Che. + +
Original 270BC game modified by Veqryn, Cernel, redrum. + +
Original 270BC map details and decorations, "TripleA Ancient" image, units images, territory names images, territory values images, dice images by Hepps. + +
+ +
Mistress of Italy, Rome, looking over the sea, discovers herself engaged in the inexorable struggle for survival against the might of Carthage, encroaching on Africa and Spain, and the islands nearby, as far as the columns of Hercules, the end of the World. + +
In Greece, the royal hegemony of Macedonia is bitterly challenged by a warring coalition of free cities and leagues, from Sicily, in the west, to Asia, in the east. + +
Across what remains of what was taken by Alexander, what had begun as a spate of civil conflicts conflated into a dynastic strife between the new realms of Egypt and Syria. + +
Beyond the Hellenistic world, the ascendancy of Parthia looms on the horizon, eager to champion the resurgence of Persian supremacy to her former greatness. + +
Meanwhile, hailing from unknown lands, the tribes of Numidia are swarming out of the wilds of Libya, their savage want for slaves and violent desire for booty, unquenchable. + +
''Tis a clash of civilizations: for either side, the other side is to be eliminated. + +
+ +
Special thanks to Navalland, guerrilla_J and Aposteles for play-testing. + + ' + download_url: https://github.com/triplea-maps/270bc_wars/archive/refs/heads/master.zip + preview_image_url: https://github.com/triplea-maps/270bc_wars/blob/master/preview.png?raw=true + download_size_bytes: 24085728 + last_commit_date: '2021-09-16 05:43:38.0' +- id: 1010 + map_name: 300BC + repo_url: https://github.com/triplea-maps/300BC + default_branch: master + description: The Mediterranean in 300BC. + download_url: https://github.com/triplea-maps/300BC/archive/refs/heads/master.zip + preview_image_url: https://github.com/triplea-maps/300BC/blob/master/preview.png?raw=true + download_size_bytes: 1903629 + last_commit_date: '2023-01-07 15:37:35.0' +- id: 1011 + map_name: 41_Oztea + repo_url: https://github.com/triplea-maps/41_Oztea_Variants + default_branch: master + description: 'No description available for: https://github.com/triplea-maps/41_Oztea_Variants. Contact the map author and request they add a ''description.html'' file' + download_url: https://github.com/triplea-maps/41_Oztea_Variants/archive/refs/heads/master.zip + preview_image_url: https://github.com/triplea-maps/41_Oztea_Variants/blob/master/preview.png?raw=true + download_size_bytes: 26833930 + last_commit_date: '2021-07-18 02:20:28.0' +- id: 1012 + map_name: 6Kingdoms + repo_url: https://github.com/triplea-maps/6Kingdoms + default_branch: master + description: A war between two alliances of fantasy kingdoms. + download_url: https://github.com/triplea-maps/6Kingdoms/archive/refs/heads/master.zip + preview_image_url: https://github.com/triplea-maps/6Kingdoms/blob/master/preview.png?raw=true + download_size_bytes: 2541514 + last_commit_date: '2023-12-06 22:09:36.0' +- id: 1013 + map_name: AA50-41-Maintenance + repo_url: https://github.com/triplea-maps/AA50-41-Maintenance + default_branch: master + description: World War 2 Version 3 with Maintenance costs. + download_url: https://github.com/triplea-maps/AA50-41-Maintenance/archive/refs/heads/master.zip + preview_image_url: https://github.com/triplea-maps/AA50-41-Maintenance/blob/master/preview.png?raw=true + download_size_bytes: 9270272 + last_commit_date: '2024-02-04 15:07:17.0' +- id: 1014 + map_name: AA50-BuildCaps + repo_url: https://github.com/triplea-maps/AA50-BuildCaps + default_branch: master + description: AA50/World World V3/Anniversary edition with production capped at what came in the box. + download_url: https://github.com/triplea-maps/AA50-BuildCaps/archive/refs/heads/master.zip + preview_image_url: https://github.com/triplea-maps/AA50-BuildCaps/blob/master/preview.png?raw=true + download_size_bytes: 9458131 + last_commit_date: '2023-11-07 01:22:11.0' +- id: 1015 + map_name: AA50-Europe + repo_url: https://github.com/triplea-maps/AA50-Europe + default_branch: master + description: World Word 2 in Europe on the AA50 map + download_url: https://github.com/triplea-maps/AA50-Europe/archive/refs/heads/master.zip + preview_image_url: https://github.com/triplea-maps/AA50-Europe/blob/master/preview.png?raw=true + download_size_bytes: 9611525 + last_commit_date: '2025-05-15 22:05:44.0' +- id: 1016 + map_name: AA50-realistic + repo_url: https://github.com/triplea-maps/AA50-realistic + default_branch: master + description: World War II v3, 1942 with an modified setup. + download_url: https://github.com/triplea-maps/AA50-realistic/archive/refs/heads/master.zip + preview_image_url: https://github.com/triplea-maps/AA50-realistic/blob/master/preview.png?raw=true + download_size_bytes: 10492327 + last_commit_date: '2024-07-04 15:01:42.0' +- id: 1017 + map_name: AAC-BuildCaps + repo_url: https://github.com/triplea-maps/AAC-BuildCaps + default_branch: master + description: 'No description available for: https://github.com/triplea-maps/AAC-BuildCaps. Contact the map author and request they add a ''description.html'' file' + download_url: https://github.com/triplea-maps/AAC-BuildCaps/archive/refs/heads/master.zip + preview_image_url: https://github.com/triplea-maps/AAC-BuildCaps/blob/master/preview.png?raw=true + download_size_bytes: 8788099 + last_commit_date: '2022-01-19 00:42:14.0' +- id: 1018 + map_name: AAR-BuildCaps + repo_url: https://github.com/triplea-maps/AAR-BuildCaps + default_branch: master + description: 'World War II Revised with caps on how many units of each type that can be built. + + ' + download_url: https://github.com/triplea-maps/AAR-BuildCaps/archive/refs/heads/master.zip + preview_image_url: https://github.com/triplea-maps/AAR-BuildCaps/blob/master/preview.png?raw=true + download_size_bytes: 3026055 + last_commit_date: '2024-12-29 20:46:17.0' +- id: 1019 + map_name: AAZ + repo_url: https://github.com/triplea-maps/AAZ + default_branch: master + description: "Axis & Allies & Zombies\n\t\t\t\t\t
\n\t\t\t\t\t
1. No strategic bombing.\n\t\t\t\t\t
2. No AA Guns / Mechanized Infantry / Cruisers.\n\t\t\t\t\t
3. No shore bombardment.\n\t\t\t\t\t
4. Cannot build new factories.\n\t\t\t\t\t
5. Recruitment centers are like factories but only build infantry\n\t\t\t\t\t
6 For balance, 3 Zombies start in Germany\n\t\t\t\t\t
Victory Condition:\n\t\t\t\t\t
Take 1 enemy capital and hold all your own\n\t\t\t\t\t
Zombies win if their income reaches 25\n
Set Game/User Notifications/Show Trigger/Condition Change Roll Failure to off\n\t\t\t\t\t\t
Based upon Axis & Allies & Zombies\n\t\t\t\t\t\t
Player nations are not allowed to invade neutrals, but can move in once zombies have taken over.\n\t\t\t\t\t\t
Note that in this weIrd alternate history scenario, the Zombies are restless dead (Draugr) and not contagious as in most modern zombie fiction. They are capable of using weaponry, but too impaired to handle sophisticated weaponry or coordinate their actions.\n\t\t\t\t\t\t
The dead were awakened uncontrallably by Nazi occultists\n\t\t\t\t\t\t
The big change from the original game is that Zombies are not spawned by killing infantry, as there was no way to handle that in TripleA. Instead every area has 1% chance per turn of spawning a horde of 10. In addition each area has a 10% chance each turn of spawning 1.\n\t\t\t\t\t
Created for TripleA by Roger Cooper" + download_url: https://github.com/triplea-maps/AAZ/archive/refs/heads/master.zip + preview_image_url: https://github.com/triplea-maps/AAZ/blob/master/preview.png?raw=true + download_size_bytes: 1908091 + last_commit_date: '2024-07-08 02:34:19.0'