This commit is contained in:
Ludy87 2025-08-09 22:53:30 +02:00
parent 979f302277
commit 4ff940c229
No known key found for this signature in database
GPG Key ID: 92696155E0220F94
17 changed files with 1069 additions and 26 deletions

View File

@ -0,0 +1,17 @@
package stirling.software.common.configuration.interfaces;
import static org.junit.Assert.assertTrue;
import org.junit.jupiter.api.Test;
class ShowAdminInterfaceTest {
// Create a simple implementation for testing
static class TestImpl implements ShowAdminInterface {}
@Test
void getShowUpdateOnlyAdmins_returnsTrueByDefault() {
ShowAdminInterface instance = new TestImpl();
assertTrue(instance.getShowUpdateOnlyAdmins(), "Default should return true");
}
}

View File

@ -18,7 +18,6 @@ class ApplicationPropertiesDynamicYamlPropertySourceTest {
@Test
void loads_yaml_into_environment() throws Exception {
// YAML-Config in Temp-Datei schreiben
String yaml =
""
+ "ui:\n"
@ -28,7 +27,6 @@ class ApplicationPropertiesDynamicYamlPropertySourceTest {
Path tmp = Files.createTempFile("spdf-settings-", ".yml");
Files.writeString(tmp, yaml);
// Pfad per statischem Mock liefern
try (MockedStatic<InstallationPathConfig> mocked =
Mockito.mockStatic(InstallationPathConfig.class)) {
mocked.when(InstallationPathConfig::getSettingsPath).thenReturn(tmp.toString());
@ -36,7 +34,7 @@ class ApplicationPropertiesDynamicYamlPropertySourceTest {
ConfigurableEnvironment env = new StandardEnvironment();
ApplicationProperties props = new ApplicationProperties();
props.dynamicYamlPropertySource(env); // fügt PropertySource an erster Stelle ein
props.dynamicYamlPropertySource(env);
assertEquals("My App", env.getProperty("ui.appName"));
assertEquals("true", env.getProperty("system.enableAnalytics"));

View File

@ -1,27 +1,32 @@
package stirling.software.common.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.Arrays;
import java.util.List;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.service.CustomPDFDocumentFactory;
@ -31,32 +36,87 @@ public class PdfUtilsTest {
@Test
void testTextToPageSize() {
assertEquals(PDRectangle.A0, PdfUtils.textToPageSize("A0"));
assertEquals(PDRectangle.A1, PdfUtils.textToPageSize("A1"));
assertEquals(PDRectangle.A2, PdfUtils.textToPageSize("A2"));
assertEquals(PDRectangle.A3, PdfUtils.textToPageSize("A3"));
assertEquals(PDRectangle.A4, PdfUtils.textToPageSize("A4"));
assertEquals(PDRectangle.A5, PdfUtils.textToPageSize("A5"));
assertEquals(PDRectangle.A6, PdfUtils.textToPageSize("A6"));
assertEquals(PDRectangle.LETTER, PdfUtils.textToPageSize("LETTER"));
assertEquals(PDRectangle.LEGAL, PdfUtils.textToPageSize("LEGAL"));
assertThrows(IllegalArgumentException.class, () -> PdfUtils.textToPageSize("INVALID"));
}
@Test
void testHasImagesOnPage() throws IOException {
// Mock a PDPage and its resources
PDPage page = Mockito.mock(PDPage.class);
PDResources resources = Mockito.mock(PDResources.class);
Mockito.when(page.getResources()).thenReturn(resources);
void testGetAllImages() throws Exception {
// Root resources
PDResources root = mock(PDResources.class);
// Case 1: No images in resources
Mockito.when(resources.getXObjectNames()).thenReturn(Collections.emptySet());
assertFalse(PdfUtils.hasImagesOnPage(page));
COSName im1 = COSName.getPDFName("Im1");
COSName form1 = COSName.getPDFName("Form1");
COSName other1 = COSName.getPDFName("Other1");
when(root.getXObjectNames()).thenReturn(Arrays.asList(im1, form1, other1));
// Case 2: Resources with an image
Set<COSName> xObjectNames = new HashSet<>();
COSName cosName = Mockito.mock(COSName.class);
xObjectNames.add(cosName);
// Direct image at root
PDImageXObject imgXObj1 = mock(PDImageXObject.class);
BufferedImage img1 = new BufferedImage(2, 2, BufferedImage.TYPE_INT_ARGB);
when(imgXObj1.getImage()).thenReturn(img1);
when(root.getXObject(im1)).thenReturn(imgXObj1);
PDImageXObject imageXObject = Mockito.mock(PDImageXObject.class);
Mockito.when(resources.getXObjectNames()).thenReturn(xObjectNames);
Mockito.when(resources.getXObject(cosName)).thenReturn(imageXObject);
// "Other" XObject that should be ignored
PDXObject otherXObj = mock(PDXObject.class);
when(root.getXObject(other1)).thenReturn(otherXObj);
assertTrue(PdfUtils.hasImagesOnPage(page));
// Form XObject with its own resources
PDFormXObject formXObj = mock(PDFormXObject.class);
PDResources formRes = mock(PDResources.class);
when(formXObj.getResources()).thenReturn(formRes);
when(root.getXObject(form1)).thenReturn(formXObj);
// Inside the form: one image and a nested form
COSName im2 = COSName.getPDFName("Im2");
COSName nestedForm = COSName.getPDFName("NestedForm");
when(formRes.getXObjectNames()).thenReturn(Arrays.asList(im2, nestedForm));
PDImageXObject imgXObj2 = mock(PDImageXObject.class);
BufferedImage img2 = new BufferedImage(3, 3, BufferedImage.TYPE_INT_RGB);
when(imgXObj2.getImage()).thenReturn(img2);
when(formRes.getXObject(im2)).thenReturn(imgXObj2);
PDFormXObject nestedFormXObj = mock(PDFormXObject.class);
PDResources nestedRes = mock(PDResources.class);
when(nestedFormXObj.getResources()).thenReturn(nestedRes);
when(formRes.getXObject(nestedForm)).thenReturn(nestedFormXObj);
// Deep nest: another image
COSName im3 = COSName.getPDFName("Im3");
when(nestedRes.getXObjectNames()).thenReturn(List.of(im3));
PDImageXObject imgXObj3 = mock(PDImageXObject.class);
BufferedImage img3 = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
when(imgXObj3.getImage()).thenReturn(img3);
when(nestedRes.getXObject(im3)).thenReturn(imgXObj3);
// Act
List<RenderedImage> result = PdfUtils.getAllImages(root);
// Assert
assertEquals(
3, result.size(), "It should find exactly 3 images (root + form + nested form).");
assertTrue(
result.containsAll(List.of(img1, img2, img3)),
"All expected images must be present.");
}
// Helper method to draw a tiny image on a PDF page
private static void drawTinyImage(PDDocument doc, PDPage page) throws IOException {
BufferedImage bi = new BufferedImage(2, 2, BufferedImage.TYPE_INT_RGB);
PDImageXObject ximg = LosslessFactory.createFromImage(doc, bi);
try (PDPageContentStream cs =
new PDPageContentStream(doc, page, AppendMode.APPEND, true, true)) {
cs.drawImage(ximg, 10, 10, 10, 10);
}
}
@Test

View File

@ -200,7 +200,7 @@ public class SPDFApplication {
log.info("Navigate to {}", url);
}
private static String[] getActiveProfile(String[] args) {
protected static String[] getActiveProfile(String[] args) {
// 1. Check for explicitly passed profiles
if (args != null) {
for (String arg : args) {
@ -224,7 +224,7 @@ public class SPDFApplication {
}
}
private static boolean isClassPresent(String className) {
protected static boolean isClassPresent(String className) {
try {
Class.forName(className, false, SPDFApplication.class.getClassLoader());
return true;

View File

@ -1,15 +1,18 @@
package stirling.software.SPDF;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.env.Environment;
import stirling.software.common.configuration.AppConfig;
import stirling.software.common.model.ApplicationProperties;
@ExtendWith(MockitoExtension.class)
@ -21,6 +24,8 @@ public class SPDFApplicationTest {
@InjectMocks private SPDFApplication sPDFApplication;
@Mock private AppConfig appConfig;
@BeforeEach
public void setUp() {
SPDFApplication.setServerPortStatic("8080");
@ -36,4 +41,49 @@ public class SPDFApplicationTest {
public void testGetStaticPort() {
assertEquals("8080", SPDFApplication.getStaticPort());
}
@Test
public void testSetServerPortStaticAuto() {
SPDFApplication.setServerPortStatic("auto");
assertEquals("0", SPDFApplication.getStaticPort());
}
@Test
public void testInit() {
when(appConfig.getBaseUrl()).thenReturn("http://localhost");
when(appConfig.getContextPath()).thenReturn("/app");
when(appConfig.getServerPort()).thenReturn("8080");
sPDFApplication.init();
assertEquals("http://localhost", SPDFApplication.getStaticBaseUrl());
assertEquals("/app", SPDFApplication.getStaticContextPath());
assertEquals("8080", SPDFApplication.getStaticPort());
}
@Test
public void testGetActiveProfileWithArgs() {
String[] args = {"--spring.profiles.active=security"};
String[] profiles = SPDFApplication.getActiveProfile(args);
assertEquals(1, profiles.length);
assertEquals("security", profiles[0]);
}
@Test
@EnabledIfEnvironmentVariable(named = "DISABLE_ADDITIONAL_FEATURES", matches = "true")
public void testGetActiveProfileWithoutArgsAdditionalEnabled() {
String[] args = {};
String[] profiles = SPDFApplication.getActiveProfile(args);
assertEquals(1, profiles.length);
assertEquals("default", profiles[0]);
}
@Test
@EnabledIfEnvironmentVariable(named = "DISABLE_ADDITIONAL_FEATURES", matches = "false")
public void testGetActiveProfileWithoutArgsAdditionalDisabled() {
String[] args = {};
String[] profiles = SPDFApplication.getActiveProfile(args);
assertEquals(1, profiles.length);
assertEquals("default", profiles[0]);
}
}

View File

@ -0,0 +1,59 @@
package stirling.software.SPDF.controller.api;
import static org.hamcrest.Matchers.containsString;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import stirling.software.SPDF.service.LanguageService;
class AdditionalLanguageJsControllerTest {
@Test
void returns_js_with_supported_languages_and_function() throws Exception {
LanguageService lang = mock(LanguageService.class);
// LinkedHashSet für deterministische Reihenfolge im Array
when(lang.getSupportedLanguages())
.thenReturn(new LinkedHashSet<>(List.of("de_DE", "en_GB")));
MockMvc mvc =
MockMvcBuilders.standaloneSetup(new AdditionalLanguageJsController(lang)).build();
mvc.perform(get("/js/additionalLanguageCode.js"))
.andExpect(status().isOk())
.andExpect(content().contentType(new MediaType("application", "javascript")))
.andExpect(
content()
.string(
containsString(
"const supportedLanguages ="
+ " [\"de_DE\",\"en_GB\"];")))
.andExpect(content().string(containsString("function getDetailedLanguageCode()")))
.andExpect(content().string(containsString("return \"en_GB\";")));
verify(lang, times(1)).getSupportedLanguages();
}
@Test
void empty_supported_languages_yields_empty_array() throws Exception {
LanguageService lang = mock(LanguageService.class);
when(lang.getSupportedLanguages()).thenReturn(Set.of());
MockMvc mvc =
MockMvcBuilders.standaloneSetup(new AdditionalLanguageJsController(lang)).build();
mvc.perform(get("/js/additionalLanguageCode.js"))
.andExpect(status().isOk())
.andExpect(content().contentType(new MediaType("application", "javascript")))
.andExpect(content().string(containsString("const supportedLanguages = [];")));
}
}

View File

@ -0,0 +1,99 @@
package stirling.software.SPDF.controller.api;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import stirling.software.SPDF.config.EndpointConfiguration;
import stirling.software.common.configuration.InstallationPathConfig;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.util.GeneralUtils;
class SettingsControllerTest {
private MockMvc mockMvc(ApplicationProperties props, EndpointConfiguration endpoints) {
SettingsController controller = new SettingsController(props, endpoints);
return MockMvcBuilders.standaloneSetup(controller).build();
}
@Test
void update_enable_analytics_returns_208_when_already_set() throws Exception {
ApplicationProperties props = new ApplicationProperties();
props.getSystem().setEnableAnalytics(Boolean.FALSE);
EndpointConfiguration endpoints = mock(EndpointConfiguration.class);
try (MockedStatic<InstallationPathConfig> install =
Mockito.mockStatic(InstallationPathConfig.class);
MockedStatic<GeneralUtils> gen = Mockito.mockStatic(GeneralUtils.class)) {
install.when(InstallationPathConfig::getSettingsPath)
.thenReturn("/etc/spdf/settings.yml");
MockMvc mvc = mockMvc(props, endpoints);
// Act + Assert
mvc.perform(
post("/api/v1/settings/update-enable-analytics")
.contentType(MediaType.APPLICATION_JSON)
.content("true"))
.andExpect(status().isAlreadyReported())
.andExpect(content().string(containsString("Setting has already been set")))
.andExpect(content().string(containsString("/etc/spdf/settings.yml")));
gen.verifyNoInteractions();
}
}
@Test
void update_enable_analytics_sets_value_and_saves_when_not_set() throws Exception {
ApplicationProperties props = new ApplicationProperties();
props.getSystem().setEnableAnalytics(null);
EndpointConfiguration endpoints = mock(EndpointConfiguration.class);
try (MockedStatic<GeneralUtils> gen = Mockito.mockStatic(GeneralUtils.class)) {
MockMvc mvc = mockMvc(props, endpoints);
// Act + Assert
mvc.perform(
post("/api/v1/settings/update-enable-analytics")
.contentType(MediaType.APPLICATION_JSON)
.content("true"))
.andExpect(status().isOk())
.andExpect(content().string("Updated"));
gen.verify(
() -> GeneralUtils.saveKeyToSettings(eq("system.enableAnalytics"), eq(true)));
assertEquals(Boolean.TRUE, props.getSystem().getEnableAnalytics());
}
}
@Test
void get_endpoints_status_returns_map() throws Exception {
ApplicationProperties props = new ApplicationProperties();
EndpointConfiguration endpoints = mock(EndpointConfiguration.class);
when(endpoints.getEndpointStatuses())
.thenReturn(Map.of("convert.pdf.markdown", true, "merge", false));
MockMvc mvc = mockMvc(props, endpoints);
mvc.perform(get("/api/v1/settings/get-endpoints-status"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.['convert.pdf.markdown']").value(true))
.andExpect(jsonPath("$.merge").value(false));
}
}

View File

@ -0,0 +1,104 @@
package stirling.software.SPDF.model;
import static org.junit.jupiter.api.Assertions.*;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
class ApiEndpointTest {
private final ObjectMapper mapper = new ObjectMapper();
private JsonNode postNodeWithParams(String description, String... names) {
ObjectNode post = mapper.createObjectNode();
post.put("description", description);
ArrayNode params = mapper.createArrayNode();
for (String n : names) {
ObjectNode p = mapper.createObjectNode();
if (n != null) {
p.put("name", n);
}
params.add(p);
}
post.set("parameters", params);
return post;
}
@Test
void parses_description_and_validates_required_parameters() {
JsonNode post = postNodeWithParams("Convert PDF to Markdown", "file", "mode");
ApiEndpoint endpoint = new ApiEndpoint("pdfToMd", post);
assertEquals("Convert PDF to Markdown", endpoint.getDescription());
Map<String, Object> provided = new HashMap<>();
provided.put("file", new byte[] {1});
provided.put("mode", "fast");
assertTrue(
endpoint.areParametersValid(provided), "All required keys present should be valid");
}
@Test
void missing_any_required_parameter_returns_false() {
JsonNode post = postNodeWithParams("desc", "file", "mode");
ApiEndpoint endpoint = new ApiEndpoint("pdfToMd", post);
Map<String, Object> provided = new HashMap<>();
provided.put("file", new byte[] {1});
assertFalse(endpoint.areParametersValid(provided));
}
@Test
void extra_parameters_are_ignored_if_required_are_present() {
JsonNode post = postNodeWithParams("desc", "file");
ApiEndpoint endpoint = new ApiEndpoint("x", post);
Map<String, Object> provided = new HashMap<>();
provided.put("file", new byte[] {1});
provided.put("extra", 123);
assertTrue(endpoint.areParametersValid(provided));
}
@Test
void no_parameters_defined_accepts_empty_input() {
JsonNode postEmptyArray = postNodeWithParams("desc" /* no names */);
ApiEndpoint endpointA = new ApiEndpoint("a", postEmptyArray);
assertTrue(endpointA.areParametersValid(Map.of()));
ObjectNode postNoField = mapper.createObjectNode();
postNoField.put("description", "desc");
ApiEndpoint endpointB = new ApiEndpoint("b", postNoField);
assertTrue(endpointB.areParametersValid(Map.of()));
}
@Test
void parameter_without_name_creates_empty_required_key() {
JsonNode post = postNodeWithParams("desc", (String) null);
ApiEndpoint endpoint = new ApiEndpoint("y", post);
assertFalse(endpoint.areParametersValid(Map.of()));
assertTrue(endpoint.areParametersValid(Map.of("", 42)));
}
@Test
void toString_contains_name_and_parameter_names() {
JsonNode post = postNodeWithParams("desc", "file", "mode");
ApiEndpoint endpoint = new ApiEndpoint("pdfToMd", post);
String s = endpoint.toString();
assertTrue(s.contains("pdfToMd"));
assertTrue(s.contains("file"));
assertTrue(s.contains("mode"));
}
}

View File

@ -0,0 +1,54 @@
package stirling.software.SPDF.model;
import static org.junit.jupiter.api.Assertions.*;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
class SortTypesTest {
private static final Set<String> EXPECTED =
Set.of(
"CUSTOM",
"REVERSE_ORDER",
"DUPLEX_SORT",
"BOOKLET_SORT",
"SIDE_STITCH_BOOKLET_SORT",
"ODD_EVEN_SPLIT",
"ODD_EVEN_MERGE",
"REMOVE_FIRST",
"REMOVE_LAST",
"REMOVE_FIRST_AND_LAST",
"DUPLICATE");
@Test
void contains_exactly_expected_constants() {
Set<String> actual =
Arrays.stream(SortTypes.values()).map(Enum::name).collect(Collectors.toSet());
assertEquals(
EXPECTED,
actual,
() -> "Enum constants mismatch.\nExpected: " + EXPECTED + "\nActual: " + actual);
}
@ParameterizedTest
@EnumSource(SortTypes.class)
void valueOf_roundtrip(SortTypes type) {
assertEquals(type, SortTypes.valueOf(type.name()));
}
@Test
void names_are_unique_and_uppercase() {
String[] names = Arrays.stream(SortTypes.values()).map(Enum::name).toArray(String[]::new);
assertEquals(names.length, Set.of(names).size(), "Duplicate enum names?");
for (String n : names) {
assertEquals(n, n.toUpperCase(), "Enum name not uppercase: " + n);
}
}
}

View File

@ -0,0 +1,84 @@
package stirling.software.SPDF.model.api;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.List;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class PDFWithPageNumsTest {
private PDFWithPageNums pdfWithPageNums;
private PDDocument mockDocument;
@BeforeEach
void setUp() {
pdfWithPageNums = new PDFWithPageNums();
mockDocument = mock(PDDocument.class);
}
@Test
void testGetPageNumbersList_AllPages() {
pdfWithPageNums.setPageNumbers("all");
when(mockDocument.getNumberOfPages()).thenReturn(10);
List<Integer> result = pdfWithPageNums.getPageNumbersList(mockDocument, true);
assertEquals(List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), result);
}
@Test
void testGetPageNumbersList_135_7Pages() {
pdfWithPageNums.setPageNumbers("1,3,5-7");
when(mockDocument.getNumberOfPages()).thenReturn(10);
List<Integer> result = pdfWithPageNums.getPageNumbersList(mockDocument, true);
assertEquals(List.of(1, 3, 5, 6, 7), result);
}
@Test
void testGetPageNumbersList_2nPlus1Pages() {
pdfWithPageNums.setPageNumbers("2n+1");
when(mockDocument.getNumberOfPages()).thenReturn(10);
List<Integer> result = pdfWithPageNums.getPageNumbersList(mockDocument, true);
assertEquals(List.of(3, 5, 7, 9), result);
}
@Test
void testGetPageNumbersList_3nPages() {
pdfWithPageNums.setPageNumbers("3n");
when(mockDocument.getNumberOfPages()).thenReturn(10);
List<Integer> result = pdfWithPageNums.getPageNumbersList(mockDocument, true);
assertEquals(List.of(3, 6, 9), result);
}
@Test
void testGetPageNumbersList_EmptyInput() {
pdfWithPageNums.setPageNumbers("");
when(mockDocument.getNumberOfPages()).thenReturn(10);
List<Integer> result = pdfWithPageNums.getPageNumbersList(mockDocument, true);
assertTrue(result.isEmpty());
}
@Test
void testGetPageNumbersList_InvalidInput() {
pdfWithPageNums.setPageNumbers("invalid");
when(mockDocument.getNumberOfPages()).thenReturn(10);
assertThrows(
IllegalArgumentException.class,
() -> {
pdfWithPageNums.getPageNumbersList(mockDocument, true);
});
}
}

View File

@ -0,0 +1,170 @@
package stirling.software.SPDF.model.api.misc;
import static org.junit.jupiter.api.Assertions.*;
import java.util.Set;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockMultipartFile;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
class ScannerEffectRequestTest {
private static Validator validator;
@BeforeAll
static void setUpValidator() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
@Test
void defaults_are_set_correctly() {
ScannerEffectRequest req = new ScannerEffectRequest();
assertNull(req.getFileInput());
assertEquals(ScannerEffectRequest.Quality.high, req.getQuality());
assertEquals(ScannerEffectRequest.Rotation.slight, req.getRotation());
assertEquals(ScannerEffectRequest.Colorspace.grayscale, req.getColorspace());
assertEquals(20, req.getBorder());
assertEquals(0, req.getRotate());
assertEquals(2, req.getRotateVariance());
assertEquals(1.0f, req.getBrightness(), 0.0001);
assertEquals(1.0f, req.getContrast(), 0.0001);
assertEquals(1.0f, req.getBlur(), 0.0001);
assertEquals(8.0f, req.getNoise(), 0.0001);
assertFalse(req.isYellowish());
assertEquals(300, req.getResolution());
assertFalse(req.isAdvancedEnabled());
}
@Test
void bean_validation_detects_missing_required_fields() {
ScannerEffectRequest req = new ScannerEffectRequest();
// Defaults: fileInput=null, quality != null, rotation != null
Set<ConstraintViolation<ScannerEffectRequest>> violations = validator.validate(req);
assertTrue(
violations.stream().anyMatch(v -> "File input is required".equals(v.getMessage())),
"Expected violation for missing fileInput");
// Make also quality and rotation invalid
req.setQuality(null);
req.setRotation(null);
violations = validator.validate(req);
assertTrue(
violations.stream().anyMatch(v -> "File input is required".equals(v.getMessage())));
assertTrue(violations.stream().anyMatch(v -> "Quality is required".equals(v.getMessage())));
assertTrue(
violations.stream().anyMatch(v -> "Rotation is required".equals(v.getMessage())));
}
@Test
void quality_value_mapping_is_correct() {
ScannerEffectRequest req = new ScannerEffectRequest();
req.setQuality(ScannerEffectRequest.Quality.low);
assertEquals(30, req.getQualityValue());
req.setQuality(ScannerEffectRequest.Quality.medium);
assertEquals(60, req.getQualityValue());
req.setQuality(ScannerEffectRequest.Quality.high);
assertEquals(100, req.getQualityValue());
}
@Test
void rotation_value_mapping_is_correct() {
ScannerEffectRequest req = new ScannerEffectRequest();
req.setRotation(ScannerEffectRequest.Rotation.none);
assertEquals(0, req.getRotationValue());
req.setRotation(ScannerEffectRequest.Rotation.slight);
assertEquals(2, req.getRotationValue());
req.setRotation(ScannerEffectRequest.Rotation.moderate);
assertEquals(5, req.getRotationValue());
req.setRotation(ScannerEffectRequest.Rotation.severe);
assertEquals(8, req.getRotationValue());
}
@Test
void high_quality_preset_applies_expected_values() {
ScannerEffectRequest req = new ScannerEffectRequest();
req.applyHighQualityPreset();
assertEquals(0.1f, req.getBlur(), 0.0001);
assertEquals(1.0f, req.getNoise(), 0.0001);
assertEquals(1.02f, req.getBrightness(), 0.0001);
assertEquals(1.05f, req.getContrast(), 0.0001);
assertEquals(600, req.getResolution());
}
@Test
void medium_quality_preset_applies_expected_values() {
ScannerEffectRequest req = new ScannerEffectRequest();
req.applyMediumQualityPreset();
assertEquals(0.5f, req.getBlur(), 0.0001);
assertEquals(3.0f, req.getNoise(), 0.0001);
assertEquals(1.05f, req.getBrightness(), 0.0001);
assertEquals(1.1f, req.getContrast(), 0.0001);
assertEquals(300, req.getResolution());
}
@Test
void low_quality_preset_applies_expected_values() {
ScannerEffectRequest req = new ScannerEffectRequest();
req.applyLowQualityPreset();
assertEquals(1.0f, req.getBlur(), 0.0001);
assertEquals(5.0f, req.getNoise(), 0.0001);
assertEquals(1.1f, req.getBrightness(), 0.0001);
assertEquals(1.2f, req.getContrast(), 0.0001);
assertEquals(150, req.getResolution());
}
@Test
void equals_and_hashCode_consider_fileInput() {
ScannerEffectRequest a = new ScannerEffectRequest();
ScannerEffectRequest b = new ScannerEffectRequest();
// same defaults -> equal
assertEquals(a, b);
assertEquals(a.hashCode(), b.hashCode());
// set file only on one -> not equal
MockMultipartFile file =
new MockMultipartFile(
"fileInput", "x.pdf", "application/pdf", new byte[] {1, 2, 3});
a.setFileInput(file);
assertNotEquals(a, b);
assertNotEquals(a.hashCode(), b.hashCode());
}
@Test
void advancedEnabled_flag_roundtrip() {
ScannerEffectRequest req = new ScannerEffectRequest();
assertFalse(req.isAdvancedEnabled());
req.setAdvancedEnabled(true);
assertTrue(req.isAdvancedEnabled());
}
@Test
void colorspace_roundtrip() {
ScannerEffectRequest req = new ScannerEffectRequest();
req.setColorspace(ScannerEffectRequest.Colorspace.color);
assertEquals(ScannerEffectRequest.Colorspace.color, req.getColorspace());
}
}

View File

@ -0,0 +1,25 @@
package stirling.software.SPDF.pdf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.apache.commons.csv.CSVFormat;
import org.junit.jupiter.api.Test;
class FlexibleCSVWriterTest {
@Test
void testDefaultConstructor() {
FlexibleCSVWriter writer = new FlexibleCSVWriter();
assertNotNull(writer, "The FlexibleCSVWriter instance should not be null");
}
@Test
void testConstructorWithCSVFormat() {
CSVFormat csvFormat = CSVFormat.DEFAULT;
FlexibleCSVWriter writer = new FlexibleCSVWriter(csvFormat);
assertNotNull(
writer,
"The FlexibleCSVWriter instance should not be null when initialized with"
+ " CSVFormat");
}
}

View File

@ -0,0 +1,124 @@
package stirling.software.SPDF.service;
import static org.junit.jupiter.api.Assertions.*;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
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 com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletContext;
import stirling.software.SPDF.model.ApiEndpoint;
import stirling.software.common.service.UserServiceInterface;
@ExtendWith(MockitoExtension.class)
class ApiDocServiceTest {
@Mock ServletContext servletContext;
@Mock UserServiceInterface userService;
ApiDocService apiDocService;
ObjectMapper mapper = new ObjectMapper();
@BeforeEach
void setUp() {
apiDocService = new ApiDocService(servletContext, userService);
}
private void setApiDocumentation(Map<String, ApiEndpoint> docs) throws Exception {
Field field = ApiDocService.class.getDeclaredField("apiDocumentation");
field.setAccessible(true);
@SuppressWarnings("unchecked")
Map<String, ApiEndpoint> map = (Map<String, ApiEndpoint>) field.get(apiDocService);
map.clear();
map.putAll(docs);
}
private void setApiDocsJsonRootNode() throws Exception {
Field field = ApiDocService.class.getDeclaredField("apiDocsJsonRootNode");
field.setAccessible(true);
field.set(apiDocService, mapper.createObjectNode());
}
@Test
void getExtensionTypesReturnsExpectedList() throws Exception {
String json = "{\"description\": \"Output:PDF\"}";
JsonNode postNode = mapper.readTree(json);
ApiEndpoint endpoint = new ApiEndpoint("/test", postNode);
setApiDocumentation(Map.of("/test", endpoint));
setApiDocsJsonRootNode();
List<String> extensions = apiDocService.getExtensionTypes(true, "/test");
assertEquals(List.of("pdf"), extensions);
}
@Test
void getExtensionTypesHandlesUnknownOperation() throws Exception {
setApiDocumentation(Map.of());
List<String> extensions = apiDocService.getExtensionTypes(true, "/unknown");
assertNull(extensions);
}
@Test
void isValidOperationChecksRequiredParameters() throws Exception {
String json =
"{\"description\": \"desc\", \"parameters\": [{\"name\":\"param1\"}, {\"name\":\"param2\"}]}";
JsonNode postNode = mapper.readTree(json);
ApiEndpoint endpoint = new ApiEndpoint("/op", postNode);
setApiDocumentation(Map.of("/op", endpoint));
setApiDocsJsonRootNode();
assertTrue(apiDocService.isValidOperation("/op", Map.of("param1", "a", "param2", "b")));
assertFalse(apiDocService.isValidOperation("/op", Map.of("param1", "a")));
}
@Test
void isValidOperationHandlesUnknownOperation() throws Exception {
setApiDocumentation(Map.of());
assertFalse(apiDocService.isValidOperation("/unknown", Map.of("param1", "a")));
}
@Test
void isMultiInputDetectsTypeMI() throws Exception {
String json = "{\"description\": \"Type:MI\"}";
JsonNode postNode = mapper.readTree(json);
ApiEndpoint endpoint = new ApiEndpoint("/multi", postNode);
setApiDocumentation(Map.of("/multi", endpoint));
setApiDocsJsonRootNode();
assertTrue(apiDocService.isMultiInput("/multi"));
}
@Test
void isMultiInputDetectsUnknownOperation() throws Exception {
setApiDocumentation(Map.of());
assertFalse(apiDocService.isMultiInput("/unknown"));
}
@Test
void isMultiInputHandlesNoDescription() throws Exception {
String json = "{\"parameters\": [{\"name\":\"param1\"}, {\"name\":\"param2\"}]}";
JsonNode postNode = mapper.readTree(json);
ApiEndpoint endpoint = new ApiEndpoint("/multi", postNode);
setApiDocumentation(Map.of("/multi", endpoint));
setApiDocsJsonRootNode();
assertFalse(apiDocService.isMultiInput("/multi"));
}
}

View File

@ -1,5 +1,6 @@
package stirling.software.SPDF.service;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.doNothing;
@ -77,6 +78,9 @@ class CertificateValidationServiceTest {
// Then validation should succeed
assertTrue(result, "Certificate with matching issuer and subject should validate");
// Ensure no exceptions are thrown during validation
assertDoesNotThrow(() -> validationService.validateTrustWithCustomCert(null, issuingCert));
}
@Test

View File

@ -1,9 +1,12 @@
package stirling.software.SPDF.service;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.IOException;
@ -16,6 +19,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.test.util.ReflectionTestUtils;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.model.ApplicationProperties.Ui;
@ -145,6 +149,45 @@ class LanguageServiceTest {
return mockResource;
}
@Test
void delegatesToResolverAndReturnsResources() throws IOException {
LanguageService service = new LanguageService(applicationProperties);
PathMatchingResourcePatternResolver resolver =
mock(PathMatchingResourcePatternResolver.class);
Resource r1 = mock(Resource.class);
Resource r2 = mock(Resource.class);
String pattern = "classpath*:messages_*.properties";
when(resolver.getResources(pattern)).thenReturn(new Resource[] {r1, r2});
// Inject the mocked resolver into the private final field
ReflectionTestUtils.setField(service, "resourcePatternResolver", resolver);
Resource[] result = service.getResourcesFromPattern(pattern);
assertArrayEquals(
new Resource[] {r1, r2}, result, "Should return exactly what the resolver returns");
verify(resolver).getResources(pattern);
}
@Test
void propagatesIOExceptionFromResolver() throws IOException {
LanguageService service = new LanguageService(applicationProperties);
PathMatchingResourcePatternResolver resolver =
mock(PathMatchingResourcePatternResolver.class);
String pattern = "classpath*:does-not-matter";
when(resolver.getResources(pattern)).thenThrow(new IOException("boom"));
// Inject the mocked resolver into the private final field
ReflectionTestUtils.setField(service, "resourcePatternResolver", resolver);
assertThrows(
IOException.class,
() -> service.getResourcesFromPattern(pattern),
"IOException from resolver should propagate");
}
// Test subclass that allows us to control the resource resolver
private static class LanguageServiceForTest extends LanguageService {
private Resource[] mockResources;

View File

@ -0,0 +1,75 @@
package stirling.software.SPDF.service;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import stirling.software.SPDF.config.EndpointInspector;
import stirling.software.common.service.PostHogService;
class MetricsAggregatorServiceTest {
private SimpleMeterRegistry meterRegistry;
private PostHogService postHogService;
private EndpointInspector endpointInspector;
private MetricsAggregatorService metricsAggregatorService;
@BeforeEach
void setUp() {
meterRegistry = new SimpleMeterRegistry();
postHogService = mock(PostHogService.class);
endpointInspector = mock(EndpointInspector.class);
when(endpointInspector.getValidGetEndpoints()).thenReturn(Set.of("/getEndpoint"));
when(endpointInspector.isValidGetEndpoint("/getEndpoint")).thenReturn(true);
metricsAggregatorService =
new MetricsAggregatorService(meterRegistry, postHogService, endpointInspector);
}
@Captor private ArgumentCaptor<Map<String, Object>> captor;
@Test
void testAggregateAndSendMetrics() {
meterRegistry.counter("http.requests", "method", "GET", "uri", "/getEndpoint").increment(3);
meterRegistry.counter("http.requests", "method", "POST", "uri", "/api/v1/do").increment(2);
metricsAggregatorService.aggregateAndSendMetrics();
ArgumentCaptor<Map<String, Object>> captor = ArgumentCaptor.forClass(Map.class);
verify(postHogService).captureEvent(eq("aggregated_metrics"), captor.capture());
Map<String, Object> metrics = captor.getValue();
assertEquals(2, metrics.size());
assertEquals(3.0, (Double) metrics.get("http_requests_GET__getEndpoint"));
assertEquals(2.0, (Double) metrics.get("http_requests_POST__api_v1_do"));
}
@Test
void testAggregateAndSendMetricsSendsOnlyDifferences() {
Counter counter =
meterRegistry.counter("http.requests", "method", "GET", "uri", "/getEndpoint");
counter.increment(5);
metricsAggregatorService.aggregateAndSendMetrics();
reset(postHogService);
counter.increment(2);
metricsAggregatorService.aggregateAndSendMetrics();
ArgumentCaptor<Map<String, Object>> captor = ArgumentCaptor.forClass(Map.class);
verify(postHogService).captureEvent(eq("aggregated_metrics"), captor.capture());
Map<String, Object> metrics = captor.getValue();
assertEquals(1, metrics.size());
assertEquals(2.0, (Double) metrics.get("http_requests_GET__getEndpoint"));
}
}

View File

@ -0,0 +1,77 @@
package stirling.software.SPDF.service.misc;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.io.IOException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.core.io.InputStreamResource;
import org.springframework.web.multipart.MultipartFile;
import stirling.software.SPDF.Factories.ReplaceAndInvertColorFactory;
import stirling.software.common.model.api.misc.HighContrastColorCombination;
import stirling.software.common.model.api.misc.ReplaceAndInvert;
import stirling.software.common.util.misc.ReplaceAndInvertColorStrategy;
class ReplaceAndInvertColorServiceTest {
@Mock private ReplaceAndInvertColorFactory replaceAndInvertColorFactory;
@Mock private MultipartFile file;
@Mock private ReplaceAndInvertColorStrategy replaceAndInvertColorStrategy;
@InjectMocks private ReplaceAndInvertColorService replaceAndInvertColorService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
void testReplaceAndInvertColor() throws IOException {
// Arrange
ReplaceAndInvert replaceAndInvertOption = mock(ReplaceAndInvert.class);
HighContrastColorCombination highContrastColorCombination =
mock(HighContrastColorCombination.class);
String backGroundColor = "#FFFFFF";
String textColor = "#000000";
when(replaceAndInvertColorFactory.replaceAndInvert(
file,
replaceAndInvertOption,
highContrastColorCombination,
backGroundColor,
textColor))
.thenReturn(replaceAndInvertColorStrategy);
InputStreamResource expectedResource = mock(InputStreamResource.class);
when(replaceAndInvertColorStrategy.replace()).thenReturn(expectedResource);
// Act
InputStreamResource result =
replaceAndInvertColorService.replaceAndInvertColor(
file,
replaceAndInvertOption,
highContrastColorCombination,
backGroundColor,
textColor);
// Assert
assertNotNull(result);
assertEquals(expectedResource, result);
verify(replaceAndInvertColorFactory, times(1))
.replaceAndInvert(
file,
replaceAndInvertOption,
highContrastColorCombination,
backGroundColor,
textColor);
verify(replaceAndInvertColorStrategy, times(1)).replace();
}
}