From 097437f96c9d57375e7537da53a5a95bc096655a Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Fri, 25 Apr 2014 20:42:57 +0200 Subject: [PATCH] Complete WebService with new param metadata --- .../org/sonar/api/server/ws/WebService.java | 65 ++++++++++++++++-- sonar-server/pom.xml | 13 +++- .../org/sonar/server/rule2/RuleService.java | 35 ++++++++++ .../server/rule2/ws/RulesWebService.java | 46 +++++++++++++ .../sonar/server/rule2/ws/SearchAction.java | 66 +++++++++++++++++++ .../org/sonar/server/rule2/ws/ShowAction.java | 57 ++++++++++++++++ .../sonar/server/rule2/ws/package-info.java | 23 +++++++ .../issue/filter/IssueFilterWsTest.java | 2 +- .../issue/ws/IssueShowWsHandlerTest.java | 2 +- .../sonar/server/issue/ws/IssuesWsTest.java | 2 +- .../platform/ws/RestartHandlerTest.java | 2 +- .../org/sonar/server/plugins/BatchWsTest.java | 2 +- .../qualitygate/ws/QgateAppHandlerTest.java | 2 +- .../qualitygate/ws/QualityGatesWsTest.java | 24 +++---- .../ws/QProfileBackupWsHandlerTest.java | 2 +- .../qualityprofile/ws/QProfilesWsTest.java | 2 +- .../server/rule/ws/AddTagsWsHandlerTest.java | 2 +- .../rule/ws/RemoveTagsWsHandlerTest.java | 2 +- .../rule/ws/RuleSearchWsHandlerTest.java | 2 +- .../server/rule/ws/RuleShowWsHandlerTest.java | 2 +- .../sonar/server/rule/ws/RuleTagsWsTest.java | 2 +- .../org/sonar/server/rule/ws/RulesWsTest.java | 2 +- .../server/rule2/ws/RulesWebServiceTest.java | 48 ++++++++++++++ .../source/ws/SourcesShowWsHandlerTest.java | 2 +- .../sonar/server/source/ws/SourcesWsTest.java | 2 +- .../java/org/sonar}/server/ws/WsTester.java | 5 +- 26 files changed, 379 insertions(+), 35 deletions(-) create mode 100644 sonar-server/src/main/java/org/sonar/server/rule2/RuleService.java create mode 100644 sonar-server/src/main/java/org/sonar/server/rule2/ws/RulesWebService.java create mode 100644 sonar-server/src/main/java/org/sonar/server/rule2/ws/SearchAction.java create mode 100644 sonar-server/src/main/java/org/sonar/server/rule2/ws/ShowAction.java create mode 100644 sonar-server/src/main/java/org/sonar/server/rule2/ws/package-info.java create mode 100644 sonar-server/src/test/java/org/sonar/server/rule2/ws/RulesWebServiceTest.java rename {sonar-testing-harness/src/main/java/org/sonar/api => sonar-server/src/test/java/org/sonar}/server/ws/WsTester.java (97%) diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java index a4725146bdd..0dbcfd0aa5c 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java @@ -35,10 +35,10 @@ import java.util.Map; /** * Defines a web service. Note that contrary to the deprecated {@link org.sonar.api.web.Webservice} * the ws is fully implemented in Java and does not require any Ruby on Rails code. - * + *

*

* The classes implementing this extension point must be declared in {@link org.sonar.api.SonarPlugin#getExtensions()}. - * + *

*

How to use

*
  * public class HelloWs implements WebService {
@@ -370,7 +370,9 @@ public interface WebService extends ServerExtension {
   }
 
   class NewParam {
-    private String key, description;
+    private String key, description, exampleValue;
+    private boolean required = false;
+    private String[] possibleValues = null;
 
     private NewParam(String key) {
       this.key = key;
@@ -381,6 +383,33 @@ public interface WebService extends ServerExtension {
       return this;
     }
 
+    /**
+     * Is the parameter required or optional ? Default value is false (optional).
+     * @since 4.4
+     */
+    public NewParam setRequired(boolean b) {
+      this.required = b;
+      return this;
+    }
+
+    /**
+     * @since 4.4
+     */
+    public NewParam setExampleValue(@Nullable String s) {
+      this.exampleValue = s;
+      return this;
+    }
+
+    /**
+     * Exhaustive list of possible values when it makes sense, for example
+     * list of severities.
+     * @since 4.4
+     */
+    public NewParam setPossibleValues(@Nullable String... s) {
+      this.possibleValues = s;
+      return this;
+    }
+
     @Override
     public String toString() {
       return key;
@@ -389,11 +418,16 @@ public interface WebService extends ServerExtension {
 
   @Immutable
   class Param {
-    private final String key, description;
+    private final String key, description, exampleValue;
+    private final boolean required;
+    private final String[] possibleValues;
 
     public Param(NewParam newParam) {
       this.key = newParam.key;
       this.description = newParam.description;
+      this.exampleValue = newParam.exampleValue;
+      this.required = newParam.required;
+      this.possibleValues = (newParam.possibleValues == null ? new String[0] : newParam.possibleValues);
     }
 
     public String key() {
@@ -405,6 +439,29 @@ public interface WebService extends ServerExtension {
       return description;
     }
 
+    /**
+     * @since 4.4
+     */
+    @CheckForNull
+    public String exampleValue() {
+      return exampleValue;
+    }
+
+    /**
+     * Is the parameter required or optional ?
+     * @since 4.4
+     */
+    public boolean isRequired() {
+      return required;
+    }
+
+    /**
+     * @since 4.4
+     */
+    public String[] possibleValues() {
+      return possibleValues;
+    }
+
     @Override
     public String toString() {
       return key;
diff --git a/sonar-server/pom.xml b/sonar-server/pom.xml
index afe7cbc6274..335705fda92 100644
--- a/sonar-server/pom.xml
+++ b/sonar-server/pom.xml
@@ -191,8 +191,13 @@
       test
     
     
-      ${project.groupId}
-      sonar-testing-harness
+      org.skyscreamer
+      jsonassert
+      test
+    
+    
+      org.hamcrest
+      hamcrest-all
       test
     
     
@@ -356,7 +361,9 @@
     
       js-tests
       
-        !skipTests
+        
+          !skipTests
+        
       
       
         
diff --git a/sonar-server/src/main/java/org/sonar/server/rule2/RuleService.java b/sonar-server/src/main/java/org/sonar/server/rule2/RuleService.java
new file mode 100644
index 00000000000..f00af95d68e
--- /dev/null
+++ b/sonar-server/src/main/java/org/sonar/server/rule2/RuleService.java
@@ -0,0 +1,35 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.rule2;
+
+import org.sonar.api.rule.RuleKey;
+
+import javax.annotation.CheckForNull;
+
+/**
+ * @since 4.4
+ */
+public class RuleService {
+
+  @CheckForNull
+  public Rule getByKey(RuleKey key) {
+    return null;
+  }
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/rule2/ws/RulesWebService.java b/sonar-server/src/main/java/org/sonar/server/rule2/ws/RulesWebService.java
new file mode 100644
index 00000000000..7b54490d79e
--- /dev/null
+++ b/sonar-server/src/main/java/org/sonar/server/rule2/ws/RulesWebService.java
@@ -0,0 +1,46 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.rule2.ws;
+
+import org.sonar.api.server.ws.WebService;
+
+public class RulesWebService implements WebService {
+
+  private final SearchAction search;
+  private final ShowAction show;
+
+  public RulesWebService(SearchAction search, ShowAction show) {
+    this.search = search;
+    this.show = show;
+  }
+
+  @Override
+  public void define(Context context) {
+    NewController controller = context
+      .createController("api/rules2")
+      .setDescription("Coding rules")
+      .setSince("4.4");
+
+    search.define(controller);
+    show.define(controller);
+
+    controller.done();
+  }
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/rule2/ws/SearchAction.java b/sonar-server/src/main/java/org/sonar/server/rule2/ws/SearchAction.java
new file mode 100644
index 00000000000..21f659a63f6
--- /dev/null
+++ b/sonar-server/src/main/java/org/sonar/server/rule2/ws/SearchAction.java
@@ -0,0 +1,66 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.rule2.ws;
+
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.RequestHandler;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.server.rule2.RuleService;
+
+/**
+ * @since 4.4
+ */
+public class SearchAction implements RequestHandler {
+
+  private final RuleService service;
+
+  public SearchAction(RuleService service) {
+    this.service = service;
+  }
+
+  void define(WebService.NewController controller) {
+    WebService.NewAction action = controller
+      .createAction("search")
+      .setDescription("Returns a collection of relevant rules matching a specified query")
+      .setSince("4.4")
+      .setHandler(this);
+
+    action
+      .createParam("q")
+      .setDescription("UTF-8 search query")
+      .setExampleValue("null pointer");
+
+    action
+      .createParam("qProfile")
+      .setDescription("Key of Quality profile")
+      .setExampleValue("java:Sonar way");
+
+    action
+      .createParam("activation")
+      .setDescription("Only if 'qProfile' is set. Possible values are: true | false | all")
+      .setExampleValue("java:Sonar way");
+  }
+
+  @Override
+  public void handle(Request request, Response response) {
+
+  }
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/rule2/ws/ShowAction.java b/sonar-server/src/main/java/org/sonar/server/rule2/ws/ShowAction.java
new file mode 100644
index 00000000000..b844aad181f
--- /dev/null
+++ b/sonar-server/src/main/java/org/sonar/server/rule2/ws/ShowAction.java
@@ -0,0 +1,57 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.rule2.ws;
+
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.RequestHandler;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.server.rule2.RuleService;
+
+/**
+ * @since 4.4
+ */
+public class ShowAction implements RequestHandler {
+
+  private final RuleService service;
+
+  public ShowAction(RuleService service) {
+    this.service = service;
+  }
+
+  void define(WebService.NewController controller) {
+    WebService.NewAction action = controller
+      .createAction("show")
+      .setDescription("Returns detailed information about a rule")
+      .setSince("4.4")
+      .setHandler(this);
+
+    action
+      .createParam("key")
+      .setDescription("Rule key")
+      .setRequired(true)
+      .setExampleValue("javascript:EmptyBlock");
+  }
+
+  @Override
+  public void handle(Request request, Response response) {
+
+  }
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/rule2/ws/package-info.java b/sonar-server/src/main/java/org/sonar/server/rule2/ws/package-info.java
new file mode 100644
index 00000000000..8fcf3f3f2f5
--- /dev/null
+++ b/sonar-server/src/main/java/org/sonar/server/rule2/ws/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.rule2.ws;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/sonar-server/src/test/java/org/sonar/server/issue/filter/IssueFilterWsTest.java b/sonar-server/src/test/java/org/sonar/server/issue/filter/IssueFilterWsTest.java
index 87e1fef48e1..8757a9855a2 100644
--- a/sonar-server/src/test/java/org/sonar/server/issue/filter/IssueFilterWsTest.java
+++ b/sonar-server/src/test/java/org/sonar/server/issue/filter/IssueFilterWsTest.java
@@ -21,7 +21,7 @@ package org.sonar.server.issue.filter;
 
 import org.junit.Test;
 import org.sonar.api.server.ws.WebService;
-import org.sonar.api.server.ws.WsTester;
+import org.sonar.server.ws.WsTester;
 import org.sonar.core.issue.DefaultIssueFilter;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.user.MockUserSession;
diff --git a/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueShowWsHandlerTest.java b/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueShowWsHandlerTest.java
index 9f21f8646cc..01ddbfbabd9 100644
--- a/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueShowWsHandlerTest.java
+++ b/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueShowWsHandlerTest.java
@@ -38,7 +38,7 @@ import org.sonar.api.issue.internal.FieldDiffs;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rules.Rule;
 import org.sonar.api.server.debt.internal.DefaultDebtCharacteristic;
-import org.sonar.api.server.ws.WsTester;
+import org.sonar.server.ws.WsTester;
 import org.sonar.api.user.User;
 import org.sonar.api.utils.DateUtils;
 import org.sonar.api.utils.Duration;
diff --git a/sonar-server/src/test/java/org/sonar/server/issue/ws/IssuesWsTest.java b/sonar-server/src/test/java/org/sonar/server/issue/ws/IssuesWsTest.java
index ba6ec3fd9fb..9fd33aa4b2d 100644
--- a/sonar-server/src/test/java/org/sonar/server/issue/ws/IssuesWsTest.java
+++ b/sonar-server/src/test/java/org/sonar/server/issue/ws/IssuesWsTest.java
@@ -21,7 +21,7 @@ package org.sonar.server.issue.ws;
 
 import org.junit.Test;
 import org.sonar.api.server.ws.WebService;
-import org.sonar.api.server.ws.WsTester;
+import org.sonar.server.ws.WsTester;
 
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
diff --git a/sonar-server/src/test/java/org/sonar/server/platform/ws/RestartHandlerTest.java b/sonar-server/src/test/java/org/sonar/server/platform/ws/RestartHandlerTest.java
index 84590153905..9a6f5641c30 100644
--- a/sonar-server/src/test/java/org/sonar/server/platform/ws/RestartHandlerTest.java
+++ b/sonar-server/src/test/java/org/sonar/server/platform/ws/RestartHandlerTest.java
@@ -21,7 +21,7 @@ package org.sonar.server.platform.ws;
 
 import org.junit.Test;
 import org.sonar.api.config.Settings;
-import org.sonar.api.server.ws.WsTester;
+import org.sonar.server.ws.WsTester;
 import org.sonar.api.utils.System2;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.platform.Platform;
diff --git a/sonar-server/src/test/java/org/sonar/server/plugins/BatchWsTest.java b/sonar-server/src/test/java/org/sonar/server/plugins/BatchWsTest.java
index d20d346180d..fbc92faa24a 100644
--- a/sonar-server/src/test/java/org/sonar/server/plugins/BatchWsTest.java
+++ b/sonar-server/src/test/java/org/sonar/server/plugins/BatchWsTest.java
@@ -27,7 +27,7 @@ import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
 import org.sonar.api.platform.Server;
-import org.sonar.api.server.ws.WsTester;
+import org.sonar.server.ws.WsTester;
 
 import java.io.File;
 import java.io.IOException;
diff --git a/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/QgateAppHandlerTest.java b/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/QgateAppHandlerTest.java
index 1e6baad3415..4771a585710 100644
--- a/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/QgateAppHandlerTest.java
+++ b/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/QgateAppHandlerTest.java
@@ -31,7 +31,7 @@ import org.mockito.stubbing.Answer;
 import org.sonar.api.i18n.I18n;
 import org.sonar.api.measures.Metric;
 import org.sonar.api.measures.Metric.ValueType;
-import org.sonar.api.server.ws.WsTester;
+import org.sonar.server.ws.WsTester;
 import org.sonar.core.timemachine.Periods;
 import org.sonar.server.qualitygate.QualityGates;
 
diff --git a/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/QualityGatesWsTest.java b/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/QualityGatesWsTest.java
index 1953a8490e5..ef9108c1a8e 100644
--- a/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/QualityGatesWsTest.java
+++ b/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/QualityGatesWsTest.java
@@ -28,7 +28,6 @@ import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 import org.sonar.api.server.ws.WebService.Action;
 import org.sonar.api.server.ws.WebService.Controller;
-import org.sonar.api.server.ws.WsTester;
 import org.sonar.core.qualitygate.db.ProjectQgateAssociation;
 import org.sonar.core.qualitygate.db.ProjectQgateAssociationQuery;
 import org.sonar.core.qualitygate.db.QualityGateConditionDto;
@@ -37,6 +36,7 @@ import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.qualitygate.QgateProjectFinder;
 import org.sonar.server.qualitygate.QgateProjectFinder.Association;
 import org.sonar.server.qualitygate.QualityGates;
+import org.sonar.server.ws.WsTester;
 
 import java.util.List;
 
@@ -253,7 +253,7 @@ public class QualityGatesWsTest {
     ));
     when(qGates.currentUserHasWritePermission()).thenReturn(false);
     tester.newRequest("list").execute().assertJson(
-        "{'qualitygates':[{'id':42,'name':'Golden'},{'id':43,'name':'Star'},{'id':666,'name':'Ninth'}]}");
+      "{'qualitygates':[{'id':42,'name':'Golden'},{'id':43,'name':'Star'},{'id':666,'name':'Ninth'}]}");
   }
 
   @Test
@@ -266,7 +266,7 @@ public class QualityGatesWsTest {
     ));
     when(qGates.getDefault()).thenReturn(defaultQgate);
     tester.newRequest("list").execute().assertJson(
-        "{'qualitygates':[{'id':42,'name':'Golden'},{'id':43,'name':'Star'},{'id':666,'name':'Ninth'}],'default':42}");
+      "{'qualitygates':[{'id':42,'name':'Golden'},{'id':43,'name':'Star'},{'id':666,'name':'Ninth'}],'default':42}");
   }
 
   @Test
@@ -282,14 +282,15 @@ public class QualityGatesWsTest {
     long gateId = 12345L;
     when(qGates.get(gateId)).thenReturn(new QualityGateDto().setId(gateId).setName("Golden"));
     when(qGates.listConditions(gateId)).thenReturn(ImmutableList.of(
-        new QualityGateConditionDto().setId(1L).setMetricKey("ncloc").setOperator("GT").setErrorThreshold("10000"),
-        new QualityGateConditionDto().setId(2L).setMetricKey("new_coverage").setOperator("LT").setWarningThreshold("90").setPeriod(3)
+      new QualityGateConditionDto().setId(1L).setMetricKey("ncloc").setOperator("GT").setErrorThreshold("10000"),
+      new QualityGateConditionDto().setId(2L).setMetricKey("new_coverage").setOperator("LT").setWarningThreshold("90").setPeriod(3)
     ));
     tester.newRequest("show").setParam("id", Long.toString(gateId)).execute().assertJson(
       "{'id':12345,'name':'Golden','conditions':["
         + "{'id':1,'metric':'ncloc','op':'GT','error':'10000'},"
         + "{'id':2,'metric':'new_coverage','op':'LT','warning':'90','period':3}"
-    + "]}");
+        + "]}"
+    );
   }
 
   @Test
@@ -298,14 +299,15 @@ public class QualityGatesWsTest {
     String gateName = "Golden";
     when(qGates.get(gateName)).thenReturn(new QualityGateDto().setId(qGateId).setName(gateName));
     when(qGates.listConditions(qGateId)).thenReturn(ImmutableList.of(
-        new QualityGateConditionDto().setId(1L).setMetricKey("ncloc").setOperator("GT").setErrorThreshold("10000"),
-        new QualityGateConditionDto().setId(2L).setMetricKey("new_coverage").setOperator("LT").setWarningThreshold("90").setPeriod(3)
+      new QualityGateConditionDto().setId(1L).setMetricKey("ncloc").setOperator("GT").setErrorThreshold("10000"),
+      new QualityGateConditionDto().setId(2L).setMetricKey("new_coverage").setOperator("LT").setWarningThreshold("90").setPeriod(3)
     ));
     tester.newRequest("show").setParam("name", gateName).execute().assertJson(
       "{'id':12345,'name':'Golden','conditions':["
         + "{'id':1,'metric':'ncloc','op':'GT','error':'10000'},"
         + "{'id':2,'metric':'new_coverage','op':'LT','warning':'90','period':3}"
-    + "]}");
+        + "]}"
+    );
   }
 
   @Test(expected = BadRequestException.class)
@@ -375,8 +377,8 @@ public class QualityGatesWsTest {
     List projects = ImmutableList.of(
       new ProjectQgateAssociation().setId(42L).setName("Project One").setMember(false),
       new ProjectQgateAssociation().setId(24L).setName("Project Two").setMember(true)
-      );
-    when(assoc.projects()).thenReturn(projects );
+    );
+    when(assoc.projects()).thenReturn(projects);
     when(projectFinder.find(any(ProjectQgateAssociationQuery.class))).thenReturn(assoc);
     tester.newRequest("search")
       .setParam("gateId", Long.toString(gateId))
diff --git a/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/QProfileBackupWsHandlerTest.java b/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/QProfileBackupWsHandlerTest.java
index bd5e43be632..2c947e16bc4 100644
--- a/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/QProfileBackupWsHandlerTest.java
+++ b/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/QProfileBackupWsHandlerTest.java
@@ -25,7 +25,7 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
-import org.sonar.api.server.ws.WsTester;
+import org.sonar.server.ws.WsTester;
 import org.sonar.server.qualityprofile.QProfileBackup;
 import org.sonar.server.qualityprofile.QProfileResult;
 
diff --git a/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/QProfilesWsTest.java b/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/QProfilesWsTest.java
index 74501b9af40..9b93e8e1cdb 100644
--- a/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/QProfilesWsTest.java
+++ b/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/QProfilesWsTest.java
@@ -26,7 +26,7 @@ import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 import org.sonar.api.server.ws.WebService;
-import org.sonar.api.server.ws.WsTester;
+import org.sonar.server.ws.WsTester;
 
 import static org.fest.assertions.Assertions.assertThat;
 
diff --git a/sonar-server/src/test/java/org/sonar/server/rule/ws/AddTagsWsHandlerTest.java b/sonar-server/src/test/java/org/sonar/server/rule/ws/AddTagsWsHandlerTest.java
index a6d58d76799..31c91202065 100644
--- a/sonar-server/src/test/java/org/sonar/server/rule/ws/AddTagsWsHandlerTest.java
+++ b/sonar-server/src/test/java/org/sonar/server/rule/ws/AddTagsWsHandlerTest.java
@@ -30,7 +30,7 @@ import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 import org.sonar.api.rule.RuleKey;
-import org.sonar.api.server.ws.WsTester;
+import org.sonar.server.ws.WsTester;
 import org.sonar.core.permission.GlobalPermissions;
 import org.sonar.server.rule.Rule;
 import org.sonar.server.rule.Rules;
diff --git a/sonar-server/src/test/java/org/sonar/server/rule/ws/RemoveTagsWsHandlerTest.java b/sonar-server/src/test/java/org/sonar/server/rule/ws/RemoveTagsWsHandlerTest.java
index 8eaf6fded06..957424eb283 100644
--- a/sonar-server/src/test/java/org/sonar/server/rule/ws/RemoveTagsWsHandlerTest.java
+++ b/sonar-server/src/test/java/org/sonar/server/rule/ws/RemoveTagsWsHandlerTest.java
@@ -28,7 +28,7 @@ import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 import org.sonar.api.rule.RuleKey;
-import org.sonar.api.server.ws.WsTester;
+import org.sonar.server.ws.WsTester;
 import org.sonar.core.permission.GlobalPermissions;
 import org.sonar.server.rule.Rule;
 import org.sonar.server.rule.Rules;
diff --git a/sonar-server/src/test/java/org/sonar/server/rule/ws/RuleSearchWsHandlerTest.java b/sonar-server/src/test/java/org/sonar/server/rule/ws/RuleSearchWsHandlerTest.java
index 566d12b1bcc..1100762fd2c 100644
--- a/sonar-server/src/test/java/org/sonar/server/rule/ws/RuleSearchWsHandlerTest.java
+++ b/sonar-server/src/test/java/org/sonar/server/rule/ws/RuleSearchWsHandlerTest.java
@@ -32,7 +32,7 @@ import org.sonar.api.resources.Languages;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.server.debt.DebtRemediationFunction;
 import org.sonar.api.server.debt.internal.DefaultDebtRemediationFunction;
-import org.sonar.api.server.ws.WsTester;
+import org.sonar.server.ws.WsTester;
 import org.sonar.server.paging.PagedResult;
 import org.sonar.server.paging.PagingResult;
 import org.sonar.server.rule.Rule;
diff --git a/sonar-server/src/test/java/org/sonar/server/rule/ws/RuleShowWsHandlerTest.java b/sonar-server/src/test/java/org/sonar/server/rule/ws/RuleShowWsHandlerTest.java
index 2719ad27c74..f44b71fbd00 100644
--- a/sonar-server/src/test/java/org/sonar/server/rule/ws/RuleShowWsHandlerTest.java
+++ b/sonar-server/src/test/java/org/sonar/server/rule/ws/RuleShowWsHandlerTest.java
@@ -31,7 +31,7 @@ import org.sonar.api.resources.Language;
 import org.sonar.api.resources.Languages;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.server.ws.WsTester;
+import org.sonar.server.ws.WsTester;
 import org.sonar.api.utils.DateUtils;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.rule.Rule;
diff --git a/sonar-server/src/test/java/org/sonar/server/rule/ws/RuleTagsWsTest.java b/sonar-server/src/test/java/org/sonar/server/rule/ws/RuleTagsWsTest.java
index 9290508cc9a..7674f66b0a5 100644
--- a/sonar-server/src/test/java/org/sonar/server/rule/ws/RuleTagsWsTest.java
+++ b/sonar-server/src/test/java/org/sonar/server/rule/ws/RuleTagsWsTest.java
@@ -26,7 +26,7 @@ import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 import org.sonar.api.server.ws.WebService;
-import org.sonar.api.server.ws.WsTester;
+import org.sonar.server.ws.WsTester;
 import org.sonar.core.rule.RuleTagDto;
 import org.sonar.server.rule.RuleTags;
 
diff --git a/sonar-server/src/test/java/org/sonar/server/rule/ws/RulesWsTest.java b/sonar-server/src/test/java/org/sonar/server/rule/ws/RulesWsTest.java
index a8e0e867578..a513d509ff4 100644
--- a/sonar-server/src/test/java/org/sonar/server/rule/ws/RulesWsTest.java
+++ b/sonar-server/src/test/java/org/sonar/server/rule/ws/RulesWsTest.java
@@ -25,7 +25,7 @@ import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 import org.sonar.api.server.ws.WebService;
-import org.sonar.api.server.ws.WsTester;
+import org.sonar.server.ws.WsTester;
 
 import static org.fest.assertions.Assertions.assertThat;
 
diff --git a/sonar-server/src/test/java/org/sonar/server/rule2/ws/RulesWebServiceTest.java b/sonar-server/src/test/java/org/sonar/server/rule2/ws/RulesWebServiceTest.java
new file mode 100644
index 00000000000..c61a550609e
--- /dev/null
+++ b/sonar-server/src/test/java/org/sonar/server/rule2/ws/RulesWebServiceTest.java
@@ -0,0 +1,48 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.rule2.ws;
+
+import org.junit.Test;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.server.rule2.RuleService;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class RulesWebServiceTest {
+
+  @Test
+  public void define() throws Exception {
+    RuleService service = mock(RuleService.class);
+    SearchAction search = new SearchAction(service);
+    ShowAction show = new ShowAction(service);
+    RulesWebService ws = new RulesWebService(search, show);
+
+    WebService.Context context = new WebService.Context();
+    ws.define(context);
+
+    WebService.Controller controller = context.controller("api/rules2");
+    assertThat(controller).isNotNull();
+    assertThat(controller.since()).isEqualTo("4.4");
+    assertThat(controller.actions()).hasSize(2);
+    assertThat(controller.action("search")).isNotNull();
+    assertThat(controller.action("show")).isNotNull();
+  }
+}
diff --git a/sonar-server/src/test/java/org/sonar/server/source/ws/SourcesShowWsHandlerTest.java b/sonar-server/src/test/java/org/sonar/server/source/ws/SourcesShowWsHandlerTest.java
index f64ec6a8d0a..537e1403974 100644
--- a/sonar-server/src/test/java/org/sonar/server/source/ws/SourcesShowWsHandlerTest.java
+++ b/sonar-server/src/test/java/org/sonar/server/source/ws/SourcesShowWsHandlerTest.java
@@ -25,7 +25,7 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
-import org.sonar.api.server.ws.WsTester;
+import org.sonar.server.ws.WsTester;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.source.SourceService;
 
diff --git a/sonar-server/src/test/java/org/sonar/server/source/ws/SourcesWsTest.java b/sonar-server/src/test/java/org/sonar/server/source/ws/SourcesWsTest.java
index 30ca4d9be55..b24da55479b 100644
--- a/sonar-server/src/test/java/org/sonar/server/source/ws/SourcesWsTest.java
+++ b/sonar-server/src/test/java/org/sonar/server/source/ws/SourcesWsTest.java
@@ -22,7 +22,7 @@ package org.sonar.server.source.ws;
 
 import org.junit.Test;
 import org.sonar.api.server.ws.WebService;
-import org.sonar.api.server.ws.WsTester;
+import org.sonar.server.ws.WsTester;
 
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
diff --git a/sonar-testing-harness/src/main/java/org/sonar/api/server/ws/WsTester.java b/sonar-server/src/test/java/org/sonar/server/ws/WsTester.java
similarity index 97%
rename from sonar-testing-harness/src/main/java/org/sonar/api/server/ws/WsTester.java
rename to sonar-server/src/test/java/org/sonar/server/ws/WsTester.java
index 6c338978eae..890699710d1 100644
--- a/sonar-testing-harness/src/main/java/org/sonar/api/server/ws/WsTester.java
+++ b/sonar-server/src/test/java/org/sonar/server/ws/WsTester.java
@@ -17,11 +17,14 @@
  * 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.api.server.ws;
+package org.sonar.server.ws;
 
 import org.apache.commons.io.Charsets;
 import org.apache.commons.io.IOUtils;
 import org.skyscreamer.jsonassert.JSONAssert;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
 import org.sonar.api.utils.text.JsonWriter;
 import org.sonar.api.utils.text.XmlWriter;
 
-- 
2.39.5