rename package sonia.scm.boot to sonia.scm.lifecycle

This commit is contained in:
Sebastian Sdorra
2019-06-25 08:36:57 +02:00
parent 56c7fcc114
commit 99f1c8c55e
45 changed files with 44 additions and 47 deletions

View File

@@ -0,0 +1,111 @@
package sonia.scm.lifecycle;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor;
import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventorFactory;
import java.io.Closeable;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class ClassLoaderLifeCycleTest {
@Mock
private ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory;
@Mock
private ClassLoaderLeakPreventor classLoaderLeakPreventor;
@Test
void shouldThrowIllegalStateExceptionWithoutInit() {
ClassLoaderLifeCycle lifeCycle = ClassLoaderLifeCycle.create();
assertThrows(IllegalStateException.class, lifeCycle::getBootstrapClassLoader);
}
@Test
void shouldThrowIllegalStateExceptionAfterShutdown() {
ClassLoaderLifeCycle lifeCycle = createMockedLifeCycle();
lifeCycle.init();
lifeCycle.shutdown();
assertThrows(IllegalStateException.class, lifeCycle::getBootstrapClassLoader);
}
@Test
void shouldCreateBootstrapClassLoaderOnInit() {
ClassLoaderLifeCycle lifeCycle = ClassLoaderLifeCycle.create();
lifeCycle.init();
assertThat(lifeCycle.getBootstrapClassLoader()).isNotNull();
}
@Test
void shouldCallTheLeakPreventor() {
ClassLoaderLifeCycle lifeCycle = createMockedLifeCycle();
lifeCycle.init();
verify(classLoaderLeakPreventor, times(2)).runPreClassLoaderInitiators();
lifeCycle.createChildFirstPluginClassLoader(new URL[0], null, "a");
lifeCycle.createPluginClassLoader(new URL[0], null, "b");
verify(classLoaderLeakPreventor, times(4)).runPreClassLoaderInitiators();
lifeCycle.shutdown();
verify(classLoaderLeakPreventor, times(4)).runCleanUps();
}
@Test
void shouldCloseCloseableClassLoaders() throws IOException {
// we use URLClassLoader, because we must be sure that the classloader is closable
URLClassLoader webappClassLoader = spy(new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()));
ClassLoaderLifeCycle lifeCycle = createMockedLifeCycle(webappClassLoader);
lifeCycle.setClassLoaderAppendListener(c -> spy(c));
lifeCycle.init();
ClassLoader pluginA = lifeCycle.createChildFirstPluginClassLoader(new URL[0], null, "a");
ClassLoader pluginB = lifeCycle.createPluginClassLoader(new URL[0], null, "b");
lifeCycle.shutdown();
closed(pluginB);
closed(pluginA);
neverClosed(webappClassLoader);
}
private void neverClosed(Object object) throws IOException {
Closeable closeable = closeable(object);
verify(closeable, never()).close();
}
private void closed(Object object) throws IOException {
Closeable closeable = closeable(object);
verify(closeable).close();
}
private Closeable closeable(Object object) {
assertThat(object).isInstanceOf(Closeable.class);
return (Closeable) object;
}
private ClassLoaderLifeCycle createMockedLifeCycle() {
return createMockedLifeCycle(Thread.currentThread().getContextClassLoader());
}
private ClassLoaderLifeCycle createMockedLifeCycle(ClassLoader classLoader) {
when(classLoaderLeakPreventorFactory.newLeakPreventor(any(ClassLoader.class))).thenReturn(classLoaderLeakPreventor);
return new ClassLoaderLifeCycle(classLoader, classLoaderLeakPreventorFactory);
}
}

View File

@@ -0,0 +1,89 @@
package sonia.scm.lifecycle;
import com.github.legman.Subscribe;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.event.RecreateEventBusEvent;
import sonia.scm.event.ScmEventBus;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
class InjectionContextRestartStrategyTest {
@Mock
private RestartStrategy.InjectionContext context;
private InjectionContextRestartStrategy strategy = new InjectionContextRestartStrategy();
@BeforeEach
void setWaitToZero() {
strategy.setWaitInMs(0L);
}
@Test
void shouldCallDestroyAndInitialize() throws InterruptedException {
strategy.restart(context);
verify(context).destroy();
Thread.sleep(50L);
verify(context).initialize();
}
@Test
void shouldFireRecreateEventBusEvent() {
Listener listener = new Listener();
ScmEventBus.getInstance().register(listener);
strategy.restart(context);
assertThat(listener.event).isNotNull();
}
@Test
void shouldRegisterContextAfterRestart() throws InterruptedException {
TestingInjectionContext ctx = new TestingInjectionContext();
strategy.restart(ctx);
Thread.sleep(50L);
ScmEventBus.getInstance().post("hello event");
assertThat(ctx.event).isEqualTo("hello event");
}
public static class Listener {
private RecreateEventBusEvent event;
@Subscribe(async = false)
public void setEvent(RecreateEventBusEvent event) {
this.event = event;
}
}
public static class TestingInjectionContext implements RestartStrategy.InjectionContext {
private volatile String event;
@Subscribe(async = false)
public void setEvent(String event) {
this.event = event;
}
@Override
public void initialize() {
}
@Override
public void destroy() {
}
}
}

