added template service, template management service bean, freemarker methods and template loading tests

added templating exception
changed embed configuration to have builders
enabled mocking of final classes for mockito
This commit is contained in:
Sheldan
2020-05-27 23:59:48 +02:00
parent f61cc8e1ff
commit f4edf7a77d
20 changed files with 951 additions and 32 deletions

View File

@@ -36,3 +36,4 @@ jobs:
-Dsonar.projectKey=abstracto-abstracto
-Dsonar.java.binaries=**/target/classes
-Dsonar.coverage.jacoco.xmlReportPaths=abstracto-application/coverage/target/site/jacoco-aggregate/jacoco.xml
-Dsonar.coverage.exclusions=**/*Test.java

View File

@@ -86,6 +86,11 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>**/*Test.java</exclude>
</excludes>
</configuration>
<executions>
<execution>
<id>report-aggregate</id>

View File

@@ -33,12 +33,6 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<configuration>
<excludes>
<exclude>**/*IntegrationTest.java</exclude>
</excludes>
<forkCount>1</forkCount>
</configuration>
</plugin>
<plugin>
@@ -50,7 +44,12 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.7.9</version>
<version>0.8.5</version>
<configuration>
<excludes>
<exclude>**/*Test.java</exclude>
</excludes>
</configuration>
<executions>
<execution>
<id>prepare-unit-tests</id>
@@ -63,6 +62,7 @@
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>

View File

