mirror of
https://github.com/bitwarden/server.git
synced 2025-07-02 00:22:50 -05:00
Merge branch 'master' into feature/flexible-collections
This commit is contained in:
@ -10,6 +10,7 @@ using Bit.Core.Enums;
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using Pipelines.Sockets.Unofficial.Arenas;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.IntegrationTest.SecretsManager.Controllers;
|
||||
@ -295,6 +296,25 @@ public class ProjectsControllerTests : IClassFixture<ApiApplicationFactory>, IAs
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_NonExistingProject_NotFound()
|
||||
{
|
||||
var (org, _) = await _organizationHelper.Initialize(true, true);
|
||||
var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
|
||||
await LoginAsync(email);
|
||||
|
||||
var createdProject = await _projectRepository.CreateAsync(new Project
|
||||
{
|
||||
OrganizationId = org.Id,
|
||||
Name = _mockEncryptedString,
|
||||
});
|
||||
|
||||
var deleteResponse = await _client.PostAsync("/projects/delete", JsonContent.Create(createdProject.Id));
|
||||
|
||||
var response = await _client.GetAsync($"/projects/{createdProject.Id}");
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(PermissionType.RunAsAdmin)]
|
||||
[InlineData(PermissionType.RunAsUserWithPermission)]
|
||||
|
@ -171,7 +171,7 @@ public class CollectionsControllerTests
|
||||
.Returns(user.Id);
|
||||
|
||||
sutProvider.GetDependency<ICollectionService>()
|
||||
.GetOrganizationCollections(orgId)
|
||||
.GetOrganizationCollectionsAsync(orgId)
|
||||
.Returns(collections);
|
||||
|
||||
// Act
|
||||
@ -237,7 +237,7 @@ public class CollectionsControllerTests
|
||||
.Returns(user.Id);
|
||||
|
||||
sutProvider.GetDependency<ICollectionService>()
|
||||
.GetOrganizationCollections(orgId)
|
||||
.GetOrganizationCollectionsAsync(orgId)
|
||||
.Returns(collections);
|
||||
|
||||
// Act
|
||||
|
@ -1181,15 +1181,6 @@
|
||||
"System.Security.Cryptography.Pkcs": "6.0.0"
|
||||
}
|
||||
},
|
||||
"Moq": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.17.2",
|
||||
"contentHash": "HytUPJ3/uks2UgJ9hIcyXm3YxpFAR4OJzbQwTHltbKGun3lFLhEHs97hiiPj1dY8jV/kasXeihTzDxct6Zf3iQ==",
|
||||
"dependencies": {
|
||||
"Castle.Core": "4.4.1",
|
||||
"System.Threading.Tasks.Extensions": "4.5.4"
|
||||
}
|
||||
},
|
||||
"MySqlConnector": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.5",
|
||||
@ -3015,129 +3006,128 @@
|
||||
"api": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"AspNetCore.HealthChecks.AzureServiceBus": "6.1.0",
|
||||
"AspNetCore.HealthChecks.AzureStorage": "6.1.2",
|
||||
"AspNetCore.HealthChecks.Network": "6.0.4",
|
||||
"AspNetCore.HealthChecks.Redis": "6.0.4",
|
||||
"AspNetCore.HealthChecks.SendGrid": "6.0.2",
|
||||
"AspNetCore.HealthChecks.SqlServer": "6.0.2",
|
||||
"AspNetCore.HealthChecks.Uris": "6.0.3",
|
||||
"Azure.Messaging.EventGrid": "4.10.0",
|
||||
"Commercial.Core": "2023.7.2",
|
||||
"Commercial.Infrastructure.EntityFramework": "2023.7.2",
|
||||
"Core": "2023.7.2",
|
||||
"SharedWeb": "2023.7.2",
|
||||
"Swashbuckle.AspNetCore": "6.5.0"
|
||||
"AspNetCore.HealthChecks.AzureServiceBus": "[6.1.0, )",
|
||||
"AspNetCore.HealthChecks.AzureStorage": "[6.1.2, )",
|
||||
"AspNetCore.HealthChecks.Network": "[6.0.4, )",
|
||||
"AspNetCore.HealthChecks.Redis": "[6.0.4, )",
|
||||
"AspNetCore.HealthChecks.SendGrid": "[6.0.2, )",
|
||||
"AspNetCore.HealthChecks.SqlServer": "[6.0.2, )",
|
||||
"AspNetCore.HealthChecks.Uris": "[6.0.3, )",
|
||||
"Azure.Messaging.EventGrid": "[4.10.0, )",
|
||||
"Commercial.Core": "[2023.7.2, )",
|
||||
"Commercial.Infrastructure.EntityFramework": "[2023.7.2, )",
|
||||
"Core": "[2023.7.2, )",
|
||||
"SharedWeb": "[2023.7.2, )",
|
||||
"Swashbuckle.AspNetCore": "[6.5.0, )"
|
||||
}
|
||||
},
|
||||
"commercial.core": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Core": "2023.7.2"
|
||||
"Core": "[2023.7.2, )"
|
||||
}
|
||||
},
|
||||
"commercial.infrastructure.entityframework": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
|
||||
"Core": "2023.7.2",
|
||||
"Infrastructure.EntityFramework": "2023.7.2"
|
||||
"AutoMapper.Extensions.Microsoft.DependencyInjection": "[12.0.1, )",
|
||||
"Core": "[2023.7.2, )",
|
||||
"Infrastructure.EntityFramework": "[2023.7.2, )"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"AutoFixture.AutoNSubstitute": "4.17.0",
|
||||
"AutoFixture.Xunit2": "4.17.0",
|
||||
"Core": "2023.7.2",
|
||||
"Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0",
|
||||
"Microsoft.NET.Test.Sdk": "17.1.0",
|
||||
"NSubstitute": "4.3.0",
|
||||
"xunit": "2.4.1"
|
||||
"AutoFixture.AutoNSubstitute": "[4.17.0, )",
|
||||
"AutoFixture.Xunit2": "[4.17.0, )",
|
||||
"Core": "[2023.7.2, )",
|
||||
"Kralizek.AutoFixture.Extensions.MockHttp": "[1.2.0, )",
|
||||
"Microsoft.NET.Test.Sdk": "[17.1.0, )",
|
||||
"NSubstitute": "[4.3.0, )",
|
||||
"xunit": "[2.4.1, )"
|
||||
}
|
||||
},
|
||||
"core": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"AWSSDK.SQS": "3.7.2.47",
|
||||
"AWSSDK.SimpleEmail": "3.7.0.150",
|
||||
"AspNetCoreRateLimit": "4.0.2",
|
||||
"AspNetCoreRateLimit.Redis": "1.0.1",
|
||||
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "1.3.2",
|
||||
"Azure.Messaging.ServiceBus": "7.15.0",
|
||||
"Azure.Storage.Blobs": "12.14.1",
|
||||
"Azure.Storage.Queues": "12.12.0",
|
||||
"BitPay.Light": "1.0.1907",
|
||||
"Braintree": "5.12.0",
|
||||
"DnsClient": "1.7.0",
|
||||
"Fido2.AspNet": "3.0.1",
|
||||
"Handlebars.Net": "2.1.2",
|
||||
"IdentityServer4": "4.1.2",
|
||||
"IdentityServer4.AccessTokenValidation": "3.0.1",
|
||||
"LaunchDarkly.ServerSdk": "7.0.0",
|
||||
"MailKit": "3.2.0",
|
||||
"Microsoft.AspNetCore.Authentication.JwtBearer": "6.0.4",
|
||||
"Microsoft.Azure.Cosmos.Table": "1.0.8",
|
||||
"Microsoft.Azure.NotificationHubs": "4.1.0",
|
||||
"Microsoft.Data.SqlClient": "5.0.1",
|
||||
"Microsoft.Extensions.Caching.StackExchangeRedis": "6.0.6",
|
||||
"Microsoft.Extensions.Configuration.EnvironmentVariables": "6.0.1",
|
||||
"Microsoft.Extensions.Configuration.UserSecrets": "6.0.1",
|
||||
"Microsoft.Extensions.Identity.Stores": "6.0.4",
|
||||
"Newtonsoft.Json": "13.0.1",
|
||||
"Otp.NET": "1.2.2",
|
||||
"Quartz": "3.4.0",
|
||||
"SendGrid": "9.27.0",
|
||||
"Sentry.Serilog": "3.16.0",
|
||||
"Serilog.AspNetCore": "5.0.0",
|
||||
"Serilog.Extensions.Logging": "3.1.0",
|
||||
"Serilog.Extensions.Logging.File": "2.0.0",
|
||||
"Serilog.Sinks.AzureCosmosDB": "2.0.0",
|
||||
"Serilog.Sinks.SyslogMessages": "2.0.6",
|
||||
"Stripe.net": "40.0.0",
|
||||
"YubicoDotNetClient": "1.2.0"
|
||||
"AWSSDK.SQS": "[3.7.2.47, )",
|
||||
"AWSSDK.SimpleEmail": "[3.7.0.150, )",
|
||||
"AspNetCoreRateLimit": "[4.0.2, )",
|
||||
"AspNetCoreRateLimit.Redis": "[1.0.1, )",
|
||||
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.3.2, )",
|
||||
"Azure.Messaging.ServiceBus": "[7.15.0, )",
|
||||
"Azure.Storage.Blobs": "[12.14.1, )",
|
||||
"Azure.Storage.Queues": "[12.12.0, )",
|
||||
"BitPay.Light": "[1.0.1907, )",
|
||||
"Braintree": "[5.12.0, )",
|
||||
"DnsClient": "[1.7.0, )",
|
||||
"Fido2.AspNet": "[3.0.1, )",
|
||||
"Handlebars.Net": "[2.1.2, )",
|
||||
"IdentityServer4": "[4.1.2, )",
|
||||
"IdentityServer4.AccessTokenValidation": "[3.0.1, )",
|
||||
"LaunchDarkly.ServerSdk": "[7.0.0, )",
|
||||
"MailKit": "[3.2.0, )",
|
||||
"Microsoft.AspNetCore.Authentication.JwtBearer": "[6.0.4, )",
|
||||
"Microsoft.Azure.Cosmos.Table": "[1.0.8, )",
|
||||
"Microsoft.Azure.NotificationHubs": "[4.1.0, )",
|
||||
"Microsoft.Data.SqlClient": "[5.0.1, )",
|
||||
"Microsoft.Extensions.Caching.StackExchangeRedis": "[6.0.6, )",
|
||||
"Microsoft.Extensions.Configuration.EnvironmentVariables": "[6.0.1, )",
|
||||
"Microsoft.Extensions.Configuration.UserSecrets": "[6.0.1, )",
|
||||
"Microsoft.Extensions.Identity.Stores": "[6.0.4, )",
|
||||
"Newtonsoft.Json": "[13.0.1, )",
|
||||
"Otp.NET": "[1.2.2, )",
|
||||
"Quartz": "[3.4.0, )",
|
||||
"SendGrid": "[9.27.0, )",
|
||||
"Sentry.Serilog": "[3.16.0, )",
|
||||
"Serilog.AspNetCore": "[5.0.0, )",
|
||||
"Serilog.Extensions.Logging": "[3.1.0, )",
|
||||
"Serilog.Extensions.Logging.File": "[2.0.0, )",
|
||||
"Serilog.Sinks.AzureCosmosDB": "[2.0.0, )",
|
||||
"Serilog.Sinks.SyslogMessages": "[2.0.6, )",
|
||||
"Stripe.net": "[40.0.0, )",
|
||||
"YubicoDotNetClient": "[1.2.0, )"
|
||||
}
|
||||
},
|
||||
"core.test": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"AutoFixture.AutoNSubstitute": "4.17.0",
|
||||
"AutoFixture.Xunit2": "4.17.0",
|
||||
"Common": "2023.7.2",
|
||||
"Core": "2023.7.2",
|
||||
"Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0",
|
||||
"Microsoft.NET.Test.Sdk": "17.1.0",
|
||||
"Moq": "4.17.2",
|
||||
"NSubstitute": "4.3.0",
|
||||
"xunit": "2.4.1"
|
||||
"AutoFixture.AutoNSubstitute": "[4.17.0, )",
|
||||
"AutoFixture.Xunit2": "[4.17.0, )",
|
||||
"Common": "[2023.7.2, )",
|
||||
"Core": "[2023.7.2, )",
|
||||
"Kralizek.AutoFixture.Extensions.MockHttp": "[1.2.0, )",
|
||||
"Microsoft.NET.Test.Sdk": "[17.1.0, )",
|
||||
"NSubstitute": "[4.3.0, )",
|
||||
"xunit": "[2.4.1, )"
|
||||
}
|
||||
},
|
||||
"infrastructure.dapper": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Core": "2023.7.2",
|
||||
"Dapper": "2.0.123"
|
||||
"Core": "[2023.7.2, )",
|
||||
"Dapper": "[2.0.123, )"
|
||||
}
|
||||
},
|
||||
"infrastructure.entityframework": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
|
||||
"Core": "2023.7.2",
|
||||
"Microsoft.EntityFrameworkCore.Relational": "7.0.5",
|
||||
"Microsoft.EntityFrameworkCore.SqlServer": "7.0.5",
|
||||
"Microsoft.EntityFrameworkCore.Sqlite": "7.0.5",
|
||||
"Npgsql.EntityFrameworkCore.PostgreSQL": "7.0.4",
|
||||
"Pomelo.EntityFrameworkCore.MySql": "7.0.0",
|
||||
"linq2db.EntityFrameworkCore": "7.5.0"
|
||||
"AutoMapper.Extensions.Microsoft.DependencyInjection": "[12.0.1, )",
|
||||
"Core": "[2023.7.2, )",
|
||||
"Microsoft.EntityFrameworkCore.Relational": "[7.0.5, )",
|
||||
"Microsoft.EntityFrameworkCore.SqlServer": "[7.0.5, )",
|
||||
"Microsoft.EntityFrameworkCore.Sqlite": "[7.0.5, )",
|
||||
"Npgsql.EntityFrameworkCore.PostgreSQL": "[7.0.4, )",
|
||||
"Pomelo.EntityFrameworkCore.MySql": "[7.0.0, )",
|
||||
"linq2db.EntityFrameworkCore": "[7.5.0, )"
|
||||
}
|
||||
},
|
||||
"sharedweb": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Core": "2023.7.2",
|
||||
"Infrastructure.Dapper": "2023.7.2",
|
||||
"Infrastructure.EntityFramework": "2023.7.2"
|
||||
"Core": "[2023.7.2, )",
|
||||
"Infrastructure.Dapper": "[2023.7.2, )",
|
||||
"Infrastructure.EntityFramework": "[2023.7.2, )"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
59
test/Common/Helpers/HtmlBuilder.cs
Normal file
59
test/Common/Helpers/HtmlBuilder.cs
Normal file
@ -0,0 +1,59 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Bit.Test.Common.Helpers;
|
||||
|
||||
public class HtmlBuilder
|
||||
{
|
||||
private string _topLevelNode;
|
||||
private readonly StringBuilder _builder = new();
|
||||
|
||||
public HtmlBuilder(string topLevelNode = "html")
|
||||
{
|
||||
_topLevelNode = CoerceTopLevelNode(topLevelNode);
|
||||
}
|
||||
|
||||
public HtmlBuilder Append(string node)
|
||||
{
|
||||
_builder.Append(node);
|
||||
return this;
|
||||
}
|
||||
|
||||
public HtmlBuilder Append(HtmlBuilder builder)
|
||||
{
|
||||
_builder.Append(builder.ToString());
|
||||
return this;
|
||||
}
|
||||
|
||||
public HtmlBuilder WithAttribute(string name, string value)
|
||||
{
|
||||
_topLevelNode = $"{_topLevelNode} {name}=\"{value}\"";
|
||||
return this;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
_builder.Insert(0, $"<{_topLevelNode}>");
|
||||
_builder.Append($"</{_topLevelNode}>");
|
||||
return _builder.ToString();
|
||||
}
|
||||
|
||||
private static string CoerceTopLevelNode(string topLevelNode)
|
||||
{
|
||||
var result = topLevelNode;
|
||||
if (topLevelNode.StartsWith("<"))
|
||||
{
|
||||
result = topLevelNode[1..];
|
||||
}
|
||||
if (topLevelNode.EndsWith(">"))
|
||||
{
|
||||
result = result[..^1];
|
||||
}
|
||||
|
||||
if (topLevelNode.IndexOf(">") != -1)
|
||||
{
|
||||
throw new ArgumentException("Top level nodes cannot contain '>' characters.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
104
test/Common/MockedHttpClient/HttpRequestMatcher.cs
Normal file
104
test/Common/MockedHttpClient/HttpRequestMatcher.cs
Normal file
@ -0,0 +1,104 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Net;
|
||||
|
||||
namespace Bit.Test.Common.MockedHttpClient;
|
||||
|
||||
public class HttpRequestMatcher : IHttpRequestMatcher
|
||||
{
|
||||
private readonly Func<HttpRequestMessage, bool> _matcher;
|
||||
private HttpRequestMatcher? _childMatcher;
|
||||
private MockedHttpResponse _mockedResponse = new(HttpStatusCode.OK);
|
||||
private bool _responseSpecified = false;
|
||||
|
||||
public int NumberOfMatches { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether or not the provided request can be handled by this matcher chain.
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
public bool Matches(HttpRequestMessage request) => _matcher(request) && (_childMatcher == null || _childMatcher.Matches(request));
|
||||
|
||||
public HttpRequestMatcher(HttpMethod method)
|
||||
{
|
||||
_matcher = request => request.Method == method;
|
||||
}
|
||||
|
||||
public HttpRequestMatcher(string uri)
|
||||
{
|
||||
_matcher = request => request.RequestUri == new Uri(uri);
|
||||
}
|
||||
|
||||
public HttpRequestMatcher(Uri uri)
|
||||
{
|
||||
_matcher = request => request.RequestUri == uri;
|
||||
}
|
||||
|
||||
public HttpRequestMatcher(HttpMethod method, string uri)
|
||||
{
|
||||
_matcher = request => request.Method == method && request.RequestUri == new Uri(uri);
|
||||
}
|
||||
|
||||
public HttpRequestMatcher(Func<HttpRequestMessage, bool> matcher)
|
||||
{
|
||||
_matcher = matcher;
|
||||
}
|
||||
|
||||
public HttpRequestMatcher WithHeader(string name, string value)
|
||||
{
|
||||
return AddChild(request => request.Headers.TryGetValues(name, out var values) && values.Contains(value));
|
||||
}
|
||||
|
||||
public HttpRequestMatcher WithQueryParameters(Dictionary<string, string> requiredQueryParameters) =>
|
||||
WithQueryParameters(requiredQueryParameters.Select(x => $"{x.Key}={x.Value}").ToArray());
|
||||
public HttpRequestMatcher WithQueryParameters(string name, string value) =>
|
||||
WithQueryParameters($"{name}={value}");
|
||||
public HttpRequestMatcher WithQueryParameters(params string[] queryKeyValues)
|
||||
{
|
||||
bool matcher(HttpRequestMessage request)
|
||||
{
|
||||
var query = request.RequestUri?.Query;
|
||||
if (query == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return queryKeyValues.All(queryKeyValue => query.Contains(queryKeyValue));
|
||||
}
|
||||
return AddChild(matcher);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure how this matcher should respond to matching HttpRequestMessages.
|
||||
/// Note, after specifying a response, you can no longer further specify match criteria.
|
||||
/// </summary>
|
||||
/// <param name="statusCode"></param>
|
||||
/// <returns></returns>
|
||||
public MockedHttpResponse RespondWith(HttpStatusCode statusCode)
|
||||
{
|
||||
_responseSpecified = true;
|
||||
_mockedResponse = new MockedHttpResponse(statusCode);
|
||||
return _mockedResponse;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called to produce an HttpResponseMessage for the given request. This is probably something you want to leave alone
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
public async Task<HttpResponseMessage> RespondToAsync(HttpRequestMessage request)
|
||||
{
|
||||
NumberOfMatches++;
|
||||
return await (_childMatcher == null ? _mockedResponse.RespondToAsync(request) : _childMatcher.RespondToAsync(request));
|
||||
}
|
||||
|
||||
private HttpRequestMatcher AddChild(Func<HttpRequestMessage, bool> matcher)
|
||||
{
|
||||
if (_responseSpecified)
|
||||
{
|
||||
throw new Exception("Cannot continue to configure a matcher after a response has been specified");
|
||||
}
|
||||
_childMatcher = new HttpRequestMatcher(matcher);
|
||||
return _childMatcher;
|
||||
}
|
||||
}
|
84
test/Common/MockedHttpClient/HttpResponseBuilder.cs
Normal file
84
test/Common/MockedHttpClient/HttpResponseBuilder.cs
Normal file
@ -0,0 +1,84 @@
|
||||
using System.Net;
|
||||
|
||||
namespace Bit.Test.Common.MockedHttpClient;
|
||||
|
||||
public class HttpResponseBuilder : IDisposable
|
||||
{
|
||||
private bool _disposedValue;
|
||||
|
||||
public HttpStatusCode StatusCode { get; set; }
|
||||
public IEnumerable<KeyValuePair<string, string>> Headers { get; set; } = new List<KeyValuePair<string, string>>();
|
||||
public IEnumerable<string> HeadersToRemove { get; set; } = new List<string>();
|
||||
public HttpContent Content { get; set; }
|
||||
|
||||
public async Task<HttpResponseMessage> ToHttpResponseAsync()
|
||||
{
|
||||
var copiedContentStream = new MemoryStream();
|
||||
await Content.CopyToAsync(copiedContentStream); // This is important, otherwise the content stream will be disposed when the response is disposed.
|
||||
copiedContentStream.Seek(0, SeekOrigin.Begin);
|
||||
var message = new HttpResponseMessage(StatusCode)
|
||||
{
|
||||
Content = new StreamContent(copiedContentStream),
|
||||
};
|
||||
|
||||
foreach (var header in Headers)
|
||||
{
|
||||
message.Headers.TryAddWithoutValidation(header.Key, header.Value);
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public HttpResponseBuilder WithStatusCode(HttpStatusCode statusCode)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
StatusCode = statusCode,
|
||||
Headers = Headers,
|
||||
HeadersToRemove = HeadersToRemove,
|
||||
Content = Content,
|
||||
};
|
||||
}
|
||||
|
||||
public HttpResponseBuilder WithHeader(string name, string value)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
StatusCode = StatusCode,
|
||||
Headers = Headers.Append(new KeyValuePair<string, string>(name, value)),
|
||||
HeadersToRemove = HeadersToRemove,
|
||||
Content = Content,
|
||||
};
|
||||
}
|
||||
|
||||
public HttpResponseBuilder WithContent(HttpContent content)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
StatusCode = StatusCode,
|
||||
Headers = Headers,
|
||||
HeadersToRemove = HeadersToRemove,
|
||||
Content = content,
|
||||
};
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Content?.Dispose();
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
10
test/Common/MockedHttpClient/IHttpRequestMatcher.cs
Normal file
10
test/Common/MockedHttpClient/IHttpRequestMatcher.cs
Normal file
@ -0,0 +1,10 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Bit.Test.Common.MockedHttpClient;
|
||||
|
||||
public interface IHttpRequestMatcher
|
||||
{
|
||||
int NumberOfMatches { get; }
|
||||
bool Matches(HttpRequestMessage request);
|
||||
Task<HttpResponseMessage> RespondToAsync(HttpRequestMessage request);
|
||||
}
|
7
test/Common/MockedHttpClient/IMockedHttpResponse.cs
Normal file
7
test/Common/MockedHttpClient/IMockedHttpResponse.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Bit.Test.Common.MockedHttpClient;
|
||||
|
||||
public interface IMockedHttpResponse
|
||||
{
|
||||
int NumberOfResponses { get; }
|
||||
Task<HttpResponseMessage> RespondToAsync(HttpRequestMessage request);
|
||||
}
|
113
test/Common/MockedHttpClient/MockedHttpMessageHandler.cs
Normal file
113
test/Common/MockedHttpClient/MockedHttpMessageHandler.cs
Normal file
@ -0,0 +1,113 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Net;
|
||||
|
||||
namespace Bit.Test.Common.MockedHttpClient;
|
||||
|
||||
public class MockedHttpMessageHandler : HttpMessageHandler
|
||||
{
|
||||
private readonly List<IHttpRequestMatcher> _matchers = new();
|
||||
|
||||
/// <summary>
|
||||
/// The fallback handler to use when the request does not match any of the provided matchers.
|
||||
/// </summary>
|
||||
/// <returns>A Matcher that responds with 404 Not Found</returns>
|
||||
public MockedHttpResponse Fallback { get; set; } = new(HttpStatusCode.NotFound);
|
||||
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
var matcher = _matchers.FirstOrDefault(x => x.Matches(request));
|
||||
if (matcher == null)
|
||||
{
|
||||
return await Fallback.RespondToAsync(request);
|
||||
}
|
||||
|
||||
return await matcher.RespondToAsync(request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new HttpRequestMessage matcher that will handle requests in fitting with the returned matcher. Configuration can be chained.
|
||||
/// </summary>
|
||||
/// <param name="requestMatcher"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public T When<T>(T requestMatcher) where T : IHttpRequestMatcher
|
||||
{
|
||||
_matchers.Add(requestMatcher);
|
||||
return requestMatcher;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new HttpRequestMessage matcher that will handle requests in fitting with the returned matcher. Configuration can be chained.
|
||||
/// </summary>
|
||||
/// <param name="requestMatcher"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public HttpRequestMatcher When(string uri)
|
||||
{
|
||||
var matcher = new HttpRequestMatcher(uri);
|
||||
_matchers.Add(matcher);
|
||||
return matcher;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new HttpRequestMessage matcher that will handle requests in fitting with the returned matcher. Configuration can be chained.
|
||||
/// </summary>
|
||||
/// <param name="requestMatcher"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public HttpRequestMatcher When(Uri uri)
|
||||
{
|
||||
var matcher = new HttpRequestMatcher(uri);
|
||||
_matchers.Add(matcher);
|
||||
return matcher;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new HttpRequestMessage matcher that will handle requests in fitting with the returned matcher. Configuration can be chained.
|
||||
/// </summary>
|
||||
/// <param name="requestMatcher"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public HttpRequestMatcher When(HttpMethod method)
|
||||
{
|
||||
var matcher = new HttpRequestMatcher(method);
|
||||
_matchers.Add(matcher);
|
||||
return matcher;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new HttpRequestMessage matcher that will handle requests in fitting with the returned matcher. Configuration can be chained.
|
||||
/// </summary>
|
||||
/// <param name="requestMatcher"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public HttpRequestMatcher When(HttpMethod method, string uri)
|
||||
{
|
||||
var matcher = new HttpRequestMatcher(method, uri);
|
||||
_matchers.Add(matcher);
|
||||
return matcher;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new HttpRequestMessage matcher that will handle requests in fitting with the returned matcher. Configuration can be chained.
|
||||
/// </summary>
|
||||
/// <param name="requestMatcher"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public HttpRequestMatcher When(Func<HttpRequestMessage, bool> matcher)
|
||||
{
|
||||
var requestMatcher = new HttpRequestMatcher(matcher);
|
||||
_matchers.Add(requestMatcher);
|
||||
return requestMatcher;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the MockedHttpMessageHandler to a HttpClient that can be used in your tests after setup.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public HttpClient ToHttpClient()
|
||||
{
|
||||
return new HttpClient(this);
|
||||
}
|
||||
}
|
68
test/Common/MockedHttpClient/MockedHttpResponse.cs
Normal file
68
test/Common/MockedHttpClient/MockedHttpResponse.cs
Normal file
@ -0,0 +1,68 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
|
||||
namespace Bit.Test.Common.MockedHttpClient;
|
||||
|
||||
public class MockedHttpResponse : IMockedHttpResponse
|
||||
{
|
||||
private MockedHttpResponse _childResponse;
|
||||
private readonly Func<HttpRequestMessage, HttpResponseBuilder, HttpResponseBuilder> _responder;
|
||||
|
||||
public int NumberOfResponses { get; private set; }
|
||||
|
||||
public MockedHttpResponse(HttpStatusCode statusCode)
|
||||
{
|
||||
_responder = (_, builder) => builder.WithStatusCode(statusCode);
|
||||
}
|
||||
|
||||
private MockedHttpResponse(Func<HttpRequestMessage, HttpResponseBuilder, HttpResponseBuilder> responder)
|
||||
{
|
||||
_responder = responder;
|
||||
}
|
||||
|
||||
public MockedHttpResponse WithStatusCode(HttpStatusCode statusCode)
|
||||
{
|
||||
return AddChild((_, builder) => builder.WithStatusCode(statusCode));
|
||||
}
|
||||
|
||||
public MockedHttpResponse WithHeader(string name, string value)
|
||||
{
|
||||
return AddChild((_, builder) => builder.WithHeader(name, value));
|
||||
}
|
||||
public MockedHttpResponse WithHeaders(params KeyValuePair<string, string>[] headers)
|
||||
{
|
||||
return AddChild((_, builder) => headers.Aggregate(builder, (b, header) => b.WithHeader(header.Key, header.Value)));
|
||||
}
|
||||
|
||||
public MockedHttpResponse WithContent(string mediaType, string content)
|
||||
{
|
||||
return WithContent(new StringContent(content, Encoding.UTF8, mediaType));
|
||||
}
|
||||
public MockedHttpResponse WithContent(string mediaType, byte[] content)
|
||||
{
|
||||
return WithContent(new ByteArrayContent(content) { Headers = { ContentType = new MediaTypeHeaderValue(mediaType) } });
|
||||
}
|
||||
public MockedHttpResponse WithContent(HttpContent content)
|
||||
{
|
||||
return AddChild((_, builder) => builder.WithContent(content));
|
||||
}
|
||||
|
||||
public async Task<HttpResponseMessage> RespondToAsync(HttpRequestMessage request)
|
||||
{
|
||||
return await RespondToAsync(request, new HttpResponseBuilder());
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> RespondToAsync(HttpRequestMessage request, HttpResponseBuilder currentBuilder)
|
||||
{
|
||||
NumberOfResponses++;
|
||||
var nextBuilder = _responder(request, currentBuilder);
|
||||
return await (_childResponse == null ? nextBuilder.ToHttpResponseAsync() : _childResponse.RespondToAsync(request, nextBuilder));
|
||||
}
|
||||
|
||||
private MockedHttpResponse AddChild(Func<HttpRequestMessage, HttpResponseBuilder, HttpResponseBuilder> responder)
|
||||
{
|
||||
_childResponse = new MockedHttpResponse(responder);
|
||||
return _childResponse;
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ using AutoFixture.Xunit2;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Test.Helpers.Factories;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Moq;
|
||||
using NSubstitute;
|
||||
|
||||
namespace Bit.Test.Common.AutoFixture;
|
||||
|
||||
@ -33,17 +33,17 @@ public class GlobalSettingsBuilder : ISpecimenBuilder
|
||||
|
||||
if (pi.ParameterType == typeof(IDataProtectionProvider))
|
||||
{
|
||||
var dataProtector = new Mock<IDataProtector>();
|
||||
dataProtector
|
||||
.Setup(d => d.Unprotect(It.IsAny<byte[]>()))
|
||||
.Returns<byte[]>(data => Encoding.UTF8.GetBytes(Constants.DatabaseFieldProtectedPrefix + Encoding.UTF8.GetString(data)));
|
||||
var dataProtector = Substitute.For<IDataProtector>();
|
||||
dataProtector.Unprotect(Arg.Any<byte[]>())
|
||||
.Returns(data =>
|
||||
Encoding.UTF8.GetBytes(Constants.DatabaseFieldProtectedPrefix +
|
||||
Encoding.UTF8.GetString((byte[])data[0])));
|
||||
|
||||
var dataProtectionProvider = new Mock<IDataProtectionProvider>();
|
||||
dataProtectionProvider
|
||||
.Setup(x => x.CreateProtector(Constants.DatabaseFieldProtectorPurpose))
|
||||
.Returns(dataProtector.Object);
|
||||
var dataProtectionProvider = Substitute.For<IDataProtectionProvider>();
|
||||
dataProtectionProvider.CreateProtector(Constants.DatabaseFieldProtectorPurpose)
|
||||
.Returns(dataProtector);
|
||||
|
||||
return dataProtectionProvider.Object;
|
||||
return dataProtectionProvider;
|
||||
}
|
||||
|
||||
return new NoSpecimen();
|
||||
|
@ -9,7 +9,6 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNetTestSdkVersion)" />
|
||||
<PackageReference Include="Moq" Version="4.17.2" />
|
||||
<PackageReference Include="NSubstitute" Version="$(NSubstituteVersion)" />
|
||||
<PackageReference Include="xunit" Version="$(XUnitVersion)" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="$(XUnitRunnerVisualStudioVersion)">
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data;
|
||||
@ -185,4 +186,56 @@ public class CollectionServiceTest
|
||||
.LogOrganizationUserEventAsync(default, default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetOrganizationCollectionsAsync_WithViewAssignedCollectionsTrue_ReturnsAssignedCollections(
|
||||
CollectionDetails collectionDetails, Guid organizationId, Guid userId, SutProvider<CollectionService> sutProvider)
|
||||
{
|
||||
collectionDetails.OrganizationId = organizationId;
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
|
||||
sutProvider.GetDependency<ICollectionRepository>()
|
||||
.GetManyByUserIdAsync(userId)
|
||||
.Returns(new List<CollectionDetails> { collectionDetails });
|
||||
sutProvider.GetDependency<ICurrentContext>().ViewAssignedCollections(organizationId).Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.GetOrganizationCollectionsAsync(organizationId);
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(collectionDetails, result.First());
|
||||
|
||||
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs().GetManyByOrganizationIdAsync(default);
|
||||
await sutProvider.GetDependency<ICollectionRepository>().Received(1).GetManyByUserIdAsync(userId);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetOrganizationCollectionsAsync_WithViewAllCollectionsTrue_ReturnsAllOrganizationCollections(
|
||||
Collection collection, Guid organizationId, Guid userId, SutProvider<CollectionService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
|
||||
sutProvider.GetDependency<ICollectionRepository>()
|
||||
.GetManyByOrganizationIdAsync(organizationId)
|
||||
.Returns(new List<Collection> { collection });
|
||||
sutProvider.GetDependency<ICurrentContext>().ViewAssignedCollections(organizationId).Returns(true);
|
||||
sutProvider.GetDependency<ICurrentContext>().ViewAllCollections(organizationId).Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.GetOrganizationCollectionsAsync(organizationId);
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(collection, result.First());
|
||||
|
||||
await sutProvider.GetDependency<ICollectionRepository>().Received(1).GetManyByOrganizationIdAsync(organizationId);
|
||||
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs().GetManyByUserIdAsync(default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetOrganizationCollectionsAsync_WithViewAssignedCollectionsFalse_ThrowsBadRequestException(
|
||||
Guid organizationId, SutProvider<CollectionService> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().ViewAssignedCollections(organizationId).Returns(false);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetOrganizationCollectionsAsync(organizationId));
|
||||
|
||||
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs().GetManyByOrganizationIdAsync(default);
|
||||
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs().GetManyByUserIdAsync(default);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using NSubstitute;
|
||||
using StackExchange.Redis;
|
||||
using Xunit;
|
||||
|
||||
@ -37,14 +37,12 @@ public class CustomRedisProcessingStrategyTests
|
||||
|
||||
#endregion
|
||||
|
||||
private readonly Mock<ICounterKeyBuilder> _mockCounterKeyBuilder = new();
|
||||
private Mock<IDatabase> _mockDb;
|
||||
private readonly ICounterKeyBuilder _mockCounterKeyBuilder = Substitute.For<ICounterKeyBuilder>();
|
||||
private IDatabase _mockDb;
|
||||
|
||||
public CustomRedisProcessingStrategyTests()
|
||||
{
|
||||
_mockCounterKeyBuilder
|
||||
.Setup(x =>
|
||||
x.Build(It.IsAny<ClientRequestIdentity>(), It.IsAny<RateLimitRule>()))
|
||||
_mockCounterKeyBuilder.Build(Arg.Any<ClientRequestIdentity>(), Arg.Any<RateLimitRule>())
|
||||
.Returns(_sampleClientId.ClientId);
|
||||
}
|
||||
|
||||
@ -55,12 +53,12 @@ public class CustomRedisProcessingStrategyTests
|
||||
var strategy = BuildProcessingStrategy();
|
||||
|
||||
// Act
|
||||
var result = await strategy.ProcessRequestAsync(_sampleClientId, _sampleRule, _mockCounterKeyBuilder.Object, _sampleOptions,
|
||||
var result = await strategy.ProcessRequestAsync(_sampleClientId, _sampleRule, _mockCounterKeyBuilder, _sampleOptions,
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, result.Count);
|
||||
VerifyRedisCalls(Times.Once());
|
||||
VerifyRedisCalls(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -70,60 +68,63 @@ public class CustomRedisProcessingStrategyTests
|
||||
var strategy = BuildProcessingStrategy(false);
|
||||
|
||||
// Act
|
||||
var result = await strategy.ProcessRequestAsync(_sampleClientId, _sampleRule, _mockCounterKeyBuilder.Object, _sampleOptions,
|
||||
var result = await strategy.ProcessRequestAsync(_sampleClientId, _sampleRule, _mockCounterKeyBuilder, _sampleOptions,
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, result.Count);
|
||||
VerifyRedisCalls(Times.Never());
|
||||
VerifyRedisNotCalled();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SkipRateLimit_When_TimeoutThresholdExceeded()
|
||||
{
|
||||
// Arrange
|
||||
var mockCache = new Mock<IMemoryCache>();
|
||||
var mockCache = Substitute.For<IMemoryCache>();
|
||||
object existingCount = new CustomRedisProcessingStrategy.TimeoutCounter
|
||||
{
|
||||
Count = _sampleSettings.DistributedIpRateLimiting.MaxRedisTimeoutsThreshold + 1
|
||||
};
|
||||
mockCache.Setup(x => x.TryGetValue(It.IsAny<object>(), out existingCount)).Returns(true);
|
||||
mockCache.TryGetValue(Arg.Any<object>(), out existingCount).ReturnsForAnyArgs(x =>
|
||||
{
|
||||
x[1] = existingCount;
|
||||
return true;
|
||||
});
|
||||
|
||||
var strategy = BuildProcessingStrategy(mockCache: mockCache.Object);
|
||||
var strategy = BuildProcessingStrategy(mockCache: mockCache);
|
||||
|
||||
// Act
|
||||
var result = await strategy.ProcessRequestAsync(_sampleClientId, _sampleRule, _mockCounterKeyBuilder.Object, _sampleOptions,
|
||||
var result = await strategy.ProcessRequestAsync(_sampleClientId, _sampleRule, _mockCounterKeyBuilder, _sampleOptions,
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, result.Count);
|
||||
VerifyRedisCalls(Times.Never());
|
||||
VerifyRedisNotCalled();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SkipRateLimit_When_RedisTimeoutException()
|
||||
{
|
||||
// Arrange
|
||||
var mockCache = new Mock<IMemoryCache>();
|
||||
var mockCacheEntry = new Mock<ICacheEntry>();
|
||||
mockCacheEntry.SetupAllProperties();
|
||||
mockCache.Setup(x => x.CreateEntry(It.IsAny<object>())).Returns(mockCacheEntry.Object);
|
||||
var mockCache = Substitute.For<IMemoryCache>();
|
||||
var mockCacheEntry = Substitute.For<ICacheEntry>();
|
||||
mockCache.CreateEntry(Arg.Any<object>()).Returns(mockCacheEntry);
|
||||
|
||||
var strategy = BuildProcessingStrategy(mockCache: mockCache.Object, throwRedisTimeout: true);
|
||||
var strategy = BuildProcessingStrategy(mockCache: mockCache, throwRedisTimeout: true);
|
||||
|
||||
// Act
|
||||
var result = await strategy.ProcessRequestAsync(_sampleClientId, _sampleRule, _mockCounterKeyBuilder.Object, _sampleOptions,
|
||||
var result = await strategy.ProcessRequestAsync(_sampleClientId, _sampleRule, _mockCounterKeyBuilder, _sampleOptions,
|
||||
CancellationToken.None);
|
||||
|
||||
var timeoutCounter = ((CustomRedisProcessingStrategy.TimeoutCounter)mockCacheEntry.Object.Value);
|
||||
var timeoutCounter = ((CustomRedisProcessingStrategy.TimeoutCounter)mockCacheEntry.Value);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, result.Count); // Skip rate limiting
|
||||
VerifyRedisCalls(Times.Once());
|
||||
VerifyRedisCalls(1);
|
||||
|
||||
Assert.Equal(1, timeoutCounter.Count); // Timeout count increased/cached
|
||||
Assert.NotNull(mockCacheEntry.Object.AbsoluteExpiration);
|
||||
mockCache.Verify(x => x.CreateEntry(It.IsAny<object>()));
|
||||
Assert.NotNull(mockCacheEntry.AbsoluteExpiration);
|
||||
mockCache.Received().CreateEntry(Arg.Any<object>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -136,26 +137,33 @@ public class CustomRedisProcessingStrategyTests
|
||||
// Act
|
||||
|
||||
// Redis Timeout 1
|
||||
await strategy.ProcessRequestAsync(_sampleClientId, _sampleRule, _mockCounterKeyBuilder.Object, _sampleOptions,
|
||||
await strategy.ProcessRequestAsync(_sampleClientId, _sampleRule, _mockCounterKeyBuilder, _sampleOptions,
|
||||
CancellationToken.None);
|
||||
|
||||
// Redis Timeout 2
|
||||
await strategy.ProcessRequestAsync(_sampleClientId, _sampleRule, _mockCounterKeyBuilder.Object, _sampleOptions,
|
||||
await strategy.ProcessRequestAsync(_sampleClientId, _sampleRule, _mockCounterKeyBuilder, _sampleOptions,
|
||||
CancellationToken.None);
|
||||
|
||||
// Skip Redis
|
||||
await strategy.ProcessRequestAsync(_sampleClientId, _sampleRule, _mockCounterKeyBuilder.Object, _sampleOptions,
|
||||
await strategy.ProcessRequestAsync(_sampleClientId, _sampleRule, _mockCounterKeyBuilder, _sampleOptions,
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
VerifyRedisCalls(Times.Exactly(_sampleSettings.DistributedIpRateLimiting.MaxRedisTimeoutsThreshold));
|
||||
VerifyRedisCalls(_sampleSettings.DistributedIpRateLimiting.MaxRedisTimeoutsThreshold);
|
||||
}
|
||||
|
||||
private void VerifyRedisCalls(Times times)
|
||||
private void VerifyRedisCalls(int times)
|
||||
{
|
||||
_mockDb.Verify(x =>
|
||||
x.ScriptEvaluateAsync(It.IsAny<LuaScript>(), It.IsAny<object>(), It.IsAny<CommandFlags>()),
|
||||
times);
|
||||
_mockDb
|
||||
.Received(times)
|
||||
.ScriptEvaluateAsync(Arg.Any<LuaScript>(), Arg.Any<object>(), Arg.Any<CommandFlags>());
|
||||
}
|
||||
|
||||
private void VerifyRedisNotCalled()
|
||||
{
|
||||
_mockDb
|
||||
.DidNotReceive()
|
||||
.ScriptEvaluateAsync(Arg.Any<LuaScript>(), Arg.Any<object>(), Arg.Any<CommandFlags>());
|
||||
}
|
||||
|
||||
private CustomRedisProcessingStrategy BuildProcessingStrategy(
|
||||
@ -163,36 +171,33 @@ public class CustomRedisProcessingStrategyTests
|
||||
bool throwRedisTimeout = false,
|
||||
IMemoryCache mockCache = null)
|
||||
{
|
||||
var mockRedisConnection = new Mock<IConnectionMultiplexer>();
|
||||
var mockRedisConnection = Substitute.For<IConnectionMultiplexer>();
|
||||
|
||||
mockRedisConnection.Setup(x => x.IsConnected).Returns(isRedisConnected);
|
||||
mockRedisConnection.IsConnected.Returns(isRedisConnected);
|
||||
|
||||
_mockDb = new Mock<IDatabase>();
|
||||
_mockDb = Substitute.For<IDatabase>();
|
||||
|
||||
var mockScriptEvaluate = _mockDb
|
||||
.Setup(x =>
|
||||
x.ScriptEvaluateAsync(It.IsAny<LuaScript>(), It.IsAny<object>(), It.IsAny<CommandFlags>()));
|
||||
.ScriptEvaluateAsync(Arg.Any<LuaScript>(), Arg.Any<object>(), Arg.Any<CommandFlags>());
|
||||
|
||||
if (throwRedisTimeout)
|
||||
{
|
||||
mockScriptEvaluate.ThrowsAsync(new RedisTimeoutException("Timeout", CommandStatus.WaitingToBeSent));
|
||||
mockScriptEvaluate.Returns<RedisResult>(x => throw new RedisTimeoutException("Timeout", CommandStatus.WaitingToBeSent));
|
||||
}
|
||||
else
|
||||
{
|
||||
mockScriptEvaluate.ReturnsAsync(RedisResult.Create(1));
|
||||
mockScriptEvaluate.Returns(RedisResult.Create(1));
|
||||
}
|
||||
|
||||
mockRedisConnection
|
||||
.Setup(x =>
|
||||
x.GetDatabase(It.IsAny<int>(), It.IsAny<object>()))
|
||||
.Returns(_mockDb.Object);
|
||||
mockRedisConnection.GetDatabase(Arg.Any<int>(), Arg.Any<object>())
|
||||
.Returns(_mockDb);
|
||||
|
||||
var mockLogger = new Mock<ILogger<CustomRedisProcessingStrategy>>();
|
||||
var mockConfig = new Mock<IRateLimitConfiguration>();
|
||||
var mockLogger = Substitute.For<ILogger<CustomRedisProcessingStrategy>>();
|
||||
var mockConfig = Substitute.For<IRateLimitConfiguration>();
|
||||
|
||||
mockCache ??= new Mock<IMemoryCache>().Object;
|
||||
mockCache ??= Substitute.For<IMemoryCache>();
|
||||
|
||||
return new CustomRedisProcessingStrategy(mockRedisConnection.Object, mockConfig.Object,
|
||||
mockLogger.Object, mockCache, _sampleSettings);
|
||||
return new CustomRedisProcessingStrategy(mockRedisConnection, mockConfig,
|
||||
mockLogger, mockCache, _sampleSettings);
|
||||
}
|
||||
}
|
||||
|
@ -48,16 +48,6 @@
|
||||
"Microsoft.TestPlatform.TestHost": "17.1.0"
|
||||
}
|
||||
},
|
||||
"Moq": {
|
||||
"type": "Direct",
|
||||
"requested": "[4.17.2, )",
|
||||
"resolved": "4.17.2",
|
||||
"contentHash": "HytUPJ3/uks2UgJ9hIcyXm3YxpFAR4OJzbQwTHltbKGun3lFLhEHs97hiiPj1dY8jV/kasXeihTzDxct6Zf3iQ==",
|
||||
"dependencies": {
|
||||
"Castle.Core": "4.4.1",
|
||||
"System.Threading.Tasks.Extensions": "4.5.4"
|
||||
}
|
||||
},
|
||||
"NSubstitute": {
|
||||
"type": "Direct",
|
||||
"requested": "[4.3.0, )",
|
||||
@ -2680,55 +2670,55 @@
|
||||
"common": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"AutoFixture.AutoNSubstitute": "4.17.0",
|
||||
"AutoFixture.Xunit2": "4.17.0",
|
||||
"Core": "2023.7.2",
|
||||
"Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0",
|
||||
"Microsoft.NET.Test.Sdk": "17.1.0",
|
||||
"NSubstitute": "4.3.0",
|
||||
"xunit": "2.4.1"
|
||||
"AutoFixture.AutoNSubstitute": "[4.17.0, )",
|
||||
"AutoFixture.Xunit2": "[4.17.0, )",
|
||||
"Core": "[2023.7.2, )",
|
||||
"Kralizek.AutoFixture.Extensions.MockHttp": "[1.2.0, )",
|
||||
"Microsoft.NET.Test.Sdk": "[17.1.0, )",
|
||||
"NSubstitute": "[4.3.0, )",
|
||||
"xunit": "[2.4.1, )"
|
||||
}
|
||||
},
|
||||
"core": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"AWSSDK.SQS": "3.7.2.47",
|
||||
"AWSSDK.SimpleEmail": "3.7.0.150",
|
||||
"AspNetCoreRateLimit": "4.0.2",
|
||||
"AspNetCoreRateLimit.Redis": "1.0.1",
|
||||
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "1.3.2",
|
||||
"Azure.Messaging.ServiceBus": "7.15.0",
|
||||
"Azure.Storage.Blobs": "12.14.1",
|
||||
"Azure.Storage.Queues": "12.12.0",
|
||||
"BitPay.Light": "1.0.1907",
|
||||
"Braintree": "5.12.0",
|
||||
"DnsClient": "1.7.0",
|
||||
"Fido2.AspNet": "3.0.1",
|
||||
"Handlebars.Net": "2.1.2",
|
||||
"IdentityServer4": "4.1.2",
|
||||
"IdentityServer4.AccessTokenValidation": "3.0.1",
|
||||
"LaunchDarkly.ServerSdk": "7.0.0",
|
||||
"MailKit": "3.2.0",
|
||||
"Microsoft.AspNetCore.Authentication.JwtBearer": "6.0.4",
|
||||
"Microsoft.Azure.Cosmos.Table": "1.0.8",
|
||||
"Microsoft.Azure.NotificationHubs": "4.1.0",
|
||||
"Microsoft.Data.SqlClient": "5.0.1",
|
||||
"Microsoft.Extensions.Caching.StackExchangeRedis": "6.0.6",
|
||||
"Microsoft.Extensions.Configuration.EnvironmentVariables": "6.0.1",
|
||||
"Microsoft.Extensions.Configuration.UserSecrets": "6.0.1",
|
||||
"Microsoft.Extensions.Identity.Stores": "6.0.4",
|
||||
"Newtonsoft.Json": "13.0.1",
|
||||
"Otp.NET": "1.2.2",
|
||||
"Quartz": "3.4.0",
|
||||
"SendGrid": "9.27.0",
|
||||
"Sentry.Serilog": "3.16.0",
|
||||
"Serilog.AspNetCore": "5.0.0",
|
||||
"Serilog.Extensions.Logging": "3.1.0",
|
||||
"Serilog.Extensions.Logging.File": "2.0.0",
|
||||
"Serilog.Sinks.AzureCosmosDB": "2.0.0",
|
||||
"Serilog.Sinks.SyslogMessages": "2.0.6",
|
||||
"Stripe.net": "40.0.0",
|
||||
"YubicoDotNetClient": "1.2.0"
|
||||
"AWSSDK.SQS": "[3.7.2.47, )",
|
||||
"AWSSDK.SimpleEmail": "[3.7.0.150, )",
|
||||
"AspNetCoreRateLimit": "[4.0.2, )",
|
||||
"AspNetCoreRateLimit.Redis": "[1.0.1, )",
|
||||
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.3.2, )",
|
||||
"Azure.Messaging.ServiceBus": "[7.15.0, )",
|
||||
"Azure.Storage.Blobs": "[12.14.1, )",
|
||||
"Azure.Storage.Queues": "[12.12.0, )",
|
||||
"BitPay.Light": "[1.0.1907, )",
|
||||
"Braintree": "[5.12.0, )",
|
||||
"DnsClient": "[1.7.0, )",
|
||||
"Fido2.AspNet": "[3.0.1, )",
|
||||
"Handlebars.Net": "[2.1.2, )",
|
||||
"IdentityServer4": "[4.1.2, )",
|
||||
"IdentityServer4.AccessTokenValidation": "[3.0.1, )",
|
||||
"LaunchDarkly.ServerSdk": "[7.0.0, )",
|
||||
"MailKit": "[3.2.0, )",
|
||||
"Microsoft.AspNetCore.Authentication.JwtBearer": "[6.0.4, )",
|
||||
"Microsoft.Azure.Cosmos.Table": "[1.0.8, )",
|
||||
"Microsoft.Azure.NotificationHubs": "[4.1.0, )",
|
||||
"Microsoft.Data.SqlClient": "[5.0.1, )",
|
||||
"Microsoft.Extensions.Caching.StackExchangeRedis": "[6.0.6, )",
|
||||
"Microsoft.Extensions.Configuration.EnvironmentVariables": "[6.0.1, )",
|
||||
"Microsoft.Extensions.Configuration.UserSecrets": "[6.0.1, )",
|
||||
"Microsoft.Extensions.Identity.Stores": "[6.0.4, )",
|
||||
"Newtonsoft.Json": "[13.0.1, )",
|
||||
"Otp.NET": "[1.2.2, )",
|
||||
"Quartz": "[3.4.0, )",
|
||||
"SendGrid": "[9.27.0, )",
|
||||
"Sentry.Serilog": "[3.16.0, )",
|
||||
"Serilog.AspNetCore": "[5.0.0, )",
|
||||
"Serilog.Extensions.Logging": "[3.1.0, )",
|
||||
"Serilog.Extensions.Logging.File": "[2.0.0, )",
|
||||
"Serilog.Sinks.AzureCosmosDB": "[2.0.0, )",
|
||||
"Serilog.Sinks.SyslogMessages": "[2.0.6, )",
|
||||
"Stripe.net": "[40.0.0, )",
|
||||
"YubicoDotNetClient": "[1.2.0, )"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Icons\Icons.csproj" />
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
38
test/Icons.Test/Models/IconHttpRequestTests.cs
Normal file
38
test/Icons.Test/Models/IconHttpRequestTests.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using System.Net;
|
||||
using Bit.Icons.Models;
|
||||
using Bit.Icons.Services;
|
||||
using Bit.Test.Common.MockedHttpClient;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Icons.Test.Models;
|
||||
|
||||
public class IconHttpRequestTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task FetchAsync_FollowsTwoRedirectsAsync()
|
||||
{
|
||||
var handler = new MockedHttpMessageHandler();
|
||||
|
||||
var request = handler
|
||||
.Fallback
|
||||
.WithStatusCode(HttpStatusCode.Redirect)
|
||||
.WithContent("text/html", "<html><head><title>Redirect 2</title></head><body><a href=\"https://icon.test\">Redirect 3</a></body></html>")
|
||||
.WithHeader(HeaderNames.Location, "https://icon.test");
|
||||
|
||||
var clientFactory = Substitute.For<IHttpClientFactory>();
|
||||
clientFactory.CreateClient("Icons").Returns(handler.ToHttpClient());
|
||||
|
||||
var uriService = Substitute.For<IUriService>();
|
||||
uriService.TryGetUri(Arg.Any<Uri>(), out Arg.Any<IconUri>()).Returns(x =>
|
||||
{
|
||||
x[1] = new IconUri(new Uri("https://icon.test"), IPAddress.Parse("192.0.2.1"));
|
||||
return true;
|
||||
});
|
||||
var result = await IconHttpRequest.FetchAsync(new Uri("https://icon.test"), NullLogger<IIconFetchingService>.Instance, clientFactory, uriService);
|
||||
|
||||
Assert.Equal(3, request.NumberOfResponses); // Initial + 2 redirects
|
||||
}
|
||||
}
|
101
test/Icons.Test/Models/IconHttpResponseTests.cs
Normal file
101
test/Icons.Test/Models/IconHttpResponseTests.cs
Normal file
@ -0,0 +1,101 @@
|
||||
using System.Net;
|
||||
using AngleSharp.Html.Parser;
|
||||
using Bit.Icons.Models;
|
||||
using Bit.Icons.Services;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using Bit.Test.Common.MockedHttpClient;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Icons.Test.Models;
|
||||
|
||||
public class IconHttpResponseTests
|
||||
{
|
||||
private readonly IUriService _mockedUriService;
|
||||
private static readonly IHtmlParser _parser = new HtmlParser();
|
||||
|
||||
public IconHttpResponseTests()
|
||||
{
|
||||
_mockedUriService = Substitute.For<IUriService>();
|
||||
_mockedUriService.TryGetUri(Arg.Any<Uri>(), out Arg.Any<IconUri>()).Returns(x =>
|
||||
{
|
||||
x[1] = new IconUri(new Uri("https://icon.test"), IPAddress.Parse("192.0.2.1"));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RetrieveIconsAsync_Processes200LinksAsync()
|
||||
{
|
||||
var htmlBuilder = new HtmlBuilder();
|
||||
var headBuilder = new HtmlBuilder("head");
|
||||
for (var i = 0; i < 200; i++)
|
||||
{
|
||||
headBuilder.Append(UnusableLinkNode());
|
||||
}
|
||||
headBuilder.Append(UsableLinkNode());
|
||||
htmlBuilder.Append(headBuilder);
|
||||
var response = GetHttpResponseMessage(htmlBuilder.ToString());
|
||||
var sut = CurriedIconHttpResponse()(response);
|
||||
|
||||
var result = await sut.RetrieveIconsAsync(new Uri("https://icon.test"), "icon.test", _parser);
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RetrieveIconsAsync_Processes10IconsAsync()
|
||||
{
|
||||
var htmlBuilder = new HtmlBuilder();
|
||||
var headBuilder = new HtmlBuilder("head");
|
||||
for (var i = 0; i < 11; i++)
|
||||
{
|
||||
headBuilder.Append(UsableLinkNode());
|
||||
}
|
||||
htmlBuilder.Append(headBuilder);
|
||||
var response = GetHttpResponseMessage(htmlBuilder.ToString());
|
||||
var sut = CurriedIconHttpResponse()(response);
|
||||
|
||||
var result = await sut.RetrieveIconsAsync(new Uri("https://icon.test"), "icon.test", _parser);
|
||||
|
||||
Assert.Equal(10, result.Count());
|
||||
}
|
||||
|
||||
private static string UsableLinkNode()
|
||||
{
|
||||
return "<link rel=\"icon\" href=\"https://icon.test/favicon.ico\" />";
|
||||
}
|
||||
|
||||
private static string UnusableLinkNode()
|
||||
{
|
||||
// Empty href links are not usable
|
||||
return "<link rel=\"icon\" href=\"\" />";
|
||||
}
|
||||
|
||||
private static HttpResponseMessage GetHttpResponseMessage(string content)
|
||||
{
|
||||
return new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
RequestMessage = new HttpRequestMessage(HttpMethod.Get, "https://icon.test"),
|
||||
Content = new StringContent(content)
|
||||
};
|
||||
}
|
||||
|
||||
private Func<HttpResponseMessage, IconHttpResponse> CurriedIconHttpResponse()
|
||||
{
|
||||
return (HttpResponseMessage response) => new IconHttpResponse(response, NullLogger<IIconFetchingService>.Instance, UsableIconHttpClientFactory(), _mockedUriService);
|
||||
}
|
||||
|
||||
private static IHttpClientFactory UsableIconHttpClientFactory()
|
||||
{
|
||||
var substitute = Substitute.For<IHttpClientFactory>();
|
||||
var handler = new MockedHttpMessageHandler();
|
||||
handler.Fallback
|
||||
.WithStatusCode(HttpStatusCode.OK)
|
||||
.WithContent("image/png", new byte[] { 137, 80, 78, 71 });
|
||||
|
||||
substitute.CreateClient("Icons").Returns(handler.ToHttpClient());
|
||||
return substitute;
|
||||
}
|
||||
}
|
85
test/Icons.Test/Models/IconLinkTests.cs
Normal file
85
test/Icons.Test/Models/IconLinkTests.cs
Normal file
@ -0,0 +1,85 @@
|
||||
using System.Net;
|
||||
using AngleSharp.Dom;
|
||||
using Bit.Icons.Models;
|
||||
using Bit.Icons.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Icons.Test.Models;
|
||||
|
||||
public class IconLinkTests
|
||||
{
|
||||
private readonly IElement _element;
|
||||
private readonly Uri _uri = new("https://icon.test");
|
||||
private readonly ILogger<IIconFetchingService> _logger = Substitute.For<ILogger<IIconFetchingService>>();
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IUriService _uriService;
|
||||
private readonly string _baseUrlPath = "/";
|
||||
|
||||
public IconLinkTests()
|
||||
{
|
||||
_element = Substitute.For<IElement>();
|
||||
_httpClientFactory = Substitute.For<IHttpClientFactory>();
|
||||
_uriService = Substitute.For<IUriService>();
|
||||
_uriService.TryGetUri(Arg.Any<Uri>(), out Arg.Any<IconUri>()).Returns(x =>
|
||||
{
|
||||
x[1] = new IconUri(new Uri("https://icon.test"), IPAddress.Parse("192.0.2.1"));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithNoHref_IsNotUsable()
|
||||
{
|
||||
_element.GetAttribute("href").Returns(string.Empty);
|
||||
|
||||
var result = new IconLink(_element, _uri, _baseUrlPath).IsUsable();
|
||||
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, false)]
|
||||
[InlineData("", false)]
|
||||
[InlineData(" ", false)]
|
||||
[InlineData("unusable", false)]
|
||||
[InlineData("ico", true)]
|
||||
public void WithNoRel_IsUsable(string extension, bool expectedResult)
|
||||
{
|
||||
SetAttributeValue("href", $"/favicon.{extension}");
|
||||
|
||||
var result = new IconLink(_element, _uri, _baseUrlPath).IsUsable();
|
||||
|
||||
Assert.Equal(expectedResult, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("icon", true)]
|
||||
[InlineData("stylesheet", false)]
|
||||
public void WithRel_IsUsable(string rel, bool expectedResult)
|
||||
{
|
||||
SetAttributeValue("href", "/favicon.ico");
|
||||
SetAttributeValue("rel", rel);
|
||||
|
||||
var result = new IconLink(_element, _uri, _baseUrlPath).IsUsable();
|
||||
|
||||
Assert.Equal(expectedResult, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FetchAsync_Unvalidated_ReturnsNull()
|
||||
{
|
||||
var result = new IconLink(_element, _uri, _baseUrlPath).FetchAsync(_logger, _httpClientFactory, _uriService);
|
||||
|
||||
Assert.Null(result.Result);
|
||||
}
|
||||
|
||||
private void SetAttributeValue(string attribute, string value)
|
||||
{
|
||||
var attr = Substitute.For<IAttr>();
|
||||
attr.Value.Returns(value);
|
||||
|
||||
_element.Attributes[attribute].Returns(attr);
|
||||
}
|
||||
}
|
22
test/Icons.Test/Models/IconUriTests.cs
Normal file
22
test/Icons.Test/Models/IconUriTests.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System.Net;
|
||||
using Bit.Icons.Models;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Icons.Test.Models;
|
||||
|
||||
public class IconUriTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("https://icon.test", "1.1.1.1", true)]
|
||||
[InlineData("https://icon.test:4443", "1.1.1.1", false)] // Non standard port
|
||||
[InlineData("http://test", "1.1.1.1", false)] // top level domain
|
||||
[InlineData("https://icon.test", "127.0.0.1", false)] // IP is internal
|
||||
[InlineData("https://icon.test", "::1", false)] // IP is internal
|
||||
[InlineData("https://1.1.1.1", "::1", false)] // host is IP
|
||||
public void IsValid(string uri, string ip, bool expectedResult)
|
||||
{
|
||||
var result = new IconUri(new Uri(uri), IPAddress.Parse(ip)).IsValid;
|
||||
|
||||
Assert.Equal(expectedResult, result);
|
||||
}
|
||||
}
|
@ -1,25 +1,25 @@
|
||||
using Bit.Icons.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Icons.Test.Services;
|
||||
|
||||
public class IconFetchingServiceTests
|
||||
public class IconFetchingServiceTests : ServiceTestBase<IconFetchingService>
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("www.twitter.com")] // https site
|
||||
[InlineData("www.google.com")] // https site
|
||||
[InlineData("neverssl.com")] // http site
|
||||
[InlineData("ameritrade.com")]
|
||||
[InlineData("neopets.com")] // uses favicon.ico
|
||||
[InlineData("hopin.com")] // uses svg+xml format
|
||||
[InlineData("ameritrade.com")] // redirects to tdameritrace.com
|
||||
[InlineData("icloud.com")]
|
||||
[InlineData("bofa.com", Skip = "Broken in pipeline for .NET 6. Tracking link: https://bitwarden.atlassian.net/browse/PS-982")]
|
||||
public async Task GetIconAsync_Success(string domain)
|
||||
{
|
||||
var sut = new IconFetchingService(GetLogger());
|
||||
var sut = BuildSut();
|
||||
var result = await sut.GetIconAsync(domain);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.NotNull(result.Icon);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@ -28,23 +28,12 @@ public class IconFetchingServiceTests
|
||||
[InlineData("localhost")]
|
||||
public async Task GetIconAsync_ReturnsNull(string domain)
|
||||
{
|
||||
var sut = new IconFetchingService(GetLogger());
|
||||
var sut = BuildSut();
|
||||
var result = await sut.GetIconAsync(domain);
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
private static ILogger<IconFetchingService> GetLogger()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddLogging(b =>
|
||||
{
|
||||
b.ClearProviders();
|
||||
b.AddDebug();
|
||||
});
|
||||
|
||||
var provider = services.BuildServiceProvider();
|
||||
|
||||
return provider.GetRequiredService<ILogger<IconFetchingService>>();
|
||||
}
|
||||
private IconFetchingService BuildSut() =>
|
||||
GetService<IconFetchingService>();
|
||||
}
|
||||
|
41
test/Icons.Test/Services/ServiceTestBase.cs
Normal file
41
test/Icons.Test/Services/ServiceTestBase.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using Bit.Icons.Extensions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Bit.Icons.Test.Services;
|
||||
|
||||
public class ServiceTestBase
|
||||
{
|
||||
internal ServiceCollection _services = new();
|
||||
internal ServiceProvider _provider;
|
||||
|
||||
public ServiceTestBase()
|
||||
{
|
||||
_services = new ServiceCollection();
|
||||
_services.AddLogging(b =>
|
||||
{
|
||||
b.ClearProviders();
|
||||
b.AddDebug();
|
||||
});
|
||||
|
||||
_services.ConfigureHttpClients();
|
||||
_services.AddHtmlParsing();
|
||||
_services.AddServices();
|
||||
|
||||
_provider = _services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
public T GetService<T>() =>
|
||||
_provider.GetRequiredService<T>();
|
||||
}
|
||||
|
||||
public class ServiceTestBase<TSut> : ServiceTestBase where TSut : class
|
||||
{
|
||||
public ServiceTestBase() : base()
|
||||
{
|
||||
_services.AddTransient<TSut>();
|
||||
_provider = _services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
public TSut Sut => GetService<TSut>();
|
||||
}
|
@ -46,11 +46,10 @@
|
||||
},
|
||||
"AngleSharp": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.16.1",
|
||||
"contentHash": "1k7Vbfmr5IUsGaR0QJwTe8XF9zacFUIoWxMgI4X/ipiyKxCWZJZoaG96fNEugL90iubvboRvE1IxuBPibET/Rg==",
|
||||
"resolved": "1.0.4",
|
||||
"contentHash": "G8R4C2MEDFQvjUbYz1QIcGfibjsTJnzP0aWy8iQgWWk7eKacYydCNGD3JMhVL0Q5pZQ9RYlqpKNESEU5NpqsRw==",
|
||||
"dependencies": {
|
||||
"System.Buffers": "4.5.1",
|
||||
"System.Text.Encoding.CodePages": "5.0.0"
|
||||
"System.Text.Encoding.CodePages": "6.0.0"
|
||||
}
|
||||
},
|
||||
"AspNetCoreRateLimit": {
|
||||
@ -73,6 +72,33 @@
|
||||
"StackExchange.Redis": "2.5.43"
|
||||
}
|
||||
},
|
||||
"AutoFixture": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.17.0",
|
||||
"contentHash": "efMRCG3Epc4QDELwdmQGf6/caQUleRXPRCnLAq5gLMpTuOTcOQWV12vEJ8qo678Rj97/TjjxHYu/34rGkXdVAA==",
|
||||
"dependencies": {
|
||||
"Fare": "[2.1.1, 3.0.0)",
|
||||
"System.ComponentModel.Annotations": "4.3.0"
|
||||
}
|
||||
},
|
||||
"AutoFixture.AutoNSubstitute": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.17.0",
|
||||
"contentHash": "iWsRiDQ7T8s6F4mvYbSvPTq0GDtxJD6D+E1Fu9gVbHUvJiNikC1yIDNTH+3tQF7RK864HH/3R8ETj9m2X8UXvg==",
|
||||
"dependencies": {
|
||||
"AutoFixture": "4.17.0",
|
||||
"NSubstitute": "[2.0.3, 5.0.0)"
|
||||
}
|
||||
},
|
||||
"AutoFixture.Xunit2": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.17.0",
|
||||
"contentHash": "lrURL/LhJLPkn2tSPUEW8Wscr5LoV2Mr8A+ikn5gwkofex3o7qWUsBswlLw+KCA7EOpeqwZOldp3k91zDF+48Q==",
|
||||
"dependencies": {
|
||||
"AutoFixture": "4.17.0",
|
||||
"xunit.extensibility.core": "[2.2.0, 3.0.0)"
|
||||
}
|
||||
},
|
||||
"AutoMapper": {
|
||||
"type": "Transitive",
|
||||
"resolved": "12.0.1",
|
||||
@ -246,6 +272,14 @@
|
||||
"Microsoft.Win32.Registry": "5.0.0"
|
||||
}
|
||||
},
|
||||
"Fare": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.1.1",
|
||||
"contentHash": "HaI8puqA66YU7/9cK4Sgbs1taUTP1Ssa4QT2PIzqJ7GvAbN1QgkjbRsjH+FSbMh1MJdvS0CIwQNLtFT+KF6KpA==",
|
||||
"dependencies": {
|
||||
"NETStandard.Library": "1.6.1"
|
||||
}
|
||||
},
|
||||
"Fido2": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.0.1",
|
||||
@ -326,6 +360,15 @@
|
||||
"IdentityModel": "4.4.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"
|
||||
}
|
||||
},
|
||||
"LaunchDarkly.Cache": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.0.2",
|
||||
@ -1148,6 +1191,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",
|
||||
@ -1568,6 +1616,24 @@
|
||||
"System.Runtime": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.ComponentModel.Annotations": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
"contentHash": "SY2RLItHt43rd8J9D8M8e8NM4m+9WLN2uUd9G0n1I4hj/7w+v3pzK6ZBjexlG1/2xvLKQsqir3UGVSyBTXMLWA==",
|
||||
"dependencies": {
|
||||
"System.Collections": "4.3.0",
|
||||
"System.ComponentModel": "4.3.0",
|
||||
"System.Globalization": "4.3.0",
|
||||
"System.Linq": "4.3.0",
|
||||
"System.Reflection": "4.3.0",
|
||||
"System.Reflection.Extensions": "4.3.0",
|
||||
"System.Resources.ResourceManager": "4.3.0",
|
||||
"System.Runtime": "4.3.0",
|
||||
"System.Runtime.Extensions": "4.3.0",
|
||||
"System.Text.RegularExpressions": "4.3.0",
|
||||
"System.Threading": "4.3.0"
|
||||
}
|
||||
},
|
||||
"System.ComponentModel.Primitives": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.3.0",
|
||||
@ -2509,10 +2575,10 @@
|
||||
},
|
||||
"System.Text.Encoding.CodePages": {
|
||||
"type": "Transitive",
|
||||
"resolved": "5.0.0",
|
||||
"contentHash": "NyscU59xX6Uo91qvhOs2Ccho3AR2TnZPomo1Z0K6YpyztBPM/A5VbkzOO19sy3A3i1TtEnTxA7bCe3Us+r5MWg==",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "ZFCILZuOvtKPauZ/j/swhvw68ZRi9ATCfvGbk1QfydmcXBkIWecWKn/250UH7rahZ5OoDBaiAudJtPvLwzw85A==",
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.Platforms": "5.0.0"
|
||||
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
|
||||
}
|
||||
},
|
||||
"System.Text.Encoding.Extensions": {
|
||||
@ -2770,6 +2836,18 @@
|
||||
"NETStandard.Library": "1.6.1"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"AutoFixture.AutoNSubstitute": "[4.17.0, )",
|
||||
"AutoFixture.Xunit2": "[4.17.0, )",
|
||||
"Core": "[2023.7.2, )",
|
||||
"Kralizek.AutoFixture.Extensions.MockHttp": "[1.2.0, )",
|
||||
"Microsoft.NET.Test.Sdk": "[17.1.0, )",
|
||||
"NSubstitute": "[4.3.0, )",
|
||||
"xunit": "[2.4.1, )"
|
||||
}
|
||||
},
|
||||
"core": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
@ -2815,7 +2893,7 @@
|
||||
"icons": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"AngleSharp": "0.16.1",
|
||||
"AngleSharp": "1.0.4",
|
||||
"Core": "2023.7.2",
|
||||
"SharedWeb": "2023.7.2"
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
|
||||
using var body = await AssertHelper.AssertResponseTypeIs<JsonDocument>(context);
|
||||
var endpointRoot = body.RootElement;
|
||||
|
||||
// WARNING: Edits to this file should NOT just be made to "get the test to work" they should be made when intentional
|
||||
// WARNING: Edits to this file should NOT just be made to "get the test to work" they should be made when intentional
|
||||
// changes were made to this endpoint and proper testing will take place to ensure clients are backwards compatible
|
||||
// or loss of functionality is properly noted.
|
||||
await using var fs = File.OpenRead("openid-configuration.json");
|
||||
@ -372,10 +372,10 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This test currently does not test any code that is not covered by other tests but
|
||||
/// This test currently does not test any code that is not covered by other tests but
|
||||
/// it shows that we probably have some dead code in <see cref="ClientStore"/>
|
||||
/// for installation, organization, and user they split on a <c>'.'</c> but have already checked that at least one
|
||||
/// <c>'.'</c> exists in the <c>client_id</c> by checking it with <see cref="string.StartsWith(string)"/>
|
||||
/// <c>'.'</c> exists in the <c>client_id</c> by checking it with <see cref="string.StartsWith(string)"/>
|
||||
/// I believe that idParts.Length > 1 will ALWAYS return true
|
||||
/// </summary>
|
||||
[Fact]
|
||||
@ -488,9 +488,9 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task TokenEndpoint_ToQuickInOneSecond_BlockRequest(string deviceId)
|
||||
public async Task TokenEndpoint_TooQuickInOneSecond_BlockRequest(string deviceId)
|
||||
{
|
||||
const int AmountInOneSecondAllowed = 5;
|
||||
const int AmountInOneSecondAllowed = 10;
|
||||
|
||||
// The rule we are testing is 10 requests in 1 second
|
||||
var username = "test+ratelimiting@email.com";
|
||||
@ -514,9 +514,9 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
|
||||
}
|
||||
|
||||
var responses = (await Task.WhenAll(tasks)).ToList();
|
||||
var blockResponses = responses.Where(c => c.Response.StatusCode == StatusCodes.Status429TooManyRequests);
|
||||
|
||||
Assert.Equal(5, responses.Count(c => c.Response.StatusCode == StatusCodes.Status200OK));
|
||||
Assert.Equal(1, responses.Count(c => c.Response.StatusCode == StatusCodes.Status429TooManyRequests));
|
||||
Assert.True(blockResponses.Count() > 0);
|
||||
|
||||
Task<HttpContext> MakeRequest()
|
||||
{
|
||||
|
@ -10,7 +10,7 @@ using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Bit.Infrastructure.EntityFramework.Vault.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Moq;
|
||||
using NSubstitute;
|
||||
|
||||
namespace Bit.Infrastructure.EFIntegration.Test.AutoFixture;
|
||||
|
||||
@ -25,20 +25,16 @@ internal class ServiceScopeFactoryBuilder : ISpecimenBuilder
|
||||
public object Create(object request, ISpecimenContext context)
|
||||
{
|
||||
var fixture = new Fixture();
|
||||
var serviceProvider = new Mock<IServiceProvider>();
|
||||
var serviceProvider = Substitute.For<IServiceProvider>();
|
||||
var dbContext = new DatabaseContext(_options);
|
||||
serviceProvider
|
||||
.Setup(x => x.GetService(typeof(DatabaseContext)))
|
||||
.Returns(dbContext);
|
||||
serviceProvider.GetService(typeof(DatabaseContext)).Returns(dbContext);
|
||||
|
||||
var serviceScope = new Mock<IServiceScope>();
|
||||
serviceScope.Setup(x => x.ServiceProvider).Returns(serviceProvider.Object);
|
||||
var serviceScope = Substitute.For<IServiceScope>();
|
||||
serviceScope.ServiceProvider.Returns(serviceProvider);
|
||||
|
||||
var serviceScopeFactory = new Mock<IServiceScopeFactory>();
|
||||
serviceScopeFactory
|
||||
.Setup(x => x.CreateScope())
|
||||
.Returns(serviceScope.Object);
|
||||
return serviceScopeFactory.Object;
|
||||
var serviceScopeFactory = Substitute.For<IServiceScopeFactory>();
|
||||
serviceScopeFactory.CreateScope().Returns(serviceScope);
|
||||
return serviceScopeFactory;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Moq;
|
||||
using NSubstitute;
|
||||
|
||||
namespace Bit.Infrastructure.EFIntegration.Test.Helpers;
|
||||
|
||||
@ -18,17 +18,17 @@ public static class DatabaseOptionsFactory
|
||||
var services = new ServiceCollection()
|
||||
.AddSingleton(sp =>
|
||||
{
|
||||
var dataProtector = new Mock<IDataProtector>();
|
||||
dataProtector
|
||||
.Setup(d => d.Unprotect(It.IsAny<byte[]>()))
|
||||
.Returns<byte[]>(data => Encoding.UTF8.GetBytes(Constants.DatabaseFieldProtectedPrefix + Encoding.UTF8.GetString(data)));
|
||||
var dataProtector = Substitute.For<IDataProtector>();
|
||||
dataProtector.Unprotect(Arg.Any<byte[]>())
|
||||
.Returns<byte[]>(data =>
|
||||
Encoding.UTF8.GetBytes(Constants.DatabaseFieldProtectedPrefix +
|
||||
Encoding.UTF8.GetString((byte[])data[0])));
|
||||
|
||||
var dataProtectionProvider = new Mock<IDataProtectionProvider>();
|
||||
dataProtectionProvider
|
||||
.Setup(x => x.CreateProtector(Constants.DatabaseFieldProtectorPurpose))
|
||||
.Returns(dataProtector.Object);
|
||||
var dataProtectionProvider = Substitute.For<IDataProtectionProvider>();
|
||||
dataProtectionProvider.CreateProtector(Constants.DatabaseFieldProtectorPurpose)
|
||||
.Returns(dataProtector);
|
||||
|
||||
return dataProtectionProvider.Object;
|
||||
return dataProtectionProvider;
|
||||
})
|
||||
.BuildServiceProvider();
|
||||
|
||||
|
@ -8,7 +8,6 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNetTestSdkVersion)" />
|
||||
<PackageReference Include="Moq" Version="4.17.2" />
|
||||
<PackageReference Include="NSubstitute" Version="$(NSubstituteVersion)" />
|
||||
<PackageReference Include="xunit" Version="$(XUnitVersion)" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="$(XUnitRunnerVisualStudioVersion)">
|
||||
|
@ -38,16 +38,6 @@
|
||||
"Microsoft.TestPlatform.TestHost": "17.1.0"
|
||||
}
|
||||
},
|
||||
"Moq": {
|
||||
"type": "Direct",
|
||||
"requested": "[4.17.2, )",
|
||||
"resolved": "4.17.2",
|
||||
"contentHash": "HytUPJ3/uks2UgJ9hIcyXm3YxpFAR4OJzbQwTHltbKGun3lFLhEHs97hiiPj1dY8jV/kasXeihTzDxct6Zf3iQ==",
|
||||
"dependencies": {
|
||||
"Castle.Core": "4.4.1",
|
||||
"System.Threading.Tasks.Extensions": "4.5.4"
|
||||
}
|
||||
},
|
||||
"NSubstitute": {
|
||||
"type": "Direct",
|
||||
"requested": "[4.3.0, )",
|
||||
@ -2843,89 +2833,88 @@
|
||||
"common": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"AutoFixture.AutoNSubstitute": "4.17.0",
|
||||
"AutoFixture.Xunit2": "4.17.0",
|
||||
"Core": "2023.7.2",
|
||||
"Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0",
|
||||
"Microsoft.NET.Test.Sdk": "17.1.0",
|
||||
"NSubstitute": "4.3.0",
|
||||
"xunit": "2.4.1"
|
||||
"AutoFixture.AutoNSubstitute": "[4.17.0, )",
|
||||
"AutoFixture.Xunit2": "[4.17.0, )",
|
||||
"Core": "[2023.7.2, )",
|
||||
"Kralizek.AutoFixture.Extensions.MockHttp": "[1.2.0, )",
|
||||
"Microsoft.NET.Test.Sdk": "[17.1.0, )",
|
||||
"NSubstitute": "[4.3.0, )",
|
||||
"xunit": "[2.4.1, )"
|
||||
}
|
||||
},
|
||||
"core": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"AWSSDK.SQS": "3.7.2.47",
|
||||
"AWSSDK.SimpleEmail": "3.7.0.150",
|
||||
"AspNetCoreRateLimit": "4.0.2",
|
||||
"AspNetCoreRateLimit.Redis": "1.0.1",
|
||||
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "1.3.2",
|
||||
"Azure.Messaging.ServiceBus": "7.15.0",
|
||||
"Azure.Storage.Blobs": "12.14.1",
|
||||
"Azure.Storage.Queues": "12.12.0",
|
||||
"BitPay.Light": "1.0.1907",
|
||||
"Braintree": "5.12.0",
|
||||
"DnsClient": "1.7.0",
|
||||
"Fido2.AspNet": "3.0.1",
|
||||
"Handlebars.Net": "2.1.2",
|
||||
"IdentityServer4": "4.1.2",
|
||||
"IdentityServer4.AccessTokenValidation": "3.0.1",
|
||||
"LaunchDarkly.ServerSdk": "7.0.0",
|
||||
"MailKit": "3.2.0",
|
||||
"Microsoft.AspNetCore.Authentication.JwtBearer": "6.0.4",
|
||||
"Microsoft.Azure.Cosmos.Table": "1.0.8",
|
||||
"Microsoft.Azure.NotificationHubs": "4.1.0",
|
||||
"Microsoft.Data.SqlClient": "5.0.1",
|
||||
"Microsoft.Extensions.Caching.StackExchangeRedis": "6.0.6",
|
||||
"Microsoft.Extensions.Configuration.EnvironmentVariables": "6.0.1",
|
||||
"Microsoft.Extensions.Configuration.UserSecrets": "6.0.1",
|
||||
"Microsoft.Extensions.Identity.Stores": "6.0.4",
|
||||
"Newtonsoft.Json": "13.0.1",
|
||||
"Otp.NET": "1.2.2",
|
||||
"Quartz": "3.4.0",
|
||||
"SendGrid": "9.27.0",
|
||||
"Sentry.Serilog": "3.16.0",
|
||||
"Serilog.AspNetCore": "5.0.0",
|
||||
"Serilog.Extensions.Logging": "3.1.0",
|
||||
"Serilog.Extensions.Logging.File": "2.0.0",
|
||||
"Serilog.Sinks.AzureCosmosDB": "2.0.0",
|
||||
"Serilog.Sinks.SyslogMessages": "2.0.6",
|
||||
"Stripe.net": "40.0.0",
|
||||
"YubicoDotNetClient": "1.2.0"
|
||||
"AWSSDK.SQS": "[3.7.2.47, )",
|
||||
"AWSSDK.SimpleEmail": "[3.7.0.150, )",
|
||||
"AspNetCoreRateLimit": "[4.0.2, )",
|
||||
"AspNetCoreRateLimit.Redis": "[1.0.1, )",
|
||||
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.3.2, )",
|
||||
"Azure.Messaging.ServiceBus": "[7.15.0, )",
|
||||
"Azure.Storage.Blobs": "[12.14.1, )",
|
||||
"Azure.Storage.Queues": "[12.12.0, )",
|
||||
"BitPay.Light": "[1.0.1907, )",
|
||||
"Braintree": "[5.12.0, )",
|
||||
"DnsClient": "[1.7.0, )",
|
||||
"Fido2.AspNet": "[3.0.1, )",
|
||||
"Handlebars.Net": "[2.1.2, )",
|
||||
"IdentityServer4": "[4.1.2, )",
|
||||
"IdentityServer4.AccessTokenValidation": "[3.0.1, )",
|
||||
"LaunchDarkly.ServerSdk": "[7.0.0, )",
|
||||
"MailKit": "[3.2.0, )",
|
||||
"Microsoft.AspNetCore.Authentication.JwtBearer": "[6.0.4, )",
|
||||
"Microsoft.Azure.Cosmos.Table": "[1.0.8, )",
|
||||
"Microsoft.Azure.NotificationHubs": "[4.1.0, )",
|
||||
"Microsoft.Data.SqlClient": "[5.0.1, )",
|
||||
"Microsoft.Extensions.Caching.StackExchangeRedis": "[6.0.6, )",
|
||||
"Microsoft.Extensions.Configuration.EnvironmentVariables": "[6.0.1, )",
|
||||
"Microsoft.Extensions.Configuration.UserSecrets": "[6.0.1, )",
|
||||
"Microsoft.Extensions.Identity.Stores": "[6.0.4, )",
|
||||
"Newtonsoft.Json": "[13.0.1, )",
|
||||
"Otp.NET": "[1.2.2, )",
|
||||
"Quartz": "[3.4.0, )",
|
||||
"SendGrid": "[9.27.0, )",
|
||||
"Sentry.Serilog": "[3.16.0, )",
|
||||
"Serilog.AspNetCore": "[5.0.0, )",
|
||||
"Serilog.Extensions.Logging": "[3.1.0, )",
|
||||
"Serilog.Extensions.Logging.File": "[2.0.0, )",
|
||||
"Serilog.Sinks.AzureCosmosDB": "[2.0.0, )",
|
||||
"Serilog.Sinks.SyslogMessages": "[2.0.6, )",
|
||||
"Stripe.net": "[40.0.0, )",
|
||||
"YubicoDotNetClient": "[1.2.0, )"
|
||||
}
|
||||
},
|
||||
"core.test": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"AutoFixture.AutoNSubstitute": "4.17.0",
|
||||
"AutoFixture.Xunit2": "4.17.0",
|
||||
"Common": "2023.7.2",
|
||||
"Core": "2023.7.2",
|
||||
"Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0",
|
||||
"Microsoft.NET.Test.Sdk": "17.1.0",
|
||||
"Moq": "4.17.2",
|
||||
"NSubstitute": "4.3.0",
|
||||
"xunit": "2.4.1"
|
||||
"AutoFixture.AutoNSubstitute": "[4.17.0, )",
|
||||
"AutoFixture.Xunit2": "[4.17.0, )",
|
||||
"Common": "[2023.7.2, )",
|
||||
"Core": "[2023.7.2, )",
|
||||
"Kralizek.AutoFixture.Extensions.MockHttp": "[1.2.0, )",
|
||||
"Microsoft.NET.Test.Sdk": "[17.1.0, )",
|
||||
"NSubstitute": "[4.3.0, )",
|
||||
"xunit": "[2.4.1, )"
|
||||
}
|
||||
},
|
||||
"infrastructure.dapper": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Core": "2023.7.2",
|
||||
"Dapper": "2.0.123"
|
||||
"Core": "[2023.7.2, )",
|
||||
"Dapper": "[2.0.123, )"
|
||||
}
|
||||
},
|
||||
"infrastructure.entityframework": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
|
||||
"Core": "2023.7.2",
|
||||
"Microsoft.EntityFrameworkCore.Relational": "7.0.5",
|
||||
"Microsoft.EntityFrameworkCore.SqlServer": "7.0.5",
|
||||
"Microsoft.EntityFrameworkCore.Sqlite": "7.0.5",
|
||||
"Npgsql.EntityFrameworkCore.PostgreSQL": "7.0.4",
|
||||
"Pomelo.EntityFrameworkCore.MySql": "7.0.0",
|
||||
"linq2db.EntityFrameworkCore": "7.5.0"
|
||||
"AutoMapper.Extensions.Microsoft.DependencyInjection": "[12.0.1, )",
|
||||
"Core": "[2023.7.2, )",
|
||||
"Microsoft.EntityFrameworkCore.Relational": "[7.0.5, )",
|
||||
"Microsoft.EntityFrameworkCore.SqlServer": "[7.0.5, )",
|
||||
"Microsoft.EntityFrameworkCore.Sqlite": "[7.0.5, )",
|
||||
"Npgsql.EntityFrameworkCore.PostgreSQL": "[7.0.4, )",
|
||||
"Pomelo.EntityFrameworkCore.MySql": "[7.0.0, )",
|
||||
"linq2db.EntityFrameworkCore": "[7.5.0, )"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user