]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-22035 New Endpoints to download Scanner Engine
authorantoine.vinot <antoine.vinot@sonarsource.com>
Thu, 2 May 2024 14:02:46 +0000 (16:02 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 3 May 2024 20:02:50 +0000 (20:02 +0000)
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/DefaultScannerEngineController.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/ScannerEngineController.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/response/EngineInfoRestResponse.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/ScannerEngineHandler.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/ScannerEngineHandlerImpl.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/ScannerEngineMetadata.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java
server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/controller/DefaultScannerEngineControllerTest.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/service/ScannerEngineHandlerImplTest.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/config/PlatformLevel4WebConfigTest.java

index 9ba51c1c17a68007efac1659cfeb370e882338b3..b1abd6013b4b63a63bd167e21d85c3ed6a74a3bc 100644 (file)
@@ -49,6 +49,7 @@ public class WebApiEndpoints {
   public static final String ANALYSIS_ENDPOINT = "/analysis";
   public static final String VERSION_ENDPOINT = ANALYSIS_ENDPOINT + "/version";
   public static final String JRE_ENDPOINT = ANALYSIS_ENDPOINT + "/jres";
+  public static final String SCANNER_ENGINE_ENDPOINT = ANALYSIS_ENDPOINT + "/engine";
 
   private WebApiEndpoints() {
   }
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/DefaultScannerEngineController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/DefaultScannerEngineController.java
new file mode 100644 (file)
index 0000000..5de4804
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.v2.api.analysis.controller;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.v2.api.analysis.response.EngineInfoRestResponse;
+import org.sonar.server.v2.api.analysis.service.ScannerEngineHandler;
+import org.sonar.server.v2.api.analysis.service.ScannerEngineMetadata;
+import org.springframework.core.io.InputStreamResource;
+
+import static java.lang.String.format;
+
+public class DefaultScannerEngineController implements ScannerEngineController {
+
+  private final ScannerEngineHandler scannerEngineHandler;
+
+  public DefaultScannerEngineController(ScannerEngineHandler scannerEngineHandler) {
+    this.scannerEngineHandler = scannerEngineHandler;
+  }
+
+  @Override
+  public EngineInfoRestResponse getScannerEngineMetadata() {
+    ScannerEngineMetadata metadata = scannerEngineHandler.getScannerEngineMetadata();
+    return new EngineInfoRestResponse(metadata.filename(), metadata.checksum());
+  }
+
+  @Override
+  public InputStreamResource downloadScannerEngine() {
+    File scannerEngine = scannerEngineHandler.getScannerEngine();
+    try {
+      return new InputStreamResource(new FileInputStream(scannerEngine));
+    } catch (FileNotFoundException e) {
+      throw new NotFoundException(format("Unable to find file: %s", scannerEngine.getName()));
+    }
+  }
+}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/ScannerEngineController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/controller/ScannerEngineController.java
new file mode 100644 (file)
index 0000000..5791666
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.v2.api.analysis.controller;
+
+import io.swagger.v3.oas.annotations.Operation;
+import org.sonar.server.v2.api.analysis.response.EngineInfoRestResponse;
+import org.springframework.core.io.InputStreamResource;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+
+import static org.sonar.server.v2.WebApiEndpoints.SCANNER_ENGINE_ENDPOINT;
+import static org.springframework.http.HttpStatus.OK;
+import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
+import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE;
+
+@RequestMapping(value = SCANNER_ENGINE_ENDPOINT, produces = APPLICATION_JSON_VALUE)
+@RestController
+public interface ScannerEngineController {
+
+  String GET_ENGINE_SUMMARY = "Scanner engine download/metadata";
+  String GET_ENGINE_DESCRIPTION =
+    "This endpoint return the Scanner Engine metadata by default. To download the Scanner Engine, set the Accept header of the request to 'application/octet-stream'.";
+
+  @GetMapping
+  @ResponseStatus(OK)
+  @Operation(summary = GET_ENGINE_SUMMARY, description = GET_ENGINE_DESCRIPTION)
+  EngineInfoRestResponse getScannerEngineMetadata();
+
+  @GetMapping(produces = APPLICATION_OCTET_STREAM_VALUE)
+  @ResponseStatus(OK)
+  @Operation(summary = GET_ENGINE_SUMMARY, description = GET_ENGINE_DESCRIPTION)
+  InputStreamResource downloadScannerEngine();
+
+}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/response/EngineInfoRestResponse.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/response/EngineInfoRestResponse.java
new file mode 100644 (file)
index 0000000..10b799d
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.v2.api.analysis.response;
+
+public record EngineInfoRestResponse(String filename, String checksum) {
+}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/ScannerEngineHandler.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/ScannerEngineHandler.java
new file mode 100644 (file)
index 0000000..d4b9f09
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.v2.api.analysis.service;
+
+import java.io.File;
+
+public interface ScannerEngineHandler {
+  ScannerEngineMetadata getScannerEngineMetadata();
+
+  File getScannerEngine();
+
+}
+
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/ScannerEngineHandlerImpl.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/ScannerEngineHandlerImpl.java
new file mode 100644 (file)
index 0000000..414770f
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.v2.api.analysis.service;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.platform.ServerFileSystem;
+
+import static java.lang.String.format;
+import static org.apache.commons.codec.digest.DigestUtils.sha256Hex;
+import static org.apache.commons.io.FileUtils.listFiles;
+import static org.apache.commons.io.filefilter.FileFilterUtils.directoryFileFilter;
+import static org.apache.commons.io.filefilter.HiddenFileFilter.VISIBLE;
+
+public class ScannerEngineHandlerImpl implements ScannerEngineHandler {
+
+  private final ServerFileSystem fs;
+
+  private ScannerEngineMetadata scannerEngineMetadata;
+
+  public ScannerEngineHandlerImpl(ServerFileSystem fs) {
+    this.fs = fs;
+  }
+
+  @Override
+  public File getScannerEngine() {
+    File scannerDir = new File(fs.getHomeDir(), "lib/scanner");
+    if (!scannerDir.exists()) {
+      throw new NotFoundException(format("Scanner directory not found: %s", scannerDir.getAbsolutePath()));
+    }
+    return listFiles(scannerDir, VISIBLE, directoryFileFilter())
+      .stream()
+      .filter(file -> file.getName().endsWith(".jar"))
+      .findFirst()
+      .orElseThrow(() -> new NotFoundException(format("Scanner JAR not found in directory: %s", scannerDir.getAbsolutePath())));
+  }
+
+  private static String getSha256(File file) {
+    try (FileInputStream fileInputStream = new FileInputStream(file)) {
+      return sha256Hex(fileInputStream);
+    } catch (IOException exception) {
+      throw new UncheckedIOException(new IOException("Unable to compute SHA-256 checksum of the Scanner Engine", exception));
+    }
+  }
+
+  @Override
+  public ScannerEngineMetadata getScannerEngineMetadata() {
+    if (scannerEngineMetadata == null) {
+      File scannerEngine = getScannerEngine();
+      scannerEngineMetadata = new ScannerEngineMetadata(scannerEngine.getName(), getSha256(scannerEngine));
+    }
+    return scannerEngineMetadata;
+  }
+
+}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/ScannerEngineMetadata.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/analysis/service/ScannerEngineMetadata.java
new file mode 100644 (file)
index 0000000..bdc7c24
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.v2.api.analysis.service;
+
+public record ScannerEngineMetadata(String filename, String checksum) {
+}
index 186a5dd4c6c5f34ecb53055c0d4a99eb01be9bdd..d63cef691382486fa789093e8e1d4fb3ae31fa5e 100644 (file)
@@ -40,15 +40,20 @@ import org.sonar.server.common.text.MacroInterpreter;
 import org.sonar.server.common.user.service.UserService;
 import org.sonar.server.health.HealthChecker;
 import org.sonar.server.platform.NodeInformation;
+import org.sonar.server.platform.ServerFileSystem;
 import org.sonar.server.rule.RuleDescriptionFormatter;
 import org.sonar.server.user.SystemPasscode;
 import org.sonar.server.user.UserSession;
 import org.sonar.server.v2.api.analysis.controller.DefaultJresController;
 import org.sonar.server.v2.api.analysis.controller.DefaultVersionController;
 import org.sonar.server.v2.api.analysis.controller.JresController;
+import org.sonar.server.v2.api.analysis.controller.ScannerEngineController;
+import org.sonar.server.v2.api.analysis.controller.DefaultScannerEngineController;
 import org.sonar.server.v2.api.analysis.controller.VersionController;
 import org.sonar.server.v2.api.analysis.service.JresHandler;
 import org.sonar.server.v2.api.analysis.service.JresHandlerImpl;
+import org.sonar.server.v2.api.analysis.service.ScannerEngineHandler;
+import org.sonar.server.v2.api.analysis.service.ScannerEngineHandlerImpl;
 import org.sonar.server.v2.api.dop.controller.DefaultDopSettingsController;
 import org.sonar.server.v2.api.dop.controller.DopSettingsController;
 import org.sonar.server.v2.api.gitlab.config.controller.DefaultGitlabConfigurationController;
@@ -173,4 +178,14 @@ public class PlatformLevel4WebConfig {
     return new DefaultJresController(jresHandler);
   }
 
+  @Bean
+  public ScannerEngineHandler scannerEngineHandler(ServerFileSystem serverFileSystem) {
+    return new ScannerEngineHandlerImpl(serverFileSystem);
+  }
+
+  @Bean
+  public ScannerEngineController scannerEngineController(ScannerEngineHandler scannerEngineHandler) {
+    return new DefaultScannerEngineController(scannerEngineHandler);
+  }
+
 }
diff --git a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/controller/DefaultScannerEngineControllerTest.java b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/controller/DefaultScannerEngineControllerTest.java
new file mode 100644 (file)
index 0000000..dc4d281
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.v2.api.analysis.controller;
+
+import java.io.File;
+import java.nio.file.Path;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.sonar.server.v2.api.analysis.service.ScannerEngineHandler;
+import org.sonar.server.v2.api.analysis.service.ScannerEngineMetadata;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+
+import static java.lang.String.format;
+import static java.nio.file.Files.createTempFile;
+import static java.nio.file.Files.write;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.server.v2.WebApiEndpoints.SCANNER_ENGINE_ENDPOINT;
+import static org.sonar.server.v2.api.ControllerTester.getMockMvc;
+import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM;
+import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+class DefaultScannerEngineControllerTest {
+
+  private final ScannerEngineHandler scannerEngineHandler = mock(ScannerEngineHandler.class);
+
+  private final MockMvc mockMvc = getMockMvc(new DefaultScannerEngineController(scannerEngineHandler));
+
+  @Test
+  void getEngine_shouldReturnScannerMetadataAsJson() throws Exception {
+    String anyName = "anyName";
+    String anyChecksum = "anyChecksum";
+    when(scannerEngineHandler.getScannerEngineMetadata()).thenReturn(new ScannerEngineMetadata(anyName, anyChecksum));
+    String expectedJson = format("{\"filename\":\"%s\",\"checksum\":\"%s\"}", anyName, anyChecksum);
+
+    mockMvc.perform(get(SCANNER_ENGINE_ENDPOINT))
+      .andExpectAll(
+        status().isOk(),
+        content().json(expectedJson));
+  }
+
+  @Test
+  void getEngine_shouldDownloadScanner_whenHeaderIsOctetStream(@TempDir Path tempDir) throws Exception {
+    File scanner = createTempFile(tempDir, "scanner", ".jar").toFile();
+    byte[] anyBinary = {1, 2, 3};
+    write(scanner.toPath(), anyBinary);
+    when(scannerEngineHandler.getScannerEngine()).thenReturn(new File(scanner.toString()));
+
+    mockMvc.perform(get(SCANNER_ENGINE_ENDPOINT)
+        .header("Accept", APPLICATION_OCTET_STREAM_VALUE))
+      .andExpectAll(
+        status().isOk(),
+        content().contentType(APPLICATION_OCTET_STREAM),
+        content().bytes(anyBinary));
+  }
+
+  @Test
+  void getEngine_shouldFail_whenScannerEngineNotFound() {
+    // Ideally we would like Spring to return a 404, but it's not the case at the moment. We suspect that it's because the Header Accept wants a binary file.
+    // So the Json corresponding to the NotFoundException is not sent and we have a 500 instead.
+    when(scannerEngineHandler.getScannerEngine()).thenReturn(new File("no-file"));
+    MockHttpServletRequestBuilder request = get(SCANNER_ENGINE_ENDPOINT).header("Accept", APPLICATION_OCTET_STREAM_VALUE);
+    assertThatThrownBy(() -> mockMvc.perform(request))
+      .hasMessageContaining("NotFoundException: Unable to find file: no-file");
+  }
+
+}
diff --git a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/service/ScannerEngineHandlerImplTest.java b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/analysis/service/ScannerEngineHandlerImplTest.java
new file mode 100644 (file)
index 0000000..f93b597
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.v2.api.analysis.service;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Path;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.platform.ServerFileSystem;
+
+import static java.lang.String.format;
+import static java.nio.file.Files.createDirectories;
+import static java.nio.file.Files.createFile;
+import static java.nio.file.Files.createTempFile;
+import static java.nio.file.Files.deleteIfExists;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+class ScannerEngineHandlerImplTest {
+
+  ServerFileSystem serverFileSystem = mock(ServerFileSystem.class);
+
+  ScannerEngineHandlerImpl scannerEngineHandler = new ScannerEngineHandlerImpl(serverFileSystem);
+
+  @TempDir
+  private File tempDir;
+
+  private Path scannerDir;
+
+  @BeforeEach
+  public void setup() throws IOException {
+    when(serverFileSystem.getHomeDir()).thenReturn(tempDir);
+    scannerDir = createDirectories(Path.of(tempDir.getAbsolutePath(), "lib/scanner"));
+  }
+
+  @Test
+  void getScannerEngineMetadata() throws IOException {
+    createFile(scannerDir.resolve("scanner.jar"));
+    ScannerEngineMetadata expected = new ScannerEngineMetadata("scanner.jar", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
+
+    ScannerEngineMetadata result = scannerEngineHandler.getScannerEngineMetadata();
+
+    assertThat(result).usingRecursiveComparison().isEqualTo(expected);
+  }
+
+  @Test
+  void getScannerEngineMetadata_shouldFail_whenHashComputingFailed() {
+    ScannerEngineHandlerImpl spy = spy(new ScannerEngineHandlerImpl(serverFileSystem));
+    doReturn(new File("no-file")).when(spy).getScannerEngine();
+    assertThatThrownBy(spy::getScannerEngineMetadata)
+      .isInstanceOf(UncheckedIOException.class)
+      .hasMessageContaining("Unable to compute SHA-256 checksum of the Scanner Engine");
+  }
+
+  @Test
+  void getScannerEngine_shouldFail_whenScannerDirNotFound() throws IOException {
+    deleteIfExists(scannerDir);
+    assertThatThrownBy(() -> scannerEngineHandler.getScannerEngine())
+      .isInstanceOf(NotFoundException.class)
+      .hasMessage(format("Scanner directory not found: %s", scannerDir.toAbsolutePath()));
+  }
+
+  @Test
+  void getScannerEngine_shouldReturnScannerJar() throws IOException {
+    File scanner = createTempFile(scannerDir, "scanner", ".jar").toFile();
+
+    File result = scannerEngineHandler.getScannerEngine();
+
+    assertThat(result).isEqualTo(scanner);
+  }
+
+  @Test
+  void getScannerEngine_shouldFail_whenScannerNotFound() throws IOException {
+    Path tempDirectory = createDirectories(scannerDir);
+
+    assertThatThrownBy(() -> scannerEngineHandler.getScannerEngine())
+      .isInstanceOf(NotFoundException.class)
+      .hasMessage(format("Scanner JAR not found in directory: %s", tempDirectory.toAbsolutePath()));
+  }
+
+
+}
index 0723c2d49b14b7b55439179bf5cf0711f91fd91e..f972c1e92839b9b86f7109fc52fb8812dd4bc142 100644 (file)
@@ -24,16 +24,20 @@ import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
 import org.sonar.api.platform.Server;
+import org.sonar.server.platform.ServerFileSystem;
 import org.sonar.server.v2.api.analysis.controller.DefaultJresController;
 import org.sonar.server.v2.api.analysis.controller.DefaultVersionController;
+import org.sonar.server.v2.api.analysis.controller.DefaultScannerEngineController;
 import org.sonar.server.v2.api.analysis.service.JresHandler;
 import org.sonar.server.v2.api.analysis.service.JresHandlerImpl;
+import org.sonar.server.v2.api.analysis.service.ScannerEngineHandler;
+import org.sonar.server.v2.api.analysis.service.ScannerEngineHandlerImpl;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.params.provider.Arguments.arguments;
 import static org.mockito.Mockito.mock;
 
-public class PlatformLevel4WebConfigTest {
+class PlatformLevel4WebConfigTest {
 
   private static final PlatformLevel4WebConfig platformLevel4WebConfig = new PlatformLevel4WebConfig();
 
@@ -41,7 +45,9 @@ public class PlatformLevel4WebConfigTest {
     return Stream.of(
       arguments(platformLevel4WebConfig.versionController(mock(Server.class)), DefaultVersionController.class),
       arguments(platformLevel4WebConfig.jresHandler(), JresHandlerImpl.class),
-      arguments(platformLevel4WebConfig.jresController(mock(JresHandler.class)), DefaultJresController.class)
+      arguments(platformLevel4WebConfig.jresController(mock(JresHandler.class)), DefaultJresController.class),
+      arguments(platformLevel4WebConfig.scannerEngineHandler(mock(ServerFileSystem.class)), ScannerEngineHandlerImpl.class),
+      arguments(platformLevel4WebConfig.scannerEngineController(mock(ScannerEngineHandler.class)), DefaultScannerEngineController.class)
     );
   }