Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[JENKINS-44581] Before we go overriding the Jackson2 version, add som…
…e basic sanity tests - The tests use a very minimal mock GitHub server that only understands a subset of the API - We just need to verify that Jackson deserializes correctly and things like paged iterables function - Left as an excercise to subsequent committers are things like making a test JAR and making the mock more fluent and comprehensive. - Ideally would also be good if we had some mock verification tests, that allow comparing the mock output with the real github and a github enterprise, to eliminate api change issues
- Loading branch information
Showing
11 changed files
with
776 additions
and
0 deletions.
There are no files selected for viewing
105 changes: 105 additions & 0 deletions
105
src/test/java/jenkins/plugins/github/api/SmokeTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package jenkins.plugins.github.api; | ||
|
||
import java.io.IOException; | ||
import java.net.URL; | ||
import java.util.Set; | ||
import java.util.TreeSet; | ||
import jenkins.plugins.github.api.mock.MockGitHub; | ||
import jenkins.plugins.github.api.mock.MockOrganization; | ||
import jenkins.plugins.github.api.mock.MockUser; | ||
import org.apache.commons.io.IOUtils; | ||
import org.junit.Test; | ||
import org.kohsuke.github.GHOrganization; | ||
import org.kohsuke.github.GHRepository; | ||
import org.kohsuke.github.GHUser; | ||
import org.kohsuke.github.GitHub; | ||
|
||
import static org.hamcrest.Matchers.contains; | ||
import static org.hamcrest.Matchers.is; | ||
import static org.junit.Assert.assertThat; | ||
|
||
public class SmokeTest { | ||
@Test | ||
public void given__veryBasicMockGitHub__when__connectingAnonymously__then__apiUrlValid() throws Exception { | ||
try (MockGitHub mock = new MockGitHub()) { | ||
GitHub.connectToEnterpriseAnonymously(mock.open()).checkApiUrlValidity(); | ||
} | ||
} | ||
|
||
@Test | ||
public void given__veryBasicMockGitHub__when__listingRepos__then__reposListed() throws Exception { | ||
try (MockGitHub mock = new MockGitHub()) { | ||
mock.withOrg("org1").withPublicRepo("repo1").withPrivateRepo("repo2"); | ||
mock.withOrg("org2").withPublicRepo("repo3"); | ||
mock.withUser("user1").withPublicRepo("repo4").withPrivateRepo("repo5"); | ||
Set<String> names = new TreeSet<>(); | ||
for (GHRepository r: GitHub.connectToEnterpriseAnonymously(mock.open()).listAllPublicRepositories()) { | ||
names.add(r.getFullName()); | ||
} | ||
assertThat(names, contains("org1/repo1", "org2/repo3", "user1/repo4")); | ||
} | ||
} | ||
|
||
@Test | ||
public void given__veryBasicMockGitHub__when__listingManyRepos__then__reposListed() throws Exception { | ||
try (MockGitHub mock = new MockGitHub()) { | ||
MockOrganization org1 = mock.withOrg("org1"); | ||
Set<String> expected = new TreeSet<>(); | ||
for (int i = 0; i < 95; i++) { | ||
org1.withPublicRepo("repo"+i); | ||
expected.add("org1/repo"+i); | ||
|
||
} | ||
Set<String> actual = new TreeSet<>(); | ||
for (GHRepository r: GitHub.connectToEnterpriseAnonymously(mock.open()).listAllPublicRepositories()) { | ||
actual.add(r.getFullName()); | ||
} | ||
assertThat(actual, is(actual)); | ||
} | ||
} | ||
|
||
@Test | ||
public void given__veryBasicMockGitHub__when__gettingUser__then__userReturned() throws Exception { | ||
try (MockGitHub mock = new MockGitHub()) { | ||
MockUser expected = mock.withUser("user1") | ||
.withAvatarUrl("http://avatar.test/user1") | ||
.withCompany("Testing Inc") | ||
.withName("User One") | ||
.withBlog("https://user1.test") | ||
.withEmail("bob@test") | ||
.withLocation("Unit test") | ||
.withPrivateRepo("repo1") | ||
.withPublicRepo("repo2") | ||
.withPublicRepo("repo3"); | ||
GHUser actual = GitHub.connectToEnterpriseAnonymously(mock.open()).getUser("user1"); | ||
assertThat(actual.getLogin(), is(expected.getLogin())); | ||
assertThat(actual.getName(), is(expected.getName())); | ||
assertThat(actual.getAvatarUrl(), is(expected.getAvatarUrl())); | ||
assertThat(actual.getBlog(), is(expected.getBlog())); | ||
assertThat(actual.getCompany(), is(expected.getCompany())); | ||
assertThat(actual.getId(), is((int)expected.getId())); | ||
assertThat(actual.getPublicRepoCount(), is(expected.getPublicRepos())); | ||
} | ||
} | ||
@Test | ||
public void given__veryBasicMockGitHub__when__gettingOrg__then__orgReturned() throws Exception { | ||
try (MockGitHub mock = new MockGitHub()) { | ||
MockOrganization expected = mock.withOrg("org1") | ||
.withAvatarUrl("http://avatar.test/org1") | ||
.withDescription("User One") | ||
.withBlog("https://org1.test") | ||
.withEmail("bob@test") | ||
.withLocation("Unit test") | ||
.withPrivateRepo("repo1") | ||
.withPublicRepo("repo2") | ||
.withPublicRepo("repo3"); | ||
GHOrganization actual = GitHub.connectToEnterpriseAnonymously(mock.open()).getOrganization("org1"); | ||
assertThat(actual.getLogin(), is(expected.getLogin())); | ||
assertThat(actual.getName(), is(expected.getName())); | ||
assertThat(actual.getAvatarUrl(), is(expected.getAvatarUrl())); | ||
assertThat(actual.getBlog(), is(expected.getBlog())); | ||
assertThat(actual.getId(), is((int)expected.getId())); | ||
assertThat(actual.getPublicRepoCount(), is(expected.getPublicRepos())); | ||
} | ||
} | ||
} |
233 changes: 233 additions & 0 deletions
233
src/test/java/jenkins/plugins/github/api/mock/MockGitHub.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,233 @@ | ||
package jenkins.plugins.github.api.mock; | ||
|
||
import com.fasterxml.jackson.core.JsonFactory; | ||
import com.fasterxml.jackson.core.JsonGenerator; | ||
import hudson.Util; | ||
import java.io.Closeable; | ||
import java.io.IOException; | ||
import java.io.StringWriter; | ||
import java.net.URI; | ||
import java.net.URISyntaxException; | ||
import java.text.SimpleDateFormat; | ||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.Comparator; | ||
import java.util.Date; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.TimeZone; | ||
import java.util.concurrent.LinkedBlockingQueue; | ||
import java.util.concurrent.ThreadFactory; | ||
import java.util.concurrent.ThreadPoolExecutor; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.concurrent.atomic.AtomicLong; | ||
import javax.servlet.ServletException; | ||
import org.eclipse.jetty.server.HttpConfiguration; | ||
import org.eclipse.jetty.server.HttpConnectionFactory; | ||
import org.eclipse.jetty.server.Server; | ||
import org.eclipse.jetty.server.ServerConnector; | ||
import org.eclipse.jetty.servlet.ServletContextHandler; | ||
import org.jvnet.hudson.test.ThreadPoolImpl; | ||
import org.kohsuke.stapler.HttpResponse; | ||
import org.kohsuke.stapler.QueryParameter; | ||
import org.kohsuke.stapler.Stapler; | ||
import org.kohsuke.stapler.StaplerRequest; | ||
import org.kohsuke.stapler.StaplerResponse; | ||
|
||
public class MockGitHub implements Closeable { | ||
private AtomicLong nextId = new AtomicLong(); | ||
private Map<String, MockUser> users = new HashMap<>(); | ||
private Map<String, MockOrganization> organizations = new HashMap<>(); | ||
|
||
private Server server; | ||
|
||
private int localPort = -1; | ||
private JsonFactory factory = new JsonFactory(); | ||
|
||
public String open() throws IOException { | ||
server = new Server(new ThreadPoolImpl( | ||
new ThreadPoolExecutor(10, 10, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), | ||
new ThreadFactory() { | ||
public Thread newThread(Runnable r) { | ||
Thread t = new Thread(r); | ||
t.setName("Jetty Thread Pool"); | ||
return t; | ||
} | ||
}))); | ||
ServletContextHandler context = new ServletContextHandler(); | ||
server.setHandler(context); | ||
; | ||
context.addServlet(Stapler.class, "/*"); | ||
|
||
ServerConnector connector = new ServerConnector(server); | ||
HttpConfiguration config = connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration(); | ||
// use a bigger buffer as Stapler traces can get pretty large on deeply nested URL | ||
config.setRequestHeaderSize(12 * 1024); | ||
connector.setHost("localhost"); | ||
|
||
server.addConnector(connector); | ||
try { | ||
server.start(); | ||
} catch (IOException | RuntimeException | Error e) { | ||
throw e; | ||
} catch (Exception e) { | ||
throw new IOException(e); | ||
} | ||
|
||
localPort = connector.getLocalPort(); | ||
context.getServletContext().setAttribute("app", this); | ||
return getUrl(); | ||
} | ||
|
||
public String getUrl() { | ||
if (localPort == -1) { | ||
throw new IllegalStateException("Not open"); | ||
} | ||
try { | ||
return new URI("http", null, "localhost", localPort, null, null, null).toASCIIString(); | ||
} catch (URISyntaxException e) { | ||
throw new IllegalStateException("Will never happen", e); | ||
} | ||
} | ||
|
||
@Override | ||
public void close() throws IOException { | ||
localPort = -1; | ||
try { | ||
server.stop(); | ||
} catch (IOException | RuntimeException | Error e) { | ||
throw e; | ||
} catch (Exception e) { | ||
throw new IOException(e); | ||
} | ||
} | ||
|
||
public Map<String, MockUser> getUsers() { | ||
return users; | ||
} | ||
|
||
public Map<String, MockOrganization> getOrgs() { | ||
return organizations; | ||
} | ||
|
||
public long nextId() { | ||
return nextId.incrementAndGet(); | ||
} | ||
|
||
public String tz(long time) { | ||
SimpleDateFormat format = new SimpleDateFormat("YYYY-MM-dd'T'HH:mm:ss'Z'"); | ||
format.setTimeZone(TimeZone.getTimeZone("UTC")); | ||
return format.format(new Date(time)); | ||
} | ||
|
||
public String pathSegment(String segment) { | ||
return Util.rawEncode(segment); | ||
} | ||
|
||
public String pathFragment(String fragment) { | ||
StringBuilder result = new StringBuilder(fragment.length() + 10); | ||
int i0 = 0; | ||
int i1 = fragment.indexOf('/'); | ||
while (i0 != -1) { | ||
if (i0 > 0) { | ||
result.append('/'); | ||
} | ||
if (i1 == -1) { | ||
result.append(pathSegment(fragment.substring(i0))); | ||
i0 = i1; | ||
} else { | ||
result.append(pathSegment(fragment.substring(i0, i1))); | ||
i0 = i1 + 1; | ||
i1 = fragment.indexOf('/', i0); | ||
} | ||
} | ||
return result.toString(); | ||
} | ||
|
||
public String json(Object object) throws IOException { | ||
if (object == null) { | ||
return "null"; | ||
} | ||
StringWriter w = new StringWriter(); | ||
JsonGenerator generator = factory.createGenerator(w); | ||
generator.writeObject(object); | ||
generator.close(); | ||
return w.toString(); | ||
} | ||
|
||
public MockUser withUser(String login) { | ||
MockUser result = new MockUser(this, login); | ||
users.put(login, result); | ||
return result; | ||
} | ||
|
||
public MockOrganization withOrg(String login) { | ||
MockOrganization result = new MockOrganization(this, login); | ||
organizations.put(login, result); | ||
return result; | ||
} | ||
|
||
public List<MockOwner<?>> owners() { | ||
List<MockOwner<?>> result = new ArrayList<>(organizations.size() + users.size()); | ||
result.addAll(users.values()); | ||
result.addAll(organizations.values()); | ||
return result; | ||
} | ||
|
||
public HttpResponse doRepositories(final @QueryParameter long since) { | ||
return new HttpResponse() { | ||
@Override | ||
public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node) | ||
throws IOException, ServletException { | ||
List<MockRepository> repositories = new ArrayList<>(); | ||
for (MockOwner<?> o : owners()) { | ||
for (MockRepository r: o.repositories().values()) { | ||
if (r.getId() > since && !r.isPrivate()) { | ||
repositories.add(r); | ||
} | ||
} | ||
} | ||
Collections.sort(repositories, new Comparator<MockRepository>() { | ||
@Override | ||
public int compare(MockRepository o1, MockRepository o2) { | ||
return Long.compare(o1.getId(), o2.getId()); | ||
} | ||
}); | ||
if (repositories.size() > 30) { | ||
rsp.addHeader("Link", String.format( | ||
"<%s/repositories?since=%d>; rel=\"next\", <%s/repositories{?since}>; rel=\"first\"", | ||
getUrl(), repositories.get(30).getId(), getUrl())); | ||
} else { | ||
rsp.addHeader("Link", String.format("<%s/repositories{?since}>; rel=\"first\"", getUrl())); | ||
} | ||
rsp.setContentType("application/json; charset=utf-8"); | ||
JsonGenerator o = factory.createGenerator(rsp.getOutputStream()); | ||
o.writeStartArray(); | ||
try { | ||
for (MockRepository r : repositories.subList(0, Math.min(30, repositories.size()))) { | ||
o.writeStartObject(); | ||
o.writeObjectField("id", r.getId()); | ||
o.writeObjectField("name", r.getName()); | ||
o.writeObjectField("full_name", r.owner().getLogin()+"/"+r.getName()); | ||
o.writeFieldName("owner"); | ||
o.writeStartObject(); | ||
o.writeObjectField("login", r.owner().getLogin()); | ||
o.writeObjectField("id", r.owner().getId()); | ||
o.writeObjectField("avatar_url", r.owner().getAvatarUrl()); | ||
o.writeObjectField("type", r.owner().getType()); | ||
o.writeEndObject(); | ||
o.writeObjectField("private", r.isPrivate()); | ||
o.writeObjectField("html_url", "https://github.com/"+r.owner().getLogin()+"/"+r.getName()); | ||
|
||
o.writeEndObject(); | ||
} | ||
} finally { | ||
o.writeEndArray(); | ||
o.close(); | ||
} | ||
|
||
} | ||
}; | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
src/test/java/jenkins/plugins/github/api/mock/MockObject.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package jenkins.plugins.github.api.mock; | ||
|
||
/** | ||
* @author Stephen Connolly | ||
*/ | ||
public class MockObject { | ||
private final MockGitHub app; | ||
|
||
private final long id; | ||
private long created; | ||
private long updated; | ||
|
||
public MockObject(MockGitHub app) { | ||
this.app = app; | ||
this.id = app.nextId(); | ||
this.created = System.currentTimeMillis(); | ||
this.updated = System.currentTimeMillis(); | ||
} | ||
|
||
public MockGitHub app() { | ||
return app; | ||
} | ||
|
||
public long getId() { | ||
return id; | ||
} | ||
|
||
public long getCreated() { | ||
return created; | ||
} | ||
|
||
public long getUpdated() { | ||
return updated; | ||
} | ||
|
||
public void touch() { | ||
updated = System.currentTimeMillis(); | ||
} | ||
} |
Oops, something went wrong.