mirror of
https://github.com/bitwarden/server.git
synced 2024-11-30 13:33:24 +01:00
Freshsales integration (#1782)
* Add FreshsalesController * Add better errors * Fix formatting issues * Add comments * Add Billing.Test to solution files * Fix unit test * Format code * Address PR feedback
This commit is contained in:
parent
a14f16b34f
commit
bb34de74cb
@ -76,6 +76,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostgresMigrations", "util\
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "test\Common\Common.csproj", "{17DA09D7-0212-4009-879E-6B9CFDE5FA60}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "test\Common\Common.csproj", "{17DA09D7-0212-4009-879E-6B9CFDE5FA60}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Billing.Test", "test\Billing.Test\Billing.Test.csproj", "{B8639B10-2157-44BC-8CE1-D9EB4B50971F}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -172,6 +174,10 @@ Global
|
|||||||
{17DA09D7-0212-4009-879E-6B9CFDE5FA60}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{17DA09D7-0212-4009-879E-6B9CFDE5FA60}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{17DA09D7-0212-4009-879E-6B9CFDE5FA60}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{17DA09D7-0212-4009-879E-6B9CFDE5FA60}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{17DA09D7-0212-4009-879E-6B9CFDE5FA60}.Release|Any CPU.Build.0 = Release|Any CPU
|
{17DA09D7-0212-4009-879E-6B9CFDE5FA60}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B8639B10-2157-44BC-8CE1-D9EB4B50971F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B8639B10-2157-44BC-8CE1-D9EB4B50971F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B8639B10-2157-44BC-8CE1-D9EB4B50971F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B8639B10-2157-44BC-8CE1-D9EB4B50971F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -199,6 +205,7 @@ Global
|
|||||||
{EDC0D688-D58C-4CE1-AA07-3606AC6874B8} = {4FDB6543-F68B-4202-9EA6-7FEA984D2D0A}
|
{EDC0D688-D58C-4CE1-AA07-3606AC6874B8} = {4FDB6543-F68B-4202-9EA6-7FEA984D2D0A}
|
||||||
{0E99A21B-684B-4C59-9831-90F775CAB6F7} = {287CFF34-BBDB-4BC4-AF88-1E19A5A4679B}
|
{0E99A21B-684B-4C59-9831-90F775CAB6F7} = {287CFF34-BBDB-4BC4-AF88-1E19A5A4679B}
|
||||||
{17DA09D7-0212-4009-879E-6B9CFDE5FA60} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
|
{17DA09D7-0212-4009-879E-6B9CFDE5FA60} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
|
||||||
|
{B8639B10-2157-44BC-8CE1-D9EB4B50971F} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {E01CBF68-2E20-425F-9EDB-E0A6510CA92F}
|
SolutionGuid = {E01CBF68-2E20-425F-9EDB-E0A6510CA92F}
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
public virtual string AppleWebhookKey { get; set; }
|
public virtual string AppleWebhookKey { get; set; }
|
||||||
public virtual string FreshdeskWebhookKey { get; set; }
|
public virtual string FreshdeskWebhookKey { get; set; }
|
||||||
public virtual string FreshdeskApiKey { get; set; }
|
public virtual string FreshdeskApiKey { get; set; }
|
||||||
|
public virtual string FreshsalesApiKey { get; set; }
|
||||||
public virtual PayPalSettings PayPal { get; set; } = new PayPalSettings();
|
public virtual PayPalSettings PayPal { get; set; } = new PayPalSettings();
|
||||||
|
|
||||||
public class PayPalSettings
|
public class PayPalSettings
|
||||||
|
244
src/Billing/Controllers/FreshsalesController.cs
Normal file
244
src/Billing/Controllers/FreshsalesController.cs
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Core.Settings;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Bit.Billing.Controllers
|
||||||
|
{
|
||||||
|
[Route("freshsales")]
|
||||||
|
public class FreshsalesController : Controller
|
||||||
|
{
|
||||||
|
private readonly IUserRepository _userRepository;
|
||||||
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly GlobalSettings _globalSettings;
|
||||||
|
|
||||||
|
private readonly string _freshsalesApiKey;
|
||||||
|
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
|
||||||
|
public FreshsalesController(IUserRepository userRepository,
|
||||||
|
IOrganizationRepository organizationRepository,
|
||||||
|
IOptions<BillingSettings> billingSettings,
|
||||||
|
ILogger<FreshsalesController> logger,
|
||||||
|
GlobalSettings globalSettings)
|
||||||
|
{
|
||||||
|
_userRepository = userRepository;
|
||||||
|
_organizationRepository = organizationRepository;
|
||||||
|
_logger = logger;
|
||||||
|
_globalSettings = globalSettings;
|
||||||
|
|
||||||
|
_httpClient = new HttpClient
|
||||||
|
{
|
||||||
|
BaseAddress = new Uri("https://bitwarden.freshsales.io/api/")
|
||||||
|
};
|
||||||
|
|
||||||
|
_freshsalesApiKey = billingSettings.Value.FreshsalesApiKey;
|
||||||
|
|
||||||
|
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
|
||||||
|
"Token",
|
||||||
|
$"token={_freshsalesApiKey}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpPost("webhook")]
|
||||||
|
public async Task<IActionResult> PostWebhook([FromHeader(Name = "Authorization")] string key,
|
||||||
|
[FromBody] CustomWebhookRequestModel request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(key) || !CoreHelpers.FixedTimeEquals(_freshsalesApiKey, key))
|
||||||
|
{
|
||||||
|
return Unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var leadResponse = await _httpClient.GetFromJsonAsync<LeadWrapper<FreshsalesLeadModel>>(
|
||||||
|
$"leads/{request.LeadId}",
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
var lead = leadResponse.Lead;
|
||||||
|
|
||||||
|
var primaryEmail = lead.Emails
|
||||||
|
.Where(e => e.IsPrimary)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (primaryEmail == null)
|
||||||
|
{
|
||||||
|
return BadRequest(new { Message = "Lead has not primary email." });
|
||||||
|
}
|
||||||
|
|
||||||
|
var user = await _userRepository.GetByEmailAsync(primaryEmail.Value);
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
var newTags = new HashSet<string>();
|
||||||
|
|
||||||
|
if (user.Premium)
|
||||||
|
{
|
||||||
|
newTags.Add("Premium");
|
||||||
|
}
|
||||||
|
|
||||||
|
var noteItems = new List<string>
|
||||||
|
{
|
||||||
|
$"User, {user.Email}: {_globalSettings.BaseServiceUri.Admin}/users/edit/{user.Id}"
|
||||||
|
};
|
||||||
|
|
||||||
|
var orgs = await _organizationRepository.GetManyByUserIdAsync(user.Id);
|
||||||
|
|
||||||
|
foreach (var org in orgs)
|
||||||
|
{
|
||||||
|
noteItems.Add($"Org, {org.Name}: {_globalSettings.BaseServiceUri.Admin}/organizations/edit/{org.Id}");
|
||||||
|
if (TryGetPlanName(org.PlanType, out var planName))
|
||||||
|
{
|
||||||
|
newTags.Add($"Org: {planName}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newTags.Any())
|
||||||
|
{
|
||||||
|
var allTags = newTags.Concat(lead.Tags);
|
||||||
|
var updateLeadResponse = await _httpClient.PutAsJsonAsync(
|
||||||
|
$"leads/{request.LeadId}",
|
||||||
|
CreateWrapper(new { tags = allTags }),
|
||||||
|
cancellationToken);
|
||||||
|
updateLeadResponse.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
var createNoteResponse = await _httpClient.PostAsJsonAsync(
|
||||||
|
"notes",
|
||||||
|
CreateNoteRequestModel(request.LeadId, string.Join('\n', noteItems)), cancellationToken);
|
||||||
|
createNoteResponse.EnsureSuccessStatusCode();
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine(ex);
|
||||||
|
_logger.LogError(ex, "Error processing freshsales webhook");
|
||||||
|
return BadRequest(new { ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LeadWrapper<T> CreateWrapper<T>(T lead)
|
||||||
|
{
|
||||||
|
return new LeadWrapper<T>
|
||||||
|
{
|
||||||
|
Lead = lead,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CreateNoteRequestModel CreateNoteRequestModel(long leadId, string content)
|
||||||
|
{
|
||||||
|
return new CreateNoteRequestModel
|
||||||
|
{
|
||||||
|
Note = new EditNoteModel
|
||||||
|
{
|
||||||
|
Description = content,
|
||||||
|
TargetableType = "Lead",
|
||||||
|
TargetableId = leadId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetPlanName(PlanType planType, out string planName)
|
||||||
|
{
|
||||||
|
switch (planType)
|
||||||
|
{
|
||||||
|
case PlanType.Free:
|
||||||
|
planName = "Free";
|
||||||
|
return true;
|
||||||
|
case PlanType.FamiliesAnnually:
|
||||||
|
case PlanType.FamiliesAnnually2019:
|
||||||
|
planName = "Families";
|
||||||
|
return true;
|
||||||
|
case PlanType.TeamsAnnually:
|
||||||
|
case PlanType.TeamsAnnually2019:
|
||||||
|
case PlanType.TeamsMonthly:
|
||||||
|
case PlanType.TeamsMonthly2019:
|
||||||
|
planName = "Teams";
|
||||||
|
return true;
|
||||||
|
case PlanType.EnterpriseAnnually:
|
||||||
|
case PlanType.EnterpriseAnnually2019:
|
||||||
|
case PlanType.EnterpriseMonthly:
|
||||||
|
case PlanType.EnterpriseMonthly2019:
|
||||||
|
planName = "Enterprise";
|
||||||
|
return true;
|
||||||
|
case PlanType.Custom:
|
||||||
|
planName = "Custom";
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
planName = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CustomWebhookRequestModel
|
||||||
|
{
|
||||||
|
[JsonPropertyName("leadId")]
|
||||||
|
public long LeadId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LeadWrapper<T>
|
||||||
|
{
|
||||||
|
[JsonPropertyName("lead")]
|
||||||
|
public T Lead { get; set; }
|
||||||
|
|
||||||
|
public static LeadWrapper<TItem> Create<TItem>(TItem lead)
|
||||||
|
{
|
||||||
|
return new LeadWrapper<TItem>
|
||||||
|
{
|
||||||
|
Lead = lead,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FreshsalesLeadModel
|
||||||
|
{
|
||||||
|
public string[] Tags { get; set; }
|
||||||
|
public FreshsalesEmailModel[] Emails { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FreshsalesEmailModel
|
||||||
|
{
|
||||||
|
[JsonPropertyName("value")]
|
||||||
|
public string Value { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("is_primary")]
|
||||||
|
public bool IsPrimary { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CreateNoteRequestModel
|
||||||
|
{
|
||||||
|
[JsonPropertyName("note")]
|
||||||
|
public EditNoteModel Note { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EditNoteModel
|
||||||
|
{
|
||||||
|
[JsonPropertyName("description")]
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("targetable_type")]
|
||||||
|
public string TargetableType { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("targetable_id")]
|
||||||
|
public long TargetableId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
27
test/Billing.Test/Billing.Test.csproj
Normal file
27
test/Billing.Test/Billing.Test.csproj
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||||
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="3.1.0">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="AutoFixture.Xunit2" Version="4.14.0" />
|
||||||
|
<PackageReference Include="NSubstitute" Version="4.2.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\Billing\Billing.csproj" />
|
||||||
|
<ProjectReference Include="..\Common\Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
88
test/Billing.Test/Controllers/FreshsalesControllerTests.cs
Normal file
88
test/Billing.Test/Controllers/FreshsalesControllerTests.cs
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Billing.Controllers;
|
||||||
|
using Bit.Core.Models.Table;
|
||||||
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Core.Settings;
|
||||||
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using NSubstitute;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Bit.Billing.Test.Controllers
|
||||||
|
{
|
||||||
|
public class FreshsalesControllerTests
|
||||||
|
{
|
||||||
|
private const string ApiKey = "TEST_FRESHSALES_APIKEY";
|
||||||
|
private const string TestLead = "TEST_FRESHSALES_TESTLEAD";
|
||||||
|
|
||||||
|
private static (FreshsalesController, IUserRepository, IOrganizationRepository) CreateSut(
|
||||||
|
string freshsalesApiKey)
|
||||||
|
{
|
||||||
|
var userRepository = Substitute.For<IUserRepository>();
|
||||||
|
var organizationRepository = Substitute.For<IOrganizationRepository>();
|
||||||
|
|
||||||
|
var billingSettings = Options.Create(new BillingSettings
|
||||||
|
{
|
||||||
|
FreshsalesApiKey = freshsalesApiKey,
|
||||||
|
});
|
||||||
|
var globalSettings = new GlobalSettings();
|
||||||
|
globalSettings.BaseServiceUri.Admin = "https://test.com";
|
||||||
|
|
||||||
|
var sut = new FreshsalesController(
|
||||||
|
userRepository,
|
||||||
|
organizationRepository,
|
||||||
|
billingSettings,
|
||||||
|
Substitute.For<ILogger<FreshsalesController>>(),
|
||||||
|
globalSettings
|
||||||
|
);
|
||||||
|
|
||||||
|
return (sut, userRepository, organizationRepository);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RequiredEnvironmentTheory(ApiKey, TestLead), EnvironmentData(ApiKey, TestLead)]
|
||||||
|
public async Task PostWebhook_Success(string freshsalesApiKey, long leadId)
|
||||||
|
{
|
||||||
|
// This test is only for development to use:
|
||||||
|
// `export TEST_FRESHSALES_APIKEY=[apikey]`
|
||||||
|
// `export TEST_FRESHSALES_TESTLEAD=[lead id]`
|
||||||
|
// `dotnet test --filter "FullyQualifiedName~FreshsalesControllerTests.PostWebhook_Success"`
|
||||||
|
var (sut, userRepository, organizationRepository) = CreateSut(freshsalesApiKey);
|
||||||
|
|
||||||
|
var user = new User
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Email = "test@email.com",
|
||||||
|
Premium = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
userRepository.GetByEmailAsync(user.Email)
|
||||||
|
.Returns(user);
|
||||||
|
|
||||||
|
organizationRepository.GetManyByUserIdAsync(user.Id)
|
||||||
|
.Returns(new List<Organization>
|
||||||
|
{
|
||||||
|
new Organization
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Name = "Test Org",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var response = await sut.PostWebhook(freshsalesApiKey, new CustomWebhookRequestModel
|
||||||
|
{
|
||||||
|
LeadId = leadId,
|
||||||
|
}, new CancellationToken(false));
|
||||||
|
|
||||||
|
var statusCodeResult = Assert.IsAssignableFrom<StatusCodeResult>(response);
|
||||||
|
Assert.Equal(StatusCodes.Status204NoContent, statusCodeResult.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using Xunit.Sdk;
|
||||||
|
|
||||||
|
namespace Bit.Test.Common.AutoFixture.Attributes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used for collecting data from environment useful for when we want to test an integration with another service and
|
||||||
|
/// it might require an api key or other piece of sensitive data that we don't want slipping into the wrong hands.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// It probably should be refactored to support fixtures and other customization so it can more easily be used in conjunction
|
||||||
|
/// with more parameters. Currently it attempt to match environment variable names to values of the parameter type in that positions.
|
||||||
|
/// It will start from the first parameter and go for each supplied name.
|
||||||
|
/// </remarks>
|
||||||
|
public class EnvironmentDataAttribute : DataAttribute
|
||||||
|
{
|
||||||
|
private readonly string[] _environmentVariableNames;
|
||||||
|
|
||||||
|
public EnvironmentDataAttribute(params string[] environmentVariableNames)
|
||||||
|
{
|
||||||
|
_environmentVariableNames = environmentVariableNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<object[]> GetData(MethodInfo testMethod)
|
||||||
|
{
|
||||||
|
var methodParameters = testMethod.GetParameters();
|
||||||
|
|
||||||
|
if (methodParameters.Length < _environmentVariableNames.Length)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"The target test method only has {methodParameters.Length} arguments but you supplied {_environmentVariableNames.Length}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var values = new object[_environmentVariableNames.Length];
|
||||||
|
|
||||||
|
for (var i = 0; i < _environmentVariableNames.Length; i++)
|
||||||
|
{
|
||||||
|
values[i] = Convert.ChangeType(Environment.GetEnvironmentVariable(_environmentVariableNames[i]), methodParameters[i].ParameterType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new[] { values };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
using System;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Bit.Test.Common.AutoFixture.Attributes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used for requiring certain environment variables exist at the time. Mostly used for more edge unit tests that shouldn't
|
||||||
|
/// be run during CI builds or should only be ran in CI builds when pieces of information are available.
|
||||||
|
/// </summary>
|
||||||
|
public class RequiredEnvironmentTheoryAttribute : TheoryAttribute
|
||||||
|
{
|
||||||
|
private readonly string[] _environmentVariableNames;
|
||||||
|
|
||||||
|
public RequiredEnvironmentTheoryAttribute(params string[] environmentVariableNames)
|
||||||
|
{
|
||||||
|
_environmentVariableNames = environmentVariableNames;
|
||||||
|
|
||||||
|
if (!HasRequiredEnvironmentVariables())
|
||||||
|
{
|
||||||
|
Skip = $"Missing one or more required environment variables. ({string.Join(", ", _environmentVariableNames)})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasRequiredEnvironmentVariables()
|
||||||
|
{
|
||||||
|
foreach (var env in _environmentVariableNames)
|
||||||
|
{
|
||||||
|
var value = Environment.GetEnvironmentVariable(env);
|
||||||
|
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Api.Test", "Api.Test\Api.Te
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csproj", "{E94B2922-EE05-435C-9472-FDEFEAD0AA37}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csproj", "{E94B2922-EE05-435C-9472-FDEFEAD0AA37}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Billing.Test", "Billing.Test\Billing.Test.csproj", "{8CD044FE-3FED-4F29-858C-B06BCE70EAA6}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -72,5 +74,17 @@ Global
|
|||||||
{E94B2922-EE05-435C-9472-FDEFEAD0AA37}.Release|x64.Build.0 = Release|Any CPU
|
{E94B2922-EE05-435C-9472-FDEFEAD0AA37}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{E94B2922-EE05-435C-9472-FDEFEAD0AA37}.Release|x86.ActiveCfg = Release|Any CPU
|
{E94B2922-EE05-435C-9472-FDEFEAD0AA37}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{E94B2922-EE05-435C-9472-FDEFEAD0AA37}.Release|x86.Build.0 = Release|Any CPU
|
{E94B2922-EE05-435C-9472-FDEFEAD0AA37}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{8CD044FE-3FED-4F29-858C-B06BCE70EAA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8CD044FE-3FED-4F29-858C-B06BCE70EAA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8CD044FE-3FED-4F29-858C-B06BCE70EAA6}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{8CD044FE-3FED-4F29-858C-B06BCE70EAA6}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{8CD044FE-3FED-4F29-858C-B06BCE70EAA6}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{8CD044FE-3FED-4F29-858C-B06BCE70EAA6}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{8CD044FE-3FED-4F29-858C-B06BCE70EAA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8CD044FE-3FED-4F29-858C-B06BCE70EAA6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8CD044FE-3FED-4F29-858C-B06BCE70EAA6}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{8CD044FE-3FED-4F29-858C-B06BCE70EAA6}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{8CD044FE-3FED-4F29-858C-B06BCE70EAA6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{8CD044FE-3FED-4F29-858C-B06BCE70EAA6}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
Loading…
Reference in New Issue
Block a user