Browse Source

MongoDB index and improvements and tests. (#481)

* MongoDB index and improvements and tests.

* Fix
pull/483/head
Sebastian Stehle 6 years ago
committed by GitHub
parent
commit
41c114c8de
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      Dockerfile
  2. 19
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs
  3. 17
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  4. 32
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContentsByIds.cs
  5. 41
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContentsByQuery.cs
  6. 10
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryIdsAsync.cs
  7. 6
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/LimitExtensions.cs
  8. 3
      backend/src/Squidex.Infrastructure/Log/ProfilerSpan.cs
  9. 19
      backend/src/Squidex/Config/Domain/StoreServices.cs
  10. 1
      backend/src/Squidex/Pipeline/Plugins/PluginExtensions.cs
  11. 8
      backend/tests/RunCoverage.ps1
  12. 5
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Invitation/InviteUserCommandMiddlewareTests.cs
  13. 136
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryFixture.cs
  14. 146
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryTests.cs
  15. 6
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/MongoDbQueryTests.cs
  16. 211
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryFixture.cs
  17. 143
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryTests.cs
  18. 6
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs
  19. 4
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Tags/TagGrainTests.cs
  20. 2
      backend/tests/Squidex.Web.Tests/Pipeline/CleanupHostMiddlewareTests.cs
  21. 2
      backend/tools/Migrate_00/Program.cs
  22. 6
      frontend/app/features/content/shared/forms/stock-photo-editor.component.scss
  23. 2
      frontend/app/framework/angular/forms/editors/tag-editor.component.scss
  24. 11
      frontend/app/shared/components/forms/markdown-editor.component.ts
  25. 6
      frontend/app/shared/components/forms/rich-editor.component.ts

8
Dockerfile

@ -19,10 +19,10 @@ COPY backend .
# Test Backend # Test Backend
RUN dotnet test tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj --filter Category!=Dependencies \ RUN dotnet test tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj --filter Category!=Dependencies \
&& dotnet test tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj \ && dotnet test tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj --filter Category!=Dependencies \
&& dotnet test tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj \ && dotnet test tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj --filter Category!=Dependencies \
&& dotnet test tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj \ && dotnet test tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj --filter Category!=Dependencies \
&& dotnet test tests/Squidex.Web.Tests/Squidex.Web.Tests.csproj && dotnet test tests/Squidex.Web.Tests/Squidex.Web.Tests.csproj --filter Category!=Dependencies
# Publish # Publish
RUN dotnet publish src/Squidex/Squidex.csproj --output /build/ --configuration Release -p:version=$SQUIDEX__VERSION RUN dotnet publish src/Squidex/Squidex.csproj --output /build/ --configuration Release -p:version=$SQUIDEX__VERSION

19
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs

@ -29,6 +29,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{ {
} }
public IMongoCollection<MongoAssetEntity> GetInternalCollection()
{
return Collection;
}
protected override string CollectionName() protected override string CollectionName()
{ {
return "States_Assets"; return "States_Assets";
@ -43,6 +48,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
.Ascending(x => x.IndexedAppId) .Ascending(x => x.IndexedAppId)
.Ascending(x => x.IsDeleted) .Ascending(x => x.IsDeleted)
.Ascending(x => x.ParentId) .Ascending(x => x.ParentId)
.Ascending(x => x.Tags)
.Descending(x => x.LastModified)), .Descending(x => x.LastModified)),
new CreateIndexModel<MongoAssetEntity>( new CreateIndexModel<MongoAssetEntity>(
Index Index
@ -53,12 +59,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
Index Index
.Ascending(x => x.IndexedAppId) .Ascending(x => x.IndexedAppId)
.Ascending(x => x.IsDeleted) .Ascending(x => x.IsDeleted)
.Ascending(x => x.FileHash)), .Ascending(x => x.FileHash))
new CreateIndexModel<MongoAssetEntity>(
Index
.Ascending(x => x.IndexedAppId)
.Ascending(x => x.IsDeleted)
.Ascending(x => x.Id))
}, ct); }, ct);
} }
@ -75,9 +76,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
var assetCount = Collection.Find(filter).CountDocumentsAsync(); var assetCount = Collection.Find(filter).CountDocumentsAsync();
var assetItems = var assetItems =
Collection.Find(filter) Collection.Find(filter)
.Take(query) .QueryLimit(query)
.Skip(query) .QuerySkip(query)
.Sort(query) .QuerySort(query)
.ToListAsync(); .ToListAsync();
await Task.WhenAll(assetItems, assetCount); await Task.WhenAll(assetItems, assetCount);

17
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs

@ -42,7 +42,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
StatusSerializer.Register(); StatusSerializer.Register();
} }
public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider, IJsonSerializer serializer, ITextIndexer indexer) public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider, ITextIndexer indexer, IJsonSerializer serializer)
: base(database) : base(database)
{ {
Guard.NotNull(appProvider); Guard.NotNull(appProvider);
@ -59,6 +59,16 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
queryScheduledItems = new QueryScheduledContents(); queryScheduledItems = new QueryScheduledContents();
} }
public IMongoCollection<MongoContentEntity> GetInternalCollection()
{
return Collection;
}
protected override string CollectionName()
{
return "State_Contents";
}
protected override async Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection, CancellationToken ct = default) protected override async Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection, CancellationToken ct = default)
{ {
await queryContentAsync.PrepareAsync(collection, ct); await queryContentAsync.PrepareAsync(collection, ct);
@ -68,11 +78,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
await queryScheduledItems.PrepareAsync(collection, ct); await queryScheduledItems.PrepareAsync(collection, ct);
} }
protected override string CollectionName()
{
return "State_Contents";
}
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[]? status, bool inDraft, ClrQuery query, bool includeDraft = true) public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[]? status, bool inDraft, ClrQuery query, bool includeDraft = true)
{ {
using (Profiler.TraceMethod<MongoContentRepository>("QueryAsyncByQuery")) using (Profiler.TraceMethod<MongoContentRepository>("QueryAsyncByQuery"))

32
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContentsByIds.cs

@ -8,7 +8,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
@ -31,18 +30,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
this.appProvider = appProvider; this.appProvider = appProvider;
} }
protected override Task PrepareAsync(CancellationToken ct = default)
{
var index =
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedAppId)
.Ascending(x => x.IsDeleted)
.Ascending(x => x.Status)
.Descending(x => x.LastModified));
return Collection.Indexes.CreateOneAsync(index, cancellationToken: ct);
}
public async Task<List<(IContentEntity Content, ISchemaEntity Schema)>> DoAsync(Guid appId, ISchemaEntity? schema, HashSet<Guid> ids, Status[]? status, bool includeDraft) public async Task<List<(IContentEntity Content, ISchemaEntity Schema)>> DoAsync(Guid appId, ISchemaEntity? schema, HashSet<Guid> ids, Status[]? status, bool includeDraft)
{ {
Guard.NotNull(ids); Guard.NotNull(ids);
@ -76,12 +63,19 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
schemas[schema.Id] = schema; schemas[schema.Id] = schema;
} }
var misingSchemaIds = contentItems.Select(x => x.IndexedSchemaId).Distinct().Where(x => !schemas.ContainsKey(x)); var schemaIds = contentItems.Select(x => x.IndexedSchemaId).Distinct();
var missingSchemas = await Task.WhenAll(misingSchemaIds.Select(x => appProvider.GetSchemaAsync(appId, x)));
foreach (var missingSchema in missingSchemas) foreach (var schemaId in schemaIds)
{ {
schemas[missingSchema.Id] = missingSchema; if (!schemas.ContainsKey(schemaId))
{
var found = await appProvider.GetSchemaAsync(appId, schemaId);
if (found != null)
{
schemas[schemaId] = found;
}
}
} }
return schemas; return schemas;
@ -99,10 +93,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
{ {
filters.Add(Filter.In(x => x.Status, status)); filters.Add(Filter.In(x => x.Status, status));
} }
else
{
filters.Add(Filter.Exists(x => x.Status));
}
if (ids != null && ids.Count > 0) if (ids != null && ids.Count > 0)
{ {

41
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContentsByQuery.cs

@ -7,7 +7,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MongoDB.Driver; using MongoDB.Driver;
@ -37,23 +36,14 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
protected override Task PrepareAsync(CancellationToken ct = default) protected override Task PrepareAsync(CancellationToken ct = default)
{ {
var index1 = var index =
new CreateIndexModel<MongoContentEntity>(Index new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedSchemaId) .Ascending(x => x.IndexedSchemaId)
.Ascending(x => x.IsDeleted) .Ascending(x => x.IsDeleted)
.Ascending(x => x.Status) .Ascending(x => x.Status)
.Ascending(x => x.Id)
.Ascending(x => x.ReferencedIds)
.Descending(x => x.LastModified)); .Descending(x => x.LastModified));
var index2 = return Collection.Indexes.CreateOneAsync(index, cancellationToken: ct);
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedSchemaId)
.Ascending(x => x.IsDeleted)
.Ascending(x => x.Status)
.Descending(x => x.LastModified));
return Collection.Indexes.CreateManyAsync(new[] { index1, index2 }, ct);
} }
public async Task<IResultList<IContentEntity>> DoAsync(IAppEntity app, ISchemaEntity schema, ClrQuery query, Status[]? status, bool inDraft, bool includeDraft = true) public async Task<IResultList<IContentEntity>> DoAsync(IAppEntity app, ISchemaEntity schema, ClrQuery query, Status[]? status, bool inDraft, bool includeDraft = true)
@ -84,9 +74,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
var contentItems = var contentItems =
Collection.Find(filter) Collection.Find(filter)
.WithoutDraft(includeDraft) .WithoutDraft(includeDraft)
.Take(query) .QueryLimit(query)
.Skip(query) .QuerySkip(query)
.Sort(query) .QuerySort(query)
.ToListAsync(); .ToListAsync();
await Task.WhenAll(contentItems, contentCount); await Task.WhenAll(contentItems, contentCount);
@ -123,29 +113,10 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
{ {
filters.Add(Filter.In(x => x.Status, status)); filters.Add(Filter.In(x => x.Status, status));
} }
else
{
filters.Add(Filter.Exists(x => x.Status));
}
if (ids != null && ids.Count > 0) if (ids != null && ids.Count > 0)
{ {
if (ids.Count > 1) filters.Add(Filter.In(x => x.Id, ids));
{
filters.Add(
Filter.Or(
Filter.In(x => x.Id, ids),
Filter.AnyIn(x => x.ReferencedIds, ids)));
}
else
{
var first = ids.First();
filters.Add(
Filter.Or(
Filter.Eq(x => x.Id, first),
Filter.AnyEq(x => x.ReferencedIds, first)));
}
} }
if (query?.Filter != null) if (query?.Filter != null)

10
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryIdsAsync.cs

