mirror of
https://github.com/bitwarden/server.git
synced 2025-07-01 08:02:49 -05:00
Validate cipher updates with revision date (#994)
* Add last updated validation to cipher replacements * Add AutoFixture scaffolding. AutoDataAttributes and ICustomizations are meant to automatically produce valid test input. Examples are the Cipher customizations, which enforce the model's mutual exclusivity of UserId and OrganizationId. FixtureExtensions create a fluent way to generate SUTs. We currently use parameter injection to fascilitate service testing, which is nicely handled by AutoNSubstitute. However, in order to gain access to the substitutions, we need to Freeze them onto the Fixture. The For fluent method allows specifying a Freeze to a specific type's constructor and optionally to a parameter name in that constructor. * Unit tests for single Cipher update version checks * Fix test runner Test runner requires Microsoft.NET.Test.Sdk * Move to provider model for SUT generation This model differs from previous in that you no longer need to specify which dependencies you would like access to. Instead, all are remembered and can be queried through the sutProvider. * User cipher provided by Put method reads Every put method already reads all relevant ciphers from database, there's no need to re-read them. JSON serialization of datetimes seems to leave truncate at second precision. Verify last known date time is within one second rather than exact. * validate revision date for share many requests * Update build script to use Github environment path Co-authored-by: Matt Gibson <mdgibson@Matts-MBP.lan>
This commit is contained in:
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using AutoFixture;
|
||||
using AutoFixture.Xunit2;
|
||||
|
||||
namespace Bit.Core.Test.AutoFixture.Attributes
|
||||
{
|
||||
internal 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,23 @@
|
||||
using System;
|
||||
using Xunit;
|
||||
using Xunit.Sdk;
|
||||
using AutoFixture.Xunit2;
|
||||
using AutoFixture;
|
||||
|
||||
namespace Bit.Core.Test.AutoFixture.Attributes
|
||||
{
|
||||
internal 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)
|
||||
})
|
||||
{ }
|
||||
}
|
||||
}
|
57
test/Core.Test/AutoFixture/CipherFixtures.cs
Normal file
57
test/Core.Test/AutoFixture/CipherFixtures.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using AutoFixture;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Test.AutoFixture.Attributes;
|
||||
using Core.Models.Data;
|
||||
|
||||
namespace Bit.Core.Test.AutoFixture.CipherFixtures
|
||||
{
|
||||
internal class OrganizationCipher : ICustomization
|
||||
{
|
||||
public Guid? OrganizationId { get; set; }
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customize<Cipher>(composer => composer
|
||||
.With(c => c.OrganizationId, OrganizationId ?? Guid.NewGuid())
|
||||
.Without(c => c.UserId));
|
||||
fixture.Customize<CipherDetails>(composer => composer
|
||||
.With(c => c.OrganizationId, Guid.NewGuid())
|
||||
.Without(c => c.UserId));
|
||||
}
|
||||
}
|
||||
|
||||
internal class UserCipher : ICustomization
|
||||
{
|
||||
public Guid? UserId { get; set; }
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customize<Cipher>(composer => composer
|
||||
.With(c => c.UserId, UserId ?? Guid.NewGuid())
|
||||
.Without(c => c.OrganizationId));
|
||||
fixture.Customize<CipherDetails>(composer => composer
|
||||
.With(c => c.UserId, Guid.NewGuid())
|
||||
.Without(c => c.OrganizationId));
|
||||
}
|
||||
}
|
||||
|
||||
internal class UserCipherAutoDataAttribute : CustomAutoDataAttribute
|
||||
{
|
||||
public UserCipherAutoDataAttribute(string userId = null) : base(new SutProviderCustomization(),
|
||||
new UserCipher { UserId = userId == null ? (Guid?)null : new Guid(userId) })
|
||||
{ }
|
||||
}
|
||||
internal class InlineUserCipherAutoDataAttribute : InlineCustomAutoDataAttribute
|
||||
{
|
||||
public InlineUserCipherAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
|
||||
typeof(UserCipher) }, values)
|
||||
{ }
|
||||
}
|
||||
|
||||
internal class InlineKnownUserCipherAutoDataAttribute : InlineCustomAutoDataAttribute
|
||||
{
|
||||
public InlineKnownUserCipherAutoDataAttribute(string userId, params object[] values) : base(new ICustomization[]
|
||||
{ new SutProviderCustomization(), new UserCipher { UserId = new Guid(userId) } }, values)
|
||||
{ }
|
||||
|
||||
}
|
||||
}
|
11
test/Core.Test/AutoFixture/FixtureExtensions.cs
Normal file
11
test/Core.Test/AutoFixture/FixtureExtensions.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using AutoFixture;
|
||||
using AutoFixture.AutoNSubstitute;
|
||||
|
||||
namespace Bit.Core.Test.AutoFixture
|
||||
{
|
||||
public static class FixtureExtensions
|
||||
{
|
||||
public static IFixture WithAutoNSubstitutions(this IFixture fixture)
|
||||
=> fixture.Customize(new AutoNSubstituteCustomization());
|
||||
}
|
||||
}
|
10
test/Core.Test/AutoFixture/ISutProvider.cs
Normal file
10
test/Core.Test/AutoFixture/ISutProvider.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Bit.Core.Test.AutoFixture
|
||||
{
|
||||
public interface ISutProvider
|
||||
{
|
||||
Type SutType { get; }
|
||||
ISutProvider Create();
|
||||
}
|
||||
}
|
130
test/Core.Test/AutoFixture/SutProvider.cs
Normal file
130
test/Core.Test/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.Core.Test.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][parameterName];
|
||||
}
|
||||
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);
|
||||
|
||||
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/Core.Test/AutoFixture/SutProviderCustomization.cs
Normal file
32
test/Core.Test/AutoFixture/SutProviderCustomization.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using AutoFixture;
|
||||
using AutoFixture.Kernel;
|
||||
|
||||
namespace Bit.Core.Test.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);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user