diff --git a/pom.xml b/pom.xml index cf2ea0b..e9d9ff3 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.rbkmoney service-parent-pom - 2.0.8 + 2.0.11 org-manager @@ -81,12 +81,6 @@ org.springframework.boot spring-boot-starter-web - - - org.apache.tomcat.embed - tomcat-embed-core - - org.springframework.boot @@ -206,11 +200,6 @@ jackson-databind-nullable 0.2.1 - - org.apache.tomcat.embed - tomcat-embed-core - 9.0.45 - diff --git a/src/main/java/com/rbkmoney/orgmanager/config/PartyManagementConfig.java b/src/main/java/com/rbkmoney/orgmanager/config/PartyManagementConfig.java new file mode 100644 index 0000000..0a91a12 --- /dev/null +++ b/src/main/java/com/rbkmoney/orgmanager/config/PartyManagementConfig.java @@ -0,0 +1,26 @@ +package com.rbkmoney.orgmanager.config; + +import com.rbkmoney.damsel.payment_processing.PartyManagementSrv; +import com.rbkmoney.woody.thrift.impl.http.THSpawnClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; + +import java.io.IOException; + +@Configuration +public class PartyManagementConfig { + + @Bean + public PartyManagementSrv.Iface partyManagementClient( + @Value("${party-management.url}") Resource resource, + @Value("${party-management.networkTimeout}") int networkTimeout + ) throws IOException { + return new THSpawnClientBuilder() + .withNetworkTimeout(networkTimeout) + .withAddress(resource.getURI()) + .build(PartyManagementSrv.Iface.class); + } + +} diff --git a/src/main/java/com/rbkmoney/orgmanager/controller/OrgsController.java b/src/main/java/com/rbkmoney/orgmanager/controller/OrgsController.java index bc1a94f..4c764fd 100644 --- a/src/main/java/com/rbkmoney/orgmanager/controller/OrgsController.java +++ b/src/main/java/com/rbkmoney/orgmanager/controller/OrgsController.java @@ -36,7 +36,10 @@ public class OrgsController implements OrgsApi { organization); resourceAccessService.checkRights(); AccessToken accessToken = keycloakService.getAccessToken(); - return organizationService.create(accessToken.getSubject(), organization, idempotencyKey); + Organization createdOrganization = organizationService.create(accessToken, organization, idempotencyKey); + return ResponseEntity + .status(HttpStatus.CREATED) + .body(createdOrganization); } @Override @@ -48,7 +51,11 @@ public class OrgsController implements OrgsApi { .orgId(orgId) .build(); resourceAccessService.checkRights(resource); - return organizationService.get(orgId); + return organizationService.get(orgId) + .map(ResponseEntity::ok) + .orElseGet(() -> ResponseEntity + .status(HttpStatus.NOT_FOUND) + .build()); } @Override @@ -166,7 +173,8 @@ public class OrgsController implements OrgsApi { .orgId(orgId) .build(); resourceAccessService.checkRights(resource); - return organizationService.modify(orgId, inlineObject.getName()); + Organization modifiedOrganization = organizationService.modify(orgId, inlineObject.getName()); + return ResponseEntity.ok(modifiedOrganization); } @Override diff --git a/src/main/java/com/rbkmoney/orgmanager/exception/PartyManagementException.java b/src/main/java/com/rbkmoney/orgmanager/exception/PartyManagementException.java new file mode 100644 index 0000000..95a9ab0 --- /dev/null +++ b/src/main/java/com/rbkmoney/orgmanager/exception/PartyManagementException.java @@ -0,0 +1,12 @@ +package com.rbkmoney.orgmanager.exception; + +public class PartyManagementException extends RuntimeException { + + public PartyManagementException() { + } + + public PartyManagementException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/com/rbkmoney/orgmanager/service/OrganizationService.java b/src/main/java/com/rbkmoney/orgmanager/service/OrganizationService.java index 2d940cb..f9eed4e 100644 --- a/src/main/java/com/rbkmoney/orgmanager/service/OrganizationService.java +++ b/src/main/java/com/rbkmoney/orgmanager/service/OrganizationService.java @@ -14,7 +14,7 @@ import com.rbkmoney.orgmanager.service.dto.MemberWithRoleDto; import com.rbkmoney.swag.organizations.model.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; +import org.keycloak.representations.AccessToken; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -44,41 +44,37 @@ public class OrganizationService { private final MemberContextRepository memberContextRepository; private final InvitationService invitationService; private final MemberRoleService memberRoleService; + private final PartyManagementService partyManagementService; // TODO [a.romanov]: idempotency - public ResponseEntity create( - String ownerId, + @Transactional + public Organization create( + AccessToken token, Organization organization, String idempotencyKey) { - OrganizationEntity entity = organizationConverter.toEntity(organization, ownerId); + String keycloakUserId = token.getSubject(); + OrganizationEntity entity = organizationConverter.toEntity(organization, keycloakUserId); OrganizationEntity savedEntity = organizationRepository.save(entity); + // TODO [v.hramov]: when org-manager will be fully operational party_id != keycloak_user_id + // most likely we will use organization_id as party_id + partyManagementService.createParty(keycloakUserId, keycloakUserId, token.getEmail()); Organization savedOrganization = organizationConverter.toDomain(savedEntity); - return ResponseEntity - .status(HttpStatus.CREATED) - .body(savedOrganization); + savedOrganization.setParty(keycloakUserId); + return savedOrganization; } @Transactional - public ResponseEntity modify(String orgId, String orgName) { + public Organization modify(String orgId, String orgName) { OrganizationEntity organizationEntity = findById(orgId); organizationEntity.setName(orgName); - Organization savedOrganization = organizationConverter.toDomain(organizationEntity); - - return ResponseEntity.ok(savedOrganization); + return organizationConverter.toDomain(organizationEntity); } - public ResponseEntity get(String orgId) { - Optional entity = organizationRepository.findById(orgId); - - if (entity.isPresent()) { - Organization organization = organizationConverter.toDomain(entity.get()); - - return ResponseEntity.ok(organization); - } - return ResponseEntity - .status(HttpStatus.NOT_FOUND) - .build(); + @Transactional(readOnly = true) + public Optional get(String orgId) { + return organizationRepository.findById(orgId) + .map(organizationConverter::toDomain); } diff --git a/src/main/java/com/rbkmoney/orgmanager/service/PartyManagementService.java b/src/main/java/com/rbkmoney/orgmanager/service/PartyManagementService.java new file mode 100644 index 0000000..5019759 --- /dev/null +++ b/src/main/java/com/rbkmoney/orgmanager/service/PartyManagementService.java @@ -0,0 +1,7 @@ +package com.rbkmoney.orgmanager.service; + +public interface PartyManagementService { + + void createParty(String partyId, String userId, String email); + +} diff --git a/src/main/java/com/rbkmoney/orgmanager/service/PartyManagementServiceImpl.java b/src/main/java/com/rbkmoney/orgmanager/service/PartyManagementServiceImpl.java new file mode 100644 index 0000000..57051ca --- /dev/null +++ b/src/main/java/com/rbkmoney/orgmanager/service/PartyManagementServiceImpl.java @@ -0,0 +1,40 @@ +package com.rbkmoney.orgmanager.service; + +import com.rbkmoney.damsel.domain.PartyContactInfo; +import com.rbkmoney.damsel.payment_processing.ExternalUser; +import com.rbkmoney.damsel.payment_processing.PartyExists; +import com.rbkmoney.damsel.payment_processing.PartyManagementSrv; +import com.rbkmoney.damsel.payment_processing.PartyParams; +import com.rbkmoney.damsel.payment_processing.UserInfo; +import com.rbkmoney.damsel.payment_processing.UserType; +import com.rbkmoney.orgmanager.exception.PartyManagementException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.thrift.TException; +import org.springframework.stereotype.Service; + +@Slf4j +@RequiredArgsConstructor +@Service +public class PartyManagementServiceImpl implements PartyManagementService { + + private final PartyManagementSrv.Iface partyManagementClient; + + @Override + public void createParty(String partyId, String userId, String email) { + UserInfo userInfo = new UserInfo(userId, UserType.external_user(new ExternalUser())); + PartyParams partyParams = new PartyParams(new PartyContactInfo(email)); + try { + partyManagementClient.create(userInfo, partyId, partyParams); + } catch (PartyExists ex) { + log.warn("Party already exists. (partyId: {}, userId: {}, email: {})", partyId, userId, email); + } catch (TException ex) { + throw new PartyManagementException( + String.format("Exception during party creation. (partyId: %s, userId: %s, email: %s)", + partyId, userId, email), + ex); + } + + log.info("Created party. (partyId: {}, userId: {}, email: {})", partyId, userId, email); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 558b274..91a443a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -82,6 +82,10 @@ dudoser: url: http://dudoser:8022/dudos networkTimeout: 10000 +party-management: + url: http://party-management:8022/party/time + networkTimeout: 10000 + dashboard: url: https://dashboard.rbk.money/organization-section/accept-invitation/ diff --git a/src/test/java/com/rbkmoney/orgmanager/TestObjectFactory.java b/src/test/java/com/rbkmoney/orgmanager/TestObjectFactory.java index 83fcc05..e10e94f 100644 --- a/src/test/java/com/rbkmoney/orgmanager/TestObjectFactory.java +++ b/src/test/java/com/rbkmoney/orgmanager/TestObjectFactory.java @@ -45,10 +45,15 @@ public abstract class TestObjectFactory { } public static AccessToken testToken() { + return testToken(randomString(), randomString()); + } + + public static AccessToken testToken(String subject, String email) { AccessToken token = new AccessToken(); token.exp(LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)) - .subject(randomString()) + .subject(subject) .id(randomString()); + token.setEmail(email); return token; } diff --git a/src/test/java/com/rbkmoney/orgmanager/service/OrganizationServiceTest.java b/src/test/java/com/rbkmoney/orgmanager/service/OrganizationServiceTest.java index 16c4d72..928c369 100644 --- a/src/test/java/com/rbkmoney/orgmanager/service/OrganizationServiceTest.java +++ b/src/test/java/com/rbkmoney/orgmanager/service/OrganizationServiceTest.java @@ -5,6 +5,7 @@ import com.rbkmoney.orgmanager.converter.MemberConverter; import com.rbkmoney.orgmanager.converter.OrganizationConverter; import com.rbkmoney.orgmanager.entity.MemberEntity; import com.rbkmoney.orgmanager.entity.OrganizationEntity; +import com.rbkmoney.orgmanager.exception.PartyManagementException; import com.rbkmoney.orgmanager.exception.ResourceNotFoundException; import com.rbkmoney.orgmanager.repository.MemberRepository; import com.rbkmoney.orgmanager.repository.OrganizationRepository; @@ -17,14 +18,13 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; +import static com.rbkmoney.orgmanager.TestObjectFactory.testToken; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.*; @@ -32,44 +32,81 @@ import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class OrganizationServiceTest { - @Mock private OrganizationConverter organizationConverter; - @Mock private OrganizationRepository organizationRepository; - @Mock private MemberConverter memberConverter; - @Mock private MemberRepository memberRepository; + @Mock + private OrganizationConverter organizationConverter; + @Mock + private OrganizationRepository organizationRepository; + @Mock + private MemberConverter memberConverter; + @Mock + private MemberRepository memberRepository; + @Mock + private PartyManagementService partyManagementService; @InjectMocks private OrganizationService service; + private static final String OWNER_ID = "testOwnerId"; + private static final String EMAIL = "email@email.org"; + + @Test + void shouldThrowPartyManagementExceptionOnCreate() { + Organization organization = new Organization(); + OrganizationEntity entity = new OrganizationEntity(); + OrganizationEntity savedEntity = new OrganizationEntity(); + + when(organizationConverter.toEntity(organization, OWNER_ID)) + .thenReturn(entity); + when(organizationRepository.save(entity)) + .thenReturn(savedEntity); + doThrow(new PartyManagementException()) + .when(partyManagementService).createParty(anyString(), anyString(), anyString()); + + assertThrows(PartyManagementException.class, + () -> service.create(testToken(OWNER_ID, EMAIL), organization, "")); + + verify(organizationConverter, times(1)) + .toEntity(organization, OWNER_ID); + verify(organizationRepository, times(1)) + .save(entity); + verify(partyManagementService, times(1)) + .createParty(OWNER_ID, OWNER_ID, EMAIL); + verify(organizationConverter, times(0)) + .toDomain(any(OrganizationEntity.class)); + } + @Test void shouldCreate() { - // Given Organization organization = new Organization(); OrganizationEntity entity = new OrganizationEntity(); OrganizationEntity savedEntity = new OrganizationEntity(); Organization savedOrganization = new Organization(); - when(organizationConverter.toEntity(organization, "testOwnerId")) + when(organizationConverter.toEntity(organization, OWNER_ID)) .thenReturn(entity); when(organizationRepository.save(entity)) .thenReturn(savedEntity); when(organizationConverter.toDomain(savedEntity)) .thenReturn(savedOrganization); - // When - ResponseEntity response = service.create("testOwnerId", organization, ""); + Organization response = service.create(testToken(OWNER_ID, EMAIL), organization, ""); - // Then + verify(organizationConverter, times(1)) + .toEntity(organization, OWNER_ID); verify(organizationRepository, times(1)) .save(entity); - assertThat(response.getStatusCode()) - .isEqualTo(HttpStatus.CREATED); - assertThat(response.getBody()) + verify(partyManagementService, times(1)) + .createParty(OWNER_ID, OWNER_ID, EMAIL); + verify(organizationConverter, times(1)) + .toDomain(savedEntity); + assertThat(response) .isEqualTo(savedOrganization); + assertThat(response.getParty()) + .isEqualTo(OWNER_ID); } @Test void shouldGet() { - // Given String orgId = "orgId"; OrganizationEntity entity = new OrganizationEntity(); Organization organization = new Organization(); @@ -79,37 +116,29 @@ public class OrganizationServiceTest { when(organizationConverter.toDomain(entity)) .thenReturn(organization); - // When - ResponseEntity response = service.get(orgId); + Optional response = service.get(orgId); - // Then - assertThat(response.getStatusCode()) - .isEqualTo(HttpStatus.OK); - assertThat(response.getBody()) + assertThat(response.isPresent()) + .isEqualTo(true); + assertThat(response.get()) .isEqualTo(organization); } @Test void shouldReturnNotFound() { - // Given String orgId = "orgId"; when(organizationRepository.findById(orgId)) .thenReturn(Optional.empty()); - // When - ResponseEntity response = service.get(orgId); + Optional response = service.get(orgId); - // Then - assertThat(response.getStatusCode()) - .isEqualTo(HttpStatus.NOT_FOUND); - assertThat(response.getBody()) - .isNull(); + assertThat(response.isPresent()) + .isEqualTo(false); } @Test void shouldListMembers() { - // Given String orgId = TestObjectFactory.randomString(); Member member = new Member(); @@ -123,10 +152,8 @@ public class OrganizationServiceTest { when(memberConverter.toDomain(memberWithRoleList)) .thenReturn(List.of(member)); - // When MemberOrgListResult response = service.listMembers(orgId); - // Then assertThat(response) .isNotNull(); assertThat(response.getResult()) @@ -174,49 +201,39 @@ public class OrganizationServiceTest { @Test void shouldReturnNotFoundIfNoOrganizationExistForMembersList() { - // Given String orgId = "orgId"; - // When when(organizationRepository.existsById(orgId)) .thenReturn(false); - // Then assertThrows(ResourceNotFoundException.class, () -> service.listMembers(orgId)); } @Test void shouldThrowExceptionIfOrganizationDoesNotExist() { - // Given String orgId = TestObjectFactory.randomString(); String userId = TestObjectFactory.randomString(); - // When when(organizationRepository.findById(orgId)) .thenReturn(Optional.empty()); - //Then assertThrows(ResourceNotFoundException.class, () -> service.getOrgMember(userId, orgId)); } @Test void shouldThrowExceptionIfUserNotMemberOfOrganization() { - // Given String orgId = TestObjectFactory.randomString(); OrganizationEntity organizationEntity = new OrganizationEntity(); organizationEntity.setId(orgId); String userId = TestObjectFactory.randomString(); - // When when(organizationRepository.findById(orgId)) .thenReturn(Optional.of(organizationEntity)); - //Then assertThrows(ResourceNotFoundException.class, () -> service.getOrgMember(userId, orgId)); } @Test void shouldGetOrgMember() { - // Given String orgId = TestObjectFactory.randomString(); OrganizationEntity organizationEntity = new OrganizationEntity(); organizationEntity.setId(orgId); @@ -231,10 +248,8 @@ public class OrganizationServiceTest { when(memberConverter.toDomain(memberEntity, Collections.emptyList())) .thenReturn(expectedMember); - // When Member actualMember = service.getOrgMember(userId, orgId); - // Then assertThat(actualMember) .isEqualTo(expectedMember); } diff --git a/src/test/java/com/rbkmoney/orgmanager/service/PartyManagementServiceImplTest.java b/src/test/java/com/rbkmoney/orgmanager/service/PartyManagementServiceImplTest.java new file mode 100644 index 0000000..9fe92b6 --- /dev/null +++ b/src/test/java/com/rbkmoney/orgmanager/service/PartyManagementServiceImplTest.java @@ -0,0 +1,97 @@ +package com.rbkmoney.orgmanager.service; + +import com.rbkmoney.damsel.payment_processing.InvalidUser; +import com.rbkmoney.damsel.payment_processing.PartyExists; +import com.rbkmoney.damsel.payment_processing.PartyManagementSrv; +import com.rbkmoney.damsel.payment_processing.PartyParams; +import com.rbkmoney.damsel.payment_processing.UserInfo; +import com.rbkmoney.orgmanager.exception.PartyManagementException; +import org.apache.thrift.TException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static com.rbkmoney.orgmanager.TestObjectFactory.randomString; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class PartyManagementServiceImplTest { + + private PartyManagementService partyManagementService; + + @Mock + private PartyManagementSrv.Iface partyManagementClient; + + @BeforeEach + void setUp() { + partyManagementService = new PartyManagementServiceImpl(partyManagementClient); + } + + @Test + void shouldThrowPartyManagementExceptionOnCreateParty() throws TException { + doThrow(new InvalidUser()) + .when(partyManagementClient).create(any(UserInfo.class), anyString(), any(PartyParams.class)); + String partyId = randomString(); + String userId = randomString(); + String email = randomString(); + + PartyManagementException partyManagementException = + assertThrows( + PartyManagementException.class, + () -> partyManagementService.createParty(partyId, userId, email) + ); + + assertTrue(partyManagementException.getMessage().contains(String.format( + "Exception during party creation. (partyId: %s, userId: %s, email: %s)", partyId, userId, email))); + } + + @Test + void shouldCreatePartyIfPartyExistThrown() throws TException { + doThrow(new PartyExists()) + .when(partyManagementClient).create(any(UserInfo.class), anyString(), any(PartyParams.class)); + String partyId = randomString(); + String userId = randomString(); + String email = randomString(); + + partyManagementService.createParty(partyId, userId, email); + + verify(partyManagementClient, times(1)) + .create(any(UserInfo.class), anyString(), any(PartyParams.class)); + } + + @Test + void shouldCreateParty() throws TException { + String partyId = randomString(); + String userId = randomString(); + String email = randomString(); + + partyManagementService.createParty(partyId, userId, email); + + ArgumentCaptor userInfoCaptor = ArgumentCaptor.forClass(UserInfo.class); + ArgumentCaptor partyIdCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor partyParamsCaptor = ArgumentCaptor.forClass(PartyParams.class); + + verify(partyManagementClient, times(1)) + .create(userInfoCaptor.capture(), partyIdCaptor.capture(), partyParamsCaptor.capture()); + + assertEquals(1, userInfoCaptor.getAllValues().size()); + assertEquals(userId, userInfoCaptor.getValue().getId()); + assertTrue(userInfoCaptor.getValue().getType().isSetExternalUser()); + + assertEquals(1, partyIdCaptor.getAllValues().size()); + assertEquals(partyId, partyIdCaptor.getValue()); + + assertEquals(1, partyParamsCaptor.getAllValues().size()); + assertEquals(email, partyParamsCaptor.getValue().getContactInfo().getEmail()); + } +}