Browse Source

More unit tests

pull/1/head
Sebastian 9 years ago
parent
commit
f91324ab75
  1. 20
      src/Squidex.Core/Contents/ContentData.cs
  2. 7
      src/Squidex.Core/Contents/ContentFieldData.cs
  3. 2
      src/Squidex.Core/Schemas/Schema.cs
  4. 2
      src/Squidex.Infrastructure/CQRS/Commands/AggregateHandler.cs
  5. 5
      src/Squidex.Infrastructure/CQRS/Commands/LogExceptionHandler.cs
  6. 6
      src/Squidex.Infrastructure/CQRS/Events/EnrichWithActorProcessor.cs
  7. 2
      src/Squidex.Infrastructure/InfrastructureErrors.cs
  8. 6
      src/Squidex.Read/Contents/IContentEntity.cs
  9. 10
      src/Squidex.Read/Contents/Repositories/IContentRepository.cs
  10. 99
      src/Squidex.Store.MongoDb/Contents/MongoContentEntity.cs
  11. 214
      src/Squidex.Store.MongoDb/Contents/MongoContentRepository.cs
  12. 8
      src/Squidex.Store.MongoDb/MongoDbModule.cs
  13. 42
      src/Squidex.Write/EnrichWithSchemaIdProcessor.cs
  14. 17
      src/Squidex/Config/Domain/WriteModule.cs
  15. 140
      src/Squidex/Controllers/ContentApi/ContentsController.cs
  16. 2
      src/Squidex/Controllers/ContentApi/Generator/SchemasSwaggerGenerator.cs
  17. 9
      src/Squidex/Controllers/ContentApi/Models/ContentDto.cs
  18. 23
      src/Squidex/Controllers/ContentApi/Models/ContentsDto.cs
  19. 5
      src/Squidex/Controllers/ControllerBase.cs
  20. 23
      src/Squidex/app/features/content/pages/content/content-page.component.html
  21. 29
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  22. 1
      src/Squidex/app/shared/declarations.ts
  23. 2
      src/Squidex/app/shared/module.ts
  24. 106
      src/Squidex/app/shared/services/contents.service.ts
  25. 0
      src/Squidex/npm-debug.log.3790864127
  26. 55
      tests/Squidex.Core.Tests/Contents/ContentDataTests.cs
  27. 2
      tests/Squidex.Core.Tests/Schemas/SchemaTests.cs
  28. 24
      tests/Squidex.Core.Tests/Schemas/SchemaValidationTests.cs
  29. 19
      tests/Squidex.Infrastructure.Tests/CQRS/Commands/LogExceptionHandlerTests.cs
  30. 8
      tests/Squidex.Write.Tests/Contents/ContentCommandHandlerTests.cs
  31. 2
      tests/Squidex.Write.Tests/Contents/ContentDomainObjectTests.cs
  32. 72
      tests/Squidex.Write.Tests/EnrichWithSchemaIdProcessorTests.cs

20
src/Squidex.Core/Contents/ContentData.cs

@ -7,7 +7,10 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Core.Contents namespace Squidex.Core.Contents
@ -16,6 +19,8 @@ namespace Squidex.Core.Contents
{ {
private readonly ImmutableDictionary<string, ContentFieldData> fields; private readonly ImmutableDictionary<string, ContentFieldData> fields;
public static readonly ContentData Empty = new ContentData(ImmutableDictionary<string, ContentFieldData>.Empty.WithComparers (StringComparer.OrdinalIgnoreCase));
public ImmutableDictionary<string, ContentFieldData> Fields public ImmutableDictionary<string, ContentFieldData> Fields
{ {
get { return fields; } get { return fields; }
@ -28,16 +33,21 @@ namespace Squidex.Core.Contents
this.fields = fields; this.fields = fields;
} }
public static ContentData Empty()
{
return new ContentData(ImmutableDictionary<string, ContentFieldData>.Empty.WithComparers(StringComparer.OrdinalIgnoreCase));
}
public ContentData AddField(string fieldName, ContentFieldData data) public ContentData AddField(string fieldName, ContentFieldData data)
{ {
Guard.ValidPropertyName(fieldName, nameof(fieldName)); Guard.ValidPropertyName(fieldName, nameof(fieldName));
return new ContentData(Fields.Add(fieldName, data)); return new ContentData(Fields.Add(fieldName, data));
} }
public static ContentData Create(Dictionary<string, Dictionary<string, JToken>> raw)
{
return new ContentData(raw.ToImmutableDictionary(x => x.Key, x => new ContentFieldData(x.Value.ToImmutableDictionary(StringComparer.OrdinalIgnoreCase)), StringComparer.OrdinalIgnoreCase));
}
public Dictionary<string, Dictionary<string, JToken>> ToRaw()
{
return fields.ToDictionary(x => x.Key, x => x.Value.ValueByLanguage.ToDictionary(y => y.Key, y => y.Value));
}
} }
} }

7
src/Squidex.Core/Contents/ContentFieldData.cs