View File

@@ -0,0 +1,256 @@
package sonia.scm.lifecycle;
import com.google.common.base.Strings;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.multibindings.Multibinder;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.CloseableModule;
import sonia.scm.Default;
import sonia.scm.EagerSingleton;
import sonia.scm.EagerSingletonModule;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.io.Closeable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(MockitoExtension.class)
class InjectionLifeCycleTest {
@Mock
private ServletContext servletContext;
@Test
void shouldInitializeEagerSingletons() {
Injector injector = initialize(new EagerSingletonModule(), new EagerModule());
Messenger messenger = injector.getInstance(Messenger.class);
assertThat(messenger.receive()).isEqualTo("eager baby!");
}
@Test
void shouldNotThrowAnExceptionWithoutEagerSingletons() {
Injector injector = initialize(new EagerSingletonModule());
Messenger messenger = injector.getInstance(Messenger.class);
assertThat(messenger.receive()).isNull();
}
@Test
void shouldInitializeServletContextListeners() {
Injector injector = initialize(new ServletContextListenerModule());
Messenger messenger = injector.getInstance(Messenger.class);
assertThat(messenger.receive()).isEqualTo("+4+2");
}
@Test
void shouldCallDestroyOnServletContextListeners() {
Injector injector = createInjector(servletContext, new ServletContextListenerModule());
InjectionLifeCycle lifeCycle = new InjectionLifeCycle(injector);
lifeCycle.shutdown();
Messenger messenger = injector.getInstance(Messenger.class);
assertThat(messenger.receive()).isEqualTo("-4-2");
}
@Test
void shouldCloseInstantiatedCloseables() {
Injector injector = createInjector(servletContext, new FortyTwoModule(), new CloseableModule());
injector.getInstance(Two.class);
injector.getInstance(Four.class);
InjectionLifeCycle lifeCycle = new InjectionLifeCycle(injector);
lifeCycle.shutdown();
Messenger messenger = injector.getInstance(Messenger.class);
assertThat(messenger.receive()).isEqualTo("42");
}
private Injector initialize(Module... modules) {
Injector injector = createInjector(servletContext, modules);
InjectionLifeCycle lifeCycle = new InjectionLifeCycle(injector);
lifeCycle.initialize();
return injector;
}
public static class EagerModule extends AbstractModule {
@Override
protected void configure() {
bind(ImEager.class);
}
}
@EagerSingleton
public static class ImEager {
@Inject
public ImEager(Messenger messenger) {
messenger.send("eager baby!");
}
}
public static class FortyTwoModule extends AbstractModule {
@Override
protected void configure() {
bind(Four.class);
bind(Two.class);
}
}
@Singleton
public static class Four implements Closeable {
private final Messenger messenger;
@Inject
public Four(Messenger messenger) {
this.messenger = messenger;
}
@Override
public void close() {
messenger.append("4");
}
}
@Singleton
public static class Two implements Closeable {
private final Messenger messenger;
@Inject
public Two(Messenger messenger) {
this.messenger = messenger;
}
@Override
public void close() {
messenger.append("2");
}
}
static Injector createInjector(ServletContext context, Module... modules) {
List<Module> moduleList = new ArrayList<>();
moduleList.add(new ServletContextModule(context));
moduleList.addAll(Arrays.asList(modules));
return Guice.createInjector(moduleList);
}
public static class ServletContextModule extends AbstractModule {
private final ServletContext servletContext;
ServletContextModule(ServletContext servletContext) {
this.servletContext = servletContext;
}
@Override
protected void configure() {
bind(ServletContext.class).annotatedWith(Default.class).toInstance(servletContext);
}
}
public static class ServletContextListenerModule extends AbstractModule {
@Override
protected void configure() {
Multibinder<ServletContextListener> multibinder = Multibinder.newSetBinder(binder(), ServletContextListener.class);
multibinder.addBinding().to(AppendingFourServletContextListener.class);
multibinder.addBinding().to(AppendingTwoServletContextListener.class);
}
}
public static class AppendingFourServletContextListener extends AppendingServletContextListener {
@Inject
public AppendingFourServletContextListener(Messenger messenger) {
super(messenger);
}
@Override
protected String getSign() {
return "4";
}
}
public static class AppendingTwoServletContextListener extends AppendingServletContextListener {
@Inject
public AppendingTwoServletContextListener(Messenger messenger) {
super(messenger);
}
@Override
protected String getSign() {
return "2";
}
}
public static abstract class AppendingServletContextListener implements ServletContextListener {
private final Messenger messenger;
@Inject
public AppendingServletContextListener(Messenger messenger) {
this.messenger = messenger;
}
@Override
public void contextInitialized(ServletContextEvent sce) {
send("+");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
send("-");
}
private void send(String prefix) {
messenger.append(prefix + getSign());
}
protected abstract String getSign();
}
@Singleton
public static class Messenger {
private String message;
void send(String message) {
this.message = message;
}
void append(String messageToAppend) {
send(Strings.nullToEmpty(message) + messageToAppend);
}
String receive() {
return message;
}
}
}

