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.

Tester.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 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.sonarqube.ws.tester;
  21. import com.google.common.base.Preconditions;
  22. import com.sonar.orchestrator.Orchestrator;
  23. import com.sonar.orchestrator.container.Edition;
  24. import com.sonar.orchestrator.container.Server;
  25. import com.sonar.orchestrator.junit4.OrchestratorRule;
  26. import java.sql.Connection;
  27. import java.sql.PreparedStatement;
  28. import java.sql.SQLException;
  29. import java.util.List;
  30. import java.util.function.Consumer;
  31. import java.util.stream.Collectors;
  32. import javax.annotation.Nullable;
  33. import org.junit.jupiter.api.extension.AfterAllCallback;
  34. import org.junit.jupiter.api.extension.AfterEachCallback;
  35. import org.junit.jupiter.api.extension.BeforeAllCallback;
  36. import org.junit.jupiter.api.extension.BeforeEachCallback;
  37. import org.junit.jupiter.api.extension.ExtensionContext;
  38. import org.junit.rules.ExternalResource;
  39. import org.sonarqube.ws.Ce;
  40. import org.sonarqube.ws.client.HttpConnector;
  41. import org.sonarqube.ws.client.WsClient;
  42. import org.sonarqube.ws.client.WsClientFactories;
  43. import org.sonarqube.ws.client.ce.ActivityRequest;
  44. import static com.sonar.orchestrator.container.Edition.DEVELOPER;
  45. import static com.sonar.orchestrator.container.Edition.ENTERPRISE;
  46. import static java.lang.String.format;
  47. import static java.util.Arrays.stream;
  48. import static org.sonarqube.ws.client.HttpConnector.DEFAULT_READ_TIMEOUT_MILLISECONDS;
  49. /**
  50. * This JUnit rule wraps an {@link OrchestratorRule} instance and provides :
  51. * <ul>
  52. * <li>clean-up of users between tests</li>
  53. * <li>clean-up of session when opening a browser (cookies, local storage)</li>
  54. * <li>quick access to {@link WsClient} instances</li>
  55. * <li>clean-up of defined settings. Properties that are not defined by a plugin are not reset.</li>
  56. * <li>helpers to generate users</li>
  57. * </ul>
  58. * <p>
  59. * Recommendation is to define a {@code @Rule} instance. If not possible, then
  60. * {@code @ClassRule} must be used through a {@link org.junit.rules.RuleChain}
  61. * around {@link OrchestratorRule}.
  62. * <p>
  63. * Not supported:
  64. * <ul>
  65. * <li>clean-up global settings</li>
  66. * <li>clean-up system administrators/roots</li>
  67. * <li>clean-up the properties that are not defined (no PropertyDefinition)</li>
  68. * </ul>
  69. *
  70. * When used with JUnit5, the tester can be started and stopped in the same pattern as Junit4 for @ClassRule or @Rule using the flag #useJunit5ClassInitialization
  71. */
  72. public class Tester extends ExternalResource implements TesterSession, BeforeEachCallback, AfterEachCallback, BeforeAllCallback, AfterAllCallback {
  73. static final String FORCE_AUTHENTICATION_PROPERTY_NAME = "sonar.forceAuthentication";
  74. private final Orchestrator orchestrator;
  75. private boolean enableForceAuthentication = false;
  76. private Elasticsearch elasticsearch = null;
  77. // initialized in #before()
  78. private boolean beforeCalled = false;
  79. private TesterSession rootSession;
  80. private final int readTimeoutMilliseconds;
  81. /**
  82. * Defines if the tester is executed at class level or method level in the Junit5 test.
  83. * If true, the tester will be started and stopped at the class level.
  84. */
  85. private boolean classLevel = false;
  86. public Tester(OrchestratorRule orchestrator) {
  87. this(orchestrator, DEFAULT_READ_TIMEOUT_MILLISECONDS);
  88. }
  89. public Tester(OrchestratorRule orchestrator, int readTimeoutMilliseconds) {
  90. this(orchestrator.getOrchestrator(), readTimeoutMilliseconds);
  91. }
  92. public Tester(Orchestrator orchestrator) {
  93. this(orchestrator, DEFAULT_READ_TIMEOUT_MILLISECONDS);
  94. }
  95. public Tester(Orchestrator orchestrator, int readTimeoutMilliseconds) {
  96. this.orchestrator = orchestrator;
  97. this.readTimeoutMilliseconds = readTimeoutMilliseconds;
  98. }
  99. public Tester enableForceAuthentication() {
  100. verifyNotStarted();
  101. enableForceAuthentication = true;
  102. return this;
  103. }
  104. /**
  105. * Enable class level initialization for Junit5.
  106. * Should only be used with Junit5.
  107. *
  108. * @return Tester
  109. */
  110. public Tester withClassLevel() {
  111. classLevel = true;
  112. return this;
  113. }
  114. @Override
  115. public void before() {
  116. verifyNotStarted();
  117. rootSession = new TesterSessionImpl(orchestrator,
  118. httpConnectorBuilder -> httpConnectorBuilder.readTimeoutMilliseconds(readTimeoutMilliseconds),
  119. httpConnectorBuilder -> httpConnectorBuilder.credentials("admin", "admin"));
  120. setForceAuthentication(enableForceAuthentication);
  121. beforeCalled = true;
  122. }
  123. @Override
  124. public void after() {
  125. waitForCeTasksToFinish();
  126. deactivateScim();
  127. users().deleteAll();
  128. projects().deleteAll();
  129. settings().deleteAll();
  130. qGates().deleteAll();
  131. qProfiles().deleteAll();
  132. webhooks().deleteAllGlobal();
  133. almSettings().deleteAll();
  134. groups().deleteAllGenerated();
  135. Edition edition = orchestrator.getDistribution().getEdition();
  136. if (edition.equals(ENTERPRISE)) {
  137. applications().deleteAll();
  138. views().deleteAll();
  139. applications().deleteAll();
  140. } else if (edition.equals(DEVELOPER)) {
  141. applications().deleteAll();
  142. }
  143. setForceAuthentication(enableForceAuthentication);
  144. }
  145. public void deactivateScim() {
  146. try (Connection connection = orchestrator.getDatabase().openConnection();
  147. PreparedStatement preparedStatement = connection.prepareStatement("delete from internal_properties where kee = ?")) {
  148. preparedStatement.setString(1, "sonar.scim.enabled");
  149. preparedStatement.execute();
  150. } catch (SQLException e) {
  151. throw new IllegalStateException(e);
  152. }
  153. }
  154. private void setForceAuthentication(boolean enableForceAuthentication) {
  155. String serverProperty = orchestrator.getDistribution().getServerProperty(FORCE_AUTHENTICATION_PROPERTY_NAME);
  156. if (serverProperty != null) {
  157. Preconditions.checkArgument(enableForceAuthentication == Boolean.parseBoolean(serverProperty),
  158. "This test was expecting to have authentication configured, but server property configuration has mismatched.");
  159. return;
  160. }
  161. if (enableForceAuthentication) {
  162. settings().resetSettings(FORCE_AUTHENTICATION_PROPERTY_NAME);
  163. } else {
  164. settings().setGlobalSetting(FORCE_AUTHENTICATION_PROPERTY_NAME, Boolean.toString(false));
  165. }
  166. }
  167. private void waitForCeTasksToFinish() {
  168. // Let's try to wait for 30s for in progress or pending tasks to finish
  169. int counter = 60;
  170. while (counter > 0 &&
  171. wsClient().ce().activity(new ActivityRequest().setStatus(List.of("PENDING", "IN_PROGRESS"))).getTasksCount() != 0) {
  172. counter--;
  173. try {
  174. Thread.sleep(500);
  175. } catch (InterruptedException e) {
  176. Thread.currentThread().interrupt();
  177. break;
  178. }
  179. }
  180. Ce.ActivityResponse activity = wsClient().ce().activity(new ActivityRequest().setStatus(List.of("PENDING", "IN_PROGRESS")));
  181. if (activity.getTasksCount() != 0) {
  182. throw new IllegalStateException(format("Waiting for 30 seconds for tasks to finish but there are still ce tasks : %n %s",
  183. activity.getTasksList().stream()
  184. .map(t -> format("analysisId: [%s] type: [%s] componentName: [%s]", t.getAnalysisId(), t.getType(), t.getComponentName()))
  185. .collect(Collectors.joining("\n"))));
  186. }
  187. }
  188. public TesterSession asAnonymous() {
  189. return as(null, null);
  190. }
  191. public TesterSession as(String login) {
  192. return as(login, login);
  193. }
  194. public TesterSession as(String login, String password) {
  195. verifyStarted();
  196. return new TesterSessionImpl(orchestrator, login, password);
  197. }
  198. public TesterSession withSystemPassCode(String systemPassCode) {
  199. verifyStarted();
  200. return new TesterSessionImpl(orchestrator, systemPassCode);
  201. }
  202. public Elasticsearch elasticsearch() {
  203. if (elasticsearch != null) {
  204. return elasticsearch;
  205. }
  206. elasticsearch = new Elasticsearch(orchestrator.getServer().getSearchPort());
  207. return elasticsearch;
  208. }
  209. private void verifyNotStarted() {
  210. if (beforeCalled) {
  211. throw new IllegalStateException("org.sonarqube.ws.tester.Tester should not be already started");
  212. }
  213. }
  214. private void verifyStarted() {
  215. if (!beforeCalled) {
  216. throw new IllegalStateException("org.sonarqube.ws.tester.Tester is not started yet");
  217. }
  218. }
  219. /**
  220. * Web service client configured with root access
  221. */
  222. @Override
  223. public WsClient wsClient() {
  224. verifyStarted();
  225. return rootSession.wsClient();
  226. }
  227. @Override
  228. public GroupTester groups() {
  229. return rootSession.groups();
  230. }
  231. @Override
  232. public ProjectTester projects() {
  233. return rootSession.projects();
  234. }
  235. @Override
  236. public QModelTester qModel() {
  237. return rootSession.qModel();
  238. }
  239. @Override
  240. public QProfileTester qProfiles() {
  241. return rootSession.qProfiles();
  242. }
  243. @Override
  244. public UserTester users() {
  245. return rootSession.users();
  246. }
  247. @Override
  248. public SettingTester settings() {
  249. return rootSession.settings();
  250. }
  251. @Override
  252. public NewCodePeriodTester newCodePeriods() {
  253. return rootSession.newCodePeriods();
  254. }
  255. @Override
  256. public QGateTester qGates() {
  257. return rootSession.qGates();
  258. }
  259. @Override
  260. public WebhookTester webhooks() {
  261. return rootSession.webhooks();
  262. }
  263. @Override
  264. public PermissionTester permissions() {
  265. return rootSession.permissions();
  266. }
  267. @Override
  268. public ViewTester views() {
  269. return rootSession.views();
  270. }
  271. @Override
  272. public ApplicationTester applications() {
  273. return rootSession.applications();
  274. }
  275. @Override
  276. public MeasureTester measures() {
  277. return rootSession.measures();
  278. }
  279. @Override
  280. public AlmSettingsTester almSettings() {
  281. return rootSession.almSettings();
  282. }
  283. @Override
  284. public void afterAll(ExtensionContext context) throws Exception {
  285. if (classLevel) {
  286. after();
  287. }
  288. }
  289. @Override
  290. public void afterEach(ExtensionContext context) throws Exception {
  291. if (!classLevel) {
  292. after();
  293. }
  294. }
  295. @Override
  296. public void beforeAll(ExtensionContext context) throws Exception {
  297. if (classLevel) {
  298. before();
  299. }
  300. }
  301. @Override
  302. public void beforeEach(ExtensionContext context) throws Exception {
  303. if (!classLevel) {
  304. before();
  305. }
  306. }
  307. private static class TesterSessionImpl implements TesterSession {
  308. private final WsClient client;
  309. private TesterSessionImpl(Orchestrator orchestrator, @Nullable String login, @Nullable String password) {
  310. Server server = orchestrator.getServer();
  311. this.client = WsClientFactories.getDefault().newClient(HttpConnector.newBuilder()
  312. .acceptGzip(true)
  313. .url(server.getUrl())
  314. .credentials(login, password)
  315. .build());
  316. }
  317. private TesterSessionImpl(Orchestrator orchestrator, @Nullable String systemPassCode) {
  318. Server server = orchestrator.getServer();
  319. this.client = WsClientFactories.getDefault().newClient(HttpConnector.newBuilder()
  320. .acceptGzip(true)
  321. .systemPassCode(systemPassCode)
  322. .url(server.getUrl())
  323. .build());
  324. }
  325. private TesterSessionImpl(Orchestrator orchestrator, Consumer<HttpConnector.Builder>... httpConnectorPopulators) {
  326. Server server = orchestrator.getServer();
  327. HttpConnector.Builder httpConnectorBuilder = HttpConnector.newBuilder()
  328. .acceptGzip(true)
  329. .url(server.getUrl());
  330. stream(httpConnectorPopulators).forEach(populator -> populator.accept(httpConnectorBuilder));
  331. this.client = WsClientFactories.getDefault().newClient(httpConnectorBuilder.build());
  332. }
  333. @Override
  334. public WsClient wsClient() {
  335. return client;
  336. }
  337. @Override
  338. public GroupTester groups() {
  339. return new GroupTester(this);
  340. }
  341. @Override
  342. public ProjectTester projects() {
  343. return new ProjectTester(this);
  344. }
  345. @Override
  346. public QModelTester qModel() {
  347. return new QModelTester(this);
  348. }
  349. @Override
  350. public QProfileTester qProfiles() {
  351. return new QProfileTester(this);
  352. }
  353. @Override
  354. public UserTester users() {
  355. return new UserTester(this);
  356. }
  357. @Override
  358. public SettingTester settings() {
  359. return new SettingTester(this);
  360. }
  361. @Override
  362. public NewCodePeriodTester newCodePeriods() {
  363. return new NewCodePeriodTester(this);
  364. }
  365. @Override
  366. public QGateTester qGates() {
  367. return new QGateTester(this);
  368. }
  369. @Override
  370. public WebhookTester webhooks() {
  371. return new WebhookTester(this);
  372. }
  373. @Override
  374. public PermissionTester permissions() {
  375. return new PermissionTester(this);
  376. }
  377. @Override
  378. public ViewTester views() {
  379. return new ViewTester(this);
  380. }
  381. @Override
  382. public ApplicationTester applications() {
  383. return new ApplicationTester(this);
  384. }
  385. @Override
  386. public MeasureTester measures() {
  387. return new MeasureTester(this);
  388. }
  389. @Override
  390. public AlmSettingsTester almSettings() {
  391. return new AlmSettingsTester(this);
  392. }
  393. }
  394. }