mirror of https://github.com/Squidex/squidex.git
Browse Source
* Batch update for elastic. * Cleanup * Fix bulk updates. * Lets encryptpull/593/head
committed by
GitHub
26 changed files with 665 additions and 247 deletions
@ -1,23 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using MongoDB.Bson.Serialization.Attributes; |
|
||||
|
|
||||
namespace Squidex.Domain.Users.MongoDb |
|
||||
{ |
|
||||
public sealed class MongoKey |
|
||||
{ |
|
||||
[BsonId] |
|
||||
public string Id { get; set; } |
|
||||
|
|
||||
[BsonElement] |
|
||||
public string Key { get; set; } |
|
||||
|
|
||||
[BsonElement] |
|
||||
public MongoKeyParameters Parameters { get; set; } |
|
||||
} |
|
||||
} |
|
||||
@ -1,64 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Security.Cryptography; |
|
||||
|
|
||||
#pragma warning disable IDE0017 // Simplify object initialization
|
|
||||
|
|
||||
namespace Squidex.Domain.Users.MongoDb |
|
||||
{ |
|
||||
public sealed class MongoKeyParameters |
|
||||
{ |
|
||||
public byte[] D { get; set; } |
|
||||
|
|
||||
public byte[] DP { get; set; } |
|
||||
|
|
||||
public byte[] DQ { get; set; } |
|
||||
|
|
||||
public byte[] Exponent { get; set; } |
|
||||
|
|
||||
public byte[] InverseQ { get; set; } |
|
||||
|
|
||||
public byte[] Modulus { get; set; } |
|
||||
|
|
||||
public byte[] P { get; set; } |
|
||||
|
|
||||
public byte[] Q { get; set; } |
|
||||
|
|
||||
public static MongoKeyParameters Create(RSAParameters source) |
|
||||
{ |
|
||||
var mongoParameters = new MongoKeyParameters(); |
|
||||
|
|
||||
mongoParameters.D = source.D; |
|
||||
mongoParameters.DP = source.DP; |
|
||||
mongoParameters.DQ = source.DQ; |
|
||||
mongoParameters.Exponent = source.Exponent; |
|
||||
mongoParameters.InverseQ = source.InverseQ; |
|
||||
mongoParameters.Modulus = source.Modulus; |
|
||||
mongoParameters.P = source.P; |
|
||||
mongoParameters.Q = source.Q; |
|
||||
|
|
||||
return mongoParameters; |
|
||||
} |
|
||||
|
|
||||
public RSAParameters ToParameters() |
|
||||
{ |
|
||||
var parameters = default(RSAParameters); |
|
||||
|
|
||||
parameters.D = D; |
|
||||
parameters.DP = DP; |
|
||||
parameters.DQ = DQ; |
|
||||
parameters.Exponent = Exponent; |
|
||||
parameters.InverseQ = InverseQ; |
|
||||
parameters.Modulus = Modulus; |
|
||||
parameters.P = P; |
|
||||
parameters.Q = Q; |
|
||||
|
|
||||
return parameters; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,20 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using MongoDB.Bson.Serialization.Attributes; |
|
||||
|
|
||||
namespace Squidex.Domain.Users.MongoDb |
|
||||
{ |
|
||||
public sealed class MongoXmlEntity |
|
||||
{ |
|
||||
[BsonId] |
|
||||
public string FriendlyName { get; set; } |
|
||||
|
|
||||
[BsonRequired] |
|
||||
public string Xml { get; set; } |
|
||||
} |
|
||||
} |
|
||||
@ -1,51 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using System.Xml.Linq; |
|
||||
using Microsoft.AspNetCore.DataProtection.Repositories; |
|
||||
using MongoDB.Bson; |
|
||||
using MongoDB.Driver; |
|
||||
using Squidex.Infrastructure; |
|
||||
|
|
||||
namespace Squidex.Domain.Users.MongoDb |
|
||||
{ |
|
||||
public sealed class MongoXmlRepository : IXmlRepository |
|
||||
{ |
|
||||
private static readonly ReplaceOptions UpsertReplace = new ReplaceOptions { IsUpsert = true }; |
|
||||
private readonly IMongoCollection<MongoXmlEntity> collection; |
|
||||
|
|
||||
public MongoXmlRepository(IMongoDatabase mongoDatabase) |
|
||||
{ |
|
||||
Guard.NotNull(mongoDatabase, nameof(mongoDatabase)); |
|
||||
|
|
||||
collection = mongoDatabase.GetCollection<MongoXmlEntity>("States_Repository"); |
|
||||
} |
|
||||
|
|
||||
public IReadOnlyCollection<XElement> GetAllElements() |
|
||||
{ |
|
||||
var documents = collection.Find(new BsonDocument()).ToList(); |
|
||||
|
|
||||
var elements = documents.Select(x => XElement.Parse(x.Xml)).ToList(); |
|
||||
|
|
||||
return elements; |
|
||||
} |
|
||||
|
|
||||
public void StoreElement(XElement element, string friendlyName) |
|
||||
{ |
|
||||
var document = new MongoXmlEntity |
|
||||
{ |
|
||||
FriendlyName = friendlyName |
|
||||
}; |
|
||||
|
|
||||
document.Xml = element.ToString(); |
|
||||
|
|
||||
collection.ReplaceOne(x => x.FriendlyName == friendlyName, document, UpsertReplace); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,59 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using LettuceEncrypt.Accounts; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.States; |
||||
|
|
||||
|
namespace Squidex.Domain.Users |
||||
|
{ |
||||
|
public sealed class DefaultCertificateAccountStore : IAccountStore |
||||
|
{ |
||||
|
private readonly ISnapshotStore<State, Guid> store; |
||||
|
|
||||
|
[CollectionName("Identity_certificateAccount")] |
||||
|
public sealed class State |
||||
|
{ |
||||
|
public AccountModel Account { get; set; } |
||||
|
|
||||
|
public State() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public State(AccountModel account) |
||||
|
{ |
||||
|
Account = account; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public DefaultCertificateAccountStore(ISnapshotStore<State, Guid> store) |
||||
|
{ |
||||
|
Guard.NotNull(store, nameof(store)); |
||||
|
|
||||
|
this.store = store; |
||||
|
} |
||||
|
|
||||
|
public async Task<AccountModel?> GetAccountAsync(CancellationToken cancellationToken) |
||||
|
{ |
||||
|
var (value, _) = await store.ReadAsync(default); |
||||
|
|
||||
|
return value?.Account; |
||||
|
} |
||||
|
|
||||
|
public Task SaveAccountAsync(AccountModel account, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
Guard.NotNull(account, nameof(account)); |
||||
|
|
||||
|
var state = new State(account); |
||||
|
|
||||
|
return store.WriteAsync(default, state, EtagVersion.Any, 0); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Security.Cryptography.X509Certificates; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using LettuceEncrypt; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.States; |
||||
|
|
||||
|
namespace Squidex.Domain.Users |
||||
|
{ |
||||
|
public sealed class DefaultCertificateStore : ICertificateRepository, ICertificateSource |
||||
|
{ |
||||
|
private readonly ISnapshotStore<State, Guid> store; |
||||
|
|
||||
|
[CollectionName("Identity_Certificates")] |
||||
|
public sealed class State |
||||
|
{ |
||||
|
public byte[] Certificate { get; set; } |
||||
|
|
||||
|
public State() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public State(X509Certificate2 certificate) |
||||
|
{ |
||||
|
Certificate = certificate.Export(X509ContentType.Pfx); |
||||
|
} |
||||
|
|
||||
|
public X509Certificate2 ToCertificate() |
||||
|
{ |
||||
|
return new X509Certificate2(Certificate); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public DefaultCertificateStore(ISnapshotStore<State, Guid> store) |
||||
|
{ |
||||
|
Guard.NotNull(store, nameof(store)); |
||||
|
|
||||
|
this.store = store; |
||||
|
} |
||||
|
|
||||
|
public async Task<IEnumerable<X509Certificate2>> GetCertificatesAsync(CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var result = new List<X509Certificate2>(); |
||||
|
|
||||
|
await store.ReadAllAsync((state, _) => |
||||
|
{ |
||||
|
result.Add(state.ToCertificate()); |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
}, cancellationToken); |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public Task SaveAsync(X509Certificate2 certificate, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
Guard.NotNull(certificate, nameof(certificate)); |
||||
|
|
||||
|
var state = new State(certificate); |
||||
|
|
||||
|
return store.WriteAsync(Guid.NewGuid(), state, EtagVersion.Any, 0); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,69 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using System.Xml.Linq; |
||||
|
using Microsoft.AspNetCore.DataProtection.Repositories; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.States; |
||||
|
|
||||
|
namespace Squidex.Domain.Users |
||||
|
{ |
||||
|
public sealed class DefaultXmlRepository : IXmlRepository |
||||
|
{ |
||||
|
private readonly ISnapshotStore<State, string> store; |
||||
|
|
||||
|
[CollectionName("Identity_Xml")] |
||||
|
public sealed class State |
||||
|
{ |
||||
|
public string Xml { get; set; } |
||||
|
|
||||
|
public State() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public State(XElement xml) |
||||
|
{ |
||||
|
Xml = xml.ToString(); |
||||
|
} |
||||
|
|
||||
|
public XElement ToXml() |
||||
|
{ |
||||
|
return XElement.Parse(Xml); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public DefaultXmlRepository(ISnapshotStore<State, string> store) |
||||
|
{ |
||||
|
Guard.NotNull(store, nameof(store)); |
||||
|
|
||||
|
this.store = store; |
||||
|
} |
||||
|
|
||||
|
public IReadOnlyCollection<XElement> GetAllElements() |
||||
|
{ |
||||
|
var result = new List<XElement>(); |
||||
|
|
||||
|
store.ReadAllAsync((state, _) => |
||||
|
{ |
||||
|
result.Add(state.ToXml()); |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
}).Wait(); |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public void StoreElement(XElement element, string friendlyName) |
||||
|
{ |
||||
|
var state = new State(element); |
||||
|
|
||||
|
store.WriteAsync(friendlyName, state, EtagVersion.Any, 0); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using FakeItEasy; |
||||
|
using LettuceEncrypt.Accounts; |
||||
|
using Squidex.Infrastructure.States; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Users |
||||
|
{ |
||||
|
public class DefaultCertificateAccountStoreTests |
||||
|
{ |
||||
|
private readonly ISnapshotStore<DefaultCertificateAccountStore.State, Guid> store = A.Fake<ISnapshotStore<DefaultCertificateAccountStore.State, Guid>>(); |
||||
|
private readonly DefaultCertificateAccountStore sut; |
||||
|
|
||||
|
public DefaultCertificateAccountStoreTests() |
||||
|
{ |
||||
|
sut = new DefaultCertificateAccountStore(store); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_read_from_store() |
||||
|
{ |
||||
|
var model = new AccountModel(); |
||||
|
|
||||
|
A.CallTo(() => store.ReadAsync(default)) |
||||
|
.Returns((new DefaultCertificateAccountStore.State { Account = model }, 0)); |
||||
|
|
||||
|
var result = await sut.GetAccountAsync(default); |
||||
|
|
||||
|
Assert.Same(model, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_write_to_store() |
||||
|
{ |
||||
|
var model = new AccountModel(); |
||||
|
|
||||
|
await sut.SaveAccountAsync(model, default); |
||||
|
|
||||
|
A.CallTo(() => store.WriteAsync(A<Guid>._, A<DefaultCertificateAccountStore.State>._, A<long>._, 0)) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,72 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Security.Cryptography; |
||||
|
using System.Security.Cryptography.X509Certificates; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using FakeItEasy; |
||||
|
using Squidex.Infrastructure.States; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Users |
||||
|
{ |
||||
|
public sealed class DefaultCertificateStoreTests |
||||
|
{ |
||||
|
private readonly ISnapshotStore<DefaultCertificateStore.State, Guid> store = A.Fake<ISnapshotStore<DefaultCertificateStore.State, Guid>>(); |
||||
|
private readonly DefaultCertificateStore sut; |
||||
|
|
||||
|
public DefaultCertificateStoreTests() |
||||
|
{ |
||||
|
sut = new DefaultCertificateStore(store); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_read_from_store() |
||||
|
{ |
||||
|
A.CallTo(() => store.ReadAllAsync(A<Func<DefaultCertificateStore.State, long, Task>>._, A<CancellationToken>._)) |
||||
|
.Invokes((Func<DefaultCertificateStore.State, long, Task> callback, CancellationToken _) => |
||||
|
{ |
||||
|
callback(new DefaultCertificateStore.State |
||||
|
{ |
||||
|
Certificate = MakeCert().Export(X509ContentType.Pfx) |
||||
|
}, 0); |
||||
|
|
||||
|
callback(new DefaultCertificateStore.State |
||||
|
{ |
||||
|
Certificate = MakeCert().Export(X509ContentType.Pfx) |
||||
|
}, 0); |
||||
|
}); |
||||
|
|
||||
|
var xml = await sut.GetCertificatesAsync(); |
||||
|
|
||||
|
Assert.Equal(2, xml.Count()); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_write_to_store() |
||||
|
{ |
||||
|
var certificate = MakeCert(); |
||||
|
|
||||
|
await sut.SaveAsync(certificate, default); |
||||
|
|
||||
|
A.CallTo(() => store.WriteAsync(A<Guid>._, A<DefaultCertificateStore.State>._, A<long>._, 0)) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
private static X509Certificate2 MakeCert() |
||||
|
{ |
||||
|
var ecdsa = ECDsa.Create(); |
||||
|
|
||||
|
var certificateRequest = new CertificateRequest("cn=foobar", ecdsa, HashAlgorithmName.SHA256); |
||||
|
|
||||
|
return certificateRequest.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(5)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using FakeItEasy; |
||||
|
using Squidex.Infrastructure.States; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Users |
||||
|
{ |
||||
|
public class DefaultKeyStoreTests |
||||
|
{ |
||||
|
private readonly ISnapshotStore<DefaultKeyStore.State, Guid> store = A.Fake<ISnapshotStore<DefaultKeyStore.State, Guid>>(); |
||||
|
private readonly DefaultKeyStore sut; |
||||
|
|
||||
|
public DefaultKeyStoreTests() |
||||
|
{ |
||||
|
sut = new DefaultKeyStore(store); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_generate_signing_credentials_once() |
||||
|
{ |
||||
|
A.CallTo(() => store.ReadAsync(A<Guid>._)) |
||||
|
.Returns((null!, 0)); |
||||
|
|
||||
|
var credentials1 = await sut.GetSigningCredentialsAsync(); |
||||
|
var credentials2 = await sut.GetSigningCredentialsAsync(); |
||||
|
|
||||
|
Assert.Same(credentials1, credentials2); |
||||
|
|
||||
|
A.CallTo(() => store.ReadAsync(A<Guid>._)) |
||||
|
.MustHaveHappenedOnceExactly(); |
||||
|
|
||||
|
A.CallTo(() => store.WriteAsync(A<Guid>._, A<DefaultKeyStore.State>._, 0, 0)) |
||||
|
.MustHaveHappenedOnceExactly(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_generate_validation_keys_once() |
||||
|
{ |
||||
|
A.CallTo(() => store.ReadAsync(A<Guid>._)) |
||||
|
.Returns((null!, 0)); |
||||
|
|
||||
|
var credentials1 = await sut.GetValidationKeysAsync(); |
||||
|
var credentials2 = await sut.GetValidationKeysAsync(); |
||||
|
|
||||
|
Assert.Same(credentials1, credentials2); |
||||
|
|
||||
|
A.CallTo(() => store.ReadAsync(A<Guid>._)) |
||||
|
.MustHaveHappenedOnceExactly(); |
||||
|
|
||||
|
A.CallTo(() => store.WriteAsync(A<Guid>._, A<DefaultKeyStore.State>._, 0, 0)) |
||||
|
.MustHaveHappenedOnceExactly(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,61 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using System.Xml.Linq; |
||||
|
using FakeItEasy; |
||||
|
using Squidex.Infrastructure.States; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Users |
||||
|
{ |
||||
|
public sealed class DefaultXmlRepositoryTests |
||||
|
{ |
||||
|
private readonly ISnapshotStore<DefaultXmlRepository.State, string> store = A.Fake<ISnapshotStore<DefaultXmlRepository.State, string>>(); |
||||
|
private readonly DefaultXmlRepository sut; |
||||
|
|
||||
|
public DefaultXmlRepositoryTests() |
||||
|
{ |
||||
|
sut = new DefaultXmlRepository(store); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_read_from_store() |
||||
|
{ |
||||
|
A.CallTo(() => store.ReadAllAsync(A< Func<DefaultXmlRepository.State, long, Task>>._, A<CancellationToken>._)) |
||||
|
.Invokes((Func<DefaultXmlRepository.State, long, Task> callback, CancellationToken _) => |
||||
|
{ |
||||
|
callback(new DefaultXmlRepository.State |
||||
|
{ |
||||
|
Xml = new XElement("a").ToString() |
||||
|
}, 0); |
||||
|
|
||||
|
callback(new DefaultXmlRepository.State |
||||
|
{ |
||||
|
Xml = new XElement("b").ToString() |
||||
|
}, 0); |
||||
|
}); |
||||
|
|
||||
|
var xml = sut.GetAllElements(); |
||||
|
|
||||
|
Assert.Equal(2, xml.Count); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_write_to_store() |
||||
|
{ |
||||
|
var xml = new XElement("x"); |
||||
|
|
||||
|
sut.StoreElement(xml, "name"); |
||||
|
|
||||
|
A.CallTo(() => store.WriteAsync("name", A<DefaultXmlRepository.State>._, A<long>._, 0)) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue