Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1783,29 +1783,20 @@ private String serializeParameterValues(List<TestCaseParameterValue> parameterVa
if (nullOrEmpty(parameterValues)) {
return "";
}

return parameterValues.stream()
.map(pv -> JsonUtils.pojoToJson(pv))
.collect(Collectors.joining(";"));
return parameterValues.stream().map(JsonUtils::pojoToJson).collect(Collectors.joining(";"));
}

private List<TestCaseParameterValue> parseParameterValues(String parameterValuesStr) {
if (nullOrEmpty(parameterValuesStr)) {
return new ArrayList<>();
}

String[] parts = parameterValuesStr.split(";");
List<TestCaseParameterValue> parameterValues = new ArrayList<>();

for (String part : parts) {
if (!nullOrEmpty(part.trim())) {
TestCaseParameterValue pv =
JsonUtils.readValue(part.trim(), TestCaseParameterValue.class);
parameterValues.add(pv);
}
}
String normalized = RestUtil.normalizeQuotes(parameterValuesStr.trim());
List<String> jsonObjects = RestUtil.extractJsonObjects(normalized);

return parameterValues;
return jsonObjects.stream()
.map(json -> JsonUtils.readValue(json, TestCaseParameterValue.class))
.collect(Collectors.toList());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Optional;
import java.util.TimeZone;
import java.util.UUID;
Expand Down Expand Up @@ -194,4 +196,64 @@ public static void validateTimestampMilliseconds(Long timestamp) {
"Timestamp %s is not valid, it should be in milliseconds since epoch", timestamp));
}
}

public static String normalizeQuotes(String input) {
return input
.replace('\u201c', '"')
.replace('\u201d', '"')
.replace('\u2018', '\'')
.replace('\u2019', '\'');
}

public static List<String> extractJsonObjects(String input) {
List<String> jsonObjects = new ArrayList<>();
int i = 0;

while (i < input.length()) {
int start = input.indexOf('{', i);
if (start == -1) break;

int end = findMatchingBrace(input, start);
if (end == -1) break;

jsonObjects.add(input.substring(start, end + 1));
i = end + 1;
}

return jsonObjects;
}

