Skip to content

Commit

Permalink
wip fix test on pg
Browse files Browse the repository at this point in the history
  • Loading branch information
ntananh committed Nov 3, 2024
1 parent be5e926 commit ebf0865
Show file tree
Hide file tree
Showing 8 changed files with 236 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package com.homihq.db2rest.rest.pg;

import com.homihq.db2rest.PostgreSQLBaseIntegrationTest;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.*;

import static com.homihq.db2rest.jdbc.rest.RdbmsRestApi.VERSION;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.core.AnyOf.anyOf;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@TestClassOrder(ClassOrderer.OrderAnnotation.class)
@Order(102)
@DisplayName("Parameterized SQL template")
class PgTemplateControllerTest extends PostgreSQLBaseIntegrationTest {

public final static int ID = 1;

@Test
@DisplayName("Test find all films with sql template")
void findAllFilms() throws Exception {
mockMvc.perform(get(VERSION + "/pgsqldb/sql/select_all")
.contentType(APPLICATION_JSON).accept(APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.*").isArray())
.andExpect(jsonPath("$.*", anyOf(hasSize(4),hasSize(8))))
.andExpect(jsonPath("$[0].*", hasSize(15)))
.andDo(document("pgsql-template-get-all-films-all-columns"));
}

@Test
@DisplayName("Find film by id with request param")
void findFilmByID() throws Exception {
mockMvc.perform(get(VERSION + "/pgsqldb/sql/select_by_id")
.param("film_id", String.valueOf(ID))
.contentType(APPLICATION_JSON).accept(APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$.*").isArray())
.andExpect(jsonPath("$.*", anyOf(hasSize(1))))
.andDo(document("pgsql-template-get-film-by-id-with-params"));
}

@Test
@DisplayName("Find film by id with headers")
void findFilmByIDWithHeader() throws Exception {
mockMvc.perform(get(VERSION + "/pgsqldb/sql/select_by_id")
.header("film_id", String.valueOf(ID))
.contentType(APPLICATION_JSON).accept(APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$.*").isArray())
.andExpect(jsonPath("$.*", anyOf(hasSize(1))))
.andDo(document("pgsql-template-get-film-by-id-with-headers"));
}

@Test
@DisplayName("Find film by id with custom path")
void findFilmByIDWithPath() throws Exception {
mockMvc.perform(get(VERSION + "/pgsqldb/sql/select_by_id/"+ ID)
.header("paths", "film_id")
.contentType(APPLICATION_JSON).accept(APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$.*").isArray())
.andExpect(jsonPath("$.*", anyOf(hasSize(1))))
.andDo(document("pgsql-template-get-film-by-id-with-user-path"));
}

@Test
@DisplayName("Failed to find film by id without provided param")
void findFilmByIDWithRequiredConstraint() throws Exception {
mockMvc.perform(get(VERSION + "/pgsqldb/sql/select_by_id_is_required")
.contentType(APPLICATION_JSON).accept(APPLICATION_JSON))
.andExpect(status().is4xxClientError())
.andDo(document("pgsql-template-get-film-by-id-with-required-constraint"));
}

@Test
@DisplayName("Conditional render: select all when no request params")
void selectWithConditionalRenderNoRequestParams() throws Exception {
mockMvc.perform(get(VERSION + "/pgsqldb/sql/conditional_render_and_op")
.contentType(APPLICATION_JSON).accept(APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.*").isArray())
.andExpect(jsonPath("$.*", anyOf(hasSize(4), hasSize(9), hasSize(8))))
.andExpect(jsonPath("$[0].*", hasSize(15)))
.andDo(document("pgsql-template-conditional-render-no-request-params"));
}

@Test
@DisplayName("Conditional render: select render one condition")
void selectWithConditionalRenderWithID() throws Exception {
var id = 4;
mockMvc.perform(get(VERSION + "/pgsqldb/sql/conditional_render_and_op")
.param("film_id", String.valueOf(id))
.contentType(APPLICATION_JSON).accept(APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.*").isArray())
.andExpect(jsonPath("$.*", anyOf(hasSize(1))))
.andExpect(jsonPath("$[0].film_id").value(id))
.andDo(document("pgsql-template-conditional-render-with-id-condition"));
}

@Test
@DisplayName("Conditional render: render both and operations")
void selectWithConditionalRenderWithField() throws Exception {
var id = 4;
var rating = "G";
mockMvc.perform(get(VERSION + "/pgsqldb/sql/conditional_render_and_op")
.param("film_id", String.valueOf(id))
.param("rating", rating)
.contentType(APPLICATION_JSON).accept(APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.*").isArray())
.andExpect(jsonPath("$.*", anyOf(hasSize(1))))
.andExpect(jsonPath("$[0].film_id").value(id))
.andExpect(jsonPath("$[0].rating").value(rating))
.andDo(document("pgsql-template-conditional-render-render-both-and-operations"));
}


@Test
@DisplayName("Conditional render join: skip render join, select film by id")
void selectWithConditionalRenderJoinWithoutInput() throws Exception {
var film_id = 1;
mockMvc.perform(get(VERSION + "/pgsqldb/sql/conditional_render_join")
.param("film_id", String.valueOf(film_id))
.contentType(APPLICATION_JSON).accept(APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.*").isArray())
.andExpect(jsonPath("$.*", anyOf(hasSize(1))))
.andExpect(jsonPath("$[0].film_id").value(film_id))
.andDo(document("pgsql-template-conditional-render-join-skip-render"));
}

@Test
@DisplayName("Conditional render join: execute rendered join")
void selectWithConditionalRenderJoin() throws Exception {
var film_id = 1;
var language_id = 1;
mockMvc.perform(get(VERSION + "/pgsqldb/sql/conditional_render_join")
.param("film_id", String.valueOf(film_id))
.param("language_id", String.valueOf(language_id))
.contentType(APPLICATION_JSON).accept(APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.*").isArray())
.andExpect(jsonPath("$.*", anyOf(hasSize(1))))
.andExpect(jsonPath("$[0].film_id").value(film_id))
.andExpect(jsonPath("$[0].language_name").value(Matchers.matchesPattern("\\s*English\\s*")))
.andDo(document("pgsql-template-conditional-render-join"));
}
}
1 change: 1 addition & 0 deletions api-rest/src/test/resources/application-it-pg.yaml
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
SQL_TEMPLATE_PATH: classpath:testdata/sql-pg

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
SELECT *
FROM film f
WHERE 1 = 1
{% if params.film_id %}
AND f.film_id = {{ params.film_id }}::integer
{% endif %}
{% if params.rating %}
AND f.rating = {{ params.rating }}::text
{% endif %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
SELECT
{% if params.film_id and params.language_id %}
f.film_id, f.title, l.name as language_name
{% else %}
*
{% endif %}
FROM film f

{% if params.film_id and params.language_id %}
JOIN
language l
ON f.language_id = l.language_id
{% endif %}

{% if params.film_id %}
WHERE f.film_id = {{ params.film_id | is_required }}::integer
{% endif %}
1 change: 1 addition & 0 deletions api-rest/src/test/resources/testdata/sql-pg/select_all.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SELECT * FROM film;
11 changes: 11 additions & 0 deletions api-rest/src/test/resources/testdata/sql-pg/select_by_id.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
SELECT *
FROM film f
WHERE 1 = 1
AND f.film_id =
{% if params.film_id %}
{{ params.film_id }}::integer
{% elif paths.film_id %}
{{ paths.film_id }}::integer
{% elif headers.film_id %}
{{ headers.film_id }}::integer
{% endif %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SELECT * FROM film f
WHERE f.film_id = {{ params.film_id | is_required }}::integer
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
import com.homihq.db2rest.core.exception.SqlTemplateReadException;
import com.homihq.db2rest.core.exception.UnsupportedConstraintException;
import com.homihq.db2rest.jdbc.JdbcManager;
import com.homihq.db2rest.jdbc.config.dialect.Dialect;
import com.homihq.db2rest.jdbc.core.DbOperationService;
import com.homihq.db2rest.jdbc.dto.Placeholder;
import com.homihq.db2rest.jdbc.validator.ConstraintValidator;
import com.homihq.db2rest.jdbc.validator.CustomPlaceholderValidators;
import com.hubspot.jinjava.Jinjava;
import com.hubspot.jinjava.interpret.TemplateError;
import com.hubspot.jinjava.tree.Node;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
Expand All @@ -19,10 +22,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -51,6 +51,9 @@ public Object execute(String dbId, String templateFile, Map<String, Object> cont

private Object executeQuery(String dbId, Map<String, Object> paramMap, String sql) {
log.debug("Execute: {}", sql);

Dialect dialect = jdbcManager.getDialect(dbId);

return dbOperationService.read(
jdbcManager.getNamedParameterJdbcTemplate(dbId),
paramMap,
Expand All @@ -69,6 +72,33 @@ private Pair<String, Map<String, Object>> executeInternal(String templateFile, M
return Pair.of(namedParamsSQL, paramMap);
}

private void analyzeTemplate(String templateContent) {
var interpreter = jinjava.newInterpreter();
Node parsedTemplate = interpreter.parse(templateContent);

// 1. Get Template Errors (if any)
List<TemplateError> errors = interpreter.getErrors();
System.out.println("Errors:");
errors.forEach(System.out::println);

// 2. Get Template Variables
Set<String> variables = interpreter.getContext().getSessionBindings().keySet();
System.out.println("Variables Used: " + variables);

// 3. Get all nodes for further metadata if needed (e.g., macros, includes, custom blocks)
List<String> nodeNames = parsedTemplate.getChildren().stream()
.map(Node::getName)
.toList();
System.out.println("Nodes in Template: " + nodeNames);

parsedTemplate.getChildren().forEach(c -> {
var child = c;

System.out.println("Child: " + child.getName());
});

}

private String renderJinJavaTemplate(String templateFile, Map<String, Object> context) {
log.debug("Rendering query from template {}", templateFile);

Expand All @@ -79,13 +109,15 @@ private String renderJinJavaTemplate(String templateFile, Map<String, Object> co
} else {
final String userTemplateLocation = db2RestConfigProperties.getTemplates();
final Path templatePath = Paths.get(userTemplateLocation, templateFile + SQL_TEMPLATE_EXTENSION);

if (!Files.exists(templatePath)) {
throw new SqlTemplateNotFoundException(templateFile);
}
try {
final String templateContent = Files.readString(templatePath);
templateCache.put(templateFile, templateContent);

analyzeTemplate(templateContent);

return jinjava.render(templateContent, context);
} catch (IOException ioe) {
log.error("Error reading template file {}: {}", templatePath, ioe.getMessage());
Expand Down

0 comments on commit ebf0865

Please sign in to comment.