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)