Browse Source

More tests

pull/1/head
Sebastian Stehle 9 years ago
parent
commit
bbe57d7448
  1. 3
      .gitignore
  2. 1
      PinkParrot.sln.DotSettings
  3. 2
      src/PinkParrot/Modules/Api/ErrorDto.cs
  4. 9
      src/PinkParrot/Modules/Api/Schemas/Models/CreateFieldDto.cs
  5. 3
      src/PinkParrot/Modules/Api/Schemas/Models/CreateSchemaDto.cs
  6. 2
      src/PinkParrot/Modules/Api/Schemas/Models/ListSchemaDto.cs
  7. 4
      src/PinkParrot/Modules/Api/Schemas/Models/UpdateFieldDto.cs
  8. 11
      src/PinkParrot/Modules/Api/Schemas/Models/UpdateSchemaDto.cs
  9. 6
      src/PinkParrot/Modules/Api/Schemas/SchemaFieldsController.cs
  10. 9
      src/PinkParrot/Modules/Api/Schemas/SchemasController.cs
  11. 77
      src/PinkParrot/Pipeline/ApiExceptionFilterAttribute.cs
  12. 4
      src/PinkParrot/project.json
  13. 37
      src/RunCoverage.bat
  14. 23
      src/pinkparrot_core/PinkParrot.Core/Schemas/Field.cs
  15. 2
      src/pinkparrot_core/PinkParrot.Core/Schemas/FieldProperties.cs
  16. 6
      src/pinkparrot_core/PinkParrot.Core/Schemas/FieldRegistry.cs
  17. 19
      src/pinkparrot_core/PinkParrot.Core/Schemas/Field_Generic.cs
  18. 2
      src/pinkparrot_core/PinkParrot.Core/Schemas/IRegisteredField.cs
  19. 4
      src/pinkparrot_core/PinkParrot.Core/Schemas/Schema.cs
  20. 2
      src/pinkparrot_events/PinkParrot.Events/Schemas/FieldAdded.cs
  21. 2
      src/pinkparrot_events/PinkParrot.Events/Schemas/FieldUpdated.cs
  22. 58
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/CollectionExtensionsTests.cs
  23. 225
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/GuardTests.cs
  24. 3
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/PinkParrot.Infrastructure.Tests.xproj
  25. 83
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/Reflection/SimpleMapperTests.cs
  26. 11
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CollectionExtensions.cs
  27. 79
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Guard.cs
  28. 22
      src/pinkparrot_infrastructure/PinkParrot.Infrastructure/TypeNameRegistry.cs
  29. 34
      src/pinkparrot_read/PinkParrot.Read/Models/FieldDto.cs
  30. 6
      src/pinkparrot_read/PinkParrot.Read/Models/SchemaDto.cs
  31. 19
      src/pinkparrot_write/PinkParrot.Write/Schemas/SchemaCommandHandler.cs
  32. 4
      src/pinkparrot_write/PinkParrot.Write/Schemas/SchemaDomainObject.cs

3
.gitignore

@ -243,3 +243,6 @@ ModelManifest.xml
# FAKE - F# Make
.fake/
# OpenCover
GeneratedReports/

1
PinkParrot.sln.DotSettings

@ -90,4 +90,5 @@
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EJavaScript_002ECodeStyle_002ESettingsUpgrade_002EJsParsFormattingSettingsUpgrader/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EJavaScript_002ECodeStyle_002ESettingsUpgrade_002EJsWrapperSettingsUpgrader/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

2
src/PinkParrot/Modules/Api/ErrorDto.cs

@ -17,6 +17,6 @@ namespace PinkParrot.Modules.Api
public string[] Details { get; set; }
public int? StatusCode { get; set; }
public int? StatusCode { get; set; } = 400;
}
}

9
src/PinkParrot/Modules/Api/Schemas/CreateFieldDto.cs → src/PinkParrot/Modules/Api/Schemas/Models/CreateFieldDto.cs

@ -10,18 +10,17 @@ using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace PinkParrot.Modules.Api.Schemas
namespace PinkParrot.Modules.Api.Schemas.Models
{
public class CreateFieldDto
{
[Required]
[JsonProperty("$type")]
public string Name { get; set; }
[Required]
public string Type { get; set; }
[Required]
public JToken Properties { get; set; }
public string Name { get; set; }
public JObject Properties { get; set; }
}
}

3
src/PinkParrot/Modules/Api/Schemas/CreateSchemaDto.cs → src/PinkParrot/Modules/Api/Schemas/Models/CreateSchemaDto.cs

@ -9,14 +9,13 @@
using System.ComponentModel.DataAnnotations;
using PinkParrot.Core.Schemas;
namespace PinkParrot.Modules.Api.Schemas
namespace PinkParrot.Modules.Api.Schemas.Models
{
public class CreateSchemaDto
{
[Required]
public string Name { get; set; }
[Required]
public FieldProperties Properties { get; set; }
}
}

2
src/PinkParrot/Modules/Api/Schemas/ListSchemaDto.cs → src/PinkParrot/Modules/Api/Schemas/Models/ListSchemaDto.cs

