mirror of https://github.com/Squidex/squidex.git
19 changed files with 579 additions and 153 deletions
@ -0,0 +1,24 @@ |
|||
// ==========================================================================
|
|||
// Cloneable.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
|
|||
namespace Squidex.Core.Schemas |
|||
{ |
|||
public abstract class Cloneable |
|||
{ |
|||
protected T Clone<T>(Action<T> updater) where T : Cloneable |
|||
{ |
|||
var clone = (T)MemberwiseClone(); |
|||
|
|||
updater(clone); |
|||
|
|||
return clone; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,148 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using FluentAssertions; |
|||
using Newtonsoft.Json.Linq; |
|||
using Squidex.Infrastructure; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Core.Schemas |
|||
{ |
|||
public class SchemaValidationTests |
|||
{ |
|||
private readonly HashSet<Language> languages = new HashSet<Language>(new[] { Language.GetLanguage("de"), Language.GetLanguage("en") }); |
|||
private readonly List<ValidationError> errors = new List<ValidationError>(); |
|||
private Schema sut = Schema.Create("my-name", new SchemaProperties()); |
|||
|
|||
[Fact] |
|||
public async Task Should_add_error_if_validating_data_with_unknown_field() |
|||
{ |
|||
var data = |
|||
new JObject( |
|||
new JProperty("unknown", 1)); |
|||
|
|||
await sut.ValidateAsync(data, errors, languages); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new List<ValidationError> |
|||
{ |
|||
new ValidationError("unknown is not a known field", "unknown") |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_error_if_validating_data_with_invalid_field() |
|||
{ |
|||
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { MaxValue = 100 })); |
|||
|
|||
var data = |
|||
new JObject( |
|||
new JProperty("my-field", 1000)); |
|||
|
|||
await sut.ValidateAsync(data, errors, languages); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new List<ValidationError> |
|||
{ |
|||
new ValidationError("my-field must be less than '100'", "my-field") |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_error_if_validating_data_with_invalid_localizable_field() |
|||
{ |
|||
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsRequired = true, IsLocalizable = true })); |
|||
|
|||
var data = |
|||
new JObject(); |
|||
|
|||
await sut.ValidateAsync(data, errors, languages); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new List<ValidationError> |
|||
{ |
|||
new ValidationError("my-field (de) is required", "my-field"), |
|||
new ValidationError("my-field (en) is required", "my-field") |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_error_if_required_field_is_not_in_bag() |
|||
{ |
|||
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsRequired = true })); |
|||
|
|||
var data = |
|||
new JObject(); |
|||
|
|||
await sut.ValidateAsync(data, errors, languages); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new List<ValidationError> |
|||
{ |
|||
new ValidationError("my-field is required", "my-field") |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_error_if_value_is_not_object_for_localizable_field() |
|||
{ |
|||
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsLocalizable = true })); |
|||
|
|||
var data = |
|||
new JObject( |
|||
new JProperty("my-field", 1)); |
|||
|
|||
await sut.ValidateAsync(data, errors, languages); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new List<ValidationError> |
|||
{ |
|||
new ValidationError("my-field is localizable and must be an object", "my-field") |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_error_if_value_contains_invalid_language() |
|||
{ |
|||
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsLocalizable = true })); |
|||
|
|||
var data = |
|||
new JObject( |
|||
new JProperty("my-field", |
|||
new JObject( |
|||
new JProperty("de", 1), |
|||
new JProperty("xx", 1)))); |
|||
|
|||
await sut.ValidateAsync(data, errors, languages); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new List<ValidationError> |
|||
{ |
|||
new ValidationError("my-field has an invalid language 'xx'", "my-field") |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_add_error_if_value_contains_unsupported_language() |
|||
{ |
|||
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsLocalizable = true })); |
|||
|
|||
var data = |
|||
new JObject( |
|||
new JProperty("my-field", |
|||
new JObject( |
|||
new JProperty("es", 1), |
|||
new JProperty("it", 1)))); |
|||
|
|||
await sut.ValidateAsync(data, errors, languages); |
|||
|
|||
errors.ShouldBeEquivalentTo( |
|||
new List<ValidationError> |
|||
{ |
|||
new ValidationError("my-field has an unsupported language 'es'", "my-field"), |
|||
new ValidationError("my-field has an unsupported language 'it'", "my-field"), |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,74 @@ |
|||
// ==========================================================================
|
|||
// LogExceptionHandlerTests.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Logging; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Commands |
|||
{ |
|||
public class LogExceptionHandlerTests |
|||
{ |
|||
private readonly MyLogger logger = new MyLogger(); |
|||
private readonly LogExceptionHandler sut; |
|||
|
|||
private sealed class MyLogger : ILogger<LogExceptionHandler> |
|||
{ |
|||
public int LogCount { get; private set; } |
|||
|
|||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatterr) |
|||
{ |
|||
LogCount++; |
|||
} |
|||
|
|||
public bool IsEnabled(LogLevel logLevel) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
public IDisposable BeginScope<TState>(TState state) |
|||
{ |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private sealed class MyCommand : ICommand |
|||
{ |
|||
} |
|||
|
|||
public LogExceptionHandlerTests() |
|||
{ |
|||
sut = new LogExceptionHandler(logger); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_do_nothing_if_context_has_no_exception() |
|||
{ |
|||
var context = new CommandContext(new MyCommand()); |
|||
|
|||
var isHandled = await sut.HandleAsync(context); |
|||
|
|||
Assert.False(isHandled); |
|||
Assert.Equal(0, logger.LogCount); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_log_if_context_has_exception2() |
|||
{ |
|||
var context = new CommandContext(new MyCommand()); |
|||
|
|||
context.Fail(new InvalidOperationException()); |
|||
|
|||
var isHandled = await sut.HandleAsync(context); |
|||
|
|||
Assert.False(isHandled); |
|||
Assert.Equal(1, logger.LogCount); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
// ==========================================================================
|
|||
// LogExecutingHandlerTests.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Logging; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Commands |
|||
{ |
|||
public class LogExecutingHandlerTests |
|||
{ |
|||
private readonly MyLogger logger = new MyLogger(); |
|||
private readonly LogExecutingHandler sut; |
|||
|
|||
private sealed class MyLogger : ILogger<LogExecutingHandler> |
|||
{ |
|||
public int LogCount { get; private set; } |
|||
|
|||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatterr) |
|||
{ |
|||
LogCount++; |
|||
} |
|||
|
|||
public bool IsEnabled(LogLevel logLevel) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
public IDisposable BeginScope<TState>(TState state) |
|||
{ |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private sealed class MyCommand : ICommand |
|||
{ |
|||
} |
|||
|
|||
public LogExecutingHandlerTests() |
|||
{ |
|||
sut = new LogExecutingHandler(logger); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_log_once() |
|||
{ |
|||
var context = new CommandContext(new MyCommand()); |
|||
|
|||
var isHandled = await sut.HandleAsync(context); |
|||
|
|||
Assert.False(isHandled); |
|||
Assert.Equal(1, logger.LogCount); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
// ==========================================================================
|
|||
// ReplayGeneratorTests.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Reactive.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Logging; |
|||
using Moq; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Infrastructure.CQRS.Replay |
|||
{ |
|||
public class ReplayGeneratorTests |
|||
{ |
|||
private readonly Mock<IEventStore> eventStore = new Mock<IEventStore>(); |
|||
private readonly Mock<IEventPublisher> eventPublisher = new Mock<IEventPublisher>(); |
|||
private readonly Mock<IReplayableStore> store1 = new Mock<IReplayableStore>(); |
|||
private readonly Mock<IReplayableStore> store2 = new Mock<IReplayableStore>(); |
|||
private readonly Mock<ILogger<ReplayGenerator>> logger = new Mock<ILogger<ReplayGenerator>>(); |
|||
private readonly ReplayGenerator sut; |
|||
|
|||
public ReplayGeneratorTests() |
|||
{ |
|||
sut = new ReplayGenerator(logger.Object, eventStore.Object, eventPublisher.Object, new[] { store1.Object, store2.Object }); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_clear_stores_and_replay_events() |
|||
{ |
|||
var event1 = new EventData(); |
|||
var event2 = new EventData(); |
|||
var event3 = new EventData(); |
|||
|
|||
eventStore.Setup(x => x.GetEventsAsync()).Returns(new[] { event1, event2, event3 }.ToObservable()); |
|||
|
|||
await sut.ReplayAllAsync(); |
|||
|
|||
store1.Verify(x => x.ClearAsync(), Times.Once()); |
|||
store2.Verify(x => x.ClearAsync(), Times.Once()); |
|||
|
|||
eventPublisher.Verify(x => x.Publish(event1)); |
|||
eventPublisher.Verify(x => x.Publish(event2)); |
|||
eventPublisher.Verify(x => x.Publish(event3)); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_publish_if_clearing_failed() |
|||
{ |
|||
var event1 = new EventData(); |
|||
var event2 = new EventData(); |
|||
var event3 = new EventData(); |
|||
|
|||
store1.Setup(x => x.ClearAsync()).Throws(new InvalidOperationException()); |
|||
|
|||
eventStore.Setup(x => x.GetEventsAsync()).Returns(new[] { event1, event2, event3 }.ToObservable()); |
|||
|
|||
await sut.ReplayAllAsync(); |
|||
|
|||
store1.Verify(x => x.ClearAsync(), Times.Once()); |
|||
store2.Verify(x => x.ClearAsync(), Times.Never()); |
|||
|
|||
eventStore.Verify(x => x.GetEventsAsync(), Times.Never()); |
|||
eventPublisher.Verify(x => x.Publish(It.IsAny<EventData>()), Times.Never()); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_throw_if_process_throws() |
|||
{ |
|||
eventStore.Setup(x => x.GetEventsAsync()).Throws(new InvalidOperationException()); |
|||
|
|||
await sut.ReplayAllAsync(); |
|||
|
|||
eventPublisher.Verify(x => x.Publish(It.IsAny<EventData>()), Times.Never()); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue