Skip to content

Commit

Permalink
Add a workflow Step to send test results to TTM in Tuleap (#67)
Browse files Browse the repository at this point in the history
* Add a workflow Step to send test results to TTM in Tuleap

This change is part of [request #16216](https://tuleap.net/plugins/tracker/?aid=16216)

In order to test this new feature you need to create a JenkinsFile in a
repository of yours and you should be able to add:

```groovy
tuleapSendTTMResults filesPath: 'pouet/*.xml', campaignId: '1', credentialId: '484ed1cb-be32-418a-9a92-7d5dbaf7858d'
```
  • Loading branch information
Erwyn committed Sep 10, 2020
1 parent 0f05ab4 commit ac0174d
Show file tree
Hide file tree
Showing 8 changed files with 325 additions and 3 deletions.
19 changes: 17 additions & 2 deletions pom.xml
Expand Up @@ -12,7 +12,7 @@
<version>2.0.1-SNAPSHOT</version>
<packaging>hpi</packaging>
<properties>
<jenkins.version>2.164.1</jenkins.version>
<jenkins.version>2.176.4</jenkins.version>
<java.level>8</java.level>
</properties>
<name>Tuleap API Plugin</name>
Expand Down Expand Up @@ -45,7 +45,7 @@
<dependencies>
<dependency>
<groupId>io.jenkins.tools.bom</groupId>
<artifactId>bom-2.164.x</artifactId>
<artifactId>bom-2.176.x</artifactId>
<version>10</version>
<scope>import</scope>
<type>pom</type>
Expand All @@ -55,6 +55,11 @@
<artifactId>kotlin-stdlib-common</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
Expand All @@ -68,6 +73,10 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-step-api</artifactId>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>credentials</artifactId>
Expand All @@ -91,6 +100,12 @@
<groupId>com.auth0</groupId>
<artifactId>jwks-rsa</artifactId>
<version>0.13.0</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>commons-io</groupId>
Expand Down
@@ -0,0 +1,11 @@
package io.jenkins.plugins.tuleap_api.client;

import hudson.util.Secret;

import java.util.List;

public interface TestCampaignApi {
String TEST_CAMPAIGN_API = "/testmanagement_campaigns";

void sendTTMResults(String campaignId, String buildUrl, List<String> results, Secret token);
}
Expand Up @@ -12,5 +12,6 @@ protected void configure() {
bind(UserApi.class).to(TuleapApiClient.class);
bind(UserGroupsApi.class).to(TuleapApiClient.class);
bind(ProjectApi.class).to(TuleapApiClient.class);
bind(TestCampaignApi.class).to(TuleapApiClient.class);
}
}
@@ -1,7 +1,9 @@
package io.jenkins.plugins.tuleap_api.client.internals;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.google.inject.Inject;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.util.Secret;
Expand All @@ -20,8 +22,9 @@
import java.util.Objects;
import java.util.logging.Logger;

public class TuleapApiClient implements TuleapAuthorization, AccessKeyApi, UserApi, UserGroupsApi, ProjectApi {
public class TuleapApiClient implements TuleapAuthorization, AccessKeyApi, UserApi, UserGroupsApi, ProjectApi , TestCampaignApi {
private static final Logger LOGGER = Logger.getLogger(TuleapApiClient.class.getName());
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");

private OkHttpClient client;

Expand Down Expand Up @@ -259,4 +262,27 @@ public List<UserGroup> getProjectUserGroups(Integer projectId, AccessToken token
throw new RuntimeException("Error while contacting Tuleap server", e);
}
}

@Override
public void sendTTMResults(String campaignId, String buildUrl, List<String> results, Secret secret) {
Request request;

try {
request = new Request.Builder()
.url(this.tuleapConfiguration.getApiBaseUrl() + this.TEST_CAMPAIGN_API + "/" + campaignId)
.addHeader(this.AUTHORIZATION_HEADER, secret.getPlainText())
.patch(RequestBody.create(this.objectMapper.writer(SerializationFeature.WRAP_ROOT_VALUE).writeValueAsString(new SendTTMResultsEntity(buildUrl,results)), JSON))
.build();
} catch (JsonProcessingException exception) {
throw new RuntimeException("Error while trying to create request for TTM results", exception);
}

try (Response response = this.client.newCall(request).execute()) {
if (! response.isSuccessful()) {
throw new InvalidTuleapResponseException(response);
}
} catch (IOException | InvalidTuleapResponseException exception) {
throw new RuntimeException("Error while contacting Tuleap server", exception);
}
}
}
@@ -0,0 +1,28 @@
package io.jenkins.plugins.tuleap_api.client.internals.entities;

import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonRootName;

import java.util.List;

@JsonRootName("automated_tests_results")
public class SendTTMResultsEntity {
private final List<String> results;
private final String buildUrl;

public SendTTMResultsEntity(final String buildUrl, final List<String> results) {
this.results = results;
this.buildUrl = buildUrl;
}

@JsonGetter("junit_contents")
public List<String> getResults() {
return this.results;
}

@JsonGetter("build_url")
public String getBuildUrl() {
return this.buildUrl;
}

}
@@ -0,0 +1,49 @@
package io.jenkins.plugins.tuleap_api.steps;

import hudson.EnvVars;
import hudson.FilePath;
import io.jenkins.plugins.tuleap_api.client.TestCampaignApi;
import io.jenkins.plugins.tuleap_credentials.TuleapAccessToken;

import javax.inject.Inject;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class TuleapSendTTMResultsRunner {
private final TestCampaignApi testCampaignApi;

@Inject
public TuleapSendTTMResultsRunner(final TestCampaignApi testCampaignApi) {
this.testCampaignApi = testCampaignApi;
}

public void run(
final TuleapAccessToken tuleapAccessToken,
final PrintStream logger,
final TuleapSendTTMResultsStep step,
final FilePath filePath,
final EnvVars envVars
) throws Exception {
logger.println("Collecting all result files");
final List<String> results = Arrays.stream(filePath.list(step.getFilesPath()))
.map(path -> {
try {
return path.readToString();
} catch (IOException | InterruptedException exception) {
throw new RuntimeException(exception);
}
})
.collect(Collectors.toList());

logger.println("Sending results to Tuleap");
this.testCampaignApi.sendTTMResults(
step.getCampaignId(),
envVars.get("BUILD_URL"),
results,
tuleapAccessToken.getToken()
);
}
}
@@ -0,0 +1,133 @@
package io.jenkins.plugins.tuleap_api.steps;

import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder;
import com.google.inject.Guice;
import com.google.inject.Injector;
import hudson.EnvVars;
import hudson.Extension;
import hudson.FilePath;
import hudson.model.Run;
import hudson.model.TaskListener;
import io.jenkins.plugins.tuleap_api.client.TuleapApiGuiceModule;
import io.jenkins.plugins.tuleap_credentials.TuleapAccessToken;
import io.jenkins.plugins.tuleap_server_configuration.TuleapConfiguration;
import org.jenkinsci.plugins.workflow.steps.*;
import org.jetbrains.annotations.NotNull;
import org.kohsuke.stapler.DataBoundConstructor;

import java.io.IOException;
import java.io.PrintStream;
import java.util.*;

public class TuleapSendTTMResultsStep extends Step {
private final transient String filesPath;
private final transient String campaignId;
private final transient String credentialId;

@DataBoundConstructor
public TuleapSendTTMResultsStep(
String filesPath,
String campaignId,
String credentialId
) {
this.filesPath = filesPath;
this.campaignId = campaignId;
this.credentialId = credentialId;
}

public String getFilesPath() {
return filesPath;
}

public String getCampaignId() {
return campaignId;
}

public String getCredentialId() {
return credentialId;
}

@Override
public StepExecution start(StepContext stepContext) throws Exception {
return new Execution(stepContext, this);
}

public static class Execution extends SynchronousStepExecution<Void> {
private final static long serialVersionUID = 1L;
private final transient TuleapSendTTMResultsStep tuleapSendTTMResultsStep;
private final transient TuleapConfiguration tuleapConfiguration;
private final transient TuleapSendTTMResultsRunner tuleapSendTTMResultsRunner;
private final transient Run run;
private final transient PrintStream logger;
private final TaskListener taskListener;
private final FilePath filePath;
private final EnvVars envVars;

protected Execution(StepContext context, TuleapSendTTMResultsStep tuleapSendTTMResultsStep) throws IOException, InterruptedException {
super(context);
this.tuleapSendTTMResultsStep = tuleapSendTTMResultsStep;
this.taskListener = context.get(TaskListener.class);
this.logger = taskListener.getLogger();
this.filePath = getContext().get(FilePath.class);
this.envVars = getContext().get(EnvVars.class);
this.run = getContext().get(Run.class);

final Injector injector = Guice.createInjector(new TuleapApiGuiceModule());
this.tuleapSendTTMResultsRunner = injector.getInstance(TuleapSendTTMResultsRunner.class);
this.tuleapConfiguration = TuleapConfiguration.get();
}

@Override
protected Void run() throws Exception {
assert filePath != null;

logger.println("Retrieving Tuleap API credentials");
final TuleapAccessToken tuleapAccessToken = CredentialsProvider.findCredentialById(
tuleapSendTTMResultsStep.getCredentialId(),
TuleapAccessToken.class,
run,
URIRequirementBuilder.fromUri(tuleapConfiguration.getApiBaseUrl()).build()
);

assert tuleapAccessToken != null;

tuleapSendTTMResultsRunner.run(
tuleapAccessToken,
logger,
tuleapSendTTMResultsStep,
filePath,
envVars
);

return null;
}
}

@Extension
public static class DescriptorImpl extends StepDescriptor {

@Override
public Set<? extends Class<?>> getRequiredContext() {
return new HashSet<>(
Arrays.asList(
TaskListener.class,
FilePath.class,
EnvVars.class,
Run.class
)
);
}

@Override
public String getFunctionName() {
return "tuleapSendTTMResults";
}

@NotNull
@Override
public String getDisplayName() {
return "Send Tuleap Test Management Results";
}
}
}
@@ -0,0 +1,59 @@
package io.jenkins.plugins.tuleap_api.steps;

import hudson.EnvVars;
import hudson.FilePath;
import hudson.util.Secret;
import io.jenkins.plugins.tuleap_api.client.TestCampaignApi;
import io.jenkins.plugins.tuleap_credentials.TuleapAccessToken;
import org.junit.Test;

import java.io.PrintStream;
import java.util.Arrays;
import java.util.List;

import static org.mockito.Mockito.*;

public class TuleapSendTTMResultsRunnerTest {

@Test
public void itSendsResultsToTTM() throws Exception {
final TestCampaignApi testCampaignApi = mock(TestCampaignApi.class);
final TuleapSendTTMResultsRunner tuleapSendTTMResultsRunner = new TuleapSendTTMResultsRunner(testCampaignApi);
final TuleapAccessToken tuleapAccessToken = mock(TuleapAccessToken.class);
final PrintStream logger = mock(PrintStream.class);
final TuleapSendTTMResultsStep tuleapSendTTMResultsStep = mock(TuleapSendTTMResultsStep.class);
final FilePath filePath = mock(FilePath.class);
final EnvVars envVars = mock(EnvVars.class);

final String path = "test/*.xml";
final String campaignId = "1";
final String buildUrl = "https://jenkins.example.com";
final Secret secret = Secret.fromString("a-very-secret-secret");
final List<String> results = Arrays.asList("some result 1", "some result 2");
final FilePath path1 = mock(FilePath.class);
final FilePath path2 = mock(FilePath.class);

when(tuleapSendTTMResultsStep.getCampaignId()).thenReturn(campaignId);
when(tuleapSendTTMResultsStep.getFilesPath()).thenReturn(path);
when(tuleapAccessToken.getToken()).thenReturn(secret);
when(envVars.get("BUILD_URL")).thenReturn(buildUrl);
when(path1.readToString()).thenReturn("some result 1");
when(path2.readToString()).thenReturn("some result 2");
when(filePath.list(path)).thenReturn(new FilePath[] {path1, path2});

tuleapSendTTMResultsRunner.run(
tuleapAccessToken,
logger,
tuleapSendTTMResultsStep,
filePath,
envVars
);

verify(testCampaignApi, atMostOnce()).sendTTMResults(
campaignId,
buildUrl,
results,
secret
);
}
}

0 comments on commit ac0174d

Please sign in to comment.