@ -17,6 +17,8 @@ namespace Squidex.Core.Contents
{ {
private readonly ImmutableDictionary<string, JToken> valueByLanguage; private readonly ImmutableDictionary<string, JToken> valueByLanguage;
public static readonly ContentFieldData Empty = new ContentFieldData(ImmutableDictionary<string, JToken>.Empty.WithComparers(StringComparer.OrdinalIgnoreCase));
public ImmutableDictionary<string, JToken> ValueByLanguage public ImmutableDictionary<string, JToken> ValueByLanguage
{ {
get { return valueByLanguage; } get { return valueByLanguage; }
@ -29,11 +31,6 @@ namespace Squidex.Core.Contents
this.valueByLanguage = valueByLanguage; this.valueByLanguage = valueByLanguage;
} }
public static ContentFieldData New()
{
return new ContentFieldData(ImmutableDictionary<string, JToken>.Empty.WithComparers(StringComparer.OrdinalIgnoreCase));
}
public ContentFieldData AddValue(JToken value) public ContentFieldData AddValue(JToken value)
{ {
return new ContentFieldData(valueByLanguage.Add("iv", value)); return new ContentFieldData(valueByLanguage.Add("iv", value));

2
src/Squidex.Core/Schemas/Schema.cs

@ -201,7 +201,7 @@ namespace Squidex.Core.Schemas
{ {
var fieldErrors = new List<string>(); var fieldErrors = new List<string>();
var fieldData = data.Fields.GetOrDefault(field.Name) ?? ContentFieldData.New(); var fieldData = data.Fields.GetOrDefault(field.Name) ?? ContentFieldData.Empty;
if (field.RawProperties.IsLocalizable) if (field.RawProperties.IsLocalizable)
{ {

2
src/Squidex.Infrastructure/CQRS/Commands/AggregateHandler.cs

@ -48,6 +48,7 @@ namespace Squidex.Infrastructure.CQRS.Commands
{ {
Guard.NotNull(creator, nameof(creator)); Guard.NotNull(creator, nameof(creator));
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
Guard.NotEmpty(command.AggregateId, nameof(command.AggregateId));
var aggregate = domainObjectFactory.CreateNew<T>(command.AggregateId); var aggregate = domainObjectFactory.CreateNew<T>(command.AggregateId);
@ -60,6 +61,7 @@ namespace Squidex.Infrastructure.CQRS.Commands
{ {
Guard.NotNull(updater, nameof(updater)); Guard.NotNull(updater, nameof(updater));
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
Guard.NotEmpty(command.AggregateId, nameof(command.AggregateId));
var aggregate = await domainObjectRepository.GetByIdAsync<T>(command.AggregateId); var aggregate = await domainObjectRepository.GetByIdAsync<T>(command.AggregateId);

5
src/Squidex.Infrastructure/CQRS/Commands/LogExceptionHandler.cs

@ -31,6 +31,11 @@ namespace Squidex.Infrastructure.CQRS.Commands
logger.LogError(InfrastructureErrors.CommandFailed, exception, "Handling {0} command failed", context.Command); logger.LogError(InfrastructureErrors.CommandFailed, exception, "Handling {0} command failed", context.Command);
} }
if (!context.IsHandled)
{
logger.LogCritical(InfrastructureErrors.CommandUnknown, exception, "Unknown command {0}", context.Command);
}
return Task.FromResult(false); return Task.FromResult(false);
} }
} }

6
src/Squidex.Infrastructure/CQRS/Events/EnrichWithActorProcessor.cs

@ -16,11 +16,11 @@ namespace Squidex.Infrastructure.CQRS.Events
{ {
public Task ProcessEventAsync(Envelope<IEvent> @event, IAggregate aggregate, ICommand command) public Task ProcessEventAsync(Envelope<IEvent> @event, IAggregate aggregate, ICommand command)
{ {
var userCommand = command as IActorCommand; var actorCommand = command as IActorCommand;
if (userCommand != null) if (actorCommand != null)
{ {
@event.SetActor(userCommand.Actor); @event.SetActor(actorCommand.Actor);
} }
return TaskHelper.Done; return TaskHelper.Done;

2
src/Squidex.Infrastructure/InfrastructureErrors.cs

@ -12,6 +12,8 @@ namespace Squidex.Infrastructure
{ {
public class InfrastructureErrors public class InfrastructureErrors
{ {
public static readonly EventId CommandUnknown = new EventId(20000, "CommandUnknown");
public static readonly EventId CommandFailed = new EventId(20001, "CommandFailed"); public static readonly EventId CommandFailed = new EventId(20001, "CommandFailed");
public static readonly EventId EventHandlingFailed = new EventId(10001, "EventHandlingFailed"); public static readonly EventId EventHandlingFailed = new EventId(10001, "EventHandlingFailed");

6
src/Squidex.Read/Contents/IContentEntity.cs

@ -6,12 +6,14 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using Newtonsoft.Json.Linq; using Squidex.Core.Contents;
namespace Squidex.Read.Contents namespace Squidex.Read.Contents
{ {
public interface IContentEntity : IEntity public interface IContentEntity : IEntity
{ {
JToken Data { get; } bool IsPublished { get; }
ContentData Data { get; }
} }
} }

10
src/Squidex.Read/Contents/Repositories/IContentRepoitory.cs → src/Squidex.Read/Contents/Repositories/IContentRepository.cs

@ -1,5 +1,5 @@
// ========================================================================== // ==========================================================================
// IContentRepoitory.cs // IContentRepository.cs
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
@ -12,10 +12,12 @@ using System.Threading.Tasks;
namespace Squidex.Read.Contents.Repositories namespace Squidex.Read.Contents.Repositories
{ {
public interface IContentRepoitory public interface IContentRepository
{ {
Task<List<IContentEntity>> QueryAsync(); Task<List<IContentEntity>> QueryAsync(Guid schemaId, bool nonPublished, int? take, int? skip, string query);
Task<IContentEntity> FindContentAsync(Guid id); Task<long> CountAsync(Guid schemaId, bool nonPublished, string query);
Task<IContentEntity> FindContentAsync(Guid schemaId, Guid id);
} }
} }

99
src/Squidex.Store.MongoDb/Contents/MongoContentEntity.cs

@ -0,0 +1,99 @@
// ==========================================================================
// MongoContentEntity.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Linq;
using System.Text;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Core.Contents;
using Squidex.Infrastructure.MongoDb;
using Squidex.Read.Contents;
// ReSharper disable InvertIf
namespace Squidex.Store.MongoDb.Contents
{
public sealed class MongoContentEntity : MongoEntity, IContentEntity
{
private BsonDocument data;
private ContentData contentData;
[BsonRequired]
[BsonElement]
public bool IsDeleted { get; set; }
[BsonRequired]
[BsonElement]
public bool IsPublished { get; set; }
[BsonRequired]
[BsonElement]
public string Text { get; set; }
[BsonRequired]
[BsonElement]
public BsonDocument Data
{
get { return data; }
set
{
data = value;
contentData = null;
}
}
ContentData IContentEntity.Data
{
get
{
if (contentData == null)
{
if (data != null)
{
contentData = JsonConvert.DeserializeObject<ContentData>(data.ToJson());
}
}
return contentData;
}
}
public void SetData(ContentData newContentData)
{
data = null;
if (newContentData != null)
{
data = BsonDocument.Parse(JsonConvert.SerializeObject(newContentData));
}
Text = ExtractText(newContentData);
}
private static string ExtractText(ContentData data)
{
if (data == null)
{
return string.Empty;
}
var stringBuilder = new StringBuilder();
foreach (var text in data.Fields.Values.SelectMany(x => x.ValueByLanguage.Values).Where(x => x != null).OfType<JValue>())
{
if (text.Type == JTokenType.String)
{
stringBuilder.Append(text);
}
}
return stringBuilder.ToString();
}
}
}

214
src/Squidex.Store.MongoDb/Contents/MongoContentRepository.cs

@ -0,0 +1,214 @@
// ==========================================================================
// MongoContentRepository.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Events;
using Squidex.Events.Contents;
using Squidex.Events.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.CQRS.Replay;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.Reflection;
using Squidex.Read.Contents;
using Squidex.Read.Contents.Repositories;
using Squidex.Store.MongoDb.Utils;
namespace Squidex.Store.MongoDb.Contents
{
public class MongoContentRepository : IContentRepository, ICatchEventConsumer, IReplayableStore
{
private const string Prefix = "Projections_Content_";
private readonly IMongoDatabase database;
protected ProjectionDefinitionBuilder<MongoContentEntity> Projection
{
get
{
return Builders<MongoContentEntity>.Projection;
}
}
protected SortDefinitionBuilder<MongoContentEntity> Sort
{
get
{
return Builders<MongoContentEntity>.Sort;
}
}
protected UpdateDefinitionBuilder<MongoContentEntity> Update
{
get
{
return Builders<MongoContentEntity>.Update;
}
}
protected FilterDefinitionBuilder<MongoContentEntity> Filter
{
get
{
return Builders<MongoContentEntity>.Filter;
}
}
protected IndexKeysDefinitionBuilder<MongoContentEntity> IndexKeys
{
get
{
return Builders<MongoContentEntity>.IndexKeys;
}
}
public MongoContentRepository(IMongoDatabase database)
{
Guard.NotNull(database, nameof(database));
this.database = database;
}
public async Task ClearAsync()
{
using (var collections = await database.ListCollectionsAsync())
{
while (await collections.MoveNextAsync())
{
foreach (var collection in collections.Current)
{
var name = collection["name"].ToString();
if (name.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase))
{
await database.DropCollectionAsync(name);
}
}
}
}
}
public async Task<List<IContentEntity>> QueryAsync(Guid schemaId, bool nonPublished, int? take, int? skip, string query)
{
var cursor = BuildQuery(schemaId, nonPublished, query);
if (take.HasValue)
{
cursor.Limit(take.Value);
}
if (skip.HasValue)
{
cursor.Skip(skip.Value);
}
cursor.SortByDescending(x => x.LastModified);
var entities =
await cursor.ToListAsync();
return entities.OfType<IContentEntity>().ToList();
}
public Task<long> CountAsync(Guid schemaId, bool nonPublished, string query)
{
var cursor = BuildQuery(schemaId, nonPublished, query);
return cursor.CountAsync();
}
private IFindFluent<MongoContentEntity, MongoContentEntity> BuildQuery(Guid schemaId, bool nonPublished, string query)
{
var filters = new List<FilterDefinition<MongoContentEntity>>
{
Filter.Eq(x => x.IsDeleted, false)
};
if (!string.IsNullOrWhiteSpace(query))
{
filters.Add(Filter.Text(query, "en"));
}
if (!nonPublished)
{
filters.Add(Filter.Eq(x => x.IsPublished, false));
}
var collection = GetCollection(schemaId);
var cursor = collection.Find(Filter.And(filters));
return cursor;
}
public async Task<IContentEntity> FindContentAsync(Guid schemaId, Guid id)
{
var collection = GetCollection(schemaId);
var entity =
await collection.Find(x => x.Id == id).FirstOrDefaultAsync();
return entity;
}
protected Task On(ContentCreated @event, EnvelopeHeaders headers)
{
var collection = GetCollection(headers.SchemaId());
return collection.CreateAsync(headers, x =>
{
SimpleMapper.Map(@event, x);
x.SetData(@event.Data);
});
}
protected Task On(ContentUpdated @event, EnvelopeHeaders headers)
{
var collection = GetCollection(headers.SchemaId());
return collection.UpdateAsync(headers, x =>
{
x.SetData(@event.Data);
});
}
protected Task On(ContentDeleted @event, EnvelopeHeaders headers)
{
var collection = GetCollection(headers.SchemaId());
return collection.UpdateAsync(headers, x =>
{
x.IsDeleted = true;
});
}
protected Task On(SchemaCreated @event, EnvelopeHeaders headers)
{
var collection = GetCollection(headers.AggregateId());
return collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.IsPublished).Text(x => x.Text));
}
public Task On(Envelope<IEvent> @event)
{
return this.DispatchActionAsync(@event.Payload, @event.Headers);
}
private IMongoCollection<MongoContentEntity> GetCollection(Guid schemaId)
{
var name = $"{Prefix}{schemaId}";
return database.GetCollection<MongoContentEntity>(name);
}
}
}

8
src/Squidex.Store.MongoDb/MongoDbModule.cs

@ -16,10 +16,12 @@ using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.CQRS.Replay; using Squidex.Infrastructure.CQRS.Replay;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
using Squidex.Read.Apps.Repositories; using Squidex.Read.Apps.Repositories;
using Squidex.Read.Contents.Repositories;
using Squidex.Read.History.Repositories; using Squidex.Read.History.Repositories;
using Squidex.Read.Schemas.Repositories; using Squidex.Read.Schemas.Repositories;
using Squidex.Read.Users.Repositories; using Squidex.Read.Users.Repositories;
using Squidex.Store.MongoDb.Apps; using Squidex.Store.MongoDb.Apps;
using Squidex.Store.MongoDb.Contents;
using Squidex.Store.MongoDb.History; using Squidex.Store.MongoDb.History;
using Squidex.Store.MongoDb.Infrastructure; using Squidex.Store.MongoDb.Infrastructure;
using Squidex.Store.MongoDb.Schemas; using Squidex.Store.MongoDb.Schemas;
@ -70,6 +72,12 @@ namespace Squidex.Store.MongoDb
.As<IUserRepository>() .As<IUserRepository>()
.InstancePerLifetimeScope(); .InstancePerLifetimeScope();
builder.RegisterType<MongoContentRepository>()
.As<IContentRepository>()
.As<ICatchEventConsumer>()
.As<IReplayableStore>()
.SingleInstance();
builder.RegisterType<MongoHistoryEventRepository>() builder.RegisterType<MongoHistoryEventRepository>()
.As<IHistoryEventRepository>() .As<IHistoryEventRepository>()
.As<ICatchEventConsumer>() .As<ICatchEventConsumer>()

42
src/Squidex.Write/EnrichWithSchemaIdProcessor.cs

@ -0,0 +1,42 @@
// ==========================================================================
// EnrichWithSchemaIdProcessor.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Threading.Tasks;
using Squidex.Events;
using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Tasks;
using Squidex.Write.Schemas;
namespace Squidex.Write
{
public sealed class EnrichWithSchemaIdProcessor : IEventProcessor
{
public Task ProcessEventAsync(Envelope<IEvent> @event, IAggregate aggregate, ICommand command)
{
var schemaDomainObject = aggregate as SchemaDomainObject;
if (schemaDomainObject != null)
{
@event.SetSchemaId(aggregate.Id);
}
else
{
var schemaCommand = command as ISchemaCommand;
if (schemaCommand != null)
{
@event.SetSchemaId(schemaCommand.SchemaId);
}
}
return TaskHelper.Done;
}
}
}

17
src/Squidex/Config/Domain/WriteModule.cs

@ -11,6 +11,7 @@ using Squidex.Infrastructure.CQRS.Events;
using Squidex.Pipeline.CommandHandlers; using Squidex.Pipeline.CommandHandlers;
using Squidex.Write; using Squidex.Write;
using Squidex.Write.Apps; using Squidex.Write.Apps;
using Squidex.Write.Contents;
using Squidex.Write.Schemas; using Squidex.Write.Schemas;
namespace Squidex.Config.Domain namespace Squidex.Config.Domain
@ -39,6 +40,10 @@ namespace Squidex.Config.Domain
.As<IEventProcessor>() .As<IEventProcessor>()
.SingleInstance(); .SingleInstance();
builder.RegisterType<EnrichWithSchemaIdProcessor>()
.As<IEventProcessor>()
.SingleInstance();
builder.RegisterType<EnrichWithAggregateIdProcessor>() builder.RegisterType<EnrichWithAggregateIdProcessor>()
.As<IEventProcessor>() .As<IEventProcessor>()
.SingleInstance(); .SingleInstance();
@ -55,6 +60,18 @@ namespace Squidex.Config.Domain
.AsSelf() .AsSelf()
.InstancePerDependency(); .InstancePerDependency();
builder.RegisterType<ContentCommandHandler>()
.As<ICommandHandler>()
.SingleInstance();
builder.RegisterType<ContentDomainObject>()
.AsSelf()
.InstancePerDependency();
builder.RegisterType<AppCommandHandler>()
.As<ICommandHandler>()
.SingleInstance();
builder.RegisterType<AppDomainObject>() builder.RegisterType<AppDomainObject>()
.AsSelf() .AsSelf()
.InstancePerDependency(); .InstancePerDependency();

140
src/Squidex/Controllers/ContentApi/ContentsController.cs

@ -0,0 +1,140 @@
// ==========================================================================
// ContentsController.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
using Squidex.Controllers.Api;
using Squidex.Controllers.ContentApi.Models;
using Squidex.Core.Contents;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.Reflection;
using Squidex.Pipeline;
using Squidex.Read.Contents.Repositories;
using Squidex.Read.Schemas.Services;
using Squidex.Write.Contents.Commands;
namespace Squidex.Controllers.ContentApi
{
[Authorize(Roles = "app-editor,app-owner,app-developer")]
[ApiExceptionFilter]
[ServiceFilter(typeof(AppFilterAttribute))]
public class ContentsController : ControllerBase
{
private readonly ISchemaProvider schemaProvider;
private readonly IContentRepository contentRepository;
public ContentsController(ICommandBus commandBus, ISchemaProvider schemaProvider, IContentRepository contentRepository)
: base(commandBus)
{
this.schemaProvider = schemaProvider;
this.contentRepository = contentRepository;
}
[HttpGet]
[Route("content/{app}/{name}")]
public async Task<IActionResult> GetContents(string name, [FromQuery] string query = null, [FromQuery] int? take = null, [FromQuery] int? skip = null, [FromQuery] bool nonPublished = false)
{
var schemaEntity = await schemaProvider.FindSchemaByNameAsync(AppId, name);
if (schemaEntity == null)
{
return NotFound();
}
var taskForContents = contentRepository.QueryAsync(schemaEntity.Id, nonPublished, take, skip, query);
var taskForCount = contentRepository.CountAsync(schemaEntity.Id, nonPublished, query);
await Task.WhenAll(taskForContents, taskForCount);
var model = new ContentsDto
{
Total = taskForCount.Result,
Items = taskForContents.Result.Select(x =>
{
var itemModel = SimpleMapper.Map(x, new ContentDto());
if (x.Data != null)
{
itemModel.Data = x.Data.ToRaw();
}
return itemModel;
}).ToArray()
};
return Ok(model);
}
[HttpGet]
[Route("content/{app}/{name}/{id}")]
public async Task<IActionResult> GetContent(string name, Guid id)
{
var schemaEntity = await schemaProvider.FindSchemaByNameAsync(AppId, name);
if (schemaEntity == null)
{
return NotFound();
}
var content = await contentRepository.FindContentAsync(schemaEntity.Id, id);
if (content == null)
{
return NotFound();
}
var model = SimpleMapper.Map(content, new ContentDto());
if (content.Data != null)
{
model.Data = content.Data.ToRaw();
}
return Ok(model);
}
[HttpPost]
[Route("content/{app}/{name}/")]
public async Task<IActionResult> PostContent([FromBody] Dictionary<string, Dictionary<string, JToken>> request)
{
var command = new CreateContent { Data = ContentData.Create(request), AggregateId = Guid.NewGuid() };
var context = await CommandBus.PublishAsync(command);
var result = context.Result<Guid>();
return CreatedAtAction(nameof(GetContent), new { id = result }, new EntityCreatedDto { Id = result.ToString() });
}
[HttpPut]
[Route("content/{app}/{name}/{id}")]
public async Task<IActionResult> PutContent(Guid id, [FromBody] Dictionary<string, Dictionary<string, JToken>> request)
{
var command = new UpdateContent { AggregateId = id, Data = ContentData.Create(request) };
await CommandBus.PublishAsync(command);
return NoContent();
}
[HttpDelete]
[Route("content/{app}/{name}/{id}")]
public async Task<IActionResult> PutContent(Guid id)
{
var command = new DeleteContent { AggregateId = id };
await CommandBus.PublishAsync(command);
return NoContent();
}
}
}

2
src/Squidex/Controllers/ContentApi/Generator/SchemasSwaggerGenerator.cs

@ -335,7 +335,7 @@ When you change the field to be localizable the value will become the value for
["createdBy"] = CreateProperty($"The user that has created the {schemaName} content element.", null), ["createdBy"] = CreateProperty($"The user that has created the {schemaName} content element.", null),
["lastModified"] = CreateProperty($"The date and time when the {schemaName} content element has been modified last.", "date-time"), ["lastModified"] = CreateProperty($"The date and time when the {schemaName} content element has been modified last.", "date-time"),
["lastModifiedBy"] = CreateProperty($"The user that has updated the {schemaName} content element.", null), ["lastModifiedBy"] = CreateProperty($"The user that has updated the {schemaName} content element.", null),
["isPublished"] = new JsonProperty ["nonPublished"] = new JsonProperty
{ {
Description = $"Indicates if the {schemaName} content element is publihed.", IsRequired = true, Type = JsonObjectType.Boolean Description = $"Indicates if the {schemaName} content element is publihed.", IsRequired = true, Type = JsonObjectType.Boolean
} }

9
src/Squidex/Controllers/ContentApi/Models/ContentEntryDto.cs → src/Squidex/Controllers/ContentApi/Models/ContentDto.cs

@ -1,5 +1,5 @@
// ========================================================================== // ==========================================================================
// ContentEntryDto.cs // ContentDto.cs
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
@ -7,13 +7,14 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Squidex.Core.Contents; using Newtonsoft.Json.Linq;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Controllers.ContentApi.Models namespace Squidex.Controllers.ContentApi.Models
{ {
public sealed class ContentEntryDto public sealed class ContentDto
{ {
/// <summary> /// <summary>
/// The if of the content element. /// The if of the content element.
@ -36,7 +37,7 @@ namespace Squidex.Controllers.ContentApi.Models
/// The data of the content item. /// The data of the content item.
/// </summary> /// </summary>
[Required] [Required]
public ContentData Data { get; set; } public Dictionary<string, Dictionary<string, JToken>> Data { get; set; }
/// <summary> /// <summary>
/// The date and time when the content element has been created. /// The date and time when the content element has been created.

23
src/Squidex/Controllers/ContentApi/Models/ContentsDto.cs

@ -0,0 +1,23 @@
// ==========================================================================
// ContentsDto.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Controllers.ContentApi.Models
{
public class ContentsDto
{
/// <summary>
/// The total number of content items.
/// </summary>
public long Total { get; set; }
/// <summary>
/// The content items.
/// </summary>
public ContentDto[] Items { get; set; }
}
}

5
src/Squidex/Controllers/ControllerBase.cs

@ -22,6 +22,11 @@ namespace Squidex.Controllers
CommandBus = commandBus; CommandBus = commandBus;
} }
protected ControllerBase()
{
throw new NotImplementedException();
}
public Guid AppId public Guid AppId
{ {
get get

23
src/Squidex/app/features/content/pages/content/content-page.component.html

@ -21,24 +21,24 @@
<div *ngFor="let field of schema.fields"> <div *ngFor="let field of schema.fields">
<div class="table-items-row"> <div class="table-items-row">
<label>{{field|displayName:'properties.label':'name'}}</label> <label>{{field|displayName:'properties.label':'name'}}</label>
<div [formGroup]="contentForm.controls[field.name]">
<div *ngFor="let language of languages">
<sqx-control-errors [for]="language" fieldName="{{field|displayName:'properties.label':'name'}}" [submitted]="contentFormSubmitted"></sqx-control-errors>
<sqx-control-errors [for]="field.name" fieldName="{{field|displayName:'properties.label':'name'}}" [submitted]="contentFormSubmitted"></sqx-control-errors>
<div>
<div [ngSwitch]="field.properties.fieldType"> <div [ngSwitch]="field.properties.fieldType">
<div *ngSwitchCase="'number'"> <div *ngSwitchCase="'number'">
<div [ngSwitch]="field.properties.editor"> <div [ngSwitch]="field.properties.editor">
<div *ngSwitchCase="'Input'"> <div *ngSwitchCase="'Input'">
<input class="form-control" type="number" [formControlName]="field.name"> <input class="form-control" type="number" [formControlName]="language">
</div> </div>
<div *ngSwitchCase="'Dropdown'"> <div *ngSwitchCase="'Dropdown'">
<select class="form-control" [formControlName]="field.name"> <select class="form-control" [formControlName]="language">
<option *ngFor="let value of field.properties.allowedValues">{{value}}</option> <option *ngFor="let value of field.properties.allowedValues">{{value}}</option>
</select> </select>
</div> </div>
<div *ngSwitchCase="'Radio'"> <div *ngSwitchCase="'Radio'">
<label *ngFor="let value of field.properties.allowedValues"> <label *ngFor="let value of field.properties.allowedValues">
<input type="radio" value="{{value}}" [formControlName]="field.name"> {{value}} <input type="radio" value="{{value}}" [formControlName]="language"> {{value}}
</label> </label>
</div> </div>
</div> </div>
@ -46,19 +46,19 @@
<div *ngSwitchCase="'string'"> <div *ngSwitchCase="'string'">
<div [ngSwitch]="field.properties.editor"> <div [ngSwitch]="field.properties.editor">
<div *ngSwitchCase="'Input'"> <div *ngSwitchCase="'Input'">
<input class="form-control" type="text" [formControlName]="field.name"> <input class="form-control" type="text" [formControlName]="language">
</div> </div>
<div *ngSwitchCase="'Dropdown'"> <div *ngSwitchCase="'Dropdown'">
<select class="form-control" [formControlName]="field.name"> <select class="form-control" [formControlName]="language">
<option *ngFor="let value of field.properties.allowedValues">{{value}}</option> <option *ngFor="let value of field.properties.allowedValues">{{value}}</option>
</select> </select>
</div> </div>
<div *ngSwitchCase="'TextArea'"> <div *ngSwitchCase="'TextArea'">
<textarea class="form-control" [formControlName]="field.name"></textarea> <textarea class="form-control" [formControlName]="language"></textarea>
</div> </div>
<div *ngSwitchCase="'Radio'"> <div *ngSwitchCase="'Radio'">
<label *ngFor="let value of field.properties.allowedValues"> <label *ngFor="let value of field.properties.allowedValues">
<input type="radio" value="{{value}}" [formControlName]="field.name"> {{value}} <input type="radio" value="{{value}}" [formControlName]="language"> {{value}}
</label> </label>
</div> </div>
</div> </div>
@ -67,7 +67,7 @@
<div [ngSwitch]="field.properties.editor"> <div [ngSwitch]="field.properties.editor">
<div *ngSwitchCase="'Checkbox'"> <div *ngSwitchCase="'Checkbox'">
<div class="form-check"> <div class="form-check">
<input type="checkbox" [formControlName]="field.name"> <input type="checkbox" [formControlName]="language">
</div> </div>
</div> </div>
<div *ngSwitchCase="'Toggle'"> <div *ngSwitchCase="'Toggle'">
@ -76,6 +76,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="form-hint" *ngIf="field.properties.hints && field.properties.hints.length > 0"> <div class="form-hint" *ngIf="field.properties.hints && field.properties.hints.length > 0">
{{field.properties.hints}} {{field.properties.hints}}

29
src/Squidex/app/features/content/pages/content/content-page.component.ts

@ -12,6 +12,7 @@ import { ActivatedRoute } from '@angular/router';
import { import {
AppComponentBase, AppComponentBase,
AppsStoreService, AppsStoreService,
ContentsService,
NotificationService, NotificationService,
NumberFieldPropertiesDto, NumberFieldPropertiesDto,
SchemaDetailsDto, SchemaDetailsDto,
@ -31,9 +32,12 @@ export class ContentPageComponent extends AppComponentBase {
public contentFormSubmitted = false; public contentFormSubmitted = false;
public contentForm: FormGroup; public contentForm: FormGroup;
public languages = ['iv'];
public isNewMode = false; public isNewMode = false;
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService, constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService,
private readonly contentsService: ContentsService,
private readonly route: ActivatedRoute private readonly route: ActivatedRoute
) { ) {
super(apps, notifications, users); super(apps, notifications, users);
@ -53,6 +57,25 @@ export class ContentPageComponent extends AppComponentBase {
public saveContent() { public saveContent() {
this.contentFormSubmitted = true; this.contentFormSubmitted = true;
if (this.contentForm.valid) {
this.contentForm.disable();
const data = this.contentForm.value;
this.appName()
.switchMap(app => this.contentsService.postContent(app, this.schema.name, data))
.subscribe(() => {
this.reset();
}, error => {
this.contentForm.enable();
});
}
}
public reset() {
this.contentForm.reset();
this.contentFormSubmitted = false;
} }
private setupForm(schema: SchemaDetailsDto) { private setupForm(schema: SchemaDetailsDto) {
@ -79,7 +102,11 @@ export class ContentPageComponent extends AppComponentBase {
} }
} }
controls[field.name] = new FormControl(undefined, validators); const group = new FormGroup({
'iv': new FormControl(undefined, validators)
});
controls[field.name] = group;
} }
this.contentForm = new FormGroup(controls); this.contentForm = new FormGroup(controls);

1
src/Squidex/app/shared/declarations.ts

@ -21,6 +21,7 @@ export * from './services/app-languages.service';
export * from './services/apps-store.service'; export * from './services/apps-store.service';
export * from './services/apps.service'; export * from './services/apps.service';
export * from './services/auth.service'; export * from './services/auth.service';
export * from './services/contents.service';
export * from './services/history.service'; export * from './services/history.service';
export * from './services/languages.service'; export * from './services/languages.service';
export * from './services/schemas.service'; export * from './services/schemas.service';

2
src/Squidex/app/shared/module.ts

@ -18,6 +18,7 @@ import {
AppsService, AppsService,
AppMustExistGuard, AppMustExistGuard,
AuthService, AuthService,
ContentsService,
DashboardLinkDirective, DashboardLinkDirective,
HistoryComponent, HistoryComponent,
HistoryService, HistoryService,
@ -58,6 +59,7 @@ export class SqxSharedModule {
AppsService, AppsService,
AppMustExistGuard, AppMustExistGuard,
AuthService, AuthService,
ContentsService,
HistoryService, HistoryService,
LanguageService, LanguageService,
MustBeAuthenticatedGuard, MustBeAuthenticatedGuard,

106
src/Squidex/app/shared/services/contents.service.ts

@ -0,0 +1,106 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import 'framework/angular/http-extensions';
import {
ApiUrlConfig,
DateTime,
EntityCreatedDto
} from 'framework';
import { AuthService } from './auth.service';
export class ContentDto {
constructor(
public readonly id: string,
public readonly isPublished: boolean,
public readonly createdBy: string,
public readonly lastModifiedBy: string,
public readonly created: DateTime,
public readonly lastModified: DateTime,
public readonly data: any
) {
}
}
@Injectable()
export class ContentsService {
constructor(
private readonly authService: AuthService,
private readonly apiUrl: ApiUrlConfig
) {
}
public getContents(appName: string, schemaName: string, take: number, skip: number, query: string): Observable<ContentDto[]> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/?query=${query}&take=${take}&skip=${skip}&query=${query}&nonPublished=true`);
return this.authService.authGet(url)
.map(response => response.json())
.map(response => {
const items: any[] = response;
return items.map(item => {
return new ContentDto(
item.id,
item.isPublished,
item.createdBy,
item.lastModifiedBy,
DateTime.parseISO(item.created),
DateTime.parseISO(item.lastModified),
item.data);
});
})
.catchError('Failed to load contents. Please reload.');
}
public getContent(appName: string, schemaName: string, id: string): Observable<ContentDto> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/`);
return this.authService.authGet(url)
.map(response => response.json())
.map(response => {
return new ContentDto(
response.id,
response.isPublished,
response.createdBy,
response.lastModifiedBy,
DateTime.parseISO(response.created),
DateTime.parseISO(response.lastModified),
response.data);
})
.catchError('Failed to load content. Please reload.');
}
public postContent(appName: string, schemaName: string, dto: any): Observable<EntityCreatedDto> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/`);
return this.authService.authPost(url, dto)
.map(response => response.json())
.map(response => {
return new EntityCreatedDto(response.id);
})
.catchError('Failed to create content. Please reload.');
}
public putContent(appName: string, schemaName: string, id: string, dto: any): Observable<any> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/`);
return this.authService.authPut(url, dto)
.catchError('Failed to update Content. Please reload.');
}
public deleteContent(appName: string, schemaName: string, id: string, dto: any): Observable<any> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/`);
return this.authService.authDelete(url, dto)
.catchError('Failed to delete Content. Please reload.');
}
}

0
src/Squidex/npm-debug.log.3790864127

55
tests/Squidex.Core.Tests/Contents/ContentDataTests.cs

@ -0,0 +1,55 @@
// ==========================================================================
// ContentDataTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using FluentAssertions;
using Newtonsoft.Json.Linq;
using Xunit;
namespace Squidex.Core.Contents
{
public class ContentDataTests
{
[Fact]
public void Should_convert_from_dictionary()
{
var input =
new Dictionary<string, Dictionary<string, JToken>>
{
["field1"] = new Dictionary<string, JToken>
{
["en"] = "en_string",
["de"] = "de_string"
},
["field2"] = new Dictionary<string, JToken>
{
["en"] = 1,
["de"] = 2
}
};
var actual = ContentData.Create(input);
var expected =
ContentData.Empty
.AddField("field1",
ContentFieldData.Empty
.AddValue("en", "en_string")
.AddValue("de", "de_string"))
.AddField("field2",
ContentFieldData.Empty
.AddValue("en", 1)
.AddValue("de", 2));
var output = actual.ToRaw();
actual.ShouldBeEquivalentTo(expected);
output.ShouldBeEquivalentTo(input);
}
}
}

2
tests/Squidex.Core.Tests/Schemas/SchemaTests.cs

@ -256,7 +256,7 @@ namespace Squidex.Core.Schemas
.AddOrUpdateField(new BooleanField(3, "admin", .AddOrUpdateField(new BooleanField(3, "admin",
new BooleanFieldProperties())) new BooleanFieldProperties()))
.AddOrUpdateField(new NumberField(4, "age", .AddOrUpdateField(new NumberField(4, "age",
new NumberFieldProperties())); new NumberFieldProperties { MinValue = 1, MaxValue = 10 }));
var languages = new HashSet<Language>(new[] { Language.GetLanguage("de"), Language.GetLanguage("en") }); var languages = new HashSet<Language>(new[] { Language.GetLanguage("de"), Language.GetLanguage("en") });

24
tests/Squidex.Core.Tests/Schemas/SchemaValidationTests.cs

@ -25,9 +25,9 @@ namespace Squidex.Core.Schemas
public async Task Should_add_error_if_validating_data_with_unknown_field() public async Task Should_add_error_if_validating_data_with_unknown_field()
{ {
var data = var data =
ContentData.Empty() ContentData.Empty
.AddField("unknown", .AddField("unknown",
ContentFieldData.New()); ContentFieldData.Empty);
await sut.ValidateAsync(data, errors, languages); await sut.ValidateAsync(data, errors, languages);
@ -44,9 +44,9 @@ namespace Squidex.Core.Schemas
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { MaxValue = 100 })); sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { MaxValue = 100 }));
var data = var data =
ContentData.Empty() ContentData.Empty
.AddField("my-field", .AddField("my-field",
ContentFieldData.New() ContentFieldData.Empty
.AddValue(1000)); .AddValue(1000));
await sut.ValidateAsync(data, errors, languages); await sut.ValidateAsync(data, errors, languages);
@ -64,9 +64,9 @@ namespace Squidex.Core.Schemas
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties())); sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties()));
var data = var data =
ContentData.Empty() ContentData.Empty
.AddField("my-field", .AddField("my-field",
ContentFieldData.New() ContentFieldData.Empty
.AddValue("es", 1) .AddValue("es", 1)
.AddValue("it", 1)); .AddValue("it", 1));
@ -85,7 +85,7 @@ namespace Squidex.Core.Schemas
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsRequired = true, IsLocalizable = true })); sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsRequired = true, IsLocalizable = true }));
var data = var data =
ContentData.Empty(); ContentData.Empty;
await sut.ValidateAsync(data, errors, languages); await sut.ValidateAsync(data, errors, languages);
@ -103,7 +103,7 @@ namespace Squidex.Core.Schemas
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsRequired = true })); sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsRequired = true }));
var data = var data =
ContentData.Empty(); ContentData.Empty;
await sut.ValidateAsync(data, errors, languages); await sut.ValidateAsync(data, errors, languages);
@ -120,9 +120,9 @@ namespace Squidex.Core.Schemas
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsLocalizable = true })); sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsLocalizable = true }));
var data = var data =
ContentData.Empty() ContentData.Empty
.AddField("my-field", .AddField("my-field",
ContentFieldData.New() ContentFieldData.Empty
.AddValue("de", 1) .AddValue("de", 1)
.AddValue("xx", 1)); .AddValue("xx", 1));
@ -141,9 +141,9 @@ namespace Squidex.Core.Schemas
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsLocalizable = true })); sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsLocalizable = true }));
var data = var data =
ContentData.Empty() ContentData.Empty
.AddField("my-field", .AddField("my-field",
ContentFieldData.New() ContentFieldData.Empty
.AddValue("es", 1) .AddValue("es", 1)
.AddValue("it", 1)); .AddValue("it", 1));

19
tests/Squidex.Infrastructure.Tests/CQRS/Commands/LogExceptionHandlerTests.cs

@ -48,10 +48,12 @@ namespace Squidex.Infrastructure.CQRS.Commands
} }
[Fact] [Fact]
public async Task Should_do_nothing_if_context_has_no_exception() public async Task Should_do_nothing_if_command_is_succeeded()
{ {
var context = new CommandContext(new MyCommand()); var context = new CommandContext(new MyCommand());
context.Succeed();
var isHandled = await sut.HandleAsync(context); var isHandled = await sut.HandleAsync(context);
Assert.False(isHandled); Assert.False(isHandled);
@ -59,7 +61,20 @@ namespace Squidex.Infrastructure.CQRS.Commands
} }
[Fact] [Fact]
public async Task Should_log_if_context_has_exception2() public async Task Should_log_if_command_failed()
{
var context = new CommandContext(new MyCommand());
context.Fail(new InvalidOperationException());
var isHandled = await sut.HandleAsync(context);
Assert.False(isHandled);
Assert.Equal(1, logger.LogCount);
}
[Fact]
public async Task Should_log_if_command_is_not_handled()
{ {
var context = new CommandContext(new MyCommand()); var context = new CommandContext(new MyCommand());

8
tests/Squidex.Write.Tests/Contents/ContentCommandHandlerTests.cs

@ -35,13 +35,13 @@ namespace Squidex.Write.Contents
private readonly Mock<IAppEntity> appEntity = new Mock<IAppEntity>(); private readonly Mock<IAppEntity> appEntity = new Mock<IAppEntity>();
private readonly Guid schemaId = Guid.NewGuid(); private readonly Guid schemaId = Guid.NewGuid();
private readonly Guid appId = Guid.NewGuid(); private readonly Guid appId = Guid.NewGuid();
private readonly ContentData data = ContentData.Empty().AddField("my-field", ContentFieldData.New().AddValue(1)); private readonly ContentData data = ContentData.Empty.AddField("my-field", ContentFieldData.Empty.AddValue(1));
public ContentCommandHandlerTests() public ContentCommandHandlerTests()
{ {
var schema = var schema =
Schema.Create("my-schema", new SchemaProperties()) Schema.Create("my-schema", new SchemaProperties())
.AddOrUpdateField(new NumberField(1, "field", .AddOrUpdateField(new NumberField(1, "my-field",
new NumberFieldProperties { IsRequired = true })); new NumberFieldProperties { IsRequired = true }));
content = new ContentDomainObject(Id, 0); content = new ContentDomainObject(Id, 0);
@ -58,7 +58,7 @@ namespace Squidex.Write.Contents
[Fact] [Fact]
public async Task Create_should_throw_exception_if_data_is_not_valid() public async Task Create_should_throw_exception_if_data_is_not_valid()
{ {
var command = new CreateContent { AggregateId = Id, AppId = appId, SchemaId = schemaId, Data = ContentData.Empty() }; var command = new CreateContent { AggregateId = Id, AppId = appId, SchemaId = schemaId, Data = ContentData.Empty };
var context = new CommandContext(command); var context = new CommandContext(command);
await TestCreate(content, async _ => await TestCreate(content, async _ =>
@ -86,7 +86,7 @@ namespace Squidex.Write.Contents
{ {
CreateContent(); CreateContent();
var command = new UpdateContent { AggregateId = Id, AppId = appId, SchemaId = schemaId, Data = ContentData.Empty() }; var command = new UpdateContent { AggregateId = Id, AppId = appId, SchemaId = schemaId, Data = ContentData.Empty };
var context = new CommandContext(command); var context = new CommandContext(command);
await TestUpdate(content, async _ => await TestUpdate(content, async _ =>

2
tests/Squidex.Write.Tests/Contents/ContentDomainObjectTests.cs

@ -26,7 +26,7 @@ namespace Squidex.Write.Contents
{ {
private readonly Guid appId = Guid.NewGuid(); private readonly Guid appId = Guid.NewGuid();
private readonly ContentDomainObject sut; private readonly ContentDomainObject sut;
private readonly ContentData data = ContentData.Empty(); private readonly ContentData data = ContentData.Empty;
public ContentDomainObjectTests() public ContentDomainObjectTests()
{ {

72
tests/Squidex.Write.Tests/EnrichWithSchemaIdProcessorTests.cs

@ -0,0 +1,72 @@
// ==========================================================================
// EnrichWithSchemaIdProcessorTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Squidex.Core.Schemas;
using Squidex.Events;
using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Write.Schemas;
using Xunit;
namespace Squidex.Write
{
public class EnrichWithSchemaIdProcessorTests
{
public sealed class MySchemaCommand : SchemaAggregateCommand
{
}
public sealed class MyNormalCommand : ICommand
{
}
public sealed class MyEvent : IEvent
{
}
private readonly EnrichWithSchemaIdProcessor sut = new EnrichWithSchemaIdProcessor();
[Fact]
public async Task Should_not_do_anything_if_not_app_command()
{
var envelope = new Envelope<IEvent>(new MyEvent());
await sut.ProcessEventAsync(envelope, null, new MyNormalCommand());
Assert.False(envelope.Headers.Contains("SchemaId"));
}
[Fact]
public async Task Should_attach_app_id_from_domain_object()
{
var appId = Guid.NewGuid();
var envelope = new Envelope<IEvent>(new MyEvent());
await sut.ProcessEventAsync(envelope, new SchemaDomainObject(appId, 1, new FieldRegistry()), new MyNormalCommand());
Assert.Equal(appId, envelope.Headers.SchemaId());
}
[Fact]
public async Task Should_attach_app_id_to_event_envelope()
{
var appId = Guid.NewGuid();
var appCommand = new MySchemaCommand { AggregateId = appId };
var envelope = new Envelope<IEvent>(new MyEvent());
await sut.ProcessEventAsync(envelope, null, appCommand);
Assert.Equal(appId, envelope.Headers.SchemaId());
}
}
}
Loading…
Cancel
Save