You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

InstalledActionIT.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2023 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.server.plugins.ws;
  21. import com.hazelcast.internal.json.Json;
  22. import com.hazelcast.internal.json.JsonArray;
  23. import com.hazelcast.internal.json.JsonObject;
  24. import com.hazelcast.internal.json.JsonValue;
  25. import com.tngtech.java.junit.dataprovider.DataProvider;
  26. import com.tngtech.java.junit.dataprovider.DataProviderRunner;
  27. import com.tngtech.java.junit.dataprovider.UseDataProvider;
  28. import java.io.File;
  29. import java.io.IOException;
  30. import java.util.Arrays;
  31. import java.util.Optional;
  32. import org.junit.Rule;
  33. import org.junit.Test;
  34. import org.junit.rules.TemporaryFolder;
  35. import org.junit.runner.RunWith;
  36. import org.sonar.api.server.ws.WebService;
  37. import org.sonar.api.server.ws.WebService.Action;
  38. import org.sonar.api.utils.System2;
  39. import org.sonar.core.platform.PluginInfo;
  40. import org.sonar.core.plugin.PluginType;
  41. import org.sonar.db.DbTester;
  42. import org.sonar.db.plugin.PluginDto.Type;
  43. import org.sonar.server.exceptions.ForbiddenException;
  44. import org.sonar.server.plugins.PluginFilesAndMd5.FileAndMd5;
  45. import org.sonar.server.plugins.ServerPlugin;
  46. import org.sonar.server.plugins.ServerPluginRepository;
  47. import org.sonar.server.plugins.UpdateCenterMatrixFactory;
  48. import org.sonar.server.tester.UserSessionRule;
  49. import org.sonar.server.ws.TestRequest;
  50. import org.sonar.server.ws.WsActionTester;
  51. import org.sonar.updatecenter.common.Plugin;
  52. import org.sonar.updatecenter.common.UpdateCenter;
  53. import org.sonar.updatecenter.common.Version;
  54. import static java.util.Arrays.asList;
  55. import static java.util.Collections.singletonList;
  56. import static org.assertj.core.api.Assertions.assertThat;
  57. import static org.assertj.core.api.Assertions.assertThatThrownBy;
  58. import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
  59. import static org.mockito.Mockito.mock;
  60. import static org.mockito.Mockito.verifyNoInteractions;
  61. import static org.mockito.Mockito.verifyNoMoreInteractions;
  62. import static org.mockito.Mockito.when;
  63. import static org.sonar.test.JsonAssert.assertJson;
  64. @RunWith(DataProviderRunner.class)
  65. public class InstalledActionIT {
  66. private static final String JSON_EMPTY_PLUGIN_LIST = "{" +
  67. " \"plugins\":" + "[]" +
  68. "}";
  69. @Rule
  70. public TemporaryFolder temp = new TemporaryFolder();
  71. @Rule
  72. public DbTester db = DbTester.create(System2.INSTANCE);
  73. @Rule
  74. public UserSessionRule userSession = UserSessionRule.standalone().logIn();
  75. private UpdateCenterMatrixFactory updateCenterMatrixFactory = mock(UpdateCenterMatrixFactory.class, RETURNS_DEEP_STUBS);
  76. private ServerPluginRepository serverPluginRepository = mock(ServerPluginRepository.class);
  77. private InstalledAction underTest = new InstalledAction(serverPluginRepository, userSession, updateCenterMatrixFactory, db.getDbClient());
  78. private WsActionTester tester = new WsActionTester(underTest);
  79. @DataProvider
  80. public static Object[][] editionBundledLicenseValues() {
  81. return new Object[][] {
  82. {"sonarsource", "SONARSOURCE"},
  83. {"SonarSource", "SONARSOURCE"},
  84. {"SonaRSOUrce", "SonarSource"},
  85. {"SONARSOURCE", "SonarSource"},
  86. {"commercial", "SONARSOURCE"},
  87. {"Commercial", "SONARSOURCE"},
  88. {"COMMERCIAL", "SonarSource"},
  89. {"COmmERCiaL", "SonarSource"},
  90. };
  91. }
  92. @Test
  93. public void action_installed_is_defined() {
  94. Action action = tester.getDef();
  95. assertThat(action.isPost()).isFalse();
  96. assertThat(action.description()).isNotEmpty();
  97. assertThat(action.responseExample()).isNotNull();
  98. }
  99. @Test
  100. public void empty_array_is_returned_when_there_is_not_plugin_installed() {
  101. String response = tester.newRequest().execute().getInput();
  102. assertJson(response).withStrictArrayOrder().isSimilarTo(JSON_EMPTY_PLUGIN_LIST);
  103. }
  104. @Test
  105. public void empty_array_when_update_center_is_unavailable() {
  106. when(updateCenterMatrixFactory.getUpdateCenter(false)).thenReturn(Optional.empty());
  107. String response = tester.newRequest().execute().getInput();
  108. assertJson(response).withStrictArrayOrder().isSimilarTo(JSON_EMPTY_PLUGIN_LIST);
  109. }
  110. @Test
  111. public void filter_by_plugin_type() throws IOException {
  112. when(serverPluginRepository.getPlugins()).thenReturn(
  113. Arrays.asList(
  114. newInstalledPlugin(new PluginInfo("foo-external-1")
  115. .setName("foo-external-1"),
  116. PluginType.EXTERNAL),
  117. newInstalledPlugin(new PluginInfo("foo-bundled-1")
  118. .setName("foo-bundled-1"),
  119. PluginType.BUNDLED),
  120. newInstalledPlugin(new PluginInfo("foo-external-2")
  121. .setName("foo-external-2"),
  122. PluginType.EXTERNAL)));
  123. db.pluginDbTester().insertPlugin(
  124. p -> p.setKee("foo-external-1"),
  125. p -> p.setType(Type.EXTERNAL),
  126. p -> p.setUpdatedAt(100L));
  127. db.pluginDbTester().insertPlugin(
  128. p -> p.setKee("foo-bundled-1"),
  129. p -> p.setType(Type.BUNDLED),
  130. p -> p.setUpdatedAt(101L));
  131. db.pluginDbTester().insertPlugin(
  132. p -> p.setKee("foo-external-2"),
  133. p -> p.setType(Type.EXTERNAL),
  134. p -> p.setUpdatedAt(102L));
  135. // no type param
  136. String response = tester.newRequest().execute().getInput();
  137. JsonArray jsonArray = Json.parse(response).asObject().get("plugins").asArray();
  138. assertThat(jsonArray).hasSize(3);
  139. assertThat(jsonArray).extracting(JsonValue::asObject)
  140. .extracting(members -> members.get("key").asString())
  141. .containsExactlyInAnyOrder("foo-external-1", "foo-bundled-1", "foo-external-2");
  142. // type param == BUNDLED
  143. response = tester.newRequest().setParam("type", "BUNDLED").execute().getInput();
  144. jsonArray = Json.parse(response).asObject().get("plugins").asArray();
  145. assertThat(jsonArray).hasSize(1);
  146. assertThat(jsonArray).extracting(JsonValue::asObject)
  147. .extracting(members -> members.get("key").asString())
  148. .containsExactlyInAnyOrder("foo-bundled-1");
  149. // type param == EXTERNAL
  150. response = tester.newRequest().setParam("type", "EXTERNAL").execute().getInput();
  151. jsonArray = Json.parse(response).asObject().get("plugins").asArray();
  152. assertThat(jsonArray).hasSize(2);
  153. assertThat(jsonArray).extracting(JsonValue::asObject)
  154. .extracting(members -> members.get("key").asString())
  155. .containsExactlyInAnyOrder("foo-external-1", "foo-external-2");
  156. }
  157. @Test
  158. public void empty_fields_are_not_serialized_to_json() throws IOException {
  159. when(serverPluginRepository.getPlugins()).thenReturn(
  160. singletonList(newInstalledPlugin(new PluginInfo("foo")
  161. .setName(null)
  162. .setDescription(null)
  163. .setLicense(null)
  164. .setOrganizationName(null)
  165. .setOrganizationUrl(null)
  166. .setImplementationBuild(null)
  167. .setHomepageUrl(null)
  168. .setIssueTrackerUrl(null))));
  169. db.pluginDbTester().insertPlugin(
  170. p -> p.setKee("foo"),
  171. p -> p.setType(Type.EXTERNAL),
  172. p -> p.setUpdatedAt(100L));
  173. String response = tester.newRequest().execute().getInput();
  174. JsonObject json = Json.parse(response).asObject().get("plugins").asArray().get(0).asObject();
  175. assertThat(json.get("key")).isNotNull();
  176. assertThat(json.get("name")).isNotNull();
  177. assertThat(json.get("description")).isNull();
  178. assertThat(json.get("license")).isNull();
  179. assertThat(json.get("organizationName")).isNull();
  180. assertThat(json.get("organizationUrl")).isNull();
  181. assertThat(json.get("homepageUrl")).isNull();
  182. assertThat(json.get("issueTrackerUrl")).isNull();
  183. assertThat(json.get("requiredForLanguage")).isNull();
  184. }
  185. private ServerPlugin newInstalledPlugin(PluginInfo plugin) throws IOException {
  186. return newInstalledPlugin(plugin, PluginType.BUNDLED);
  187. }
  188. private ServerPlugin newInstalledPlugin(PluginInfo plugin, PluginType type) throws IOException {
  189. FileAndMd5 jar = new FileAndMd5(temp.newFile());
  190. return new ServerPlugin(plugin, type, null, jar, null);
  191. }
  192. @Test
  193. public void return_default_fields() throws Exception {
  194. ServerPlugin plugin = newInstalledPlugin(new PluginInfo("foo")
  195. .setName("plugName")
  196. .setDescription("desc_it")
  197. .setVersion(Version.create("1.0"))
  198. .setLicense("license_hey")
  199. .setOrganizationName("org_name")
  200. .setOrganizationUrl("org_url")
  201. .setHomepageUrl("homepage_url")
  202. .setIssueTrackerUrl("issueTracker_url")
  203. .setImplementationBuild("sou_rev_sha1")
  204. .addRequiredForLanguage("bar")
  205. .setSonarLintSupported(true));
  206. when(serverPluginRepository.getPlugins()).thenReturn(singletonList(plugin));
  207. db.pluginDbTester().insertPlugin(
  208. p -> p.setKee(plugin.getPluginInfo().getKey()),
  209. p -> p.setType(Type.valueOf(plugin.getType().name())),
  210. p -> p.setUpdatedAt(100L));
  211. String response = tester.newRequest().execute().getInput();
  212. verifyNoMoreInteractions(updateCenterMatrixFactory);
  213. String expected = String.format("""
  214. {
  215. "plugins":
  216. [
  217. {
  218. "key": "foo",
  219. "name": "plugName",
  220. "description": "desc_it",
  221. "version": "1.0",
  222. "license": "license_hey",
  223. "organizationName": "org_name",
  224. "organizationUrl": "org_url",
  225. "editionBundled": false,
  226. "homepageUrl": "homepage_url",
  227. "issueTrackerUrl": "issueTracker_url",
  228. "implementationBuild": "sou_rev_sha1",
  229. "sonarLintSupported": true,
  230. "filename": "%s",
  231. "hash": "%s",
  232. "updatedAt": 100,
  233. "requiredForLanguages": ["bar"]
  234. }
  235. ]
  236. }""", plugin.getJar().getFile().getName(), plugin.getJar().getMd5());
  237. assertJson(response).isSimilarTo(expected);
  238. }
  239. @Test
  240. public void category_is_returned_when_in_additional_fields() throws Exception {
  241. String jarFilename = getClass().getSimpleName() + "/" + "some.jar";
  242. File jar = new File(getClass().getResource(jarFilename).toURI());
  243. when(serverPluginRepository.getPlugins()).thenReturn(asList(
  244. newInstalledPlugin(new PluginInfo("plugKey")
  245. .setName("plugName")
  246. .setDescription("desc_it")
  247. .setVersion(Version.create("1.0"))
  248. .setLicense("license_hey")
  249. .setOrganizationName("org_name")
  250. .setOrganizationUrl("org_url")
  251. .setHomepageUrl("homepage_url")
  252. .setIssueTrackerUrl("issueTracker_url")
  253. .setImplementationBuild("sou_rev_sha1"))));
  254. // .setJarFile(jar), new FileAndMd5(jar), null
  255. UpdateCenter updateCenter = mock(UpdateCenter.class);
  256. when(updateCenterMatrixFactory.getUpdateCenter(false)).thenReturn(Optional.of(updateCenter));
  257. when(updateCenter.findAllCompatiblePlugins()).thenReturn(
  258. asList(
  259. Plugin.factory("plugKey")
  260. .setCategory("cat_1")));
  261. db.pluginDbTester().insertPlugin(
  262. p -> p.setKee("plugKey"),
  263. p -> p.setType(Type.EXTERNAL),
  264. p -> p.setFileHash("abcdplugKey"),
  265. p -> p.setUpdatedAt(111111L));
  266. String response = tester.newRequest()
  267. .setParam(WebService.Param.FIELDS, "category")
  268. .execute().getInput();
  269. assertJson(response).isSimilarTo(
  270. "{" +
  271. " \"plugins\":" +
  272. " [" +
  273. " {" +
  274. " \"key\": \"plugKey\"," +
  275. " \"name\": \"plugName\"," +
  276. " \"description\": \"desc_it\"," +
  277. " \"version\": \"1.0\"," +
  278. " \"category\":\"cat_1\"," +
  279. " \"license\": \"license_hey\"," +
  280. " \"organizationName\": \"org_name\"," +
  281. " \"organizationUrl\": \"org_url\",\n" +
  282. " \"editionBundled\": false," +
  283. " \"homepageUrl\": \"homepage_url\"," +
  284. " \"issueTrackerUrl\": \"issueTracker_url\"," +
  285. " \"implementationBuild\": \"sou_rev_sha1\"" +
  286. " }" +
  287. " ]" +
  288. "}");
  289. }
  290. @Test
  291. public void plugins_are_sorted_by_name_then_key_and_only_one_plugin_can_have_a_specific_name() throws IOException {
  292. when(serverPluginRepository.getPlugins()).thenReturn(
  293. asList(
  294. plugin("A", "name2"),
  295. plugin("B", "name1"),
  296. plugin("C", "name0"),
  297. plugin("D", "name0")));
  298. db.pluginDbTester().insertPlugin(
  299. p -> p.setKee("A"),
  300. p -> p.setType(Type.EXTERNAL),
  301. p -> p.setFileHash("abcdA"),
  302. p -> p.setUpdatedAt(111111L));
  303. db.pluginDbTester().insertPlugin(
  304. p -> p.setKee("B"),
  305. p -> p.setType(Type.EXTERNAL),
  306. p -> p.setFileHash("abcdB"),
  307. p -> p.setUpdatedAt(222222L));
  308. db.pluginDbTester().insertPlugin(
  309. p -> p.setKee("C"),
  310. p -> p.setType(Type.EXTERNAL),
  311. p -> p.setFileHash("abcdC"),
  312. p -> p.setUpdatedAt(333333L));
  313. db.pluginDbTester().insertPlugin(
  314. p -> p.setKee("D"),
  315. p -> p.setType(Type.EXTERNAL),
  316. p -> p.setFileHash("abcdD"),
  317. p -> p.setUpdatedAt(444444L));
  318. String resp = tester.newRequest().execute().getInput();
  319. assertJson(resp).withStrictArrayOrder().isSimilarTo(
  320. "{" +
  321. " \"plugins\":" +
  322. " [" +
  323. " {\"key\": \"C\"}" + "," +
  324. " {\"key\": \"D\"}" + "," +
  325. " {\"key\": \"B\"}" + "," +
  326. " {\"key\": \"A\"}" +
  327. " ]" +
  328. "}");
  329. }
  330. @Test
  331. @UseDataProvider("editionBundledLicenseValues")
  332. public void commercial_plugins_from_SonarSource_has_flag_editionBundled_true_based_on_jar_info(String license, String organization) throws Exception {
  333. String jarFilename = getClass().getSimpleName() + "/" + "some.jar";
  334. String pluginKey = "plugKey";
  335. File jar = new File(getClass().getResource(jarFilename).toURI());
  336. when(serverPluginRepository.getPlugins()).thenReturn(asList(
  337. new ServerPlugin(new PluginInfo(pluginKey)
  338. .setName("plugName")
  339. .setVersion(Version.create("1.0"))
  340. .setLicense(license)
  341. .setOrganizationName(organization)
  342. .setImplementationBuild("sou_rev_sha1"),
  343. PluginType.BUNDLED,
  344. null,
  345. new FileAndMd5(jar), null)));
  346. db.pluginDbTester().insertPlugin(
  347. p -> p.setKee(pluginKey),
  348. p -> p.setType(Type.BUNDLED),
  349. p -> p.setFileHash("abcdplugKey"),
  350. p -> p.setUpdatedAt(111111L));
  351. // ensure flag editionBundled is computed from jar info by enabling datacenter with other organization and license values
  352. UpdateCenter updateCenter = mock(UpdateCenter.class);
  353. when(updateCenterMatrixFactory.getUpdateCenter(false)).thenReturn(Optional.of(updateCenter));
  354. when(updateCenter.findAllCompatiblePlugins()).thenReturn(
  355. singletonList(
  356. Plugin.factory(pluginKey)
  357. .setOrganization("foo")
  358. .setLicense("bar")
  359. .setCategory("cat_1")));
  360. String response = tester.newRequest().execute().getInput();
  361. verifyNoInteractions(updateCenterMatrixFactory);
  362. assertJson(response)
  363. .isSimilarTo("{" +
  364. " \"plugins\":" +
  365. " [" +
  366. " {" +
  367. " \"key\": \"plugKey\"," +
  368. " \"name\": \"plugName\"," +
  369. " \"license\": \"" + license + "\"," +
  370. " \"organizationName\": \"" + organization + "\"," +
  371. " \"editionBundled\": true" +
  372. " }" +
  373. " ]" +
  374. "}");
  375. }
  376. @Test
  377. public void only_one_plugin_can_have_a_specific_name_and_key() throws IOException {
  378. when(serverPluginRepository.getPlugins()).thenReturn(
  379. asList(
  380. plugin("A", "name2"),
  381. plugin("A", "name2")));
  382. db.pluginDbTester().insertPlugin(
  383. p -> p.setKee("A"),
  384. p -> p.setType(Type.EXTERNAL),
  385. p -> p.setFileHash("abcdA"),
  386. p -> p.setUpdatedAt(111111L));
  387. String response = tester.newRequest().execute().getInput();
  388. assertJson(response).withStrictArrayOrder().isSimilarTo(
  389. "{" +
  390. " \"plugins\":" +
  391. " [" +
  392. " {\"key\": \"A\"}" +
  393. " ]" +
  394. "}");
  395. assertThat(response).containsOnlyOnce("name2");
  396. }
  397. @Test
  398. public void fail_if_not_logged_in() {
  399. userSession.anonymous();
  400. TestRequest testRequest = tester.newRequest();
  401. assertThatThrownBy(testRequest::execute)
  402. .isInstanceOf(ForbiddenException.class);
  403. }
  404. private ServerPlugin plugin(String key, String name) throws IOException {
  405. File file = temp.newFile();
  406. PluginInfo info = new PluginInfo(key)
  407. .setName(name)
  408. .setVersion(Version.create("1.0"));
  409. info.setJarFile(file);
  410. return new ServerPlugin(info, PluginType.BUNDLED, null, new FileAndMd5(file), null);
  411. }
  412. }