@ -9,7 +9,7 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace PinkParrot.Modules.Api.Schemas
namespace PinkParrot.Modules.Api.Schemas.Models
{
public class ListSchemaDto
{

4
src/PinkParrot/Modules/Api/Schemas/UpdateFieldDto.cs → src/PinkParrot/Modules/Api/Schemas/Models/UpdateFieldDto.cs

@ -9,11 +9,11 @@
using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json.Linq;
namespace PinkParrot.Modules.Api.Schemas
namespace PinkParrot.Modules.Api.Schemas.Models
{
public class UpdateFieldDto
{
[Required]
public JToken Properties { get; set; }
public JObject Properties { get; set; }
}
}

11
src/pinkparrot_core/PinkParrot.Core/Schemas/IFieldProperties.cs → src/PinkParrot/Modules/Api/Schemas/Models/UpdateSchemaDto.cs

@ -1,16 +1,19 @@
// ==========================================================================
// IFieldProperties.cs
// UpdateSchemaDto.cs
// PinkParrot Headless CMS
// ==========================================================================
// Copyright (c) PinkParrot Group
// All rights reserved.
// ==========================================================================
using PinkParrot.Infrastructure;
using System.ComponentModel.DataAnnotations;
using PinkParrot.Core.Schemas;
namespace PinkParrot.Core.Schemas
namespace PinkParrot.Modules.Api.Schemas.Models
{
public interface IFieldProperties : IValidatable
public class UpdateSchemaDto
{
[Required]
public SchemaProperties Properties { get; set; }
}
}

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

@ -8,12 +8,16 @@
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;
using PinkParrot.Pipeline;
using PinkParrot.Write.Schemas.Commands;
namespace PinkParrot.Modules.Api.Schemas
{
[ApiExceptionFilter]
public class SchemasFieldsController : ControllerBase
{
public SchemasFieldsController(ICommandBus commandBus)
@ -27,6 +31,8 @@ namespace PinkParrot.Modules.Api.Schemas
{
var command = SimpleMapper.Map(model, new AddField());
command.Properties = command.Properties ?? new JObject();
return CommandBus.PublishAsync(command);
}

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

@ -14,12 +14,15 @@ using Microsoft.AspNetCore.Mvc;
using PinkParrot.Core.Schemas;
using PinkParrot.Infrastructure.CQRS.Commands;
using PinkParrot.Infrastructure.Reflection;
using PinkParrot.Modules.Api.Schemas.Models;
using PinkParrot.Pipeline;
using PinkParrot.Read.Models;
using PinkParrot.Read.Repositories;
using PinkParrot.Write.Schemas.Commands;
namespace PinkParrot.Modules.Api.Schemas
{
[ApiExceptionFilter]
public class SchemasController : ControllerBase
{
private readonly ISchemaRepository schemaRepository;
@ -59,6 +62,8 @@ 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 });
@ -66,9 +71,9 @@ namespace PinkParrot.Modules.Api.Schemas
[HttpPut]
[Route("api/schemas/{name}/")]
public async Task<ActionResult> Update(string name, [FromBody] SchemaProperties schema)
public async Task<ActionResult> Update(string name, [FromBody] UpdateSchemaDto model)
{
var command = new UpdateSchema { Properties = schema };
var command = SimpleMapper.Map(model, new UpdateSchema());
await CommandBus.PublishAsync(command);

77
src/PinkParrot/Pipeline/ApiExceptionFilterAttribute.cs

@ -0,0 +1,77 @@
// ==========================================================================
// ExceptionFilter.cs
// PinkParrot Headless CMS
// ==========================================================================
// Copyright (c) PinkParrot Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using PinkParrot.Infrastructure;
using PinkParrot.Modules.Api;
// ReSharper disable InvertIf
namespace PinkParrot.Pipeline
{
public class ApiExceptionFilterAttribute : ActionFilterAttribute, IExceptionFilter
{
private static readonly List<Func<Exception, IActionResult>> handlers = new List<Func<Exception, IActionResult>>();
private static void AddHandler<T>(Func<T, IActionResult> handler) where T : Exception
{
handlers.Add(ex =>
{
var typed = ex as T;
return typed != null ? handler(typed) : null;
});
}
static ApiExceptionFilterAttribute()
{
AddHandler<DomainObjectNotFoundException>(ex =>
new NotFoundResult());
AddHandler<DomainException>(ex =>
new BadRequestObjectResult(new ErrorDto { Message = ex.Message }));
AddHandler<ValidationException>(ex =>
new BadRequestObjectResult(new ErrorDto { Message = ex.Message, Details = ex.Errors.Select(e => e.Message).ToArray() }));
}
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var errors = context.ModelState.Values.SelectMany(g => g.Errors).Select(e => new ValidationError(e.ErrorMessage)).ToList();
throw new ValidationException("The model is not valid.", errors);
}
}
public void OnException(ExceptionContext context)
{
IActionResult result = null;
foreach (var handler in handlers)
{
result = handler(context.Exception);
if (result != null)
{
break;
}
}
if (result != null)
{
context.Result = result;
}
}
}
}

4
src/PinkParrot/project.json

@ -22,11 +22,13 @@
"type": "platform"
},
"MongoDB.Driver": "2.3.0-rc1",
"OpenCover": "4.6.519",
"PinkParrot.Core": "1.0.0-*",
"PinkParrot.Events": "1.0.0-*",
"PinkParrot.Infrastructure": "1.0.0-*",
"PinkParrot.Read": "1.0.0-*",
"PinkParrot.Write": "1.0.0-*"
"PinkParrot.Write": "1.0.0-*",
"ReportGenerator": "2.4.5"
},
"tools": {

37
src/RunCoverage.bat

@ -0,0 +1,37 @@
REM Create a 'GeneratedReports' folder if it does not exist
if not exist "%~dp0GeneratedReports" mkdir "%~dp0GeneratedReports"
REM Remove any previously created test output directories
CD %~dp0
FOR /D /R %%X IN (%USERNAME%*) DO RD /S /Q "%%X"
REM Run the tests against the targeted output
call :RunOpenCoverUnitTestMetrics
REM Generate the report output based on the test results
if %errorlevel% equ 0 (
call :RunReportGeneratorOutput
)
REM Launch the report
if %errorlevel% equ 0 (
call :RunLaunchReport
)
exit /b %errorlevel%
:RunOpenCoverUnitTestMetrics
"%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" ^
-filter:"+[PinkParrot*]*" ^
-skipautoprops ^
-output:"%~dp0\GeneratedReports\Infrastructure.xml" ^
-oldStyle
exit /b %errorlevel%
:RunReportGeneratorOutput
"%UserProfile%\.nuget\packages\ReportGenerator\2.4.5\tools\ReportGenerator.exe" ^
-reports:"%~dp0\GeneratedReports\Infrastructure.xml" ^
-targetdir:"%~dp0\GeneratedReports\Output"
exit /b %errorlevel%

23
src/pinkparrot_core/PinkParrot.Core/Schemas/Field.cs

@ -34,6 +34,16 @@ namespace PinkParrot.Core.Schemas
get { return name; }
}
public string Hints
{
get { return RawProperties.Hints; }
}
public string Label
{
get { return RawProperties.Label; }
}
public bool IsHidden
{
get { return isHidden; }
@ -44,13 +54,12 @@ namespace PinkParrot.Core.Schemas
get { return isDisabled; }
}
public abstract IFieldProperties RawProperties { get; }
public abstract string Label { get; }
public abstract string Hints { get; }
public bool IsRequired
{
get { return RawProperties.IsRequired; }
}
public abstract bool IsRequired { get; }
public abstract FieldProperties RawProperties { get; }
protected Field(long id, string name)
{
@ -62,7 +71,7 @@ namespace PinkParrot.Core.Schemas
this.name = name;
}
public abstract Field Update(IFieldProperties newProperties);
public abstract Field Update(FieldProperties newProperties);
public Task ValidateAsync(PropertyValue property, ICollection<string> errors)
{

2
src/pinkparrot_core/PinkParrot.Core/Schemas/FieldProperties.cs

@ -11,7 +11,7 @@ using PinkParrot.Infrastructure;
namespace PinkParrot.Core.Schemas
{
public abstract class FieldProperties : NamedElementProperties, IFieldProperties
public abstract class FieldProperties : NamedElementProperties, IValidatable
{
public bool IsRequired { get; }

6
src/pinkparrot_core/PinkParrot.Core/Schemas/FieldRegistry.cs

@ -12,7 +12,7 @@ using PinkParrot.Infrastructure;
namespace PinkParrot.Core.Schemas
{
public delegate Field FactoryFunction(long id, string name, IFieldProperties properties);
public delegate Field FactoryFunction(long id, string name, FieldProperties properties);
public sealed class FieldRegistry
{
@ -43,7 +43,7 @@ namespace PinkParrot.Core.Schemas
this.propertiesType = propertiesType;
}
Field IRegisteredField.CreateField(long id, string name, IFieldProperties properties)
Field IRegisteredField.CreateField(long id, string name, FieldProperties properties)
{
return fieldFactory(id, name, properties);
}
@ -64,7 +64,7 @@ namespace PinkParrot.Core.Schemas
fieldsByPropertyType[registered.PropertiesType] = registered;
}
public Field CreateField(long id, string name, IFieldProperties properties)
public Field CreateField(long id, string name, FieldProperties properties)
{
var registered = fieldsByPropertyType[properties.GetType()];

19
src/pinkparrot_core/PinkParrot.Core/Schemas/Field_Generic.cs

@ -15,26 +15,11 @@ namespace PinkParrot.Core.Schemas
{
private T properties;
public override IFieldProperties RawProperties
public override FieldProperties RawProperties
{
get { return properties; }
}
public override string Label
{
get { return properties.Label ?? Name; }
}
public override string Hints
{
get { return properties.Hints; }
}
public override bool IsRequired
{
get { return properties.IsRequired; }
}
public T Properties
{
get { return properties; }
@ -48,7 +33,7 @@ namespace PinkParrot.Core.Schemas
this.properties = properties;
}
public override Field Update(IFieldProperties newProperties)
public override Field Update(FieldProperties newProperties)
{
Guard.NotNull(newProperties, nameof(newProperties));

2
src/pinkparrot_core/PinkParrot.Core/Schemas/IRegisteredField.cs

@ -14,6 +14,6 @@ namespace PinkParrot.Core.Schemas
{
Type PropertiesType { get; }
Field CreateField(long id, string name, IFieldProperties properties);
Field CreateField(long id, string name, FieldProperties properties);
}
}

4
src/pinkparrot_core/PinkParrot.Core/Schemas/Schema.cs

@ -55,8 +55,6 @@ namespace PinkParrot.Core.Schemas
public static Schema Create(string name, SchemaProperties newProperties)
{
newProperties = newProperties ?? new SchemaProperties(null, null);
if (!name.IsSlug())
{
var error = new ValidationError("Name must be a valid slug", "Name");
@ -86,7 +84,7 @@ namespace PinkParrot.Core.Schemas
return new Schema(name, properties, fieldsById.SetItem(field.Id, field));
}
public Schema UpdateField(long fieldId, IFieldProperties newProperties)
public Schema UpdateField(long fieldId, FieldProperties newProperties)
{
return UpdateField(fieldId, field => field.Update(newProperties));
}

2
src/pinkparrot_events/PinkParrot.Events/Schemas/FieldAdded.cs

@ -18,6 +18,6 @@ namespace PinkParrot.Events.Schemas
public string Name { get; set; }
public IFieldProperties Properties { get; set; }
public FieldProperties Properties { get; set; }
}
}

2
src/pinkparrot_events/PinkParrot.Events/Schemas/FieldUpdated.cs

@ -16,6 +16,6 @@ namespace PinkParrot.Events.Schemas
{
public long FieldId { get; set; }
public IFieldProperties Properties { get; set; }
public FieldProperties Properties { get; set; }
}
}

58
src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/CollectionExtensionsTests.cs

@ -112,6 +112,14 @@ namespace PinkParrot.Infrastructure
Assert.Equal(list, listDictionary[12]);
}
[Fact]
public void SequentialHashCode_should_ignore_null_values()
{
var collection = new string[] { null, null };
Assert.Equal(17, collection.SequentialHashCode());
}
[Fact]
public void SequentialHashCode_should_return_same_hash_codes_for_list_with_same_order()
{
@ -165,5 +173,55 @@ namespace PinkParrot.Infrastructure
Assert.Equal(collection2.OrderedHashCode(), collection1.OrderedHashCode());
}
[Fact]
public void EqualsDictionary_should_return_true_for_equal_dictionaries()
{
var lhs = new Dictionary<int, int>
{
[1] = 1,
[2] = 2
};
var rhs = new Dictionary<int, int>
{
[1] = 1,
[2] = 2
};
Assert.True(lhs.EqualsDictionary(rhs));
}
[Fact]
public void EqualsDictionary_should_return_false_for_different_sizes()
{
var lhs = new Dictionary<int, int>
{
[1] = 1,
[2] = 2
};
var rhs = new Dictionary<int, int>
{
[1] = 1
};
Assert.False(lhs.EqualsDictionary(rhs));
}
[Fact]
public void EqualsDictionary_should_return_false_for_different_values()
{
var lhs = new Dictionary<int, int>
{
[1] = 1,
[2] = 2
};
var rhs = new Dictionary<int, int>
{
[1] = 1,
[3] = 3
};
Assert.False(lhs.EqualsDictionary(rhs));
}
}
}

225
src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/GuardTests.cs

@ -16,85 +16,103 @@ namespace PinkParrot.Infrastructure
[Theory]
[InlineData("")]
[InlineData(" ")]
public void Should_throw_when_target_is_null_for_empty_string(string invalidString)
public void NotNullOrEmpty_should_throw_for_empy_strings(string invalidString)
{
Assert.Throws<ArgumentException>(() => Guard.NotNullOrEmpty(invalidString, "parameter"));
}
[Fact]
public void Should_do_nothing_if_target_string_is_valid()
public void NotNullOrEmpty_should_throw_for_null_string()
{
Assert.Throws<ArgumentNullException>(() => Guard.NotNullOrEmpty(null, "parameter"));
}
[Fact]
public void NotNullOrEmpty_should_do_nothing_for_vaid_string()
{
Guard.NotNullOrEmpty("value", "parameter");
}
[Fact]
public void Should_do_nothing_if_target_is_not_null()
public void NotNull_should_throw_for_null_value()
{
Guard.NotNull("value", "parameter");
Assert.Throws<ArgumentNullException>(() => Guard.NotNull(null, "parameter"));
}
[Fact]
public void Should_do_nothing_if_enum_is_valid()
public void NotNull_should_do_nothing_for_valid_value()
{
Guard.Enum(DateTimeKind.Local, "Parameter");
Guard.NotNull("value", "parameter");
}
[Fact]
public void Should_throw_if_enum_is_not_valid()
public void Enum_should_throw_for_invalid_enum()
{
Assert.Throws<ArgumentException>(() => Guard.Enum((DateTimeKind)13, "Parameter"));
}
[Fact]
public void Should_do_nothing_when_guid_is_not_empty()
public void Enum_should_do_nothing_for_valid_enum()
{
Guard.NotEmpty(Guid.NewGuid(), "parameter");
Guard.Enum(DateTimeKind.Local, "Parameter");
}
[Fact]
public void Should_throw_when_guid_is_empty()
public void NotEmpty_should_throw_for_empty_guid()
{
Assert.Throws<ArgumentException>(() => Guard.NotEmpty(Guid.Empty, "parameter"));
}
[Fact]
public void Should_throw_when_target_is_null()
public void NotEmpty_should_do_nothing_for_valid_guid()
{
Assert.Throws<ArgumentNullException>(() => Guard.NotNull(null, "parameter"));
Guard.NotEmpty(Guid.NewGuid(), "parameter");
}
[Fact]
public void Should_throw_when_target_is_null_for_null_string()
public void HasType_should_throw_for_other_type()
{
Assert.Throws<ArgumentNullException>(() => Guard.NotNullOrEmpty(null, "parameter"));
Assert.Throws<ArgumentException>(() => Guard.HasType<int>("value", "parameter"));
}
[Fact]
public void Should_do_nothing_when_target_has_correct_type()
public void HasType_should_do_nothing_for_null_value()
{
Guard.HasType<int>(null, "parameter");
}
[Fact]
public void HasType_should_do_nothing_for_correct_type()
{
Guard.HasType<int>(123, "parameter");
}
[Fact]
public void Should_throw_when_target_has_wrong_type()
public void HasType_nongeneric_should_throw_for_other_type()
{
Assert.Throws<ArgumentException>(() => Guard.HasType<int>("value", "parameter"));
Assert.Throws<ArgumentException>(() => Guard.HasType("value", typeof(int), "parameter"));
}
[Fact]
public void Should_throw_when_checking_for_null_and_target_is_null()
public void HasType_nongeneric_should_do_nothing_for_null_value()
{
Assert.Throws<ArgumentNullException>(() => Guard.HasType<int>(null, "parameter"));
Guard.HasType(null, typeof(int), "parameter");
}
[Fact]
public void Should_do_nothing_when_target_is_not_default_value()
public void HasType_nongeneric_should_do_nothing_for_correct_type()
{
Guard.NotDefault(Guid.NewGuid(), "parameter");
Guard.HasType(123, typeof(int), "parameter");
}
[Fact]
public void Should_throw_exception_when_value_has_default()
public void HasType_nongeneric_should_do_nothing_for_null_type()
{
Guard.HasType(123, null, "parameter");
}
[Fact]
public void NotDefault_should_throw_for_default_values()
{
Assert.Throws<ArgumentException>(() => Guard.NotDefault(Guid.Empty, "parameter"));
Assert.Throws<ArgumentException>(() => Guard.NotDefault(0, "parameter"));
@ -102,6 +120,12 @@ namespace PinkParrot.Infrastructure
Assert.Throws<ArgumentException>(() => Guard.NotDefault(false, "parameter"));
}
[Fact]
public void NotDefault_should_do_nothing_for_non_default_value()
{
Guard.NotDefault(Guid.NewGuid(), "parameter");
}
[Theory]
[InlineData("")]
[InlineData(" ")]
@ -110,7 +134,7 @@ namespace PinkParrot.Infrastructure
[InlineData(" not-a-slug ")]
[InlineData("-not-a-slug-")]
[InlineData("not$-a-slug")]
public void Should_throw_exception_for_invalid_slug(string slug)
public void ValidSlug_should_throw_for_invalid_slugs(string slug)
{
Assert.Throws<ArgumentException>(() => Guard.ValidSlug(slug, "slug"));
}
@ -120,9 +144,162 @@ namespace PinkParrot.Infrastructure
[InlineData("slug23")]
[InlineData("other-slug")]
[InlineData("just-another-slug")]
public void Should_do_nothing_for_valid_slug(string slug)
public void ValidSlug_should_do_nothing_for_valid_slugs(string slug)
{
Guard.ValidSlug(slug, "parameter");
}
[Theory]
[InlineData(double.PositiveInfinity)]
[InlineData(double.NegativeInfinity)]
[InlineData(double.NaN)]
public void ValidNumber_should_throw_for_invalid_doubles(double value)
{
Assert.Throws<ArgumentException>(() => Guard.ValidNumber(value, "parameter"));
}
[Theory]
[InlineData(0d)]
[InlineData(-1000d)]
[InlineData(1000d)]
public void ValidNumber_do_nothing_for_valid_double(double value)
{
Guard.ValidNumber(value, "parameter");
}
[Theory]
[InlineData(float.PositiveInfinity)]
[InlineData(float.NegativeInfinity)]
[InlineData(float.NaN)]
public void ValidNumber_should_throw_for_invalid_float(float value)
{
Assert.Throws<ArgumentException>(() => Guard.ValidNumber(value, "parameter"));
}
[Theory]
[InlineData(0f)]
[InlineData(-1000f)]
[InlineData(1000f)]
public void ValidNumber_do_nothing_for_valid_float(float value)
{
Guard.ValidNumber(value, "parameter");
}
[Theory]
[InlineData(4)]
[InlineData(104)]
public void Between_should_throw_for_values_outside_of_range(int value)
{
Assert.Throws<ArgumentException>(() => Guard.Between(value, 10, 100, "parameter"));
}
[Theory]
[InlineData(10)]
[InlineData(55)]
[InlineData(100)]
public void Between_should_do_nothing_for_values_in_range(int value)
{
Guard.Between(value, 10, 100, "parameter");
}
[Theory]
[InlineData(0)]
[InlineData(100)]
public void GreaterThan_should_throw_for_smaller_values(int value)
{
Assert.Throws<ArgumentException>(() => Guard.GreaterThan(value, 100, "parameter"));
}
[Theory]
[InlineData(101)]
[InlineData(200)]
public void GreaterThan_should_do_nothing_for_greater_values(int value)
{
Guard.GreaterThan(value, 100, "parameter");
}
[Theory]
[InlineData(0)]
[InlineData(99)]
public void GreaterEquals_should_throw_for_smaller_values(int value)
{
Assert.Throws<ArgumentException>(() => Guard.GreaterEquals(value, 100, "parameter"));
}
[Theory]
[InlineData(100)]
[InlineData(200)]
public void GreaterEquals_should_do_nothing_for_greater_values(int value)
{
Guard.GreaterEquals(value, 100, "parameter");
}
[Theory]
[InlineData(1000)]
[InlineData(100)]
public void LessThan_should_throw_for_greater_values(int value)
{
Assert.Throws<ArgumentException>(() => Guard.LessThan(value, 100, "parameter"));
}
[Theory]
[InlineData(99)]
[InlineData(50)]
public void LessThan_should_do_nothing_for_smaller_values(int value)
{
Guard.LessThan(value, 100, "parameter");
}
[Theory]
[InlineData(1000)]
[InlineData(101)]
public void LessEquals_should_throw_for_greater_values(int value)
{
Assert.Throws<ArgumentException>(() => Guard.LessEquals(value, 100, "parameter"));
}
[Theory]
[InlineData(100)]
[InlineData(50)]
public void LessEquals_should_do_nothing_for_smaller_values(int value)
{
Guard.LessEquals(value, 100, "parameter");
}
[Fact]
public void NotEmpty_should_throw_for_empty_collection()
{
Assert.Throws<ArgumentException>(() => Guard.NotEmpty(new int[0], "parameter"));
}
[Fact]
public void NotEmpty_should_throw_for_null_collection()
{
Assert.Throws<ArgumentNullException>(() => Guard.NotEmpty((int[])null, "parameter"));
}
[Fact]
public void NotEmpty_should_do_nothing_for_value_collection()
{
Guard.NotEmpty(new [] { 1, 2, 3 }, "parameter");
}
[Fact]
public void ValidFileName_should_throw_for_invalid_file_name()
{
Assert.Throws<ArgumentException>(() => Guard.ValidFileName("File/Name", "Parameter"));
}
[Fact]
public void ValidFileName_should_throw_for_null_file_name()
{
Assert.Throws<ArgumentNullException>(() => Guard.ValidFileName(null, "Parameter"));
}
[Fact]
public void ValidFileName_should_do_nothing_for_valid_file_name()
{
Guard.ValidFileName("FileName", "Parameter");
}
}
}

