package org.koshinuke.service;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.MediaType;
import org.eclipse.jgit.api.AddCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Before;
import org.junit.Test;
import org.koshinuke.App;
import org.koshinuke._;
import org.koshinuke.jersey.TestConfigurationtProvider;
import org.koshinuke.jersey.TestPrincipalProvider;
import org.koshinuke.jgit.server.EachRefPackWriter;
import org.koshinuke.jgit.server.GitHttpdService;
import org.koshinuke.jgit.server.ReceivePackWriter;
import org.koshinuke.jgit.server.UploadPackWriter;
import org.koshinuke.model.BlameModel;
import org.koshinuke.model.BlobModel;
import org.koshinuke.model.BranchHistoryModel;
import org.koshinuke.model.CommitModel;
import org.koshinuke.model.DiffEntryModel;
import org.koshinuke.model.DiffModel;
import org.koshinuke.model.NodeModel;
import org.koshinuke.model.RepositoryModel;
import org.koshinuke.test.KoshinukeTest;
import org.koshinuke.util.GitUtil;
import org.koshinuke.util.ServletUtil;
import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.io.Resources;
import com.sun.jersey.api.client.GenericType;
import com.sun.jersey.api.client.UniformInterfaceException;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.representation.Form;
/**
* @author taichi
*/
public class RepositoryServiceTest extends KoshinukeTest {
public static class AP extends Application {
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> s = new HashSet<>();
s.add(RepositoryService.class);
s.add(GitHttpdService.class);
s.add(TestConfigurationtProvider.class);
s.add(TestPrincipalProvider.class);
return s;
}
@Override
public Set<Object> getSingletons() {
HashSet<Object> singletons = new HashSet<Object>();
singletons.add(App.makeJsonProvider());
singletons.add(new EachRefPackWriter());
singletons.add(new UploadPackWriter());
singletons.add(new ReceivePackWriter());
return singletons;
}
}
@Override
protected Class<? extends Application> getApplicationClass() {
return AP.class;
}
@Override
@Before
public void setUp() throws Exception {
super.setUp();
deleteDirs();
}
@Test
public void testListNoRepositories() throws Exception {
List<RepositoryModel> list = this.resource().path("/api/1.0")
.accept(MediaType.APPLICATION_JSON_TYPE)
.get(new GenericType<List<RepositoryModel>>() {
});
assertEquals(0, list.size());
}
@Test
public void testInit() throws Exception {
final File dest = new File("bin", "testInit");
final String readme = "readme text";
final String path = "test/init";
Form form = new Form();
form.add("!", "init");
form.add("rn", path);
form.add("rr", readme);
List<RepositoryModel> list = this.resource().path("/api/1.0")
.accept(MediaType.APPLICATION_JSON_TYPE)
.type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
.post(new GenericType<List<RepositoryModel>>() {
}, form);
assertNotNull(list);
assertEquals(1, list.size());
RepositoryModel rm = list.get(0);
assertEquals(rm.getPath(), path);
assertEquals(rm.getName(), "init");
assertEquals(1, rm.getBranches().size());
assertEquals("master", rm.getBranches().get(0).getName());
Path repo = config.getRepositoryRootDir().resolve(path);
assertTrue(Files.exists(repo));
GitUtil.handleClone(repo.toUri(), dest, new Function<Git, _>() {
@Override
public _ apply(Git g) {
try {
File R = new File(dest, "README");
assertTrue(R.exists());
String destText = Resources.toString(R.toURI().toURL(),
Charsets.UTF_8);
assertEquals(readme, destText);
} catch (IOException e) {
throw new AssertionError(e);
}
return _._;
}
});
}
@Test
public void testClone() throws Exception {
this.cloneTestRepo();
Form form = new Form();
form.add("!", "clone");
form.add("uri", this.getBaseURI().resolve("/proj/repo.git").toString());
form.add("un", "username");
form.add("up", "password");
List<RepositoryModel> list = this.resource().path("/api/1.0")
.accept(MediaType.APPLICATION_JSON_TYPE)
.type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
.post(new GenericType<List<RepositoryModel>>() {
}, form);
assertNotNull(list);
assertEquals(2, list.size());
}
@Test
public void testTree() throws Exception {
this.cloneTestRepo();
this.get(this.resource(), "api/1.0/proj/repo/tree/master");
}
@Test
public void testTreeWithParam() throws Exception {
this.cloneTestRepo();
List<NodeModel> list = this.get(
this.resource().queryParam("offset", "1")
.queryParam("limit", "4"),
"api/1.0/proj/repo/tree/branchbranch/");
assertEquals(4, list.size());
assertEquals(0, list.get(1).getTimestamp());
assertEquals(1, list.get(1).getChildren());
assertEquals(0, list.get(2).getTimestamp());
assertEquals(2, list.get(2).getChildren());
}
@Test
public void testTreeWithContext() throws Exception {
this.cloneTestRepo();
List<NodeModel> list = this.get(this.resource(),
"api/1.0/proj/repo/tree/branchbranch/moge/");
assertEquals(3, list.size());
list = this.get(this.resource(),
"api/1.0/proj/repo/tree/branchbranch/hoge/piyo");
assertEquals(2, list.size());
assertFalse(0 == list.get(0).getTimestamp());
assertEquals(0, list.get(0).getChildren());
}
@Test
public void testTreeWithTag() throws Exception {
this.cloneTestRepo();
List<NodeModel> list = this.get(this.resource(),
"api/1.0/proj/repo/tree/beta/0.0.1");
assertEquals(4, list.size());
}
protected List<NodeModel> get(WebResource webResource, String url)
throws Exception {
List<NodeModel> list = webResource.path(url)
.accept(MediaType.APPLICATION_JSON_TYPE)
.get(new GenericType<List<NodeModel>>() {
});
System.out.println("======================");
assertNotNull(list);
Collections.sort(list, new Comparator<NodeModel>() {
@Override
public int compare(NodeModel l, NodeModel r) {
return l.getPath().compareTo(r.getPath());
}
});
for (NodeModel nm : list) {
System.out.printf("%s %10s %s %s %n", nm.getChildren(),
nm.getTimestamp(), nm.getPath(), nm.getMessage());
}
return list;
}
@Test
public void testBlob() throws Exception {
this.cloneTestRepo();
BlobModel bm = this.resource()
.path("/api/1.0/proj/repo/blob/master/README")
.accept(MediaType.APPLICATION_JSON_TYPE).get(BlobModel.class);
assertNotNull(bm);
assertEquals("readme readme", bm.getContent());
assertEquals("initial commit", bm.getMessage());
bm = this.resource()
.path("/api/1.0/proj/repo/blob/test/hoge/hoge/moge/piro.txt")
.accept(MediaType.APPLICATION_JSON_TYPE).get(BlobModel.class);
assertNotNull(bm);
assertEquals("gyappa gyappa", bm.getContent());
assertEquals("gyawawa", bm.getMessage());
assertEquals("monster1", bm.getAuthor());
}
@Test
public void testModifyBlob() throws Exception {
this.cloneTestRepo();
{
String path = "/api/1.0/proj/repo/blob/master/README";
BlobModel bm = this.getBlob(path);
BlobModel newone = new BlobModel(bm);
newone.setMessage("modifiy!!");
newone.setContent(bm.getContent() + "\nhogehoge");
this.testModifyBlob(path, newone);
}
String path = "/api/1.0/proj/repo/blob/test/hoge/hoge/moge/piro.txt";
BlobModel bm = this.getBlob(path);
BlobModel newone = new BlobModel(bm);
newone.setMessage("mod mod");
newone.setContent(bm.getContent() + "\nhogehoge");
this.testModifyBlob(path, newone);
}
@Test
public void testModiryErrorBecauseTagCannotModify() throws Exception {
this.cloneTestRepo();
String path = "/api/1.0/proj/repo/blob/beta/0.0.2/myomyo/muga/piyopiyo.txt";
BlobModel bm = this.getBlob(path);
BlobModel newone = new BlobModel(bm);
newone.setMessage("mod mod");
newone.setContent(bm.getContent() + "\nhogehoge");
try {
this.testModifyBlob(path, newone);
fail();
} catch (UniformInterfaceException e) {
assertEquals(ServletUtil.SC_UNPROCESSABLE_ENTITY, e.getResponse()
.getStatus());
}
}
protected BlobModel getBlob(String path) {
BlobModel bm = this.resource().path(path)
.accept(MediaType.APPLICATION_JSON_TYPE).get(BlobModel.class);
assertNotNull(bm);
return bm;
}
protected void testModifyBlob(String path, BlobModel newone) {
BlobModel modified = this.resource().path(path)
.accept(MediaType.APPLICATION_JSON)
.entity(newone, MediaType.APPLICATION_JSON)
.post(BlobModel.class);
assertNotNull(modified);
assertNotSame(newone.getTimestamp(), modified.getTimestamp());
assertEquals(newone.getMessage(), modified.getMessage());
assertEquals(newone.getContent(), modified.getContent());
}
@Test
public void testHistories() throws Exception {
this.setUpTestHistories(this.cloneTestRepo());
String path = "/api/1.0/proj/repo/history";
List<BranchHistoryModel> list = this.resource().path(path)
.get(new GenericType<List<BranchHistoryModel>>() {
});
assertNotNull(list);
for (BranchHistoryModel model : list) {
assertTrue(0 != model.getTimestamp());
assertEquals(model.getName(), 30, model.getActivities().size());
assertEquals(model.getPath(), model.getName());
System.out.printf("%12s ", model.getName());
for (long[] a : model.getActivities()) {
System.out.printf("[%s %s] ", new SimpleDateFormat("MM-dd")
.format(new Date(a[0] * 1000)), a[1]);
}
System.out.println();
if (model.getName().equals("test/hoge")) {
assertEquals(1, model.getActivities().get(29)[1]);
}
}
}
protected void setUpTestHistories(final File f) throws Exception {
final File working = new File("bin", "testHist");
GitUtil.handleClone(f.toURI(), working, new Function<Git, _>() {
@Override
public _ apply(Git g) {
try {
String sp = "test/hoge";
g.checkout()
.setCreateBranch(true)
.setName(sp)
.setStartPoint(Constants.R_REMOTES + "origin/" + sp)
.call();
String content = "ぎょぱぎょぱ";
String path = "ppp/zzz/gg.txt";
File newone = new File(working, path);
if (newone.getParentFile().mkdirs() == false) {
throw new IllegalStateException();
}
com.google.common.io.Files.write(content, newone,
java.nio.charset.Charset.forName("UTF-8"));
g.add().addFilepattern(path).call();
g.commit().setMessage("ぎょっわ")
.setAuthor("testtest", "testHist@koshinuke.org")
.call();
g.push().call();
return _._;
} catch (GitAPIException | IOException e) {
throw new IllegalStateException(e);
}
}
});
}
@Test
public void testGetCommits() throws Exception {
this.setUpTestHistories(this.cloneTestRepo());
String path = "/api/1.0/proj/repo/commits/test/moge";
List<CommitModel> list = this.resource().path(path)
.get(new GenericType<List<CommitModel>>() {
});
assertNotNull(list);
assertEquals(3, list.size());
assertEquals("ぐわわ…", list.get(0).getMessage());
assertEquals("gyawawa", list.get(1).getMessage());
assertEquals("initial commit", list.get(2).getMessage());
}
protected String setUpTestDiff(File f) throws Exception {
final File working = new File("bin", "testDiff");
return GitUtil.handleClone(f.toURI(), working,
new Function<Git, String>() {
@Override
public String apply(Git g) {
try {
String sp = "test/hoge";
g.checkout()
.setCreateBranch(true)
.setName(sp)
.setStartPoint(
Constants.R_REMOTES + "origin/"
+ sp).call();
AddCommand add = g.add();
{
String content = "ぎょぱぎょぱ";
String path = "ppp/zzz/gg.txt";
File newone = new File(working, path);
if (newone.getParentFile().mkdirs() == false) {
throw new IllegalStateException();
}
com.google.common.io.Files.write(content,
newone, java.nio.charset.Charset
.forName("UTF-8"));
add.addFilepattern(path);
}
{
String content = "gyappa \nguwawa\ngyowagyowa";
String path = "hoge/moge/piro.txt";
File newone = new File(working, path);
com.google.common.io.Files.write(content,
newone, java.nio.charset.Charset
.forName("UTF-8"));
add.addFilepattern(path);
}
add.call();
RevCommit commit = g
.commit()
.setMessage("ぎょっわぎょわ")
.setAuthor("testdiff",
"testdiff@koshinuke.org").call();
g.push().call();
return commit.getId().name();
} catch (GitAPIException | IOException e) {
throw new IllegalStateException(e);
}
}
});
}
@Test
public void testDiff() throws Exception {
String commitid = this.setUpTestDiff(this.cloneTestRepo());
String path = "/api/1.0/proj/repo/commit/" + commitid;
DiffModel model = this.resource().path(path).get(DiffModel.class);
assertNotNull(model);
assertEquals(commitid, model.getCommit().name());
assertEquals(1, model.getParents().length);
List<DiffEntryModel> list = model.getDiff();
assertEquals(2, list.size());
assertTrue(list.get(0).getPatch().startsWith("@@ -1 +1,3 @@"));
assertTrue(list.get(1).getPatch().startsWith("@@ -0,0 +1 @@"));
for (DiffEntryModel dem : list) {
System.out.printf("[%s] %s -> %s %n", dem.getOperation(),
dem.getOldPath(), dem.getNewPath());
System.out.println("=============================================");
System.out.println(dem.getOldContent());
System.out.println("=============================================");
System.out.println(dem.getPatch());
}
}
@Test
public void testBlame() throws Exception {
this.setUpTestDiff(this.cloneTestRepo());
List<BlameModel> list = this.resource()
.path("/api/1.0/proj/repo/blame/test/hoge/hoge/moge/piro.txt")
.accept(MediaType.APPLICATION_JSON_TYPE)
.get(new GenericType<List<BlameModel>>() {
});
assertNotNull(list);
assertEquals(3, list.size());
assertEquals("gyappa ", list.get(0).getContent());
assertEquals("ぎょっわぎょわ", list.get(0).getMessage());
for (BlameModel bm : list) {
System.out.printf("%s %s [%-20s] %s%n", bm.getAuthor(),
bm.getTimestamp(), bm.getMessage(), bm.getContent());
}
}
}