Skip to content

Commit

Permalink
feat(OfferService)! : unsubscribe OfferSubscription for apps and serv…
Browse files Browse the repository at this point in the history
…ice (#180)

Refs: CPLP-3027
Reviewed-By: Phil Schneider <[email protected]>
  • Loading branch information
VPrasannaK94 authored Aug 16, 2023
1 parent 21f4ad8 commit 0bf8b39
Show file tree
Hide file tree
Showing 14 changed files with 282 additions and 116 deletions.
29 changes: 2 additions & 27 deletions src/marketplace/Apps.Service/BusinessLogic/AppsBusinessLogic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -239,33 +239,8 @@ public async Task ActivateOwnCompanyProvidedAppSubscriptionAsync(Guid subscripti
}

/// <inheritdoc/>
public async Task UnsubscribeOwnCompanyAppSubscriptionAsync(Guid subscriptionId, Guid companyId)
{
var offerSubscriptionsRepository = _portalRepositories.GetInstance<IOfferSubscriptionsRepository>();
var assignedAppData = await offerSubscriptionsRepository.GetCompanyAssignedAppDataForCompanyUserAsync(subscriptionId, companyId).ConfigureAwait(false);
if (assignedAppData == default)
{
throw new NotFoundException($"Subscription {subscriptionId} does not exist.");
}

var (status, isSubscribingCompany, _) = assignedAppData;

if (!isSubscribingCompany)
{
throw new ForbiddenException("the calling user does not belong to the subscribing company");
}

if (status != OfferSubscriptionStatusId.ACTIVE && status != OfferSubscriptionStatusId.PENDING)
{
throw new ConflictException($"There is no active or pending subscription for company '{companyId}' and subscriptionId '{subscriptionId}'");
}

offerSubscriptionsRepository.AttachAndModifyOfferSubscription(subscriptionId, os =>
{
os.OfferSubscriptionStatusId = OfferSubscriptionStatusId.INACTIVE;
});
await _portalRepositories.SaveAsync().ConfigureAwait(false);
}
public Task UnsubscribeOwnCompanyAppSubscriptionAsync(Guid subscriptionId, Guid companyId) =>
_offerService.UnsubscribeOwnCompanySubscriptionAsync(subscriptionId, companyId);

/// <inheritdoc/>
public IAsyncEnumerable<AllOfferData> GetCompanyProvidedAppsDataForUserAsync(Guid companyId) =>
Expand Down
8 changes: 8 additions & 0 deletions src/marketplace/Offers.Library/Service/IOfferService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -269,4 +269,12 @@ Task CreateOrUpdateOfferSubscriptionAgreementConsentAsync(Guid subscriptionId,
/// <param name="contactUserRoles">The roles of the users that will be listed as contact</param>
/// <returns>Returns the details of the subscription</returns>
Task<AppProviderSubscriptionDetailData> GetAppSubscriptionDetailsForProviderAsync(Guid offerId, Guid subscriptionId, Guid companyId, OfferTypeId offerTypeId, IEnumerable<UserRoleConfig> contactUserRoles);

/// <summary>
/// Unsubscribe the Offer subscription by subscriptionId
/// </summary>
/// <param name="subscriptionId">Id of the subscription</param>
/// <param name="companyId">Identity of the user</param>
/// <param name="offerTypeId">Offer type</param>
Task UnsubscribeOwnCompanySubscriptionAsync(Guid subscriptionId, Guid companyId);
}
47 changes: 47 additions & 0 deletions src/marketplace/Offers.Library/Service/OfferService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -873,4 +873,51 @@ private async Task<IEnumerable<Guid>> ValidateRoleData(IEnumerable<UserRoleConfi
}
return await Pagination.CreateResponseAsync(page, size, 15, GetCompanySubscribedOfferSubscriptionStatusesData).ConfigureAwait(false);
}
/// <inheritdoc/>
public async Task UnsubscribeOwnCompanySubscriptionAsync(Guid subscriptionId, Guid companyId)
{
var offerSubscriptionsRepository = _portalRepositories.GetInstance<IOfferSubscriptionsRepository>();
var connectorsRepository = _portalRepositories.GetInstance<IConnectorsRepository>();
var userRepository = _portalRepositories.GetInstance<IUserRepository>();
var assignedOfferSubscriptionData = await offerSubscriptionsRepository.GetCompanyAssignedOfferSubscriptionDataForCompanyUserAsync(subscriptionId, companyId).ConfigureAwait(false);
if (assignedOfferSubscriptionData == default)
{
throw new NotFoundException($"Subscription {subscriptionId} does not exist.");
}

var (status, isSubscribingCompany, _, connectorIds, serviceAccounts) = assignedOfferSubscriptionData;

if (!isSubscribingCompany)
{
throw new ForbiddenException("the calling user does not belong to the subscribing company");
}

if (status != OfferSubscriptionStatusId.ACTIVE && status != OfferSubscriptionStatusId.PENDING)
{
throw new ConflictException($"There is no active or pending subscription for company '{companyId}' and subscriptionId '{subscriptionId}'");
}

offerSubscriptionsRepository.AttachAndModifyOfferSubscription(subscriptionId, os =>
{
os.OfferSubscriptionStatusId = OfferSubscriptionStatusId.INACTIVE;
});

foreach (var cid in connectorIds)
{
connectorsRepository.AttachAndModifyConnector(cid, null, con =>
{
con.StatusId = ConnectorStatusId.INACTIVE;
});
}

foreach (var sid in serviceAccounts)
{
userRepository.AttachAndModifyIdentity(sid, null, iden =>
{
iden.UserStatusId = UserStatusId.INACTIVE;
});
}

await _portalRepositories.SaveAsync().ConfigureAwait(false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,11 @@ public interface IServiceBusinessLogic
/// <param name="companyId">Id of the company</param>
/// <returns>Returns the response data</returns>
Task StartAutoSetupAsync(OfferAutoSetupData data, Guid companyId);

/// <summary>
/// Unsubscribes an Service for the current users company.
/// </summary>
/// <param name="subscriptionId">ID of the subscription to unsubscribe from.</param>
/// <param name="companyId">Id of the users company that initiated app unsubscription.</param>
public Task UnsubscribeOwnCompanyServiceSubscriptionAsync(Guid subscriptionId, Guid companyId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -199,4 +199,8 @@ public Task<SubscriberSubscriptionDetailData> GetSubscriptionDetailForSubscriber
/// <inheritdoc />
public Task StartAutoSetupAsync(OfferAutoSetupData data, Guid companyId) =>
_offerSetupService.StartAutoSetupAsync(data, companyId, OfferTypeId.SERVICE);

/// <inheritdoc/>
public Task UnsubscribeOwnCompanyServiceSubscriptionAsync(Guid subscriptionId, Guid companyId) =>
_offerService.UnsubscribeOwnCompanySubscriptionAsync(subscriptionId, companyId);
}
21 changes: 21 additions & 0 deletions src/marketplace/Services.Service/Controllers/ServicesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -300,4 +300,25 @@ public Task<SubscriberSubscriptionDetailData> GetSubscriptionDetailForSubscriber
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
public Task<Pagination.Response<OfferSubscriptionStatusDetailData>> GetCompanySubscribedServiceSubscriptionStatusesForUserAsync([FromQuery] int page = 0, [FromQuery] int size = 15) =>
this.WithCompanyId(companyId => _serviceBusinessLogic.GetCompanySubscribedServiceSubscriptionStatusesForUserAsync(page, size, companyId));

/// <summary>
/// Unsubscribes an service from the current user's company's subscriptions.
/// </summary>
/// <param name="subscriptionId" example="D3B1ECA2-6148-4008-9E6C-C1C2AEA5C645">ID of the subscription to unsubscribe from.</param>
/// <remarks>Example: PUT: /api/service/{subscriptionId}/unsubscribe</remarks>
/// <response code="204">The service was successfully unsubscribed from.</response>
/// <response code="400">Either the sub claim is empty/invalid, user does not exist or the subscription might not have the correct status or the companyID is incorrect.</response>
/// <response code="404">Service does not exist.</response>
[HttpPut]
[Route("{subscriptionId}/unsubscribe")]
[Authorize(Roles = "unsubscribe_apps")]
[Authorize(Policy = PolicyTypes.ValidCompany)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)]
public async Task<IActionResult> UnsubscribeCompanyServiceSubscriptionAsync([FromRoute] Guid subscriptionId)
{
await this.WithCompanyId(companyId => _serviceBusinessLogic.UnsubscribeOwnCompanyServiceSubscriptionAsync(subscriptionId, companyId)).ConfigureAwait(false);
return NoContent();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public interface IOfferSubscriptionsRepository

Task<(OfferSubscriptionStatusId SubscriptionStatusId, Guid RequestorId, Guid AppId, string? AppName, bool IsUserOfProvider, RequesterData Requester)> GetCompanyAssignedAppDataForProvidingCompanyUserAsync(Guid subscriptionId, Guid userCompanyId);

Task<(OfferSubscriptionStatusId OfferSubscriptionStatusId, bool IsSubscribingCompany, bool IsValidSubscriptionId)> GetCompanyAssignedAppDataForCompanyUserAsync(Guid subscriptionId, Guid userCompanyId);
Task<(OfferSubscriptionStatusId OfferSubscriptionStatusId, bool IsSubscribingCompany, bool IsValidSubscriptionId, IEnumerable<Guid> ConnectorIds, IEnumerable<Guid> ServiceAccounts)> GetCompanyAssignedOfferSubscriptionDataForCompanyUserAsync(Guid subscriptionId, Guid userCompanyId);

Task<(Guid companyId, OfferSubscription? offerSubscription)> GetCompanyIdWithAssignedOfferForCompanyUserAndSubscriptionAsync(Guid subscriptionId, Guid userId, OfferTypeId offerTypeId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,19 @@ public OfferSubscription CreateOfferSubscription(Guid offerId, Guid companyId, O
.SingleOrDefaultAsync();

/// <inheritdoc />
public Task<(OfferSubscriptionStatusId OfferSubscriptionStatusId, bool IsSubscribingCompany, bool IsValidSubscriptionId)> GetCompanyAssignedAppDataForCompanyUserAsync(Guid subscriptionId, Guid userCompanyId) =>
public Task<(OfferSubscriptionStatusId OfferSubscriptionStatusId, bool IsSubscribingCompany, bool IsValidSubscriptionId, IEnumerable<Guid> ConnectorIds, IEnumerable<Guid> ServiceAccounts)> GetCompanyAssignedOfferSubscriptionDataForCompanyUserAsync(Guid subscriptionId, Guid userCompanyId) =>
_context.OfferSubscriptions
.Where(os =>
os.Id == subscriptionId
)
.Select(os => new ValueTuple<OfferSubscriptionStatusId, bool, bool>(
.Select(os => new ValueTuple<OfferSubscriptionStatusId, bool, bool, IEnumerable<Guid>, IEnumerable<Guid>>(
os.OfferSubscriptionStatusId,
os.CompanyId == userCompanyId,
true
true,
os.ConnectorAssignedOfferSubscriptions.Where(caos => caos.Connector!.StatusId != ConnectorStatusId.INACTIVE).Select(caos =>
caos.Connector!.Id),
os.ConnectorAssignedOfferSubscriptions.Where(caos => caos.Connector!.CompanyServiceAccountId != null && caos.Connector.CompanyServiceAccount!.Identity!.UserStatusId != UserStatusId.INACTIVE).Select(caos =>
caos.Connector!.CompanyServiceAccount!.Identity!.Id)
))
.SingleOrDefaultAsync();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -494,96 +494,16 @@ public async Task ActivateOwnCompanyProvidedAppSubscriptionAsync_WithProviderEma
#region UnsubscribeOwnCompanyAppSubscriptionAsync

[Fact]
public async Task UnsubscribeOwnCompanyAppSubscriptionAsync_WithNotExistingApp_ThrowsNotFoundException()
public async Task UnsubscribeOwnCompanyAppSubscriptionAsync_ExpectedCall()
{
// Arrange
var notExistingSubscriptionId = _fixture.Create<Guid>();
A.CallTo(() => _offerSubscriptionRepository.GetCompanyAssignedAppDataForCompanyUserAsync(A<Guid>._, A<Guid>._))
.Returns(((OfferSubscriptionStatusId, bool, bool))default);

var sut = new AppsBusinessLogic(_portalRepositories, null!, null!, null!, Options.Create(new AppsSettings()), null!);
var sut = new AppsBusinessLogic(_portalRepositories, null!, _offerService, null!, Options.Create(new AppsSettings()), _mailingService);

// Act
async Task Act() => await sut.UnsubscribeOwnCompanyAppSubscriptionAsync(notExistingSubscriptionId, _identity.CompanyId).ConfigureAwait(false);
// Act
await sut.UnsubscribeOwnCompanyAppSubscriptionAsync(_fixture.Create<Guid>(), _identity.CompanyId).ConfigureAwait(false);

// Assert
var ex = await Assert.ThrowsAsync<NotFoundException>(Act);
ex.Message.Should().Be($"Subscription {notExistingSubscriptionId} does not exist.");
A.CallTo(() => _offerSubscriptionRepository.GetCompanyAssignedAppDataForCompanyUserAsync(notExistingSubscriptionId, _identity.CompanyId))
.MustHaveHappenedOnceExactly();
}

[Fact]
public async Task UnsubscribeOwnCompanyAppSubscriptionAsync_IsNoMemberOfCompanyProvidingApp_ThrowsArgumentException()
{
// Arrange
var identity = _fixture.Create<IdentityData>();
var subscriptionId = _fixture.Create<Guid>();
A.CallTo(() => _offerSubscriptionRepository.GetCompanyAssignedAppDataForCompanyUserAsync(A<Guid>._, A<Guid>._))
.Returns((OfferSubscriptionStatusId.ACTIVE, false, true));

var sut = new AppsBusinessLogic(_portalRepositories, null!, null!, null!, Options.Create(new AppsSettings()), null!);

// Act
async Task Act() => await sut.UnsubscribeOwnCompanyAppSubscriptionAsync(subscriptionId, identity.CompanyId).ConfigureAwait(false);

// Assert
var ex = await Assert.ThrowsAsync<ForbiddenException>(Act);
ex.Message.Should().Be("the calling user does not belong to the subscribing company");
A.CallTo(() => _offerSubscriptionRepository.GetCompanyAssignedAppDataForCompanyUserAsync(subscriptionId, identity.CompanyId))
.MustHaveHappenedOnceExactly();
}

[Fact]
public async Task UnsubscribeOwnCompanyAppSubscriptionAsync_WithInactiveApp_ThrowsArgumentException()
{
// Arrange
var offerSubscriptionId = _fixture.Create<Guid>();
A.CallTo(() => _offerSubscriptionRepository.GetCompanyAssignedAppDataForCompanyUserAsync(A<Guid>._, A<Guid>._))
.Returns((
OfferSubscriptionStatusId.INACTIVE,
true,
true));

var sut = new AppsBusinessLogic(_portalRepositories, null!, null!, null!, Options.Create(new AppsSettings()), null!);

// Act
async Task Act() => await sut.UnsubscribeOwnCompanyAppSubscriptionAsync(offerSubscriptionId, _identity.CompanyId).ConfigureAwait(false);

// Assert
var ex = await Assert.ThrowsAsync<ConflictException>(Act);
ex.Message.Should().Be($"There is no active or pending subscription for company '{_identity.CompanyId}' and subscriptionId '{offerSubscriptionId}'");
A.CallTo(() => _offerSubscriptionRepository.GetCompanyAssignedAppDataForCompanyUserAsync(offerSubscriptionId, _identity.CompanyId))
.MustHaveHappenedOnceExactly();
}

[Fact]
public async Task UnsubscribeOwnCompanyAppSubscriptionAsync_CallsExpected()
{
// Arrange
var offerSubscription = _fixture.Build<OfferSubscription>()
.With(x => x.OfferSubscriptionStatusId, OfferSubscriptionStatusId.PENDING)
.Create();
A.CallTo(() => _offerSubscriptionRepository.GetCompanyAssignedAppDataForCompanyUserAsync(A<Guid>._, A<Guid>._))
.Returns((OfferSubscriptionStatusId.ACTIVE, true, true));
A.CallTo(() => _offerSubscriptionRepository.AttachAndModifyOfferSubscription(A<Guid>._, A<Action<OfferSubscription>>._))
.Invokes((Guid _, Action<OfferSubscription> setFields) =>
{
setFields.Invoke(offerSubscription);
});

var sut = new AppsBusinessLogic(_portalRepositories, null!, null!, null!, Options.Create(new AppsSettings()), _mailingService);

// Act
await sut.UnsubscribeOwnCompanyAppSubscriptionAsync(offerSubscription.Id, _identity.CompanyId).ConfigureAwait(false);

// Assert
offerSubscription.OfferSubscriptionStatusId.Should().Be(OfferSubscriptionStatusId.INACTIVE);
A.CallTo(() => _portalRepositories.SaveAsync()).MustHaveHappenedOnceExactly();
A.CallTo(() => _offerSubscriptionRepository.GetCompanyAssignedAppDataForCompanyUserAsync(offerSubscription.Id, _identity.CompanyId))
.MustHaveHappenedOnceExactly();
A.CallTo(() => _offerSubscriptionRepository.AttachAndModifyOfferSubscription(offerSubscription.Id, A<Action<OfferSubscription>>._))
.MustHaveHappenedOnceExactly();
A.CallTo(() => _offerService.UnsubscribeOwnCompanySubscriptionAsync(A<Guid>._, A<Guid>._)).MustHaveHappened();
}

#endregion
Expand Down
Loading

0 comments on commit 0bf8b39

Please sign in to comment.