diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs index f536807ad..378e4e11e 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs @@ -108,17 +108,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents return ResultList.Create(contentItems.Result, contentCount.Result); } - public async Task FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id) - { - var contentEntity = - await Collection.Find(x => x.IndexedSchemaId == schema.Id && x.Id == id && x.IsDeleted != true).Not(x => x.DataText) - .FirstOrDefaultAsync(); - - contentEntity?.ParseData(schema.SchemaDef); - - return contentEntity; - } - public Task CleanupAsync(Guid id) { return Collection.UpdateManyAsync( diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentDraftCollection.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentDraftCollection.cs index 6921fbdd2..33111963f 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentDraftCollection.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentDraftCollection.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Threading.Tasks; using MongoDB.Driver; using NodaTime; +using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents.State; using Squidex.Domain.Apps.Entities.Schemas; @@ -67,6 +68,17 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents }); } + public async Task FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id) + { + var contentEntity = + await Collection.Find(x => x.IndexedSchemaId == schema.Id && x.Id == id && x.IsDeleted != true).Not(x => x.DataText) + .FirstOrDefaultAsync(); + + contentEntity?.ParseData(schema.SchemaDef); + + return contentEntity; + } + public async Task<(ContentState Value, long Version)> ReadAsync(Guid key, Func> getSchema) { var contentEntity = diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentPublishedCollection.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentPublishedCollection.cs index 48c54d04a..50f643d38 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentPublishedCollection.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentPublishedCollection.cs @@ -8,6 +8,10 @@ using System; using System.Threading.Tasks; using MongoDB.Driver; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Contents; +using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Infrastructure.MongoDb; namespace Squidex.Domain.Apps.Entities.MongoDb.Contents { @@ -30,6 +34,17 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents await base.SetupCollectionAsync(collection); } + public async Task FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id) + { + var contentEntity = + await Collection.Find(x => x.IndexedSchemaId == schema.Id && x.Id == id).Not(x => x.DataText) + .FirstOrDefaultAsync(); + + contentEntity?.ParseData(schema.SchemaDef); + + return contentEntity; + } + public Task UpsertAsync(MongoContentEntity content) { content.DataText = content.DataByIds.ToFullText(); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs index 3ce6941ca..4ca7f298e 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs @@ -27,6 +27,10 @@ namespace Squidex.Domain.Apps.Entities.Contents { public sealed class ContentQueryService : IContentQueryService { + private static readonly Status[] StatusAll = { Status.Archived, Status.Draft, Status.Published }; + private static readonly Status[] StatusArchived = { Status.Archived }; + private static readonly Status[] StatusPublished = { Status.Published }; + private static readonly Status[] StatusDraftOrPublished = { Status.Draft, Status.Published }; private readonly IContentRepository contentRepository; private readonly IContentVersionLoader contentVersionLoader; private readonly IAppProvider appProvider; @@ -67,24 +71,22 @@ namespace Squidex.Domain.Apps.Entities.Contents var schema = await GetSchemaAsync(app, schemaIdOrName); - var isFrontendClient = IsFrontendClient(user); var isVersioned = version > EtagVersion.Empty; + var isFrontend = IsFrontendClient(user); - var parsedStatus = isFrontendClient ? null : new[] { Status.Published }; + var parsedStatus = isFrontend ? StatusAll : StatusPublished; var content = isVersioned ? await FindContentByVersionAsync(id, version) : await FindContentAsync(app, id, parsedStatus, schema); - if (content == null || (content.Status != Status.Published && !isFrontendClient) || content.SchemaId.Id != schema.Id) + if (content == null || (content.Status != Status.Published && !isFrontend) || content.SchemaId.Id != schema.Id) { throw new DomainObjectNotFoundException(id.ToString(), typeof(ISchemaEntity)); } - content = TransformContent(app, schema, user, Enumerable.Repeat(content, 1), isVersioned, isFrontendClient).FirstOrDefault(); - - return content; + return TransformContent(app, schema, user, content, isFrontend, isVersioned); } public async Task> QueryAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, string query) @@ -95,14 +97,14 @@ namespace Squidex.Domain.Apps.Entities.Contents var schema = await GetSchemaAsync(app, schemaIdOrName); - var isFrontendClient = IsFrontendClient(user); + var isFrontend = IsFrontendClient(user); var parsedQuery = ParseQuery(app, query, schema); - var parsedStatus = ParseStatus(isFrontendClient, archived); + var parsedStatus = ParseStatus(isFrontend, archived); - var contents = await contentRepository.QueryAsync(app, schema, parsedStatus.ToArray(), parsedQuery); + var contents = await contentRepository.QueryAsync(app, schema, parsedStatus, parsedQuery); - return TransformContents(app, schema, user, contents, false, isFrontendClient); + return TransformContents(app, schema, user, contents, false, isFrontend); } public async Task> QueryAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, HashSet ids) @@ -114,13 +116,21 @@ namespace Squidex.Domain.Apps.Entities.Contents var schema = await GetSchemaAsync(app, schemaIdOrName); - var isFrontendClient = IsFrontendClient(user); + var isFrontend = IsFrontendClient(user); - var parsedStatus = ParseStatus(isFrontendClient, archived); + var parsedStatus = ParseStatus(isFrontend, archived); - var contents = await contentRepository.QueryAsync(app, schema, parsedStatus.ToArray(), ids); + var contents = await contentRepository.QueryAsync(app, schema, parsedStatus, ids); - return TransformContents(app, schema, user, contents, false, isFrontendClient); + return TransformContents(app, schema, user, contents, false, isFrontend); + } + + private IContentEntity TransformContent(IAppEntity app, ISchemaEntity schema, ClaimsPrincipal user, + IContentEntity content, + bool isFrontend, + bool isVersioned) + { + return TransformContents(app, schema, user, Enumerable.Repeat(content, 1), isVersioned, isFrontend).FirstOrDefault(); } private IResultList TransformContents(IAppEntity app, ISchemaEntity schema, ClaimsPrincipal user, @@ -128,12 +138,12 @@ namespace Squidex.Domain.Apps.Entities.Contents bool isTypeChecking, bool isFrontendClient) { - var transformed = TransformContent(app, schema, user, contents, isTypeChecking, isFrontendClient); + var transformed = TransformContents(app, schema, user, (IEnumerable)contents, isTypeChecking, isFrontendClient); return ResultList.Create(transformed, contents.Total); } - private IEnumerable TransformContent(IAppEntity app, ISchemaEntity schema, ClaimsPrincipal user, + private IEnumerable TransformContents(IAppEntity app, ISchemaEntity schema, ClaimsPrincipal user, IEnumerable contents, bool isTypeChecking, bool isFrontendClient) @@ -203,28 +213,19 @@ namespace Squidex.Domain.Apps.Entities.Contents return schema; } - private static List ParseStatus(bool isFrontendClient, bool archived) + private static Status[] ParseStatus(bool isFrontendClient, bool archived) { - var status = new List(); - if (isFrontendClient) { if (archived) { - status.Add(Status.Archived); + return StatusArchived; } - else - { - status.Add(Status.Draft); - status.Add(Status.Published); - } - } - else - { - status.Add(Status.Published); + + return StatusDraftOrPublished; } - return status; + return StatusPublished; } private Task FindContentByVersionAsync(Guid id, long version) diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/ContentChangedTriggerTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/ContentChangedTriggerTests.cs index 67ded737d..6b31152bb 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/ContentChangedTriggerTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/ContentChangedTriggerTests.cs @@ -20,6 +20,8 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Xunit; +#pragma warning disable SA1401 // Fields must be private + namespace Squidex.Domain.Apps.Core.Operations.HandleRules.Triggers { public class ContentChangedTriggerTests @@ -28,28 +30,22 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules.Triggers private static readonly NamedId SchemaMatch = new NamedId(Guid.NewGuid(), "my-schema1"); private static readonly NamedId SchemaNonMatch = new NamedId(Guid.NewGuid(), "my-schema2"); - public static IEnumerable TestData + public static IEnumerable TestData = new[] { - get - { - return new[] - { - new object[] { 0, 1, 1, 1, 1, new RuleCreated() }, - new object[] { 0, 1, 1, 1, 1, new ContentCreated { SchemaId = SchemaNonMatch } }, - new object[] { 1, 1, 0, 0, 0, new ContentCreated { SchemaId = SchemaMatch } }, - new object[] { 0, 0, 0, 0, 0, new ContentCreated { SchemaId = SchemaMatch } }, - new object[] { 1, 0, 1, 0, 0, new ContentUpdated { SchemaId = SchemaMatch } }, - new object[] { 0, 0, 0, 0, 0, new ContentUpdated { SchemaId = SchemaMatch } }, - new object[] { 1, 0, 0, 1, 0, new ContentDeleted { SchemaId = SchemaMatch } }, - new object[] { 0, 0, 0, 0, 0, new ContentDeleted { SchemaId = SchemaMatch } }, - new object[] { 1, 0, 0, 0, 1, new ContentStatusChanged { SchemaId = SchemaMatch, Status = Status.Published } }, - new object[] { 0, 0, 0, 0, 0, new ContentStatusChanged { SchemaId = SchemaMatch, Status = Status.Published } }, - new object[] { 0, 1, 1, 1, 1, new ContentStatusChanged { SchemaId = SchemaMatch, Status = Status.Archived } }, - new object[] { 0, 1, 1, 1, 1, new ContentStatusChanged { SchemaId = SchemaMatch, Status = Status.Draft } }, - new object[] { 0, 1, 1, 1, 1, new SchemaCreated { SchemaId = SchemaNonMatch } } - }; - } - } + new object[] { 0, 1, 1, 1, 1, new RuleCreated() }, + new object[] { 0, 1, 1, 1, 1, new ContentCreated { SchemaId = SchemaNonMatch } }, + new object[] { 1, 1, 0, 0, 0, new ContentCreated { SchemaId = SchemaMatch } }, + new object[] { 0, 0, 0, 0, 0, new ContentCreated { SchemaId = SchemaMatch } }, + new object[] { 1, 0, 1, 0, 0, new ContentUpdated { SchemaId = SchemaMatch } }, + new object[] { 0, 0, 0, 0, 0, new ContentUpdated { SchemaId = SchemaMatch } }, + new object[] { 1, 0, 0, 1, 0, new ContentDeleted { SchemaId = SchemaMatch } }, + new object[] { 0, 0, 0, 0, 0, new ContentDeleted { SchemaId = SchemaMatch } }, + new object[] { 1, 0, 0, 0, 1, new ContentStatusChanged { SchemaId = SchemaMatch, Status = Status.Published } }, + new object[] { 0, 0, 0, 0, 0, new ContentStatusChanged { SchemaId = SchemaMatch, Status = Status.Published } }, + new object[] { 0, 1, 1, 1, 1, new ContentStatusChanged { SchemaId = SchemaMatch, Status = Status.Archived } }, + new object[] { 0, 1, 1, 1, 1, new ContentStatusChanged { SchemaId = SchemaMatch, Status = Status.Draft } }, + new object[] { 0, 1, 1, 1, 1, new SchemaCreated { SchemaId = SchemaNonMatch } } + }; [Fact] public void Should_return_false_when_trigger_contains_no_schemas() diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs index 197292ce6..55cc96e75 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using FakeItEasy; +using Microsoft.OData; using Microsoft.OData.UriParser; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; @@ -24,6 +25,8 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.Security; using Xunit; +#pragma warning disable SA1401 // Fields must be private + namespace Squidex.Domain.Apps.Entities.Contents { public class ContentQueryServiceTests @@ -56,6 +59,7 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => content.Id).Returns(contentId); A.CallTo(() => content.Data).Returns(contentData); + A.CallTo(() => content.DataDraft).Returns(contentData); A.CallTo(() => content.Status).Returns(Status.Published); A.CallTo(() => schema.SchemaDef).Returns(new Schema("my-schema")); @@ -95,11 +99,29 @@ namespace Squidex.Domain.Apps.Entities.Contents } [Fact] - public async Task Should_return_content_from_repository_and_transform() + public async Task Should_throw_if_schema_not_found_in_check() + { + A.CallTo(() => appProvider.GetSchemaAsync(appId, "my-schema")) + .Returns((ISchemaEntity)null); + + await Assert.ThrowsAsync(() => sut.ThrowIfSchemaNotExistsAsync(app, "my-schema")); + } + + public static IEnumerable SingleRequestData = new[] { + new object[] { true, new[] { Status.Archived, Status.Draft, Status.Published } }, + new object[] { false, new[] { Status.Published } } + }; + + [Theory] + [MemberData(nameof(SingleRequestData))] + public async Task Should_return_content_from_repository_and_transform(bool isFrontend, params Status[] status) + { + SetupClaims(isFrontend); + A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) .Returns(schema); - A.CallTo(() => contentRepository.FindContentAsync(app, schema, A.Ignored, contentId)) + A.CallTo(() => contentRepository.FindContentAsync(app, schema, A.That.IsSameSequenceAs(status), contentId)) .Returns(content); A.CallTo(() => schema.ScriptQuery) @@ -146,66 +168,38 @@ namespace Squidex.Domain.Apps.Entities.Contents await Assert.ThrowsAsync(async () => await sut.FindContentAsync(app, schemaId.ToString(), user, contentId)); } - [Fact] - public async Task Should_return_contents_with_ids_from_repository() + public static IEnumerable ManyRequestData = new[] { - await TestManyIdRequest(true, false, new HashSet { Guid.NewGuid() }, Status.Draft, Status.Published); - } - - [Fact] - public async Task Should_return_contents_with_ids_from_repository_and_transform_as_non_frontend() - { - await TestManyIdRequest(false, false, new HashSet { Guid.NewGuid() }, Status.Published); - } - - [Fact] - public async Task Should_return_non_archived_contents_from_repository() - { - await TestManyRequest(true, false, Status.Draft, Status.Published); - } - - [Fact] - public async Task Should_return_non_archived_contents_from_repository_and_transform_as_non_frontend() - { - await TestManyRequest(false, false, Status.Published); - } - - [Fact] - public async Task Should_return_archived_contents_from_repository() - { - await TestManyRequest(true, true, Status.Archived); - } - - [Fact] - public async Task Should_return_draft_contents_from_repository() - { - await TestManyRequest(false, false, Status.Published); - } - - [Fact] - public async Task Should_return_draft_contents_from_repository_and_transform_when_requesting_archive_as_non_frontend() - { - await TestManyRequest(false, true, Status.Published); - } - - private async Task TestManyRequest(bool isFrontend, bool archive, params Status[] status) + new object[] { 5, 200, false, true, new[] { Status.Published } }, + new object[] { 5, 200, false, false, new[] { Status.Published } }, + new object[] { 5, 200, true, false, new[] { Status.Draft, Status.Published } }, + new object[] { 5, 200, true, true, new[] { Status.Archived } } + }; + + [Theory] + [MemberData(nameof(ManyRequestData))] + public async Task Should_query_contents_by_query_from_repository_and_transform(int count, int total, bool isFrontend, bool archive, params Status[] status) { SetupClaims(isFrontend); - - SetupFakeWithOdataQuery(status); SetupFakeWithScripting(); + A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) + .Returns(schema); + + A.CallTo(() => contentRepository.QueryAsync(app, schema, A.That.IsSameSequenceAs(status), A.Ignored)) + .Returns(ResultList.Create(Enumerable.Repeat(content, count), total)); + var result = await sut.QueryAsync(app, schemaId.ToString(), user, archive, string.Empty); Assert.Equal(contentData, result[0].Data); Assert.Equal(content.Id, result[0].Id); - Assert.Equal(123, result.Total); + Assert.Equal(total, result.Total); if (!isFrontend) { A.CallTo(() => scriptEngine.Transform(A.Ignored, A.Ignored)) - .MustHaveHappened(Repeated.Exactly.Times(result.Count)); + .MustHaveHappened(Repeated.Exactly.Times(count)); } else { @@ -214,24 +208,52 @@ namespace Squidex.Domain.Apps.Entities.Contents } } - private async Task TestManyIdRequest(bool isFrontend, bool archive, HashSet ids, params Status[] status) + [Fact] + public Task Should_throw_if_query_is_invalid() { - SetupClaims(isFrontend); + A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) + .Returns(schema); + + A.CallTo(() => modelBuilder.BuildEdmModel(schema, app)) + .Throws(new ODataException()); - SetupFakeWithIdQuery(status, ids); + return Assert.ThrowsAsync(() => sut.QueryAsync(app, schemaId.ToString(), user, false, "query")); + } + + public static IEnumerable ManyIdRequestData = new[] + { + new object[] { 5, 200, false, true, new[] { Status.Published } }, + new object[] { 5, 200, false, false, new[] { Status.Published } }, + new object[] { 5, 200, true, false, new[] { Status.Draft, Status.Published } }, + new object[] { 5, 200, true, true, new[] { Status.Archived } } + }; + + [Theory] + [MemberData(nameof(ManyIdRequestData))] + public async Task Should_query_contents_by_id_from_repository_and_transform(int count, int total, bool isFrontend, bool archive, params Status[] status) + { + var ids = new HashSet(Enumerable.Range(0, count).Select(x => Guid.NewGuid())); + + SetupClaims(isFrontend); SetupFakeWithScripting(); + A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) + .Returns(schema); + + A.CallTo(() => contentRepository.QueryAsync(app, schema, A.That.IsSameSequenceAs(status), ids)) + .Returns(ResultList.Create(Enumerable.Repeat(content, count), total)); + var result = await sut.QueryAsync(app, schemaId.ToString(), user, archive, ids); Assert.Equal(contentData, result[0].Data); Assert.Equal(content.Id, result[0].Id); - Assert.Equal(123, result.Total); + Assert.Equal(total, result.Total); if (!isFrontend) { A.CallTo(() => scriptEngine.Transform(A.Ignored, A.Ignored)) - .MustHaveHappened(Repeated.Exactly.Times(result.Count)); + .MustHaveHappened(Repeated.Exactly.Times(count)); } else { @@ -248,24 +270,6 @@ namespace Squidex.Domain.Apps.Entities.Contents } } - private void SetupFakeWithIdQuery(Status[] status, HashSet ids) - { - A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) - .Returns(schema); - - A.CallTo(() => contentRepository.QueryAsync(app, schema, A.That.IsSameSequenceAs(status), ids)) - .Returns(ResultList.Create(Enumerable.Repeat(content, 1), 123)); - } - - private void SetupFakeWithOdataQuery(Status[] status) - { - A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) - .Returns(schema); - - A.CallTo(() => contentRepository.QueryAsync(app, schema, A.That.IsSameSequenceAs(status), A.Ignored)) - .Returns(ResultList.Create(Enumerable.Repeat(content, 1), 123)); - } - private void SetupFakeWithScripting() { A.CallTo(() => schema.ScriptQuery)