Browse Source

Tests for assets.

pull/206/head
Sebastian Stehle 8 years ago
parent
commit
50610d5cdd
  1. 16
      src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs
  2. 1
      src/Squidex.Domain.Apps.Entities/DomainObjectState.cs
  3. 31
      src/Squidex.Domain.Apps.Entities/EntityMapper.cs
  4. 6
      src/Squidex.Domain.Apps.Entities/IEntity.cs
  5. 22
      src/Squidex.Domain.Apps.Entities/IUpdateableEntity.cs
  6. 2
      src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore.cs
  7. 5
      src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs
  8. 49
      src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStore.cs
  9. 6
      src/Squidex.Infrastructure/States/ISnapshotStore.cs
  10. 28
      src/Squidex.Infrastructure/States/Persistence.cs
  11. 12
      src/Squidex.Infrastructure/States/StateFactory.cs
  12. 23
      src/Squidex.Infrastructure/States/Store.cs
  13. 4
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs
  14. 7
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs
  15. 137
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs
  16. 209
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetDomainObjectTests.cs
  17. 65
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/Guards/GuardAssetTests.cs
  18. 6
      tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleCommandMiddlewareTests.cs
  19. 14
      tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDomainObjectTests.cs

16
src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs

@ -26,14 +26,19 @@ namespace Squidex.Domain.Apps.Entities.Assets
{ {
FileName = command.File.FileName, FileName = command.File.FileName,
FileSize = command.File.FileSize, FileSize = command.File.FileSize,
FileVersion = State.FileVersion + 1, FileVersion = 0,
MimeType = command.File.MimeType, MimeType = command.File.MimeType,
PixelWidth = command.ImageInfo?.PixelWidth, PixelWidth = command.ImageInfo?.PixelWidth,
PixelHeight = command.ImageInfo?.PixelHeight, PixelHeight = command.ImageInfo?.PixelHeight,
IsImage = command.ImageInfo != null IsImage = command.ImageInfo != null
}); });
UpdateState(command, s => SimpleMapper.Map(@event, s)); UpdateState(command, s =>
{
s.TotalSize = @event.FileSize;
SimpleMapper.Map(@event, s);
});
RaiseEvent(@event); RaiseEvent(@event);
@ -54,7 +59,12 @@ namespace Squidex.Domain.Apps.Entities.Assets
IsImage = command.ImageInfo != null IsImage = command.ImageInfo != null
}); });
UpdateState(command, s => SimpleMapper.Map(@event, s)); UpdateState(command, s =>
{
s.TotalSize += @event.FileSize;
SimpleMapper.Map(@event, s);
});
RaiseEvent(@event); RaiseEvent(@event);

1
src/Squidex.Domain.Apps.Entities/DomainObjectState.cs