@ -29,18 +29,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
protected override Task PrepareAsync(CancellationToken ct = default) protected override Task PrepareAsync(CancellationToken ct = default)
{ {
var index1 = var index =
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedAppId)
.Ascending(x => x.Id)
.Ascending(x => x.IsDeleted));
var index2 =
new CreateIndexModel<MongoContentEntity>(Index new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedSchemaId) .Ascending(x => x.IndexedSchemaId)
.Ascending(x => x.IsDeleted)); .Ascending(x => x.IsDeleted));
return Collection.Indexes.CreateManyAsync(new[] { index1, index2 }, ct); return Collection.Indexes.CreateOneAsync(index, cancellationToken: ct);
} }
public async Task<IReadOnlyList<(Guid SchemaId, Guid Id)>> DoAsync(Guid appId, HashSet<Guid> ids) public async Task<IReadOnlyList<(Guid SchemaId, Guid Id)>> DoAsync(Guid appId, HashSet<Guid> ids)

6
backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/LimitExtensions.cs

@ -12,7 +12,7 @@ namespace Squidex.Infrastructure.MongoDb.Queries
{ {
public static class LimitExtensions public static class LimitExtensions
{ {
public static IFindFluent<T, T> Take<T>(this IFindFluent<T, T> cursor, ClrQuery query) public static IFindFluent<T, T> QueryLimit<T>(this IFindFluent<T, T> cursor, ClrQuery query)
{ {
if (query.Take < long.MaxValue) if (query.Take < long.MaxValue)
{ {
@ -22,7 +22,7 @@ namespace Squidex.Infrastructure.MongoDb.Queries
return cursor; return cursor;
} }
public static IFindFluent<T, T> Skip<T>(this IFindFluent<T, T> cursor, ClrQuery query) public static IFindFluent<T, T> QuerySkip<T>(this IFindFluent<T, T> cursor, ClrQuery query)
{ {
if (query.Skip > 0) if (query.Skip > 0)
{ {
@ -32,7 +32,7 @@ namespace Squidex.Infrastructure.MongoDb.Queries
return cursor; return cursor;
} }
public static IFindFluent<T, T> Sort<T>(this IFindFluent<T, T> cursor, ClrQuery query) public static IFindFluent<T, T> QuerySort<T>(this IFindFluent<T, T> cursor, ClrQuery query)
{ {
return cursor.Sort(query.BuildSort<T>()); return cursor.Sort(query.BuildSort<T>());
} }

3
backend/src/Squidex.Infrastructure/Log/ProfilerSpan.cs

@ -8,6 +8,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
#pragma warning disable IDE0044 // Add readonly modifier
#pragma warning disable RECS0092 // Convert field to readonly
namespace Squidex.Infrastructure.Log namespace Squidex.Infrastructure.Log
{ {
public sealed class ProfilerSpan : IDisposable public sealed class ProfilerSpan : IDisposable

19
backend/src/Squidex/Config/Domain/StoreServices.cs

@ -32,12 +32,10 @@ using Squidex.Domain.Users.MongoDb;
using Squidex.Domain.Users.MongoDb.Infrastructure; using Squidex.Domain.Users.MongoDb.Infrastructure;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Diagnostics; using Squidex.Infrastructure.Diagnostics;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Log.Store; using Squidex.Infrastructure.Log.Store;
using Squidex.Infrastructure.Migrations; using Squidex.Infrastructure.Migrations;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
using Squidex.Infrastructure.UsageTracking; using Squidex.Infrastructure.UsageTracking;
@ -103,25 +101,20 @@ namespace Squidex.Config.Domain
.As<IRoleStore<IdentityRole>>(); .As<IRoleStore<IdentityRole>>();
services.AddSingletonAs<MongoUserStore>() services.AddSingletonAs<MongoUserStore>()
.As<IUserStore<IdentityUser>>() .As<IUserStore<IdentityUser>>().As<IUserFactory>();
.As<IUserFactory>();
services.AddSingletonAs<MongoAssetRepository>() services.AddSingletonAs<MongoAssetRepository>()
.As<IAssetRepository>() .As<IAssetRepository>().As<ISnapshotStore<AssetState, Guid>>();
.As<ISnapshotStore<AssetState, Guid>>();
services.AddSingletonAs<MongoAssetFolderRepository>() services.AddSingletonAs<MongoAssetFolderRepository>()
.As<IAssetFolderRepository>() .As<IAssetFolderRepository>().As<ISnapshotStore<AssetFolderState, Guid>>();
.As<ISnapshotStore<AssetFolderState, Guid>>();
services.AddSingletonAs(c => new MongoContentRepository( services.AddSingletonAs(c => new MongoContentRepository(
c.GetRequiredService<IMongoClient>().GetDatabase(mongoContentDatabaseName), c.GetRequiredService<IMongoClient>().GetDatabase(mongoContentDatabaseName),
c.GetRequiredService<IAppProvider>(), c.GetRequiredService<IAppProvider>(),
c.GetRequiredService<IJsonSerializer>(), c.GetRequiredService<ITextIndexer>(),
c.GetRequiredService<ITextIndexer>())) c.GetRequiredService<IJsonSerializer>()))
.As<IContentRepository>() .As<IContentRepository>().As<ISnapshotStore<ContentState, Guid>>();
.As<ISnapshotStore<ContentState, Guid>>()
.As<IEventConsumer>();
var registration = services.FirstOrDefault(x => x.ServiceType == typeof(IPersistedGrantStore)); var registration = services.FirstOrDefault(x => x.ServiceType == typeof(IPersistedGrantStore));

1
backend/src/Squidex/Pipeline/Plugins/PluginExtensions.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;

8
backend/tests/RunCoverage.ps1

@ -37,7 +37,7 @@ if ($all -Or $appsCore) {
&"$folderHome\.nuget\packages\OpenCover\4.7.922\tools\OpenCover.Console.exe" ` &"$folderHome\.nuget\packages\OpenCover\4.7.922\tools\OpenCover.Console.exe" `
-register:user ` -register:user `
-target:"C:\Program Files\dotnet\dotnet.exe" ` -target:"C:\Program Files\dotnet\dotnet.exe" `
-targetargs:"test $folderWorking\Squidex.Domain.Apps.Core.Tests\Squidex.Domain.Apps.Core.Tests.csproj" ` -targetargs:"test --filter Category!=Dependencies $folderWorking\Squidex.Domain.Apps.Core.Tests\Squidex.Domain.Apps.Core.Tests.csproj" `
-filter:"+[Squidex.*]* -[*.Tests]* -[Squidex.*]*CodeGen*" ` -filter:"+[Squidex.*]* -[*.Tests]* -[Squidex.*]*CodeGen*" `
-excludebyattribute:*.ExcludeFromCodeCoverage* ` -excludebyattribute:*.ExcludeFromCodeCoverage* `
-skipautoprops ` -skipautoprops `
@ -49,7 +49,7 @@ if ($all -Or $appsEntities) {
&"$folderHome\.nuget\packages\OpenCover\4.7.922\tools\OpenCover.Console.exe" ` &"$folderHome\.nuget\packages\OpenCover\4.7.922\tools\OpenCover.Console.exe" `
-register:user ` -register:user `
-target:"C:\Program Files\dotnet\dotnet.exe" ` -target:"C:\Program Files\dotnet\dotnet.exe" `
-targetargs:"test $folderWorking\Squidex.Domain.Apps.Entities.Tests\Squidex.Domain.Apps.Entities.Tests.csproj" ` -targetargs:"test --filter Category!=Dependencies $folderWorking\Squidex.Domain.Apps.Entities.Tests\Squidex.Domain.Apps.Entities.Tests.csproj" `
-filter:"+[Squidex.*]* -[*.Tests]* -[Squidex.*]*CodeGen*" ` -filter:"+[Squidex.*]* -[*.Tests]* -[Squidex.*]*CodeGen*" `
-excludebyattribute:*.ExcludeFromCodeCoverage* ` -excludebyattribute:*.ExcludeFromCodeCoverage* `
-skipautoprops ` -skipautoprops `
@ -61,7 +61,7 @@ if ($all -Or $users) {
&"$folderHome\.nuget\packages\OpenCover\4.7.922\tools\OpenCover.Console.exe" ` &"$folderHome\.nuget\packages\OpenCover\4.7.922\tools\OpenCover.Console.exe" `
-register:user ` -register:user `
-target:"C:\Program Files\dotnet\dotnet.exe" ` -target:"C:\Program Files\dotnet\dotnet.exe" `
-targetargs:"test $folderWorking\Squidex.Domain.Users.Tests\Squidex.Domain.Users.Tests.csproj" ` -targetargs:"test --filter Category!=Dependencies $folderWorking\Squidex.Domain.Users.Tests\Squidex.Domain.Users.Tests.csproj" `
-filter:"+[Squidex.*]* -[*.Tests]* -[Squidex.*]*CodeGen*" ` -filter:"+[Squidex.*]* -[*.Tests]* -[Squidex.*]*CodeGen*" `
-excludebyattribute:*.ExcludeFromCodeCoverage* ` -excludebyattribute:*.ExcludeFromCodeCoverage* `
-skipautoprops ` -skipautoprops `
@ -73,7 +73,7 @@ if ($all -Or $web) {
&"$folderHome\.nuget\packages\OpenCover\4.7.922\tools\OpenCover.Console.exe" ` &"$folderHome\.nuget\packages\OpenCover\4.7.922\tools\OpenCover.Console.exe" `
-register:user ` -register:user `
-target:"C:\Program Files\dotnet\dotnet.exe" ` -target:"C:\Program Files\dotnet\dotnet.exe" `
-targetargs:"test $folderWorking\Squidex.Web.Tests\Squidex.Web.Tests.csproj" ` -targetargs:"test --filter Category!=Dependencies $folderWorking\Squidex.Web.Tests\Squidex.Web.Tests.csproj" `
-filter:"+[Squidex.*]* -[*.Tests]* -[Squidex.*]*CodeGen*" ` -filter:"+[Squidex.*]* -[*.Tests]* -[Squidex.*]*CodeGen*" `
-excludebyattribute:*.ExcludeFromCodeCoverage* ` -excludebyattribute:*.ExcludeFromCodeCoverage* `
-skipautoprops ` -skipautoprops `

5
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Invitation/InviteUserCommandMiddlewareTests.cs

@ -98,10 +98,5 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
A.CallTo(() => userResolver.CreateUserIfNotExistsAsync(A<string>.Ignored, A<bool>.Ignored)) A.CallTo(() => userResolver.CreateUserIfNotExistsAsync(A<string>.Ignored, A<bool>.Ignored))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
private CommandContext Context(AssignContributor command)
{
return new CommandContext(command, commandBus);
}
} }
} }

136
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryFixture.cs

@ -0,0 +1,136 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using Newtonsoft.Json;
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.MongoDb.Assets;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
{
public sealed class AssetsQueryFixture
{
private readonly Random random = new Random();
private readonly int numValues = 250;
private readonly IMongoClient mongoClient = new MongoClient("mongodb://localhost");
private readonly IMongoDatabase mongoDatabase;
public IAssetRepository AssetRepository { get; }
public NamedId<Guid>[] AppIds { get; } = new[]
{
NamedId.Of(Guid.Parse("3b5ba909-e5a5-4858-9d0d-df4ff922d452"), "my-app1"),
NamedId.Of(Guid.Parse("4b3672c1-97c6-4e0b-a067-71e9e9a29db9"), "my-app1")
};
public AssetsQueryFixture()
{
mongoDatabase = mongoClient.GetDatabase("QueryTests");
SetupJson();
var assetRepository = new MongoAssetRepository(mongoDatabase);
Task.Run(async () =>
{
await assetRepository.InitializeAsync();
await mongoDatabase.RunCommandAsync<BsonDocument>("{ profile : 0 }");
await mongoDatabase.DropCollectionAsync("system.profile");
var collection = assetRepository.GetInternalCollection();
var assetCount = await collection.Find(new BsonDocument()).CountDocumentsAsync();
if (assetCount == 0)
{
var batch = new List<MongoAssetEntity>();
async Task ExecuteBatchAsync(MongoAssetEntity? entity)
{
if (entity != null)
{
batch.Add(entity);
}
if ((entity == null || batch.Count >= 1000) && batch.Count > 0)
{
await collection.InsertManyAsync(batch);
batch.Clear();
}
}
foreach (var appId in AppIds)
{
for (var i = 0; i < numValues; i++)
{
var fileName = i.ToString();
for (var j = 0; j < numValues; j++)
{
var tag = j.ToString();
var asset = new MongoAssetEntity
{
Id = Guid.NewGuid(),
AppId = appId,
Tags = new HashSet<string> { tag },
FileHash = fileName,
FileName = fileName,
FileSize = 1024,
IndexedAppId = appId.Id,
IsDeleted = false,
IsProtected = false,
Metadata = new AssetMetadata
{
["value"] = JsonValue.Create(tag)
},
Slug = fileName,
};
await ExecuteBatchAsync(asset);
}
}
}
await ExecuteBatchAsync(null);
}
await mongoDatabase.RunCommandAsync<BsonDocument>("{ profile : 2 }");
}).Wait();
AssetRepository = assetRepository;
}
private static void SetupJson()
{
var jsonSerializer = JsonSerializer.Create(JsonHelper.DefaultSettings());
BsonJsonConvention.Register(jsonSerializer);
}
public Guid RandomAppId()
{
return AppIds[random.Next(0, AppIds.Length)].Id;
}
public string RandomValue()
{
return random.Next(0, numValues).ToString();
}
}
}

146
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryTests.cs

@ -0,0 +1,146 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Queries;
using Xunit;
using F = Squidex.Infrastructure.Queries.ClrFilter;
#pragma warning disable SA1300 // Element should begin with upper-case letter
namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
{
[Trait("Category", "Dependencies")]
public class AssetsQueryTests : IClassFixture<AssetsQueryFixture>
{
public AssetsQueryFixture _ { get; }
public AssetsQueryTests(AssetsQueryFixture fixture)
{
_ = fixture;
}
[Fact]
public async Task Should_find_asset_by_slug()
{
var asset = await _.AssetRepository.FindAssetBySlugAsync(_.RandomAppId(), _.RandomValue());
Assert.NotNull(asset);
}
[Fact]
public async Task Should_query_asset_by_hash()
{
var assets = await _.AssetRepository.QueryByHashAsync(_.RandomAppId(), _.RandomValue());
Assert.NotNull(assets);
}
[Fact]
public async Task Should_verify_ids()
{
var ids = Enumerable.Repeat(0, 50).Select(_ => Guid.NewGuid()).ToHashSet();
var assets = await _.AssetRepository.QueryIdsAsync(_.RandomAppId(), ids);
Assert.NotNull(assets);
}
[Theory]
[MemberData(nameof(ParentIds))]
public async Task Should_query_assets_by_default(Guid? parentId)
{
var query = new ClrQuery();
var assets = await QueryAsync(parentId, query);
Assert.NotNull(assets);
}
[Theory]
[MemberData(nameof(ParentIds))]
public async Task Should_query_assets_by_tags(Guid? parentId)
{
var query = new ClrQuery
{
Filter = F.Eq("Tags", _.RandomValue())
};
var assets = await QueryAsync(parentId, query);
Assert.NotNull(assets);
}
[Theory]
[MemberData(nameof(ParentIds))]
public async Task Should_query_assets_by_tags_and_name(Guid? parentId)
{
var query = new ClrQuery
{
Filter = F.And(F.Eq("Tags", _.RandomValue()), F.Contains("FileName", _.RandomValue()))
};
var assets = await QueryAsync(parentId, query);
Assert.NotNull(assets);
}
[Theory]
[MemberData(nameof(ParentIds))]
public async Task Should_query_assets_by_fileName(Guid? parentId)
{
var query = new ClrQuery
{
Filter = F.Contains("FileName", _.RandomValue())
};
var assets = await QueryAsync(parentId, query);
Assert.NotNull(assets);
}
[Theory]
[MemberData(nameof(ParentIds))]
public async Task Should_query_assets_by_fileName_and_tags(Guid? parentId)
{
var query = new ClrQuery
{
Filter = F.And(F.Contains("FileName", _.RandomValue()), F.Eq("Tags", _.RandomValue()))
};
var assets = await QueryAsync(parentId, query);
Assert.NotNull(assets);
}
public static IEnumerable<object?[]> ParentIds()
{
yield return new object?[] { null };
yield return new object?[] { Guid.Empty };
}
private async Task<IResultList<IAssetEntity>> QueryAsync(Guid? parentId, ClrQuery query)
{
query.Top = 1000;
query.Skip = 100;
query.Sort = new List<SortNode>
{
new SortNode("LastModified", SortOrder.Descending)
};
var assets = await _.AssetRepository.QueryAsync(_.RandomAppId(), parentId, query);
return assets;
}
}
}

6
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/MongoDbQueryTests.cs

@ -170,7 +170,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
var query = new ClrQuery { Take = 3 }; var query = new ClrQuery { Take = 3 };
var cursor = A.Fake<IFindFluent<MongoAssetEntity, MongoAssetEntity>>(); var cursor = A.Fake<IFindFluent<MongoAssetEntity, MongoAssetEntity>>();
cursor.Take(query.AdjustToModel()); cursor.QueryLimit(query.AdjustToModel());
A.CallTo(() => cursor.Limit(3)) A.CallTo(() => cursor.Limit(3))
.MustHaveHappened(); .MustHaveHappened();
@ -182,7 +182,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
var query = new ClrQuery { Skip = 3 }; var query = new ClrQuery { Skip = 3 };
var cursor = A.Fake<IFindFluent<MongoAssetEntity, MongoAssetEntity>>(); var cursor = A.Fake<IFindFluent<MongoAssetEntity, MongoAssetEntity>>();
cursor.Skip(query.AdjustToModel()); cursor.QuerySkip(query.AdjustToModel());
A.CallTo(() => cursor.Skip(3)) A.CallTo(() => cursor.Skip(3))
.MustHaveHappened(); .MustHaveHappened();
@ -210,7 +210,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
i = sortDefinition.Render(Serializer, Registry).ToString(); i = sortDefinition.Render(Serializer, Registry).ToString();
}); });
cursor.Sort(new ClrQuery { Sort = sorts.ToList() }.AdjustToModel()); cursor.QuerySort(new ClrQuery { Sort = sorts.ToList() }.AdjustToModel());
return i; return i;
} }

211
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryFixture.cs

@ -0,0 +1,211 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FakeItEasy;
using MongoDB.Bson;
using MongoDB.Driver;
using Newtonsoft.Json;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Entities.Contents.Text;
using Squidex.Domain.Apps.Entities.MongoDb.Contents;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
{
public sealed class ContentsQueryFixture
{
private readonly Random random = new Random();
private readonly int numValues = 10000;
private readonly IMongoClient mongoClient = new MongoClient("mongodb://localhost");
private readonly IMongoDatabase mongoDatabase;
public IContentRepository ContentRepository { get; }
public NamedId<Guid>[] AppIds { get; } = new[]
{
NamedId.Of(Guid.Parse("3b5ba909-e5a5-4858-9d0d-df4ff922d452"), "my-app1"),
NamedId.Of(Guid.Parse("4b3672c1-97c6-4e0b-a067-71e9e9a29db9"), "my-app1")
};
public NamedId<Guid>[] SchemaIds { get; } = new[]
{
NamedId.Of(Guid.Parse("3b5ba909-e5a5-4858-9d0d-df4ff922d452"), "my-schema1"),
NamedId.Of(Guid.Parse("4b3672c1-97c6-4e0b-a067-71e9e9a29db9"), "my-schema2"),
NamedId.Of(Guid.Parse("76357c9b-0514-4377-9fcc-a632e7ef960d"), "my-schema3"),
NamedId.Of(Guid.Parse("164c451e-e5a8-41f8-8aaf-e4b56603d7e7"), "my-schema4"),
NamedId.Of(Guid.Parse("741e902c-fdfa-41ad-8e5a-b7cb9d6e3d94"), "my-schema5")
};
public ContentsQueryFixture()
{
mongoDatabase = mongoClient.GetDatabase("QueryTests");
SetupJson();
var contentRepository =
new MongoContentRepository(
mongoDatabase,
CreateAppProvider(),
CreateTextIndexer(),
JsonHelper.DefaultSerializer);
Task.Run(async () =>
{
await contentRepository.InitializeAsync();
await mongoDatabase.RunCommandAsync<BsonDocument>("{ profile : 0 }");
await mongoDatabase.DropCollectionAsync("system.profile");
var collection = contentRepository.GetInternalCollection();
var contentCount = await collection.Find(new BsonDocument()).CountDocumentsAsync();
if (contentCount == 0)
{
var batch = new List<MongoContentEntity>();
async Task ExecuteBatchAsync(MongoContentEntity? entity)
{
if (entity != null)
{
batch.Add(entity);
}
if ((entity == null || batch.Count >= 1000) && batch.Count > 0)
{
await collection.InsertManyAsync(batch);
batch.Clear();
}
}
foreach (var appId in AppIds)
{
foreach (var schemaId in SchemaIds)
{
for (var i = 0; i < numValues; i++)
{
var value = i.ToString();
var data =
new IdContentData()
.AddField(1,
new ContentFieldData()
.AddJsonValue(JsonValue.Create(value)));
var content = new MongoContentEntity
{
Id = Guid.NewGuid(),
AppId = appId,
DataByIds = data,
DataDraftByIds = data,
IndexedAppId = appId.Id,
IndexedSchemaId = schemaId.Id,
IsDeleted = false,
IsPending = false,
SchemaId = schemaId,
Status = Status.Published
};
await ExecuteBatchAsync(content);
}
}
}
await ExecuteBatchAsync(null);
}
await mongoDatabase.RunCommandAsync<BsonDocument>("{ profile : 2 }");
}).Wait();
ContentRepository = contentRepository;
}
private static IAppProvider CreateAppProvider()
{
var appProvider = A.Fake<IAppProvider>();
A.CallTo(() => appProvider.GetSchemaAsync(A<Guid>.Ignored, A<Guid>.Ignored, false))
.ReturnsLazily(x => Task.FromResult<ISchemaEntity?>(CreateSchema(x.GetArgument<Guid>(0)!, x.GetArgument<Guid>(1)!)));
return appProvider;
}
private static ITextIndexer CreateTextIndexer()
{
var textIndexer = A.Fake<ITextIndexer>();
A.CallTo(() => textIndexer.SearchAsync(A<string>.Ignored, A<IAppEntity>.Ignored, A<Guid>.Ignored, A<Scope>.Ignored))
.Returns(new List<Guid> { Guid.NewGuid() });
return textIndexer;
}
private static void SetupJson()
{
var jsonSerializer = JsonSerializer.Create(JsonHelper.DefaultSettings());
BsonJsonConvention.Register(jsonSerializer);
}
public Guid RandomAppId()
{
return AppIds[random.Next(0, AppIds.Length)].Id;
}
public IAppEntity RandomApp()
{
return CreateApp(RandomAppId());
}
public Guid RandomSchemaId()
{
return SchemaIds[random.Next(0, SchemaIds.Length)].Id;
}
public ISchemaEntity RandomSchema()
{
return CreateSchema(RandomAppId(), RandomSchemaId());
}
public string RandomValue()
{
return random.Next(0, numValues).ToString();
}
private static IAppEntity CreateApp(Guid appId)
{
return Mocks.App(NamedId.Of(appId, "my-app"));
}
private static ISchemaEntity CreateSchema(Guid appId, Guid schemaId)
{
var schemaDef =
new Schema("my-schema")
.AddField(Fields.String(1, "value", Partitioning.Invariant));
var schema =
Mocks.Schema(
NamedId.Of(appId, "my-app"),
NamedId.Of(schemaId, "my-schema"),
schemaDef);
return schema;
}
}
}

143
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryTests.cs

@ -0,0 +1,143 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Tasks;
using Xunit;
using F = Squidex.Infrastructure.Queries.ClrFilter;
#pragma warning disable SA1300 // Element should begin with upper-case letter
namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
{
[Trait("Category", "Dependencies")]
public class ContentsQueryTests : IClassFixture<ContentsQueryFixture>
{
public ContentsQueryFixture _ { get; }
public ContentsQueryTests(ContentsQueryFixture fixture)
{
_ = fixture;
}
[Fact]
public async Task Should_verify_ids()
{
var ids = Enumerable.Repeat(0, 50).Select(_ => Guid.NewGuid()).ToHashSet();
var contents = await _.ContentRepository.QueryIdsAsync(_.RandomAppId(), ids);
Assert.NotNull(contents);
}
[Fact]
public async Task Should_query_contents_by_ids()
{
var ids = Enumerable.Repeat(0, 50).Select(_ => Guid.NewGuid()).ToHashSet();
var contents = await _.ContentRepository.QueryAsync(_.RandomApp(), new[] { Status.Published }, ids, true);
Assert.NotNull(contents);
}
[Fact]
public async Task Should_query_contents_by_ids_and_schema()
{
var ids = Enumerable.Repeat(0, 50).Select(_ => Guid.NewGuid()).ToHashSet();
var contents = await _.ContentRepository.QueryAsync(_.RandomApp(), _.RandomSchema(), new[] { Status.Published }, ids, true);
Assert.NotNull(contents);
}
[Fact]
public async Task Should_query_contents_by_filter()
{
var filter = F.Eq("data.value.iv", _.RandomValue());
var contents = await _.ContentRepository.QueryIdsAsync(_.RandomAppId(), _.RandomSchemaId(), filter);
Assert.NotNull(contents);
}
[Fact]
public async Task Should_query_contents_scheduled()
{
var time = SystemClock.Instance.GetCurrentInstant();
await _.ContentRepository.QueryScheduledWithoutDataAsync(time, _ => TaskHelper.Done);
}
[Theory]
[MemberData(nameof(Statuses))]
public async Task Should_query_contents_by_default(Status[]? status)
{
var query = new ClrQuery();
var contents = await QueryAsync(status, query);
Assert.NotNull(contents);
}
[Theory]
[MemberData(nameof(Statuses))]
public async Task Should_query_contents_with_query_fulltext(Status[]? status)
{
var query = new ClrQuery
{
FullText = "hello"
};
var contents = await QueryAsync(status, query);
Assert.NotNull(contents);
}
[Theory]
[MemberData(nameof(Statuses))]
public async Task Should_query_contents_with_query_filter(Status[]? status)
{
var query = new ClrQuery
{
Filter = F.Eq("data.value.iv", _.RandomValue())
};
var contents = await QueryAsync(status, query);
Assert.NotNull(contents);
}
public static IEnumerable<object?[]> Statuses()
{
yield return new object?[] { null };
yield return new object?[] { new[] { Status.Published } };
}
private async Task<IResultList<IContentEntity>> QueryAsync(Status[]? status, ClrQuery query)
{
query.Top = 1000;
query.Skip = 100;
query.Sort = new List<SortNode>
{
new SortNode("LastModified", SortOrder.Descending)
};
var contents = await _.ContentRepository.QueryAsync(_.RandomApp(), _.RandomSchema(), status, true, query, true);
return contents;
}
}
}

6
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs

@ -276,7 +276,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
var query = new ClrQuery { Take = 3 }; var query = new ClrQuery { Take = 3 };
var cursor = A.Fake<IFindFluent<MongoContentEntity, MongoContentEntity>>(); var cursor = A.Fake<IFindFluent<MongoContentEntity, MongoContentEntity>>();
cursor.Take(query); cursor.QueryLimit(query);
A.CallTo(() => cursor.Limit(3)) A.CallTo(() => cursor.Limit(3))
.MustHaveHappened(); .MustHaveHappened();
@ -288,7 +288,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
var query = new ClrQuery { Skip = 3 }; var query = new ClrQuery { Skip = 3 };
var cursor = A.Fake<IFindFluent<MongoContentEntity, MongoContentEntity>>(); var cursor = A.Fake<IFindFluent<MongoContentEntity, MongoContentEntity>>();
cursor.Skip(query); cursor.QuerySkip(query);
A.CallTo(() => cursor.Skip(3)) A.CallTo(() => cursor.Skip(3))
.MustHaveHappened(); .MustHaveHappened();
@ -316,7 +316,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
i = sortDefinition.Render(Serializer, Registry).ToString(); i = sortDefinition.Render(Serializer, Registry).ToString();
}); });
cursor.Sort(new ClrQuery { Sort = sorts.ToList() }.AdjustToModel(schemaDef, false)); cursor.QuerySort(new ClrQuery { Sort = sorts.ToList() }.AdjustToModel(schemaDef, false));
return i; return i;
} }