public static int findMatchingBrace(String str, int start) {
int depth = 0;
boolean inString = false;
boolean escape = false;

for (int i = start; i < str.length(); i++) {
char c = str.charAt(i);

if (escape) {
escape = false;
continue;
}

if (c == '\\' && inString) {
escape = true;
continue;
}

if (c == '"') {
inString = !inString;
continue;
}

if (!inString) {
if (c == '{') depth++;
else if (c == '}') {
depth--;
if (depth == 0) return i;
}
}
}
return -1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
package org.openmetadata.service.util;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import jakarta.ws.rs.core.UriInfo;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -83,4 +85,188 @@ private UriInfo mockUriInfo(String uri) throws URISyntaxException {
Mockito.when(uriInfo.getBaseUri()).thenReturn(uriObject);
return uriInfo;
}

@Test
void testNormalizeQuotes_leftDoubleQuote() {
String input = "\u201ctest";
assertEquals("\"test", RestUtil.normalizeQuotes(input));
}

@Test
void testNormalizeQuotes_rightDoubleQuote() {
String input = "test\u201d";
assertEquals("test\"", RestUtil.normalizeQuotes(input));
}

@Test
void testNormalizeQuotes_leftSingleQuote() {
String input = "\u2018test";
assertEquals("'test", RestUtil.normalizeQuotes(input));
}

@Test
void testNormalizeQuotes_rightSingleQuote() {
String input = "test\u2019";
assertEquals("test'", RestUtil.normalizeQuotes(input));
}

@Test
void testNormalizeQuotes_mixedQuotes() {
String input = "\u201cHello\u201d and \u2018World\u2019";
assertEquals("\"Hello\" and 'World'", RestUtil.normalizeQuotes(input));
}

@Test
void testNormalizeQuotes_jsonWithSmartQuotes() {
String input = "{\u201cname\u201d:\u201ccolumnCount\u201d,\u201cvalue\u201d:\u201c5\u201d}";
assertEquals("{\"name\":\"columnCount\",\"value\":\"5\"}", RestUtil.normalizeQuotes(input));
}

@Test
void testNormalizeQuotes_noQuotes() {
String input = "plain text without quotes";
assertEquals("plain text without quotes", RestUtil.normalizeQuotes(input));
}

@Test
void testNormalizeQuotes_emptyString() {
assertEquals("", RestUtil.normalizeQuotes(""));
}

@Test
void testNormalizeQuotes_alreadyNormalQuotes() {
String input = "{\"name\":\"value\"}";
assertEquals("{\"name\":\"value\"}", RestUtil.normalizeQuotes(input));
}

@Test
void testFindMatchingBrace_simpleObject() {
String input = "{\"name\":\"value\"}";
assertEquals(15, RestUtil.findMatchingBrace(input, 0));
}

@Test
void testFindMatchingBrace_nestedObject() {
String input = "{\"outer\":{\"inner\":\"value\"}}";
assertEquals(26, RestUtil.findMatchingBrace(input, 0));
}

@Test
void testFindMatchingBrace_bracesInsideString() {
String input = "{\"value\":\"contains { and } braces\"}";
assertEquals(34, RestUtil.findMatchingBrace(input, 0));
}

@Test
void testFindMatchingBrace_escapedQuotesInString() {
String input = "{\"value\":\"has \\\"escaped\\\" quotes\"}";
assertEquals(33, RestUtil.findMatchingBrace(input, 0));
}

@Test
void testFindMatchingBrace_noClosingBrace() {
String input = "{\"incomplete\":\"object\"";
assertEquals(-1, RestUtil.findMatchingBrace(input, 0));
}

@Test
void testFindMatchingBrace_startFromMiddle() {
String input = "prefix{\"name\":\"value\"}suffix";
assertEquals(21, RestUtil.findMatchingBrace(input, 6));
}

@Test
void testExtractJsonObjects_singleObject() {
String input = "{\"name\":\"value\"}";
List<String> result = RestUtil.extractJsonObjects(input);
assertEquals(1, result.size());
assertEquals("{\"name\":\"value\"}", result.get(0));
}

@Test
void testExtractJsonObjects_multipleObjectsCommaDelimited() {
String input = "{\"name\":\"a\"},{\"name\":\"b\"}";
List<String> result = RestUtil.extractJsonObjects(input);
assertEquals(2, result.size());
assertEquals("{\"name\":\"a\"}", result.get(0));
assertEquals("{\"name\":\"b\"}", result.get(1));
}

@Test
void testExtractJsonObjects_multipleObjectsSemicolonDelimited() {
String input = "{\"name\":\"a\"};{\"name\":\"b\"}";
List<String> result = RestUtil.extractJsonObjects(input);
assertEquals(2, result.size());
assertEquals("{\"name\":\"a\"}", result.get(0));
assertEquals("{\"name\":\"b\"}", result.get(1));
}

@Test
void testExtractJsonObjects_valueContainsSemicolon() {
String input = "{\"sql\":\"SELECT * FROM t WHERE x > 0;\"},{\"name\":\"b\"}";
List<String> result = RestUtil.extractJsonObjects(input);
assertEquals(2, result.size());
assertEquals("{\"sql\":\"SELECT * FROM t WHERE x > 0;\"}", result.get(0));
assertEquals("{\"name\":\"b\"}", result.get(1));
}

@Test
void testExtractJsonObjects_valueContainsNewlines() {
String input = "{\"sql\":\"SELECT\\ncustomer_id\\nFROM DUAL\"},{\"name\":\"b\"}";
List<String> result = RestUtil.extractJsonObjects(input);
assertEquals(2, result.size());
assertEquals("{\"sql\":\"SELECT\\ncustomer_id\\nFROM DUAL\"}", result.get(0));
assertEquals("{\"name\":\"b\"}", result.get(1));
}

@Test
void testExtractJsonObjects_valueContainsBraces() {
String input = "{\"value\":\"contains { and } braces\"},{\"name\":\"b\"}";
List<String> result = RestUtil.extractJsonObjects(input);
assertEquals(2, result.size());
assertEquals("{\"value\":\"contains { and } braces\"}", result.get(0));
assertEquals("{\"name\":\"b\"}", result.get(1));
}

@Test
void testExtractJsonObjects_emptyString() {
List<String> result = RestUtil.extractJsonObjects("");
assertTrue(result.isEmpty());
}

@Test
void testExtractJsonObjects_noJsonObjects() {
List<String> result = RestUtil.extractJsonObjects("plain text");
assertTrue(result.isEmpty());
}

@Test
void testExtractJsonObjects_prefixAndSuffix() {
String input = "prefix {\"name\":\"value\"} suffix";
List<String> result = RestUtil.extractJsonObjects(input);
assertEquals(1, result.size());
assertEquals("{\"name\":\"value\"}", result.get(0));
}

@Test
void testExtractJsonObjects_nestedObjects() {
String input = "{\"outer\":{\"inner\":\"value\"}},{\"name\":\"b\"}";
List<String> result = RestUtil.extractJsonObjects(input);
assertEquals(2, result.size());
assertEquals("{\"outer\":{\"inner\":\"value\"}}", result.get(0));
assertEquals("{\"name\":\"b\"}", result.get(1));
}

@Test
void testExtractJsonObjects_realWorldSqlExample() {
String input =
"{\"name\":\"sqlExpression\",\"value\":\"SELECT customer_id FROM DUAL WHERE lifetime_value < 0;\"},"
+ "{\"name\":\"threshold\",\"value\":\"10\"}";
List<String> result = RestUtil.extractJsonObjects(input);
assertEquals(2, result.size());
assertEquals(
"{\"name\":\"sqlExpression\",\"value\":\"SELECT customer_id FROM DUAL WHERE lifetime_value < 0;\"}",
result.get(0));
assertEquals("{\"name\":\"threshold\",\"value\":\"10\"}", result.get(1));
}
}
Loading