Browse Source

Merge branch 'master' of github.com:Squidex/squidex

pull/400/head
Sebastian Stehle 6 years ago
parent
commit
579c06e515
  1. 41
      src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs
  2. 11
      src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs
  3. 50
      src/Squidex/Areas/Api/Config/OpenApi/CommonProcessor.cs
  4. 10
      src/Squidex/Areas/Api/Config/OpenApi/ODataExtensions.cs
  5. 2
      src/Squidex/Areas/Api/Config/OpenApi/ODataQueryParamsProcessor.cs
  6. 5
      src/Squidex/Areas/Api/Config/OpenApi/OpenApiServices.cs
  7. 7
      src/Squidex/Areas/Api/Config/OpenApi/SecurityProcessor.cs
  8. 37
      src/Squidex/Areas/Api/Config/OpenApi/ThemeProcessor.cs
  9. 31
      src/Squidex/Areas/Api/Config/OpenApi/VersionProcessor.cs
  10. 104
      src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaOpenApiGenerator.cs
  11. 3
      src/Squidex/Config/Domain/EntitiesServices.cs
  12. 2
      src/Squidex/Docs/schemabody.md
  13. 6
      src/Squidex/Docs/security.md
  14. 21
      src/Squidex/Pipeline/OpenApi/NSwagHelper.cs
  15. 58
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs

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

@ -0,0 +1,41 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Orleans;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Apps
{
public sealed class AppCommandMiddleware : GrainCommandMiddleware<AppCommand, IAppGrain>
{
private readonly IContextProvider contextProvider;
public AppCommandMiddleware(IGrainFactory grainFactory, IContextProvider contextProvider)
: base(grainFactory)
{
Guard.NotNull(contextProvider, nameof(contextProvider));
this.contextProvider = contextProvider;
}
public override async Task HandleAsync(CommandContext context, Func<Task> next)
{
await ExecuteCommandAsync(context);
if (context.PlainResult is IAppEntity app)
{
contextProvider.Context.App = app;
}
await next();
}
}
}

11
src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs

@ -23,6 +23,13 @@ namespace Squidex.Infrastructure.Commands
}
public virtual async Task HandleAsync(CommandContext context, Func<Task> next)
{
await ExecuteCommandAsync(context);
await next();
}
protected async Task ExecuteCommandAsync(CommandContext context)
{
if (context.Command is TCommand typedCommand)
{
@ -30,11 +37,9 @@ namespace Squidex.Infrastructure.Commands
context.Complete(result);
}
await next();
}
protected async Task<object> ExecuteCommandAsync(TCommand typedCommand)
private async Task<object> ExecuteCommandAsync(TCommand typedCommand)
{
var grain = grainFactory.GetGrain<TGrain>(typedCommand.AggregateId);

50
src/Squidex/Areas/Api/Config/OpenApi/CommonProcessor.cs

@ -0,0 +1,50 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Microsoft.Extensions.Options;
using NSwag;
using NSwag.Generation.Processors;
using NSwag.Generation.Processors.Contexts;
using Squidex.Web;
namespace Squidex.Areas.Api.Config.OpenApi
{
public sealed class CommonProcessor : IDocumentProcessor
{
private readonly string version;
private readonly string backgroundColor = "#3f83df";
private readonly string logoUrl;
private readonly OpenApiExternalDocumentation documentation = new OpenApiExternalDocumentation
{
Url = "https://docs.squidex.io"
};
public CommonProcessor(ExposedValues exposedValues, IOptions<UrlsOptions> urlOptions)
{
logoUrl = urlOptions.Value.BuildUrl("images/logo-white.png", false);
if (!exposedValues.TryGetValue("version", out version))
{
version = "1.0";
}
}
public void Process(DocumentProcessorContext context)
{
context.Document.BasePath = Constants.ApiPrefix;
context.Document.Info.Version = version;
context.Document.Info.ExtensionData = new Dictionary<string, object>
{
["x-logo"] = new { url = logoUrl, backgroundColor }
};
context.Document.ExternalDocumentation = documentation;
}
}
}

10
src/Squidex/Areas/Api/Config/OpenApi/ODataExtensions.cs

@ -17,13 +17,13 @@ namespace Squidex.Areas.Api.Config.OpenApi
{
if (supportSearch)
{
operation.AddQueryParameter("$search", JsonObjectType.String, "Optional OData full text search.");
operation.AddQuery("$search", JsonObjectType.String, "Optional OData full text search.");
}
operation.AddQueryParameter("$top", JsonObjectType.Number, $"Optional number of {entity} to take.");
operation.AddQueryParameter("$skip", JsonObjectType.Number, $"Optional number of {entity} to skip.");
operation.AddQueryParameter("$orderby", JsonObjectType.String, "Optional OData order definition.");
operation.AddQueryParameter("$filter", JsonObjectType.String, "Optional OData filter definition.");
operation.AddQuery("$top", JsonObjectType.Number, $"Optional number of {entity} to take.");
operation.AddQuery("$skip", JsonObjectType.Number, $"Optional number of {entity} to skip.");
operation.AddQuery("$orderby", JsonObjectType.String, "Optional OData order definition.");
operation.AddQuery("$filter", JsonObjectType.String, "Optional OData filter definition.");
}
}
}

2
src/Squidex/Areas/Api/Config/OpenApi/ODataQueryParamsProcessor.cs

@ -5,10 +5,8 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using NJsonSchema;
using NSwag.Generation.Processors;
using NSwag.Generation.Processors.Contexts;
using Squidex.Pipeline.OpenApi;
namespace Squidex.Areas.Api.Config.OpenApi
{

5
src/Squidex/Areas/Api/Config/OpenApi/OpenApiServices.cs

@ -29,10 +29,7 @@ namespace Squidex.Areas.Api.Config.OpenApi
services.AddSingletonAs<RuleActionProcessor>()
.As<IDocumentProcessor>();
services.AddSingletonAs<ThemeProcessor>()
.As<IDocumentProcessor>();
services.AddSingletonAs<VersionProcessor>()
services.AddSingletonAs<CommonProcessor>()
.As<IDocumentProcessor>();
services.AddSingletonAs<XmlTagProcessor>()

7
src/Squidex/Areas/Api/Config/OpenApi/SecurityProcessor.cs

@ -35,7 +35,7 @@ namespace Squidex.Areas.Api.Config.OpenApi
SetupDescription(security, tokenUrl);
SetupFlow(security);
SetupScropes(security);
SetupScopes(security);
return security;
}
@ -45,7 +45,7 @@ namespace Squidex.Areas.Api.Config.OpenApi
security.Flow = OpenApiOAuth2Flow.Application;
}
private static void SetupScropes(OpenApiSecurityScheme security)
private static void SetupScopes(OpenApiSecurityScheme security)
{
security.Scopes = new Dictionary<string, string>
{
@ -55,8 +55,7 @@ namespace Squidex.Areas.Api.Config.OpenApi
private static void SetupDescription(OpenApiSecurityScheme securityScheme, string tokenUrl)
{
var securityDocs = NSwagHelper.LoadDocs("security");
var securityText = securityDocs.Replace("<TOKEN_URL>", tokenUrl);
var securityText = NSwagHelper.SecurityDocs.Replace("<TOKEN_URL>", tokenUrl);
securityScheme.Description = securityText;
}

37
src/Squidex/Areas/Api/Config/OpenApi/ThemeProcessor.cs

@ -1,37 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Microsoft.Extensions.Options;
using NSwag.Generation.Processors;
using NSwag.Generation.Processors.Contexts;
using Squidex.Web;
namespace Squidex.Areas.Api.Config.OpenApi
{
public sealed class ThemeProcessor : IDocumentProcessor
{
private const string Background = "#3f83df";
private readonly string url;
public ThemeProcessor(IOptions<UrlsOptions> urlOptions)
{
url = urlOptions.Value.BuildUrl("images/logo-white.png", false);
}
public void Process(DocumentProcessorContext context)
{
context.Document.BasePath = Constants.ApiPrefix;
context.Document.Info.ExtensionData = new Dictionary<string, object>
{
["x-logo"] = new { url, backgroundColor = Background }
};
}
}
}

31
src/Squidex/Areas/Api/Config/OpenApi/VersionProcessor.cs

@ -1,31 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using NSwag.Generation.Processors;
using NSwag.Generation.Processors.Contexts;
using Squidex.Web;
namespace Squidex.Areas.Api.Config.OpenApi
{
public sealed class VersionProcessor : IDocumentProcessor
{
private readonly ExposedValues exposedValues;
public VersionProcessor(ExposedValues exposedValues)
{
this.exposedValues = exposedValues;
}
public void Process(DocumentProcessorContext context)
{
if (exposedValues.TryGetValue("version", out var version))
{
context.Document.Info.Version = version;
}
}
}
}

104
src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaOpenApiGenerator.cs

@ -23,8 +23,6 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
{
public sealed class SchemaOpenApiGenerator
{
private static readonly string SchemaQueryDescription;
private static readonly string SchemaBodyDescription;
private readonly ContentSchemaBuilder schemaBuilder = new ContentSchemaBuilder();
private readonly OpenApiDocument document;
private readonly JsonSchema contentSchema;
@ -36,12 +34,6 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
private readonly JsonSchema statusSchema;
private readonly string appName;
static SchemaOpenApiGenerator()
{
SchemaBodyDescription = NSwagHelper.LoadDocs("schemabody");
SchemaQueryDescription = NSwagHelper.LoadDocs("schemaquery");
}
public SchemaOpenApiGenerator(
OpenApiDocument document,
string appName,
@ -72,10 +64,10 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
document.Tags.Add(
new OpenApiTag
{
Name = schemaName, Description = $"API to managed {schemaName} contents."
Name = schemaName, Description = $"API to manage {schemaName} contents."
});
var schemaOperations = new List<OpenApiPathItem>
var schemaOperations = new[]
{
GenerateSchemaGetsOperation(),
GenerateSchemaGetOperation(),
@ -95,109 +87,103 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
private OpenApiPathItem GenerateSchemaGetsOperation()
{
return AddOperation(OpenApiOperationMethod.Get, null, $"{appPath}/{schemaPath}", operation =>
return Add(OpenApiOperationMethod.Get, Permissions.AppContentsRead, "/",
operation =>
{
operation.OperationId = $"Query{schemaType}Contents";
operation.Summary = $"Queries {schemaName} contents.";
operation.Description = SchemaQueryDescription;
operation.Description = NSwagHelper.SchemaQueryDocs;
operation.AddOData("contents", true);
operation.AddResponse("200", $"{schemaName} contents retrieved.", CreateContentsSchema(schemaName, contentSchema));
operation.AddResponse("400", $"{schemaName} query not valid.");
AddSecurity(operation, Permissions.AppContentsRead);
});
}
private OpenApiPathItem GenerateSchemaGetOperation()
{
return AddOperation(OpenApiOperationMethod.Get, schemaName, $"{appPath}/{schemaPath}/{{id}}", operation =>
return Add(OpenApiOperationMethod.Get, Permissions.AppContentsRead, "/{id}", operation =>
{
operation.OperationId = $"Get{schemaType}Content";
operation.Summary = $"Get a {schemaName} content.";
operation.AddResponse("200", $"{schemaName} content found.", contentSchema);
AddSecurity(operation, Permissions.AppContentsRead);
});
}
private OpenApiPathItem GenerateSchemaCreateOperation()
{
return AddOperation(OpenApiOperationMethod.Post, null, $"{appPath}/{schemaPath}", operation =>
return Add(OpenApiOperationMethod.Post, Permissions.AppContentsCreate, "/",
operation =>
{
operation.OperationId = $"Create{schemaType}Content";
operation.Summary = $"Create a {schemaName} content.";
operation.AddBodyParameter("data", dataSchema, SchemaBodyDescription);
operation.AddQueryParameter("publish", JsonObjectType.Boolean, "Set to true to autopublish content.");
operation.AddBody("data", dataSchema, NSwagHelper.SchemaBodyDocs);
operation.AddQuery("publish", JsonObjectType.Boolean, "Set to true to autopublish content.");
operation.AddResponse("201", $"{schemaName} content created.", contentSchema);
operation.AddResponse("400", $"{schemaName} content not valid.");
AddSecurity(operation, Permissions.AppContentsCreate);
});
}
private OpenApiPathItem GenerateSchemaUpdateOperation()
{
return AddOperation(OpenApiOperationMethod.Put, schemaName, $"{appPath}/{schemaPath}/{{id}}", operation =>
return Add(OpenApiOperationMethod.Put, Permissions.AppContentsUpdate, "/{id}",
operation =>
{
operation.OperationId = $"Update{schemaType}Content";
operation.Summary = $"Update a {schemaName} content.";
operation.AddBodyParameter("data", dataSchema, SchemaBodyDescription);
operation.AddBody("data", dataSchema, NSwagHelper.SchemaBodyDocs);
operation.AddResponse("200", $"{schemaName} content updated.", contentSchema);
operation.AddResponse("400", $"{schemaName} content not valid.");
AddSecurity(operation, Permissions.AppContentsUpdate);
});
}
private OpenApiPathItem GenerateSchemaUpdatePatchOperation()
{
return AddOperation(OpenApiOperationMethod.Patch, schemaName, $"{appPath}/{schemaPath}/{{id}}", operation =>
return Add(OpenApiOperationMethod.Patch, Permissions.AppContentsUpdate, "/{id}",
operation =>
{
operation.OperationId = $"Path{schemaType}Content";
operation.Summary = $"Patch a {schemaName} content.";
operation.AddBodyParameter("data", dataSchema, SchemaBodyDescription);
operation.AddBody("data", dataSchema, NSwagHelper.SchemaBodyDocs);
operation.AddResponse("200", $"{schemaName} content patched.", contentSchema);
operation.AddResponse("400", $"{schemaName} status not valid.");
AddSecurity(operation, Permissions.AppContentsUpdate);
});
}
private OpenApiPathItem GenerateSchemaStatusOperation()
{
return AddOperation(OpenApiOperationMethod.Put, schemaName, $"{appPath}/{schemaPath}/{{id}}/status", operation =>
return Add(OpenApiOperationMethod.Put, Permissions.AppContentsUpdate, "/{id}/status",
operation =>
{
operation.OperationId = $"Change{schemaType}ContentStatus";
operation.Summary = $"Change status of {schemaName} content.";
operation.AddBodyParameter("request", statusSchema, "The request to change content status.");
operation.AddBody("request", statusSchema, "The request to change content status.");
operation.AddResponse("204", $"{schemaName} content status changed.", contentSchema);
operation.AddResponse("200", $"{schemaName} content status changed.", contentSchema);
operation.AddResponse("400", $"{schemaName} content not valid.");
AddSecurity(operation, Permissions.AppContentsUpdate);
});
}
private OpenApiPathItem GenerateSchemaDiscardOperation()
{
return AddOperation(OpenApiOperationMethod.Put, schemaName, $"{appPath}/{schemaPath}/{{id}}/discard", operation =>
return Add(OpenApiOperationMethod.Put, Permissions.AppContentsDraftDiscard, "/{id}/discard",
operation =>
{
operation.OperationId = $"Discard{schemaType}Content";
@ -205,39 +191,48 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
operation.AddResponse("200", $"{schemaName} content status changed.", contentSchema);
operation.AddResponse("400", $"{schemaName} content has no pending draft.");
AddSecurity(operation, Permissions.AppContentsDraftDiscard);
});
}
private OpenApiPathItem GenerateSchemaDeleteOperation()
{
return AddOperation(OpenApiOperationMethod.Delete, schemaName, $"{appPath}/{schemaPath}/{{id}}/", operation =>
return Add(OpenApiOperationMethod.Delete, Permissions.AppContentsDelete, "/{id}",
operation =>
{
operation.OperationId = $"Delete{schemaType}Content";
operation.Summary = $"Delete a {schemaName} content.";
operation.AddResponse("204", $"{schemaName} content deleted.");
AddSecurity(operation, Permissions.AppContentsDelete);
});
}
private OpenApiPathItem AddOperation(string method, string entityName, string path, Action<OpenApiOperation> updater)
private OpenApiPathItem Add(string method, string permission, string path, Action<OpenApiOperation> updater)
{
var operations = document.Paths.GetOrAddNew(path);
var operation = new OpenApiOperation();
var operations = document.Paths.GetOrAddNew($"{appPath}/{schemaPath}{path}");
var operation = new OpenApiOperation
{
Security = new List<OpenApiSecurityRequirement>
{
new OpenApiSecurityRequirement
{
[Constants.SecurityDefinition] = new[]
{
Permissions.ForApp(permission, appName, schemaPath).Id
}
}
}
};
updater(operation);
operations[method] = operation;
if (entityName != null)
if (path.StartsWith("/{id}", StringComparison.OrdinalIgnoreCase))
{
operation.AddPathParameter("id", JsonObjectType.String, $"The id of the {entityName} content (GUID).");
operation.AddPathParameter("id", JsonObjectType.String, $"The id of the {schemaName} content.", JsonFormatStrings.Guid);
operation.AddResponse("404", $"App, schema or {entityName} content not found.");
operation.AddResponse("404", $"App, schema or {schemaName} content not found.");
}
return operations;
@ -255,7 +250,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
},
["items"] = new JsonSchemaProperty
{
Type = JsonObjectType.Array, IsRequired = true, Item = contentSchema, Description = $"The {schemaName} contents."
Type = JsonObjectType.Array, IsRequired = true, Description = $"The {schemaName} contents.", Item = contentSchema
}
},
Type = JsonObjectType.Object
@ -263,18 +258,5 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
return schema;
}
private void AddSecurity(OpenApiOperation operation, string permission)
{
if (operation.Security == null)
{
operation.Security = new List<OpenApiSecurityRequirement>();
}
operation.Security.Add(new OpenApiSecurityRequirement
{
[Constants.SecurityDefinition] = new[] { Permissions.ForApp(permission, appName, schemaPath).Id }
});
}
}
}

3
src/Squidex/Config/Domain/EntitiesServices.cs

@ -22,7 +22,6 @@ using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Indexes;
using Squidex.Domain.Apps.Entities.Apps.Invitation;
using Squidex.Domain.Apps.Entities.Apps.Templates;
@ -246,7 +245,7 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<AppsByNameIndexCommandMiddleware>()
.As<ICommandMiddleware>();
services.AddSingletonAs<GrainCommandMiddleware<AppCommand, IAppGrain>>()
services.AddSingletonAs<AppCommandMiddleware>()
.As<ICommandMiddleware>();
services.AddSingletonAs<GrainCommandMiddleware<CommentsCommand, ICommentGrain>>()

2
src/Squidex/Docs/schemabody.md

@ -1,6 +1,6 @@
The data of the content to be created or updated.
Please note that each field is an object with one entry per language.
If the field is not localizable you must use `iv` (Invariant Language) as a key.
If the field is not localizable you must use `iv` (invariant language) as a key.
Read more about it at: https://docs.squidex.io/04-guides/02-api.html

6
src/Squidex/Docs/security.md

@ -10,4 +10,8 @@ To retrieve an access token, the client id must make a request to the token url.
client_secret=[CLIENT_SECRET]&
scope=squidex-api'
[APP_NAME] is the name of your app. You have to create a client to generate an access token.
`[APP_NAME]` is the name of your app. You have to create a client to generate an access token.
You must send this token in the `Authorization` header when making requests to the API:
Authorization: Bearer <token>

21
src/Squidex/Pipeline/OpenApi/NSwagHelper.cs

@ -16,7 +16,13 @@ namespace Squidex.Pipeline.OpenApi
{
public static class NSwagHelper
{
public static string LoadDocs(string name)
public static readonly string SecurityDocs = LoadDocs("security");
public static readonly string SchemaBodyDocs = LoadDocs("schemabody");
public static readonly string SchemaQueryDocs = LoadDocs("schemaquery");
private static string LoadDocs(string name)
{
var assembly = typeof(NSwagHelper).Assembly;
@ -65,21 +71,21 @@ namespace Squidex.Pipeline.OpenApi
return document;
}
public static void AddQueryParameter(this OpenApiOperation operation, string name, JsonObjectType type, string description = null)
public static void AddQuery(this OpenApiOperation operation, string name, JsonObjectType type, string description)
{
var schema = new JsonSchema { Type = type };
operation.AddParameter(name, schema, OpenApiParameterKind.Query, description, false);
}
public static void AddPathParameter(this OpenApiOperation operation, string name, JsonObjectType type, string description = null)
public static void AddPathParameter(this OpenApiOperation operation, string name, JsonObjectType type, string description, string format = null)
{
var schema = new JsonSchema { Type = type };
var schema = new JsonSchema { Type = type, Format = format };
operation.AddParameter(name, schema, OpenApiParameterKind.Path, description, true);
}
public static void AddBodyParameter(this OpenApiOperation operation, string name, JsonSchema schema, string description)
public static void AddBody(this OpenApiOperation operation, string name, JsonSchema schema, string description)
{
operation.AddParameter(name, schema, OpenApiParameterKind.Body, description, true);
}
@ -93,10 +99,7 @@ namespace Squidex.Pipeline.OpenApi
parameter.Description = description;
}
if (isRequired)
{
parameter.IsRequired = true;
}
parameter.IsRequired = isRequired;
operation.Parameters.Add(parameter);
}

58
tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs

@ -0,0 +1,58 @@
// ==========================================================================
// 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.Domain.Apps.Entities.Apps.State;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure.Commands;
using Xunit;
namespace Squidex.Domain.Apps.Entities.Apps
{
public class AppCommandMiddlewareTests : HandlerTestBase<AppState>
{
private readonly IContextProvider contextProvider = A.Fake<IContextProvider>();
private readonly Guid appId = Guid.NewGuid();
private readonly Context requestContext = new Context();
private readonly AppCommandMiddleware sut;
public sealed class MyCommand : SquidexCommand
{
}
protected override Guid Id
{
get { return appId; }
}
public AppCommandMiddlewareTests()
{
A.CallTo(() => contextProvider.Context)
.Returns(requestContext);
sut = new AppCommandMiddleware(A.Fake<IGrainFactory>(), contextProvider);
}
[Fact]
public async Task Should_replace_context_app_with_grain_result()
{
var result = A.Fake<IAppEntity>();
var command = CreateCommand(new MyCommand());
var context = CreateContextForCommand(command);
context.Complete(result);
await sut.HandleAsync(context);
Assert.Same(result, requestContext.App);
}
}
}
Loading…
Cancel
Save