4
backend/tests/Squidex.Domain.Apps.Entities.Tests/Tags/TagGrainTests.cs

@ -14,6 +14,8 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
using Xunit; using Xunit;
#pragma warning disable IDE0059 // Unnecessary assignment of a value
namespace Squidex.Domain.Apps.Entities.Tags namespace Squidex.Domain.Apps.Entities.Tags
{ {
public class TagGrainTests public class TagGrainTests
@ -106,7 +108,9 @@ namespace Squidex.Domain.Apps.Entities.Tags
public async Task Should_remove_tags_from_grain() public async Task Should_remove_tags_from_grain()
{ {
var result1 = await sut.NormalizeTagsAsync(HashSet.Of("name1", "name2"), null); var result1 = await sut.NormalizeTagsAsync(HashSet.Of("name1", "name2"), null);
#pragma warning disable IDE0059 // Unnecessary assignment of a value
var result2 = await sut.NormalizeTagsAsync(HashSet.Of("name2", "name3"), null); var result2 = await sut.NormalizeTagsAsync(HashSet.Of("name2", "name3"), null);
#pragma warning restore IDE0059 // Unnecessary assignment of a value
await sut.NormalizeTagsAsync(null, new HashSet<string>(result1.Values)); await sut.NormalizeTagsAsync(null, new HashSet<string>(result1.Values));

2
backend/tests/Squidex.Web.Tests/Pipeline/CleanupHostMiddlewareTests.cs

@ -10,6 +10,8 @@ using Microsoft.AspNetCore.Http;
using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Tasks;
using Xunit; using Xunit;
#pragma warning disable RECS0092 // Convert field to readonly
namespace Squidex.Web.Pipeline namespace Squidex.Web.Pipeline
{ {
public class CleanupHostMiddlewareTests public class CleanupHostMiddlewareTests

2
backend/tools/Migrate_00/Program.cs

@ -13,7 +13,7 @@ namespace Migrate_00
{ {
public class Program public class Program
{ {
public static void Main(string[] args) public static void Main()
{ {
Console.WriteLine("Migrate EventStore"); Console.WriteLine("Migrate EventStore");

6
frontend/app/features/content/shared/forms/stock-photo-editor.component.scss

@ -73,7 +73,7 @@ sqx-list-view {
& { & {
border: 2px solid $color-border; border: 2px solid $color-border;
border-radius: 0; border-radius: 0;
display: inline-block; break-inside: avoid-column;
margin-bottom: .5rem; margin-bottom: .5rem;
margin-right: .5rem; margin-right: .5rem;
position: relative; position: relative;
@ -100,4 +100,8 @@ sqx-list-view {
font-size: 90%; font-size: 90%;
font-weight: normal; font-weight: normal;
} }
img {
width: 100%;
}
} }

2
frontend/app/framework/angular/forms/editors/tag-editor.component.scss

@ -134,7 +134,7 @@ div {
pointer-events: none; pointer-events: none;
i { i {
display: none; opacity: .5;
} }
} }

11
frontend/app/shared/components/forms/markdown-editor.component.ts

@ -214,6 +214,17 @@ export class MarkdownEditorComponent extends StatefulControlComponent<State, str
let content = ''; let content = '';
for (const asset of assets) { for (const asset of assets) {
switch (asset.type) {
case 'Image':
content += `![${asset.fileName}](${asset.fullUrl(this.apiUrl)} '${asset.fileName}')`;
break;
case 'Video':
content += `[${asset.fileName}](${asset.fullUrl(this.apiUrl)} '${asset.fileName}')`;
break;
default:
content += `[${asset.fileName}](${asset.fullUrl(this.apiUrl)} '${asset.fileName}')`;
break;
}
content += `![${asset.fileName}](${asset.fullUrl(this.apiUrl)} '${asset.fileName}')`; content += `![${asset.fileName}](${asset.fullUrl(this.apiUrl)} '${asset.fileName}')`;
} }

6
frontend/app/shared/components/forms/rich-editor.component.ts

@ -211,11 +211,11 @@ export class RichEditorComponent extends StatefulControlComponent<undefined, str
for (const asset of assets) { for (const asset of assets) {
switch (asset.type) { switch (asset.type) {
case 'Video': case 'Image':
content += `<video src="${asset.fullUrl(this.apiUrl)}" />`; content += `<img src="${asset.fullUrl(this.apiUrl)}" alt="${asset.fileName}" />`;
break; break;
case 'Video': case 'Video':
content += `<img src="${asset.fullUrl(this.apiUrl)}" alt="${asset.fileName}" />`; content += `<video src="${asset.fullUrl(this.apiUrl)}" />`;
break; break;
default: default:
content += `<a href="${asset.fullUrl(this.apiUrl)}" alt="${asset.fileName}">${asset.fileName}</a>`; content += `<a href="${asset.fullUrl(this.apiUrl)}" alt="${asset.fileName}">${asset.fileName}</a>`;

Loading…
Cancel
Save