Browse Source

Merge branch 'master' into test

# Conflicts:
#	src/Squidex/package-lock.json
pull/356/head
Sebastian 7 years ago
parent
commit
145693bc5a
  1. 2
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs
  2. 48
      src/Squidex.Infrastructure/Orleans/LoggingFilter.cs
  3. 21
      src/Squidex.Infrastructure/ValidationException.cs
  4. 90
      src/Squidex/Areas/Frontend/Middlewares/WebpackMiddleware.cs
  5. 2
      src/Squidex/Config/Logging.cs
  6. 6
      src/Squidex/Config/Orleans/OrleansServices.cs
  7. 2
      src/Squidex/Program.cs
  8. 38
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs
  9. 61
      tests/Squidex.Infrastructure.Tests/Orleans/LoggingFilterTests.cs
  10. 12
      tests/Squidex.Infrastructure.Tests/ValidationExceptionTests.cs

2
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs

@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
{
var pathString = path.ToPathString();
errors.Add(new ValidationError($"{pathString}: {message}", pathString));
errors.Add(new ValidationError(message, pathString));
}
public Task ValidatePartialAsync(NamedContentData data)

48
src/Squidex.Infrastructure/Orleans/LoggingFilter.cs

@ -0,0 +1,48 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Orleans;
using Squidex.Infrastructure.Log;
namespace Squidex.Infrastructure.Orleans
{
public sealed class LoggingFilter : IIncomingGrainCallFilter
{
private readonly ISemanticLog log;
public LoggingFilter(ISemanticLog log)
{
Guard.NotNull(log, nameof(log));
this.log = log;
}
public async Task Invoke(IIncomingGrainCallContext context)
{
try
{
await context.Invoke();
}
catch (DomainException)
{
throw;
}
catch (Exception ex)
{
log.LogError(ex, w => w
.WriteProperty("action", "GrainInvoked")
.WriteProperty("status", "Failed")
.WriteProperty("grain", context.Grain.ToString())
.WriteProperty("grainMethod", context.ImplementationMethod.ToString()));
throw;
}
}
}
}

21
src/Squidex.Infrastructure/ValidationException.cs

@ -78,18 +78,21 @@ namespace Squidex.Infrastructure
for (var i = 0; i < errors.Count; i++)
{
var error = errors[i].Message;
var error = errors[i]?.Message;
sb.Append(error);
if (!error.EndsWith(".", StringComparison.OrdinalIgnoreCase))
if (!string.IsNullOrWhiteSpace(error))
{
sb.Append(".");
}
sb.Append(error);
if (i < errors.Count - 1)
{
sb.Append(" ");
if (!error.EndsWith(".", StringComparison.OrdinalIgnoreCase))
{
sb.Append(".");
}
if (i < errors.Count - 1)
{
sb.Append(" ");
}
}
}
}

90
src/Squidex/Areas/Frontend/Middlewares/WebpackMiddleware.cs

@ -5,8 +5,8 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
@ -15,10 +15,7 @@ namespace Squidex.Areas.Frontend.Middlewares
{
public sealed class WebpackMiddleware
{
private const string Host = "localhost";
private const string Port = "3000";
private static readonly string[] Scripts = { "shims", "app" };
private static readonly string[] Styles = Array.Empty<string>();
private const string WebpackUrl = "http://localhost:3000/index.html";
private readonly RequestDelegate next;
public WebpackMiddleware(RequestDelegate next)
@ -28,7 +25,28 @@ namespace Squidex.Areas.Frontend.Middlewares
public async Task Invoke(HttpContext context)
{
if (context.IsHtmlPath())
if (context.IsIndex())
{
if (context.Response.StatusCode != 304)
{
using (var client = new HttpClient())
{
var result = await client.GetAsync(WebpackUrl);
context.Response.StatusCode = (int)result.StatusCode;
if (result.IsSuccessStatusCode)
{
var html = await result.Content.ReadAsStringAsync();
html = AdjustBase(html, context.Request.PathBase);
await context.Response.WriteHtmlAsync(html);
}
}
}
}
else if (context.IsHtmlPath())
{
var responseBuffer = new MemoryStream();
var responseBody = context.Response.Body;
@ -39,25 +57,14 @@ namespace Squidex.Areas.Frontend.Middlewares
context.Response.Body = responseBody;
var response = Encoding.UTF8.GetString(responseBuffer.ToArray());
var html = Encoding.UTF8.GetString(responseBuffer.ToArray());
if (context.IsIndex())
{
response = InjectStyles(response);
response = InjectScripts(response);
}
var basePath = context.Request.PathBase;
if (basePath.HasValue)
{
response = AdjustBase(response, basePath.Value);
}
html = AdjustBase(html, context.Request.PathBase);
context.Response.ContentLength = Encoding.UTF8.GetByteCount(response);
context.Response.ContentLength = Encoding.UTF8.GetByteCount(html);
context.Response.Body = responseBody;
await context.Response.WriteAsync(response);
await context.Response.WriteAsync(html);
}
else
{
@ -65,47 +72,16 @@ namespace Squidex.Areas.Frontend.Middlewares
}
}
private static string InjectStyles(string response)
private static string AdjustBase(string html, PathString baseUrl)
{
if (!response.Contains("</head>"))
if (baseUrl.HasValue)
{
return response;
return html.Replace("<base href=\"/\">", $"<base href=\"{baseUrl}/\">");
}
var sb = new StringBuilder();
foreach (var file in Styles)
{
sb.AppendLine($"<link href=\"http://{Host}:{Port}/{file}.css\" rel=\"stylesheet\">");
}
response = response.Replace("</head>", $"{sb}</head>");
return response;
}
private static string InjectScripts(string response)
{
if (!response.Contains("</body>"))
{
return response;
}
var sb = new StringBuilder();
foreach (var file in Scripts)
else
{
sb.AppendLine($"<script type=\"text/javascript\" src=\"http://{Host}:{Port}/{file}.js\"></script>");
return html;
}
response = response.Replace("</body>", $"{sb}</body>");
return response;
}
private static string AdjustBase(string response, string baseUrl)
{
return response.Replace("<base href=\"/\">", $"<base href=\"{baseUrl}/\">");
}
}
}

2
src/Squidex/Config/Logging.cs

@ -14,7 +14,7 @@ namespace Squidex.Config
{
public static class Logging
{
public static void AddFilter(this ILoggingBuilder builder)
public static void AddFilters(this ILoggingBuilder builder)
{
builder.AddFilter((category, level) =>
{

6
src/Squidex/Config/Orleans/OrleansServices.cs

@ -28,9 +28,15 @@ namespace Squidex.Config.Orleans
{
services.AddOrleans(config, environment, builder =>
{
builder.ConfigureLogging(logging =>
{
logging.AddFilters();
});
builder.ConfigureServices(siloServices =>
{
siloServices.AddSingleton<IIncomingGrainCallFilter, LocalCacheFilter>();
siloServices.AddSingleton<IIncomingGrainCallFilter, LoggingFilter>();
});
builder.ConfigureApplicationParts(parts =>

2
src/Squidex/Program.cs

@ -31,7 +31,7 @@ namespace Squidex
{
builder.AddConfiguration(hostingContext.Configuration.GetSection("logging"));
builder.AddSemanticLog();
builder.AddFilter();
builder.AddFilters();
})
.ConfigureAppConfiguration((hostContext, builder) =>
{

38
tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs

@ -38,7 +38,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("unknown: Not a known field.", "unknown")
new ValidationError("Not a known field.", "unknown")
});
}
@ -59,7 +59,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field: Must be less or equal to '100'.", "my-field")
new ValidationError("Must be less or equal to '100'.", "my-field")
});
}
@ -80,8 +80,8 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field(es): Not a known invariant value.", "my-field(es)"),
new ValidationError("my-field(it): Not a known invariant value.", "my-field(it)")
new ValidationError("Not a known invariant value.", "my-field(es)"),
new ValidationError("Not a known invariant value.", "my-field(it)")
});
}
@ -99,8 +99,8 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field(de): Field is required.", "my-field(de)"),
new ValidationError("my-field(en): Field is required.", "my-field(en)")
new ValidationError("Field is required.", "my-field(de)"),
new ValidationError("Field is required.", "my-field(en)")
});
}
@ -118,7 +118,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field: Field is required.", "my-field")
new ValidationError("Field is required.", "my-field")
});
}
@ -139,7 +139,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field(xx): Not a known language.", "my-field(xx)")
new ValidationError("Not a known language.", "my-field(xx)")
});
}
@ -182,8 +182,8 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field(es): Not a known language.", "my-field(es)"),
new ValidationError("my-field(it): Not a known language.", "my-field(it)")
new ValidationError("Not a known language.", "my-field(es)"),
new ValidationError("Not a known language.", "my-field(it)")
});
}
@ -200,7 +200,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("unknown: Not a known field.", "unknown")
new ValidationError("Not a known field.", "unknown")
});
}
@ -221,7 +221,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field: Must be less or equal to '100'.", "my-field")
new ValidationError("Must be less or equal to '100'.", "my-field")
});
}
@ -242,8 +242,8 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field(es): Not a known invariant value.", "my-field(es)"),
new ValidationError("my-field(it): Not a known invariant value.", "my-field(it)")
new ValidationError("Not a known invariant value.", "my-field(es)"),
new ValidationError("Not a known invariant value.", "my-field(it)")
});
}
@ -292,7 +292,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field(xx): Not a known language.", "my-field(xx)")
new ValidationError("Not a known language.", "my-field(xx)")
});
}
@ -313,8 +313,8 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field(es): Not a known language.", "my-field(es)"),
new ValidationError("my-field(it): Not a known language.", "my-field(it)")
new ValidationError("Not a known language.", "my-field(es)"),
new ValidationError("Not a known language.", "my-field(it)")
});
}
@ -340,8 +340,8 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field[1].my-nested: Field is required.", "my-field[1].my-nested"),
new ValidationError("my-field[3].my-nested: Field is required.", "my-field[3].my-nested")
new ValidationError("Field is required.", "my-field[1].my-nested"),
new ValidationError("Field is required.", "my-field[3].my-nested")
});
}
}

61
tests/Squidex.Infrastructure.Tests/Orleans/LoggingFilterTests.cs

@ -0,0 +1,61 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using FakeItEasy;
using Orleans;
using Squidex.Infrastructure.Log;
using Xunit;
namespace Squidex.Infrastructure.Orleans
{
public class LoggingFilterTests
{
private readonly ISemanticLog log = A.Fake<ISemanticLog>();
private readonly IIncomingGrainCallContext context = A.Fake<IIncomingGrainCallContext>();
private readonly LoggingFilter sut;
public LoggingFilterTests()
{
sut = new LoggingFilter(log);
}
[Fact]
public async Task Should_not_log_if_no_exception_happened()
{
await sut.Invoke(context);
A.CallTo(() => log.Log(A<SemanticLogLevel>.Ignored, A<None>.Ignored, A<Action<None, IObjectWriter>>.Ignored))
.MustNotHaveHappened();
}
[Fact]
public async Task Should_not_log_domain_exceptions()
{
A.CallTo(() => context.Invoke())
.Throws(new ValidationException("Failed"));
await Assert.ThrowsAsync<ValidationException>(() => sut.Invoke(context));
A.CallTo(() => log.Log(A<SemanticLogLevel>.Ignored, A<None>.Ignored, A<Action<None, IObjectWriter>>.Ignored))
.MustNotHaveHappened();
}
[Fact]
public async Task Should_log_exception_and_forward_it()
{
A.CallTo(() => context.Invoke())
.Throws(new InvalidOperationException());
await Assert.ThrowsAsync<InvalidOperationException>(() => sut.Invoke(context));
A.CallTo(() => log.Log(A<SemanticLogLevel>.Ignored, A<None>.Ignored, A<Action<None, IObjectWriter>>.Ignored))
.MustHaveHappened();
}
}
}

12
tests/Squidex.Infrastructure.Tests/ValidationExceptionTests.cs

@ -53,6 +53,18 @@ namespace Squidex.Infrastructure
Assert.Equal("Summary: Error1. Error2.", ex.Message);
}
[Fact]
public void Should_serialize_and_deserialize1()
{
var source = new ValidationException("Summary", new ValidationError("Error1"), null);
var result = source.SerializeAndDeserializeBinary();
result.Errors.Should().BeEquivalentTo(source.Errors);
Assert.Equal(source.Message, result.Message);
Assert.Equal(source.Summary, result.Summary);
}
[Fact]
public void Should_serialize_and_deserialize()
{

Loading…
Cancel
Save