From 051dce8562dc6152dece5d0313280d261f761293 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Tue, 27 Aug 2024 15:56:09 +0530 Subject: [PATCH] avniproject/avni-webapp#1306 - added guava for set operations used in headers in csv mismatch. made headers mismatch message specific to the missing headers. data setup for integration test, now creates basic org data. first user and catchment test. do not delete data created by migration in setup sql scripts for test. --- avni-server-api/build.gradle | 1 + .../service/MessageTemplateService.java | 5 +- .../batch/csv/writer/BulkLocationEditor.java | 2 + .../csv/writer/UserAndCatchmentWriter.java | 41 ++++--- .../resources/db/migration/R__Functions.sql | 102 ++++++++-------- ...1_343__User1ShouldHaveNoOrganisationId.sql | 2 + .../api/MessageTemplateControllerTest.java | 6 +- .../factory/TestOrganisationBuilder.java | 7 +- .../BulkLocationCreatorIntegrationTest.java | 4 +- ...UserAndCatchmentWriterIntegrationTest.java | 110 ++++++++++++++++++ .../service/builder/TestDataSetupService.java | 22 ++-- .../builder/TestOrganisationService.java | 17 ++- .../server/web/TestWebContextService.java | 6 +- .../src/test/resources/tear-down.sql | 13 ++- .../test-data-openchs-organisation.sql | 15 +-- .../src/test/resources/test-data.sql | 14 +-- 16 files changed, 255 insertions(+), 112 deletions(-) create mode 100644 avni-server-api/src/main/resources/db/migration/V1_343__User1ShouldHaveNoOrganisationId.sql create mode 100644 avni-server-api/src/test/java/org/avni/server/importer/batch/csv/writer/UserAndCatchmentWriterIntegrationTest.java diff --git a/avni-server-api/build.gradle b/avni-server-api/build.gradle index fd6f03335..aedf8c3fd 100644 --- a/avni-server-api/build.gradle +++ b/avni-server-api/build.gradle @@ -91,6 +91,7 @@ dependencies { implementation 'org.apache.commons:commons-csv:1.10.0' compile 'com.googlecode.libphonenumber:libphonenumber:8.12.32' testImplementation 'org.slf4j:slf4j-reload4j:2.0.6' + compile 'com.google.guava:guava:33.2.1-jre' } bootRun { diff --git a/avni-server-api/src/main/java/org/avni/messaging/service/MessageTemplateService.java b/avni-server-api/src/main/java/org/avni/messaging/service/MessageTemplateService.java index 4c91e3143..91de2cbbf 100644 --- a/avni-server-api/src/main/java/org/avni/messaging/service/MessageTemplateService.java +++ b/avni-server-api/src/main/java/org/avni/messaging/service/MessageTemplateService.java @@ -13,9 +13,8 @@ @Service public class MessageTemplateService { - - private GlificMessageTemplateRepository messageTemplateRepository; - private OrganisationConfigService organisationConfigService; + private final GlificMessageTemplateRepository messageTemplateRepository; + private final OrganisationConfigService organisationConfigService; @Autowired public MessageTemplateService(GlificMessageTemplateRepository messageTemplateRepository, OrganisationConfigService organisationConfigService) { diff --git a/avni-server-api/src/main/java/org/avni/server/importer/batch/csv/writer/BulkLocationEditor.java b/avni-server-api/src/main/java/org/avni/server/importer/batch/csv/writer/BulkLocationEditor.java index 6582ac37c..2b236a89f 100644 --- a/avni-server-api/src/main/java/org/avni/server/importer/batch/csv/writer/BulkLocationEditor.java +++ b/avni-server-api/src/main/java/org/avni/server/importer/batch/csv/writer/BulkLocationEditor.java @@ -10,6 +10,7 @@ import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; +import javax.transaction.Transactional; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -70,6 +71,7 @@ private void updateExistingLocation(AddressLevel location, AddressLevel newParen updateLocationProperties(row, allErrorMsgs, location); } + @Transactional(Transactional.TxType.REQUIRES_NEW) public void write(List rows) { List allErrorMsgs = new ArrayList<>(); validateEditModeHeaders(rows.get(0).getHeaders(), allErrorMsgs); diff --git a/avni-server-api/src/main/java/org/avni/server/importer/batch/csv/writer/UserAndCatchmentWriter.java b/avni-server-api/src/main/java/org/avni/server/importer/batch/csv/writer/UserAndCatchmentWriter.java index 32b489e94..74be9a2f9 100644 --- a/avni-server-api/src/main/java/org/avni/server/importer/batch/csv/writer/UserAndCatchmentWriter.java +++ b/avni-server-api/src/main/java/org/avni/server/importer/batch/csv/writer/UserAndCatchmentWriter.java @@ -1,5 +1,7 @@ package org.avni.server.importer.batch.csv.writer; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; import org.avni.server.dao.LocationRepository; import org.avni.server.dao.UserRepository; import org.avni.server.domain.Locale; @@ -17,10 +19,12 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import javax.transaction.Transactional; import java.io.Serializable; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import static java.lang.String.format; import static org.avni.server.domain.OperatingIndividualScope.ByCatchment; @@ -44,8 +48,8 @@ public class UserAndCatchmentWriter implements ItemWriter, Serializable { private static final String ERR_MSG_LOCATION_FIELD = "Provided Location does not exist in Avni. Please add it or check for spelling mistakes '%s'"; private static final String ERR_MSG_LOCALE_FIELD = "Provided value '%s' for Preferred Language is invalid"; private static final String ERR_MSG_DATE_PICKER_FIELD = "Provided value '%s' for Date picker mode is invalid"; - private static final String ERR_MSG_UNKNOWN_HEADERS = "Unknown headers included in file. Please refer to sample file for valid list of headers"; - private static final String ERR_MSG_MISSING_MANDATORY_FIELDS = "Mandatory columns are missing from uploaded file. Please refer to sample file for the list of mandatory headers."; + private static final String ERR_MSG_UNKNOWN_HEADERS = "Unknown headers - %s included in file. Please refer to sample file for valid list of headers"; + private static final String ERR_MSG_MISSING_MANDATORY_FIELDS = "Mandatory columns are missing from uploaded file - %s. Please refer to sample file for the list of mandatory headers."; private static final String ERR_MSG_INVALID_CONCEPT_ANSWER = "'%s' is not a valid value for the concept '%s'" + "To input this value, add this as an answer to the coded concept '%s'"; private static final String METADATA_ROW_START_STRING = "Mandatory field."; @@ -71,9 +75,10 @@ public UserAndCatchmentWriter(CatchmentService catchmentService, this.compoundHeaderPattern = Pattern.compile("^(?.*?)->(?.*)$"); } + @Transactional(Transactional.TxType.REQUIRES_NEW) @Override - public void write(List rows) throws Exception { - if(!CollectionUtils.isEmpty(rows)) { + public void write(List rows) throws IDPException { + if (!CollectionUtils.isEmpty(rows)) { validateHeaders(rows.get(0).getHeaders()); for (Row row : rows) write(row); } @@ -87,27 +92,33 @@ private void validateHeaders(String[] headers) { List syncAttributeHeadersForSubjectTypes = subjectTypeService.constructSyncAttributeHeadersForSubjectTypes(); checkForMissingHeaders(headerList, allErrorMsgs, expectedStandardHeaders, syncAttributeHeadersForSubjectTypes); checkForUnknownHeaders(headerList, allErrorMsgs, expectedStandardHeaders, syncAttributeHeadersForSubjectTypes); - if(!allErrorMsgs.isEmpty()) { + if (!allErrorMsgs.isEmpty()) { throw new RuntimeException(String.join(ERR_MSG_DELIMITER, allErrorMsgs)); } } private void checkForUnknownHeaders(List headerList, List allErrorMsgs, List expectedStandardHeaders, List syncAttributeHeadersForSubjectTypes) { headerList.removeIf(StringUtils::isEmpty); - headerList.removeIf(header -> expectedStandardHeaders.contains(header)); - headerList.removeIf(header -> syncAttributeHeadersForSubjectTypes.contains(header)); - if (!headerList.isEmpty()) { - allErrorMsgs.add(ERR_MSG_UNKNOWN_HEADERS); + HashSet expectedHeaders = new HashSet<>(expectedStandardHeaders); + expectedHeaders.addAll(syncAttributeHeadersForSubjectTypes); + Sets.SetView unknownHeaders = Sets.difference(new HashSet<>(headerList), expectedHeaders); + if (!unknownHeaders.isEmpty()) { + allErrorMsgs.add(String.format(ERR_MSG_UNKNOWN_HEADERS, String.join(", ", unknownHeaders))); } } private void checkForMissingHeaders(List headerList, List allErrorMsgs, List expectedStandardHeaders, List syncAttributeHeadersForSubjectTypes) { - if (headerList.isEmpty() || !headerList.containsAll(expectedStandardHeaders) || !headerList.containsAll(syncAttributeHeadersForSubjectTypes)) { - allErrorMsgs.add(ERR_MSG_MISSING_MANDATORY_FIELDS); + HashSet expectedHeaders = new HashSet<>(expectedStandardHeaders); + expectedHeaders.addAll(syncAttributeHeadersForSubjectTypes); + HashSet presentHeaders = new HashSet<>(headerList); + presentHeaders.addAll(syncAttributeHeadersForSubjectTypes); + Sets.SetView missingHeaders = Sets.difference(expectedHeaders, presentHeaders); + if (!missingHeaders.isEmpty()) { + allErrorMsgs.add(String.format(ERR_MSG_MISSING_MANDATORY_FIELDS, String.join(", ", missingHeaders))); } } - private void write(Row row) throws Exception { + private void write(Row row) throws IDPException { List rowValidationErrorMsgs = new ArrayList<>(); String fullAddress = row.get(LOCATION_WITH_FULL_HIERARCHY); if (fullAddress != null && fullAddress.startsWith(METADATA_ROW_START_STRING)) return; @@ -179,7 +190,7 @@ private void validateRowAndAssimilateErrors(List rowValidationErrorMsgs, extractUserUsernameValidationErrMsg(rowValidationErrorMsgs, username, userSuffix); extractUserNameValidationErrMsg(rowValidationErrorMsgs, nameOfUser); extractUserEmailValidationErrMsg(rowValidationErrorMsgs, email); - if(!rowValidationErrorMsgs.isEmpty()) { + if (!rowValidationErrorMsgs.isEmpty()) { throw new RuntimeException(String.join(ERR_MSG_DELIMITER, rowValidationErrorMsgs)); } } @@ -209,7 +220,7 @@ private void extractUserUsernameValidationErrMsg(List rowValidationError } private void addErrMsgIfValidationFails(boolean validationCheckResult, List rowValidationErrorMsgs, String validationErrorMessage) { - if(validationCheckResult) { + if (validationCheckResult) { rowValidationErrorMsgs.add(validationErrorMessage); } } @@ -273,7 +284,7 @@ private List findSyncSettingCodedConceptValues(List syncSettings List syncSettingCodedConceptValues = new ArrayList<>(); for (String syncSettingsValue : syncSettingsValues) { Optional conceptAnswer = Optional.ofNullable(conceptService.getByName(syncSettingsValue)); - if(conceptAnswer.isPresent()) { + if (conceptAnswer.isPresent()) { syncSettingCodedConceptValues.add(conceptAnswer.get().getUuid()); } else { rowValidationErrorMsgs.add(format(ERR_MSG_INVALID_CONCEPT_ANSWER, syncSettingsValue, concept.getName(), concept.getName())); diff --git a/avni-server-api/src/main/resources/db/migration/R__Functions.sql b/avni-server-api/src/main/resources/db/migration/R__Functions.sql index c5dc431dc..a85973026 100644 --- a/avni-server-api/src/main/resources/db/migration/R__Functions.sql +++ b/avni-server-api/src/main/resources/db/migration/R__Functions.sql @@ -10,18 +10,18 @@ $BODY$ EXECUTE 'GRANT ' || quote_ident(inrolname) || ' TO openchs'; PERFORM grant_all_on_all(inrolname); RETURN 1; - END +END $BODY$ LANGUAGE PLPGSQL; DROP FUNCTION IF EXISTS create_implementation_schema(text); CREATE OR REPLACE FUNCTION create_implementation_schema(schema_name text, db_user text) - RETURNS BIGINT AS + RETURNS BIGINT AS $BODY$ BEGIN - EXECUTE 'CREATE SCHEMA IF NOT EXISTS "' || schema_name || '" AUTHORIZATION "' || db_user || '"'; - EXECUTE 'GRANT ALL PRIVILEGES ON SCHEMA "' || schema_name || '" TO "' || db_user || '"'; - RETURN 1; + EXECUTE 'CREATE SCHEMA IF NOT EXISTS "' || schema_name || '" AUTHORIZATION "' || db_user || '"'; + EXECUTE 'GRANT ALL PRIVILEGES ON SCHEMA "' || schema_name || '" TO "' || db_user || '"'; + RETURN 1; END $BODY$ LANGUAGE PLPGSQL; @@ -29,20 +29,20 @@ $BODY$ LANGUAGE PLPGSQL; CREATE OR REPLACE FUNCTION jsonb_object_values_contain(obs JSONB, pattern TEXT) RETURNS BOOLEAN AS $$ BEGIN - return EXISTS (select true from jsonb_each_text(obs) where value ilike pattern); + return EXISTS (select true from jsonb_each_text(obs) where value ilike pattern); END; $$ -LANGUAGE plpgsql IMMUTABLE; + LANGUAGE plpgsql IMMUTABLE; CREATE OR REPLACE FUNCTION create_audit(user_id NUMERIC) RETURNS INTEGER AS $$ DECLARE result INTEGER; BEGIN - INSERT INTO audit(created_by_id, last_modified_by_id, created_date_time, last_modified_date_time) + INSERT INTO audit(created_by_id, last_modified_by_id, created_date_time, last_modified_date_time) VALUES(user_id, user_id, now(), now()) RETURNING id into result; - RETURN result; + RETURN result; END $$ - LANGUAGE plpgsql; + LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION create_audit() RETURNS INTEGER AS 'select create_audit(1)' language sql; @@ -58,7 +58,7 @@ CREATE OR REPLACE FUNCTION create_view(schema_name text, view_name text, sql_que RETURNS BIGINT AS $BODY$ BEGIN --- EXECUTE 'set search_path = ' || ; + -- EXECUTE 'set search_path = ' || ; EXECUTE 'DROP VIEW IF EXISTS ' || schema_name || '.' || view_name; EXECUTE 'CREATE OR REPLACE VIEW ' || schema_name || '.' || view_name || ' AS ' || sql_query; RETURN 1; @@ -207,13 +207,13 @@ $body$ BEGIN EXECUTE ( SELECT 'GRANT ALL ON TABLE ' - || tablename + || tablename || ' TO ' || quote_ident(rolename) ); EXECUTE ( SELECT 'GRANT SELECT ON ' - || tablename + || tablename || ' TO ' || quote_ident(rolename) ); @@ -228,19 +228,19 @@ $body$ BEGIN EXECUTE ( SELECT 'GRANT ALL ON TABLE ' - || string_agg(format('%I.%I', table_schema, table_name), ',') - || ' TO ' || quote_ident(rolename) || '' - FROM information_schema.tables - WHERE table_schema = 'public' + || string_agg(format('%I.%I', table_schema, table_name), ',') + || ' TO ' || quote_ident(rolename) || '' + FROM information_schema.tables + WHERE table_schema = 'public' AND table_type = 'BASE TABLE' ); EXECUTE ( SELECT 'GRANT SELECT ON ' - || string_agg(format('%I.%I', schemaname, viewname), ',') - || ' TO ' || quote_ident(rolename) || '' - FROM pg_catalog.pg_views - WHERE schemaname = 'public' + || string_agg(format('%I.%I', schemaname, viewname), ',') + || ' TO ' || quote_ident(rolename) || '' + FROM pg_catalog.pg_views + WHERE schemaname = 'public' and viewowner in ('openchs') ); @@ -276,21 +276,21 @@ CREATE OR REPLACE FUNCTION multi_select_coded(obs JSONB) AS $$ DECLARE result VARCHAR; BEGIN - BEGIN - IF JSONB_TYPEOF(obs) = 'array' - THEN + BEGIN + IF JSONB_TYPEOF(obs) = 'array' + THEN SELECT STRING_AGG(C.NAME, ' ,') FROM JSONB_ARRAY_ELEMENTS_TEXT(obs) AS OB (UUID) - JOIN CONCEPT C ON C.UUID = OB.UUID - INTO RESULT; - ELSE - SELECT SINGLE_SELECT_CODED(obs) INTO RESULT; - END IF; - RETURN RESULT; + JOIN CONCEPT C ON C.UUID = OB.UUID + INTO RESULT; + ELSE + SELECT SINGLE_SELECT_CODED(obs) INTO RESULT; + END IF; + RETURN RESULT; EXCEPTION WHEN OTHERS - THEN - RAISE NOTICE 'Failed while processing multi_select_coded(''%'')', obs :: TEXT; - RAISE NOTICE '% %', SQLERRM, SQLSTATE; - END; + THEN + RAISE NOTICE 'Failed while processing multi_select_coded(''%'')', obs :: TEXT; + RAISE NOTICE '% %', SQLERRM, SQLSTATE; + END; END $$; CREATE OR REPLACE FUNCTION single_select_coded(obs TEXT) @@ -298,34 +298,34 @@ CREATE OR REPLACE FUNCTION single_select_coded(obs TEXT) AS $$ DECLARE result VARCHAR; BEGIN - BEGIN + BEGIN SELECT name FROM concept WHERE uuid = obs - INTO result; - RETURN result; - END; + INTO result; + RETURN result; + END; END $$ - STABLE; + STABLE; CREATE OR REPLACE FUNCTION single_select_coded(obs JSONB) RETURNS VARCHAR LANGUAGE plpgsql AS $$ DECLARE result VARCHAR; BEGIN - BEGIN - IF JSONB_TYPEOF(obs) = 'array' - THEN - SELECT name FROM concept WHERE (obs->>0) = uuid INTO result; - ELSEIF JSONB_TYPEOF(obs) = 'string' - THEN - select name from concept where (array_to_json(array[obs])->>0) = uuid into result; - END IF; - RETURN result; - END; + BEGIN + IF JSONB_TYPEOF(obs) = 'array' + THEN + SELECT name FROM concept WHERE (obs ->> 0) = uuid INTO result; + ELSEIF JSONB_TYPEOF(obs) = 'string' + THEN + select name from concept where (array_to_json(array [obs]) ->> 0) = uuid into result; + END IF; + RETURN result; + END; END $$ - STABLE; + STABLE; CREATE OR REPLACE FUNCTION no_op() RETURNS trigger AS $$ BEGIN RETURN NULL; END; -$$ LANGUAGE plpgsql; \ No newline at end of file +$$ LANGUAGE plpgsql; diff --git a/avni-server-api/src/main/resources/db/migration/V1_343__User1ShouldHaveNoOrganisationId.sql b/avni-server-api/src/main/resources/db/migration/V1_343__User1ShouldHaveNoOrganisationId.sql new file mode 100644 index 000000000..a4b873598 --- /dev/null +++ b/avni-server-api/src/main/resources/db/migration/V1_343__User1ShouldHaveNoOrganisationId.sql @@ -0,0 +1,2 @@ +-- In earlier migration we have set parent_organisation as 1 but fix in production +update users set organisation_id = null where id = 1 and username = 'admin'; diff --git a/avni-server-api/src/test/java/org/avni/messaging/api/MessageTemplateControllerTest.java b/avni-server-api/src/test/java/org/avni/messaging/api/MessageTemplateControllerTest.java index ad32ea28d..22e311a1e 100644 --- a/avni-server-api/src/test/java/org/avni/messaging/api/MessageTemplateControllerTest.java +++ b/avni-server-api/src/test/java/org/avni/messaging/api/MessageTemplateControllerTest.java @@ -4,10 +4,7 @@ import com.github.tomakehurst.wiremock.client.WireMock; import org.avni.messaging.contract.MessageTemplateContract; import org.avni.server.common.AbstractControllerIntegrationTest; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.*; import org.springframework.test.context.jdbc.Sql; import java.util.List; @@ -16,6 +13,7 @@ import static org.assertj.core.api.Assertions.assertThat; @Sql(scripts = {"/test-data.sql"}) +@Ignore // this is not testing much and is setup with admin user which is incorrect public class MessageTemplateControllerTest extends AbstractControllerIntegrationTest { private String SAMPLE_AUTH_RESPONSE = "{\"data\":{\"access_token\":\"SFMyNTY.YjQ2M2MzMmMtNGZlOC00OTEyLWIzYTEtZmRhZTRkOGQ1ZTIx.3TjKqpElrD5N2ffGHEAFX91cyp7zwoTztYR8p1jwwgA\",\"renewal_token\":\"SFMyNTY.MjYxODllMTgtNDM1OC00YjJjLTlmN2MtOTA5MzMwYzM3ZjA2.dDigSwftcGFGHu4o9MwkASp2KqH6eitp1aRmeYSgi5M\",\"token_expiry_time\":\"2022-10-13T21:42:33.342529Z\"}}"; diff --git a/avni-server-api/src/test/java/org/avni/server/domain/factory/TestOrganisationBuilder.java b/avni-server-api/src/test/java/org/avni/server/domain/factory/TestOrganisationBuilder.java index 6729f5939..b7a26058e 100644 --- a/avni-server-api/src/test/java/org/avni/server/domain/factory/TestOrganisationBuilder.java +++ b/avni-server-api/src/test/java/org/avni/server/domain/factory/TestOrganisationBuilder.java @@ -12,7 +12,7 @@ public class TestOrganisationBuilder { public TestOrganisationBuilder withMandatoryFields() { String placeholder = UUID.randomUUID().toString(); - return withUuid(placeholder).withDbUser("testDbUser").withName(placeholder).withSchemaName(placeholder); + return withUuid(placeholder).withDbUser("testDbUserNew").withName(placeholder).withSchemaName(placeholder); } public TestOrganisationBuilder setId(long id) { @@ -20,6 +20,11 @@ public TestOrganisationBuilder setId(long id) { return this; } + public TestOrganisationBuilder withUsernameSuffix(String usernameSuffix) { + organisation.setUsernameSuffix(usernameSuffix); + return this; + } + public TestOrganisationBuilder withSchemaName(String schemaName) { organisation.setSchemaName(schemaName); return this; diff --git a/avni-server-api/src/test/java/org/avni/server/importer/batch/csv/writer/BulkLocationCreatorIntegrationTest.java b/avni-server-api/src/test/java/org/avni/server/importer/batch/csv/writer/BulkLocationCreatorIntegrationTest.java index b2331ade3..8dd8ce855 100644 --- a/avni-server-api/src/test/java/org/avni/server/importer/batch/csv/writer/BulkLocationCreatorIntegrationTest.java +++ b/avni-server-api/src/test/java/org/avni/server/importer/batch/csv/writer/BulkLocationCreatorIntegrationTest.java @@ -82,7 +82,7 @@ private static int newLocationsCreated(int count) { return count; } - private void assertlineageExists(String... lineage) { + private void assertLineageExists(String... lineage) { String titleLineage = String.join(", ", lineage); AddressLevel address = this.locationRepository.findByTitleLineageIgnoreCase(titleLineage).get(); assertNotNull(titleLineage, address); @@ -106,7 +106,7 @@ private void success(String[] headers, String[] cells, int numberOfNewLocations, bulkLocationCreator.write(Collections.singletonList(new Row(headers, cells)), hierarchy); long after = addressLevelRepository.count(); assertEquals(before + newLocationsCreated(numberOfNewLocations), after); - Arrays.stream(lineages).forEach(this::assertlineageExists); + Arrays.stream(lineages).forEach(this::assertLineageExists); } private void failure(String[] headers, String[] cells, String errorMessage) { diff --git a/avni-server-api/src/test/java/org/avni/server/importer/batch/csv/writer/UserAndCatchmentWriterIntegrationTest.java b/avni-server-api/src/test/java/org/avni/server/importer/batch/csv/writer/UserAndCatchmentWriterIntegrationTest.java new file mode 100644 index 000000000..7986a3d55 --- /dev/null +++ b/avni-server-api/src/test/java/org/avni/server/importer/batch/csv/writer/UserAndCatchmentWriterIntegrationTest.java @@ -0,0 +1,110 @@ +package org.avni.server.importer.batch.csv.writer; + +import org.avni.server.application.*; +import org.avni.server.dao.CatchmentRepository; +import org.avni.server.dao.LocationRepository; +import org.avni.server.dao.UserRepository; +import org.avni.server.dao.application.FormRepository; +import org.avni.server.domain.*; +import org.avni.server.domain.factory.AddressLevelBuilder; +import org.avni.server.domain.factory.AddressLevelTypeBuilder; +import org.avni.server.domain.factory.metadata.TestFormBuilder; +import org.avni.server.domain.metadata.SubjectTypeBuilder; +import org.avni.server.importer.batch.model.Row; +import org.avni.server.service.IDPException; +import org.avni.server.service.builder.TestConceptService; +import org.avni.server.service.builder.TestDataSetupService; +import org.avni.server.service.builder.TestLocationService; +import org.avni.server.service.builder.TestSubjectTypeService; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.jdbc.Sql; + +import java.util.Arrays; +import java.util.Collections; +import java.util.UUID; + +import static org.junit.Assert.assertEquals; + +@Sql(value = {"/tear-down.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) +@Sql(value = {"/tear-down.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) +public class UserAndCatchmentWriterIntegrationTest extends BaseCSVImportTest { + @Autowired + private TestDataSetupService testDataSetupService; + @Autowired + private TestConceptService testConceptService; + @Autowired + private FormRepository formRepository; + @Autowired + private TestLocationService testLocationService; + @Autowired + private UserAndCatchmentWriter userAndCatchmentWriter; + @Autowired + private TestSubjectTypeService testSubjectTypeService; + @Autowired + private UserRepository userRepository; + @Autowired + private CatchmentRepository catchmentRepository; + + @Override + public void setUp() { + TestDataSetupService.TestOrganisationData organisationData = testDataSetupService.setupOrganisation("example", "User Group 1"); + AddressLevelType block = new AddressLevelTypeBuilder().name("Block").level(2d).withUuid(UUID.randomUUID()).build(); + AddressLevelType district = new AddressLevelTypeBuilder().name("District").level(3d).withUuid(UUID.randomUUID()).build(); + AddressLevelType state = new AddressLevelTypeBuilder().name("State").level(4d).withUuid(UUID.randomUUID()).build(); + testDataSetupService.saveLocationTypes(Arrays.asList(block, district, state)); + Concept codedConcept = testConceptService.createCodedConcept("Sync Concept", "Answer 1", "Answer 2"); + Concept textConcept = testConceptService.createConcept("Text Concept", ConceptDataType.Text); + + testSubjectTypeService.createWithDefaults( + new SubjectTypeBuilder() + .setMandatoryFieldsForNewEntity() + .setUuid(UUID.randomUUID().toString()) + .setName("SubjectTypeWithSyncAttributeBasedSync") + .setSyncRegistrationConcept1Usable(true) + .setSyncRegistrationConcept1(codedConcept.getUuid()).build()); + + AddressLevel bihar = testLocationService.save(new AddressLevelBuilder().withDefaultValuesForNewEntity().title("Bihar").type(state).build()); + AddressLevel district1 = testLocationService.save(new AddressLevelBuilder().withDefaultValuesForNewEntity().title("District1").parent(bihar).type(district).build()); + testLocationService.save(new AddressLevelBuilder().withDefaultValuesForNewEntity().title("Block11").parent(district1).type(block).build()); + testLocationService.save(new AddressLevelBuilder().withDefaultValuesForNewEntity().title("Block12").parent(district1).type(block).build()); + testLocationService.save(new AddressLevelBuilder().withDefaultValuesForNewEntity().title("Block13").parent(district1).type(block).build()); + + AddressLevel district2 = testLocationService.save(new AddressLevelBuilder().withDefaultValuesForNewEntity().title("District2").parent(bihar).type(district).build()); + testLocationService.save(new AddressLevelBuilder().withDefaultValuesForNewEntity().title("Block21").parent(district2).type(block).build()); + testLocationService.save(new AddressLevelBuilder().withDefaultValuesForNewEntity().title("Block22").parent(district2).type(block).build()); + testLocationService.save(new AddressLevelBuilder().withDefaultValuesForNewEntity().title("Block23").parent(district2).type(block).build()); + } + + private boolean catchmentCreated(boolean b) { + return b; + } + + private boolean userCreated(boolean b) { + return b; + } + + private void success(String[] headers, String[] cells, boolean catchmentCreated, boolean userCreated) throws IDPException { + long numberOfUsers = userRepository.count(); + long numberOfCatchments = catchmentRepository.count(); + userAndCatchmentWriter.write(Collections.singletonList(new Row(headers, cells))); + if (catchmentCreated) + assertEquals(catchmentRepository.count(), numberOfCatchments + 1); + else + assertEquals(catchmentRepository.count(), numberOfCatchments); + if (userCreated) + assertEquals(userRepository.count(), numberOfUsers + 1); + else + assertEquals(userRepository.count(), numberOfUsers); + } + + @Test + public void shouldCreateUpdate() throws IDPException { + // new catchment, new user + success( + header("Location with full hierarchy", "Catchment Name", "Username", "Full Name of User", "Email Address", "Mobile Number", "Preferred Language", "Track Location", "Date picker mode", "Enable Beneficiary mode", "Identifier Prefix", "User Groups", "SubjectTypeWithSyncAttributeBasedSync->Sync Concept"), + dataRow("Bihar, District1, Block11", "Catchment 1", "username1@example", "User 1", "username1@example.com", "9455509147", "English", "true", "spinner", "false", "", "User Group 1", "Answer 1"), + catchmentCreated(true), + userCreated(true)); + } +} diff --git a/avni-server-api/src/test/java/org/avni/server/service/builder/TestDataSetupService.java b/avni-server-api/src/test/java/org/avni/server/service/builder/TestDataSetupService.java index fa6a18936..a03cb6395 100644 --- a/avni-server-api/src/test/java/org/avni/server/service/builder/TestDataSetupService.java +++ b/avni-server-api/src/test/java/org/avni/server/service/builder/TestDataSetupService.java @@ -45,21 +45,25 @@ public class TestDataSetupService { @Autowired private OrganisationStatusRepository organisationStatusRepository; - public TestOrganisationData setupOrganisation(String orgSuffix) { - Group group = new TestGroupBuilder().withMandatoryFieldsForNewEntity().build(); - User user1 = new UserBuilder().withDefaultValuesForNewEntity().userName(String.format("user@%s", orgSuffix)).withAuditUser(userRepository.getDefaultSuperAdmin()).build(); - User user2 = new UserBuilder().withDefaultValuesForNewEntity().userName(String.format("user2@%s", orgSuffix)).withAuditUser(userRepository.getDefaultSuperAdmin()).build(); + public TestOrganisationData setupOrganisation(String orgSuffix, String userGroupName) { + User defaultSuperAdmin = userRepository.getDefaultSuperAdmin(); + testWebContextService.setUser(defaultSuperAdmin); + User user1 = new UserBuilder().withDefaultValuesForNewEntity().userName(String.format("user@%s", orgSuffix)).withAuditUser(defaultSuperAdmin).build(); Organisation organisation = new TestOrganisationBuilder() + .withUsernameSuffix(orgSuffix) .setCategory(organisationCategoryRepository.findEntity(1L)) .withStatus(organisationStatusRepository.findEntity(1L)) .withMandatoryFields() .withAccount(accountRepository.getDefaultAccount()).build(); testOrganisationService.createOrganisation(organisation, user1); - testOrganisationService.createUser(organisation, user2); - userRepository.save(new UserBuilder(user1).withAuditUser(user1).build()); - userRepository.save(new UserBuilder(user2).withAuditUser(user1).build()); + testWebContextService.setUser(user1.getUsername()); + + Group group = new TestGroupBuilder().withMandatoryFieldsForNewEntity().withName(userGroupName).build(); + User user2 = new UserBuilder().withDefaultValuesForNewEntity().userName(String.format("user2@%s", orgSuffix)).withAuditUser(user1).build(); + testOrganisationService.createUser(organisation, user2); + userRepository.save(new UserBuilder(user2).withAuditUser(user1).build()); organisationConfigRepository.save(new TestOrganisationConfigBuilder().withMandatoryFields().withOrganisationId(organisation.getId()).build()); groupRepository.save(group); @@ -70,6 +74,10 @@ public TestOrganisationData setupOrganisation(String orgSuffix) { return testOrganisationData; } + public TestOrganisationData setupOrganisation(String orgSuffix) { + return this.setupOrganisation(orgSuffix, UUID.randomUUID().toString()); + } + public TestOrganisationData setupOrganisation() { return this.setupOrganisation("example"); } diff --git a/avni-server-api/src/test/java/org/avni/server/service/builder/TestOrganisationService.java b/avni-server-api/src/test/java/org/avni/server/service/builder/TestOrganisationService.java index bb97fb909..f21ccf132 100644 --- a/avni-server-api/src/test/java/org/avni/server/service/builder/TestOrganisationService.java +++ b/avni-server-api/src/test/java/org/avni/server/service/builder/TestOrganisationService.java @@ -3,6 +3,9 @@ import org.avni.server.dao.*; import org.avni.server.domain.Organisation; import org.avni.server.domain.User; +import org.avni.server.framework.security.UserContextHolder; +import org.avni.server.service.OrganisationService; +import org.avni.server.web.TestWebContextService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -11,22 +14,28 @@ public class TestOrganisationService { private final ImplementationRepository implementationRepository; private final OrganisationRepository organisationRepository; private final UserRepository userRepository; + private final TestWebContextService testWebContextService; + private final OrganisationService organisationService; @Autowired - public TestOrganisationService(ImplementationRepository implementationRepository, OrganisationRepository organisationRepository, UserRepository userRepository) { + public TestOrganisationService(ImplementationRepository implementationRepository, OrganisationRepository organisationRepository, UserRepository userRepository, TestWebContextService testWebContextService, OrganisationService organisationService) { this.implementationRepository = implementationRepository; this.organisationRepository = organisationRepository; this.userRepository = userRepository; + this.testWebContextService = testWebContextService; + this.organisationService = organisationService; } public void createOrganisation(Organisation organisation, User adminUser) { organisationRepository.save(organisation); - createUser(organisation, adminUser); + User orgUser = createUser(organisation, adminUser); implementationRepository.createDBUser(organisation); + testWebContextService.setUser(orgUser); + organisationService.setupBaseOrganisationMetadata(organisation); } - public void createUser(Organisation organisation, User user) { + public User createUser(Organisation organisation, User user) { user.setOrganisationId(organisation.getId()); - userRepository.save(user); + return userRepository.save(user); } } diff --git a/avni-server-api/src/test/java/org/avni/server/web/TestWebContextService.java b/avni-server-api/src/test/java/org/avni/server/web/TestWebContextService.java index f4c5a03d9..3d40e1123 100644 --- a/avni-server-api/src/test/java/org/avni/server/web/TestWebContextService.java +++ b/avni-server-api/src/test/java/org/avni/server/web/TestWebContextService.java @@ -2,6 +2,7 @@ import org.avni.server.dao.OrganisationRepository; import org.avni.server.dao.UserRepository; +import org.avni.server.domain.Organisation; import org.avni.server.domain.User; import org.avni.server.domain.UserContext; import org.avni.server.framework.security.UserContextHolder; @@ -40,7 +41,10 @@ public void setUser(User user) { userContext.setUser(user); UserContextHolder.create(userContext); - userContext.setOrganisation(organisationRepository.findOne(user.getOrganisationId())); + if (user.getOrganisationId() != null) { + Organisation organisation = organisationRepository.findOne(user.getOrganisationId()); + userContext.setOrganisation(organisation); + } SimpleGrantedAuthority[] authorities = Stream.of(USER_AUTHORITY) .filter(authority -> userContext.getRoles().contains(authority.getAuthority())) .toArray(SimpleGrantedAuthority[]::new); diff --git a/avni-server-api/src/test/resources/tear-down.sql b/avni-server-api/src/test/resources/tear-down.sql index cbe5c0aa2..f4f88a9e2 100644 --- a/avni-server-api/src/test/resources/tear-down.sql +++ b/avni-server-api/src/test/resources/tear-down.sql @@ -30,7 +30,8 @@ DELETE FROM location_location_mapping where 1 = 1; DELETE FROM address_level where 1 = 1; DELETE FROM address_level_type where 1 = 1; DELETE FROM catchment where 1 = 1; -DELETE FROM account_admin where 1 = 1; +DELETE FROM account_admin + where admin_id <> (select id from users where username = 'admin'); DELETE FROM user_group where 1 = 1; DELETE FROM external_system_config where 1 = 1; DELETE FROM organisation_config where 1 = 1; @@ -40,12 +41,18 @@ delete from message_rule where 1 = 1; delete from identifier_user_assignment where 1 = 1; delete from identifier_source where 1 = 1; DELETE FROM reset_sync where 1 = 1; -DELETE FROM users where id <> 1; DELETE FROM operational_subject_type where 1 = 1; DELETE FROM subject_type where 1 = 1; delete from group_role where 1 = 1; +delete from dashboard_section_card_mapping where 1 = 1; +delete from report_card where 1 = 1; +delete from group_dashboard where 1 = 1; +delete from dashboard_section where 1 = 1; +delete from dashboard_filter where 1 = 1; +delete from dashboard where 1 = 1; DELETE FROM groups where 1 = 1; -DELETE FROM organisation where id <> 1; +DELETE FROM users where username <> 'admin'; +DELETE FROM organisation where name <> 'OpenCHS'; DELETE FROM audit where 1 = 1; ALTER SEQUENCE non_applicable_form_element_id_seq RESTART WITH 1; diff --git a/avni-server-api/src/test/resources/test-data-openchs-organisation.sql b/avni-server-api/src/test/resources/test-data-openchs-organisation.sql index 07975de20..b32d004b3 100644 --- a/avni-server-api/src/test/resources/test-data-openchs-organisation.sql +++ b/avni-server-api/src/test/resources/test-data-openchs-organisation.sql @@ -38,8 +38,7 @@ DELETE FROM address_level; DELETE FROM catchment; -DELETE -FROM account_admin; +DELETE FROM account_admin where admin_id <> (select id from users where username = 'admin'); delete from user_group; DELETE @@ -52,16 +51,14 @@ DELETE from message_receiver; DELETE from message_rule; -DELETE -FROM users; +DELETE FROM users where username <> 'admin'; DELETE FROM subject_type; DELETE FROM groups; DELETE FROM group_privilege; -DELETE -FROM organisation; +DELETE FROM organisation where name <> 'OpenCHS'; ALTER SEQUENCE form_element_id_seq RESTART WITH 1; ALTER SEQUENCE form_element_group_id_seq RESTART WITH 1; @@ -85,9 +82,3 @@ ALTER SEQUENCE individual_relation_id_seq RESTART WITH 1; ALTER SEQUENCE individual_relation_gender_mapping_id_seq RESTART WITH 1; ALTER SEQUENCE individual_relationship_type_id_seq RESTART WITH 1; ALTER SEQUENCE individual_relationship_id_seq RESTART WITH 1; - -INSERT INTO organisation (id, name, db_user, media_directory, uuid, schema_name, category_id, status_id) -VALUES (1, 'OpenCHS', 'openchs', 'openchs_impl', '3539a906-dfae-4ec3-8fbb-1b08f35c3884', 'openchs', 1, 1); - -INSERT INTO users (id, username, uuid, organisation_id, operating_individual_scope, name) -VALUES (1, 'admin', '5fed2907-df3a-4867-aef5-c87f4c78a31a', 1, 'None', 'admin'); diff --git a/avni-server-api/src/test/resources/test-data.sql b/avni-server-api/src/test/resources/test-data.sql index f83447a44..9ac4f3f38 100644 --- a/avni-server-api/src/test/resources/test-data.sql +++ b/avni-server-api/src/test/resources/test-data.sql @@ -22,9 +22,10 @@ DELETE FROM individual_relation_gender_mapping; DELETE FROM individual_relation; DELETE FROM gender; DELETE FROM catchment_address_mapping; +DELETE FROM location_location_mapping; DELETE FROM address_level; DELETE FROM catchment; -DELETE FROM account_admin; +DELETE FROM account_admin where admin_id <> (select id from users where username = 'admin'); DELETE FROM user_group; DELETE FROM external_system_config; DELETE FROM organisation_config; @@ -32,11 +33,12 @@ DELETE from message_request_queue; DELETE from message_receiver; DELETE from message_rule; DELETE FROM reset_sync; -DELETE FROM users; +DELETE FROM users where username <> 'admin'; DELETE FROM subject_type; DELETE FROM group_privilege; +DELETE FROM group_dashboard; DELETE FROM groups; -DELETE FROM organisation; +DELETE FROM organisation where name <> 'OpenCHS'; DELETE FROM audit; ALTER SEQUENCE non_applicable_form_element_id_seq RESTART WITH 1; @@ -68,10 +70,6 @@ ALTER SEQUENCE message_receiver_id_seq RESTART WITH 1; ALTER SEQUENCE message_request_queue_id_seq RESTART WITH 1; ALTER SEQUENCE message_rule_id_seq RESTART WITH 1; -INSERT into organisation(id, name, db_user, uuid, media_directory, parent_organisation_id, schema_name, category_id, status_id) -values (1, 'OpenCHS', 'openchs', '3539a906-dfae-4ec3-8fbb-1b08f35c3884', 'openchs_impl', null, 'openchs', 1, 1) -ON CONFLICT (uuid) DO NOTHING; - select create_db_user('demo', 'password'); INSERT INTO organisation(id, name, db_user, media_directory, uuid, parent_organisation_id, schema_name, category_id, status_id) @@ -90,8 +88,6 @@ ON CONFLICT (uuid) DO NOTHING; insert into subject_type(id, uuid, name, organisation_id, created_by_id, last_modified_by_id, created_date_time, last_modified_date_time) VALUES (1, '9f2af1f9-e150-4f8e-aad3-40bb7eb05aa3', 'Individual', 1, 1, 1, now(), now()); -INSERT INTO users (id, username, uuid, organisation_id, operating_individual_scope, is_org_admin, name) -VALUES (1, 'admin', '5fed2907-df3a-4867-aef5-c87f4c78a31a', 1, 'None', false, 'admin'); INSERT INTO users (id, username, uuid, organisation_id, operating_individual_scope, is_org_admin, name) VALUES (2, 'demo-admin', '0e53a72c-a109-49f2-918c-9599b266a585', 2, 'None', true, 'demo-admin'); INSERT INTO users (id, username, uuid, organisation_id, operating_individual_scope, is_org_admin, name)