Files
SCM-Manager/scm-webapp/src/test/java/sonia/scm/plugin/DefaultPluginManagerTest.java

538 lines
19 KiB
Java
Raw Normal View History

2019-08-20 12:29:59 +02:00
package sonia.scm.plugin;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
2019-08-21 09:25:44 +02:00
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
2019-08-20 12:29:59 +02:00
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
2019-09-16 13:22:26 +02:00
import org.junitpioneer.jupiter.TempDirectory;
2019-08-21 12:49:15 +02:00
import org.mockito.ArgumentCaptor;
2019-08-20 12:29:59 +02:00
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
2019-08-21 09:25:44 +02:00
import sonia.scm.NotFoundException;
2019-09-16 13:22:26 +02:00
import sonia.scm.ScmConstraintViolationException;
2019-08-21 11:22:49 +02:00
import sonia.scm.event.ScmEventBus;
import sonia.scm.lifecycle.RestartEvent;
2019-08-20 12:29:59 +02:00
import java.io.IOException;
import java.nio.file.Files;
2019-09-16 13:22:26 +02:00
import java.nio.file.Path;
2019-08-20 12:29:59 +02:00
import java.util.List;
import java.util.Optional;
2019-09-16 17:50:05 +02:00
import static java.util.Arrays.asList;
2019-09-16 13:22:26 +02:00
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
2019-08-20 12:29:59 +02:00
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
2019-09-16 13:22:26 +02:00
import static org.mockito.Mockito.any;
2019-09-27 11:40:06 +02:00
import static org.mockito.Mockito.doNothing;
2019-09-16 13:22:26 +02:00
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static sonia.scm.plugin.PluginTestHelper.createAvailable;
import static sonia.scm.plugin.PluginTestHelper.createInstalled;
2019-08-20 12:29:59 +02:00
@ExtendWith(MockitoExtension.class)
2019-09-16 13:22:26 +02:00
@ExtendWith(TempDirectory.class)
2019-08-20 12:29:59 +02:00
class DefaultPluginManagerTest {
2019-08-21 11:22:49 +02:00
@Mock
private ScmEventBus eventBus;
2019-08-20 12:29:59 +02:00
@Mock
private PluginLoader loader;
@Mock
private PluginCenter center;
2019-08-20 14:43:48 +02:00
@Mock
private PluginInstaller installer;
2019-08-20 12:29:59 +02:00
@InjectMocks
private DefaultPluginManager manager;
2019-08-21 09:25:44 +02:00
@Mock
private Subject subject;
2019-08-20 12:29:59 +02:00
2019-08-21 12:49:15 +02:00
@BeforeEach
void mockInstaller() {
lenient().when(installer.install(any())).then(ic -> {
AvailablePlugin plugin = ic.getArgument(0);
return new PendingPluginInstallation(plugin.install(), null);
});
}
2019-08-21 09:25:44 +02:00
@Nested
class WithAdminPermissions {
2019-08-20 12:29:59 +02:00
2019-08-21 09:25:44 +02:00
@BeforeEach
void setUpSubject() {
ThreadContext.bind(subject);
}
2019-08-20 12:29:59 +02:00
2019-08-21 09:25:44 +02:00
@AfterEach
void clearThreadContext() {
ThreadContext.unbindSubject();
}
2019-08-20 12:29:59 +02:00
2019-08-21 09:25:44 +02:00
@Test
void shouldReturnInstalledPlugins() {
InstalledPlugin review = createInstalled("scm-review-plugin");
InstalledPlugin git = createInstalled("scm-git-plugin");
2019-08-20 12:29:59 +02:00
2019-08-21 09:25:44 +02:00
when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(review, git));
2019-08-20 12:29:59 +02:00
2019-08-21 09:25:44 +02:00
List<InstalledPlugin> installed = manager.getInstalled();
assertThat(installed).containsOnly(review, git);
}
2019-08-20 12:29:59 +02:00
2019-08-21 09:25:44 +02:00
@Test
void shouldReturnReviewPlugin() {
InstalledPlugin review = createInstalled("scm-review-plugin");
InstalledPlugin git = createInstalled("scm-git-plugin");
2019-08-20 12:29:59 +02:00
2019-08-21 09:25:44 +02:00
when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(review, git));
2019-08-20 12:29:59 +02:00
2019-08-21 09:25:44 +02:00
Optional<InstalledPlugin> plugin = manager.getInstalled("scm-review-plugin");
assertThat(plugin).contains(review);
}
2019-08-20 12:29:59 +02:00
2019-08-21 09:25:44 +02:00
@Test
void shouldReturnEmptyForNonInstalledPlugin() {
when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of());
2019-08-20 12:29:59 +02:00
2019-08-21 09:25:44 +02:00
Optional<InstalledPlugin> plugin = manager.getInstalled("scm-review-plugin");
assertThat(plugin).isEmpty();
}
@Test
void shouldReturnAvailablePlugins() {
AvailablePlugin review = createAvailable("scm-review-plugin");
AvailablePlugin git = createAvailable("scm-git-plugin");
2019-08-20 12:29:59 +02:00
2019-08-21 09:25:44 +02:00
when(center.getAvailable()).thenReturn(ImmutableSet.of(review, git));
List<AvailablePlugin> available = manager.getAvailable();
assertThat(available).containsOnly(review, git);
}
2019-08-20 12:29:59 +02:00
2019-08-21 09:25:44 +02:00
@Test
void shouldFilterOutAllInstalled() {
InstalledPlugin installedGit = createInstalled("scm-git-plugin");
when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(installedGit));
2019-08-20 12:29:59 +02:00
2019-08-21 09:25:44 +02:00
AvailablePlugin review = createAvailable("scm-review-plugin");
AvailablePlugin git = createAvailable("scm-git-plugin");
when(center.getAvailable()).thenReturn(ImmutableSet.of(review, git));
2019-08-20 12:29:59 +02:00
2019-08-21 09:25:44 +02:00
List<AvailablePlugin> available = manager.getAvailable();
assertThat(available).containsOnly(review);
}
2019-08-20 12:29:59 +02:00
2019-08-21 09:25:44 +02:00
@Test
void shouldReturnAvailable() {
AvailablePlugin review = createAvailable("scm-review-plugin");
AvailablePlugin git = createAvailable("scm-git-plugin");
when(center.getAvailable()).thenReturn(ImmutableSet.of(review, git));
2019-08-20 12:29:59 +02:00
2019-08-21 09:25:44 +02:00
Optional<AvailablePlugin> available = manager.getAvailable("scm-git-plugin");
assertThat(available).contains(git);
}
2019-08-20 12:29:59 +02:00
2019-08-21 09:25:44 +02:00
@Test
void shouldReturnEmptyForNonExistingAvailable() {
AvailablePlugin review = createAvailable("scm-review-plugin");
when(center.getAvailable()).thenReturn(ImmutableSet.of(review));
2019-08-20 12:29:59 +02:00
2019-08-21 09:25:44 +02:00
Optional<AvailablePlugin> available = manager.getAvailable("scm-git-plugin");
assertThat(available).isEmpty();
}
2019-08-20 12:29:59 +02:00
2019-08-21 09:25:44 +02:00
@Test
void shouldReturnEmptyForInstalledPlugin() {
InstalledPlugin installedGit = createInstalled("scm-git-plugin");
when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(installedGit));
2019-08-20 12:29:59 +02:00
2019-08-21 09:25:44 +02:00
AvailablePlugin git = createAvailable("scm-git-plugin");
when(center.getAvailable()).thenReturn(ImmutableSet.of(git));
2019-08-20 14:43:48 +02:00
2019-08-21 09:25:44 +02:00
Optional<AvailablePlugin> available = manager.getAvailable("scm-git-plugin");
assertThat(available).isEmpty();
}
2019-08-20 14:43:48 +02:00
2019-08-21 09:25:44 +02:00
@Test
void shouldInstallThePlugin() {
AvailablePlugin git = createAvailable("scm-git-plugin");
when(center.getAvailable()).thenReturn(ImmutableSet.of(git));
2019-08-20 14:43:48 +02:00
2019-08-21 11:22:49 +02:00
manager.install("scm-git-plugin", false);
2019-08-20 14:43:48 +02:00
2019-08-21 09:25:44 +02:00
verify(installer).install(git);
2019-08-21 11:22:49 +02:00
verify(eventBus, never()).post(any());
2019-08-21 09:25:44 +02:00
}
2019-08-20 14:43:48 +02:00
2019-08-21 09:25:44 +02:00
@Test
void shouldInstallDependingPlugins() {
AvailablePlugin review = createAvailable("scm-review-plugin");
when(review.getDescriptor().getDependencies()).thenReturn(ImmutableSet.of("scm-mail-plugin"));
AvailablePlugin mail = createAvailable("scm-mail-plugin");
when(center.getAvailable()).thenReturn(ImmutableSet.of(review, mail));
2019-08-21 11:22:49 +02:00
manager.install("scm-review-plugin", false);
2019-08-21 09:25:44 +02:00
verify(installer).install(mail);
verify(installer).install(review);
}
@Test
void shouldNotInstallAlreadyInstalledDependencies() {
AvailablePlugin review = createAvailable("scm-review-plugin");
when(review.getDescriptor().getDependencies()).thenReturn(ImmutableSet.of("scm-mail-plugin"));
AvailablePlugin mail = createAvailable("scm-mail-plugin");
when(center.getAvailable()).thenReturn(ImmutableSet.of(review, mail));
2019-08-20 14:43:48 +02:00
2019-08-21 09:25:44 +02:00
InstalledPlugin installedMail = createInstalled("scm-mail-plugin");
when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(installedMail));
2019-08-20 14:43:48 +02:00
2019-08-21 11:22:49 +02:00
manager.install("scm-review-plugin", false);
2019-08-20 14:43:48 +02:00
2019-08-21 12:49:15 +02:00
ArgumentCaptor<AvailablePlugin> captor = ArgumentCaptor.forClass(AvailablePlugin.class);
verify(installer).install(captor.capture());
assertThat(captor.getValue().getDescriptor().getInformation().getName()).isEqualTo("scm-review-plugin");
2019-08-21 09:25:44 +02:00
}
@Test
void shouldRollbackOnFailedInstallation() {
AvailablePlugin review = createAvailable("scm-review-plugin");
when(review.getDescriptor().getDependencies()).thenReturn(ImmutableSet.of("scm-mail-plugin"));
AvailablePlugin mail = createAvailable("scm-mail-plugin");
when(mail.getDescriptor().getDependencies()).thenReturn(ImmutableSet.of("scm-notification-plugin"));
AvailablePlugin notification = createAvailable("scm-notification-plugin");
when(center.getAvailable()).thenReturn(ImmutableSet.of(review, mail, notification));
PendingPluginInstallation pendingNotification = mock(PendingPluginInstallation.class);
doReturn(pendingNotification).when(installer).install(notification);
PendingPluginInstallation pendingMail = mock(PendingPluginInstallation.class);
doReturn(pendingMail).when(installer).install(mail);
doThrow(new PluginChecksumMismatchException("checksum does not match")).when(installer).install(review);
2019-08-21 11:22:49 +02:00
assertThrows(PluginInstallException.class, () -> manager.install("scm-review-plugin", false));
2019-08-21 09:25:44 +02:00
verify(pendingNotification).cancel();
verify(pendingMail).cancel();
}
@Test
void shouldInstallNothingIfOneOfTheDependenciesIsNotAvailable() {
AvailablePlugin review = createAvailable("scm-review-plugin");
when(review.getDescriptor().getDependencies()).thenReturn(ImmutableSet.of("scm-mail-plugin"));
AvailablePlugin mail = createAvailable("scm-mail-plugin");
when(mail.getDescriptor().getDependencies()).thenReturn(ImmutableSet.of("scm-notification-plugin"));
when(center.getAvailable()).thenReturn(ImmutableSet.of(review, mail));
2019-08-21 11:22:49 +02:00
assertThrows(NotFoundException.class, () -> manager.install("scm-review-plugin", false));
2019-08-21 09:25:44 +02:00
verify(installer, never()).install(any());
}
2019-08-20 14:43:48 +02:00
2019-08-21 11:22:49 +02:00
@Test
void shouldSendRestartEventAfterInstallation() {
AvailablePlugin git = createAvailable("scm-git-plugin");
when(center.getAvailable()).thenReturn(ImmutableSet.of(git));
manager.install("scm-git-plugin", true);
verify(installer).install(git);
verify(eventBus).post(any(RestartEvent.class));
}
2019-08-21 12:49:15 +02:00
@Test
void shouldNotSendRestartEventIfNoPluginWasInstalled() {
InstalledPlugin gitInstalled = createInstalled("scm-git-plugin");
when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(gitInstalled));
manager.install("scm-git-plugin", true);
verify(eventBus, never()).post(any());
}
@Test
void shouldNotInstallAlreadyPendingPlugins() {
AvailablePlugin review = createAvailable("scm-review-plugin");
when(center.getAvailable()).thenReturn(ImmutableSet.of(review));
manager.install("scm-review-plugin", false);
manager.install("scm-review-plugin", false);
// only one interaction
verify(installer).install(any());
}
@Test
void shouldSendRestartEvent() {
AvailablePlugin review = createAvailable("scm-review-plugin");
when(center.getAvailable()).thenReturn(ImmutableSet.of(review));
manager.install("scm-review-plugin", false);
manager.executePendingAndRestart();
2019-08-21 12:49:15 +02:00
verify(eventBus).post(any(RestartEvent.class));
}
@Test
void shouldNotSendRestartEventWithoutPendingPlugins() {
manager.executePendingAndRestart();
2019-08-21 12:49:15 +02:00
verify(eventBus, never()).post(any());
}
@Test
void shouldReturnSingleAvailableAsPending() {
AvailablePlugin review = createAvailable("scm-review-plugin");
when(center.getAvailable()).thenReturn(ImmutableSet.of(review));
manager.install("scm-review-plugin", false);
Optional<AvailablePlugin> available = manager.getAvailable("scm-review-plugin");
assertThat(available.get().isPending()).isTrue();
}
@Test
void shouldReturnAvailableAsPending() {
AvailablePlugin review = createAvailable("scm-review-plugin");
when(center.getAvailable()).thenReturn(ImmutableSet.of(review));
manager.install("scm-review-plugin", false);
List<AvailablePlugin> available = manager.getAvailable();
assertThat(available.get(0).isPending()).isTrue();
}
2019-09-16 13:22:26 +02:00
@Test
void shouldThrowExceptionWhenUninstallingUnknownPlugin() {
assertThrows(NotFoundException.class, () -> manager.uninstall("no-such-plugin", false));
}
@Test
void shouldUseDependencyTrackerForUninstall() {
InstalledPlugin mailPlugin = createInstalled("scm-mail-plugin");
InstalledPlugin reviewPlugin = createInstalled("scm-review-plugin");
when(reviewPlugin.getDescriptor().getDependencies()).thenReturn(singleton("scm-mail-plugin"));
when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(mailPlugin, reviewPlugin));
2019-09-16 17:50:05 +02:00
manager.computeInstallationDependencies();
2019-09-16 13:22:26 +02:00
assertThrows(ScmConstraintViolationException.class, () -> manager.uninstall("scm-mail-plugin", false));
}
@Test
void shouldCreateUninstallFile(@TempDirectory.TempDir Path temp) {
InstalledPlugin mailPlugin = createInstalled("scm-mail-plugin");
when(mailPlugin.getDirectory()).thenReturn(temp);
when(loader.getInstalledPlugins()).thenReturn(singletonList(mailPlugin));
manager.uninstall("scm-mail-plugin", false);
assertThat(temp.resolve("uninstall")).exists();
}
@Test
void shouldMarkPluginForUninstall(@TempDirectory.TempDir Path temp) {
InstalledPlugin mailPlugin = createInstalled("scm-mail-plugin");
when(mailPlugin.getDirectory()).thenReturn(temp);
when(loader.getInstalledPlugins()).thenReturn(singletonList(mailPlugin));
manager.uninstall("scm-mail-plugin", false);
verify(mailPlugin).setMarkedForUninstall(true);
}
@Test
void shouldNotChangeStateWhenUninstallFileCouldNotBeCreated() {
InstalledPlugin mailPlugin = createInstalled("scm-mail-plugin");
InstalledPlugin reviewPlugin = createInstalled("scm-review-plugin");
when(reviewPlugin.getDescriptor().getDependencies()).thenReturn(singleton("scm-mail-plugin"));
when(reviewPlugin.getDirectory()).thenThrow(new PluginException("when the file could not be written an exception like this is thrown"));
when(loader.getInstalledPlugins()).thenReturn(asList(mailPlugin, reviewPlugin));
manager.computeInstallationDependencies();
assertThrows(PluginException.class, () -> manager.uninstall("scm-review-plugin", false));
verify(mailPlugin, never()).setMarkedForUninstall(true);
assertThrows(ScmConstraintViolationException.class, () -> manager.uninstall("scm-mail-plugin", false));
}
@Test
void shouldThrowExceptionWhenUninstallingCorePlugin(@TempDirectory.TempDir Path temp) {
InstalledPlugin mailPlugin = createInstalled("scm-mail-plugin");
when(mailPlugin.getDirectory()).thenReturn(temp);
when(mailPlugin.isCore()).thenReturn(true);
when(loader.getInstalledPlugins()).thenReturn(singletonList(mailPlugin));
assertThrows(ScmConstraintViolationException.class, () -> manager.uninstall("scm-mail-plugin", false));
assertThat(temp.resolve("uninstall")).doesNotExist();
}
2019-09-16 17:50:05 +02:00
@Test
void shouldMarkUninstallablePlugins() {
InstalledPlugin mailPlugin = createInstalled("scm-mail-plugin");
InstalledPlugin reviewPlugin = createInstalled("scm-review-plugin");
when(reviewPlugin.getDescriptor().getDependencies()).thenReturn(singleton("scm-mail-plugin"));
when(loader.getInstalledPlugins()).thenReturn(asList(mailPlugin, reviewPlugin));
manager.computeInstallationDependencies();
verify(reviewPlugin).setUninstallable(true);
verify(mailPlugin).setUninstallable(false);
}
@Test
void shouldUpdateMayUninstallFlagAfterDependencyIsUninstalled() {
InstalledPlugin mailPlugin = createInstalled("scm-mail-plugin");
InstalledPlugin reviewPlugin = createInstalled("scm-review-plugin");
when(reviewPlugin.getDescriptor().getDependencies()).thenReturn(singleton("scm-mail-plugin"));
when(loader.getInstalledPlugins()).thenReturn(asList(mailPlugin, reviewPlugin));
manager.computeInstallationDependencies();
manager.uninstall("scm-review-plugin", false);
verify(mailPlugin).setUninstallable(true);
}
@Test
void shouldUpdateMayUninstallFlagAfterDependencyIsInstalled() {
InstalledPlugin mailPlugin = createInstalled("scm-mail-plugin");
AvailablePlugin reviewPlugin = createAvailable("scm-review-plugin");
when(reviewPlugin.getDescriptor().getDependencies()).thenReturn(singleton("scm-mail-plugin"));
when(loader.getInstalledPlugins()).thenReturn(singletonList(mailPlugin));
when(center.getAvailable()).thenReturn(singleton(reviewPlugin));
manager.computeInstallationDependencies();
manager.install("scm-review-plugin", false);
verify(mailPlugin).setUninstallable(false);
}
@Test
void shouldRestartWithUninstallOnly() {
InstalledPlugin mailPlugin = createInstalled("scm-mail-plugin");
when(mailPlugin.isMarkedForUninstall()).thenReturn(true);
when(loader.getInstalledPlugins()).thenReturn(singletonList(mailPlugin));
manager.executePendingAndRestart();
verify(eventBus).post(any(RestartEvent.class));
}
@Test
void shouldUndoPendingInstallations(@TempDirectory.TempDir Path temp) throws IOException {
InstalledPlugin mailPlugin = createInstalled("scm-ssh-plugin");
Path mailPluginPath = temp.resolve("scm-mail-plugin");
Files.createDirectories(mailPluginPath);
when(mailPlugin.getDirectory()).thenReturn(mailPluginPath);
when(loader.getInstalledPlugins()).thenReturn(singletonList(mailPlugin));
2019-09-27 11:40:06 +02:00
ArgumentCaptor<Boolean> uninstallCaptor = ArgumentCaptor.forClass(Boolean.class);
doNothing().when(mailPlugin).setMarkedForUninstall(uninstallCaptor.capture());
AvailablePlugin git = createAvailable("scm-git-plugin");
when(center.getAvailable()).thenReturn(ImmutableSet.of(git));
PendingPluginInstallation gitPendingPluginInformation = mock(PendingPluginInstallation.class);
when(installer.install(git)).thenReturn(gitPendingPluginInformation);
manager.install("scm-git-plugin", false);
manager.uninstall("scm-ssh-plugin", false);
2019-09-27 11:46:14 +02:00
manager.cancelPending();
assertThat(mailPluginPath.resolve("uninstall")).doesNotExist();
verify(gitPendingPluginInformation).cancel();
2019-09-27 11:40:06 +02:00
Boolean lasUninstallMarkerSet = uninstallCaptor.getAllValues().get(uninstallCaptor.getAllValues().size() - 1);
assertThat(lasUninstallMarkerSet).isFalse();
}
2019-08-20 14:43:48 +02:00
}
2019-08-21 09:25:44 +02:00
@Nested
class WithoutReadPermissions {
@BeforeEach
void setUpSubject() {
ThreadContext.bind(subject);
doThrow(AuthorizationException.class).when(subject).checkPermission("plugin:read");
}
@AfterEach
void clearThreadContext() {
ThreadContext.unbindSubject();
}
@Test
void shouldThrowAuthorizationExceptionsForReadMethods() {
assertThrows(AuthorizationException.class, () -> manager.getInstalled());
assertThrows(AuthorizationException.class, () -> manager.getInstalled("test"));
assertThrows(AuthorizationException.class, () -> manager.getAvailable());
assertThrows(AuthorizationException.class, () -> manager.getAvailable("test"));
}
}
2019-08-21 09:25:44 +02:00
@Nested
class WithoutManagePermissions {
2019-08-21 09:25:44 +02:00
@BeforeEach
void setUpSubject() {
ThreadContext.bind(subject);
doThrow(AuthorizationException.class).when(subject).checkPermission("plugin:manage");
}
2019-08-21 09:25:44 +02:00
@AfterEach
void clearThreadContext() {
ThreadContext.unbindSubject();
}
2019-08-21 09:25:44 +02:00
@Test
void shouldThrowAuthorizationExceptionsForInstallMethod() {
2019-08-21 11:22:49 +02:00
assertThrows(AuthorizationException.class, () -> manager.install("test", false));
2019-08-21 09:25:44 +02:00
}
2019-09-16 13:22:26 +02:00
@Test
void shouldThrowAuthorizationExceptionsForUninstallMethod() {
assertThrows(AuthorizationException.class, () -> manager.uninstall("test", false));
}
2019-08-21 12:49:15 +02:00
@Test
void shouldThrowAuthorizationExceptionsForExecutePendingAndRestart() {
assertThrows(AuthorizationException.class, () -> manager.executePendingAndRestart());
2019-08-21 12:49:15 +02:00
}
2019-09-27 11:46:14 +02:00
@Test
void shouldThrowAuthorizationExceptionsForCancelPending() {
assertThrows(AuthorizationException.class, () -> manager.cancelPending());
}
}
2019-08-20 12:29:59 +02:00
}