diff --git a/.gitignore b/.gitignore index 1af60c9d4..db42564bb 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,6 @@ apikit-tools/apikit-studio-plugin/org.mule.tooling.apikit.deps/lib/ apikit-tools/apikit-studio-plugin/org.mule.tooling.apikit/examples/ build.log .mule +.codegenie +.vscode/ mule-apikit-module/logs/ diff --git a/build.yaml b/build.yaml index 2968666af..c47e10a8c 100644 --- a/build.yaml +++ b/build.yaml @@ -2,6 +2,15 @@ enableAllureTestReportStage: false mavenSettingsXmlId: ab7820eb-e393-4e88-9962-92104044ed75 projectType: extension additionalTestConfigs: - testsJava17: + jdk17-4.9.x: testJdkTool: OPEN-JDK17 - mavenAdditionalArgs: -DruntimeProduct=MULE_EE -DruntimeVersion=4.6.0 + mavenAdditionalArgs: -DruntimeVersion=4.9.7-rc2 -DruntimeProduct=MULE_EE + jdk17-4.8.x: + testJdkTool: OPEN-JDK17 + mavenAdditionalArgs: -DruntimeVersion=4.8.5 -DruntimeProduct=MULE_EE + jdk17-4.7.x: + testJdkTool: OPEN-JDK17 + mavenAdditionalArgs: -DruntimeVersion=4.7.4 -DruntimeProduct=MULE_EE + jdk17-4.6.x: + testJdkTool: OPEN-JDK17 + mavenAdditionalArgs: -DruntimeVersion=4.6.16 -DruntimeProduct=MULE_EE diff --git a/pom.xml b/pom.xml index ee631e62f..bcb724078 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ crafted 1.1.5 - 1.9.0 + 1.10.3 1.1.6 2.1.8 @@ -35,9 +35,10 @@ 2.15.1 4.4 2.17.1 + 0.7.5 - 2.7.9 + 3.0.0-SNAPSHOT 6.6.73 @@ -45,9 +46,9 @@ src/test/munit ${basedir}/target/test-mule/munit - 1.2.0 - 3.1.0 - 3.1.0 + 1.5.0 + 3.4.0 + 3.3.2 3.0.2 5.8.0 0.8.10 @@ -116,6 +117,12 @@ ${mule.version} provided + + org.mule.sdk + mule-sdk-api + ${sdk-api.version} + provided + org.mule.apikit diff --git a/src/main/java/org/mule/module/apikit/api/deserializing/ArrayHeaderDelimiter.java b/src/main/java/org/mule/module/apikit/api/deserializing/ArrayHeaderDelimiter.java index 402863ab4..36295841e 100644 --- a/src/main/java/org/mule/module/apikit/api/deserializing/ArrayHeaderDelimiter.java +++ b/src/main/java/org/mule/module/apikit/api/deserializing/ArrayHeaderDelimiter.java @@ -11,7 +11,8 @@ */ public enum ArrayHeaderDelimiter { - COMMA(','); + COMMA(','), + SEMICOLON(';'); private final char delimiter; diff --git a/src/main/java/org/mule/module/apikit/api/uri/URIResolver.java b/src/main/java/org/mule/module/apikit/api/uri/URIResolver.java index a8bf10fb5..178d4a8ec 100644 --- a/src/main/java/org/mule/module/apikit/api/uri/URIResolver.java +++ b/src/main/java/org/mule/module/apikit/api/uri/URIResolver.java @@ -225,13 +225,24 @@ private URIPattern findBest(Set patterns) { } URIPattern best = null; for (URIPattern p : patterns) { - if (p.match(this._uri) && (!this._uri.endsWith("/") || p.toString().endsWith("/"))) { + if (p.match(this._uri) && (!this._uri.endsWith("/") || p.toString().endsWith("/"))) if (best == null || p.score() > best.score()) { best = p; } - } + } + if (best == null) { + best = findBestMethodWithEmptyParams(patterns); } return best; } + private URIPattern findBestMethodWithEmptyParams(Set patterns) { + URIPattern currentBest = null; + for (URIPattern p : patterns) { + if (p.match(this._uri) && (currentBest == null || p.score() > currentBest.score())) { + currentBest = p; + } + } + return currentBest; + } } diff --git a/src/main/java/org/mule/module/apikit/deserializing/MimeType.java b/src/main/java/org/mule/module/apikit/deserializing/MimeType.java index baa4e1ccd..12e70f5c8 100644 --- a/src/main/java/org/mule/module/apikit/deserializing/MimeType.java +++ b/src/main/java/org/mule/module/apikit/deserializing/MimeType.java @@ -12,6 +12,8 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; +import static org.mule.module.apikit.api.deserializing.ArrayHeaderDelimiter.SEMICOLON; + public class MimeType { private String type; @@ -228,9 +230,13 @@ private String nextQuotedString() throws MimeTypeParseException { private boolean trySkip(char expected) { if (atEnd() || peek() != expected) { return false; + } else if ((peek() == SEMICOLON.getDelimiterChar() && i + 1 == input.length())) { + i++; + return false; + } else { + skip(); + return true; } - skip(); - return true; } private void skip(char expected) throws MimeTypeParseException { diff --git a/src/main/java/org/mule/module/apikit/input/stream/RewindableInputStream.java b/src/main/java/org/mule/module/apikit/input/stream/RewindableInputStream.java index fa44edac9..15fdbe37f 100644 --- a/src/main/java/org/mule/module/apikit/input/stream/RewindableInputStream.java +++ b/src/main/java/org/mule/module/apikit/input/stream/RewindableInputStream.java @@ -63,6 +63,7 @@ public RewindableInputStream(InputStream in) { this.in = in; } + @Override public void close() throws IOException { if (saving) { curBlockAvail = 0; diff --git a/src/main/java/org/mule/module/apikit/validation/body/form/transformation/MultipartBuilder.java b/src/main/java/org/mule/module/apikit/validation/body/form/transformation/MultipartBuilder.java index 254ac91dd..c2689d1f9 100644 --- a/src/main/java/org/mule/module/apikit/validation/body/form/transformation/MultipartBuilder.java +++ b/src/main/java/org/mule/module/apikit/validation/body/form/transformation/MultipartBuilder.java @@ -17,6 +17,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import java.util.OptionalLong; import java.util.Set; import java.util.regex.Matcher; @@ -91,7 +92,7 @@ public Multipart build() throws InvalidFormParameterException { APIKitMultipartStream multipartStream = new APIKitMultipartStream(inputStream, boundary.getBytes(MIME.UTF8_CHARSET), BUFFER_SIZE, sizeLimit); - Set parametersInPayload = new HashSet<>(); + Map parametersInPayloadToCount = new HashMap<>(); MultipartEntityBuilder multipartEntityBuilder = defaultValues.isEmpty() && cursorProvider != null ? new MultipartEntityBuilderWithoutDefaults(contentType, cursorProvider, boundary, sizeLimit, byteLength) @@ -106,16 +107,16 @@ public Multipart build() throws InvalidFormParameterException { String fileName = getFileName(headers); String contentType = getContentType(headers); - parametersInPayload.add(name); + parametersInPayloadToCount.put(name, parametersInPayloadToCount.getOrDefault(name, 0) + 1); multipartEntityBuilder.handlePart(multipartStream, formParameters.get(name), name, contentType, fileName, headers); - nextPart = multipartStream.readBoundary(); + nextPart = multipartStream.readBoundary(); //Checking the next part items here multipartEntityBuilder.handleBoundary(false); } for (Entry defaultValue : defaultValues.entrySet()) { - if (!parametersInPayload.contains(defaultValue.getKey())) { + if (!parametersInPayloadToCount.containsKey(defaultValue.getKey())) { multipartEntityBuilder.addDefault(defaultValue.getKey(), defaultValue.getValue()); multipartEntityBuilder.handleBoundary(false); } @@ -125,9 +126,18 @@ public Multipart build() throws InvalidFormParameterException { multipartStream.readEpilogue(multipartEntityBuilder); for (Entry formParameter : formParameters.entrySet()) { - if (!parametersInPayload.contains(formParameter.getKey()) && formParameter.getValue().isRequired() + if (!parametersInPayloadToCount.containsKey(formParameter.getKey()) && formParameter.getValue().isRequired() && formParameter.getValue().getDefaultValues().isEmpty()) { - throw new InvalidFormParameterException("Required form parameter " + formParameter.getKey() + " not specified"); + throw new InvalidFormParameterException("Required form parameter " + formParameter.getKey() + " not specified");//We can also validate the minItems and maxItem count here + } else if (parametersInPayloadToCount.containsKey(formParameter.getKey()) && formParameter.getValue().isRequired()) { + Optional minItemsCount = formParameter.getValue().getMinItems(); + if (minItemsCount.isPresent() && (minItemsCount.get() > parametersInPayloadToCount.get(formParameter.getKey()))) { + throw new InvalidFormParameterException("parameter does not comply with minItems for " + formParameter.getKey()); + } + Optional maxItemsCount = formParameter.getValue().getMaxItems(); + if (maxItemsCount.isPresent() && parametersInPayloadToCount.get(formParameter.getKey()) > maxItemsCount.get()) { + throw new InvalidFormParameterException("parameter does not comply with maxItems for " + formParameter.getKey()); + } } } diff --git a/src/test/java/org/mule/module/apikit/ConfigurationTestCase.java b/src/test/java/org/mule/module/apikit/ConfigurationTestCase.java index f305d9569..7f4c94f81 100644 --- a/src/test/java/org/mule/module/apikit/ConfigurationTestCase.java +++ b/src/test/java/org/mule/module/apikit/ConfigurationTestCase.java @@ -10,7 +10,9 @@ import static org.hamcrest.core.IsEqual.equalTo; import org.junit.Test; +import org.junit.jupiter.api.Assertions; import org.mule.apikit.ApiType; +import org.mule.module.apikit.exception.NotAcceptableException; public class ConfigurationTestCase { @@ -20,4 +22,10 @@ public void avoidNullPointerWhenConfigNotInitialised() { assertThat(configuration.getType(), equalTo(ApiType.AMF)); } + + @Test + public void testNotAcceptableException() { + NotAcceptableException notAcceptableException = new NotAcceptableException(); + Assertions.assertEquals(notAcceptableException.getStringRepresentation(), NotAcceptableException.STRING_REPRESENTATION); + } } diff --git a/src/test/java/org/mule/module/apikit/RoutingTableTestCase.java b/src/test/java/org/mule/module/apikit/RoutingTableTestCase.java index b4922cf7a..02149f18f 100644 --- a/src/test/java/org/mule/module/apikit/RoutingTableTestCase.java +++ b/src/test/java/org/mule/module/apikit/RoutingTableTestCase.java @@ -102,14 +102,14 @@ public void URIWithTrailingForwardSlashAreNotMatched() { patterns.add(new URIPattern("/api/")); URIResolver resolver1 = new URIResolver("/api/hello/"); - Assert.assertNull(resolver1.find(patterns, URIResolver.MatchRule.BEST_MATCH)); + Assert.assertNotNull(resolver1.find(patterns, URIResolver.MatchRule.BEST_MATCH)); URIResolver resolver3 = new URIResolver("/api"); Assert.assertNull(resolver3.find(patterns, URIResolver.MatchRule.BEST_MATCH)); patterns.add(new URIPattern("/{param}")); URIResolver resolver2 = new URIResolver("/"); - Assert.assertNull(resolver2.find(patterns, URIResolver.MatchRule.BEST_MATCH)); + Assert.assertNotNull(resolver2.find(patterns, URIResolver.MatchRule.BEST_MATCH)); } @Test @@ -127,4 +127,57 @@ public void getResourceByString() { Assert.assertNotNull(routingTable.getResource("/single-resource")); Assert.assertNotNull(routingTable.getResource("/api/sub-resource")); } + + @Test + public void testFindBestMethodWithEmptyParams() { + // Test Case 1: Empty parameter in the last + HashSet patterns1 = new HashSet<>(); + patterns1.add(new URIPattern("/api/users/{id}")); + + URIResolver resolver1 = new URIResolver("/api/users/"); + URIPattern bestPattern1 = resolver1.find(patterns1, URIResolver.MatchRule.BEST_MATCH); + Assert.assertNotNull(bestPattern1); + Assert.assertEquals("/api/users/{id}", bestPattern1.toString()); + + + // Test Case 2: Testing empty parameter in middle + HashSet patterns2 = new HashSet<>(); + patterns2.add(new URIPattern("/users/{id}/posts")); + patterns2.add(new URIPattern("/users/{id}")); + patterns2.add(new URIPattern("/users")); + + URIResolver resolver2 = new URIResolver("/users//posts"); + URIPattern bestPattern2 = resolver2.find(patterns2, URIResolver.MatchRule.BEST_MATCH); + Assert.assertEquals("/users/{id}/posts", bestPattern2.toString()); + + // Test Case 3: Testing multiple empty parameters + HashSet patterns3 = new HashSet<>(); + patterns3.add(new URIPattern("/api/{version}/users/{id}")); + patterns3.add(new URIPattern("/users/{id}")); + patterns3.add(new URIPattern("/users")); + + URIResolver resolver3 = new URIResolver("/api//users/"); + URIPattern bestPattern3 = resolver3.find(patterns3, URIResolver.MatchRule.BEST_MATCH); + Assert.assertEquals("/api/{version}/users/{id}", bestPattern3.toString()); + + // Test Case 4: Testing empty parameter with nested resources + HashSet patterns4 = new HashSet<>(); + patterns4.add(new URIPattern("/users/{id}/posts/{postId}/comments")); + patterns4.add(new URIPattern("/users/{id}/posts/{postId}")); + patterns4.add(new URIPattern("/users/{id}/posts")); + + URIResolver resolver4 = new URIResolver("/users//posts//comments"); + URIPattern bestPattern4 = resolver4.find(patterns4, URIResolver.MatchRule.BEST_MATCH); + Assert.assertEquals("/users/{id}/posts/{postId}/comments", bestPattern4.toString()); + + // Test Case 5: Testing empty parameter with root path + HashSet patterns5 = new HashSet<>(); + patterns5.add(new URIPattern("/{version}")); + patterns5.add(new URIPattern("/")); + patterns5.add(new URIPattern("")); + + URIResolver resolver12 = new URIResolver("//"); + URIPattern bestPattern12 = resolver12.find(patterns5, URIResolver.MatchRule.BEST_MATCH); + Assert.assertNull(bestPattern12); + } } diff --git a/src/test/java/org/mule/module/apikit/deserializing/MimeTypeTest.java b/src/test/java/org/mule/module/apikit/deserializing/MimeTypeTest.java index 052cada5e..717e252c7 100644 --- a/src/test/java/org/mule/module/apikit/deserializing/MimeTypeTest.java +++ b/src/test/java/org/mule/module/apikit/deserializing/MimeTypeTest.java @@ -71,6 +71,14 @@ public void supportsRepeatedParameters() throws MimeTypeParseException { assertEquals(expected, MimeType.from("text/plain; param=value1; param=value2")); } + @Test + public void supportsEndSemiColon() throws MimeTypeParseException { + MimeType expected = new MimeType("text", "plain", + list(new Parameter("param", "value1"), + new Parameter("param", "value2"))); + assertEquals(expected, MimeType.from("text/plain; param=value1; param=value2;")); + } + @Test public void ignoresEscapedQuotesInsideQuotes() throws MimeTypeParseException { MimeType expected = new MimeType("text", "plain", @@ -91,10 +99,8 @@ public void failsIfAdditionalTextIsAtTheEndOfAValidMime() { @Test public void failsIfTextIsMissing() { assertThrows(MimeTypeParseException.class, () -> MimeType.from("text/")); - assertThrows(MimeTypeParseException.class, () -> MimeType.from("text/plain;")); assertThrows(MimeTypeParseException.class, () -> MimeType.from("text/plain; param")); assertThrows(MimeTypeParseException.class, () -> MimeType.from("text/plain; param=")); - assertThrows(MimeTypeParseException.class, () -> MimeType.from("text/plain; param=value;")); } @Test diff --git a/src/test/java/org/mule/module/apikit/error/EventProcessingExceptionHandlerTest.java b/src/test/java/org/mule/module/apikit/error/EventProcessingExceptionHandlerTest.java new file mode 100644 index 000000000..3911a1759 --- /dev/null +++ b/src/test/java/org/mule/module/apikit/error/EventProcessingExceptionHandlerTest.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com + * The software in this package is published under the terms of the CPAL v1.0 + * license, a copy of which has been included with this distribution in the + * LICENSE.txt file. + */ +package org.mule.module.apikit.error; + +import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.mule.runtime.api.event.Event; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; + +public class EventProcessingExceptionHandlerTest { + + @Test + public void testHandle() throws Exception { + MuleMessagingExceptionHandler handler = new MuleMessagingExceptionHandler(); + Event event = mock(Event.class); + Exception exception = mock(Exception.class); + Assertions.assertThrows(IllegalArgumentException.class, () -> handler.handle(event, exception)); + } + + @Test + public void testGetMessagingExceptionConstructor() throws Exception { + MuleMessagingExceptionHandler handler = new MuleMessagingExceptionHandler(); + assertNotNull(handler); + } +} \ No newline at end of file diff --git a/src/test/java/org/mule/module/apikit/input/stream/RewindableInputStreamTest.java b/src/test/java/org/mule/module/apikit/input/stream/RewindableInputStreamTest.java new file mode 100644 index 000000000..21a3b9397 --- /dev/null +++ b/src/test/java/org/mule/module/apikit/input/stream/RewindableInputStreamTest.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com + * The software in this package is published under the terms of the CPAL v1.0 + * license, a copy of which has been included with this distribution in the + * LICENSE.txt file. + */ +package org.mule.module.apikit.input.stream; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mule.module.apikit.validation.body.form.MultipartFormValidatorTest; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.*; + +class RewindableInputStreamTest { + + @Test + void testClose() throws IOException { + RewindableInputStream rewindableInputStream = new RewindableInputStream(new ByteArrayInputStream(MultipartFormValidatorTest.MULTIPART_BODY.getBytes())); + int i = rewindableInputStream.available(); + Assertions.assertTrue(i > 0); + Assertions.assertTrue(rewindableInputStream.canRewind()); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + byte[] data = new byte[1024]; + int nRead; + while ((nRead = rewindableInputStream.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + Assertions.assertTrue(rewindableInputStream.canRewind()); + rewindableInputStream.rewind(); + rewindableInputStream.willNotRewind(); + Assertions.assertThrows(IllegalStateException.class, rewindableInputStream::rewind); + rewindableInputStream.close(); + } + + @Test + void testRewind() throws IOException { + RewindableInputStream rewindableInputStream = new RewindableInputStream(new ByteArrayInputStream(MultipartFormValidatorTest.MULTIPART_BODY.getBytes())); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int nRead; + byte[] data = new byte[1024]; + RewindableInputStream.Block block = new RewindableInputStream.Block(); + block.append(data[0]); + try { + while ((nRead = rewindableInputStream.read()) != -1) { + buffer.write(data, 0, nRead); + } + buffer.flush(); + byte[] byteArray = buffer.toByteArray(); + assertFalse(new String(byteArray, UTF_8).isEmpty()); + rewindableInputStream.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Test + void testRewindableStreamFailure() { + assertThrows(NullPointerException.class, () -> { + RewindableInputStream rewindableInputStream = new RewindableInputStream(null); + }); + } + + @Test + public void testCloseSucces() throws IOException, NoSuchFieldException, IllegalAccessException { + // Create a test input stream + InputStream testInputStream = new InputStream() { + @Override + public int read() throws IOException { + return 0; + } + }; + + // Create a RewindableInputStream based on the test input stream + RewindableInputStream rewindableInputStream = new RewindableInputStream(testInputStream); + + // Close the RewindableInputStream + rewindableInputStream.close(); + Field eofField = RewindableInputStream.class.getDeclaredField("eof"); + eofField.setAccessible(true); + boolean eof = (boolean) eofField.get(rewindableInputStream); + + // Assert that the input stream is closed + assertFalse(eof); + } + + @Test + public void testRewindSuccess() throws IOException { + // Create a test input stream + InputStream testInputStream = new InputStream() { + private int count = 0; + private final int[] data = {1, 2, 3}; + + @Override + public int read() throws IOException { + if (count >= data.length) { + return -1; + } + return data[count++]; + } + }; + + // Create a RewindableInputStream based on the test input stream + RewindableInputStream rewindableInputStream = new RewindableInputStream(testInputStream); + + // Read the first byte + rewindableInputStream.read(); + + // Rewind the input stream + rewindableInputStream.rewind(); + + // Assert that the first byte can be read again + assertEquals(1, rewindableInputStream.read()); + } + + @Test + public void testWillNotRewind() { + // Create a test input stream + InputStream testInputStream = new InputStream() { + @Override + public int read() throws IOException { + return -1; + } + }; + + // Create a RewindableInputStream based on the test input stream + RewindableInputStream rewindableInputStream = new RewindableInputStream(testInputStream); + + // Call willNotRewind() + rewindableInputStream.willNotRewind(); + + // Assert that canRewind() returns false + assertFalse(rewindableInputStream.canRewind()); + } +} \ No newline at end of file diff --git a/src/test/java/org/mule/module/apikit/uri/TokenBaseDummy.java b/src/test/java/org/mule/module/apikit/uri/TokenBaseDummy.java new file mode 100644 index 000000000..7ac75d12f --- /dev/null +++ b/src/test/java/org/mule/module/apikit/uri/TokenBaseDummy.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com + * The software in this package is published under the terms of the CPAL v1.0 + * license, a copy of which has been included with this distribution in the + * LICENSE.txt file. + */ +package org.mule.module.apikit.uri; + +import java.util.Map; + +class TokenBaseDummy extends TokenBase{ + + /** + * Creates a new expansion token. + * + * @param exp The expression corresponding to this URI token. + * @throws NullPointerException If the specified expression is null. + */ + public TokenBaseDummy(String exp) throws NullPointerException { + super(exp); + } + + @Override + public boolean resolve(String expanded, Map values) { + return false; + } + + public static String forTest(String exp) { + return TokenBase.strip(exp); + } +} \ No newline at end of file diff --git a/src/test/java/org/mule/module/apikit/uri/TokenBaseDummyTest.java b/src/test/java/org/mule/module/apikit/uri/TokenBaseDummyTest.java new file mode 100644 index 000000000..1ec5f3861 --- /dev/null +++ b/src/test/java/org/mule/module/apikit/uri/TokenBaseDummyTest.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com + * The software in this package is published under the terms of the CPAL v1.0 + * license, a copy of which has been included with this distribution in the + * LICENSE.txt file. + */ +package org.mule.module.apikit.uri; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class TokenBaseDummyTest { + + @Test + void testCreateTokenBaseFailure() { + Assertions.assertThrows(NullPointerException.class, () -> new TokenBaseDummy(null)); + TokenBaseDummy tokenBaseDummyA = new TokenBaseDummy("testA"); + TokenBaseDummy tokenBaseDummyB = new TokenBaseDummy("testB"); + assertFalse(tokenBaseDummyA.isResolvable()); + assertNotEquals(tokenBaseDummyA, tokenBaseDummyB); + TokenBaseDummy.forTest("{test}"); + } +} \ No newline at end of file diff --git a/src/test/java/org/mule/module/apikit/uri/URICoderTest.java b/src/test/java/org/mule/module/apikit/uri/URICoderTest.java new file mode 100644 index 000000000..cdca903df --- /dev/null +++ b/src/test/java/org/mule/module/apikit/uri/URICoderTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com + * The software in this package is published under the terms of the CPAL v1.0 + * license, a copy of which has been included with this distribution in the + * LICENSE.txt file. + */ +package org.mule.module.apikit.uri; + +import org.junit.jupiter.api.Test; +import org.mule.module.apikit.api.UrlUtils; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +class URICoderTest { + + private Set ESCAPE_CHARS = new HashSet<>(Arrays.asList('/', '{', '}')); + + @Test + void testEncode() { + String input = "https://example.com/path?query=value"; + String expectedOutput = "https%3A//example.com/path%3Fquery%3Dvalue"; + assertEquals(expectedOutput, URICoder.encode(input, ESCAPE_CHARS)); + } + + @Test + void testDecode() { + String input = "https%3A%2F%2Fexample.com%2Fpath%3Fquery%3Dvalue"; + String expectedOutput = "https://example.com/path?query=value"; + assertEquals(expectedOutput, URICoder.decode(input)); + } + + @Test + void testEncodeNull() { + assertEquals("", URICoder.encode("", ESCAPE_CHARS)); + } + + @Test + void testDecodeNull() { + assertEquals("", URICoder.decode("")); + } + +} \ No newline at end of file diff --git a/src/test/java/org/mule/module/apikit/uri/URITemplateSyntaxExceptionTest.java b/src/test/java/org/mule/module/apikit/uri/URITemplateSyntaxExceptionTest.java new file mode 100644 index 000000000..dbb321526 --- /dev/null +++ b/src/test/java/org/mule/module/apikit/uri/URITemplateSyntaxExceptionTest.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com + * The software in this package is published under the terms of the CPAL v1.0 + * license, a copy of which has been included with this distribution in the + * LICENSE.txt file. + */ +package org.mule.module.apikit.uri; + +import org.junit.Test; +import static org.junit.Assert.*; + +public class URITemplateSyntaxExceptionTest { + + @Test + public void testConstructor() { + String input = "testInput"; + String reason = "testReason"; + URITemplateSyntaxException exception = new URITemplateSyntaxException(input, reason); + assertEquals("testReason : testInput", exception.getMessage()); + assertEquals(input, exception.getInput()); + assertEquals(reason, exception.getReason()); + } + + @Test(expected = NullPointerException.class) + public void testConstructorWithNullInput() { + String input = null; + String reason = "testReason"; + URITemplateSyntaxException exception = new URITemplateSyntaxException(input, reason); + } + + @Test(expected = NullPointerException.class) + public void testConstructorWithNullReason() { + String input = "testInput"; + String reason = null; + URITemplateSyntaxException exception = new URITemplateSyntaxException(input, reason); + } + +} \ No newline at end of file diff --git a/src/test/java/org/mule/module/apikit/uri/URITemplateTest.java b/src/test/java/org/mule/module/apikit/uri/URITemplateTest.java new file mode 100644 index 000000000..ae3d2354c --- /dev/null +++ b/src/test/java/org/mule/module/apikit/uri/URITemplateTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com + * The software in this package is published under the terms of the CPAL v1.0 + * license, a copy of which has been included with this distribution in the + * LICENSE.txt file. + */ +package org.mule.module.apikit.uri; + +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.*; + +public class URITemplateTest { + + + @Test + public void testConstructor() { + // Test with a valid template + try { + new URITemplate("/users/{userId}"); + } catch (Exception e) { + fail("Failed to create a valid URI template"); + } + + // Test with a null template + try { + new URITemplate(null); + fail("Expected a NullPointerException for a null template"); + } catch (NullPointerException e) { + // Expected exception + } + } + + @Test + public void testDigest() { + // Test with a valid template + try { + List tokens = URITemplate.digest("/users/{userId}"); + assertNotNull("Token list should not be null", tokens); + assertEquals("Token list should have 2 elements", 2, tokens.size()); + assertEquals("First token should be a literal token", "/users/", tokens.get(0).toString()); + assertEquals("Second token should be a variable token", "{userId}", tokens.get(1).toString()); + } catch (URITemplateSyntaxException e) { + fail("Failed to digest a valid URI template"); + } + } + + @Test + public void testEquals() { + URITemplate template1 = new URITemplate("/users/{userId}"); + URITemplate template2 = new URITemplate("/users/{userId}"); + URITemplate template3 = new URITemplate("/users/{username}"); + + assertEquals("Template 1 should equal itself", template1, template1); + assertEquals("Template 1 should equal template 2", template1, template2); + assertNotEquals("Template 1 should not equal template 3", template1, template3); + assertNotEquals("Template 1 should not equal null", null, template1); + } + + @Test + public void testHashCode() { + URITemplate template = new URITemplate("/users/{userId}"); + assertEquals("Hash code should match", 127 * "/users/{userId}".hashCode() + "/users/{userId}".hashCode(), template.hashCode()); + } + + @Test + public void testToString() { + URITemplate template = new URITemplate("/users/{userId}"); + assertEquals("String representation should match", "/users/{userId}", template.toString()); + } + +} \ No newline at end of file diff --git a/src/test/java/org/mule/module/apikit/uri/VariableTest.java b/src/test/java/org/mule/module/apikit/uri/VariableTest.java new file mode 100644 index 000000000..424e548e4 --- /dev/null +++ b/src/test/java/org/mule/module/apikit/uri/VariableTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com + * The software in this package is published under the terms of the CPAL v1.0 + * license, a copy of which has been included with this distribution in the + * LICENSE.txt file. + */ +package org.mule.module.apikit.uri; + +import org.junit.Test; +import static org.junit.Assert.*; + +public class VariableTest { + + + @Test + public void testVariable() { + Variable variable = new Variable("foo", "bar"); + assertEquals("foo", variable.name()); + assertEquals("bar", variable.defaultValue()); + assertNull(variable.type()); + + variable = new Variable(Variable.Reserved.WILDCARD); + assertEquals("*", variable.name()); + assertEquals("", variable.defaultValue()); + assertNull(variable.type()); + } + + @Test + public void testForm() { + assertEquals(Variable.Form.STRING, Variable.Form.getType("")); + assertEquals(Variable.Form.LIST, Variable.Form.getType("@")); + assertEquals(Variable.Form.MAP, Variable.Form.getType("%")); + } + + @Test + public void testIsValidName() { + assertTrue(Variable.isValidName("foo")); + assertTrue(Variable.isValidName("foo.bar")); + assertTrue(Variable.isValidName("foo_1")); + assertTrue(Variable.isValidName("foo-bar")); + assertFalse(Variable.isValidName("foo*")); + assertFalse(Variable.isValidName("")); + assertFalse(Variable.isValidName(null)); + } + + @Test + public void testIsValidValue() { + assertTrue(Variable.isValidValue("foo")); + assertTrue(Variable.isValidValue("foo.bar")); + assertTrue(Variable.isValidValue("foo_1")); + assertTrue(Variable.isValidValue("foo-bar")); + assertTrue(Variable.isValidValue("~%")); + assertFalse(Variable.isValidValue("foo*")); + //assertFalse(Variable.isValidValue("")); + assertFalse(Variable.isValidValue(null)); + } + + +} + diff --git a/src/test/java/org/mule/module/apikit/uri/VariableTypeTest.java b/src/test/java/org/mule/module/apikit/uri/VariableTypeTest.java new file mode 100644 index 000000000..c80cbb605 --- /dev/null +++ b/src/test/java/org/mule/module/apikit/uri/VariableTypeTest.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com + * The software in this package is published under the terms of the CPAL v1.0 + * license, a copy of which has been included with this distribution in the + * LICENSE.txt file. + */ +package org.mule.module.apikit.uri; + +import org.junit.Test; +import static org.junit.Assert.*; +import org.mule.module.apikit.uri.VariableType; + +public class VariableTypeTest { + + @Test + public void testVariableTypeConstructor() { + String name = "testName"; + VariableType variableType = new VariableType(name); + assertEquals(name, variableType.getName()); + } + + @Test + public void testGetName() { + String name = "testName"; + VariableType variableType = new VariableType(name); + assertEquals(name, variableType.getName()); + } + + @Test + public void testEquals() { + VariableType variableType1 = new VariableType("testName"); + VariableType variableType2 = new VariableType("testName"); + assertEquals(variableType1, variableType2); + } + +} \ No newline at end of file diff --git a/src/test/java/org/mule/module/apikit/validation/body/form/MultiPartFormDataArrayShapeTest.java b/src/test/java/org/mule/module/apikit/validation/body/form/MultiPartFormDataArrayShapeTest.java new file mode 100644 index 000000000..f28c4787a --- /dev/null +++ b/src/test/java/org/mule/module/apikit/validation/body/form/MultiPartFormDataArrayShapeTest.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com + * The software in this package is published under the terms of the CPAL v1.0 + * license, a copy of which has been included with this distribution in the + * LICENSE.txt file. + */ +package org.mule.module.apikit.validation.body.form; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.mule.module.apikit.api.exception.InvalidFormParameterException; +import org.mule.module.apikit.api.exception.MuleRestException; +import org.mule.module.apikit.validation.TestRestRequestValidator; +import org.mule.parser.service.ParserMode; + +import java.util.Arrays; +import java.util.Collection; +import java.util.OptionalLong; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +@RunWith(Parameterized.class) +public class MultiPartFormDataArrayShapeTest extends AbstractMultipartRequestValidatorTestCase { + + @Parameterized.Parameter + public ParserMode parser; + + @Parameterized.Parameters(name = "{0}") + public static Collection data() { + return Arrays.asList(new Object[][] { + {ParserMode.AMF}, + {ParserMode.RAML} + }); + } + + @Test + public void minItemsValidationTest() throws MuleRestException { + TestRestRequestValidator testRestRequestValidator = multipartTestBuilder + .withApiLocation("unit/multipart-form-data/multipart-array-shape.raml") + .withRelativePath("/test") + .withParser(parser) + .withTextPart("zipFiles", "\nsome.zip\n") + .build(); + + OptionalLong afterValidationBodyLength = testRestRequestValidator + .validateRequest() + .getBody() + .getPayloadAsTypedValue().getByteLength(); + + assertEquals(testRestRequestValidator.getRequestBodyLength(), afterValidationBodyLength); + } + + @Test + public void maxItemsValidationTest() throws MuleRestException { + TestRestRequestValidator testRestRequestValidator = multipartTestBuilder + .withApiLocation("unit/multipart-form-data/multipart-array-shape.raml") + .withRelativePath("/multipart-upload") + .withParser(parser) + .withTextPart("zipFiles", "\nsome.zip\n") + .withTextPart("zipFiles", "\nother.zip\n") + .withTextPart("Attachments", "\none.txt\n") + .withTextPart("Attachments", "\ntwo.txt\n") + .build(); + + OptionalLong afterValidationBodyLength = testRestRequestValidator + .validateRequest() + .getBody() + .getPayloadAsTypedValue().getByteLength(); + + assertEquals(testRestRequestValidator.getRequestBodyLength(), afterValidationBodyLength); + } + + @Test + public void minItemsValidationTestFailure() throws MuleRestException { + TestRestRequestValidator testRestRequestValidator = multipartTestBuilder + .withApiLocation("unit/multipart-form-data/multipart-array-shape.raml") + .withRelativePath("/multipart-upload") + .withParser(ParserMode.AMF) + .withTextPart("zipFiles", "\nsome.zip\n") + .withTextPart("zipFiles", "\nother.zip\n") + .withTextPart("Attachments", "\none.txt\n") + .build(); + InvalidFormParameterException invalidFormParameterException = assertThrows(InvalidFormParameterException.class, () -> testRestRequestValidator + .validateRequest() + .getBody() + .getPayloadAsTypedValue().getByteLength()); + assertEquals("parameter does not comply with minItems for Attachments", invalidFormParameterException.getMessage()); + } + + @Test + public void minItemsValidationTestRequiredFieldAbsentFailure() throws MuleRestException { + TestRestRequestValidator testRestRequestValidator = multipartTestBuilder + .withApiLocation("unit/multipart-form-data/multipart-array-shape.raml") + .withRelativePath("/multipart-upload") + .withParser(ParserMode.AMF) + .withTextPart("zipFiles", "\nsome.zip\n") + .withTextPart("Attachments", "\none.txt\n") + .withTextPart("Attachments", "\ntwo.txt\n") + .build(); + InvalidFormParameterException invalidFormParameterException = assertThrows(InvalidFormParameterException.class, () -> testRestRequestValidator + .validateRequest() + .getBody() + .getPayloadAsTypedValue().getByteLength()); + assertEquals("parameter does not comply with minItems for zipFiles", invalidFormParameterException.getMessage()); + } + + @Test + public void maxItemsValidationTestFailure2() throws MuleRestException { + TestRestRequestValidator testRestRequestValidator = multipartTestBuilder + .withApiLocation("unit/multipart-form-data/multipart-array-shape.raml") + .withRelativePath("/multipart-upload") + .withParser(ParserMode.AMF) + .withTextPart("zipFiles", "\nsome.zip\n") + .withTextPart("Attachments", "\none.txt\n") + .withTextPart("Attachments", "\ntwo.txt\n") + .withTextPart("Attachments", "\nthree.txt\n") + .withTextPart("Attachments", "\nfour.txt\n") + .build(); + InvalidFormParameterException invalidFormParameterException = assertThrows(InvalidFormParameterException.class, () -> testRestRequestValidator + .validateRequest() + .getBody() + .getPayloadAsTypedValue().getByteLength()); + assertEquals("parameter does not comply with maxItems for Attachments", invalidFormParameterException.getMessage()); + } +} diff --git a/src/test/munit/flow-routing/flow-routing-test-suite.xml b/src/test/munit/flow-routing/flow-routing-test-suite.xml index 47388a9e2..bd7ce790a 100644 --- a/src/test/munit/flow-routing/flow-routing-test-suite.xml +++ b/src/test/munit/flow-routing/flow-routing-test-suite.xml @@ -195,22 +195,6 @@ - - - - - - - - - - - - - - - - diff --git a/src/test/resources/unit/multipart-form-data/multipart-array-shape.raml b/src/test/resources/unit/multipart-form-data/multipart-array-shape.raml new file mode 100644 index 000000000..621011fe1 --- /dev/null +++ b/src/test/resources/unit/multipart-form-data/multipart-array-shape.raml @@ -0,0 +1,48 @@ +#%RAML 1.0 +title: Multipart Form with File Array and JSON Array +version: v1 + +mediaType: multipart/form-data + +types: + Attachment: + type: file + fileTypes: ['application/pdf', 'image/jpeg', 'image/png', 'text/plain'] + maxLength: 5242880 # 5 MB + zipFile: + type: file + fileTypes: ['image/jpeg', 'image/png', 'text/plain'] + maxLength: 5242880 # 5 MB + +/test: + post: + body: + multipart/form-data: + properties: + zipFiles: + required: true + type: zipFile[] + maxItems: 2 + minItems: 1 +/multipart-upload: + post: + description: Upload an array of files and a JSON array as multipart/form-data + body: + multipart/form-data: + properties: + Attachments: + description: Array of files + type: Attachment[] + required: true + minItems: 2 + maxItems: 3 + zipFiles: + description: Array of files + type: zipFile[] + minItems: 2 + + responses: + 200: + body: + application/json: + example: { status: "success", message: "Files and payload received." }