Browse Source

Some first tests

pull/1/head
Sebastian 9 years ago
parent
commit
8d8c04ac6f
  1. 3
      src/PinkParrot.Events/Schemas/FieldAdded.cs
  2. 3
      src/PinkParrot.Events/Schemas/FieldDeleted.cs
  3. 3
      src/PinkParrot.Events/Schemas/FieldDisabled.cs
  4. 3
      src/PinkParrot.Events/Schemas/FieldEnabled.cs
  5. 3
      src/PinkParrot.Events/Schemas/FieldHidden.cs
  6. 3
      src/PinkParrot.Events/Schemas/FieldShown.cs
  7. 3
      src/PinkParrot.Events/Schemas/FieldUpdated.cs
  8. 3
      src/PinkParrot.Events/Schemas/SchemaDeleted.cs
  9. 3
      src/PinkParrot.Events/Schemas/SchemaUpdated.cs
  10. 2
      src/PinkParrot.Infrastructure/CQRS/DomainObject.cs
  11. 4
      src/PinkParrot.Infrastructure/Guard.cs
  12. 4
      src/PinkParrot.Infrastructure/TypeNameRegistry.cs
  13. 3
      src/PinkParrot.Read/Apps/Repositories/IAppRepository.cs
  14. 2
      src/PinkParrot.Read/Schemas/Repositories/ISchemaRepository.cs
  15. 21
      src/PinkParrot.Store.MongoDb/Apps/MongoAppEntity.cs
  16. 57
      src/PinkParrot.Store.MongoDb/Apps/MongoAppRepository.cs
  17. 12
      src/PinkParrot.Store.MongoDb/MongoDbModule.cs
  18. 15
      src/PinkParrot.Store.MongoDb/Schemas/MongoSchemaEntity.cs
  19. 10
      src/PinkParrot.Store.MongoDb/Schemas/MongoSchemaRepository.cs
  20. 31
      src/PinkParrot.Store.MongoDb/Utils/MongoEntity.cs
  21. 35
      src/PinkParrot.Write/Apps/AppCommandHandler.cs
  22. 2
      src/PinkParrot.Write/Apps/AppDomainObject.cs
  23. 5
      src/PinkParrot.Write/Apps/Commands/CreateApp.cs
  24. 22
      src/PinkParrot.Write/Schemas/Commands/AddField.cs
  25. 23
      src/PinkParrot.Write/Schemas/Commands/CreateSchema.cs
  26. 12
      src/PinkParrot.Write/Schemas/Commands/UpdateField.cs
  27. 12
      src/PinkParrot.Write/Schemas/Commands/UpdateSchema.cs
  28. 8
      src/PinkParrot.Write/Schemas/SchemaCommandHandler.cs
  29. 32
      src/PinkParrot.Write/Schemas/SchemaDomainObject.cs
  30. 9
      src/PinkParrot/Configurations/WriteModule.cs
  31. 31
      src/PinkParrot/Modules/Api/Apps/AppController.cs
  32. 23
      src/PinkParrot/Modules/Api/Apps/Models/ListAppDto.cs
  33. 2
      src/PinkParrot/Modules/Api/Schemas/Models/CreateFieldDto.cs
  34. 2
      src/PinkParrot/Modules/Api/Schemas/Models/CreateSchemaDto.cs
  35. 5
      src/PinkParrot/Modules/Api/Schemas/Models/ListSchemaDto.cs
  36. 2
      src/PinkParrot/Modules/Api/Schemas/Models/UpdateFieldDto.cs
  37. 2
      src/PinkParrot/Modules/Api/Schemas/Models/UpdateSchemaDto.cs
  38. 5
      src/PinkParrot/Modules/Api/Schemas/SchemaFieldsController.cs
  39. 2
      src/PinkParrot/Modules/Api/Schemas/SchemasController.cs
  40. 17
      src/PinkParrot/Pipeline/AppMiddleware.cs
  41. 42
      tests/PinkParrot.Infrastructure.Tests/CQRS/Autofac/AutofacDomainObjectFactoryTests.cs
  42. 6
      tests/PinkParrot.Infrastructure.Tests/GuardTests.cs
  43. 24
      tests/PinkParrot.Write.Tests/Apps/AppDomainObjectTest.cs
  44. 3
      tests/PinkParrot.Write.Tests/PinkParrot.Write.Tests.xproj
  45. 106
      tests/PinkParrot.Write.Tests/Schemas/SchemaDomainObjectTest.cs
  46. 28
      tests/PinkParrot.Write.Tests/Utils/SchemaFixture.cs
  47. 1
      tests/PinkParrot.Write.Tests/project.json
  48. 4
      tests/RunCoverage.bat

3
src/PinkParrot.Events/Schemas/FieldAdded.cs