3
src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/PinkParrot.Infrastructure.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>

83
src/pinkparrot_infrastructure/PinkParrot.Infrastructure.Tests/Reflection/SimpleMapperTests.cs

@ -0,0 +1,83 @@
// ==========================================================================
// SimpleMapperTests.cs
// PinkParrot Headless CMS
// ==========================================================================
// Copyright (c) PinkParrot Group
// All rights reserved.
// ==========================================================================
using System;
using Xunit;
namespace PinkParrot.Infrastructure.Reflection
{
public class SimpleMapperTests
{
public class Class1Base
{
public string MappedString { get; set; }
public string MappedNull { get; set; }
public long MappedNumber { get; set; }
public Guid MappedGuid { get; set; }
}
public class Class1 : Class1Base
{
public string UnmappedString { get; set; }
}
public class Class2Base
{
public string MappedString { get; protected set; }
public int MappedNull { get; set; }
public int MappedNumber { get; set; }
public string MappedGuid { get; set; }
}
public class Class2 : Class2Base
{
public string UnmappedString
{
get { return "Value"; }
}
}
[Fact]
public void Should_throw_if_mapping_with_null_source()
{
Assert.Throws<ArgumentNullException>(() => SimpleMapper.Map((Class1)null, new Class2()));
}
[Fact]
public void Should_throw_if_mapping_with_null_target()
{
Assert.Throws<ArgumentNullException>(() => SimpleMapper.Map(new Class1(), (Class2)null));
}
[Fact]
public void Should_map_between_types()
{
var class1 = new Class1
{
UnmappedString = Guid.NewGuid().ToString(),
MappedString = Guid.NewGuid().ToString(),
MappedNumber = 123,
MappedGuid = Guid.NewGuid()
};
var class2 = new Class2();
SimpleMapper.Map(class1, class2);
Assert.Equal(class1.MappedString, class2.MappedString);
Assert.Equal(class1.MappedNumber, class2.MappedNumber);
Assert.Equal(class1.MappedGuid.ToString(), class2.MappedGuid);
Assert.NotEqual(class1.UnmappedString, class2.UnmappedString);
}
}
}

