diff --git a/pom.xml b/pom.xml
index 5bab9bb26..c0cd0c787 100644
--- a/pom.xml
+++ b/pom.xml
@@ -135,6 +135,12 @@
4.13
test
+
+ org.mockito
+ mockito-core
+ 1.10.19
+ test
+
org.hamcrest
hamcrest-all
diff --git a/src/main/java/org/zendesk/client/v2/Zendesk.java b/src/main/java/org/zendesk/client/v2/Zendesk.java
index 072130bdb..8b0498fa9 100644
--- a/src/main/java/org/zendesk/client/v2/Zendesk.java
+++ b/src/main/java/org/zendesk/client/v2/Zendesk.java
@@ -38,10 +38,13 @@
import org.zendesk.client.v2.model.Organization;
import org.zendesk.client.v2.model.OrganizationField;
import org.zendesk.client.v2.model.OrganizationMembership;
+import org.zendesk.client.v2.model.Page;
import org.zendesk.client.v2.model.SatisfactionRating;
import org.zendesk.client.v2.model.SearchResultEntity;
import org.zendesk.client.v2.model.SortOrder;
+import org.zendesk.client.v2.model.Sorting;
import org.zendesk.client.v2.model.Status;
+import org.zendesk.client.v2.model.SupportCenterLocale;
import org.zendesk.client.v2.model.SuspendedTicket;
import org.zendesk.client.v2.model.Ticket;
import org.zendesk.client.v2.model.TicketForm;
@@ -63,6 +66,8 @@
import org.zendesk.client.v2.model.hc.Translation;
import org.zendesk.client.v2.model.hc.PermissionGroup;
import org.zendesk.client.v2.model.hc.UserSegment;
+import org.zendesk.client.v2.model.oauth.OAuthRequest;
+import org.zendesk.client.v2.model.oauth.OAuthToken;
import org.zendesk.client.v2.model.schedules.Holiday;
import org.zendesk.client.v2.model.schedules.Schedule;
import org.zendesk.client.v2.model.targets.BasecampTarget;
@@ -86,12 +91,12 @@
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
-import java.util.regex.Pattern;
/**
* @author stephenc
@@ -118,7 +123,7 @@ private static Map> searchResultType
result.put("group", Group.class);
result.put("organization", Organization.class);
result.put("topic", Topic.class);
- result.put("article", Article.class);
+ result.put("article", Article.class);
return Collections.unmodifiableMap(result);
}
@@ -202,6 +207,13 @@ public void close() {
// Action methods
//////////////////////////////////////////////////////////////////////
+ public String getOAuthToken(String subdomain, String code, String redirectUri, String clientId, String clientSecret) {
+ return complete(submit(reqUnauthorized("POST", new TemplateUri("https://{subdomain}.zendesk.com/oauth/tokens").set("subdomain", subdomain),
+ JSON, json(new OAuthRequest(code, redirectUri, clientId, clientSecret))
+ ),
+ handle(OAuthToken.class))).getAccessToken();
+ }
+
public JobStatus getJobStatus(JobStatus status) {
return complete(getJobStatusAsync(status));
}
@@ -411,6 +423,31 @@ public Iterable getArticleFromSearch(String searchTerm, Long sectionId)
.set("query", searchTerm).set("section", sectionId), handleList(Article.class, "results"));
}
+ public Iterable getArticleFromSearch(String locale, String searchTerm, Section section, Category category, Page page) {
+
+ TemplateUri tmpl = tmpl("/help_center/articles/search.json{?locale, query, category, section, page, per_page")
+ .set("locale", locale)
+ .set("query", searchTerm);
+
+ if(category != null)
+ {
+ tmpl.set("category", category.getId());
+ }
+
+ if(section != null)
+ {
+ tmpl.set("section", section.getId());
+ }
+
+ if(page != null)
+ {
+ tmpl.set("page", page.getPageNo());
+ tmpl.set("per_page", page.getPerPage());
+ }
+
+ return complete(submit(req("GET", tmpl), handleList(Article.class, "results")));
+ }
+
public Iterable getArticlesFromAnyLabels(List labels) {
return new PagedIterable<>(tmpl("/help_center/articles/search.json{?label_names}").set("label_names", labels),
handleList(Article.class, "results"));
@@ -426,6 +463,13 @@ public List getAttachmentsFromArticle(Long articleID) {
handleArticleAttachmentsList("article_attachments")));
}
+ public List getAttachmentsFromArticle(String locale, Long articleId) {
+ return complete(submit(req("GET", tmpl("/help_center/{locale}/articles/{articleId}/attachments.json")
+ .set("locale", locale)
+ .set("articleId", articleId)),
+ handleArticleAttachmentsList("article_attachments")));
+ }
+
public List getTickets(long id, long... ids) {
return complete(submit(req("GET", tmpl("/tickets/show_many.json{?ids}").set("ids", idArray(id, ids))),
handleList(Ticket.class, "tickets")));
@@ -1678,6 +1722,18 @@ public Iterable getDynamicContentItems() {
return new PagedIterable<>(cnst("/dynamic_content/items.json"), handleList(DynamicContentItem.class, "items"));
}
+ public Iterable getDynamicContentItems(Page page, Sorting sorting) {
+ return complete(submit(
+ req("GET",
+ tmpl("/dynamic_content/items.json?page={page}&per_page={per_page}&sort_by={sort_by}&sort_order={sort_order}")
+ .set("page", page.getPageNo())
+ .set("per_page", page.getPerPage())
+ .set("sort_by", sorting.getSortBy())
+ .set("sort_order", sorting.getSortOrder().getQueryParameter())
+ ),
+ handleList(DynamicContentItem.class, "items")));
+ }
+
public DynamicContentItem getDynamicContentItem(long id) {
return complete(submit(req("GET", tmpl("/dynamic_content/items/{id}.json").set("id", id)), handle(DynamicContentItem.class, "item")));
}
@@ -1703,8 +1759,13 @@ public void deleteDynamicContentItem(DynamicContentItem item) {
public Iterable getDynamicContentItemVariants(DynamicContentItem item) {
checkHasId(item);
+ return getDynamicContentItemVariants(item.getId());
+ }
+
+ public Iterable getDynamicContentItemVariants(Long itemId) {
+ checkHasItemId(itemId);
return new PagedIterable<>(
- tmpl("/dynamic_content/items/{id}/variants.json").set("id", item.getId()),
+ tmpl("/dynamic_content/items/{id}/variants.json").set("id", itemId),
handleList(DynamicContentItemVariant.class, "variants"));
}
@@ -1734,6 +1795,25 @@ public void deleteDynamicContentItemVariant(Long itemId, DynamicContentItemVaria
// TODO search with query building API
+ //////////////////////////////////////////////////////////////////////
+ // Support Center locales
+ //////////////////////////////////////////////////////////////////////
+ /**
+ * Get enabled locales for Support Center
+ * @return
+ */
+ public List getEnabledSupportCenterLocales() {
+ return (List)complete(this.submit(this.req("GET", this.cnst("/locales.json")), this.handleList(Locale.class, "locales")));
+ }
+
+ /**
+ * Get all available locales for Support Center
+ * @return
+ */
+ public List getAvailableSupportCenterLocales() {
+ return (List)complete(this.submit(this.req("GET", this.cnst("/locales/public.json")), this.handleList(Locale.class, "locales")));
+ }
+
//////////////////////////////////////////////////////////////////////
// Action methods for Help Center
//////////////////////////////////////////////////////////////////////
@@ -1941,17 +2021,44 @@ public List getArticlesFromPage(int page) {
handleList(Article.class, "articles")));
}
+ public Iterable getArticles(String locale, Page page, Sorting sorting) {
+ return complete(submit(
+ req("GET",
+ tmpl("/help_center/{locale}/articles.json{?page, per_page, sort_by, sort_order}")
+ .set("locale", locale)
+ .set("page", page.getPageNo())
+ .set("per_page", page.getPerPage())
+ .set("sort_by", sorting.getSortBy())
+ .set("sort_order", sorting.getSortOrder())
+ ),
+ handleList(Article.class, "articles")));
+ }
+
public Article getArticle(long id) {
return complete(submit(req("GET", tmpl("/help_center/articles/{id}.json").set("id", id)),
handle(Article.class, "article")));
}
+ public Article getArticle(String locale, long id) {
+ return complete(submit(req("GET", tmpl("/help_center/{locale}/articles/{id}.json")
+ .set("id", id).set("locale", locale)),
+ handle(Article.class, "article")));
+ }
+
public Iterable getArticleTranslations(Long articleId) {
return new PagedIterable<>(
tmpl("/help_center/articles/{articleId}/translations.json").set("articleId", articleId),
handleList(Translation.class, "translations"));
}
+ public Translation getArticleTranslation(Long articleId, String locale) {
+ return complete(submit(req("GET", tmpl("/help_center/articles/{articleId}/translations/{locale}.json")
+ .set("articleId", articleId)
+ .set("locale", locale)),
+ handle(Translation.class, "translation")
+ ));
+ }
+
public Article createArticle(Article article) {
checkHasSectionId(article);
return complete(submit(req("POST", tmpl("/help_center/sections/{id}/articles.json").set("id", article.getSectionId()),
@@ -1978,13 +2085,13 @@ public Article updateArticle(Article article) {
public Translation createArticleTranslation(Long articleId, Translation translation) {
checkHasArticleId(articleId);
return complete(submit(req("POST", tmpl("/help_center/articles/{id}/translations.json").set("id", articleId),
- JSON, json(Collections.singletonMap("translation", translation))), handle(Translation.class, "translation")));
+ JSON, json(Collections.singletonMap("translation", translation))), handleWithExceptionIfNotFound(Translation.class, "translation")));
}
public Translation updateArticleTranslation(Long articleId, String locale, Translation translation) {
checkHasId(translation);
return complete(submit(req("PUT", tmpl("/help_center/articles/{id}/translations/{locale}.json").set("id", articleId).set("locale",locale),
- JSON, json(Collections.singletonMap("translation", translation))), handle(Translation.class, "translation")));
+ JSON, json(Collections.singletonMap("translation", translation))), handleWithExceptionIfNotFound(Translation.class, "translation")));
}
public void deleteArticle(Article article) {
@@ -2033,16 +2140,44 @@ public Iterable getCategories() {
handleList(Category.class, "categories"));
}
+ public Iterable getCategories(String locale, Page page, Sorting sorting) {
+ return complete(submit(
+ req("GET",
+ tmpl("/help_center/{locale}/categories.json?page={page}&per_page={per_page}&sort_by={sort_by}&sort_order={sort_order}")
+ .set("locale", locale)
+ .set("page", page.getPageNo())
+ .set("per_page", page.getPerPage())
+ .set("sort_by", sorting.getSortBy())
+ .set("sort_order", sorting.getSortOrder().getQueryParameter())
+ ),
+ handleList(Category.class, "categories")));
+ }
+
public Category getCategory(long id) {
return complete(submit(req("GET", tmpl("/help_center/categories/{id}.json").set("id", id)),
handle(Category.class, "category")));
}
+ public Category getCategory(String locale, long id) {
+ return complete(submit(req("GET", tmpl("/help_center/{locale}/categories/{id}.json")
+ .set("id", id).set("locale", locale)),
+ handle(Category.class, "category")));
+ }
+
public Iterable getCategoryTranslations(Long categoryId) {
return new PagedIterable<>(
tmpl("/help_center/categories/{categoryId}/translations.json").set("categoryId", categoryId),
handleList(Translation.class, "translations"));
}
+
+ public Translation getCategoryTranslation(Long categoryId, String locale) {
+ return complete(submit(req("GET", tmpl("/help_center/categories/{categoryId}/translations/{locale}.json")
+ .set("categoryId", categoryId)
+ .set("locale", locale)),
+ handle(Translation.class, "translation")
+ ));
+ }
+
public Category createCategory(Category category) {
return complete(submit(req("POST", cnst("/help_center/categories.json"),
JSON, json(Collections.singletonMap("category", category))), handle(Category.class, "category")));
@@ -2057,13 +2192,13 @@ public Category updateCategory(Category category) {
public Translation createCategoryTranslation(Long categoryId, Translation translation) {
checkHasCategoryId(categoryId);
return complete(submit(req("POST", tmpl("/help_center/categories/{id}/translations.json").set("id", categoryId),
- JSON, json(Collections.singletonMap("translation", translation))), handle(Translation.class, "translation")));
+ JSON, json(Collections.singletonMap("translation", translation))), handleWithExceptionIfNotFound(Translation.class, "translation")));
}
public Translation updateCategoryTranslation(Long categoryId, String locale, Translation translation) {
checkHasId(translation);
return complete(submit(req("PUT", tmpl("/help_center/categories/{id}/translations/{locale}.json").set("id", categoryId).set("locale",locale),
- JSON, json(Collections.singletonMap("translation", translation))), handle(Translation.class, "translation")));
+ JSON, json(Collections.singletonMap("translation", translation))), handleWithExceptionIfNotFound(Translation.class, "translation")));
}
public void deleteCategory(Category category) {
@@ -2084,16 +2219,55 @@ public Iterable getSections(Category category) {
handleList(Section.class, "sections"));
}
+ public Iterable getSections(String locale, Page page, Sorting sorting) {
+ return complete(submit(
+ req("GET",
+ tmpl("/help_center/{locale}/sections.json?page={page}&per_page={per_page}&sort_by={sort_by}&sort_order={sort_order}")
+ .set("locale", locale)
+ .set("page", page.getPageNo())
+ .set("per_page", page.getPerPage())
+ .set("sort_by", sorting.getSortBy())
+ .set("sort_order", sorting.getSortOrder().getQueryParameter())
+ ),
+ handleList(Section.class, "sections")));
+ }
+
+ public Iterable getSections(String locale, Category category, Page page, Sorting sorting) {
+ checkHasId(category);
+ return complete(submit(
+ req("GET",
+ tmpl("/help_center/{locale}/categories/{categoryId}/sections.json?page={page}&per_page={per_page}&sort_by={sort_by}&sort_order={sort_order}")
+ .set("locale", locale)
+ .set("categoryId", category.getId())
+ .set("page", page.getPageNo())
+ .set("per_page", page.getPerPage())
+ .set("sort_by", sorting.getSortBy())
+ .set("sort_order", sorting.getSortOrder().getQueryParameter())
+ ),
+ handleList(Section.class, "sections")));
+ }
+
public Section getSection(long id) {
return complete(submit(req("GET", tmpl("/help_center/sections/{id}.json").set("id", id)),
handle(Section.class, "section")));
}
+ public Section getSection(String locale, long id) {
+ return complete(submit(req("GET", tmpl("/help_center/{locale}/sections/{sectionId}.json").set("id", id).set("locale", locale)),
+ handle(Section.class, "section")));
+ }
+
public Iterable getSectionTranslations(Long sectionId) {
return new PagedIterable<>(
tmpl("/help_center/sections/{sectionId}/translations.json").set("sectionId", sectionId),
handleList(Translation.class, "translations"));
}
+
+ public Translation getSectionTranslation(String locale, long sectionId) {
+ return complete(submit(req("GET", tmpl("/help_center/sections/{sectionId}/translations/{locale}.json").set("sectionId", sectionId).set("locale", locale)),
+ handle(Translation.class, "translation")));
+ }
+
public Section createSection(Section section) {
checkHasCategoryId(section);
return complete(submit(req("POST", tmpl("/help_center/categories/{id}/sections.json").set("id", section.getCategoryId()),
@@ -2109,13 +2283,13 @@ public Section updateSection(Section section) {
public Translation createSectionTranslation(Long sectionId, Translation translation) {
checkHasSectionId(sectionId);
return complete(submit(req("POST", tmpl("/help_center/sections/{id}/translations.json").set("id", sectionId),
- JSON, json(Collections.singletonMap("translation", translation))), handle(Translation.class, "translation")));
+ JSON, json(Collections.singletonMap("translation", translation))), handleWithExceptionIfNotFound(Translation.class, "translation")));
}
public Translation updateSectionTranslation(Long sectionId, String locale, Translation translation) {
checkHasId(translation);
return complete(submit(req("PUT", tmpl("/help_center/sections/{id}/translations/{locale}.json").set("id", sectionId).set("locale",locale),
- JSON, json(Collections.singletonMap("translation", translation))), handle(Translation.class, "translation")));
+ JSON, json(Collections.singletonMap("translation", translation))), handleWithExceptionIfNotFound(Translation.class, "translation")));
}
public void deleteSection(Section section) {
@@ -2228,8 +2402,6 @@ private Request req(String method, Uri template) {
return req(method, template.toString());
}
- private static final Pattern RESTRICTED_PATTERN = Pattern.compile("%2B", Pattern.LITERAL);
-
private Request req(String method, String url) {
return reqBuilder(method, url).build();
}
@@ -2249,7 +2421,15 @@ private RequestBuilder reqBuilder(String method, String url) {
builder.addHeader("Authorization", "Bearer " + oauthToken);
}
headers.forEach(builder::setHeader);
- return builder.setUrl(RESTRICTED_PATTERN.matcher(url).replaceAll("+")); // replace out %2B with + due to API restriction
+ return builder.setUrl(url);
+ }
+
+ private Request reqUnauthorized(String method, Uri template, String contentType, byte[] body) {
+ RequestBuilder builder = new RequestBuilder(method);
+ builder.setUrl(template.toString());
+ builder.addHeader("Content-type", contentType);
+ builder.setBody(body);
+ return builder.build();
}
protected ZendeskAsyncCompletionHandler handleStatus() {
@@ -2308,20 +2488,42 @@ public T onCompleted(Response response) throws Exception {
return mapper.convertValue(mapper.readTree(response.getResponseBodyAsStream()).get(name), clazz);
} else if (isRateLimitResponse(response)) {
throw new ZendeskResponseRateLimitException(response);
+ } else if (response.getStatusCode() == 404) {
+ throw new ZendeskEntityNotFoundException(response);
+ } else {
+ throw new ZendeskResponseException(response);
}
+ }
+ }
+
+ private class BasicAsyncCompletionHandlerWithNullIfNotFound extends BasicAsyncCompletionHandler {
+ public BasicAsyncCompletionHandlerWithNullIfNotFound(Class clazz, String name, Class... typeParams) {
+ super(clazz, name, typeParams);
+ }
+
+ @Override
+ public T onCompleted(Response response) throws Exception {
+ logResponse(response);
+
if (response.getStatusCode() == 404) {
return null;
+ } else {
+ return super.onCompleted(response);
}
- throw new ZendeskResponseException(response);
}
}
protected ZendeskAsyncCompletionHandler handle(final Class clazz, final String name, final Class... typeParams) {
- return new BasicAsyncCompletionHandler<>(clazz, name, typeParams);
+ return new BasicAsyncCompletionHandlerWithNullIfNotFound<>(clazz, name, typeParams);
}
+ private ZendeskAsyncCompletionHandler handleWithExceptionIfNotFound(final Class clazz, final String name, final Class... typeParams) {
+ return new BasicAsyncCompletionHandler(clazz, name, typeParams);
+ }
+
+
protected ZendeskAsyncCompletionHandler> handleJobStatus(final Class resultClass) {
- return new BasicAsyncCompletionHandler>(JobStatus.class, "job_status", resultClass) {
+ return new BasicAsyncCompletionHandlerWithNullIfNotFound>(JobStatus.class, "job_status", resultClass) {
@Override
public JobStatus onCompleted(Response response) throws Exception {
JobStatus result = super.onCompleted(response);
diff --git a/src/main/java/org/zendesk/client/v2/ZendeskEntityNotFoundException.java b/src/main/java/org/zendesk/client/v2/ZendeskEntityNotFoundException.java
new file mode 100644
index 000000000..7ade92ce8
--- /dev/null
+++ b/src/main/java/org/zendesk/client/v2/ZendeskEntityNotFoundException.java
@@ -0,0 +1,11 @@
+package org.zendesk.client.v2;
+
+import org.asynchttpclient.Response;
+
+import java.io.IOException;
+
+public class ZendeskEntityNotFoundException extends ZendeskResponseException {
+ public ZendeskEntityNotFoundException(Response response) throws IOException {
+ super(response);
+ }
+}
diff --git a/src/main/java/org/zendesk/client/v2/model/Page.java b/src/main/java/org/zendesk/client/v2/model/Page.java
new file mode 100644
index 000000000..ff377486d
--- /dev/null
+++ b/src/main/java/org/zendesk/client/v2/model/Page.java
@@ -0,0 +1,66 @@
+package org.zendesk.client.v2.model;
+
+import java.util.Objects;
+
+public class Page
+{
+ int pageNo;
+ int perPage;
+
+ public Page()
+ {
+ }
+
+ public Page(int pageNo, int perPage)
+ {
+ this.pageNo = pageNo;
+ this.perPage = perPage;
+ }
+
+ public int getPageNo()
+ {
+ return pageNo;
+ }
+
+ public void setPageNo(int pageNo)
+ {
+ this.pageNo = pageNo;
+ }
+
+ public int getPerPage()
+ {
+ return perPage;
+ }
+
+ public void setPerPage(int perPage)
+ {
+ this.perPage = perPage;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "Page{" +
+ "page=" + pageNo +
+ ", perPage=" + perPage +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ Page page1 = (Page) o;
+ return pageNo == page1.pageNo &&
+ perPage == page1.perPage;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(pageNo, perPage);
+ }
+}
diff --git a/src/main/java/org/zendesk/client/v2/model/Sorting.java b/src/main/java/org/zendesk/client/v2/model/Sorting.java
new file mode 100644
index 000000000..92fd615b4
--- /dev/null
+++ b/src/main/java/org/zendesk/client/v2/model/Sorting.java
@@ -0,0 +1,62 @@
+package org.zendesk.client.v2.model;
+
+import java.util.Objects;
+
+public class Sorting
+{
+ String sortBy;
+ SortOrder sortOrder;
+
+ public Sorting(String sortBy, SortOrder sortOrder)
+ {
+ this.sortBy = sortBy;
+ this.sortOrder = sortOrder;
+ }
+
+ public String getSortBy()
+ {
+ return sortBy;
+ }
+
+ public void setSortBy(String sortBy)
+ {
+ this.sortBy = sortBy;
+ }
+
+ public SortOrder getSortOrder()
+ {
+ return sortOrder;
+ }
+
+ public void setSortOrder(SortOrder sortOrder)
+ {
+ this.sortOrder = sortOrder;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "Sorting{" +
+ "sortBy='" + sortBy + '\'' +
+ ", sortOrder=" + sortOrder +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ Sorting sorting = (Sorting) o;
+ return Objects.equals(sortBy, sorting.sortBy) &&
+ sortOrder == sorting.sortOrder;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(sortBy, sortOrder);
+ }
+}
diff --git a/src/main/java/org/zendesk/client/v2/model/SupportCenterLocale.java b/src/main/java/org/zendesk/client/v2/model/SupportCenterLocale.java
new file mode 100644
index 000000000..c9d6b46b9
--- /dev/null
+++ b/src/main/java/org/zendesk/client/v2/model/SupportCenterLocale.java
@@ -0,0 +1,213 @@
+package org.zendesk.client.v2.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.Objects;
+
+public class SupportCenterLocale implements Serializable
+{
+ private static final long serialVersionUID = 1L;
+
+ /** Automatically assigned when creating items */
+ private Long id;
+
+ /** The API url of this item */
+ private String url;
+
+ /** ISO Locale code */
+ private String locale;
+
+ /** Human readable locale name */
+ private String name;
+
+ /** Human readable locale name on the locale language */
+ @JsonProperty("native_name")
+ private String nativeName;
+
+ /** Display name */
+ @JsonProperty("presentation_name")
+ private String presentationName;
+
+ /** Right to left flag */
+ private Boolean rtl;
+
+ /** If the locale is the default for the account */
+ @JsonProperty("default")
+ private Boolean isDefault;
+
+ /** When this record was created */
+ @JsonProperty("created_at")
+ private Date createdAt;
+
+ /** When this record last got updated */
+ @JsonProperty("updated_at")
+ private Date updatedAt;
+
+ public SupportCenterLocale()
+ {
+ }
+
+ public SupportCenterLocale(Long id, String url, String locale, String name, String nativeName, String presentationName, Boolean rtl, Boolean isDefault, Date createdAt, Date updatedAt)
+ {
+ this.id = id;
+ this.url = url;
+ this.locale = locale;
+ this.name = name;
+ this.nativeName = nativeName;
+ this.presentationName = presentationName;
+ this.rtl = rtl;
+ this.isDefault = isDefault;
+ this.createdAt = createdAt;
+ this.updatedAt = updatedAt;
+ }
+
+ public static long getSerialVersionUID()
+ {
+ return serialVersionUID;
+ }
+
+ public Long getId()
+ {
+ return id;
+ }
+
+ public void setId(Long id)
+ {
+ this.id = id;
+ }
+
+ public String getUrl()
+ {
+ return url;
+ }
+
+ public void setUrl(String url)
+ {
+ this.url = url;
+ }
+
+ public String getLocale()
+ {
+ return locale;
+ }
+
+ public void setLocale(String locale)
+ {
+ this.locale = locale;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public void setName(String name)
+ {
+ this.name = name;
+ }
+
+ public String getNativeName()
+ {
+ return nativeName;
+ }
+
+ public void setNativeName(String nativeName)
+ {
+ this.nativeName = nativeName;
+ }
+
+ public String getPresentationName()
+ {
+ return presentationName;
+ }
+
+ public void setPresentationName(String presentationName)
+ {
+ this.presentationName = presentationName;
+ }
+
+ public Boolean getRtl()
+ {
+ return rtl;
+ }
+
+ public void setRtl(Boolean rtl)
+ {
+ this.rtl = rtl;
+ }
+
+ public Boolean getDefault()
+ {
+ return isDefault;
+ }
+
+ public void setDefault(Boolean aDefault)
+ {
+ isDefault = aDefault;
+ }
+
+ public Date getCreatedAt()
+ {
+ return createdAt;
+ }
+
+ public void setCreatedAt(Date createdAt)
+ {
+ this.createdAt = createdAt;
+ }
+
+ public Date getUpdatedAt()
+ {
+ return updatedAt;
+ }
+
+ public void setUpdatedAt(Date updatedAt)
+ {
+ this.updatedAt = updatedAt;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ SupportCenterLocale that = (SupportCenterLocale) o;
+ return Objects.equals(id, that.id) &&
+ Objects.equals(url, that.url) &&
+ Objects.equals(locale, that.locale) &&
+ Objects.equals(name, that.name) &&
+ Objects.equals(nativeName, that.nativeName) &&
+ Objects.equals(presentationName, that.presentationName) &&
+ Objects.equals(rtl, that.rtl) &&
+ Objects.equals(isDefault, that.isDefault) &&
+ Objects.equals(createdAt, that.createdAt) &&
+ Objects.equals(updatedAt, that.updatedAt);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(id, url, locale, name, nativeName, presentationName, rtl, isDefault, createdAt, updatedAt);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "SupportLocale{" +
+ "id=" + id +
+ ", url='" + url + '\'' +
+ ", locale='" + locale + '\'' +
+ ", name='" + name + '\'' +
+ ", nativeName='" + nativeName + '\'' +
+ ", presentationName='" + presentationName + '\'' +
+ ", rtl=" + rtl +
+ ", isDefault=" + isDefault +
+ ", createdAt=" + createdAt +
+ ", updatedAt=" + updatedAt +
+ '}';
+ }
+}
diff --git a/src/main/java/org/zendesk/client/v2/model/hc/TranslationSourceType.java b/src/main/java/org/zendesk/client/v2/model/hc/TranslationSourceType.java
new file mode 100644
index 000000000..30cbc2ca7
--- /dev/null
+++ b/src/main/java/org/zendesk/client/v2/model/hc/TranslationSourceType.java
@@ -0,0 +1,34 @@
+package org.zendesk.client.v2.model.hc;
+
+import java.util.Arrays;
+
+public enum TranslationSourceType {
+ ARTICLE("articles", "Article"), CATEGORY("categories", "Category"), SECTION("sections", "Section");
+
+ private String urlPath;
+ private String sourceType;
+
+ TranslationSourceType(String urlPath, String sourceType)
+ {
+ this.urlPath = urlPath;
+ this.sourceType = sourceType;
+ }
+
+ public String getUrlPath()
+ {
+ return urlPath;
+ }
+
+ public String getSourceType()
+ {
+ return sourceType;
+ }
+
+ public static TranslationSourceType getBySourceName(String sourceType)
+ {
+ return Arrays.stream(TranslationSourceType.values())
+ .filter(t -> t.getSourceType().equals(sourceType))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("sourceType=" + sourceType + " does not exist"));
+ }
+};
diff --git a/src/main/java/org/zendesk/client/v2/model/oauth/OAuthRequest.java b/src/main/java/org/zendesk/client/v2/model/oauth/OAuthRequest.java
new file mode 100644
index 000000000..5064a1707
--- /dev/null
+++ b/src/main/java/org/zendesk/client/v2/model/oauth/OAuthRequest.java
@@ -0,0 +1,55 @@
+package org.zendesk.client.v2.model.oauth;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class OAuthRequest
+{
+ @JsonProperty ("grant_type")
+ private final String grantType = "authorization_code";
+ private final String code;
+ @JsonProperty ("client_id")
+ private final String clientId;
+ @JsonProperty ("client_secret")
+ private final String clientSecret;
+ @JsonProperty ("redirect_uri")
+ private final String redirectUri;
+ private final String scope = "read";
+
+ public OAuthRequest(final String code, final String redirectUri, final String clientId, final String clientSecret)
+ {
+ this.code = code;
+ this.clientId = clientId;
+ this.clientSecret = clientSecret;
+ this.redirectUri = redirectUri;
+ }
+
+ public String getGrantType()
+ {
+ return grantType;
+ }
+
+ public String getCode()
+ {
+ return code;
+ }
+
+ public String getClientId()
+ {
+ return clientId;
+ }
+
+ public String getClientSecret()
+ {
+ return clientSecret;
+ }
+
+ public String getRedirectUri()
+ {
+ return redirectUri;
+ }
+
+ public String getScope()
+ {
+ return scope;
+ }
+}
diff --git a/src/main/java/org/zendesk/client/v2/model/oauth/OAuthToken.java b/src/main/java/org/zendesk/client/v2/model/oauth/OAuthToken.java
new file mode 100644
index 000000000..873151cb9
--- /dev/null
+++ b/src/main/java/org/zendesk/client/v2/model/oauth/OAuthToken.java
@@ -0,0 +1,42 @@
+package org.zendesk.client.v2.model.oauth;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class OAuthToken
+{
+ @JsonProperty ("access_token")
+ private String accessToken;
+ @JsonProperty ("token_type")
+ private String tokenType;
+ private String scope;
+
+ public String getAccessToken()
+ {
+ return accessToken;
+ }
+
+ public void setAccessToken(final String accessToken)
+ {
+ this.accessToken = accessToken;
+ }
+
+ public String getTokenType()
+ {
+ return tokenType;
+ }
+
+ public void setTokenType(final String tokenType)
+ {
+ this.tokenType = tokenType;
+ }
+
+ public String getScope()
+ {
+ return scope;
+ }
+
+ public void setScope(final String scope)
+ {
+ this.scope = scope;
+ }
+}
diff --git a/src/test/java/org/zendesk/client/v2/RealSmokeTest.java b/src/test/java/org/zendesk/client/v2/RealSmokeTest.java
index 9ceed3687..ad5d552d8 100644
--- a/src/test/java/org/zendesk/client/v2/RealSmokeTest.java
+++ b/src/test/java/org/zendesk/client/v2/RealSmokeTest.java
@@ -2,6 +2,7 @@
import org.hamcrest.core.IsCollectionContaining;
import org.junit.After;
+import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
@@ -18,10 +19,13 @@
import org.zendesk.client.v2.model.Identity;
import org.zendesk.client.v2.model.JobStatus;
import org.zendesk.client.v2.model.Organization;
+import org.zendesk.client.v2.model.Page;
import org.zendesk.client.v2.model.Priority;
import org.zendesk.client.v2.model.Request;
import org.zendesk.client.v2.model.SortOrder;
+import org.zendesk.client.v2.model.Sorting;
import org.zendesk.client.v2.model.Status;
+import org.zendesk.client.v2.model.SupportCenterLocale;
import org.zendesk.client.v2.model.SuspendedTicket;
import org.zendesk.client.v2.model.Ticket;
import org.zendesk.client.v2.model.TicketForm;
@@ -30,6 +34,7 @@
import org.zendesk.client.v2.model.dynamic.DynamicContentItemVariant;
import org.zendesk.client.v2.model.events.Event;
import org.zendesk.client.v2.model.hc.Article;
+import org.zendesk.client.v2.model.hc.ArticleAttachments;
import org.zendesk.client.v2.model.hc.Category;
import org.zendesk.client.v2.model.hc.Section;
import org.zendesk.client.v2.model.hc.Subscription;
@@ -45,6 +50,7 @@
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
@@ -60,11 +66,17 @@
import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.hamcrest.core.StringContains.containsString;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeNoException;
+import static org.junit.Assume.assumeNotNull;
import static org.junit.Assume.assumeThat;
+import static org.junit.Assume.assumeTrue;
+import static org.zendesk.client.v2.model.SortOrder.ASCENDING;
+import static org.zendesk.client.v2.model.SortOrder.DESCENDING;
/**
* @author stephenc
@@ -80,6 +92,12 @@ public class RealSmokeTest {
private static Properties config;
+ private Long sectionId;
+ private Long categoryId;
+ private Long dynamicContentItemId;
+ private Long variantId;
+ private String queryString;
+
private Zendesk instance;
@BeforeClass
@@ -105,6 +123,22 @@ public void assumeHaveTokenOrPassword() {
true));
}
+ @Before
+ public void init() throws Exception {
+ sectionId = parseLongOrNull(config.getProperty("sectionId"));
+ categoryId = parseLongOrNull(config.getProperty("categoryId"));
+ dynamicContentItemId = parseLongOrNull(config.getProperty("dynamicContentItemId"));
+ variantId = parseLongOrNull(config.getProperty("variantId"));
+ queryString = config.getProperty("queryString");
+
+ createClientWithTokenOrPassword();
+ }
+
+ private static Long parseLongOrNull(final String string)
+ {
+ return string != null ? Long.valueOf(string) : null;
+ }
+
@After
public void closeClient() {
if (instance != null) {
@@ -153,6 +187,7 @@ public void getTicket() throws Exception {
}
@Test
+ @Ignore("Needs specfic ticket form instance")
public void getTicketForm() throws Exception {
createClientWithTokenOrPassword();
TicketForm ticketForm = instance.getTicketForm(PUBLIC_FORM_ID);
@@ -161,6 +196,7 @@ public void getTicketForm() throws Exception {
}
@Test
+ @Ignore
public void getTicketForms() throws Exception {
createClientWithTokenOrPassword();
Iterable ticketForms = instance.getTicketForms();
@@ -171,6 +207,7 @@ public void getTicketForms() throws Exception {
}
@Test
+ @Ignore("Needs specfic ticket form instance")
public void getTicketFieldsOnForm() throws Exception {
createClientWithTokenOrPassword();
TicketForm ticketForm = instance.getTicketForm(PUBLIC_FORM_ID);
@@ -197,6 +234,7 @@ public void getTargets() throws Exception {
}
@Test
+ @Ignore("Needs test data setup correctly")
public void getTicketsPagesRequests() throws Exception {
createClientWithTokenOrPassword();
int count = 0;
@@ -210,6 +248,7 @@ public void getTicketsPagesRequests() throws Exception {
}
@Test
+ @Ignore("Needs test data setup correctly")
public void getRecentTickets() throws Exception {
createClientWithTokenOrPassword();
int count = 0;
@@ -224,6 +263,7 @@ public void getRecentTickets() throws Exception {
}
@Test
+ @Ignore
public void getTicketsById() throws Exception {
createClientWithTokenOrPassword();
long count = 24;
@@ -303,6 +343,7 @@ public void createAnonymousClient() {
}
@Test
+ @Ignore("Don't spam zendesk")
public void createDeleteTicket() throws Exception {
createClientWithTokenOrPassword();
assumeThat("Must have a requester email", config.getProperty("requester.email"), notNullValue());
@@ -333,6 +374,7 @@ public void createDeleteTicket() throws Exception {
}
@Test
+ @Ignore("Don't spam zendesk")
public void createPermanentlyDeleteTicket() throws Exception {
createClientWithTokenOrPassword();
assumeThat("Must have a requester email", config.getProperty("requester.email"), notNullValue());
@@ -391,6 +433,7 @@ public void createPermanentlyDeleteTickets() throws Exception {
}
@Test
+ @Ignore("Don't spam zendesk")
public void createSolveTickets() throws Exception {
createClientWithTokenOrPassword();
assumeThat("Must have a requester email", config.getProperty("requester.email"), notNullValue());
@@ -419,6 +462,7 @@ public void createSolveTickets() throws Exception {
}
@Test
+ @Ignore("Don't spam zendesk")
public void testUpdateTickets() throws Exception {
createClientWithTokenOrPassword();
Ticket t = new Ticket(
@@ -926,10 +970,7 @@ public void getArticleTranslations() throws Exception {
break; // Do not overwhelm the getArticles API
}
for (Translation t : instance.getArticleTranslations(art.getId())) {
- assertNotNull(t.getId());
- assertNotNull(t.getTitle());
- // body is not mandatory
- //assertNotNull(t.getBody());
+ assertTranslationValid(t);
if (++translationCount > 3) {
return;
}
@@ -937,6 +978,17 @@ public void getArticleTranslations() throws Exception {
}
}
+ @Test
+ public void getArticleTranslation() throws Exception
+ {
+ createClientWithTokenOrPassword();
+ Article article = getFirstIfExist(instance.getArticles());
+
+ Translation translation = instance.getArticleTranslation(article.getId(), article.getSourceLocale());
+
+ assertTranslationValid(translation);
+ }
+
@Test
public void getSectionTranslations() throws Exception {
createClientWithTokenOrPassword();
@@ -948,10 +1000,7 @@ public void getSectionTranslations() throws Exception {
break;
}
for (Translation t : instance.getSectionTranslations(sect.getId())) {
- assertNotNull(t.getId());
- assertNotNull(t.getTitle());
- // body is not mandatory
- //assertNotNull(t.getBody());
+ assertTranslationValid(t);
if (++translationCount > 3) {
return;
}
@@ -959,6 +1008,17 @@ public void getSectionTranslations() throws Exception {
}
}
+ @Test
+ public void getSectionTranslation() throws Exception
+ {
+ createClientWithTokenOrPassword();
+ Section section = getFirstIfExist(instance.getSections());
+
+ Translation translation = instance.getSectionTranslation(section.getSourceLocale(), section.getId());
+
+ assertTranslationValid(translation);
+ }
+
@Test
public void getCategoryTranslations() throws Exception {
createClientWithTokenOrPassword();
@@ -970,10 +1030,7 @@ public void getCategoryTranslations() throws Exception {
break;
}
for (Translation t: instance.getCategoryTranslations(cat.getId())) {
- assertNotNull(t.getId());
- assertNotNull(t.getTitle());
- // body is not mandatory
- //assertNotNull(t.getBody());
+ assertTranslationValid(t);
if (++translationCount > 3) {
return;
}
@@ -981,6 +1038,17 @@ public void getCategoryTranslations() throws Exception {
}
}
+ @Test
+ public void getCategoryTranslation() throws Exception
+ {
+ createClientWithTokenOrPassword();
+ Category category = getFirstIfExist(instance.getCategories());
+
+ Translation translation = instance.getCategoryTranslation(category.getId(), category.getSourceLocale());
+
+ assertTranslationValid(translation);
+ }
+
@Test
public void getArticlesIncrementally() throws Exception {
createClientWithTokenOrPassword();
@@ -1188,6 +1256,54 @@ public void getDynamicContentItems() throws Exception {
}
}
+ @Test
+ public void getDynamicContentItemsPaged() throws Exception {
+ createClientWithTokenOrPassword();
+
+ Iterable items = (instance.getDynamicContentItems(new Page(1, 5), new Sorting("created_at", SortOrder.DESCENDING)));
+
+ int count = 0;
+ DynamicContentItem previous = null;
+ for (DynamicContentItem item : items) {
+ if (previous != null)
+ {
+ assertTrue(previous.getCreatedAt().after(item.getCreatedAt()));
+ }
+ previous = item;
+
+ count++;
+ }
+ assertEquals(5, count);
+ }
+
+ @Test
+ public void getEnabledSupportCenterLocales() throws Exception
+ {
+ createClientWithTokenOrPassword();
+ int count = 0;
+ for (SupportCenterLocale locale : instance.getEnabledSupportCenterLocales()) {
+ assertThat(locale.getName(), notNullValue());
+ assertThat(locale.getId(), notNullValue());
+ if (++count > 10) {
+ break;
+ }
+ }
+ }
+
+ @Test
+ public void getAvailableSupportCenterLocales() throws Exception
+ {
+ createClientWithTokenOrPassword();
+ int count = 0;
+ for (SupportCenterLocale locale : instance.getAvailableSupportCenterLocales()) {
+ assertThat(locale.getName(), notNullValue());
+ assertThat(locale.getId(), notNullValue());
+ if (++count > 10) {
+ break;
+ }
+ }
+ }
+
@Test
public void getTicketCommentsShouldBeAscending() throws Exception {
createClientWithTokenOrPassword();
@@ -1237,4 +1353,346 @@ public void getTicketCommentsDescending() throws Exception {
}
}
}
+
+ @Test
+ public void shouldReturnArticlesForSingleLocale() throws Exception {
+ Iterable articlesForSpecificLocale = instance.getArticles("en-us", new Page(1, 20), new Sorting("updated_at", ASCENDING));
+
+ for (Article article : articlesForSpecificLocale) {
+ assertTrue(article.getLocale().equals("en-us"));
+ }
+ }
+
+ @Test
+ public void sizeShouldShouldBeNotMoreThanPerPageSize() throws Exception {
+ Iterable articlesForSpecificLocale = instance.getArticles("en-us", new Page(1, 20), new Sorting("updated_at", ASCENDING));
+
+ List articleList = getList(articlesForSpecificLocale);
+ assertTrue(articleList.size() <= 20);
+ }
+
+ @Test
+ public void shouldBeSortedByTitleInAsc() throws Exception {
+ Iterable articlesForSpecificLocale = instance.getArticles("en-us", new Page(1, 20), new Sorting("title", ASCENDING));
+
+ String previous = null;
+
+ for (Article article : articlesForSpecificLocale) {
+ if (previous == null) {
+ previous = article.getTitle();
+ } else {
+ String current = article.getTitle();
+
+ assertTrue(current.compareTo(previous) >= 0);
+
+ previous = current;
+ }
+
+ }
+ }
+
+ @Test
+ public void shouldBeSortedByUpdatedAtInAsc() throws Exception {
+ Iterable articlesForSpecificLocale = instance.getArticles("en-us", new Page(1, 20), new Sorting("updated_at", ASCENDING));
+
+ Date previous = null;
+
+ for (Article article : articlesForSpecificLocale) {
+ if (previous == null) {
+ previous = article.getUpdatedAt();
+ } else {
+ Date current = article.getUpdatedAt();
+
+ assertTrue(current.after(previous) || current.equals(previous));
+
+ previous = current;
+ }
+
+ }
+ }
+
+ @Test
+ public void shouldBeSortedByTitleInDesc() throws Exception {
+ Iterable articlesForSpecificLocale = instance.getArticles("en-us", new Page(1, 20), new Sorting("title", DESCENDING));
+
+ String previous = null;
+
+ for (Article article : articlesForSpecificLocale) {
+ if (previous == null) {
+ previous = article.getTitle();
+ } else {
+ String current = article.getTitle();
+
+ assertTrue(current.compareTo(previous) <= 0);
+
+ previous = current;
+ }
+
+ }
+ }
+
+ @Test
+ public void shouldSearchArticleByCategory() throws Exception {
+ assumeNotNull("Category ID is required to run this test", categoryId);
+
+ Iterable articleFromSearch = instance.getArticleFromSearch("en-us", null, null, category(categoryId), new Page(1, 10));
+
+ List result = getList(articleFromSearch);
+ assertEquals(Integer.parseInt(config.getProperty("expected.articles.by.category")), result.size());
+ }
+
+ @Test
+ public void shouldSearchArticleBySection() throws Exception {
+ assumeNotNull("Section ID is required to run this test", sectionId);
+
+ Iterable articleFromSearch = instance.getArticleFromSearch("en-us", "*", section(sectionId), null, new Page(1, 10));
+
+ List result = getList(articleFromSearch);
+ assertEquals(Integer.parseInt(config.getProperty("expected.articles.by.section")), result.size());
+ }
+
+ @Test
+ public void shouldSearchArticleByQuery() throws Exception {
+ assumeNotNull("Search query is required to run this test", queryString);
+
+ Iterable articleFromSearch = instance.getArticleFromSearch("en-us", queryString, null, null, new Page(1, 10));
+
+ List result = getList(articleFromSearch);
+ assertEquals(Integer.parseInt(config.getProperty("expected.articles.by.query")), result.size());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldThrowExceptionIfQueryNotProvided() throws Exception {
+ Iterable articleFromSearch = instance.getArticleFromSearch("en-us", null, null, null, new Page(1, 10));
+ }
+
+ @Test
+ public void shouldSearchArticleByQueryInOneCategory() throws Exception {
+ assumeNotNull("Search query is required to run this test", queryString);
+ assumeNotNull("Category ID is required to run this test", categoryId);
+
+ Iterable articleFromSearch = instance.getArticleFromSearch("en-us", queryString, null, category(categoryId), new Page(1, 10));
+
+ List result = getList(articleFromSearch);
+ assertEquals(Integer.parseInt(config.getProperty("expected.articles.by.query.and.category")), result.size());
+ }
+
+ @Test
+ public void shouldReturnEmptyIterableIfQueryNotProvidedAndNonExistentCategoryId() throws Exception {
+ Iterable articlesByQueryAndCategory = instance.getArticleFromSearch("en-us", null, null, category(0L), new Page(1, 10));
+
+ assertTrue(getList(articlesByQueryAndCategory).isEmpty());
+ }
+
+ @Test
+ public void shouldSearchArticleByQueryInOneSection() throws Exception {
+ assumeNotNull("Search query is required to run this test", queryString);
+ assumeNotNull("Section ID is required to run this test", sectionId);
+
+ Iterable articleFromSearch = instance.getArticleFromSearch("en-us", queryString, section(sectionId), null, new Page(1, 10));
+
+ List result = getList(articleFromSearch);
+ assertEquals(Integer.parseInt(config.getProperty("expected.articles.by.query.and.section")), result.size());
+ }
+
+ @Test
+ public void shouldReturnSectionsByLocaleAndCategory() throws Exception {
+ assumeNotNull("Category ID is required to run this test", categoryId);
+
+ Iterable sectionsByCategory = instance.getSections("en-us", category(categoryId), new Page(1, 10), new Sorting("updated_at", DESCENDING));
+
+ List result = getList(sectionsByCategory);
+ assertEquals(Integer.parseInt(config.getProperty("expected.sections.by.category")), result.size());
+ }
+
+ @Test
+ public void shouldReturnEmptyIterableIfQueryNotProvidedAndNonExistentSectionId() throws Exception {
+ Iterable articlesByQueryAndCategory = instance.getArticleFromSearch("en-us", null, section(0L), null, new Page(1, 10));
+
+ assertTrue(getList(articlesByQueryAndCategory).isEmpty());
+ }
+
+ @Test
+ public void shouldGetArticleByLocaleAndId() throws Exception
+ {
+ Article existingArticle = getRandomArticle();
+
+ Article loadedArticle = instance.getArticle("en-us", existingArticle.getId().intValue());
+
+ assertEquals(loadedArticle.getId(), existingArticle.getId());
+ }
+
+ @Test
+ public void shouldGetCategoryByLocaleAndId() throws Exception
+ {
+ Category existingCategory = getRandomCategory();
+
+ Category loadedCategory = instance.getCategory("en-us", existingCategory.getId().intValue());
+
+ assertEquals(loadedCategory.getId(), existingCategory.getId());
+ }
+
+ @Test
+ public void shouldGetSectionByLocaleAndId() throws Exception
+ {
+ Section existingSection = getRandomSection();
+
+ Section loadedCategory = instance.getSection("en-us",existingSection.getId().intValue());
+
+ assertEquals(loadedCategory.getId(), existingSection.getId());
+ }
+
+ @Test
+ public void shouldReturnNotEmptyVariantsCollection() throws Exception
+ {
+ assumeNotNull("DynamicContentItemId ID is required to run this test", dynamicContentItemId);
+
+ Iterable variants = instance.getDynamicContentItemVariants(dynamicContentItemId);
+ assertFalse(getList(variants).isEmpty());
+ }
+
+ @Test
+ public void shouldReturnVariantById() throws Exception {
+ assumeNotNull("DynamicContentItemId is required to run this test", dynamicContentItemId);
+ assumeNotNull("variantId is required to run this test", variantId);
+
+ DynamicContentItemVariant variantById = instance.getDynamicContentItemVariant(dynamicContentItemId, variantId);
+
+ assertEquals(variantId, variantById.getId());
+ }
+
+ @Test
+ public void shouldCreateVariant() throws Exception {
+ assumeNotNull("DynamicContentItemId ID is required to run this test", dynamicContentItemId);
+
+ Iterable variants = instance.getDynamicContentItemVariants(dynamicContentItemId);
+ int initialSize = getList(variants).size();
+
+ instance.createDynamicContentItemVariant(dynamicContentItemId, getVariant(null, 2L, "Spanish variant"));
+
+ Iterable resultVariants = instance.getDynamicContentItemVariants(dynamicContentItemId);
+ assertEquals(initialSize + 1, getList(resultVariants).size());
+ }
+
+ @Test
+ public void createdVariantShouldContainAppropriateFields() throws Exception{
+ assumeNotNull("DynamicContentItemId ID is required to run this test", dynamicContentItemId);
+
+ DynamicContentItemVariant resultVariant = instance.createDynamicContentItemVariant(dynamicContentItemId, getVariant(null, 1365L, "French variant"));
+
+ assertTrue(resultVariant.getId()!=null);
+ assertEquals(new Long(1365), resultVariant.getLocaleId());
+ assertEquals("French variant", resultVariant.getContent());
+ }
+
+ @Test
+ public void shouldUpdateVariant() throws Exception{
+ assumeNotNull("dynamicContentItemId is required to run this test", dynamicContentItemId);
+ assumeNotNull("variantId is required to run this test", variantId);
+
+ DynamicContentItemVariant resultVariant = instance.updateDynamicContentItemVariant(dynamicContentItemId, getVariant(variantId, 1365L, "French variant updated"));
+
+ DynamicContentItemVariant variantById = instance.getDynamicContentItemVariant(dynamicContentItemId, resultVariant.getId());
+
+ assertEquals("French variant updated", variantById.getContent());
+ }
+
+ @Test
+ public void shouldReturnNotEmptyDynamicContentCollection() throws Exception
+ {
+ Iterable dynamicContent = instance.getDynamicContentItems(new Page(1, 10), new Sorting("updated_at", ASCENDING));
+ assertFalse(getList(dynamicContent).isEmpty());
+ }
+
+ @Test
+ public void shouldReturnArticleAttachment() throws Exception
+ {
+ Article existingArticle = getRandomArticle();
+
+ List attachments = instance.getAttachmentsFromArticle("en-us", existingArticle.getId());
+
+ assertNotNull(attachments);
+ }
+
+ private Article getRandomArticle()
+ {
+ try {
+ return getFirstIfExist(instance.getArticles());
+ } catch (ZendeskException e) {
+ assumeNoException("Need to be able to fetch articles to run this test", e);
+ throw new IllegalStateException("Not possible here");
+ }
+ }
+
+ private Category getRandomCategory()
+ {
+ try {
+ return getFirstIfExist(instance.getCategories());
+ } catch (ZendeskException e) {
+ assumeNoException("Need to be able to fetch category to run this test", e);
+ throw new IllegalStateException("Not possible here");
+ }
+ }
+
+ private Section getRandomSection()
+ {
+ try {
+ return getFirstIfExist(instance.getSections());
+ } catch (ZendeskException e) {
+ assumeNoException("Need to be able to fetch section to run this test", e);
+ throw new IllegalStateException("Not possible here");
+ }
+ }
+
+ private DynamicContentItemVariant getVariant(Long variantId, Long localeId, String content)
+ {
+ DynamicContentItemVariant variant = new DynamicContentItemVariant();
+ variant.setId(variantId);
+ variant.setContent(content);
+ variant.setActive(true);
+ variant.setIsDefault(false);
+ variant.setLocaleId(localeId);
+ return variant;
+ }
+
+ private List getList(Iterable iterable)
+ {
+ List result = new ArrayList<>();
+ for (T article : iterable) {
+ result.add(article);
+ }
+ return result;
+ }
+
+ private static T getFirstIfExist(final Iterable iterable)
+ {
+ Iterator iterator = iterable.iterator();
+ assumeTrue("At least one entity is required for the test", iterator.hasNext());
+
+ return iterator.next();
+ }
+
+ private static void assertTranslationValid(final Translation translation)
+ {
+ assertNotNull(translation);
+ assertNotNull(translation.getId());
+ assertNotNull(translation.getTitle());
+ // body is not mandatory
+ //assertNotNull(t.getBody());
+ }
+
+
+ private Category category(Long categoryId)
+ {
+ Category category = new Category();
+ category.setId(categoryId);
+ return category;
+ }
+
+ private Section section(Long sectionId)
+ {
+ Section section = new Section();
+ section.setId(sectionId);
+ return section;
+ }
}
diff --git a/src/test/java/org/zendesk/client/v2/ZendeskTranslationTest.java b/src/test/java/org/zendesk/client/v2/ZendeskTranslationTest.java
new file mode 100644
index 000000000..e96c3b6a2
--- /dev/null
+++ b/src/test/java/org/zendesk/client/v2/ZendeskTranslationTest.java
@@ -0,0 +1,192 @@
+package org.zendesk.client.v2;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.asynchttpclient.AsyncCompletionHandler;
+import org.asynchttpclient.AsyncHandler;
+import org.asynchttpclient.AsyncHttpClient;
+import org.asynchttpclient.ListenableFuture;
+import org.asynchttpclient.Request;
+import org.asynchttpclient.Response;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.zendesk.client.v2.model.hc.Translation;
+import org.zendesk.client.v2.model.hc.TranslationSourceType;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Collections;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.zendesk.client.v2.model.hc.TranslationSourceType.ARTICLE;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ZendeskTranslationTest
+{
+ private static final String NOT_FOUND_RESPONSE = "{\"error\":\"RecordNotFound\",\"description\":\"Not found\"}";
+ private static final String DEFAULT_URL = "https://test.zendesk.com";
+ private static final String DEFAULT_USER = "user";
+ private static final String DEFAULT_TOKEN = "token";
+ private static final String DEFAULT_BODY = "body";
+ private static final String DEFAULT_TITLE = "title";
+
+ private Zendesk instance;
+
+ @Mock
+ private AsyncHttpClient client;
+
+ @Before
+ public void setUp()
+ {
+ this.instance = new Zendesk.Builder(DEFAULT_URL)
+ .setClient(client)
+ .setUsername(DEFAULT_USER)
+ .setToken(DEFAULT_TOKEN)
+ .build();
+ }
+
+ @Test
+ public void testGetTranslation() throws Exception
+ {
+ long id = 18;
+ String locale = "zo";
+ String url = DEFAULT_URL + "/api/v2/help_center/" + ARTICLE.getUrlPath() + "/" + id + "/translations/" + locale + ".json";
+ Translation responseTranslation = getTranslation(locale, ARTICLE, id);
+ Response response = toContentResponse(200, responseTranslation);
+ setupHttpCall("GET", url, null, response);
+
+ Translation result = instance.getArticleTranslation( 18L, "zo");
+
+ assertNotNull(result);
+ assertEquals(responseTranslation.getId(), result.getId());
+ assertEquals(responseTranslation.getLocale(), result.getLocale());
+ assertEquals(responseTranslation.getSourceType(), result.getSourceType());
+ assertEquals(responseTranslation.getSourceId(), result.getSourceId());
+ assertEquals(responseTranslation.getTitle(), result.getTitle());
+ assertEquals(responseTranslation.getBody(), result.getBody());
+ }
+
+ @Test
+ public void testGetTranslationNotFound() throws Exception
+ {
+ long id = 18;
+ String locale = "zo";
+ String url = DEFAULT_URL + "/api/v2/help_center/" + ARTICLE.getUrlPath() + "/" + id + "/translations/" + locale + ".json";
+ Response response = toContentResponse(200, null);
+ setupHttpCall("GET", url, null, response);
+
+ Translation result = instance.getArticleTranslation(18L, "zo");
+
+ assertNull(result);
+ }
+
+ @Test
+ public void testCreateTranslation() throws Exception
+ {
+ long id = 18;
+ String locale = "zo";
+ String url = DEFAULT_URL + "/api/v2/help_center/" + ARTICLE.getUrlPath() + "/" + id + "/translations.json";
+ Translation translation = getTranslation(locale, ARTICLE, id);
+ Response response = toContentResponse(200, translation);
+ setupHttpCall("POST", url, serialize(translation), response);
+
+ Translation result = instance.createArticleTranslation(id, translation);
+
+ assertNotNull(result);
+ assertEquals(translation.getId(), result.getId());
+ assertEquals(translation.getLocale(), result.getLocale());
+ assertEquals(translation.getSourceType(), result.getSourceType());
+ assertEquals(translation.getSourceId(), result.getSourceId());
+ assertEquals(translation.getTitle(), result.getTitle());
+ assertEquals(translation.getBody(), result.getBody());
+ }
+
+ @Test
+ public void testUpdateTranslation() throws Exception
+ {
+ long id = 18;
+ String locale = "zo";
+ String url = DEFAULT_URL + "/api/v2/help_center/" + ARTICLE.getUrlPath() + "/" + id + "/translations/" + locale + ".json";
+ Translation translation = getTranslation(locale, ARTICLE, id);
+ Response response = toContentResponse(200, translation);
+ setupHttpCall("PUT", url, serialize(translation), response);
+
+ Translation result = instance.updateArticleTranslation(id, locale, translation);
+
+ assertNotNull(result);
+ assertEquals(translation.getId(), result.getId());
+ assertEquals(translation.getLocale(), result.getLocale());
+ assertEquals(translation.getSourceType(), result.getSourceType());
+ assertEquals(translation.getSourceId(), result.getSourceId());
+ assertEquals(translation.getTitle(), result.getTitle());
+ assertEquals(translation.getBody(), result.getBody());
+ }
+
+ @Test
+ public void testDeleteTranslation() throws Exception
+ {
+ long id = 168;
+ Translation translation = new Translation();
+ translation.setId(id);
+ String url = DEFAULT_URL + "/api/v2/help_center/translations/" + id + ".json";
+ Response response = toContentResponse(200, null);
+ setupHttpCall("DELETE", url, null, response);
+
+ instance.deleteTranslation(translation);
+ }
+
+ private void setupHttpCall(String method, String url, byte[] body, Response response)
+ {
+ when(client.executeRequest(any(Request.class), Mockito.>any())).thenAnswer(invocation -> {
+ assertEquals(method, invocation.getArgumentAt(0, Request.class).getMethod());
+ assertEquals(url, invocation.getArgumentAt(0, Request.class).getUrl());
+ assertArrayEquals(body, invocation.getArgumentAt(0, Request.class).getByteData());
+
+ Object result = invocation.getArgumentAt(1, AsyncCompletionHandler.class).onCompleted(response);
+ ListenableFuture