diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java index 4b79e3621c..6112d233f4 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -79,6 +79,7 @@ public class VndMediaType { public static final String ME = PREFIX + "me" + SUFFIX; public static final String SOURCE = PREFIX + "source" + SUFFIX; public static final String ANNOTATE = PREFIX + "annotate" + SUFFIX; + public static final String ADMIN_INFO = PREFIX + "adminInfo" + SUFFIX; public static final String ERROR_TYPE = PREFIX + "error" + SUFFIX; public static final String REPOSITORY_ROLE = PREFIX + "repositoryRole" + SUFFIX; diff --git a/scm-core/src/main/java/sonia/scm/xml/XmlDateWithTimezoneAdapter.java b/scm-core/src/main/java/sonia/scm/xml/XmlDateWithTimezoneAdapter.java new file mode 100644 index 0000000000..97097b395b --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/xml/XmlDateWithTimezoneAdapter.java @@ -0,0 +1,42 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.xml; + +import javax.xml.bind.annotation.adapters.XmlAdapter; +import java.text.SimpleDateFormat; +import java.util.Date; + +public class XmlDateWithTimezoneAdapter extends XmlAdapter { + @Override + public Date unmarshal(String date) throws Exception { + SimpleDateFormat formatter = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz"); + return formatter.parse(date); + } + + @Override + public String marshal(Date date) { + return date.toString(); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/admin/ReleaseFeedDto.java b/scm-webapp/src/main/java/sonia/scm/admin/ReleaseFeedDto.java index 98892cbe04..0eb8f1f963 100644 --- a/scm-webapp/src/main/java/sonia/scm/admin/ReleaseFeedDto.java +++ b/scm-webapp/src/main/java/sonia/scm/admin/ReleaseFeedDto.java @@ -26,63 +26,61 @@ package sonia.scm.admin; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import sonia.scm.xml.XmlDateWithTimezoneAdapter; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; -import java.time.Instant; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.util.Date; import java.util.List; -@XmlRootElement +@XmlRootElement(name = "rss") @XmlAccessorType(XmlAccessType.FIELD) +@NoArgsConstructor @AllArgsConstructor +@Getter +@Setter public final class ReleaseFeedDto { - @XmlElement(name = "rss") - private final RSS rss; + @XmlElement(name = "channel") + private Channel channel; - public RSS getRSS() { - return rss; + public Channel getChannel() { + return channel; } - @XmlRootElement(name = "rss") @XmlAccessorType(XmlAccessType.FIELD) @AllArgsConstructor - public static class RSS { - - @XmlElement(name = "channel") - private final Channel channel; - - public Channel getChannel() { - return channel; - } - } - - @XmlAccessorType(XmlAccessType.FIELD) - @XmlRootElement(name = "channel") + @NoArgsConstructor @Getter - @AllArgsConstructor + @Setter public static class Channel { - private final String title; - private final String description; - private final String link; - private final String generator; - private final Instant lastBuildDate; - @XmlElement(name = "releases") - private final List releases; + private String title; + private String description; + private String link; + private String generator; + @XmlJavaTypeAdapter(XmlDateWithTimezoneAdapter.class) + private Date lastBuildDate; + @XmlElement(name = "item") + private List releases; } @XmlAccessorType(XmlAccessType.FIELD) - @XmlRootElement(name = "conditions") - @Getter + @NoArgsConstructor @AllArgsConstructor + @Getter + @Setter public static class Release { - private final String title; - private final String description; - private final String link; - private final String guid; - private final Instant pubDate; + private String title; + private String description; + private String link; + private String guid; + @XmlJavaTypeAdapter(XmlDateWithTimezoneAdapter.class) + private Date pubDate; } } diff --git a/scm-webapp/src/main/java/sonia/scm/admin/ReleaseFeedReader.java b/scm-webapp/src/main/java/sonia/scm/admin/ReleaseFeedParser.java similarity index 91% rename from scm-webapp/src/main/java/sonia/scm/admin/ReleaseFeedReader.java rename to scm-webapp/src/main/java/sonia/scm/admin/ReleaseFeedParser.java index da22f04f8c..ad909d010a 100644 --- a/scm-webapp/src/main/java/sonia/scm/admin/ReleaseFeedReader.java +++ b/scm-webapp/src/main/java/sonia/scm/admin/ReleaseFeedParser.java @@ -30,17 +30,18 @@ import sonia.scm.net.ahc.AdvancedHttpClient; import javax.inject.Inject; import java.io.IOException; +import java.time.Instant; import java.util.Comparator; import java.util.Optional; -public class ReleaseFeedReader { +public class ReleaseFeedParser { - private static final Logger LOG = LoggerFactory.getLogger(ReleaseFeedReader.class); + private static final Logger LOG = LoggerFactory.getLogger(ReleaseFeedParser.class); private final AdvancedHttpClient client; @Inject - public ReleaseFeedReader(AdvancedHttpClient client) { + public ReleaseFeedParser(AdvancedHttpClient client) { this.client = client; } @@ -50,13 +51,13 @@ public class ReleaseFeedReader { Optional latestRelease = filterForLatestRelease(releaseFeed); if (latestRelease.isPresent()) { ReleaseFeedDto.Release release = latestRelease.get(); - return Optional.of(new ReleaseInfo(release.getTitle(), release.getLink(), release.getPubDate())); + return Optional.of(new ReleaseInfo(release.getTitle(), release.getLink(), Instant.now())); } return Optional.empty(); } private Optional filterForLatestRelease(ReleaseFeedDto releaseFeed) { - return releaseFeed.getRSS().getChannel().getReleases() + return releaseFeed.getChannel().getReleases() .stream() .max(Comparator.comparing(ReleaseFeedDto.Release::getPubDate)); } diff --git a/scm-webapp/src/main/java/sonia/scm/admin/ReleaseInfoMapper.java b/scm-webapp/src/main/java/sonia/scm/admin/ReleaseInfoMapper.java new file mode 100644 index 0000000000..23d35e4065 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/admin/ReleaseInfoMapper.java @@ -0,0 +1,37 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.admin; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import sonia.scm.api.v2.resources.HalAppenderMapper; +import sonia.scm.api.v2.resources.ReleaseInfoDto; + +@Mapper +public abstract class ReleaseInfoMapper extends HalAppenderMapper { + + @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes + public abstract ReleaseInfoDto map(ReleaseInfo releaseInfo); +} diff --git a/scm-webapp/src/main/java/sonia/scm/admin/ReleaseVersionChecker.java b/scm-webapp/src/main/java/sonia/scm/admin/ReleaseVersionChecker.java index b5bac61e31..371f4daef1 100644 --- a/scm-webapp/src/main/java/sonia/scm/admin/ReleaseVersionChecker.java +++ b/scm-webapp/src/main/java/sonia/scm/admin/ReleaseVersionChecker.java @@ -24,7 +24,12 @@ package sonia.scm.admin; +import com.google.common.annotations.VisibleForTesting; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import sonia.scm.SCMContextProvider; +import sonia.scm.cache.Cache; +import sonia.scm.cache.CacheManager; import sonia.scm.config.ScmConfiguration; import sonia.scm.version.Version; @@ -34,28 +39,47 @@ import java.util.Optional; public class ReleaseVersionChecker { - private final ReleaseFeedReader releaseFeedReader; + private static final Logger LOG = LoggerFactory.getLogger(ReleaseVersionChecker.class); + private static final String CACHE_NAME = "sonia.cache.releaseInfo"; + + private final ReleaseFeedParser releaseFeedParser; private final ScmConfiguration scmConfiguration; private final SCMContextProvider scmContextProvider; + private Cache cache; @Inject - public ReleaseVersionChecker(ReleaseFeedReader releaseFeedReader, ScmConfiguration scmConfiguration, SCMContextProvider scmContextProvider) { - this.releaseFeedReader = releaseFeedReader; + public ReleaseVersionChecker(ReleaseFeedParser releaseFeedParser, ScmConfiguration scmConfiguration, SCMContextProvider scmContextProvider, CacheManager cacheManager) { + this.releaseFeedParser = releaseFeedParser; this.scmConfiguration = scmConfiguration; this.scmContextProvider = scmContextProvider; + this.cache = cacheManager.getCache(CACHE_NAME); } - Optional checkForNewerVersion() { + @VisibleForTesting + void setCache(Cache cache) { + this.cache = cache; + } + + public Optional checkForNewerVersion() { + ReleaseInfo cachedReleaseInfo = cache.get("latest"); + if (cachedReleaseInfo != null) { + return Optional.of(cachedReleaseInfo); + } else { + return findLatestReleaseInRssFeed(); + } + } + + private Optional findLatestReleaseInRssFeed() { try { String releaseFeedUrl = scmConfiguration.getReleaseFeedUrl(); - Optional latestRelease = releaseFeedReader.findLatestRelease(releaseFeedUrl); - if (latestRelease.isPresent()) { - if (isNewerVersion(latestRelease.get())) { - return latestRelease; - } + Optional latestRelease = releaseFeedParser.findLatestRelease(releaseFeedUrl); + if (latestRelease.isPresent() && isNewerVersion(latestRelease.get())) { + cache.put("latest", latestRelease.get()); + return latestRelease; } } catch (IOException e) { // This is an silent action. We don't want the user to get any kind of error for this. + LOG.info("No newer version found for SCM-Manager"); return Optional.empty(); } return Optional.empty(); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AdminInfoResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AdminInfoResource.java new file mode 100644 index 0000000000..03a703440d --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AdminInfoResource.java @@ -0,0 +1,80 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.api.v2.resources; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import sonia.scm.admin.ReleaseInfo; +import sonia.scm.admin.ReleaseInfoMapper; +import sonia.scm.admin.ReleaseVersionChecker; +import sonia.scm.web.VndMediaType; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import java.util.Optional; + +@OpenAPIDefinition(tags = { + @Tag(name = "AdminInfo", description = "Admin information endpoints") +}) +@Path("") +public class AdminInfoResource { + + private final ReleaseVersionChecker checker; + private final ReleaseInfoMapper mapper; + + @Inject + public AdminInfoResource(ReleaseVersionChecker checker, ReleaseInfoMapper mapper) { + this.checker = checker; + this.mapper = mapper; + } + + /** + * Checks for a newer core version of SCM-Manager. + */ + @GET + @Path("releaseInfo") + @Produces(VndMediaType.ADMIN_INFO) + @Operation(summary = "Returns release info.", description = "Returns a release info if a newer version of SCM-Manager is available.", tags = "AdminInfo") + @ApiResponse(responseCode = "200", description = "success") + @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") + @ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the information") + @ApiResponse( + responseCode = "500", + description = "internal server error", + content = @Content( + mediaType = VndMediaType.ERROR_TYPE, + schema = @Schema(implementation = ErrorDto.class) + )) + public ReleaseInfoDto getReleaseInfo() { + Optional releaseInfo = checker.checkForNewerVersion(); + return releaseInfo.map(mapper::map).orElse(null); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java index d8c9acc7f6..19e7dfc0a3 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java @@ -102,6 +102,7 @@ public class IndexDtoGenerator extends HalAppenderMapper { } if (ConfigurationPermissions.list().isPermitted()) { builder.single(link("config", resourceLinks.config().self())); + builder.single(link("releaseInfo", resourceLinks.adminInfo().releaseInfo())); } builder.single(link("repositories", resourceLinks.repositoryCollection().self())); builder.single(link("namespaces", resourceLinks.namespaceCollection().self())); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java index b6c8e9d8ea..affa80468a 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java @@ -27,6 +27,7 @@ package sonia.scm.api.v2.resources; import com.google.inject.AbstractModule; import com.google.inject.servlet.ServletScopes; import org.mapstruct.factory.Mappers; +import sonia.scm.admin.ReleaseInfoMapper; import sonia.scm.security.gpg.PublicKeyMapper; import sonia.scm.web.api.RepositoryToHalMapper; @@ -76,6 +77,7 @@ public class MapperModule extends AbstractModule { bind(RepositoryToHalMapper.class).to(Mappers.getMapperClass(RepositoryToRepositoryDtoMapper.class)); bind(BlameResultToBlameDtoMapper.class).to(Mappers.getMapperClass(BlameResultToBlameDtoMapper.class)); + bind(ReleaseInfoMapper.class).to(Mappers.getMapperClass(ReleaseInfoMapper.class)); // no mapstruct required bind(MeDtoFactory.class); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ReleaseInfoDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ReleaseInfoDto.java new file mode 100644 index 0000000000..1f3e2c92e3 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ReleaseInfoDto.java @@ -0,0 +1,43 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.Instant; + +@NoArgsConstructor +@Setter +@Getter +@SuppressWarnings("squid:S2160") // we do not need equals for dto +public class ReleaseInfoDto extends HalRepresentation { + private String title; + private String link; + private Instant releaseDate; +} + diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java index 99a2058249..4dc694d24d 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java @@ -264,6 +264,22 @@ class ResourceLinks { } } + AdminInfoLinks adminInfo() { + return new AdminInfoLinks(scmPathInfoStore.get()); + } + + static class AdminInfoLinks { + private final LinkBuilder adminInfoLinkBuilder; + + AdminInfoLinks(ScmPathInfo pathInfo) { + adminInfoLinkBuilder = new LinkBuilder(pathInfo, AdminInfoResource.class); + } + + String releaseInfo() { + return adminInfoLinkBuilder.method("getReleaseInfo").parameters().href(); + } + } + public RepositoryLinks repository() { return new RepositoryLinks(scmPathInfoStore.get()); } diff --git a/scm-webapp/src/test/java/sonia/scm/admin/ReleaseFeedReaderTest.java b/scm-webapp/src/test/java/sonia/scm/admin/ReleaseFeedParserTest.java similarity index 84% rename from scm-webapp/src/test/java/sonia/scm/admin/ReleaseFeedReaderTest.java rename to scm-webapp/src/test/java/sonia/scm/admin/ReleaseFeedParserTest.java index 0f88839da5..7dab58f2fd 100644 --- a/scm-webapp/src/test/java/sonia/scm/admin/ReleaseFeedReaderTest.java +++ b/scm-webapp/src/test/java/sonia/scm/admin/ReleaseFeedParserTest.java @@ -34,20 +34,20 @@ import org.mockito.junit.jupiter.MockitoExtension; import sonia.scm.net.ahc.AdvancedHttpClient; import java.io.IOException; -import java.time.Instant; +import java.util.Date; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) -class ReleaseFeedReaderTest { +class ReleaseFeedParserTest { @Mock(answer = Answers.RETURNS_DEEP_STUBS) AdvancedHttpClient client; @InjectMocks - ReleaseFeedReader releaseFeedReader; + ReleaseFeedParser releaseFeedParser; @Test void shouldFindLatestRelease() throws IOException { @@ -55,7 +55,7 @@ class ReleaseFeedReaderTest { when(client.get(url).request().contentFromXml(ReleaseFeedDto.class)).thenReturn(createReleaseFeedDto()); - Optional release = releaseFeedReader.findLatestRelease(url); + Optional release = releaseFeedParser.findLatestRelease(url); assertThat(release).isPresent(); assertThat(release.get().getTitle()).isEqualTo("3"); @@ -63,16 +63,14 @@ class ReleaseFeedReaderTest { } private ReleaseFeedDto createReleaseFeedDto() { - ReleaseFeedDto.Release release1 = createRelease("1", "download-1", 1000000000L); - ReleaseFeedDto.Release release2 = createRelease("2", "download-2", 2000000000L); - ReleaseFeedDto.Release release3 = createRelease("3", "download-3", 3000000000L); - ReleaseFeedDto.Channel channel = new ReleaseFeedDto.Channel("scm", "scm releases", "scm-download", "gatsby", Instant.now(), ImmutableList.of(release1, release2, release3)); - ReleaseFeedDto.RSS rss = new ReleaseFeedDto.RSS(channel); - return new ReleaseFeedDto(rss); + ReleaseFeedDto.Release release1 = createRelease("1", "download-1", new Date(1000000000L)); + ReleaseFeedDto.Release release2 = createRelease("2", "download-2", new Date(2000000000L)); + ReleaseFeedDto.Release release3 = createRelease("3", "download-3", new Date(3000000000L)); + ReleaseFeedDto.Channel channel = new ReleaseFeedDto.Channel("scm", "scm releases", "scm-download", "gatsby", new Date(1L), ImmutableList.of(release1, release2, release3)); + return new ReleaseFeedDto(channel); } - private ReleaseFeedDto.Release createRelease(String version, String link, long date) { - return new ReleaseFeedDto.Release(version, version, link, version, Instant.ofEpochMilli(date)); + private ReleaseFeedDto.Release createRelease(String version, String link, Date date) { + return new ReleaseFeedDto.Release(version, version, link, version, date); } - } diff --git a/scm-webapp/src/test/java/sonia/scm/admin/ReleaseInfoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/admin/ReleaseInfoMapperTest.java new file mode 100644 index 0000000000..82abd6ce74 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/admin/ReleaseInfoMapperTest.java @@ -0,0 +1,48 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.admin; + +import org.junit.jupiter.api.Test; +import sonia.scm.api.v2.resources.ReleaseInfoDto; + +import java.time.Instant; + +import static org.assertj.core.api.Assertions.assertThat; + +class ReleaseInfoMapperTest { + + ReleaseInfoMapper mapper = new ReleaseInfoMapperImpl(); + + @Test + void shouldMapToDto() { + Instant releaseDate = Instant.now(); + ReleaseInfo releaseInfo = new ReleaseInfo("1.2.3", "download-link", releaseDate); + ReleaseInfoDto dto = mapper.map(releaseInfo); + + assertThat(dto.getLink()).isEqualTo(releaseInfo.getLink()); + assertThat(dto.getReleaseDate()).isEqualTo(releaseDate); + assertThat(dto.getTitle()).isEqualTo(releaseInfo.getTitle()); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/admin/ReleaseVersionCheckerTest.java b/scm-webapp/src/test/java/sonia/scm/admin/ReleaseVersionCheckerTest.java index e9dd8360b2..87a607a9cb 100644 --- a/scm-webapp/src/test/java/sonia/scm/admin/ReleaseVersionCheckerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/admin/ReleaseVersionCheckerTest.java @@ -24,8 +24,77 @@ package sonia.scm.admin; -import static 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.junit.jupiter.MockitoExtension; +import sonia.scm.SCMContextProvider; +import sonia.scm.cache.Cache; +import sonia.scm.cache.CacheManager; +import sonia.scm.cache.MapCacheManager; +import sonia.scm.config.ScmConfiguration; +import java.io.IOException; +import java.time.Instant; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) class ReleaseVersionCheckerTest { + private ReleaseFeedParser feedReader; + private ScmConfiguration scmConfiguration; + private SCMContextProvider contextProvider; + private ReleaseVersionChecker checker; + + @BeforeEach + void setup() { + feedReader = mock(ReleaseFeedParser.class); + scmConfiguration = mock(ScmConfiguration.class); + contextProvider = mock(SCMContextProvider.class); + CacheManager cacheManager = new MapCacheManager(); + + checker = new ReleaseVersionChecker(feedReader, scmConfiguration, contextProvider, cacheManager); + } + + @Test + void shouldReturnEmptyOptional() throws IOException { + when(scmConfiguration.getReleaseFeedUrl()).thenReturn("releaseFeed"); + when(feedReader.findLatestRelease("releaseFeed")).thenReturn(Optional.empty()); + + Optional releaseInfo = checker.checkForNewerVersion(); + + assertThat(releaseInfo).isNotPresent(); + } + + @Test + void shouldReturnReleaseInfoFromCache() { + ReleaseInfo cachedReleaseInfo = new ReleaseInfo("1.42.9", "download-link", Instant.now()); + Cache cache = new MapCacheManager().getCache("sonia.cache.releaseInfo"); + cache.put("latest", cachedReleaseInfo); + checker.setCache(cache); + + Optional releaseInfo = checker.checkForNewerVersion(); + + assertThat(releaseInfo).isPresent(); + assertThat(releaseInfo.get().getTitle()).isEqualTo("1.42.9"); + assertThat(releaseInfo.get().getLink()).isEqualTo("download-link"); + } + + @Test + void shouldReturnReleaseInfo() throws IOException { + ReleaseInfo releaseInfo = new ReleaseInfo("2.0.0", "download-link", Instant.now()); + when(scmConfiguration.getReleaseFeedUrl()).thenReturn("releaseFeed"); + when(feedReader.findLatestRelease("releaseFeed")).thenReturn(Optional.of(releaseInfo)); + when(contextProvider.getVersion()).thenReturn("1.9.0"); + + Optional latestRelease = checker.checkForNewerVersion(); + + assertThat(latestRelease).isPresent(); + assertThat(latestRelease.get().getTitle()).isEqualTo("2.0.0"); + assertThat(latestRelease.get().getLink()).isEqualTo("download-link"); + } } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java index f2d6e85710..e7c21f5556 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java @@ -79,6 +79,7 @@ public class ResourceLinksMock { lenient().when(resourceLinks.namespace()).thenReturn(new ResourceLinks.NamespaceLinks(pathInfo)); lenient().when(resourceLinks.namespaceCollection()).thenReturn(new ResourceLinks.NamespaceCollectionLinks(pathInfo)); lenient().when(resourceLinks.namespacePermission()).thenReturn(new ResourceLinks.NamespacePermissionLinks(pathInfo)); + lenient().when(resourceLinks.adminInfo()).thenReturn(new ResourceLinks.AdminInfoLinks(pathInfo)); return resourceLinks; }