Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(OfferService)! : unsubscribe OfferSubscription for apps and service #180

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -474,96 +474,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
Loading