@@ -34,7 +34,11 @@ public class DurationMethod implements TemplateMethodModelEx {
if (arguments.size() != 1) {
throw new TemplateModelException("Incorrect parameters passed.");
}
Object wrappedObject = ((StringModel) arguments.get(0)).getWrappedObject();
Object o = arguments.get(0);
if(!(o instanceof StringModel)) {
throw new TemplateModelException("Passed object was not a StringModel.");
}
Object wrappedObject = ((StringModel) o).getWrappedObject();
if(!(wrappedObject instanceof Duration)) {
throw new TemplateModelException("Passed argument was not a duration object");
}

View File

@@ -20,15 +20,38 @@ public class SafeFieldIterations implements TemplateMethodModelEx {
@Override
public Object exec(List arguments) throws TemplateModelException {
List wrappedObject = (List) ((DefaultListAdapter) arguments.get(0)).getWrappedObject();
String appliedTemplate = ((SimpleScalar) arguments.get(1)).getAsString();
String nameTemplate = ((SimpleScalar) arguments.get(2)).getAsString();
String inline = ((SimpleScalar) arguments.get(3)).getAsString();
if(arguments.size() != 4) {
throw new TemplateModelException("Incorrect amount of parameters.");
}
Object adapterObject = arguments.get(0);
if(!(adapterObject instanceof DefaultListAdapter)) {
throw new TemplateModelException("Passed object was not a DefaultListAdapter.");
}
Object wrappedList = ((DefaultListAdapter) adapterObject).getWrappedObject();
if(!(wrappedList instanceof List)) {
throw new TemplateModelException("Passed object was not a List.");
}
List wrappedObject = (List) wrappedList;
Object templateParameter = arguments.get(1);
if(!(templateParameter instanceof SimpleScalar)) {
throw new TemplateModelException("Passed object for template was not a SimpleScalar.");
}
String appliedTemplate = ((SimpleScalar) templateParameter).getAsString();
Object fieldNameTemplateParameter = arguments.get(2);
if(!(fieldNameTemplateParameter instanceof SimpleScalar)) {
throw new TemplateModelException("Passed object for field name template was not a SimpleScalar.");
}
String fieldNameTemplate = ((SimpleScalar) fieldNameTemplateParameter).getAsString();
Object inlineParameter = arguments.get(3);
if(!(inlineParameter instanceof SimpleScalar)) {
throw new TemplateModelException("Passed object for inline was not a SimpleScalar.");
}
String inline = ((SimpleScalar) inlineParameter).getAsString();
List<StringBuilder> result = new ArrayList<>();
StringBuilder currentBuilder = new StringBuilder();
String firstEmbedTitle = service.renderTemplateWithMap(nameTemplate, getEmbedCountParameters(1, wrappedObject));
String firstEmbedTitle = service.renderTemplateWithMap(fieldNameTemplate, getEmbedCountParameters(1, wrappedObject));
currentBuilder.append(newFieldHeader(firstEmbedTitle, inline));
String finalClosingString = "\"}";
String closingString = finalClosingString + ",";
@@ -42,7 +65,7 @@ public class SafeFieldIterations implements TemplateMethodModelEx {
result.add(currentBuilder);
currentBuilder = new StringBuilder();
splitFieldCounts += 1;
String renderedName = service.renderTemplateWithMap(nameTemplate, getEmbedCountParameters(splitFieldCounts, wrappedObject));
String renderedName = service.renderTemplateWithMap(fieldNameTemplate, getEmbedCountParameters(splitFieldCounts, wrappedObject));
currentBuilder.append(newFieldHeader(renderedName, inline));
}
currentBuilder.append(s);
@@ -54,14 +77,14 @@ public class SafeFieldIterations implements TemplateMethodModelEx {
for (StringBuilder innerBuilder: result) {
bigBuilder.append(innerBuilder.toString());
}
return bigBuilder;
return bigBuilder.toString();
}
private String newFieldHeader(String name, String inline) {
return String.format("{ \"name\": \"%s\", \"inline\": \"%s\", \"value\": \"", name, inline);
}
private HashMap<String, Object> getEmbedCountParameters(Integer count, List objects) {
private HashMap<String, Object> getEmbedCountParameters(Integer count, List<? extends Object> objects) {
HashMap<String, Object> parameters = new HashMap<>();
parameters.put("count", count);
parameters.put("list", objects);

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.templating.model;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@@ -9,6 +10,7 @@ import lombok.Setter;
*/
@Getter
@Setter
@Builder
public class EmbedAuthor {
/**
* The name used in the {@link net.dv8tion.jda.api.entities.MessageEmbed} author

View File

@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.templating.model;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@@ -8,6 +9,7 @@ import lombok.Setter;
*/
@Setter
@Getter
@Builder
public class EmbedColor {
/**
* The red part of RGB

View File

@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.templating.model;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@@ -12,6 +13,7 @@ import java.util.List;
*/
@Getter
@Setter
@Builder
public class EmbedConfiguration {
/**
* The {@link EmbedAuthor} object holding the configuration for the author of the {@link net.dv8tion.jda.api.entities.MessageEmbed}

View File

@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.templating.model;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@@ -8,6 +9,7 @@ import lombok.Setter;
*/
@Getter
@Setter
@Builder
public class EmbedFooter {
/**
* The text which is going to be used as the footer text

View File

@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.templating.model;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@@ -8,6 +9,7 @@ import lombok.Setter;
*/
@Getter
@Setter
@Builder
public class EmbedTitle {
/**
* The text which is going to be used as the title of the embed

View File

@@ -2,18 +2,20 @@ package dev.sheldan.abstracto.templating.service;
import com.google.gson.Gson;
import dev.sheldan.abstracto.templating.Templatable;
import dev.sheldan.abstracto.templating.exception.TemplatingException;
import dev.sheldan.abstracto.templating.model.*;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.MessageEmbed;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import java.awt.*;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -89,7 +91,7 @@ public class TemplateServiceBean implements TemplateService {
firstBuilder.setFooter(footer.getText(), footer.getIcon());
}
if(embedConfiguration.getFields() != null) {
createFieldsForEmbed(key, embedBuilders, embedConfiguration);
createFieldsForEmbed(embedBuilders, embedConfiguration);
}
firstBuilder.setTimestamp(embedConfiguration.getTimeStamp());
@@ -112,18 +114,14 @@ public class TemplateServiceBean implements TemplateService {
.build();
}
private void createFieldsForEmbed(String key, List<EmbedBuilder> embedBuilders, EmbedConfiguration configuration) {
private void createFieldsForEmbed(List<EmbedBuilder> embedBuilders, EmbedConfiguration configuration) {
for (int i = 0; i < configuration.getFields().size(); i++) {
EmbedField field = configuration.getFields().get(i);
if(field != null && field.getValue() != null) {
if(field.getValue().length() > MessageEmbed.VALUE_MAX_LENGTH) {
String substring = field.getValue().substring(MessageEmbed.VALUE_MAX_LENGTH);
field.setValue(field.getValue().substring(0, MessageEmbed.VALUE_MAX_LENGTH));
EmbedField secondPart = EmbedField.builder().inline(field.getInline()).name(field.getName() + " 2").value(substring).build();
configuration.getFields().add(i + 1, secondPart);
}
} else {
log.warn("Field {} in template {} is null.", i, key);
if(field != null && field.getValue() != null && field.getValue().length() > MessageEmbed.VALUE_MAX_LENGTH) {
String substring = field.getValue().substring(MessageEmbed.VALUE_MAX_LENGTH);
field.setValue(field.getValue().substring(0, MessageEmbed.VALUE_MAX_LENGTH));
EmbedField secondPart = EmbedField.builder().inline(field.getInline()).name(field.getName() + " 2").value(substring).build();
configuration.getFields().add(i + 1, secondPart);
}
}
double neededIndex = Math.ceil(configuration.getFields().size() / 25D) - 1;
@@ -161,10 +159,10 @@ public class TemplateServiceBean implements TemplateService {
@Override
public String renderTemplateWithMap(String key, HashMap<String, Object> parameters) {
try {
return FreeMarkerTemplateUtils.processTemplateIntoString(configuration.getTemplate(key), parameters);
return renderTemplateToString(key, parameters);
} catch (IOException | TemplateException e) {
log.warn("Failed to render template. ", e);
throw new RuntimeException(e);
throw new TemplatingException(e);
}
}
@@ -177,13 +175,28 @@ public class TemplateServiceBean implements TemplateService {
@Override
public String renderTemplate(String key, Object model) {
try {
return FreeMarkerTemplateUtils.processTemplateIntoString(configuration.getTemplate(key), model);
return renderTemplateToString(key, model);
} catch (IOException | TemplateException e) {
log.warn("Failed to render template. ", e);
throw new RuntimeException(e);
throw new TemplatingException(e);
}
}
/**
* Loads the given key as a template, and renders it, returns the result as a String
* @param key The key of the template to render
* @param model The parameters which are given to the template
* @return The rendered template in a String
* @throws freemarker.template.TemplateNotFoundException In case the template could not be found
* @throws TemplateException In case the rendering failed
*/
private String renderTemplateToString(String key, Object model) throws IOException, TemplateException {
StringWriter result = new StringWriter();
Template template = configuration.getTemplate(key);
template.process(model, result);
return result.toString();
}
/**
* Renders a simple template identified by key without any model. This will cause exceptions in case there are references to a model in the provided template.
* @param key The key of the template to be rendered

View File

@@ -0,0 +1,53 @@
package dev.sheldan.abstracto.templating.loading;
import dev.sheldan.abstracto.templating.model.database.Template;
import dev.sheldan.abstracto.templating.service.management.TemplateManagementService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.io.IOException;
import java.io.Reader;
import java.util.Optional;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class TemplateLoaderTest {
public static final String TEST_CONTENT = "test";
public static final String TEMPLATE_KEY = "key";
@InjectMocks
private DatabaseTemplateLoader loader;
@Mock
private TemplateManagementService templateManagementService;
@Test
public void testProperLoading() throws IOException {
Template mocked = Template.builder().key(TEMPLATE_KEY).content(TEST_CONTENT).build();
when(templateManagementService.getTemplateByKey(TEMPLATE_KEY)).thenReturn(Optional.of(mocked));
Template templateSource = (Template) loader.findTemplateSource(TEMPLATE_KEY);
assertThat(TEST_CONTENT, equalTo(templateSource.getContent()));
}
@Test(expected = IOException.class)
public void testMissingTemplate() throws IOException {
loader.findTemplateSource(TEMPLATE_KEY);
}
@Test
public void testReader() throws IOException {
Template mocked = Template.builder().key(TEMPLATE_KEY).content(TEST_CONTENT).build();
Reader reader = loader.getReader(mocked, null);
char[] chars = new char[4];
reader.read(chars, 0, 4);
assertThat(TEST_CONTENT, equalTo(new String(chars)));
}
}

View File

@@ -0,0 +1,113 @@
package dev.sheldan.abstracto.templating.methods;
import freemarker.ext.beans.StringModel;
import freemarker.template.*;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.junit.MockitoJUnitRunner;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
@RunWith(MockitoJUnitRunner.class)
public class DateMethodTest {
public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final Instant DATE = Instant.ofEpochSecond(1590615937);
@InjectMocks
private DateMethod dateMethod;
@Test
public void testInstantFormat() throws TemplateModelException {
String exec = (String) dateMethod.exec(getCorrectParametersInstant());
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT)
.withZone(ZoneId.systemDefault());
Assert.assertEquals(exec, formatter.format(DATE));
}
@Test
public void testOffsetDateTimeObject() throws TemplateModelException {
String exec = (String) dateMethod.exec(getCorrectParametersOffsetDateTime());
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT)
.withZone(ZoneId.systemDefault());
Assert.assertEquals(exec, formatter.format(DATE));
}
@Test(expected = TemplateModelException.class)
public void incorrectParameterCount() throws TemplateModelException {
dateMethod.exec(new ArrayList<Object>());
}
@Test(expected = IllegalArgumentException.class)
public void testIncorrectDateFormat() throws TemplateModelException {
dateMethod.exec(getIncorrectDateFormat());
}
@Test(expected = TemplateModelException.class)
public void incorrectPassedObject() throws TemplateModelException {
dateMethod.exec(getIncorrectTimeParameter());
}
private List<Object> getIncorrectDateFormat() {
List<Object> params = new ArrayList<>();
params.add(getInstantDateObject());
params.add(incorrectDateFormat());
return params;
}
private List<Object> getIncorrectTimeParameter() {
List<Object> params = new ArrayList<>();
params.add(getNotCompatibleObject());
params.add(incorrectDateFormat());
return params;
}
private List<Object> getCorrectParametersInstant() {
List<Object> params = new ArrayList<>();
params.add(getInstantDateObject());
params.add(simpleDateFormat());
return params;
}
private List<Object> getCorrectParametersOffsetDateTime() {
List<Object> params = new ArrayList<>();
params.add(getOffsetDateTimeObject());
params.add(simpleDateFormat());
return params;
}
private SimpleScalar simpleDateFormat() {
return new SimpleScalar(DATE_TIME_FORMAT);
}
private SimpleScalar incorrectDateFormat() {
return new SimpleScalar("INCORRECT");
}
private StringModel getInstantDateObject() {
DefaultObjectWrapper wrapper = getWrapper();
return new StringModel(DATE, wrapper);
}
private DefaultObjectWrapper getWrapper() {
return new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_0).build();
}
private StringModel getOffsetDateTimeObject() {
DefaultObjectWrapper wrapper = getWrapper();
return new StringModel(OffsetDateTime.ofInstant(DATE, ZoneId.systemDefault()), wrapper);
}
private StringModel getNotCompatibleObject() {
DefaultObjectWrapper wrapper = getWrapper();
return new StringModel("", wrapper);
}
}

View File

@@ -0,0 +1,124 @@
package dev.sheldan.abstracto.templating.methods;
import dev.sheldan.abstracto.templating.service.TemplateService;
import freemarker.ext.beans.StringModel;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.DefaultObjectWrapperBuilder;
import freemarker.template.TemplateModelException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import static org.mockito.Mockito.*;
import org.mockito.junit.MockitoJUnitRunner;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@RunWith(MockitoJUnitRunner.class)
public class DurationMethodTest {
public static final long SECOND_AMOUNT = 10L;
public static final long MINUTES_AMOUNT = 2L;
public static final long HOURS_AMOUNT = 3L;
public static final long DAYS_AMOUNT = 5L;
public static final String DURATION_TEMPLATE = "duration_formatting";
public static final String SECONDS = "seconds";
public static final String HOURS = "hours";
public static final String MINUTES = "minutes";
public static final String DAYS = "days";
@InjectMocks
private DurationMethod durationMethod;
@Mock
private TemplateService templateService;
@Test
public void testSeconds() throws TemplateModelException {
durationMethod.exec(getSecondParameters());
verify(templateService, times(1)).renderTemplateWithMap(DURATION_TEMPLATE, getHashMap(0, 0, 0, SECOND_AMOUNT));
}
@Test
public void testMinutes() throws TemplateModelException {
durationMethod.exec(getMinuteParameter());
verify(templateService, times(1)).renderTemplateWithMap(DURATION_TEMPLATE, getHashMap(0, 0, MINUTES_AMOUNT, 0));
}
@Test
public void testHours() throws TemplateModelException {
durationMethod.exec(getHourParameter());
verify(templateService, times(1)).renderTemplateWithMap(DURATION_TEMPLATE, getHashMap(0, HOURS_AMOUNT, 0, 0));
}
@Test
public void testDays() throws TemplateModelException {
durationMethod.exec(getDayParameter());
verify(templateService, times(1)).renderTemplateWithMap(DURATION_TEMPLATE, getHashMap(DAYS_AMOUNT, 0, 0, 0));
}
@Test
public void testAllTime() throws TemplateModelException {
durationMethod.exec(getMixedParameter());
verify(templateService, times(1)).renderTemplateWithMap(DURATION_TEMPLATE, getHashMap(DAYS_AMOUNT, HOURS_AMOUNT, MINUTES_AMOUNT, SECOND_AMOUNT));
}
@Test(expected = TemplateModelException.class)
public void testNoParamGiven() throws TemplateModelException {
durationMethod.exec(Collections.emptyList());
}
@Test(expected = TemplateModelException.class)
public void testNoDurationObject() throws TemplateModelException {
durationMethod.exec(Arrays.asList(new StringModel("", getWrapper())));
}
@Test(expected = TemplateModelException.class)
public void testNoStringModelObject() throws TemplateModelException {
durationMethod.exec(Arrays.asList(""));
}
private List<Object> getSecondParameters() {
return Arrays.asList(new StringModel(Duration.ofSeconds(SECOND_AMOUNT), getWrapper()));
}
private List<Object> getMinuteParameter() {
return Arrays.asList(new StringModel(Duration.ofMinutes(MINUTES_AMOUNT), getWrapper()));
}
private List<Object> getHourParameter() {
return Arrays.asList(new StringModel(Duration.ofHours(HOURS_AMOUNT), getWrapper()));
}
private List<Object> getDayParameter() {
return Arrays.asList(new StringModel(Duration.ofDays(DAYS_AMOUNT), getWrapper()));
}
private List<Object> getMixedParameter() {
return Arrays.asList(new StringModel(Duration.ofSeconds(SECOND_AMOUNT)
.plus(Duration.ofMinutes(MINUTES_AMOUNT))
.plus(Duration.ofHours(HOURS_AMOUNT))
.plus(Duration.ofDays(DAYS_AMOUNT)), getWrapper()));
}
private DefaultObjectWrapper getWrapper() {
return new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_0).build();
}
private HashMap<String, Object> getHashMap(long days, long hours, long minutes, long seconds) {
HashMap<String, Object> map = new HashMap<>();
map.put(DAYS, days);
map.put(HOURS, hours);
map.put(MINUTES, minutes);
map.put(SECONDS, seconds);
return map;
}
}

View File

@@ -0,0 +1,140 @@
package dev.sheldan.abstracto.templating.methods;
import dev.sheldan.abstracto.templating.service.TemplateService;
import freemarker.template.*;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.times;
@RunWith(MockitoJUnitRunner.class)
public class SafeFieldIterationsTest {
public static final String TEMPLATE_KEY = "template";
public static final String FIELD_NAME_TEMPLATE = "fieldName";
public static final String FIELD_NAME_VALUE = "fieldName";
public static final String EXPECTED_START_PART = "{ \"name\": \"" + FIELD_NAME_VALUE + "\", \"inline\": \"true\", \"value\": \"";
public static final String INLINE_VALUE = "true";
public static final String FIRST_LIST_ENTRY = "text";
public static final String SIX_HUNDRED_CHARACTERS = RandomStringUtils.randomAlphabetic(600);
@InjectMocks
private SafeFieldIterations safeFieldIterations;
@Mock
private TemplateService templateService;
@Captor
private ArgumentCaptor<String> templateKeyCaptor;
@Before
public void setup() {
when(templateService.renderTemplateWithMap(eq(FIELD_NAME_TEMPLATE), any())).thenReturn(FIELD_NAME_VALUE);
}
@Test
public void testEmptyList() throws TemplateModelException {
String resultingValue = (String) safeFieldIterations.exec(getSimpleParameters());
verify(templateService, times(1)).renderTemplateWithMap(templateKeyCaptor.capture(), any());
List<String> usedTemplateKeys = templateKeyCaptor.getAllValues();
assertEquals(FIELD_NAME_TEMPLATE, usedTemplateKeys.get(0));
assertEquals(EXPECTED_START_PART + "\"}", resultingValue);
}
@Test
public void testOneElement() throws TemplateModelException {
when(templateService.renderTemplateWithMap(eq(TEMPLATE_KEY), any())).thenReturn(FIRST_LIST_ENTRY);
String resultingValue = (String) safeFieldIterations.exec(oneListEntryParameter());
assertEquals(EXPECTED_START_PART + FIRST_LIST_ENTRY + "\"}", resultingValue);
}
@Test
public void testTwoElements() throws TemplateModelException {
when(templateService.renderTemplateWithMap(eq(TEMPLATE_KEY), any())).thenReturn(FIRST_LIST_ENTRY);
String resultingValue = (String) safeFieldIterations.exec(twoListEntryParameter());
assertEquals(EXPECTED_START_PART + FIRST_LIST_ENTRY + FIRST_LIST_ENTRY + "\"}", resultingValue);
}
@Test
public void testElementsStaySolo() throws TemplateModelException {
when(templateService.renderTemplateWithMap(eq(TEMPLATE_KEY), any())).thenReturn(SIX_HUNDRED_CHARACTERS);
String resultingValue = (String) safeFieldIterations.exec(twoListEntryParameter());
assertEquals(EXPECTED_START_PART + SIX_HUNDRED_CHARACTERS + "\"}," + EXPECTED_START_PART + SIX_HUNDRED_CHARACTERS + "\"}", resultingValue);
}
@Test(expected = TemplateModelException.class)
public void testTooLittleParameters() throws TemplateModelException {
safeFieldIterations.exec(Arrays.asList(""));
}
@Test(expected = TemplateModelException.class)
public void testWrongListAdapterType() throws TemplateModelException {
safeFieldIterations.exec(wrongListAdapter());
}
@Test(expected = TemplateModelException.class)
public void testWrongTemplateKeyParameterType() throws TemplateModelException {
safeFieldIterations.exec(wrongTemplateKeyParameterType());
}
@Test(expected = TemplateModelException.class)
public void testWrongFieldNameTemplateKeyParameterType() throws TemplateModelException {
safeFieldIterations.exec(wrongFieldNameTemplateKeyParameterType());
}
@Test(expected = TemplateModelException.class)
public void testWrongInlineParameterType() throws TemplateModelException {
safeFieldIterations.exec(wrongInLineValueParameterType());
}
public List<Object> wrongListAdapter() {
return Arrays.asList(new Object(), new Object(), new SimpleScalar(FIELD_NAME_TEMPLATE), new SimpleScalar(INLINE_VALUE));
}
public List<Object> wrongTemplateKeyParameterType() {
return Arrays.asList(validEmptyList(), new Object(), new SimpleScalar(FIELD_NAME_TEMPLATE), new SimpleScalar(INLINE_VALUE));
}
public List<Object> wrongFieldNameTemplateKeyParameterType() {
return Arrays.asList(validEmptyList(), new SimpleScalar(TEMPLATE_KEY), new Object(), new SimpleScalar(INLINE_VALUE));
}
public List<Object> wrongInLineValueParameterType() {
return Arrays.asList(validEmptyList(), new SimpleScalar(TEMPLATE_KEY), new SimpleScalar(FIELD_NAME_TEMPLATE), new Object());
}
public List<Object> getSimpleParameters() {
return Arrays.asList(validEmptyList(), new SimpleScalar(TEMPLATE_KEY), new SimpleScalar(FIELD_NAME_TEMPLATE), new SimpleScalar(INLINE_VALUE));
}
private DefaultListAdapter validEmptyList() {
return DefaultListAdapter.adapt(new ArrayList<Object>(), getWrapper());
}
public List<Object> oneListEntryParameter() {
return Arrays.asList(DefaultListAdapter.adapt(Arrays.asList("testing"), getWrapper()), new SimpleScalar(TEMPLATE_KEY), new SimpleScalar(FIELD_NAME_TEMPLATE), new SimpleScalar(INLINE_VALUE));
}
public List<Object> twoListEntryParameter() {
return Arrays.asList(DefaultListAdapter.adapt(Arrays.asList("testing", "othertext"), getWrapper()), new SimpleScalar(TEMPLATE_KEY), new SimpleScalar(FIELD_NAME_TEMPLATE), new SimpleScalar(INLINE_VALUE));
}
private DefaultObjectWrapper getWrapper() {
return new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_0).build();
}
}

View File

@@ -0,0 +1,330 @@
package dev.sheldan.abstracto.templating.service;
import com.google.gson.Gson;
import dev.sheldan.abstracto.templating.Templatable;
import dev.sheldan.abstracto.templating.exception.TemplatingException;
import dev.sheldan.abstracto.templating.model.*;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateNotFoundException;
import net.dv8tion.jda.api.entities.MessageEmbed;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ui.freemarker.FreeMarkerConfigurationFactory;
import java.awt.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class TemplateServiceBeanTest {
public static final String EXAMPLE_URL = "https://example.com";
public static final String FIELD_VALUE = "value";
public static final String EMBED_PAGE_COUNT_TEMPLATE = "embed_page_count";
public static final String FIELD_TEMPLATE = "{\n" +
"\"name\": \"name\",\n" +
"\"value\": \"value\",\n" +
"\"inline\": \"true\"\n" +
"}";
public static final String FIELD_TEMPLATE_WITH_VALUE = "{\n" +
"\"name\": \"name\",\n" +
"\"value\": \"%s\",\n" +
"\"inline\": \"true\"\n" +
"}";
@InjectMocks
private TemplateServiceBean templateServiceBean;
@Mock
private Configuration configuration;
@Mock
private Gson gson;
@Autowired
private Gson actualGsonInstance;
private static final String SIMPLE_TEMPLATE_SOURCE = "source";
private static final String TEMPLATE_KEY = "template";
@Test
public void testSimpleTemplate() throws IOException, TemplateException {
when(configuration.getTemplate(TEMPLATE_KEY)).thenReturn(getSimpleTemplate());
String rendered = templateServiceBean.renderSimpleTemplate(TEMPLATE_KEY);
Assert.assertEquals(SIMPLE_TEMPLATE_SOURCE, rendered);
}
@Test
public void renderTemplatable() throws IOException, TemplateException {
when(configuration.getTemplate(TEMPLATE_KEY)).thenReturn(getSimpleTemplate());
Templatable templatable = getTemplatableWithSimpleTemplate();
String rendered = templateServiceBean.renderTemplatable(templatable);
Assert.assertEquals(SIMPLE_TEMPLATE_SOURCE, rendered);
}
@Test
public void testTemplateWithMapParameter() throws IOException, TemplateException {
when(configuration.getTemplate(TEMPLATE_KEY)).thenReturn(getSimpleTemplate());
String rendered = templateServiceBean.renderTemplateWithMap(TEMPLATE_KEY, new HashMap<>());
Assert.assertEquals(SIMPLE_TEMPLATE_SOURCE, rendered);
}
@Test
public void testEmbedWithDescription() throws IOException, TemplateException {
String descriptionText = "test";
String fullEmbedTemplateKey = getEmbedTemplateKey();
when(configuration.getTemplate(fullEmbedTemplateKey)).thenReturn(getEmbedTemplateWithDescription(descriptionText));
when(gson.fromJson(embedTemplateWithDescription(descriptionText), EmbedConfiguration.class)).thenReturn(embedConfigWithDescription(descriptionText));
MessageToSend messageToSend = templateServiceBean.renderEmbedTemplate(TEMPLATE_KEY, new HashMap<>());
Assert.assertEquals(descriptionText, messageToSend.getEmbeds().get(0).getDescription());
}
@Test
public void testEmbedWithAllUsableAttributes() throws IOException, TemplateException {
when(configuration.getTemplate(getEmbedTemplateKey())).thenReturn(getEmbedTemplateWithFallFieldsUsedOnce());
when(gson.fromJson(getFullEmbedConfigString(), EmbedConfiguration.class)).thenReturn(getFullEmbedConfiguration());
MessageToSend messageToSend = templateServiceBean.renderEmbedTemplate(TEMPLATE_KEY, new HashMap<>());
Assert.assertEquals(messageToSend.getMessage(), "additionalMessage");
MessageEmbed onlyEmbed = messageToSend.getEmbeds().get(0);
Assert.assertEquals(onlyEmbed.getAuthor().getIconUrl(), EXAMPLE_URL);
Assert.assertEquals(onlyEmbed.getAuthor().getName(), "name");
Assert.assertEquals(onlyEmbed.getDescription(), "description");
MessageEmbed.Field onlyField = onlyEmbed.getFields().get(0);
Assert.assertEquals(onlyField.getName(), "name");
Assert.assertEquals(onlyField.getValue(), FIELD_VALUE);
Assert.assertTrue(onlyField.isInline());
Color color = onlyEmbed.getColor();
Assert.assertEquals(color.getBlue(), 255);
Assert.assertEquals(color.getRed(), 255);
Assert.assertEquals(color.getGreen(), 255);
Assert.assertEquals(onlyEmbed.getUrl(), EXAMPLE_URL);
Assert.assertEquals(onlyEmbed.getFooter().getText(), "text");
Assert.assertEquals(onlyEmbed.getFooter().getIconUrl(), EXAMPLE_URL);
Assert.assertEquals(onlyEmbed.getImage().getUrl(), EXAMPLE_URL);
Assert.assertEquals(onlyEmbed.getThumbnail().getUrl(), EXAMPLE_URL);
Assert.assertEquals(1, messageToSend.getEmbeds().size());
}
@Test
public void testEmbedWithTooLongDescription() throws IOException, TemplateException {
int tooMuchCharacterCount = 1024;
String descriptionText = RandomStringUtils.randomAlphabetic(MessageEmbed.TEXT_MAX_LENGTH + tooMuchCharacterCount);
when(configuration.getTemplate(getEmbedTemplateKey())).thenReturn(getEmbedTemplateWithDescription(descriptionText));
when(configuration.getTemplate(EMBED_PAGE_COUNT_TEMPLATE)).thenReturn(getPageCountTemplate(1));
when(gson.fromJson(embedTemplateWithDescription(descriptionText), EmbedConfiguration.class)).thenReturn(embedConfigWithDescription(descriptionText));
MessageToSend messageToSend = templateServiceBean.renderEmbedTemplate(TEMPLATE_KEY, new HashMap<>());
MessageEmbed firstEmbed = messageToSend.getEmbeds().get(0);
Assert.assertEquals(MessageEmbed.TEXT_MAX_LENGTH, firstEmbed.getDescription().length());
Assert.assertEquals(descriptionText.substring(0, MessageEmbed.TEXT_MAX_LENGTH), firstEmbed.getDescription());
MessageEmbed secondEmbed = messageToSend.getEmbeds().get(1);
Assert.assertEquals(tooMuchCharacterCount, secondEmbed.getDescription().length());
Assert.assertEquals(descriptionText.substring(MessageEmbed.TEXT_MAX_LENGTH, MessageEmbed.TEXT_MAX_LENGTH + tooMuchCharacterCount), secondEmbed.getDescription());
}
@Test
public void testEmbedWithTooManyFields() throws IOException, TemplateException {
int totalFieldCount = 30;
when(configuration.getTemplate(getEmbedTemplateKey())).thenReturn(getEmbedTemplateWithFieldCount(totalFieldCount));
when(configuration.getTemplate(EMBED_PAGE_COUNT_TEMPLATE)).thenReturn(getPageCountTemplate(1));
when(gson.fromJson(getFieldsEmbedConfigAsString(totalFieldCount), EmbedConfiguration.class)).thenReturn(getTooManyFieldsEmbedConfiguration());
MessageToSend messageToSend = templateServiceBean.renderEmbedTemplate(TEMPLATE_KEY, new HashMap<>());
MessageEmbed firstEmbed = messageToSend.getEmbeds().get(0);
Assert.assertEquals(25, firstEmbed.getFields().size());
MessageEmbed secondEmbed = messageToSend.getEmbeds().get(1);
Assert.assertEquals(totalFieldCount % 25, secondEmbed.getFields().size());
}
@Test
public void testEmbedWithTooLongField() throws IOException, TemplateException {
String fieldValue = RandomStringUtils.randomAlphabetic(1500);
when(configuration.getTemplate(getEmbedTemplateKey())).thenReturn(getEmbedTemplateWithTooLongField(fieldValue));
when(gson.fromJson(getSingleFieldWithValue(fieldValue), EmbedConfiguration.class)).thenReturn(getEmbedWithSingleFieldOfValue(fieldValue));
MessageToSend messageToSend = templateServiceBean.renderEmbedTemplate(TEMPLATE_KEY, new HashMap<>());
MessageEmbed firstEmbed = messageToSend.getEmbeds().get(0);
Assert.assertEquals(2, firstEmbed.getFields().size());
Assert.assertEquals(fieldValue.substring(0, MessageEmbed.VALUE_MAX_LENGTH), firstEmbed.getFields().get(0).getValue());
Assert.assertEquals(fieldValue.substring(MessageEmbed.VALUE_MAX_LENGTH), firstEmbed.getFields().get(1).getValue());
}
@Test(expected = TemplatingException.class)
public void tryToRenderMissingTemplate() throws IOException {
when(configuration.getTemplate(TEMPLATE_KEY)).thenThrow(new TemplateNotFoundException(TEMPLATE_KEY, new Object(), ""));
templateServiceBean.renderSimpleTemplate(TEMPLATE_KEY);
}
@Test(expected = TemplatingException.class)
public void tryToRenderMissingEmbedTemplate() throws IOException {
when(configuration.getTemplate(getEmbedTemplateKey())).thenThrow(new TemplateNotFoundException(TEMPLATE_KEY, new Object(), ""));
templateServiceBean.renderEmbedTemplate(TEMPLATE_KEY, new Object());
}
@NotNull
private String getEmbedTemplateKey() {
return TEMPLATE_KEY + "_embed";
}
@Test(expected = TemplatingException.class)
public void tryToRenderMissingTemplateWithMap() throws IOException {
when(configuration.getTemplate(TEMPLATE_KEY)).thenThrow(new TemplateNotFoundException(TEMPLATE_KEY, new Object(), ""));
templateServiceBean.renderTemplateWithMap(TEMPLATE_KEY, new HashMap<>());
}
private EmbedConfiguration embedConfigWithDescription(String descriptionText) {
return EmbedConfiguration.builder().description(descriptionText).build();
}
private EmbedConfiguration getEmbedWithSingleFieldOfValue(String value) {
List<EmbedField> fields = new ArrayList<>();
fields.add(EmbedField.builder().name("name").value(value).build());
return EmbedConfiguration.builder().fields(fields).build();
}
private EmbedConfiguration getTooManyFieldsEmbedConfiguration() {
List<EmbedField> fields = new ArrayList<>();
for (int i = 0; i < 30; i++) {
EmbedField field = EmbedField
.builder()
.value(FIELD_VALUE)
.inline(true)
.name("name")
.build();
fields.add(field);
}
return EmbedConfiguration.builder().fields(fields).build();
}
private Templatable getTemplatableWithSimpleTemplate() {
return new Templatable() {
@Override
public String getTemplateName() {
return TEMPLATE_KEY;
}
@Override
public Object getTemplateModel() {
return new Object();
}
};
}
private EmbedConfiguration getFullEmbedConfiguration() {
EmbedAuthor author = EmbedAuthor
.builder()
.avatar(EXAMPLE_URL)
.name("name")
.url(EXAMPLE_URL)
.build();
EmbedColor color = EmbedColor
.builder()
.r(255)
.g(255)
.b(255)
.build();
EmbedField field = EmbedField
.builder()
.value(FIELD_VALUE)
.inline(true)
.name("name")
.build();
EmbedFooter footer = EmbedFooter
.builder()
.icon(EXAMPLE_URL)
.text("text")
.build();
EmbedTitle title = EmbedTitle
.builder()
.title("title")
.url(EXAMPLE_URL)
.build();
return EmbedConfiguration
.builder()
.fields(Arrays.asList(field))
.footer(footer)
.author(author)
.title(title)
.color(color)
.description("description")
.additionalMessage("additionalMessage")
.imageUrl(EXAMPLE_URL)
.thumbnail(EXAMPLE_URL)
.build();
}
private Template getSimpleTemplate() throws IOException, TemplateException {
return new Template(TEMPLATE_KEY, SIMPLE_TEMPLATE_SOURCE, getNonMockedConfiguration());
}
private Template getEmbedTemplateWithDescription(String description) throws IOException, TemplateException {
return new Template(getEmbedTemplateKey(), embedTemplateWithDescription(description), getNonMockedConfiguration());
}
private Template getPageCountTemplate(Integer page) throws IOException, TemplateException {
return new Template(EMBED_PAGE_COUNT_TEMPLATE, getEmbedPageCount(page), getNonMockedConfiguration());
}
private Template getEmbedTemplateWithFallFieldsUsedOnce() throws IOException, TemplateException {
return new Template(getEmbedTemplateKey(), getFullEmbedConfigString(), getNonMockedConfiguration());
}
private Template getEmbedTemplateWithFieldCount(Integer count) throws IOException, TemplateException {
return new Template(getEmbedTemplateKey(), getFieldsEmbedConfigAsString(count), getNonMockedConfiguration());
}
private Template getEmbedTemplateWithTooLongField(String value) throws IOException, TemplateException {
return new Template(getEmbedTemplateKey(), getSingleFieldWithValue(value), getNonMockedConfiguration());
}
private String getFullEmbedConfigString() throws IOException {
return IOUtils.toString(this.getClass().getResourceAsStream("/full_embed.json"), StandardCharsets.UTF_8);
}
private String getFieldsEmbedConfigAsString(Integer count) {
StringBuilder sb = new StringBuilder();
sb.append("{\"fields\": [");
for (int i = 0; i < count - 1; i++) {
sb.append(FIELD_TEMPLATE + ",");
}
sb.append(FIELD_TEMPLATE +
"]\n" +
"}");
return sb.toString();
}
private String getSingleFieldWithValue(String value) {
return String.format("{\"fields\": [" + FIELD_TEMPLATE_WITH_VALUE + "]\n}",value);
}
private String embedTemplateWithDescription(String description) {
return String.format("{ \"description\": \"%s\"}", description);
}
private String getEmbedPageCount(Integer page) {
return String.format("Page %d", page);
}
private Configuration getNonMockedConfiguration() throws IOException, TemplateException {
return new FreeMarkerConfigurationFactory().createConfiguration();
}
}

View File

@@ -0,0 +1,67 @@
package dev.sheldan.abstracto.templating.service.management;
import dev.sheldan.abstracto.templating.model.database.Template;
import dev.sheldan.abstracto.templating.repository.TemplateRepository;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.time.Duration;
import java.time.Instant;
import java.util.Optional;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class TemplateManagementServiceBeanTest {
@InjectMocks
private TemplateManagementServiceBean templateManagementServiceBean;
@Mock
private TemplateRepository repository;
private static final String TEMPLATE_KEY = "templateKey";
private static final String TEMPLATE_SOURCE = "source";
@Test
public void testFindByKey() {
when(repository.findById(TEMPLATE_KEY)).thenReturn(Optional.of(getTemplate()));
Optional<Template> templateByKey = templateManagementServiceBean.getTemplateByKey(TEMPLATE_KEY);
templateByKey.ifPresent(template -> {
Assert.assertEquals(TEMPLATE_KEY, template.getKey());
Assert.assertEquals(TEMPLATE_SOURCE, template.getContent());
});
Assert.assertTrue(templateByKey.isPresent());
}
@Test
public void testNotFindTemplate() {
when(repository.findById(TEMPLATE_KEY)).thenReturn(Optional.empty());
Optional<Template> templateByKey = templateManagementServiceBean.getTemplateByKey(TEMPLATE_KEY);
Assert.assertFalse(templateByKey.isPresent());
}
@Test
public void testTemplateExists() {
when(repository.existsById(TEMPLATE_KEY)).thenReturn(true);
Assert.assertTrue(templateManagementServiceBean.templateExists(TEMPLATE_KEY));
}
@Test
public void testCreateTemplate() {
Template template = templateManagementServiceBean.createTemplate(TEMPLATE_KEY, TEMPLATE_SOURCE);
Assert.assertEquals(template.getContent(), TEMPLATE_SOURCE);
Assert.assertEquals(template.getKey(), TEMPLATE_KEY);
verify(repository, times(1)).save(any(Template.class));
Assert.assertTrue(Duration.between(template.getLastModified(), Instant.now()).getSeconds() < 1);
}
private Template getTemplate() {
return Template.builder().content(TEMPLATE_SOURCE).key(TEMPLATE_KEY).build();
}
}

View File

@@ -0,0 +1,28 @@
{
"author": {
"name": "name",
"avatar": "https://example.com",
"url": "https://example.com"
},
"color" : {
"r": 255,
"g": 255,
"b": 255
},
"description": "description",
"thumbnail": "https://example.com",
"imageURL": "https://example.com",
"fields": [
{
"name": "name",
"value": "value",
"inline": "true"
}
],
"footer": {
"text": "text",
"icon": "https://example.com"
},
"timeStamp": "timestamp",
"additionalMessage": "additionalMessage"
}

View File

@@ -0,0 +1,7 @@
package dev.sheldan.abstracto.templating.exception;
public class TemplatingException extends RuntimeException {
public TemplatingException(Throwable cause) {
super(cause);
}
}