package com.kaidi.oa.service; import com.fasterxml.jackson.databind.ObjectMapper; import com.kaidi.oa.common.ApiException; import com.kaidi.oa.config.UpdateProperties; import com.sun.net.httpserver.HttpServer; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.info.BuildProperties; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.time.Instant; import java.util.Properties; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; class SystemUpdateServiceTest { @Test void comparesReleaseVersionsWithoutLexicographicMistakes() { assertThat(SystemUpdateService.compareVersions("v0.10.0", "0.9.9")).isPositive(); assertThat(SystemUpdateService.compareVersions("1.0.0", "1.0.0-rc.1")).isPositive(); assertThat(SystemUpdateService.compareVersions("1.2", "1.2.0")).isZero(); assertThat(SystemUpdateService.compareVersions("1.0.0+build.2", "1.0.0+build.1")).isZero(); assertThat(SystemUpdateService.compareVersions("1.0.0-alpha.10", "1.0.0-alpha.2")).isPositive(); assertThat(SystemUpdateService.compareVersions("1.0.0-alpha.1", "1.0.0-alpha.beta")).isNegative(); assertThat(SystemUpdateService.compareVersions("999999999999999999999.0.0", "2.0.0")).isPositive(); assertThat(SystemUpdateService.compareVersions("1.0.0", "dev")).isPositive(); } @Test void checksLatestGiteaReleaseAndRequiresSignedAssetSet() throws Exception { HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0); server.createContext("/api/v1/repos/awaioi/ERP/releases/latest", exchange -> { byte[] body = """ { "tag_name": "v0.2.0", "body": "PostgreSQL production release", "draft": false, "prerelease": false, "published_at": "2026-08-03T10:00:00Z", "assets": [ {"name":"kaidi-erp-0.2.0.tar.gz","browser_download_url":"https://example.test/app","size":123}, {"name":"SHA256SUMS","browser_download_url":"https://example.test/sums","size":64}, {"name":"SHA256SUMS.sig","browser_download_url":"https://example.test/sig","size":64} ] } """.getBytes(StandardCharsets.UTF_8); exchange.getResponseHeaders().add("Content-Type", "application/json"); exchange.sendResponseHeaders(200, body.length); exchange.getResponseBody().write(body); exchange.close(); }); server.start(); try { UpdateProperties properties = configuredProperties(server.getAddress().getPort()); SystemUpdateService service = service(properties, "0.1.0"); SystemUpdateService.UpdateStatus status = service.check(); assertThat(status.updateAvailable()).isTrue(); assertThat(status.currentVersion()).isEqualTo("0.1.0"); assertThat(status.latestVersion()).isEqualTo("0.2.0"); assertThat(status.assets()).extracting(SystemUpdateService.ReleaseAsset::name) .containsExactly("kaidi-erp-0.2.0.tar.gz", "SHA256SUMS", "SHA256SUMS.sig"); } finally { server.stop(0); } } @Test void rejectsPlainHttpUnlessDevelopmentOverrideIsExplicit() { UpdateProperties properties = configuredProperties(1); properties.setAllowInsecureHttp(false); SystemUpdateService service = service(properties, "0.1.0"); assertThatThrownBy(service::check) .isInstanceOf(ApiException.class) .hasMessageContaining("HTTPS"); } @Test void persistedHelperStateComputesAvailabilityFromVersionAndKeepsTimestamp() throws Exception { Path stateFile = Files.createTempFile("system-update-state-", ".json"); try { UpdateProperties properties = configuredProperties(1); properties.setStateFile(stateFile.toString()); SystemUpdateService service = service(properties, "0.2.0"); Files.writeString(stateFile, """ {"phase":"FAILED","progress":0,"message":"update failed",\ "version":"0.2.0","updatedAt":"2026-08-03T12:34:56Z"} """); SystemUpdateService.UpdateStatus failed = service.status(); assertThat(failed.updateAvailable()).isFalse(); assertThat(failed.checkedAt()).isEqualTo(Instant.parse("2026-08-03T12:34:56Z")); Files.writeString(stateFile, """ {"phase":"ROLLED_BACK","progress":100,"message":"rolled back",\ "version":"0.3.0","updatedAt":"2026-08-03T12:35:56Z"} """); assertThat(service.status().updateAvailable()).isTrue(); } finally { Files.deleteIfExists(stateFile); } } private static UpdateProperties configuredProperties(int port) { UpdateProperties properties = new UpdateProperties(); properties.setEnabled(true); properties.setAllowInsecureHttp(true); properties.setGiteaBaseUrl("http://127.0.0.1:" + port); properties.setRepository("awaioi/ERP"); return properties; } @SuppressWarnings("unchecked") private static SystemUpdateService service(UpdateProperties properties, String version) { Properties buildValues = new Properties(); buildValues.setProperty("version", version); ObjectProvider provider = mock(ObjectProvider.class); when(provider.getIfAvailable()).thenReturn(new BuildProperties(buildValues)); return new SystemUpdateService(properties, new ObjectMapper(), provider); } }