diff --git a/pom.xml b/pom.xml index eccf95f7fd..3a637f2227 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ github-api - 1.76 + 1.77 GitHub API for Java http://github-api.kohsuke.org/ GitHub API for Java @@ -16,7 +16,7 @@ scm:git:git@github.com/kohsuke/${project.artifactId}.git scm:git:ssh://git@github.com/kohsuke/${project.artifactId}.git http://${project.artifactId}.kohsuke.org/ - github-api-1.76 + github-api-1.77 @@ -135,7 +135,7 @@ com.squareup.okhttp okhttp-urlconnection - 2.0.0 + 2.7.5 true diff --git a/src/main/java/org/kohsuke/github/AbuseLimitHandler.java b/src/main/java/org/kohsuke/github/AbuseLimitHandler.java new file mode 100644 index 0000000000..4cb6bfe6c5 --- /dev/null +++ b/src/main/java/org/kohsuke/github/AbuseLimitHandler.java @@ -0,0 +1,63 @@ +package org.kohsuke.github; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.HttpURLConnection; + +/** + * Pluggable strategy to determine what to do when the API abuse limit is hit. + * + * @author Kohsuke Kawaguchi + * @see GitHubBuilder#withAbuseLimitHandler(AbuseLimitHandler) + * @see documentation + * @see RateLimitHandler + */ +public abstract class AbuseLimitHandler { + /** + * Called when the library encounters HTTP error indicating that the API abuse limit is reached. + * + *

