Commit c3293f63 authored by Lukas Wiest's avatar Lukas Wiest 🚂
Browse files

Merge branch '2-add-github-impl' into 'master'

add GitHub implementation

Closes #2

See merge request !2
parents b2324a1f 28dbee8c
Pipeline #1706 passed with stages
in 3 minutes and 12 seconds
package de.hftstuttgart.unifiedticketing.core;
import de.hftstuttgart.unifiedticketing.systems.github.GithubTicketSystemBuilder;
import de.hftstuttgart.unifiedticketing.systems.gitlab.GitlabTicketSystemBuilder;
import java.util.logging.Level;
......@@ -26,6 +27,14 @@ public class RegisteredSystems
return instance;
}
/**
* Starts the builder mechanism for a GitHub connection
*/
public GithubTicketSystemBuilder github()
{
return new GithubTicketSystemBuilder();
}
/**
* Starts the builder mechanism for a GitLab connection
*/
......
package de.hftstuttgart.unifiedticketing.core;
import de.hftstuttgart.unifiedticketing.exceptions.AssertionException;
import de.hftstuttgart.unifiedticketing.systems.github.GithubTicketSystem;
import de.hftstuttgart.unifiedticketing.systems.gitlab.GitlabTicketSystem;
import java.util.HashMap;
......@@ -57,6 +58,9 @@ public abstract class TicketSystem<T extends Ticket, TS extends TicketSystem, TB
switch (matcher.group(2))
{
case "github":
return GithubTicketSystem.fromUri(matcher.group(3));
case "gitlab":
return GitlabTicketSystem.fromUri(matcher.group(3));
......
package de.hftstuttgart.unifiedticketing.systems.github;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.hftstuttgart.unifiedticketing.core.Filter;
import de.hftstuttgart.unifiedticketing.core.Logging;
import de.hftstuttgart.unifiedticketing.core.TicketSystem;
import de.hftstuttgart.unifiedticketing.exceptions.*;
import okhttp3.*;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class GithubFilter extends Filter<GithubTicket, GithubFilter>
{
private final static Logger logger = Logging.getLogger(GithubFilter.class.getName());
protected final GithubTicketSystem parent;
protected GithubFilter(GithubTicketSystem parent)
{
this.parent = parent;
}
protected OkHttpClient getHttpClient() { return new OkHttpClient(); }
@Override
public List<GithubTicket> get()
{
ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Request.Builder requestBuilder = new Request.Builder()
.url(parent.baseUrl)
.addHeader("accept", parent.acceptHeader)
.get();
if (parent.username != null && parent.apiKey != null)
{
requestBuilder.addHeader("Authorization", Credentials.basic(parent.username, parent.apiKey));
logger.log(Level.FINEST, "added token authentication header");
}
HttpUrl.Builder urlBuilder = requestBuilder.build().url().newBuilder();
for (Map.Entry<String, Object> mapEntry : setFilters.entrySet())
{
String f = mapEntry.getKey();
Object v = mapEntry.getValue();
try
{
if (f.equals(FilterNames.ASSIGNEEID.name()))
{
logger.log(Level.WARNING, "assignee-id check only possible after request |" +
" Filter: " + FilterNames.ASSIGNEEID.name());
} else if (f.equals(FilterNames.ASSIGNEENAME.name()))
{
Set<String> names = (Set<String>) v;
if (names.size() > 0)
{
urlBuilder.addQueryParameter("assignee", names.stream()
.findFirst()
.get());
if (names.size() > 1)
{
logger.log(Level.WARNING, "assignee-name filter natively only for one name supported");
}
}
} else if (f.equals(FilterNames.DESCRIPTION_CONTAIN.name()))
{
logger.log(Level.FINE, "Description contain check only possible after request |" +
" Filter: " + FilterNames.DESCRIPTION_CONTAIN.name());
} else if (f.equals(FilterNames.DESCRIPTION_MATCH.name()))
{
logger.log(Level.FINE, "Regex matching only possible after request |" +
" Filter: " + FilterNames.DESCRIPTION_MATCH.name());
} else if (f.equals(FilterNames.IDS.name()))
{
logger.log(Level.FINE, "Ticket id matching only possible after request |" +
" Filter: " + FilterNames.IDS.name());
} else if (f.equals(FilterNames.LABELS.name()))
{
urlBuilder.addQueryParameter("labels", ((Set<String>) v).stream()
.reduce((l1, l2) -> l1 + "," + l2)
.orElse(""));
} else if (f.equals(FilterNames.PAGE.name()))
{
urlBuilder.addQueryParameter("page", String.valueOf(v));
} else if (f.equals(FilterNames.PAGINATION.name()))
{
urlBuilder.addQueryParameter("per_page", String.valueOf(v));
} else if (f.equals(FilterNames.OPEN.name()))
{
urlBuilder.addQueryParameter("state", ((boolean) v) ? "open" : "closed");
} else if (f.equals(FilterNames.TITLE_CONTAINS.name()))
{
logger.log(Level.FINE, "title contain check only possible after request |" +
" Filter: " + FilterNames.TITLE_CONTAINS.name());
} else if (f.equals(FilterNames.TITLE_MATCH.name()))
{
logger.log(Level.FINE, "Regex matching only possible after request |" +
" Filter: " + FilterNames.TITLE_MATCH.name());
} else
{
logger.log(Level.WARNING, String.format("unrecognized filter key: %s", f));
}
}
catch (ClassCastException e)
{
logger.log(Level.SEVERE, "Filter with key "
+ f
+ " unexpectedly had type "
+ v.getClass().getName());
if (!this.parent.getConfigTrue(TicketSystem.ConfigurationOptions.RETURN_NULL_ON_ERROR))
{
throw new AssertionException(e);
}
}
}
requestBuilder.url(urlBuilder.build());
OkHttpClient client = getHttpClient();
Response response;
try
{
Request request = requestBuilder.build();
logger.log(Level.FINEST, String.format(
"created request:\n%s",
(this.parent.apiKey != null)
? request.toString().replace(this.parent.apiKey, "SECRET")
: request.toString()
));
response = client.newCall(request).execute();
}
catch (IOException e)
{
logger.log(Level.SEVERE, String.format("get request FAILED with: %s", e.getMessage()));
if (this.parent.getConfigTrue(TicketSystem.ConfigurationOptions.RETURN_NULL_ON_ERROR)) return null;
else throw new HttpReqeustException(e);
}
if (response.code() >= 400)
{
logger.log(Level.SEVERE, String.format(
"request failed with response code %d",
response.code()
));
if (this.parent.getConfigTrue(TicketSystem.ConfigurationOptions.RETURN_NULL_ON_ERROR)) return null;
else throw new HttpResponseException(
String.format("ticket query failed, error response code: %d", response.code()),
response.code());
}
logger.log(Level.FINEST, "response received\n");
ResponseBody responseBody;
responseBody = response.body();
if (responseBody == null)
{
logger.log(Level.SEVERE, "query didn't deliver a body in response");
if (this.parent.getConfigTrue(TicketSystem.ConfigurationOptions.RETURN_NULL_ON_ERROR)) return null;
else throw new HttpResponseException("ticket query failed, no response body", response.code());
}
List<GithubTicketResponse> tr;
try
{
tr = mapper.readValue(responseBody.bytes(), new TypeReference<List<GithubTicketResponse>>(){});
logger.log(Level.FINER, "parsed response body to ticketResponse list instance");
logger.log(Level.FINEST, String.format("found %d items pre post-filter", tr.size()));
} catch (IOException e)
{
logger.log(Level.SEVERE, String.format("parsing query response FAILED with: %s", e.getMessage()));
if (this.parent.getConfigTrue(TicketSystem.ConfigurationOptions.RETURN_NULL_ON_ERROR)) return null;
else throw new DeserializationException(e);
}
logger.log(Level.FINER, "starting query post filter");
Stream<GithubTicketResponse> ticketStream = tr.stream();
for (Map.Entry<String, Object> entry : setFilters.entrySet())
{
String f = entry.getKey();
Object v = entry.getValue();
try
{
if (f.equals(FilterNames.ASSIGNEEID.name()))
{
ticketStream = ticketStream.filter(t -> t.assignees.stream()
.map(a -> String.valueOf(a.id))
.collect(Collectors.toSet())
.equals((Set<String>) v));
}
else if (f.equals(FilterNames.ASSIGNEENAME.name()) && ((Set) v).size() > 1)
{
ticketStream = ticketStream.filter(t -> t.assignees.stream()
.map(a -> a.login)
.collect(Collectors.toSet())
.equals((Set<String>) v));
}
else if (f.equals(FilterNames.DESCRIPTION_CONTAIN))
{
ticketStream = ticketStream.filter(t -> t.body
.toLowerCase()
.contains(((String) v).toLowerCase()));
}
else if (f.equals(FilterNames.DESCRIPTION_MATCH.name()))
{
ticketStream = ticketStream.filter(t -> t.body.matches((String) v));
}
else if (f.equals(FilterNames.TITLE_CONTAINS.name()))
{
ticketStream = ticketStream.filter(t -> t.title
.toLowerCase()
.contains(((String) v).toLowerCase()));
}
else if (f.equals(FilterNames.TITLE_MATCH.name()))
{
ticketStream = ticketStream.filter(t -> t.title.matches((String) v));
}
} catch (ClassCastException e)
{
logger.log(Level.SEVERE, "Filter with key "
+ f
+ " unexpectedly had type "
+ v.getClass().getName());
}
}
logger.log(Level.FINER, "post-filter finished");
List<GithubTicket> ret = ticketStream.map(t -> GithubTicket.fromTicketResponse(parent, t))
.collect(Collectors.toList());
logger.log(Level.FINEST, String.format("remaining items: %d", ret.size()));
return ret;
}
}
package de.hftstuttgart.unifiedticketing.systems.github;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import de.hftstuttgart.unifiedticketing.core.Logging;
import de.hftstuttgart.unifiedticketing.core.Ticket;
import de.hftstuttgart.unifiedticketing.core.TicketSystem;
import de.hftstuttgart.unifiedticketing.exceptions.DeserializationException;
import de.hftstuttgart.unifiedticketing.exceptions.HttpReqeustException;
import de.hftstuttgart.unifiedticketing.exceptions.HttpResponseException;
import de.hftstuttgart.unifiedticketing.exceptions.SerializationException;
import okhttp3.*;
import java.io.IOException;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
public class GithubTicket extends Ticket<GithubTicketSystem, GithubTicket>
{
private final static Logger logger = Logging.getLogger(GithubTicket.class.getName());
protected static GithubTicket fromTicketResponse(GithubTicketSystem parent, GithubTicketResponse response)
{
GithubTicket ret = new GithubTicket(parent);
ret.description = response.body;
ret.id = String.valueOf(response.number);
ret.open = response.state == null || response.state.equalsIgnoreCase("open");
ret.title = response.title;
if (response.assignees != null)
{
ret.assignees = response.assignees.stream()
.map(a -> new GithubTicketAssignee(a.id, a.login))
.collect(Collectors.toSet());
}
if (response.labels != null)
{
ret.labels = response.labels.stream()
.map(l -> l.name)
.collect(Collectors.toSet());
}
return ret;
}
protected GithubTicket(GithubTicketSystem parent)
{
super(parent);
}
@Override
public GithubTicket addAssignee(String identifier)
{
this.assignees.add(new GithubTicketAssignee(0, identifier));
this.updatedFields.add(FieldNames.ASSIGNEES.name());
return this;
}
@Override
public GithubTicket removeAssignee(String identifier)
{
this.assignees.removeIf(a -> Objects.equals(a.username, identifier));
this.updatedFields.add(FieldNames.ASSIGNEES.name());
return null;
}
protected OkHttpClient getHttpClient() { return new OkHttpClient(); }
@Override
public GithubTicket save()
{
if (updatedFields.size() == 0)
{
logger.info("No changed fields, no save required");
return this;
}
OkHttpClient client = this.getHttpClient();
ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
logger.log(Level.FINEST, String.format(
"[Ticket %s] preparing body for update request",
this.id
));
ObjectNode body = mapper.createObjectNode();
for (String name : this.updatedFields)
{
if (FieldNames.ASSIGNEES.name().equals(name))
{
ArrayNode arrayNode = body.putArray("assignees");
this.assignees.forEach(a -> arrayNode.add(a.username));
}
else if (FieldNames.DESCRIPTION.name().equals(name))
{
body.put("body", this.description);
}
else if (FieldNames.LABELS.name().equals(name))
{
ArrayNode arrayNode = body.putArray("labels");
this.labels.forEach(arrayNode::add);
}
else if (FieldNames.OPEN.name().equals(name))
{
body.put("state", (this.open) ? "open" : "closed");
}
else if (FieldNames.TITLE.name().equals(name))
{
body.put("title", this.title);
}
else
{
logger.log(Level.WARNING, String.format(
"[Ticket %s] unknown field %s will be ignored from update",
this.id,
name
));
continue;
}
logger.log(Level.FINEST, String.format(
"[Ticket %s] %s added to update",
this.id,
name
));
}
logger.log(Level.FINEST, String.format(
"[Ticket %s] request body for update prepared",
this.id
));
Request.Builder builder = new Request.Builder()
.url(String.format("%s/%s", this.parent.baseUrl, this.id))
.addHeader("accept", parent.acceptHeader);
try
{
logger.log(Level.FINEST, String.format(
"[Ticket %s] serializing update request body",
this.id
));
String bodyJson = mapper.writeValueAsString(body);
logger.log(Level.FINEST, String.format(
"[Ticket %s] serialized JSON:\n%s",
this.id,
bodyJson
));
builder
.patch(RequestBody.create(bodyJson, MediaType.get("application/json")));
} catch (JsonProcessingException e)
{
logger.log(Level.SEVERE, String.format(
"[Ticket %s] serializing update request body FAILED!",
this.id
));
if (this.parent.getConfigTrue(TicketSystem.ConfigurationOptions.RETURN_NULL_ON_ERROR)) return null;
else throw new SerializationException(e);
}
if (parent.username != null && parent.apiKey != null)
{
builder.addHeader("Authorization", Credentials.basic(parent.username, parent.apiKey));
logger.log(Level.FINEST, String.format(
"[Ticket %s] added token authentication header",
this.id
));
}
Response response;
try
{
Request request = builder.build();
logger.log(Level.FINEST, String.format(
"[Ticket %s] created request:\n%s",
this.id,
(this.parent.apiKey != null)
? request.toString().replace(this.parent.apiKey, "SECRET")
: request.toString()
));
response = client.newCall(request).execute();
}
catch (IOException e)
{
logger.log(Level.SEVERE, String.format(
"[Ticket %s] update request FAILED",
this.id
));
if (this.parent.getConfigTrue(TicketSystem.ConfigurationOptions.RETURN_NULL_ON_ERROR)) return null;
else throw new HttpReqeustException(e);
}
if (response.code() >= 400)
{
logger.log(Level.SEVERE, String.format(
"[Ticket %s] update request failed with response code %d",
this.id,
response.code()
));
if (this.parent.getConfigTrue(TicketSystem.ConfigurationOptions.RETURN_NULL_ON_ERROR)) return null;
else throw new HttpResponseException(
String.format("ticket save failed, error response code: %d", response.code()),
response.code());
}
logger.log(Level.FINEST, String.format(
"[Ticket %s] response received",
this.id
));
ResponseBody responseBody;
responseBody = response.body();
if (responseBody == null)
{
logger.log(Level.SEVERE, String.format(
"[Ticket %s] update request didn't deliver a body in response",
this.id
));
if (this.parent.getConfigTrue(TicketSystem.ConfigurationOptions.RETURN_NULL_ON_ERROR)) return null;
else throw new HttpResponseException("ticket save failed, no response body", response.code());
}
GithubTicketResponse ticketResponse;
try
{
ticketResponse = mapper.readValue(responseBody.bytes(), GithubTicketResponse.class);
logger.log(Level.FINEST, String.format(
"[Ticket %s] parsed response body to ticketResponse instance",
this.id
));
} catch (IOException e)
{
logger.log(Level.SEVERE, String.format("parsing update response FAILED with: %s", e.getMessage()));
if (this.parent.getConfigTrue(TicketSystem.ConfigurationOptions.RETURN_NULL_ON_ERROR)) return null;
else throw new DeserializationException(e);
}
this.updatedFields.clear();
logger.log(Level.FINEST, String.format(
"[Ticket %s] update mark state reset",
this.id
));
return GithubTicket.fromTicketResponse(this.parent, ticketResponse);
}
/**
* compares a given Object to this one, including all data fields.
* The normal {@link #equals(Object)} method does only compare Ticket Type and id,
* but not the data fields like this one.
* @param o Object to compare to this
* @return true if the given object is equal in terms of type, id and content to this
*/
public boolean deepEquals(Object o)
{
if (!equals(o)) return false;
GithubTicket t = (GithubTicket) o;
return Objects.equals(title, t.title)
&& Objects.equals(description, t.description)
&& Objects.equals(labels, t.labels)
&& Objects.equals(assignees, t.assignees);
}
}
package de.hftstuttgart.unifiedticketing.systems.github;
import de.hftstuttgart.unifiedticketing.core.TicketAssignee;
public class GithubTicketAssignee extends TicketAssignee
{
protected GithubTicketAssignee(int id, String username)
{
super(null, String.valueOf(id), username, null);
}
}
package de.hftstuttgart.unifiedticketing.systems.github;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import de.hftstuttgart.unifiedticketing.core.Logging;
import de.hftstuttgart.unifiedticketing.core.TicketBuilder;
import de.hftstuttgart.unifiedticketing.core.TicketSystem;
import de.hftstuttgart.unifiedticketing.exceptions.*;
import okhttp3.*;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
public class GithubTicketBuilder extends TicketBuilder<GithubTicketBuilder, GithubTicket, GithubTicketSystem>
{
private final static Logger logger = Logging.getLogger(GithubTicketBuilder.class.getName());
protected GithubTicketBuilder(GithubTicketSystem parent)
{
super(parent);
}
protected OkHttpClient getHttpClient() { return new OkHttpClient(); }
@Override
public GithubTicket create()
{
logger.log(Level.FINEST, "starting Ticket creation from builder data");
ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
ObjectNode body = mapper.createObjectNode();
// title is mandatory field
if (this.title == null)
{
String msg = "mandatory field title not set before building ticket";
logger.log(Level.SEVERE, msg);
if (parent.getConfigTrue(TicketSystem.ConfigurationOptions.RETURN_NULL_ON_ERROR)) return null;
else throw new AssertionException(msg);
}
if (this.description != null)
{
body.put("body", this.description);
logger.log(Level.FINEST, "description set");
}
body.put("title", this.title);
logger.log(Level.FINEST, "title set");
if (this.assignees != null)
{
ArrayNode arrayNode = body.putArray("assignees");
assignees.forEach(arrayNode::add);
logger.log(Level.FINEST, "assignees set");
}
if (this.labels != null)
{
ArrayNode arrayNode = body.putArray("labels");
this.labels.forEach(arrayNode::add);
logger.log(Level.FINEST, "labels set");
}
String jsonBody;
try
{
jsonBody = mapper.writeValueAsString(body);
} catch (JsonProcessingException e)
{
String msg = "json serialization FAILED";
logger.log(Level.SEVERE, msg);
if (parent.getConfigTrue(TicketSystem.ConfigurationOptions.RETURN_NULL_ON_ERROR)) return null;
else throw new SerializationException(e);
}
OkHttpClient client = getHttpClient();
Request.Builder builder = new Request.Builder()
.url(parent.baseUrl)
.addHeader("accept", parent.acceptHeader)
.post(RequestBody.create(jsonBody, MediaType.get("application/json")));
if (this.parent.username != null && this.parent.apiKey != null)
{
builder.addHeader("Authorization", Credentials.basic(this.parent.username, this.parent.apiKey));
logger.log(Level.FINEST, "added basic auth header with api token");
}
Response response;
try
{
Request request = builder.build();
logger.log(Level.FINEST, String.format(
"created request:\n%s",
(this.parent.apiKey != null)
? request.toString().replace(this.parent.apiKey, "SECRET")
: request.toString()
));
response = client.newCall(request).execute();
} catch (IOException e)
{
logger.log(Level.SEVERE, "create request FAILED");
if (parent.getConfigTrue(TicketSystem.ConfigurationOptions.RETURN_NULL_ON_ERROR)) return null;
else throw new HttpReqeustException(e);
}
if (response.code() >= 400)
{
logger.log(Level.SEVERE, String.format(
"create request failed with response code %d",
response.code()
));
if (this.parent.getConfigTrue(TicketSystem.ConfigurationOptions.RETURN_NULL_ON_ERROR)) return null;
else throw new HttpResponseException(
String.format("ticket creation failed, error response code %d", response.code()),
response.code());
}
logger.log(Level.FINEST, "response received\n");
ResponseBody responseBody;
responseBody = response.body();
if (responseBody == null)
{
logger.log(Level.SEVERE, "create request didn't deliver a body in response");
if (this.parent.getConfigTrue(TicketSystem.ConfigurationOptions.RETURN_NULL_ON_ERROR)) return null;
else throw new HttpResponseException("ticket creation failed, no response body", response.code());
}
GithubTicketResponse ticketResponse;
try
{
ticketResponse = mapper.readValue(responseBody.bytes(), GithubTicketResponse.class);
logger.log(Level.FINEST, "parsed response to ticketResponse instance");
} catch (IOException e)
{
logger.severe(String.format("parsing create response FAILED with: %s", e.getMessage()));
if (parent.getConfigTrue(TicketSystem.ConfigurationOptions.RETURN_NULL_ON_ERROR)) return null;
else throw new DeserializationException(e);
}
return GithubTicket.fromTicketResponse(parent, ticketResponse);
}
}
package de.hftstuttgart.unifiedticketing.systems.github;
import java.util.Set;
public class GithubTicketResponse
{
public Set<Assignee> assignees;
public String body;
public int number;
public Set<Label> labels;
public String state;
public String title;
protected static class Label
{
public String name;
}
protected static class Assignee
{
public int id;
public String login;
}
}
package de.hftstuttgart.unifiedticketing.systems.github;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.hftstuttgart.unifiedticketing.core.*;
import de.hftstuttgart.unifiedticketing.exceptions.*;
import okhttp3.*;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class GithubTicketSystem extends TicketSystem<GithubTicket, GithubTicketSystem, GithubTicketBuilder, GithubFilter>
{
private final static Logger logger = Logging.getLogger(GithubTicketSystem.class.getName());
protected final String acceptHeader;
protected final String apiKey;
protected final String username;
/**
* creates a new instance of this class from an uri.<br>
* <br>
* <b>Attention:</b> This method should not be called from the enduser!<br>
* Instead call the same method from {@link TicketSystem}.<br>
* To call this method directly, the uri has to be shortend by the {@code "unifiedticketing:github:"} part.
* @param uri gitlab specific part of full uri
* @return new github ticket system instance
*/
public static GithubTicketSystem fromUri(String uri)
{
Matcher matcher = Pattern.compile("^((http|https):\\/\\/)?(.*?(:[0-9]+.*?)?)::(.*?):(.*?)(:(.*?):(.*?))?$").matcher(uri);
if (!matcher.matches())
{
String msg = "uri didn't match regex";
logger.log(Level.SEVERE, msg);
throw new AssertionException(msg);
}
GithubTicketSystemBuilder builder = new GithubTicketSystemBuilder();
if (matcher.group(2) != null && matcher.group(2).length() > 0)
{
if (matcher.group(2).equalsIgnoreCase("http")) builder.withHttp();
else builder.withHttps();
}
if (matcher.group(3) == null || matcher.group(3).length() == 0)
{
String msg = "no base url identified in uri";
throw new AssertionException(msg);
}
builder.withBaseUrl(matcher.group(3));
if (matcher.group(5) == null || matcher.group(5).length() == 0)
{
String msg = "no owner identified in uri";
throw new AssertionException(msg);
}
builder.withOwner(matcher.group(5));
if (matcher.group(6) == null || matcher.group(6).length() == 0)
{
String msg = "no repo identified in uri";
throw new AssertionException(msg);
}
builder.withRepo(matcher.group(6));
if (matcher.group(8) != null && matcher.group(9) != null)
{
builder.withAuthentication(matcher.group(8), matcher.group(9));
}
else logger.log(Level.INFO, "no authentication given, creating anonymous instance");
return builder.build();
}
protected GithubTicketSystem(String acceptHeader, String baseUrl)
{
this(acceptHeader, baseUrl, null, null);
}
protected GithubTicketSystem(String acceptHeader, String baseUrl, String username, String apiKey)
{
super();
this.acceptHeader = acceptHeader;
this.apiKey = apiKey;
this.baseUrl = baseUrl;
this.username = username;
}
/**
* starts builder process for new github ticket
*/
@Override
public GithubTicketBuilder createTicket()
{
return new GithubTicketBuilder(this);
}
@Override
public GithubFilter find()
{
return new GithubFilter(this);
}
protected OkHttpClient getHttpClient() { return new OkHttpClient(); }
@Override
public GithubTicket getTicketById(String id)
{
ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Request.Builder requestBuilder = new Request.Builder()
.url(String.format("%s/%s", baseUrl, id))
.addHeader("accept", acceptHeader)
.get();
if (username != null && apiKey != null)
{
requestBuilder.addHeader("Authorization", Credentials.basic(username, apiKey));
logger.log(Level.FINEST, "added token authentication header");
}
HttpUrl.Builder urlBuilder = requestBuilder.build().url().newBuilder();
requestBuilder.url(urlBuilder.build());
OkHttpClient client = getHttpClient();
Response response;
try
{
Request request = requestBuilder.build();
logger.log(Level.FINEST, String.format(
"created request:\n%s",
(apiKey != null)
? request.toString().replace(apiKey, "SECRET")
: request.toString()
));
response = client.newCall(request).execute();
}
catch (IOException e)
{
logger.log(Level.SEVERE, String.format("get request FAILED with: %s", e.getMessage()));
if (getConfigTrue(TicketSystem.ConfigurationOptions.RETURN_NULL_ON_ERROR)) return null;
else throw new HttpReqeustException(e);
}
if (response.code() >= 400)
{
logger.log(Level.SEVERE, String.format(
"request failed with response code %d",
response.code()
));
if (getConfigTrue(TicketSystem.ConfigurationOptions.RETURN_NULL_ON_ERROR)) return null;
else throw new HttpResponseException(
String.format("ticket query failed, error response code: %d", response.code()),
response.code());
}
logger.log(Level.FINEST, "response received\n");
ResponseBody responseBody;
responseBody = response.body();
if (responseBody == null)
{
logger.log(Level.SEVERE, "query didn't deliver a body in response");
if (getConfigTrue(TicketSystem.ConfigurationOptions.RETURN_NULL_ON_ERROR)) return null;
else throw new HttpResponseException("ticket query failed, no response body", response.code());
}
GithubTicketResponse tr;
try
{
tr = mapper.readValue(responseBody.bytes(), GithubTicketResponse.class);
logger.log(Level.FINER, "parsed response body to ticketResponse instance");
} catch (IOException e)
{
logger.log(Level.SEVERE, String.format("parsing query response FAILED with: %s", e.getMessage()));
if (getConfigTrue(TicketSystem.ConfigurationOptions.RETURN_NULL_ON_ERROR)) return null;
else throw new DeserializationException(e);
}
return GithubTicket.fromTicketResponse(this, tr);
}
@Override
public boolean hasAssigneeSupport()
{
return true;
}
@Override
public boolean hasDefaultPagination()
{
return true;
}
@Override
public boolean hasLabelSupport()
{
return true;
}
@Override
public boolean hasPaginationSupport()
{
return true;
}
@Override
public boolean hasReturnNullOnErrorSupport()
{
return true;
}
}
package de.hftstuttgart.unifiedticketing.systems.github;
import de.hftstuttgart.unifiedticketing.core.Logging;
import de.hftstuttgart.unifiedticketing.core.TicketSystemBuilder;
import de.hftstuttgart.unifiedticketing.exceptions.AssertionException;
import java.util.logging.Level;
import java.util.logging.Logger;
public class GithubTicketSystemBuilder extends TicketSystemBuilder<GithubTicketSystemBuilder, GithubTicketSystem>
{
private static final Logger logger = Logging.getLogger(GithubTicketSystemBuilder.class.getName());
protected String acceptHeader;
protected String apiKey;
protected boolean https;
protected String owner;
protected String repo;
protected String username;
/**
* starts builder process for a {@link GithubTicketSystem} instance
*/
public GithubTicketSystemBuilder()
{
super();
acceptHeader = "application/vnd.github.v3+json";
https = true;
logger.log(Level.FINEST, "Builder for Github System instance started");
}
public GithubTicketSystemBuilder withAuthentication(String username, String apiKey)
{
this.username = username;
this.apiKey = apiKey;
logger.log(Level.FINEST, "set authentication values");
return this;
}
public GithubTicketSystemBuilder withHttp()
{
this.https = false;
logger.log(Level.FINEST, "set http");
return this;
}
public GithubTicketSystemBuilder withHttps()
{
this.https = true;
logger.log(Level.FINEST, "set https");
return this;
}
public GithubTicketSystemBuilder withOwner(String owner)
{
this.owner = owner;
logger.log(Level.FINEST, String.format("set owner to %s", this.owner));
return this;
}
public GithubTicketSystemBuilder withRepo(String repo)
{
this.repo = repo;
logger.log(Level.FINEST, String.format("set repo to %s", this.repo));
return this;
}
@Override
public GithubTicketSystem build()
{
if (baseUrl == null
|| owner == null
|| repo == null)
{
String msg = "one of mandatory fields 'baseUrl', 'owner' or 'repo' missing!";
throw new AssertionException(msg);
}
return new GithubTicketSystem(
acceptHeader,
String.format(
"%s://%s/repos/%s/%s/issues",
(https) ? "https" : "http",
baseUrl,
owner,
repo
),
username,
apiKey
);
}
}
package de.hftstuttgart.unifiedticketing.systems.github;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import de.hftstuttgart.unifiedticketing.core.Logging;
import de.hftstuttgart.unifiedticketing.exceptions.DeserializationException;
import de.hftstuttgart.unifiedticketing.exceptions.HttpResponseException;
import okhttp3.*;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
public class GithubFilterTest
{
public GithubFilter instance;
public Call call;
public ArgumentCaptor<Request> requestCaptor;
public Response.Builder responseBuilder;
@BeforeAll
public static void initBeforeAll()
{
Logging.setLevel(Level.ALL);
}
@BeforeEach
public void initBeforeEach()
{
GithubTicketSystem parent =
spy(new GithubTicketSystem("someHeader", "https://api.github.com"));
instance = spy(new GithubFilter(parent));
OkHttpClient client = mock(OkHttpClient.class);
call = mock(Call.class);
requestCaptor = ArgumentCaptor.forClass(Request.class);
responseBuilder = new Response.Builder()
.request(new Request.Builder().url("http://test.some.tld/").build())
.protocol(Protocol.HTTP_1_1)
.message("some message");
doReturn(client).when(instance).getHttpClient();
doReturn(call).when(client).newCall(requestCaptor.capture());
}
@Test
public void testGetServerError() throws IOException
{
doReturn(responseBuilder.code(500).build()).when(call).execute();
assertThrows(HttpResponseException.class, () -> instance.get());
}
@Test
public void testGetClientError() throws IOException
{
doReturn(responseBuilder.code(400).build()).when(call).execute();
assertThrows(HttpResponseException.class, () -> instance.get());
}
@Test
public void testGetNullBody() throws IOException
{
doReturn(responseBuilder.code(200).build()).when(call).execute();
assertThrows(HttpResponseException.class, () -> instance.get());
}
@Test
public void testGetNoJsonBody() throws IOException
{
doReturn(
responseBuilder
.code(200)
.body(ResponseBody.create("somestrangething", MediaType.get("application/json")))
.build())
.when(call).execute();
assertThrows(DeserializationException.class, () -> instance.get());
}
@Test
public void testGetRequestParamsPart1() throws IOException
{
doReturn(
responseBuilder
.code(200)
.body(ResponseBody.create("[]".getBytes(), MediaType.get("application/json")))
.build())
.when(call).execute();
String[] values = new String[]
{
"bug",
"label1",
"2",
"50",
};
instance
.withLabel(values[0])
.withLabel(values[1])
.setPage(Integer.parseInt(values[2]))
.setPageSize(Integer.parseInt(values[3]))
.isOpen()
.get();
HttpUrl url = requestCaptor.getValue().url();
assertAll(
() -> assertEquals(String.format("%s,%s", values[0], values[1]), url.queryParameter("labels")),
() -> assertEquals(values[2], url.queryParameter("page")),
() -> assertEquals(values[3], url.queryParameter("per_page")),
() -> assertEquals("open", url.queryParameter("state"))
);
}
@Test
public void testGetRequestParamsPart2() throws IOException
{
doReturn(
responseBuilder
.code(200)
.body(ResponseBody.create("[]".getBytes(), MediaType.get("application/json")))
.build())
.when(call).execute();
instance
.isClosed()
.get();
HttpUrl url = requestCaptor.getValue().url();
assertAll(
() -> assertEquals("closed", url.queryParameter("state"))
);
}
@Test
public void testGetLocalFilter() throws IOException
{
GithubTicketResponse res = new GithubTicketResponse();
res.number = 5;
res.title = "some title";
res.body = "descriptive text";
res.assignees = new HashSet<>();
res.state = "open";
res.labels = Arrays.stream(new String[]{"unifiedticketing", "bug"})
.map(l -> {
GithubTicketResponse.Label label = new GithubTicketResponse.Label();
label.name = l;
return label;
})
.collect(Collectors.toSet());
ObjectMapper mapper = new ObjectMapper();
ArrayNode arrayNode = mapper.createArrayNode();
arrayNode.add(mapper.valueToTree(res));
res.number = 8;
res.title = "some special title";
arrayNode.add(mapper.valueToTree(res));
res.number = 94;
res.body = "description with @username marked";
arrayNode.add(mapper.valueToTree(res));
doReturn(
responseBuilder
.code(200)
.body(ResponseBody.create(arrayNode.toString().getBytes(), MediaType.get("application/json")))
.build())
.when(call).execute();
List<GithubTicket> result = instance
.withTitleMatch("^.*special.*$")
.withDescriptionMatch("^.*@username.*$")
.get();
assertAll(
() -> assertEquals(1, result.size()),
() -> Assertions.assertEquals("94", result.get(0).getId())
);
}
@Test
public void testGetDeserialization() throws IOException
{
GithubTicketResponse res = new GithubTicketResponse();
res.number = 99;
res.title = "some title";
res.body = "descriptive text";
res.assignees = new HashSet<>();
res.state = "open";
res.labels = Arrays.stream(new String[]{"unifiedticketing", "feature-request"})
.map(l -> {
GithubTicketResponse.Label label = new GithubTicketResponse.Label();
label.name = l;
return label;
})
.collect(Collectors.toSet());
ObjectMapper mapper = new ObjectMapper();
ArrayNode arrayNode = mapper.createArrayNode();
arrayNode.add(mapper.valueToTree(res));
doReturn(
responseBuilder
.code(200)
.body(ResponseBody.create(arrayNode.toString().getBytes(), MediaType.get("application/json")))
.build())
.when(call).execute();
List<GithubTicket> expected = new LinkedList<>(
Collections.singleton(GithubTicket.fromTicketResponse(instance.parent, res)));
List<GithubTicket> actual = instance.get();
assertEquals(expected, actual);
assertTrue(expected.get(0).deepEquals(actual.get(0)));
}
}
package de.hftstuttgart.unifiedticketing.systems.github;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.hftstuttgart.unifiedticketing.core.Logging;
import de.hftstuttgart.unifiedticketing.exceptions.AssertionException;
import de.hftstuttgart.unifiedticketing.exceptions.DeserializationException;
import de.hftstuttgart.unifiedticketing.exceptions.HttpResponseException;
import okhttp3.*;
import okio.Buffer;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
public class GithubTicketBuilderTest
{
public GithubTicketBuilder instance;
public Call call;
public ArgumentCaptor<Request> requestCaptor;
public Response.Builder responseBuilder;
@BeforeAll
public static void initBeforeAll()
{
Logging.setLevel(Level.ALL);
}
@BeforeEach
public void initBeforeEach()
{
GithubTicketSystem parent =
spy(new GithubTicketSystem("something", "https://example.org"));
instance = spy(new GithubTicketBuilder(parent));
OkHttpClient client = mock(OkHttpClient.class);
call = mock(Call.class);
requestCaptor = ArgumentCaptor.forClass(Request.class);
responseBuilder = new Response.Builder()
.request(new Request.Builder().url("http://test.some.tld/").build())
.protocol(Protocol.HTTP_1_1)
.message("some message");
doReturn(client).when(instance).getHttpClient();
doReturn(call).when(client).newCall(requestCaptor.capture());
}
@Test
public void testSaveNoCreationWithoutMinimumRequirements()
{
assertThrows(AssertionException.class, () -> instance.create());
verify(instance, never()).getHttpClient();
}
@Test
public void testSaveServerError() throws IOException
{
doReturn(responseBuilder.code(500).build()).when(call).execute();
instance.title("we have a new title");
assertThrows(HttpResponseException.class, () -> instance.create());
}
@Test
public void testSaveClientError() throws IOException
{
doReturn(responseBuilder.code(400).build()).when(call).execute();
instance.title("we have another new title");
assertThrows(HttpResponseException.class, () -> instance.create());
}
@Test
public void testSaveNullBody() throws IOException
{
doReturn(responseBuilder.code(200).build()).when(call).execute();
instance.title("we have no body this time");
assertThrows(HttpResponseException.class, () -> instance.create());
}
@Test
public void testSaveNoJsonBody() throws IOException
{
doReturn(
responseBuilder
.code(200)
.body(ResponseBody.create("somestrangething", MediaType.get("application/json")))
.build())
.when(call).execute();
instance.title("this body is no json");
assertThrows(DeserializationException.class, () -> instance.create());
}
@Test
public void testSaveSuccessfulUpdate() throws IOException
{
GithubTicketResponse ticketResponse = new GithubTicketResponse();
ticketResponse.title = "title of ticket";
ticketResponse.body = "description";
ticketResponse.number = 5;
ticketResponse.labels = Arrays.stream(new String[]{"unifiedticketing", "feature-request"})
.map(l -> {
GithubTicketResponse.Label label = new GithubTicketResponse.Label();
label.name = l;
return label;
})
.collect(Collectors.toSet());
GithubTicketResponse.Assignee assignee = new GithubTicketResponse.Assignee();
assignee.id = 234;
assignee.login = "username";
ticketResponse.assignees = new HashSet<>(Collections.singleton(assignee));
GithubTicket expected = GithubTicket.fromTicketResponse(instance.parent, ticketResponse);
String responseJson = new ObjectMapper().writeValueAsString(ticketResponse);
doReturn(
responseBuilder
.code(200)
.body(ResponseBody.create(responseJson.getBytes(), MediaType.get("application/json")))
.build())
.when(call).execute();
GithubTicket actual = instance.title("some title").create();
assertEquals(expected, actual);
assertTrue(expected.deepEquals(actual));
}
@Test
public void testSaveRequestJson() throws IOException
{
doReturn(
responseBuilder
.code(200)
.body(ResponseBody.create("{}".getBytes(), MediaType.get("application/json")))
.build())
.when(call).execute();
instance.title("title of ticket")
.description("description")
.labels(new HashSet<>(Arrays.asList("bug", "unifiedticketing")))
.assignees("somebody")
.create();
Buffer buffer = new Buffer();
ObjectMapper mapper = new ObjectMapper();
requestCaptor.getValue().body().writeTo(buffer);
String expectedJson = "{\"assignees\":[\"somebody\"],\"body\":\"description\"," +
"\"title\":\"title of ticket\",\"labels\":[\"bug\",\"unifiedticketing\"]}";
assertEquals(mapper.readTree(expectedJson), mapper.readTree(buffer.readUtf8()));
}
}
package de.hftstuttgart.unifiedticketing.systems.github;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.hftstuttgart.unifiedticketing.core.Logging;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.util.logging.Level;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.*;
public class GithubTicketResponseTest
{
@BeforeAll
public static void initBeforeAll()
{
Logging.setLevel(Level.ALL);
}
@Test
public void testDeserialization() throws IOException
{
ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
String[] values = new String[]
// title descr iid label label userid username
{"title of ticket", "description", "5", "bug", "unifiedticketing", "234", "username"};
String responseJson = String.format(
"{\"title\":\"%s\",\"body\":\"%s\",\"number\":%s," +
"\"labels\":[{\"name\":\"%s\"},{\"name\":\"%s\"}],\"assignees\":[{\"id\":%s,\"login\":\"%s\"}]}",
(Object[]) values);
GithubTicketResponse response = mapper.readValue(responseJson.getBytes(), GithubTicketResponse.class);
assertAll(
() -> assertEquals(values[0], response.title),
() -> assertEquals(values[1], response.body),
() -> assertEquals(values[2], String.valueOf(response.number)),
() -> assertTrue(response.labels.stream().map(l -> l.name).collect(Collectors.toSet()).contains(values[3])),
() -> assertTrue(response.labels.stream().map(l -> l.name).collect(Collectors.toSet()).contains(values[4])),
() -> assertEquals(1, response.assignees.size()),
() -> assertEquals(values[5], String.valueOf(response.assignees.stream().findFirst().get().id)),
() -> assertEquals(values[6], response.assignees.stream().findFirst().get().login)
);
}
}
package de.hftstuttgart.unifiedticketing.systems.github;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import de.hftstuttgart.unifiedticketing.core.Logging;
import de.hftstuttgart.unifiedticketing.exceptions.AssertionException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.util.logging.Level;
import java.util.regex.Pattern;
public class GithubTicketSystemBuilderTest
{
public GithubTicketSystemBuilder instance;
@BeforeAll
public static void initBeforeAll()
{
Logging.setLevel(Level.ALL);
}
@BeforeEach
public void initBeforeEach()
{
instance = Mockito.spy(new GithubTicketSystemBuilder());
}
@Test
public void testInit()
{
assertAll(
() -> assertEquals("application/vnd.github.v3+json", instance.acceptHeader),
() -> assertNull(instance.apiKey),
() -> assertNull(instance.owner),
() -> assertNull(instance.repo),
() -> assertTrue(instance.https),
() -> assertNull(instance.username)
);
}
@Test
public void testWithApiKey()
{
String username = "someUser";
String apiKey = "asoashdboahasjhdfbeuazhvfef4q34v3h4v435v2";
instance.withAuthentication(username, apiKey);
assertAll(
() -> assertEquals(username, instance.username),
() -> assertEquals(apiKey, instance.apiKey)
);
}
@Test
public void testWithHttp()
{
instance.https = true;
instance.withHttp();
assertFalse(instance.https);
}
@Test
public void testWithHttps()
{
instance.https = false;
instance.withHttps();
assertTrue(instance.https);
}
@Test
public void testWithProjectIdByInt()
{
String expected = "255254123";
instance.withRepo(expected);
assertEquals(expected, instance.repo);
}
@Test
public void testBuild()
{
instance.withBaseUrl("blabla.tld");
assertThrows(AssertionException.class, () -> instance.build());
String apiKey = "aaesfsef32qqfq3f";
String baseUrl = "api.github.com";
String owner = "someOwner";
String repo = "42";
String username = "someUsername";
instance = new GithubTicketSystemBuilder();
instance.withOwner(owner);
instance.withRepo(repo);
assertThrows(AssertionException.class, () -> instance.build());
instance = new GithubTicketSystemBuilder();
String regex = "^%s://%s/repos/%s/%s/issues$";
Pattern pattern = Pattern.compile(
String.format(regex, "https", baseUrl, owner, repo));
instance.withBaseUrl(baseUrl)
.withOwner(owner)
.withRepo(repo);
GithubTicketSystem system = instance.build();
assertTrue(pattern.matcher(system.baseUrl).matches());
pattern = Pattern.compile(String.format(regex, "http", baseUrl, owner, repo));
instance
.withHttp()
.withAuthentication(username, apiKey);
system = instance.build();
assertTrue(pattern.matcher(system.baseUrl).matches());
assertEquals(username, system.username);
assertEquals(apiKey, system.apiKey);
}
}
package de.hftstuttgart.unifiedticketing.systems.github;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import de.hftstuttgart.unifiedticketing.core.Logging;
import de.hftstuttgart.unifiedticketing.core.TicketSystem;
import de.hftstuttgart.unifiedticketing.exceptions.AssertionException;
import de.hftstuttgart.unifiedticketing.exceptions.DeserializationException;
import de.hftstuttgart.unifiedticketing.exceptions.HttpResponseException;
import okhttp3.*;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
public class GithubTicketSystemTest
{
public GithubTicketSystem instance;
public Call call;
public ArgumentCaptor<Request> requestCaptor;
public Response.Builder responseBuilder;
@BeforeAll
public static void initBeforeAll()
{
Logging.setLevel(Level.ALL);
}
@BeforeEach
public void initBeforeEach()
{
instance = spy(new GithubTicketSystem("someHeader", "https://api.github.com"));
OkHttpClient client = mock(OkHttpClient.class);
call = mock(Call.class);
requestCaptor = ArgumentCaptor.forClass(Request.class);
responseBuilder = new Response.Builder()
.request(new Request.Builder().url("http://test.some.tld/").build())
.protocol(Protocol.HTTP_1_1)
.message("some message");
doReturn(client).when(instance).getHttpClient();
doReturn(call).when(client).newCall(requestCaptor.capture());
}
@Test
public void testFromUri()
{
String base = "api.github.com";
String owner = "myOwner";
String repo = "myRepo";
String username = "someUser";
String apikey = "afdaf3aqraf3afafmyxcbvmyxvbas3wrawra";
GithubTicketSystem actual = (GithubTicketSystem) TicketSystem.fromUri(buildTestUri(true, base, owner, repo, username, apikey));
assertEquals(buildFinalBaseurl(true, base, owner, repo), actual.baseUrl);
assertEquals(username, actual.username);
assertEquals(apikey, actual.apiKey);
assertEquals(GithubTicketSystem.class, actual.getClass());
base = "api.github.somedomain.tld:8080";
owner = "otherOwner";
repo = "otherRepo";
username = "me";
apikey = "a34adfae3ra3rasd";
actual = (GithubTicketSystem) TicketSystem.fromUri(buildTestUri(false, base, owner, repo, username, apikey));
assertEquals(buildFinalBaseurl(false, base, owner, repo), actual.baseUrl);
assertEquals(username, actual.username);
assertEquals(apikey, actual.apiKey);
assertEquals(GithubTicketSystem.class, actual.getClass());
actual = (GithubTicketSystem) TicketSystem.fromUri(buildTestUri(false, base, owner, repo, null, null));
assertEquals(buildFinalBaseurl(false, base, owner, repo), actual.baseUrl);
assertNull(actual.username);
assertNull(actual.apiKey);
assertEquals(GithubTicketSystem.class, actual.getClass());
assertThrows(AssertionException.class, () -> TicketSystem.fromUri("unifiedticketing:github:blablabal"));
}
@Test
public void testCreateTicket()
{
assertEquals(GithubTicketBuilder.class, instance.createTicket().getClass());
assertSame(instance, instance.createTicket().parent);
}
@Test
public void testFind()
{
assertEquals(GithubFilter.class, instance.find().getClass());
assertSame(instance, instance.find().parent);
}
@Test
public void testGetServerError() throws IOException
{
doReturn(responseBuilder.code(500).build()).when(call).execute();
assertThrows(HttpResponseException.class, () -> instance.getTicketById("bla"));
}
@Test
public void testGetClientError() throws IOException
{
doReturn(responseBuilder.code(400).build()).when(call).execute();
assertThrows(HttpResponseException.class, () -> instance.getTicketById("bla"));
}
@Test
public void testGetNullBody() throws IOException
{
doReturn(responseBuilder.code(200).build()).when(call).execute();
assertThrows(HttpResponseException.class, () -> instance.getTicketById("bla"));
}
@Test
public void testGetNoJsonBody() throws IOException
{
doReturn(
responseBuilder
.code(200)
.body(ResponseBody.create("somestrangething", MediaType.get("application/json")))
.build())
.when(call).execute();
assertThrows(DeserializationException.class, () -> instance.getTicketById("bla"));
}
@Test
public void testGetTicketById() throws IOException
{
GithubTicketResponse res = new GithubTicketResponse();
res.number = 99;
res.title = "some title";
res.body = "descriptive text";
res.assignees = new HashSet<>();
res.state = "open";
res.labels = Arrays.stream(new String[]{"unifiedticketing", "feature-request"})
.map(l -> {
GithubTicketResponse.Label label = new GithubTicketResponse.Label();
label.name = l;
return label;
})
.collect(Collectors.toSet());
ObjectMapper mapper = new ObjectMapper();
doReturn(
responseBuilder
.code(200)
.body(ResponseBody.create(mapper.writeValueAsString(res), MediaType.get("application/json")))
.build())
.when(call).execute();
GithubTicket expected = GithubTicket.fromTicketResponse(instance, res);
GithubTicket actual = instance.getTicketById(res.number);
assertEquals(expected, actual);
assertTrue(expected.deepEquals(actual));
}
@Test
public void testSupport()
{
assertAll(
() -> assertTrue(instance.hasAssigneeSupport()),
() -> assertTrue(instance.hasDefaultPagination()),
() -> assertTrue(instance.hasLabelSupport()),
() -> assertTrue(instance.hasPaginationSupport()),
() -> assertTrue(instance.hasReturnNullOnErrorSupport())
);
}
private static String buildTestUri(boolean https, String base, String owner, String repo, String username, String apikey)
{
return String.format(
"unifiedticketing:github:%s://%s::%s:%s%s",
(https) ? "https": "http",
base,
owner,
repo,
(username == null || apikey == null) ? "" : ":" + username + ":" + apikey);
}
private static String buildFinalBaseurl(boolean https, String base, String owner, String repo)
{
return String.format("%s://%s/repos/%s/%s/issues", (https) ? "https" : "http", base, owner, repo);
}
}
package de.hftstuttgart.unifiedticketing.systems.github;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.hftstuttgart.unifiedticketing.core.Logging;
import de.hftstuttgart.unifiedticketing.exceptions.AssertionException;
import de.hftstuttgart.unifiedticketing.exceptions.DeserializationException;
import de.hftstuttgart.unifiedticketing.exceptions.HttpResponseException;
import okhttp3.*;
import okio.Buffer;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
public class GithubTicketTest
{
public GithubTicket instance;
public Call call;
public ArgumentCaptor<Request> requestCaptor;
public Response.Builder responseBuilder;
@BeforeAll
public static void initBeforeAll()
{
Logging.setLevel(Level.ALL);
}
@BeforeEach
public void initBeforeEach()
{
instance =
spy(new GithubTicket(
spy(new GithubTicketSystem("someHeader", "https://example.org"))));
OkHttpClient client = mock(OkHttpClient.class);
call = mock(Call.class);
requestCaptor = ArgumentCaptor.forClass(Request.class);
responseBuilder = new Response.Builder()
.request(new Request.Builder().url("http://test.some.tld/").build())
.protocol(Protocol.HTTP_1_1)
.message("some message");
doReturn(client).when(instance).getHttpClient();
doReturn(call).when(client).newCall(requestCaptor.capture());
}
@Test
public void testFromTicketResponse()
{
GithubTicketResponse res = new GithubTicketResponse();
GithubTicket ticket = GithubTicket.fromTicketResponse(instance.getParent(), res);
assertAll(
() -> assertSame(instance.getParent(), ticket.getParent()),
() -> assertNull(ticket.getTitle()),
() -> assertNull(ticket.getDescription()),
() -> assertEquals("0", ticket.getId()),
() -> assertNotNull(ticket.getLabels()),
() -> assertTrue(ticket.isOpen()),
() -> assertNull(ticket.getTitle()),
() -> assertNotNull(ticket.getAssignees())
);
}
@Test
public void testAddAssigneeByString()
{
String username = "someUsername";
instance.addAssignee(username);
assertEquals(1, instance.getAssignees().size());
assertEquals(username, instance.getAssignees().stream().findFirst().get().username);
}
@Test
public void testRemoveAssigneeByString()
{
// first add assignee and check it was added properly
testAddAssigneeByString();
String username = instance.getAssignees().stream().findFirst().get().username;
instance.removeAssignee(username);
assertEquals(0, instance.getAssignees().size());
}
@Test
public void testSaveNoHttpClientWithoutChanges()
{
GithubTicket actual = instance.save();
verify(instance, never()).getHttpClient();
assertSame(instance, actual);
}
@Test
public void testSaveServerError() throws IOException
{
doReturn(responseBuilder.code(500).build()).when(call).execute();
instance.setTitle("we have a new title");
assertThrows(HttpResponseException.class, () -> instance.save());
}
@Test
public void testSaveClientError() throws IOException
{
doReturn(responseBuilder.code(400).build()).when(call).execute();
instance.setTitle("we have another new title");
assertThrows(HttpResponseException.class, () -> instance.save());
}
@Test
public void testSaveNullBody() throws IOException
{
doReturn(responseBuilder.code(200).build()).when(call).execute();
instance.setTitle("we have no body this time");
assertThrows(HttpResponseException.class, () -> instance.save());
}
@Test
public void testSaveNoJsonBody() throws IOException
{
doReturn(
responseBuilder
.code(200)
.body(ResponseBody.create("somestrangething", MediaType.get("application/json")))
.build())
.when(call).execute();
instance.setTitle("this body is no json");
assertThrows(DeserializationException.class, () -> instance.save());
}
@Test
public void testSaveSuccessfulUpdate() throws IOException
{
GithubTicketResponse ticketResponse = new GithubTicketResponse();
ticketResponse.title = "title of ticket";
ticketResponse.body = "description";
ticketResponse.number = 5;
ticketResponse.labels = Arrays.stream(new String[]{"bug", "unifiedticketing"})
.map(l -> {
GithubTicketResponse.Label label = new GithubTicketResponse.Label();
label.name = l;
return label;
})
.collect(Collectors.toSet());
GithubTicketResponse.Assignee assignee = new GithubTicketResponse.Assignee();
assignee.id = 234;
assignee.login = "username";
ticketResponse.assignees = new HashSet<>(Collections.singleton(assignee));
GithubTicket expected = GithubTicket.fromTicketResponse(instance.getParent(), ticketResponse);
String responseJson = new ObjectMapper().writeValueAsString(ticketResponse);
doReturn(
responseBuilder
.code(200)
.body(ResponseBody.create(responseJson.getBytes(), MediaType.get("application/json")))
.build())
.when(call).execute();
GithubTicket actual = instance.close().save();
assertEquals(expected, actual);
assertTrue(expected.deepEquals(actual));
}
@Test
public void testSaveRequestJson() throws IOException
{
doReturn(
responseBuilder
.code(200)
.body(ResponseBody.create("{}".getBytes(), MediaType.get("application/json")))
.build())
.when(call).execute();
instance.setTitle("title of ticket")
.setDescription("description")
.setLabels(new HashSet<>(Arrays.asList("bug", "unifiedticketing")))
.addAssignee("someUsername")
.save();
Buffer buffer = new Buffer();
ObjectMapper mapper = new ObjectMapper();
requestCaptor.getValue().body().writeTo(buffer);
String expectedJson = "{\"assignees\":[\"someUsername\"],\"body\":\"description\"," +
"\"title\":\"title of ticket\",\"labels\":[\"bug\",\"unifiedticketing\"]}";
assertEquals(mapper.readTree(expectedJson), mapper.readTree(buffer.readUtf8()));
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment