Headless CMS and Content Managment Hub
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

332 lines
12 KiB

// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Security;
using Squidex.Shared;
namespace Squidex.Domain.Apps.Entities.Contents.Queries;
public class ContentQueryServiceTests : GivenContext
{
private readonly IContentEnricher contentEnricher = A.Fake<IContentEnricher>();
private readonly IContentRepository contentRepository = A.Fake<IContentRepository>();
private readonly IContentLoader contentVersionLoader = A.Fake<IContentLoader>();
private readonly ContentQueryParser queryParser = A.Fake<ContentQueryParser>();
private readonly ContentQueryService sut;
public ContentQueryServiceTests()
{
Schema = Schema with
{
Scripts = new SchemaScripts
{
Query = "<query-script>",
},
IsPublished = true,
};
SetupEnricher();
A.CallTo(() => AppProvider.GetSchemasAsync(AppId.Id, CancellationToken))
.Returns([Schema]);
A.CallTo(() => queryParser.ParseAsync(A<Context>._, A<Q>._, A<Schema?>._, CancellationToken))
.ReturnsLazily(c => Task.FromResult<Q?>(c.GetArgument<Q>(1)!));
var options = Options.Create(new ContentsOptions());
sut = new ContentQueryService(
AppProvider,
contentEnricher,
contentRepository,
contentVersionLoader,
options,
queryParser);
}
[Fact]
public async Task Should_get_schema_from_guid_string()
{
var requestContext = SetupContext();
var actual = await sut.GetSchemaOrThrowAsync(requestContext, SchemaId.Id.ToString(), CancellationToken);
Assert.Equal(Schema, actual);
}
[Fact]
public async Task Should_get_schema_from_name()
{
var requestContext = SetupContext();
var actual = await sut.GetSchemaOrThrowAsync(requestContext, SchemaId.Name, CancellationToken);
Assert.Equal(Schema, actual);
}
[Fact]
public async Task Should_throw_notfound_exception_if_schema_to_get_not_found()
{
var requestContext = SetupContext();
Schema = null!;
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.GetSchemaOrThrowAsync(requestContext, SchemaId.Name, CancellationToken));
}
[Fact]
public async Task Should_throw_permission_exception_if_content_to_find_is_restricted()
{
var requestContext = SetupContext(allowSchema: false);
var content = CreateContent() as Content;
A.CallTo(() => contentRepository.FindContentAsync(App, Schema, content.Id, null, A<SearchScope>._, A<CancellationToken>._))
.Returns(content);
await Assert.ThrowsAsync<DomainForbiddenException>(() => sut.FindAsync(requestContext, SchemaId.Name, content.Id, ct: CancellationToken));
}
[Fact]
public async Task Should_return_null_if_content_by_id_cannot_be_found()
{
var requestContext = SetupContext();
var content = CreateContent();
A.CallTo(() => contentRepository.FindContentAsync(App, Schema, content.Id, A<IReadOnlySet<string>>._, A<SearchScope>._, A<CancellationToken>._))
.Returns<Content?>(null);
var actual = await sut.FindAsync(requestContext, SchemaId.Name, content.Id, ct: CancellationToken);
Assert.Null(actual);
}
[Fact]
public async Task Should_return_content_by_special_id()
{
var requestContext = SetupContext();
var content = CreateContent();
A.CallTo(() => contentRepository.FindContentAsync(App, Schema, SchemaId.Id, A<IReadOnlySet<string>>._, SearchScope.Published, A<CancellationToken>._))
.Returns(content);
var actual = await sut.FindAsync(requestContext, SchemaId.Name, DomainId.Create("_schemaId_"), ct: CancellationToken);
AssertContent(content, actual);
}
[Theory]
[InlineData(1, 0, SearchScope.All)]
[InlineData(1, 1, SearchScope.All)]
[InlineData(0, 1, SearchScope.All)]
[InlineData(0, 0, SearchScope.Published)]
public async Task Should_return_content_by_id(int isFrontend, int unpublished, SearchScope scope)
{
var requestContext = SetupContext(isFrontend, isUnpublished: unpublished);
var content = CreateContent();
A.CallTo(() => contentRepository.FindContentAsync(App, Schema, content.Id, A<IReadOnlySet<string>>._, scope, A<CancellationToken>._))
.Returns(content);
var actual = await sut.FindAsync(requestContext, SchemaId.Name, content.Id, ct: CancellationToken);
AssertContent(content, actual);
}
[Fact]
public async Task Should_return_content_by_id_and_version()
{
var requestContext = SetupContext();
var content = CreateContent();
A.CallTo(() => contentVersionLoader.GetAsync(AppId.Id, content.Id, 13, A<CancellationToken>._))
.Returns(content);
var actual = await sut.FindAsync(requestContext, SchemaId.Name, content.Id, 13, CancellationToken);
AssertContent(content, actual);
}
[Fact]
public async Task Should_throw_exception_if_user_has_no_permission_to_query_content()
{
var requestContext = SetupContext(allowSchema: false);
await Assert.ThrowsAsync<DomainForbiddenException>(() => sut.QueryAsync(requestContext, SchemaId.Name, Q.Empty, CancellationToken));
}
[Theory]
[InlineData(1, 0, SearchScope.All)]
[InlineData(1, 1, SearchScope.All)]
[InlineData(0, 1, SearchScope.All)]
[InlineData(0, 0, SearchScope.Published)]
public async Task Should_query_contents(int isFrontend, int unpublished, SearchScope scope)
{
var requestContext = SetupContext(isFrontend, isUnpublished: unpublished);
var content1 = CreateContent();
var content2 = CreateContent();
var q = Q.Empty.WithReference(DomainId.NewGuid());
A.CallTo(() => contentRepository.QueryAsync(App, Schema, q, scope, A<CancellationToken>._))
.Returns(ResultList.CreateFrom(5, content1, content2));
var actual = await sut.QueryAsync(requestContext, SchemaId.Name, q, CancellationToken);
Assert.Equal(5, actual.Total);
AssertContent(content1, actual[0]);
AssertContent(content2, actual[1]);
}
[Theory]
[InlineData(1, 0, SearchScope.All)]
[InlineData(1, 1, SearchScope.All)]
[InlineData(0, 1, SearchScope.All)]
[InlineData(0, 0, SearchScope.Published)]
public async Task Should_query_contents_by_ids(int isFrontend, int unpublished, SearchScope scope)
{
var requestContext = SetupContext(isFrontend, isUnpublished: unpublished);
var contentIds = Enumerable.Range(0, 5).Select(x => DomainId.NewGuid()).ToList();
var contents = contentIds.Select(x => CreateContent().WithId(x)).ToList();
var q = Q.Empty.WithIds(contentIds);
A.CallTo(() => contentRepository.QueryAsync(App,
A<List<Schema>>.That.Matches(x => x.Count == 1), q, scope,
A<CancellationToken>._))
.Returns(ResultList.Create(5, contents));
var actual = await sut.QueryAsync(requestContext, q, CancellationToken);
Assert.Equal(5, actual.Total);
for (var i = 0; i < contents.Count; i++)
{
AssertContent(contents[i], actual[i]);
}
}
[Fact]
public async Task Should_query_contents_with_matching_permissions()
{
var requestContext = SetupContext(allowSchema: false);
var contentIds = Enumerable.Range(0, 5).Select(x => DomainId.NewGuid()).ToList();
var contents = contentIds.Select(x => CreateContent().WithId(x)).ToList();
var q = Q.Empty.WithIds(contentIds);
A.CallTo(() => contentRepository.QueryAsync(App,
A<List<Schema>>.That.Matches(x => x.Count == 0), q, SearchScope.All,
A<CancellationToken>._))
.Returns(ResultList.Create(0, contents));
var actual = await sut.QueryAsync(requestContext, q, CancellationToken);
Assert.Empty(actual);
}
[Fact]
public async Task Should_query_contents_from_user_if_user_has_only_own_permission()
{
var requestContext = SetupContext(permissionId: PermissionIds.AppContentsReadOwn);
await sut.QueryAsync(requestContext, SchemaId.Name, Q.Empty, CancellationToken);
A.CallTo(() => contentRepository.QueryAsync(App, Schema,
A<Q>.That.Matches(x => Equals(x.CreatedBy, requestContext.UserPrincipal.Token())), SearchScope.Published, A
<CancellationToken>._))
.MustHaveHappened();
}
[Fact]
public async Task Should_query_all_contents_if_user_has_read_permission()
{
var requestContext = SetupContext(permissionId: PermissionIds.AppContentsRead);
await sut.QueryAsync(requestContext, SchemaId.Name, Q.Empty, CancellationToken);
A.CallTo(() => contentRepository.QueryAsync(App, Schema,
A<Q>.That.Matches(x => x.CreatedBy == null), SearchScope.Published,
A<CancellationToken>._))
.MustHaveHappened();
}
[Fact]
public async Task Should_not_query_contents_from_one_schema_if_query_cannot_be_parsed()
{
var requestContext = SetupContext(permissionId: PermissionIds.AppContentsRead);
A.CallTo(() => queryParser.ParseAsync(requestContext, A<Q>._, Schema, CancellationToken))
.Returns(Task.FromResult<Q?>(null));
await sut.QueryAsync(requestContext, SchemaId.Name, Q.Empty, CancellationToken);
A.CallTo(contentRepository)
.MustNotHaveHappened();
}
[Fact]
public async Task Should_not_query_contents_from_all_schemas_if_query_cannot_be_parsed()
{
var requestContext = SetupContext(permissionId: PermissionIds.AppContentsRead);
A.CallTo(() => queryParser.ParseAsync(requestContext, A<Q>._, null, CancellationToken))
.Returns(Task.FromResult<Q?>(null));
await sut.QueryAsync(requestContext, Q.Empty, CancellationToken);
A.CallTo(contentRepository)
.MustNotHaveHappened();
}
private void SetupEnricher()
{
A.CallTo(() => contentEnricher.EnrichAsync(A<IEnumerable<Content>>._, A<Context>._, CancellationToken))
.ReturnsLazily(x =>
{
var input = x.GetArgument<IEnumerable<Content>>(0)!;
return Task.FromResult<IReadOnlyList<EnrichedContent>>(input.Select(c => SimpleMapper.Map(c, new EnrichedContent())).ToList());
});
}
private Context SetupContext(
int isFrontend = 0,
int isUnpublished = 0,
bool allowSchema = true,
string permissionId = PermissionIds.AppContentsRead)
{
var permissions = new List<string>();
if (allowSchema)
{
var concretePermission = PermissionIds.ForApp(permissionId, AppId.Name, SchemaId.Name).Id;
permissions.Add(concretePermission);
}
return CreateContext(isFrontend == 1, permissions.ToArray()).Clone(b => b.WithUnpublished(isUnpublished == 1));
}
private static void AssertContent(EnrichedContent source, EnrichedContent? actual)
{
Assert.NotNull(actual);
Assert.NotSame(source, actual);
Assert.Same(source.Data, actual?.Data);
Assert.Equal(source.Id, actual?.Id);
}
}