@ -14,6 +14,7 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities namespace Squidex.Domain.Apps.Entities
{ {
public abstract class DomainObjectState<T> : Cloneable<T>, public abstract class DomainObjectState<T> : Cloneable<T>,
IUpdateableEntity,
IUpdateableEntityWithCreatedBy, IUpdateableEntityWithCreatedBy,
IUpdateableEntityWithLastModifiedBy, IUpdateableEntityWithLastModifiedBy,
IUpdateableEntityWithVersion IUpdateableEntityWithVersion

31
src/Squidex.Domain.Apps.Entities/EntityMapper.cs

@ -8,6 +8,7 @@
using System; using System;
using NodaTime; using NodaTime;
using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities namespace Squidex.Domain.Apps.Entities
{ {
@ -17,28 +18,24 @@ namespace Squidex.Domain.Apps.Entities
{ {
var timestamp = SystemClock.Instance.GetCurrentInstant(); var timestamp = SystemClock.Instance.GetCurrentInstant();
SetId(entity, command);
SetAppId(entity, command); SetAppId(entity, command);
SetVersion(entity);
SetCreated(entity, timestamp); SetCreated(entity, timestamp);
SetCreatedBy(entity, command); SetCreatedBy(entity, command);
SetLastModified(entity, timestamp); SetLastModified(entity, timestamp);
SetLastModifiedBy(entity, command); SetLastModifiedBy(entity, command);
SetVersion(entity);
updater?.Invoke(entity); updater?.Invoke(entity);
return entity; return entity;
} }
private static void SetLastModified(IEntity entity, Instant timestamp) private static void SetId(IEntity entity, SquidexCommand command)
{
entity.LastModified = timestamp;
}
private static void SetCreated(IEntity entity, Instant timestamp)
{ {
if (entity.Created == default(Instant)) if (entity is IUpdateableEntity updateable && command is IAggregateCommand aggregateCommand)
{ {
entity.Created = timestamp; updateable.Id = aggregateCommand.AggregateId;
} }
} }
@ -50,6 +47,14 @@ namespace Squidex.Domain.Apps.Entities
} }
} }
private static void SetCreated(IEntity entity, Instant timestamp)
{
if (entity is IUpdateableEntity updateable && updateable.Created == default(Instant))
{
updateable.Created = timestamp;
}
}
private static void SetCreatedBy(IEntity entity, SquidexCommand command) private static void SetCreatedBy(IEntity entity, SquidexCommand command)
{ {
if (entity is IUpdateableEntityWithCreatedBy withCreatedBy && withCreatedBy.CreatedBy == null) if (entity is IUpdateableEntityWithCreatedBy withCreatedBy && withCreatedBy.CreatedBy == null)
@ -58,6 +63,14 @@ namespace Squidex.Domain.Apps.Entities
} }
} }
private static void SetLastModified(IEntity entity, Instant timestamp)
{
if (entity is IUpdateableEntity updateable)
{
updateable.LastModified = timestamp;
}
}
private static void SetLastModifiedBy(IEntity entity, SquidexCommand command) private static void SetLastModifiedBy(IEntity entity, SquidexCommand command)
{ {
if (entity is IUpdateableEntityWithLastModifiedBy withModifiedBy) if (entity is IUpdateableEntityWithLastModifiedBy withModifiedBy)

6
src/Squidex.Domain.Apps.Entities/IEntity.cs

@ -13,10 +13,10 @@ namespace Squidex.Domain.Apps.Entities
{ {
public interface IEntity public interface IEntity
{ {
Guid Id { get; set; } Guid Id { get; }
Instant Created { get; set; } Instant Created { get; }
Instant LastModified { get; set; } Instant LastModified { get; }
} }
} }

22
src/Squidex.Domain.Apps.Entities/IUpdateableEntity.cs

@ -0,0 +1,22 @@
// ==========================================================================
// IUpdateableEntity.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using NodaTime;
namespace Squidex.Domain.Apps.Entities
{
public interface IUpdateableEntity
{
Guid Id { get; set; }
Instant Created { get; set; }
Instant LastModified { get; set; }
}
}

2
src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore.cs

@ -200,7 +200,7 @@ namespace Squidex.Infrastructure.EventSourcing
{ {
var document = var document =
await Collection.Find(Filter.Eq(EventStreamField, streamName)) await Collection.Find(Filter.Eq(EventStreamField, streamName))
.Project<BsonDocument>(Project .Project<BsonDocument>(Projection
.Include(EventStreamOffsetField) .Include(EventStreamOffsetField)
.Include(EventsCountField)) .Include(EventsCountField))
.Sort(Sort.Descending(EventStreamOffsetField)).Limit(1) .Sort(Sort.Descending(EventStreamOffsetField)).Limit(1)

5
src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs

@ -13,18 +13,21 @@ using MongoDB.Bson;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Tasks;
#pragma warning disable RECS0108 // Warns about static fields in generic types
namespace Squidex.Infrastructure.MongoDb namespace Squidex.Infrastructure.MongoDb
{ {
public abstract class MongoRepositoryBase<TEntity> : IExternalSystem public abstract class MongoRepositoryBase<TEntity> : IExternalSystem
{ {
private const string CollectionFormat = "{0}Set"; private const string CollectionFormat = "{0}Set";
protected static readonly UpdateOptions Upsert = new UpdateOptions { IsUpsert = true };
protected static readonly SortDefinitionBuilder<TEntity> Sort = Builders<TEntity>.Sort; protected static readonly SortDefinitionBuilder<TEntity> Sort = Builders<TEntity>.Sort;
protected static readonly UpdateDefinitionBuilder<TEntity> Update = Builders<TEntity>.Update; protected static readonly UpdateDefinitionBuilder<TEntity> Update = Builders<TEntity>.Update;
protected static readonly FieldDefinitionBuilder<TEntity> Fields = FieldDefinitionBuilder<TEntity>.Instance; protected static readonly FieldDefinitionBuilder<TEntity> Fields = FieldDefinitionBuilder<TEntity>.Instance;
protected static readonly FilterDefinitionBuilder<TEntity> Filter = Builders<TEntity>.Filter; protected static readonly FilterDefinitionBuilder<TEntity> Filter = Builders<TEntity>.Filter;
protected static readonly IndexKeysDefinitionBuilder<TEntity> Index = Builders<TEntity>.IndexKeys; protected static readonly IndexKeysDefinitionBuilder<TEntity> Index = Builders<TEntity>.IndexKeys;
protected static readonly ProjectionDefinitionBuilder<TEntity> Project = Builders<TEntity>.Projection; protected static readonly ProjectionDefinitionBuilder<TEntity> Projection = Builders<TEntity>.Projection;
private readonly IMongoDatabase mongoDatabase; private readonly IMongoDatabase mongoDatabase;
private Lazy<IMongoCollection<TEntity>> mongoCollection; private Lazy<IMongoCollection<TEntity>> mongoCollection;

49
src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStore.cs

@ -6,46 +6,34 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using MongoDB.Driver; using MongoDB.Driver;
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Infrastructure.States namespace Squidex.Infrastructure.States
{ {
public sealed class MongoSnapshotStore : ISnapshotStore, IExternalSystem public class MongoSnapshotStore<T> : MongoRepositoryBase<MongoState<T>>, ISnapshotStore<T>, IExternalSystem
{ {
private static readonly UpdateOptions Upsert = new UpdateOptions { IsUpsert = true };
private readonly IMongoDatabase database;
private readonly JsonSerializer serializer; private readonly JsonSerializer serializer;
public MongoSnapshotStore(IMongoDatabase database, JsonSerializer serializer) public MongoSnapshotStore(IMongoDatabase database, JsonSerializer serializer)
: base(database)
{ {
Guard.NotNull(database, nameof(database));
Guard.NotNull(serializer, nameof(serializer)); Guard.NotNull(serializer, nameof(serializer));
this.database = database;
this.serializer = serializer; this.serializer = serializer;
} }
public void Connect() protected override string CollectionName()
{ {
try return $"States_{typeof(T).Name}";
{
database.ListCollections();
}
catch (Exception ex)
{
throw new ConfigurationException($"MongoDb connection failed to connect to database {database.DatabaseNamespace.DatabaseName}", ex);
}
} }
public async Task<(T Value, long Version)> ReadAsync<T>(string key) public async Task<(T Value, long Version)> ReadAsync(string key)
{ {
var collection = GetCollection<T>();
var existing = var existing =
await collection.Find(x => x.Id == key) await Collection.Find(x => x.Id == key)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
if (existing != null) if (existing != null)
@ -56,18 +44,16 @@ namespace Squidex.Infrastructure.States
return (default(T), -1); return (default(T), -1);
} }
public async Task WriteAsync<T>(string key, T value, long oldVersion, long newVersion) public async Task WriteAsync(string key, T value, long oldVersion, long newVersion)
{ {
var collection = GetCollection<T>();
try try
{ {
await collection.UpdateOneAsync( await Collection.UpdateOneAsync(
Builders<MongoState<T>>.Filter.And( Filter.And(
Builders<MongoState<T>>.Filter.Eq(x => x.Id, key), Filter.Eq(x => x.Id, key),
Builders<MongoState<T>>.Filter.Eq(x => x.Version, oldVersion) Filter.Eq(x => x.Version, oldVersion)
), ),
Builders<MongoState<T>>.Update Update
.Set(x => x.Doc, value) .Set(x => x.Doc, value)
.Set(x => x.Version, newVersion), .Set(x => x.Version, newVersion),
Upsert); Upsert);
@ -77,8 +63,8 @@ namespace Squidex.Infrastructure.States
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{ {
var existingVersion = var existingVersion =
await collection.Find(x => x.Id == key) await Collection.Find(x => x.Id == key)
.Project<MongoState<T>>(Builders<MongoState<T>>.Projection.Exclude(x => x.Id)).FirstOrDefaultAsync(); .Project<MongoState<T>>(Projection.Exclude(x => x.Id)).FirstOrDefaultAsync();
if (existingVersion != null) if (existingVersion != null)
{ {
@ -91,10 +77,5 @@ namespace Squidex.Infrastructure.States
} }
} }
} }
private IMongoCollection<MongoState<T>> GetCollection<T>()
{
return database.GetCollection<MongoState<T>>($"States_{typeof(T).Name}");
}
} }
} }

6
src/Squidex.Infrastructure/States/ISnapshotStore.cs

@ -10,10 +10,10 @@ using System.Threading.Tasks;
namespace Squidex.Infrastructure.States namespace Squidex.Infrastructure.States
{ {
public interface ISnapshotStore public interface ISnapshotStore<T>
{ {
Task WriteAsync<T>(string key, T value, long oldVersion, long newVersion); Task WriteAsync(string key, T value, long oldVersion, long newVersion);
Task<(T Value, long Version)> ReadAsync<T>(string key); Task<(T Value, long Version)> ReadAsync(string key);
} }
} }

28
src/Squidex.Infrastructure/States/Persistence.cs

@ -13,10 +13,10 @@ using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Infrastructure.States namespace Squidex.Infrastructure.States
{ {
public sealed class Persistence<TOwner, TState> : IPersistence<TState> internal sealed class Persistence<TOwner, TState> : IPersistence<TState>
{ {
private readonly string ownerKey; private readonly string ownerKey;
private readonly ISnapshotStore snapshotStore; private readonly ISnapshotStore<TState> snapshotStore;
private readonly IStreamNameResolver streamNameResolver; private readonly IStreamNameResolver streamNameResolver;
private readonly IEventStore eventStore; private readonly IEventStore eventStore;
private readonly IEventDataFormatter eventDataFormatter; private readonly IEventDataFormatter eventDataFormatter;
@ -30,13 +30,11 @@ namespace Squidex.Infrastructure.States
Action invalidate, Action invalidate,
IEventStore eventStore, IEventStore eventStore,
IEventDataFormatter eventDataFormatter, IEventDataFormatter eventDataFormatter,
ISnapshotStore snapshotStore, ISnapshotStore<TState> snapshotStore,
IStreamNameResolver streamNameResolver, IStreamNameResolver streamNameResolver,
Func<TState, Task> applyState, Func<TState, Task> applyState,
Func<Envelope<IEvent>, Task> applyEvent) Func<Envelope<IEvent>, Task> applyEvent)
{ {
Guard.NotNull(ownerKey, nameof(ownerKey));
this.ownerKey = ownerKey; this.ownerKey = ownerKey;
this.applyState = applyState; this.applyState = applyState;
this.applyEvent = applyEvent; this.applyEvent = applyEvent;
@ -54,7 +52,7 @@ namespace Squidex.Infrastructure.States
if (snapshotStore != null) if (snapshotStore != null)
{ {
var (state, position) = await snapshotStore.ReadAsync<TState>(ownerKey); var (state, position) = await snapshotStore.ReadAsync(ownerKey);
positionSnapshot = position; positionSnapshot = position;
positionEvent = position; positionEvent = position;
@ -104,13 +102,10 @@ namespace Squidex.Infrastructure.States
public async Task WriteSnapshotAsync(TState state) public async Task WriteSnapshotAsync(TState state)
{ {
if (snapshotStore == null)
{
throw new InvalidOperationException("Snapshots are not supported.");
}
var newPosition = var newPosition =
eventStore != null ? positionEvent : positionSnapshot + 1; eventStore != null ?
positionEvent :
positionSnapshot + 1;
if (newPosition != positionSnapshot) if (newPosition != positionSnapshot)
{ {
@ -126,18 +121,13 @@ namespace Squidex.Infrastructure.States
positionSnapshot = newPosition; positionSnapshot = newPosition;
} }
invalidate(); invalidate?.Invoke();
} }
public async Task WriteEventsAsync(params Envelope<IEvent>[] @events) public async Task WriteEventsAsync(params Envelope<IEvent>[] @events)
{ {
Guard.NotNull(events, nameof(@events)); Guard.NotNull(events, nameof(@events));
if (eventStore == null)
{
throw new InvalidOperationException("Events are not supported.");
}
if (@events.Length > 0) if (@events.Length > 0)
{ {
var commitId = Guid.NewGuid(); var commitId = Guid.NewGuid();
@ -157,7 +147,7 @@ namespace Squidex.Infrastructure.States
positionEvent += events.Length; positionEvent += events.Length;
} }
invalidate(); invalidate?.Invoke();
} }
private EventData[] GetEventData(Envelope<IEvent>[] events, Guid commitId) private EventData[] GetEventData(Envelope<IEvent>[] events, Guid commitId)

12
src/Squidex.Infrastructure/States/StateFactory.cs

@ -21,7 +21,6 @@ namespace Squidex.Infrastructure.States
private readonly IPubSub pubSub; private readonly IPubSub pubSub;
private readonly IMemoryCache statesCache; private readonly IMemoryCache statesCache;
private readonly IServiceProvider services; private readonly IServiceProvider services;
private readonly ISnapshotStore snapshotStore;
private readonly IStreamNameResolver streamNameResolver; private readonly IStreamNameResolver streamNameResolver;
private readonly IEventStore eventStore; private readonly IEventStore eventStore;
private readonly IEventDataFormatter eventDataFormatter; private readonly IEventDataFormatter eventDataFormatter;
@ -54,22 +53,19 @@ namespace Squidex.Infrastructure.States
IEventStore eventStore, IEventStore eventStore,
IEventDataFormatter eventDataFormatter, IEventDataFormatter eventDataFormatter,
IServiceProvider services, IServiceProvider services,
ISnapshotStore snapshotStore,
IStreamNameResolver streamNameResolver) IStreamNameResolver streamNameResolver)
{ {
Guard.NotNull(services, nameof(services)); Guard.NotNull(services, nameof(services));
Guard.NotNull(eventStore, nameof(eventStore)); Guard.NotNull(eventStore, nameof(eventStore));
Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter)); Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter));
Guard.NotNull(pubSub, nameof(pubSub)); Guard.NotNull(pubSub, nameof(pubSub));
Guard.NotNull(snapshotStore, nameof(snapshotStore));
Guard.NotNull(statesCache, nameof(statesCache)); Guard.NotNull(statesCache, nameof(statesCache));
Guard.NotNull(streamNameResolver, nameof(streamNameResolver)); Guard.NotNull(streamNameResolver, nameof(streamNameResolver));
this.services = services;
this.eventStore = eventStore; this.eventStore = eventStore;
this.eventDataFormatter = eventDataFormatter; this.eventDataFormatter = eventDataFormatter;
this.pubSub = pubSub; this.pubSub = pubSub;
this.snapshotStore = snapshotStore; this.services = services;
this.statesCache = statesCache; this.statesCache = statesCache;
this.streamNameResolver = streamNameResolver; this.streamNameResolver = streamNameResolver;
} }
@ -89,7 +85,7 @@ namespace Squidex.Infrastructure.States
{ {
Guard.NotNull(key, nameof(key)); Guard.NotNull(key, nameof(key));
var stateStore = new Store(() => { }, eventStore, eventDataFormatter, snapshotStore, streamNameResolver); var stateStore = new Store(eventStore, eventDataFormatter, services, streamNameResolver);
var state = (T)services.GetService(typeof(T)); var state = (T)services.GetService(typeof(T));
await state.ActivateAsync(key, stateStore); await state.ActivateAsync(key, stateStore);
@ -110,10 +106,10 @@ namespace Squidex.Infrastructure.States
var state = (T)services.GetService(typeof(T)); var state = (T)services.GetService(typeof(T));
var stateStore = new Store(() => var stateStore = new Store(eventStore, eventDataFormatter, services, streamNameResolver, () =>
{ {
pubSub.Publish(new InvalidateMessage { Key = key }, false); pubSub.Publish(new InvalidateMessage { Key = key }, false);
}, eventStore, eventDataFormatter, snapshotStore, streamNameResolver); });
stateObj = new ObjectHolder<T>(state, key, stateStore); stateObj = new ObjectHolder<T>(state, key, stateStore);

23
src/Squidex.Infrastructure/States/Store.cs

@ -15,37 +15,46 @@ namespace Squidex.Infrastructure.States
public sealed class Store : IStore public sealed class Store : IStore
{ {
private readonly Action invalidate; private readonly Action invalidate;
private readonly ISnapshotStore snapshotStore; private readonly IServiceProvider services;
private readonly IStreamNameResolver streamNameResolver; private readonly IStreamNameResolver streamNameResolver;
private readonly IEventStore eventStore; private readonly IEventStore eventStore;
private readonly IEventDataFormatter eventDataFormatter; private readonly IEventDataFormatter eventDataFormatter;
public Store( public Store(
Action invalidate,
IEventStore eventStore, IEventStore eventStore,
IEventDataFormatter eventDataFormatter, IEventDataFormatter eventDataFormatter,
ISnapshotStore snapshotStore, IServiceProvider services,
IStreamNameResolver streamNameResolver) IStreamNameResolver streamNameResolver,
Action invalidate = null)
{ {
this.eventStore = eventStore; this.eventStore = eventStore;
this.eventDataFormatter = eventDataFormatter; this.eventDataFormatter = eventDataFormatter;
this.invalidate = invalidate; this.invalidate = invalidate;
this.snapshotStore = snapshotStore; this.services = services;
this.streamNameResolver = streamNameResolver; this.streamNameResolver = streamNameResolver;
} }
public IPersistence<object> WithEventSourcing<TOwner>(string key, Func<Envelope<IEvent>, Task> applyEvent) public IPersistence<object> WithEventSourcing<TOwner>(string key, Func<Envelope<IEvent>, Task> applyEvent)
{ {
return new Persistence<TOwner, object>(key, invalidate, eventStore, eventDataFormatter, null, streamNameResolver, null, applyEvent); return CreatePersistence<TOwner, object>(key, null, applyEvent);
} }
public IPersistence<TState> WithSnapshots<TOwner, TState>(string key, Func<TState, Task> applySnapshot) public IPersistence<TState> WithSnapshots<TOwner, TState>(string key, Func<TState, Task> applySnapshot)
{ {
return new Persistence<TOwner, TState>(key, invalidate, null, null, snapshotStore, null, applySnapshot, null); return CreatePersistence<TOwner, TState>(key, applySnapshot, null);
} }
public IPersistence<TState> WithSnapshotsAndEventSourcing<TOwner, TState>(string key, Func<TState, Task> applySnapshot, Func<Envelope<IEvent>, Task> applyEvent) public IPersistence<TState> WithSnapshotsAndEventSourcing<TOwner, TState>(string key, Func<TState, Task> applySnapshot, Func<Envelope<IEvent>, Task> applyEvent)
{ {
return CreatePersistence<TOwner, TState>(key, applySnapshot, applyEvent);
}
private IPersistence<TState> CreatePersistence<TOwner, TState>(string key, Func<TState, Task> applySnapshot, Func<Envelope<IEvent>, Task> applyEvent)
{
Guard.NotNullOrEmpty(key, nameof(key));
var snapshotStore = (ISnapshotStore<TState>)services.GetService(typeof(ISnapshotStore<TState>));
return new Persistence<TOwner, TState>(key, invalidate, eventStore, eventDataFormatter, snapshotStore, streamNameResolver, applySnapshot, applyEvent); return new Persistence<TOwner, TState>(key, invalidate, eventStore, eventDataFormatter, snapshotStore, streamNameResolver, applySnapshot, applyEvent);
} }
} }

4
tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs

@ -26,7 +26,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
private readonly IAppPlansProvider appPlansProvider = A.Fake<IAppPlansProvider>(); private readonly IAppPlansProvider appPlansProvider = A.Fake<IAppPlansProvider>();
private readonly IAppPlanBillingManager appPlansBillingManager = A.Fake<IAppPlanBillingManager>(); private readonly IAppPlanBillingManager appPlansBillingManager = A.Fake<IAppPlanBillingManager>();
private readonly IUserResolver userResolver = A.Fake<IUserResolver>(); private readonly IUserResolver userResolver = A.Fake<IUserResolver>();
private readonly AppDomainObject app; private readonly AppDomainObject app = new AppDomainObject();
private readonly Language language = Language.DE; private readonly Language language = Language.DE;
private readonly string contributorId = Guid.NewGuid().ToString(); private readonly string contributorId = Guid.NewGuid().ToString();
private readonly string clientName = "client"; private readonly string clientName = "client";
@ -34,8 +34,6 @@ namespace Squidex.Domain.Apps.Entities.Apps
public AppCommandMiddlewareTests() public AppCommandMiddlewareTests()
{ {
app = new AppDomainObject();
A.CallTo(() => appProvider.GetAppAsync(AppName)) A.CallTo(() => appProvider.GetAppAsync(AppName))
.Returns((IAppEntity)null); .Returns((IAppEntity)null);

7
tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs

@ -24,12 +24,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
private readonly string clientId = "client"; private readonly string clientId = "client";
private readonly string clientNewName = "My Client"; private readonly string clientNewName = "My Client";
private readonly string planId = "premium"; private readonly string planId = "premium";
private readonly AppDomainObject sut; private readonly AppDomainObject sut = new AppDomainObject();
public AppDomainObjectTests()
{
sut = new AppDomainObject();
}
[Fact] [Fact]
public void Create_should_throw_exception_if_created() public void Create_should_throw_exception_if_created()

137
tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs

@ -0,0 +1,137 @@
// ==========================================================================
// AssetCommandMiddlewareTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.IO;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Tasks;
using Xunit;
namespace Squidex.Domain.Apps.Entities.Assets
{
public class AssetCommandMiddlewareTests : HandlerTestBase<AssetDomainObject>
{
private readonly IAssetThumbnailGenerator assetThumbnailGenerator = A.Fake<IAssetThumbnailGenerator>();
private readonly IAssetStore assetStore = A.Fake<IAssetStore>();
private readonly Guid assetId = Guid.NewGuid();
private readonly Stream stream = new MemoryStream();
private readonly ImageInfo image = new ImageInfo(2048, 2048);
private readonly AssetDomainObject asset = new AssetDomainObject();
private readonly AssetFile file;
private readonly AssetCommandMiddleware sut;
public AssetCommandMiddlewareTests()
{
file = new AssetFile("my-image.png", "image/png", 1024, () => stream);
sut = new AssetCommandMiddleware(Handler, assetStore, assetThumbnailGenerator);
}
[Fact]
public async Task Create_should_create_domain_object()
{
var context = CreateContextForCommand(new CreateAsset { AssetId = assetId, File = file });
SetupStore(0, context.ContextId);
SetupImageInfo();
await TestCreate(asset, async _ =>
{
await sut.HandleAsync(context);
});
Assert.Equal(assetId, context.Result<EntityCreatedResult<Guid>>().IdOrValue);
AssertAssetHasBeenUploaded(0, context.ContextId);
AssertAssetImageChecked();
}
[Fact]
public async Task Update_should_update_domain_object()
{
var context = CreateContextForCommand(new UpdateAsset { AssetId = assetId, File = file });
SetupStore(1, context.ContextId);
SetupImageInfo();
CreateAsset();
await TestUpdate(asset, async _ =>
{
await sut.HandleAsync(context);
});
AssertAssetHasBeenUploaded(1, context.ContextId);
AssertAssetImageChecked();
}
[Fact]
public async Task Rename_should_update_domain_object()
{
CreateAsset();
var context = CreateContextForCommand(new RenameAsset { AssetId = assetId, FileName = "my-new-image.png" });
await TestUpdate(asset, async _ =>
{
await sut.HandleAsync(context);
});
}
[Fact]
public async Task Delete_should_update_domain_object()
{
CreateAsset();
var command = CreateContextForCommand(new DeleteAsset { AssetId = assetId });
await TestUpdate(asset, async _ =>
{
await sut.HandleAsync(command);
});
}
private void CreateAsset()
{
asset.Create(CreateCommand(new CreateAsset { File = file }));
}
private void SetupImageInfo()
{
A.CallTo(() => assetThumbnailGenerator.GetImageInfoAsync(stream))
.Returns(image);
}
private void SetupStore(long version, Guid commitId)
{
A.CallTo(() => assetStore.UploadTemporaryAsync(commitId.ToString(), stream))
.Returns(TaskHelper.Done);
A.CallTo(() => assetStore.CopyTemporaryAsync(commitId.ToString(), assetId.ToString(), version, null))
.Returns(TaskHelper.Done);
A.CallTo(() => assetStore.DeleteTemporaryAsync(commitId.ToString()))
.Returns(TaskHelper.Done);
}
private void AssertAssetImageChecked()
{
A.CallTo(() => assetThumbnailGenerator.GetImageInfoAsync(stream)).MustHaveHappened();
}
private void AssertAssetHasBeenUploaded(long version, Guid commitId)
{
A.CallTo(() => assetStore.UploadTemporaryAsync(commitId.ToString(), stream)).MustHaveHappened();
A.CallTo(() => assetStore.CopyTemporaryAsync(commitId.ToString(), assetId.ToString(), version, null)).MustHaveHappened();
A.CallTo(() => assetStore.DeleteTemporaryAsync(commitId.ToString())).MustHaveHappened();
}
}
}

209
tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetDomainObjectTests.cs

@ -0,0 +1,209 @@
// ==========================================================================
// AssetDomainObjectTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.IO;
using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Domain.Apps.Events.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Assets;
using Xunit;
namespace Squidex.Domain.Apps.Entities.Assets
{
public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject>
{
private readonly ImageInfo image = new ImageInfo(2048, 2048);
private readonly Guid assetId = Guid.NewGuid();
private readonly AssetFile file = new AssetFile("my-image.png", "image/png", 1024, () => new MemoryStream());
private readonly AssetDomainObject sut = new AssetDomainObject();
[Fact]
public void Create_should_throw_exception_if_created()
{
CreateAsset();
Assert.Throws<DomainException>(() =>
{
sut.Create(CreateAssetCommand(new CreateAsset { File = file }));
});
}
[Fact]
public void Create_should_create_events()
{
sut.Create(CreateAssetCommand(new CreateAsset { File = file, ImageInfo = image }));
Assert.Equal(0, sut.State.FileVersion);
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateAssetEvent(new AssetCreated
{
IsImage = true,
FileName = file.FileName,
FileSize = file.FileSize,
FileVersion = 0,
MimeType = file.MimeType,
PixelWidth = image.PixelWidth,
PixelHeight = image.PixelHeight
})
);
}
[Fact]
public void Update_should_throw_exception_if_not_created()
{
Assert.Throws<DomainException>(() =>
{
sut.Update(CreateAssetCommand(new UpdateAsset { File = file }));
});
}
[Fact]
public void Update_should_throw_exception_if_asset_is_deleted()
{
CreateAsset();
DeleteAsset();
Assert.Throws<DomainException>(() =>
{
sut.Update(CreateAssetCommand(new UpdateAsset()));
});
}
[Fact]
public void Update_should_create_events()
{
CreateAsset();
sut.Update(CreateAssetCommand(new UpdateAsset { File = file, ImageInfo = image }));
Assert.Equal(1, sut.State.FileVersion);
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateAssetEvent(new AssetUpdated
{
IsImage = true,
FileSize = file.FileSize,
FileVersion = 1,
MimeType = file.MimeType,
PixelWidth = image.PixelWidth,
PixelHeight = image.PixelHeight
})
);
}
[Fact]
public void Rename_should_throw_exception_if_not_created()
{
Assert.Throws<DomainException>(() =>
{
sut.Rename(CreateAssetCommand(new RenameAsset { FileName = "new-file.png" }));
});
}
[Fact]
public void Rename_should_throw_exception_if_asset_is_deleted()
{
CreateAsset();
DeleteAsset();
Assert.Throws<DomainException>(() =>
{
sut.Update(CreateAssetCommand(new UpdateAsset()));
});
}
[Fact]
public void Rename_should_create_events()
{
CreateAsset();
sut.Rename(CreateAssetCommand(new RenameAsset { FileName = "my-new-image.png" }));
Assert.Equal("my-new-image.png", sut.State.FileName);
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateAssetEvent(new AssetRenamed { FileName = "my-new-image.png" })
);
}
[Fact]
public void Delete_should_throw_exception_if_not_created()
{
Assert.Throws<DomainException>(() =>
{
sut.Delete(CreateAssetCommand(new DeleteAsset()));
});
}
[Fact]
public void Delete_should_throw_exception_if_already_deleted()
{
CreateAsset();
DeleteAsset();
Assert.Throws<DomainException>(() =>
{
sut.Delete(CreateAssetCommand(new DeleteAsset()));
});
}
[Fact]
public void Delete_should_create_events_with_total_file_size()
{
CreateAsset();
UpdateAsset();
sut.Delete(CreateAssetCommand(new DeleteAsset()));
Assert.True(sut.State.IsDeleted);
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateAssetEvent(new AssetDeleted { DeletedSize = 2048 })
);
}
private void CreateAsset()
{
sut.Create(CreateAssetCommand(new CreateAsset { File = file }));
sut.ClearUncommittedEvents();
}
private void UpdateAsset()
{
sut.Update(CreateAssetCommand(new UpdateAsset { File = file }));
sut.ClearUncommittedEvents();
}
private void DeleteAsset()
{
sut.Delete(CreateAssetCommand(new DeleteAsset()));
sut.ClearUncommittedEvents();
}
protected T CreateAssetEvent<T>(T @event) where T : AssetEvent
{
@event.AssetId = assetId;
return CreateEvent(@event);
}
protected T CreateAssetCommand<T>(T command) where T : AssetAggregateCommand
{
command.AssetId = assetId;
return CreateCommand(command);
}
}
}

65
tests/Squidex.Domain.Apps.Entities.Tests/Assets/Guards/GuardAssetTests.cs

@ -0,0 +1,65 @@
// ==========================================================================
// GuardAssetTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Infrastructure;
using Xunit;
namespace Squidex.Domain.Apps.Entities.Assets.Guards
{
public class GuardAssetTests
{
[Fact]
public void CanRename_should_throw_exception_if_name_not_defined()
{
var command = new RenameAsset();
Assert.Throws<ValidationException>(() => GuardAsset.CanRename(command, "asset-name"));
}
[Fact]
public void CanRename_should_throw_exception_if_name_are_the_same()
{
var command = new RenameAsset { FileName = "asset-name" };
Assert.Throws<ValidationException>(() => GuardAsset.CanRename(command, "asset-name"));
}
[Fact]
public void CanRename_not_should_throw_exception_if_name_are_different()
{
var command = new RenameAsset { FileName = "new-name" };
GuardAsset.CanRename(command, "asset-name");
}
[Fact]
public void CanCreate_should_not_throw_exception()
{
var command = new CreateAsset();
GuardAsset.CanCreate(command);
}
[Fact]
public void CanUpdate_should_not_throw_exception()
{
var command = new UpdateAsset();
GuardAsset.CanUpdate(command);
}
[Fact]
public void CanDelete_should_not_throw_exception()
{
var command = new DeleteAsset();
GuardAsset.CanDelete(command);
}
}
}

6
tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleCommandMiddlewareTests.cs

@ -23,19 +23,17 @@ namespace Squidex.Domain.Apps.Entities.Rules
public class RuleCommandMiddlewareTests : HandlerTestBase<RuleDomainObject> public class RuleCommandMiddlewareTests : HandlerTestBase<RuleDomainObject>
{ {
private readonly IAppProvider appProvider = A.Fake<IAppProvider>(); private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly RuleCommandMiddleware sut; private readonly RuleDomainObject rule = new RuleDomainObject();
private readonly RuleDomainObject rule;
private readonly RuleTrigger ruleTrigger = new ContentChangedTrigger(); private readonly RuleTrigger ruleTrigger = new ContentChangedTrigger();
private readonly RuleAction ruleAction = new WebhookAction { Url = new Uri("https://squidex.io") }; private readonly RuleAction ruleAction = new WebhookAction { Url = new Uri("https://squidex.io") };
private readonly Guid ruleId = Guid.NewGuid(); private readonly Guid ruleId = Guid.NewGuid();
private readonly RuleCommandMiddleware sut;
public RuleCommandMiddlewareTests() public RuleCommandMiddlewareTests()
{ {
A.CallTo(() => appProvider.GetSchemaAsync(A<string>.Ignored, A<Guid>.Ignored, false)) A.CallTo(() => appProvider.GetSchemaAsync(A<string>.Ignored, A<Guid>.Ignored, false))
.Returns(A.Fake<ISchemaEntity>()); .Returns(A.Fake<ISchemaEntity>());
rule = new RuleDomainObject();
sut = new RuleCommandMiddleware(Handler, appProvider); sut = new RuleCommandMiddleware(Handler, appProvider);
} }

14
tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDomainObjectTests.cs

@ -21,16 +21,10 @@ namespace Squidex.Domain.Apps.Entities.Rules
{ {
public class RuleDomainObjectTests : HandlerTestBase<RuleDomainObject> public class RuleDomainObjectTests : HandlerTestBase<RuleDomainObject>
{ {
private readonly Guid ruleId = Guid.NewGuid();
private readonly RuleTrigger ruleTrigger = new ContentChangedTrigger(); private readonly RuleTrigger ruleTrigger = new ContentChangedTrigger();
private readonly RuleAction ruleAction = new WebhookAction { Url = new Uri("https://squidex.io") }; private readonly RuleAction ruleAction = new WebhookAction { Url = new Uri("https://squidex.io") };
private readonly RuleDomainObject sut; private readonly RuleDomainObject sut = new RuleDomainObject();
public Guid RuleId { get; } = Guid.NewGuid();
public RuleDomainObjectTests()
{
sut = new RuleDomainObject();
}
[Fact] [Fact]
public void Create_should_throw_exception_if_created() public void Create_should_throw_exception_if_created()
@ -234,14 +228,14 @@ namespace Squidex.Domain.Apps.Entities.Rules
protected T CreateRuleEvent<T>(T @event) where T : RuleEvent protected T CreateRuleEvent<T>(T @event) where T : RuleEvent
{ {
@event.RuleId = RuleId; @event.RuleId = ruleId;
return CreateEvent(@event); return CreateEvent(@event);
} }
protected T CreateRuleCommand<T>(T command) where T : RuleAggregateCommand protected T CreateRuleCommand<T>(T command) where T : RuleAggregateCommand
{ {
command.RuleId = RuleId; command.RuleId = ruleId;
return CreateCommand(command); return CreateCommand(command);
} }

Loading…
Cancel
Save