mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 07:36:14 -05:00
Feature/self hosted families for enterprise (#1991)
* Families for enterprise/split up organization sponsorship service (#1829) * Split OrganizationSponsorshipService into commands * Use tokenable for token validation * Use interfaces to set up for DI * Use commands over services * Move service tests to command tests * Value types can't be null * Run dotnet format * Update src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CancelSponsorshipCommand.cs Co-authored-by: Justin Baur <admin@justinbaur.com> * Fix controller tests Co-authored-by: Justin Baur <admin@justinbaur.com> * Families for enterprise/split up organization sponsorship service (#1875) * Split OrganizationSponsorshipService into commands * Use tokenable for token validation * Use interfaces to set up for DI * Use commands over services * Move service tests to command tests * Value types can't be null * Run dotnet format * Update src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CancelSponsorshipCommand.cs Co-authored-by: Justin Baur <admin@justinbaur.com> * Fix controller tests * Split create and send sponsorships * Split up create sponsorship * Add self hosted commands to dependency injection * Add field to store cloud billing sync key on self host instances * Fix typo * Fix data protector purpose of sponsorship offers * Split cloud and selfhosted sponsorship offer tokenable * Generate offer from self hosted with all necessary auth data * Add Required properties to constructor * Split up cancel sponsorship command * Split revoke sponsorship command between cloud and self hosted * Fix/f4e multiple sponsorships (#1838) * Use sponosorship from validate to redeem * Update tests * Format * Remove sponsorship service * Run dotnet format * Fix self hosted only controller attribute * Clean up file structure and fixes * Remove unneeded tokenables * Remove obsolete commands * Do not require file/class prefix if unnecessary * Update Organizaiton sprocs * Remove unnecessary models * Fix tests * Generalize LicenseService path calculation Use async file read and deserialization * Use interfaces for testability * Remove unused usings * Correct test direction * Test license reading * remove unused usings * Format Co-authored-by: Justin Baur <admin@justinbaur.com> * Improve DataProtectorTokenFactory test coverage (#1884) * Add encstring to server * Test factory Co-authored-by: Carlos Muentes <cmuentes@bitwarden.com> * Format * Remove SymmetricKeyProtectedString Not needed * Set ForcInvalid Co-authored-by: Carlos Muentes <cmuentes@bitwarden.com> * Feature/self f4e/api keys (#1896) * Add in ApiKey * Work on API Key table * Work on apikey table * Fix response model * Work on information for UI * Work on last sync date * Work on sync status * Work on auth * Work on tokenable * Work on merge * Add custom requirement * Add policy * Run formatting * Work on EF Migrations * Work on OrganizationConnection * Work on database * Work on additional database table * Run formatting * Small fixes * More cleanup * Cleanup * Add RevisionDate * Add GO * Finish Sql project * Add newlines * Fix stored proc file * Fix sqlproj * Add newlines * Fix table * Add navigation property * Delete Connections when organization is deleted * Add connection validation * Start adding ID column * Work on ID column * Work on SQL migration * Work on migrations * Run formatting * Fix test build * Fix sprocs * Work on migrations * Fix Create table * Fix sproc * Add prints to migration * Add default value * Update EF migrations * Formatting * Add to integration tests * Minor fixes * Formatting * Cleanup * Address PR feedback * Address more PR feedback * Fix formatting * Fix formatting * Fix * Address PR feedback * Remove accidential change * Fix SQL build * Run formatting * Address PR feedback * Add sync data to OrganizationUserOrgDetails * Add comments * Remove OrganizationConnectionService interface * Remove unused using * Address PR feedback * Formatting * Minor fix * Feature/self f4e/update db (#1930) * Fix migration * Fix TimesRenewed * Add comments * Make two properties non-nullable * Remove need for SponsoredOrg on SH (#1934) * Remove need for SponsoredOrg on SH * Add Family prefix * Add check for enterprise org on BillingSync key (#1936) * [PS-10] Feature/sponsorships removed at end of term (#1938) * Rename commands to min unique names * Inject revoke command based on self hosting * WIP: Remove/Revoke marks to delete * Complete WIP * Improve remove/revoke tests * PR review * Fail validation if sponsorship has failed to sync for 6 months * Feature/do not accept old self host sponsorships (#1939) * Do not accept >6mo old self-hosted sponsorships * Give disabled grace period of 3 months * Fix issues of Sql.proj differing from migration outcome (#1942) * Fix issues of Sql.proj differing from migration outcome * Yoink int tests * Add missing assert helpers * Feature/org sponsorship sync (#1922) * Self-hosted side sync first pass TODO: * flush out org sponsorship model * implement cloud side * process cloud-side response and update self-hosted records * sync scaffolding second pass * remove list of Org User ids from sync and begin work on SelfHostedRevokeSponsorship * allow authenticated http calls from server to return a result * update models * add logic for sync and change offer email template * add billing sync key and hide CreateSponsorship without user * fix tests * add job scheduling * add authorize attributes to endpoints * separate models into data/model and request/response * batch sync more, add EnableCloudCommunication for testing * send emails in bulk * make userId and sponsorshipType non nullable * batch more on self hosted side of sync * remove TODOs and formatting * changed logic of cloud sync * let BaseIdentityClientService handle all logging * call sync from scheduled job on self host * create bulk db operations for OrganizationSponsorships * remove SponsoredOrgId from sync, return default from server http call * validate BillingSyncKey during sync revert changes to CreateSponsorshipCommand * revert changes to ICreateSponsorshipCommand * add some tests * add DeleteExpiredSponsorshipsJob * add cloud sync test * remove extra method * formatting * prevent new sponsorships from disabled orgs * update packages * - pulled out send sponsorship command dependency from sync on cloud - don't throw error when sponsorships are empty - formatting * formatting models * more formatting * remove licensingService dependency from selfhosted sync * use installation urls and formatting * create constructor for RequestModel and formatting * add date parameter to OrganizationSponsorship_DeleteExpired * add new migration * formatting * rename OrganizationCreateSponsorshipRequestModel to OrganizationSponsorshipCreateRequestModel * prevent whole sync from failing if one sponsorship type is unsupported * deserialize config and billingsynckey from org connection * alter log message when sync disabled * Add grace period to disabled orgs * return early on self hosted if there are no sponsorships in database * rename BillingSyncConfig * send sponsorship offers from controller * allow config to be a null object * better exception handling in sync scheduler * add ef migrations * formatting * fix tests * fix validate test Co-authored-by: Matt Gibson <mgibson@bitwarden.com> * Fix OrganizationApiKey issues (#1941) Co-authored-by: Justin Baur <admin@justinbaur.com> * Feature/org sponsorship self hosted tests (#1947) * Self-hosted side sync first pass TODO: * flush out org sponsorship model * implement cloud side * process cloud-side response and update self-hosted records * sync scaffolding second pass * remove list of Org User ids from sync and begin work on SelfHostedRevokeSponsorship * allow authenticated http calls from server to return a result * update models * add logic for sync and change offer email template * add billing sync key and hide CreateSponsorship without user * fix tests * add job scheduling * add authorize attributes to endpoints * separate models into data/model and request/response * batch sync more, add EnableCloudCommunication for testing * send emails in bulk * make userId and sponsorshipType non nullable * batch more on self hosted side of sync * remove TODOs and formatting * changed logic of cloud sync * let BaseIdentityClientService handle all logging * call sync from scheduled job on self host * create bulk db operations for OrganizationSponsorships * remove SponsoredOrgId from sync, return default from server http call * validate BillingSyncKey during sync revert changes to CreateSponsorshipCommand * revert changes to ICreateSponsorshipCommand * add some tests * add DeleteExpiredSponsorshipsJob * add cloud sync test * remove extra method * formatting * prevent new sponsorships from disabled orgs * update packages * - pulled out send sponsorship command dependency from sync on cloud - don't throw error when sponsorships are empty - formatting * formatting models * more formatting * remove licensingService dependency from selfhosted sync * use installation urls and formatting * create constructor for RequestModel and formatting * add date parameter to OrganizationSponsorship_DeleteExpired * add new migration * formatting * rename OrganizationCreateSponsorshipRequestModel to OrganizationSponsorshipCreateRequestModel * prevent whole sync from failing if one sponsorship type is unsupported * deserialize config and billingsynckey from org connection * add mockHttp nuget package and use httpclientfactory * fix current tests * WIP of creating tests * WIP of new self hosted tests * WIP self hosted tests * finish self hosted tests * formatting * format of interface * remove extra config file * added newlines Co-authored-by: Matt Gibson <mgibson@bitwarden.com> * Fix Organization_DeleteById (#1950) * Fix Organization_Delete * Fix L * [PS-4] block enterprise user from sponsoring itself (#1943) * [PS-248] Feature/add connections enabled endpoint (#1953) * Move Organization models to sub namespaces * Add Organization Connection api endpoints * Get all connections rather than just enabled ones * Add missing services to DI * pluralize private api endpoints * Add type protection to org connection request/response * Fix route * Use nullable Id to signify no connection * Test Get Connections enabled * Fix data discoverer * Also drop this sproc for rerunning * Id is the OUTPUT of create sprocs * Fix connection config parsing * Linter fixes * update sqlproj file name * Use param xdocs on methods * Simplify controller path attribute * Use JsonDocument to avoid escaped json in our response/request strings * Fix JsonDoc tests * Linter fixes * Fix ApiKey Command and add tests (#1949) * Fix ApiKey command * Formatting * Fix test failures introduced in #1943 (#1957) * Remove "Did you know?" copy from emails. (#1962) * Remove "Did you know" * Remove jsonIf helper * Feature/fix send single sponsorship offer email (#1956) * Fix sponsorship offer email * Do not sanitize org name * PR feedback * Feature/f4e sync event [PS-75] (#1963) * Create sponsorship sync event type * Add InstallationId to Event model * Add combinatorics-based test case generators * Log sponsorships sync event on sync * Linter and test fixes * Fix failing test * Migrate sprocs and view * Remove unused `using`s * [PS-190] Add manual sync trigger in self hosted (#1955) * WIP add button to admin project for billing sync * add connection table to view page * minor fixes for self hosted side of sync * fixes number of bugs for cloud side of sync * deserialize before returning for some reason * add json attributes to return models * list of sponsorships parameter is immutable, add secondary list * change sproc name * add error handling * Fix tests * modify call to connection * Update src/Admin/Controllers/OrganizationsController.cs Co-authored-by: Matt Gibson <mgibson@bitwarden.com> * undo change to sproc name * simplify logic * Update src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/CloudSyncSponsorshipsCommand.cs Co-authored-by: Matt Gibson <mgibson@bitwarden.com> * register services despite if self hosted or cloud * remove json properties * revert merge conflict Co-authored-by: Matt Gibson <mgibson@bitwarden.com> * Update OrganizationSponsorship valid until when updating org expirati… (#1966) * Update OrganizationSponsorship valid until when updating org expiration date * Linter fixes * [PS-7] change revert email copy and add ValidUntil to sponsorship (#1965) * change revert email copy and add ValidUntil to sponsorship * add 15 days if no ValidUntil * Chore/merge/self hosted families for enterprise (#1972) * Log swallowed HttpRequestExceptions (#1866) Co-authored-by: Hinton <oscar@oscarhinton.com> * Allow for utilization of readonly db connection (#1937) * Bump the pin of the download-artifacts action to bypass the broken GitHub api (#1952) * Bumped version to 1.48.0 (#1958) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * [EC-160] Give Provider Users access to all org ciphers and collections (#1959) * Bumped version to 1.48.1 (#1961) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Avoid sending "user need confirmation" emails when there are no org admins (#1960) * Remove noncompliant users for new policies (#1951) * [PS-284] Allow installation clients to not need a user. (#1968) * Allow installation clients to not need a user. * Run formatting Co-authored-by: Andrei <30410186+Manolachi@users.noreply.github.com> Co-authored-by: Hinton <oscar@oscarhinton.com> Co-authored-by: sneakernuts <671942+sneakernuts@users.noreply.github.com> Co-authored-by: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Co-authored-by: Justin Baur <136baur@gmail.com> * Fix/license file not found (#1974) * Handle null license * Throw hint message if license is not found by the admin project. * Use CloudOrganizationId from Connection config * Change test to support change * Fix test Co-authored-by: Matt Gibson <mgibson@bitwarden.com> * Feature/f4e selfhosted rename migration to .sql (#1971) * rename migration to .sql * format * Add unit tests to self host F4E (#1975) * Work on tests * Added more tests * Run linting * Address PR feedback * Fix AssertRecent * Linting * Fixed empty tests * Fix/misc self hosted f4e (#1973) * Allow setting of ApiUri * Return updates sponsorshipsData objects * Bind arguments by name * Greedy load sponsorships to email. When upsert was called, it creates Ids on _all_ records, which meant that the lazy-evaluation from this call always returned an empty list. * add scope for sync command DI in job. simplify error logic * update the sync job to get CloudOrgId from the BillingSyncKey Co-authored-by: Jacob Fink <jfink@bitwarden.com> * Chore/merge/self hosted families for enterprise (#1987) * Log swallowed HttpRequestExceptions (#1866) Co-authored-by: Hinton <oscar@oscarhinton.com> * Allow for utilization of readonly db connection (#1937) * Bump the pin of the download-artifacts action to bypass the broken GitHub api (#1952) * Bumped version to 1.48.0 (#1958) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * [EC-160] Give Provider Users access to all org ciphers and collections (#1959) * Bumped version to 1.48.1 (#1961) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Avoid sending "user need confirmation" emails when there are no org admins (#1960) * Remove noncompliant users for new policies (#1951) * [PS-284] Allow installation clients to not need a user. (#1968) * Allow installation clients to not need a user. * Run formatting * Use accept flow for sponsorship offers (#1964) * PS-82 check send 2FA email for new devices on TwoFactorController send-email-login (#1977) * [Bug] Skip WebAuthn 2fa event logs during login flow (#1978) * [Bug] Supress WebAuthn 2fa event logs during login process * Formatting * Simplified method call with new paramter input * Update RealIps Description (#1980) Describe the syntax of the real_ips configuration key with an example, to prevent type errors in the `setup` container when parsing `config.yml` * add proper URI validation to duo host (#1984) * captcha scores (#1967) * captcha scores * some api fixes * check bot on captcha attribute * Update src/Core/Services/Implementations/HCaptchaValidationService.cs Co-authored-by: e271828- <e271828-@users.noreply.github.com> Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com> Co-authored-by: e271828- <e271828-@users.noreply.github.com> * ensure no path specific in duo host (#1985) Co-authored-by: Andrei <30410186+Manolachi@users.noreply.github.com> Co-authored-by: Hinton <oscar@oscarhinton.com> Co-authored-by: sneakernuts <671942+sneakernuts@users.noreply.github.com> Co-authored-by: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Co-authored-by: Justin Baur <136baur@gmail.com> Co-authored-by: Federico Maccaroni <fedemkr@gmail.com> Co-authored-by: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com> Co-authored-by: Jordan Cooks <notnamed@users.noreply.github.com> Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com> Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com> Co-authored-by: e271828- <e271828-@users.noreply.github.com> * Address feedback (#1990) Co-authored-by: Justin Baur <admin@justinbaur.com> Co-authored-by: Carlos Muentes <cmuentes@bitwarden.com> Co-authored-by: Jake Fink <jfink@bitwarden.com> Co-authored-by: Justin Baur <136baur@gmail.com> Co-authored-by: Andrei <30410186+Manolachi@users.noreply.github.com> Co-authored-by: Hinton <oscar@oscarhinton.com> Co-authored-by: sneakernuts <671942+sneakernuts@users.noreply.github.com> Co-authored-by: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Co-authored-by: Federico Maccaroni <fedemkr@gmail.com> Co-authored-by: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com> Co-authored-by: Jordan Cooks <notnamed@users.noreply.github.com> Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com> Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com> Co-authored-by: e271828- <e271828-@users.noreply.github.com>
This commit is contained in:
@ -4,9 +4,17 @@ using Bit.Test.Common.AutoFixture.Attributes;
|
||||
|
||||
namespace Bit.Api.Test.AutoFixture.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// Disables setting of Auto Properties on the Controller to avoid ASP.net initialization errors from a mock environment. Still sets constructor dependencies.
|
||||
/// </summary>
|
||||
public class ControllerCustomizeAttribute : BitCustomizeAttribute
|
||||
{
|
||||
private readonly Type _controllerType;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize an instance of the ControllerCustomizeAttribute class
|
||||
/// </summary>
|
||||
/// <param name="controllerType">The Type of the controller to allow autofixture to create</param>
|
||||
public ControllerCustomizeAttribute(Type controllerType)
|
||||
{
|
||||
_controllerType = controllerType;
|
||||
|
@ -0,0 +1,230 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Api.Controllers;
|
||||
using Bit.Api.Models.Request.Organizations;
|
||||
using Bit.Api.Models.Response.Organizations;
|
||||
using Bit.Api.Test.AutoFixture.Attributes;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.OrganizationConnectionConfigs;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.Controllers
|
||||
{
|
||||
[ControllerCustomize(typeof(OrganizationConnectionsController))]
|
||||
[SutProviderCustomize]
|
||||
[JsonDocumentCustomize]
|
||||
public class OrganizationConnectionsControllerTests
|
||||
{
|
||||
public static IEnumerable<object[]> ConnectionTypes =>
|
||||
Enum.GetValues<OrganizationConnectionType>().Select(p => new object[] { p });
|
||||
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(true, true)]
|
||||
[BitAutoData(false, true)]
|
||||
[BitAutoData(true, false)]
|
||||
[BitAutoData(false, false)]
|
||||
public void ConnectionEnabled_RequiresBothSelfHostAndCommunications(bool selfHosted, bool enableCloudCommunication, SutProvider<OrganizationConnectionsController> sutProvider)
|
||||
{
|
||||
var globalSettingsMock = sutProvider.GetDependency<IGlobalSettings>();
|
||||
globalSettingsMock.SelfHosted.Returns(selfHosted);
|
||||
globalSettingsMock.EnableCloudCommunication.Returns(enableCloudCommunication);
|
||||
|
||||
Action<bool> assert = selfHosted && enableCloudCommunication ? Assert.True : Assert.False;
|
||||
|
||||
var result = sutProvider.Sut.ConnectionsEnabled();
|
||||
|
||||
assert(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CreateConnection_RequiresOwnerPermissions(SutProvider<OrganizationConnectionsController> sutProvider)
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.CreateConnection(null));
|
||||
|
||||
Assert.Contains("Only the owner of an organization can create a connection.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitMemberAutoData(nameof(ConnectionTypes))]
|
||||
public async Task CreateConnection_OnlyOneConnectionOfEachType(OrganizationConnectionType type,
|
||||
OrganizationConnectionRequestModel model, BillingSyncConfig config, Guid existingEntityId,
|
||||
SutProvider<OrganizationConnectionsController> sutProvider)
|
||||
{
|
||||
model.Type = type;
|
||||
model.Config = JsonDocumentFromObject(config);
|
||||
var typedModel = new OrganizationConnectionRequestModel<BillingSyncConfig>(model);
|
||||
var existing = typedModel.ToData(existingEntityId).ToEntity();
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.OrganizationId).Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationConnectionRepository>().GetByOrganizationIdTypeAsync(model.OrganizationId, type).Returns(new[] { existing });
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.CreateConnection(model));
|
||||
|
||||
Assert.Contains($"The requested organization already has a connection of type {model.Type}. Only one of each connection type may exist per organization.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CreateConnection_Success(OrganizationConnectionRequestModel model, BillingSyncConfig config,
|
||||
Guid cloudOrgId, SutProvider<OrganizationConnectionsController> sutProvider)
|
||||
{
|
||||
model.Config = JsonDocumentFromObject(config);
|
||||
var typedModel = new OrganizationConnectionRequestModel<BillingSyncConfig>(model);
|
||||
typedModel.ParsedConfig.CloudOrganizationId = cloudOrgId;
|
||||
|
||||
sutProvider.GetDependency<ICreateOrganizationConnectionCommand>().CreateAsync<BillingSyncConfig>(default)
|
||||
.ReturnsForAnyArgs(typedModel.ToData(Guid.NewGuid()).ToEntity());
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.OrganizationId).Returns(true);
|
||||
sutProvider.GetDependency<ILicensingService>()
|
||||
.ReadOrganizationLicenseAsync(Arg.Any<Guid>())
|
||||
.Returns(new OrganizationLicense
|
||||
{
|
||||
Id = cloudOrgId,
|
||||
});
|
||||
|
||||
await sutProvider.Sut.CreateConnection(model);
|
||||
|
||||
await sutProvider.GetDependency<ICreateOrganizationConnectionCommand>().Received(1)
|
||||
.CreateAsync(Arg.Is(AssertHelper.AssertPropertyEqual(typedModel.ToData())));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateConnection_RequiresOwnerPermissions(SutProvider<OrganizationConnectionsController> sutProvider)
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateConnection(default, null));
|
||||
|
||||
Assert.Contains("Only the owner of an organization can update a connection.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitMemberAutoData(nameof(ConnectionTypes))]
|
||||
public async Task UpdateConnection_OnlyOneConnectionOfEachType(OrganizationConnectionType type,
|
||||
OrganizationConnection existing1, OrganizationConnection existing2, BillingSyncConfig config,
|
||||
SutProvider<OrganizationConnectionsController> sutProvider)
|
||||
{
|
||||
existing1.Config = JsonSerializer.Serialize(config);
|
||||
var typedModel = RequestModelFromEntity(existing1);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(typedModel.OrganizationId).Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationConnectionRepository>().GetByOrganizationIdTypeAsync(typedModel.OrganizationId, type).Returns(new[] { existing1, existing2 });
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateConnection(existing1.Id, typedModel));
|
||||
|
||||
Assert.Contains($"The requested organization already has a connection of type {typedModel.Type}. Only one of each connection type may exist per organization.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateConnection_Success(OrganizationConnection existing, BillingSyncConfig config,
|
||||
OrganizationConnection updated,
|
||||
SutProvider<OrganizationConnectionsController> sutProvider)
|
||||
{
|
||||
updated.Config = JsonSerializer.Serialize(config);
|
||||
updated.Id = existing.Id;
|
||||
var model = RequestModelFromEntity(updated);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.OrganizationId).Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationConnectionRepository>().GetByOrganizationIdTypeAsync(model.OrganizationId, model.Type).Returns(new[] { existing });
|
||||
sutProvider.GetDependency<IUpdateOrganizationConnectionCommand>().UpdateAsync<BillingSyncConfig>(default).ReturnsForAnyArgs(updated);
|
||||
|
||||
var expected = new OrganizationConnectionResponseModel(updated, typeof(BillingSyncConfig));
|
||||
var result = await sutProvider.Sut.UpdateConnection(existing.Id, model);
|
||||
|
||||
AssertHelper.AssertPropertyEqual(expected, result);
|
||||
await sutProvider.GetDependency<IUpdateOrganizationConnectionCommand>().Received(1)
|
||||
.UpdateAsync(Arg.Is(AssertHelper.AssertPropertyEqual(model.ToData(updated.Id))));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetConnection_RequiresOwnerPermissions(Guid connectionId, SutProvider<OrganizationConnectionsController> sutProvider)
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.GetConnection(connectionId, OrganizationConnectionType.CloudBillingSync));
|
||||
|
||||
Assert.Contains("Only the owner of an organization can retrieve a connection.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetConnection_Success(OrganizationConnection connection, BillingSyncConfig config,
|
||||
SutProvider<OrganizationConnectionsController> sutProvider)
|
||||
{
|
||||
connection.Config = JsonSerializer.Serialize(config);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationConnectionRepository>().GetByOrganizationIdTypeAsync(connection.OrganizationId, connection.Type).Returns(new[] { connection });
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(connection.OrganizationId).Returns(true);
|
||||
|
||||
var expected = new OrganizationConnectionResponseModel(connection, typeof(BillingSyncConfig));
|
||||
var actual = await sutProvider.Sut.GetConnection(connection.OrganizationId, connection.Type);
|
||||
|
||||
AssertHelper.AssertPropertyEqual(expected, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteConnection_NotFound(Guid connectionId,
|
||||
SutProvider<OrganizationConnectionsController> sutProvider)
|
||||
{
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteConnection(connectionId));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteConnection_RequiresOwnerPermissions(OrganizationConnection connection,
|
||||
SutProvider<OrganizationConnectionsController> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationConnectionRepository>().GetByIdAsync(connection.Id).Returns(connection);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.DeleteConnection(connection.Id));
|
||||
|
||||
Assert.Contains("Only the owner of an organization can remove a connection.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteConnection_Success(OrganizationConnection connection,
|
||||
SutProvider<OrganizationConnectionsController> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationConnectionRepository>().GetByIdAsync(connection.Id).Returns(connection);
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(connection.OrganizationId).Returns(true);
|
||||
|
||||
await sutProvider.Sut.DeleteConnection(connection.Id);
|
||||
|
||||
await sutProvider.GetDependency<IDeleteOrganizationConnectionCommand>().DeleteAsync(connection);
|
||||
}
|
||||
|
||||
private static OrganizationConnectionRequestModel<BillingSyncConfig> RequestModelFromEntity(OrganizationConnection entity)
|
||||
{
|
||||
return new(new OrganizationConnectionRequestModel()
|
||||
{
|
||||
Type = entity.Type,
|
||||
OrganizationId = entity.OrganizationId,
|
||||
Enabled = entity.Enabled,
|
||||
Config = JsonDocument.Parse(entity.Config),
|
||||
});
|
||||
}
|
||||
|
||||
private static JsonDocument JsonDocumentFromObject<T>(T obj) => JsonDocument.Parse(JsonSerializer.Serialize(obj));
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
@ -44,14 +45,14 @@ namespace Bit.Api.Test.Controllers
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
|
||||
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id)
|
||||
.Returns(user);
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipService>().ValidateRedemptionTokenAsync(sponsorshipToken,
|
||||
sutProvider.GetDependency<IValidateRedemptionTokenCommand>().ValidateRedemptionTokenAsync(sponsorshipToken,
|
||||
user.Email).Returns((false, null));
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RedeemSponsorship(sponsorshipToken, model));
|
||||
|
||||
Assert.Contains("Failed to parse sponsorship token.", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipService>()
|
||||
await sutProvider.GetDependency<ISetUpSponsorshipCommand>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SetUpSponsorshipAsync(default, default);
|
||||
}
|
||||
@ -65,7 +66,7 @@ namespace Bit.Api.Test.Controllers
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
|
||||
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id)
|
||||
.Returns(user);
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipService>().ValidateRedemptionTokenAsync(sponsorshipToken,
|
||||
sutProvider.GetDependency<IValidateRedemptionTokenCommand>().ValidateRedemptionTokenAsync(sponsorshipToken,
|
||||
user.Email).Returns((true, sponsorship));
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.SponsoredOrganizationId).Returns(false);
|
||||
|
||||
@ -73,7 +74,7 @@ namespace Bit.Api.Test.Controllers
|
||||
sutProvider.Sut.RedeemSponsorship(sponsorshipToken, model));
|
||||
|
||||
Assert.Contains("Can only redeem sponsorship for an organization you own.", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipService>()
|
||||
await sutProvider.GetDependency<ISetUpSponsorshipCommand>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SetUpSponsorshipAsync(default, default);
|
||||
}
|
||||
@ -87,14 +88,14 @@ namespace Bit.Api.Test.Controllers
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
|
||||
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id)
|
||||
.Returns(user);
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipService>().ValidateRedemptionTokenAsync(sponsorshipToken,
|
||||
sutProvider.GetDependency<IValidateRedemptionTokenCommand>().ValidateRedemptionTokenAsync(sponsorshipToken,
|
||||
user.Email).Returns((true, sponsorship));
|
||||
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(model.SponsoredOrganizationId).Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(model.SponsoredOrganizationId).Returns(sponsoringOrganization);
|
||||
|
||||
await sutProvider.Sut.RedeemSponsorship(sponsorshipToken, model);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipService>().Received(1)
|
||||
await sutProvider.GetDependency<ISetUpSponsorshipCommand>().Received(1)
|
||||
.SetUpSponsorshipAsync(sponsorship, sponsoringOrganization);
|
||||
}
|
||||
|
||||
@ -106,12 +107,12 @@ namespace Bit.Api.Test.Controllers
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(user.Id);
|
||||
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(user.Id)
|
||||
.Returns(user);
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipService>()
|
||||
sutProvider.GetDependency<IValidateRedemptionTokenCommand>()
|
||||
.ValidateRedemptionTokenAsync(sponsorshipToken, user.Email).Returns((true, sponsorship));
|
||||
|
||||
await sutProvider.Sut.PreValidateSponsorshipToken(sponsorshipToken);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipService>().Received(1)
|
||||
await sutProvider.GetDependency<IValidateRedemptionTokenCommand>().Received(1)
|
||||
.ValidateRedemptionTokenAsync(sponsorshipToken, user.Email);
|
||||
}
|
||||
|
||||
@ -128,9 +129,9 @@ namespace Bit.Api.Test.Controllers
|
||||
sutProvider.Sut.RevokeSponsorship(sponsoringOrgUser.Id));
|
||||
|
||||
Assert.Contains("Can only revoke a sponsorship you granted.", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipService>()
|
||||
await sutProvider.GetDependency<IRemoveSponsorshipCommand>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.RemoveSponsorshipAsync(default, default);
|
||||
.RemoveSponsorshipAsync(default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@ -144,9 +145,9 @@ namespace Bit.Api.Test.Controllers
|
||||
sutProvider.Sut.RemoveSponsorship(sponsoredOrg.Id));
|
||||
|
||||
Assert.Contains("Only the owner of an organization can remove sponsorship.", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipService>()
|
||||
await sutProvider.GetDependency<IRemoveSponsorshipCommand>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.RemoveSponsorshipAsync(default, default);
|
||||
.RemoveSponsorshipAsync(default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationApiKeys.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
@ -27,6 +28,9 @@ namespace Bit.Api.Test.Controllers
|
||||
private readonly ISsoConfigRepository _ssoConfigRepository;
|
||||
private readonly ISsoConfigService _ssoConfigService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IGetOrganizationApiKeyCommand _getOrganizationApiKeyCommand;
|
||||
private readonly IRotateOrganizationApiKeyCommand _rotateOrganizationApiKeyCommand;
|
||||
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
|
||||
|
||||
private readonly OrganizationsController _sut;
|
||||
|
||||
@ -41,11 +45,15 @@ namespace Bit.Api.Test.Controllers
|
||||
_policyRepository = Substitute.For<IPolicyRepository>();
|
||||
_ssoConfigRepository = Substitute.For<ISsoConfigRepository>();
|
||||
_ssoConfigService = Substitute.For<ISsoConfigService>();
|
||||
_getOrganizationApiKeyCommand = Substitute.For<IGetOrganizationApiKeyCommand>();
|
||||
_rotateOrganizationApiKeyCommand = Substitute.For<IRotateOrganizationApiKeyCommand>();
|
||||
_organizationApiKeyRepository = Substitute.For<IOrganizationApiKeyRepository>();
|
||||
_userService = Substitute.For<IUserService>();
|
||||
|
||||
_sut = new OrganizationsController(_organizationRepository, _organizationUserRepository,
|
||||
_policyRepository, _organizationService, _userService, _paymentService, _currentContext,
|
||||
_ssoConfigRepository, _ssoConfigService, _globalSettings);
|
||||
_ssoConfigRepository, _ssoConfigService, _getOrganizationApiKeyCommand, _rotateOrganizationApiKeyCommand,
|
||||
_organizationApiKeyRepository, _globalSettings);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@ -112,4 +120,3 @@ namespace Bit.Api.Test.Controllers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -309,6 +309,15 @@
|
||||
"IdentityModel": "4.3.0"
|
||||
}
|
||||
},
|
||||
"Kralizek.AutoFixture.Extensions.MockHttp": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.2.0",
|
||||
"contentHash": "6zmks7/5mVczazv910N7V2EdiU6B+rY61lwdgVO0o2iZuTI6KI3T+Hgkrjv0eGOKYucq2OMC+gnAc5Ej2ajoTQ==",
|
||||
"dependencies": {
|
||||
"AutoFixture": "4.11.0",
|
||||
"RichardSzalay.MockHttp": "6.0.0"
|
||||
}
|
||||
},
|
||||
"libsodium": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.18",
|
||||
@ -1591,6 +1600,11 @@
|
||||
"System.Diagnostics.DiagnosticSource": "4.7.1"
|
||||
}
|
||||
},
|
||||
"RichardSzalay.MockHttp": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "bStGNqIX/MGYtML7K3EzdsE/k5HGVAcg7XgN23TQXGXqxNC9fvYFR94fA0sGM5hAT36R+BBGet6ZDQxXL/IPxg=="
|
||||
},
|
||||
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.2",
|
||||
@ -3540,6 +3554,7 @@
|
||||
"AutoFixture.AutoNSubstitute": "4.14.0",
|
||||
"AutoFixture.Xunit2": "4.14.0",
|
||||
"Core": "1.47.1",
|
||||
"Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0",
|
||||
"Microsoft.NET.Test.Sdk": "16.6.1",
|
||||
"NSubstitute": "4.2.2",
|
||||
"xunit": "2.4.1"
|
||||
|
@ -317,6 +317,15 @@
|
||||
"IdentityModel": "4.3.0"
|
||||
}
|
||||
},
|
||||
"Kralizek.AutoFixture.Extensions.MockHttp": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.2.0",
|
||||
"contentHash": "6zmks7/5mVczazv910N7V2EdiU6B+rY61lwdgVO0o2iZuTI6KI3T+Hgkrjv0eGOKYucq2OMC+gnAc5Ej2ajoTQ==",
|
||||
"dependencies": {
|
||||
"AutoFixture": "4.11.0",
|
||||
"RichardSzalay.MockHttp": "6.0.0"
|
||||
}
|
||||
},
|
||||
"libsodium": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.18",
|
||||
@ -1663,6 +1672,11 @@
|
||||
"System.Diagnostics.DiagnosticSource": "4.7.1"
|
||||
}
|
||||
},
|
||||
"RichardSzalay.MockHttp": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "bStGNqIX/MGYtML7K3EzdsE/k5HGVAcg7XgN23TQXGXqxNC9fvYFR94fA0sGM5hAT36R+BBGet6ZDQxXL/IPxg=="
|
||||
},
|
||||
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.2",
|
||||
@ -3639,6 +3653,7 @@
|
||||
"AutoFixture.AutoNSubstitute": "4.14.0",
|
||||
"AutoFixture.Xunit2": "4.14.0",
|
||||
"Core": "1.47.1",
|
||||
"Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0",
|
||||
"Microsoft.NET.Test.Sdk": "16.6.1",
|
||||
"NSubstitute": "4.2.2",
|
||||
"xunit": "2.4.1"
|
||||
|
@ -7,6 +7,7 @@ using Xunit.Sdk;
|
||||
|
||||
namespace Bit.Test.Common.AutoFixture.Attributes
|
||||
{
|
||||
[DataDiscoverer("AutoFixture.Xunit2.NoPreDiscoveryDataDiscoverer", "AutoFixture.Xunit2")]
|
||||
public class BitAutoDataAttribute : DataAttribute
|
||||
{
|
||||
private readonly Func<IFixture> _createFixture;
|
||||
|
@ -0,0 +1,11 @@
|
||||
using AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.JsonDocumentFixtures;
|
||||
|
||||
namespace Bit.Test.Common.AutoFixture.Attributes
|
||||
{
|
||||
public class JsonDocumentCustomizeAttribute : BitCustomizeAttribute
|
||||
{
|
||||
public string Json { get; set; }
|
||||
public override ICustomization GetCustomization() => new JsonDocumentCustomization() { Json = Json };
|
||||
}
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using AutoFixture;
|
||||
using AutoFixture.Xunit2;
|
||||
|
||||
namespace Bit.Test.Common.AutoFixture.Attributes
|
||||
{
|
||||
|
@ -7,5 +7,8 @@ namespace Bit.Test.Common.AutoFixture
|
||||
{
|
||||
public static IFixture WithAutoNSubstitutions(this IFixture fixture)
|
||||
=> fixture.Customize(new AutoNSubstituteCustomization());
|
||||
|
||||
public static IFixture WithAutoNSubstitutionsAutoPopulatedProperties(this IFixture fixture)
|
||||
=> fixture.Customize(new AutoNSubstituteCustomization { ConfigureMembers = true });
|
||||
}
|
||||
}
|
||||
|
33
test/Common/AutoFixture/JsonDocumentFixtures.cs
Normal file
33
test/Common/AutoFixture/JsonDocumentFixtures.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using AutoFixture;
|
||||
using AutoFixture.Kernel;
|
||||
|
||||
namespace Bit.Test.Common.AutoFixture.JsonDocumentFixtures
|
||||
{
|
||||
public class JsonDocumentCustomization : ICustomization, ISpecimenBuilder
|
||||
{
|
||||
|
||||
public string Json { get; set; }
|
||||
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customizations.Add(this);
|
||||
}
|
||||
|
||||
public object Create(object request, ISpecimenContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
var type = request as Type;
|
||||
if (type == null || (type != typeof(JsonDocument)))
|
||||
{
|
||||
return new NoSpecimen();
|
||||
}
|
||||
|
||||
return JsonDocument.Parse(Json ?? "{}");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,25 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
<RootNamespace>Bit.Test.Common</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NSubstitute" Version="4.2.2" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NSubstitute" Version="4.2.2"/>
|
||||
<PackageReference Include="xunit" Version="2.4.1"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="AutoFixture.Xunit2" Version="4.14.0" />
|
||||
<PackageReference Include="AutoFixture.AutoNSubstitute" Version="4.14.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
||||
<PackageReference Include="AutoFixture.Xunit2" Version="4.14.0"/>
|
||||
<PackageReference Include="AutoFixture.AutoNSubstitute" Version="4.14.0"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1"/>
|
||||
<PackageReference Include="Kralizek.AutoFixture.Extensions.MockHttp" Version="1.2.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Core\Core.csproj" />
|
||||
<ProjectReference Include="..\..\src\Api\Api.csproj" />
|
||||
<ProjectReference Include="..\..\src\Core\Core.csproj"/>
|
||||
<ProjectReference Include="..\..\src\Api\Api.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using Bit.Core.Utilities;
|
||||
using Xunit;
|
||||
@ -20,7 +22,7 @@ namespace Bit.Test.Common.Helpers
|
||||
|
||||
if (actual == null)
|
||||
{
|
||||
throw new Exception("Expected object is null but actual is not");
|
||||
throw new Exception("Actual object is null but expected is not");
|
||||
}
|
||||
|
||||
foreach (var expectedPropInfo in expected.GetType().GetProperties().Where(pi => !relevantExcludedProperties.Contains(pi.Name)))
|
||||
@ -38,6 +40,11 @@ namespace Bit.Test.Common.Helpers
|
||||
{
|
||||
Assert.Equal(expectedPropInfo.GetValue(expected), actualPropInfo.GetValue(actual));
|
||||
}
|
||||
else if (expectedPropInfo.PropertyType == typeof(JsonDocument) && actualPropInfo.PropertyType == typeof(JsonDocument))
|
||||
{
|
||||
static string JsonDocString(PropertyInfo info, object obj) => JsonSerializer.Serialize(info.GetValue(obj));
|
||||
Assert.Equal(JsonDocString(expectedPropInfo, expected), JsonDocString(actualPropInfo, actual));
|
||||
}
|
||||
else
|
||||
{
|
||||
var prefix = $"{expectedPropInfo.PropertyType.Name}.";
|
||||
@ -48,12 +55,24 @@ namespace Bit.Test.Common.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
public static Predicate<T> AssertEqualExpectedPredicate<T>(T expected) => (actual) =>
|
||||
private static Predicate<T> AssertPropertyEqualPredicate<T>(T expected, params string[] excludedPropertyStrings) => (actual) =>
|
||||
{
|
||||
AssertPropertyEqual(expected, actual, excludedPropertyStrings);
|
||||
return true;
|
||||
};
|
||||
|
||||
public static Expression<Predicate<T>> AssertPropertyEqual<T>(T expected, params string[] excludedPropertyStrings) =>
|
||||
(T actual) => AssertPropertyEqualPredicate(expected, excludedPropertyStrings)(actual);
|
||||
|
||||
private static Predicate<T> AssertEqualExpectedPredicate<T>(T expected) => (actual) =>
|
||||
{
|
||||
Assert.Equal(expected, actual);
|
||||
return true;
|
||||
};
|
||||
|
||||
public static Expression<Predicate<T>> AssertEqualExpected<T>(T expected) =>
|
||||
(T actual) => AssertEqualExpectedPredicate(expected)(actual);
|
||||
|
||||
public static JsonElement AssertJsonProperty(JsonElement element, string propertyName, JsonValueKind jsonValueKind)
|
||||
{
|
||||
if (!element.TryGetProperty(propertyName, out var subElement))
|
||||
@ -64,5 +83,15 @@ namespace Bit.Test.Common.Helpers
|
||||
Assert.Equal(jsonValueKind, subElement.ValueKind);
|
||||
return subElement;
|
||||
}
|
||||
|
||||
public static TimeSpan AssertRecent(DateTime dateTime, int skewSeconds = 2)
|
||||
=> AssertRecent(dateTime, TimeSpan.FromSeconds(skewSeconds));
|
||||
|
||||
public static TimeSpan AssertRecent(DateTime dateTime, TimeSpan skew)
|
||||
{
|
||||
var difference = DateTime.UtcNow - dateTime;
|
||||
Assert.True(difference < skew);
|
||||
return difference;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
50
test/Common/Helpers/TestCaseHelper.cs
Normal file
50
test/Common/Helpers/TestCaseHelper.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Bit.Test.Common.Helpers
|
||||
{
|
||||
public static class TestCaseHelper
|
||||
{
|
||||
public static IEnumerable<IEnumerable<T>> GetCombinations<T>(params T[] items)
|
||||
{
|
||||
var count = Math.Pow(2, items.Length);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var str = Convert.ToString(i, 2).PadLeft(items.Length, '0');
|
||||
List<T> combination = new();
|
||||
for (var j = 0; j < str.Length; j++)
|
||||
{
|
||||
if (str[j] == '1')
|
||||
{
|
||||
combination.Add(items[j]);
|
||||
}
|
||||
}
|
||||
yield return combination;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<IEnumerable<object>> GetCombinationsOfMultipleLists(params IEnumerable<object>[] optionLists)
|
||||
{
|
||||
if (!optionLists.Any())
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var item in optionLists.First())
|
||||
{
|
||||
var itemArray = new[] { item };
|
||||
|
||||
if (optionLists.Length == 1)
|
||||
{
|
||||
yield return itemArray;
|
||||
}
|
||||
|
||||
foreach (var nextCombination in GetCombinationsOfMultipleLists(optionLists.Skip(1).ToArray()))
|
||||
{
|
||||
yield return itemArray.Concat(nextCombination);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
53
test/Common/Test/TestCaseHelperTests.cs
Normal file
53
test/Common/Test/TestCaseHelperTests.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Test.Common.Test
|
||||
{
|
||||
public class TestCaseHelperTests
|
||||
{
|
||||
[Fact]
|
||||
public void GetCombinations_EmptyList()
|
||||
{
|
||||
Assert.Equal(new[] { Array.Empty<int>() }, TestCaseHelper.GetCombinations(Array.Empty<int>()).ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCombinations_OneItemList()
|
||||
{
|
||||
Assert.Equal(new[] { Array.Empty<int>(), new[] { 1 } }, TestCaseHelper.GetCombinations(1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCombinations_TwoItemList()
|
||||
{
|
||||
Assert.Equal(new[] { Array.Empty<int>(), new[] { 2 }, new[] { 1 }, new[] { 1, 2 } }, TestCaseHelper.GetCombinations(1, 2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCombinationsOfMultipleLists_OneOne()
|
||||
{
|
||||
Assert.Equal(new[] { new object[] { 1, "1" } }, TestCaseHelper.GetCombinationsOfMultipleLists(new object[] { 1 }, new object[] { "1" }));
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void GetCombinationsOfMultipleLists_OneTwo()
|
||||
{
|
||||
Assert.Equal(new[] { new object[] { 1, "1" }, new object[] { 1, "2" } }, TestCaseHelper.GetCombinationsOfMultipleLists(new object[] { 1 }, new object[] { "1", "2" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCombinationsOfMultipleLists_TwoOne()
|
||||
{
|
||||
Assert.Equal(new[] { new object[] { 1, "1" }, new object[] { 2, "1" } }, TestCaseHelper.GetCombinationsOfMultipleLists(new object[] { 1, 2 }, new object[] { "1" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCombinationsOfMultipleLists_TwoTwo()
|
||||
{
|
||||
Assert.Equal(new[] { new object[] { 1, "1" }, new object[] { 1, "2" }, new object[] { 2, "1" }, new object[] { 2, "2" } }, TestCaseHelper.GetCombinationsOfMultipleLists(new object[] { 1, 2 }, new object[] { "1", "2" }));
|
||||
}
|
||||
}
|
||||
}
|
@ -22,6 +22,16 @@
|
||||
"xunit.extensibility.core": "[2.2.0, 3.0.0)"
|
||||
}
|
||||
},
|
||||
"Kralizek.AutoFixture.Extensions.MockHttp": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.2.0, )",
|
||||
"resolved": "1.2.0",
|
||||
"contentHash": "6zmks7/5mVczazv910N7V2EdiU6B+rY61lwdgVO0o2iZuTI6KI3T+Hgkrjv0eGOKYucq2OMC+gnAc5Ej2ajoTQ==",
|
||||
"dependencies": {
|
||||
"AutoFixture": "4.11.0",
|
||||
"RichardSzalay.MockHttp": "6.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.NET.Test.Sdk": {
|
||||
"type": "Direct",
|
||||
"requested": "[16.6.1, )",
|
||||
@ -1586,6 +1596,11 @@
|
||||
"System.Diagnostics.DiagnosticSource": "4.7.1"
|
||||
}
|
||||
},
|
||||
"RichardSzalay.MockHttp": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "bStGNqIX/MGYtML7K3EzdsE/k5HGVAcg7XgN23TQXGXqxNC9fvYFR94fA0sGM5hAT36R+BBGet6ZDQxXL/IPxg=="
|
||||
},
|
||||
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.2",
|
||||
|
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using AutoFixture;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
|
||||
namespace Bit.Core.Test.AutoFixture
|
||||
{
|
||||
public class OrganizationLicenseCustomizeAttribute : BitCustomizeAttribute
|
||||
{
|
||||
public override ICustomization GetCustomization() => new OrganizationLicenseCustomization();
|
||||
}
|
||||
public class OrganizationLicenseCustomization : ICustomization
|
||||
{
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customize<OrganizationLicense>(composer => composer
|
||||
.With(o => o.Signature, Guid.NewGuid().ToString().Replace('-', '+')));
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,33 @@ using Bit.Test.Common.AutoFixture.Attributes;
|
||||
|
||||
namespace Bit.Core.Test.AutoFixture.OrganizationSponsorshipFixtures
|
||||
{
|
||||
public class OrganizationSponsorshipCustomizeAttribute : BitCustomizeAttribute
|
||||
{
|
||||
public bool ToDelete = false;
|
||||
public override ICustomization GetCustomization() => ToDelete ?
|
||||
new ToDeleteOrganizationSponsorship() :
|
||||
new ValidOrganizationSponsorship();
|
||||
}
|
||||
|
||||
public class ValidOrganizationSponsorship : ICustomization
|
||||
{
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customize<OrganizationSponsorship>(composer => composer
|
||||
.With(s => s.ToDelete, false)
|
||||
.With(s => s.LastSyncDate, DateTime.UtcNow.AddDays(new Random().Next(-90, 0))));
|
||||
}
|
||||
}
|
||||
|
||||
public class ToDeleteOrganizationSponsorship : ICustomization
|
||||
{
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customize<OrganizationSponsorship>(composer => composer
|
||||
.With(s => s.ToDelete, true));
|
||||
}
|
||||
}
|
||||
|
||||
internal class OrganizationSponsorshipBuilder : ISpecimenBuilder
|
||||
{
|
||||
public object Create(object request, ISpecimenContext context)
|
||||
|
@ -1,36 +1,34 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
<RootNamespace>Bit.Core.Test</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="3.0.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
||||
<PackageReference Include="Moq" Version="4.16.1" />
|
||||
<PackageReference Include="NSubstitute" Version="4.2.2" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1"/>
|
||||
<PackageReference Include="Moq" Version="4.16.1"/>
|
||||
<PackageReference Include="NSubstitute" Version="4.2.2"/>
|
||||
<PackageReference Include="xunit" Version="2.4.1"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="AutoFixture.Xunit2" Version="4.14.0" />
|
||||
<PackageReference Include="AutoFixture.AutoNSubstitute" Version="4.14.0" />
|
||||
<PackageReference Include="AutoFixture.Xunit2" Version="4.14.0"/>
|
||||
<PackageReference Include="AutoFixture.AutoNSubstitute" Version="4.14.0"/>
|
||||
<PackageReference Include="Kralizek.AutoFixture.Extensions.MockHttp" Version="1.2.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Core\Core.csproj" />
|
||||
<ProjectReference Include="..\..\src\Api\Api.csproj" />
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Utilities\data\embeddedResource.txt" />
|
||||
<None Remove="Utilities\data\embeddedResource.txt"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Utilities\data\embeddedResource.txt" />
|
||||
<EmbeddedResource Include="Utilities\data\embeddedResource.txt"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -0,0 +1,159 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Business.Tokenables;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Models.Business.Tokenables
|
||||
{
|
||||
public class OrganizationSponsorshipOfferTokenableTests
|
||||
{
|
||||
public static IEnumerable<object[]> PlanSponsorshipTypes() => Enum.GetValues<PlanSponsorshipType>().Select(x => new object[] { x });
|
||||
|
||||
[Fact]
|
||||
public void IsInvalidIfIdentifierIsWrong()
|
||||
{
|
||||
var token = new OrganizationSponsorshipOfferTokenable()
|
||||
{
|
||||
Email = "email",
|
||||
Id = Guid.NewGuid(),
|
||||
Identifier = "not correct",
|
||||
SponsorshipType = PlanSponsorshipType.FamiliesForEnterprise,
|
||||
};
|
||||
|
||||
Assert.False(token.Valid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsInvalidIfIdIsDefault()
|
||||
{
|
||||
var token = new OrganizationSponsorshipOfferTokenable()
|
||||
{
|
||||
Email = "email",
|
||||
Id = default,
|
||||
SponsorshipType = PlanSponsorshipType.FamiliesForEnterprise,
|
||||
};
|
||||
|
||||
Assert.False(token.Valid);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void IsInvalidIfEmailIsEmpty()
|
||||
{
|
||||
var token = new OrganizationSponsorshipOfferTokenable()
|
||||
{
|
||||
Email = "",
|
||||
Id = Guid.NewGuid(),
|
||||
SponsorshipType = PlanSponsorshipType.FamiliesForEnterprise,
|
||||
};
|
||||
|
||||
Assert.False(token.Valid);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void IsValid_Success(OrganizationSponsorship sponsorship)
|
||||
{
|
||||
var token = new OrganizationSponsorshipOfferTokenable(sponsorship);
|
||||
|
||||
Assert.True(token.IsValid(sponsorship, sponsorship.OfferedToEmail));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void IsValid_RequiresNonNullSponsorship(OrganizationSponsorship sponsorship)
|
||||
{
|
||||
var token = new OrganizationSponsorshipOfferTokenable(sponsorship);
|
||||
|
||||
Assert.False(token.IsValid(null, sponsorship.OfferedToEmail));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void IsValid_RequiresCurrentEmailToBeSameAsOfferedToEmail(OrganizationSponsorship sponsorship, string currentEmail)
|
||||
{
|
||||
var token = new OrganizationSponsorshipOfferTokenable(sponsorship);
|
||||
|
||||
Assert.False(token.IsValid(sponsorship, currentEmail));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void IsValid_RequiresSameSponsorshipId(OrganizationSponsorship sponsorship1, OrganizationSponsorship sponsorship2)
|
||||
{
|
||||
sponsorship1.Id = sponsorship2.Id;
|
||||
|
||||
var token = new OrganizationSponsorshipOfferTokenable(sponsorship1);
|
||||
|
||||
Assert.False(token.IsValid(sponsorship2, sponsorship1.OfferedToEmail));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void IsValid_RequiresSameEmail(OrganizationSponsorship sponsorship1, OrganizationSponsorship sponsorship2)
|
||||
{
|
||||
sponsorship1.OfferedToEmail = sponsorship2.OfferedToEmail;
|
||||
|
||||
var token = new OrganizationSponsorshipOfferTokenable(sponsorship1);
|
||||
|
||||
Assert.False(token.IsValid(sponsorship2, sponsorship1.OfferedToEmail));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Constructor_GrabsIdFromSponsorship(OrganizationSponsorship sponsorship)
|
||||
{
|
||||
var token = new OrganizationSponsorshipOfferTokenable(sponsorship);
|
||||
|
||||
Assert.Equal(sponsorship.Id, token.Id);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Constructor_GrabsEmailFromSponsorshipOfferedToEmail(OrganizationSponsorship sponsorship)
|
||||
{
|
||||
var token = new OrganizationSponsorshipOfferTokenable(sponsorship);
|
||||
|
||||
Assert.Equal(sponsorship.OfferedToEmail, token.Email);
|
||||
}
|
||||
|
||||
[Theory, BitMemberAutoData(nameof(PlanSponsorshipTypes))]
|
||||
public void Constructor_GrabsSponsorshipType(PlanSponsorshipType planSponsorshipType,
|
||||
OrganizationSponsorship sponsorship)
|
||||
{
|
||||
sponsorship.PlanSponsorshipType = planSponsorshipType;
|
||||
var token = new OrganizationSponsorshipOfferTokenable(sponsorship);
|
||||
|
||||
Assert.Equal(sponsorship.PlanSponsorshipType, token.SponsorshipType);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Constructor_DefaultId_Throws(OrganizationSponsorship sponsorship)
|
||||
{
|
||||
sponsorship.Id = default;
|
||||
|
||||
Assert.Throws<ArgumentException>(() => new OrganizationSponsorshipOfferTokenable(sponsorship));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Constructor_NoOfferedToEmail_Throws(OrganizationSponsorship sponsorship)
|
||||
{
|
||||
sponsorship.OfferedToEmail = null;
|
||||
|
||||
Assert.Throws<ArgumentException>(() => new OrganizationSponsorshipOfferTokenable(sponsorship));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Constructor_EmptyOfferedToEmail_Throws(OrganizationSponsorship sponsorship)
|
||||
{
|
||||
sponsorship.OfferedToEmail = "";
|
||||
|
||||
Assert.Throws<ArgumentException>(() => new OrganizationSponsorshipOfferTokenable(sponsorship));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Constructor_NoPlanSponsorshipType_Throws(OrganizationSponsorship sponsorship)
|
||||
{
|
||||
sponsorship.PlanSponsorshipType = null;
|
||||
|
||||
Assert.Throws<ArgumentException>(() => new OrganizationSponsorshipOfferTokenable(sponsorship));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationApiKeys;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationApiKeys
|
||||
{
|
||||
[SutProviderCustomize]
|
||||
public class GetOrganizationApiKeyCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetOrganizationApiKey_HasOne_Returns(SutProvider<GetOrganizationApiKeyCommand> sutProvider,
|
||||
Guid id, Guid organizationId, OrganizationApiKeyType keyType)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationApiKeyRepository>()
|
||||
.GetManyByOrganizationIdTypeAsync(organizationId, keyType)
|
||||
.Returns(new List<OrganizationApiKey>
|
||||
{
|
||||
new OrganizationApiKey
|
||||
{
|
||||
Id = id,
|
||||
OrganizationId = organizationId,
|
||||
ApiKey = "test",
|
||||
Type = keyType,
|
||||
RevisionDate = DateTime.Now.AddDays(-1),
|
||||
},
|
||||
});
|
||||
|
||||
var apiKey = await sutProvider.Sut.GetOrganizationApiKeyAsync(organizationId, keyType);
|
||||
Assert.NotNull(apiKey);
|
||||
Assert.Equal(id, apiKey.Id);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetOrganizationApiKey_HasTwo_Throws(SutProvider<GetOrganizationApiKeyCommand> sutProvider,
|
||||
Guid organizationId, OrganizationApiKeyType keyType)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationApiKeyRepository>()
|
||||
.GetManyByOrganizationIdTypeAsync(organizationId, keyType)
|
||||
.Returns(new List<OrganizationApiKey>
|
||||
{
|
||||
new OrganizationApiKey
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = organizationId,
|
||||
ApiKey = "test",
|
||||
Type = keyType,
|
||||
RevisionDate = DateTime.Now.AddDays(-1),
|
||||
},
|
||||
new OrganizationApiKey
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = organizationId,
|
||||
ApiKey = "test_other",
|
||||
Type = keyType,
|
||||
RevisionDate = DateTime.Now.AddDays(-1),
|
||||
},
|
||||
});
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
async () => await sutProvider.Sut.GetOrganizationApiKeyAsync(organizationId, keyType));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetOrganizationApiKey_HasNone_CreatesAndReturns(SutProvider<GetOrganizationApiKeyCommand> sutProvider,
|
||||
Guid organizationId, OrganizationApiKeyType keyType)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationApiKeyRepository>()
|
||||
.GetManyByOrganizationIdTypeAsync(organizationId, keyType)
|
||||
.Returns(Enumerable.Empty<OrganizationApiKey>());
|
||||
|
||||
var apiKey = await sutProvider.Sut.GetOrganizationApiKeyAsync(organizationId, keyType);
|
||||
|
||||
Assert.NotNull(apiKey);
|
||||
Assert.Equal(organizationId, apiKey.OrganizationId);
|
||||
Assert.Equal(keyType, apiKey.Type);
|
||||
await sutProvider.GetDependency<IOrganizationApiKeyRepository>()
|
||||
.Received(1)
|
||||
.CreateAsync(Arg.Any<OrganizationApiKey>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetOrganizationApiKey_BadType_Throws(SutProvider<GetOrganizationApiKeyCommand> sutProvider,
|
||||
Guid organizationId, OrganizationApiKeyType keyType)
|
||||
{
|
||||
keyType = (OrganizationApiKeyType)byte.MaxValue;
|
||||
|
||||
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(
|
||||
async () => await sutProvider.Sut.GetOrganizationApiKeyAsync(organizationId, keyType));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationApiKeys;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationApiKeys
|
||||
{
|
||||
[SutProviderCustomize]
|
||||
public class RotateOrganizationApiKeyCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task RotateApiKeyAsync_RotatesKey(SutProvider<RotateOrganizationApiKeyCommand> sutProvider,
|
||||
OrganizationApiKey organizationApiKey)
|
||||
{
|
||||
var existingKey = organizationApiKey.ApiKey;
|
||||
organizationApiKey = await sutProvider.Sut.RotateApiKeyAsync(organizationApiKey);
|
||||
Assert.NotEqual(existingKey, organizationApiKey.ApiKey);
|
||||
AssertHelper.AssertRecent(organizationApiKey.RevisionDate);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationConnections;
|
||||
using Bit.Core.Models.OrganizationConnectionConfigs;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationConnections;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationConnections
|
||||
{
|
||||
[SutProviderCustomize]
|
||||
public class CreateOrganizationConnectionCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CreateAsync_CallsCreate(OrganizationConnectionData<BillingSyncConfig> data,
|
||||
SutProvider<CreateOrganizationConnectionCommand> sutProvider)
|
||||
{
|
||||
await sutProvider.Sut.CreateAsync(data);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationConnectionRepository>().Received(1)
|
||||
.CreateAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data.ToEntity())));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationConnections;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationConnections;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationConnections
|
||||
{
|
||||
[SutProviderCustomize]
|
||||
public class DeleteOrganizationConnectionCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DeleteAsync_CallsDelete(OrganizationConnection connection,
|
||||
SutProvider<DeleteOrganizationConnectionCommand> sutProvider)
|
||||
{
|
||||
await sutProvider.Sut.DeleteAsync(connection);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationConnectionRepository>().Received(1)
|
||||
.DeleteAsync(connection);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationConnections;
|
||||
using Bit.Core.Models.OrganizationConnectionConfigs;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationConnections;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationConnections
|
||||
{
|
||||
[SutProviderCustomize]
|
||||
public class UpdateOrganizationConnectionCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateAsync_NoId_Fails(OrganizationConnectionData<BillingSyncConfig> data,
|
||||
SutProvider<UpdateOrganizationConnectionCommand> sutProvider)
|
||||
{
|
||||
data.Id = null;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<Exception>(() => sutProvider.Sut.UpdateAsync(data));
|
||||
|
||||
Assert.Contains("Cannot update connection, Connection does not exist.", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationConnectionRepository>().DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateAsync_ConnectionDoesNotExist_ThrowsNotFound(
|
||||
OrganizationConnectionData<BillingSyncConfig> data,
|
||||
SutProvider<UpdateOrganizationConnectionCommand> sutProvider)
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(data));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationConnectionRepository>().DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateAsync_CallsUpsert(OrganizationConnectionData<BillingSyncConfig> data,
|
||||
OrganizationConnection existing,
|
||||
SutProvider<UpdateOrganizationConnectionCommand> sutProvider)
|
||||
{
|
||||
data.Id = existing.Id;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationConnectionRepository>().GetByIdAsync(data.Id.Value).Returns(existing);
|
||||
await sutProvider.Sut.UpdateAsync(data);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationConnectionRepository>().Received(1)
|
||||
.UpsertAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data.ToEntity())));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using NSubstitute;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise
|
||||
{
|
||||
public abstract class CancelSponsorshipCommandTestsBase : FamiliesForEnterpriseTestsBase
|
||||
{
|
||||
protected async Task AssertRemovedSponsoredPaymentAsync<T>(Organization sponsoredOrg,
|
||||
OrganizationSponsorship sponsorship, SutProvider<T> sutProvider)
|
||||
{
|
||||
await sutProvider.GetDependency<IPaymentService>().Received(1)
|
||||
.RemoveOrganizationSponsorshipAsync(sponsoredOrg, sponsorship);
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).UpsertAsync(sponsoredOrg);
|
||||
if (sponsorship != null)
|
||||
{
|
||||
await sutProvider.GetDependency<IMailService>().Received(1)
|
||||
.SendFamiliesForEnterpriseSponsorshipRevertingEmailAsync(sponsoredOrg.BillingEmailAddress(), sponsorship.ValidUntil.GetValueOrDefault());
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task AssertDeletedSponsorshipAsync<T>(OrganizationSponsorship sponsorship,
|
||||
SutProvider<T> sutProvider)
|
||||
{
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().Received(1)
|
||||
.DeleteAsync(sponsorship);
|
||||
}
|
||||
|
||||
protected static async Task AssertDidNotRemoveSponsorshipAsync<T>(SutProvider<T> sutProvider)
|
||||
{
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
|
||||
.DeleteAsync(default);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
}
|
||||
|
||||
protected async Task AssertRemovedSponsorshipAsync<T>(OrganizationSponsorship sponsorship,
|
||||
SutProvider<T> sutProvider)
|
||||
{
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().Received(1)
|
||||
.DeleteAsync(sponsorship);
|
||||
}
|
||||
|
||||
protected static async Task AssertDidNotRemoveSponsoredPaymentAsync<T>(SutProvider<T> sutProvider)
|
||||
{
|
||||
await sutProvider.GetDependency<IPaymentService>().DidNotReceiveWithAnyArgs()
|
||||
.RemoveOrganizationSponsorshipAsync(default, default);
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
await sutProvider.GetDependency<IMailService>().DidNotReceiveWithAnyArgs()
|
||||
.SendFamiliesForEnterpriseSponsorshipRevertingEmailAsync(default, default);
|
||||
}
|
||||
|
||||
protected static async Task AssertDidNotDeleteSponsorshipAsync<T>(SutProvider<T> sutProvider)
|
||||
{
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
|
||||
.DeleteAsync(default);
|
||||
}
|
||||
|
||||
protected static async Task AssertDidNotUpdateSponsorshipAsync<T>(SutProvider<T> sutProvider)
|
||||
{
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
}
|
||||
|
||||
protected static async Task AssertUpdatedSponsorshipAsync<T>(OrganizationSponsorship sponsorship,
|
||||
SutProvider<T> sutProvider)
|
||||
{
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().Received(1).UpsertAsync(sponsorship);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
|
||||
using Bit.Core.Test.AutoFixture.OrganizationSponsorshipFixtures;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud
|
||||
{
|
||||
[SutProviderCustomize]
|
||||
[OrganizationSponsorshipCustomize]
|
||||
public class CloudRevokeSponsorshipCommandTests : CancelSponsorshipCommandTestsBase
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RevokeSponsorship_NoExistingSponsorship_ThrowsBadRequest(
|
||||
SutProvider<CloudRevokeSponsorshipCommand> sutProvider)
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RevokeSponsorshipAsync(null));
|
||||
|
||||
Assert.Contains("You are not currently sponsoring an organization.", exception.Message);
|
||||
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
|
||||
await AssertDidNotUpdateSponsorshipAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RevokeSponsorship_SponsorshipNotRedeemed_DeletesSponsorship(OrganizationSponsorship sponsorship,
|
||||
SutProvider<CloudRevokeSponsorshipCommand> sutProvider)
|
||||
{
|
||||
sponsorship.SponsoredOrganizationId = null;
|
||||
|
||||
await sutProvider.Sut.RevokeSponsorshipAsync(sponsorship);
|
||||
await AssertDeletedSponsorshipAsync(sponsorship, sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RevokeSponsorship_SponsorshipRedeemed_MarksForDelete(OrganizationSponsorship sponsorship,
|
||||
SutProvider<CloudRevokeSponsorshipCommand> sutProvider)
|
||||
{
|
||||
await sutProvider.Sut.RevokeSponsorshipAsync(sponsorship);
|
||||
|
||||
Assert.True(sponsorship.ToDelete);
|
||||
await AssertUpdatedSponsorshipAsync(sponsorship, sutProvider);
|
||||
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,230 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationSponsorships;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud
|
||||
{
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class CloudSyncSponsorshipsCommandTests : FamiliesForEnterpriseTestsBase
|
||||
{
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SyncOrganization_SponsoringOrgNotFound_ThrowsBadRequest(
|
||||
IEnumerable<OrganizationSponsorshipData> sponsorshipsData,
|
||||
SutProvider<CloudSyncSponsorshipsCommand> sutProvider)
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.SyncOrganization(null, sponsorshipsData));
|
||||
|
||||
Assert.Contains("Failed to sync sponsorship - missing organization.", exception.Message);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertManyAsync(default);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.DeleteManyAsync(default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SyncOrganization_NoSponsorships_EarlyReturn(
|
||||
Organization organization,
|
||||
SutProvider<CloudSyncSponsorshipsCommand> sutProvider)
|
||||
{
|
||||
var result = await sutProvider.Sut.SyncOrganization(organization, Enumerable.Empty<OrganizationSponsorshipData>());
|
||||
|
||||
Assert.Empty(result.Item1.SponsorshipsBatch);
|
||||
Assert.Empty(result.Item2);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertManyAsync(default);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.DeleteManyAsync(default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitMemberAutoData(nameof(NonEnterprisePlanTypes))]
|
||||
public async Task SyncOrganization_BadSponsoringOrgPlan_NoSync(
|
||||
PlanType planType,
|
||||
Organization organization, IEnumerable<OrganizationSponsorshipData> sponsorshipsData,
|
||||
SutProvider<CloudSyncSponsorshipsCommand> sutProvider)
|
||||
{
|
||||
organization.PlanType = planType;
|
||||
|
||||
await sutProvider.Sut.SyncOrganization(organization, sponsorshipsData);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertManyAsync(default);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.DeleteManyAsync(default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SyncOrganization_Success_RecordsEvent(Organization organization,
|
||||
SutProvider<CloudSyncSponsorshipsCommand> sutProvider)
|
||||
{
|
||||
await sutProvider.Sut.SyncOrganization(organization, Array.Empty<OrganizationSponsorshipData>());
|
||||
|
||||
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationEventAsync(organization, EventType.Organization_SponsorshipsSynced, Arg.Any<DateTime?>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SyncOrganization_OneExisting_OneNew_Success(SutProvider<CloudSyncSponsorshipsCommand> sutProvider,
|
||||
Organization sponsoringOrganization, OrganizationSponsorship existingSponsorship, OrganizationSponsorship newSponsorship)
|
||||
{
|
||||
// Arrange
|
||||
sponsoringOrganization.PlanType = PlanType.EnterpriseAnnually;
|
||||
|
||||
existingSponsorship.ToDelete = false;
|
||||
newSponsorship.ToDelete = false;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetManyBySponsoringOrganizationAsync(sponsoringOrganization.Id)
|
||||
.Returns(new List<OrganizationSponsorship>
|
||||
{
|
||||
existingSponsorship,
|
||||
});
|
||||
|
||||
// Act
|
||||
var (syncData, toEmailSponsorships) = await sutProvider.Sut.SyncOrganization(sponsoringOrganization, new[]
|
||||
{
|
||||
new OrganizationSponsorshipData(existingSponsorship),
|
||||
new OrganizationSponsorshipData(newSponsorship),
|
||||
});
|
||||
|
||||
// Assert
|
||||
// Should have updated the cloud copy for each item given
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.Received(1)
|
||||
.UpsertManyAsync(Arg.Is<IEnumerable<OrganizationSponsorship>>(sponsorships => sponsorships.Count() == 2));
|
||||
|
||||
// Neither were marked as delete, should not have deleted
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.DeleteManyAsync(default);
|
||||
|
||||
// Only one sponsorship was new so it should only send one
|
||||
Assert.Single(toEmailSponsorships);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SyncOrganization_TwoToDelete_OneCanDelete_Success(SutProvider<CloudSyncSponsorshipsCommand> sutProvider,
|
||||
Organization sponsoringOrganization, OrganizationSponsorship canDeleteSponsorship, OrganizationSponsorship cannotDeleteSponsorship)
|
||||
{
|
||||
// Arrange
|
||||
sponsoringOrganization.PlanType = PlanType.EnterpriseAnnually;
|
||||
|
||||
canDeleteSponsorship.ToDelete = true;
|
||||
canDeleteSponsorship.SponsoredOrganizationId = null;
|
||||
|
||||
cannotDeleteSponsorship.ToDelete = true;
|
||||
cannotDeleteSponsorship.SponsoredOrganizationId = Guid.NewGuid();
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetManyBySponsoringOrganizationAsync(sponsoringOrganization.Id)
|
||||
.Returns(new List<OrganizationSponsorship>
|
||||
{
|
||||
canDeleteSponsorship,
|
||||
cannotDeleteSponsorship,
|
||||
});
|
||||
|
||||
// Act
|
||||
var (syncData, toEmailSponsorships) = await sutProvider.Sut.SyncOrganization(sponsoringOrganization, new[]
|
||||
{
|
||||
new OrganizationSponsorshipData(canDeleteSponsorship),
|
||||
new OrganizationSponsorshipData(cannotDeleteSponsorship),
|
||||
});
|
||||
|
||||
// Assert
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.Received(1)
|
||||
.UpsertManyAsync(Arg.Is<IEnumerable<OrganizationSponsorship>>(sponsorships => sponsorships.Count() == 2));
|
||||
|
||||
// Deletes the sponsorship that had delete requested and is not sponsoring an org
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.Received(1)
|
||||
.DeleteManyAsync(Arg.Is<IEnumerable<Guid>>(toDeleteIds =>
|
||||
toDeleteIds.Count() == 1 && toDeleteIds.ElementAt(0) == canDeleteSponsorship.Id));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SyncOrganization_BadData_DoesNotSave(SutProvider<CloudSyncSponsorshipsCommand> sutProvider,
|
||||
Organization sponsoringOrganization, OrganizationSponsorship badOrganizationSponsorship)
|
||||
{
|
||||
sponsoringOrganization.PlanType = PlanType.EnterpriseAnnually;
|
||||
|
||||
badOrganizationSponsorship.ToDelete = true;
|
||||
badOrganizationSponsorship.LastSyncDate = null;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetManyBySponsoringOrganizationAsync(sponsoringOrganization.Id)
|
||||
.Returns(new List<OrganizationSponsorship>());
|
||||
|
||||
var (syncData, toEmailSponsorships) = await sutProvider.Sut.SyncOrganization(sponsoringOrganization, new[]
|
||||
{
|
||||
new OrganizationSponsorshipData(badOrganizationSponsorship),
|
||||
});
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertManyAsync(default);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.DeleteManyAsync(default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SyncOrganization_OrgDisabledForFourMonths_DoesNotSave(SutProvider<CloudSyncSponsorshipsCommand> sutProvider,
|
||||
Organization sponsoringOrganization, OrganizationSponsorship organizationSponsorship)
|
||||
{
|
||||
sponsoringOrganization.PlanType = PlanType.EnterpriseAnnually;
|
||||
sponsoringOrganization.Enabled = false;
|
||||
sponsoringOrganization.ExpirationDate = DateTime.UtcNow.AddDays(-120);
|
||||
|
||||
organizationSponsorship.ToDelete = false;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetManyBySponsoringOrganizationAsync(sponsoringOrganization.Id)
|
||||
.Returns(new List<OrganizationSponsorship>());
|
||||
|
||||
var (syncData, toEmailSponsorships) = await sutProvider.Sut.SyncOrganization(sponsoringOrganization, new[]
|
||||
{
|
||||
new OrganizationSponsorshipData(organizationSponsorship),
|
||||
});
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertManyAsync(default);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.DeleteManyAsync(default);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud
|
||||
{
|
||||
[SutProviderCustomize]
|
||||
public class OrganizationSponsorshipRenewCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task UpdateExpirationDate_UpdatesValidUntil(OrganizationSponsorship sponsorship, DateTime expireDate,
|
||||
SutProvider<OrganizationSponsorshipRenewCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().GetBySponsoredOrganizationIdAsync(sponsorship.SponsoredOrganizationId.Value).Returns(sponsorship);
|
||||
|
||||
await sutProvider.Sut.UpdateExpirationDateAsync(sponsorship.SponsoredOrganizationId.Value, expireDate);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().Received(1)
|
||||
.UpsertAsync(sponsorship);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
|
||||
using Bit.Core.Test.AutoFixture.OrganizationSponsorshipFixtures;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise
|
||||
{
|
||||
[SutProviderCustomize]
|
||||
[OrganizationSponsorshipCustomize]
|
||||
public class RemoveSponsorshipCommandTests : CancelSponsorshipCommandTestsBase
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RemoveSponsorship_SponsoredOrgNull_ThrowsBadRequest(OrganizationSponsorship sponsorship,
|
||||
SutProvider<RemoveSponsorshipCommand> sutProvider)
|
||||
{
|
||||
sponsorship.SponsoredOrganizationId = null;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RemoveSponsorshipAsync(sponsorship));
|
||||
|
||||
Assert.Contains("The requested organization is not currently being sponsored.", exception.Message);
|
||||
Assert.False(sponsorship.ToDelete);
|
||||
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
|
||||
await AssertDidNotUpdateSponsorshipAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RemoveSponsorship_SponsorshipNotFound_ThrowsBadRequest(SutProvider<RemoveSponsorshipCommand> sutProvider)
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RemoveSponsorshipAsync(null));
|
||||
|
||||
Assert.Contains("The requested organization is not currently being sponsored.", exception.Message);
|
||||
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
|
||||
await AssertDidNotUpdateSponsorshipAsync(sutProvider);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.AutoFixture.OrganizationSponsorshipFixtures;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise
|
||||
{
|
||||
[SutProviderCustomize]
|
||||
[OrganizationSponsorshipCustomize]
|
||||
public class SendSponsorshipOfferCommandTests : FamiliesForEnterpriseTestsBase
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SendSponsorshipOffer_SendSponsorshipOfferAsync_ExistingAccount_Success(OrganizationSponsorship sponsorship, string sponsoringOrgName, User user, SutProvider<SendSponsorshipOfferCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IUserRepository>().GetByEmailAsync(sponsorship.OfferedToEmail).Returns(user);
|
||||
|
||||
await sutProvider.Sut.SendSponsorshipOfferAsync(sponsorship, sponsoringOrgName);
|
||||
|
||||
await sutProvider.GetDependency<IMailService>().Received(1).SendFamiliesForEnterpriseOfferEmailAsync(sponsoringOrgName, sponsorship.OfferedToEmail, true, Arg.Any<string>());
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SendSponsorshipOffer_SendSponsorshipOfferAsync_NewAccount_Success(OrganizationSponsorship sponsorship, string sponsoringOrgName, SutProvider<SendSponsorshipOfferCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IUserRepository>().GetByEmailAsync(sponsorship.OfferedToEmail).Returns((User)null);
|
||||
|
||||
await sutProvider.Sut.SendSponsorshipOfferAsync(sponsorship, sponsoringOrgName);
|
||||
|
||||
await sutProvider.GetDependency<IMailService>().Received(1).SendFamiliesForEnterpriseOfferEmailAsync(sponsoringOrgName, sponsorship.OfferedToEmail, false, Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ResendSponsorshipOffer_SponsoringOrgNotFound_ThrowsBadRequest(
|
||||
OrganizationUser orgUser, OrganizationSponsorship sponsorship,
|
||||
SutProvider<SendSponsorshipOfferCommand> sutProvider)
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.SendSponsorshipOfferAsync(null, orgUser, sponsorship));
|
||||
|
||||
Assert.Contains("Cannot find the requested sponsoring organization.", exception.Message);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SendFamiliesForEnterpriseOfferEmailAsync(default, default, default, default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ResendSponsorshipOffer_SponsoringOrgUserNotFound_ThrowsBadRequest(Organization org,
|
||||
OrganizationSponsorship sponsorship, SutProvider<SendSponsorshipOfferCommand> sutProvider)
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.SendSponsorshipOfferAsync(org, null, sponsorship));
|
||||
|
||||
Assert.Contains("Only confirmed users can sponsor other organizations.", exception.Message);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SendFamiliesForEnterpriseOfferEmailAsync(default, default, default, default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
[BitMemberAutoData(nameof(NonConfirmedOrganizationUsersStatuses))]
|
||||
public async Task ResendSponsorshipOffer_SponsoringOrgUserNotConfirmed_ThrowsBadRequest(OrganizationUserStatusType status,
|
||||
Organization org, OrganizationUser orgUser, OrganizationSponsorship sponsorship,
|
||||
SutProvider<SendSponsorshipOfferCommand> sutProvider)
|
||||
{
|
||||
orgUser.Status = status;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.SendSponsorshipOfferAsync(org, orgUser, sponsorship));
|
||||
|
||||
Assert.Contains("Only confirmed users can sponsor other organizations.", exception.Message);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SendFamiliesForEnterpriseOfferEmailAsync(default, default, default, default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ResendSponsorshipOffer_SponsorshipNotFound_ThrowsBadRequest(Organization org,
|
||||
OrganizationUser orgUser,
|
||||
SutProvider<SendSponsorshipOfferCommand> sutProvider)
|
||||
{
|
||||
orgUser.Status = OrganizationUserStatusType.Confirmed;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.SendSponsorshipOfferAsync(org, orgUser, null));
|
||||
|
||||
Assert.Contains("Cannot find an outstanding sponsorship offer for this organization.", exception.Message);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SendFamiliesForEnterpriseOfferEmailAsync(default, default, default, default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ResendSponsorshipOffer_NoOfferToEmail_ThrowsBadRequest(Organization org,
|
||||
OrganizationUser orgUser, OrganizationSponsorship sponsorship,
|
||||
SutProvider<SendSponsorshipOfferCommand> sutProvider)
|
||||
{
|
||||
orgUser.Status = OrganizationUserStatusType.Confirmed;
|
||||
sponsorship.OfferedToEmail = null;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.SendSponsorshipOfferAsync(org, orgUser, sponsorship));
|
||||
|
||||
Assert.Contains("Cannot find an outstanding sponsorship offer for this organization.", exception.Message);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SendFamiliesForEnterpriseOfferEmailAsync(default, default, default, default);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.AutoFixture.OrganizationSponsorshipFixtures;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud
|
||||
{
|
||||
[SutProviderCustomize]
|
||||
[OrganizationSponsorshipCustomize]
|
||||
public class SetUpSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SetUpSponsorship_SponsorshipNotFound_ThrowsBadRequest(Organization org,
|
||||
SutProvider<SetUpSponsorshipCommand> sutProvider)
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.SetUpSponsorshipAsync(null, org));
|
||||
|
||||
Assert.Contains("No unredeemed sponsorship offer exists for you.", exception.Message);
|
||||
await AssertDidNotSetUpAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SetUpSponsorship_OrgAlreadySponsored_ThrowsBadRequest(Organization org,
|
||||
OrganizationSponsorship sponsorship, OrganizationSponsorship existingSponsorship,
|
||||
SutProvider<SetUpSponsorshipCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetBySponsoredOrganizationIdAsync(org.Id).Returns(existingSponsorship);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.SetUpSponsorshipAsync(sponsorship, org));
|
||||
|
||||
Assert.Contains("Cannot redeem a sponsorship offer for an organization that is already sponsored. Revoke existing sponsorship first.", exception.Message);
|
||||
await AssertDidNotSetUpAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitMemberAutoData(nameof(FamiliesPlanTypes))]
|
||||
public async Task SetUpSponsorship_TooLongSinceLastSync_ThrowsBadRequest(PlanType planType, Organization org,
|
||||
OrganizationSponsorship sponsorship,
|
||||
SutProvider<SetUpSponsorshipCommand> sutProvider)
|
||||
{
|
||||
org.PlanType = planType;
|
||||
sponsorship.LastSyncDate = DateTime.UtcNow.AddDays(-365);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.SetUpSponsorshipAsync(sponsorship, org));
|
||||
|
||||
Assert.Contains("This sponsorship offer is more than 6 months old and has expired.", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.Received(1)
|
||||
.DeleteAsync(sponsorship);
|
||||
await AssertDidNotSetUpAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitMemberAutoData(nameof(NonFamiliesPlanTypes))]
|
||||
public async Task SetUpSponsorship_OrgNotFamiles_ThrowsBadRequest(PlanType planType,
|
||||
OrganizationSponsorship sponsorship, Organization org,
|
||||
SutProvider<SetUpSponsorshipCommand> sutProvider)
|
||||
{
|
||||
org.PlanType = planType;
|
||||
sponsorship.LastSyncDate = DateTime.UtcNow;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.SetUpSponsorshipAsync(sponsorship, org));
|
||||
|
||||
Assert.Contains("Can only redeem sponsorship offer on families organizations.", exception.Message);
|
||||
await AssertDidNotSetUpAsync(sutProvider);
|
||||
}
|
||||
|
||||
private static async Task AssertDidNotSetUpAsync(SutProvider<SetUpSponsorshipCommand> sutProvider)
|
||||
{
|
||||
await sutProvider.GetDependency<IPaymentService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SponsorOrganizationAsync(default, default);
|
||||
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud
|
||||
{
|
||||
[SutProviderCustomize]
|
||||
public class ValidateBillingSyncKeyCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateBillingSyncKeyAsync_NullOrganization_Throws(SutProvider<ValidateBillingSyncKeyCommand> sutProvider)
|
||||
{
|
||||
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.ValidateBillingSyncKeyAsync(null, null));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData((string)null)]
|
||||
[BitAutoData("")]
|
||||
[BitAutoData(" ")]
|
||||
public async Task ValidateBillingSyncKeyAsync_BadString_ReturnsFalse(string billingSyncKey, SutProvider<ValidateBillingSyncKeyCommand> sutProvider)
|
||||
{
|
||||
Assert.False(await sutProvider.Sut.ValidateBillingSyncKeyAsync(new Organization(), billingSyncKey));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateBillingSyncKeyAsync_KeyEquals_ReturnsTrue(SutProvider<ValidateBillingSyncKeyCommand> sutProvider,
|
||||
Organization organization, OrganizationApiKey orgApiKey, string billingSyncKey)
|
||||
{
|
||||
orgApiKey.ApiKey = billingSyncKey;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationApiKeyRepository>()
|
||||
.GetManyByOrganizationIdTypeAsync(organization.Id, OrganizationApiKeyType.BillingSync)
|
||||
.Returns(new[] { orgApiKey });
|
||||
|
||||
Assert.True(await sutProvider.Sut.ValidateBillingSyncKeyAsync(organization, billingSyncKey));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateBillingSyncKeyAsync_KeyDoesNotEqual_ReturnsFalse(SutProvider<ValidateBillingSyncKeyCommand> sutProvider,
|
||||
Organization organization, OrganizationApiKey orgApiKey, string billingSyncKey)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationApiKeyRepository>()
|
||||
.GetManyByOrganizationIdTypeAsync(organization.Id, OrganizationApiKeyType.BillingSync)
|
||||
.Returns(new[] { orgApiKey });
|
||||
|
||||
Assert.False(await sutProvider.Sut.ValidateBillingSyncKeyAsync(organization, billingSyncKey));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Business.Tokenables;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Tokens;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud
|
||||
{
|
||||
[SutProviderCustomize]
|
||||
public class ValidateRedemptionTokenCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateRedemptionTokenAsync_CannotUnprotect_ReturnsFalse(SutProvider<ValidateRedemptionTokenCommand> sutProvider,
|
||||
string encryptedString)
|
||||
{
|
||||
sutProvider
|
||||
.GetDependency<IDataProtectorTokenFactory<OrganizationSponsorshipOfferTokenable>>()
|
||||
.TryUnprotect(encryptedString, out _)
|
||||
.Returns(call =>
|
||||
{
|
||||
call[1] = null;
|
||||
return false;
|
||||
});
|
||||
|
||||
var (valid, sponsorship) = await sutProvider.Sut.ValidateRedemptionTokenAsync(encryptedString, null);
|
||||
Assert.False(valid);
|
||||
Assert.Null(sponsorship);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateRedemptionTokenAsync_NoSponsorship_ReturnsFalse(SutProvider<ValidateRedemptionTokenCommand> sutProvider,
|
||||
string encryptedString, OrganizationSponsorshipOfferTokenable tokenable)
|
||||
{
|
||||
sutProvider
|
||||
.GetDependency<IDataProtectorTokenFactory<OrganizationSponsorshipOfferTokenable>>()
|
||||
.TryUnprotect(encryptedString, out _)
|
||||
.Returns(call =>
|
||||
{
|
||||
call[1] = tokenable;
|
||||
return true;
|
||||
});
|
||||
|
||||
var (valid, sponsorship) = await sutProvider.Sut.ValidateRedemptionTokenAsync(encryptedString, "test@email.com");
|
||||
Assert.False(valid);
|
||||
Assert.Null(sponsorship);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateRedemptionTokenAsync_ValidSponsorship_ReturnsFalse(SutProvider<ValidateRedemptionTokenCommand> sutProvider,
|
||||
string encryptedString, string email, OrganizationSponsorshipOfferTokenable tokenable)
|
||||
{
|
||||
tokenable.Email = email;
|
||||
|
||||
sutProvider
|
||||
.GetDependency<IDataProtectorTokenFactory<OrganizationSponsorshipOfferTokenable>>()
|
||||
.TryUnprotect(encryptedString, out _)
|
||||
.Returns(call =>
|
||||
{
|
||||
call[1] = tokenable;
|
||||
return true;
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetByIdAsync(tokenable.Id)
|
||||
.Returns(new OrganizationSponsorship
|
||||
{
|
||||
Id = tokenable.Id,
|
||||
PlanSponsorshipType = PlanSponsorshipType.FamiliesForEnterprise,
|
||||
OfferedToEmail = email
|
||||
});
|
||||
|
||||
var (valid, sponsorship) = await sutProvider.Sut
|
||||
.ValidateRedemptionTokenAsync(encryptedString, email);
|
||||
|
||||
Assert.True(valid);
|
||||
Assert.NotNull(sponsorship);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,256 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Test.AutoFixture.OrganizationSponsorshipFixtures;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud
|
||||
{
|
||||
[SutProviderCustomize]
|
||||
[OrganizationSponsorshipCustomize]
|
||||
public class ValidateSponsorshipCommandTests : CancelSponsorshipCommandTestsBase
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateSponsorshipAsync_NoSponsoredOrg_EarlyReturn(Guid sponsoredOrgId,
|
||||
SutProvider<ValidateSponsorshipCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrgId).Returns((Organization)null);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrgId);
|
||||
|
||||
Assert.False(result);
|
||||
await AssertDidNotRemoveSponsoredPaymentAsync(sutProvider);
|
||||
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateSponsorshipAsync_NoExistingSponsorship_UpdatesStripePlan(Organization sponsoredOrg,
|
||||
SutProvider<ValidateSponsorshipCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
|
||||
|
||||
Assert.False(result);
|
||||
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, null, sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateSponsorshipAsync_SponsoringOrgDefault_UpdatesStripePlan(Organization sponsoredOrg,
|
||||
OrganizationSponsorship existingSponsorship, SutProvider<ValidateSponsorshipCommand> sutProvider)
|
||||
{
|
||||
existingSponsorship.SponsoringOrganizationId = default;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
|
||||
|
||||
Assert.False(result);
|
||||
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
|
||||
await AssertDeletedSponsorshipAsync(existingSponsorship, sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateSponsorshipAsync_SponsoringOrgUserDefault_UpdatesStripePlan(Organization sponsoredOrg,
|
||||
OrganizationSponsorship existingSponsorship, SutProvider<ValidateSponsorshipCommand> sutProvider)
|
||||
{
|
||||
existingSponsorship.SponsoringOrganizationUserId = default;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
|
||||
|
||||
Assert.False(result);
|
||||
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
|
||||
await AssertDeletedSponsorshipAsync(existingSponsorship, sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateSponsorshipAsync_SponsorshipTypeNull_UpdatesStripePlan(Organization sponsoredOrg,
|
||||
OrganizationSponsorship existingSponsorship, SutProvider<ValidateSponsorshipCommand> sutProvider)
|
||||
{
|
||||
existingSponsorship.PlanSponsorshipType = null;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
|
||||
|
||||
Assert.False(result);
|
||||
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
|
||||
await AssertDeletedSponsorshipAsync(existingSponsorship, sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateSponsorshipAsync_SponsoringOrgNotFound_UpdatesStripePlan(Organization sponsoredOrg,
|
||||
OrganizationSponsorship existingSponsorship, SutProvider<ValidateSponsorshipCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
|
||||
|
||||
Assert.False(result);
|
||||
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
|
||||
await AssertDeletedSponsorshipAsync(existingSponsorship, sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitMemberAutoData(nameof(NonEnterprisePlanTypes))]
|
||||
public async Task ValidateSponsorshipAsync_SponsoringOrgNotEnterprise_UpdatesStripePlan(PlanType planType,
|
||||
Organization sponsoredOrg, OrganizationSponsorship existingSponsorship, Organization sponsoringOrg,
|
||||
SutProvider<ValidateSponsorshipCommand> sutProvider)
|
||||
{
|
||||
sponsoringOrg.PlanType = planType;
|
||||
existingSponsorship.SponsoringOrganizationId = sponsoringOrg.Id;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoringOrg.Id).Returns(sponsoringOrg);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
|
||||
|
||||
Assert.False(result);
|
||||
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
|
||||
await AssertDeletedSponsorshipAsync(existingSponsorship, sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitMemberAutoData(nameof(EnterprisePlanTypes))]
|
||||
public async Task ValidateSponsorshipAsync_SponsoringOrgDisabledLongerThanGrace_UpdatesStripePlan(PlanType planType,
|
||||
Organization sponsoredOrg, OrganizationSponsorship existingSponsorship, Organization sponsoringOrg,
|
||||
SutProvider<ValidateSponsorshipCommand> sutProvider)
|
||||
{
|
||||
sponsoringOrg.PlanType = planType;
|
||||
sponsoringOrg.Enabled = false;
|
||||
sponsoringOrg.ExpirationDate = DateTime.UtcNow.AddDays(-100);
|
||||
existingSponsorship.SponsoringOrganizationId = sponsoringOrg.Id;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoringOrg.Id).Returns(sponsoringOrg);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
|
||||
|
||||
Assert.False(result);
|
||||
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
|
||||
await AssertDeletedSponsorshipAsync(existingSponsorship, sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[OrganizationSponsorshipCustomize(ToDelete = true)]
|
||||
[BitMemberAutoData(nameof(EnterprisePlanTypes))]
|
||||
public async Task ValidateSponsorshipAsync_ToDeleteSponsorship_IsInvalid(PlanType planType,
|
||||
Organization sponsoredOrg, OrganizationSponsorship sponsorship, Organization sponsoringOrg,
|
||||
SutProvider<ValidateSponsorshipCommand> sutProvider)
|
||||
{
|
||||
sponsoringOrg.PlanType = planType;
|
||||
sponsoringOrg.Enabled = true;
|
||||
sponsorship.SponsoringOrganizationId = sponsoringOrg.Id;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(sponsorship);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoringOrg.Id).Returns(sponsoringOrg);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
|
||||
|
||||
Assert.False(result);
|
||||
|
||||
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, sponsorship, sutProvider);
|
||||
await AssertDeletedSponsorshipAsync(sponsorship, sutProvider);
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[BitMemberAutoData(nameof(EnterprisePlanTypes))]
|
||||
public async Task ValidateSponsorshipAsync_SponsoringOrgDisabledUnknownTime_UpdatesStripePlan(PlanType planType,
|
||||
Organization sponsoredOrg, OrganizationSponsorship existingSponsorship, Organization sponsoringOrg,
|
||||
SutProvider<ValidateSponsorshipCommand> sutProvider)
|
||||
{
|
||||
sponsoringOrg.PlanType = planType;
|
||||
sponsoringOrg.Enabled = false;
|
||||
sponsoringOrg.ExpirationDate = null;
|
||||
existingSponsorship.SponsoringOrganizationId = sponsoringOrg.Id;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoringOrg.Id).Returns(sponsoringOrg);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
|
||||
|
||||
Assert.False(result);
|
||||
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
|
||||
await AssertRemovedSponsorshipAsync(existingSponsorship, sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitMemberAutoData(nameof(EnterprisePlanTypes))]
|
||||
public async Task ValidateSponsorshipAsync_SponsoringOrgDisabledLessThanGrace_Valid(PlanType planType,
|
||||
Organization sponsoredOrg, OrganizationSponsorship existingSponsorship, Organization sponsoringOrg,
|
||||
SutProvider<ValidateSponsorshipCommand> sutProvider)
|
||||
{
|
||||
sponsoringOrg.PlanType = planType;
|
||||
sponsoringOrg.Enabled = true;
|
||||
sponsoringOrg.ExpirationDate = DateTime.UtcNow.AddDays(-1);
|
||||
existingSponsorship.SponsoringOrganizationId = sponsoringOrg.Id;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoringOrg.Id).Returns(sponsoringOrg);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
|
||||
|
||||
Assert.True(result);
|
||||
|
||||
await AssertDidNotRemoveSponsoredPaymentAsync(sutProvider);
|
||||
await AssertDidNotRemoveSponsorshipAsync(sutProvider);
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[BitMemberAutoData(nameof(EnterprisePlanTypes))]
|
||||
public async Task ValidateSponsorshipAsync_Valid(PlanType planType,
|
||||
Organization sponsoredOrg, OrganizationSponsorship existingSponsorship, Organization sponsoringOrg,
|
||||
SutProvider<ValidateSponsorshipCommand> sutProvider)
|
||||
{
|
||||
sponsoringOrg.PlanType = planType;
|
||||
sponsoringOrg.Enabled = true;
|
||||
existingSponsorship.SponsoringOrganizationId = sponsoringOrg.Id;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoringOrg.Id).Returns(sponsoringOrg);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
|
||||
|
||||
Assert.True(result);
|
||||
|
||||
await AssertDidNotRemoveSponsoredPaymentAsync(sutProvider);
|
||||
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,181 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.AutoFixture.OrganizationSponsorshipFixtures;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ExceptionExtensions;
|
||||
using NSubstitute.ReturnsExtensions;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise
|
||||
{
|
||||
[SutProviderCustomize]
|
||||
public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
|
||||
{
|
||||
private bool SponsorshipValidator(OrganizationSponsorship sponsorship, OrganizationSponsorship expectedSponsorship)
|
||||
{
|
||||
try
|
||||
{
|
||||
AssertHelper.AssertPropertyEqual(sponsorship, expectedSponsorship, nameof(OrganizationSponsorship.Id));
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateSponsorship_OfferedToNotFound_ThrowsBadRequest(OrganizationUser orgUser, SutProvider<CreateSponsorshipCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId.Value).ReturnsNull();
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.CreateSponsorshipAsync(null, orgUser, PlanSponsorshipType.FamiliesForEnterprise, default, default));
|
||||
|
||||
Assert.Contains("Cannot offer a Families Organization Sponsorship to yourself. Choose a different email.", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
|
||||
.CreateAsync(default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateSponsorship_OfferedToSelf_ThrowsBadRequest(OrganizationUser orgUser, string sponsoredEmail, User user, SutProvider<CreateSponsorshipCommand> sutProvider)
|
||||
{
|
||||
user.Email = sponsoredEmail;
|
||||
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId.Value).Returns(user);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.CreateSponsorshipAsync(null, orgUser, PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, default));
|
||||
|
||||
Assert.Contains("Cannot offer a Families Organization Sponsorship to yourself. Choose a different email.", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
|
||||
.CreateAsync(default);
|
||||
}
|
||||
|
||||
[Theory, BitMemberAutoData(nameof(NonEnterprisePlanTypes))]
|
||||
public async Task CreateSponsorship_BadSponsoringOrgPlan_ThrowsBadRequest(PlanType sponsoringOrgPlan,
|
||||
Organization org, OrganizationUser orgUser, User user, SutProvider<CreateSponsorshipCommand> sutProvider)
|
||||
{
|
||||
org.PlanType = sponsoringOrgPlan;
|
||||
orgUser.Status = OrganizationUserStatusType.Confirmed;
|
||||
|
||||
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId.Value).Returns(user);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.CreateSponsorshipAsync(org, orgUser, PlanSponsorshipType.FamiliesForEnterprise, default, default));
|
||||
|
||||
Assert.Contains("Specified Organization cannot sponsor other organizations.", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
|
||||
.CreateAsync(default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitMemberAutoData(nameof(NonConfirmedOrganizationUsersStatuses))]
|
||||
public async Task CreateSponsorship_BadSponsoringUserStatus_ThrowsBadRequest(
|
||||
OrganizationUserStatusType statusType, Organization org, OrganizationUser orgUser, User user,
|
||||
SutProvider<CreateSponsorshipCommand> sutProvider)
|
||||
{
|
||||
org.PlanType = PlanType.EnterpriseAnnually;
|
||||
orgUser.Status = statusType;
|
||||
|
||||
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId.Value).Returns(user);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.CreateSponsorshipAsync(org, orgUser, PlanSponsorshipType.FamiliesForEnterprise, default, default));
|
||||
|
||||
Assert.Contains("Only confirmed users can sponsor other organizations.", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
|
||||
.CreateAsync(default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[OrganizationSponsorshipCustomize]
|
||||
[BitAutoData]
|
||||
public async Task CreateSponsorship_AlreadySponsoring_Throws(Organization org,
|
||||
OrganizationUser orgUser, User user, OrganizationSponsorship sponsorship,
|
||||
SutProvider<CreateSponsorshipCommand> sutProvider)
|
||||
{
|
||||
org.PlanType = PlanType.EnterpriseAnnually;
|
||||
orgUser.Status = OrganizationUserStatusType.Confirmed;
|
||||
|
||||
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId.Value).Returns(user);
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetBySponsoringOrganizationUserIdAsync(orgUser.Id).Returns(sponsorship);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.CreateSponsorshipAsync(org, orgUser, sponsorship.PlanSponsorshipType.Value, default, default));
|
||||
|
||||
Assert.Contains("Can only sponsor one organization per Organization User.", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
|
||||
.CreateAsync(default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CreateSponsorship_CreatesSponsorship(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser, User user,
|
||||
string sponsoredEmail, string friendlyName, Guid sponsorshipId, SutProvider<CreateSponsorshipCommand> sutProvider)
|
||||
{
|
||||
sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
|
||||
sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed;
|
||||
|
||||
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId.Value).Returns(user);
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().WhenForAnyArgs(x => x.UpsertAsync(default)).Do(callInfo =>
|
||||
{
|
||||
var sponsorship = callInfo.Arg<OrganizationSponsorship>();
|
||||
sponsorship.Id = sponsorshipId;
|
||||
});
|
||||
|
||||
|
||||
await sutProvider.Sut.CreateSponsorshipAsync(sponsoringOrg, sponsoringOrgUser,
|
||||
PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName);
|
||||
|
||||
var expectedSponsorship = new OrganizationSponsorship
|
||||
{
|
||||
Id = sponsorshipId,
|
||||
SponsoringOrganizationId = sponsoringOrg.Id,
|
||||
SponsoringOrganizationUserId = sponsoringOrgUser.Id,
|
||||
FriendlyName = friendlyName,
|
||||
OfferedToEmail = sponsoredEmail,
|
||||
PlanSponsorshipType = PlanSponsorshipType.FamiliesForEnterprise,
|
||||
};
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().Received(1)
|
||||
.UpsertAsync(Arg.Is<OrganizationSponsorship>(s => SponsorshipValidator(s, expectedSponsorship)));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CreateSponsorship_CreateSponsorshipThrows_RevertsDatabase(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser, User user,
|
||||
string sponsoredEmail, string friendlyName, SutProvider<CreateSponsorshipCommand> sutProvider)
|
||||
{
|
||||
sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
|
||||
sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed;
|
||||
|
||||
var expectedException = new Exception();
|
||||
OrganizationSponsorship createdSponsorship = null;
|
||||
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId.Value).Returns(user);
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().UpsertAsync(default).ThrowsForAnyArgs(callInfo =>
|
||||
{
|
||||
createdSponsorship = callInfo.ArgAt<OrganizationSponsorship>(0);
|
||||
createdSponsorship.Id = Guid.NewGuid();
|
||||
return expectedException;
|
||||
});
|
||||
|
||||
var actualException = await Assert.ThrowsAsync<Exception>(() =>
|
||||
sutProvider.Sut.CreateSponsorshipAsync(sponsoringOrg, sponsoringOrgUser,
|
||||
PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName));
|
||||
Assert.Same(expectedException, actualException);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().Received(1)
|
||||
.DeleteAsync(createdSponsorship);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise
|
||||
{
|
||||
public abstract class FamiliesForEnterpriseTestsBase
|
||||
{
|
||||
public static IEnumerable<object[]> EnterprisePlanTypes =>
|
||||
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product == ProductType.Enterprise).Select(p => new object[] { p });
|
||||
|
||||
public static IEnumerable<object[]> NonEnterprisePlanTypes =>
|
||||
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Enterprise).Select(p => new object[] { p });
|
||||
|
||||
public static IEnumerable<object[]> FamiliesPlanTypes =>
|
||||
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product == ProductType.Families).Select(p => new object[] { p });
|
||||
|
||||
public static IEnumerable<object[]> NonFamiliesPlanTypes =>
|
||||
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Families).Select(p => new object[] { p });
|
||||
|
||||
public static IEnumerable<object[]> NonConfirmedOrganizationUsersStatuses =>
|
||||
Enum.GetValues<OrganizationUserStatusType>()
|
||||
.Where(s => s != OrganizationUserStatusType.Confirmed)
|
||||
.Select(s => new object[] { s });
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SelfHosted;
|
||||
using Bit.Core.Test.AutoFixture.OrganizationSponsorshipFixtures;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SelfHosted
|
||||
{
|
||||
[SutProviderCustomize]
|
||||
[OrganizationSponsorshipCustomize]
|
||||
public class SelfHostedRevokeSponsorshipCommandTests : CancelSponsorshipCommandTestsBase
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RevokeSponsorship_NoExistingSponsorship_ThrowsBadRequest(
|
||||
SutProvider<SelfHostedRevokeSponsorshipCommand> sutProvider)
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RevokeSponsorshipAsync(null));
|
||||
|
||||
Assert.Contains("You are not currently sponsoring an organization.", exception.Message);
|
||||
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
|
||||
await AssertDidNotUpdateSponsorshipAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RevokeSponsorship_SponsorshipNotSynced_DeletesSponsorship(OrganizationSponsorship sponsorship,
|
||||
SutProvider<SelfHostedRevokeSponsorshipCommand> sutProvider)
|
||||
{
|
||||
sponsorship.LastSyncDate = null;
|
||||
|
||||
await sutProvider.Sut.RevokeSponsorshipAsync(sponsorship);
|
||||
await AssertDeletedSponsorshipAsync(sponsorship, sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RevokeSponsorship_SponsorshipSynced_MarksForDeletion(OrganizationSponsorship sponsorship,
|
||||
SutProvider<SelfHostedRevokeSponsorshipCommand> sutProvider)
|
||||
{
|
||||
sponsorship.LastSyncDate = DateTime.UtcNow;
|
||||
|
||||
await sutProvider.Sut.RevokeSponsorshipAsync(sponsorship);
|
||||
|
||||
Assert.True(sponsorship.ToDelete);
|
||||
await AssertUpdatedSponsorshipAsync(sponsorship, sutProvider);
|
||||
await AssertDidNotDeleteSponsorshipAsync(sutProvider);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,194 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using AutoFixture;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Api.Response.OrganizationSponsorships;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationSponsorships;
|
||||
using Bit.Core.Models.OrganizationConnectionConfigs;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SelfHosted;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Test.AutoFixture.OrganizationSponsorshipFixtures;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using RichardSzalay.MockHttp;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SelfHosted
|
||||
{
|
||||
|
||||
public class SelfHostedSyncSponsorshipsCommandTests : FamiliesForEnterpriseTestsBase
|
||||
{
|
||||
|
||||
public static SutProvider<SelfHostedSyncSponsorshipsCommand> GetSutProvider(bool enableCloudCommunication = true, string identityResponse = null, string apiResponse = null)
|
||||
{
|
||||
var fixture = new Fixture().WithAutoNSubstitutionsAutoPopulatedProperties();
|
||||
fixture.AddMockHttp();
|
||||
|
||||
var settings = fixture.Create<IGlobalSettings>();
|
||||
settings.SelfHosted = true;
|
||||
settings.EnableCloudCommunication = enableCloudCommunication;
|
||||
|
||||
var apiUri = fixture.Create<Uri>();
|
||||
var identityUri = fixture.Create<Uri>();
|
||||
settings.Installation.ApiUri.Returns(apiUri.ToString());
|
||||
settings.Installation.IdentityUri.Returns(identityUri.ToString());
|
||||
|
||||
var apiHandler = new MockHttpMessageHandler();
|
||||
var identityHandler = new MockHttpMessageHandler();
|
||||
var syncUri = string.Concat(apiUri, "organization/sponsorship/sync");
|
||||
var tokenUri = string.Concat(identityUri, "connect/token");
|
||||
|
||||
apiHandler.When(HttpMethod.Post, syncUri)
|
||||
.Respond("application/json", apiResponse);
|
||||
identityHandler.When(HttpMethod.Post, tokenUri)
|
||||
.Respond("application/json", identityResponse ?? "{\"access_token\":\"string\",\"expires_in\":3600,\"token_type\":\"Bearer\",\"scope\":\"string\"}");
|
||||
|
||||
|
||||
var apiHttp = apiHandler.ToHttpClient();
|
||||
var identityHttp = identityHandler.ToHttpClient();
|
||||
|
||||
var mockHttpClientFactory = Substitute.For<IHttpClientFactory>();
|
||||
mockHttpClientFactory.CreateClient(Arg.Is("client")).Returns(apiHttp);
|
||||
mockHttpClientFactory.CreateClient(Arg.Is("identity")).Returns(identityHttp);
|
||||
|
||||
return new SutProvider<SelfHostedSyncSponsorshipsCommand>(fixture)
|
||||
.SetDependency(settings)
|
||||
.SetDependency(mockHttpClientFactory)
|
||||
.Create();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SyncOrganization_BillingSyncKeyDisabled_ThrowsBadRequest(
|
||||
Guid cloudOrganizationId, OrganizationConnection billingSyncConnection)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
billingSyncConnection.Enabled = false;
|
||||
billingSyncConnection.SetConfig(new BillingSyncConfig
|
||||
{
|
||||
BillingSyncKey = "okslkcslkjf"
|
||||
});
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.SyncOrganization(billingSyncConnection.OrganizationId, cloudOrganizationId, billingSyncConnection));
|
||||
|
||||
Assert.Contains($"Billing Sync Key disabled", exception.Message);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.DeleteManyAsync(default);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertManyAsync(default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SyncOrganization_BillingSyncKeyEmpty_ThrowsBadRequest(
|
||||
Guid cloudOrganizationId, OrganizationConnection billingSyncConnection)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
billingSyncConnection.Config = "";
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.SyncOrganization(billingSyncConnection.OrganizationId, cloudOrganizationId, billingSyncConnection));
|
||||
|
||||
Assert.Contains($"No Billing Sync Key known", exception.Message);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.DeleteManyAsync(default);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertManyAsync(default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SyncOrganization_CloudCommunicationDisabled_EarlyReturn(
|
||||
Guid cloudOrganizationId, OrganizationConnection billingSyncConnection)
|
||||
{
|
||||
var sutProvider = GetSutProvider(false);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.SyncOrganization(billingSyncConnection.OrganizationId, cloudOrganizationId, billingSyncConnection));
|
||||
|
||||
Assert.Contains($"Cloud communication is disabled", exception.Message);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.DeleteManyAsync(default);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertManyAsync(default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[OrganizationSponsorshipCustomize]
|
||||
[BitAutoData]
|
||||
public async Task SyncOrganization_SyncsSponsorships(
|
||||
Guid cloudOrganizationId, OrganizationConnection billingSyncConnection, IEnumerable<OrganizationSponsorship> sponsorships)
|
||||
{
|
||||
var syncJsonResponse = JsonSerializer.Serialize(new OrganizationSponsorshipSyncResponseModel(
|
||||
new OrganizationSponsorshipSyncData
|
||||
{
|
||||
SponsorshipsBatch = sponsorships.Select(o => new OrganizationSponsorshipData(o))
|
||||
}));
|
||||
|
||||
var sutProvider = GetSutProvider(apiResponse: syncJsonResponse);
|
||||
billingSyncConnection.SetConfig(new BillingSyncConfig
|
||||
{
|
||||
BillingSyncKey = "okslkcslkjf"
|
||||
});
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetManyBySponsoringOrganizationAsync(Arg.Any<Guid>()).Returns(sponsorships.ToList());
|
||||
|
||||
await sutProvider.Sut.SyncOrganization(billingSyncConnection.OrganizationId, cloudOrganizationId, billingSyncConnection);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.DeleteManyAsync(default);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.Received(1)
|
||||
.UpsertManyAsync(Arg.Any<IEnumerable<OrganizationSponsorship>>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[OrganizationSponsorshipCustomize(ToDelete = true)]
|
||||
[BitAutoData]
|
||||
public async Task SyncOrganization_DeletesSponsorships(
|
||||
Guid cloudOrganizationId, OrganizationConnection billingSyncConnection, IEnumerable<OrganizationSponsorship> sponsorships)
|
||||
{
|
||||
var syncJsonResponse = JsonSerializer.Serialize(new OrganizationSponsorshipSyncResponseModel(
|
||||
new OrganizationSponsorshipSyncData
|
||||
{
|
||||
SponsorshipsBatch = sponsorships.Select(o => new OrganizationSponsorshipData(o) { CloudSponsorshipRemoved = true })
|
||||
}));
|
||||
|
||||
var sutProvider = GetSutProvider(apiResponse: syncJsonResponse);
|
||||
billingSyncConnection.SetConfig(new BillingSyncConfig
|
||||
{
|
||||
BillingSyncKey = "okslkcslkjf"
|
||||
});
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetManyBySponsoringOrganizationAsync(Arg.Any<Guid>()).Returns(sponsorships.ToList());
|
||||
|
||||
await sutProvider.Sut.SyncOrganization(billingSyncConnection.OrganizationId, cloudOrganizationId, billingSyncConnection);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.Received(1)
|
||||
.DeleteManyAsync(Arg.Any<IEnumerable<Guid>>());
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertManyAsync(default);
|
||||
}
|
||||
}
|
||||
}
|
@ -42,7 +42,6 @@ namespace Bit.Core.Test.Repositories.EntityFramework.EqualityComparers
|
||||
x.ReferenceData.Equals(y.ReferenceData) &&
|
||||
x.Enabled.Equals(y.Enabled) &&
|
||||
x.LicenseKey.Equals(y.LicenseKey) &&
|
||||
x.ApiKey.Equals(y.ApiKey) &&
|
||||
x.TwoFactorProviders.Equals(y.TwoFactorProviders) &&
|
||||
x.ExpirationDate.ToString().Equals(y.ExpirationDate.ToString());
|
||||
}
|
||||
|
@ -8,14 +8,12 @@ namespace Bit.Core.Test.Repositories.EntityFramework.EqualityComparers
|
||||
{
|
||||
public bool Equals(OrganizationSponsorship x, OrganizationSponsorship y)
|
||||
{
|
||||
return x.InstallationId.Equals(y.InstallationId) &&
|
||||
x.SponsoringOrganizationId.Equals(y.SponsoringOrganizationId) &&
|
||||
return x.SponsoringOrganizationId.Equals(y.SponsoringOrganizationId) &&
|
||||
x.SponsoringOrganizationUserId.Equals(y.SponsoringOrganizationUserId) &&
|
||||
x.SponsoredOrganizationId.Equals(y.SponsoredOrganizationId) &&
|
||||
x.OfferedToEmail.Equals(y.OfferedToEmail) &&
|
||||
x.CloudSponsor.Equals(y.CloudSponsor) &&
|
||||
x.TimesRenewedWithoutValidation.Equals(y.TimesRenewedWithoutValidation) &&
|
||||
x.SponsorshipLapsedDate.ToString().Equals(y.SponsorshipLapsedDate.ToString());
|
||||
x.ToDelete.Equals(y.ToDelete) &&
|
||||
x.ValidUntil.ToString().Equals(y.ValidUntil.ToString());
|
||||
}
|
||||
|
||||
public int GetHashCode([DisallowNull] OrganizationSponsorship obj)
|
||||
|
@ -1,10 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Test.AutoFixture;
|
||||
using Bit.Core.Models.Data.Organizations;
|
||||
using Bit.Core.Test.AutoFixture.Attributes;
|
||||
using Bit.Core.Test.AutoFixture.OrganizationFixtures;
|
||||
using Bit.Core.Test.Helpers.Factories;
|
||||
using Bit.Core.Test.Repositories.EntityFramework.EqualityComparers;
|
||||
using Xunit;
|
||||
using EfRepo = Bit.Infrastructure.EntityFramework.Repositories;
|
||||
|
@ -21,7 +21,6 @@ namespace Bit.Core.Test.Repositories.EntityFramework
|
||||
OrganizationSponsorshipCompare equalityComparer,
|
||||
List<EfRepo.OrganizationSponsorshipRepository> suts)
|
||||
{
|
||||
organizationSponsorship.InstallationId = null;
|
||||
organizationSponsorship.SponsoredOrganizationId = null;
|
||||
|
||||
var savedOrganizationSponsorships = new List<OrganizationSponsorship>();
|
||||
@ -56,9 +55,7 @@ namespace Bit.Core.Test.Repositories.EntityFramework
|
||||
SqlRepo.OrganizationSponsorshipRepository sqlOrganizationSponsorshipRepo,
|
||||
OrganizationSponsorshipCompare equalityComparer, List<EfRepo.OrganizationSponsorshipRepository> suts)
|
||||
{
|
||||
postOrganizationSponsorship.InstallationId = null;
|
||||
postOrganizationSponsorship.SponsoredOrganizationId = null;
|
||||
replaceOrganizationSponsorship.InstallationId = null;
|
||||
replaceOrganizationSponsorship.SponsoredOrganizationId = null;
|
||||
|
||||
var savedOrganizationSponsorships = new List<OrganizationSponsorship>();
|
||||
@ -100,7 +97,6 @@ namespace Bit.Core.Test.Repositories.EntityFramework
|
||||
SqlRepo.OrganizationSponsorshipRepository sqlOrganizationSponsorshipRepo,
|
||||
List<EfRepo.OrganizationSponsorshipRepository> suts)
|
||||
{
|
||||
organizationSponsorship.InstallationId = null;
|
||||
organizationSponsorship.SponsoredOrganizationId = null;
|
||||
|
||||
foreach (var (sut, orgRepo) in suts.Zip(efOrgRepos))
|
||||
|
@ -1,49 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Services
|
||||
{
|
||||
[SutProviderCustomize]
|
||||
public class EventServiceTests
|
||||
{
|
||||
private readonly EventService _sut;
|
||||
public static IEnumerable<object[]> InstallationIdTestCases => TestCaseHelper.GetCombinationsOfMultipleLists(
|
||||
new object[] { Guid.NewGuid(), null },
|
||||
Enum.GetValues<EventType>().Select(e => (object)e)
|
||||
).Select(p => p.ToArray());
|
||||
|
||||
private readonly IEventWriteService _eventWriteService;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IProviderUserRepository _providerUserRepository;
|
||||
private readonly IApplicationCacheService _applicationCacheService;
|
||||
private readonly CurrentContext _currentContext;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
|
||||
public EventServiceTests()
|
||||
[Theory]
|
||||
[BitMemberAutoData(nameof(InstallationIdTestCases))]
|
||||
public async Task LogOrganizationEvent_ProvidesInstallationId(Guid? installationId, EventType eventType,
|
||||
Organization organization, SutProvider<EventService> sutProvider)
|
||||
{
|
||||
_eventWriteService = Substitute.For<IEventWriteService>();
|
||||
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
|
||||
_providerUserRepository = Substitute.For<IProviderUserRepository>();
|
||||
_applicationCacheService = Substitute.For<IApplicationCacheService>();
|
||||
_currentContext = new CurrentContext(null);
|
||||
_globalSettings = new GlobalSettings();
|
||||
organization.Enabled = true;
|
||||
organization.UseEvents = true;
|
||||
|
||||
_sut = new EventService(
|
||||
_eventWriteService,
|
||||
_organizationUserRepository,
|
||||
_providerUserRepository,
|
||||
_applicationCacheService,
|
||||
_currentContext,
|
||||
_globalSettings
|
||||
);
|
||||
}
|
||||
sutProvider.GetDependency<ICurrentContext>().InstallationId.Returns(installationId);
|
||||
|
||||
// Remove this test when we add actual tests. It only proves that
|
||||
// we've properly constructed the system under test.
|
||||
[Fact]
|
||||
public void ServiceExists()
|
||||
{
|
||||
Assert.NotNull(_sut);
|
||||
await sutProvider.Sut.LogOrganizationEventAsync(organization, eventType);
|
||||
|
||||
await sutProvider.GetDependency<IEventWriteService>().Received(1).CreateAsync(Arg.Is<IEvent>(e =>
|
||||
e.OrganizationId == organization.Id &&
|
||||
e.Type == eventType &&
|
||||
e.InstallationId == installationId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -115,7 +115,6 @@ namespace Bit.Core.Test.Services
|
||||
{ ("familyUserEmail", typeof(string)), "test@bitwarden.com" },
|
||||
{ ("sponsorEmail", typeof(string)), "test@bitwarden.com" },
|
||||
{ ("familyOrgName", typeof(string)), "Test Org Name" },
|
||||
{ ("orgCanSponsor", typeof(bool)), true },
|
||||
{ ("existingAccount", typeof(bool)), true },
|
||||
{ ("sponsorshipEndDate", typeof(DateTime)), DateTime.UtcNow.AddDays(1)},
|
||||
};
|
||||
|
@ -1,53 +1,64 @@
|
||||
using System;
|
||||
using Bit.Core.Repositories;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using AutoFixture;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Bit.Core.Test.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Services
|
||||
{
|
||||
[SutProviderCustomize]
|
||||
public class LicensingServiceTests
|
||||
{
|
||||
private readonly LicensingService _sut;
|
||||
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IMailService _mailService;
|
||||
private readonly IWebHostEnvironment _hostingEnvironment;
|
||||
private readonly ILogger<LicensingService> _logger;
|
||||
|
||||
public LicensingServiceTests()
|
||||
private static string licenseFilePath(Guid orgId) =>
|
||||
Path.Combine(OrganizationLicenseDirectory.Value, $"{orgId}.json");
|
||||
private static string LicenseDirectory => Path.GetDirectoryName(OrganizationLicenseDirectory.Value);
|
||||
private static Lazy<string> OrganizationLicenseDirectory => new(() =>
|
||||
{
|
||||
_userRepository = Substitute.For<IUserRepository>();
|
||||
_organizationRepository = Substitute.For<IOrganizationRepository>();
|
||||
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
|
||||
_mailService = Substitute.For<IMailService>();
|
||||
_hostingEnvironment = Substitute.For<IWebHostEnvironment>();
|
||||
_logger = Substitute.For<ILogger<LicensingService>>();
|
||||
_globalSettings = new GlobalSettings();
|
||||
var directory = Path.Combine(Path.GetTempPath(), "organization");
|
||||
if (!Directory.Exists(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
return directory;
|
||||
});
|
||||
|
||||
_sut = new LicensingService(
|
||||
_userRepository,
|
||||
_organizationRepository,
|
||||
_organizationUserRepository,
|
||||
_mailService,
|
||||
_hostingEnvironment,
|
||||
_logger,
|
||||
_globalSettings
|
||||
);
|
||||
public static SutProvider<LicensingService> GetSutProvider()
|
||||
{
|
||||
var fixture = new Fixture().WithAutoNSubstitutions();
|
||||
|
||||
var settings = fixture.Create<IGlobalSettings>();
|
||||
settings.LicenseDirectory = LicenseDirectory;
|
||||
settings.SelfHosted = true;
|
||||
|
||||
return new SutProvider<LicensingService>(fixture)
|
||||
.SetDependency(settings)
|
||||
.Create();
|
||||
}
|
||||
|
||||
// Remove this test when we add actual tests. It only proves that
|
||||
// we've properly constructed the system under test.
|
||||
[Fact(Skip = "Needs additional work")]
|
||||
public void ServiceExists()
|
||||
[Theory, BitAutoData, OrganizationLicenseCustomize]
|
||||
public async Task ReadOrganizationLicense(Organization organization, OrganizationLicense license)
|
||||
{
|
||||
Assert.NotNull(_sut);
|
||||
var sutProvider = GetSutProvider();
|
||||
|
||||
File.WriteAllText(licenseFilePath(organization.Id), JsonSerializer.Serialize(license));
|
||||
|
||||
var actual = await sutProvider.Sut.ReadOrganizationLicenseAsync(organization);
|
||||
try
|
||||
{
|
||||
Assert.Equal(JsonSerializer.Serialize(license), JsonSerializer.Serialize(actual));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(OrganizationLicenseDirectory.Value, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
@ -13,6 +14,7 @@ namespace Bit.Core.Test.Services
|
||||
{
|
||||
private readonly MultiServicePushNotificationService _sut;
|
||||
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly IDeviceRepository _deviceRepository;
|
||||
private readonly IInstallationDeviceRepository _installationDeviceRepository;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
@ -23,6 +25,7 @@ namespace Bit.Core.Test.Services
|
||||
|
||||
public MultiServicePushNotificationServiceTests()
|
||||
{
|
||||
_httpFactory = Substitute.For<IHttpClientFactory>();
|
||||
_deviceRepository = Substitute.For<IDeviceRepository>();
|
||||
_installationDeviceRepository = Substitute.For<IInstallationDeviceRepository>();
|
||||
_globalSettings = new GlobalSettings();
|
||||
@ -32,6 +35,7 @@ namespace Bit.Core.Test.Services
|
||||
_hubLogger = Substitute.For<ILogger<NotificationsApiPushNotificationService>>();
|
||||
|
||||
_sut = new MultiServicePushNotificationService(
|
||||
_httpFactory,
|
||||
_deviceRepository,
|
||||
_installationDeviceRepository,
|
||||
_globalSettings,
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
@ -12,17 +13,20 @@ namespace Bit.Core.Test.Services
|
||||
{
|
||||
private readonly NotificationsApiPushNotificationService _sut;
|
||||
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly ILogger<NotificationsApiPushNotificationService> _logger;
|
||||
|
||||
public NotificationsApiPushNotificationServiceTests()
|
||||
{
|
||||
_httpFactory = Substitute.For<IHttpClientFactory>();
|
||||
_globalSettings = new GlobalSettings();
|
||||
_httpContextAccessor = Substitute.For<IHttpContextAccessor>();
|
||||
_logger = Substitute.For<ILogger<NotificationsApiPushNotificationService>>();
|
||||
|
||||
_sut = new NotificationsApiPushNotificationService(
|
||||
_httpFactory,
|
||||
_globalSettings,
|
||||
_httpContextAccessor,
|
||||
_logger
|
||||
|
@ -9,6 +9,7 @@ using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
@ -66,7 +67,6 @@ namespace Bit.Core.Test.Services
|
||||
.CreateManyAsync(Arg.Is<IEnumerable<OrganizationUser>>(users => users.Count() == expectedNewUsersCount));
|
||||
await sutProvider.GetDependency<IMailService>().Received(1)
|
||||
.BulkSendOrganizationInviteEmailAsync(org.Name,
|
||||
Arg.Any<bool>(),
|
||||
Arg.Is<IEnumerable<(OrganizationUser, ExpiringToken)>>(messages => messages.Count() == expectedNewUsersCount));
|
||||
|
||||
// Send events
|
||||
@ -125,7 +125,6 @@ namespace Bit.Core.Test.Services
|
||||
.CreateManyAsync(Arg.Is<IEnumerable<OrganizationUser>>(users => users.Count() == expectedNewUsersCount));
|
||||
await sutProvider.GetDependency<IMailService>().Received(1)
|
||||
.BulkSendOrganizationInviteEmailAsync(org.Name,
|
||||
Arg.Any<bool>(),
|
||||
Arg.Is<IEnumerable<(OrganizationUser, ExpiringToken)>>(messages => messages.Count() == expectedNewUsersCount));
|
||||
|
||||
// Sent events
|
||||
@ -362,7 +361,7 @@ namespace Bit.Core.Test.Services
|
||||
await sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, invites);
|
||||
|
||||
await sutProvider.GetDependency<IMailService>().Received(1)
|
||||
.BulkSendOrganizationInviteEmailAsync(organization.Name, Arg.Any<bool>(),
|
||||
.BulkSendOrganizationInviteEmailAsync(organization.Name,
|
||||
Arg.Is<IEnumerable<(OrganizationUser, ExpiringToken)>>(v => v.Count() == invites.SelectMany(i => i.invite.Emails).Count()));
|
||||
}
|
||||
|
||||
|
@ -1,680 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ExceptionExtensions;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Services
|
||||
{
|
||||
[SutProviderCustomize]
|
||||
public class OrganizationSponsorshipServiceTests
|
||||
{
|
||||
private bool SponsorshipValidator(OrganizationSponsorship sponsorship, OrganizationSponsorship expectedSponsorship)
|
||||
{
|
||||
try
|
||||
{
|
||||
AssertHelper.AssertPropertyEqual(sponsorship, expectedSponsorship, nameof(OrganizationSponsorship.Id));
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> EnterprisePlanTypes =>
|
||||
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product == ProductType.Enterprise).Select(p => new object[] { p });
|
||||
|
||||
public static IEnumerable<object[]> NonEnterprisePlanTypes =>
|
||||
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Enterprise).Select(p => new object[] { p });
|
||||
|
||||
public static IEnumerable<object[]> NonFamiliesPlanTypes =>
|
||||
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Families).Select(p => new object[] { p });
|
||||
|
||||
public static IEnumerable<object[]> NonConfirmedOrganizationUsersStatuses =>
|
||||
Enum.GetValues<OrganizationUserStatusType>()
|
||||
.Where(s => s != OrganizationUserStatusType.Confirmed)
|
||||
.Select(s => new object[] { s });
|
||||
|
||||
[Theory]
|
||||
[BitMemberAutoData(nameof(NonEnterprisePlanTypes))]
|
||||
public async Task OfferSponsorship_BadSponsoringOrgPlan_ThrowsBadRequest(PlanType sponsoringOrgPlan,
|
||||
Organization org, OrganizationUser orgUser, SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
org.PlanType = sponsoringOrgPlan;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(org.Id).Returns(org);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.OfferSponsorshipAsync(org, orgUser, PlanSponsorshipType.FamiliesForEnterprise, default, default, "test@bitwarden.com"));
|
||||
|
||||
Assert.Contains("Specified Organization cannot sponsor other organizations.", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
|
||||
.CreateAsync(default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitMemberAutoData(nameof(NonConfirmedOrganizationUsersStatuses))]
|
||||
public async Task CreateSponsorship_BadSponsoringUserStatus_ThrowsBadRequest(
|
||||
OrganizationUserStatusType statusType, Organization org, OrganizationUser orgUser,
|
||||
SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
org.PlanType = PlanType.EnterpriseAnnually;
|
||||
orgUser.Status = statusType;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(org.Id).Returns(org);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.OfferSponsorshipAsync(org, orgUser, PlanSponsorshipType.FamiliesForEnterprise, default, default, "test@bitwarden.com"));
|
||||
|
||||
Assert.Contains("Only confirmed users can sponsor other organizations.", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
|
||||
.CreateAsync(default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task OfferSponsorship_AlreadySponsoring_Throws(Organization org,
|
||||
OrganizationUser orgUser, OrganizationSponsorship sponsorship,
|
||||
SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
org.PlanType = PlanType.EnterpriseAnnually;
|
||||
orgUser.Status = OrganizationUserStatusType.Confirmed;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetBySponsoringOrganizationUserIdAsync(orgUser.Id).Returns(sponsorship);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.OfferSponsorshipAsync(org, orgUser, sponsorship.PlanSponsorshipType.Value, default, default, "test@bitwarden.com"));
|
||||
|
||||
Assert.Contains("Can only sponsor one organization per Organization User.", exception.Message);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
|
||||
.CreateAsync(default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task OfferSponsorship_CreatesSponsorship(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser,
|
||||
string sponsoredEmail, string friendlyName, Guid sponsorshipId,
|
||||
SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
const string email = "test@bitwarden.com";
|
||||
|
||||
sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
|
||||
sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed;
|
||||
|
||||
var dataProtector = Substitute.For<IDataProtector>();
|
||||
sutProvider.GetDependency<IDataProtectionProvider>().CreateProtector(default).ReturnsForAnyArgs(dataProtector);
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().CreateAsync(default).ReturnsForAnyArgs(callInfo =>
|
||||
{
|
||||
var sponsorship = callInfo.Arg<OrganizationSponsorship>();
|
||||
sponsorship.Id = sponsorshipId;
|
||||
return sponsorship;
|
||||
});
|
||||
|
||||
await sutProvider.Sut.OfferSponsorshipAsync(sponsoringOrg, sponsoringOrgUser,
|
||||
PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName, email);
|
||||
|
||||
var expectedSponsorship = new OrganizationSponsorship
|
||||
{
|
||||
Id = sponsorshipId,
|
||||
SponsoringOrganizationId = sponsoringOrg.Id,
|
||||
SponsoringOrganizationUserId = sponsoringOrgUser.Id,
|
||||
FriendlyName = friendlyName,
|
||||
OfferedToEmail = sponsoredEmail,
|
||||
PlanSponsorshipType = PlanSponsorshipType.FamiliesForEnterprise,
|
||||
CloudSponsor = true,
|
||||
};
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().Received(1)
|
||||
.UpsertAsync(Arg.Is<OrganizationSponsorship>(s => SponsorshipValidator(s, expectedSponsorship)));
|
||||
|
||||
await sutProvider.GetDependency<IMailService>().Received(1).
|
||||
SendFamiliesForEnterpriseOfferEmailAsync(sponsoredEmail, email,
|
||||
false, Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task OfferSponsorship_CreateSponsorshipThrows_RevertsDatabase(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser,
|
||||
string sponsoredEmail, string friendlyName, SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
|
||||
sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed;
|
||||
|
||||
var expectedException = new Exception();
|
||||
OrganizationSponsorship createdSponsorship = null;
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().UpsertAsync(default).ThrowsForAnyArgs(callInfo =>
|
||||
{
|
||||
createdSponsorship = callInfo.ArgAt<OrganizationSponsorship>(0);
|
||||
createdSponsorship.Id = Guid.NewGuid();
|
||||
return expectedException;
|
||||
});
|
||||
|
||||
var actualException = await Assert.ThrowsAsync<Exception>(() =>
|
||||
sutProvider.Sut.OfferSponsorshipAsync(sponsoringOrg, sponsoringOrgUser,
|
||||
PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName, "test@bitwarden.com"));
|
||||
Assert.Same(expectedException, actualException);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().Received(1)
|
||||
.DeleteAsync(createdSponsorship);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ResendSponsorshipOffer_SponsoringOrgNotFound_ThrowsBadRequest(
|
||||
OrganizationUser orgUser, OrganizationSponsorship sponsorship,
|
||||
SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.ResendSponsorshipOfferAsync(null, orgUser, sponsorship, "test@bitwarden.com"));
|
||||
|
||||
Assert.Contains("Cannot find the requested sponsoring organization.", exception.Message);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SendFamiliesForEnterpriseOfferEmailAsync(default, default, default, default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ResendSponsorshipOffer_SponsoringOrgUserNotFound_ThrowsBadRequest(Organization org,
|
||||
OrganizationSponsorship sponsorship, SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(org.Id).Returns(org);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.ResendSponsorshipOfferAsync(org, null, sponsorship, "test@bitwarden.com"));
|
||||
|
||||
Assert.Contains("Only confirmed users can sponsor other organizations.", exception.Message);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SendFamiliesForEnterpriseOfferEmailAsync(default, default, default, default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
[BitMemberAutoData(nameof(NonConfirmedOrganizationUsersStatuses))]
|
||||
public async Task ResendSponsorshipOffer_SponsoringOrgUserNotConfirmed_ThrowsBadRequest(OrganizationUserStatusType status,
|
||||
Organization org, OrganizationUser orgUser, OrganizationSponsorship sponsorship,
|
||||
SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
orgUser.Status = status;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.ResendSponsorshipOfferAsync(org, orgUser, sponsorship, "test@bitwarden.com"));
|
||||
|
||||
Assert.Contains("Only confirmed users can sponsor other organizations.", exception.Message);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SendFamiliesForEnterpriseOfferEmailAsync(default, default, default, default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ResendSponsorshipOffer_SponsorshipNotFound_ThrowsBadRequest(Organization org,
|
||||
OrganizationUser orgUser,
|
||||
SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
orgUser.Status = OrganizationUserStatusType.Confirmed;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.ResendSponsorshipOfferAsync(org, orgUser, null, "test@bitwarden.com"));
|
||||
|
||||
Assert.Contains("Cannot find an outstanding sponsorship offer for this organization.", exception.Message);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SendFamiliesForEnterpriseOfferEmailAsync(default, default, default, default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ResendSponsorshipOffer_NoOfferToEmail_ThrowsBadRequest(Organization org,
|
||||
OrganizationUser orgUser, OrganizationSponsorship sponsorship,
|
||||
SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
orgUser.Status = OrganizationUserStatusType.Confirmed;
|
||||
sponsorship.OfferedToEmail = null;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().GetBySponsoringOrganizationUserIdAsync(orgUser.Id)
|
||||
.Returns(sponsorship);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.ResendSponsorshipOfferAsync(org, orgUser, sponsorship, "test@bitwarden.com"));
|
||||
|
||||
Assert.Contains("Cannot find an outstanding sponsorship offer for this organization.", exception.Message);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SendFamiliesForEnterpriseOfferEmailAsync(default, default, default, default);
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SendSponsorshipOfferAsync(OrganizationSponsorship sponsorship,
|
||||
SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
const string email = "test@bitwarden.com";
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetByEmailAsync(sponsorship.OfferedToEmail)
|
||||
.Returns(Task.FromResult(new User()));
|
||||
|
||||
await sutProvider.Sut.SendSponsorshipOfferAsync(sponsorship, email);
|
||||
|
||||
await sutProvider.GetDependency<IMailService>().Received(1)
|
||||
.SendFamiliesForEnterpriseOfferEmailAsync(sponsorship.OfferedToEmail, email, true, Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SetUpSponsorship_SponsorshipNotFound_ThrowsBadRequest(Organization org,
|
||||
SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.SetUpSponsorshipAsync(null, org));
|
||||
|
||||
Assert.Contains("No unredeemed sponsorship offer exists for you.", exception.Message);
|
||||
await sutProvider.GetDependency<IPaymentService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SponsorOrganizationAsync(default, default);
|
||||
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SetUpSponsorship_OrgAlreadySponsored_ThrowsBadRequest(Organization org,
|
||||
OrganizationSponsorship sponsorship, OrganizationSponsorship existingSponsorship,
|
||||
SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetBySponsoredOrganizationIdAsync(org.Id).Returns(existingSponsorship);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.SetUpSponsorshipAsync(sponsorship, org));
|
||||
|
||||
Assert.Contains("Cannot redeem a sponsorship offer for an organization that is already sponsored. Revoke existing sponsorship first.", exception.Message);
|
||||
await sutProvider.GetDependency<IPaymentService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SponsorOrganizationAsync(default, default);
|
||||
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitMemberAutoData(nameof(NonFamiliesPlanTypes))]
|
||||
public async Task SetUpSponsorship_OrgNotFamiles_ThrowsBadRequest(PlanType planType,
|
||||
OrganizationSponsorship sponsorship, Organization org,
|
||||
SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
org.PlanType = planType;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.SetUpSponsorshipAsync(sponsorship, org));
|
||||
|
||||
Assert.Contains("Can only redeem sponsorship offer on families organizations.", exception.Message);
|
||||
await sutProvider.GetDependency<IPaymentService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SponsorOrganizationAsync(default, default);
|
||||
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
}
|
||||
|
||||
private async Task AssertRemovedSponsoredPaymentAsync(Organization sponsoredOrg,
|
||||
OrganizationSponsorship sponsorship, SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
await sutProvider.GetDependency<IPaymentService>().Received(1)
|
||||
.RemoveOrganizationSponsorshipAsync(sponsoredOrg, sponsorship);
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).UpsertAsync(sponsoredOrg);
|
||||
await sutProvider.GetDependency<IMailService>().Received(1)
|
||||
.SendFamiliesForEnterpriseSponsorshipRevertingEmailAsync(sponsoredOrg.BillingEmailAddress(), sponsoredOrg.Name);
|
||||
}
|
||||
|
||||
private async Task AssertRemovedSponsorshipAsync(OrganizationSponsorship sponsorship,
|
||||
SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
if (sponsorship.CloudSponsor || sponsorship.SponsorshipLapsedDate.HasValue)
|
||||
{
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().Received(1)
|
||||
.DeleteAsync(sponsorship);
|
||||
}
|
||||
else
|
||||
{
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().Received(1)
|
||||
.UpsertAsync(sponsorship);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task AssertDidNotRemoveSponsoredPaymentAsync(SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
await sutProvider.GetDependency<IPaymentService>().DidNotReceiveWithAnyArgs()
|
||||
.RemoveOrganizationSponsorshipAsync(default, default);
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
await sutProvider.GetDependency<IMailService>().DidNotReceiveWithAnyArgs()
|
||||
.SendFamiliesForEnterpriseSponsorshipRevertingEmailAsync(default, default);
|
||||
}
|
||||
|
||||
private static async Task AssertDidNotRemoveSponsorshipAsync(SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
|
||||
.DeleteAsync(default);
|
||||
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateSponsorshipAsync_NoSponsoredOrg_EarlyReturn(Guid sponsoredOrgId,
|
||||
SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrgId).Returns((Organization)null);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrgId);
|
||||
|
||||
Assert.False(result);
|
||||
await AssertDidNotRemoveSponsoredPaymentAsync(sutProvider);
|
||||
await AssertDidNotRemoveSponsorshipAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateSponsorshipAsync_NoExistingSponsorship_UpdatesStripePlan(Organization sponsoredOrg,
|
||||
SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
|
||||
|
||||
Assert.False(result);
|
||||
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, null, sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateSponsorshipAsync_SponsoringOrgNull_UpdatesStripePlan(Organization sponsoredOrg,
|
||||
OrganizationSponsorship existingSponsorship, SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
existingSponsorship.SponsoringOrganizationId = null;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
|
||||
|
||||
Assert.False(result);
|
||||
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
|
||||
await AssertRemovedSponsorshipAsync(existingSponsorship, sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateSponsorshipAsync_SponsoringOrgUserNull_UpdatesStripePlan(Organization sponsoredOrg,
|
||||
OrganizationSponsorship existingSponsorship, SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
existingSponsorship.SponsoringOrganizationUserId = null;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
|
||||
|
||||
Assert.False(result);
|
||||
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
|
||||
await AssertRemovedSponsorshipAsync(existingSponsorship, sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateSponsorshipAsync_SponsorshipTypeNull_UpdatesStripePlan(Organization sponsoredOrg,
|
||||
OrganizationSponsorship existingSponsorship, SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
existingSponsorship.PlanSponsorshipType = null;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
|
||||
|
||||
Assert.False(result);
|
||||
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
|
||||
await AssertRemovedSponsorshipAsync(existingSponsorship, sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task ValidateSponsorshipAsync_SponsoringOrgNotFound_UpdatesStripePlan(Organization sponsoredOrg,
|
||||
OrganizationSponsorship existingSponsorship, SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
|
||||
|
||||
Assert.False(result);
|
||||
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
|
||||
await AssertRemovedSponsorshipAsync(existingSponsorship, sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitMemberAutoData(nameof(NonEnterprisePlanTypes))]
|
||||
public async Task ValidateSponsorshipAsync_SponsoringOrgNotEnterprise_UpdatesStripePlan(PlanType planType,
|
||||
Organization sponsoredOrg, OrganizationSponsorship existingSponsorship, Organization sponsoringOrg,
|
||||
SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
sponsoringOrg.PlanType = planType;
|
||||
existingSponsorship.SponsoringOrganizationId = sponsoringOrg.Id;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoringOrg.Id).Returns(sponsoringOrg);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
|
||||
|
||||
Assert.False(result);
|
||||
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
|
||||
await AssertRemovedSponsorshipAsync(existingSponsorship, sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitMemberAutoData(nameof(EnterprisePlanTypes))]
|
||||
public async Task ValidateSponsorshipAsync_SponsoringOrgDisabled_UpdatesStripePlan(PlanType planType,
|
||||
Organization sponsoredOrg, OrganizationSponsorship existingSponsorship, Organization sponsoringOrg,
|
||||
SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
sponsoringOrg.PlanType = planType;
|
||||
sponsoringOrg.Enabled = false;
|
||||
existingSponsorship.SponsoringOrganizationId = sponsoringOrg.Id;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoringOrg.Id).Returns(sponsoringOrg);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
|
||||
|
||||
Assert.False(result);
|
||||
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, existingSponsorship, sutProvider);
|
||||
await AssertRemovedSponsorshipAsync(existingSponsorship, sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitMemberAutoData(nameof(EnterprisePlanTypes))]
|
||||
public async Task ValidateSponsorshipAsync_Valid(PlanType planType,
|
||||
Organization sponsoredOrg, OrganizationSponsorship existingSponsorship, Organization sponsoringOrg,
|
||||
SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
sponsoringOrg.PlanType = planType;
|
||||
sponsoringOrg.Enabled = true;
|
||||
existingSponsorship.SponsoringOrganizationId = sponsoringOrg.Id;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||
.GetBySponsoredOrganizationIdAsync(sponsoredOrg.Id).Returns(existingSponsorship);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoredOrg.Id).Returns(sponsoredOrg);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(sponsoringOrg.Id).Returns(sponsoringOrg);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateSponsorshipAsync(sponsoredOrg.Id);
|
||||
|
||||
Assert.True(result);
|
||||
|
||||
await AssertDidNotRemoveSponsoredPaymentAsync(sutProvider);
|
||||
await AssertDidNotRemoveSponsorshipAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RevokeSponsorship_NoExistingSponsorship_ThrowsBadRequest(Organization org,
|
||||
SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RevokeSponsorshipAsync(org, null));
|
||||
|
||||
Assert.Contains("You are not currently sponsoring an organization.", exception.Message);
|
||||
await AssertDidNotRemoveSponsoredPaymentAsync(sutProvider);
|
||||
await AssertDidNotRemoveSponsorshipAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RevokeSponsorship_SponsorshipNotRedeemed_DeletesSponsorship(Organization org,
|
||||
OrganizationSponsorship sponsorship, SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
sponsorship.SponsoredOrganizationId = null;
|
||||
|
||||
await sutProvider.Sut.RevokeSponsorshipAsync(org, sponsorship);
|
||||
|
||||
await AssertDidNotRemoveSponsoredPaymentAsync(sutProvider);
|
||||
await AssertRemovedSponsorshipAsync(sponsorship, sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RevokeSponsorship_SponsoredOrgNotFound_ThrowsBadRequest(OrganizationSponsorship sponsorship,
|
||||
SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RevokeSponsorshipAsync(null, sponsorship));
|
||||
|
||||
Assert.Contains("Unable to find the sponsored Organization.", exception.Message);
|
||||
|
||||
await AssertDidNotRemoveSponsoredPaymentAsync(sutProvider);
|
||||
await AssertDidNotRemoveSponsorshipAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RemoveSponsorship_SponsoredOrgNull_ThrowsBadRequest(OrganizationSponsorship sponsorship,
|
||||
SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
sponsorship.SponsoredOrganizationId = null;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RemoveSponsorshipAsync(null, sponsorship));
|
||||
|
||||
Assert.Contains("The requested organization is not currently being sponsored.", exception.Message);
|
||||
|
||||
await AssertDidNotRemoveSponsoredPaymentAsync(sutProvider);
|
||||
await AssertDidNotRemoveSponsorshipAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RemoveSponsorship_SponsorshipNotFound_ThrowsBadRequest(Organization sponsoredOrg,
|
||||
SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RemoveSponsorshipAsync(sponsoredOrg, null));
|
||||
|
||||
Assert.Contains("The requested organization is not currently being sponsored.", exception.Message);
|
||||
|
||||
await AssertDidNotRemoveSponsoredPaymentAsync(sutProvider);
|
||||
await AssertDidNotRemoveSponsorshipAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RemoveSponsorship_SponsoredOrgNotFound_ThrowsBadRequest(OrganizationSponsorship sponsorship,
|
||||
SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RemoveSponsorshipAsync(null, sponsorship));
|
||||
|
||||
Assert.Contains("Unable to find the sponsored Organization.", exception.Message);
|
||||
|
||||
await AssertDidNotRemoveSponsoredPaymentAsync(sutProvider);
|
||||
await AssertDidNotRemoveSponsorshipAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DoRemoveSponsorshipAsync_NullDoNothing(SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
await sutProvider.Sut.DoRemoveSponsorshipAsync(null, null);
|
||||
|
||||
await AssertDidNotRemoveSponsoredPaymentAsync(sutProvider);
|
||||
await AssertDidNotRemoveSponsorshipAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DoRemoveSponsorshipAsync_NullSponsoredOrg(OrganizationSponsorship sponsorship,
|
||||
SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
await sutProvider.Sut.DoRemoveSponsorshipAsync(null, sponsorship);
|
||||
|
||||
await AssertDidNotRemoveSponsoredPaymentAsync(sutProvider);
|
||||
await AssertRemovedSponsorshipAsync(sponsorship, sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DoRemoveSponsorshipAsync_NullSponsorship(Organization sponsoredOrg,
|
||||
SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
await sutProvider.Sut.DoRemoveSponsorshipAsync(sponsoredOrg, null);
|
||||
|
||||
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, null, sutProvider);
|
||||
await AssertDidNotRemoveSponsorshipAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task DoRemoveSponsorshipAsync_RemoveBoth(Organization sponsoredOrg,
|
||||
OrganizationSponsorship sponsorship, SutProvider<OrganizationSponsorshipService> sutProvider)
|
||||
{
|
||||
await sutProvider.Sut.DoRemoveSponsorshipAsync(sponsoredOrg, sponsorship);
|
||||
|
||||
await AssertRemovedSponsoredPaymentAsync(sponsoredOrg, sponsorship, sutProvider);
|
||||
await AssertRemovedSponsorshipAsync(sponsorship, sutProvider);
|
||||
}
|
||||
}
|
||||
}
|
@ -276,7 +276,7 @@ namespace Bit.Core.Test.Services
|
||||
Enabled = false,
|
||||
});
|
||||
|
||||
var orgUserDetail = new Core.Models.Data.OrganizationUserUserDetails
|
||||
var orgUserDetail = new Core.Models.Data.Organizations.OrganizationUsers.OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Accepted,
|
||||
@ -289,7 +289,7 @@ namespace Bit.Core.Test.Services
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policy.OrganizationId)
|
||||
.Returns(new List<Core.Models.Data.OrganizationUserUserDetails>
|
||||
.Returns(new List<Core.Models.Data.Organizations.OrganizationUsers.OrganizationUserUserDetails>
|
||||
{
|
||||
orgUserDetail,
|
||||
});
|
||||
@ -345,7 +345,7 @@ namespace Bit.Core.Test.Services
|
||||
Enabled = false,
|
||||
});
|
||||
|
||||
var orgUserDetail = new Core.Models.Data.OrganizationUserUserDetails
|
||||
var orgUserDetail = new Core.Models.Data.Organizations.OrganizationUsers.OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Accepted,
|
||||
@ -358,7 +358,7 @@ namespace Bit.Core.Test.Services
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policy.OrganizationId)
|
||||
.Returns(new List<Core.Models.Data.OrganizationUserUserDetails>
|
||||
.Returns(new List<Core.Models.Data.Organizations.OrganizationUsers.OrganizationUserUserDetails>
|
||||
{
|
||||
orgUserDetail,
|
||||
});
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
@ -13,6 +14,7 @@ namespace Bit.Core.Test.Services
|
||||
{
|
||||
private readonly RelayPushNotificationService _sut;
|
||||
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly IDeviceRepository _deviceRepository;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
@ -20,12 +22,14 @@ namespace Bit.Core.Test.Services
|
||||
|
||||
public RelayPushNotificationServiceTests()
|
||||
{
|
||||
_httpFactory = Substitute.For<IHttpClientFactory>();
|
||||
_deviceRepository = Substitute.For<IDeviceRepository>();
|
||||
_globalSettings = new GlobalSettings();
|
||||
_httpContextAccessor = Substitute.For<IHttpContextAccessor>();
|
||||
_logger = Substitute.For<ILogger<RelayPushNotificationService>>();
|
||||
|
||||
_sut = new RelayPushNotificationService(
|
||||
_httpFactory,
|
||||
_deviceRepository,
|
||||
_globalSettings,
|
||||
_httpContextAccessor,
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@ -11,15 +12,18 @@ namespace Bit.Core.Test.Services
|
||||
{
|
||||
private readonly RelayPushRegistrationService _sut;
|
||||
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly ILogger<RelayPushRegistrationService> _logger;
|
||||
|
||||
public RelayPushRegistrationServiceTests()
|
||||
{
|
||||
_globalSettings = new GlobalSettings();
|
||||
_httpFactory = Substitute.For<IHttpClientFactory>();
|
||||
_logger = Substitute.For<ILogger<RelayPushRegistrationService>>();
|
||||
|
||||
_sut = new RelayPushRegistrationService(
|
||||
_httpFactory,
|
||||
_globalSettings,
|
||||
_logger
|
||||
);
|
||||
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
|
@ -1,4 +1,5 @@
|
||||
using AutoFixture;
|
||||
using System.Security.Cryptography;
|
||||
using AutoFixture;
|
||||
using Bit.Core.Tokens;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
@ -50,5 +51,78 @@ namespace Bit.Core.Test.Tokens
|
||||
|
||||
Assert.NotEqual(new Token(token).RemovePrefix(prefix), tokenable.ToToken());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ThrowsIfUnprotectFails(TestTokenable tokenable)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
|
||||
var token = sutProvider.Sut.Protect(tokenable);
|
||||
token += "stuff to make sure decryption fails";
|
||||
|
||||
Assert.Throws<CryptographicException>(() => sutProvider.Sut.Unprotect(token));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void TryUnprotect_FalseIfUnprotectFails(TestTokenable tokenable)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var token = sutProvider.Sut.Protect(tokenable) + "fail decryption";
|
||||
|
||||
var result = sutProvider.Sut.TryUnprotect(token, out var data);
|
||||
|
||||
Assert.False(result);
|
||||
Assert.Null(data);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void TokenValid_FalseIfUnprotectFails(TestTokenable tokenable)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var token = sutProvider.Sut.Protect(tokenable) + "fail decryption";
|
||||
|
||||
var result = sutProvider.Sut.TokenValid(token);
|
||||
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void TokenValid_FalseIfTokenInvalid(TestTokenable tokenable)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
|
||||
tokenable.ForceInvalid = true;
|
||||
var token = sutProvider.Sut.Protect(tokenable);
|
||||
|
||||
var result = sutProvider.Sut.TokenValid(token);
|
||||
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void TryUnprotect_TrueIfSuccess(TestTokenable tokenable)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var token = sutProvider.Sut.Protect(tokenable);
|
||||
|
||||
var result = sutProvider.Sut.TryUnprotect(token, out var data);
|
||||
|
||||
Assert.True(result);
|
||||
AssertHelper.AssertPropertyEqual(tokenable, data);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void TokenValid_TrueIfSuccess(TestTokenable tokenable)
|
||||
{
|
||||
tokenable.ForceInvalid = false;
|
||||
var sutProvider = GetSutProvider();
|
||||
var token = sutProvider.Sut.Protect(tokenable);
|
||||
|
||||
var result = sutProvider.Sut.TokenValid(token);
|
||||
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,14 @@
|
||||
using Bit.Core.Tokens;
|
||||
using System.Text.Json.Serialization;
|
||||
using Bit.Core.Tokens;
|
||||
|
||||
namespace Bit.Core.Test.Tokens
|
||||
{
|
||||
public class TestTokenable : Tokenable
|
||||
{
|
||||
public override bool Valid => true;
|
||||
public bool ForceInvalid { get; set; } = false;
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool Valid => !ForceInvalid;
|
||||
}
|
||||
|
||||
public class TestExpiringTokenable : ExpiringTokenable
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using AutoFixture;
|
||||
using Bit.Core.Context;
|
||||
@ -35,6 +36,43 @@ namespace Bit.Core.Test.Utilities
|
||||
// the comb are working properly
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> GenerateCombCases = new[]
|
||||
{
|
||||
new object[]
|
||||
{
|
||||
Guid.Parse("a58db474-43d8-42f1-b4ee-0c17647cd0c0"), // Input Guid
|
||||
new DateTime(2022, 3, 12, 12, 12, 0, DateTimeKind.Utc), // Input Time
|
||||
Guid.Parse("a58db474-43d8-42f1-b4ee-ae5600c90cc1"), // Expected Comb
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
Guid.Parse("f776e6ee-511f-4352-bb28-88513002bdeb"),
|
||||
new DateTime(2021, 5, 10, 10, 52, 0, DateTimeKind.Utc),
|
||||
Guid.Parse("f776e6ee-511f-4352-bb28-ad2400b313c1"),
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
Guid.Parse("51a25fc7-3cad-497d-8e2f-8d77011648a1"),
|
||||
new DateTime(1999, 2, 26, 16, 53, 13, DateTimeKind.Utc),
|
||||
Guid.Parse("51a25fc7-3cad-497d-8e2f-8d77011649cd"),
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
Guid.Parse("bfb8f353-3b32-4a9e-bef6-24fe0b54bfb0"),
|
||||
new DateTime(2024, 10, 20, 1, 32, 16, DateTimeKind.Utc),
|
||||
Guid.Parse("bfb8f353-3b32-4a9e-bef6-b20f00195780"),
|
||||
}
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(GenerateCombCases))]
|
||||
public void GenerateComb_WithInputs_Success(Guid inputGuid, DateTime inputTime, Guid expectedComb)
|
||||
{
|
||||
var comb = CoreHelpers.GenerateComb(inputGuid, inputTime);
|
||||
|
||||
Assert.Equal(expectedComb, comb);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(2, 5, new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 })]
|
||||
[InlineData(2, 3, new[] { 1, 2, 3, 4, 5 })]
|
||||
|
@ -28,6 +28,16 @@
|
||||
"resolved": "3.0.3",
|
||||
"contentHash": "PdyhdzG2LK7YUEtccObPql+3OuFODaFNeYayxdPoK1eHb2StZoeQf1WMb16QrKiIdi4fs5Kog8jxXtlZOgAEuA=="
|
||||
},
|
||||
"Kralizek.AutoFixture.Extensions.MockHttp": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.2.0, )",
|
||||
"resolved": "1.2.0",
|
||||
"contentHash": "6zmks7/5mVczazv910N7V2EdiU6B+rY61lwdgVO0o2iZuTI6KI3T+Hgkrjv0eGOKYucq2OMC+gnAc5Ej2ajoTQ==",
|
||||
"dependencies": {
|
||||
"AutoFixture": "4.11.0",
|
||||
"RichardSzalay.MockHttp": "6.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.NET.Test.Sdk": {
|
||||
"type": "Direct",
|
||||
"requested": "[16.6.1, )",
|
||||
@ -1602,6 +1612,11 @@
|
||||
"System.Diagnostics.DiagnosticSource": "4.7.1"
|
||||
}
|
||||
},
|
||||
"RichardSzalay.MockHttp": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "bStGNqIX/MGYtML7K3EzdsE/k5HGVAcg7XgN23TQXGXqxNC9fvYFR94fA0sGM5hAT36R+BBGet6ZDQxXL/IPxg=="
|
||||
},
|
||||
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.2",
|
||||
@ -3551,6 +3566,7 @@
|
||||
"AutoFixture.AutoNSubstitute": "4.14.0",
|
||||
"AutoFixture.Xunit2": "4.14.0",
|
||||
"Core": "1.47.1",
|
||||
"Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0",
|
||||
"Microsoft.NET.Test.Sdk": "16.6.1",
|
||||
"NSubstitute": "4.2.2",
|
||||
"xunit": "2.4.1"
|
||||
|
@ -309,6 +309,15 @@
|
||||
"IdentityModel": "4.3.0"
|
||||
}
|
||||
},
|
||||
"Kralizek.AutoFixture.Extensions.MockHttp": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.2.0",
|
||||
"contentHash": "6zmks7/5mVczazv910N7V2EdiU6B+rY61lwdgVO0o2iZuTI6KI3T+Hgkrjv0eGOKYucq2OMC+gnAc5Ej2ajoTQ==",
|
||||
"dependencies": {
|
||||
"AutoFixture": "4.11.0",
|
||||
"RichardSzalay.MockHttp": "6.0.0"
|
||||
}
|
||||
},
|
||||
"libsodium": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.18",
|
||||
@ -1591,6 +1600,11 @@
|
||||
"System.Diagnostics.DiagnosticSource": "4.7.1"
|
||||
}
|
||||
},
|
||||
"RichardSzalay.MockHttp": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "bStGNqIX/MGYtML7K3EzdsE/k5HGVAcg7XgN23TQXGXqxNC9fvYFR94fA0sGM5hAT36R+BBGet6ZDQxXL/IPxg=="
|
||||
},
|
||||
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.2",
|
||||
@ -3540,6 +3554,7 @@
|
||||
"AutoFixture.AutoNSubstitute": "4.14.0",
|
||||
"AutoFixture.Xunit2": "4.14.0",
|
||||
"Core": "1.47.1",
|
||||
"Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0",
|
||||
"Microsoft.NET.Test.Sdk": "16.6.1",
|
||||
"NSubstitute": "4.2.2",
|
||||
"xunit": "2.4.1"
|
||||
|
Reference in New Issue
Block a user