11
src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CollectionExtensions.cs

@ -106,16 +106,5 @@ namespace PinkParrot.Infrastructure
return result;
}
public static bool TryGetValueAsObject<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dictionary, TKey key, out object value)
{
TValue result;
var isFound = dictionary.TryGetValue(key, out result);
value = result;
return isFound;
}
}
}

79
src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Guard.cs

@ -56,11 +56,19 @@ namespace PinkParrot.Infrastructure
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void HasType<T>(object target, string parameterName)
{
NotNull(target, "parameterName");
if (target != null && target.GetType() != typeof(T))
{
throw new ArgumentException($"The parameter must be of type {typeof(T)}", parameterName);
}
}
if (target.GetType() != typeof(T))
[DebuggerStepThrough]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void HasType(object target, Type expectedType, string parameterName)
{
if (target != null && expectedType != null && target.GetType() != expectedType)
{
throw new ArgumentException("The parameter must be of type " + typeof(T), parameterName);
throw new ArgumentException($"The parameter must be of type {expectedType}", parameterName);
}
}
@ -70,9 +78,7 @@ namespace PinkParrot.Infrastructure
{
if (!target.IsBetween(lower, upper))
{
var message = string.Format(CultureInfo.CurrentCulture, "Value must be between {0} and {1}", lower, upper);
throw new ArgumentException(message, parameterName);
throw new ArgumentException($"Value must be between {lower} and {upper}", parameterName);
}
}
@ -82,9 +88,7 @@ namespace PinkParrot.Infrastructure
{
if (!target.IsEnumValue())
{
var message = string.Format(CultureInfo.CurrentCulture, "Value must be a valid enum type {0}", typeof(TEnum));
throw new ArgumentException(message, parameterName);
throw new ArgumentException($"Value must be a valid enum type {typeof(TEnum)}", parameterName);
}
}
@ -94,9 +98,7 @@ namespace PinkParrot.Infrastructure
{
if (target.CompareTo(lower) <= 0)
{
var message = string.Format(CultureInfo.CurrentCulture, "Value must be greater than {0}", lower);
throw new ArgumentException(message, parameterName);
throw new ArgumentException($"Value must be greater than {lower}", parameterName);
}
}
@ -106,9 +108,7 @@ namespace PinkParrot.Infrastructure
{
if (target.CompareTo(lower) < 0)
{
var message = string.Format(CultureInfo.CurrentCulture, "Value must be greater than {0}", lower);
throw new ArgumentException(message, parameterName);
throw new ArgumentException($"Value must be greater or equals than {lower}", parameterName);
}
}
@ -118,9 +118,7 @@ namespace PinkParrot.Infrastructure
{
if (target.CompareTo(upper) >= 0)
{
var message = string.Format(CultureInfo.CurrentCulture, "Value must be less than {0}", upper);
throw new ArgumentException(message, parameterName);
throw new ArgumentException($"Value must be less than {upper}", parameterName);
}
}
@ -130,9 +128,7 @@ namespace PinkParrot.Infrastructure
{
if (target.CompareTo(upper) > 0)
{
var message = string.Format(CultureInfo.CurrentCulture, "Value must be less than {0}", upper);
throw new ArgumentException(message, parameterName);
throw new ArgumentException($"Value must be less or equals than {upper}", parameterName);
}
}
@ -140,10 +136,7 @@ namespace PinkParrot.Infrastructure
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void NotEmpty<TType>(ICollection<TType> enumerable, string parameterName)
{
if (enumerable == null)
{
throw new ArgumentNullException(nameof(enumerable));
}
NotNull(enumerable, parameterName);
if (enumerable.Count == 0)
{
@ -183,22 +176,14 @@ namespace PinkParrot.Infrastructure
[DebuggerStepThrough]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void NotNullOrEmpty(string target, string parameterName, bool allowWhitespacesAtStartOrEnd = true)
public static void NotNullOrEmpty(string target, string parameterName)
{
if (target == null)
{
throw new ArgumentNullException(parameterName);
}
NotNull(target, parameterName);
if (string.IsNullOrWhiteSpace(target))
{
throw new ArgumentException("String parameter cannot be null or empty and cannot contain only blanks.", parameterName);
}
if (!allowWhitespacesAtStartOrEnd && target.Trim() != target)
{
throw new ArgumentException("String cannot start or end with whitespaces", parameterName);
}
}
[DebuggerStepThrough]
@ -212,29 +197,5 @@ namespace PinkParrot.Infrastructure
throw new ArgumentException("Value contains an invalid character.", parameterName);
}
}
[DebuggerStepThrough]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void IsType<T>(object target, string parameterName)
{
if (target != null && target.GetType() != typeof(T))
{
var message = string.Format(CultureInfo.CurrentCulture, "Value must be of type {0}", typeof(T));
throw new ArgumentException(message, parameterName);
}
}
[DebuggerStepThrough]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void IsType(object target, Type expectedType, string parameterName)
{
if (target != null && expectedType != null && target.GetType() != expectedType)
{
var message = string.Format(CultureInfo.CurrentCulture, "Value must be of type {0}", expectedType);
throw new ArgumentException(message, parameterName);
}
}
}
}

