mirror of https://github.com/Squidex/squidex.git
10 changed files with 814 additions and 81 deletions
@ -0,0 +1,37 @@ |
|||
// ==========================================================================
|
|||
// NoopGraphType.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using GraphQL.Language.AST; |
|||
using GraphQL.Types; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Contents.GraphQL.Types |
|||
{ |
|||
public sealed class NoopGraphType : ScalarGraphType |
|||
{ |
|||
public NoopGraphType(string name) |
|||
{ |
|||
Name = name; |
|||
} |
|||
|
|||
public override object Serialize(object value) |
|||
{ |
|||
return value; |
|||
} |
|||
|
|||
public override object ParseValue(object value) |
|||
{ |
|||
return value; |
|||
} |
|||
|
|||
public override object ParseLiteral(IValue value) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,607 @@ |
|||
// ==========================================================================
|
|||
// GraphQLTests.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Caching.Memory; |
|||
using Microsoft.Extensions.Options; |
|||
using Moq; |
|||
using Newtonsoft.Json; |
|||
using Newtonsoft.Json.Linq; |
|||
using Squidex.Domain.Apps.Core; |
|||
using Squidex.Domain.Apps.Core.Schemas; |
|||
using Squidex.Domain.Apps.Read.Apps; |
|||
using Squidex.Domain.Apps.Read.Assets.Repositories; |
|||
using Squidex.Domain.Apps.Read.Contents.GraphQL; |
|||
using Squidex.Domain.Apps.Read.Contents.Repositories; |
|||
using Squidex.Domain.Apps.Read.Contents.TestData; |
|||
using Squidex.Domain.Apps.Read.Schemas; |
|||
using Squidex.Domain.Apps.Read.Schemas.Repositories; |
|||
using Xunit; |
|||
using NodaTime.Extensions; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Domain.Apps.Read.Assets; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Contents |
|||
{ |
|||
public class GraphQLTests |
|||
{ |
|||
private static readonly Guid schemaId = Guid.NewGuid(); |
|||
private static readonly Guid appId = Guid.NewGuid(); |
|||
|
|||
private readonly Schema schema = |
|||
Schema.Create("my-schema", new SchemaProperties()) |
|||
.AddOrUpdateField(new JsonField(1, "my-json", Partitioning.Invariant, |
|||
new JsonFieldProperties())) |
|||
.AddOrUpdateField(new StringField(2, "my-string", Partitioning.Language, |
|||
new StringFieldProperties())) |
|||
.AddOrUpdateField(new NumberField(3, "my-number", Partitioning.Invariant, |
|||
new NumberFieldProperties())) |
|||
.AddOrUpdateField(new AssetsField(4, "my-assets", Partitioning.Invariant, |
|||
new AssetsFieldProperties())) |
|||
.AddOrUpdateField(new BooleanField(5, "my-boolean", Partitioning.Invariant, |
|||
new BooleanFieldProperties())) |
|||
.AddOrUpdateField(new DateTimeField(6, "my-datetime", Partitioning.Invariant, |
|||
new DateTimeFieldProperties())) |
|||
.AddOrUpdateField(new ReferencesField(7, "my-references", Partitioning.Invariant, |
|||
new ReferencesFieldProperties { SchemaId = schemaId })) |
|||
.AddOrUpdateField(new GeolocationField(8, "my-geolocation", Partitioning.Invariant, |
|||
new GeolocationFieldProperties())); |
|||
|
|||
private readonly Mock<ISchemaRepository> schemaRepository = new Mock<ISchemaRepository>(); |
|||
private readonly Mock<IContentRepository> contentRepository = new Mock<IContentRepository>(); |
|||
private readonly Mock<IAssetRepository> assetRepository = new Mock<IAssetRepository>(); |
|||
private readonly IAppEntity app; |
|||
private readonly IMemoryCache cache = new MemoryCache(Options.Create(new MemoryCacheOptions())); |
|||
private readonly IGraphQLInvoker sut; |
|||
|
|||
public GraphQLTests() |
|||
{ |
|||
var appEntity = new Mock<IAppEntity>(); |
|||
appEntity.Setup(x => x.Id).Returns(appId); |
|||
appEntity.Setup(x => x.PartitionResolver).Returns(x => InvariantPartitioning.Instance); |
|||
|
|||
app = appEntity.Object; |
|||
|
|||
var schemaEntity = new Mock<ISchemaEntity>(); |
|||
schemaEntity.Setup(x => x.Id).Returns(schemaId); |
|||
schemaEntity.Setup(x => x.Name).Returns(schema.Name); |
|||
schemaEntity.Setup(x => x.Schema).Returns(schema); |
|||
schemaEntity.Setup(x => x.IsPublished).Returns(true); |
|||
|
|||
var schemas = new List<ISchemaEntity> { schemaEntity.Object }; |
|||
|
|||
schemaRepository.Setup(x => x.QueryAllAsync(appId)).Returns(Task.FromResult<IReadOnlyList<ISchemaEntity>>(schemas)); |
|||
|
|||
sut = new CachingGraphQLInvoker(cache, schemaRepository.Object, assetRepository.Object, contentRepository.Object); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_make_assets_request() |
|||
{ |
|||
const string query = @"
|
|||
query { |
|||
queryAssets(search: ""my-query"", top: 30, skip: 5) { |
|||
id |
|||
version |
|||
created |
|||
createdBy |
|||
lastModified |
|||
lastModifiedBy |
|||
mimeType |
|||
fileName |
|||
fileSize |
|||
fileVersion |
|||
isImage |
|||
pixelWidth, |
|||
pixelHeight |
|||
} |
|||
}";
|
|||
|
|||
var assetEntity = CreateAsset(Guid.NewGuid()); |
|||
|
|||
var assets = new List<IAssetEntity> { assetEntity }; |
|||
|
|||
assetRepository.Setup(x => x.QueryAsync(app.Id, null, null, "my-query", 30, 5)) |
|||
.Returns(Task.FromResult<IReadOnlyList<IAssetEntity>>(assets)) |
|||
.Verifiable(); |
|||
|
|||
dynamic result = await sut.QueryAsync(app, new GraphQLQuery { Query = query }); |
|||
|
|||
var expected = new |
|||
{ |
|||
data = new |
|||
{ |
|||
queryAssets = new dynamic[] |
|||
{ |
|||
new |
|||
{ |
|||
id = assetEntity.Id, |
|||
version = 1, |
|||
created = assetEntity.Created.ToDateTimeUtc(), |
|||
createdBy = "subject:user1", |
|||
lastModified = assetEntity.LastModified.ToDateTimeUtc(), |
|||
lastModifiedBy = "subject:user2", |
|||
mimeType = "image/png", |
|||
fileName = "MyFile.png", |
|||
fileSize = 1024, |
|||
fileVersion = 123, |
|||
isImage = true, |
|||
pixelWidth = 800, |
|||
pixelHeight = 600 |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
AssertJson(expected, result); |
|||
|
|||
assetRepository.VerifyAll(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_make_asset_request() |
|||
{ |
|||
var assetId = Guid.NewGuid(); |
|||
var assetEntity = CreateAsset(Guid.NewGuid()); |
|||
|
|||
var query = $@"
|
|||
query {{ |
|||
findAsset(id: ""{assetId}"") {{ |
|||
id |
|||
version |
|||
created |
|||
createdBy |
|||
lastModified |
|||
lastModifiedBy |
|||
mimeType |
|||
fileName |
|||
fileSize |
|||
fileVersion |
|||
isImage |
|||
pixelWidth, |
|||
pixelHeight |
|||
}} |
|||
}}";
|
|||
|
|||
assetRepository.Setup(x => x.FindAssetAsync(assetId)) |
|||
.Returns(Task.FromResult(assetEntity)) |
|||
.Verifiable(); |
|||
|
|||
dynamic result = await sut.QueryAsync(app, new GraphQLQuery { Query = query }); |
|||
|
|||
var expected = new |
|||
{ |
|||
data = new |
|||
{ |
|||
findAsset = new |
|||
{ |
|||
id = assetEntity.Id, |
|||
version = 1, |
|||
created = assetEntity.Created.ToDateTimeUtc(), |
|||
createdBy = "subject:user1", |
|||
lastModified = assetEntity.LastModified.ToDateTimeUtc(), |
|||
lastModifiedBy = "subject:user2", |
|||
mimeType = "image/png", |
|||
fileName = "MyFile.png", |
|||
fileSize = 1024, |
|||
fileVersion = 123, |
|||
isImage = true, |
|||
pixelWidth = 800, |
|||
pixelHeight = 600 |
|||
} |
|||
} |
|||
}; |
|||
|
|||
AssertJson(expected, result); |
|||
|
|||
assetRepository.VerifyAll(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_make_contents_request() |
|||
{ |
|||
const string query = @"
|
|||
query { |
|||
queryMySchemaContents(top: 30, skip: 5) { |
|||
id |
|||
version |
|||
created |
|||
createdBy |
|||
lastModified |
|||
lastModifiedBy |
|||
data { |
|||
myString { |
|||
iv |
|||
} |
|||
myNumber { |
|||
iv |
|||
} |
|||
myBoolean { |
|||
iv |
|||
} |
|||
myDatetime { |
|||
iv |
|||
} |
|||
myJson { |
|||
iv |
|||
} |
|||
myGeolocation { |
|||
iv |
|||
} |
|||
} |
|||
} |
|||
}";
|
|||
|
|||
var contentEntity = CreateContent(Guid.NewGuid(), Guid.Empty, Guid.Empty); |
|||
|
|||
var contents = new List<IContentEntity> { contentEntity }; |
|||
|
|||
contentRepository.Setup(x => x.QueryAsync(app, schemaId, false, null, "?$top=30&$skip=5")) |
|||
.Returns(Task.FromResult<IReadOnlyList<IContentEntity>>(contents)) |
|||
.Verifiable(); |
|||
|
|||
dynamic result = await sut.QueryAsync(app, new GraphQLQuery { Query = query }); |
|||
|
|||
var expected = new |
|||
{ |
|||
data = new |
|||
{ |
|||
queryMySchemaContents = new dynamic[] |
|||
{ |
|||
new |
|||
{ |
|||
id = contentEntity.Id, |
|||
version = 1, |
|||
created = contentEntity.Created.ToDateTimeUtc(), |
|||
createdBy = "subject:user1", |
|||
lastModified = contentEntity.LastModified.ToDateTimeUtc(), |
|||
lastModifiedBy = "subject:user2", |
|||
data = new |
|||
{ |
|||
myString = new |
|||
{ |
|||
iv = "value" |
|||
}, |
|||
myNumber = new |
|||
{ |
|||
iv = 1 |
|||
}, |
|||
myBoolean = new |
|||
{ |
|||
iv = true |
|||
}, |
|||
myDatetime = new |
|||
{ |
|||
iv = contentEntity.LastModified.ToDateTimeUtc() |
|||
}, |
|||
myJson = new |
|||
{ |
|||
iv = new |
|||
{ |
|||
value = 1 |
|||
} |
|||
}, |
|||
myGeolocation = new |
|||
{ |
|||
iv = new |
|||
{ |
|||
latitude = 10, |
|||
longitude = 20 |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
AssertJson(expected, result); |
|||
|
|||
contentRepository.VerifyAll(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_make_content_request() |
|||
{ |
|||
var contentId = Guid.NewGuid(); |
|||
var contentEntity = CreateContent(contentId, Guid.Empty, Guid.Empty); |
|||
|
|||
var query = $@"
|
|||
query {{ |
|||
findMySchemaContent(id: ""{contentId}"") {{ |
|||
id |
|||
version |
|||
created |
|||
createdBy |
|||
lastModified |
|||
lastModifiedBy |
|||
data {{ |
|||
myString {{ |
|||
iv |
|||
}} |
|||
myNumber {{ |
|||
iv |
|||
}} |
|||
myBoolean {{ |
|||
iv |
|||
}} |
|||
myDatetime {{ |
|||
iv |
|||
}} |
|||
myJson {{ |
|||
iv |
|||
}} |
|||
myGeolocation {{ |
|||
iv |
|||
}} |
|||
}} |
|||
}} |
|||
}}";
|
|||
|
|||
contentRepository.Setup(x => x.FindContentAsync(app, schemaId, contentId)) |
|||
.Returns(Task.FromResult(contentEntity)) |
|||
.Verifiable(); |
|||
|
|||
dynamic result = await sut.QueryAsync(app, new GraphQLQuery { Query = query }); |
|||
|
|||
var expected = new |
|||
{ |
|||
data = new |
|||
{ |
|||
findMySchemaContent = new |
|||
{ |
|||
id = contentEntity.Id, |
|||
version = 1, |
|||
created = contentEntity.Created.ToDateTimeUtc(), |
|||
createdBy = "subject:user1", |
|||
lastModified = contentEntity.LastModified.ToDateTimeUtc(), |
|||
lastModifiedBy = "subject:user2", |
|||
data = new |
|||
{ |
|||
myString = new |
|||
{ |
|||
iv = "value" |
|||
}, |
|||
myNumber = new |
|||
{ |
|||
iv = 1 |
|||
}, |
|||
myBoolean = new |
|||
{ |
|||
iv = true |
|||
}, |
|||
myDatetime = new |
|||
{ |
|||
iv = contentEntity.LastModified.ToDateTimeUtc() |
|||
}, |
|||
myJson = new |
|||
{ |
|||
iv = new |
|||
{ |
|||
value = 1 |
|||
} |
|||
}, |
|||
myGeolocation = new |
|||
{ |
|||
iv = new |
|||
{ |
|||
latitude = 10, |
|||
longitude = 20 |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
AssertJson(expected, result); |
|||
|
|||
contentRepository.VerifyAll(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_make_content_request_and_resolve_references() |
|||
{ |
|||
var contentRefId = Guid.NewGuid(); |
|||
var contentRefEntity = CreateContent(contentRefId, Guid.Empty, Guid.Empty); |
|||
|
|||
var contentId = Guid.NewGuid(); |
|||
var contentEntity = CreateContent(contentId, contentRefId, Guid.Empty); |
|||
|
|||
var query = $@"
|
|||
query {{ |
|||
findMySchemaContent(id: ""{contentId}"") {{ |
|||
id |
|||
data {{ |
|||
myReferences {{ |
|||
iv {{ |
|||
id |
|||
}} |
|||
}} |
|||
}} |
|||
}} |
|||
}}";
|
|||
|
|||
var refContents = new List<IContentEntity> { contentRefEntity }; |
|||
|
|||
contentRepository.Setup(x => x.FindContentAsync(app, schemaId, contentId)) |
|||
.Returns(Task.FromResult(contentEntity)) |
|||
.Verifiable(); |
|||
|
|||
contentRepository.Setup(x => x.QueryAsync(app, schemaId, false, new HashSet<Guid> {contentRefId }, null)) |
|||
.Returns(Task.FromResult<IReadOnlyList<IContentEntity>>(refContents)) |
|||
.Verifiable(); |
|||
|
|||
dynamic result = await sut.QueryAsync(app, new GraphQLQuery { Query = query }); |
|||
|
|||
var expected = new |
|||
{ |
|||
data = new |
|||
{ |
|||
findMySchemaContent = new |
|||
{ |
|||
id = contentEntity.Id, |
|||
data = new |
|||
{ |
|||
myReferences = new |
|||
{ |
|||
iv = new[] |
|||
{ |
|||
new |
|||
{ |
|||
id = contentRefId |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
AssertJson(expected, result); |
|||
|
|||
contentRepository.VerifyAll(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_make_content_request_and_resolve_assets() |
|||
{ |
|||
var assetRefId = Guid.NewGuid(); |
|||
var assetRefEntity = CreateAsset(assetRefId); |
|||
|
|||
var contentId = Guid.NewGuid(); |
|||
var contentEntity = CreateContent(contentId, Guid.Empty, assetRefId); |
|||
|
|||
var query = $@"
|
|||
query {{ |
|||
findMySchemaContent(id: ""{contentId}"") {{ |
|||
id |
|||
data {{ |
|||
myAssets {{ |
|||
iv {{ |
|||
id |
|||
}} |
|||
}} |
|||
}} |
|||
}} |
|||
}}";
|
|||
|
|||
var refAssets = new List<IAssetEntity> { assetRefEntity }; |
|||
|
|||
contentRepository.Setup(x => x.FindContentAsync(app, schemaId, contentId)) |
|||
.Returns(Task.FromResult(contentEntity)) |
|||
.Verifiable(); |
|||
|
|||
assetRepository.Setup(x => x.QueryAsync(app.Id, null, new HashSet<Guid> { assetRefId }, null, int.MaxValue, 0)) |
|||
.Returns(Task.FromResult<IReadOnlyList<IAssetEntity>>(refAssets)) |
|||
.Verifiable(); |
|||
|
|||
dynamic result = await sut.QueryAsync(app, new GraphQLQuery { Query = query }); |
|||
|
|||
var expected = new |
|||
{ |
|||
data = new |
|||
{ |
|||
findMySchemaContent = new |
|||
{ |
|||
id = contentEntity.Id, |
|||
data = new |
|||
{ |
|||
myAssets = new |
|||
{ |
|||
iv = new[] |
|||
{ |
|||
new |
|||
{ |
|||
id = assetRefId |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
AssertJson(expected, result); |
|||
|
|||
contentRepository.VerifyAll(); |
|||
} |
|||
|
|||
private static IContentEntity CreateContent(Guid id, Guid refId, Guid assetId) |
|||
{ |
|||
var now = DateTime.UtcNow.ToInstant(); |
|||
|
|||
var data = |
|||
new NamedContentData() |
|||
.AddField("my-json", |
|||
new ContentFieldData().AddValue("iv", JToken.FromObject(new { value = 1 }))) |
|||
.AddField("my-string", |
|||
new ContentFieldData().AddValue("iv", "value")) |
|||
.AddField("my-assets", |
|||
new ContentFieldData().AddValue("iv", JToken.FromObject(new[] { assetId }))) |
|||
.AddField("my-number", |
|||
new ContentFieldData().AddValue("iv", 1)) |
|||
.AddField("my-boolean", |
|||
new ContentFieldData().AddValue("iv", true)) |
|||
.AddField("my-datetime", |
|||
new ContentFieldData().AddValue("iv", now.ToDateTimeUtc())) |
|||
.AddField("my-references", |
|||
new ContentFieldData().AddValue("iv", JToken.FromObject(new[] { refId }))) |
|||
.AddField("my-geolocation", |
|||
new ContentFieldData().AddValue("iv", JToken.FromObject(new { latitude = 10, longitude = 20 }))); |
|||
|
|||
var contentEntity = new FakeContentEntity |
|||
{ |
|||
Id = id, |
|||
Version = 1, |
|||
Created = now, |
|||
CreatedBy = new RefToken("subject", "user1"), |
|||
LastModified = now, |
|||
LastModifiedBy = new RefToken("subject", "user2"), |
|||
Data = data |
|||
}; |
|||
|
|||
return contentEntity; |
|||
} |
|||
|
|||
private static IAssetEntity CreateAsset(Guid id) |
|||
{ |
|||
var now = DateTime.UtcNow.ToInstant(); |
|||
|
|||
var assetEntity = new FakeAssetEntity |
|||
{ |
|||
Id = id, |
|||
Version = 1, |
|||
Created = now, |
|||
CreatedBy = new RefToken("subject", "user1"), |
|||
LastModified = now, |
|||
LastModifiedBy = new RefToken("subject", "user2"), |
|||
FileName = "MyFile.png", |
|||
FileSize = 1024, |
|||
FileVersion = 123, |
|||
MimeType = "image/png", |
|||
IsImage = true, |
|||
PixelWidth = 800, |
|||
PixelHeight = 600 |
|||
}; |
|||
|
|||
return assetEntity; |
|||
} |
|||
|
|||
private static void AssertJson(object expected, object result) |
|||
{ |
|||
var resultJson = JsonConvert.SerializeObject(result, Formatting.Indented); |
|||
var expectJson = JsonConvert.SerializeObject(expected, Formatting.Indented); |
|||
|
|||
Assert.Equal(expectJson, resultJson); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
// ==========================================================================
|
|||
// MockupAssetEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using NodaTime; |
|||
using Squidex.Domain.Apps.Read.Assets; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Contents.TestData |
|||
{ |
|||
public sealed class FakeAssetEntity : IAssetEntity |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
public Guid AppId { get; set; } |
|||
|
|||
public Instant Created { get; set; } |
|||
|
|||
public Instant LastModified { get; set; } |
|||
|
|||
public RefToken CreatedBy { get; set; } |
|||
|
|||
public RefToken LastModifiedBy { get; set; } |
|||
|
|||
public long Version { get; set; } |
|||
|
|||
public string MimeType { get; set; } |
|||
|
|||
public string FileName { get; set; } |
|||
|
|||
public long FileSize { get; set; } |
|||
|
|||
public long FileVersion { get; set; } |
|||
|
|||
public bool IsImage { get; set; } |
|||
|
|||
public bool IsDeleted { get; set; } |
|||
|
|||
public int? PixelWidth { get; set; } |
|||
|
|||
public int? PixelHeight { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
// ==========================================================================
|
|||
// FakeContentEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using NodaTime; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Contents.TestData |
|||
{ |
|||
public sealed class FakeContentEntity : IContentEntity |
|||
{ |
|||
public Guid Id { get; set; } |
|||
|
|||
public Guid AppId { get; set; } |
|||
|
|||
public Instant Created { get; set; } |
|||
|
|||
public Instant LastModified { get; set; } |
|||
|
|||
public RefToken CreatedBy { get; set; } |
|||
|
|||
public RefToken LastModifiedBy { get; set; } |
|||
|
|||
public long Version { get; set; } |
|||
|
|||
public bool IsPublished { get; set; } |
|||
|
|||
public NamedContentData Data { get; set; } |
|||
} |
|||
} |
|||
Loading…
Reference in new issue