Browse Source

A lot of small fixes.

pull/206/head
Sebastian Stehle 8 years ago
parent
commit
f4f5c357be
  1. 5
      src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository.cs
  2. 5
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs
  3. 4
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  4. 5
      src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository.cs
  5. 7
      src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository.cs
  6. 18
      src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs
  7. 6
      src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs
  8. 2
      src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs
  9. 3
      src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs
  10. 2
      src/Squidex.Domain.Apps.Entities/DomainObjectState.cs
  11. 11
      src/Squidex.Domain.Apps.Entities/EntityMapper.cs
  12. 10
      src/Squidex.Domain.Apps.Entities/IUpdateableEntityWithVersion.cs
  13. 8
      src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs
  14. 28
      src/Squidex.Domain.Apps.Entities/Schemas/SchemaCommandMiddleware.cs
  15. 2
      src/Squidex.Domain.Apps.Entities/SquidexCommand.cs
  16. 2
      src/Squidex.Infrastructure.GetEventStore/EventSourcing/Events/GetEventStore.cs
  17. 10
      src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore.cs
  18. 2
      src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStore.cs
  19. 18
      src/Squidex.Infrastructure/Commands/AggregateHandler.cs
  20. 41
      src/Squidex.Infrastructure/Commands/DomainObjectBase.cs
  21. 2
      src/Squidex.Infrastructure/Commands/ICommand.cs
  22. 15
      src/Squidex.Infrastructure/Commands/IDomainState.cs
  23. 19
      src/Squidex.Infrastructure/EtagVersion.cs
  24. 2
      src/Squidex.Infrastructure/EventSourcing/CommonHeaders.cs
  25. 14
      src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs
  26. 2
      src/Squidex.Infrastructure/States/IPersistence.cs
  27. 25
      src/Squidex.Infrastructure/States/Persistence.cs
  28. 3
      src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs
  29. 5
      src/Squidex/Pipeline/CommandMiddlewares/ETagCommandMiddleware.cs
  30. 6
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQLTests.cs
  31. 51
      tests/Squidex.Infrastructure.Tests/Commands/AggregateHandlerTests.cs
  32. 2
      tests/Squidex.Infrastructure.Tests/Commands/CommandContextTests.cs
  33. 10
      tests/Squidex.Infrastructure.Tests/Commands/DomainObjectBaseTests.cs
  34. 2
      tests/Squidex.Infrastructure.Tests/Commands/EnrichWithTimestampCommandMiddlewareTests.cs
  35. 5
      tests/Squidex.Infrastructure.Tests/EventSourcing/CompoundEventConsumerTests.cs
  36. 13
      tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeExtensionsTests.cs
  37. 6
      tests/Squidex.Infrastructure.Tests/EventSourcing/EventDataFormatterTests.cs
  38. 7
      tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs
  39. 14
      tests/Squidex.Infrastructure.Tests/PropertiesBagTests.cs
  40. 25
      tests/Squidex.Infrastructure.Tests/States/StateEventSourcingTests.cs
  41. 20
      tests/Squidex.Infrastructure.Tests/States/StateSnapshotTests.cs
  42. 5
      tests/Squidex.Infrastructure.Tests/TestHelpers/MyCommand.cs
  43. 6
      tests/Squidex.Infrastructure.Tests/TestHelpers/MyDomainObject.cs
  44. 17
      tests/Squidex.Infrastructure.Tests/TestHelpers/MyDomainState.cs
  45. 3
      tests/Squidex.Infrastructure.Tests/TestHelpers/MyEvent.cs

5
src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository.cs

@ -13,6 +13,7 @@ using System.Threading.Tasks;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Apps.Repositories; using Squidex.Domain.Apps.Entities.Apps.Repositories;
using Squidex.Domain.Apps.Entities.Apps.State; using Squidex.Domain.Apps.Entities.Apps.State;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
@ -74,15 +75,13 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Apps
return (existing.State, existing.Version); return (existing.State, existing.Version);
} }
return (null, -1); return (null, EtagVersion.NotFound);
} }
public async Task WriteAsync(string key, AppState value, long oldVersion, long newVersion) public async Task WriteAsync(string key, AppState value, long oldVersion, long newVersion)
{ {
try try
{ {
value.Version = newVersion;
await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion, await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion,
Update Update
.Set(x => x.UserIds, value.Contributors.Keys.ToArray()) .Set(x => x.UserIds, value.Contributors.Keys.ToArray())

5
src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs

@ -15,6 +15,7 @@ using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.Assets.State; using Squidex.Domain.Apps.Entities.Assets.State;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
@ -53,7 +54,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
return (existing.State, existing.Version); return (existing.State, existing.Version);
} }
return (null, -1); return (null, EtagVersion.NotFound);
} }
public async Task<IReadOnlyList<IAssetEntity>> QueryAsync(Guid appId, HashSet<string> mimeTypes = null, HashSet<Guid> ids = null, string query = null, int take = 10, int skip = 0) public async Task<IReadOnlyList<IAssetEntity>> QueryAsync(Guid appId, HashSet<string> mimeTypes = null, HashSet<Guid> ids = null, string query = null, int take = 10, int skip = 0)
@ -116,8 +117,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{ {
try try
{ {
value.Version = newVersion;
await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion, await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion,
Update Update
.Set(x => x.State, value) .Set(x => x.State, value)

4
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs

@ -89,8 +89,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
try try
{ {
value.Version = newVersion;
await Collection.InsertOneAsync(document); await Collection.InsertOneAsync(document);
} }
catch (MongoWriteException ex) catch (MongoWriteException ex)
@ -126,7 +124,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
return (SimpleMapper.Map(existing, new ContentState()), existing.Version); return (SimpleMapper.Map(existing, new ContentState()), existing.Version);
} }
return (null, -1); return (null, EtagVersion.NotFound);
} }
public async Task<IReadOnlyList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery) public async Task<IReadOnlyList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery)

5
src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository.cs

