diff --git a/CHANGELOG.md b/CHANGELOG.md
index 085f918b7..876b44ef1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,7 @@
# Changelog
All notable changes to this project will be documented in this file.
+## [6.19.0] (https://github.com/Backbase/stream-services/compare/6.18.0...6.19.0)
+- Integrate Customer Profile Service into Legal Entity Saga and Legal Entity Saga V2 ingestion
## [6.18.0](https://github.com/Backbase/stream-services/compare/6.17.0...6.18.0)
### Added
diff --git a/api/stream-legal-entity/openapi.yaml b/api/stream-legal-entity/openapi.yaml
index 43d609b4b..f6a1bf515 100644
--- a/api/stream-legal-entity/openapi.yaml
+++ b/api/stream-legal-entity/openapi.yaml
@@ -1132,6 +1132,10 @@ components:
type: array
items:
$ref: '#/components/schemas/JobProfileUser'
+ parties:
+ type: array
+ items:
+ $ref: '#/components/schemas/Party'
referenceJobRoles:
type: array
items:
@@ -1205,6 +1209,10 @@ components:
type: array
items:
$ref: '#/components/schemas/User'
+ parties:
+ type: array
+ items:
+ $ref: '#/components/schemas/Party'
masterServiceAgreement:
$ref: '#/components/schemas/ServiceAgreementV2'
contacts:
@@ -3102,7 +3110,440 @@ components:
description: Subscription identifier
required:
- identifier
-
+ Party:
+ type: object
+ properties:
+ partyId:
+ type: string
+ description: Core System Identifier
+ maxLength: 100
+ isCustomer:
+ type: boolean
+ description: Indicates if it is a customer or not.
+ partyType:
+ type: string
+ enum:
+ - PERSON
+ - ORGANISATION
+ description: Type of party in different business contexts.
+ state:
+ type: string
+ enum:
+ - ENROLLED
+ - EXITED
+ subState:
+ type: string
+ enum:
+ - OPENED
+ - APPROVED
+ - ACTIVE
+ - DORMANT
+ - CLOSED
+ description: Status of the party.
+ openingDateTime:
+ type: string
+ format: date-time
+ description: Date on which the party and related basic services are effectively operational for the party.
+ closingDateTime:
+ type: string
+ format: date-time
+ description: Date on which the party and related services cease effectively to be operational for the party.
+ liveDateTime:
+ type: string
+ format: date-time
+ description: Date of the first movement on the party.
+ approvedDateTime:
+ type: string
+ format: date-time
+ description: Date on which the party and related basic services are approved.
+ lastUpdatedDateTime:
+ type: string
+ format: date-time
+ description: Date on which there was any update to the party and related basic services.
+ preferredLanguage:
+ type: string
+ maxLength: 50
+ description: Language preferred by party.
+ notes:
+ type: string
+ maxLength: 100
+ description: Notes on the party.
+ organisation:
+ $ref: '#/components/schemas/Organization'
+ person:
+ $ref: '#/components/schemas/Person'
+ partyPartyRelationships:
+ type: array
+ items:
+ $ref: '#/components/schemas/PartyPartyRelationship'
+ electronicAddresses:
+ $ref: '#/components/schemas/ElectronicAddress'
+ postalAddresses:
+ type: array
+ items:
+ $ref: '#/components/schemas/PartyPostalAddress'
+ phoneNumbers:
+ type: array
+ items:
+ $ref: '#/components/schemas/PartyPhoneNumber'
+ customFields:
+ type: object
+ additionalProperties:
+ type: string
+ required:
+ - partyId
+ - isCustomer
+ - partyType
+ Organization:
+ type: object
+ properties:
+ name:
+ type: string
+ description: Organisation Name
+ maxLength: 64
+ type:
+ type: string
+ maxLength: 64
+ description: Specifies a type of organisation.
+ sector:
+ type: string
+ maxLength: 255
+ description: Sector of business of the organisation, for example, pharmaceutical. (ISO20022)
+ establishmentDate:
+ type: string
+ format: date
+ description: Date when the organisation was established.
+ legalStructure:
+ type: object
+ properties:
+ type:
+ type: string
+ maxLength: 64
+ description: Individual, Partnership and Corporation
+ required:
+ - type
+ identifications:
+ type: array
+ description: List of specific identification assigned to a party.
+ minItems: 1
+ items:
+ $ref: '#/components/schemas/Identification'
+ required:
+ - name
+ - identifications
+ Identification:
+ type: object
+ properties:
+ identificationType:
+ type: string
+ enum:
+ - TaxIdentificationNumber
+ - NationalRegistrationNumber
+ - RegistrationAuthorityIdentification
+ - LegalEntityIdentifier
+ - AlienRegistrationNumber
+ - PassportNumber
+ - TaxExemptionIdentificationNumber
+ - CorporateIdentification
+ - DriverLicenseNumber
+ - ForeignInvestmentIdentityNumber
+ - SocialSecurityNumber
+ - IdentityCardNumber
+ - Concat
+ - NationalRegistrationIdentificationNumber
+ - CustomerIdentificationNumber
+ - EmployeeIdentificationNumber
+ - NationalIdentityNumber
+ - TelephoneNumber
+ - EmployerIdentificationNumber
+ - CentralBankIdentificationNumber
+ - ClearingIdentificationNumber
+ - BankPartyIdentification
+ - CertificateOfIncorporationNumber
+ - CountryIdentificationCode
+ - CustomerNumber
+ - DataUniversalNumberingSystem
+ - GS1GLNIdentifier
+ - ELF
+ - SIREN
+ - SIRET
+ - MIC
+ - BICFI
+ - DUNS
+ - EANGLN
+ description: Identification Type of the identity document.
+ identificationNumber:
+ type: string
+ maxLength: 100
+ description: Number or code assigned by the government authority to an entity
+ issuingCountry:
+ type: string
+ maxLength: 100
+ description: Identifies issuing country of the identity document.
+ issuingAuthority:
+ type: string
+ maxLength: 100
+ description: Identifies issuing authority of the identity document.
+ issueDate:
+ type: string
+ format: date
+ description: Identifies issue date of the identity document.
+ expiryDate:
+ type: string
+ format: date
+ description: Identifies expiry date of the identity document.
+ required:
+ - identificationType
+ - identificationNumber
+ Person:
+ type: object
+ properties:
+ birthDate:
+ type: string
+ format: date
+ description: Indicates person birthdate
+ gender:
+ type: string
+ enum:
+ - MALE
+ - FEMALE
+ - NON_BINARY
+ description: Person's gender
+ personName:
+ $ref: '#/components/schemas/PersonName'
+ demographics:
+ $ref: '#/components/schemas/Demographics'
+ identifications:
+ type: array
+ description: List of specific identification assigned to a party.
+ minItems: 1
+ items:
+ $ref: '#/components/schemas/Identification'
+ required:
+ - personName
+ - identifications
+ PersonName:
+ type: object
+ properties:
+ salutation:
+ type: string
+ maxLength: 20
+ firstName:
+ type: string
+ description: First name of the person.
+ maxLength: 64
+ middleName:
+ type: string
+ description: Middle name of the person.
+ maxLength: 64
+ familyName:
+ type: string
+ description: Family name of the person.
+ maxLength: 64
+ required:
+ - firstName
+ Demographics:
+ type: object
+ properties:
+ occupation:
+ type: object
+ properties:
+ employment:
+ type: string
+ description: Type of employment of the party.
+ maxLength: 100
+ employer:
+ type: string
+ description: Details of the party's employment.
+ maxLength: 100
+ education:
+ type: object
+ properties:
+ educationLevel:
+ type: string
+ description: Party's education level, such as qualifications and certifications.
+ maxLength: 100
+ yearOfPassing:
+ type: string
+ maxLength: 20
+ description: Year of completing the qualification.
+ PartyPartyRelationship:
+ type: object
+ properties:
+ partyId:
+ type: string
+ maxLength: 100
+ description: Core System Id
+ partyType:
+ type: string
+ enum:
+ - PERSON
+ - ORGANISATION
+ description: Specifies the type of party in different business contexts.
+ partyRole:
+ type: string
+ description: Identifies role of a party
+ enum:
+ - COMPANY_ROLE
+ - COMPANY_OWNERSHIP
+ - COMPANY_CONTROL_PERSON
+ - COMPANY_SIGNATORY
+ - GUARDIAN
+ roleStartDate:
+ type: string
+ format: date
+ description: Identifies start date of the party role
+ roleEndDate:
+ type: string
+ format: date
+ description: Identifies end date of the party role
+ ownershipPercent:
+ type: integer
+ description: Identifies the percentage of stake/ownership for a related person/organisation in this organisation. Total ownership percent must not exceed 100%
+ minimum: 0
+ maximum: 100
+ required:
+ - partyId
+ - partyRole
+ ElectronicAddress:
+ type: object
+ description: Address which is accessed by electronic means.
+ properties:
+ emails:
+ type: array
+ description: Email(s) of the party.
+ items:
+ $ref: '#/components/schemas/Email'
+ urls:
+ type: array
+ description: Address for the Universal Resource Locator (URL), used over the www (HTTP) service.
+ items:
+ $ref: '#/components/schemas/Url'
+ Email:
+ type: object
+ properties:
+ type:
+ type: string
+ enum:
+ - WORK
+ - PERSONAL
+ - HOME
+ - OTHERS
+ description: Identifies the type of email. Possible values - WORK, PERSONAL, HOME, OTHERS.
+ primary:
+ type: boolean
+ description: Flag denoting whether this is the primary email address of the party.
+ address:
+ type: string
+ maxLength: 100
+ description: Address for electronic mail (e-mail) (ISO20022).
+ required:
+ - type
+ - address
+ Url:
+ type: object
+ properties:
+ type:
+ type: string
+ enum:
+ - WORK
+ - PERSONAL
+ description: Identifies the type of URL. Possible Values - WORK, PERSONAL.
+ primary:
+ type: boolean
+ description: Flag denoting whether this is the primary url address of the party.
+ address:
+ type: string
+ maxLength: 255
+ description: Address for the Universal Resource Locator (URL), used over the www (HTTP) service.
+ required:
+ - address
+ - type
+ PartyPostalAddress:
+ type: object
+ properties:
+ type:
+ type: string
+ enum:
+ - Business
+ - Correspondence
+ - DeliveryTo
+ - MailTo
+ - POBox
+ - Postal
+ - Residential
+ - Statement
+ description: Identifies the type of postal address.
+ primary:
+ type: boolean
+ description: Flag denoting whether this is the primary postal address of the party.
+ department:
+ type: string
+ maxLength: 100
+ description: Identification of a division of a large organisation or building (ISO20022).
+ subDepartment:
+ type: string
+ maxLength: 100
+ description: Identification of a sub-division of a large organisation or building (ISO20022).
+ addressLine:
+ type: string
+ maxLength: 100
+ description: Information that locates and identifies a specific address, as defined by postal services, presented in free format text (ISO20022).
+ buildingNumber:
+ type: string
+ maxLength: 50
+ description: Number that identifies the position of a building on a street (ISO20022).
+ streetName:
+ type: string
+ maxLength: 100
+ description: Name of a street or thoroughfare (ISO20022).
+ townName:
+ type: string
+ maxLength: 100
+ description: Name of a built-up area, with defined boundaries, and a local government (ISO20022).
+ postalCode:
+ type: string
+ maxLength: 12
+ description: Identifier consisting of a group of letters and/or numbers that is added to a postal address to assist the sorting of mail (ISO20022).
+ countrySubDivision:
+ type: string
+ maxLength: 100
+ description: Identifies name of a country subdivision such as state, region, county. For example - Oregon.
+ country:
+ type: string
+ maxLength: 100
+ description: Country name.
+ required:
+ - type
+ PartyPhoneNumber:
+ type: object
+ properties:
+ type:
+ type: string
+ enum:
+ - MOBILE
+ - HOME
+ - WORK
+ - OTHER
+ description: Identifies the type of the phone address. For example - MOBILE, LANDLINE, HOME, WORK, FAX.
+ primary:
+ type: boolean
+ description: Flag denoting if its the primary phone address of the party.
+ number:
+ type: string
+ maxLength: 50
+ description: Collection of information that identifies a phone address, as defined by telecom services (ISO20022).
+ countryCode:
+ type: string
+ maxLength: 3
+ description: Phone’s country calling code. Country calling code can be obtained from https://countrycode.org/.
+ countryIsoCode:
+ type: string
+ maxLength: 3
+ description: Phone’s ISO country code. Country code can be obtained from the United Nations (ISO 3166, Alpha-2 code)- https://www.iban.com/country-codes.
+ required:
+ - type
+ - number
examples:
RootLegalEntityHierarchyExample:
description: "Example Request for setting up Root Legal Entity Structure as described on Backbase Community"
diff --git a/pom.xml b/pom.xml
index 3eb5baa97..c12340fec 100644
--- a/pom.xml
+++ b/pom.xml
@@ -35,6 +35,7 @@
stream-audiences
stream-compositions
stream-plan-manager
+ stream-customer-profile
diff --git a/stream-customer-profile/customer-profile-core/pom.xml b/stream-customer-profile/customer-profile-core/pom.xml
new file mode 100644
index 000000000..0de7b95b4
--- /dev/null
+++ b/stream-customer-profile/customer-profile-core/pom.xml
@@ -0,0 +1,57 @@
+
+
+ 4.0.0
+
+
+ com.backbase.stream
+ stream-customer-profile
+ 6.18.0
+
+
+ customer-profile-core
+ jar
+ Stream :: Customer Profile Core
+
+
+ true
+
+
+
+
+ com.backbase.stream
+ legal-entity-model
+ ${project.version}
+ compile
+
+
+
+ org.mapstruct
+ mapstruct
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+ com.backbase.stream
+ stream-dbs-clients
+ ${project.version}
+ compile
+
+
+ org.mapstruct
+ mapstruct-processor
+ provided
+
+
+
+ com.backbase.stream
+ stream-test-support
+ ${project.version}
+ test
+
+
+
+
diff --git a/stream-customer-profile/customer-profile-core/src/main/java/com/backbase/stream/configuration/CustomerProfileConfiguration.java b/stream-customer-profile/customer-profile-core/src/main/java/com/backbase/stream/configuration/CustomerProfileConfiguration.java
new file mode 100644
index 000000000..5c457fd8b
--- /dev/null
+++ b/stream-customer-profile/customer-profile-core/src/main/java/com/backbase/stream/configuration/CustomerProfileConfiguration.java
@@ -0,0 +1,22 @@
+package com.backbase.stream.configuration;
+
+import com.backbase.customerprofile.api.integration.v1.PartyManagementIntegrationApi;
+import com.backbase.stream.clients.config.CustomerProfileClientConfig;
+import com.backbase.stream.mapper.PartyMapper;
+import com.backbase.stream.service.CustomerProfileService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@Slf4j
+@EnableConfigurationProperties(CustomerProfileClientConfig.class)
+public class CustomerProfileConfiguration {
+
+ @Bean
+ public CustomerProfileService createCustomerProfileService(
+ PartyManagementIntegrationApi partyManagementIntegrationApi, PartyMapper partyMapper) {
+ return new CustomerProfileService(partyManagementIntegrationApi, partyMapper);
+ }
+}
diff --git a/stream-customer-profile/customer-profile-core/src/main/java/com/backbase/stream/configuration/package-info.java b/stream-customer-profile/customer-profile-core/src/main/java/com/backbase/stream/configuration/package-info.java
new file mode 100644
index 000000000..3b4bde136
--- /dev/null
+++ b/stream-customer-profile/customer-profile-core/src/main/java/com/backbase/stream/configuration/package-info.java
@@ -0,0 +1 @@
+package com.backbase.stream.configuration;
\ No newline at end of file
diff --git a/stream-customer-profile/customer-profile-core/src/main/java/com/backbase/stream/mapper/PartyMapper.java b/stream-customer-profile/customer-profile-core/src/main/java/com/backbase/stream/mapper/PartyMapper.java
new file mode 100644
index 000000000..ed43eaea8
--- /dev/null
+++ b/stream-customer-profile/customer-profile-core/src/main/java/com/backbase/stream/mapper/PartyMapper.java
@@ -0,0 +1,15 @@
+package com.backbase.stream.mapper;
+
+import com.backbase.customerprofile.api.integration.v1.model.PartyUpsertDto;
+import com.backbase.stream.legalentity.model.Party;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingConstants;
+
+@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
+public interface PartyMapper {
+
+ @Mapping(target = "additions", source = "customFields")
+ PartyUpsertDto partyToPartyUpsertDto(Party party);
+
+}
\ No newline at end of file
diff --git a/stream-customer-profile/customer-profile-core/src/main/java/com/backbase/stream/service/CustomerProfileService.java b/stream-customer-profile/customer-profile-core/src/main/java/com/backbase/stream/service/CustomerProfileService.java
new file mode 100644
index 000000000..dfc0ccdba
--- /dev/null
+++ b/stream-customer-profile/customer-profile-core/src/main/java/com/backbase/stream/service/CustomerProfileService.java
@@ -0,0 +1,27 @@
+package com.backbase.stream.service;
+
+import com.backbase.customerprofile.api.integration.v1.PartyManagementIntegrationApi;
+import com.backbase.customerprofile.api.integration.v1.model.PartyResponseUpsertDto;
+import com.backbase.stream.legalentity.model.Party;
+import com.backbase.stream.mapper.PartyMapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.StringUtils;
+import reactor.core.publisher.Mono;
+
+@Slf4j
+@RequiredArgsConstructor
+public class CustomerProfileService {
+
+ private final PartyManagementIntegrationApi partyManagementIntegrationApi;
+
+ private final PartyMapper partyMapper;
+
+ public Mono upsertParty(Party party, String legalEntityInternalId) {
+ var partyUpsertDto = partyMapper.partyToPartyUpsertDto(party);
+ if (StringUtils.hasText(legalEntityInternalId) && party.getIsCustomer()) {
+ partyUpsertDto.setLegalEntityId(legalEntityInternalId);
+ }
+ return partyManagementIntegrationApi.upsertParty(partyUpsertDto);
+ }
+}
diff --git a/stream-customer-profile/customer-profile-core/src/main/java/com/backbase/stream/service/package-info.java b/stream-customer-profile/customer-profile-core/src/main/java/com/backbase/stream/service/package-info.java
new file mode 100644
index 000000000..29590942e
--- /dev/null
+++ b/stream-customer-profile/customer-profile-core/src/main/java/com/backbase/stream/service/package-info.java
@@ -0,0 +1 @@
+package com.backbase.stream.service;
\ No newline at end of file
diff --git a/stream-customer-profile/customer-profile-core/src/test/java/com/backbase/stream/configuration/CustomerProfileConfigurationTest.java b/stream-customer-profile/customer-profile-core/src/test/java/com/backbase/stream/configuration/CustomerProfileConfigurationTest.java
new file mode 100644
index 000000000..e768c9b6a
--- /dev/null
+++ b/stream-customer-profile/customer-profile-core/src/test/java/com/backbase/stream/configuration/CustomerProfileConfigurationTest.java
@@ -0,0 +1,38 @@
+package com.backbase.stream.configuration;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+import com.backbase.buildingblocks.webclient.InterServiceWebClientConfiguration;
+import com.backbase.customerprofile.api.integration.v1.PartyManagementIntegrationApi;
+import com.backbase.stream.clients.config.CustomerProfileClientConfig;
+import com.backbase.stream.mapper.PartyMapper;
+import com.backbase.stream.service.CustomerProfileService;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+import org.springframework.web.reactive.function.client.WebClient;
+
+@SpringJUnitConfig
+class CustomerProfileConfigurationTest {
+
+ ApplicationContextRunner contextRunner = new ApplicationContextRunner();
+
+ @Test
+ void configurationTest() {
+ contextRunner
+ .withBean(WebClientAutoConfiguration.class)
+ .withBean(InterServiceWebClientConfiguration.class)
+ .withBean(PartyManagementIntegrationApi.class, () -> mock(PartyManagementIntegrationApi.class))
+ .withBean(PartyMapper.class, () -> mock(PartyMapper.class))
+ .withUserConfiguration(CustomerProfileConfiguration.class)
+ .run(context -> {
+ assertThat(context).hasSingleBean(CustomerProfileService.class);
+ assertThat(context).hasSingleBean(PartyManagementIntegrationApi.class);
+ assertThat(context).hasSingleBean(WebClient.class);
+ assertThat(context).hasSingleBean(CustomerProfileClientConfig.class);
+ assertThat(context).hasSingleBean(PartyMapper.class);
+ });
+ }
+}
\ No newline at end of file
diff --git a/stream-customer-profile/customer-profile-core/src/test/java/com/backbase/stream/mapper/MapperTest.java b/stream-customer-profile/customer-profile-core/src/test/java/com/backbase/stream/mapper/MapperTest.java
new file mode 100644
index 000000000..bd0845d80
--- /dev/null
+++ b/stream-customer-profile/customer-profile-core/src/test/java/com/backbase/stream/mapper/MapperTest.java
@@ -0,0 +1,202 @@
+package com.backbase.stream.mapper;
+
+
+import static com.backbase.stream.FixtureUtils.reflectiveAlphaFixtureMonkey;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.backbase.stream.legalentity.model.Party;
+import com.navercorp.fixturemonkey.FixtureMonkey;
+import java.util.ArrayList;
+import java.util.HashMap;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest(classes = PartyMapperImpl.class)
+class MapperTest {
+
+ @Autowired
+ private PartyMapper partyMapper;
+ private final FixtureMonkey fixtureMonkey = reflectiveAlphaFixtureMonkey;
+
+ @Test
+ @DisplayName("Should map basic fields correctly when not null")
+ void shouldMapBasicFields() {
+ var party = fixtureMonkey.giveMeOne(Party.class);
+
+ var resultDto = partyMapper.partyToPartyUpsertDto(party);
+
+ assertNotNull(resultDto);
+ assertEquals(party.getPartyId(), resultDto.getPartyId());
+ assertEquals(party.getIsCustomer(), resultDto.getIsCustomer());
+ assertEquals(party.getPreferredLanguage(), resultDto.getPreferredLanguage());
+ assertEquals(party.getNotes(), resultDto.getNotes());
+ assertEquals(party.getClosingDateTime(), resultDto.getClosingDateTime());
+ assertEquals(party.getApprovedDateTime(), resultDto.getApprovedDateTime());
+ assertEquals(party.getLastUpdatedDateTime(), resultDto.getLastUpdatedDateTime());
+ assertEquals(party.getOpeningDateTime(), resultDto.getOpeningDateTime());
+ assertEquals(party.getLiveDateTime(), resultDto.getLiveDateTime());
+
+ assertNotNull(resultDto.getPartyType());
+ assertEquals(party.getPartyType().getValue(), resultDto.getPartyType().getValue());
+ assertNotNull(resultDto.getState());
+ assertEquals(party.getState().getValue(), resultDto.getState().getValue());
+ assertNotNull(resultDto.getSubState());
+ assertEquals(party.getSubState().getValue(), resultDto.getSubState().getValue());
+ }
+
+ @Test
+ @DisplayName("Should map Person when PartyType is PERSON and Person is not null")
+ void shouldMapPersonWhenPartyTypeIsPerson() {
+ var party = fixtureMonkey.giveMeBuilder(Party.class)
+ .set("partyType", Party.PartyTypeEnum.PERSON)
+ .setNotNull("person")
+ .setNotNull("person.personName")
+ .set("person.personName.firstName", "John")
+ .set("person.personName.familyName", "Doe")
+ .setNull("organisation")
+ .sample();
+
+ var resultDto = partyMapper.partyToPartyUpsertDto(party);
+
+ assertNotNull(resultDto.getPerson());
+ assertEquals("John", resultDto.getPerson().getPersonName().getFirstName());
+ assertEquals("Doe", resultDto.getPerson().getPersonName().getFamilyName());
+ if (party.getPerson().getIdentifications() != null) {
+ assertNotNull(resultDto.getPerson().getIdentifications());
+ assertEquals(party.getPerson().getIdentifications().size(),
+ resultDto.getPerson().getIdentifications().size());
+ }
+ assertNull(resultDto.getOrganisation(), "Organisation should be null if PartyType is PERSON");
+ }
+
+ @Test
+ @DisplayName("Should handle null Person from source")
+ void shouldHandleNullPerson() {
+ var party = fixtureMonkey.giveMeBuilder(Party.class)
+ .setNull("person")
+ .sample();
+
+ var resultDto = partyMapper.partyToPartyUpsertDto(party);
+
+ assertNull(resultDto.getPerson());
+ }
+
+ @Test
+ @DisplayName("Should map Organisation when PartyType is ORGANISATION and Organisation is not null")
+ void shouldMapOrganisationWhenPartyTypeIsOrganisation() {
+ var party = fixtureMonkey.giveMeBuilder(Party.class)
+ .set("partyType", Party.PartyTypeEnum.ORGANISATION)
+ .setNotNull("organisation")
+ .set("organisation.name", "My Company")
+ .setNull("person")
+ .sample();
+ var resultDto = partyMapper.partyToPartyUpsertDto(party);
+ assertNotNull(resultDto.getOrganisation());
+ assertEquals("My Company", resultDto.getOrganisation().getName());
+ assertNull(resultDto.getPerson());
+ }
+
+ @Test
+ @DisplayName("Should handle null Organisation from source")
+ void shouldHandleNullOrganisation() {
+ Party party = fixtureMonkey.giveMeBuilder(Party.class)
+ .setNull("organisation")
+ .sample();
+ var resultDto = partyMapper.partyToPartyUpsertDto(party);
+ assertNull(resultDto.getOrganisation());
+ }
+
+ @Test
+ @DisplayName("Should map populated collections correctly")
+ void shouldMapPopulatedCollections() {
+
+ var party = fixtureMonkey.giveMeBuilder(Party.class)
+ .size("phoneNumbers", 2)
+ .size("postalAddresses", 1)
+ .size("customFields", 3)
+ .size("partyPartyRelationships", 1)
+ .sample();
+
+ var resultDto = partyMapper.partyToPartyUpsertDto(party);
+
+ assertNotNull(resultDto.getPhoneNumbers());
+ assertEquals(2, resultDto.getPhoneNumbers().size());
+
+ assertNotNull(resultDto.getPostalAddresses());
+ assertEquals(1, resultDto.getPostalAddresses().size());
+
+ assertNotNull(resultDto.getAdditions());
+ assertEquals(3, resultDto.getAdditions().size());
+
+ assertNotNull(resultDto.getPartyPartyRelationships());
+ assertEquals(1, resultDto.getPartyPartyRelationships().size());
+ }
+
+ @Test
+ @DisplayName("Should map empty collections correctly")
+ void shouldMapEmptyCollections() {
+ var party = fixtureMonkey.giveMeBuilder(Party.class)
+ .set("phoneNumbers", new ArrayList<>())
+ .set("postalAddresses", new ArrayList<>())
+ .set("customFields", new HashMap<>())
+ .set("partyPartyRelationships", new ArrayList<>())
+ .sample();
+
+ var resultDto = partyMapper.partyToPartyUpsertDto(party);
+
+ assertNotNull(resultDto.getPhoneNumbers());
+ assertTrue(resultDto.getPhoneNumbers().isEmpty());
+
+ assertNotNull(resultDto.getPostalAddresses());
+ assertTrue(resultDto.getPostalAddresses().isEmpty());
+
+ assertNotNull(resultDto.getAdditions());
+ assertTrue(resultDto.getAdditions().isEmpty());
+
+ assertNotNull(resultDto.getPartyPartyRelationships());
+ assertTrue(resultDto.getPartyPartyRelationships().isEmpty());
+ }
+
+ @Test
+ @DisplayName("Should map empty collections correctly")
+ void shouldMapNullCollections() {
+
+ var party = fixtureMonkey.giveMeBuilder(Party.class)
+ .setNull("phoneNumbers")
+ .setNull("postalAddresses")
+ .setNull("customFields")
+ .setNull("partyPartyRelationships")
+ .setNull("electronicAddresses.emails")
+ .setNull("electronicAddresses.urls")
+ .setNull("person.identifications")
+ .setNull("organisation.identifications")
+ .sample();
+
+ var resultDto = partyMapper.partyToPartyUpsertDto(party);
+
+ assertNotNull(resultDto.getPhoneNumbers());
+ assertTrue(resultDto.getPhoneNumbers().isEmpty());
+
+ assertNotNull(resultDto.getPostalAddresses());
+ assertTrue(resultDto.getPostalAddresses().isEmpty());
+
+ assertNotNull(resultDto.getAdditions());
+ assertTrue(resultDto.getAdditions().isEmpty());
+
+ assertNotNull(resultDto.getPartyPartyRelationships());
+ assertTrue(resultDto.getPartyPartyRelationships().isEmpty());
+
+ assertNotNull(resultDto.getElectronicAddresses());
+ assertNotNull(resultDto.getElectronicAddresses().getEmails());
+ assertTrue(resultDto.getElectronicAddresses().getEmails().isEmpty());
+
+ assertNotNull(resultDto.getElectronicAddresses().getUrls());
+ assertTrue(resultDto.getElectronicAddresses().getUrls().isEmpty());
+ }
+
+}
diff --git a/stream-customer-profile/customer-profile-core/src/test/java/com/backbase/stream/service/CustomerProfileServiceTest.java b/stream-customer-profile/customer-profile-core/src/test/java/com/backbase/stream/service/CustomerProfileServiceTest.java
new file mode 100644
index 000000000..ea657d0f0
--- /dev/null
+++ b/stream-customer-profile/customer-profile-core/src/test/java/com/backbase/stream/service/CustomerProfileServiceTest.java
@@ -0,0 +1,101 @@
+package com.backbase.stream.service;
+
+import static com.backbase.stream.FixtureUtils.reflectiveAlphaFixtureMonkey;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import com.backbase.customerprofile.api.integration.v1.PartyManagementIntegrationApi;
+import com.backbase.customerprofile.api.integration.v1.model.PartyResponseUpsertDto;
+import com.backbase.customerprofile.api.integration.v1.model.PartyUpsertDto;
+import com.backbase.stream.legalentity.model.Party;
+import com.backbase.stream.mapper.PartyMapper;
+import com.navercorp.fixturemonkey.FixtureMonkey;
+import java.nio.charset.StandardCharsets;
+import java.util.UUID;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mapstruct.factory.Mappers;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+@ExtendWith(MockitoExtension.class)
+class CustomerProfileServiceTest {
+
+ private CustomerProfileService customerProfileService;
+
+ private final FixtureMonkey fixtureMonkey = reflectiveAlphaFixtureMonkey;
+ @Mock
+ private PartyManagementIntegrationApi partyManagementIntegrationApiMock;
+
+
+ @BeforeEach
+ void setup() {
+ customerProfileService = new CustomerProfileService(partyManagementIntegrationApiMock, Mappers.getMapper(PartyMapper.class));
+ }
+
+ @Test
+ @DisplayName("Upsert party should return PartyResponseUpsertDto when API call is successful")
+ void createCustomer_success() {
+ var legalEntityId = UUID.randomUUID().toString();
+ var requestDto = fixtureMonkey.giveMeBuilder(Party.class)
+ .set("legalEntityId", legalEntityId)
+ .sample();
+ var expectedResponseDto = fixtureMonkey.giveMeBuilder(PartyResponseUpsertDto.class)
+ .set("partyReferenceId", legalEntityId).sample();
+
+ when(partyManagementIntegrationApiMock.upsertParty(any(PartyUpsertDto.class)))
+ .thenReturn(Mono.just(expectedResponseDto));
+
+ var result = customerProfileService.upsertParty(requestDto, legalEntityId);
+
+ StepVerifier.create(result)
+ .assertNext(response -> {
+ assertNotNull(response);
+ assertNotNull(response.getCustomerReferenceId());
+ assertNotNull(response.getPartyReferenceId());
+ })
+ .verifyComplete();
+ }
+
+ @Test
+ @DisplayName("Upsert party should propagate WebClientResponseException when API call fails")
+ void upsertParty_apiError() {
+ var requestDto = fixtureMonkey.giveMeOne(Party.class);
+ var expectedException = new WebClientResponseException(
+ HttpStatus.BAD_REQUEST.value(),
+ "Bad Request from API",
+ null,
+ null,
+ StandardCharsets.UTF_8);
+ when(partyManagementIntegrationApiMock.upsertParty(any(PartyUpsertDto.class)))
+ .thenReturn(Mono.error(expectedException));
+ var result = customerProfileService.upsertParty(requestDto, null);
+ StepVerifier.create(result)
+ .expectErrorMatches(throwable ->
+ throwable instanceof WebClientResponseException &&
+ ((WebClientResponseException) throwable).getStatusCode() == HttpStatus.BAD_REQUEST &&
+ throwable.getMessage().contains("Bad Request from API")
+ )
+ .verify();
+ }
+
+ @Test
+ @DisplayName("Upsert party should propagate other RuntimeExceptions when API call fails unexpectedly")
+ void upsertParty_otherError() {
+ var requestDto = fixtureMonkey.giveMeOne(Party.class);
+ var expectedException = new RuntimeException("Unexpected error");
+ when(partyManagementIntegrationApiMock.upsertParty(any(PartyUpsertDto.class)))
+ .thenReturn(Mono.error(expectedException));
+ var result = customerProfileService.upsertParty(requestDto, null);
+ StepVerifier.create(result)
+ .expectErrorMatches(throwable -> throwable == expectedException)
+ .verify();
+ }
+}
diff --git a/stream-customer-profile/pom.xml b/stream-customer-profile/pom.xml
new file mode 100644
index 000000000..a83364cce
--- /dev/null
+++ b/stream-customer-profile/pom.xml
@@ -0,0 +1,19 @@
+
+
+ 4.0.0
+
+
+ com.backbase.stream
+ stream-services
+ 6.18.0
+
+
+ stream-customer-profile
+
+ pom
+ Stream :: Customer Profile
+
+
+ customer-profile-core
+
+
diff --git a/stream-dbs-clients/pom.xml b/stream-dbs-clients/pom.xml
index b588976c1..0c7a9eee8 100644
--- a/stream-dbs-clients/pom.xml
+++ b/stream-dbs-clients/pom.xml
@@ -218,6 +218,15 @@
${project.build.directory}/yaml
true
+
+ com.backbase.flow.customer-profile.api
+ customer-profile
+ 1.17.1
+ api
+ zip
+ ${project.build.directory}/yaml
+ true
+
**/*.yaml, **/*.json
@@ -482,6 +491,19 @@
com.backbase.tailoredvalue.planmanager.service.api.v1.model
+
+ generate-customer-profile-integration-api-code
+
+ generate-webclient-embedded
+
+ generate-sources
+
+ REFACTOR_ALLOF_WITH_PROPERTIES_ONLY=true
+ ${project.build.directory}/yaml/customer-profile/customer-profile-integration-api-v*.yaml
+ com.backbase.customerprofile.api.integration.v1
+ com.backbase.customerprofile.api.integration.v1.model
+
+
diff --git a/stream-dbs-clients/src/main/java/com/backbase/stream/clients/autoconfigure/DbsApiClientsAutoConfiguration.java b/stream-dbs-clients/src/main/java/com/backbase/stream/clients/autoconfigure/DbsApiClientsAutoConfiguration.java
index 2547408b9..e2e23b076 100644
--- a/stream-dbs-clients/src/main/java/com/backbase/stream/clients/autoconfigure/DbsApiClientsAutoConfiguration.java
+++ b/stream-dbs-clients/src/main/java/com/backbase/stream/clients/autoconfigure/DbsApiClientsAutoConfiguration.java
@@ -4,6 +4,7 @@
import com.backbase.stream.clients.config.ApprovalClientConfig;
import com.backbase.stream.clients.config.ArrangementManagerClientConfig;
import com.backbase.stream.clients.config.ContactManagerClientConfig;
+import com.backbase.stream.clients.config.CustomerProfileClientConfig;
import com.backbase.stream.clients.config.IdentityIntegrationClientConfig;
import com.backbase.stream.clients.config.InstrumentApiConfiguration;
import com.backbase.stream.clients.config.LimitsClientConfig;
@@ -42,7 +43,8 @@
UserProfileManagerClientConfig.class,
InstrumentApiConfiguration.class,
PortfolioApiConfiguration.class,
- PlanManagerClientConfig.class
+ PlanManagerClientConfig.class,
+ CustomerProfileClientConfig.class
})
@EnableConfigurationProperties
public class DbsApiClientsAutoConfiguration {
diff --git a/stream-dbs-clients/src/main/java/com/backbase/stream/clients/config/CustomerProfileClientConfig.java b/stream-dbs-clients/src/main/java/com/backbase/stream/clients/config/CustomerProfileClientConfig.java
new file mode 100644
index 000000000..d4057680a
--- /dev/null
+++ b/stream-dbs-clients/src/main/java/com/backbase/stream/clients/config/CustomerProfileClientConfig.java
@@ -0,0 +1,41 @@
+package com.backbase.stream.clients.config;
+
+
+import com.backbase.customerprofile.api.integration.ApiClient;
+import com.backbase.customerprofile.api.integration.v1.CustomerLifeCycleManagementIntegrationApi;
+import com.backbase.customerprofile.api.integration.v1.CustomerManagementIntegrationApi;
+import com.backbase.customerprofile.api.integration.v1.CustomerProfileManagementIntegrationApi;
+import com.backbase.customerprofile.api.integration.v1.PartyManagementIntegrationApi;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.text.DateFormat;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConfigurationProperties("backbase.communication.services.customer-profile")
+public class CustomerProfileClientConfig extends CompositeApiClientConfig {
+
+ public static final String CUSTOMER_PROFILE_SERVICE_ID = "customer-profile";
+
+ public CustomerProfileClientConfig() {
+ super(CUSTOMER_PROFILE_SERVICE_ID);
+ }
+
+
+ @Bean
+ @ConditionalOnMissingBean
+ public ApiClient customerProfileApiIntegrationClient(
+ ObjectMapper objectMapper, DateFormat dateFormat) {
+ return new ApiClient(getWebClient(), objectMapper, dateFormat)
+ .setBasePath(createBasePath());
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public PartyManagementIntegrationApi partyManagementIntegrationApi(
+ ApiClient customerProfileApiIntegrationClient) {
+ return new PartyManagementIntegrationApi(customerProfileApiIntegrationClient);
+ }
+}
diff --git a/stream-dbs-clients/src/test/java/com/backbase/stream/clients/config/CompositeApiClientConfigTest.java b/stream-dbs-clients/src/test/java/com/backbase/stream/clients/config/CompositeApiClientConfigTest.java
index 2cbb80637..213c5a923 100644
--- a/stream-dbs-clients/src/test/java/com/backbase/stream/clients/config/CompositeApiClientConfigTest.java
+++ b/stream-dbs-clients/src/test/java/com/backbase/stream/clients/config/CompositeApiClientConfigTest.java
@@ -98,5 +98,72 @@ void shouldReturnDefaultServicePortWhenServicePortIsEmptyTest() {
assertEquals("http://user-profile-manager:8181", config.createBasePath());
});
}
+ @Test
+ void shouldReturnServiceIdWhenCustomerProfileWithLoadBalancerTest() {
+ contextRunner
+ .withBean(Factory.class, () -> loadBalancerFactory)
+ .withBean(WebClientAutoConfiguration.class)
+ .withBean(InterServiceWebClientConfiguration.class)
+ .withUserConfiguration(CustomerProfileClientConfig.class)
+ .run(context -> {
+ var config = context.getBean(CustomerProfileClientConfig.class);
+ assertEquals("http://customer-profile", config.createBasePath());
+ });
+ }
+
+ @Test
+ void shouldReturnDirectUriWhenCustomerProfileWithoutLoadBalancerAndWithDirectUriTest() {
+ contextRunner
+ .withPropertyValues("backbase.communication.services.customer-profile.direct-uri=http://custom-profile-uri/api")
+ .withBean(WebClientAutoConfiguration.class)
+ .withBean(InterServiceWebClientConfiguration.class)
+ .withBean(CustomerProfileClientConfig.class)
+ .run(context -> {
+ var config = context.getBean(CustomerProfileClientConfig.class);
+ assertEquals("http://custom-profile-uri/api", config.createBasePath());
+ });
+ }
+
+ @Test
+ void shouldReturnServiceIdWhenCustomerProfileWithLoadBalancerAndWithDirectUriTest() {
+ contextRunner
+ .withPropertyValues("backbase.communication.services.customer-profile.direct-uri=http://custom-profile-uri/api")
+ .withBean(Factory.class, () -> loadBalancerFactory)
+ .withBean(WebClientAutoConfiguration.class)
+ .withBean(InterServiceWebClientConfiguration.class)
+ .withUserConfiguration(CustomerProfileClientConfig.class)
+ .run(context -> {
+ var config = context.getBean(CustomerProfileClientConfig.class);
+ assertEquals("http://customer-profile", config.createBasePath());
+ });
+ }
+
+ @Test
+ void shouldNotReturnDefaultServicePortWhenCustomerProfileServicePortIsSetTest() {
+ contextRunner
+ .withPropertyValues("backbase.communication.http.default-service-port=8181",
+ "backbase.communication.services.customer-profile.service-port=8080")
+ .withBean(Factory.class, () -> loadBalancerFactory)
+ .withBean(WebClientAutoConfiguration.class)
+ .withBean(InterServiceWebClientConfiguration.class)
+ .withUserConfiguration(CustomerProfileClientConfig.class)
+ .run(context -> {
+ var config = context.getBean(CustomerProfileClientConfig.class);
+ assertEquals("http://customer-profile:8080", config.createBasePath());
+ });
+ }
+
+ @Test
+ void shouldReturnDefaultServicePortWhenCustomerProfileServicePortIsEmptyTest() {
+ contextRunner
+ .withPropertyValues("backbase.communication.http.default-service-port=8080")
+ .withBean(WebClientAutoConfiguration.class)
+ .withBean(InterServiceWebClientConfiguration.class)
+ .withUserConfiguration(CustomerProfileClientConfig.class)
+ .run(context -> {
+ var config = context.getBean(CustomerProfileClientConfig.class);
+ assertEquals("http://customer-profile:8080", config.createBasePath());
+ });
+ }
}
diff --git a/stream-legal-entity/legal-entity-core/pom.xml b/stream-legal-entity/legal-entity-core/pom.xml
index 8842c0e08..a43006e67 100644
--- a/stream-legal-entity/legal-entity-core/pom.xml
+++ b/stream-legal-entity/legal-entity-core/pom.xml
@@ -40,6 +40,13 @@
${project.version}
+
+ com.backbase.stream
+ customer-profile-core
+ ${project.version}
+ compile
+
+
com.backbase.stream
legal-entity-model
@@ -105,14 +112,9 @@
- io.projectreactor
- reactor-test
- test
-
-
-
- com.backbase.buildingblocks
- service-sdk-starter-test
+ com.backbase.stream
+ stream-test-support
+ ${project.version}
test
diff --git a/stream-legal-entity/legal-entity-core/src/main/java/com/backbase/stream/HelperProcessor.java b/stream-legal-entity/legal-entity-core/src/main/java/com/backbase/stream/HelperProcessor.java
new file mode 100644
index 000000000..539598f34
--- /dev/null
+++ b/stream-legal-entity/legal-entity-core/src/main/java/com/backbase/stream/HelperProcessor.java
@@ -0,0 +1,146 @@
+package com.backbase.stream;
+
+import static com.backbase.stream.product.utils.StreamUtils.nullableCollectionToStream;
+import static org.springframework.util.CollectionUtils.isEmpty;
+
+import com.backbase.stream.legalentity.model.Party;
+import com.backbase.stream.service.CustomerProfileService;
+import com.backbase.stream.worker.model.StreamTask;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
+import lombok.extern.slf4j.Slf4j;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+@Slf4j
+class HelperProcessor {
+
+ protected static final String LEGAL_ENTITY = "LEGAL_ENTITY";
+ protected static final String PROCESS_CUSTOMER_PROFILE = "process-customer-profile";
+ protected static final String PARTY = "party";
+ protected static final String FAILED = "failed";
+
+ protected static final String SERVICE_AGREEMENT = "SERVICE_AGREEMENT";
+ protected static final String BUSINESS_FUNCTION_GROUP = "BUSINESS_FUNCTION_GROUP";
+ protected static final String USER = "USER";
+ protected static final String DEFAULT_DATA_GROUP = "Default data group";
+ protected static final String DEFAULT_DATA_DESCRIPTION = "Default data group description";
+ protected static final String UPSERT_LEGAL_ENTITY = "upsert-legal-entity";
+ protected static final String EXISTS = "exists";
+ protected static final String CREATED = "created";
+
+ protected static final String UPDATED = "updated";
+ protected static final String PROCESS_PRODUCTS = "process-products";
+ protected static final String PROCESS_JOB_PROFILES = "process-job-profiles";
+ protected static final String PROCESS_LIMITS = "process-limits";
+ protected static final String PROCESS_CONTACTS = "process-contacts";
+ protected static final String REJECTED = "rejected";
+ protected static final String UPSERT = "upsert";
+ protected static final String SETUP_SERVICE_AGREEMENT = "setup-service-agreement";
+ protected static final String BATCH_PRODUCT_GROUP_ID = "batch_product_group_task-";
+
+ protected static final String LEGAL_ENTITY_E_TYPE = "LE";
+ protected static final String SERVICE_AGREEMENT_E_TYPE = "SA";
+ protected static final String FUNCTION_GROUP_E_TYPE = "FAG";
+ protected static final String FUNCTION_E_TYPE = "FUN";
+ protected static final String PRIVILEGE_E_TYPE = "PRV";
+ protected static final String JOB_ROLE_LIMITS = "job-role-limits";
+ protected static final String USER_JOB_ROLE_LIMITS = "user-job-role-limits";
+ protected static final String LEGAL_ENTITY_LIMITS = "legal-entity-limits";
+ protected static final String IDENTITY_USER = "IDENTITY_USER";
+
+ private boolean isValidParty(
+ T legalEntityTask,
+ List parties,
+ String legalEntityInternalId,
+ String legalEntityExternalId) {
+ if (isEmpty(parties)) {
+ legalEntityTask.info(LEGAL_ENTITY, PROCESS_CUSTOMER_PROFILE, "skipped", legalEntityExternalId,
+ legalEntityInternalId, "No parties found in Legal Entity to process.");
+ return false;
+ }
+ return true;
+ }
+
+
+ public Mono processParties(
+ T legalEntityTask,
+ List parties,
+ String legalEntityInternalId,
+ String legalEntityExternalId,
+ CustomerProfileService customerProfileService
+ ) {
+ log.info("Processing Customer Profile Parties for LE: {}", legalEntityExternalId);
+
+ if (!isValidParty(legalEntityTask, parties, legalEntityInternalId, legalEntityExternalId)) {
+ return Mono.just(legalEntityTask);
+ }
+
+ var processingErrors = new CopyOnWriteArrayList();
+
+ return Flux.fromStream(nullableCollectionToStream(parties))
+ .filter(Objects::nonNull)
+ .concatMap(party -> handlePartyUpsert(
+ party,
+ legalEntityInternalId,
+ legalEntityExternalId,
+ customerProfileService,
+ processingErrors,
+ legalEntityTask
+ ))
+ .then(Mono.fromRunnable(() -> logFinalProcessingStatus(
+ processingErrors,
+ legalEntityInternalId,
+ legalEntityExternalId,
+ legalEntityTask
+ )))
+ .thenReturn(legalEntityTask);
+ }
+
+ private void logFinalProcessingStatus(
+ List processingErrors,
+ String legalEntityInternalId,
+ String legalEntityExternalId,
+ T legalEntityTask
+ ) {
+ if (!processingErrors.isEmpty()) {
+ int errorCount = processingErrors.size();
+ log.warn("Completed processing parties for LE {} with {} error(s).", legalEntityExternalId, errorCount);
+ legalEntityTask.warn(LEGAL_ENTITY, PROCESS_CUSTOMER_PROFILE, "completed_with_errors",
+ legalEntityExternalId, legalEntityInternalId,
+ "Party processing completed with %d error(s).", errorCount);
+ } else {
+ log.info("Successfully processed all parties for LE {}.", legalEntityExternalId);
+ legalEntityTask.info(LEGAL_ENTITY, PROCESS_CUSTOMER_PROFILE, "completed_successfully",
+ legalEntityExternalId, legalEntityInternalId,
+ "Party processing completed successfully.");
+ }
+ }
+
+ private Mono handlePartyUpsert(
+ Party party,
+ String legalEntityInternalId,
+ String legalEntityExternalId,
+ CustomerProfileService customerProfileService,
+ List processingErrors,
+ T legalEntityTask
+ ) {
+ String partyId = party.getPartyId();
+ log.debug("Attempting to upsert party with partyId: {}", partyId);
+
+ return customerProfileService.upsertParty(party, legalEntityInternalId)
+ .doOnSuccess(result ->
+ legalEntityTask.info(PARTY, PROCESS_CUSTOMER_PROFILE, "upserted", partyId, null,
+ "Successfully upserted party: %s for LE: %s", partyId, legalEntityExternalId)
+ )
+ .doOnError(throwable -> {
+ log.error("Failed to upsert party {}: {}", partyId, throwable.getMessage(), throwable);
+ processingErrors.add(throwable);
+ legalEntityTask.error(PARTY, PROCESS_CUSTOMER_PROFILE, FAILED, partyId, null, throwable,
+ throwable.getMessage(), "Error upserting party: %s for LE: %s", partyId, legalEntityExternalId);
+ })
+ .onErrorResume(throwable -> Mono.empty())
+ .then();
+ }
+}
diff --git a/stream-legal-entity/legal-entity-core/src/main/java/com/backbase/stream/LegalEntitySaga.java b/stream-legal-entity/legal-entity-core/src/main/java/com/backbase/stream/LegalEntitySaga.java
index b19be0b00..4e9e4bd10 100644
--- a/stream-legal-entity/legal-entity-core/src/main/java/com/backbase/stream/LegalEntitySaga.java
+++ b/stream-legal-entity/legal-entity-core/src/main/java/com/backbase/stream/LegalEntitySaga.java
@@ -64,6 +64,7 @@
import com.backbase.stream.product.task.ProductGroupTask;
import com.backbase.stream.product.utils.StreamUtils;
import com.backbase.stream.service.AccessGroupService;
+import com.backbase.stream.service.CustomerProfileService;
import com.backbase.stream.service.LegalEntityService;
import com.backbase.stream.service.UserProfileService;
import com.backbase.stream.service.UserService;
@@ -103,38 +104,8 @@
* After the users are created / retrieved and enriched with their internal Ids we can setup the Master Service
*/
@Slf4j
-public class LegalEntitySaga implements StreamTaskExecutor {
-
- public static final String LEGAL_ENTITY = "LEGAL_ENTITY";
- public static final String IDENTITY_USER = "IDENTITY_USER";
- public static final String SERVICE_AGREEMENT = "SERVICE_AGREEMENT";
- public static final String BUSINESS_FUNCTION_GROUP = "BUSINESS_FUNCTION_GROUP";
- public static final String USER = "USER";
- private static final String DEFAULT_DATA_GROUP = "Default data group";
- private static final String DEFAULT_DATA_DESCRIPTION = "Default data group description";
- public static final String UPSERT_LEGAL_ENTITY = "upsert-legal-entity";
- public static final String FAILED = "failed";
- public static final String EXISTS = "exists";
- public static final String CREATED = "created";
-
- public static final String UPDATED = "updated";
- public static final String PROCESS_PRODUCTS = "process-products";
- public static final String PROCESS_JOB_PROFILES = "process-job-profiles";
- public static final String PROCESS_LIMITS = "process-limits";
- public static final String PROCESS_CONTACTS = "process-contacts";
- public static final String REJECTED = "rejected";
- public static final String UPSERT = "upsert";
- public static final String SETUP_SERVICE_AGREEMENT = "setup-service-agreement";
- private static final String BATCH_PRODUCT_GROUP_ID = "batch_product_group_task-";
-
- private static final String LEGAL_ENTITY_E_TYPE = "LE";
- private static final String SERVICE_AGREEMENT_E_TYPE = "SA";
- private static final String FUNCTION_GROUP_E_TYPE = "FAG";
- private static final String FUNCTION_E_TYPE = "FUN";
- private static final String PRIVILEGE_E_TYPE = "PRV";
- private static final String JOB_ROLE_LIMITS = "job-role-limits";
- private static final String USER_JOB_ROLE_LIMITS = "user-job-role-limits";
- private static final String LEGAL_ENTITY_LIMITS = "legal-entity-limits";
+public class LegalEntitySaga extends HelperProcessor implements StreamTaskExecutor {
+
private final BusinessFunctionGroupMapper businessFunctionGroupMapper = Mappers.getMapper(BusinessFunctionGroupMapper.class);
private final UserProfileMapper userProfileMapper = Mappers.getMapper(UserProfileMapper.class);
@@ -148,10 +119,11 @@ public class LegalEntitySaga implements StreamTaskExecutor {
private final ContactsSaga contactsSaga;
private final LegalEntitySagaConfigurationProperties legalEntitySagaConfigurationProperties;
private final UserKindSegmentationSaga userKindSegmentationSaga;
-
+ private final CustomerProfileService customerProfileService;
private static final ExternalContactMapper externalContactMapper = ExternalContactMapper.INSTANCE;
- public LegalEntitySaga(LegalEntityService legalEntityService,
+ public LegalEntitySaga(
+ LegalEntityService legalEntityService,
UserService userService,
UserProfileService userProfileService,
AccessGroupService accessGroupService,
@@ -159,7 +131,8 @@ public LegalEntitySaga(LegalEntityService legalEntityService,
LimitsSaga limitsSaga,
ContactsSaga contactsSaga,
LegalEntitySagaConfigurationProperties legalEntitySagaConfigurationProperties,
- UserKindSegmentationSaga userKindSegmentationSaga) {
+ UserKindSegmentationSaga userKindSegmentationSaga,
+ CustomerProfileService customerProfileService) {
this.legalEntityService = legalEntityService;
this.userService = userService;
this.userProfileService = userProfileService;
@@ -169,12 +142,14 @@ public LegalEntitySaga(LegalEntityService legalEntityService,
this.contactsSaga = contactsSaga;
this.legalEntitySagaConfigurationProperties = legalEntitySagaConfigurationProperties;
this.userKindSegmentationSaga = userKindSegmentationSaga;
+ this.customerProfileService = customerProfileService;
}
@Override
public Mono executeTask(@SpanTag(value = "streamTask") LegalEntityTask streamTask) {
return upsertLegalEntity(streamTask)
.flatMap(this::linkLegalEntityToRealm)
+ .flatMap(this::setupParties)
.flatMap(this::setupAdministrators)
.flatMap(this::setupUsers)
.flatMap(this::processAudiencesSegmentation)
@@ -1091,6 +1066,16 @@ private Mono processSubsidiaries(LegalEntityTask streamTask) {
});
}
+ private Mono setupParties(LegalEntityTask legalEntityTask) {
+
+ var legalEntity = legalEntityTask.getData();
+
+ return processParties(legalEntityTask, legalEntity.getParties(),
+ legalEntity.getInternalId(),
+ legalEntity.getExternalId(), customerProfileService);
+
+ }
+
private Mono linkLegalEntityToRealm(LegalEntityTask streamTask) {
return Mono.just(streamTask)
.filter(task -> legalEntitySagaConfigurationProperties.isUseIdentityIntegration())
diff --git a/stream-legal-entity/legal-entity-core/src/main/java/com/backbase/stream/LegalEntitySagaV2.java b/stream-legal-entity/legal-entity-core/src/main/java/com/backbase/stream/LegalEntitySagaV2.java
index 3e77dd1fc..eed85a83e 100644
--- a/stream-legal-entity/legal-entity-core/src/main/java/com/backbase/stream/LegalEntitySagaV2.java
+++ b/stream-legal-entity/legal-entity-core/src/main/java/com/backbase/stream/LegalEntitySagaV2.java
@@ -47,6 +47,7 @@
import com.backbase.stream.mapper.UserProfileMapper;
import com.backbase.stream.product.utils.StreamUtils;
import com.backbase.stream.service.AccessGroupService;
+import com.backbase.stream.service.CustomerProfileService;
import com.backbase.stream.service.LegalEntityService;
import com.backbase.stream.service.UserProfileService;
import com.backbase.stream.service.UserService;
@@ -56,7 +57,9 @@
import io.micrometer.tracing.annotation.SpanTag;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
import lombok.extern.slf4j.Slf4j;
@@ -74,26 +77,7 @@
* After the users are created / retrieved and enriched with their internal Ids we can setup the Master Service
*/
@Slf4j
-public class LegalEntitySagaV2 implements StreamTaskExecutor {
-
- public static final String LEGAL_ENTITY = "LEGAL_ENTITY";
- public static final String IDENTITY_USER = "IDENTITY_USER";
- public static final String USER = "USER";
- public static final String UPSERT_LEGAL_ENTITY = "upsert-legal-entity";
- public static final String FAILED = "failed";
- public static final String EXISTS = "exists";
- public static final String CREATED = "created";
-
- public static final String UPDATED = "updated";
- public static final String PROCESS_LIMITS = "process-limits";
- public static final String PROCESS_CONTACTS = "process-contacts";
- public static final String UPSERT = "upsert";
-
- private static final String LEGAL_ENTITY_E_TYPE = "LE";
- private static final String SERVICE_AGREEMENT_E_TYPE = "SA";
- private static final String FUNCTION_E_TYPE = "FUN";
- private static final String PRIVILEGE_E_TYPE = "PRV";
- private static final String LEGAL_ENTITY_LIMITS = "legal-entity-limits";
+public class LegalEntitySagaV2 extends HelperProcessor implements StreamTaskExecutor {
private final UserProfileMapper userProfileMapper = Mappers.getMapper(UserProfileMapper.class);
private final LegalEntityV2toV1Mapper leV2Mapper = Mappers.getMapper(LegalEntityV2toV1Mapper.class);
@@ -107,17 +91,20 @@ public class LegalEntitySagaV2 implements StreamTaskExecutor
private final ContactsSaga contactsSaga;
private final LegalEntitySagaConfigurationProperties legalEntitySagaConfigurationProperties;
private final UserKindSegmentationSaga userKindSegmentationSaga;
+ private final CustomerProfileService customerProfileService;
private static final ExternalContactMapper externalContactMapper = ExternalContactMapper.INSTANCE;
- public LegalEntitySagaV2(LegalEntityService legalEntityService,
+ public LegalEntitySagaV2(
+ LegalEntityService legalEntityService,
UserService userService,
UserProfileService userProfileService,
AccessGroupService accessGroupService,
LimitsSaga limitsSaga,
ContactsSaga contactsSaga,
LegalEntitySagaConfigurationProperties legalEntitySagaConfigurationProperties,
- UserKindSegmentationSaga userKindSegmentationSaga) {
+ UserKindSegmentationSaga userKindSegmentationSaga,
+ CustomerProfileService customerProfileService) {
this.legalEntityService = legalEntityService;
this.userService = userService;
this.userProfileService = userProfileService;
@@ -126,12 +113,14 @@ public LegalEntitySagaV2(LegalEntityService legalEntityService,
this.contactsSaga = contactsSaga;
this.legalEntitySagaConfigurationProperties = legalEntitySagaConfigurationProperties;
this.userKindSegmentationSaga = userKindSegmentationSaga;
+ this.customerProfileService = customerProfileService;
}
@Override
public Mono executeTask(@SpanTag(value = "streamTask") LegalEntityTaskV2 streamTask) {
return upsertLegalEntity(streamTask)
.flatMap(this::linkLegalEntityToRealm)
+ .flatMap(this::setupParties)
.flatMap(this::setupAdministrators)
.flatMap(this::setupUsers)
.flatMap(this::processAudiencesSegmentation)
@@ -580,6 +569,15 @@ private Mono setupLegalEntityLevelBusinessFunctionLimits(Lega
});
}
+ private Mono setupParties(LegalEntityTaskV2 legalEntityTaskV2) {
+
+ var legalEntity = legalEntityTaskV2.getData();
+
+ return processParties(legalEntityTaskV2, legalEntity.getParties(),
+ legalEntity.getInternalId(),
+ legalEntity.getExternalId(), customerProfileService);
+ }
+
private Stream createLimitsTask(LegalEntityTaskV2 streamTask, String legalEntityId,
BusinessFunctionLimit businessFunctionLimit) {
diff --git a/stream-legal-entity/legal-entity-core/src/main/java/com/backbase/stream/configuration/LegalEntitySagaConfiguration.java b/stream-legal-entity/legal-entity-core/src/main/java/com/backbase/stream/configuration/LegalEntitySagaConfiguration.java
index d39239923..97129abc4 100644
--- a/stream-legal-entity/legal-entity-core/src/main/java/com/backbase/stream/configuration/LegalEntitySagaConfiguration.java
+++ b/stream-legal-entity/legal-entity-core/src/main/java/com/backbase/stream/configuration/LegalEntitySagaConfiguration.java
@@ -13,12 +13,13 @@
import com.backbase.stream.product.ProductIngestionSagaConfiguration;
import com.backbase.stream.product.configuration.ProductConfiguration;
import com.backbase.stream.service.AccessGroupService;
+import com.backbase.stream.service.CustomerProfileService;
import com.backbase.stream.service.LegalEntityService;
import com.backbase.stream.service.UserProfileService;
import com.backbase.stream.service.UserService;
import com.backbase.stream.worker.repository.impl.InMemoryReactiveUnitOfWorkRepository;
-import com.backbase.streams.tailoredvalue.configuration.PlanServiceConfiguration;
import com.backbase.streams.tailoredvalue.PlansService;
+import com.backbase.streams.tailoredvalue.configuration.PlanServiceConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
@@ -34,8 +35,8 @@
ContactsServiceConfiguration.class,
LoansServiceConfiguration.class,
AudiencesSegmentationConfiguration.class,
- PlanServiceConfiguration.class
-
+ PlanServiceConfiguration.class,
+ CustomerProfileConfiguration.class
})
@EnableConfigurationProperties(
{LegalEntitySagaConfigurationProperties.class}
@@ -51,7 +52,8 @@ public LegalEntitySaga reactiveLegalEntitySaga(LegalEntityService legalEntitySer
LimitsSaga limitsSaga,
ContactsSaga contactsSaga,
LegalEntitySagaConfigurationProperties sinkConfigurationProperties,
- UserKindSegmentationSaga userKindSegmentationSaga
+ UserKindSegmentationSaga userKindSegmentationSaga,
+ CustomerProfileService customerProfileService
) {
return new LegalEntitySaga(
legalEntityService,
@@ -62,7 +64,8 @@ public LegalEntitySaga reactiveLegalEntitySaga(LegalEntityService legalEntitySer
limitsSaga,
contactsSaga,
sinkConfigurationProperties,
- userKindSegmentationSaga
+ userKindSegmentationSaga,
+ customerProfileService
);
}
@@ -74,7 +77,8 @@ public LegalEntitySagaV2 reactiveLegalEntitySagaV2(LegalEntityService legalEntit
LimitsSaga limitsSaga,
ContactsSaga contactsSaga,
LegalEntitySagaConfigurationProperties sinkConfigurationProperties,
- UserKindSegmentationSaga userKindSegmentationSaga
+ UserKindSegmentationSaga userKindSegmentationSaga,
+ CustomerProfileService customerProfileService
) {
return new LegalEntitySagaV2(
legalEntityService,
@@ -84,7 +88,8 @@ public LegalEntitySagaV2 reactiveLegalEntitySagaV2(LegalEntityService legalEntit
limitsSaga,
contactsSaga,
sinkConfigurationProperties,
- userKindSegmentationSaga
+ userKindSegmentationSaga,
+ customerProfileService
);
}
@@ -103,7 +108,7 @@ public ServiceAgreementSagaV2 reactiveServiceAgreementV2Saga(LegalEntityService
batchProductIngestionSaga,
limitsSaga,
contactsSaga,
- plansService,
+ plansService,
sinkConfigurationProperties
);
}
diff --git a/stream-legal-entity/legal-entity-core/src/test/java/com/backbase/stream/HelperProcessorTest.java b/stream-legal-entity/legal-entity-core/src/test/java/com/backbase/stream/HelperProcessorTest.java
new file mode 100644
index 000000000..990458429
--- /dev/null
+++ b/stream-legal-entity/legal-entity-core/src/test/java/com/backbase/stream/HelperProcessorTest.java
@@ -0,0 +1,188 @@
+package com.backbase.stream;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.backbase.customerprofile.api.integration.v1.model.PartyResponseUpsertDto;
+import com.backbase.stream.legalentity.model.Party;
+import com.backbase.stream.legalentity.model.Party.PartyTypeEnum;
+import com.backbase.stream.service.CustomerProfileService;
+import com.backbase.stream.worker.model.StreamTask;
+import java.util.Collections;
+import java.util.List;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+@ExtendWith(MockitoExtension.class)
+class HelperProcessorTest {
+
+ @Mock
+ private CustomerProfileService customerProfileService;
+
+ @Mock
+ private StreamTask streamTask;
+
+ @InjectMocks
+ private HelperProcessor helperProcessor;
+
+ private Party party1;
+ private Party party2;
+ private final String internalId = "int-123";
+ private final String externalId = "ext-abc";
+
+ @BeforeEach
+ void setUp() {
+ party1 = new Party("party-001", true, PartyTypeEnum.PERSON);
+ party2 = new Party("party-002", true, PartyTypeEnum.PERSON);
+
+ }
+
+ @Nested
+ @DisplayName("When parties list is empty or null")
+ class EmptyOrNullParties {
+
+ @Test
+ @DisplayName("should log skipped and return immediately for empty list")
+ void processParties_emptyList_shouldSkip() {
+ List emptyList = Collections.emptyList();
+
+ Mono result = helperProcessor.processParties(
+ streamTask, emptyList, internalId, externalId, customerProfileService
+ );
+
+ StepVerifier.create(result)
+ .expectNext(streamTask)
+ .verifyComplete();
+
+ verify(customerProfileService, never()).upsertParty(any(), anyString());
+ }
+
+ @Test
+ @DisplayName("should log skipped and return immediately for null list")
+ void processParties_nullList_shouldSkip() {
+ Mono result = helperProcessor.processParties(
+ streamTask, null, internalId, externalId, customerProfileService
+ );
+
+ StepVerifier.create(result)
+ .expectNext(streamTask)
+ .verifyComplete();
+
+ verify(customerProfileService, never()).upsertParty(any(), anyString());
+ }
+ }
+
+ @Nested
+ @DisplayName("When processing valid parties")
+ class ValidParties {
+
+ @Test
+ @DisplayName("should process all parties successfully")
+ void processParties_allSuccess() {
+ List parties = List.of(party1, party2);
+ when(customerProfileService.upsertParty(any(Party.class), eq(internalId)))
+ .thenReturn(Mono.just(new PartyResponseUpsertDto()));
+
+ Mono result = helperProcessor.processParties(
+ streamTask, parties, internalId, externalId, customerProfileService
+ );
+
+ StepVerifier.create(result)
+ .expectNext(streamTask)
+ .verifyComplete();
+
+ verify(customerProfileService, times(2)).upsertParty(any(Party.class), eq(internalId));
+
+ verify(streamTask, never()).warn(anyString(), anyString(), anyString(), anyString(), anyString(),
+ anyString(), any());
+ verify(streamTask, never()).error(anyString(), anyString(), anyString(), anyString(), any(),
+ any(Throwable.class), anyString(), any());
+ }
+
+ @Test
+ @DisplayName("should handle one error and complete with warning")
+ void processParties_oneError() {
+ List parties = List.of(party1, party2);
+ RuntimeException dbError = new RuntimeException("DB connection failed");
+
+ when(customerProfileService.upsertParty(eq(party1), eq(internalId)))
+ .thenReturn(Mono.just(new PartyResponseUpsertDto()));
+ when(customerProfileService.upsertParty(eq(party2), eq(internalId)))
+ .thenReturn(Mono.error(dbError));
+
+ Mono result = helperProcessor.processParties(
+ streamTask, parties, internalId, externalId, customerProfileService
+ );
+
+ StepVerifier.create(result)
+ .expectNext(streamTask)
+ .verifyComplete();
+
+ verify(customerProfileService, times(2)).upsertParty(any(Party.class), eq(internalId));
+
+ verify(streamTask, never()).info(anyString(), anyString(), eq("completed_successfully"), anyString(),
+ anyString(), anyString(), any());
+ }
+
+ @Test
+ @DisplayName("should handle all errors and complete with warning")
+ void processParties_allErrors() {
+ List parties = List.of(party1, party2);
+ RuntimeException error1 = new RuntimeException("Error 1");
+ RuntimeException error2 = new RuntimeException("Error 2");
+
+ when(customerProfileService.upsertParty(eq(party1), eq(internalId)))
+ .thenReturn(Mono.error(error1));
+ when(customerProfileService.upsertParty(eq(party2), eq(internalId)))
+ .thenReturn(Mono.error(error2));
+
+ Mono result = helperProcessor.processParties(
+ streamTask, parties, internalId, externalId, customerProfileService
+ );
+
+ StepVerifier.create(result)
+ .expectNext(streamTask)
+ .verifyComplete();
+
+ verify(customerProfileService, times(2)).upsertParty(any(Party.class), eq(internalId));
+
+ verify(streamTask, never()).info(anyString(), anyString(), eq("upserted"), anyString(), any(), anyString(),
+ any());
+ verify(streamTask, never()).info(anyString(), anyString(), eq("completed_successfully"), anyString(),
+ anyString(), anyString(), any());
+ }
+
+ @Test
+ @DisplayName("should filter out null parties from the list")
+ void processParties_withNullInList() {
+ List parties = List.of(party1, party2);
+ when(customerProfileService.upsertParty(any(Party.class), eq(internalId)))
+ .thenReturn(Mono.just(new PartyResponseUpsertDto()));
+
+ Mono result = helperProcessor.processParties(
+ streamTask, parties, internalId, externalId, customerProfileService
+ );
+
+ StepVerifier.create(result)
+ .expectNext(streamTask)
+ .verifyComplete();
+
+ verify(customerProfileService, times(2)).upsertParty(any(Party.class), eq(internalId));
+ verify(customerProfileService).upsertParty(eq(party1), eq(internalId));
+ verify(customerProfileService).upsertParty(eq(party2), eq(internalId));
+ }
+ }
+}
diff --git a/stream-legal-entity/legal-entity-core/src/test/java/com/backbase/stream/LegalEntitySagaTest.java b/stream-legal-entity/legal-entity-core/src/test/java/com/backbase/stream/LegalEntitySagaTest.java
index 0db62d36f..242fea463 100644
--- a/stream-legal-entity/legal-entity-core/src/test/java/com/backbase/stream/LegalEntitySagaTest.java
+++ b/stream-legal-entity/legal-entity-core/src/test/java/com/backbase/stream/LegalEntitySagaTest.java
@@ -1,16 +1,20 @@
package com.backbase.stream;
+import static com.backbase.stream.FixtureUtils.reflectiveAlphaFixtureMonkey;
import static com.backbase.stream.service.UserService.REMOVED_PREFIX;
import static java.util.Collections.singletonList;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.lenient;
+import com.backbase.customerprofile.api.integration.v1.model.PartyResponseUpsertDto;
import com.backbase.dbs.accesscontrol.api.service.v3.model.ServiceAgreementParticipantsGetResponseBody;
import com.backbase.dbs.contact.api.service.v2.model.AccessContextScope;
import com.backbase.dbs.contact.api.service.v2.model.ContactsBulkPostRequestBody;
@@ -45,6 +49,7 @@
import com.backbase.stream.legalentity.model.Limit;
import com.backbase.stream.legalentity.model.Loan;
import com.backbase.stream.legalentity.model.Multivalued;
+import com.backbase.stream.legalentity.model.Party;
import com.backbase.stream.legalentity.model.PhoneNumber;
import com.backbase.stream.legalentity.model.Privilege;
import com.backbase.stream.legalentity.model.ProductGroup;
@@ -58,10 +63,12 @@
import com.backbase.stream.product.task.BatchProductGroupTask;
import com.backbase.stream.product.task.ProductGroupTask;
import com.backbase.stream.service.AccessGroupService;
+import com.backbase.stream.service.CustomerProfileService;
import com.backbase.stream.service.LegalEntityService;
import com.backbase.stream.service.UserProfileService;
import com.backbase.stream.service.UserService;
import com.backbase.stream.worker.exception.StreamTaskException;
+import com.navercorp.fixturemonkey.FixtureMonkey;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.time.Duration;
@@ -122,10 +129,17 @@ class LegalEntitySagaTest {
@Mock
private UserKindSegmentationSaga userKindSegmentationSaga;
+ @Mock
+ private CustomerProfileService customerProfileService;
+
@Spy
private final LegalEntitySagaConfigurationProperties legalEntitySagaConfigurationProperties =
getLegalEntitySagaConfigurationProperties();
+ private final FixtureMonkey fixtureMonkey = reflectiveAlphaFixtureMonkey;
+
+ private static final int PARTY_SIZE = 10;
+
String leExternalId = "someLeExternalId";
String leParentExternalId = "someParentLeExternalId";
String leInternalId = "someLeInternalId";
@@ -338,6 +352,9 @@ void masterServiceAgreementCreation_activateSingleServiceAgreement() {
void testCustomServiceAgreement_IfFetchedServiceAgreementExists_ThenSettingUp() {
var task = setupLegalEntityTask();
+ when(customerProfileService.upsertParty(any(Party.class), anyString())).thenReturn(
+ Mono.just(fixtureMonkey.giveMeOne(PartyResponseUpsertDto.class)));
+
mockAccessGroupService(userId);
mockUserService(userId);
@@ -351,6 +368,8 @@ void testCustomServiceAgreement_IfFetchedServiceAgreementExists_ThenSettingUp()
void testCustomServiceAgreement_IfNoCustomServiceAgreementExists_ThenCreateMaster() {
var task = setupLegalEntityTask();
+ when(customerProfileService.upsertParty(any(Party.class), anyString())).thenReturn(
+ Mono.just(fixtureMonkey.giveMeOne(PartyResponseUpsertDto.class)));
when(accessGroupService.getUserContextsByUserId(userId)).thenReturn(Mono.empty());
mockUserService(userId);
@@ -364,6 +383,9 @@ void testCustomServiceAgreement_IfNoCustomServiceAgreementExists_ThenCreateMaste
void testCustomServiceAgreement_IfNoMatchingCustomServiceAgreementExists_ThenCreateMaster() {
LegalEntityTask task = setupLegalEntityTask();
+ when(customerProfileService.upsertParty(any(Party.class), anyString())).thenReturn(
+ Mono.just(fixtureMonkey.giveMeOne(PartyResponseUpsertDto.class)));
+
when(accessGroupService.getUserContextsByUserId(userId))
.thenReturn(
Mono.just(List.of(new ServiceAgreement().internalId("sa_id").externalId("sa_ext_id").purpose("TEST"))));
@@ -380,6 +402,8 @@ void testCustomServiceAgreement_IfNoUserDataFoundWhileServiceAgreementFetch_thro
LegalEntityTask task = setupLegalEntityTask();
when(userService.getUsersByLegalEntity(any(), anyInt(), anyInt()))
.thenReturn(Mono.just(new GetUsersList().totalElements(0L).users(null)));
+ when(customerProfileService.upsertParty(any(Party.class), anyString())).thenReturn(
+ Mono.just(fixtureMonkey.giveMeOne(PartyResponseUpsertDto.class)));
Assertions.assertThrows(
StreamTaskException.class,
() -> executeLegalEntityTaskAndBlock(task),
@@ -388,6 +412,49 @@ void testCustomServiceAgreement_IfNoUserDataFoundWhileServiceAgreementFetch_thro
verifyUserService();
}
+ @Test
+ void testSetupParties_IfPartyFound_ThenUpsertParty() {
+ var task = setupLegalEntityTask();
+ when(customerProfileService.upsertParty(any(Party.class), anyString())).thenReturn(
+ Mono.just(fixtureMonkey.giveMeOne(PartyResponseUpsertDto.class)));
+ mockAccessGroupService(userId);
+ mockUserService(userId);
+ legalEntitySaga.executeTask(task).block();
+ verify(customerProfileService, times(PARTY_SIZE)).upsertParty(any(Party.class), anyString());
+ }
+
+ @Test
+ void testProcessCustomerProfile_IfUpsertPartyError_ThenTrowException() {
+ var task = setupLegalEntityTask();
+ var mockException = new WebClientResponseException(
+ "CPS Error",
+ 400,
+ "Bad Request",
+ null,
+ null,
+ null
+ );
+ when(customerProfileService.upsertParty(any(Party.class), anyString())).thenReturn(Mono.error(mockException));
+
+ mockAccessGroupService(userId);
+ mockUserService(userId);
+ legalEntitySaga.executeTask(task).block();
+ verify(customerProfileService, times(PARTY_SIZE)).upsertParty(any(Party.class), anyString());
+ verify(task, times(PARTY_SIZE)).error(
+ eq(LegalEntitySaga.PARTY),
+ eq(LegalEntitySaga.PROCESS_CUSTOMER_PROFILE),
+ eq("failed"),
+ anyString(),
+ isNull(),
+ any(Throwable.class),
+ anyString(),
+ eq("Error upserting party: %s for LE: %s"),
+ anyString(),
+ anyString()
+ );
+ }
+
+
private void mockUserService(String userId) {
when(userService.getUsersByLegalEntity(any(), anyInt(), anyInt()))
.thenReturn(Mono.just(new GetUsersList().totalElements(1L)
@@ -422,31 +489,32 @@ private LegalEntityTask setupLegalEntityTask() {
var jobRole = new JobRole("someJobRole", "someJobRole");
jobRole.setFunctionGroups(singletonList(functionGroup));
- legalEntity = new LegalEntity("le_name", null, null);
- legalEntity.setInternalId(leInternalId);
- legalEntity.setExternalId(leExternalId);
- legalEntity.setParentExternalId(leExternalId);
- legalEntity.setProductGroups(singletonList(productGroup));
+ var setuplegalEntity = new LegalEntity("le_name", null, null);
+ setuplegalEntity.setInternalId(leInternalId);
+ setuplegalEntity.setExternalId(leExternalId);
+ setuplegalEntity.setParentExternalId(leExternalId);
+ setuplegalEntity.setProductGroups(singletonList(productGroup));
+ setuplegalEntity.setParties(fixtureMonkey.giveMe(Party.class, PARTY_SIZE));
var sa = new ServiceAgreement().externalId(customSaExId).addJobRolesItem(jobRole)
.creatorLegalEntity(leExternalId);
- var task = mockLegalEntityTask(legalEntity);
+ var task = mockLegalEntityTask(setuplegalEntity);
- when(task.getLegalEntity()).thenReturn(legalEntity);
+ when(task.getLegalEntity()).thenReturn(setuplegalEntity);
when(legalEntityService.getLegalEntityByExternalId(leExternalId)).thenReturn(Mono.empty());
- when(legalEntityService.getLegalEntityByInternalId(leInternalId)).thenReturn(Mono.just(legalEntity));
+ when(legalEntityService.getLegalEntityByInternalId(leInternalId)).thenReturn(Mono.just(setuplegalEntity));
when(legalEntityService.getMasterServiceAgreementForInternalLegalEntityId(leInternalId)).thenReturn(
Mono.empty());
- when(legalEntityService.createLegalEntity(any())).thenReturn(Mono.just(legalEntity));
+ when(legalEntityService.createLegalEntity(any())).thenReturn(Mono.just(setuplegalEntity));
when(accessGroupService.setupJobRole(any(), any(), any())).thenReturn(Mono.just(jobRole));
when(accessGroupService.createServiceAgreement(any(), any())).thenReturn(Mono.just(sa));
when(batchProductIngestionSaga.process(any(ProductGroupTask.class))).thenReturn(productGroupTaskMono);
when(legalEntitySagaConfigurationProperties.getServiceAgreementPurposes()).thenReturn(
Set.of("FAMILY_BANKING"));
when(userService.setupRealm(task.getLegalEntity())).thenReturn(Mono.just(new Realm()));
- when(userService.linkLegalEntityToRealm(task.getLegalEntity())).thenReturn(Mono.just(legalEntity));
- when(legalEntityService.getLegalEntityByExternalId(leExternalId)).thenReturn(Mono.just(legalEntity));
- when(legalEntityService.putLegalEntity(any())).thenReturn(Mono.just(legalEntity));
+ when(userService.linkLegalEntityToRealm(task.getLegalEntity())).thenReturn(Mono.just(setuplegalEntity));
+ when(legalEntityService.getLegalEntityByExternalId(leExternalId)).thenReturn(Mono.just(setuplegalEntity));
+ when(legalEntityService.putLegalEntity(any())).thenReturn(Mono.just(setuplegalEntity));
return task;
}
diff --git a/stream-legal-entity/legal-entity-core/src/test/java/com/backbase/stream/LegalEntitySagaV2Test.java b/stream-legal-entity/legal-entity-core/src/test/java/com/backbase/stream/LegalEntitySagaV2Test.java
index 9380ab548..431a9680f 100644
--- a/stream-legal-entity/legal-entity-core/src/test/java/com/backbase/stream/LegalEntitySagaV2Test.java
+++ b/stream-legal-entity/legal-entity-core/src/test/java/com/backbase/stream/LegalEntitySagaV2Test.java
@@ -1,15 +1,18 @@
package com.backbase.stream;
+import static com.backbase.stream.FixtureUtils.reflectiveAlphaFixtureMonkey;
import static com.backbase.stream.service.UserService.REMOVED_PREFIX;
import static java.util.Collections.singletonList;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import com.backbase.customerprofile.api.integration.v1.model.PartyResponseUpsertDto;
import com.backbase.dbs.contact.api.service.v2.model.AccessContextScope;
import com.backbase.dbs.contact.api.service.v2.model.ContactsBulkPostRequestBody;
import com.backbase.dbs.contact.api.service.v2.model.ContactsBulkPostResponseBody;
@@ -29,6 +32,7 @@
import com.backbase.stream.legalentity.model.LegalEntity;
import com.backbase.stream.legalentity.model.LegalEntityType;
import com.backbase.stream.legalentity.model.LegalEntityV2;
+import com.backbase.stream.legalentity.model.Party;
import com.backbase.stream.legalentity.model.SavingsAccount;
import com.backbase.stream.legalentity.model.ServiceAgreement;
import com.backbase.stream.legalentity.model.ServiceAgreementV2;
@@ -36,9 +40,11 @@
import com.backbase.stream.mapper.LegalEntityV2toV1Mapper;
import com.backbase.stream.mapper.ServiceAgreementV2ToV1Mapper;
import com.backbase.stream.service.AccessGroupService;
+import com.backbase.stream.service.CustomerProfileService;
import com.backbase.stream.service.LegalEntityService;
import com.backbase.stream.service.UserService;
import com.backbase.stream.worker.exception.StreamTaskException;
+import com.navercorp.fixturemonkey.FixtureMonkey;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
@@ -84,10 +90,17 @@ class LegalEntitySagaV2Test {
@Mock
private UserKindSegmentationSaga userKindSegmentationSaga;
+ @Mock
+ private CustomerProfileService customerProfileService;
+
@Spy
private final LegalEntitySagaConfigurationProperties legalEntitySagaConfigurationProperties =
getLegalEntitySagaConfigurationProperties();
+ private final FixtureMonkey fixtureMonkey = reflectiveAlphaFixtureMonkey;
+
+ private static final int PARTY_SIZE = 20;
+
String leExternalId = "someLeExternalId";
String leParentExternalId = "someParentLeExternalId";
String leInternalId = "someLeInternalId";
@@ -352,6 +365,7 @@ void getMockLegalEntity() {
legalEntityV2 = new LegalEntityV2()
.internalId(leInternalId)
.externalId(leExternalId)
+ .parties(fixtureMonkey.giveMe(Party.class, PARTY_SIZE))
.addAdministratorsItem(adminUser)
.parentExternalId(leParentExternalId)
.users(singletonList(regularUser))
@@ -383,6 +397,9 @@ void test_PostLegalContacts() {
LegalEntityTaskV2 task = mockLegalEntityTask(legalEntityV2);
when(contactsSaga.executeTask(any(ContactsTask.class))).thenReturn(getContactsTask(AccessContextScope.LE));
+ when(customerProfileService.upsertParty(any(Party.class), anyString())).thenReturn(
+ Mono.just(fixtureMonkey.giveMeOne(PartyResponseUpsertDto.class)));
+
LegalEntityTaskV2 result = legalEntitySaga.executeTask(task).block();
Assertions.assertNotNull(result);
@@ -442,6 +459,9 @@ void test_PostUserContacts() {
when(contactsSaga.executeTask(any(ContactsTask.class))).thenReturn(getContactsTask(AccessContextScope.USER));
+ when(customerProfileService.upsertParty(any(Party.class), anyString())).thenReturn(
+ Mono.just(fixtureMonkey.giveMeOne(PartyResponseUpsertDto.class)));
+
LegalEntityTaskV2 result = legalEntitySaga.executeTask(task).block();
Assertions.assertNotNull(result);
@@ -456,6 +476,9 @@ void userKindSegmentationIsDisabled() {
when(userKindSegmentationSaga.isEnabled()).thenReturn(false);
+ when(customerProfileService.upsertParty(any(Party.class), anyString())).thenReturn(
+ Mono.just(fixtureMonkey.giveMeOne(PartyResponseUpsertDto.class)));
+
legalEntitySaga.executeTask(mockLegalEntityTask(legalEntityV2)).block();
verify(userKindSegmentationSaga, never()).executeTask(Mockito.any());
@@ -468,6 +491,9 @@ void userKindSegmentationUsesLegalEntityCustomerCategory() {
when(userKindSegmentationSaga.isEnabled()).thenReturn(true);
+ when(customerProfileService.upsertParty(any(Party.class), anyString())).thenReturn(
+ Mono.just(fixtureMonkey.giveMeOne(PartyResponseUpsertDto.class)));
+
legalEntitySaga.executeTask(mockLegalEntityTask(legalEntityV2)).block();
verify(userKindSegmentationSaga, times(0)).getDefaultCustomerCategory();
@@ -482,6 +508,8 @@ void userKindSegmentationUsesDefaultCustomerCategory() {
when(userKindSegmentationSaga.getDefaultCustomerCategory()).thenReturn(CustomerCategory.RETAIL.getValue());
when(userKindSegmentationSaga.executeTask(any())).thenReturn(
Mono.just(Mockito.mock(UserKindSegmentationTask.class)));
+ when(customerProfileService.upsertParty(any(Party.class), anyString())).thenReturn(
+ Mono.just(fixtureMonkey.giveMeOne(PartyResponseUpsertDto.class)));
legalEntitySaga.executeTask(mockLegalEntityTask(legalEntityV2)).block();
@@ -496,6 +524,9 @@ void whenUserKindSegmentationIsEnabledAndNoCustomerCategoryCanBeDeterminedReturn
when(userKindSegmentationSaga.isEnabled()).thenReturn(true);
when(userKindSegmentationSaga.getDefaultCustomerCategory()).thenReturn(null);
+ when(customerProfileService.upsertParty(any(Party.class), anyString())).thenReturn(
+ Mono.just(fixtureMonkey.giveMeOne(PartyResponseUpsertDto.class)));
+
var task = mockLegalEntityTask(legalEntityV2);
Assertions.assertThrows(
@@ -505,6 +536,37 @@ void whenUserKindSegmentationIsEnabledAndNoCustomerCategoryCanBeDeterminedReturn
);
}
+ @Test
+ void testSetupParties_IfPartyFound_ThenUpsertParty() {
+ getMockLegalEntity();
+ var task = mockLegalEntityTask(legalEntityV2);
+ when(contactsSaga.executeTask(any(ContactsTask.class))).thenReturn(getContactsTask(AccessContextScope.LE));
+ when(customerProfileService.upsertParty(any(Party.class), anyString())).thenReturn(
+ Mono.just(fixtureMonkey.giveMeOne(PartyResponseUpsertDto.class)));
+ var result = legalEntitySaga.executeTask(task).block();
+ verify(customerProfileService, times(PARTY_SIZE)).upsertParty(any(Party.class), anyString());
+ Assertions.assertNotNull(result);
+ }
+
+ @Test
+ void testProcessCustomerProfile_IfUpsertPartyError_ThenTrowException() {
+ getMockLegalEntity();
+ var task = mockLegalEntityTask(legalEntityV2);
+ when(contactsSaga.executeTask(any(ContactsTask.class))).thenReturn(getContactsTask(AccessContextScope.LE));
+ var mockException = new WebClientResponseException(
+ "CPS Error",
+ 400,
+ "Bad Request",
+ null,
+ null,
+ null
+ );
+ when(customerProfileService.upsertParty(any(Party.class), anyString())).thenReturn(Mono.error(mockException));
+ var result = legalEntitySaga.executeTask(task).block();
+ verify(customerProfileService, times(PARTY_SIZE)).upsertParty(any(Party.class), anyString());
+ Assertions.assertNotNull(result);
+ }
+
@ParameterizedTest
@MethodSource("parameters_upster_error")
void upster_error(Exception ex, String error) {
diff --git a/stream-legal-entity/legal-entity-http/src/test/java/com/backbase/stream/controller/ServiceAgreementControllerTest.java b/stream-legal-entity/legal-entity-http/src/test/java/com/backbase/stream/controller/ServiceAgreementControllerTest.java
index df271a0b5..f8d0e2ced 100644
--- a/stream-legal-entity/legal-entity-http/src/test/java/com/backbase/stream/controller/ServiceAgreementControllerTest.java
+++ b/stream-legal-entity/legal-entity-http/src/test/java/com/backbase/stream/controller/ServiceAgreementControllerTest.java
@@ -7,6 +7,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
+import com.backbase.customerprofile.api.integration.v1.PartyManagementIntegrationApi;
import com.backbase.dbs.accesscontrol.api.service.v3.LegalEntitiesApi;
import com.backbase.dbs.accesscontrol.api.service.v3.model.FunctionGroupItem;
import com.backbase.dbs.arrangement.api.service.v3.ArrangementsApi;
@@ -18,6 +19,7 @@
import com.backbase.dbs.user.api.service.v2.model.GetUser;
import com.backbase.loan.inbound.api.service.v1.LoansApi;
import com.backbase.stream.audiences.UserKindSegmentationSaga;
+import com.backbase.stream.clients.config.CustomerProfileClientConfig;
import com.backbase.stream.config.LegalEntityHttpConfiguration;
import com.backbase.stream.configuration.LegalEntitySagaConfiguration;
import com.backbase.stream.configuration.UpdatedServiceAgreementSagaConfiguration;
@@ -32,6 +34,7 @@
import com.backbase.stream.legalentity.model.UpdatedServiceAgreement;
import com.backbase.stream.legalentity.model.UpdatedServiceAgreementResponse;
import com.backbase.stream.legalentity.model.User;
+import com.backbase.stream.mapper.PartyMapper;
import com.backbase.stream.product.task.BatchProductGroupTask;
import com.backbase.stream.product.task.ProductGroupTask;
import com.backbase.stream.service.AccessGroupService;
@@ -82,6 +85,9 @@ class ServiceAgreementControllerTest {
@MockBean
private com.backbase.identity.integration.api.service.ApiClient identityApiClient;
+ @MockBean
+ private com.backbase.customerprofile.api.integration.ApiClient customerProfileApiClient;
+
@MockBean
private LimitsServiceApi limitsApi;
@@ -106,6 +112,9 @@ class ServiceAgreementControllerTest {
@MockBean
private UserProfileManagementApi userProfileManagementApi;
+ @MockBean
+ private PartyManagementIntegrationApi partyManagementIntegrationApi;
+
@MockBean
private com.backbase.dbs.user.profile.api.service.v2.UserProfileManagementApi userProfileManagement;
@@ -121,6 +130,12 @@ class ServiceAgreementControllerTest {
@MockBean
private PlansService plansService;
+ @MockBean
+ private PartyMapper partyMapper;
+
+ @MockBean
+ private CustomerProfileClientConfig customerProfileClientConfig;
+
@Autowired
private WebTestClient webTestClient;
diff --git a/stream-sdk/stream-parent/stream-test-support/pom.xml b/stream-sdk/stream-parent/stream-test-support/pom.xml
index 2161d0eb7..b46a66139 100644
--- a/stream-sdk/stream-parent/stream-test-support/pom.xml
+++ b/stream-sdk/stream-parent/stream-test-support/pom.xml
@@ -25,6 +25,12 @@
1.2
+
+ com.navercorp.fixturemonkey
+ fixture-monkey-starter
+ 1.1.11
+
+
org.iban4j
iban4j
@@ -82,6 +88,12 @@
wiremock-jre8-standalone
2.35.1
+
+
+ com.backbase.buildingblocks
+ service-sdk-starter-test
+ test
+
diff --git a/stream-sdk/stream-parent/stream-test-support/src/main/java/com/backbase/stream/FixtureUtils.java b/stream-sdk/stream-parent/stream-test-support/src/main/java/com/backbase/stream/FixtureUtils.java
new file mode 100644
index 000000000..7f433845e
--- /dev/null
+++ b/stream-sdk/stream-parent/stream-test-support/src/main/java/com/backbase/stream/FixtureUtils.java
@@ -0,0 +1,23 @@
+package com.backbase.stream;
+
+import com.navercorp.fixturemonkey.FixtureMonkey;
+import com.navercorp.fixturemonkey.api.introspector.FieldReflectionArbitraryIntrospector;
+import com.navercorp.fixturemonkey.api.jqwik.JavaTypeArbitraryGenerator;
+import com.navercorp.fixturemonkey.api.jqwik.JqwikPlugin;
+import net.jqwik.api.Arbitraries;
+import net.jqwik.api.arbitraries.StringArbitrary;
+
+public class FixtureUtils {
+
+ public static final FixtureMonkey reflectiveAlphaFixtureMonkey = FixtureMonkey.builder()
+ .objectIntrospector(FieldReflectionArbitraryIntrospector.INSTANCE)
+ .defaultNotNull(true)
+ .plugin(new JqwikPlugin().javaTypeArbitraryGenerator(new JavaTypeArbitraryGenerator() {
+ @Override
+ public StringArbitrary strings() {
+ return Arbitraries.strings().alpha();
+ }
+ }))
+ .build();
+
+}