3 * Copyright (C) 2009-2024 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
20 package org.sonar.server.v2.api.rule.controller;
22 import com.google.gson.Gson;
23 import com.google.gson.GsonBuilder;
24 import java.util.List;
25 import javax.annotation.Nullable;
26 import org.junit.Before;
27 import org.junit.Rule;
28 import org.junit.Test;
29 import org.junit.runner.RunWith;
30 import org.mockito.junit.MockitoJUnitRunner;
31 import org.sonar.api.resources.Languages;
32 import org.sonar.api.rule.RuleKey;
33 import org.sonar.db.rule.RuleDescriptionSectionDto;
34 import org.sonar.db.rule.RuleDto;
35 import org.sonar.db.rule.RuleTesting;
36 import org.sonar.server.common.rule.ReactivationException;
37 import org.sonar.server.common.rule.service.RuleInformation;
38 import org.sonar.server.common.rule.service.RuleService;
39 import org.sonar.server.common.text.MacroInterpreter;
40 import org.sonar.server.language.LanguageTesting;
41 import org.sonar.server.rule.RuleDescriptionFormatter;
42 import org.sonar.server.tester.UserSessionRule;
43 import org.sonar.server.v2.api.ControllerTester;
44 import org.sonar.server.v2.api.rule.converter.RuleRestResponseGenerator;
45 import org.sonar.server.v2.api.rule.enums.CleanCodeAttributeRestEnum;
46 import org.sonar.server.v2.api.rule.enums.ImpactSeverityRestEnum;
47 import org.sonar.server.v2.api.rule.enums.RuleStatusRestEnum;
48 import org.sonar.server.v2.api.rule.enums.SoftwareQualityRestEnum;
49 import org.sonar.server.v2.api.rule.request.RuleCreateRestRequest;
50 import org.sonar.server.v2.api.rule.resource.Impact;
51 import org.sonar.server.v2.api.rule.resource.Parameter;
52 import org.sonar.server.v2.api.rule.response.RuleRestResponse;
53 import org.springframework.http.MediaType;
54 import org.springframework.test.web.servlet.MockMvc;
55 import org.springframework.test.web.servlet.MvcResult;
57 import static org.assertj.core.api.Assertions.assertThat;
58 import static org.mockito.ArgumentMatchers.any;
59 import static org.mockito.ArgumentMatchers.anyString;
60 import static org.mockito.Mockito.mock;
61 import static org.mockito.Mockito.when;
62 import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFILES;
63 import static org.sonar.server.v2.WebApiEndpoints.RULES_ENDPOINT;
64 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
65 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
66 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
68 @RunWith(MockitoJUnitRunner.class)
69 public class DefaultRuleControllerTest {
72 public UserSessionRule userSession = UserSessionRule.standalone();
74 private final RuleService ruleService = mock(RuleService.class);
75 private final Languages languages = mock(Languages.class);
76 private final MacroInterpreter macroInterpreter = mock(MacroInterpreter.class);
77 private final RuleDescriptionFormatter ruleDescriptionFormatter = mock(RuleDescriptionFormatter.class);
78 private final RuleRestResponseGenerator ruleRestResponseGenerator = new RuleRestResponseGenerator(languages, macroInterpreter,
79 ruleDescriptionFormatter);
81 private final MockMvc mockMvc = ControllerTester.getMockMvc(new DefaultRuleController(userSession, ruleService,
82 ruleRestResponseGenerator));
83 private static final Gson gson = new GsonBuilder().create();
87 when(macroInterpreter.interpret(anyString())).thenAnswer(invocation -> "interpreted" + invocation.getArgument(0));
88 when(ruleDescriptionFormatter.toHtml(any(), any())).thenAnswer(invocation -> "html" + ((RuleDescriptionSectionDto) invocation.getArgument(1)).getContent());
89 when(languages.get(anyString())).thenAnswer(invocation -> LanguageTesting.newLanguage(invocation.getArgument(0, String.class),
94 public void create_shouldReturnRule() throws Exception {
95 userSession.logIn().addPermission(ADMINISTER_QUALITY_PROFILES);
97 RuleInformation ruleInformation = generateRuleInformation();
98 when(ruleService.createCustomRule(any())).thenReturn(ruleInformation);
100 MvcResult mvcResult = mockMvc.perform(
102 .contentType(MediaType.APPLICATION_JSON_VALUE)
103 .content(gson.toJson(newRequest())))
104 .andExpect(status().isOk())
107 RuleRestResponse response = gson.fromJson(mvcResult.getResponse().getContentAsString(), RuleRestResponse.class);
108 assertThat(response).isEqualTo(ruleRestResponseGenerator.toRuleRestResponse(ruleInformation));
112 public void create_whenNotLoggedIn_shouldFailWithUnauthorized() throws Exception {
115 .contentType(MediaType.APPLICATION_JSON_VALUE)
116 .content(gson.toJson(newRequest())))
117 .andExpectAll(status().isUnauthorized(), content().json("{message:'Authentication is required'}"));
121 public void create_whenNoPermission_shouldFailWithForbidden() throws Exception {
126 .contentType(MediaType.APPLICATION_JSON_VALUE)
127 .content(gson.toJson(newRequest())))
128 .andExpectAll(status().isForbidden(), content().json("{message:'Insufficient privileges'}"));
132 public void create_whenReactivationExceptionThrown_shouldFailWithConflict() throws Exception {
133 userSession.logIn().addPermission(ADMINISTER_QUALITY_PROFILES);
135 String errorMessage = "reactivation_exception";
136 when(ruleService.createCustomRule(any())).thenThrow(new ReactivationException(errorMessage, null));
140 .contentType(MediaType.APPLICATION_JSON_VALUE)
141 .content(gson.toJson(newRequest())))
142 .andExpectAll(status().isConflict(), content().json(String.format("{message:'%s'}", errorMessage)));
146 public void create_whenMissingBodyField_shouldFailWithBadRequest() throws Exception {
147 userSession.logIn().addPermission(ADMINISTER_QUALITY_PROFILES);
149 RuleCreateRestRequest request = newRequest(null);
153 .contentType(MediaType.APPLICATION_JSON_VALUE)
154 .content(gson.toJson(request)))
155 .andExpectAll(status().isBadRequest(), content().json("{message:'Value {} for field name was rejected. Error: must not be null.'}"));
159 public void create_whenInvalidParam_shouldFailWithBadRequest() throws Exception {
160 userSession.logIn().addPermission(ADMINISTER_QUALITY_PROFILES);
162 RuleCreateRestRequest request = newRequest("a".repeat(201));
166 .contentType(MediaType.APPLICATION_JSON_VALUE)
167 .content(gson.toJson(request)))
168 .andExpectAll(status().isBadRequest(),
169 content().json(String.format("{message:'Value %s for field name was rejected. Error: size must be between 0 and 200.'}",
173 private static RuleCreateRestRequest newRequest() {
174 return newRequest("custom rule name");
177 private static RuleCreateRestRequest newRequest(@Nullable String name) {
178 return new RuleCreateRestRequest(
180 "java:template_rule",
183 RuleStatusRestEnum.BETA,
184 List.of(new Parameter("key1", "desc", "value1", "text")),
185 CleanCodeAttributeRestEnum.MODULAR,
186 List.of(new Impact(SoftwareQualityRestEnum.MAINTAINABILITY, ImpactSeverityRestEnum.LOW)));
189 private RuleInformation generateRuleInformation() {
190 RuleDto ruleDto = RuleTesting.newCustomRule(RuleTesting.newTemplateRule(RuleKey.parse("java:template_rule")));
191 RuleTesting.newRuleParam(ruleDto);
192 return new RuleInformation(ruleDto, List.of(RuleTesting.newRuleParam(ruleDto), RuleTesting.newRuleParam(ruleDto)));