@ -13,6 +13,7 @@ using System.Threading.Tasks;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Rules.Repositories; using Squidex.Domain.Apps.Entities.Rules.Repositories;
using Squidex.Domain.Apps.Entities.Rules.State; using Squidex.Domain.Apps.Entities.Rules.State;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
@ -47,7 +48,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
return (existing.State, existing.Version); return (existing.State, existing.Version);
} }
return (null, -1); return (null, EtagVersion.NotFound);
} }
public async Task<IReadOnlyList<Guid>> QueryRuleIdsAsync(Guid appId) public async Task<IReadOnlyList<Guid>> QueryRuleIdsAsync(Guid appId)
@ -63,8 +64,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
{ {
try try
{ {
value.Version = newVersion;
await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion, await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion,
Update Update
.Set(x => x.State, value) .Set(x => x.State, value)

7
src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository.cs

@ -13,6 +13,7 @@ using System.Threading.Tasks;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Schemas.Repositories; using Squidex.Domain.Apps.Entities.Schemas.Repositories;
using Squidex.Domain.Apps.Entities.Schemas.State; using Squidex.Domain.Apps.Entities.Schemas.State;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
@ -47,7 +48,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas
return (existing.State, existing.Version); return (existing.State, existing.Version);
} }
return (null, -1); return (null, EtagVersion.NotFound);
} }
public async Task<Guid> FindSchemaIdAsync(Guid appId, string name) public async Task<Guid> FindSchemaIdAsync(Guid appId, string name)
@ -62,7 +63,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas
public async Task<IReadOnlyList<Guid>> QuerySchemaIdsAsync(Guid appId) public async Task<IReadOnlyList<Guid>> QuerySchemaIdsAsync(Guid appId)
{ {
var schemaEntities = var schemaEntities =
await Collection.Find(x => x.State.AppId == appId).Only(x => x.Id) await Collection.Find(x => x.AppId == appId).Only(x => x.Id)
.ToListAsync(); .ToListAsync();
return schemaEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList(); return schemaEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList();
@ -72,8 +73,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas
{ {
try try
{ {
value.Version = newVersion;
await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion, await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion,
Update Update
.Set(x => x.State, value) .Set(x => x.State, value)

18
src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs

@ -60,7 +60,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
protected Task On(AssignContributor command, CommandContext context) protected Task On(AssignContributor command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<AppDomainObject>(context, async a => return handler.UpdateSyncedAsync<AppDomainObject>(context, async a =>
{ {
await GuardAppContributors.CanAssign(a.State.Contributors, command, userResolver, appPlansProvider.GetPlan(a.State.Plan?.PlanId)); await GuardAppContributors.CanAssign(a.State.Contributors, command, userResolver, appPlansProvider.GetPlan(a.State.Plan?.PlanId));
@ -70,7 +70,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
protected Task On(RemoveContributor command, CommandContext context) protected Task On(RemoveContributor command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<AppDomainObject>(context, a => return handler.UpdateSyncedAsync<AppDomainObject>(context, a =>
{ {
GuardAppContributors.CanRemove(a.State.Contributors, command); GuardAppContributors.CanRemove(a.State.Contributors, command);
@ -80,7 +80,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
protected Task On(AttachClient command, CommandContext context) protected Task On(AttachClient command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<AppDomainObject>(context, a => return handler.UpdateSyncedAsync<AppDomainObject>(context, a =>
{ {
GuardAppClients.CanAttach(a.State.Clients, command); GuardAppClients.CanAttach(a.State.Clients, command);
@ -90,7 +90,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
protected Task On(UpdateClient command, CommandContext context) protected Task On(UpdateClient command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<AppDomainObject>(context, a => return handler.UpdateSyncedAsync<AppDomainObject>(context, a =>
{ {
GuardAppClients.CanUpdate(a.State.Clients, command); GuardAppClients.CanUpdate(a.State.Clients, command);
@ -100,7 +100,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
protected Task On(RevokeClient command, CommandContext context) protected Task On(RevokeClient command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<AppDomainObject>(context, a => return handler.UpdateSyncedAsync<AppDomainObject>(context, a =>
{ {
GuardAppClients.CanRevoke(a.State.Clients, command); GuardAppClients.CanRevoke(a.State.Clients, command);
@ -110,7 +110,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
protected Task On(AddLanguage command, CommandContext context) protected Task On(AddLanguage command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<AppDomainObject>(context, a => return handler.UpdateSyncedAsync<AppDomainObject>(context, a =>
{ {
GuardAppLanguages.CanAdd(a.State.LanguagesConfig, command); GuardAppLanguages.CanAdd(a.State.LanguagesConfig, command);
@ -120,7 +120,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
protected Task On(RemoveLanguage command, CommandContext context) protected Task On(RemoveLanguage command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<AppDomainObject>(context, a => return handler.UpdateSyncedAsync<AppDomainObject>(context, a =>
{ {
GuardAppLanguages.CanRemove(a.State.LanguagesConfig, command); GuardAppLanguages.CanRemove(a.State.LanguagesConfig, command);
@ -130,7 +130,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
protected Task On(UpdateLanguage command, CommandContext context) protected Task On(UpdateLanguage command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<AppDomainObject>(context, a => return handler.UpdateSyncedAsync<AppDomainObject>(context, a =>
{ {
GuardAppLanguages.CanUpdate(a.State.LanguagesConfig, command); GuardAppLanguages.CanUpdate(a.State.LanguagesConfig, command);
@ -140,7 +140,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
protected Task On(ChangePlan command, CommandContext context) protected Task On(ChangePlan command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<AppDomainObject>(context, async a => return handler.UpdateSyncedAsync<AppDomainObject>(context, async a =>
{ {
GuardApp.CanChangePlan(command, a.State.Plan, appPlansProvider); GuardApp.CanChangePlan(command, a.State.Plan, appPlansProvider);

6
src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs

@ -67,7 +67,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
try try
{ {
var asset = await handler.handler.UpdateSyncedAsync<AssetDomainObject>(context, async a => var asset = await handler.UpdateSyncedAsync<AssetDomainObject>(context, async a =>
{ {
GuardAsset.CanUpdate(command); GuardAsset.CanUpdate(command);
@ -88,7 +88,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
protected Task On(RenameAsset command, CommandContext context) protected Task On(RenameAsset command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<AssetDomainObject>(context, a => return handler.UpdateSyncedAsync<AssetDomainObject>(context, a =>
{ {
GuardAsset.CanRename(command, a.State.FileName); GuardAsset.CanRename(command, a.State.FileName);
@ -98,7 +98,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
protected Task On(DeleteAsset command, CommandContext context) protected Task On(DeleteAsset command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<AssetDomainObject>(context, a => return handler.UpdateSyncedAsync<AssetDomainObject>(context, a =>
{ {
GuardAsset.CanDelete(command); GuardAsset.CanDelete(command);

2
src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs

@ -61,7 +61,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var schema = await FindSchemaAsync(app, schemaIdOrName); var schema = await FindSchemaAsync(app, schemaIdOrName);
var content = var content =
version > 0 ? version > EtagVersion.Empty ?
await contentRepository.FindContentAsync(app, schema, id, version) : await contentRepository.FindContentAsync(app, schema, id, version) :
await contentRepository.FindContentAsync(app, schema, id); await contentRepository.FindContentAsync(app, schema, id);

3
src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs

@ -12,6 +12,7 @@ using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents namespace Squidex.Domain.Apps.Entities.Contents
{ {
@ -21,7 +22,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
Task<(ISchemaEntity Schema, long Total, IReadOnlyList<IContentEntity> Items)> QueryWithCountAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, string query); Task<(ISchemaEntity Schema, long Total, IReadOnlyList<IContentEntity> Items)> QueryWithCountAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, string query);
Task<(ISchemaEntity Schema, IContentEntity Content)> FindContentAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, Guid id, long version = -1); Task<(ISchemaEntity Schema, IContentEntity Content)> FindContentAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, Guid id, long version = EtagVersion.Any);
Task<ISchemaEntity> FindSchemaAsync(IAppEntity app, string schemaIdOrName); Task<ISchemaEntity> FindSchemaAsync(IAppEntity app, string schemaIdOrName);
} }

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

@ -10,10 +10,12 @@ using System;
using Newtonsoft.Json; using Newtonsoft.Json;
using NodaTime; using NodaTime;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities namespace Squidex.Domain.Apps.Entities
{ {
public abstract class DomainObjectState<T> : Cloneable<T>, public abstract class DomainObjectState<T> : Cloneable<T>,
IDomainState,
IEntity, IEntity,
IEntityWithCreatedBy, IEntityWithCreatedBy,
IEntityWithLastModifiedBy, IEntityWithLastModifiedBy,

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

@ -23,6 +23,7 @@ namespace Squidex.Domain.Apps.Entities
SetCreatedBy(entity, @event); SetCreatedBy(entity, @event);
SetLastModified(entity, headers); SetLastModified(entity, headers);
SetLastModifiedBy(entity, @event); SetLastModifiedBy(entity, @event);
SetVersion(entity, headers);
updater?.Invoke(entity); updater?.Invoke(entity);
@ -31,12 +32,20 @@ namespace Squidex.Domain.Apps.Entities
private static void SetId(IEntity entity, EnvelopeHeaders headers) private static void SetId(IEntity entity, EnvelopeHeaders headers)
{ {
if (entity is IUpdateableEntity updateable) if (entity is IUpdateableEntity updateable && updateable.Id == Guid.Empty)
{ {
updateable.Id = headers.AggregateId(); updateable.Id = headers.AggregateId();
} }
} }
private static void SetVersion(IEntity entity, EnvelopeHeaders headers)
{
if (entity is IUpdateableEntityWithVersion updateable)
{
updateable.Version = headers.EventStreamNumber();
}
}
private static void SetCreated(IEntity entity, EnvelopeHeaders headers) private static void SetCreated(IEntity entity, EnvelopeHeaders headers)
{ {
if (entity is IUpdateableEntity updateable && updateable.Created == default(Instant)) if (entity is IUpdateableEntity updateable && updateable.Created == default(Instant))

10
src/Squidex.Infrastructure/EventSourcing/ExpectedVersion.cs → src/Squidex.Domain.Apps.Entities/IUpdateableEntityWithVersion.cs

@ -1,17 +1,15 @@
// ========================================================================== // ==========================================================================
// ExpectedVersion.cs // IUpdateableEntityWithVersion.cs
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
namespace Squidex.Infrastructure.EventSourcing namespace Squidex.Domain.Apps.Entities
{ {
public static class ExpectedVersion public interface IUpdateableEntityWithVersion
{ {
public const int Any = -2; long Version { get; set; }
public const int Empty = -1;
} }
} }

8
src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs

@ -43,7 +43,7 @@ namespace Squidex.Domain.Apps.Entities.Rules
protected Task On(UpdateRule command, CommandContext context) protected Task On(UpdateRule command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<RuleDomainObject>(context, async c => return handler.UpdateSyncedAsync<RuleDomainObject>(context, async c =>
{ {
await GuardRule.CanUpdate(command, appProvider); await GuardRule.CanUpdate(command, appProvider);
@ -53,7 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Rules
protected Task On(EnableRule command, CommandContext context) protected Task On(EnableRule command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<RuleDomainObject>(context, r => return handler.UpdateSyncedAsync<RuleDomainObject>(context, r =>
{ {
GuardRule.CanEnable(command, r.State.RuleDef); GuardRule.CanEnable(command, r.State.RuleDef);
@ -63,7 +63,7 @@ namespace Squidex.Domain.Apps.Entities.Rules
protected Task On(DisableRule command, CommandContext context) protected Task On(DisableRule command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<RuleDomainObject>(context, r => return handler.UpdateSyncedAsync<RuleDomainObject>(context, r =>
{ {
GuardRule.CanDisable(command, r.State.RuleDef); GuardRule.CanDisable(command, r.State.RuleDef);
@ -73,7 +73,7 @@ namespace Squidex.Domain.Apps.Entities.Rules
protected Task On(DeleteRule command, CommandContext context) protected Task On(DeleteRule command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<RuleDomainObject>(context, c => return handler.UpdateSyncedAsync<RuleDomainObject>(context, c =>
{ {
GuardRule.CanDelete(command); GuardRule.CanDelete(command);

28
src/Squidex.Domain.Apps.Entities/Schemas/SchemaCommandMiddleware.cs

@ -47,7 +47,7 @@ namespace Squidex.Domain.Apps.Entities.State.SchemaDefs
protected Task On(AddField command, CommandContext context) protected Task On(AddField command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<SchemaDomainObject>(context, s => return handler.UpdateSyncedAsync<SchemaDomainObject>(context, s =>
{ {
GuardSchemaField.CanAdd(s.State.SchemaDef, command); GuardSchemaField.CanAdd(s.State.SchemaDef, command);
@ -59,7 +59,7 @@ namespace Squidex.Domain.Apps.Entities.State.SchemaDefs
protected Task On(DeleteField command, CommandContext context) protected Task On(DeleteField command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<SchemaDomainObject>(context, s => return handler.UpdateSyncedAsync<SchemaDomainObject>(context, s =>
{ {
GuardSchemaField.CanDelete(s.State.SchemaDef, command); GuardSchemaField.CanDelete(s.State.SchemaDef, command);
@ -69,7 +69,7 @@ namespace Squidex.Domain.Apps.Entities.State.SchemaDefs
protected Task On(LockField command, CommandContext context) protected Task On(LockField command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<SchemaDomainObject>(context, s => return handler.UpdateSyncedAsync<SchemaDomainObject>(context, s =>
{ {
GuardSchemaField.CanLock(s.State.SchemaDef, command); GuardSchemaField.CanLock(s.State.SchemaDef, command);
@ -79,7 +79,7 @@ namespace Squidex.Domain.Apps.Entities.State.SchemaDefs
protected Task On(HideField command, CommandContext context) protected Task On(HideField command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<SchemaDomainObject>(context, s => return handler.UpdateSyncedAsync<SchemaDomainObject>(context, s =>
{ {
GuardSchemaField.CanHide(s.State.SchemaDef, command); GuardSchemaField.CanHide(s.State.SchemaDef, command);
@ -89,7 +89,7 @@ namespace Squidex.Domain.Apps.Entities.State.SchemaDefs
protected Task On(ShowField command, CommandContext context) protected Task On(ShowField command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<SchemaDomainObject>(context, s => return handler.UpdateSyncedAsync<SchemaDomainObject>(context, s =>
{ {
GuardSchemaField.CanShow(s.State.SchemaDef, command); GuardSchemaField.CanShow(s.State.SchemaDef, command);
@ -99,7 +99,7 @@ namespace Squidex.Domain.Apps.Entities.State.SchemaDefs
protected Task On(DisableField command, CommandContext context) protected Task On(DisableField command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<SchemaDomainObject>(context, s => return handler.UpdateSyncedAsync<SchemaDomainObject>(context, s =>
{ {
GuardSchemaField.CanDisable(s.State.SchemaDef, command); GuardSchemaField.CanDisable(s.State.SchemaDef, command);
@ -109,7 +109,7 @@ namespace Squidex.Domain.Apps.Entities.State.SchemaDefs
protected Task On(EnableField command, CommandContext context) protected Task On(EnableField command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<SchemaDomainObject>(context, s => return handler.UpdateSyncedAsync<SchemaDomainObject>(context, s =>
{ {
GuardSchemaField.CanEnable(s.State.SchemaDef, command); GuardSchemaField.CanEnable(s.State.SchemaDef, command);
@ -119,7 +119,7 @@ namespace Squidex.Domain.Apps.Entities.State.SchemaDefs
protected Task On(UpdateField command, CommandContext context) protected Task On(UpdateField command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<SchemaDomainObject>(context, s => return handler.UpdateSyncedAsync<SchemaDomainObject>(context, s =>
{ {
GuardSchemaField.CanUpdate(s.State.SchemaDef, command); GuardSchemaField.CanUpdate(s.State.SchemaDef, command);
@ -129,7 +129,7 @@ namespace Squidex.Domain.Apps.Entities.State.SchemaDefs
protected Task On(ReorderFields command, CommandContext context) protected Task On(ReorderFields command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<SchemaDomainObject>(context, s => return handler.UpdateSyncedAsync<SchemaDomainObject>(context, s =>
{ {
GuardSchema.CanReorder(s.State.SchemaDef, command); GuardSchema.CanReorder(s.State.SchemaDef, command);
@ -139,7 +139,7 @@ namespace Squidex.Domain.Apps.Entities.State.SchemaDefs
protected Task On(UpdateSchema command, CommandContext context) protected Task On(UpdateSchema command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<SchemaDomainObject>(context, s => return handler.UpdateSyncedAsync<SchemaDomainObject>(context, s =>
{ {
GuardSchema.CanUpdate(s.State.SchemaDef, command); GuardSchema.CanUpdate(s.State.SchemaDef, command);
@ -149,7 +149,7 @@ namespace Squidex.Domain.Apps.Entities.State.SchemaDefs
protected Task On(PublishSchema command, CommandContext context) protected Task On(PublishSchema command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<SchemaDomainObject>(context, s => return handler.UpdateSyncedAsync<SchemaDomainObject>(context, s =>
{ {
GuardSchema.CanPublish(s.State.SchemaDef, command); GuardSchema.CanPublish(s.State.SchemaDef, command);
@ -159,7 +159,7 @@ namespace Squidex.Domain.Apps.Entities.State.SchemaDefs
protected Task On(UnpublishSchema command, CommandContext context) protected Task On(UnpublishSchema command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<SchemaDomainObject>(context, s => return handler.UpdateSyncedAsync<SchemaDomainObject>(context, s =>
{ {
GuardSchema.CanUnpublish(s.State.SchemaDef, command); GuardSchema.CanUnpublish(s.State.SchemaDef, command);
@ -169,7 +169,7 @@ namespace Squidex.Domain.Apps.Entities.State.SchemaDefs
protected Task On(ConfigureScripts command, CommandContext context) protected Task On(ConfigureScripts command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<SchemaDomainObject>(context, s => return handler.UpdateSyncedAsync<SchemaDomainObject>(context, s =>
{ {
GuardSchema.CanConfigureScripts(s.State.SchemaDef, command); GuardSchema.CanConfigureScripts(s.State.SchemaDef, command);
@ -179,7 +179,7 @@ namespace Squidex.Domain.Apps.Entities.State.SchemaDefs
protected Task On(DeleteSchema command, CommandContext context) protected Task On(DeleteSchema command, CommandContext context)
{ {
return handler.handler.UpdateSyncedAsync<SchemaDomainObject>(context, s => return handler.UpdateSyncedAsync<SchemaDomainObject>(context, s =>
{ {
GuardSchema.CanDelete(s.State.SchemaDef, command); GuardSchema.CanDelete(s.State.SchemaDef, command);

2
src/Squidex.Domain.Apps.Entities/SquidexCommand.cs

@ -15,6 +15,6 @@ namespace Squidex.Domain.Apps.Entities
{ {
public RefToken Actor { get; set; } public RefToken Actor { get; set; }
public long? ExpectedVersion { get; set; } public long ExpectedVersion { get; set; }
} }
} }

2
src/Squidex.Infrastructure.GetEventStore/EventSourcing/Events/GetEventStore.cs

@ -89,7 +89,7 @@ namespace Squidex.Infrastructure.EventSourcing
public Task AppendEventsAsync(Guid commitId, string streamName, ICollection<EventData> events) public Task AppendEventsAsync(Guid commitId, string streamName, ICollection<EventData> events)
{ {
return AppendEventsInternalAsync(streamName, ExpectedVersion.Any, events); return AppendEventsInternalAsync(streamName, EtagVersion.Any, events);
} }
public Task AppendEventsAsync(Guid commitId, string streamName, long expectedVersion, ICollection<EventData> events) public Task AppendEventsAsync(Guid commitId, string streamName, long expectedVersion, ICollection<EventData> events)

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

@ -129,12 +129,12 @@ namespace Squidex.Infrastructure.EventSourcing
public Task AppendEventsAsync(Guid commitId, string streamName, ICollection<EventData> events) public Task AppendEventsAsync(Guid commitId, string streamName, ICollection<EventData> events)
{ {
return AppendEventsInternalAsync(commitId, streamName, ExpectedVersion.Any, events); return AppendEventsInternalAsync(commitId, streamName, EtagVersion.Any, events);
} }
public Task AppendEventsAsync(Guid commitId, string streamName, long expectedVersion, ICollection<EventData> events) public Task AppendEventsAsync(Guid commitId, string streamName, long expectedVersion, ICollection<EventData> events)
{ {
Guard.GreaterEquals(expectedVersion, -1, nameof(expectedVersion)); Guard.GreaterEquals(expectedVersion, EtagVersion.Any, nameof(expectedVersion));
return AppendEventsInternalAsync(commitId, streamName, expectedVersion, events); return AppendEventsInternalAsync(commitId, streamName, expectedVersion, events);
} }
@ -151,7 +151,7 @@ namespace Squidex.Infrastructure.EventSourcing
var currentVersion = await GetEventStreamOffset(streamName); var currentVersion = await GetEventStreamOffset(streamName);
if (expectedVersion != ExpectedVersion.Any && expectedVersion != currentVersion) if (expectedVersion != EtagVersion.Any && expectedVersion != currentVersion)
{ {
throw new WrongEventVersionException(currentVersion, expectedVersion); throw new WrongEventVersionException(currentVersion, expectedVersion);
} }
@ -174,7 +174,7 @@ namespace Squidex.Infrastructure.EventSourcing
{ {
currentVersion = await GetEventStreamOffset(streamName); currentVersion = await GetEventStreamOffset(streamName);
if (expectedVersion != ExpectedVersion.Any) if (expectedVersion != EtagVersion.Any)
{ {
throw new WrongEventVersionException(currentVersion, expectedVersion); throw new WrongEventVersionException(currentVersion, expectedVersion);
} }
@ -210,7 +210,7 @@ namespace Squidex.Infrastructure.EventSourcing
return document[nameof(MongoEventCommit.EventStreamOffset)].ToInt64() + document[nameof(MongoEventCommit.EventsCount)].ToInt64(); return document[nameof(MongoEventCommit.EventStreamOffset)].ToInt64() + document[nameof(MongoEventCommit.EventsCount)].ToInt64();
} }
return -1; return EtagVersion.Empty;
} }
private static FilterDefinition<MongoEventCommit> CreateFilter(string streamFilter, StreamPosition streamPosition) private static FilterDefinition<MongoEventCommit> CreateFilter(string streamFilter, StreamPosition streamPosition)

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

@ -41,7 +41,7 @@ namespace Squidex.Infrastructure.States
return (existing.Doc, existing.Version); return (existing.Doc, existing.Version);
} }
return (default(T), -1); return (default(T), EtagVersion.NotFound);
} }
public async Task WriteAsync(string key, T value, long oldVersion, long newVersion) public async Task WriteAsync(string key, T value, long oldVersion, long newVersion)

18
src/Squidex.Infrastructure/Commands/AggregateHandler.cs

@ -65,10 +65,15 @@ namespace Squidex.Infrastructure.Commands
{ {
Guard.NotNull(context, nameof(context)); Guard.NotNull(context, nameof(context));
var domainObjectCommand = GetCommand(context); var domainCommand = GetCommand(context);
var domainObjectId = domainObjectCommand.AggregateId; var domainObjectId = domainCommand.AggregateId;
var domainObject = await stateFactory.CreateAsync<T>(domainObjectId.ToString()); var domainObject = await stateFactory.CreateAsync<T>(domainObjectId.ToString());
if (domainCommand.ExpectedVersion != EtagVersion.Any && domainCommand.ExpectedVersion != domainObject.Version)
{
throw new DomainObjectVersionException(domainObjectId.ToString(), typeof(T), domainObject.Version, domainCommand.ExpectedVersion);
}
await handler(domainObject); await handler(domainObject);
await domainObject.WriteAsync(log); await domainObject.WriteAsync(log);
@ -92,13 +97,18 @@ namespace Squidex.Infrastructure.Commands
{ {
Guard.NotNull(context, nameof(context)); Guard.NotNull(context, nameof(context));
var domainObjectCommand = GetCommand(context); var domainCommand = GetCommand(context);
var domainObjectId = domainObjectCommand.AggregateId; var domainObjectId = domainCommand.AggregateId;
using (await lockPool.LockAsync(Tuple.Create(typeof(T), domainObjectId))) using (await lockPool.LockAsync(Tuple.Create(typeof(T), domainObjectId)))
{ {
var domainObject = await stateFactory.GetSingleAsync<T>(domainObjectId.ToString()); var domainObject = await stateFactory.GetSingleAsync<T>(domainObjectId.ToString());
if (domainCommand.ExpectedVersion != EtagVersion.Any && domainCommand.ExpectedVersion != domainObject.Version)
{
throw new DomainObjectVersionException(domainObjectId.ToString(), typeof(T), domainObject.Version, domainCommand.ExpectedVersion);
}
await handler(domainObject); await handler(domainObject);
await domainObject.WriteAsync(log); await domainObject.WriteAsync(log);

41
src/Squidex.Infrastructure/Commands/DomainObjectBase.cs

@ -15,16 +15,16 @@ using Squidex.Infrastructure.States;
namespace Squidex.Infrastructure.Commands namespace Squidex.Infrastructure.Commands
{ {
public abstract class DomainObjectBase<TBase, TState> : IDomainObject where TState : new() public abstract class DomainObjectBase<TBase, TState> : IDomainObject where TState : IDomainState, new()
{ {
private readonly List<Envelope<IEvent>> uncomittedEvents = new List<Envelope<IEvent>>(); private readonly List<Envelope<IEvent>> uncomittedEvents = new List<Envelope<IEvent>>();
private Guid id; private Guid id;
private TState state = new TState(); private TState state;
private IPersistence<TState> persistence; private IPersistence<TState> persistence;
public long Version public long Version
{ {
get { return persistence?.Version ?? -1; } get { return state.Version; }
} }
public TState State public TState State
@ -32,6 +32,12 @@ namespace Squidex.Infrastructure.Commands
get { return state; } get { return state; }
} }
protected DomainObjectBase()
{
state = new TState();
state.Version = EtagVersion.Empty;
}
public IReadOnlyList<Envelope<IEvent>> GetUncomittedEvents() public IReadOnlyList<Envelope<IEvent>> GetUncomittedEvents()
{ {
return uncomittedEvents; return uncomittedEvents;
@ -78,19 +84,26 @@ namespace Squidex.Infrastructure.Commands
public async Task WriteAsync(ISemanticLog log) public async Task WriteAsync(ISemanticLog log)
{ {
await persistence.WriteSnapshotAsync(state); var events = uncomittedEvents;
try if (events.Count > 0)
{
await persistence.WriteEventsAsync(uncomittedEvents.ToArray());
}
catch (Exception ex)
{
log.LogFatal(ex, w => w.WriteProperty("action", "writeEvents"));
}
finally
{ {
uncomittedEvents.Clear(); state.Version += events.Count;
await persistence.WriteSnapshotAsync(state);
try
{
await persistence.WriteEventsAsync(uncomittedEvents.ToArray());
}
catch (Exception ex)
{
log.LogFatal(ex, w => w.WriteProperty("action", "writeEvents"));
}
finally
{
uncomittedEvents.Clear();
}
} }
} }
} }

2
src/Squidex.Infrastructure/Commands/ICommand.cs

@ -10,6 +10,6 @@ namespace Squidex.Infrastructure.Commands
{ {
public interface ICommand public interface ICommand
{ {
long? ExpectedVersion { get; set; } long ExpectedVersion { get; set; }
} }
} }

15
src/Squidex.Infrastructure/Commands/IDomainState.cs

@ -0,0 +1,15 @@
// ==========================================================================
// IDomainState.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Infrastructure.Commands
{
public interface IDomainState
{
long Version { get; set; }
}
}

19
src/Squidex.Infrastructure/EtagVersion.cs

@ -0,0 +1,19 @@
// ==========================================================================
// EtagVersion.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Infrastructure
{
public static class EtagVersion
{
public const long Any = -2;
public const long Empty = -1;
public const long NotFound = long.MinValue;
}
}

2
src/Squidex.Infrastructure/EventSourcing/CommonHeaders.cs

@ -20,6 +20,8 @@ namespace Squidex.Infrastructure.EventSourcing
public static readonly string EventStreamNumber = "EventStreamNumber"; public static readonly string EventStreamNumber = "EventStreamNumber";
public static readonly string SnapshotVersion = "SnapshotVersion";
public static readonly string Timestamp = "Timestamp"; public static readonly string Timestamp = "Timestamp";
public static readonly string Actor = "Actor"; public static readonly string Actor = "Actor";

14
src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs

@ -26,9 +26,21 @@ namespace Squidex.Infrastructure.EventSourcing
return envelope; return envelope;
} }
public static long SnapshotVersion(this EnvelopeHeaders headers)
{
return headers[CommonHeaders.SnapshotVersion].ToInt64(CultureInfo.InvariantCulture);
}
public static Envelope<T> SetSnapshotVersion<T>(this Envelope<T> envelope, long value) where T : class
{
envelope.Headers.Set(CommonHeaders.SnapshotVersion, value);
return envelope;
}
public static long EventStreamNumber(this EnvelopeHeaders headers) public static long EventStreamNumber(this EnvelopeHeaders headers)
{ {
return headers[CommonHeaders.EventStreamNumber].ToInt32(CultureInfo.InvariantCulture); return headers[CommonHeaders.EventStreamNumber].ToInt64(CultureInfo.InvariantCulture);
} }
public static Envelope<T> SetEventStreamNumber<T>(this Envelope<T> envelope, long value) where T : class public static Envelope<T> SetEventStreamNumber<T>(this Envelope<T> envelope, long value) where T : class

2
src/Squidex.Infrastructure/States/IPersistence.cs

@ -20,6 +20,6 @@ namespace Squidex.Infrastructure.States
Task WriteSnapshotAsync(TState state); Task WriteSnapshotAsync(TState state);
Task ReadAsync(long expectedVersion = ExpectedVersion.Any); Task ReadAsync(long expectedVersion = EtagVersion.Any);
} }
} }

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

@ -12,6 +12,8 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
#pragma warning disable RECS0012 // 'if' statement can be re-written as 'switch' statement
namespace Squidex.Infrastructure.States namespace Squidex.Infrastructure.States
{ {
internal sealed class Persistence<TOwner, TState> : IPersistence<TState> internal sealed class Persistence<TOwner, TState> : IPersistence<TState>
@ -25,8 +27,8 @@ namespace Squidex.Infrastructure.States
private readonly Action invalidate; private readonly Action invalidate;
private readonly Func<TState, Task> applyState; private readonly Func<TState, Task> applyState;
private readonly Func<Envelope<IEvent>, Task> applyEvent; private readonly Func<Envelope<IEvent>, Task> applyEvent;
private long versionSnapshot = -1; private long versionSnapshot = EtagVersion.Empty;
private long versionEvents = -1; private long versionEvents = EtagVersion.Empty;
private long version; private long version;
public long Version public long Version
@ -55,19 +57,19 @@ namespace Squidex.Infrastructure.States
this.streamNameResolver = streamNameResolver; this.streamNameResolver = streamNameResolver;
} }
public async Task ReadAsync(long expectedVersion = ExpectedVersion.Any) public async Task ReadAsync(long expectedVersion = EtagVersion.Any)
{ {
versionSnapshot = -1; versionSnapshot = EtagVersion.Empty;
versionEvents = -1; versionEvents = EtagVersion.Empty;
await ReadSnapshotAsync(); await ReadSnapshotAsync();
await ReadEventsAsync(); await ReadEventsAsync();
UpdateVersion(); UpdateVersion();
if (expectedVersion != ExpectedVersion.Any && expectedVersion != version) if (expectedVersion != EtagVersion.Any && expectedVersion != version)
{ {
if (version == ExpectedVersion.Empty) if (version == EtagVersion.Empty)
{ {
throw new DomainObjectNotFoundException(ownerKey, typeof(TOwner)); throw new DomainObjectNotFoundException(ownerKey, typeof(TOwner));
} }
@ -84,6 +86,11 @@ namespace Squidex.Infrastructure.States
{ {
var (state, position) = await snapshotStore.ReadAsync(ownerKey); var (state, position) = await snapshotStore.ReadAsync(ownerKey);
if (position < EtagVersion.Empty)
{
position = EtagVersion.Empty;
}
versionSnapshot = position; versionSnapshot = position;
versionEvents = position; versionEvents = position;
@ -150,7 +157,7 @@ namespace Squidex.Infrastructure.States
if (eventArray.Length > 0) if (eventArray.Length > 0)
{ {
var expectedVersion = UseEventSourcing() ? version : ExpectedVersion.Any; var expectedVersion = UseEventSourcing() ? version : EtagVersion.Any;
var commitId = Guid.NewGuid(); var commitId = Guid.NewGuid();
@ -159,7 +166,7 @@ namespace Squidex.Infrastructure.States
try try
{ {
await eventStore.AppendEventsAsync(commitId, GetStreamName(), Version, eventData); await eventStore.AppendEventsAsync(commitId, GetStreamName(), expectedVersion, eventData);
} }
catch (WrongEventVersionException ex) catch (WrongEventVersionException ex)
{ {

3
src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs

@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using NSwag.Annotations; using NSwag.Annotations;
using Squidex.Areas.Api.Controllers.Apps.Models; using Squidex.Areas.Api.Controllers.Apps.Models;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
@ -81,7 +82,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
await CommandBus.PublishAsync(command); await CommandBus.PublishAsync(command);
var response = SimpleMapper.Map(command, new ClientDto { Name = command.Id }); var response = SimpleMapper.Map(command, new ClientDto { Name = command.Id, Permission = AppClientPermission.Editor });
return CreatedAtAction(nameof(GetClients), new { app }, response); return CreatedAtAction(nameof(GetClients), new { app }, response);
} }

5
src/Squidex/Pipeline/CommandMiddlewares/ETagCommandMiddleware.cs

@ -11,6 +11,7 @@ using System.Globalization;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
namespace Squidex.Pipeline.CommandMiddlewares namespace Squidex.Pipeline.CommandMiddlewares
@ -33,6 +34,10 @@ namespace Squidex.Pipeline.CommandMiddlewares
{ {
context.Command.ExpectedVersion = expectedVersion; context.Command.ExpectedVersion = expectedVersion;
} }
else
{
context.Command.ExpectedVersion = EtagVersion.Any;
}
await next(); await next();

6
tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQLTests.cs

@ -483,7 +483,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var refContents = new List<IContentEntity> { contentRef }; var refContents = new List<IContentEntity> { contentRef };
A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId, -1)) A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId, EtagVersion.Any))
.Returns((schema, content)); .Returns((schema, content));
A.CallTo(() => contentQuery.QueryWithCountAsync(app, schema.Id.ToString(), user, false, A<HashSet<Guid>>.That.Matches(x => x.Contains(contentRefId)))) A.CallTo(() => contentQuery.QueryWithCountAsync(app, schema.Id.ToString(), user, false, A<HashSet<Guid>>.That.Matches(x => x.Contains(contentRefId))))
@ -543,7 +543,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var refAssets = new List<IAssetEntity> { assetRef }; var refAssets = new List<IAssetEntity> { assetRef };
A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId, -1)) A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId, EtagVersion.Any))
.Returns((schema, content)); .Returns((schema, content));
A.CallTo(() => assetRepository.QueryAsync(app.Id, null, A<HashSet<Guid>>.That.Matches(x => x.Contains(assetRefId)), null, int.MaxValue, 0)) A.CallTo(() => assetRepository.QueryAsync(app.Id, null, A<HashSet<Guid>>.That.Matches(x => x.Contains(assetRefId)), null, int.MaxValue, 0))
@ -602,7 +602,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
}} }}
}}"; }}";
A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId, -1)) A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId, EtagVersion.Any))
.Returns((schema, content)); .Returns((schema, content));
var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query });

51
tests/Squidex.Infrastructure.Tests/Commands/AggregateHandlerTests.cs

@ -10,11 +10,11 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using Squidex.Infrastructure.Commands.TestHelpers;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Tasks;
using Squidex.Infrastructure.TestHelpers;
using Xunit; using Xunit;
namespace Squidex.Infrastructure.Commands namespace Squidex.Infrastructure.Commands
@ -29,17 +29,16 @@ namespace Squidex.Infrastructure.Commands
private readonly Envelope<IEvent> event1 = new Envelope<IEvent>(new MyEvent()); private readonly Envelope<IEvent> event1 = new Envelope<IEvent>(new MyEvent());
private readonly Envelope<IEvent> event2 = new Envelope<IEvent>(new MyEvent()); private readonly Envelope<IEvent> event2 = new Envelope<IEvent>(new MyEvent());
private readonly CommandContext context; private readonly CommandContext context;
private readonly CommandContext invalidContext = new CommandContext(A.Dummy<ICommand>());
private readonly Guid domainObjectId = Guid.NewGuid(); private readonly Guid domainObjectId = Guid.NewGuid();
private readonly MyCommand command;
private readonly MyDomainObject domainObject = new MyDomainObject(); private readonly MyDomainObject domainObject = new MyDomainObject();
private readonly AggregateHandler sut; private readonly AggregateHandler sut;
public sealed class MyEvent : IEvent
{
}
public AggregateHandlerTests() public AggregateHandlerTests()
{ {
context = new CommandContext(new MyCommand { AggregateId = domainObjectId }); command = new MyCommand { AggregateId = domainObjectId };
context = new CommandContext(command);
A.CallTo(() => store.WithSnapshots<MyDomainObject, object>(domainObjectId.ToString(), A<Func<object, Task>>.Ignored)) A.CallTo(() => store.WithSnapshots<MyDomainObject, object>(domainObjectId.ToString(), A<Func<object, Task>>.Ignored))
.Returns(persistence); .Returns(persistence);
@ -58,13 +57,29 @@ namespace Squidex.Infrastructure.Commands
[Fact] [Fact]
public Task Create_with_task_should_throw_exception_if_not_aggregate_command() public Task Create_with_task_should_throw_exception_if_not_aggregate_command()
{ {
return Assert.ThrowsAnyAsync<ArgumentException>(() => sut.CreateAsync<MyDomainObject>(new CommandContext(A.Dummy<ICommand>()), x => TaskHelper.False)); return Assert.ThrowsAnyAsync<ArgumentException>(() => sut.CreateAsync<MyDomainObject>(invalidContext, x => TaskHelper.False));
} }
[Fact] [Fact]
public Task Create_synced_with_task_should_throw_exception_if_not_aggregate_command() public Task Create_synced_with_task_should_throw_exception_if_not_aggregate_command()
{ {
return Assert.ThrowsAnyAsync<ArgumentException>(() => sut.CreateSyncedAsync<MyDomainObject>(new CommandContext(A.Dummy<ICommand>()), x => TaskHelper.False)); return Assert.ThrowsAnyAsync<ArgumentException>(() => sut.CreateSyncedAsync<MyDomainObject>(invalidContext, x => TaskHelper.False));
}
[Fact]
public Task Create_with_task_should_should_throw_exception_if_version_is_wrong()
{
command.ExpectedVersion = 2;
return Assert.ThrowsAnyAsync<DomainObjectVersionException>(() => sut.CreateAsync<MyDomainObject>(context, x => TaskHelper.False));
}
[Fact]
public Task Create_synced_with_task_should_should_throw_exception_if_version_is_wrong()
{
command.ExpectedVersion = 2;
return Assert.ThrowsAnyAsync<DomainObjectVersionException>(() => sut.CreateSyncedAsync<MyDomainObject>(context, x => TaskHelper.False));
} }
[Fact] [Fact]
@ -150,13 +165,29 @@ namespace Squidex.Infrastructure.Commands
[Fact] [Fact]
public Task Update_with_task_should_throw_exception_if_not_aggregate_command() public Task Update_with_task_should_throw_exception_if_not_aggregate_command()
{ {
return Assert.ThrowsAnyAsync<ArgumentException>(() => sut.UpdateAsync<MyDomainObject>(new CommandContext(A.Dummy<ICommand>()), x => TaskHelper.False)); return Assert.ThrowsAnyAsync<ArgumentException>(() => sut.UpdateAsync<MyDomainObject>(invalidContext, x => TaskHelper.False));
} }
[Fact] [Fact]
public Task Update_synced_with_task_should_throw_exception_if_not_aggregate_command() public Task Update_synced_with_task_should_throw_exception_if_not_aggregate_command()
{ {
return Assert.ThrowsAnyAsync<ArgumentException>(() => sut.UpdateSyncedAsync<MyDomainObject>(new CommandContext(A.Dummy<ICommand>()), x => TaskHelper.False)); return Assert.ThrowsAnyAsync<ArgumentException>(() => sut.UpdateSyncedAsync<MyDomainObject>(invalidContext, x => TaskHelper.False));
}
[Fact]
public Task Update_with_task_should_should_throw_exception_if_version_is_wrong()
{
command.ExpectedVersion = 2;
return Assert.ThrowsAnyAsync<DomainObjectVersionException>(() => sut.UpdateAsync<MyDomainObject>(context, x => TaskHelper.False));
}
[Fact]
public Task Update_synced_with_task_should_should_throw_exception_if_version_is_wrong()
{
command.ExpectedVersion = 2;
return Assert.ThrowsAnyAsync<DomainObjectVersionException>(() => sut.UpdateSyncedAsync<MyDomainObject>(context, x => TaskHelper.False));
} }
[Fact] [Fact]

2
tests/Squidex.Infrastructure.Tests/Commands/CommandContextTests.cs

@ -7,7 +7,7 @@
// ========================================================================== // ==========================================================================
using System; using System;
using Squidex.Infrastructure.Commands.TestHelpers; using Squidex.Infrastructure.TestHelpers;
using Xunit; using Xunit;
namespace Squidex.Infrastructure.Commands namespace Squidex.Infrastructure.Commands

10
tests/Squidex.Infrastructure.Tests/Commands/DomainObjectBaseTests.cs

@ -11,10 +11,10 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using Squidex.Infrastructure.Commands.TestHelpers;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
using Squidex.Infrastructure.TestHelpers;
using Xunit; using Xunit;
namespace Squidex.Infrastructure.Commands namespace Squidex.Infrastructure.Commands
@ -35,7 +35,7 @@ namespace Squidex.Infrastructure.Commands
[Fact] [Fact]
public void Should_instantiate() public void Should_instantiate()
{ {
Assert.Equal(-1, sut.Version); Assert.Equal(EtagVersion.NotFound, sut.Version);
} }
[Fact] [Fact]
@ -47,7 +47,7 @@ namespace Squidex.Infrastructure.Commands
sut.RaiseEvent(event1); sut.RaiseEvent(event1);
sut.RaiseEvent(event2); sut.RaiseEvent(event2);
Assert.Equal(-1, sut.Version); Assert.Equal(EtagVersion.NotFound, sut.Version);
Assert.Equal(new IEvent[] { event1, event2 }, sut.GetUncomittedEvents().Select(x => x.Payload).ToArray()); Assert.Equal(new IEvent[] { event1, event2 }, sut.GetUncomittedEvents().Select(x => x.Payload).ToArray());
sut.ClearUncommittedEvents(); sut.ClearUncommittedEvents();
@ -71,7 +71,7 @@ namespace Squidex.Infrastructure.Commands
sut.RaiseEvent(event1); sut.RaiseEvent(event1);
sut.RaiseEvent(event2); sut.RaiseEvent(event2);
var newState = "STATE"; var newState = new MyDomainState();
sut.UpdateState(newState); sut.UpdateState(newState);
@ -104,7 +104,7 @@ namespace Squidex.Infrastructure.Commands
sut.RaiseEvent(event1); sut.RaiseEvent(event1);
sut.RaiseEvent(event2); sut.RaiseEvent(event2);
var newState = "STATE"; var newState = new MyDomainState();
sut.UpdateState(newState); sut.UpdateState(newState);

2
tests/Squidex.Infrastructure.Tests/Commands/EnrichWithTimestampCommandMiddlewareTests.cs

@ -9,7 +9,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using NodaTime; using NodaTime;
using Squidex.Infrastructure.Commands.TestHelpers; using Squidex.Infrastructure.TestHelpers;
using Xunit; using Xunit;
namespace Squidex.Infrastructure.Commands namespace Squidex.Infrastructure.Commands

5
tests/Squidex.Infrastructure.Tests/EventSourcing/CompoundEventConsumerTests.cs

@ -9,6 +9,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Tasks;
using Squidex.Infrastructure.TestHelpers;
using Xunit; using Xunit;
namespace Squidex.Infrastructure.EventSourcing namespace Squidex.Infrastructure.EventSourcing
@ -18,10 +19,6 @@ namespace Squidex.Infrastructure.EventSourcing
private readonly IEventConsumer consumer1 = A.Fake<IEventConsumer>(); private readonly IEventConsumer consumer1 = A.Fake<IEventConsumer>();
private readonly IEventConsumer consumer2 = A.Fake<IEventConsumer>(); private readonly IEventConsumer consumer2 = A.Fake<IEventConsumer>();
private sealed class MyEvent : IEvent
{
}
[Fact] [Fact]
public void Should_return_given_name() public void Should_return_given_name()
{ {

13
tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeExtensionsTests.cs

@ -81,7 +81,18 @@ namespace Squidex.Infrastructure.EventSourcing
sut.SetEventStreamNumber(eventStreamNumber); sut.SetEventStreamNumber(eventStreamNumber);
Assert.Equal(eventStreamNumber, sut.Headers.EventStreamNumber()); Assert.Equal(eventStreamNumber, sut.Headers.EventStreamNumber());
Assert.Equal(eventStreamNumber, sut.Headers["EventStreamNumber"].ToInt32(culture)); Assert.Equal(eventStreamNumber, sut.Headers["EventStreamNumber"].ToInt64(culture));
}
[Fact]
public void Should_set_and_get_snapshot_version()
{
const int snapshotVersion = 123;
sut.SetSnapshotVersion(snapshotVersion);
Assert.Equal(snapshotVersion, sut.Headers.SnapshotVersion());
Assert.Equal(snapshotVersion, sut.Headers["SnapshotVersion"].ToInt64(culture));
} }
} }
} }

6
tests/Squidex.Infrastructure.Tests/EventSourcing/EventDataFormatterTests.cs

@ -11,17 +11,13 @@ using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using NodaTime; using NodaTime;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.TestHelpers;
using Xunit; using Xunit;
namespace Squidex.Infrastructure.EventSourcing namespace Squidex.Infrastructure.EventSourcing
{ {
public class EventDataFormatterTests public class EventDataFormatterTests
{ {
public sealed class MyEvent : IEvent
{
public string MyProperty { get; set; }
}
public sealed class MyOldEvent : IEvent, IMigratedEvent public sealed class MyOldEvent : IEvent, IMigratedEvent
{ {
public string MyProperty { get; set; } public string MyProperty { get; set; }

7
tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs

@ -12,16 +12,13 @@ using FakeItEasy;
using FluentAssertions; using FluentAssertions;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
using Squidex.Infrastructure.TestHelpers;
using Xunit; using Xunit;
namespace Squidex.Infrastructure.EventSourcing.Grains namespace Squidex.Infrastructure.EventSourcing.Grains
{ {
public class EventConsumerGrainTests public class EventConsumerGrainTests
{ {
public sealed class MyEvent : IEvent
{
}
public sealed class MyEventConsumerGrain : EventConsumerGrain public sealed class MyEventConsumerGrain : EventConsumerGrain
{ {
public MyEventConsumerGrain(IEventStore eventStore, IEventDataFormatter eventDataFormatter, ISemanticLog log) public MyEventConsumerGrain(IEventStore eventStore, IEventDataFormatter eventDataFormatter, ISemanticLog log)
@ -67,7 +64,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
A.CallTo(() => eventConsumer.Name) A.CallTo(() => eventConsumer.Name)
.Returns(consumerName); .Returns(consumerName);
A.CallTo(() => persistence.ReadAsync(ExpectedVersion.Any)) A.CallTo(() => persistence.ReadAsync(EtagVersion.Any))
.Invokes(new Action<long>(s => apply(state))); .Invokes(new Action<long>(s => apply(state)));
A.CallTo(() => persistence.WriteSnapshotAsync(A<EventConsumerState>.Ignored)) A.CallTo(() => persistence.WriteSnapshotAsync(A<EventConsumerState>.Ignored))

14
tests/Squidex.Infrastructure.Tests/PropertiesBagTests.cs

@ -86,7 +86,7 @@ namespace Squidex.Infrastructure
Assert.True(bag.Contains("NewKey")); Assert.True(bag.Contains("NewKey"));
Assert.Equal(1, bag.Count); Assert.Equal(1, bag.Count);
Assert.Equal(123, bag["NewKey"].ToInt32(c)); Assert.Equal(123, bag["NewKey"].ToInt64(c));
Assert.False(bag.Contains("OldKey")); Assert.False(bag.Contains("OldKey"));
} }
@ -174,7 +174,7 @@ namespace Squidex.Infrastructure
{ {
bag.Set("Key", "abc"); bag.Set("Key", "abc");
Assert.Throws<InvalidCastException>(() => bag["Key"].ToInt32(CultureInfo.InvariantCulture)); Assert.Throws<InvalidCastException>(() => bag["Key"].ToInt64(CultureInfo.InvariantCulture));
} }
[Fact] [Fact]
@ -214,7 +214,7 @@ namespace Squidex.Infrastructure
{ {
bag.Set("Key", long.MaxValue); bag.Set("Key", long.MaxValue);
Assert.Throws<InvalidCastException>(() => bag["Key"].ToInt32(c)); Assert.Throws<InvalidCastException>(() => bag["Key"].ToInt64(c));
} }
[Fact] [Fact]
@ -347,7 +347,7 @@ namespace Squidex.Infrastructure
private void AssertNumber() private void AssertNumber()
{ {
AssertInt32(123); AssertInt64(123);
AssertInt64(123); AssertInt64(123);
AssertSingle(123); AssertSingle(123);
AssertDouble(123); AssertDouble(123);
@ -420,10 +420,10 @@ namespace Squidex.Infrastructure
Assert.Equal(expected, (long?)dynamicBag.Key); Assert.Equal(expected, (long?)dynamicBag.Key);
} }
private void AssertInt32(int expected) private void AssertInt64(int expected)
{ {
Assert.Equal(expected, bag["Key"].ToInt32(c)); Assert.Equal(expected, bag["Key"].ToInt64(c));
Assert.Equal(expected, bag["Key"].ToNullableInt32(c)); Assert.Equal(expected, bag["Key"].ToNullableInt64(c));
Assert.Equal(expected, (int)dynamicBag.Key); Assert.Equal(expected, (int)dynamicBag.Key);
Assert.Equal(expected, (int?)dynamicBag.Key); Assert.Equal(expected, (int?)dynamicBag.Key);

25
tests/Squidex.Infrastructure.Tests/States/StateEventSourcingTests.cs

@ -15,16 +15,13 @@ using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Tasks;
using Squidex.Infrastructure.TestHelpers;
using Xunit; using Xunit;
namespace Squidex.Infrastructure.States namespace Squidex.Infrastructure.States
{ {
public class StateEventSourcingTests public class StateEventSourcingTests
{ {
public sealed class MyEvent : IEvent
{
}
private class MyStatefulObject : IStatefulObject private class MyStatefulObject : IStatefulObject
{ {
private readonly List<IEvent> appliedEvents = new List<IEvent>(); private readonly List<IEvent> appliedEvents = new List<IEvent>();
@ -115,7 +112,7 @@ namespace Squidex.Infrastructure.States
[Fact] [Fact]
public async Task Should_read_events_from_snapshot() public async Task Should_read_events_from_snapshot()
{ {
statefulObjectWithSnapShot.ExpectedVersion = ExpectedVersion.Any; statefulObjectWithSnapShot.ExpectedVersion = EtagVersion.Any;
A.CallTo(() => snapshotStore.ReadAsync(key)) A.CallTo(() => snapshotStore.ReadAsync(key))
.Returns((2, 2L)); .Returns((2, 2L));
@ -131,7 +128,7 @@ namespace Squidex.Infrastructure.States
[Fact] [Fact]
public async Task Should_throw_exception_if_events_are_older_than_snapshot() public async Task Should_throw_exception_if_events_are_older_than_snapshot()
{ {
statefulObjectWithSnapShot.ExpectedVersion = ExpectedVersion.Any; statefulObjectWithSnapShot.ExpectedVersion = EtagVersion.Any;
A.CallTo(() => snapshotStore.ReadAsync(key)) A.CallTo(() => snapshotStore.ReadAsync(key))
.Returns((2, 2L)); .Returns((2, 2L));
@ -144,7 +141,7 @@ namespace Squidex.Infrastructure.States
[Fact] [Fact]
public async Task Should_throw_exception_if_events_have_gaps_to_snapshot() public async Task Should_throw_exception_if_events_have_gaps_to_snapshot()
{ {
statefulObjectWithSnapShot.ExpectedVersion = ExpectedVersion.Any; statefulObjectWithSnapShot.ExpectedVersion = EtagVersion.Any;
A.CallTo(() => snapshotStore.ReadAsync(key)) A.CallTo(() => snapshotStore.ReadAsync(key))
.Returns((2, 2L)); .Returns((2, 2L));
@ -177,7 +174,7 @@ namespace Squidex.Infrastructure.States
[Fact] [Fact]
public async Task Should_not_throw_exception_if_noting_expected() public async Task Should_not_throw_exception_if_noting_expected()
{ {
statefulObject.ExpectedVersion = ExpectedVersion.Any; statefulObject.ExpectedVersion = EtagVersion.Any;
SetupEventStore(0); SetupEventStore(0);
@ -187,7 +184,7 @@ namespace Squidex.Infrastructure.States
[Fact] [Fact]
public async Task Should_provide_state_from_services_and_add_to_cache() public async Task Should_provide_state_from_services_and_add_to_cache()
{ {
statefulObject.ExpectedVersion = ExpectedVersion.Any; statefulObject.ExpectedVersion = EtagVersion.Any;
SetupEventStore(0); SetupEventStore(0);
@ -200,7 +197,7 @@ namespace Squidex.Infrastructure.States
[Fact] [Fact]
public async Task Should_serve_next_request_from_cache() public async Task Should_serve_next_request_from_cache()
{ {
statefulObject.ExpectedVersion = ExpectedVersion.Any; statefulObject.ExpectedVersion = EtagVersion.Any;
SetupEventStore(0); SetupEventStore(0);
@ -218,7 +215,7 @@ namespace Squidex.Infrastructure.States
[Fact] [Fact]
public async Task Should_write_to_store_with_previous_position() public async Task Should_write_to_store_with_previous_position()
{ {
statefulObject.ExpectedVersion = ExpectedVersion.Any; statefulObject.ExpectedVersion = EtagVersion.Any;
InvalidateMessage message = null; InvalidateMessage message = null;
@ -248,7 +245,7 @@ namespace Squidex.Infrastructure.States
[Fact] [Fact]
public async Task Should_wrap_exception_when_writing_to_store_with_previous_position() public async Task Should_wrap_exception_when_writing_to_store_with_previous_position()
{ {
statefulObject.ExpectedVersion = ExpectedVersion.Any; statefulObject.ExpectedVersion = EtagVersion.Any;
SetupEventStore(3); SetupEventStore(3);
@ -263,7 +260,7 @@ namespace Squidex.Infrastructure.States
[Fact] [Fact]
public async Task Should_remove_from_cache_when_invalidation_message_received() public async Task Should_remove_from_cache_when_invalidation_message_received()
{ {
statefulObject.ExpectedVersion = ExpectedVersion.Any; statefulObject.ExpectedVersion = EtagVersion.Any;
var actualObject = await sut.GetSingleAsync<MyStatefulObject>(key); var actualObject = await sut.GetSingleAsync<MyStatefulObject>(key);
@ -275,7 +272,7 @@ namespace Squidex.Infrastructure.States
[Fact] [Fact]
public async Task Should_return_same_instance_for_parallel_requests() public async Task Should_return_same_instance_for_parallel_requests()
{ {
statefulObject.ExpectedVersion = ExpectedVersion.Any; statefulObject.ExpectedVersion = EtagVersion.Any;
A.CallTo(() => snapshotStore.ReadAsync(key)) A.CallTo(() => snapshotStore.ReadAsync(key))
.ReturnsLazily(() => Task.Delay(1).ContinueWith(x => ((object)1, 1L))); .ReturnsLazily(() => Task.Delay(1).ContinueWith(x => ((object)1, 1L)));

20
tests/Squidex.Infrastructure.Tests/States/StateSnapshotTests.cs

@ -100,7 +100,7 @@ namespace Squidex.Infrastructure.States
statefulObject.ExpectedVersion = 0; statefulObject.ExpectedVersion = 0;
A.CallTo(() => snapshotStore.ReadAsync(key)) A.CallTo(() => snapshotStore.ReadAsync(key))
.Returns((0, -1)); .Returns((0, EtagVersion.Empty));
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.GetSingleAsync<MyStatefulObject>(key)); await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.GetSingleAsync<MyStatefulObject>(key));
} }
@ -119,10 +119,10 @@ namespace Squidex.Infrastructure.States
[Fact] [Fact]
public async Task Should_not_throw_exception_if_noting_expected() public async Task Should_not_throw_exception_if_noting_expected()
{ {
statefulObject.ExpectedVersion = ExpectedVersion.Any; statefulObject.ExpectedVersion = EtagVersion.Any;
A.CallTo(() => snapshotStore.ReadAsync(key)) A.CallTo(() => snapshotStore.ReadAsync(key))
.Returns((0, -1)); .Returns((0, EtagVersion.Empty));
await sut.GetSingleAsync<MyStatefulObject>(key); await sut.GetSingleAsync<MyStatefulObject>(key);
} }
@ -130,7 +130,7 @@ namespace Squidex.Infrastructure.States
[Fact] [Fact]
public async Task Should_provide_state_from_services_and_add_to_cache() public async Task Should_provide_state_from_services_and_add_to_cache()
{ {
statefulObject.ExpectedVersion = ExpectedVersion.Any; statefulObject.ExpectedVersion = EtagVersion.Any;
var actualObject = await sut.GetSingleAsync<MyStatefulObject>(key); var actualObject = await sut.GetSingleAsync<MyStatefulObject>(key);
@ -141,7 +141,7 @@ namespace Squidex.Infrastructure.States
[Fact] [Fact]
public async Task Should_serve_next_request_from_cache() public async Task Should_serve_next_request_from_cache()
{ {
statefulObject.ExpectedVersion = ExpectedVersion.Any; statefulObject.ExpectedVersion = EtagVersion.Any;
var actualObject1 = await sut.GetSingleAsync<MyStatefulObject>(key); var actualObject1 = await sut.GetSingleAsync<MyStatefulObject>(key);
@ -157,7 +157,7 @@ namespace Squidex.Infrastructure.States
[Fact] [Fact]
public async Task Should_not_serve_next_request_from_cache_when_detached() public async Task Should_not_serve_next_request_from_cache_when_detached()
{ {
statefulObject.ExpectedVersion = ExpectedVersion.Any; statefulObject.ExpectedVersion = EtagVersion.Any;
var actualObject1 = await sut.CreateAsync<MyStatefulObject>(key); var actualObject1 = await sut.CreateAsync<MyStatefulObject>(key);
@ -173,7 +173,7 @@ namespace Squidex.Infrastructure.States
[Fact] [Fact]
public async Task Should_write_to_store_with_previous_version() public async Task Should_write_to_store_with_previous_version()
{ {
statefulObject.ExpectedVersion = ExpectedVersion.Any; statefulObject.ExpectedVersion = EtagVersion.Any;
InvalidateMessage message = null; InvalidateMessage message = null;
@ -204,7 +204,7 @@ namespace Squidex.Infrastructure.States
[Fact] [Fact]
public async Task Should_wrap_exception_when_writing_to_store_with_previous_version() public async Task Should_wrap_exception_when_writing_to_store_with_previous_version()
{ {
statefulObject.ExpectedVersion = ExpectedVersion.Any; statefulObject.ExpectedVersion = EtagVersion.Any;
A.CallTo(() => snapshotStore.ReadAsync(key)) A.CallTo(() => snapshotStore.ReadAsync(key))
.Returns((123, 13)); .Returns((123, 13));
@ -220,7 +220,7 @@ namespace Squidex.Infrastructure.States
[Fact] [Fact]
public async Task Should_remove_from_cache_when_invalidation_message_received() public async Task Should_remove_from_cache_when_invalidation_message_received()
{ {
statefulObject.ExpectedVersion = ExpectedVersion.Any; statefulObject.ExpectedVersion = EtagVersion.Any;
var actualObject = await sut.GetSingleAsync<MyStatefulObject>(key); var actualObject = await sut.GetSingleAsync<MyStatefulObject>(key);
@ -232,7 +232,7 @@ namespace Squidex.Infrastructure.States
[Fact] [Fact]
public async Task Should_return_same_instance_for_parallel_requests() public async Task Should_return_same_instance_for_parallel_requests()
{ {
statefulObject.ExpectedVersion = ExpectedVersion.Any; statefulObject.ExpectedVersion = EtagVersion.Any;
A.CallTo(() => snapshotStore.ReadAsync(key)) A.CallTo(() => snapshotStore.ReadAsync(key))
.ReturnsLazily(() => Task.Delay(1).ContinueWith(x => (1, 1L))); .ReturnsLazily(() => Task.Delay(1).ContinueWith(x => (1, 1L)));

5
tests/Squidex.Infrastructure.Tests/Commands/TestHelpers/MyCommand.cs → tests/Squidex.Infrastructure.Tests/TestHelpers/MyCommand.cs

@ -8,14 +8,15 @@
using System; using System;
using NodaTime; using NodaTime;
using Squidex.Infrastructure.Commands;
namespace Squidex.Infrastructure.Commands.TestHelpers namespace Squidex.Infrastructure.TestHelpers
{ {
internal sealed class MyCommand : IAggregateCommand, ITimestampCommand internal sealed class MyCommand : IAggregateCommand, ITimestampCommand
{ {
public Guid AggregateId { get; set; } public Guid AggregateId { get; set; }
public long? ExpectedVersion { get; set; } public long ExpectedVersion { get; set; }
public Instant Timestamp { get; set; } public Instant Timestamp { get; set; }
} }

6
tests/Squidex.Infrastructure.Tests/Commands/TestHelpers/MyDomainObject.cs → tests/Squidex.Infrastructure.Tests/TestHelpers/MyDomainObject.cs

@ -6,11 +6,11 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Commands;
namespace Squidex.Infrastructure.Commands.TestHelpers namespace Squidex.Infrastructure.TestHelpers
{ {
internal sealed class MyDomainObject : DomainObjectBase<MyDomainObject, object> internal sealed class MyDomainObject : DomainObjectBase<MyDomainObject, MyDomainState>
{ {
} }
} }

17
tests/Squidex.Infrastructure.Tests/TestHelpers/MyDomainState.cs

@ -0,0 +1,17 @@
// ==========================================================================
// MyDomainState.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Squidex.Infrastructure.Commands;
namespace Squidex.Infrastructure.TestHelpers
{
public class MyDomainState : IDomainState
{
public long Version { get; set; }
}
}

3
tests/Squidex.Infrastructure.Tests/Commands/TestHelpers/MyEvent.cs → tests/Squidex.Infrastructure.Tests/TestHelpers/MyEvent.cs

@ -8,9 +8,10 @@
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Infrastructure.Commands.TestHelpers namespace Squidex.Infrastructure.TestHelpers
{ {
internal sealed class MyEvent : IEvent internal sealed class MyEvent : IEvent
{ {
public string MyProperty { get; set; }
} }
} }
Loading…
Cancel
Save