Skip to content

Commit

Permalink
fix(app): add single app subscription activation (#182)
Browse files Browse the repository at this point in the history
* add single app subscription activation
* change the endpoint subscription/{offerSubscriptionId}/activate-single-instance from post to put

-----------

Refs: CPLP-3046
Reviewed-by: Norbert Truchsess <[email protected]>
  • Loading branch information
Phil91 authored Aug 7, 2023
1 parent a45ed48 commit d630a61
Show file tree
Hide file tree
Showing 12 changed files with 135 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,10 @@ public Task<OfferAutoSetupResponseData> AutoSetupAppAsync(OfferAutoSetupData dat
public Task StartAutoSetupAsync(OfferAutoSetupData data, Guid companyId) =>
_offerSetupService.StartAutoSetupAsync(data, companyId, OfferTypeId.APP);

/// <inheritdoc />
public Task ActivateSingleInstance(Guid offerSubscriptionId, Guid companyId) =>
_offerSetupService.CreateSingleInstanceSubscriptionDetail(offerSubscriptionId, companyId);

/// <inheritdoc />
public IAsyncEnumerable<AgreementData> GetAppAgreement(Guid appId) =>
_offerService.GetOfferAgreementsAsync(appId, OfferTypeId.APP);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,15 @@ public interface IAppsBusinessLogic
/// </summary>
/// <param name="data">The offer subscription id and url for the service</param>
/// <param name="companyId">Id of the company</param>
/// <returns>Returns the response data</returns>
Task StartAutoSetupAsync(OfferAutoSetupData data, Guid companyId);

/// <summary>
/// Triggers the activation of a single instance app subscription.
/// </summary>
/// <param name="offerSubscriptionId">The offer subscription id</param>
/// <param name="companyId">Id of the company</param>
Task ActivateSingleInstance(Guid offerSubscriptionId, Guid companyId);

/// <summary>
/// Gets the app agreement data
/// </summary>
Expand Down
20 changes: 20 additions & 0 deletions src/marketplace/Apps.Service/Controllers/AppsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,26 @@ public async Task<NoContentResult> StartAutoSetupAppProcess([FromBody] OfferAuto
return NoContent();
}

/// <summary>
/// Triggers the activation of a single instance app subscription
/// </summary>
/// <remarks>Example: PUT: /api/apps/subscription/{offerSubscriptionId}/activate-single-instance</remarks>
/// <response code="204">The activation of the subscription has successfully been started.</response>
/// <response code="400">Offer Subscription is pending or not the providing company.</response>
/// <response code="404">Offer Subscription not found.</response>
[HttpPut]
[Route("subscription/{offerSubscriptionId}/activate-single-instance")]
[Authorize(Roles = "activate_subscription")]
[Authorize(Policy = PolicyTypes.ValidCompany)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)]
public async Task<NoContentResult> ActivateSingleInstance([FromRoute] Guid offerSubscriptionId)
{
await this.WithCompanyId(companyId => _appsBusinessLogic.ActivateSingleInstance(offerSubscriptionId, companyId)).ConfigureAwait(false);
return NoContent();
}

/// <summary>
/// Retrieve Document Content for document type "App Lead Image" and "App Image" by ID
/// </summary>
Expand Down
9 changes: 6 additions & 3 deletions src/marketplace/Offers.Library/Service/IOfferSetupService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,14 @@ public interface IOfferSetupService
/// <param name="data">The offer subscription id and url for the service</param>
/// <param name="companyId">Id of the company</param>
/// <param name="offerTypeId">OfferTypeId of offer to be created</param>
/// <returns>Returns the response data</returns>
Task StartAutoSetupAsync(OfferAutoSetupData data, Guid companyId, OfferTypeId offerTypeId);

/// <inheritdoc />
Task<(IEnumerable<ProcessStepTypeId>? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateSingleInstanceSubscriptionDetail(Guid offerSubscriptionId);
/// <summary>
/// Creates the single instance subscription detail and creates the activation step.
/// </summary>
/// <param name="offerSubscriptionId">The offer subscription id and url for the service</param>
/// <param name="companyId">Id of the company</param>
Task CreateSingleInstanceSubscriptionDetail(Guid offerSubscriptionId, Guid companyId);

Task<(IEnumerable<ProcessStepTypeId>? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateClient(Guid offerSubscriptionId);

Expand Down
24 changes: 15 additions & 9 deletions src/marketplace/Offers.Library/Service/OfferSetupService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ public async Task StartAutoSetupAsync(OfferAutoSetupData data, Guid companyId, O
}

/// <inheritdoc />
public async Task<(IEnumerable<ProcessStepTypeId>? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateSingleInstanceSubscriptionDetail(Guid offerSubscriptionId)
public async Task CreateSingleInstanceSubscriptionDetail(Guid offerSubscriptionId, Guid companyId)
{
var offerSubscriptionRepository = _portalRepositories.GetInstance<IOfferSubscriptionsRepository>();
var offerDetails = await offerSubscriptionRepository.GetSubscriptionActivationDataByIdAsync(offerSubscriptionId).ConfigureAwait(false);
Expand All @@ -424,21 +424,27 @@ public async Task StartAutoSetupAsync(OfferAutoSetupData data, Guid companyId, O
case true when offerDetails.AppInstanceIds.Count() != 1:
throw new ConflictException("There must only be one app instance for single instance apps");
default:
if (offerDetails.ProviderCompanyId != companyId)
{
throw new ConflictException("Subscription can only be activated by the provider of the offer");
}

_portalRepositories.GetInstance<IAppSubscriptionDetailRepository>()
.CreateAppSubscriptionDetail(offerSubscriptionId, appSubscriptionDetail =>
{
appSubscriptionDetail.AppInstanceId = offerDetails.AppInstanceIds.Single();
appSubscriptionDetail.AppSubscriptionUrl = offerDetails.InstanceData.InstanceUrl;
});

return new ValueTuple<IEnumerable<ProcessStepTypeId>?, ProcessStepStatusId, bool, string?>(
new[]
{
ProcessStepTypeId.ACTIVATE_SUBSCRIPTION
},
ProcessStepStatusId.DONE,
true,
null);
var context = await _offerSubscriptionProcessService.VerifySubscriptionAndProcessSteps(offerSubscriptionId,
ProcessStepTypeId.SINGLE_INSTANCE_SUBSCRIPTION_DETAILS_CREATION, null, true).ConfigureAwait(false);

_offerSubscriptionProcessService.FinalizeProcessSteps(context, new[]
{
ProcessStepTypeId.ACTIVATE_SUBSCRIPTION
});
await _portalRepositories.SaveAsync().ConfigureAwait(false);
break;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,6 @@ public record SubscriptionActivationData(
(bool IsSingleInstance, string? InstanceUrl) InstanceData,
IEnumerable<Guid> AppInstanceIds,
bool HasOfferSubscriptionProcessData,
Guid? SalesManagerId
Guid? SalesManagerId,
Guid? ProviderCompanyId
);
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,8 @@ public Task<Guid> GetOfferSubscriptionDataForProcessIdAsync(Guid processId) =>
: new ValueTuple<bool, string?>(x.Offer.AppInstanceSetup.IsSingleInstance, x.Offer.AppInstanceSetup.InstanceUrl),
x.Offer.AppInstances.Select(ai => ai.Id),
x.OfferSubscriptionProcessData != null,
x.Offer.SalesManagerId
x.Offer.SalesManagerId,
x.Offer.ProviderCompanyId
))
.SingleOrDefaultAsync();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ public class OfferSubscriptionProcessTypeExecutor : IProcessTypeExecutor

private readonly IEnumerable<ProcessStepTypeId> _executableProcessSteps = ImmutableArray.Create(
ProcessStepTypeId.TRIGGER_PROVIDER,
ProcessStepTypeId.SINGLE_INSTANCE_SUBSCRIPTION_DETAILS_CREATION,
ProcessStepTypeId.OFFERSUBSCRIPTION_CLIENT_CREATION,
ProcessStepTypeId.OFFERSUBSCRIPTION_TECHNICALUSER_CREATION,
ProcessStepTypeId.ACTIVATE_SUBSCRIPTION,
Expand Down Expand Up @@ -106,9 +105,6 @@ public OfferSubscriptionProcessTypeExecutor(
ProcessStepTypeId.TRIGGER_PROVIDER => await _offerProviderBusinessLogic
.TriggerProvider(_offerSubscriptionId, cancellationToken)
.ConfigureAwait(false),
ProcessStepTypeId.SINGLE_INSTANCE_SUBSCRIPTION_DETAILS_CREATION => await _offerSetupService
.CreateSingleInstanceSubscriptionDetail(_offerSubscriptionId)
.ConfigureAwait(false),
ProcessStepTypeId.OFFERSUBSCRIPTION_CLIENT_CREATION => await _offerSetupService
.CreateClient(_offerSubscriptionId)
.ConfigureAwait(false),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,26 @@ public async Task StartAutoSetupService_ReturnsExcepted()

#endregion

#region ActivateSingleInstance

[Fact]
public async Task ActivateSingleInstance_ReturnsExcepted()
{
// Arrange
var offerSetupService = A.Fake<IOfferSetupService>();
var offerSubscriptionId = Guid.NewGuid();

var sut = new AppsBusinessLogic(null!, null!, null!, offerSetupService, _fixture.Create<IOptions<AppsSettings>>(), A.Fake<MailingService>());

// Act
await sut.ActivateSingleInstance(offerSubscriptionId, _identity.CompanyId).ConfigureAwait(false);

// Assert
A.CallTo(() => offerSetupService.CreateSingleInstanceSubscriptionDetail(offerSubscriptionId, _identity.CompanyId)).MustHaveHappenedOnceExactly();
}

#endregion

#region GetCompanyProvidedAppSubscriptionStatusesForUser

[Theory]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,20 @@ public async Task StartAutoSetupProcess_ReturnsExpected()
result.Should().BeOfType<NoContentResult>();
}

[Fact]
public async Task ActivateSingleInstance_ReturnsExpected()
{
//Arrange
var offerSubscriptionId = Guid.NewGuid();

//Act
var result = await this._controller.ActivateSingleInstance(offerSubscriptionId).ConfigureAwait(false);

//Assert
A.CallTo(() => _logic.ActivateSingleInstance(offerSubscriptionId, _identity.CompanyId)).MustHaveHappenedOnceExactly();
result.Should().BeOfType<NoContentResult>();
}

[Fact]
public async Task GetAppImageDocumentContentAsync_ReturnsExpected()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -799,7 +799,7 @@ public async Task CreateSingleInstanceSubscriptionDetail_WithNotExistingOfferSub
.Returns((SubscriptionActivationData?)null);

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

// Assert
var ex = await Assert.ThrowsAsync<NotFoundException>(Act);
Expand All @@ -820,7 +820,7 @@ public async Task CreateSingleInstanceSubscriptionDetail_WithMultipleInstancesFo
.Returns(transferData);

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

// Assert
var ex = await Assert.ThrowsAsync<ConflictException>(Act);
Expand All @@ -841,21 +841,53 @@ public async Task CreateSingleInstanceSubscriptionDetail_WithMultipleInstance_Th
.Returns(transferData);

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

// Assert
var ex = await Assert.ThrowsAsync<ConflictException>(Act);
ex.Message.Should().Be("The process step is only executable for single instance apps");
}

[Fact]
public async Task CreateSingleInstanceSubscriptionDetail_WithWrongCompanyId_ThrowsConflictException()
{
// Arrange
var transferData = _fixture.Build<SubscriptionActivationData>()
.With(x => x.Status, OfferSubscriptionStatusId.PENDING)
.With(x => x.InstanceData, new ValueTuple<bool, string?>(true, "https://www.test.de"))
.With(x => x.AppInstanceIds, new[] { Guid.NewGuid() })
.With(x => x.ProviderCompanyId, Guid.NewGuid())
.Create();
var offerSubscriptionId = Guid.NewGuid();
A.CallTo(() => _offerSubscriptionsRepository.GetSubscriptionActivationDataByIdAsync(offerSubscriptionId))
.Returns(transferData);

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

// Assert
var ex = await Assert.ThrowsAsync<ConflictException>(Act);
ex.Message.Should().Be("Subscription can only be activated by the provider of the offer");
}

[Fact]
public async Task CreateSingleInstanceSubscriptionDetail_WithValidData_ReturnsExpected()
{
// Arrange
var process = _fixture.Create<Process>();
var manualProcessStepData = new ManualProcessStepData(
ProcessStepTypeId.SINGLE_INSTANCE_SUBSCRIPTION_DETAILS_CREATION,
process,
Enumerable.Repeat(new ProcessStep(Guid.NewGuid(),
ProcessStepTypeId.SINGLE_INSTANCE_SUBSCRIPTION_DETAILS_CREATION, ProcessStepStatusId.TODO, process.Id,
DateTimeOffset.UtcNow), 1),
_portalRepositories);
var offerSubscriptionId = Guid.NewGuid();
var transferData = _fixture.Build<SubscriptionActivationData>()
.With(x => x.Status, OfferSubscriptionStatusId.PENDING)
.With(x => x.InstanceData, new ValueTuple<bool, string?>(true, "https://www.test.de"))
.With(x => x.AppInstanceIds, new[] { Guid.NewGuid() })
.With(x => x.ProviderCompanyId, _identity.CompanyId)
.Create();
var detail = new AppSubscriptionDetail(Guid.NewGuid(), offerSubscriptionId);
A.CallTo(() => _offerSubscriptionsRepository.GetSubscriptionActivationDataByIdAsync(offerSubscriptionId))
Expand All @@ -865,17 +897,18 @@ public async Task CreateSingleInstanceSubscriptionDetail_WithValidData_ReturnsEx
{
setOptionalParameter.Invoke(detail);
});
A.CallTo(() => _offerSubscriptionProcessService.VerifySubscriptionAndProcessSteps(offerSubscriptionId,
ProcessStepTypeId.SINGLE_INSTANCE_SUBSCRIPTION_DETAILS_CREATION, null, true))
.Returns(manualProcessStepData);

// Act
var result = await _sut.CreateSingleInstanceSubscriptionDetail(offerSubscriptionId).ConfigureAwait(false);
await _sut.CreateSingleInstanceSubscriptionDetail(offerSubscriptionId, _identity.CompanyId).ConfigureAwait(false);

// Assert
detail.AppSubscriptionUrl.Should().Be("https://www.test.de");
result.nextStepTypeIds.Should().ContainSingle().And
.AllSatisfy(x => x.Should().Be(ProcessStepTypeId.ACTIVATE_SUBSCRIPTION));
result.stepStatusId.Should().Be(ProcessStepStatusId.DONE);
result.modified.Should().BeTrue();
result.processMessage.Should().BeNull();
A.CallTo(() => _offerSubscriptionProcessService.FinalizeProcessSteps(A<ManualProcessStepData>._, A<IEnumerable<ProcessStepTypeId>>.That.Matches(x => x.Count() == 1 && x.Single() == ProcessStepTypeId.ACTIVATE_SUBSCRIPTION)))
.MustHaveHappenedOnceExactly();
A.CallTo(() => _portalRepositories.SaveAsync()).MustHaveHappenedOnceExactly();
}

#endregion
Expand Down Expand Up @@ -1088,7 +1121,7 @@ public async Task ActivateSubscription_WithValidData_ReturnsExpected(string? req
A.CallTo(() => _notificationService.CreateNotificationsWithExistenceCheck(A<IEnumerable<UserRoleConfig>>._, null, A<IEnumerable<(string?, NotificationTypeId)>>._, A<Guid>._, A<string>._, A<string>._, A<bool?>._))
.Returns(new[] { Guid.NewGuid() }.AsFakeIAsyncEnumerable(out var createNotificationsEnumerator));
A.CallTo(() => _offerSubscriptionsRepository.GetSubscriptionActivationDataByIdAsync(offerSubscription.Id))
.Returns(new SubscriptionActivationData(_validOfferId, OfferSubscriptionStatusId.PENDING, offerTypeId, "Test App", "Stark Industries", _identity.CompanyId, requesterEmail, "Tony", "Stark", Guid.NewGuid(), new(isSingleInstance, null), new[] { Guid.NewGuid() }, true, Guid.NewGuid()));
.Returns(new SubscriptionActivationData(_validOfferId, OfferSubscriptionStatusId.PENDING, offerTypeId, "Test App", "Stark Industries", _identity.CompanyId, requesterEmail, "Tony", "Stark", Guid.NewGuid(), new(isSingleInstance, null), new[] { Guid.NewGuid() }, true, Guid.NewGuid(), _identity.CompanyId));
A.CallTo(() => _notificationRepository.CheckNotificationExistsForParam(A<Guid>._, A<NotificationTypeId>._, A<string>._, A<string>._))
.Returns(false);
A.CallTo(() => _offerSubscriptionsRepository.AttachAndModifyOfferSubscription(offerSubscription.Id, A<Action<OfferSubscription>>._))
Expand Down
Loading

0 comments on commit d630a61

Please sign in to comment.