You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ol...@apache.org on 2020/08/30 20:05:29 UTC
[sling-org-apache-sling-clam] 03/07: SLING-9710 Provide a Mail
Sending Scan Result Handler
This is an automated email from the ASF dual-hosted git repository.
olli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-clam.git
commit 96701940c35f6a4270ee3b6119f72a04155ed132
Author: Oliver Lietz <ol...@apache.org>
AuthorDate: Sun Aug 30 21:52:07 2020 +0200
SLING-9710 Provide a Mail Sending Scan Result Handler
---
bnd.bnd | 13 ++
pom.xml | 45 ++++-
.../internal/MailSendingScanResultHandler.java | 196 +++++++++++++++++++++
.../MailSendingScanResultHandlerConfiguration.java | 105 +++++++++++
.../it/tests/MailSendingScanResultHandlerIT.java | 174 ++++++++++++++++++
src/test/resources/password | 1 +
6 files changed, 533 insertions(+), 1 deletion(-)
diff --git a/bnd.bnd b/bnd.bnd
index e36a0d6..aecb1cd 100644
--- a/bnd.bnd
+++ b/bnd.bnd
@@ -1,3 +1,16 @@
+DynamicImport-Package:\
+ javax.mail.*,\
+ org.apache.commons.lang3.*,\
+ org.apache.sling.commons.messaging.*,\
+ org.thymeleaf.*
+
+Import-Package:\
+ javax.mail.*;resolution:=optional,\
+ org.apache.commons.lang3.*;resolution:=optional,\
+ org.apache.sling.commons.messaging.*;resolution:=optional,\
+ org.thymeleaf.*;resolution:=optional,\
+ *
+
-removeheaders:\
Include-Resource,\
Private-Package
diff --git a/pom.xml b/pom.xml
index 52c552a..eb13797 100644
--- a/pom.xml
+++ b/pom.xml
@@ -136,6 +136,12 @@
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>jakarta.mail</groupId>
+ <artifactId>jakarta.mail-api</artifactId>
+ <version>1.6.5</version>
+ <scope>provided</scope>
+ </dependency>
<!-- ok io/http -->
<dependency>
<groupId>org.apache.servicemix.bundles</groupId>
@@ -188,6 +194,18 @@
<version>2.5</version>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-email</artifactId>
+ <version>1.5</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <version>3.9</version>
+ <scope>provided</scope>
+ </dependency>
<!-- Apache Felix -->
<dependency>
<groupId>org.apache.felix</groupId>
@@ -241,6 +259,18 @@
</dependency>
<dependency>
<groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.messaging</artifactId>
+ <version>1.0.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.messaging.mail</artifactId>
+ <version>1.0.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.commons.threads</artifactId>
<version>3.2.18</version>
<scope>provided</scope>
@@ -266,9 +296,16 @@
<dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.testing.paxexam</artifactId>
- <version>3.1.0</version>
+ <version>3.1.1-SNAPSHOT</version>
<scope>test</scope>
</dependency>
+ <!-- Thymeleaf -->
+ <dependency>
+ <groupId>org.thymeleaf</groupId>
+ <artifactId>thymeleaf</artifactId>
+ <version>3.0.11.RELEASE</version>
+ <scope>provided</scope>
+ </dependency>
<!-- nullability -->
<dependency>
<groupId>org.jetbrains</groupId>
@@ -306,6 +343,12 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>com.icegreen</groupId>
+ <artifactId>greenmail</artifactId>
+ <version>1.5.11</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam</artifactId>
<version>${org.ops4j.pax.exam.version}</version>
diff --git a/src/main/java/org/apache/sling/clam/result/internal/MailSendingScanResultHandler.java b/src/main/java/org/apache/sling/clam/result/internal/MailSendingScanResultHandler.java
new file mode 100644
index 0000000..e9070cb
--- /dev/null
+++ b/src/main/java/org/apache/sling/clam/result/internal/MailSendingScanResultHandler.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.clam.result.internal;
+
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+
+import javax.mail.internet.MimeMessage;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.clam.result.JcrPropertyScanResultHandler;
+import org.apache.sling.commons.clam.ScanResult;
+import org.apache.sling.commons.messaging.mail.MailService;
+import org.apache.sling.commons.messaging.mail.MessageBuilder;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.osgi.framework.Constants;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
+import org.osgi.service.metatype.annotations.Designate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.thymeleaf.ITemplateEngine;
+import org.thymeleaf.TemplateEngine;
+import org.thymeleaf.TemplateSpec;
+import org.thymeleaf.context.Context;
+import org.thymeleaf.context.IContext;
+import org.thymeleaf.templatemode.TemplateMode;
+
+import static org.apache.sling.clam.internal.ClamUtil.properties;
+
+@Component(
+ configurationPolicy = ConfigurationPolicy.REQUIRE,
+ property = {
+ Constants.SERVICE_DESCRIPTION + "=Apache Sling Clam Mail Sending Scan Result Handler",
+ Constants.SERVICE_VENDOR + "=The Apache Software Foundation"
+ }
+)
+@Designate(
+ ocd = MailSendingScanResultHandlerConfiguration.class,
+ factory = true
+)
+public class MailSendingScanResultHandler implements JcrPropertyScanResultHandler {
+
+ @Reference(
+ policy = ReferencePolicy.DYNAMIC,
+ policyOption = ReferencePolicyOption.GREEDY
+ )
+ private volatile MailService mailService;
+
+ private MailSendingScanResultHandlerConfiguration configuration;
+
+ private final ITemplateEngine templateEngine = new TemplateEngine();
+
+ private final Logger logger = LoggerFactory.getLogger(MailSendingScanResultHandler.class);
+
+ public MailSendingScanResultHandler() {
+ }
+
+ @Activate
+ private void activate(final MailSendingScanResultHandlerConfiguration configuration) {
+ logger.debug("activating");
+ this.configuration = configuration;
+ }
+
+ @Modified
+ private void modified(final MailSendingScanResultHandlerConfiguration configuration) {
+ logger.debug("modifying");
+ this.configuration = configuration;
+ }
+
+ @Deactivate
+ private void deactivate() {
+ logger.debug("deactivating");
+ this.configuration = null;
+ }
+
+ @Override
+ public void handleJcrPropertyScanResult(@NotNull ScanResult scanResult, @NotNull String path, int propertyType, @Nullable String userId) {
+ if (checkPublish(scanResult)) {
+ final Map<String, Object> properties = properties(path, userId, scanResult);
+ try {
+ sendMail(properties);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ @Override
+ public void handleJcrPropertyScanResult(@NotNull ScanResult scanResult, @NotNull String path, int index, int propertyType, @Nullable String userId) {
+ if (checkPublish(scanResult)) {
+ final Map<String, Object> properties = properties(path, index, userId, scanResult);
+ try {
+ sendMail(properties);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private boolean checkPublish(final ScanResult scanResult) {
+ return !scanResult.isOk() || configuration.result_status_ok_send();
+ }
+
+ private void sendMail(final Map<String, Object> properties) throws Exception {
+ final MailService mailService = this.mailService;
+ final MessageBuilder builder = mailService.getMessageBuilder();
+
+ final String from = configuration.mail_from();
+ final String[] to = configuration.mail_to();
+ final String[] cc = configuration.mail_cc();
+ final String[] bcc = configuration.mail_bcc();
+ final String[] replyTo = configuration.mail_replyTo();
+ final String subject = configuration.mail_subject();
+ final String text = configuration.mail_text();
+ final String html = configuration.mail_html();
+
+ if (StringUtils.isNotBlank(from)) {
+ builder.from(from);
+ }
+ for (final String address : to) {
+ if (StringUtils.isNotBlank(address)) {
+ builder.to(address);
+ }
+ }
+ for (final String address : cc) {
+ if (StringUtils.isNotBlank(address)) {
+ builder.cc(address);
+ }
+ }
+ for (final String address : bcc) {
+ if (StringUtils.isNotBlank(address)) {
+ builder.bcc(address);
+ }
+ }
+ for (final String address : replyTo) {
+ if (StringUtils.isNotBlank(address)) {
+ builder.replyTo(address);
+ }
+ }
+ if (StringUtils.isNotBlank(subject)) {
+ final IContext context = new Context(Locale.ENGLISH, properties);
+ final TemplateSpec templateSpec = new TemplateSpec(subject, TemplateMode.TEXT);
+ final String s = templateEngine.process(templateSpec, context);
+ builder.subject(s);
+ }
+ if (StringUtils.isNotBlank(text)) {
+ final IContext context = new Context(Locale.ENGLISH, properties);
+ final TemplateSpec templateSpec = new TemplateSpec(text, TemplateMode.TEXT);
+ final String t = templateEngine.process(templateSpec, context);
+ builder.text(t);
+ }
+ if (StringUtils.isNotBlank(html)) {
+ final IContext context = new Context(Locale.ENGLISH, properties);
+ final TemplateSpec templateSpec = new TemplateSpec(html, TemplateMode.HTML);
+ final String h = templateEngine.process(templateSpec, context);
+ builder.html(h);
+ }
+ final MimeMessage message = builder.build();
+ logger.debug("sending scan result mail: {}", properties);
+ final CompletableFuture<Void> future = mailService.sendMessage(message);
+ future.whenComplete((v, e) -> {
+ if (Objects.nonNull(e)) {
+ logger.error("sending scan result mail failed", e);
+ } else {
+ logger.debug("sending scan result mail succeeded");
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/clam/result/internal/MailSendingScanResultHandlerConfiguration.java b/src/main/java/org/apache/sling/clam/result/internal/MailSendingScanResultHandlerConfiguration.java
new file mode 100644
index 0000000..f319285
--- /dev/null
+++ b/src/main/java/org/apache/sling/clam/result/internal/MailSendingScanResultHandlerConfiguration.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.clam.result.internal;
+
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+@ObjectClassDefinition(
+ name = "Apache Sling Clam Mail Sending Scan Result Handler",
+ description = "Sends JCR property scan results via mail"
+)
+@SuppressWarnings("java:S100")
+@interface MailSendingScanResultHandlerConfiguration {
+
+ @AttributeDefinition(
+ name = "Mail From",
+ description = "Mail From address",
+ required = false
+ )
+ String mail_from();
+
+ @AttributeDefinition(
+ name = "Mail To",
+ description = "Mail To addresses"
+ )
+ String[] mail_to() default {};
+
+ @AttributeDefinition(
+ name = "Mail CC",
+ description = "Mail CC addresses"
+ )
+ String[] mail_cc() default {};
+
+ @AttributeDefinition(
+ name = "Mail BCC",
+ description = "Mail BCC addresses"
+ )
+ String[] mail_bcc() default {};
+
+ @AttributeDefinition(
+ name = "Mail Reply-To",
+ description = "Mail Reply-To addresses"
+ )
+ String[] mail_replyTo() default {};
+
+ @AttributeDefinition(
+ name = "Mail Subject",
+ description = "Mail Subject template (available variables: path, index, message, status, userId, started, size, timestamp)",
+ required = false
+ )
+ String mail_subject() default "Clam scan result: [(${status})] for [(${path})][# th:if=\"${index}\"] [(${index})][/]";
+
+ @AttributeDefinition(
+ name = "Mail Text",
+ description = "Mail Text template (available variables: path, index, message, status, userId, started, size, timestamp)",
+ required = false
+ ) // newlines get lost in scr and metatype XML
+ String mail_text() default "status: [(${status})]\n" +
+ "message: [(${message})]\n" +
+ "path: [(${path})]\n" +
+ "[# th:if=\"${index}\"]index: [(${index})][/]\n" +
+ "size: [(${size})]\n" +
+ "[# th:if=\"${userId}\"]userId: [(${userId})][/]\n" +
+ "started: [(${#dates.formatISO(new java.util.Date(started))})]\n" +
+ "timestamp: [(${#dates.formatISO(new java.util.Date(timestamp))})]\n";
+
+ @AttributeDefinition(
+ name = "Mail HTML",
+ description = "Mail HTML template (available variables: path, index, message, status, userId, started, size, timestamp)",
+ required = false
+ )
+ String mail_html();
+
+ @AttributeDefinition(
+ name = "send status ok",
+ description = "Send scan results with status OK also"
+ )
+ boolean result_status_ok_send() default false;
+
+ @AttributeDefinition(
+ name = "Mail Service target",
+ description = "Filter expression to target a Mail Service",
+ required = false
+ )
+ String mailService_target();
+
+ String webconsole_configurationFactory_nameHint() default "{mail.to}:{result.status.ok.send}:{mail.subject}";
+
+}
diff --git a/src/test/java/org/apache/sling/clam/it/tests/MailSendingScanResultHandlerIT.java b/src/test/java/org/apache/sling/clam/it/tests/MailSendingScanResultHandlerIT.java
new file mode 100644
index 0000000..8111932
--- /dev/null
+++ b/src/test/java/org/apache/sling/clam/it/tests/MailSendingScanResultHandlerIT.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.clam.it.tests;
+
+import java.security.Security;
+import java.util.Objects;
+
+import javax.inject.Inject;
+import javax.mail.internet.MimeMessage;
+
+import com.icegreen.greenmail.util.DummySSLSocketFactory;
+import com.icegreen.greenmail.util.GreenMail;
+import com.icegreen.greenmail.util.ServerSetup;
+import org.apache.commons.mail.util.MimeMessageParser;
+import org.apache.sling.clam.jcr.NodeDescendingJcrPropertyDigger;
+import org.apache.sling.clam.result.JcrPropertyScanResultHandler;
+import org.apache.sling.resource.presence.ResourcePresence;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerClass;
+import org.ops4j.pax.exam.util.Filter;
+import org.ops4j.pax.exam.util.PathUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.apache.sling.testing.paxexam.SlingOptions.greenmail;
+import static org.apache.sling.testing.paxexam.SlingOptions.slingCommonsMessagingMail;
+import static org.apache.sling.testing.paxexam.SlingOptions.slingResourcePresence;
+import static org.apache.sling.testing.paxexam.SlingOptions.slingStarterContent;
+import static org.apache.sling.testing.paxexam.SlingOptions.thymeleaf;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.factoryConfiguration;
+import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.newConfiguration;
+
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerClass.class)
+public class MailSendingScanResultHandlerIT extends ClamTestSupport {
+
+ @Inject
+ @Filter("(service.pid=org.apache.sling.clam.result.internal.MailSendingScanResultHandler)")
+ private JcrPropertyScanResultHandler jcrPropertyScanResultHandler;
+
+ @Inject
+ @Filter(value = "(path=/content/starter)", timeout = 300000)
+ private ResourcePresence resourcePresence;
+
+ @Inject
+ private NodeDescendingJcrPropertyDigger nodeDescendingJcrPropertyDigger;
+
+ private GreenMail greenMail;
+
+ private final Logger logger = LoggerFactory.getLogger(MailSendingScanResultHandlerIT.class);
+
+ @Configuration
+ public Option[] configuration() {
+ final int port = findFreePort();
+ final String path = String.format("%s/src/test/resources/password", PathUtils.getBaseDir());
+ return options(
+ baseConfiguration(),
+ clamdConfiguration(),
+ slingResourcePresence(),
+ factoryConfiguration("org.apache.sling.resource.presence.internal.ResourcePresenter")
+ .put("path", "/content/starter")
+ .asOption(),
+ newConfiguration("org.apache.sling.clam.result.internal.MailSendingScanResultHandler")
+ .put("mail.from", "from@example.org")
+ .put("mail.to", "to@example.org")
+ .put("mail.cc", "cc@example.org")
+ .put("mail.bcc", "bcc@example.org")
+ .put("mail.replyTo", "reply-to@example.org")
+ .put("result.status.ok.send", true)
+ .asOption(),
+ slingStarterContent(),
+ // Commons Messaging Mail
+ factoryConfiguration("org.apache.sling.commons.messaging.mail.internal.SimpleMessageIdProvider")
+ .put("host", "localhost")
+ .asOption(),
+ factoryConfiguration("org.apache.sling.commons.messaging.mail.internal.SimpleMailService")
+ .put("mail.smtps.from", "envelope-from@example.org")
+ .put("mail.smtps.host", "localhost")
+ .put("mail.smtps.port", port)
+ .put("username", "username")
+ .put("password", "OEKPFL5cVJRqVjh4QaDZhvBiqv8wgWBMJ8PGbYHTqev046oV6888mna9w1mIGCXK")
+ .asOption(),
+ slingCommonsMessagingMail(),
+ // Commons Crypto
+ factoryConfiguration("org.apache.sling.commons.crypto.jasypt.internal.JasyptStandardPBEStringCryptoService")
+ .put("algorithm", "PBEWITHHMACSHA512ANDAES_256")
+ .asOption(),
+ factoryConfiguration("org.apache.sling.commons.crypto.jasypt.internal.JasyptRandomIvGeneratorRegistrar")
+ .put("algorithm", "SHA1PRNG")
+ .asOption(),
+ factoryConfiguration("org.apache.sling.commons.crypto.internal.FilePasswordProvider")
+ .put("path", path)
+ .asOption(),
+ // Thymeleaf
+ thymeleaf(),
+ // testing – mail
+ greenmail(),
+ mavenBundle().groupId("org.apache.commons").artifactId("commons-email").versionAsInProject()
+ );
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ if (Objects.isNull(greenMail)) {
+ // set up GreenMail server
+ Security.setProperty("ssl.SocketFactory.provider", DummySSLSocketFactory.class.getName());
+ final org.osgi.service.cm.Configuration[] configurations = configurationAdmin.listConfigurations("(service.factoryPid=org.apache.sling.commons.messaging.mail.internal.SimpleMailService)");
+ final org.osgi.service.cm.Configuration configuration = configurations[0];
+ final int port = (int) configuration.getProperties().get("mail.smtps.port");
+ final ServerSetup serverSetup = new ServerSetup(port, "127.0.0.1", "smtps");
+ greenMail = new GreenMail(serverSetup);
+ greenMail.setUser("username", "password");
+ greenMail.start();
+ }
+ }
+
+ @After
+ public void tearDown() {
+ if (Objects.nonNull(greenMail)) {
+ greenMail.stop();
+ greenMail = null;
+ }
+ }
+
+ @Test
+ public void testJcrPropertyScanResultHandler() {
+ assertThat(jcrPropertyScanResultHandler).isNotNull();
+ }
+
+ @Test
+ public void testSentResults() throws Exception {
+ digBinaries(nodeDescendingJcrPropertyDigger, "/content/starter");
+ greenMail.waitForIncomingEmail(360000, 24);
+ final MimeMessage[] messages = greenMail.getReceivedMessages();
+ assertThat(messages.length).isEqualTo(24);
+ for (final MimeMessage message : messages) {
+ assertThat(message.getSubject()).startsWith("Clam scan result: OK for /content/starter/");
+ final MimeMessageParser parser = new MimeMessageParser(message).parse();
+ final String text = parser.getPlainContent();
+ assertThat(text).contains("status: OK");
+ assertThat(text).contains("message: ");
+ assertThat(text).contains("path: /content/starter/");
+ assertThat(text).contains("started: ");
+ assertThat(text).contains("timestamp: ");
+ }
+ }
+
+}
diff --git a/src/test/resources/password b/src/test/resources/password
new file mode 100644
index 0000000..ad66ce8
--- /dev/null
+++ b/src/test/resources/password
@@ -0,0 +1 @@
++AQ?aDes!'DBMkrCi:FE6q\sOn=Pbmn=PK8n=PK?
\ No newline at end of file