mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-01-19 14:02:13 +01:00
Create new modification command
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
import com.google.common.io.ByteSource;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.Files;
|
||||
import sonia.scm.repository.spi.ModificationCommand;
|
||||
import sonia.scm.repository.util.WorkdirProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class ModificationCommandBuilder {
|
||||
|
||||
private final ModificationCommand command;
|
||||
private final File workdir;
|
||||
|
||||
private final List<FileModification> modifications = new ArrayList<>();
|
||||
|
||||
ModificationCommandBuilder(ModificationCommand command, WorkdirProvider workdirProvider) {
|
||||
this.command = command;
|
||||
this.workdir = workdirProvider.createNewWorkdir();
|
||||
}
|
||||
|
||||
ModificationCommandBuilder setBranchToModify(String branchToModify) {
|
||||
return this;
|
||||
}
|
||||
|
||||
ContentLoader createFile(String path) {
|
||||
return new ContentLoader(
|
||||
content -> modifications.add(new CreateFile(path, content))
|
||||
);
|
||||
}
|
||||
|
||||
ContentLoader modifyFile(String path) {
|
||||
return new ContentLoader(
|
||||
content -> modifications.add(new ModifyFile(path, content))
|
||||
);
|
||||
}
|
||||
|
||||
ModificationCommandBuilder deleteFile(String path) {
|
||||
modifications.add(new DeleteFile(path));
|
||||
return this;
|
||||
}
|
||||
|
||||
ModificationCommandBuilder moveFile(String sourcePath, String targetPath) {
|
||||
modifications.add(new MoveFile(sourcePath, targetPath));
|
||||
return this;
|
||||
}
|
||||
|
||||
String execute() {
|
||||
modifications.forEach(FileModification::execute);
|
||||
modifications.forEach(FileModification::cleanup);
|
||||
return command.commit();
|
||||
}
|
||||
|
||||
private Content loadData(ByteSource data) throws IOException {
|
||||
File file = createTemporaryFile();
|
||||
data.copyTo(Files.asByteSink(file));
|
||||
return new Content(file);
|
||||
}
|
||||
|
||||
private Content loadData(InputStream data) throws IOException {
|
||||
File file = createTemporaryFile();
|
||||
OutputStream out = Files.asByteSink(file).openBufferedStream();
|
||||
ByteStreams.copy(data, out);
|
||||
out.close();
|
||||
return new Content(file);
|
||||
}
|
||||
|
||||
private File createTemporaryFile() throws IOException {
|
||||
return File.createTempFile("upload-", "", workdir);
|
||||
}
|
||||
|
||||
private interface FileModification {
|
||||
void execute();
|
||||
|
||||
default void cleanup() {
|
||||
}
|
||||
}
|
||||
|
||||
private class DeleteFile implements FileModification {
|
||||
private final String path;
|
||||
|
||||
public DeleteFile(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
command.delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
private class MoveFile implements FileModification {
|
||||
private final String sourcePath;
|
||||
private final String targetPath;
|
||||
|
||||
private MoveFile(String sourcePath, String targetPath) {
|
||||
this.sourcePath = sourcePath;
|
||||
this.targetPath = targetPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
command.move(sourcePath, targetPath);
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class DataBasedModification implements FileModification {
|
||||
|
||||
private final Content content;
|
||||
|
||||
DataBasedModification(Content content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public Content getContent() {
|
||||
return content;
|
||||
}
|
||||
@Override
|
||||
public void cleanup() {
|
||||
content.deleteFile();
|
||||
}
|
||||
}
|
||||
|
||||
private class CreateFile extends DataBasedModification {
|
||||
|
||||
private final String path;
|
||||
|
||||
CreateFile(String path, Content content) {
|
||||
super(content);
|
||||
this.path = path;
|
||||
}
|
||||
@Override
|
||||
public void execute() {
|
||||
command.create(path, getContent().getFile());
|
||||
}
|
||||
}
|
||||
|
||||
private class ModifyFile extends DataBasedModification {
|
||||
|
||||
private final String path;
|
||||
|
||||
ModifyFile(String path, Content content) {
|
||||
super(content);
|
||||
this.path = path;
|
||||
}
|
||||
@Override
|
||||
public void execute() {
|
||||
command.modify(path, getContent().getFile());
|
||||
}
|
||||
}
|
||||
|
||||
public class ContentLoader {
|
||||
|
||||
private final Consumer<Content> contentConsumer;
|
||||
|
||||
private ContentLoader(Consumer<Content> contentConsumer) {
|
||||
this.contentConsumer = contentConsumer;
|
||||
}
|
||||
|
||||
public ModificationCommandBuilder withData(ByteSource data) throws IOException {
|
||||
Content content = loadData(data);
|
||||
contentConsumer.accept(content);
|
||||
return ModificationCommandBuilder.this;
|
||||
}
|
||||
public ModificationCommandBuilder withData(InputStream data) throws IOException {
|
||||
Content content = loadData(data);
|
||||
contentConsumer.accept(content);
|
||||
return ModificationCommandBuilder.this;
|
||||
}
|
||||
}
|
||||
|
||||
private class Content {
|
||||
|
||||
private final File file;
|
||||
|
||||
Content(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
private File getFile() {
|
||||
return file;
|
||||
}
|
||||
public void deleteFile() {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public interface ModificationCommand {
|
||||
String commit();
|
||||
|
||||
void delete(String toBeDeleted);
|
||||
|
||||
void create(String toBeCreated, File file);
|
||||
|
||||
void modify(String path, File file);
|
||||
|
||||
void move(String sourcePath, String targetPath);
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
import com.google.common.io.ByteSource;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junitpioneer.jupiter.TempDirectory;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import sonia.scm.repository.spi.ModificationCommand;
|
||||
import sonia.scm.repository.util.WorkdirProvider;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@ExtendWith(TempDirectory.class)
|
||||
class ModificationCommandBuilderTest {
|
||||
|
||||
@Mock
|
||||
ModificationCommand command;
|
||||
@Mock
|
||||
WorkdirProvider workdirProvider;
|
||||
|
||||
ModificationCommandBuilder commandBuilder;
|
||||
|
||||
@BeforeEach
|
||||
void initWorkdir(@TempDirectory.TempDir Path temp) throws IOException {
|
||||
lenient().when(workdirProvider.createNewWorkdir()).thenReturn(temp.toFile());
|
||||
commandBuilder = new ModificationCommandBuilder(command, workdirProvider);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnTargetRevisionFromCommit() {
|
||||
when(command.commit()).thenReturn("target");
|
||||
|
||||
String targetRevision = commandBuilder.execute();
|
||||
|
||||
assertThat(targetRevision).isEqualTo("target");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExecuteDelete() {
|
||||
commandBuilder
|
||||
.deleteFile("toBeDeleted")
|
||||
.execute();
|
||||
|
||||
verify(command).delete("toBeDeleted");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExecuteMove() {
|
||||
commandBuilder
|
||||
.moveFile("source", "target")
|
||||
.execute();
|
||||
|
||||
verify(command).move("source", "target");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExecuteCreateWithByteSourceContent() throws IOException {
|
||||
ArgumentCaptor<String> nameCaptor = ArgumentCaptor.forClass(String.class);
|
||||
List<String> contentCaptor = new ArrayList<>();
|
||||
doAnswer(new ExtractContent(contentCaptor)).when(command).create(nameCaptor.capture(), any());
|
||||
|
||||
commandBuilder
|
||||
.createFile("toBeCreated").withData(ByteSource.wrap("content".getBytes()))
|
||||
.execute();
|
||||
|
||||
assertThat(nameCaptor.getValue()).isEqualTo("toBeCreated");
|
||||
assertThat(contentCaptor).contains("content");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExecuteCreateWithInputStreamContent() throws IOException {
|
||||
ArgumentCaptor<String> nameCaptor = ArgumentCaptor.forClass(String.class);
|
||||
List<String> contentCaptor = new ArrayList<>();
|
||||
doAnswer(new ExtractContent(contentCaptor)).when(command).create(nameCaptor.capture(), any());
|
||||
|
||||
commandBuilder
|
||||
.createFile("toBeCreated").withData(new ByteArrayInputStream("content".getBytes()))
|
||||
.execute();
|
||||
|
||||
assertThat(nameCaptor.getValue()).isEqualTo("toBeCreated");
|
||||
assertThat(contentCaptor).contains("content");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExecuteCreateMultipleTimes() throws IOException {
|
||||
ArgumentCaptor<String> nameCaptor = ArgumentCaptor.forClass(String.class);
|
||||
List<String> contentCaptor = new ArrayList<>();
|
||||
doAnswer(new ExtractContent(contentCaptor)).when(command).create(nameCaptor.capture(), any());
|
||||
|
||||
commandBuilder
|
||||
.createFile("toBeCreated_1").withData(new ByteArrayInputStream("content_1".getBytes()))
|
||||
.createFile("toBeCreated_2").withData(new ByteArrayInputStream("content_2".getBytes()))
|
||||
.execute();
|
||||
|
||||
List<String> createdNames = nameCaptor.getAllValues();
|
||||
assertThat(createdNames.get(0)).isEqualTo("toBeCreated_1");
|
||||
assertThat(createdNames.get(1)).isEqualTo("toBeCreated_2");
|
||||
assertThat(contentCaptor).contains("content_1", "content_2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExecuteModify() throws IOException {
|
||||
ArgumentCaptor<String> nameCaptor = ArgumentCaptor.forClass(String.class);
|
||||
List<String> contentCaptor = new ArrayList<>();
|
||||
doAnswer(new ExtractContent(contentCaptor)).when(command).modify(nameCaptor.capture(), any());
|
||||
|
||||
commandBuilder
|
||||
.modifyFile("toBeModified").withData(ByteSource.wrap("content".getBytes()))
|
||||
.execute();
|
||||
|
||||
assertThat(nameCaptor.getValue()).isEqualTo("toBeModified");
|
||||
assertThat(contentCaptor).contains("content");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDeleteTemporaryFiles(@TempDirectory.TempDir Path temp) throws IOException {
|
||||
ArgumentCaptor<String> nameCaptor = ArgumentCaptor.forClass(String.class);
|
||||
ArgumentCaptor<File> fileCaptor = ArgumentCaptor.forClass(File.class);
|
||||
doNothing().when(command).modify(nameCaptor.capture(), fileCaptor.capture());
|
||||
|
||||
commandBuilder
|
||||
.modifyFile("toBeModified").withData(ByteSource.wrap("content".getBytes()))
|
||||
.execute();
|
||||
|
||||
assertThat(Files.list(temp)).isEmpty();
|
||||
}
|
||||
|
||||
private static class ExtractContent implements Answer {
|
||||
private final List<String> contentCaptor;
|
||||
|
||||
public ExtractContent(List<String> contentCaptor) {
|
||||
this.contentCaptor = contentCaptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||
return contentCaptor.add(Files.readAllLines(((File) invocation.getArgument(1)).toPath()).get(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user