22
src/pinkparrot_infrastructure/PinkParrot.Infrastructure/TypeNameRegistry.cs

@ -24,9 +24,27 @@ namespace PinkParrot.Infrastructure
lock (namesByType)
{
namesByType.Add(type, name);
try
{
namesByType.Add(type, name);
}
catch (ArgumentException)
{
var message = $"The type '{type}' is already registered with name '{namesByType[type]}'";
throw new ArgumentException(message, nameof(type));
}
typesByName.Add(name, type);
try
{
typesByName.Add(name, type);
}
catch (ArgumentException)
{
var message = $"The name '{name}' is already registered with type '{typesByName[name]}'";
throw new ArgumentException(message, nameof(type));
}
}
}

34
src/pinkparrot_read/PinkParrot.Read/Models/FieldDto.cs

@ -6,22 +6,46 @@
// All rights reserved.
// ==========================================================================
using Newtonsoft.Json;
using PinkParrot.Core.Schemas;
namespace PinkParrot.Read.Models
{
public class FieldDto
{
[JsonProperty]
public string Name { get; set; }
[JsonProperty]
public IFieldProperties Properties { get; set; }
public bool IsHidden { get; set; }
public bool IsDisabled { get; set; }
public FieldProperties Properties { get; set; }
public static FieldDto Create(Field field)
{
return new FieldDto { Name = field.Name, Properties = field.RawProperties };
return new FieldDto
{
Name = field.Name,
IsHidden = field.IsHidden,
IsDisabled = field.IsDisabled,
Properties = field.RawProperties
};
}
public Field ToField(long id, FieldRegistry registry)
{
var field = registry.CreateField(id, Name, Properties);
if (IsHidden)
{
field = field.Hide();
}
if (IsDisabled)
{
field = field.Disable();
}
return field;
}
}
}