+ * Any exception thrown from this method will cause the request to fail, and the caller of github-api + * will receive an exception. If this method returns normally, another request will be attempted. + * For that to make sense, the implementation needs to wait for some time. + * + * @see API documentation from GitHub + * @param e + * Exception from Java I/O layer. If you decide to fail the processing, you can throw + * this exception (or wrap this exception into another exception and throw it.) + * @param uc + * Connection that resulted in an error. Useful for accessing other response headers. + */ + public abstract void onError(IOException e, HttpURLConnection uc) throws IOException; + + /** + * Wait until the API abuse "wait time" is passed. + */ + public static final AbuseLimitHandler WAIT = new AbuseLimitHandler() { + @Override + public void onError(IOException e, HttpURLConnection uc) throws IOException { + try { + Thread.sleep(parseWaitTime(uc)); + } catch (InterruptedException _) { + throw (InterruptedIOException)new InterruptedIOException().initCause(e); + } + } + + private long parseWaitTime(HttpURLConnection uc) { + String v = uc.getHeaderField("Retry-After"); + if (v==null) return 60 * 1000; // can't tell, return 1 min + + return Math.max(1000, Long.parseLong(v)*1000); + } + }; + + /** + * Fail immediately. + */ + public static final AbuseLimitHandler FAIL = new AbuseLimitHandler() { + @Override + public void onError(IOException e, HttpURLConnection uc) throws IOException { + throw (IOException)new IOException("Abust limit reached").initCause(e); + } + }; +} diff --git a/src/main/java/org/kohsuke/github/GHBranch.java b/src/main/java/org/kohsuke/github/GHBranch.java index 1de47a156e..4c80db9ef4 100644 --- a/src/main/java/org/kohsuke/github/GHBranch.java +++ b/src/main/java/org/kohsuke/github/GHBranch.java @@ -7,6 +7,8 @@ import java.util.Arrays; import java.util.Collection; +import static org.kohsuke.github.Previews.LOKI; + /** * A branch in a repository. * @@ -53,6 +55,7 @@ public String getSHA1() { /** * Disables branch protection and allows anyone with push access to push changes. */ + @Preview @Deprecated public void disableProtection() throws IOException { BranchProtection bp = new BranchProtection(); bp.enabled = false; @@ -64,6 +67,7 @@ public void disableProtection() throws IOException { * * @see GHCommitStatus#getContext() */ + @Preview @Deprecated public void enableProtection(EnforcementLevel level, Collection contexts) throws IOException { BranchProtection bp = new BranchProtection(); bp.enabled = true; @@ -73,14 +77,13 @@ public void enableProtection(EnforcementLevel level, Collection contexts setProtection(bp); } + @Preview @Deprecated public void enableProtection(EnforcementLevel level, String... contexts) throws IOException { enableProtection(level, Arrays.asList(contexts)); } private void setProtection(BranchProtection bp) throws IOException { - new Requester(root).method("PATCH") - .withHeader("Accept","application/vnd.github.loki-preview+json") - ._with("protection",bp).to(getApiRoute()); + new Requester(root).method("PATCH").withPreview(LOKI)._with("protection",bp).to(getApiRoute()); } String getApiRoute() { diff --git a/src/main/java/org/kohsuke/github/GHCommit.java b/src/main/java/org/kohsuke/github/GHCommit.java index 91510fc88c..6e606ef603 100644 --- a/src/main/java/org/kohsuke/github/GHCommit.java +++ b/src/main/java/org/kohsuke/github/GHCommit.java @@ -179,7 +179,8 @@ static class User { public ShortInfo getCommitShortInfo() throws IOException { - populate(); + if (commit==null) + populate(); return commit; } diff --git a/src/main/java/org/kohsuke/github/GHContentWithLicense.java b/src/main/java/org/kohsuke/github/GHContentWithLicense.java new file mode 100644 index 0000000000..bbd115e649 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHContentWithLicense.java @@ -0,0 +1,21 @@ +package org.kohsuke.github; + +/** + * {@link GHContent} with license information. + * + * @author Kohsuke Kawaguchi + * @see documentation + * @see GHRepository#getLicense() + */ +@Preview @Deprecated +class GHContentWithLicense extends GHContent { + GHLicense license; + + @Override + GHContentWithLicense wrap(GHRepository owner) { + super.wrap(owner); + if (license!=null) + license.wrap(owner.root); + return this; + } +} diff --git a/src/main/java/org/kohsuke/github/GHLicense.java b/src/main/java/org/kohsuke/github/GHLicense.java new file mode 100644 index 0000000000..2c5c4299c1 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHLicense.java @@ -0,0 +1,168 @@ +/* + * The MIT License + * + * Copyright (c) 2016, Duncan Dickinson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.kohsuke.github; + +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import static org.kohsuke.github.Previews.DRAX; + +/** + * The GitHub Preview API's license information + *

+ * WARNING: This uses a PREVIEW API - subject to change. + * + * @author Duncan Dickinson + * @see GitHub#getLicense(String) + * @see GHRepository#getLicense() + * @see https://developer.github.com/v3/licenses/ + */ +@Preview @Deprecated +@SuppressWarnings({"UnusedDeclaration"}) +@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", + "NP_UNWRITTEN_FIELD"}, justification = "JSON API") +public class GHLicense extends GHObject { + @SuppressFBWarnings("IS2_INCONSISTENT_SYNC") // root is set before the object is returned to the app + /*package almost final*/ GitHub root; + + // these fields are always present, even in the short form + protected String key, name; + + // the rest is only after populated + protected Boolean featured; + + protected String html_url, description, category, implementation, body; + + protected List required = new ArrayList(); + protected List permitted = new ArrayList(); + protected List forbidden = new ArrayList(); + + /** + * @return a mnemonic for the license + */ + public String getKey() { + return key; + } + + /** + * @return the license name + */ + public String getName() { + return name; + } + + /** + * @return API URL of this object. + */ + @WithBridgeMethods(value = String.class, adapterMethod = "urlToString") + public URL getUrl() { + return GitHub.parseURL(url); + } + + /** + * Featured licenses are bold in the new repository drop-down + * + * @return True if the license is featured, false otherwise + */ + public Boolean isFeatured() throws IOException { + populate(); + return featured; + } + + public URL getHtmlUrl() throws IOException { + populate(); + return GitHub.parseURL(html_url); + } + + public String getDescription() throws IOException { + populate(); + return description; + } + + public String getCategory() throws IOException { + populate(); + return category; + } + + public String getImplementation() throws IOException { + populate(); + return implementation; + } + + public List getRequired() throws IOException { + populate(); + return required; + } + + public List getPermitted() throws IOException { + populate(); + return permitted; + } + + public List getForbidden() throws IOException { + populate(); + return forbidden; + } + + public String getBody() throws IOException { + populate(); + return body; + } + + /** + * Fully populate the data by retrieving missing data. + * + * Depending on the original API call where this object is created, it may not contain everything. + */ + protected synchronized void populate() throws IOException { + if (description!=null) return; // already populated + + root.retrieve().withPreview(DRAX).to(url, this); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof GHLicense)) return false; + + GHLicense that = (GHLicense) o; + return this.url.equals(that.url); + } + + @Override + public int hashCode() { + return url.hashCode(); + } + + /*package*/ GHLicense wrap(GitHub root) { + this.root = root; + return this; + } +} diff --git a/src/main/java/org/kohsuke/github/GHObject.java b/src/main/java/org/kohsuke/github/GHObject.java index 323e72cd29..a81d913a5c 100644 --- a/src/main/java/org/kohsuke/github/GHObject.java +++ b/src/main/java/org/kohsuke/github/GHObject.java @@ -51,7 +51,7 @@ public URL getUrl() { * URL of this object for humans, which renders some HTML. */ @WithBridgeMethods(value=String.class, adapterMethod="urlToString") - public abstract URL getHtmlUrl(); + public abstract URL getHtmlUrl() throws IOException; /** * When was this resource last updated? diff --git a/src/main/java/org/kohsuke/github/GHRepository.java b/src/main/java/org/kohsuke/github/GHRepository.java index 41a44e9dab..472adf41eb 100644 --- a/src/main/java/org/kohsuke/github/GHRepository.java +++ b/src/main/java/org/kohsuke/github/GHRepository.java @@ -51,6 +51,7 @@ import java.util.TreeMap; import static java.util.Arrays.asList; +import static org.kohsuke.github.Previews.DRAX; /** * A repository on GitHub. @@ -65,6 +66,13 @@ public class GHRepository extends GHObject { private String description, homepage, name, full_name; private String html_url; // this is the UI + /* + * The license information makes use of the preview API. + * + * See: https://developer.github.com/v3/licenses/ + */ + private GHLicense license; + private String git_url, ssh_url, clone_url, svn_url, mirror_url; private GHUser owner; // not fully populated. beware. private boolean has_issues, has_wiki, fork, has_downloads; @@ -840,6 +848,46 @@ protected void wrapUp(GHCommitComment[] page) { }; } + /** + * Gets the basic license details for the repository. + *

+ * This is a preview item and subject to change. + * + * @throws IOException as usual but also if you don't use the preview connector + * @return null if there's no license. + */ + @Preview @Deprecated + public GHLicense getLicense() throws IOException{ + GHContentWithLicense lic = getLicenseContent_(); + return lic!=null ? lic.license : null; + } + + /** + * Retrieves the contents of the repository's license file - makes an additional API call + *

+ * This is a preview item and subject to change. + * + * @return details regarding the license contents, or null if there's no license. + * @throws IOException as usual but also if you don't use the preview connector + */ + @Preview @Deprecated + public GHContent getLicenseContent() throws IOException { + return getLicenseContent_(); + } + + @Preview @Deprecated + private GHContentWithLicense getLicenseContent_() throws IOException { + try { + return root.retrieve() + .withPreview(DRAX) + .to(getApiTailUrl("license"), GHContentWithLicense.class).wrap(this); + } catch (FileNotFoundException e) { + return null; + } + } + + /** + /** * Lists all the commit statues attached to the given commit, newer ones first. */ diff --git a/src/main/java/org/kohsuke/github/GHTeam.java b/src/main/java/org/kohsuke/github/GHTeam.java index 172f859657..ae950aed70 100644 --- a/src/main/java/org/kohsuke/github/GHTeam.java +++ b/src/main/java/org/kohsuke/github/GHTeam.java @@ -124,7 +124,13 @@ public void remove(GHUser u) throws IOException { } public void add(GHRepository r) throws IOException { - org.root.retrieve().method("PUT").to(api("/repos/" + r.getOwnerName() + '/' + r.getName()), null); + add(r,null); + } + + public void add(GHRepository r, GHOrganization.Permission permission) throws IOException { + org.root.retrieve().method("PUT") + .with("permission",permission) + .to(api("/repos/" + r.getOwnerName() + '/' + r.getName()), null); } public void remove(GHRepository r) throws IOException { diff --git a/src/main/java/org/kohsuke/github/GitHub.java b/src/main/java/org/kohsuke/github/GitHub.java index 22792a8798..e5bf16b1de 100644 --- a/src/main/java/org/kohsuke/github/GitHub.java +++ b/src/main/java/org/kohsuke/github/GitHub.java @@ -27,6 +27,7 @@ import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE; import static java.util.logging.Level.FINE; import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; +import static org.kohsuke.github.Previews.DRAX; import java.io.ByteArrayInputStream; import java.io.FileNotFoundException; @@ -56,6 +57,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.introspect.VisibilityChecker.Std; import com.infradna.tool.bridge_method_injector.WithBridgeMethods; + import java.util.logging.Logger; /** @@ -83,6 +85,7 @@ public class GitHub { private final String apiUrl; /*package*/ final RateLimitHandler rateLimitHandler; + /*package*/ final AbuseLimitHandler abuseLimitHandler; private HttpConnector connector = HttpConnector.DEFAULT; @@ -122,7 +125,7 @@ public class GitHub { * @param connector * HttpConnector to use. Pass null to use default connector. */ - /* package */ GitHub(String apiUrl, String login, String oauthAccessToken, String password, HttpConnector connector, RateLimitHandler rateLimitHandler) throws IOException { + /* package */ GitHub(String apiUrl, String login, String oauthAccessToken, String password, HttpConnector connector, RateLimitHandler rateLimitHandler, AbuseLimitHandler abuseLimitHandler) throws IOException { if (apiUrl.endsWith("/")) apiUrl = apiUrl.substring(0, apiUrl.length()-1); // normalize this.apiUrl = apiUrl; if (null != connector) this.connector = connector; @@ -140,6 +143,7 @@ public class GitHub { } this.rateLimitHandler = rateLimitHandler; + this.abuseLimitHandler = abuseLimitHandler; if (login==null && encodedAuthorization!=null) login = getMyself().getLogin(); @@ -337,6 +341,46 @@ public GHRepository getRepository(String name) throws IOException { String[] tokens = name.split("/"); return retrieve().to("/repos/" + tokens[0] + '/' + tokens[1], GHRepository.class).wrap(this); } + /** + * Returns a list of popular open source licenses + * + * WARNING: This uses a PREVIEW API. + * + * @see GitHub API - Licenses + * + * @return a list of popular open source licenses + */ + @Preview @Deprecated + public PagedIterable listLicenses() throws IOException { + return new PagedIterable() { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(retrieve().withPreview(DRAX).asIterator("/licenses", GHLicense[].class, pageSize)) { + @Override + protected void wrapUp(GHLicense[] page) { + for (GHLicense c : page) + c.wrap(GitHub.this); + } + }; + } + }; + } + + /** + * Returns the full details for a license + * + * WARNING: This uses a PREVIEW API. + * + * @param key The license key provided from the API + * @return The license details + * @throws IOException + * @see GHLicense#getKey() + */ + @Preview @Deprecated + public GHLicense getLicense(String key) throws IOException { + return retrieve().withPreview(DRAX).to("/licenses/" + key, GHLicense.class); + } + + /** /** * This method returns a shallowly populated organizations. diff --git a/src/main/java/org/kohsuke/github/GitHubBuilder.java b/src/main/java/org/kohsuke/github/GitHubBuilder.java index 2abe16f9c1..62a99a6272 100644 --- a/src/main/java/org/kohsuke/github/GitHubBuilder.java +++ b/src/main/java/org/kohsuke/github/GitHubBuilder.java @@ -30,6 +30,7 @@ public class GitHubBuilder { private HttpConnector connector; private RateLimitHandler rateLimitHandler = RateLimitHandler.WAIT; + private AbuseLimitHandler abuseLimitHandler = AbuseLimitHandler.WAIT; public GitHubBuilder() { } @@ -178,6 +179,10 @@ public GitHubBuilder withRateLimitHandler(RateLimitHandler handler) { this.rateLimitHandler = handler; return this; } + public GitHubBuilder withAbuseLimitHandler(AbuseLimitHandler handler) { + this.abuseLimitHandler = handler; + return this; + } /** * Configures {@linkplain #withConnector(HttpConnector) connector} @@ -193,6 +198,6 @@ public HttpURLConnection connect(URL url) throws IOException { } public GitHub build() throws IOException { - return new GitHub(endpoint, user, oauthToken, password, connector, rateLimitHandler); + return new GitHub(endpoint, user, oauthToken, password, connector, rateLimitHandler, abuseLimitHandler); } } diff --git a/src/main/java/org/kohsuke/github/Preview.java b/src/main/java/org/kohsuke/github/Preview.java new file mode 100644 index 0000000000..2eeed60456 --- /dev/null +++ b/src/main/java/org/kohsuke/github/Preview.java @@ -0,0 +1,18 @@ +package org.kohsuke.github; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Indicates that the method/class/etc marked maps to GitHub API in the preview period. + * + * These APIs are subject to change and not a part of the backward compatibility commitment. + * Always used in conjunction with 'deprecated' to raise awareness to clients. + * + * @author Kohsuke Kawaguchi + */ +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Preview { +} diff --git a/src/main/java/org/kohsuke/github/Previews.java b/src/main/java/org/kohsuke/github/Previews.java new file mode 100644 index 0000000000..a5d061cbd4 --- /dev/null +++ b/src/main/java/org/kohsuke/github/Previews.java @@ -0,0 +1,9 @@ +package org.kohsuke.github; + +/** + * @author Kohsuke Kawaguchi + */ +/*package*/ class Previews { + static final String LOKI = "application/vnd.github.loki-preview+json"; + static final String DRAX = "application/vnd.github.drax-preview+json"; +} diff --git a/src/main/java/org/kohsuke/github/RateLimitHandler.java b/src/main/java/org/kohsuke/github/RateLimitHandler.java index b00624ce2b..053fd783e0 100644 --- a/src/main/java/org/kohsuke/github/RateLimitHandler.java +++ b/src/main/java/org/kohsuke/github/RateLimitHandler.java @@ -9,6 +9,7 @@ * * @author Kohsuke Kawaguchi * @see GitHubBuilder#withRateLimitHandler(RateLimitHandler) + * @see AbuseLimitHandler */ public abstract class RateLimitHandler { /** diff --git a/src/main/java/org/kohsuke/github/Requester.java b/src/main/java/org/kohsuke/github/Requester.java index dfbee6256f..6175191ea6 100644 --- a/src/main/java/org/kohsuke/github/Requester.java +++ b/src/main/java/org/kohsuke/github/Requester.java @@ -110,6 +110,10 @@ public Requester withHeader(String name, String value) { return this; } + /*package*/ Requester withPreview(String name) { + return withHeader("Accept",name); + } + /** * Makes a request with authentication credential. */ @@ -512,6 +516,10 @@ private T parse(Class type, T instance) throws IOException { if (responseCode == 304) { return null; // special case handling for 304 unmodified, as the content will be "" } + if (responseCode == 204 && type!=null && type.isArray()) { + // no content + return type.cast(Array.newInstance(type.getComponentType(),0)); + } r = new InputStreamReader(wrapStream(uc.getInputStream()), "UTF-8"); String data = IOUtils.toString(r); @@ -569,6 +577,13 @@ private InputStream wrapStream(InputStream in) throws IOException { return; } + // Retry-After is not documented but apparently that field exists + if (responseCode == HttpURLConnection.HTTP_FORBIDDEN && + uc.getHeaderField("Retry-After") != null) { + this.root.abuseLimitHandler.onError(e,uc); + return; + } + InputStream es = wrapStream(uc.getErrorStream()); try { if (es!=null) { diff --git a/src/test/java/Foo.java b/src/test/java/Foo.java index a29baac4b4..35e5eb5955 100644 --- a/src/test/java/Foo.java +++ b/src/test/java/Foo.java @@ -1,4 +1,5 @@ import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GHRepository.Contributor; import org.kohsuke.github.GHUser; import org.kohsuke.github.GitHub; @@ -9,11 +10,10 @@ */ public class Foo { public static void main(String[] args) throws Exception { - Collection lst = GitHub.connect().getUser("kohsuke").getRepositories().values(); - for (GHRepository r : lst) { - System.out.println(r.getName()); + GitHub gh = GitHub.connect(); + for (Contributor c : gh.getRepository("kohsuke/yo").listContributors()) { + System.out.println(c); } - System.out.println(lst.size()); } private static void testRateLimit() throws Exception { diff --git a/src/test/java/org/kohsuke/github/GHLicenseTest.java b/src/test/java/org/kohsuke/github/GHLicenseTest.java new file mode 100644 index 0000000000..b2cc73f928 --- /dev/null +++ b/src/test/java/org/kohsuke/github/GHLicenseTest.java @@ -0,0 +1,193 @@ +/* + * The MIT License + * + * Copyright (c) 2016, Duncan Dickinson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.kohsuke.github; + +import org.apache.commons.io.IOUtils; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.net.URL; + +/** + * @author Duncan Dickinson + */ +public class GHLicenseTest extends Assert { + private GitHub gitHub; + + @Before + public void setUp() throws Exception { + gitHub = new GitHubBuilder() + .fromCredentials() + .build(); + } + + /** + * Basic test to ensure that the list of licenses from {@link GitHub#listLicenses()} is returned + * + * @throws IOException + */ + @Test + public void listLicenses() throws IOException { + Iterable licenses = gitHub.listLicenses(); + assertTrue(licenses.iterator().hasNext()); + } + + /** + * Tests that {@link GitHub#listLicenses()} returns the MIT license + * in the expected manner. + * + * @throws IOException + */ + @Test + public void listLicensesCheckIndividualLicense() throws IOException { + PagedIterable licenses = gitHub.listLicenses(); + for (GHLicense lic : licenses) { + if (lic.getKey().equals("mit")) { + assertTrue(lic.getUrl().equals(new URL("https://api.github.com/licenses/mit"))); + return; + } + } + fail("The MIT license was not found"); + } + + /** + * Checks that the request for an individual license using {@link GitHub#getLicense(String)} + * returns expected values (not all properties are checked) + * + * @throws IOException + */ + @Test + public void getLicense() throws IOException { + String key = "mit"; + GHLicense license = gitHub.getLicense(key); + assertNotNull(license); + assertTrue("The name is correct", license.getName().equals("MIT License")); + assertTrue("The HTML URL is correct", license.getHtmlUrl().equals(new URL("http://choosealicense.com/licenses/mit/"))); + } + + /** + * Accesses the 'kohsuke/github-api' repo using {@link GitHub#getRepository(String)} + * and checks that the license is correct + * + * @throws IOException + */ + @Test + public void checkRepositoryLicense() throws IOException { + GHRepository repo = gitHub.getRepository("kohsuke/github-api"); + GHLicense license = repo.getLicense(); + assertNotNull("The license is populated", license); + assertTrue("The key is correct", license.getKey().equals("mit")); + assertTrue("The name is correct", license.getName().equals("MIT License")); + assertTrue("The URL is correct", license.getUrl().equals(new URL("https://api.github.com/licenses/mit"))); + } + + /** + * Accesses the 'atom/atom' repo using {@link GitHub#getRepository(String)} + * and checks that the license is correct + * + * @throws IOException + */ + @Test + public void checkRepositoryLicenseAtom() throws IOException { + GHRepository repo = gitHub.getRepository("atom/atom"); + GHLicense license = repo.getLicense(); + assertNotNull("The license is populated", license); + assertTrue("The key is correct", license.getKey().equals("mit")); + assertTrue("The name is correct", license.getName().equals("MIT License")); + assertTrue("The URL is correct", license.getUrl().equals(new URL("https://api.github.com/licenses/mit"))); + } + + /** + * Accesses the 'pomes/pomes' repo using {@link GitHub#getRepository(String)} + * and checks that the license is correct + * + * @throws IOException + */ + @Test + public void checkRepositoryLicensePomes() throws IOException { + GHRepository repo = gitHub.getRepository("pomes/pomes"); + GHLicense license = repo.getLicense(); + assertNotNull("The license is populated", license); + assertTrue("The key is correct", license.getKey().equals("apache-2.0")); + assertTrue("The name is correct", license.getName().equals("Apache License 2.0")); + assertTrue("The URL is correct", license.getUrl().equals(new URL("https://api.github.com/licenses/apache-2.0"))); + } + + /** + * Accesses the 'dedickinson/test-repo' repo using {@link GitHub#getRepository(String)} + * and checks that *no* license is returned as the repo doesn't have one + * + * @throws IOException + */ + @Test + public void checkRepositoryWithoutLicense() throws IOException { + GHRepository repo = gitHub.getRepository("dedickinson/test-repo"); + GHLicense license = repo.getLicense(); + assertNull("There is no license", license); + } + + /** + * Accesses the 'kohsuke/github-api' repo using {@link GitHub#getRepository(String)} + * and then calls {@link GHRepository#getLicense()} and checks that certain + * properties are correct + * + * @throws IOException + */ + @Test + public void checkRepositoryFullLicense() throws IOException { + GHRepository repo = gitHub.getRepository("kohsuke/github-api"); + GHLicense license = repo.getLicense(); + assertNotNull("The license is populated", license); + assertTrue("The key is correct", license.getKey().equals("mit")); + assertTrue("The name is correct", license.getName().equals("MIT License")); + assertTrue("The URL is correct", license.getUrl().equals(new URL("https://api.github.com/licenses/mit"))); + assertTrue("The HTML URL is correct", license.getHtmlUrl().equals(new URL("http://choosealicense.com/licenses/mit/"))); + } + + /** + * Accesses the 'pomes/pomes' repo using {@link GitHub#getRepository(String)} + * and then calls {@link GHRepository#getLicenseContent()} and checks that certain + * properties are correct + * + * @throws IOException + */ + @Test + public void checkRepositoryLicenseContent() throws IOException { + GHRepository repo = gitHub.getRepository("pomes/pomes"); + GHContent content = repo.getLicenseContent(); + assertNotNull("The license content is populated", content); + assertTrue("The type is 'file'", content.getType().equals("file")); + assertTrue("The license file is 'LICENSE'", content.getName().equals("LICENSE")); + + if (content.getEncoding().equals("base64")) { + String licenseText = new String(IOUtils.toByteArray(content.read())); + assertTrue("The license appears to be an Apache License", licenseText.contains("Apache License")); + } else { + fail("Expected the license to be Base64 encoded but instead it was " + content.getEncoding()); + } + } +} diff --git a/src/test/java/org/kohsuke/github/RepositoryTest.java b/src/test/java/org/kohsuke/github/RepositoryTest.java index 61cdc09dca..dae2892a45 100644 --- a/src/test/java/org/kohsuke/github/RepositoryTest.java +++ b/src/test/java/org/kohsuke/github/RepositoryTest.java @@ -50,4 +50,12 @@ public void listLanguages() throws IOException { String mainLanguage = r.getLanguage(); assertTrue(r.listLanguages().containsKey(mainLanguage)); } + + @Test // Issue #261 + public void listEmptyContributors() throws IOException { + GitHub gh = GitHub.connect(); + for (Contributor c : gh.getRepository("github-api-test-org/empty").listContributors()) { + System.out.println(c); + } + } }