blob: 018626b3fd5221db1621aae8e18c1f719162d90d [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.dataqueries.service;
import static java.util.Collections.emptyList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyCollection;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.when;
import com.google.gson.JsonObject;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.List;
import java.util.stream.Stream;
import org.apache.fineract.TestConfiguration;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
import org.apache.fineract.infrastructure.core.service.database.DatabaseType;
import org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver;
import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
import org.apache.fineract.infrastructure.dataqueries.exception.DatatableNotFoundException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.test.context.ContextConfiguration;
@SpringBootTest
@ContextConfiguration(classes = TestConfiguration.class)
@SuppressFBWarnings(value = "RV_EXCEPTION_NOT_THROWN", justification = "False positive")
public class ReadWriteNonCoreDataServiceImplTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private GenericDataService genericDataService;
@Mock
private DatabaseTypeResolver databaseTypeResolver;
@Mock
private DatabaseSpecificSQLGenerator sqlGenerator;
@Autowired
private ReadWriteNonCoreDataService underTest;
@Test
public void testSqlInjectionCaughtQueryDataTable() {
when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class), anyString())).thenReturn(1);
assertThrows(PlatformApiDataValidationException.class, () -> {
underTest.queryDataTable("table", "cf1", "vf1", "' or 1=1");
});
}
@Test
public void testSqlInjectionCaughtQueryDataTable2() {
when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class), anyString())).thenReturn(1);
assertThrows(PlatformApiDataValidationException.class, () -> {
underTest.queryDataTable("table", "cf1", "vf1", "1; DROP TABLE m_loan; SELECT");
});
}
@Test
public void testQueryDataTableSuccess() {
SqlRowSet sqlRS = Mockito.mock(SqlRowSet.class);
when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class), anyString())).thenReturn(1);
when(jdbcTemplate.queryForRowSet(eq("SELECT \"rc1\", \"rc2\" FROM \"table\" WHERE \"cf1\" = ?"), any(Object.class)))
.thenReturn(sqlRS);
when(sqlRS.next()).thenReturn(true).thenReturn(false);
when(sqlRS.getObject(anyString())).thenReturn("value1").thenReturn("value2");
when(sqlGenerator.buildSelect(anyCollection(), nullable(String.class), eq(false))).thenReturn("SELECT \"rc1\", \"rc2\"");
when(sqlGenerator.buildFrom(anyString(), nullable(String.class), eq(false))).thenReturn("FROM \"table\"");
when(sqlGenerator.escape(anyString())).thenReturn("\"cf1\"");
when(sqlGenerator.alias(anyString(), nullable(String.class))).thenReturn("\"cf1\"");
when(databaseTypeResolver.isPostgreSQL()).thenReturn(true);
when(databaseTypeResolver.databaseType()).thenReturn(DatabaseType.POSTGRESQL);
DatabaseType dialect = databaseTypeResolver.databaseType();
ResultsetColumnHeaderData cf1 = ResultsetColumnHeaderData.detailed("cf1", "text", 10L, false, false, emptyList(), null, false,
false, dialect);
ResultsetColumnHeaderData rc1 = ResultsetColumnHeaderData.detailed("rc1", "text", 10L, false, false, emptyList(), null, false,
false, dialect);
ResultsetColumnHeaderData rc2 = ResultsetColumnHeaderData.detailed("rc2", "text", 10L, false, false, emptyList(), null, false,
false, dialect);
when(genericDataService.fillResultsetColumnHeaders("table")).thenReturn(List.of(cf1, rc1, rc2));
List<JsonObject> results = underTest.queryDataTable("table", "cf1", "vf1", "rc1,rc2");
assertEquals("value1", results.get(0).get("rc1").getAsString());
assertEquals("value2", results.get(0).get("rc2").getAsString());
}
@Test
public void testQueryDataTableValidationError() {
when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class), anyString())).thenReturn(1);
when(genericDataService.fillResultsetColumnHeaders("table")).thenReturn(emptyList());
assertThrows(PlatformApiDataValidationException.class, () -> underTest.queryDataTable("table", "cf1", "vf1", "rc1,rc2"));
}
@Test
public void testDatatableValidationError() {
when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class), anyString())).thenReturn(0);
assertThrows(DatatableNotFoundException.class, () -> underTest.queryDataTable("table", "cf1", "vf1", "rc1,rc2"));
}
@ParameterizedTest
@MethodSource("provideParameters")
public void testQueryDataTableInvalidParameterError(String columnType, String errorMessage) {
when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class), anyString())).thenReturn(1);
when(databaseTypeResolver.databaseType()).thenReturn(DatabaseType.POSTGRESQL);
DatabaseType dialect = databaseTypeResolver.databaseType();
ResultsetColumnHeaderData cf1 = ResultsetColumnHeaderData.detailed("cf1", columnType, 10L, false, false, emptyList(), null, false,
false, dialect);
ResultsetColumnHeaderData rc1 = ResultsetColumnHeaderData.detailed("rc1", "text", 10L, false, false, emptyList(), null, false,
false, dialect);
ResultsetColumnHeaderData rc2 = ResultsetColumnHeaderData.detailed("rc2", "text", 10L, false, false, emptyList(), null, false,
false, dialect);
when(genericDataService.fillResultsetColumnHeaders("table")).thenReturn(List.of(cf1, rc1, rc2));
PlatformApiDataValidationException thrown = assertThrows(PlatformApiDataValidationException.class,
() -> underTest.queryDataTable("table", "cf1", "vf1", "rc1,rc2"));
assertEquals(1, thrown.getErrors().size());
assertEquals(errorMessage, thrown.getErrors().get(0).getUserMessageGlobalisationCode());
}
private static Stream<Arguments> provideParameters() {
return Stream.of(Arguments.of("timestamp without time zone", "validation.msg.invalid.dateFormat.format"),
Arguments.of("INTEGER", "validation.msg.invalid.integer.format"),
Arguments.of("BOOLEAN", "validation.msg.invalid.boolean.format"));
}
}