View File

@@ -0,0 +1,132 @@
package sonia.scm.lifecycle;
import com.github.legman.Subscribe;
import com.google.common.base.Charsets;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.Stage;
import sonia.scm.event.ScmEventBus;
import sonia.scm.event.ScmTestEventBus;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class RestartServletTest {
@Mock
private HttpServletRequest request;
@Mock
private HttpServletResponse response;
private RestartServlet restartServlet;
private EventListener listener;
private void setUpObjectUnderTest(Stage stage) {
listener = new EventListener();
ScmEventBus eventBus = ScmTestEventBus.getInstance();
eventBus.register(listener);
restartServlet = new RestartServlet(eventBus, stage);
}
@Test
public void testRestart() throws IOException {
setUpObjectUnderTest(Stage.DEVELOPMENT);
setRequestInputReason("something changed");
restartServlet.doPost(request, response);
verify(response).setStatus(HttpServletResponse.SC_ACCEPTED);
RestartEvent restartEvent = listener.restartEvent;
assertThat(restartEvent).isNotNull();
assertThat(restartEvent.getCause()).isEqualTo(RestartServlet.class);
assertThat(restartEvent.getReason()).isEqualTo("something changed");
}
@Test
public void testRestartCalledTwice() throws IOException {
setUpObjectUnderTest(Stage.DEVELOPMENT);
setRequestInputReason("initial change");
restartServlet.doPost(request, response);
verify(response).setStatus(HttpServletResponse.SC_ACCEPTED);
setRequestInputReason("changed again");
restartServlet.doPost(request, response);
verify(response).setStatus(HttpServletResponse.SC_CONFLICT);
}
@Test
public void testRestartWithInvalidContent() throws IOException {
setUpObjectUnderTest(Stage.DEVELOPMENT);
setRequestInputContent("invalid json");
restartServlet.doPost(request, response);
verify(response).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
@Test
public void testRestartInProductionStage() throws IOException {
setUpObjectUnderTest(Stage.PRODUCTION);
setRequestInputReason("initial change");
restartServlet.doPost(request, response);
verify(response).setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
}
private void setRequestInputReason(String message) throws IOException {
String content = createReason(message);
setRequestInputContent(content);
}
private void setRequestInputContent(String content) throws IOException {
InputStream input = createReasonAsInputStream(content);
when(request.getInputStream()).thenReturn(createServletInputStream(input));
}
private ServletInputStream createServletInputStream(final InputStream inputStream) {
return new ServletInputStream() {
@Override
public int read() throws IOException {
return inputStream.read();
}
};
}
private InputStream createReasonAsInputStream(String content) {
return new ByteArrayInputStream(content.getBytes(Charsets.UTF_8));
}
private String createReason(String message) {
return String.format("{\"message\": \"%s\"}", message);
}
public static class EventListener {
private RestartEvent restartEvent;
@Subscribe(async = false)
public void store(RestartEvent restartEvent) {
this.restartEvent = restartEvent;
}
}
}

View File

@@ -0,0 +1,52 @@
package sonia.scm.lifecycle;
import com.google.common.collect.ImmutableSet;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import javax.servlet.ServletContext;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Set;
import java.util.Vector;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class ServletContextCleanerTest {
@Mock
private ServletContext servletContext;
@Test
public void testCleanup() {
Set<String> names = ImmutableSet.of(
"org.jboss.resteasy.Dispatcher",
"resteasy.Deployment",
"sonia.scm.Context",
"org.eclipse.jetty.HttpServer",
"javax.servlet.Context",
"org.apache.shiro.SecurityManager"
);
when(servletContext.getAttributeNames()).thenReturn(toEnumeration(names));
ServletContextCleaner.cleanup(servletContext);
verify(servletContext).removeAttribute("org.jboss.resteasy.Dispatcher");
verify(servletContext).removeAttribute("resteasy.Deployment");
verify(servletContext).removeAttribute("sonia.scm.Context");
verify(servletContext, never()).removeAttribute("org.eclipse.jetty.HttpServer");
verify(servletContext, never()).removeAttribute("javax.servlet.Context");
verify(servletContext).removeAttribute("org.apache.shiro.SecurityManager");
}
private <T> Enumeration<T> toEnumeration(Collection<T> collection) {
return new Vector<>(collection).elements();
}
}

View File

@@ -0,0 +1,64 @@
package sonia.scm.lifecycle;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.servlet.GuiceFilter;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.Default;
import javax.inject.Inject;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class ServletContextModuleTest {
@Mock
private ServletContext servletContext;
private GuiceFilter guiceFilter;
@BeforeEach
void setUpEnvironment() throws ServletException {
guiceFilter = new GuiceFilter();
FilterConfig filterConfig = mock(FilterConfig.class);
when(filterConfig.getServletContext()).thenReturn(servletContext);
guiceFilter.init(filterConfig);
}
@AfterEach
void tearDownEnvironment() {
guiceFilter.destroy();
}
@Test
void shouldBeAbleToInjectServletContext() {
Injector injector = Guice.createInjector(new ServletContextModule());
WebComponent instance = injector.getInstance(WebComponent.class);
assertThat(instance.context).isSameAs(servletContext);
}
public static class WebComponent {
private ServletContext context;
@Inject
public WebComponent(@Default ServletContext context) {
this.context = context;
}
}
}

View File

@@ -0,0 +1,111 @@
package sonia.scm.lifecycle;
import com.google.common.collect.Lists;
import org.apache.shiro.authc.credential.PasswordService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import sonia.scm.security.PermissionAssigner;
import sonia.scm.security.PermissionDescriptor;
import sonia.scm.user.User;
import sonia.scm.user.UserManager;
import sonia.scm.user.UserTestData;
import sonia.scm.web.security.AdministrationContext;
import java.util.Collection;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class SetupContextListenerTest {
@Mock
private AdministrationContext administrationContext;
@InjectMocks
private SetupContextListener setupContextListener;
@Mock
private UserManager userManager;
@Mock
private PasswordService passwordService;
@Mock
private PermissionAssigner permissionAssigner;
@InjectMocks
private SetupContextListener.SetupAction setupAction;
@BeforeEach
void setupObjectUnderTest() {
doAnswer(ic -> {
setupAction.run();
return null;
}).when(administrationContext).runAsAdmin(SetupContextListener.SetupAction.class);
}
@Test
void shouldCreateAdminAccountAndAssignPermissions() {
when(passwordService.encryptPassword("scmadmin")).thenReturn("secret");
setupContextListener.contextInitialized(null);
verifyAdminCreated();
verifyAdminPermissionsAssigned();
}
@Test
@MockitoSettings(strictness = Strictness.LENIENT)
void shouldSkipAdminAccountCreationIfPropertyIsSet() {
System.setProperty("sonia.scm.skipAdminCreation", "true");
try {
setupContextListener.contextInitialized(null);
verify(userManager, never()).create(any());
verify(permissionAssigner, never()).setPermissionsForUser(anyString(), any(Collection.class));
} finally {
System.setProperty("sonia.scm.skipAdminCreation", "");
}
}
@Test
void shouldDoNothingOnSecondStart() {
List<User> users = Lists.newArrayList(UserTestData.createTrillian());
when(userManager.getAll()).thenReturn(users);
setupContextListener.contextInitialized(null);
verify(userManager, never()).create(any(User.class));
verify(permissionAssigner, never()).setPermissionsForUser(anyString(), any(Collection.class));
}
private void verifyAdminPermissionsAssigned() {
ArgumentCaptor<String> usernameCaptor = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<Collection<PermissionDescriptor>> permissionCaptor = ArgumentCaptor.forClass(Collection.class);
verify(permissionAssigner).setPermissionsForUser(usernameCaptor.capture(), permissionCaptor.capture());
String username = usernameCaptor.getValue();
assertThat(username).isEqualTo("scmadmin");
PermissionDescriptor descriptor = permissionCaptor.getValue().iterator().next();
assertThat(descriptor.getValue()).isEqualTo("*");
}
private void verifyAdminCreated() {
ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
verify(userManager).create(userCaptor.capture());
User user = userCaptor.getValue();
assertThat(user.getName()).isEqualTo("scmadmin");
assertThat(user.getPassword()).isEqualTo("secret");
}
}

View File

@@ -0,0 +1,89 @@
package sonia.scm.lifecycle;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.template.Template;
import sonia.scm.template.TemplateEngine;
import sonia.scm.template.TemplateEngineFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class SingleViewServletTest {
@Mock
private TemplateEngineFactory templateEngineFactory;
@Mock
private TemplateEngine templateEngine;
@Mock
private Template template;
@Mock
private HttpServletRequest request;
@Mock
private HttpServletResponse response;
@Mock
private PrintWriter writer;
@Mock
private ViewController controller;
@Test
void shouldRenderTheTemplateOnGet() throws IOException {
prepareTemplate("/template");
doReturn(new View(200, "hello")).when(controller).createView(request);
new SingleViewServlet(templateEngineFactory, controller).doGet(request, response);
verifyResponse(200, "hello");
}
private void verifyResponse(int sc, Object model) throws IOException {
verify(response).setStatus(sc);
verify(response).setContentType("text/html");
verify(response).setCharacterEncoding("UTF-8");
verify(template).execute(writer, model);
}
@Test
void shouldRenderTheTemplateOnPost() throws IOException {
prepareTemplate("/template");
doReturn(new View(201, "hello")).when(controller).createView(request);
new SingleViewServlet(templateEngineFactory, controller).doPost(request, response);
verifyResponse(201, "hello");
}
@Test
void shouldThrowIllegalStateExceptionOnIOException() throws IOException {
doReturn("/template").when(controller).getTemplate();
doReturn(templateEngine).when(templateEngineFactory).getEngineByExtension("/template");
doThrow(IOException.class).when(templateEngine).getTemplate("/template");
assertThrows(IllegalStateException.class, () -> new SingleViewServlet(templateEngineFactory, controller));
}
private void prepareTemplate(String templatePath) throws IOException {
doReturn(templateEngine).when(templateEngineFactory).getEngineByExtension(templatePath);
doReturn(template).when(templateEngine).getTemplate(templatePath);
doReturn(templatePath).when(controller).getTemplate();
doReturn(writer).when(response).getWriter();
}
}

View File

@@ -0,0 +1,96 @@
package sonia.scm.lifecycle;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.servlet.GuiceFilter;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class SingleViewTest {
@Mock
private HttpServletRequest request;
private GuiceFilter guiceFilter;
@BeforeEach
void setUpGuiceFilter() throws ServletException {
guiceFilter = new GuiceFilter();
ServletContext servletContext = mock(ServletContext.class);
FilterConfig config = mock(FilterConfig.class);
doReturn(servletContext).when(config).getServletContext();
guiceFilter.init(config);
}
@AfterEach
void tearDownGuiceFilter() {
guiceFilter.destroy();
}
@Test
void shouldCreateViewControllerForView() {
ModuleProvider moduleProvider = SingleView.view("/my-template", 409);
when(request.getContextPath()).thenReturn("/scm");
ViewController instance = findViewController(moduleProvider);
assertThat(instance.getTemplate()).isEqualTo("/my-template");
View view = instance.createView(request);
assertThat(view.getStatusCode()).isEqualTo(409);
}
@Test
void shouldCreateViewControllerForError() {
ModuleProvider moduleProvider = SingleView.error(new IOException("awesome io"));
when(request.getContextPath()).thenReturn("/scm");
ViewController instance = findViewController(moduleProvider);
assertErrorViewController(instance, "awesome io");
}
@Test
void shouldBindServlets() {
ModuleProvider moduleProvider = SingleView.error(new IOException("awesome io"));
Injector injector = Guice.createInjector(moduleProvider.createModules());
assertThat(injector.getInstance(StaticResourceServlet.class)).isNotNull();
assertThat(injector.getInstance(SingleViewServlet.class)).isNotNull();
}
@SuppressWarnings("unchecked")
private void assertErrorViewController(ViewController instance, String contains) {
assertThat(instance.getTemplate()).isEqualTo("/templates/error.mustache");
View view = instance.createView(request);
assertThat(view.getStatusCode()).isEqualTo(500);
assertThat(view.getModel()).isInstanceOfSatisfying(Map.class, map -> {
assertThat(map).containsEntry("contextPath", "/scm");
String error = (String) map.get("error");
assertThat(error).contains(contains);
}
);
}
private ViewController findViewController(ModuleProvider moduleProvider) {
Injector injector = Guice.createInjector(moduleProvider.createModules());
return injector.getInstance(ViewController.class);
}
}

View File

@@ -0,0 +1,61 @@
package sonia.scm.lifecycle;
import com.google.common.io.Resources;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import javax.servlet.ServletContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URL;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
class StaticResourceServletTest {
@Mock
private HttpServletRequest request;
@Mock
private ServletOutputStream stream;
@Mock
private HttpServletResponse response;
@Mock
private ServletContext context;
@Test
void shouldServeResource() throws IOException {
doReturn("/scm").when(request).getContextPath();
doReturn("/scm/resource.txt").when(request).getRequestURI();
doReturn(context).when(request).getServletContext();
URL resource = Resources.getResource("sonia/scm/boot/resource.txt");
doReturn(resource).when(context).getResource("/resource.txt");
doReturn(stream).when(response).getOutputStream();
StaticResourceServlet servlet = new StaticResourceServlet();
servlet.doGet(request, response);
verify(response).setStatus(HttpServletResponse.SC_OK);
}
@Test
void shouldReturnNotFound() {
doReturn("/scm").when(request).getContextPath();
doReturn("/scm/resource.txt").when(request).getRequestURI();
doReturn(context).when(request).getServletContext();
StaticResourceServlet servlet = new StaticResourceServlet();
servlet.doGet(request, response);
verify(response).setStatus(HttpServletResponse.SC_NOT_FOUND);
}
}

View File

@@ -0,0 +1,86 @@
package sonia.scm.lifecycle;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junitpioneer.jupiter.TempDirectory;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.SCMContextProvider;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.doReturn;
@ExtendWith({MockitoExtension.class, TempDirectory.class})
class VersionsTest {
@Mock
private SCMContextProvider contextProvider;
@InjectMocks
private Versions versions;
@Test
void shouldReturnTrueForVersionsPreviousTo160(@TempDirectory.TempDir Path directory) throws IOException {
setVersion(directory, "1.59");
assertThat(versions.isPreviousVersionTooOld()).isTrue();
setVersion(directory, "1.12");
assertThat(versions.isPreviousVersionTooOld()).isTrue();
}
@Test
void shouldReturnFalseForVersion160(@TempDirectory.TempDir Path directory) throws IOException {
setVersion(directory, "1.60");
assertThat(versions.isPreviousVersionTooOld()).isFalse();
}
@Test
void shouldNotFailIfVersionContainsLineBreak(@TempDirectory.TempDir Path directory) throws IOException {
setVersion(directory, "1.59\n");
assertThat(versions.isPreviousVersionTooOld()).isTrue();
}
@Test
void shouldReturnFalseForVersionsNewerAs160(@TempDirectory.TempDir Path directory) throws IOException {
setVersion(directory, "1.61");
assertThat(versions.isPreviousVersionTooOld()).isFalse();
setVersion(directory, "1.82");
assertThat(versions.isPreviousVersionTooOld()).isFalse();
}
@Test
void shouldReturnFalseForNonExistingVersionFile(@TempDirectory.TempDir Path directory) {
setVersionFile(directory.resolve("version.txt"));
assertThat(versions.isPreviousVersionTooOld()).isFalse();
}
@Test
void shouldWriteNewVersion(@TempDirectory.TempDir Path directory) {
Path config = directory.resolve("config");
doReturn(config).when(contextProvider).resolve(Paths.get("config"));
doReturn("2.0.0").when(contextProvider).getVersion();
versions.writeNewVersion();
Path versionFile = config.resolve("version.txt");
assertThat(versionFile).exists().hasContent("2.0.0");
}
private void setVersion(Path directory, String version) throws IOException {
Path file = directory.resolve("version.txt");
Files.write(file, version.getBytes(StandardCharsets.UTF_8));
setVersionFile(file);
}
private void setVersionFile(Path file) {
doReturn(file).when(contextProvider).resolve(Paths.get("config", "version.txt"));
}
}