package de.superx.rest; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.web.servlet.MvcResult; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import de.superx.BaseWebTest; import de.superx.bianalysis.metadata.Identifier; import de.superx.bianalysis.metadata.MetadataImporter; import de.superx.bianalysis.models.Condition; import de.superx.bianalysis.models.CriteriaGroup; import de.superx.bianalysis.repository.DimensionAttributeRepository; import de.superx.bianalysis.repository.DimensionRepository; import de.superx.bianalysis.repository.dto.AttributeDto; import de.superx.bianalysis.repository.dto.DimensionDto; import de.superx.bianalysis.service.DbMetaAdapter; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.nio.charset.StandardCharsets; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Optional; public class BiAnalysisAdminApiTest extends BaseWebTest { private static final String ADMIN_BASE_ROUTE = "/api/bianalysis/admin"; private static final String ATTRIBUTE_GROUP_ROUTE = ADMIN_BASE_ROUTE + "/attribute-group"; private static final String ATTRIBUTE_GROUP_VALIDATE = ATTRIBUTE_GROUP_ROUTE + "/validate"; @Autowired DbMetaAdapter dbMetaAdapter; @Autowired DimensionAttributeRepository dimensionAttrRepo; @Autowired DimensionRepository dimensionRepository; @After public void cleanup() { JdbcTemplate jt = new JdbcTemplate(dataSource); String sqlCleanup = """ DELETE FROM metadata.facttable WHERE namespace = 'dummy_api'; DELETE FROM metadata.dimension WHERE namespace = 'dummy_api' OR namespace = 'conf_dummy'; DELETE FROM metadata.dimension_attribute WHERE namespace = 'dummy_api' OR namespace = 'conf_dummy'; DELETE FROM metadata.dimension_attribute WHERE id like 'custom%'; DROP TABLE IF EXISTS presentation.dim_person_dummy; """; jt.execute(sqlCleanup); } @Before public void init() { assertEquals(0, getCustomAttributes().size()); // sanity check String dummyConformedDimension = """ { "namespace" : "conf_dummy", "conformed_dimensions" : [ { "id" : "conf_dummy:1", "caption" : "Person", "dimension" : "dim_person_dummy", "fact_column" : "person_id", "attributes" : [ { "id" : "conf_dummy:2", "caption" : "Geburtsjahr", "dim_column" : "year_of_birth" } ] } ] } """; String dummyFacttable = """ { "namespace" : "dummy_api", "facts" : [ { "id" : "dummy_api:2", "caption" : "Publikationsstern", "facttable" : "fact_publication_dummy", "dimensions" : [ { "id" : "dummy_api:3", "caption" : "Dozent", "ref_to" : "dim_person_dummy", "fact_column" : "person_id", "attributes" : [ { "id" : "dummy_api:4", "ref_to" : "year_of_birth" } ] } ], "measures" : [] }, { "id" : "dummy_api:5", "caption" : "Bewerberstern", "facttable" : "fact_applicant_dummy", "dimensions" : [ { "id" : "dummy_api:6", "caption" : "Bewerber", "ref_to" : "dim_person_dummy", "fact_column" : "person_id", "attributes" : [ { "id" : "dummy_api:7", "ref_to" : "year_of_birth" } ] } ], "measures" : [] } ] } """; JdbcTemplate jt = new JdbcTemplate(dataSource); MetadataImporter importer = new MetadataImporter(); importer.deserializeMetadataFromStrings(dummyConformedDimension, dummyFacttable); String upserts = String.join("\n", importer.getAllUpsertStrings(false)); jt.execute(upserts); jt.execute("CREATE TABLE presentation.dim_person_dummy" + " (id SERIAL PRIMARY KEY, year_of_birth varchar NOT NULL);"); String sql = "INSERT INTO presentation.dim_person_dummy (year_of_birth) VALUES (?)"; for (int year = 1990; year <= 2015; year++) { jt.update(sql, String.valueOf(year)); } // TODO fix pre authorization guard for tests //user.getRightsMap().put("RIGHT_CS_BIA_ANALYSIS_MANAGE_METADATA", null); } @Test public void testCreationAndDeletionOfAttributeGroup() throws Exception { MvcResult result = mockMvc.perform(put(ATTRIBUTE_GROUP_ROUTE) .contentType(MediaType.APPLICATION_JSON) .characterEncoding("UTF-8") .param("dimension_id", "dummy_api:6") .content(getAttributeJson("Neue Geburtsjahrgaenge", "").getBytes(StandardCharsets.UTF_8))) .andDo(print()) .andExpect(status().isOk()) .andReturn(); String responseBody = result.getResponse().getContentAsString(); Identifier id = new ObjectMapper().readValue(responseBody, Identifier.class); assertNotNull(id); Optional attributeOpt = this.dimensionAttrRepo.findById(id); assertTrue(attributeOpt.isPresent()); AttributeDto attribute = attributeOpt.get(); Optional dimensionOpt = this.dimensionRepository.findById(attribute.dimensionId); assertTrue(dimensionOpt.isPresent()); DimensionDto dimension = dimensionOpt.get(); assertEquals(new Identifier("dummy_api:6"), dimension.id); List customAttributes = getCustomAttributes(); assertEquals(3, customAttributes.size()); AttributeDto applicantRolePlaying = this.dimensionAttrRepo.findByIdOrThrow(new Identifier("custom:1")); AttributeDto teacherRolePlaying = this.dimensionAttrRepo.findByIdOrThrow(new Identifier("custom:3")); AttributeDto personConformed = this.dimensionAttrRepo.findByIdOrThrow(new Identifier("custom:2")); assertEquals("Neue Geburtsjahrgaenge", personConformed.caption); assertEquals("custom_neue_geburtsjahrgaenge", personConformed.columnname); assertNotNull(personConformed.criteriaGroups); assertEquals("my description", personConformed.description); assertTrue(personConformed.attrConformedId == null); assertTrue(personConformed.isHidden.booleanValue()); assertEquals("Neue Geburtsjahrgaenge", applicantRolePlaying.caption); assertEquals("custom_neue_geburtsjahrgaenge", applicantRolePlaying.columnname); assertEquals("custom_neue_geburtsjahrgaenge", applicantRolePlaying.columnname); assertEquals("my description", applicantRolePlaying.description); assertEquals("custom:2", applicantRolePlaying.attrConformedId); assertEquals("dummy_api:6", applicantRolePlaying.dimensionId.composedId); assertNotNull(applicantRolePlaying.criteriaGroups); assertTrue(!applicantRolePlaying.isHidden.booleanValue()); assertEquals("Neue Geburtsjahrgaenge", teacherRolePlaying.caption); assertEquals("custom_neue_geburtsjahrgaenge", teacherRolePlaying.columnname); assertEquals("custom_neue_geburtsjahrgaenge", teacherRolePlaying.columnname); assertEquals("my description", teacherRolePlaying.description); assertEquals("custom:2", teacherRolePlaying.attrConformedId); assertEquals("dummy_api:3", teacherRolePlaying.dimensionId.composedId); assertNotNull(teacherRolePlaying.criteriaGroups); assertTrue(teacherRolePlaying.isHidden.booleanValue()); List criteriaGroups = attribute.readCriteriaGroups(); assertEquals(criteriaGroups.size(), 3); for (CriteriaGroup criteriaGroup : criteriaGroups) { if(criteriaGroup.caption.equals("2001 - 2005")) { assertEquals(List.of("2001", "2002", "2003", "2004", "2005"), Condition.fromMap(criteriaGroup.conditions.get(0)).values); } else if(criteriaGroup.caption.equals("2006 - 2010")) { assertEquals(List.of("2006", "2007", "2008", "2009"), Condition.fromMap(criteriaGroup.conditions.get(0)).values); } else if(criteriaGroup.caption.equals("n.a")) { assertTrue(criteriaGroup.isElse); } else { Assert.fail("Wrong criteria group: " + criteriaGroup.caption); } } assertTrue(columnExists("presentation", dimension.tablename, "custom_neue_geburtsjahrgaenge")); assertTrue(columnExists("presentation", dimension.tablename, "custom_neue_geburtsjahrgaenge_sort_order")); result = mockMvc.perform(put(ATTRIBUTE_GROUP_ROUTE) .contentType(MediaType.APPLICATION_JSON) .characterEncoding("UTF-8") .param("dimension_id", "dummy_api:6") .content(getAttributeJson("Umbenannte Caption", "\"id\": \"custom:1\",").getBytes(StandardCharsets.UTF_8))) .andDo(print()) .andExpect(status().isOk()) .andReturn(); applicantRolePlaying = this.dimensionAttrRepo.findByIdOrThrow(new Identifier("custom:1")); teacherRolePlaying = this.dimensionAttrRepo.findByIdOrThrow(new Identifier("custom:3")); personConformed = this.dimensionAttrRepo.findByIdOrThrow(new Identifier("custom:2")); assertEquals("Umbenannte Caption", personConformed.caption); assertEquals("custom_umbenannte_caption", personConformed.columnname); assertEquals("custom_umbenannte_caption_sort_order", personConformed.sortOrderColumn); assertNotNull(personConformed.criteriaGroups); assertEquals("my description", personConformed.description); assertTrue(personConformed.attrConformedId == null); assertTrue(personConformed.isHidden.booleanValue()); assertEquals("Umbenannte Caption", applicantRolePlaying.caption); assertEquals("custom_umbenannte_caption", applicantRolePlaying.columnname); assertEquals("custom_umbenannte_caption_sort_order", applicantRolePlaying.sortOrderColumn); assertEquals("my description", applicantRolePlaying.description); assertEquals("custom:2", applicantRolePlaying.attrConformedId); assertEquals("dummy_api:6", applicantRolePlaying.dimensionId.composedId); assertNotNull(applicantRolePlaying.criteriaGroups); assertTrue(!applicantRolePlaying.isHidden.booleanValue()); assertEquals("Umbenannte Caption", teacherRolePlaying.caption); assertEquals("custom_umbenannte_caption", teacherRolePlaying.columnname); assertEquals("custom_umbenannte_caption_sort_order", teacherRolePlaying.sortOrderColumn); assertEquals("my description", teacherRolePlaying.description); assertEquals("custom:2", teacherRolePlaying.attrConformedId); assertEquals("dummy_api:3", teacherRolePlaying.dimensionId.composedId); assertNotNull(teacherRolePlaying.criteriaGroups); assertTrue(teacherRolePlaying.isHidden.booleanValue()); this.dbMetaAdapter.deleteAttributeGroupById(id); assertEquals(0, getCustomAttributes().size()); assertTrue(!columnExists("presentation", dimension.tablename, "custom_umbenannte_caption")); assertTrue(!columnExists("presentation", dimension.tablename, "custom_umbenannte_caption_sort_order")); assertTrue(!columnExists("presentation", dimension.tablename, "custom_neue_geburtsjahrgaenge")); assertTrue(!columnExists("presentation", dimension.tablename, "custom_neue_geburtsjahrgaenge_sort_order")); assertTrue(this.dimensionAttrRepo.findById(attribute.id).isEmpty()); } @Test public void testValidation() throws Exception { String json = """ { "caption" : "Altersgruppe", "description": "my description", "criteriaGroups" : [ { "caption" : "Unter 18", "conditions" : [ { "column" : "age", "operator" : "<", "value" : "25" } ] }, { "caption" : "Ab 18 bis 50", "logic" : "AND", "conditions" : [ { "column" : "age", "operator" : ">=", "value" : "18" }, { "column" : "age", "operator" : "<=", "value" : "50" } ] }, { "caption":"n.a", "isElse" : true } ] } """; MvcResult result = mockMvc.perform(put(ATTRIBUTE_GROUP_VALIDATE) .contentType(MediaType.APPLICATION_JSON) .characterEncoding("UTF-8") .param("dimension_id", "app_basic:5") .content(json.getBytes(StandardCharsets.UTF_8))) .andDo(print()) .andExpect(status().isOk()) .andReturn(); String responseBody = result.getResponse().getContentAsString(); assertNotNull(responseBody); assertTrue(responseBody.length() > 0); ObjectMapper mapper = new ObjectMapper(); List stringList = mapper.readValue(responseBody, new TypeReference>(){}); assertTrue(!stringList.isEmpty()); } public String getAttributeJson(String caption, String id) { String json = """ { %s "caption": "%s", "description": "my description", "criteriaGroups":[ { "caption":"2001 - 2005", "conditions": [ { "column" : "year_of_birth", "operator": "IN", "values": [ "2001", "2002", "2003", "2004", "2005" ] } ] }, { "caption":"2006 - 2010", "conditions": [ { "column" : "year_of_birth", "operator": "IN", "values": [ "2006", "2007", "2008", "2009" ] } ] }, { "caption":"n.a", "isElse" : true } ] } """.formatted(id, caption); return json; } private List getCustomAttributes() { List customAttributes = new ArrayList<>(); for (AttributeDto attributeDto : this.dimensionAttrRepo.findAll()) { if(attributeDto.isCustom.booleanValue()) { customAttributes.add(attributeDto); } } return customAttributes; } boolean columnExists(String schema, String tableName, String columnName) throws SQLException { DatabaseMetaData meta = dbMetaAdapter.getDataSource().getConnection().getMetaData(); try (ResultSet rs = meta.getColumns(null, schema, tableName, columnName)) { return rs.next(); // true if at least one matching column } } }