Skip to content

Commit d5d5053

Browse files
committed
FINERACT-2676: Fix named PPI survey score overview
1 parent d8fe7bd commit d5d5053

4 files changed

Lines changed: 222 additions & 5 deletions

File tree

fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/SurveyApiResource.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ public String getClientSurveyOverview(@PathParam("surveyName") final String surv
123123

124124
this.context.authenticatedUser().validateHasReadPermission(SurveyApiConstants.SURVEY_RESOURCE_NAME);
125125

126-
List<ClientScoresOverview> scores = this.readSurveyService.retrieveClientSurveyScoreOverview(clientId);
126+
List<ClientScoresOverview> scores = this.readSurveyService.retrieveClientSurveyScoreOverview(surveyName, clientId);
127127

128128
return this.toApiJsonClientScoreOverviewSerializer.serialize(scores);
129129
}

fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/ReadSurveyServiceImpl.java

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,22 +114,27 @@ public SurveyDataTableData retrieveSurvey(String surveyName) {
114114

115115
@Override
116116
public List<ClientScoresOverview> retrieveClientSurveyScoreOverview(String surveyName, Long clientId) {
117+
sqlValidator.validate(surveyName);
118+
final String registeredSurveyName = retrievePermittedSurveyName(surveyName);
119+
if (registeredSurveyName == null) {
120+
return List.of();
121+
}
117122

118-
final String sql = "SELECT tz.id, lkh.name, lkh.code, poverty_line, tz.date, tz.score FROM ? tz"
123+
final String sql = "SELECT tz.id, lkh.name, lkh.code, poverty_line, tz.date, tz.score FROM " + registeredSurveyName + " tz"
119124
+ " JOIN ppi_likelihoods_ppi lkp on lkp.ppi_name = ? AND enabled = ? "
120125
+ " JOIN ppi_scores sc on score_from <= tz.score AND score_to >=tz.score"
121126
+ " JOIN ppi_poverty_line pvl on pvl.likelihood_ppi_id = lkp.id AND pvl.score_id = sc.id"
122127
+ " JOIN ppi_likelihoods lkh on lkh.id = lkp.likelihood_id " + " WHERE client_id = ? ";
123128

124129
final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql,
125-
new Object[] { surveyName, surveyName, LikelihoodStatus.ENABLED, clientId });
130+
new Object[] { registeredSurveyName, LikelihoodStatus.ENABLED, clientId });
126131

127132
List<ClientScoresOverview> scoresOverviews = new ArrayList<>();
128133

129134
while (rs.next()) {
130135
scoresOverviews.add(new ClientScoresOverview().setLikelihoodCode(rs.getString("code")).setLikelihoodName(rs.getString("name"))
131136
.setScore(rs.getLong("score")).setPovertyLine(rs.getDouble("poverty_line")).setDate(rs.getDate("date").toLocalDate())
132-
.setId(rs.getLong("id")).setSurveyName(surveyName));
137+
.setId(rs.getLong("id")).setSurveyName(registeredSurveyName));
133138
}
134139
return scoresOverviews;
135140
}
@@ -170,16 +175,31 @@ public List<ClientScoresOverview> retrieveClientSurveyScoreOverview(Long clientI
170175
}
171176

172177
private String retrieveAllSurveyNameSQL() {
178+
return retrieveAllSurveyNameSQL("");
179+
}
180+
181+
private String retrieveAllSurveyNameSQL(String andClause) {
173182
// PERMITTED datatables
174183
return "select cf.name from x_registered_table " + " join c_configuration cf on x_registered_table.registered_table_name = cf.name "
175184
+ " where exists" + " (select 'f'" + " from m_appuser_role ur " + " join m_role r on r.id = ur.role_id"
176185
+ " left join m_role_permission rp on rp.role_id = r.id" + " left join m_permission p on p.id = rp.permission_id"
177186
+ " where ur.appuser_id = " + this.context.authenticatedUser().getId()
178187
+ " and (p.code in ('ALL_FUNCTIONS', 'ALL_FUNCTIONS_READ') or p.code = concat('READ_', registered_table_name))) "
179-
+ " and x_registered_table.category = " + DataTableApiConstant.CATEGORY_PPI
188+
+ " and x_registered_table.category = " + DataTableApiConstant.CATEGORY_PPI + andClause
180189
+ " order by application_table_name, registered_table_name";
181190
}
182191

192+
private String retrievePermittedSurveyName(String surveyName) {
193+
final String sql = retrieveAllSurveyNameSQL(" and x_registered_table.registered_table_name = ?");
194+
final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql, surveyName);
195+
if (rs.next()) {
196+
final String registeredSurveyName = rs.getString("name");
197+
sqlValidator.validate(registeredSurveyName);
198+
return registeredSurveyName;
199+
}
200+
return null;
201+
}
202+
183203
@Override
184204
public GenericResultsetData retrieveSurveyEntry(String surveyName, Long clientId, Long entryId) {
185205

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.fineract.infrastructure.survey.api;
20+
21+
import static org.mockito.Mockito.verify;
22+
import static org.mockito.Mockito.when;
23+
24+
import java.util.List;
25+
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
26+
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
27+
import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService;
28+
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
29+
import org.apache.fineract.infrastructure.survey.data.ClientScoresOverview;
30+
import org.apache.fineract.infrastructure.survey.data.SurveyData;
31+
import org.apache.fineract.infrastructure.survey.service.ReadSurveyService;
32+
import org.apache.fineract.useradministration.domain.AppUser;
33+
import org.junit.jupiter.api.BeforeEach;
34+
import org.junit.jupiter.api.Test;
35+
import org.junit.jupiter.api.extension.ExtendWith;
36+
import org.mockito.Mock;
37+
import org.mockito.junit.jupiter.MockitoExtension;
38+
39+
@ExtendWith(MockitoExtension.class)
40+
class SurveyApiResourceTest {
41+
42+
private static final String SURVEY_NAME = "ppi_kenya_2026";
43+
private static final Long CLIENT_ID = 11L;
44+
45+
@Mock
46+
private DefaultToApiJsonSerializer<SurveyData> toApiJsonSerializer;
47+
@Mock
48+
private DefaultToApiJsonSerializer<ClientScoresOverview> toApiJsonClientScoreOverviewSerializer;
49+
@Mock
50+
private PlatformSecurityContext context;
51+
@Mock
52+
private ReadSurveyService readSurveyService;
53+
@Mock
54+
private PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
55+
@Mock
56+
private GenericDataService genericDataService;
57+
@Mock
58+
private AppUser appUser;
59+
60+
private SurveyApiResource underTest;
61+
62+
@BeforeEach
63+
void setUp() {
64+
when(context.authenticatedUser()).thenReturn(appUser);
65+
underTest = new SurveyApiResource(toApiJsonSerializer, toApiJsonClientScoreOverviewSerializer, context, readSurveyService,
66+
commandsSourceWritePlatformService, genericDataService);
67+
}
68+
69+
@Test
70+
void getClientSurveyOverviewRetrievesScoresForRequestedSurvey() {
71+
List<ClientScoresOverview> scores = List.of();
72+
when(readSurveyService.retrieveClientSurveyScoreOverview(SURVEY_NAME, CLIENT_ID)).thenReturn(scores);
73+
when(toApiJsonClientScoreOverviewSerializer.serialize(scores)).thenReturn("[]");
74+
75+
underTest.getClientSurveyOverview(SURVEY_NAME, CLIENT_ID);
76+
77+
verify(appUser).validateHasReadPermission(SurveyApiConstants.SURVEY_RESOURCE_NAME);
78+
verify(readSurveyService).retrieveClientSurveyScoreOverview(SURVEY_NAME, CLIENT_ID);
79+
}
80+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.fineract.infrastructure.survey.service;
20+
21+
import static org.assertj.core.api.Assertions.assertThat;
22+
import static org.mockito.ArgumentMatchers.any;
23+
import static org.mockito.ArgumentMatchers.anyString;
24+
import static org.mockito.Mockito.times;
25+
import static org.mockito.Mockito.verify;
26+
import static org.mockito.Mockito.when;
27+
28+
import java.util.List;
29+
import org.apache.fineract.infrastructure.dataqueries.service.DatatableReadService;
30+
import org.apache.fineract.infrastructure.dataqueries.service.GenericDataService;
31+
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
32+
import org.apache.fineract.infrastructure.security.service.SqlValidator;
33+
import org.apache.fineract.infrastructure.survey.data.ClientScoresOverview;
34+
import org.apache.fineract.infrastructure.survey.data.LikelihoodStatus;
35+
import org.apache.fineract.useradministration.domain.AppUser;
36+
import org.junit.jupiter.api.BeforeEach;
37+
import org.junit.jupiter.api.Test;
38+
import org.junit.jupiter.api.extension.ExtendWith;
39+
import org.mockito.ArgumentCaptor;
40+
import org.mockito.Mock;
41+
import org.mockito.Mockito;
42+
import org.mockito.junit.jupiter.MockitoExtension;
43+
import org.mockito.junit.jupiter.MockitoSettings;
44+
import org.mockito.quality.Strictness;
45+
import org.springframework.jdbc.core.JdbcTemplate;
46+
import org.springframework.jdbc.support.rowset.SqlRowSet;
47+
48+
@ExtendWith(MockitoExtension.class)
49+
@MockitoSettings(strictness = Strictness.LENIENT)
50+
class ReadSurveyServiceImplTest {
51+
52+
private static final Long USER_ID = 7L;
53+
private static final Long CLIENT_ID = 11L;
54+
private static final String SURVEY_NAME = "ppi_kenya_2026";
55+
56+
@Mock
57+
private PlatformSecurityContext context;
58+
@Mock
59+
private JdbcTemplate jdbcTemplate;
60+
@Mock
61+
private SqlValidator sqlValidator;
62+
@Mock
63+
private GenericDataService genericDataService;
64+
@Mock
65+
private DatatableReadService datatableReadService;
66+
@Mock
67+
private AppUser appUser;
68+
69+
private ReadSurveyServiceImpl underTest;
70+
71+
@BeforeEach
72+
void setUp() {
73+
when(context.authenticatedUser()).thenReturn(appUser);
74+
when(appUser.getId()).thenReturn(USER_ID);
75+
underTest = new ReadSurveyServiceImpl(context, jdbcTemplate, sqlValidator, genericDataService, datatableReadService);
76+
}
77+
78+
@Test
79+
void retrieveClientSurveyScoreOverviewUsesRegisteredSurveyNameAsSqlIdentifier() {
80+
SqlRowSet surveyNames = Mockito.mock(SqlRowSet.class);
81+
when(surveyNames.next()).thenReturn(true);
82+
when(surveyNames.getString("name")).thenReturn(SURVEY_NAME);
83+
84+
SqlRowSet scoreRows = Mockito.mock(SqlRowSet.class);
85+
when(scoreRows.next()).thenReturn(false);
86+
87+
when(jdbcTemplate.queryForRowSet(anyString(), any(Object[].class))).thenReturn(surveyNames, scoreRows);
88+
89+
List<ClientScoresOverview> result = underTest.retrieveClientSurveyScoreOverview(SURVEY_NAME, CLIENT_ID);
90+
91+
assertThat(result).isEmpty();
92+
93+
ArgumentCaptor<String> sqlCaptor = ArgumentCaptor.forClass(String.class);
94+
ArgumentCaptor<Object[]> paramsCaptor = ArgumentCaptor.forClass(Object[].class);
95+
verify(jdbcTemplate, times(2)).queryForRowSet(sqlCaptor.capture(), paramsCaptor.capture());
96+
97+
String scoreSql = sqlCaptor.getAllValues().get(1);
98+
Object[] scoreParams = paramsCaptor.getAllValues().get(1);
99+
100+
assertThat(scoreSql).contains("FROM " + SURVEY_NAME + " tz");
101+
assertThat(scoreSql).doesNotContain("FROM ? tz");
102+
assertThat(scoreParams).containsExactly(SURVEY_NAME, LikelihoodStatus.ENABLED, CLIENT_ID);
103+
verify(sqlValidator, times(2)).validate(SURVEY_NAME);
104+
}
105+
106+
@Test
107+
void retrieveClientSurveyScoreOverviewReturnsEmptyListWhenSurveyIsNotPermitted() {
108+
SqlRowSet surveyNames = Mockito.mock(SqlRowSet.class);
109+
when(surveyNames.next()).thenReturn(false);
110+
when(jdbcTemplate.queryForRowSet(anyString(), any(Object[].class))).thenReturn(surveyNames);
111+
112+
List<ClientScoresOverview> result = underTest.retrieveClientSurveyScoreOverview(SURVEY_NAME, CLIENT_ID);
113+
114+
assertThat(result).isEmpty();
115+
verify(jdbcTemplate, times(1)).queryForRowSet(anyString(), any(Object[].class));
116+
}
117+
}

0 commit comments

Comments
 (0)