6
src/pinkparrot_read/PinkParrot.Read/Models/SchemaDto.cs

@ -8,7 +8,6 @@
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using PinkParrot.Core.Schemas;
using PinkParrot.Infrastructure;
@ -19,13 +18,10 @@ namespace PinkParrot.Read.Models
{
public sealed class SchemaDto
{
[JsonProperty]
public string Name { get; set; }
[JsonProperty]
public Dictionary<long, FieldDto> Fields { get; set; }
[JsonProperty]
public SchemaProperties Properties { get; set; }
public static SchemaDto Create(Schema schema)
@ -54,7 +50,7 @@ namespace PinkParrot.Read.Models
{
var field = kvp.Value;
schema = schema.AddOrUpdateField(registry.CreateField(kvp.Key, field.Name, field.Properties));
schema = schema.AddOrUpdateField(field.ToField(kvp.Key, registry));
}
}

19
src/pinkparrot_write/PinkParrot.Write/Schemas/SchemaCommandHandler.cs

@ -14,6 +14,7 @@ using PinkParrot.Core.Schemas;
using PinkParrot.Infrastructure;
using PinkParrot.Infrastructure.CQRS.Commands;
using PinkParrot.Infrastructure.Dispatching;
using PinkParrot.Read.Services;
using PinkParrot.Write.Schemas.Commands;
namespace PinkParrot.Write.Schemas
@ -21,18 +22,20 @@ namespace PinkParrot.Write.Schemas
public class SchemaCommandHandler : CommandHandler<SchemaDomainObject>
{
private readonly FieldRegistry registry;
private readonly ISchemaProvider schemaProvider;
private readonly JsonSerializer serializer;
public SchemaCommandHandler(
FieldRegistry registry,
ISchemaProvider schemaProvider,
IDomainObjectFactory domainObjectFactory,
IDomainObjectRepository domainObjectRepository,
JsonSerializer serializer)
: base(domainObjectFactory, domainObjectRepository)
{
this.registry = registry;
this.serializer = serializer;
this.schemaProvider = schemaProvider;
}
public override Task<bool> HandleAsync(CommandContext context)
@ -40,9 +43,15 @@ namespace PinkParrot.Write.Schemas
return context.IsHandled ? Task.FromResult(false) : this.DispatchActionAsync(context.Command);
}
public Task On(CreateSchema command)
public async Task On(CreateSchema command)
{
return CreateAsync(command, s => s.Create(command.TenantId, command.Name, command.Properties));
if (await schemaProvider.FindSchemaIdByNameAsync(command.TenantId, command.Name) != null)
{
var error = new ValidationError($"A schema with name '{command.Name}' already exists", "Name");
throw new ValidationException("Cannot create a new schema", error);
}
await CreateAsync(command, s => s.Create(command.TenantId, command.Name, command.Properties));
}
public Task On(DeleteSchema command)
@ -106,9 +115,9 @@ namespace PinkParrot.Write.Schemas
});
}
private IFieldProperties CreateProperties(JToken token, Type type)
private FieldProperties CreateProperties(JToken token, Type type)
{
return (IFieldProperties)token.ToObject(type, serializer);
return (FieldProperties)token.ToObject(type, serializer);
}
}
}

4
src/pinkparrot_write/PinkParrot.Write/Schemas/SchemaDomainObject.cs

@ -101,7 +101,7 @@ namespace PinkParrot.Write.Schemas
isDeleted = false;
}
public void AddField(string name, IFieldProperties properties)
public void AddField(string name, FieldProperties properties)
{
VerifyCreatedAndNotDeleted();
@ -122,7 +122,7 @@ namespace PinkParrot.Write.Schemas
RaiseEvent(new SchemaUpdated { Properties = properties });
}
public void UpdateField(long fieldId, IFieldProperties properties)
public void UpdateField(long fieldId, FieldProperties properties)
{
VerifyCreatedAndNotDeleted();

Loading…
Cancel
Save