You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
415 lines
16 KiB
415 lines
16 KiB
package de.superx; |
|
|
|
import static org.junit.Assert.assertEquals; |
|
import static org.junit.Assert.assertTrue; |
|
|
|
import java.io.BufferedReader; |
|
import java.io.File; |
|
import java.io.IOException; |
|
import java.io.InputStreamReader; |
|
import java.io.Reader; |
|
import java.math.BigDecimal; |
|
import java.math.BigInteger; |
|
import java.math.MathContext; |
|
import java.math.RoundingMode; |
|
import java.nio.charset.StandardCharsets; |
|
import java.nio.file.Files; |
|
import java.nio.file.Path; |
|
import java.nio.file.Paths; |
|
import java.sql.Connection; |
|
import java.sql.SQLException; |
|
import java.text.DecimalFormat; |
|
import java.util.ArrayList; |
|
import java.util.HashMap; |
|
import java.util.LinkedHashMap; |
|
import java.util.List; |
|
import java.util.Map; |
|
import java.util.stream.Collectors; |
|
|
|
import javax.sql.DataSource; |
|
|
|
import org.apache.commons.lang.SystemUtils; |
|
import org.h2.jdbc.JdbcClob; |
|
import org.junit.Ignore; |
|
import org.postgresql.shaded.com.ongres.scram.common.bouncycastle.pbkdf2.RuntimeCryptoException; |
|
import org.springframework.core.io.FileSystemResource; |
|
import org.springframework.jdbc.core.JdbcTemplate; |
|
import org.springframework.jdbc.datasource.init.ScriptUtils; |
|
|
|
import com.fasterxml.jackson.core.JsonProcessingException; |
|
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; |
|
import com.fasterxml.jackson.databind.JsonNode; |
|
import com.fasterxml.jackson.databind.ObjectMapper; |
|
import com.fasterxml.jackson.databind.ObjectWriter; |
|
import com.fasterxml.jackson.databind.json.JsonMapper; |
|
|
|
import de.superx.bianalysis.ReportDefinition; |
|
import de.superx.bianalysis.ReportMetadata; |
|
import de.superx.bianalysis.StoredReport; |
|
import de.superx.bianalysis.metadata.MetadataImporter; |
|
import de.superx.bianalysis.service.BiAnalysisManager; |
|
import de.superx.bianalysis.service.DbMetaAdapter; |
|
import de.superx.dbt.DbtWrapper; |
|
import de.superx.dbt.DbtUtils; |
|
import de.superx.rest.Report; |
|
import de.superx.rest.model.Column; |
|
import de.superx.rest.model.Result; |
|
import de.superx.rest.model.Row; |
|
import de.superx.servlet.SuperXManager; |
|
import de.superx.util.PathAndFileUtils; |
|
import net.sf.jsqlparser.parser.CCJSqlParserUtil; |
|
import net.sf.jsqlparser.statement.Statement; |
|
|
|
@Ignore |
|
public class BIATestUtils { |
|
|
|
public static String RW_TEST_PATH = String.join(File.separator, new String[] {"test", "resources", "db", "fixtures", "bianalysis" }); |
|
|
|
public static boolean setupDone = false; |
|
|
|
public static List<String> readInsertsFromFile(String file) { |
|
List<String> expected = new ArrayList<>(); |
|
Path path = Path.of(String.join(File.separator, file)); |
|
try { |
|
expected = Files.readAllLines(path); |
|
} catch (IOException e) { |
|
throw new RuntimeException(e); |
|
} |
|
return expected; |
|
} |
|
|
|
public static String formatSQL(String sql) { |
|
return sql.trim() |
|
.replaceAll("\n", "") |
|
.replaceAll("\r", "") |
|
.replaceAll(" +", " "); |
|
} |
|
|
|
public static Result readResultFromJson(String response) { |
|
ObjectMapper mapper = JsonMapper.builder().findAndAddModules().build(); |
|
Result result = null; |
|
try { |
|
result = mapper.readValue(new File(response), Result.class); |
|
} catch (Exception e) { |
|
throw new RuntimeException(e); |
|
} |
|
return result; |
|
} |
|
|
|
public static String getStringFromStream(Reader reader) throws IOException { |
|
char[] buffer = new char[4096]; |
|
StringBuilder builder = new StringBuilder(); |
|
int numChars; |
|
while ((numChars = reader.read(buffer)) >= 0) { |
|
builder.append(buffer, 0, numChars); |
|
} |
|
return builder.toString(); |
|
} |
|
|
|
public static void executeSqlScript(String filePath, DataSource dataSource) throws SQLException { |
|
ScriptUtils.executeSqlScript(dataSource.getConnection(), new FileSystemResource(new File(filePath))); |
|
} |
|
|
|
|
|
public static void compareCellsForEquality(Result actualResult, Result expectedResult) { |
|
HashMap<String, Row> actRowsMap = new HashMap<>(); |
|
HashMap<String, Row> expRowsMap = new HashMap<>(); |
|
for (Row actRow : actualResult.rows) { actRowsMap.put(actRow.rowKey, actRow); } |
|
for (Row expRow : expectedResult.rows) { expRowsMap.put(expRow.rowKey, expRow); } |
|
|
|
assertEquals(expRowsMap.size(), actRowsMap.size()); |
|
|
|
for(Row expRow : expectedResult.rows) { |
|
|
|
Map<String, Object> cellsExp = expRow.cells; |
|
assertTrue("Rows are missing Row Key: " + expRow.rowKey, actRowsMap.containsKey(expRow.rowKey)); |
|
|
|
Map<String, Object> cellsAct = actRowsMap.get(expRow.rowKey).cells; |
|
|
|
for (String keyExp : cellsExp.keySet()) { |
|
Object objExp = cellsExp.get(keyExp); |
|
Object objAct = cellsAct.get(keyExp); |
|
boolean areCellsEqual = compareTwoCells(objExp, objAct); |
|
|
|
String failureMessage = String.format( |
|
"\n Cell Mismatch Detected!\n" + |
|
"------------------------------\n" + |
|
" Row Key: %s\n" + |
|
" Cell Key: %s\n" + |
|
" Expected -> %s\n" + |
|
" Actual -> %s\n" + |
|
"------------------------------", |
|
expRow.rowKey, |
|
keyExp, |
|
String.valueOf(objExp), |
|
String.valueOf(objAct) |
|
); |
|
|
|
assertTrue(failureMessage, areCellsEqual); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
public static boolean compareTwoCells(Object objExp, Object objAct) { |
|
// Handle strings and nulls first |
|
if (objExp instanceof String && objAct instanceof String) { |
|
return objExp.equals(objAct); |
|
} |
|
if (objExp == null && objAct == null) { |
|
return true; |
|
} |
|
if((objExp == null && objAct != null) || (objExp != null && objAct == null)) { |
|
return false; |
|
} |
|
|
|
// Convert both to rounded doubles and compare |
|
double expDouble = toDoubleRounded(objExp); |
|
double actDouble = toDoubleRounded(objAct); |
|
|
|
return Math.abs(expDouble - actDouble) < 0.001; // Fuzzy comparison |
|
} |
|
|
|
private static double toDoubleRounded(Object obj) { |
|
if (obj == null) { |
|
throw new IllegalArgumentException("Cannot convert null to double"); |
|
} |
|
|
|
// Handle LinkedHashMap |
|
if (obj instanceof LinkedHashMap<?, ?>) { |
|
LinkedHashMap<String, Object> map = (LinkedHashMap<String, Object>) obj; |
|
Object value = map.get("parsedValue"); |
|
if (value == null) { |
|
value = map.get("source"); |
|
} |
|
if (value == null) { |
|
throw new IllegalArgumentException("No numeric value in map: " + map); |
|
} |
|
|
|
return BigDecimal.valueOf(toPrimitiveDouble(value)) |
|
.setScale(2, RoundingMode.HALF_UP) |
|
.doubleValue(); |
|
} |
|
|
|
// Handle all numbers uniformly |
|
if (obj instanceof Number num) { |
|
return BigDecimal.valueOf(num.doubleValue()) |
|
.setScale(2, RoundingMode.HALF_UP) |
|
.doubleValue(); |
|
} |
|
|
|
throw new IllegalArgumentException("Unsupported type: " + obj.getClass() + " - " + obj); |
|
} |
|
|
|
private static double toPrimitiveDouble(Object value) { |
|
if (value instanceof Number num) { |
|
return num.doubleValue(); |
|
} |
|
return Double.parseDouble(value.toString().trim()); |
|
} |
|
|
|
public static String getSQLFormatPath() { |
|
boolean isLinux = SystemUtils.IS_OS_LINUX; |
|
String version = DbtUtils.getPythonVersion(); |
|
SuperXManager.setWEB_INFPfad(PathAndFileUtils.getWebinfPath()); |
|
Path dbtInstallDir = Path |
|
.of(SuperXManager.getWEB_INFPfad(), "..", "dbt", "dbt_" + (isLinux ? "linux" : "windows")) |
|
.toAbsolutePath().normalize(); |
|
Path sl = Path.of(dbtInstallDir.toString(), "bin", "dbt"); |
|
if (!isLinux) { |
|
String[] majorMinor=version.split("\\."); |
|
sl = Path.of(dbtInstallDir.toString(), "Python" + majorMinor[0] + majorMinor[1], "Scripts", "sqlformat.exe").toAbsolutePath(); |
|
} |
|
return sl.toString(); |
|
} |
|
|
|
public static String getFormattedSQL(String sql) { |
|
try { |
|
File tempFile = File.createTempFile("rw_test_utils-", ".tmp"); |
|
Files.write(Paths.get(tempFile.getAbsolutePath()), sql.getBytes()); |
|
ProcessBuilder builder = new ProcessBuilder(DbtUtils.getPython3ExecutableName(), getSQLFormatPath(),"-a", "-k", "upper", tempFile.getAbsolutePath()); |
|
Process last = builder.start(); |
|
BufferedReader error = new BufferedReader(new InputStreamReader(last.getErrorStream())); |
|
BufferedReader output = new BufferedReader(new InputStreamReader(last.getInputStream())); |
|
List<String> results = output.lines().collect(Collectors.toList()); |
|
List<String> errors = error.lines().collect(Collectors.toList()); |
|
tempFile.delete(); |
|
String result = ""; |
|
for (String string : results) { |
|
result += string + "\n"; |
|
} |
|
if(!errors.isEmpty()) { |
|
for (String string : errors) { |
|
System.out.println(string); |
|
} |
|
return sql; |
|
} |
|
return result; |
|
} catch (Exception e) { |
|
throw new RuntimeException(e); |
|
} |
|
} |
|
|
|
public static ReportDefinition readReportDefinitionFromJsonInTestDir(String name) { |
|
String path = String.join(File.separator, new String[] {RW_TEST_PATH, "req", name + ".json"}); |
|
ObjectMapper mapper = JsonMapper.builder().findAndAddModules().build(); |
|
ReportDefinition reportDefinition = null; |
|
try { |
|
reportDefinition = mapper.readValue(new File(path), ReportDefinition.class); |
|
} catch (Exception e) { |
|
throw new RuntimeException(e); |
|
} |
|
return reportDefinition; |
|
} |
|
|
|
public static ReportDefinition getReportDefinitionFromFile(String name) { |
|
ObjectMapper mapper = JsonMapper.builder().findAndAddModules().build(); |
|
ReportDefinition reportDefinition = null; |
|
try { |
|
reportDefinition = mapper.readValue(new File(name), ReportDefinition.class); |
|
} catch (Exception e) { |
|
throw new RuntimeException(e); |
|
} |
|
return reportDefinition; |
|
} |
|
|
|
public static StoredReport readStoredReportFromJson(String name) { |
|
String path = String.join(File.separator, new String[] {RW_TEST_PATH, "req", name + ".json"}); |
|
ObjectMapper mapper = JsonMapper.builder().findAndAddModules().build(); |
|
StoredReport storedReport = null; |
|
try { |
|
storedReport = mapper.readValue(new File(path), StoredReport.class); |
|
} catch (Exception e) { |
|
throw new RuntimeException(e); |
|
} |
|
return storedReport; |
|
} |
|
|
|
public static String getStoredReportDefinition(StoredReport storedReport) { |
|
ObjectWriter ow = new ObjectMapper().writer(); |
|
String reportDefinitionJson = null; |
|
try { |
|
reportDefinitionJson = ow.writeValueAsString(storedReport.reportDefinition); |
|
} catch (JsonProcessingException e) { |
|
throw new RuntimeException(e); |
|
} |
|
return reportDefinitionJson; |
|
} |
|
|
|
public static void compareResultColumns(Result resultActual, Result result) { |
|
assertEquals(resultActual.columns.size(), result.columns.size()); |
|
|
|
Map<String, Column> actColMap = new HashMap<>(); |
|
for(Column actCol : resultActual.columns) { |
|
actColMap.put(actCol.field, actCol); |
|
} |
|
|
|
for (Column expCol : result.columns) { |
|
Column actCol = actColMap.get(expCol.field); |
|
if(actCol == null) { |
|
throw new RuntimeException("Expected to find column with field: " + expCol.field); |
|
} |
|
assertEquals(actCol.aggregation, expCol.aggregation); |
|
assertEquals(Boolean.valueOf(actCol.isTotalColumn), Boolean.valueOf(expCol.isTotalColumn)); |
|
assertEquals(Boolean.valueOf(actCol.groupable), Boolean.valueOf(expCol.groupable)); |
|
assertEquals(actCol.type, expCol.type); |
|
} |
|
} |
|
|
|
public static void writeReportResultToFile(Result result, File file) { |
|
ObjectMapper mapper = new ObjectMapper(); |
|
DefaultPrettyPrinter printer = new DefaultPrettyPrinter(); |
|
try { |
|
mapper.writer(printer).writeValue(file, result); |
|
} catch (Exception e) { |
|
throw new RuntimeException(e); |
|
} |
|
} |
|
|
|
public static String formatSQLIfEclipse(String sql) { |
|
String inEclipseStr = System.getProperty("runInEclipse"); |
|
if (inEclipseStr != null && inEclipseStr.equals("true")) { |
|
return BIATestUtils.getFormattedSQL(sql); |
|
} |
|
return formatSQL(sql); |
|
} |
|
|
|
public static String getTotalColumnSqlFromReportDefinition(String json, DbMetaAdapter dbAdapter) { |
|
ObjectMapper mapper = new ObjectMapper(); |
|
try { |
|
ReportDefinition definition = mapper.readValue(json, ReportDefinition.class); |
|
String sql = BiAnalysisManager.getTotalsColumnSqlStatement(definition, dbAdapter); |
|
Statement sqlStatement = CCJSqlParserUtil.parse(sql); |
|
return sqlStatement.toString(); |
|
} catch (Exception e) { |
|
throw new RuntimeException(e); |
|
} |
|
} |
|
|
|
public static List<Column> getResultColumnsFromReport(String json, DbMetaAdapter dbAdapter, BiAnalysisManager biAnalysisManager) { |
|
ObjectMapper mapper = new ObjectMapper(); |
|
try { |
|
ReportDefinition definition = mapper.readValue(json, ReportDefinition.class); |
|
ReportMetadata metadata = new ReportMetadata(definition, definition.factTableId, dbAdapter); |
|
Result result = biAnalysisManager.getReportData(metadata, dbAdapter, definition); |
|
return result.columns; |
|
} catch (Exception e) { |
|
throw new RuntimeException(e); |
|
} |
|
} |
|
|
|
|
|
public static ReportDefinition getReportDefinitionFromString(String json) { |
|
ObjectMapper mapper = new ObjectMapper(); |
|
try { |
|
ReportDefinition definition = mapper.readValue(json, ReportDefinition.class); |
|
return definition; |
|
} catch (Exception e) { |
|
throw new RuntimeException(e); |
|
} |
|
} |
|
|
|
public static String getSqlFromReportDefinition(String json, DbMetaAdapter dbAdapter) { |
|
ObjectMapper mapper = new ObjectMapper(); |
|
try { |
|
ReportDefinition definition = mapper.readValue(json, ReportDefinition.class); |
|
ReportMetadata metadata = new ReportMetadata(definition, definition.factTableId, dbAdapter); |
|
String sql = BiAnalysisManager.getSqlStatement(metadata); |
|
Statement sqlStatement = CCJSqlParserUtil.parse(sql); |
|
return sqlStatement.toString(); |
|
} catch (Exception e) { |
|
throw new RuntimeException(e); |
|
} |
|
} |
|
|
|
public static String getParsedSqlFromString(String expectedSql) { |
|
try { |
|
Statement sqlStatement = CCJSqlParserUtil.parse(expectedSql); |
|
return sqlStatement.toString(); |
|
} catch (Exception e) { |
|
throw new RuntimeException(e); |
|
} |
|
} |
|
|
|
public static List<Column> parseColumns(String json) throws Exception { |
|
ObjectMapper objectMapper = new ObjectMapper(); |
|
JsonNode root = objectMapper.readTree(json); |
|
|
|
JsonNode columnsNode = root.get("columns"); |
|
if (columnsNode == null || !columnsNode.isArray()) { |
|
throw new IllegalArgumentException("JSON must contain an array field named 'columns'"); |
|
} |
|
|
|
List<Column> result = new ArrayList<>(); |
|
for (JsonNode node : columnsNode) { |
|
String field = node.has("field") ? node.get("field").asText() : null; |
|
String header = node.has("header") ? node.get("header").asText() : null; |
|
result.add(new Column(field, header)); |
|
} |
|
|
|
return result; |
|
|
|
} |
|
|
|
} |