From 43c55009b043540d5fab54898ba28f871ca97958 Mon Sep 17 00:00:00 2001 From: Joze RIHTARSIC <joze.rihtarsic@ext.ec.europa.eu> Date: Thu, 29 Nov 2018 04:21:28 +0100 Subject: [PATCH] - update sml unit tests --- .../smp/data/ui/ParticipantSMLRecord.java | 57 +++++ .../smp/data/ui/enums/SMLAction.java | 22 ++ .../smp/services/SMLIntegrationService.java | 58 ++++- .../services/ui/UIServiceGroupService.java | 141 +++++++++-- .../ec/edelivery/smp/sml/SmlConnector.java | 5 +- .../SMLIntegrationServiceIntegrationTest.java | 37 ++- ...ntegrationServiceNoSMLIntegrationTest.java | 6 + ...GroupServiceUpdateListIntegrationTest.java | 221 ++++++++++++++++++ .../smp/smlintegration/SmlConnectorTest.java | 9 +- .../edelivery/smp/testutil/TestDBUtils.java | 4 + .../edelivery/smp/testutil/TestROUtils.java | 9 + .../smp-keystore_multiple_domains.jks | Bin 26112 -> 28718 bytes 12 files changed, 529 insertions(+), 40 deletions(-) create mode 100644 smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/ParticipantSMLRecord.java create mode 100644 smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMLAction.java create mode 100644 smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIServiceGroupServiceUpdateListIntegrationTest.java diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/ParticipantSMLRecord.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/ParticipantSMLRecord.java new file mode 100644 index 000000000..f673f479c --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/ParticipantSMLRecord.java @@ -0,0 +1,57 @@ +package eu.europa.ec.edelivery.smp.data.ui; + +import eu.europa.ec.edelivery.smp.data.model.DBDomain; +import eu.europa.ec.edelivery.smp.data.model.DBServiceGroupDomain; +import eu.europa.ec.edelivery.smp.data.ui.enums.EntityROStatus; +import eu.europa.ec.edelivery.smp.data.ui.enums.SMLAction; + +import java.io.Serializable; + +public class ParticipantSMLRecord implements Serializable { + + private SMLAction status = SMLAction.REGISTER; + + private String participantIdentifier; + private String participantScheme; + private DBDomain domain; + + public ParticipantSMLRecord(SMLAction status, String participantId,String participantScheme, DBDomain domain ) { + this.status = status; + this.participantIdentifier = participantId; + this.participantScheme = participantScheme; + this.domain = domain; + + } + + public SMLAction getStatus() { + return status; + } + + public void setStatus(SMLAction status) { + this.status = status; + } + + public String getParticipantIdentifier() { + return participantIdentifier; + } + + public void setParticipantId(String participantId) { + this.participantIdentifier = participantId; + } + + public String getParticipantScheme() { + return participantScheme; + } + + public void setParticipantScheme(String participantScheme) { + this.participantScheme = participantScheme; + } + + public DBDomain getDomain() { + return domain; + } + + public void setDomain(DBDomain domain) { + this.domain = domain; + } +} diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMLAction.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMLAction.java new file mode 100644 index 000000000..402b37625 --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMLAction.java @@ -0,0 +1,22 @@ +package eu.europa.ec.edelivery.smp.data.ui.enums; + + +/** + * Enumeraton of Resourceobject statuse . + * @author Joze Rihtarsic + * @since 4.1 + */ +public enum SMLAction { + REGISTER(0), + UNREGISTER(1); + + int actionNumber; + + SMLAction(int actionNumber) { + this.actionNumber = actionNumber; + } + + public int getAction() { + return actionNumber; + } +} diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationService.java index 438799d5a..e5c22634d 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationService.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationService.java @@ -7,6 +7,7 @@ import eu.europa.ec.edelivery.smp.data.dao.ServiceGroupDao; import eu.europa.ec.edelivery.smp.data.model.DBDomain; import eu.europa.ec.edelivery.smp.data.model.DBServiceGroup; import eu.europa.ec.edelivery.smp.data.model.DBServiceGroupDomain; +import eu.europa.ec.edelivery.smp.data.ui.ParticipantSMLRecord; import eu.europa.ec.edelivery.smp.exceptions.SMPRuntimeException; import eu.europa.ec.edelivery.smp.logging.SMPLogger; import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; @@ -126,7 +127,10 @@ public class SMLIntegrationService { } /** - * Method in transaction update servicegroupDomain status and registers participant to SML. + * Method in transaction update servicegroupDomain status and unregisters participant to SML. + * Method is meant for unregistering participants which are still in database. If they are delete + * then this method should not be used. + * * If registration fails - transaction is rolled back * * @param participantId - Participant schema @@ -146,27 +150,63 @@ public class SMLIntegrationService { DBServiceGroupDomain serviceGroupDomain = getAndValidateServiceGroupDomain(participantId, participantSchema, domainCode, BUS_SML_UNREGISTER_SERVICE_GROUP_FAILED); - ParticipantIdentifierType normalizedParticipantId = caseSensitivityNormalizer - .normalizeParticipantIdentifier(participantSchema, - participantId); - // unregister only registered participants if (serviceGroupDomain.isSmlRegistered()) { // update value serviceGroupDomain.setSmlRegistered(false); serviceGroupDao.updateServiceGroupDomain(serviceGroupDomain); - smlConnector.unregisterFromDns(normalizedParticipantId, serviceGroupDomain.getDomain()); + unregisterParticipantFromSML(participantId, participantSchema, serviceGroupDomain.getDomain()); LOG.businessDebug(BUS_SML_UNREGISTER_SERVICE_GROUP, participantId, participantSchema, domainCode); } else { LOG.businessWarn(BUS_SML_UNREGISTER_SERVICE_GROUP_ALREADY_REGISTERED, participantId, participantSchema, domainCode ); } } + /** + * Method unregisters participant from SML. It does not check if Participant is in database or of is unregistered + * + * @param participantId - Participant schema + * @param participantSchema - Participant schema + * @param domain - register to domain + */ + + public boolean unregisterParticipantFromSML(String participantId, String participantSchema, DBDomain domain){ + LOG.businessDebug(BUS_SML_UNREGISTER_SERVICE_GROUP, participantId, participantSchema, domain.getDomainCode()); + + ParticipantIdentifierType normalizedParticipantId = caseSensitivityNormalizer + .normalizeParticipantIdentifier(participantSchema, + participantId); + + // unregister only registered participants + return smlConnector.unregisterFromDns(normalizedParticipantId, domain); + + } + + /** + * Method registers participant to SML. It does not check if Participant is in database or of is already registered + * + * @param participantId - Participant schema + * @param participantSchema - Participant schema + * @param domain - register to domain + */ + + public boolean registerParticipantToSML(String participantId, String participantSchema, DBDomain domain){ + LOG.businessDebug(BUS_SML_UNREGISTER_SERVICE_GROUP, participantId, participantSchema, domain.getDomainCode()); + + ParticipantIdentifierType normalizedParticipantId = caseSensitivityNormalizer + .normalizeParticipantIdentifier(participantSchema, + participantId); + + // unregister only registered participants + return smlConnector.registerInDns(normalizedParticipantId, domain); + + } + private DBServiceGroupDomain getAndValidateServiceGroupDomain(String participantId, String participantSchema, String domainCode, SMPMessageCode messageCode) { // retrieve participant (session must be on - lazy loading... ) Optional<DBServiceGroup> optionalServiceGroup = serviceGroupDao.findServiceGroup(participantId, participantSchema); if (!optionalServiceGroup.isPresent()){ - String msg = "Service group not exists!"; + String msg = "Service group not exists anymore !"; LOG.businessError(messageCode, participantId, participantId, domainCode, msg); throw new SMPRuntimeException(SG_NOT_EXISTS, participantId, participantSchema); } @@ -179,4 +219,8 @@ public class SMLIntegrationService { } return optionalServiceGroupDomain.get(); } + + public boolean isSmlIntegrationEnabled(){ + return smlConnector.isSmlIntegrationEnabled(); + } } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIServiceGroupService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIServiceGroupService.java index 74a5e8cfb..c67e2b4a1 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIServiceGroupService.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIServiceGroupService.java @@ -10,9 +10,11 @@ import eu.europa.ec.edelivery.smp.data.dao.UserDao; import eu.europa.ec.edelivery.smp.data.model.*; import eu.europa.ec.edelivery.smp.data.ui.*; import eu.europa.ec.edelivery.smp.data.ui.enums.EntityROStatus; +import eu.europa.ec.edelivery.smp.data.ui.enums.SMLAction; import eu.europa.ec.edelivery.smp.exceptions.SMPRuntimeException; import eu.europa.ec.edelivery.smp.logging.SMPLogger; import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; +import eu.europa.ec.edelivery.smp.services.SMLIntegrationService; import eu.europa.ec.edelivery.smp.services.ui.filters.ServiceGroupFilter; import eu.europa.ec.smp.api.exceptions.XmlInvalidAgainstSchemaException; import org.apache.commons.lang3.StringUtils; @@ -35,7 +37,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.stream.Collectors; import static eu.europa.ec.edelivery.smp.data.ui.ServiceGroupValidationRO.ERROR_CODE_INVALID_EXTENSION; import static eu.europa.ec.edelivery.smp.data.ui.ServiceGroupValidationRO.ERROR_CODE_OK; @@ -56,7 +57,10 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service UserDao userDao; @Autowired - private CaseSensitivityNormalizer caseSensitivityNormalizer; + CaseSensitivityNormalizer caseSensitivityNormalizer; + + @Autowired + SMLIntegrationService smlIntegrationService; @Override @@ -127,27 +131,91 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service } @Transactional - public void updateServiceGroupList(List<ServiceGroupRO> lst) { + public List<ParticipantSMLRecord> updateServiceGroupList(List<ServiceGroupRO> lst) { boolean suc = false; + List<ParticipantSMLRecord> lstRecords = new ArrayList<>(); for (ServiceGroupRO dRo : lst) { if (dRo.getStatus() == EntityROStatus.NEW.getStatusNumber()) { - addNewServiceGroup(dRo); + lstRecords.addAll(addNewServiceGroup(dRo)); } else if (dRo.getStatus() == EntityROStatus.UPDATED.getStatusNumber()) { - updateServiceGroup(dRo); + lstRecords.addAll(updateServiceGroup(dRo)); } else if (dRo.getStatus() == EntityROStatus.REMOVE.getStatusNumber()) { - DBServiceGroup upd = getDatabaseDao().find(dRo.getId()); - serviceGroupDao.removeServiceGroup(upd); + lstRecords.addAll(removeServiceGroup(dRo)); } } + // register/unregister participants from domain + processSMLRecords(lstRecords); + + + return lstRecords; + } + + /** + * Final process of SML records. If participant is to be unregistered it does not update status to database because + * it should not be there anymore! For registering it update status! + * @param lstRecords + */ + public void processSMLRecords( List<ParticipantSMLRecord> lstRecords){ + if (!smlIntegrationService.isSmlIntegrationEnabled()){ + return; + } + for (ParticipantSMLRecord record: lstRecords){ + if (record.getStatus()== SMLAction.REGISTER){ + boolean result = smlIntegrationService.registerParticipantToSML(record.getParticipantIdentifier(), + record.getParticipantScheme(), record.getDomain()); + + updateServiceGroupDomainStatus(result, record); + }else if (record.getStatus()== SMLAction.UNREGISTER){ + boolean result = smlIntegrationService.unregisterParticipantFromSML(record.getParticipantIdentifier(), + record.getParticipantScheme(), record.getDomain()); + // no need to update database because record is deleted + updateServiceGroupDomainStatus(result, record); + + } + } + } + + protected void updateServiceGroupDomainStatus(boolean smlActionStatus, ParticipantSMLRecord record){ + Optional<DBServiceGroupDomain> optionalServiceGroupDomain = serviceGroupDao.findServiceGroupDomain(record.getParticipantIdentifier(), + record.getParticipantScheme(), record.getDomain().getDomainCode()); + if (optionalServiceGroupDomain.isPresent()) { + DBServiceGroupDomain serviceGroupDomain = optionalServiceGroupDomain.get(); + if (serviceGroupDomain.isSmlRegistered()!= smlActionStatus){ + serviceGroupDomain.setSmlRegistered(smlActionStatus); + serviceGroupDao.updateServiceGroupDomain(serviceGroupDomain); + } + + } + } + + /** + * Remove service group + * @param dRo + * @return + */ + public List<ParticipantSMLRecord> removeServiceGroup(ServiceGroupRO dRo){ + List<ParticipantSMLRecord> participantSMLRecordList = new ArrayList<>(); + + DBServiceGroup dbServiceGroup = getDatabaseDao().find(dRo.getId()); + // first update domains + List<DBServiceGroupDomain> dbServiceGroupDomainList = dbServiceGroup.getServiceGroupDomains(); + dbServiceGroupDomainList.forEach(dro -> { + participantSMLRecordList.add( new ParticipantSMLRecord(SMLAction.UNREGISTER, dro.getServiceGroup().getParticipantIdentifier(), + dro.getServiceGroup().getParticipantScheme(),dro.getDomain())); + }); + serviceGroupDao.removeServiceGroup(dbServiceGroup); + return participantSMLRecordList; } + + /** * Method validates and converts UI resource object entity to database entity and persists it to database * * @param serviceGroupRO */ - private void addNewServiceGroup(ServiceGroupRO serviceGroupRO) { - // normalize indentifiers + protected List<ParticipantSMLRecord> addNewServiceGroup(ServiceGroupRO serviceGroupRO) { + // normalize identifiers normalizeIdentifiers(serviceGroupRO); DBServiceGroup dbServiceGroup = new DBServiceGroup(); @@ -159,7 +227,7 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service // first update domains // validate (if domains are added only once) and create domain list for service group. - createDomainsForNewServiceGroup(serviceGroupRO, dbServiceGroup); + List<ParticipantSMLRecord> listOfActions = createDomainsForNewServiceGroup(serviceGroupRO, dbServiceGroup); // sort service metadata by domain @@ -181,6 +249,7 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service dbServiceGroup.setExtension(buff); } getDatabaseDao().persistFlushDetach(dbServiceGroup); + return listOfActions; } private void normalizeIdentifiers(ServiceGroupRO sgo) { @@ -202,19 +271,26 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service * @param serviceGroupRO * @param dbServiceGroup */ - protected void createDomainsForNewServiceGroup(ServiceGroupRO serviceGroupRO, DBServiceGroup dbServiceGroup) { + protected List<ParticipantSMLRecord> createDomainsForNewServiceGroup(ServiceGroupRO serviceGroupRO, DBServiceGroup dbServiceGroup) { + + List<ParticipantSMLRecord> participantSMLRecordList = new ArrayList<>(); // first update domains - List<ServiceGroupDomainRO> serviceGroupDomainROList = validateDomainList(serviceGroupRO); + List<ServiceGroupDomainRO> serviceGroupDomainROList = serviceGroupRO.getServiceGroupDomains(); // validate (if domains are added only once) and create domain list for service group. serviceGroupDomainROList.forEach(dro -> { // everting ok find domain and add it to service group Optional<DBDomain> dmn = domainDao.getDomainByCode(dro.getDomainCode()); if (dmn.isPresent()) { - dbServiceGroup.addDomain(dmn.get()); + DBServiceGroupDomain domain = dbServiceGroup.addDomain(dmn.get()); + participantSMLRecordList.add( new ParticipantSMLRecord(SMLAction.REGISTER, + serviceGroupRO.getParticipantIdentifier(), + serviceGroupRO.getParticipantScheme(), + domain.getDomain())); } else { throw new SMPRuntimeException(DOMAIN_NOT_EXISTS, dro.getDomainCode()); } }); + return participantSMLRecordList; } @@ -223,8 +299,9 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service * * @param serviceGroupRO */ - protected void updateServiceGroup(ServiceGroupRO serviceGroupRO) { - // normalize indentifiers + protected List<ParticipantSMLRecord> updateServiceGroup(ServiceGroupRO serviceGroupRO) { + + // normalize identifiers normalizeIdentifiers(serviceGroupRO); // find and validate service group DBServiceGroup dbServiceGroup = findAndValidateServiceGroup(serviceGroupRO); @@ -233,7 +310,7 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service updateUsersOnServiceGroup(serviceGroupRO, dbServiceGroup); // update domain - updateDomainsForServiceGroup(serviceGroupRO, dbServiceGroup); + List<ParticipantSMLRecord> participantSMLRecordList = updateDomainsForServiceGroup(serviceGroupRO, dbServiceGroup); //update service metadata List<ServiceMetadataRO> serviceMetadataROList = serviceGroupRO.getServiceMetadata(); @@ -304,6 +381,7 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service // persist it to database getDatabaseDao().update(dbServiceGroup); + return participantSMLRecordList; } /** @@ -312,9 +390,12 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service * @param serviceGroupRO * @param dbServiceGroup */ - protected void updateDomainsForServiceGroup(ServiceGroupRO serviceGroupRO, DBServiceGroup dbServiceGroup) { + protected List<ParticipantSMLRecord> updateDomainsForServiceGroup(ServiceGroupRO serviceGroupRO, DBServiceGroup dbServiceGroup) { + List<ParticipantSMLRecord> participantSMLRecordList = new ArrayList<>(); + // / validate (if domains are added only once) and create domain list for service group. - List<ServiceGroupDomainRO> serviceGroupDomainROList = validateDomainList(serviceGroupRO); + // List<ServiceGroupDomainRO> serviceGroupDomainROList = validateDomainList(serviceGroupRO); + List<ServiceGroupDomainRO> serviceGroupDomainROList = serviceGroupRO.getServiceGroupDomains(); // copy array list of old domains and then put them back. Domain not added back will be deleted by hibernate // ... List<DBServiceGroupDomain> lstOldSGDomains = new ArrayList<>(); @@ -327,22 +408,34 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service if (dsg != null) { // put it back - no need to call addDomain dbServiceGroup.getServiceGroupDomains().add(dsg); - // remove from list + // remove from old domain list lstOldSGDomains.remove(dsg); } else { - // everting ok find domain and add it to service group + // new domain - find dbDomain and add it to service group Optional<DBDomain> dmn = domainDao.getDomainByCode(serviceGroupDomainRO.getDomainCode()); if (dmn.isPresent()) { - dbServiceGroup.addDomain(dmn.get()); + + DBServiceGroupDomain sgd = dbServiceGroup.addDomain(dmn.get()); + participantSMLRecordList.add(new ParticipantSMLRecord( SMLAction.REGISTER, + sgd.getServiceGroup().getParticipantIdentifier(), + sgd.getServiceGroup().getParticipantScheme(), + sgd.getDomain())); } else { throw new SMPRuntimeException(DOMAIN_NOT_EXISTS, serviceGroupDomainRO.getDomainCode()); } } }); - // remove references + // remove old domains lstOldSGDomains.forEach(dbServiceGroupDomain -> { + participantSMLRecordList.add(new ParticipantSMLRecord( SMLAction.UNREGISTER, + dbServiceGroupDomain.getServiceGroup().getParticipantIdentifier(), + dbServiceGroupDomain.getServiceGroup().getParticipantScheme(), + dbServiceGroupDomain.getDomain())); + dbServiceGroupDomain.setServiceGroup(null); + }); + return participantSMLRecordList; } /** @@ -350,7 +443,7 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service * * @param serviceGroupRO * @return - */ + * protected List<ServiceGroupDomainRO> validateDomainList(ServiceGroupRO serviceGroupRO) { List<ServiceGroupDomainRO> serviceGroupDomainROList = serviceGroupRO.getServiceGroupDomains(); // validate (if domains are added only once) and create domain list for service group. @@ -365,7 +458,7 @@ public class UIServiceGroupService extends UIServiceBase<DBServiceGroup, Service } }); return serviceGroupDomainROList; - } + }*/ /** * Update users on service group. Method is OK for update and add new domain diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/sml/SmlConnector.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/sml/SmlConnector.java index e82629674..34f175055 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/sml/SmlConnector.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/sml/SmlConnector.java @@ -93,14 +93,15 @@ public class SmlConnector implements ApplicationContextAware { } - public void unregisterFromDns(ParticipantIdentifierType normalizedParticipantId, DBDomain domain) { + public boolean unregisterFromDns(ParticipantIdentifierType normalizedParticipantId, DBDomain domain) { if (!smlIntegrationEnabled) { - return; + return false; } log.info("Removing Participant from BDMSL: {} ", asString(normalizedParticipantId)); try { ServiceMetadataPublisherServiceForParticipantType smlRequest = toBusdoxParticipantId(normalizedParticipantId, domain.getSmlSmpId()); getClient(domain).delete(smlRequest); + return true; } catch (Exception e) { throw new SMPRuntimeException(ErrorCode.SML_INTEGRATION_EXCEPTION,e, ExceptionUtils.getRootCauseMessage(e)); } diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationServiceIntegrationTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationServiceIntegrationTest.java index 87c3b4849..855903ada 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationServiceIntegrationTest.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationServiceIntegrationTest.java @@ -107,7 +107,7 @@ public class SMLIntegrationServiceIntegrationTest extends AbstractServiceIntegra } @Test - public void registerParticipantToSML() throws NotFoundFault, UnauthorizedFault, InternalErrorFault, BadRequestFault { + public void registerParticipant() throws NotFoundFault, UnauthorizedFault, InternalErrorFault, BadRequestFault { /* given (init database - check setup) * Domain: TEST_DOMAIN_CODE_1 * Users: USERNAME_1, USER_CERT_2 @@ -125,7 +125,7 @@ public class SMLIntegrationServiceIntegrationTest extends AbstractServiceIntegra } @Test - public void unRegisterParticipantFromSML() throws NotFoundFault, UnauthorizedFault, InternalErrorFault, BadRequestFault { + public void unRegisterParticipant() throws NotFoundFault, UnauthorizedFault, InternalErrorFault, BadRequestFault { /* given (init database - check setup) * Domain: TEST_DOMAIN_CODE_1 * Users: USERNAME_1, USER_CERT_2 @@ -143,7 +143,7 @@ public class SMLIntegrationServiceIntegrationTest extends AbstractServiceIntegra } @Test - public void registerParticipantToSML_NotExists(){ + public void registerParticipant_NotExists(){ expectedExeption.expect(SMPRuntimeException.class); String notExistsId = TEST_SG_ID_1+"NotExists"; expectedExeption.expectMessage("ServiceGroup not found (part. id: '"+TEST_SG_ID_1+"NotExists', part. sch.: '"+TEST_SG_SCHEMA_1+"')!"); @@ -153,7 +153,7 @@ public class SMLIntegrationServiceIntegrationTest extends AbstractServiceIntegra } @Test - public void registerParticipantToSML_NotOnDomain(){ + public void registerParticipant_NotOnDomain(){ expectedExeption.expect(SMPRuntimeException.class); expectedExeption.expectMessage("Service group not registered for domain (domain: "+TEST_DOMAIN_CODE_2+", part. id: '"+TEST_SG_ID_1+"', part. sch.: '"+TEST_SG_SCHEMA_1+"')!"); @@ -161,6 +161,35 @@ public class SMLIntegrationServiceIntegrationTest extends AbstractServiceIntegra testInstance.registerParticipant(TEST_SG_ID_1,TEST_SG_SCHEMA_1,TEST_DOMAIN_CODE_2 ); } + @Test + public void isSmlIntegrationEnabled(){ + assertTrue(testInstance.isSmlIntegrationEnabled()); + } + + @Test + public void registerParticipantToSML() throws NotFoundFault, UnauthorizedFault, InternalErrorFault, BadRequestFault { + DBDomain testDomain01 = domainDao.getDomainByCode(TEST_DOMAIN_CODE_1).get(); + // when + testInstance.registerParticipantToSML(TEST_SG_ID_1,TEST_SG_SCHEMA_1,testDomain01 ); + + //then -- expect on call + assertEquals(1, integrationMock.getParticipantManagmentClientMocks().size()); + verify(integrationMock.getParticipantManagmentClientMocks().get(0)).create(any()); + Mockito.verifyNoMoreInteractions(integrationMock.getParticipantManagmentClientMocks().toArray()); + } + + @Test + public void unregisterParticipantFromSML() throws NotFoundFault, UnauthorizedFault, InternalErrorFault, BadRequestFault { + DBDomain testDomain01 = domainDao.getDomainByCode(TEST_DOMAIN_CODE_1).get(); + // when + testInstance.unregisterParticipantFromSML(TEST_SG_ID_1,TEST_SG_SCHEMA_1,testDomain01 ); + + //then -- expect on call + assertEquals(1, integrationMock.getParticipantManagmentClientMocks().size()); + verify(integrationMock.getParticipantManagmentClientMocks().get(0)).delete(any()); + Mockito.verifyNoMoreInteractions(integrationMock.getParticipantManagmentClientMocks().toArray()); + } + } diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationServiceNoSMLIntegrationTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationServiceNoSMLIntegrationTest.java index 22465e8fc..3d20fd6a9 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationServiceNoSMLIntegrationTest.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/SMLIntegrationServiceNoSMLIntegrationTest.java @@ -26,6 +26,7 @@ import org.springframework.transaction.annotation.Transactional; import static eu.europa.ec.edelivery.smp.testutil.TestConstants.*; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; /** * Purpose of class is to test ServiceGroupService base methods @@ -87,4 +88,9 @@ public class SMLIntegrationServiceNoSMLIntegrationTest extends AbstractServiceIn testInstance.registerParticipant(TEST_SG_ID_1, TEST_SG_SCHEMA_1, TEST_DOMAIN_CODE_1); } + @Test + public void isSmlIntegrationEnabled(){ + assertFalse(testInstance.isSmlIntegrationEnabled()); + } + } diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIServiceGroupServiceUpdateListIntegrationTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIServiceGroupServiceUpdateListIntegrationTest.java new file mode 100644 index 000000000..03ad6459d --- /dev/null +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIServiceGroupServiceUpdateListIntegrationTest.java @@ -0,0 +1,221 @@ +package eu.europa.ec.edelivery.smp.services.ui; + + +import eu.europa.ec.edelivery.smp.config.SmlIntegrationConfiguration; +import eu.europa.ec.edelivery.smp.data.model.DBDomain; +import eu.europa.ec.edelivery.smp.data.model.DBServiceGroup; +import eu.europa.ec.edelivery.smp.data.model.DBUser; +import eu.europa.ec.edelivery.smp.data.ui.ParticipantSMLRecord; +import eu.europa.ec.edelivery.smp.data.ui.ServiceGroupDomainRO; +import eu.europa.ec.edelivery.smp.data.ui.ServiceGroupRO; +import eu.europa.ec.edelivery.smp.data.ui.ServiceResult; +import eu.europa.ec.edelivery.smp.data.ui.enums.EntityROStatus; +import eu.europa.ec.edelivery.smp.data.ui.enums.SMLAction; +import eu.europa.ec.edelivery.smp.services.AbstractServiceIntegrationTest; +import eu.europa.ec.edelivery.smp.testutil.TestConstants; +import eu.europa.ec.edelivery.smp.testutil.TestDBUtils; +import eu.europa.ec.edelivery.smp.testutil.TestROUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +import static eu.europa.ec.edelivery.smp.testutil.TestConstants.TEST_DOMAIN_CODE_1; +import static eu.europa.ec.edelivery.smp.testutil.TestConstants.TEST_DOMAIN_CODE_2; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + + +/** + * Purpose of class is to test ServiceGroupService base methods + * + * @author Joze Rihtarsic + * @since 4.1 + */ +@ContextConfiguration(classes = {UIServiceGroupService.class, UIServiceMetadataService.class, SmlIntegrationConfiguration.class}) +@TestPropertySource(properties = {"bdmsl.integration.enabled=true"}) +public class UIServiceGroupServiceUpdateListIntegrationTest extends AbstractServiceIntegrationTest { + + @Rule + public ExpectedException expectedExeption = ExpectedException.none(); + + @Autowired + protected UIServiceGroupService testInstance; + + @Autowired + protected UIServiceMetadataService uiServiceMetadataService; + + @Before + public void setup() { + integrationMock.reset(); + prepareDatabaseForMultipeDomainEnv(); + } + + @Autowired + SmlIntegrationConfiguration integrationMock; + + protected void insertDataObjectsForOwner(int size, DBUser owner) { + for (int i = 0; i < size; i++) { + insertServiceGroup(String.format("%4d", i), true, owner); + } + } + + protected void insertDataObjects(int size) { + insertDataObjectsForOwner(size, null); + } + + protected DBServiceGroup insertServiceGroup(String id, boolean withExtension, DBUser owner) { + DBServiceGroup d = TestDBUtils.createDBServiceGroup(String.format("0007:%s:utest", id), TestConstants.TEST_SG_SCHEMA_1, withExtension); + if (owner != null) { + d.getUsers().add(owner); + } + serviceGroupDao.persistFlushDetach(d); + return d; + } + + @Test + public void addNewServiceGroupTestSMLRecords() { + // given + DBDomain dbDomain1 = domainDao.getDomainByCode(TEST_DOMAIN_CODE_1).get(); + DBDomain dbDomain2 = domainDao.getDomainByCode(TEST_DOMAIN_CODE_2).get(); + ServiceGroupRO serviceGroupRO = TestROUtils.createROServiceGroupForDomains(UUID.randomUUID().toString(), UUID.randomUUID().toString(), + dbDomain1, dbDomain2); + // When + List<ParticipantSMLRecord> lst = testInstance.addNewServiceGroup(serviceGroupRO); + // then + assertEquals(2, lst.size()); + assertEquals(SMLAction.REGISTER, lst.get(0).getStatus()); + assertEquals(SMLAction.REGISTER, lst.get(1).getStatus()); + assertEquals(dbDomain1, lst.get(0).getDomain()); + assertEquals(dbDomain2, lst.get(1).getDomain()); + assertEquals(lst.get(0).getParticipantIdentifier(), lst.get(1).getParticipantIdentifier()); + assertEquals(serviceGroupRO.getParticipantIdentifier(), lst.get(0).getParticipantIdentifier()); + assertEquals(serviceGroupRO.getParticipantScheme(), lst.get(0).getParticipantScheme()); + } + + @Test + @Transactional + public void removeServiceGroupTestSMLRecords() { + // given + ServiceResult<ServiceGroupRO> res = testInstance.getTableList(-1, -1, null, null, null); + assertFalse(res.getServiceEntities().isEmpty()); + ServiceGroupRO roToDelete = res.getServiceEntities().get(0); + assertFalse(roToDelete.getServiceGroupDomains().isEmpty()); + + // When + List<ParticipantSMLRecord> lst = testInstance.removeServiceGroup(roToDelete); + // then + assertEquals(roToDelete.getServiceGroupDomains().size(), lst.size()); + lst.forEach(val -> { + assertEquals(SMLAction.UNREGISTER, val.getStatus()); + }); + + } + + @Test + @Transactional + public void updateServiceGroupTestSMLRecordsRemoveDomain() { + + // given + DBDomain dbDomain1 = domainDao.getDomainByCode(TEST_DOMAIN_CODE_1).get(); + DBDomain dbDomain2 = domainDao.getDomainByCode(TEST_DOMAIN_CODE_2).get(); + DBServiceGroup dbServiceGroup = TestDBUtils.createDBServiceGroupRandom(); + dbServiceGroup.addDomain(dbDomain1); + dbServiceGroup.addDomain(dbDomain2); + serviceGroupDao.persistFlushDetach(dbServiceGroup); + ServiceGroupRO roToUpdate = testInstance.getServiceGroupById(dbServiceGroup.getId()); + // when + ServiceGroupDomainRO dro = roToUpdate.getServiceGroupDomains().remove(0); + List<ParticipantSMLRecord> lst = testInstance.updateServiceGroup(roToUpdate); + // then + assertEquals(1, lst.size()); + assertEquals(SMLAction.UNREGISTER, lst.get(0).getStatus()); + assertEquals(dro.getDomainCode(), lst.get(0).getDomain().getDomainCode()); + assertEquals(roToUpdate.getParticipantIdentifier(), lst.get(0).getParticipantIdentifier()); + assertEquals(roToUpdate.getParticipantScheme(), lst.get(0).getParticipantScheme()); + } + + @Test + @Transactional + public void updateServiceGroupTestSMLRecordsAddDomain() { + + // given + DBDomain dbDomain1 = domainDao.getDomainByCode(TEST_DOMAIN_CODE_1).get(); + DBDomain dbDomain2 = domainDao.getDomainByCode(TEST_DOMAIN_CODE_2).get(); + DBServiceGroup dbServiceGroup = TestDBUtils.createDBServiceGroupRandom(); + dbServiceGroup.addDomain(dbDomain1); + serviceGroupDao.persistFlushDetach(dbServiceGroup); + ServiceGroupRO roToUpdate = testInstance.getServiceGroupById(dbServiceGroup.getId()); + // when + ServiceGroupDomainRO sgr = new ServiceGroupDomainRO(); + sgr.setDomainCode(dbDomain2.getDomainCode()); + sgr.setSmlSubdomain(dbDomain2.getSmlSubdomain()); + sgr.setDomainId(dbDomain2.getId()); + roToUpdate.getServiceGroupDomains().add(sgr); + List<ParticipantSMLRecord> lst = testInstance.updateServiceGroup(roToUpdate); + // then + assertEquals(1, lst.size()); + assertEquals(SMLAction.REGISTER, lst.get(0).getStatus()); + assertEquals(sgr.getDomainCode(), lst.get(0).getDomain().getDomainCode()); + assertEquals(roToUpdate.getParticipantIdentifier(), lst.get(0).getParticipantIdentifier()); + } + + @Test + @Transactional + public void updateListSMLRecordsAddDomain() { + + // given + DBDomain dbDomain1 = domainDao.getDomainByCode(TEST_DOMAIN_CODE_1).get(); + DBDomain dbDomain2 = domainDao.getDomainByCode(TEST_DOMAIN_CODE_2).get(); + DBServiceGroup dbServiceGroup1 = TestDBUtils.createDBServiceGroupRandom(); + DBServiceGroup dbServiceGroup2 = TestDBUtils.createDBServiceGroupRandom(); + dbServiceGroup1.addDomain(dbDomain1); + dbServiceGroup2.addDomain(dbDomain1); + serviceGroupDao.persistFlushDetach(dbServiceGroup1); + serviceGroupDao.persistFlushDetach(dbServiceGroup2); + ServiceGroupRO serviceGroupROAdd = TestROUtils.createROServiceGroupForDomains(UUID.randomUUID().toString(), UUID.randomUUID().toString(), + dbDomain1, dbDomain2); + ServiceGroupRO serviceGroupROUpdate = testInstance.getServiceGroupById(dbServiceGroup1.getId()); + ServiceGroupRO serviceGroupRORemove = testInstance.getServiceGroupById(dbServiceGroup2.getId()); + serviceGroupROAdd.setStatus(EntityROStatus.NEW.getStatusNumber()); + serviceGroupRORemove.setStatus(EntityROStatus.REMOVE.getStatusNumber()); + serviceGroupROUpdate.setStatus(EntityROStatus.UPDATED.getStatusNumber()); + serviceGroupROUpdate.getServiceGroupDomains().clear(); + ServiceGroupDomainRO sgr = new ServiceGroupDomainRO(); + sgr.setDomainCode(dbDomain2.getDomainCode()); + sgr.setSmlSubdomain(dbDomain2.getSmlSubdomain()); + sgr.setDomainId(dbDomain2.getId()); + serviceGroupROUpdate.getServiceGroupDomains().add(sgr); + + List<ServiceGroupRO> lstRo = Arrays.asList(serviceGroupROAdd, serviceGroupRORemove, serviceGroupROUpdate); + + List<ParticipantSMLRecord> lst = testInstance.updateServiceGroupList(lstRo); + // then + assertEquals(5, lst.size()); + assertEquals(SMLAction.REGISTER, lst.get(0).getStatus()); + assertEquals(serviceGroupROAdd.getParticipantIdentifier(), lst.get(0).getParticipantIdentifier()); + assertEquals(SMLAction.REGISTER, lst.get(1).getStatus()); + assertEquals(serviceGroupROAdd.getParticipantIdentifier(), lst.get(1).getParticipantIdentifier()); + + + assertEquals(SMLAction.UNREGISTER, lst.get(2).getStatus()); + assertEquals(serviceGroupRORemove.getParticipantIdentifier(), lst.get(2).getParticipantIdentifier()); + + assertEquals(SMLAction.REGISTER, lst.get(3).getStatus()); + assertEquals(dbDomain2.getDomainCode(), lst.get(3).getDomain().getDomainCode()); + assertEquals(serviceGroupROUpdate.getParticipantIdentifier(), lst.get(3).getParticipantIdentifier()); + assertEquals(SMLAction.UNREGISTER, lst.get(4).getStatus()); + assertEquals(dbDomain1.getDomainCode(), lst.get(4).getDomain().getDomainCode()); + + assertEquals(5, integrationMock.getParticipantManagmentClientMocks().size()); + } + +} diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/smlintegration/SmlConnectorTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/smlintegration/SmlConnectorTest.java index e5d5fe42b..7d23f47b5 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/smlintegration/SmlConnectorTest.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/smlintegration/SmlConnectorTest.java @@ -33,6 +33,7 @@ import java.util.ArrayList; import java.util.List; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.springframework.beans.factory.config.ConfigurableBeanFactory.SCOPE_PROTOTYPE; @@ -79,10 +80,11 @@ public class SmlConnectorTest { @Test public void testRegisterInDns() throws UnauthorizedFault, NotFoundFault, InternalErrorFault, BadRequestFault { - //when - smlConnector.registerInDns(PARTICIPANT_ID, DEFAULT_DOMAIN); + //when + boolean result = smlConnector.registerInDns(PARTICIPANT_ID, DEFAULT_DOMAIN); //then + assertTrue(result); assertEquals(1, smlClientMocks.size()); verify(smlClientMocks.get(0)).create(any()); Mockito.verifyNoMoreInteractions(smlClientMocks.toArray()); @@ -104,9 +106,10 @@ public class SmlConnectorTest { @Test public void testUnregisterFromDns() throws UnauthorizedFault, NotFoundFault, InternalErrorFault, BadRequestFault { //when - smlConnector.unregisterFromDns(PARTICIPANT_ID, DEFAULT_DOMAIN); + boolean result = smlConnector.unregisterFromDns(PARTICIPANT_ID, DEFAULT_DOMAIN); //then + assertTrue(result); assertEquals(1, smlClientMocks.size()); verify(smlClientMocks.get(0)).delete(any()); Mockito.verifyNoMoreInteractions(smlClientMocks.toArray()); diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestDBUtils.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestDBUtils.java index 19a6f798e..6b8371403 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestDBUtils.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestDBUtils.java @@ -57,6 +57,10 @@ public class TestDBUtils { return createDBServiceGroup(id, sch, true); } + public static DBServiceGroup createDBServiceGroupRandom() { + return createDBServiceGroup(UUID.randomUUID().toString(), UUID.randomUUID().toString(), true); + } + public static DBServiceGroup createDBServiceGroup(String id, String sch, boolean withExtension) { DBServiceGroup grp = new DBServiceGroup(); grp.setParticipantIdentifier(id); diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestROUtils.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestROUtils.java index 7be76dde9..f107f79b3 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestROUtils.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestROUtils.java @@ -50,6 +50,15 @@ public class TestROUtils { return sgo; } + public static ServiceGroupRO createROServiceGroupForDomains(String id, String sch, DBDomain ... domains) { + ServiceGroupRO sgo = createROServiceGroup(id, sch); + Arrays.asList(domains).forEach(domain -> { + ServiceGroupDomainRO sgd = createServiceGroupDomain(domain); + sgo.getServiceGroupDomains().add(sgd); + }); + return sgo; + } + public static ServiceGroupRO createROServiceGroup(String id, String sch) { return createROServiceGroup(id, sch, true); } diff --git a/smp-webapp/src/test/resources/keystores/smp-keystore_multiple_domains.jks b/smp-webapp/src/test/resources/keystores/smp-keystore_multiple_domains.jks index eedfa138dafe601321b05278cac7fcd2ce830b9c..22e4cc5e038d4501a4bf0d0d95e8d0d0b72d13e4 100644 GIT binary patch delta 1425 zcmZoT!?^ANBhSCL|K2h%FfcJNFo;bK<d@p0wo{qYl7WFSJ?eZ*_T-1rGWGd8ZkbFl z;+Ex8d=kd*UK07+dh!1KZ#g+7?!2>HtGD3eFX>jhU03(oMSjorH(*$B>Ih>|*NiI> zx;2xRZ@GHE-K5t{#`MX{N1J`;{+itRpnF&I+bq)rOQtuQw+Y={Iy-TLOYwZWt1_9- z7m4I7S+ge9@1s=neoOtj;13^{rG9z+v_3LZmw%<x>RcP|Td@Zf7b#v~p1+-md&a}t zU%T8U>%HhLZL^)7eYKiTyEbG)VRn_H>-)3T9yVz&w)NLc$P}H~5WTu{f}DBe^9O!8 z%jc~XdzW#^o<(5wr_#GuVxF)XNGzC?emm=*%F@`m7bO^L=9+px({R{guzW+YNRi6L z!*WmSd$;y)?eX1GV_LYw)zaj~q0sQ2+Be)wLx28}2(#NIVtA~<_(yu}_y2pA1V~L* zKPPaIb#C>=eX5FA_WCmEWxp<OJ@0k$h9$fIF`pB2e*W1~cBe+8x%eK3W7CO0KXVLK zH*N1wJ^uCdL}8bT<KG0PF4%4pdfEQd>^WhtQ+L-n9o4QE5tl2P>AbY~x-(<Zd!GnC z+v5{wY+wJV^vkmCYbX2a)r<D;4|@LSiFEPBpR0DwueEA#|2*xP0%Op^`aemltq+S! zuTp>2W6`#9*|p^7wXISITWt4S?Yz|)>$TOyVWx&+#AL_WVZXEZq?$i5M7`Y~es)pE zxmV9$t!G)F?(vqtb5*^rz=eGb+qZ`t`Xn}S{sU3x=hltdHsN`zx|CUOYFm}v5T51T zW#6ddp7eL)Gg-FPr`TeI85lTKANk(a=s39eg8IHWy_drNhA!HiUE|y>eYSm}n&{&) z{exaTI&p_Ic3spvyCjIo;`zg4C-%+R>2CE#MMeC^8KcPW-&8NW{k7}kb>Sk>TPb}1 z{Jj$Q&6;v2r|Cky^n=ZbA^uF0`He+4-&a1T%VlH$O0coT*Zsi>_J^tVW3QRBCK)A# zC2zd^?$cJLhV}Bs%M31NT}+nn{M4+wDCKs(+mGm!Dz8nlHqX;6^nbkc|Ic})dR_JL z`))5HHbq$;NHz#QGvSe~>9Wr?tSVFNX2w+t`Q)`Z#o2$#y*<l(Q<nAJ5U!PnUzaR@ zCtY}<ZpWRO>h{+k)?5mh<#MdPwfn@H*geTz)1G&pyw~<epK<!;#yL~>MB6r{c$m6; zKjp}v7+f-C`*ORQ;|Cd(rg1Cqw7(V#eZ_ct=ib<mpC^pCmD(rX-rk_5c)M=$MhDlL z)e>8-)GfSUQP=*dRJPt?dY7Vy_M=xu+wLe%d1|<+>dV^u!IH1_^v;%Qifpv!<E>Xp zO4xMv$J6e2wHb?6zdOzPl$A$-ec$=D_jU)VICq@8q91&4<$bgDZCQn#*Z#io=#Mc~ zdwk-_k?YSVOqxIASLX3gzaHt?&v>ZnqamTO?T>lAaQ@m12d_i*FZ(@b98=}4x9~6D z;#PbxPyC0c3R~)=7v0SBdYZ1z``X7?;@Zj{QlIIiRlIKQg<Q4LI_c7<Wxhf)eL8~# z6XmqLoc5KmFL*C`dfk~%#gkfN&Zj&TQ$A|;<YZvX)y&VGmh0a9$hhTx|LOIm{<=kr zs;=9AX#7-@cGZu6dfBCmMLJt{pV`f%q;N61^6_c8A5M4d9?U&(Pjb_<AEy*<xyUpK zY<#+X!&CRr`j%Gd7^XE#W&}MeS+?@9`<X{ZyYm(9pI`j)!}9V}quXKGf)^dOyG%Y& zeZaYn_h&C>wZc6azNNp4PI9j5kbZx|eV_QU`|&p<vWx!3xy_xhci)bsc2Di9s^+@# zSpQ5g?>+d2q4f6uz5kcQbZ*#b+Pg<>L)M;@6DRps{*B7Av~PJJT$k{Ti~rzai<hlG urN3;seqpm>C?vt!bBS(V7icFU;&5>5`;$#O*S2mj&XeeHVb?f2jROD>X~x9> delta 58 zcmZ4YfU)5WBhSCL|K2h%FfcJNFo;YJ<d@p0wo`fYH;r?;n{P+ki-?$Azvd_to69(9 Qwy3v!?&gi@cUK4j0H}NyHvj+t -- GitLab