mirror of
https://github.com/bitwarden/mobile
synced 2026-01-07 11:03:54 +00:00
Port send jslib to mobile (#1219)
* Expand Hkdf crypto functions * Add tests for hkdf crypto functions Took the testing infrastructure from bitwarden/server * Move Hkdf to cryptoFunctionService * Port changes from bitwarden/jslib#192 * Port changes from bitwarden/jslib#205 * Make Send Expiration Optional implement changes from bitwarden/jslib#242 * Bug fixes found by testing * Test helpers * Test conversion between model types * Test SendService These are mostly happy-path tests to ensure a reasonably correct implementation * Add run tests step to GitHub Actions * Test send decryption * Test Request generation from Send * Constructor dependencies on separate lines * Remove unused testing infrastructure * Rename to match class name * Move fat arrows to previous lines * Handle exceptions in App layer * PR review cleanups * Throw when attempting to save an unkown Send Type I think it's best to only throw on unknown send types here. I don't think we want to throw whenever we encounter one since that would do bad things like lock up Sync if clients get out of date relative to servers. Instead, keep the client from ruining saved data by complaining last minute that it doesn't know what it's doing.
This commit is contained in:
10
test/Common/AutoFixture/Attributes/AutoSubDataAttribute.cs
Normal file
10
test/Common/AutoFixture/Attributes/AutoSubDataAttribute.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using AutoFixture.AutoNSubstitute;
|
||||
|
||||
namespace Bit.Test.Common.AutoFixture.Attributes
|
||||
{
|
||||
public class AutoSubstitutionData : CustomAutoDataAttribute
|
||||
{
|
||||
public AutoSubstitutionData() : base(typeof(AutoNSubstituteCustomization))
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using AutoFixture;
|
||||
using AutoFixture.Xunit2;
|
||||
|
||||
namespace Bit.Test.Common.AutoFixture.Attributes
|
||||
{
|
||||
public class CustomAutoDataAttribute : AutoDataAttribute
|
||||
{
|
||||
public CustomAutoDataAttribute(params Type[] iCustomizationTypes) : this(iCustomizationTypes
|
||||
.Select(t => (ICustomization)Activator.CreateInstance(t)).ToArray())
|
||||
{ }
|
||||
|
||||
public CustomAutoDataAttribute(params ICustomization[] customizations) : base(() =>
|
||||
{
|
||||
var fixture = new Fixture();
|
||||
foreach (var customization in customizations)
|
||||
{
|
||||
fixture.Customize(customization);
|
||||
}
|
||||
return fixture;
|
||||
})
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using AutoFixture.AutoNSubstitute;
|
||||
|
||||
namespace Bit.Test.Common.AutoFixture.Attributes
|
||||
{
|
||||
public class InlineAutoSubstitutionData : InlineCustomAutoDataAttribute
|
||||
{
|
||||
public InlineAutoSubstitutionData(params object[] values) : base(new[] { typeof(AutoNSubstituteCustomization) }, values)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using Xunit;
|
||||
using Xunit.Sdk;
|
||||
using AutoFixture.Xunit2;
|
||||
using AutoFixture;
|
||||
|
||||
namespace Bit.Test.Common.AutoFixture.Attributes
|
||||
{
|
||||
public class InlineCustomAutoDataAttribute : CompositeDataAttribute
|
||||
{
|
||||
public InlineCustomAutoDataAttribute(Type[] iCustomizationTypes, params object[] values) : base(new DataAttribute[] {
|
||||
new InlineDataAttribute(values),
|
||||
new CustomAutoDataAttribute(iCustomizationTypes)
|
||||
})
|
||||
{ }
|
||||
|
||||
public InlineCustomAutoDataAttribute(ICustomization[] customizations, params object[] values) : base(new DataAttribute[] {
|
||||
new InlineDataAttribute(values),
|
||||
new CustomAutoDataAttribute(customizations)
|
||||
})
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using AutoFixture;
|
||||
|
||||
namespace Bit.Test.Common.AutoFixture.Attributes
|
||||
{
|
||||
public class InlineSutAutoDataAttribute : InlineCustomAutoDataAttribute
|
||||
{
|
||||
public InlineSutAutoDataAttribute(params object[] values) : base(
|
||||
new Type[] { typeof(SutProviderCustomization) }, values)
|
||||
{ }
|
||||
public InlineSutAutoDataAttribute(Type[] iCustomizationTypes, params object[] values) : base(
|
||||
iCustomizationTypes.Append(typeof(SutProviderCustomization)).ToArray(), values)
|
||||
{ }
|
||||
|
||||
public InlineSutAutoDataAttribute(ICustomization[] customizations, params object[] values) : base(
|
||||
customizations.Append(new SutProviderCustomization()).ToArray(), values)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
12
test/Common/AutoFixture/Attributes/SutAutoDataAttribute.cs
Normal file
12
test/Common/AutoFixture/Attributes/SutAutoDataAttribute.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Bit.Test.Common.AutoFixture.Attributes
|
||||
{
|
||||
public class SutAutoDataAttribute : CustomAutoDataAttribute
|
||||
{
|
||||
public SutAutoDataAttribute(params Type[] iCustomizationTypes) : base(
|
||||
iCustomizationTypes.Append(typeof(SutProviderCustomization)).ToArray())
|
||||
{ }
|
||||
}
|
||||
}
|
||||
11
test/Common/AutoFixture/FixtureExtensions.cs
Normal file
11
test/Common/AutoFixture/FixtureExtensions.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using AutoFixture;
|
||||
using AutoFixture.AutoNSubstitute;
|
||||
|
||||
namespace Bit.Test.Common.AutoFixture
|
||||
{
|
||||
public static class FixtureExtensions
|
||||
{
|
||||
public static IFixture WithAutoNSubstitutions(this IFixture fixture) =>
|
||||
fixture.Customize(new AutoNSubstituteCustomization());
|
||||
}
|
||||
}
|
||||
10
test/Common/AutoFixture/ISutProvider.cs
Normal file
10
test/Common/AutoFixture/ISutProvider.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Bit.Test.Common.AutoFixture
|
||||
{
|
||||
public interface ISutProvider
|
||||
{
|
||||
Type SutType { get; }
|
||||
ISutProvider Create();
|
||||
}
|
||||
}
|
||||
130
test/Common/AutoFixture/SutProvider.cs
Normal file
130
test/Common/AutoFixture/SutProvider.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AutoFixture;
|
||||
using AutoFixture.Kernel;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
|
||||
namespace Bit.Test.Common.AutoFixture
|
||||
{
|
||||
public class SutProvider<TSut> : ISutProvider
|
||||
{
|
||||
private Dictionary<Type, Dictionary<string, object>> _dependencies;
|
||||
private readonly IFixture _fixture;
|
||||
private readonly ConstructorParameterRelay<TSut> _constructorParameterRelay;
|
||||
|
||||
public TSut Sut { get; private set; }
|
||||
public Type SutType => typeof(TSut);
|
||||
|
||||
public SutProvider()
|
||||
{
|
||||
_dependencies = new Dictionary<Type, Dictionary<string, object>>();
|
||||
_fixture = new Fixture().WithAutoNSubstitutions();
|
||||
_constructorParameterRelay = new ConstructorParameterRelay<TSut>(this, _fixture);
|
||||
_fixture.Customizations.Add(_constructorParameterRelay);
|
||||
}
|
||||
|
||||
public SutProvider<TSut> SetDependency<T>(T dependency, string parameterName = "") =>
|
||||
SetDependency(typeof(T), dependency, parameterName);
|
||||
public SutProvider<TSut> SetDependency(Type dependencyType, object dependency, string parameterName = "")
|
||||
{
|
||||
if (_dependencies.ContainsKey(dependencyType))
|
||||
{
|
||||
_dependencies[dependencyType][parameterName] = dependency;
|
||||
}
|
||||
else
|
||||
{
|
||||
_dependencies[dependencyType] = new Dictionary<string, object> { { parameterName, dependency } };
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public T GetDependency<T>(string parameterName = "") => (T)GetDependency(typeof(T), parameterName);
|
||||
public object GetDependency(Type dependencyType, string parameterName = "")
|
||||
{
|
||||
if (DependencyIsSet(dependencyType, parameterName))
|
||||
{
|
||||
return _dependencies[dependencyType].ContainsKey(parameterName) ? _dependencies[dependencyType][parameterName] : _dependencies[dependencyType][""];
|
||||
}
|
||||
else if (_dependencies.ContainsKey(dependencyType))
|
||||
{
|
||||
var knownDependencies = _dependencies[dependencyType];
|
||||
if (knownDependencies.Values.Count == 1)
|
||||
{
|
||||
return _dependencies[dependencyType].Values.Single();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException(string.Concat($"Dependency of type {dependencyType.Name} and name ",
|
||||
$"{parameterName} does not exist. Available dependency names are: ",
|
||||
string.Join(", ", knownDependencies.Keys)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Dependency of type {dependencyType.Name} and name {parameterName} has not been set.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_dependencies = new Dictionary<Type, Dictionary<string, object>>();
|
||||
Sut = default;
|
||||
}
|
||||
|
||||
ISutProvider ISutProvider.Create() => Create();
|
||||
public SutProvider<TSut> Create()
|
||||
{
|
||||
Sut = _fixture.Create<TSut>();
|
||||
return this;
|
||||
}
|
||||
|
||||
private bool DependencyIsSet(Type dependencyType, string parameterName = "") =>
|
||||
_dependencies.ContainsKey(dependencyType) && (_dependencies[dependencyType].ContainsKey(parameterName) || _dependencies[dependencyType].ContainsKey(""));
|
||||
|
||||
private object GetDefault(Type type) => type.IsValueType ? Activator.CreateInstance(type) : null;
|
||||
|
||||
private class ConstructorParameterRelay<T> : ISpecimenBuilder
|
||||
{
|
||||
private readonly SutProvider<T> _sutProvider;
|
||||
private readonly IFixture _fixture;
|
||||
|
||||
public ConstructorParameterRelay(SutProvider<T> sutProvider, IFixture fixture)
|
||||
{
|
||||
_sutProvider = sutProvider;
|
||||
_fixture = fixture;
|
||||
}
|
||||
|
||||
public object Create(object request, ISpecimenContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
if (!(request is ParameterInfo parameterInfo))
|
||||
{
|
||||
return new NoSpecimen();
|
||||
}
|
||||
if (parameterInfo.Member.DeclaringType != typeof(T) ||
|
||||
parameterInfo.Member.MemberType != MemberTypes.Constructor)
|
||||
{
|
||||
return new NoSpecimen();
|
||||
}
|
||||
|
||||
if (_sutProvider.DependencyIsSet(parameterInfo.ParameterType, parameterInfo.Name))
|
||||
{
|
||||
return _sutProvider.GetDependency(parameterInfo.ParameterType, parameterInfo.Name);
|
||||
}
|
||||
|
||||
|
||||
// This is the equivalent of _fixture.Create<parameterInfo.ParameterType>, but no overload for
|
||||
// Create(Type type) exists.
|
||||
var dependency = new SpecimenContext(_fixture).Resolve(new SeededRequest(parameterInfo.ParameterType,
|
||||
_sutProvider.GetDefault(parameterInfo.ParameterType)));
|
||||
_sutProvider.SetDependency(parameterInfo.ParameterType, dependency, parameterInfo.Name);
|
||||
return dependency;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
test/Common/AutoFixture/SutProviderCustomization.cs
Normal file
32
test/Common/AutoFixture/SutProviderCustomization.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using AutoFixture;
|
||||
using AutoFixture.Kernel;
|
||||
|
||||
namespace Bit.Test.Common.AutoFixture
|
||||
{
|
||||
public class SutProviderCustomization : ICustomization, ISpecimenBuilder
|
||||
{
|
||||
public object Create(object request, ISpecimenContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
if (!(request is Type typeRequest))
|
||||
{
|
||||
return new NoSpecimen();
|
||||
}
|
||||
if (!typeof(ISutProvider).IsAssignableFrom(typeRequest))
|
||||
{
|
||||
return new NoSpecimen();
|
||||
}
|
||||
|
||||
return ((ISutProvider)Activator.CreateInstance(typeRequest)).Create();
|
||||
}
|
||||
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customizations.Add(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
test/Common/Common.csproj
Normal file
21
test/Common/Common.csproj
Normal file
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<RootNamespace>Bit.Test.Common</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NSubstitute" Version="4.2.2" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="AutoFixture.Xunit2" Version="4.14.0" />
|
||||
<PackageReference Include="AutoFixture.AutoNSubstitute" Version="4.14.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
58
test/Common/TestHelper.cs
Normal file
58
test/Common/TestHelper.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System.Reflection;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Bit.Test.Common
|
||||
{
|
||||
public static class TestHelper
|
||||
{
|
||||
public static void AssertPropertyEqual(object expected, object actual, params string[] excludedPropertyStrings)
|
||||
{
|
||||
var relevantExcludedProperties = excludedPropertyStrings.Where(name => !name.Contains('.')).ToList();
|
||||
if (expected == null)
|
||||
{
|
||||
Assert.Null(actual);
|
||||
return;
|
||||
}
|
||||
|
||||
if (actual == null)
|
||||
{
|
||||
throw new Exception("Expected object is null but actual is not");
|
||||
}
|
||||
|
||||
foreach (var expectedPi in expected.GetType().GetProperties().Where(pi => !relevantExcludedProperties.Contains(pi.Name)))
|
||||
{
|
||||
var actualPi = actual.GetType().GetProperty(expectedPi.Name);
|
||||
|
||||
if (actualPi == null)
|
||||
{
|
||||
var settings = new JsonSerializerSettings { Formatting = Formatting.Indented };
|
||||
throw new Exception(string.Concat($"Expected actual object to contain a property named {expectedPi.Name}, but it does not\n",
|
||||
$"Expected:\n{JsonConvert.SerializeObject(expected, settings)}\n",
|
||||
$"Actual:\n{JsonConvert.SerializeObject(actual, new JsonSerializerSettings { Formatting = Formatting.Indented })}"));
|
||||
}
|
||||
|
||||
if (expectedPi.PropertyType == typeof(string) || expectedPi.PropertyType.IsValueType)
|
||||
{
|
||||
Assert.Equal(expectedPi.GetValue(expected), actualPi.GetValue(actual));
|
||||
}
|
||||
else
|
||||
{
|
||||
var prefix = $"{expectedPi.PropertyType.Name}.";
|
||||
var nextExcludedProperties = excludedPropertyStrings.Where(name => name.StartsWith(prefix))
|
||||
.Select(name => name[prefix.Length..]).ToArray();
|
||||
AssertPropertyEqual(expectedPi.GetValue(expected), actualPi.GetValue(actual), nextExcludedProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Predicate<T> AssertEqualExpectedPredicate<T>(T expected) => (actual) =>
|
||||
{
|
||||
Assert.Equal(expected, actual);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using AutoFixture;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.Core.Test.AutoFixture
|
||||
{
|
||||
public class SymmetricCryptoKeyCustomization : ICustomization
|
||||
{
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
var keyMaterial = (new PclCryptoFunctionService(null)).RandomBytes(32);
|
||||
fixture.Register(() => new SymmetricCryptoKey(keyMaterial));
|
||||
}
|
||||
}
|
||||
}
|
||||
65
test/Core.Test/AutoFixture/Send/SendCustomizations.cs
Normal file
65
test/Core.Test/AutoFixture/Send/SendCustomizations.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using AutoFixture;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.Request;
|
||||
using Bit.Core.Models.Response;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Test.AutoFixture
|
||||
{
|
||||
internal class TextSendCustomization : ICustomization
|
||||
{
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customize<SendData>(composer => composer
|
||||
.With(c => c.Type, SendType.Text)
|
||||
.With(c => c.Text, fixture.Create<SendTextData>())
|
||||
.Without(c => c.File));
|
||||
fixture.Customize<Send>(composer => composer
|
||||
.With(c => c.Type, SendType.Text)
|
||||
.With(c => c.Text, fixture.Create<SendText>())
|
||||
.Without(c => c.File));
|
||||
fixture.Customize<SendView>(composer => composer
|
||||
.With(c => c.Type, SendType.Text)
|
||||
.With(c => c.Text, fixture.Create<SendTextView>())
|
||||
.Without(c => c.File));
|
||||
fixture.Customize<SendRequest>(composer => composer
|
||||
.With(c => c.Type, SendType.Text)
|
||||
.With(c => c.Text, fixture.Create<SendTextApi>())
|
||||
.Without(c => c.File));
|
||||
fixture.Customize<SendResponse>(composer => composer
|
||||
.With(c => c.Type, SendType.Text)
|
||||
.With(c => c.Text, fixture.Create<SendTextApi>())
|
||||
.Without(c => c.File));
|
||||
}
|
||||
}
|
||||
|
||||
internal class FileSendCustomization : ICustomization
|
||||
{
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customize<SendData>(composer => composer
|
||||
.With(c => c.Type, SendType.File)
|
||||
.With(c => c.File, fixture.Create<SendFileData>())
|
||||
.Without(c => c.Text));
|
||||
fixture.Customize<Send>(composer => composer
|
||||
.With(c => c.Type, SendType.File)
|
||||
.With(c => c.File, fixture.Create<SendFile>())
|
||||
.Without(c => c.Text));
|
||||
fixture.Customize<SendView>(composer => composer
|
||||
.With(c => c.Type, SendType.File)
|
||||
.With(c => c.File, fixture.Create<SendFileView>())
|
||||
.Without(c => c.Text));
|
||||
fixture.Customize<SendRequest>(composer => composer
|
||||
.With(c => c.Type, SendType.File)
|
||||
.With(c => c.File, fixture.Create<SendFileApi>())
|
||||
.Without(c => c.Text));
|
||||
fixture.Customize<SendResponse>(composer => composer
|
||||
.With(c => c.Type, SendType.File)
|
||||
.With(c => c.File, fixture.Create<SendFileApi>())
|
||||
.Without(c => c.Text));
|
||||
}
|
||||
}
|
||||
}
|
||||
26
test/Core.Test/Core.Test.csproj
Normal file
26
test/Core.Test/Core.Test.csproj
Normal file
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<RootNamespace>Bit.Core.Test</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
||||
<PackageReference Include="NSubstitute" Version="4.2.2" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="AutoFixture.Xunit2" Version="4.14.0" />
|
||||
<PackageReference Include="AutoFixture.AutoNSubstitute" Version="4.14.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Core\Core.csproj" />
|
||||
<ProjectReference Include="..\common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
23
test/Core.Test/Models/Data/SendDataTests.cs
Normal file
23
test/Core.Test/Models/Data/SendDataTests.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Response;
|
||||
using Bit.Core.Test.AutoFixture;
|
||||
using Bit.Test.Common;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Models.Data
|
||||
{
|
||||
public class SendDataTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineCustomAutoData(new[] { typeof(TextSendCustomization) })]
|
||||
[InlineCustomAutoData(new[] { typeof(FileSendCustomization) })]
|
||||
public void SendData_FromSendResponse_Success(string userId, SendResponse response)
|
||||
{
|
||||
var data = new SendData(response, userId);
|
||||
|
||||
TestHelper.AssertPropertyEqual(response, data, "UserId");
|
||||
Assert.Equal(data.UserId, userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
78
test/Core.Test/Models/Domain/SendTests.cs
Normal file
78
test/Core.Test/Models/Domain/SendTests.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Test.Common;
|
||||
using System.Text;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using Bit.Core.Test.AutoFixture;
|
||||
using AutoFixture.AutoNSubstitute;
|
||||
|
||||
namespace Bit.Core.Test.Models.Domain
|
||||
{
|
||||
public class SendTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineCustomAutoData(new[] { typeof(FileSendCustomization) })]
|
||||
[InlineCustomAutoData(new[] { typeof(TextSendCustomization) })]
|
||||
public void Send_FromSendData_Success(SendData data)
|
||||
{
|
||||
var send = new Send(data);
|
||||
|
||||
TestHelper.AssertPropertyEqual(data, send, "Name", "Notes", "Key", "SendFileData.FileName", "SendFileData.Key", "SendTextData.Text");
|
||||
Assert.Equal(data.Name, send.Name?.EncryptedString);
|
||||
Assert.Equal(data.Notes, send.Notes?.EncryptedString);
|
||||
Assert.Equal(data.Key, send.Key?.EncryptedString);
|
||||
Assert.Equal(data.Text?.Text, send.Text?.Text?.EncryptedString);
|
||||
Assert.Equal(data.File?.FileName, send.File?.FileName?.EncryptedString);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineCustomAutoData(new[] { typeof(AutoNSubstituteCustomization), typeof(TextSendCustomization) })]
|
||||
[InlineCustomAutoData(new[] { typeof(AutoNSubstituteCustomization), typeof(FileSendCustomization) })]
|
||||
public async void DecryptAsync_Success(ICryptoService cryptoService, Send send)
|
||||
{
|
||||
var prefix = "decrypted_";
|
||||
var prefixBytes = Encoding.UTF8.GetBytes(prefix);
|
||||
|
||||
cryptoService.DecryptToBytesAsync(Arg.Any<CipherString>(), Arg.Any<SymmetricCryptoKey>())
|
||||
.Returns(info => prefixBytes.Concat(Encoding.UTF8.GetBytes(((CipherString)info[0]).EncryptedString)).ToArray());
|
||||
cryptoService.DecryptFromBytesAsync(Arg.Any<byte[]>(), Arg.Any<SymmetricCryptoKey>())
|
||||
.Returns(info => prefixBytes.Concat((byte[])info[0]).ToArray());
|
||||
cryptoService.DecryptToUtf8Async(Arg.Any<CipherString>(), Arg.Any<SymmetricCryptoKey>())
|
||||
.Returns(info => $"{prefix}{((CipherString)info[0]).EncryptedString}");
|
||||
ServiceContainer.Register("cryptoService", cryptoService);
|
||||
|
||||
var view = await send.DecryptAsync();
|
||||
|
||||
string expectedDecryptionString(CipherString encryptedString) =>
|
||||
encryptedString?.EncryptedString == null ? null : $"{prefix}{encryptedString.EncryptedString}";
|
||||
|
||||
TestHelper.AssertPropertyEqual(send, view, "Name", "Notes", "File", "Text", "Key", "UserId");
|
||||
Assert.Equal(expectedDecryptionString(send.Name), view.Name);
|
||||
Assert.Equal(expectedDecryptionString(send.Notes), view.Notes);
|
||||
Assert.Equal(Encoding.UTF8.GetBytes(expectedDecryptionString(send.Key)), view.Key);
|
||||
|
||||
switch (send.Type)
|
||||
{
|
||||
case SendType.File:
|
||||
TestHelper.AssertPropertyEqual(send.File, view.File, "FileName");
|
||||
Assert.Equal(expectedDecryptionString(send.File.FileName), view.File.FileName);
|
||||
break;
|
||||
case SendType.Text:
|
||||
TestHelper.AssertPropertyEqual(send.Text, view?.Text, "Text");
|
||||
Assert.Equal(expectedDecryptionString(send.Text.Text), view.Text.Text);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Untested Send type");
|
||||
}
|
||||
|
||||
ServiceContainer.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
43
test/Core.Test/Models/Request/SendRequestTests.cs
Normal file
43
test/Core.Test/Models/Request/SendRequestTests.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.Request;
|
||||
using Bit.Core.Test.AutoFixture;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Models.Request
|
||||
{
|
||||
public class SendRequestTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineCustomAutoData(new[] { typeof(TextSendCustomization) })]
|
||||
[InlineCustomAutoData(new[] { typeof(FileSendCustomization) })]
|
||||
public void SendRequest_FromSend_Success(Send send)
|
||||
{
|
||||
var request = new SendRequest(send);
|
||||
|
||||
TestHelper.AssertPropertyEqual(send, request, "Id", "AccessId", "UserId", "Name", "Notes", "File", "Text", "Key", "AccessCount", "RevisionDate");
|
||||
Assert.Equal(send.Name?.EncryptedString, request.Name);
|
||||
Assert.Equal(send.Notes?.EncryptedString, request.Notes);
|
||||
|
||||
switch (send.Type)
|
||||
{
|
||||
case SendType.File:
|
||||
// Only sets filename
|
||||
Assert.Equal(send.File.FileName?.EncryptedString, request.File.FileName);
|
||||
break;
|
||||
case SendType.Text:
|
||||
TestHelper.AssertPropertyEqual(send.Text, request?.Text, "Text");
|
||||
Assert.Equal(send.Text.Text?.EncryptedString, request.Text.Text);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Untested Send type");
|
||||
}
|
||||
|
||||
ServiceContainer.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
87
test/Core.Test/Services/CryptoFunctionServiceTests.cs
Normal file
87
test/Core.Test/Services/CryptoFunctionServiceTests.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using System.Text;
|
||||
|
||||
namespace Bit.Core.Test.Services
|
||||
{
|
||||
public class CryptoFunctionServiceTests
|
||||
{
|
||||
const string regular256Key = "qBUmEYtwTwwGPuw/z6bs/qYXXYNUlocFlyAuuANI8Pw=";
|
||||
const string utf8256Key = "6DfJwW1R3txgiZKkIFTvVAb7qVlG7lKcmJGJoxR2GBU=";
|
||||
const string unicode256Key = "gejGI82xthA+nKtKmIh82kjw+ttHr+ODsUoGdu5sf0A=";
|
||||
const string regular512Key = "xe5cIG6ZfwGmb1FvsOedM0XKOm21myZkjL/eDeKIqqM=";
|
||||
const string utf8512Key = "XQMVBnxVEhlvjSFDQc77j5GDE9aorvbS0vKnjhRg0LY=";
|
||||
const string unicode512Key = "148GImrTbrjaGAe/iWEpclINM8Ehhko+9lB14+52lqc=";
|
||||
const string regularSalt = "salt";
|
||||
const string utf8Salt = "üser_salt";
|
||||
const string unicodeSalt = "😀salt🙏";
|
||||
const string regularInfo = "info";
|
||||
const string utf8Info = "üser_info";
|
||||
const string unicodeInfo = "😀info🙏";
|
||||
|
||||
const string prk16Byte = "criAmKtfzxanbgea5/kelQ==";
|
||||
const string prk32Byte = "F5h4KdYQnIVH4rKH0P9CZb1GrR4n16/sJrS0PsQEn0Y=";
|
||||
const string prk64Byte = "ssBK0mRG17VHdtsgt8yo4v25CRNpauH+0r2fwY/E9rLyaFBAOMbIeTry+" +
|
||||
"gUJ28p8y+hFh3EI9pcrEWaNvFYonQ==";
|
||||
|
||||
|
||||
[Theory, AutoSubstitutionData]
|
||||
async public Task HkdfExpand_PrkTooSmall_Throws(PclCryptoFunctionService sut)
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<ArgumentException>(
|
||||
() => sut.HkdfExpandAsync(Convert.FromBase64String(prk16Byte), "info", 32, HkdfAlgorithm.Sha256));
|
||||
Assert.Contains("too small", exception.Message);
|
||||
}
|
||||
|
||||
[Theory, AutoSubstitutionData]
|
||||
async public Task HkdfoExpand_OutputTooBig_Throws(PclCryptoFunctionService sut)
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<ArgumentException>(
|
||||
() => sut.HkdfExpandAsync(Convert.FromBase64String(prk32Byte), "info", 8161, HkdfAlgorithm.Sha256));
|
||||
Assert.Contains("too large", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineAutoSubstitutionData(regular256Key, HkdfAlgorithm.Sha256, prk16Byte, regularSalt, regularInfo)]
|
||||
[InlineAutoSubstitutionData(utf8256Key, HkdfAlgorithm.Sha256, prk16Byte, utf8Salt, utf8Info)]
|
||||
[InlineAutoSubstitutionData(unicode256Key, HkdfAlgorithm.Sha256, prk16Byte, unicodeSalt, unicodeInfo)]
|
||||
[InlineAutoSubstitutionData(regular512Key, HkdfAlgorithm.Sha512, prk16Byte, regularSalt, regularInfo)]
|
||||
[InlineAutoSubstitutionData(utf8512Key, HkdfAlgorithm.Sha512, prk16Byte, utf8Salt, utf8Info)]
|
||||
[InlineAutoSubstitutionData(unicode512Key, HkdfAlgorithm.Sha512, prk16Byte, unicodeSalt, unicodeInfo)]
|
||||
async public Task Hkdf_Success(string expectedKey, HkdfAlgorithm algorithm, string ikmString, string salt, string info, PclCryptoFunctionService sut)
|
||||
{
|
||||
byte[] ikm = Convert.FromBase64String(ikmString);
|
||||
|
||||
var key = await sut.HkdfAsync(ikm, salt, info, 32, algorithm);
|
||||
Assert.Equal(expectedKey, Convert.ToBase64String(key));
|
||||
|
||||
var keyFromByteArray = await sut.HkdfAsync(ikm, Encoding.UTF8.GetBytes(salt), Encoding.UTF8.GetBytes(info), 32, algorithm);
|
||||
Assert.Equal(key, keyFromByteArray);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineAutoSubstitutionData("BnIqJlfnHm0e/2iB/15cbHyR19ARPIcWRp4oNS22CD8=",
|
||||
HkdfAlgorithm.Sha256, prk32Byte, 32, regularInfo)]
|
||||
[InlineAutoSubstitutionData("BnIqJlfnHm0e/2iB/15cbHyR19ARPIcWRp4oNS22CD9BV+/queOZenPNkDhmlVyL2WZ3OSU5+7ISNF5NhNfvZA==",
|
||||
HkdfAlgorithm.Sha256, prk32Byte, 64, regularInfo)]
|
||||
[InlineAutoSubstitutionData("uLWbMWodSBms5uGJ5WTRTesyW+MD7nlpCZvagvIRXlk=",
|
||||
HkdfAlgorithm.Sha512, prk64Byte, 32, regularInfo)]
|
||||
[InlineAutoSubstitutionData("uLWbMWodSBms5uGJ5WTRTesyW+MD7nlpCZvagvIRXlkY5Pv0sB+MqvaopmkC6sD/j89zDwTV9Ib2fpucUydO8w==",
|
||||
HkdfAlgorithm.Sha512, prk64Byte, 64, regularInfo)]
|
||||
async public Task HkdfExpand_Success(string expectedKey, HkdfAlgorithm algorithm, string prkString, int outputByteSize, string info, PclCryptoFunctionService sut)
|
||||
{
|
||||
var prk = Convert.FromBase64String(prkString);
|
||||
|
||||
var key = await sut.HkdfExpandAsync(prk, info, outputByteSize, algorithm);
|
||||
Assert.Equal(expectedKey, Convert.ToBase64String(key));
|
||||
|
||||
var keyFromByteArray = await sut.HkdfExpandAsync(prk, Encoding.UTF8.GetBytes(info), outputByteSize, algorithm);
|
||||
Assert.Equal(key, keyFromByteArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
376
test/Core.Test/Services/SendServiceTests.cs
Normal file
376
test/Core.Test/Services/SendServiceTests.cs
Normal file
@@ -0,0 +1,376 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.Response;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Test.Common;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Newtonsoft.Json;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using System.Text;
|
||||
using System.Net.Http;
|
||||
using Bit.Core.Models.Request;
|
||||
using Bit.Core.Test.AutoFixture;
|
||||
using System.Linq.Expressions;
|
||||
using Bit.Core.Models.View;
|
||||
|
||||
namespace Bit.Core.Test.Services
|
||||
{
|
||||
public class SendServiceTests
|
||||
{
|
||||
private string GetSendKey(string userId) => SendService.GetSendKey(userId);
|
||||
|
||||
[Theory]
|
||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(TextSendCustomization) })]
|
||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(FileSendCustomization) })]
|
||||
public async Task ReplaceAsync_Success(SutProvider<SendService> sutProvider, string userId, IEnumerable<SendData> sendDatas)
|
||||
{
|
||||
var actualSendDataDict = sendDatas.ToDictionary(d => d.Id, d => d);
|
||||
sutProvider.GetDependency<IUserService>().GetUserIdAsync().Returns(userId);
|
||||
|
||||
await sutProvider.Sut.ReplaceAsync(actualSendDataDict);
|
||||
|
||||
await sutProvider.GetDependency<IStorageService>()
|
||||
.Received(1).SaveAsync(GetSendKey(userId), actualSendDataDict);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(TextSendCustomization) }, 0)]
|
||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(TextSendCustomization) }, 1)]
|
||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(TextSendCustomization) }, 2)]
|
||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(TextSendCustomization) }, 3)]
|
||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(TextSendCustomization) }, 4)]
|
||||
public async Task DeleteAsync_Success(int numberToDelete, SutProvider<SendService> sutProvider, string userId, IEnumerable<SendData> sendDatas)
|
||||
{
|
||||
var actualSendDataDict = sendDatas.ToDictionary(d => d.Id, d => d);
|
||||
sutProvider.GetDependency<IUserService>().GetUserIdAsync().Returns(userId);
|
||||
sutProvider.GetDependency<IStorageService>()
|
||||
.GetAsync<Dictionary<string, SendData>>(GetSendKey(userId)).Returns(actualSendDataDict);
|
||||
|
||||
var idsToDelete = actualSendDataDict.Take(numberToDelete).Select(kvp => kvp.Key).ToArray();
|
||||
var expectedSends = actualSendDataDict.Skip(numberToDelete).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||
|
||||
await sutProvider.Sut.DeleteAsync(idsToDelete);
|
||||
|
||||
|
||||
await sutProvider.GetDependency<IStorageService>().Received(1)
|
||||
.SaveAsync(GetSendKey(userId),
|
||||
Arg.Is<Dictionary<string, SendData>>(s => TestHelper.AssertEqualExpectedPredicate(expectedSends)(s)));
|
||||
}
|
||||
|
||||
[Theory, SutAutoData]
|
||||
public async Task ClearAsync_Success(SutProvider<SendService> sutProvider, string userId)
|
||||
{
|
||||
await sutProvider.Sut.ClearAsync(userId);
|
||||
|
||||
await sutProvider.GetDependency<IStorageService>().Received(1).RemoveAsync(GetSendKey(userId));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(TextSendCustomization) })]
|
||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(FileSendCustomization) })]
|
||||
public async Task DeleteWithServerAsync_Success(SutProvider<SendService> sutProvider, string userId, IEnumerable<SendData> sendDatas)
|
||||
{
|
||||
var initialSendDatas = sendDatas.ToDictionary(d => d.Id, d => d);
|
||||
var idToDelete = initialSendDatas.First().Key;
|
||||
var expectedSends = initialSendDatas.Skip(1).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||
sutProvider.GetDependency<IUserService>().GetUserIdAsync().Returns(userId);
|
||||
sutProvider.GetDependency<IStorageService>()
|
||||
.GetAsync<Dictionary<string, SendData>>(Arg.Any<string>()).Returns(initialSendDatas);
|
||||
|
||||
await sutProvider.Sut.DeleteWithServerAsync(idToDelete);
|
||||
|
||||
await sutProvider.GetDependency<IApiService>().Received(1).DeleteSendAsync(idToDelete);
|
||||
await sutProvider.GetDependency<IStorageService>().Received(1)
|
||||
.SaveAsync(GetSendKey(userId),
|
||||
Arg.Is<Dictionary<string, SendData>>(s => TestHelper.AssertEqualExpectedPredicate(expectedSends)(s)));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(TextSendCustomization) })]
|
||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(FileSendCustomization) })]
|
||||
public async Task GetAsync_Success(SutProvider<SendService> sutProvider, string userId, IEnumerable<SendData> sendDatas)
|
||||
{
|
||||
var sendDataDict = sendDatas.ToDictionary(d => d.Id, d => d);
|
||||
sutProvider.GetDependency<IUserService>().GetUserIdAsync().Returns(userId);
|
||||
sutProvider.GetDependency<IStorageService>().GetAsync<Dictionary<string, SendData>>(GetSendKey(userId)).Returns(sendDataDict);
|
||||
|
||||
foreach (var dataKvp in sendDataDict)
|
||||
{
|
||||
var expected = new Send(dataKvp.Value);
|
||||
var actual = await sutProvider.Sut.GetAsync(dataKvp.Key);
|
||||
TestHelper.AssertPropertyEqual(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory, SutAutoData]
|
||||
public async Task GetAsync_NonExistringId_ReturnsNull(SutProvider<SendService> sutProvider, string userId, IEnumerable<SendData> sendDatas)
|
||||
{
|
||||
var sendDataDict = sendDatas.ToDictionary(d => d.Id, d => d);
|
||||
sutProvider.GetDependency<IUserService>().GetUserIdAsync().Returns(userId);
|
||||
sutProvider.GetDependency<IStorageService>().GetAsync<Dictionary<string, SendData>>(GetSendKey(userId)).Returns(sendDataDict);
|
||||
|
||||
var actual = await sutProvider.Sut.GetAsync(Guid.NewGuid().ToString());
|
||||
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(TextSendCustomization) })]
|
||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(FileSendCustomization) })]
|
||||
public async Task GetAllAsync_Success(SutProvider<SendService> sutProvider, string userId, IEnumerable<SendData> sendDatas)
|
||||
{
|
||||
var sendDataDict = sendDatas.ToDictionary(d => d.Id, d => d);
|
||||
sutProvider.GetDependency<IUserService>().GetUserIdAsync().Returns(userId);
|
||||
sutProvider.GetDependency<IStorageService>().GetAsync<Dictionary<string, SendData>>(GetSendKey(userId)).Returns(sendDataDict);
|
||||
|
||||
var allExpected = sendDataDict.Select(kvp => new Send(kvp.Value));
|
||||
var allActual = await sutProvider.Sut.GetAllAsync();
|
||||
foreach (var (actual, expected) in allActual.Zip(allExpected))
|
||||
{
|
||||
TestHelper.AssertPropertyEqual(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory, SutAutoData]
|
||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(TextSendCustomization) })]
|
||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(FileSendCustomization) })]
|
||||
public async Task GetAllDecryptedAsync_Success(SutProvider<SendService> sutProvider, string userId, IEnumerable<SendData> sendDatas)
|
||||
{
|
||||
var sendDataDict = sendDatas.ToDictionary(d => d.Id, d => d);
|
||||
sutProvider.GetDependency<ICryptoService>().HasKeyAsync().Returns(true);
|
||||
ServiceContainer.Register("cryptoService", sutProvider.GetDependency<ICryptoService>());
|
||||
sutProvider.GetDependency<II18nService>().StringComparer.Returns(StringComparer.CurrentCulture);
|
||||
sutProvider.GetDependency<IUserService>().GetUserIdAsync().Returns(userId);
|
||||
sutProvider.GetDependency<IStorageService>().GetAsync<Dictionary<string, SendData>>(GetSendKey(userId)).Returns(sendDataDict);
|
||||
|
||||
var actual = await sutProvider.Sut.GetAllDecryptedAsync();
|
||||
|
||||
Assert.Equal(sendDataDict.Count, actual.Count);
|
||||
foreach (var (actualView, expectedId) in actual.Zip(sendDataDict.Select(s => s.Key)))
|
||||
{
|
||||
// Note Send -> SendView is tested in SendTests
|
||||
Assert.Equal(expectedId, actualView.Id);
|
||||
}
|
||||
|
||||
ServiceContainer.Reset();
|
||||
}
|
||||
|
||||
// SaveWithServer()
|
||||
[Theory]
|
||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(TextSendCustomization) })]
|
||||
public async Task SaveWithServerAsync_NewTextSend_Success(SutProvider<SendService> sutProvider, string userId, SendResponse response, Send send)
|
||||
{
|
||||
send.Id = null;
|
||||
sutProvider.GetDependency<IUserService>().GetUserIdAsync().Returns(userId);
|
||||
sutProvider.GetDependency<IApiService>().PostSendAsync(Arg.Any<SendRequest>()).Returns(response);
|
||||
sutProvider.GetDependency<IApiService>().PostSendFileAsync(Arg.Any<MultipartFormDataContent>()).Returns(response);
|
||||
|
||||
var fileContentBytes = Encoding.UTF8.GetBytes("This is the file content");
|
||||
|
||||
await sutProvider.Sut.SaveWithServerAsync(send, fileContentBytes);
|
||||
|
||||
Predicate<SendRequest> sendRequestPredicate = r =>
|
||||
{
|
||||
// Note Send -> SendRequest tested in SendRequestTests
|
||||
TestHelper.AssertPropertyEqual(new SendRequest(send), r);
|
||||
return true;
|
||||
};
|
||||
|
||||
switch (send.Type)
|
||||
{
|
||||
case SendType.Text:
|
||||
await sutProvider.GetDependency<IApiService>().Received(1)
|
||||
.PostSendAsync(Arg.Is<SendRequest>(r => sendRequestPredicate(r)));
|
||||
break;
|
||||
case SendType.File:
|
||||
default:
|
||||
throw new Exception("Untested send type");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(FileSendCustomization) })]
|
||||
public async Task SaveWithServerAsync_NewFileSend_Success(SutProvider<SendService> sutProvider, string userId, SendResponse response, Send send)
|
||||
{
|
||||
send.Id = null;
|
||||
sutProvider.GetDependency<IUserService>().GetUserIdAsync().Returns(userId);
|
||||
sutProvider.GetDependency<IApiService>().PostSendAsync(Arg.Any<SendRequest>()).Returns(response);
|
||||
sutProvider.GetDependency<IApiService>().PostSendFileAsync(Arg.Any<MultipartFormDataContent>()).Returns(response);
|
||||
|
||||
var fileContentBytes = Encoding.UTF8.GetBytes("This is the file content");
|
||||
|
||||
await sutProvider.Sut.SaveWithServerAsync(send, fileContentBytes);
|
||||
|
||||
Predicate<MultipartFormDataContent> formDataPredicate = fd =>
|
||||
{
|
||||
Assert.Equal(2, fd.Count()); // expect a request and file content
|
||||
|
||||
var expectedRequest = JsonConvert.SerializeObject(new SendRequest(send));
|
||||
var actualRequest = fd.First().ReadAsStringAsync().GetAwaiter().GetResult();
|
||||
Assert.Equal(expectedRequest, actualRequest);
|
||||
|
||||
var actualFileContent = fd.Skip(1).First().ReadAsByteArrayAsync().GetAwaiter().GetResult();
|
||||
Assert.Equal(fileContentBytes, actualFileContent);
|
||||
return true;
|
||||
};
|
||||
|
||||
switch (send.Type)
|
||||
{
|
||||
case SendType.File:
|
||||
await sutProvider.GetDependency<IApiService>().Received(1)
|
||||
.PostSendFileAsync(Arg.Is<MultipartFormDataContent>(f => formDataPredicate(f)));
|
||||
break;
|
||||
case SendType.Text:
|
||||
default:
|
||||
throw new Exception("Untested send type");
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(TextSendCustomization) })]
|
||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(FileSendCustomization) })]
|
||||
public async Task SaveWithServerAsync_PutSend_Success(SutProvider<SendService> sutProvider, string userId, SendResponse response, Send send)
|
||||
{
|
||||
sutProvider.GetDependency<IUserService>().GetUserIdAsync().Returns(userId);
|
||||
sutProvider.GetDependency<IApiService>().PutSendAsync(send.Id, Arg.Any<SendRequest>()).Returns(response);
|
||||
|
||||
await sutProvider.Sut.SaveWithServerAsync(send, null);
|
||||
|
||||
Predicate<SendRequest> sendRequestPredicate = r =>
|
||||
{
|
||||
// Note Send -> SendRequest tested in SendRequestTests
|
||||
TestHelper.AssertPropertyEqual(new SendRequest(send), r);
|
||||
return true;
|
||||
};
|
||||
|
||||
await sutProvider.GetDependency<IApiService>().Received(1)
|
||||
.PutSendAsync(send.Id, Arg.Is<SendRequest>(r => sendRequestPredicate(r)));
|
||||
}
|
||||
|
||||
[Theory, SutAutoData]
|
||||
public async Task RemovePasswordWithServerAsync_Success(SutProvider<SendService> sutProvider, SendResponse response, string sendId)
|
||||
{
|
||||
sutProvider.GetDependency<IApiService>().PutSendRemovePasswordAsync(sendId).Returns(response);
|
||||
|
||||
await sutProvider.Sut.RemovePasswordWithServerAsync(sendId);
|
||||
|
||||
await sutProvider.GetDependency<IApiService>().Received(1).PutSendRemovePasswordAsync(sendId);
|
||||
await sutProvider.GetDependency<IStorageService>().ReceivedWithAnyArgs(1).SaveAsync<Dictionary<string, SendData>>(default, default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(TextSendCustomization) })]
|
||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(FileSendCustomization) })]
|
||||
public async Task UpsertAsync_Update_Success(SutProvider<SendService> sutProvider, string userId, IEnumerable<SendData> initialSends)
|
||||
{
|
||||
var initialSendDict = initialSends.ToDictionary(s => s.Id, s => s);
|
||||
sutProvider.GetDependency<IUserService>().GetUserIdAsync().Returns(userId);
|
||||
sutProvider.GetDependency<IStorageService>().GetAsync<Dictionary<string, SendData>>(GetSendKey(userId)).Returns(initialSendDict);
|
||||
|
||||
var updatedSends = CoreHelpers.Clone(initialSendDict);
|
||||
foreach (var kvp in updatedSends)
|
||||
{
|
||||
kvp.Value.Disabled = !kvp.Value.Disabled;
|
||||
}
|
||||
|
||||
await sutProvider.Sut.UpsertAsync(updatedSends.Values.ToArray());
|
||||
|
||||
Predicate<Dictionary<string, SendData>> matchSendsPredicate = actual =>
|
||||
{
|
||||
Assert.Equal(updatedSends.Count, actual.Count);
|
||||
foreach (var (expectedKvp, actualKvp) in updatedSends.Zip(actual))
|
||||
{
|
||||
Assert.Equal(expectedKvp.Key, actualKvp.Key);
|
||||
TestHelper.AssertPropertyEqual(expectedKvp.Value, actualKvp.Value);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
await sutProvider.GetDependency<IStorageService>().Received(1).SaveAsync(GetSendKey(userId), Arg.Is<Dictionary<string, SendData>>(d => matchSendsPredicate(d)));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(TextSendCustomization) })]
|
||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(FileSendCustomization) })]
|
||||
public async Task UpsertAsync_NewSends_Success(SutProvider<SendService> sutProvider, string userId, IEnumerable<SendData> initialSends, IEnumerable<SendData> newSends)
|
||||
{
|
||||
var initialSendDict = initialSends.ToDictionary(s => s.Id, s => s);
|
||||
sutProvider.GetDependency<IUserService>().GetUserIdAsync().Returns(userId);
|
||||
sutProvider.GetDependency<IStorageService>().GetAsync<Dictionary<string, SendData>>(GetSendKey(userId)).Returns(initialSendDict);
|
||||
|
||||
var expectedDict = CoreHelpers.Clone(initialSendDict).Concat(newSends.Select(s => new KeyValuePair<string, SendData>(s.Id, s)));
|
||||
|
||||
await sutProvider.Sut.UpsertAsync(newSends.ToArray());
|
||||
|
||||
Predicate<Dictionary<string, SendData>> matchSendsPredicate = actual =>
|
||||
{
|
||||
Assert.Equal(expectedDict.Count(), actual.Count);
|
||||
foreach (var (expectedKvp, actualKvp) in expectedDict.Zip(actual))
|
||||
{
|
||||
Assert.Equal(expectedKvp.Key, actualKvp.Key);
|
||||
TestHelper.AssertPropertyEqual(expectedKvp.Value, actualKvp.Value);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
await sutProvider.GetDependency<IStorageService>().Received(1).SaveAsync(GetSendKey(userId), Arg.Is<Dictionary<string, SendData>>(d => matchSendsPredicate(d)));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(SymmetricCryptoKeyCustomization), typeof(TextSendCustomization) })]
|
||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(SymmetricCryptoKeyCustomization), typeof(FileSendCustomization) })]
|
||||
public async Task EncryptAsync_Success(SutProvider<SendService> sutProvider, SendView view, byte[] fileData, SymmetricCryptoKey privateKey)
|
||||
{
|
||||
var prefix = "encrypted_";
|
||||
var prefixBytes = Encoding.UTF8.GetBytes(prefix);
|
||||
|
||||
|
||||
byte[] getPbkdf(string password, byte[] key) =>
|
||||
prefixBytes.Concat(Encoding.UTF8.GetBytes(password)).Concat(key).ToArray();
|
||||
CipherString encryptBytes(byte[] secret, SymmetricCryptoKey key) =>
|
||||
new CipherString($"{prefix}{Convert.ToBase64String(secret)}{Convert.ToBase64String(key.Key)}");
|
||||
CipherString encrypt(string secret, SymmetricCryptoKey key) =>
|
||||
new CipherString($"{prefix}{secret}{Convert.ToBase64String(key.Key)}");
|
||||
|
||||
sutProvider.GetDependency<ICryptoFunctionService>().Pbkdf2Async(Arg.Any<string>(), Arg.Any<byte[]>(), Arg.Any<CryptoHashAlgorithm>(), Arg.Any<int>())
|
||||
.Returns(info => getPbkdf((string)info[0], (byte[])info[1]));
|
||||
sutProvider.GetDependency<ICryptoService>().EncryptAsync(Arg.Any<byte[]>(), Arg.Any<SymmetricCryptoKey>())
|
||||
.Returns(info => encryptBytes((byte[])info[0], (SymmetricCryptoKey)info[1]));
|
||||
sutProvider.GetDependency<ICryptoService>().EncryptAsync(Arg.Any<string>(), Arg.Any<SymmetricCryptoKey>())
|
||||
.Returns(info => encrypt((string)info[0], (SymmetricCryptoKey)info[1]));
|
||||
|
||||
var (send, encryptedFileData) = await sutProvider.Sut.EncryptAsync(view, fileData, view.Password, privateKey);
|
||||
|
||||
TestHelper.AssertPropertyEqual(view, send, "Password", "Key", "Name", "Notes", "Text", "File",
|
||||
"AccessCount", "AccessId", "CryptoKey", "RevisionDate", "DeletionDate", "ExpirationDate", "UrlB64Key",
|
||||
"MaxAccessCountReached", "Expired", "PendingDelete");
|
||||
Assert.Equal(Convert.ToBase64String(getPbkdf(view.Password, view.Key)), send.Password);
|
||||
TestHelper.AssertPropertyEqual(encryptBytes(view.Key, privateKey), send.Key);
|
||||
TestHelper.AssertPropertyEqual(encrypt(view.Name, view.CryptoKey), send.Name);
|
||||
TestHelper.AssertPropertyEqual(encrypt(view.Notes, view.CryptoKey), send.Notes);
|
||||
|
||||
switch (view.Type)
|
||||
{
|
||||
case SendType.Text:
|
||||
TestHelper.AssertPropertyEqual(view.Text, send.Text, "Text", "MaskedText");
|
||||
TestHelper.AssertPropertyEqual(encrypt(view.Text.Text, view.CryptoKey), send.Text.Text);
|
||||
break;
|
||||
case SendType.File:
|
||||
// Only set filename
|
||||
TestHelper.AssertPropertyEqual(encrypt(view.File.FileName, view.CryptoKey), send.File.FileName);
|
||||
TestHelper.AssertPropertyEqual(encryptBytes(fileData, view.CryptoKey), encryptedFileData);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Untested send type");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user