@ -8,11 +8,12 @@
using PinkParrot.Core.Schemas;
using PinkParrot.Infrastructure;
using PinkParrot.Infrastructure.CQRS.Events;
namespace PinkParrot.Events.Schemas
{
[TypeName("FieldAddedEvent")]
public class FieldAdded : AppEvent
public class FieldAdded : IEvent
{
public long FieldId { get; set; }

3
src/PinkParrot.Events/Schemas/FieldDeleted.cs

@ -7,11 +7,12 @@
// ==========================================================================
using PinkParrot.Infrastructure;
using PinkParrot.Infrastructure.CQRS.Events;
namespace PinkParrot.Events.Schemas
{
[TypeName("FieldDeletedEvent")]
public class FieldDeleted : AppEvent
public class FieldDeleted : IEvent
{
public long FieldId { get; set; }
}

3
src/PinkParrot.Events/Schemas/FieldDisabled.cs

@ -7,11 +7,12 @@
// ==========================================================================
using PinkParrot.Infrastructure;
using PinkParrot.Infrastructure.CQRS.Events;
namespace PinkParrot.Events.Schemas
{
[TypeName("FieldDisabledEvent")]
public class FieldDisabled : AppEvent
public class FieldDisabled : IEvent
{
public long FieldId { get; set; }
}

3
src/PinkParrot.Events/Schemas/FieldEnabled.cs

@ -7,11 +7,12 @@
// ==========================================================================
using PinkParrot.Infrastructure;
using PinkParrot.Infrastructure.CQRS.Events;
namespace PinkParrot.Events.Schemas
{
[TypeName("FieldEnabledEvent")]
public class FieldEnabled : AppEvent
public class FieldEnabled : IEvent
{
public long FieldId { get; set; }
}

3
src/PinkParrot.Events/Schemas/FieldHidden.cs

@ -7,11 +7,12 @@
// ==========================================================================
using PinkParrot.Infrastructure;
using PinkParrot.Infrastructure.CQRS.Events;
namespace PinkParrot.Events.Schemas
{
[TypeName("FieldHiddenEvent")]
public class FieldHidden : AppEvent
public class FieldHidden : IEvent
{
public long FieldId { get; set; }
}

3
src/PinkParrot.Events/Schemas/FieldShown.cs

@ -7,11 +7,12 @@
// ==========================================================================
using PinkParrot.Infrastructure;
using PinkParrot.Infrastructure.CQRS.Events;
namespace PinkParrot.Events.Schemas
{
[TypeName("FieldShownEvent")]
public class FieldShown : AppEvent
public class FieldShown : IEvent
{
public long FieldId { get; set; }
}

3
src/PinkParrot.Events/Schemas/FieldUpdated.cs

@ -8,11 +8,12 @@
using PinkParrot.Core.Schemas;
using PinkParrot.Infrastructure;
using PinkParrot.Infrastructure.CQRS.Events;
namespace PinkParrot.Events.Schemas
{
[TypeName("FieldUpdatedEvent")]
public class FieldUpdated : AppEvent
public class FieldUpdated : IEvent
{
public long FieldId { get; set; }

3
src/PinkParrot.Events/Schemas/SchemaDeleted.cs

@ -7,11 +7,12 @@
// ==========================================================================
using PinkParrot.Infrastructure;
using PinkParrot.Infrastructure.CQRS.Events;
namespace PinkParrot.Events.Schemas
{
[TypeName("SchemaDeleted")]
public class SchemaDeleted : AppEvent
public class SchemaDeleted : IEvent
{
}
}

3
src/PinkParrot.Events/Schemas/SchemaUpdated.cs

@ -8,11 +8,12 @@
using PinkParrot.Core.Schemas;
using PinkParrot.Infrastructure;
using PinkParrot.Infrastructure.CQRS.Events;
namespace PinkParrot.Events.Schemas
{
[TypeName("SchemaUpdated")]
public class SchemaUpdated : AppEvent
public class SchemaUpdated : IEvent
{
public SchemaProperties Properties { get; set; }
}

2
src/PinkParrot.Infrastructure/CQRS/DomainObject.cs

@ -71,7 +71,7 @@ namespace PinkParrot.Infrastructure.CQRS
uncomittedEvents.Clear();
}
ICollection<Envelope<IEvent>> IAggregate.GetUncomittedEvents()
public ICollection<Envelope<IEvent>> GetUncomittedEvents()
{
return uncomittedEvents;
}

4
src/PinkParrot.Infrastructure/Guard.cs

@ -199,11 +199,11 @@ namespace PinkParrot.Infrastructure
[DebuggerStepThrough]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Valid(IValidatable target, string parameterName, string message)
public static void Valid(IValidatable target, string parameterName, Func<string> message)
{
NotNull(target, parameterName);
target.Validate(() => message);
target.Validate(message);
}
}
}

4
src/PinkParrot.Infrastructure/TypeNameRegistry.cs

@ -72,7 +72,7 @@ namespace PinkParrot.Infrastructure
if (result == null)
{
throw new ArgumentException($"There is not name for type '{type}");
throw new ArgumentException($"There is no name for type '{type}");
}
return result;
@ -84,7 +84,7 @@ namespace PinkParrot.Infrastructure
if (result == null)
{
throw new ArgumentException($"There is not type for name '{name}");
throw new ArgumentException($"There is no type for name '{name}");
}
return result;

3
src/PinkParrot.Read/Apps/Repositories/IAppRepository.cs

@ -6,12 +6,15 @@
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System.Threading.Tasks;
namespace PinkParrot.Read.Apps.Repositories
{
public interface IAppRepository
{
Task<IReadOnlyList<IAppEntity>> QueryAllAsync();
Task<IAppEntity> FindAppByNameAsync(string name);
}
}

2
src/PinkParrot.Read/Schemas/Repositories/ISchemaRepository.cs

@ -14,7 +14,7 @@ namespace PinkParrot.Read.Schemas.Repositories
{
public interface ISchemaRepository
{
Task<List<ISchemaEntity>> QueryAllAsync(Guid appId);
Task<IReadOnlyList<ISchemaEntity>> QueryAllAsync(Guid appId);
Task<Guid?> FindSchemaIdAsync(Guid appId, string name);

21
src/PinkParrot.Store.MongoDb/Apps/MongoAppEntity.cs

@ -0,0 +1,21 @@
// ==========================================================================
// MongoAppEntity.cs
// PinkParrot Headless CMS
// ==========================================================================
// Copyright (c) PinkParrot Group
// All rights reserved.
// ==========================================================================
using MongoDB.Bson.Serialization.Attributes;
using PinkParrot.Read.Apps;
using PinkParrot.Store.MongoDb.Utils;
namespace PinkParrot.Store.MongoDb.Apps
{
public sealed class MongoAppEntity : MongoEntity, IAppEntity
{
[BsonRequired]
[BsonElement]
public string Name { get; set; }
}
}

57
src/PinkParrot.Store.MongoDb/Apps/MongoAppRepository.cs

@ -0,0 +1,57 @@
// ==========================================================================
// MongoAppRepository.cs
// PinkParrot Headless CMS
// ==========================================================================
// Copyright (c) PinkParrot Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using PinkParrot.Events.Apps;
using PinkParrot.Infrastructure.CQRS;
using PinkParrot.Infrastructure.CQRS.Events;
using PinkParrot.Infrastructure.Dispatching;
using PinkParrot.Infrastructure.Reflection;
using PinkParrot.Read.Apps;
using PinkParrot.Read.Apps.Repositories;
using PinkParrot.Store.MongoDb.Utils;
namespace PinkParrot.Store.MongoDb.Apps
{
public sealed class MongoAppRepository : MongoRepositoryBase<MongoAppEntity>, IAppRepository, ICatchEventConsumer
{
public MongoAppRepository(IMongoDatabase database)
: base(database)
{
}
public async Task<IReadOnlyList<IAppEntity>> QueryAllAsync()
{
var entities =
await Collection.Find(new BsonDocument()).ToListAsync();
return entities;
}
public async Task<IAppEntity> FindAppByNameAsync(string name)
{
var entity =
await Collection.Find(s => s.Name == name).FirstOrDefaultAsync();
return entity;
}
public Task On(AppCreated @event, EnvelopeHeaders headers)
{
return Collection.CreateAsync(headers, a => SimpleMapper.Map(a, @event));
}
public Task On(Envelope<IEvent> @event)
{
return this.DispatchActionAsync(@event.Payload, @event.Headers);
}
}
}

12
src/PinkParrot.Store.MongoDb/MongoDbModule.cs

@ -12,7 +12,11 @@ using Microsoft.AspNetCore.Identity.MongoDB;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using PinkParrot.Infrastructure.CQRS.EventStore;
using PinkParrot.Read.Apps.Repositories;
using PinkParrot.Read.Schemas.Repositories;
using PinkParrot.Store.MongoDb.Apps;
using PinkParrot.Store.MongoDb.Infrastructure;
using PinkParrot.Store.MongoDb.Schemas;
namespace PinkParrot.Store.MongoDb
{
@ -52,6 +56,14 @@ namespace PinkParrot.Store.MongoDb
builder.RegisterType<MongoPositionStorage>()
.As<IStreamPositionStorage>()
.SingleInstance();
builder.RegisterType<MongoSchemaRepository>()
.As<ISchemaRepository>()
.SingleInstance();
builder.RegisterType<MongoAppRepository>()
.As<IAppRepository>()
.SingleInstance();
}
}
}

15
src/PinkParrot.Store.MongoDb/Schemas/MongoSchemaEntity.cs

@ -17,27 +17,14 @@ using PinkParrot.Store.MongoDb.Utils;
namespace PinkParrot.Store.MongoDb.Schemas
{
public sealed class MongoSchemaEntity : ISchemaEntityWithSchema
public sealed class MongoSchemaEntity : MongoEntity, ISchemaEntityWithSchema
{
private Schema schema;
[BsonId]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public Guid Id { get; set; }
[BsonRequired]
[BsonElement]
public string Name { get; set; }
[BsonRequired]
[BsonElement]
public DateTime Created { get; set; }
[BsonRequired]
[BsonElement]
public DateTime LastModified { get; set; }
[BsonRequired]
[BsonElement]
public Guid AppId { get; set; }

10
src/PinkParrot.Store.MongoDb/Schemas/MongoSchemaRepository.cs

@ -18,6 +18,7 @@ using PinkParrot.Infrastructure;
using PinkParrot.Infrastructure.CQRS;
using PinkParrot.Infrastructure.CQRS.Events;
using PinkParrot.Infrastructure.Dispatching;
using PinkParrot.Infrastructure.Reflection;
using PinkParrot.Read.Schemas.Repositories;
using PinkParrot.Store.MongoDb.Schemas.Models;
using PinkParrot.Store.MongoDb.Utils;
@ -44,7 +45,7 @@ namespace PinkParrot.Store.MongoDb.Schemas
return collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.Name));
}
public async Task<List<ISchemaEntity>> QueryAllAsync(Guid appId)
public async Task<IReadOnlyList<ISchemaEntity>> QueryAllAsync(Guid appId)
{
var entities = await Collection.Find(s => s.AppId == appId && !s.IsDeleted).ToListAsync();
@ -131,12 +132,7 @@ namespace PinkParrot.Store.MongoDb.Schemas
public Task On(SchemaCreated @event, EnvelopeHeaders headers)
{
return Collection.CreateAsync(headers, e =>
{
e.Name = @event.Name;
Serialize(e, Schema.Create(@event.Name, @event.Properties));
});
return Collection.CreateAsync(headers, s => SimpleMapper.Map(s, @event));
}
public Task On(Envelope<IEvent> @event)

31
src/PinkParrot.Store.MongoDb/Utils/MongoEntity.cs

@ -0,0 +1,31 @@
// ==========================================================================
// MongoEntity.cs
// PinkParrot Headless CMS
// ==========================================================================
// Copyright (c) PinkParrot Group
// All rights reserved.
// ==========================================================================
using System;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using PinkParrot.Read;
namespace PinkParrot.Store.MongoDb.Utils
{
public abstract class MongoEntity : IEntity
{
[BsonId]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public Guid Id { get; set; }
[BsonRequired]
[BsonElement]
public DateTime Created { get; set; }
[BsonRequired]
[BsonElement]
public DateTime LastModified { get; set; }
}
}

35
src/PinkParrot.Write/Apps/AppCommandHandler.cs

@ -0,0 +1,35 @@
// ==========================================================================
// AppCommandHandler.cs
// PinkParrot Headless CMS
// ==========================================================================
// Copyright (c) PinkParrot Group
// All rights reserved.
// ==========================================================================
using System.Threading.Tasks;
using PinkParrot.Infrastructure.CQRS.Commands;
using PinkParrot.Infrastructure.Dispatching;
using PinkParrot.Write.Apps.Commands;
namespace PinkParrot.Write.Apps
{
public class AppCommandHandler : CommandHandler<AppDomainObject>
{
public AppCommandHandler(
IDomainObjectFactory domainObjectFactory,
IDomainObjectRepository domainObjectRepository)
: base(domainObjectFactory, domainObjectRepository)
{
}
public Task On(CreateApp command)
{
return CreateAsync(command, x => x.Create(command));
}
public override Task<bool> HandleAsync(CommandContext context)
{
return context.IsHandled ? Task.FromResult(false) : this.DispatchActionAsync(context.Command);
}
}
}

2
src/PinkParrot.Write/Apps/AppDomainObject.cs

@ -41,7 +41,7 @@ namespace PinkParrot.Write.Apps
public void Create(CreateApp command)
{
Guard.Valid(command, nameof(command), "Cannot create app");
Guard.Valid(command, nameof(command), () => "Cannot create app");
VerifyNotCreated();

5
src/PinkParrot.Write/Apps/Commands/CreateApp.cs

@ -8,10 +8,11 @@
using System.Collections.Generic;
using PinkParrot.Infrastructure;
using PinkParrot.Infrastructure.CQRS.Commands;
namespace PinkParrot.Write.Apps.Commands
{
public sealed class CreateApp : IValidatable
public sealed class CreateApp : AggregateCommand, IValidatable
{
public string Name { get; set; }
@ -19,7 +20,7 @@ namespace PinkParrot.Write.Apps.Commands
{
if (!Name.IsSlug())
{
errors.Add(new ValidationError("Name must be a valid slug", "Name"));
errors.Add(new ValidationError("Name must be a valid slug", nameof(Name)));
}
}
}

22
src/PinkParrot.Write/Schemas/Commands/AddField.cs

@ -6,16 +6,36 @@
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using PinkParrot.Infrastructure;
namespace PinkParrot.Write.Schemas.Commands
{
public class AddField : AppCommand
public class AddField : AppCommand, IValidatable
{
public string Name { get; set; }
public string Type { get; set; }
public JToken Properties { get; set; }
public void Validate(IList<ValidationError> errors)
{
if (!Name.IsSlug())
{
errors.Add(new ValidationError("Name must be a valid slug", nameof(Name)));
}
if (string.IsNullOrWhiteSpace(Type))
{
errors.Add(new ValidationError("Type must be specified", nameof(Type)));
}
if (Properties != null && !(Properties is JObject))
{
errors.Add(new ValidationError("Properties must be a object or null.", nameof(Properties)));
}
}
}
}

23
src/PinkParrot.Write/Schemas/Commands/CreateSchema.cs

@ -6,14 +6,33 @@
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using PinkParrot.Core.Schemas;
using PinkParrot.Infrastructure;
namespace PinkParrot.Write.Schemas.Commands
{
public class CreateSchema : AppCommand
public class CreateSchema : AppCommand, IValidatable
{
private SchemaProperties properties;
public string Name { get; set; }
public SchemaProperties Properties { get; set; }
public SchemaProperties Properties
{
get
{
return properties ?? (properties = new SchemaProperties(null, null));
}
set { properties = value; }
}
public void Validate(IList<ValidationError> errors)
{
if (!Name.IsSlug())
{
errors.Add(new ValidationError("Name must be a valid slug", nameof(Name)));
}
}
}
}

12
src/PinkParrot.Write/Schemas/Commands/UpdateField.cs

@ -6,14 +6,24 @@
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using PinkParrot.Infrastructure;
namespace PinkParrot.Write.Schemas.Commands
{
public class UpdateField : AppCommand
public class UpdateField : AppCommand, IValidatable
{
public long FieldId { get; set; }
public JToken Properties { get; set; }
public void Validate(IList<ValidationError> errors)
{
if (!(Properties is JObject))
{
errors.Add(new ValidationError("Properties must be a object.", nameof(Properties)));
}
}
}
}

12
src/PinkParrot.Write/Schemas/Commands/UpdateSchema.cs

@ -6,12 +6,22 @@
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using PinkParrot.Core.Schemas;
using PinkParrot.Infrastructure;
namespace PinkParrot.Write.Schemas.Commands
{
public class UpdateSchema : AppCommand
public class UpdateSchema : AppCommand, IValidatable
{
public SchemaProperties Properties { get; set; }
public void Validate(IList<ValidationError> errors)
{
if (Properties == null)
{
errors.Add(new ValidationError("Properties must be specified", nameof(Properties)));
}
}
}
}

8
src/PinkParrot.Write/Schemas/SchemaCommandHandler.cs

@ -51,7 +51,7 @@ namespace PinkParrot.Write.Schemas
throw new ValidationException("Cannot create a new schema", error);
}
await CreateAsync(command, s => s.Create(command.AppId, command.Name, command.Properties));
await CreateAsync(command, s => s.Create(command));
}
public Task On(DeleteSchema command)
@ -86,7 +86,7 @@ namespace PinkParrot.Write.Schemas
public Task On(UpdateSchema command)
{
return UpdateAsync(command, s => s.Update(command.Properties));
return UpdateAsync(command, s => s.Update(command));
}
public Task On(AddField command)
@ -94,7 +94,7 @@ namespace PinkParrot.Write.Schemas
var propertiesType = registry.FindByTypeName(command.Type).PropertiesType;
var propertiesValue = CreateProperties(command.Properties, propertiesType);
return UpdateAsync(command, s => s.AddField(command.Name, propertiesValue));
return UpdateAsync(command, s => s.AddField(command, propertiesValue));
}
public Task On(UpdateField command)
@ -111,7 +111,7 @@ namespace PinkParrot.Write.Schemas
var propertiesType = registry.FindByPropertiesType(field.RawProperties.GetType()).PropertiesType;
var propertiesValue = CreateProperties(command.Properties, propertiesType);
s.UpdateField(command.FieldId, propertiesValue);
s.UpdateField(command, propertiesValue);
});
}

32
src/PinkParrot.Write/Schemas/SchemaDomainObject.cs

@ -13,6 +13,8 @@ using PinkParrot.Infrastructure;
using PinkParrot.Infrastructure.CQRS;
using PinkParrot.Infrastructure.CQRS.Events;
using PinkParrot.Infrastructure.Dispatching;
using PinkParrot.Infrastructure.Reflection;
using PinkParrot.Write.Schemas.Commands;
namespace PinkParrot.Write.Schemas
{
@ -101,32 +103,42 @@ namespace PinkParrot.Write.Schemas
isDeleted = false;
}
public void AddField(string name, FieldProperties properties)
public void AddField(AddField command, FieldProperties properties)
{
Guard.Valid(command, nameof(command), () => $"Cannot add field to schema {Id}");
Guard.NotNull(properties, nameof(properties));
VerifyCreatedAndNotDeleted();
RaiseEvent(new FieldAdded { FieldId = ++totalFields, Name = name, Properties = properties });
RaiseEvent(new FieldAdded { FieldId = ++totalFields, Name = command.Name, Properties = properties });
}
public void Create(Guid newAppId, string name, SchemaProperties properties)
public void UpdateField(UpdateField command, FieldProperties properties)
{
VerifyNotCreated();
Guard.Valid(command, nameof(command), () => $"Cannot update schema '{schema.Name} ({Id})'");
Guard.NotNull(properties, nameof(properties));
VerifyCreatedAndNotDeleted();
RaiseEvent(new SchemaCreated { AppId = newAppId, Name = name, Properties = properties });
RaiseEvent(new FieldUpdated { FieldId = command.FieldId, Properties = properties });
}
public void Update(SchemaProperties properties)
public void Create(CreateSchema command)
{
VerifyCreatedAndNotDeleted();
Guard.Valid(command, nameof(command), () => "Cannot create schema");
VerifyNotCreated();
RaiseEvent(new SchemaUpdated { Properties = properties });
RaiseEvent(SimpleMapper.Map(command, new SchemaCreated()));
}
public void UpdateField(long fieldId, FieldProperties properties)
public void Update(UpdateSchema command)
{
Guard.Valid(command, nameof(command), () => $"Cannot update schema '{schema.Name} ({Id})'");
VerifyCreatedAndNotDeleted();
RaiseEvent(new FieldUpdated { FieldId = fieldId, Properties = properties });
RaiseEvent(SimpleMapper.Map(command, new SchemaUpdated()));
}
public void HideField(long fieldId)

9
src/PinkParrot/Configurations/WriteModule.cs

@ -9,6 +9,7 @@
using Autofac;
using PinkParrot.Infrastructure.CQRS.Commands;
using PinkParrot.Pipeline.CommandHandlers;
using PinkParrot.Write.Apps;
using PinkParrot.Write.Schemas;
namespace PinkParrot.Configurations
@ -25,6 +26,14 @@ namespace PinkParrot.Configurations
.As<ICommandHandler>()
.SingleInstance();
builder.RegisterType<AppCommandHandler>()
.As<ICommandHandler>()
.SingleInstance();
builder.RegisterType<AppDomainObject>()
.AsSelf()
.InstancePerDependency();
builder.RegisterType<SchemaCommandHandler>()
.As<ICommandHandler>()
.SingleInstance();

31
src/PinkParrot/Modules/Api/Apps/AppController.cs

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using PinkParrot.Infrastructure.Reflection;
using PinkParrot.Modules.Api.Apps.Models;
using PinkParrot.Modules.Api.Schemas.Models;
using PinkParrot.Read.Apps.Repositories;
namespace PinkParrot.Modules.Api.Apps
{
public class AppController
{
private readonly IAppRepository appRepository;
public AppController(IAppRepository appRepository)
{
this.appRepository = appRepository;
}
[HttpGet]
[Route("api/schemas/")]
public async Task<List<ListAppDto>> Query()
{
var schemas = await appRepository.QueryAllAsync();
return schemas.Select(s => SimpleMapper.Map(s, new ListAppDto())).ToList();
}
}
}

23
src/PinkParrot/Modules/Api/Apps/Models/ListAppDto.cs

@ -0,0 +1,23 @@
// ==========================================================================
// ListAppDto.cs
// PinkParrot Headless CMS
// ==========================================================================
// Copyright (c) PinkParrot Group
// All rights reserved.
// ==========================================================================
using System;
namespace PinkParrot.Modules.Api.Apps.Models
{
public sealed class ListAppDto
{
public Guid Id { get; set; }
public string Name { get; set; }
public DateTime Created { get; set; }
public DateTime LastModified { get; set; }
}
}

2
src/PinkParrot/Modules/Api/Schemas/Models/CreateFieldDto.cs

@ -14,11 +14,9 @@ namespace PinkParrot.Modules.Api.Schemas.Models
{
public class CreateFieldDto
{
[Required]
[JsonProperty("$type")]
public string Type { get; set; }
[Required]
public string Name { get; set; }
public JObject Properties { get; set; }

2
src/PinkParrot/Modules/Api/Schemas/Models/CreateSchemaDto.cs

@ -6,14 +6,12 @@
// All rights reserved.
// ==========================================================================
using System.ComponentModel.DataAnnotations;
using PinkParrot.Core.Schemas;
namespace PinkParrot.Modules.Api.Schemas.Models
{
public class CreateSchemaDto
{
[Required]
public string Name { get; set; }
public FieldProperties Properties { get; set; }

5
src/PinkParrot/Modules/Api/Schemas/Models/ListSchemaDto.cs

@ -7,22 +7,17 @@
// ==========================================================================
using System;
using System.ComponentModel.DataAnnotations;
namespace PinkParrot.Modules.Api.Schemas.Models
{
public class ListSchemaDto
{
[Required]
public Guid Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public DateTime Created { get; set; }
[Required]
public DateTime LastModified { get; set; }
}
}

2
src/PinkParrot/Modules/Api/Schemas/Models/UpdateFieldDto.cs

@ -6,14 +6,12 @@
// All rights reserved.
// ==========================================================================
using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json.Linq;
namespace PinkParrot.Modules.Api.Schemas.Models
{
public class UpdateFieldDto
{
[Required]
public JObject Properties { get; set; }
}
}

2
src/PinkParrot/Modules/Api/Schemas/Models/UpdateSchemaDto.cs

@ -6,14 +6,12 @@
// All rights reserved.
// ==========================================================================
using System.ComponentModel.DataAnnotations;
using PinkParrot.Core.Schemas;
namespace PinkParrot.Modules.Api.Schemas.Models
{
public class UpdateSchemaDto
{
[Required]
public SchemaProperties Properties { get; set; }
}
}

5
src/PinkParrot/Modules/Api/Schemas/SchemaFieldsController.cs

@ -8,7 +8,6 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
using PinkParrot.Infrastructure.CQRS.Commands;
using PinkParrot.Infrastructure.Reflection;
using PinkParrot.Modules.Api.Schemas.Models;
@ -31,8 +30,6 @@ namespace PinkParrot.Modules.Api.Schemas
{
var command = SimpleMapper.Map(model, new AddField());
command.Properties = command.Properties ?? new JObject();
return CommandBus.PublishAsync(command);
}
@ -40,7 +37,7 @@ namespace PinkParrot.Modules.Api.Schemas
[Route("api/schemas/{name}/fields/{fieldId:long}/")]
public Task Update(string name, long fieldId, [FromBody] UpdateFieldDto model)
{
var command = new UpdateField { FieldId = fieldId, Properties = model.Properties };
var command = SimpleMapper.Map(model, new UpdateField());
return CommandBus.PublishAsync(command);
}

2
src/PinkParrot/Modules/Api/Schemas/SchemasController.cs

@ -62,8 +62,6 @@ namespace PinkParrot.Modules.Api.Schemas
{
var command = SimpleMapper.Map(model, new CreateSchema { AggregateId = Guid.NewGuid() });
command.Properties = command.Properties ?? new SchemaProperties(null, null);
await CommandBus.PublishAsync(command);
return CreatedAtAction("Query", new EntityCreatedDto { Id = command.AggregateId });

17
src/PinkParrot/Pipeline/AppMiddleware.cs

@ -7,6 +7,7 @@
// ==========================================================================
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using PinkParrot.Read.Apps.Services;
@ -15,22 +16,28 @@ namespace PinkParrot.Pipeline
public sealed class AppMiddleware
{
private readonly IAppProvider appProvider;
private readonly IHostingEnvironment appEnvironment;
private readonly RequestDelegate next;
public AppMiddleware(RequestDelegate next, IAppProvider appProvider)
public AppMiddleware(RequestDelegate next, IAppProvider appProvider, IHostingEnvironment appEnvironment)
{
this.next = next;
this.appProvider = appProvider;
this.appEnvironment = appEnvironment;
}
public async Task Invoke(HttpContext context)
{
var appId = await appProvider.FindAppIdByNameAsync(context.Request.Host.ToString().Split('.')[0]);
var hostParts = context.Request.Host.ToString().Split('.');
if (appId.HasValue)
if (appEnvironment.IsDevelopment() || hostParts.Length >= 3)
{
context.Features.Set<IAppFeature>(new AppFeature(appId.Value));
var appId = await appProvider.FindAppIdByNameAsync(hostParts[0]);
if (appId.HasValue)
{
context.Features.Set<IAppFeature>(new AppFeature(appId.Value));
}
}
await next(context);

42
tests/PinkParrot.Infrastructure.Tests/CQRS/Autofac/AutofacDomainObjectFactoryTests.cs

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Autofac;
using PinkParrot.Infrastructure.CQRS.Events;
using Xunit;
namespace PinkParrot.Infrastructure.CQRS.Autofac
{
public class AutofacDomainObjectFactoryTests
{
private sealed class DO : DomainObject
{
public DO(Guid id, int version) : base(id, version)
{
}
protected override void DispatchEvent(Envelope<IEvent> @event)
{
}
}
[Fact]
public void Should_create_domain_object_with_autofac()
{
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterType<DO>()
.AsSelf();
var factory = new AutofacDomainObjectFactory(containerBuilder.Build());
var id = Guid.NewGuid();
var domainObject = factory.CreateNew(typeof(DO), id);
Assert.Equal(id, domainObject.Id);
Assert.Equal(0, domainObject.Version);
}
}
}

6
tests/PinkParrot.Infrastructure.Tests/GuardTests.cs

@ -321,19 +321,19 @@ namespace PinkParrot.Infrastructure
[Fact]
public void Valid_should_throw_if_null()
{
Assert.Throws<ArgumentNullException>(() => Guard.Valid(null, "Parameter", "Message"));
Assert.Throws<ArgumentNullException>(() => Guard.Valid(null, "Parameter", () => "Message"));
}
[Fact]
public void Valid_should_throw_if_invalid()
{
Assert.Throws<ValidationException>(() => Guard.Valid(new ValidatableInvalid(), "Parameter", "Message"));
Assert.Throws<ValidationException>(() => Guard.Valid(new ValidatableInvalid(), "Parameter", () => "Message"));
}
[Fact]
public void Valid_should_do_nothing_if_valid()
{
Guard.Valid(new ValidatableValid(), "Parameter", "Message");
Guard.Valid(new ValidatableValid(), "Parameter", () => "Message");
}
}
}

24
tests/PinkParrot.Write.Tests/Apps/AppDomainObjectTest.cs

@ -11,27 +11,43 @@ using PinkParrot.Infrastructure;
using PinkParrot.Write.Apps;
using PinkParrot.Write.Apps.Commands;
using Xunit;
using System.Linq;
using FluentAssertions;
using PinkParrot.Events.Apps;
namespace PinkParrot.Write.Tests.Apps
{
public class AppDomainObjectTest
{
private const string TestName = "app";
private readonly AppDomainObject sut = new AppDomainObject(Guid.NewGuid(), 0);
[Fact]
public void Create_should_throw_if_created()
{
sut.Create(new CreateApp { Name = "app" });
sut.Create(new CreateApp { Name = TestName });
Assert.Throws<DomainException>(() => sut.Create(new CreateApp { Name = "app" }));
Assert.Throws<DomainException>(() => sut.Create(new CreateApp { Name = TestName }));
}
[Fact]
public void Create_should_throw_if_command_is_invalid()
{
Assert.Throws<ValidationException>(() => sut.Create(new CreateApp()));
}
[Fact]
public void Create_should_specify_name()
{
sut.Create(new CreateApp { Name = "app" });
sut.Create(new CreateApp { Name = TestName });
Assert.Equal(TestName, sut.Name);
Assert.Equal("app", sut.Name);
sut.GetUncomittedEvents()
.Select(x => x.Payload as AppCreated)
.Single()
.ShouldBeEquivalentTo(
new AppCreated { Name = TestName });
}
}
}

3
tests/PinkParrot.Write.Tests/PinkParrot.Write.Tests.xproj

@ -15,5 +15,8 @@
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

106
tests/PinkParrot.Write.Tests/Schemas/SchemaDomainObjectTest.cs

@ -0,0 +1,106 @@
// ==========================================================================
// SchemaDomainObjectTest.cs
// PinkParrot Headless CMS
// ==========================================================================
// Copyright (c) PinkParrot Group
// All rights reserved.
// ==========================================================================
using System;
using System.Linq;
using FluentAssertions;
using PinkParrot.Core.Schemas;
using PinkParrot.Events.Schemas;
using PinkParrot.Infrastructure;
using PinkParrot.Write.Schemas;
using PinkParrot.Write.Schemas.Commands;
using Xunit;
namespace PinkParrot.Write.Tests.Schemas
{
[Collection("Schema")]
public class SchemaDomainObjectTest
{
private const string TestName = "schema";
private readonly Guid appId = Guid.NewGuid();
private readonly FieldRegistry registry = new FieldRegistry();
private readonly SchemaDomainObject sut;
public SchemaDomainObjectTest()
{
sut = new SchemaDomainObject(Guid.NewGuid(), 0, registry);
}
[Fact]
public void Create_should_throw_if_created()
{
sut.Create(new CreateSchema { Name = TestName });
Assert.Throws<DomainException>(() => sut.Create(new CreateSchema { Name = TestName }));
}
[Fact]
public void Create_should_throw_if_command_is_invalid()
{
Assert.Throws<ValidationException>(() => sut.Create(new CreateSchema()));
}
[Fact]
public void Create_should_create_schema()
{
var props = new SchemaProperties(null, null);
sut.Create(new CreateSchema { Name = TestName, AppId = appId, Properties = props });
Assert.Equal("schema", sut.Schema.Name);
Assert.Equal(props, sut.Schema.Properties);
Assert.Equal(appId, sut.AppId);
sut.GetUncomittedEvents()
.Select(x => x.Payload as SchemaCreated)
.Single()
.ShouldBeEquivalentTo(
new SchemaCreated { Name = TestName, AppId = appId, Properties = props });
}
[Fact]
public void Update_should_throw_if_not_created()
{
Assert.Throws<DomainException>(() => sut.Update(new UpdateSchema { Properties = new SchemaProperties(null, null) }));
}
[Fact]
public void Update_should_throw_if_command_is_deleted()
{
sut.Create(new CreateSchema { Name = TestName });
sut.Delete();
Assert.Throws<ValidationException>(() => sut.Update(new UpdateSchema()));
}
[Fact]
public void Update_should_throw_if_command_is_invalid()
{
sut.Create(new CreateSchema { Name = TestName });
Assert.Throws<ValidationException>(() => sut.Update(new UpdateSchema()));
}
[Fact]
public void Update_should_refresh_properties()
{
var props = new SchemaProperties(null, null);
sut.Create(new CreateSchema { Name = TestName, AppId = appId });
sut.Update(new UpdateSchema { Properties = props });
Assert.Equal(props, sut.Schema.Properties);
sut.GetUncomittedEvents()
.Select(x => x.Payload as SchemaUpdated)
.Last()
.ShouldBeEquivalentTo(
new SchemaUpdated { Properties = props });
}
}
}

28
tests/PinkParrot.Write.Tests/Utils/SchemaFixture.cs

@ -0,0 +1,28 @@
// ==========================================================================
// SchemaFixture.cs
// PinkParrot Headless CMS
// ==========================================================================
// Copyright (c) PinkParrot Group
// All rights reserved.
// ==========================================================================
using PinkParrot.Core.Schemas;
using PinkParrot.Infrastructure;
using System.Reflection;
using Xunit;
namespace PinkParrot.Write.Tests.Utils
{
public class SchemaFixture
{
public SchemaFixture()
{
TypeNameRegistry.Map(typeof(Schema).GetTypeInfo().Assembly);
}
}
[CollectionDefinition("Schema")]
public class DatabaseCollection : ICollectionFixture<SchemaFixture>
{
}
}

1
tests/PinkParrot.Write.Tests/project.json

@ -7,6 +7,7 @@
}
},
"dependencies": {
"FluentAssertions": "4.15.0",
"dotnet-test-xunit": "2.2.0-preview2-build1029",
"PinkParrot.Core": "1.0.0-*",
"PinkParrot.Infrastructure": "1.0.0-*",

4
src/RunCoverage.bat → tests/RunCoverage.bat

@ -19,7 +19,7 @@ exit /b %errorlevel%
"%UserProfile%\.nuget\packages\OpenCover\4.6.519\tools\OpenCover.Console.exe" ^
-register:user ^
-target:"C:\Program Files\dotnet\dotnet.exe" ^
-targetargs:"test %~dp0\pinkparrot_infrastructure\PinkParrot.Infrastructure.Tests" ^
-targetargs:"test %~dp0\PinkParrot.Infrastructure.Tests" ^
-filter:"+[PinkParrot*]*" ^
-skipautoprops ^
-output:"%~dp0\GeneratedReports\Infrastructure.xml" ^
@ -28,7 +28,7 @@ exit /b %errorlevel%
"%UserProfile%\.nuget\packages\OpenCover\4.6.519\tools\OpenCover.Console.exe" ^
-register:user ^
-target:"C:\Program Files\dotnet\dotnet.exe" ^
-targetargs:"test %~dp0\pinkparrot_write\PinkParrot.Write.Tests" ^
-targetargs:"test %~dp0\PinkParrot.Write.Tests" ^
-filter:"+[PinkParrot*]*" ^
-skipautoprops ^
-output:"%~dp0\GeneratedReports\Write.xml" ^
Loading…
Cancel
Save