Browse Source

Fix job cleanup (#1075)

* Fix job cleanup

* Fix assets.

* More graphql tests.

* Fix tests

* Another asset test.

* Use final image.
pull/1076/head
Sebastian Stehle 3 years ago
committed by GitHub
parent
commit
31b011f5d4
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 14
      backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj
  2. 2
      backend/src/Migrations/Migrations.csproj
  3. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj
  4. 18
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringAsyncJintExtension.cs
  5. 6
      backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
  6. 4
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj
  7. 5
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetCommandMiddleware.cs
  8. 3
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs
  9. 29
      backend/src/Squidex.Domain.Apps.Entities/Jobs/DefaultJobService.cs
  10. 18
      backend/src/Squidex.Domain.Apps.Entities/Jobs/JobProcessor.cs
  11. 9
      backend/src/Squidex.Domain.Apps.Entities/Jobs/JobWorker.cs
  12. 2
      backend/src/Squidex.Domain.Apps.Entities/Jobs/Messages.cs
  13. 12
      backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj
  14. 2
      backend/src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj
  15. 4
      backend/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj
  16. 6
      backend/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj
  17. 50
      backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs
  18. 10
      backend/src/Squidex.Infrastructure.GetEventStore/Squidex.Infrastructure.GetEventStore.csproj
  19. 6
      backend/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj
  20. 20
      backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
  21. 2
      backend/src/Squidex.Shared/Squidex.Shared.csproj
  22. 8
      backend/src/Squidex.Web/Squidex.Web.csproj
  23. 4
      backend/src/Squidex/Areas/Api/Controllers/Apps/AppImageController.cs
  24. 4
      backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs
  25. 20
      backend/src/Squidex/Areas/Api/Controllers/Translations/TranslationsController.cs
  26. 4
      backend/src/Squidex/Config/Domain/FontendServices.cs
  27. 2
      backend/src/Squidex/Config/Domain/InfrastructureServices.cs
  28. 52
      backend/src/Squidex/Squidex.csproj
  29. 16
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineHelperTests.cs
  30. 8
      backend/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj
  31. 2
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs
  32. 16
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj
  33. 8
      backend/tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj
  34. 8
      backend/tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj
  35. 8
      backend/tests/Squidex.Web.Tests/Squidex.Web.Tests.csproj
  36. 40
      tools/TestSuite/TestSuite.ApiTests/AssetFormatTests.cs
  37. 16
      tools/TestSuite/TestSuite.ApiTests/AssetScriptingTests.cs
  38. BIN
      tools/TestSuite/TestSuite.ApiTests/Assets/SampleImage_100kb.webp
  39. BIN
      tools/TestSuite/TestSuite.ApiTests/Assets/SampleImage_WithLocation.jpg
  40. 4
      tools/TestSuite/TestSuite.ApiTests/ContentLanguageTests.cs
  41. 80
      tools/TestSuite/TestSuite.ApiTests/GraphQLTests.cs
  42. 7
      tools/TestSuite/TestSuite.ApiTests/StatisticsTests.cs
  43. 20
      tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj
  44. 8
      tools/TestSuite/TestSuite.ApiTests/Verify/AssetFormatTests.Should_fix_orientation.verified.txt
  45. 8
      tools/TestSuite/TestSuite.ApiTests/Verify/AssetFormatTests.Should_upload_image_jpg_and_resize.verified.txt
  46. 2
      tools/TestSuite/TestSuite.ApiTests/Verify/AssetFormatTests.Should_upload_image_tiff_and_resize.verified.txt
  47. 8
      tools/TestSuite/TestSuite.ApiTests/Verify/AssetFormatTests.Should_upload_image_webp_and_resize.verified.txt
  48. 44
      tools/TestSuite/TestSuite.ApiTests/Verify/GraphQLTests.Should_query_assets.verified.txt

14
backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj

@ -10,26 +10,26 @@
<ProjectReference Include="..\..\src\Squidex.Web\Squidex.Web.csproj" /> <ProjectReference Include="..\..\src\Squidex.Web\Squidex.Web.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Algolia.Search" Version="6.16.0" /> <PackageReference Include="Algolia.Search" Version="6.17.0" />
<PackageReference Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.2.0" /> <PackageReference Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.2.0" />
<PackageReference Include="Azure.Search.Documents" Version="11.5.1" /> <PackageReference Include="Azure.Search.Documents" Version="11.5.1" />
<PackageReference Include="Confluent.SchemaRegistry.Serdes.Avro" Version="2.3.0" /> <PackageReference Include="Confluent.SchemaRegistry.Serdes.Avro" Version="2.3.0" />
<PackageReference Include="CoreTweet" Version="1.0.0.483" /> <PackageReference Include="CoreTweet" Version="1.0.0.483" />
<PackageReference Include="Elasticsearch.Net" Version="7.17.5" /> <PackageReference Include="Elasticsearch.Net" Version="7.17.5" />
<PackageReference Include="Google.Cloud.Diagnostics.Common" Version="5.1.0" /> <PackageReference Include="Google.Cloud.Diagnostics.Common" Version="5.1.0" />
<PackageReference Include="Google.Cloud.Logging.V2" Version="4.1.0" /> <PackageReference Include="Google.Cloud.Logging.V2" Version="4.2.0" />
<PackageReference Include="Google.Cloud.Monitoring.V3" Version="3.5.0" /> <PackageReference Include="Google.Cloud.Monitoring.V3" Version="3.6.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.139"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.145">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Azure.CognitiveServices.Vision.ComputerVision" Version="7.0.1" /> <PackageReference Include="Microsoft.Azure.CognitiveServices.Vision.ComputerVision" Version="7.0.1" />
<PackageReference Include="Microsoft.Azure.SignalR.Management" Version="1.24.0" /> <PackageReference Include="Microsoft.Azure.SignalR.Management" Version="1.25.1" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="Microsoft.OData.Core" Version="7.20.0" /> <PackageReference Include="Microsoft.OData.Core" Version="7.20.0" />
<PackageReference Include="NodaTime" Version="3.1.10" /> <PackageReference Include="NodaTime" Version="3.1.11" />
<PackageReference Include="OpenSearch.Net" Version="1.6.0" /> <PackageReference Include="OpenSearch.Net" Version="1.7.0" />
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.7.0" /> <PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.7.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.7.0" /> <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.7.0" />
<PackageReference Include="OpenTelemetry.Exporter.Zipkin" Version="1.7.0" /> <PackageReference Include="OpenTelemetry.Exporter.Zipkin" Version="1.7.0" />

2
backend/src/Migrations/Migrations.csproj

@ -6,7 +6,7 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="2.0.139"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.145">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

2
backend/src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj

@ -12,7 +12,7 @@
<DebugSymbols>True</DebugSymbols> <DebugSymbols>True</DebugSymbols>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="2.0.139"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.145">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

18
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringAsyncJintExtension.cs

@ -20,12 +20,12 @@ public sealed class StringAsyncJintExtension : IJintExtension, IScriptDescriptor
private delegate void TextGenerateDelegate(string prompt, Action<JsValue> callback); private delegate void TextGenerateDelegate(string prompt, Action<JsValue> callback);
private delegate void TextTranslateDelegate(string text, string language, Action<JsValue> callback, string sourceLanguage); private delegate void TextTranslateDelegate(string text, string language, Action<JsValue> callback, string sourceLanguage);
private readonly ITranslator translator; private readonly ITranslator translator;
private readonly IChatBot chatBot; private readonly IChatAgent chatAgent;
public StringAsyncJintExtension(ITranslator translator, IChatBot chatBot) public StringAsyncJintExtension(ITranslator translator, IChatAgent chatAgent)
{ {
this.translator = translator; this.translator = translator;
this.chatBot = chatBot; this.chatAgent = chatAgent;
} }
public void ExtendAsync(ScriptExecutionContext context) public void ExtendAsync(ScriptExecutionContext context)
@ -61,9 +61,17 @@ public sealed class StringAsyncJintExtension : IJintExtension, IScriptDescriptor
return; return;
} }
var result = await chatBot.AskQuestionAsync(prompt, ct); var conversationId = Guid.NewGuid().ToString();
try
{
var result = await chatAgent.PromptAsync(conversationId, prompt, ct);
scheduler.Run(callback, JsValue.FromObject(context.Engine, result.Choices.FirstOrDefault())); scheduler.Run(callback, JsValue.FromObject(context.Engine, result.Text));
}
finally
{
await chatAgent.StopConversationAsync(conversationId);
}
} }
catch (Exception ex) catch (Exception ex)
{ {

6
backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj

@ -20,15 +20,15 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Fluid.Core" Version="2.5.0" /> <PackageReference Include="Fluid.Core" Version="2.5.0" />
<PackageReference Include="GeoJSON.Net" Version="1.2.19" /> <PackageReference Include="GeoJSON.Net" Version="1.2.19" />
<PackageReference Include="Jint" Version="3.0.0" /> <PackageReference Include="Jint" Version="3.0.1" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.139"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.145">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="NJsonSchema" Version="11.0.0" /> <PackageReference Include="NJsonSchema" Version="11.0.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="Squidex.Messaging.Subscriptions" Version="6.3.7" /> <PackageReference Include="Squidex.Messaging.Subscriptions" Version="6.4.1" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="System.Collections.Immutable" Version="8.0.0" /> <PackageReference Include="System.Collections.Immutable" Version="8.0.0" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" /> <PackageReference Include="System.Linq.Async" Version="6.0.1" />

4
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj

@ -19,11 +19,11 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00016" /> <PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00016" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.139"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.145">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="MongoDB.Driver" Version="2.23.1" /> <PackageReference Include="MongoDB.Driver" Version="2.24.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />

5
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetCommandMiddleware.cs

@ -75,6 +75,7 @@ public sealed class AssetCommandMiddleware : CachingDomainObjectMiddleware<Asset
try try
{ {
await EnrichWithMetadataAsync(command, ct);
await EnrichWithHashAndUploadAsync(command, command.FileId, ct); await EnrichWithHashAndUploadAsync(command, command.FileId, ct);
if (!duplicate) if (!duplicate)
@ -95,8 +96,6 @@ public sealed class AssetCommandMiddleware : CachingDomainObjectMiddleware<Asset
} }
} }
await EnrichWithMetadataAsync(command, ct);
await base.HandleAsync(context, next, ct); await base.HandleAsync(context, next, ct);
} }
finally finally
@ -113,8 +112,8 @@ public sealed class AssetCommandMiddleware : CachingDomainObjectMiddleware<Asset
try try
{ {
await EnrichWithHashAndUploadAsync(command, command.FileId, ct);
await EnrichWithMetadataAsync(command, ct); await EnrichWithMetadataAsync(command, ct);
await EnrichWithHashAndUploadAsync(command, command.FileId, ct);
await base.HandleAsync(context, next, ct); await base.HandleAsync(context, next, ct);
} }

3
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs

@ -43,8 +43,7 @@ public sealed class GraphQLExecutionContext : QueryExecutionContext
Context = context.Clone(b => b Context = context.Clone(b => b
.WithResolveSchemaNames() .WithResolveSchemaNames()
.WithNoCleanup() .WithNoCleanup()
.WithNoEnrichment() .WithNoEnrichment());
.WithNoAssetEnrichment());
this.options = options.Value; this.options = options.Value;

29
backend/src/Squidex.Domain.Apps.Entities/Jobs/DefaultJobService.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Concurrent;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
@ -15,6 +16,7 @@ namespace Squidex.Domain.Apps.Entities.Jobs;
public sealed class DefaultJobService : IJobService, IDeleter public sealed class DefaultJobService : IJobService, IDeleter
{ {
private readonly ConcurrentDictionary<DomainId, bool> wokenUp = [];
private readonly IMessageBus messaging; private readonly IMessageBus messaging;
private readonly IEnumerable<IJobRunner> runners; private readonly IEnumerable<IJobRunner> runners;
private readonly IPersistenceFactory<JobsState> persistence; private readonly IPersistenceFactory<JobsState> persistence;
@ -42,6 +44,7 @@ public sealed class DefaultJobService : IJobService, IDeleter
throw new InvalidOperationException("Invalid job."); throw new InvalidOperationException("Invalid job.");
} }
// This should never happen, but just in case we remove a task, it is there to get a proper error.
var runner = runners.FirstOrDefault(x => x.Name == job.TaskName) ?? var runner = runners.FirstOrDefault(x => x.Name == job.TaskName) ??
throw new InvalidOperationException("Invalid job."); throw new InvalidOperationException("Invalid job.");
@ -51,12 +54,13 @@ public sealed class DefaultJobService : IJobService, IDeleter
public async Task StartAsync(DomainId ownerId, JobRequest request, public async Task StartAsync(DomainId ownerId, JobRequest request,
CancellationToken ct = default) CancellationToken ct = default)
{ {
// This should never happen, but just in case we remove a task, it is there to get a proper error.
var runner = runners.FirstOrDefault(x => x.Name == request.TaskName) ?? var runner = runners.FirstOrDefault(x => x.Name == request.TaskName) ??
throw new DomainException(T.Get("jobs.invalidTaskName")); throw new DomainException(T.Get("jobs.invalidTaskName"));
var state = await GetStateAsync(ownerId, ct); // Wakeup the job handler to clear cancelled runs from previous incarnations of the service.
await EnsureWakeupAsync(ownerId, ct);
state.EnsureCanStart(runner); await EnsureCanRunAsync(ownerId, runner, ct);
await messaging.PublishAsync(new JobStart(ownerId, request), null, ct); await messaging.PublishAsync(new JobStart(ownerId, request), null, ct);
} }
@ -76,6 +80,9 @@ public sealed class DefaultJobService : IJobService, IDeleter
public async Task<List<Job>> GetJobsAsync(DomainId ownerId, public async Task<List<Job>> GetJobsAsync(DomainId ownerId,
CancellationToken ct = default) CancellationToken ct = default)
{ {
// Wakeup the job handler to clear cancelled runs from previous incarnations of the service.
await EnsureWakeupAsync(ownerId, default);
var state = await GetStateAsync(ownerId, ct); var state = await GetStateAsync(ownerId, ct);
return state.Jobs; return state.Jobs;
@ -90,4 +97,20 @@ public sealed class DefaultJobService : IJobService, IDeleter
return state.Value; return state.Value;
} }
private async Task EnsureCanRunAsync(DomainId ownerId, IJobRunner runner, CancellationToken ct)
{
var state = await GetStateAsync(ownerId, ct);
state.EnsureCanStart(runner);
}
private async Task EnsureWakeupAsync(DomainId ownerId,
CancellationToken ct)
{
if (wokenUp.TryAdd(ownerId, true))
{
await messaging.PublishAsync(new JobWakeup(ownerId), null, ct);
}
}
} }

18
backend/src/Squidex.Domain.Apps.Entities/Jobs/JobProcessor.cs

@ -54,10 +54,24 @@ public sealed class JobProcessor
{ {
await state.LoadAsync(ct); await state.LoadAsync(ct);
if (state.Value.Jobs.RemoveAll(x => x.Stopped == null) > 0) var pending = state.Value.Jobs.Where(x => x.Stopped == null);
if (pending.Any())
{ {
// This should actually never happen, so we log with warning. // This should actually never happen, so we log with warning.
log.LogWarning("Removed unfinished backups for owner {ownerId} after start.", ownerId); log.LogWarning("Removed unfinished jobs for owner {ownerId} after start.", ownerId);
foreach (var job in pending.ToList())
{
var runner = runners.FirstOrDefault(x => x.Name == job.TaskName);
if (runner != null)
{
await runner.CleanupAsync(job);
}
state.Value.Jobs.Remove(job);
}
await state.WriteAsync(ct); await state.WriteAsync(ct);
} }

9
backend/src/Squidex.Domain.Apps.Entities/Jobs/JobWorker.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Messaging; using Squidex.Messaging;
@ -15,7 +16,8 @@ public sealed class JobWorker :
IMessageHandler<JobStart>, IMessageHandler<JobStart>,
IMessageHandler<JobDelete>, IMessageHandler<JobDelete>,
IMessageHandler<JobCancel>, IMessageHandler<JobCancel>,
IMessageHandler<JobClear> IMessageHandler<JobClear>,
IMessageHandler<JobWakeup>
{ {
private readonly Dictionary<DomainId, Task<JobProcessor>> processors = []; private readonly Dictionary<DomainId, Task<JobProcessor>> processors = [];
private readonly Func<DomainId, JobProcessor> processorFactory; private readonly Func<DomainId, JobProcessor> processorFactory;
@ -62,6 +64,11 @@ public sealed class JobWorker :
await processor.ClearAsync(); await processor.ClearAsync();
} }
public Task HandleAsync(JobWakeup message, CancellationToken ct)
{
return GetJobProcessorAsync(message.OwnerId);
}
private Task<JobProcessor> GetJobProcessorAsync(DomainId appId) private Task<JobProcessor> GetJobProcessorAsync(DomainId appId)
{ {
lock (processors) lock (processors)

2
backend/src/Squidex.Domain.Apps.Entities/Jobs/Messages.cs

@ -20,4 +20,6 @@ public sealed record JobDelete(DomainId OwnerId, DomainId JobId) : JobMessage(Ow
public sealed record JobClear(DomainId OwnerId) : JobMessage(OwnerId); public sealed record JobClear(DomainId OwnerId) : JobMessage(OwnerId);
public sealed record JobWakeup(DomainId OwnerId) : JobMessage(OwnerId);
public abstract record JobMessage(DomainId OwnerId); public abstract record JobMessage(DomainId OwnerId);

12
backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj

@ -24,15 +24,15 @@
<ProjectReference Include="..\Squidex.Shared\Squidex.Shared.csproj" /> <ProjectReference Include="..\Squidex.Shared\Squidex.Shared.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CsvHelper" Version="30.1.0" /> <PackageReference Include="CsvHelper" Version="31.0.2" />
<PackageReference Include="GraphQL" Version="7.7.2" /> <PackageReference Include="GraphQL" Version="7.8.0" />
<PackageReference Include="GraphQL.DataLoader" Version="7.7.2" /> <PackageReference Include="GraphQL.DataLoader" Version="7.8.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.139"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.145">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
<PackageReference Include="Notifo.SDK" Version="1.7.4" /> <PackageReference Include="Notifo.SDK" Version="1.7.5" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="Squidex.CLI.Core" Version="13.0.0" /> <PackageReference Include="Squidex.CLI.Core" Version="13.0.0" />
<PackageReference Include="Squidex.YDotNet.Extensions" Version="0.2.9" /> <PackageReference Include="Squidex.YDotNet.Extensions" Version="0.2.9" />
@ -41,7 +41,7 @@
<PackageReference Include="System.Collections.Immutable" Version="8.0.0" /> <PackageReference Include="System.Collections.Immutable" Version="8.0.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="taglib-sharp-netstandard2.0" Version="2.1.0" /> <PackageReference Include="taglib-sharp-netstandard2.0" Version="2.1.0" />
<PackageReference Include="tusdotnet" Version="2.7.2" /> <PackageReference Include="tusdotnet" Version="2.8.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<AdditionalFiles Include="..\..\stylecop.json" Link="stylecop.json" /> <AdditionalFiles Include="..\..\stylecop.json" Link="stylecop.json" />

2
backend/src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj

@ -14,7 +14,7 @@
<ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" /> <ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="2.0.139"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.145">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

4
backend/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj

@ -19,12 +19,12 @@
<ProjectReference Include="..\Squidex.Shared\Squidex.Shared.csproj" /> <ProjectReference Include="..\Squidex.Shared\Squidex.Shared.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="2.0.139"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.145">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" /> <PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="MongoDB.Driver" Version="2.23.1" /> <PackageReference Include="MongoDB.Driver" Version="2.24.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="System.Security.Principal.Windows" Version="5.0.0" /> <PackageReference Include="System.Security.Principal.Windows" Version="5.0.0" />

6
backend/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj

@ -18,13 +18,13 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="IdentityModel" Version="6.2.0" /> <PackageReference Include="IdentityModel" Version="6.2.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.139"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.145">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="8.0.1" /> <PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="8.0.2" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" /> <PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="OpenIddict.AspNetCore" Version="5.1.0" /> <PackageReference Include="OpenIddict.AspNetCore" Version="5.2.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="SharpPwned.NET" Version="2.0.1" /> <PackageReference Include="SharpPwned.NET" Version="2.0.1" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />

50
backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs

@ -7,14 +7,12 @@
using EventStore.Client; using EventStore.Client;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.EventSourcing; namespace Squidex.Infrastructure.EventSourcing;
internal sealed class GetEventStoreSubscription : IEventSubscription internal sealed class GetEventStoreSubscription : IEventSubscription
{ {
private readonly CancellationTokenSource cts = new CancellationTokenSource(); private readonly CancellationTokenSource cts = new CancellationTokenSource();
private StreamSubscription subscription;
public GetEventStoreSubscription( public GetEventStoreSubscription(
IEventSubscriber<StoredEvent> eventSubscriber, IEventSubscriber<StoredEvent> eventSubscriber,
@ -25,58 +23,44 @@ internal sealed class GetEventStoreSubscription : IEventSubscription
string? prefix, string? prefix,
StreamFilter filter) StreamFilter filter)
{ {
var ct = cts.Token;
#pragma warning disable MA0134 // Observe result of async calls #pragma warning disable MA0134 // Observe result of async calls
Task.Run(async () => Task.Run(async () =>
{ {
var ct = cts.Token;
var streamName = await projectionClient.CreateProjectionAsync(filter); var streamName = await projectionClient.CreateProjectionAsync(filter);
async Task OnEvent(StreamSubscription subscription, ResolvedEvent @event, var start = FromStream.Start;
CancellationToken ct) if (!string.IsNullOrWhiteSpace(position))
{ {
var storedEvent = Formatter.Read(@event, prefix, serializer); start = FromStream.After(position.ToPosition(true));
await eventSubscriber.OnNextAsync(this, storedEvent);
} }
void OnError(StreamSubscription subscription, SubscriptionDroppedReason reason, Exception? ex) await using var subscription = client.SubscribeToStream(streamName, start, true, cancellationToken: ct);
try
{ {
if (reason != SubscriptionDroppedReason.Disposed && await foreach (var message in subscription.Messages)
reason != SubscriptionDroppedReason.SubscriberError)
{ {
ex ??= new InvalidOperationException($"Subscription closed with reason {reason}."); if (message is StreamMessage.Event @event)
{
var storedEvent = Formatter.Read(@event.ResolvedEvent, prefix, serializer);
eventSubscriber.OnErrorAsync(this, ex).AsTask().Forget(); await eventSubscriber.OnNextAsync(this, storedEvent);
}
} }
} }
catch (Exception ex)
if (!string.IsNullOrWhiteSpace(position))
{
var from = FromStream.After(position.ToPosition(true));
subscription = await client.SubscribeToStreamAsync(streamName, from,
OnEvent, true,
OnError,
cancellationToken: ct);
}
else
{ {
var from = FromStream.Start; var inner = new InvalidOperationException($"Subscription closed.", ex);
subscription = await client.SubscribeToStreamAsync(streamName, from, await eventSubscriber.OnErrorAsync(this, ex);
OnEvent, true,
OnError,
cancellationToken: ct);
} }
}, cts.Token); }, ct);
#pragma warning restore MA0134 // Observe result of async calls #pragma warning restore MA0134 // Observe result of async calls
} }
public void Dispose() public void Dispose()
{ {
subscription?.Dispose();
cts.Cancel(); cts.Cancel();
} }

10
backend/src/Squidex.Infrastructure.GetEventStore/Squidex.Infrastructure.GetEventStore.csproj

@ -11,11 +11,11 @@
<DebugSymbols>True</DebugSymbols> <DebugSymbols>True</DebugSymbols>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="EventStore.Client.Grpc.PersistentSubscriptions" Version="23.1.0" /> <PackageReference Include="EventStore.Client.Grpc.PersistentSubscriptions" Version="23.2.0" />
<PackageReference Include="EventStore.Client.Grpc.ProjectionManagement" Version="23.1.0" /> <PackageReference Include="EventStore.Client.Grpc.ProjectionManagement" Version="23.2.0" />
<PackageReference Include="EventStore.Client.Grpc.Streams" Version="23.1.0" /> <PackageReference Include="EventStore.Client.Grpc.Streams" Version="23.2.0" />
<PackageReference Include="Grpc.Net.Client" Version="2.60.0" /> <PackageReference Include="Grpc.Net.Client" Version="2.61.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.139"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.145">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

6
backend/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj

@ -14,12 +14,12 @@
<ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" /> <ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="2.0.139"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.145">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="MongoDB.Driver" Version="2.23.1" /> <PackageReference Include="MongoDB.Driver" Version="2.24.0" />
<PackageReference Include="MongoDB.Driver.GridFS" Version="2.23.1" /> <PackageReference Include="MongoDB.Driver.GridFS" Version="2.24.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />

20
backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj

@ -11,25 +11,25 @@
<DebugSymbols>True</DebugSymbols> <DebugSymbols>True</DebugSymbols>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MailKit" Version="4.3.0" /> <PackageReference Include="MailKit" Version="4.4.0" />
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" /> <PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.139"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.145">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions" Version="8.0.1" /> <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions" Version="8.0.2" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" /> <PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
<PackageReference Include="Microsoft.OData.Core" Version="7.20.0" /> <PackageReference Include="Microsoft.OData.Core" Version="7.20.0" />
<PackageReference Include="NodaTime" Version="3.1.10" /> <PackageReference Include="NodaTime" Version="3.1.11" />
<PackageReference Include="OpenTelemetry.Api" Version="1.7.0" /> <PackageReference Include="OpenTelemetry.Api" Version="1.7.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="Squidex.Assets" Version="6.3.7" /> <PackageReference Include="Squidex.Assets" Version="6.4.1" />
<PackageReference Include="Squidex.Caching" Version="6.3.7" /> <PackageReference Include="Squidex.Caching" Version="6.4.1" />
<PackageReference Include="Squidex.Hosting.Abstractions" Version="6.3.7" /> <PackageReference Include="Squidex.Hosting.Abstractions" Version="6.4.1" />
<PackageReference Include="Squidex.Log" Version="6.3.7" /> <PackageReference Include="Squidex.Log" Version="6.4.1" />
<PackageReference Include="Squidex.Messaging" Version="6.3.7" /> <PackageReference Include="Squidex.Messaging" Version="6.4.1" />
<PackageReference Include="Squidex.Text" Version="6.3.7" /> <PackageReference Include="Squidex.Text" Version="6.4.1" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="System.Collections.Immutable" Version="8.0.0" /> <PackageReference Include="System.Collections.Immutable" Version="8.0.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />

2
backend/src/Squidex.Shared/Squidex.Shared.csproj

@ -10,7 +10,7 @@
<DebugSymbols>True</DebugSymbols> <DebugSymbols>True</DebugSymbols>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="2.0.139"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.145">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

8
backend/src/Squidex.Web/Squidex.Web.csproj

@ -13,10 +13,10 @@
<FrameworkReference Include="Microsoft.AspNetCore.App" /> <FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="GraphQL" Version="7.7.2" /> <PackageReference Include="GraphQL" Version="7.8.0" />
<PackageReference Include="GraphQL.SystemTextJson" Version="7.7.2" /> <PackageReference Include="GraphQL.SystemTextJson" Version="7.8.0" />
<PackageReference Include="GraphQL.Server.Transports.AspNetCore" Version="7.6.0" /> <PackageReference Include="GraphQL.Server.Transports.AspNetCore" Version="7.7.1" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.139"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.145">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

4
backend/src/Squidex/Areas/Api/Controllers/Apps/AppImageController.cs

@ -87,8 +87,8 @@ public sealed class AppImageController : ApiController
activity?.SetTag("fileType", mimeType); activity?.SetTag("fileType", mimeType);
await using var assetOriginal = new TempAssetFile(resizedAsset, mimeType, 0); await using var assetOriginal = new TempAssetFile(resizedAsset, mimeType);
await using var assetResized = new TempAssetFile(resizedAsset, mimeType, 0); await using var assetResized = new TempAssetFile(resizedAsset, mimeType);
var resizeOptions = new ResizeOptions var resizeOptions = new ResizeOptions
{ {

4
backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs

@ -211,8 +211,8 @@ public sealed class AssetContentController : ApiController
activity?.SetTag("fileType", asset.MimeType); activity?.SetTag("fileType", asset.MimeType);
activity?.SetTag("fileSize", asset.FileSize); activity?.SetTag("fileSize", asset.FileSize);
await using var assetOriginal = new TempAssetFile(asset.FileName, asset.MimeType, 0); await using var assetOriginal = new TempAssetFile(asset.FileName, asset.MimeType);
await using var assetResized = new TempAssetFile(asset.FileName, asset.MimeType, 0); await using var assetResized = new TempAssetFile(asset.FileName, asset.MimeType);
using (Telemetry.Activities.StartActivity("Read")) using (Telemetry.Activities.StartActivity("Read"))
{ {

20
backend/src/Squidex/Areas/Api/Controllers/Translations/TranslationsController.cs

@ -23,13 +23,13 @@ namespace Squidex.Areas.Api.Controllers.Translations;
public sealed class TranslationsController : ApiController public sealed class TranslationsController : ApiController
{ {
private readonly ITranslator translator; private readonly ITranslator translator;
private readonly IChatBot chatBot; private readonly IChatAgent chatAgent;
public TranslationsController(ICommandBus commandBus, ITranslator translator, IChatBot chatBot) public TranslationsController(ICommandBus commandBus, ITranslator translator, IChatAgent chatAgent)
: base(commandBus) : base(commandBus)
{ {
this.translator = translator; this.translator = translator;
this.chatBot = chatBot; this.chatAgent = chatAgent;
} }
/// <summary> /// <summary>
@ -64,9 +64,17 @@ public sealed class TranslationsController : ApiController
[ApiCosts(10)] [ApiCosts(10)]
public async Task<IActionResult> PostQuestion(string app, [FromBody] AskDto request) public async Task<IActionResult> PostQuestion(string app, [FromBody] AskDto request)
{ {
var result = await chatBot.AskQuestionAsync(request.Prompt, HttpContext.RequestAborted); var conversationId = Guid.NewGuid().ToString();
var response = result.Choices; try
{
var result = await chatAgent.PromptAsync(conversationId, request.Prompt, HttpContext.RequestAborted);
var response = new string[] { result.Text };
return Ok(response); return Ok(response);
}
finally
{
await chatAgent.StopConversationAsync(conversationId, default);
}
} }
} }

4
backend/src/Squidex/Config/Domain/FontendServices.cs

@ -51,9 +51,9 @@ public static class FontendServices
services.Configure<MyUIOptions>((services, options) => services.Configure<MyUIOptions>((services, options) =>
{ {
var chatBot = services.GetRequiredService<IChatBot>(); var chatAgent = services.GetRequiredService<IChatAgent>();
options.More["canUseChatBot"] = chatBot.IsConfigured; options.More["canUseChatBot"] = chatAgent.IsConfigured;
}); });
services.Configure<MyUIOptions>((services, options) => services.Configure<MyUIOptions>((services, options) =>

2
backend/src/Squidex/Config/Domain/InfrastructureServices.cs

@ -135,7 +135,7 @@ public static class InfrastructureServices
services.AddDeepLTranslations(config); services.AddDeepLTranslations(config);
services.AddGoogleCloudTranslations(config); services.AddGoogleCloudTranslations(config);
services.AddOpenAIChatBot(config); services.AddOpenAIChatAgent(config);
} }
public static void AddSquidexLocalization(this IServiceCollection services) public static void AddSquidexLocalization(this IServiceCollection services)

52
backend/src/Squidex/Squidex.csproj

@ -34,47 +34,47 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="AspNet.Security.OAuth.GitHub" Version="8.0.0" /> <PackageReference Include="AspNet.Security.OAuth.GitHub" Version="8.0.0" />
<PackageReference Include="GraphQL" Version="7.7.2" /> <PackageReference Include="GraphQL" Version="7.8.0" />
<PackageReference Include="GraphQL.MicrosoftDI" Version="7.7.2" /> <PackageReference Include="GraphQL.MicrosoftDI" Version="7.8.0" />
<PackageReference Include="GraphQL.SystemTextJson" Version="7.7.2" /> <PackageReference Include="GraphQL.SystemTextJson" Version="7.8.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.139"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.145">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="8.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="8.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="8.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="8.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.2" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="8.0.1" /> <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="8.0.2" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" /> <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
<PackageReference Include="Microsoft.CodeAnalysis.RulesetToEditorconfigConverter" Version="3.3.3" /> <PackageReference Include="Microsoft.CodeAnalysis.RulesetToEditorconfigConverter" Version="3.3.3" />
<PackageReference Include="Microsoft.Data.Edm" Version="5.8.5" /> <PackageReference Include="Microsoft.Data.Edm" Version="5.8.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.1" /> <PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
<PackageReference Include="Microsoft.OData.Core" Version="7.20.0" /> <PackageReference Include="Microsoft.OData.Core" Version="7.20.0" />
<PackageReference Include="MongoDB.Driver" Version="2.23.1" /> <PackageReference Include="MongoDB.Driver" Version="2.24.0" />
<PackageReference Include="MongoDB.Driver.Core.Extensions.OpenTelemetry" Version="1.0.0" /> <PackageReference Include="MongoDB.Driver.Core.Extensions.OpenTelemetry" Version="1.0.0" />
<PackageReference Include="NetTopologySuite.IO.GeoJSON4STJ" Version="4.0.0" /> <PackageReference Include="NetTopologySuite.IO.GeoJSON4STJ" Version="4.0.0" />
<PackageReference Include="NJsonSchema" Version="11.0.0" /> <PackageReference Include="NJsonSchema" Version="11.0.0" />
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.1.2" /> <PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.1.2" />
<PackageReference Include="NSwag.AspNetCore" Version="14.0.2" /> <PackageReference Include="NSwag.AspNetCore" Version="14.0.3" />
<PackageReference Include="OpenCover" Version="4.7.1221" PrivateAssets="all" /> <PackageReference Include="OpenCover" Version="4.7.1221" PrivateAssets="all" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.7.0" /> <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.7.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.7.0" /> <PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.7.1" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="ReportGenerator" Version="5.2.0" PrivateAssets="all" /> <PackageReference Include="ReportGenerator" Version="5.2.2" PrivateAssets="all" />
<PackageReference Include="Squidex.Assets.Azure" Version="6.3.7" /> <PackageReference Include="Squidex.Assets.Azure" Version="6.4.1" />
<PackageReference Include="Squidex.Assets.GoogleCloud" Version="6.3.7" /> <PackageReference Include="Squidex.Assets.GoogleCloud" Version="6.4.1" />
<PackageReference Include="Squidex.Assets.FTP" Version="6.3.7" /> <PackageReference Include="Squidex.Assets.FTP" Version="6.4.1" />
<PackageReference Include="Squidex.Assets.ImageMagick" Version="6.3.7" /> <PackageReference Include="Squidex.Assets.ImageMagick" Version="6.4.1" />
<PackageReference Include="Squidex.Assets.ImageSharp" Version="6.3.7" /> <PackageReference Include="Squidex.Assets.ImageSharp" Version="6.4.1" />
<PackageReference Include="Squidex.Assets.Mongo" Version="6.3.7" /> <PackageReference Include="Squidex.Assets.Mongo" Version="6.4.1" />
<PackageReference Include="Squidex.Assets.S3" Version="6.3.7" /> <PackageReference Include="Squidex.Assets.S3" Version="6.4.1" />
<PackageReference Include="Squidex.Assets.TusAdapter" Version="6.3.7" /> <PackageReference Include="Squidex.Assets.TusAdapter" Version="6.4.1" />
<PackageReference Include="Squidex.ClientLibrary" Version="19.1.0" /> <PackageReference Include="Squidex.ClientLibrary" Version="19.1.0" />
<PackageReference Include="Squidex.Hosting" Version="6.3.7" /> <PackageReference Include="Squidex.Hosting" Version="6.4.1" />
<PackageReference Include="Squidex.Messaging.All" Version="6.3.7" /> <PackageReference Include="Squidex.Messaging.All" Version="6.4.1" />
<PackageReference Include="Squidex.Messaging.Subscriptions" Version="6.3.7" /> <PackageReference Include="Squidex.Messaging.Subscriptions" Version="6.4.1" />
<PackageReference Include="Squidex.OpenIddict.MongoDb" Version="5.1.0" /> <PackageReference Include="Squidex.OpenIddict.MongoDb" Version="5.1.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="YDotNet" Version="0.3.0" /> <PackageReference Include="YDotNet" Version="0.3.0" />

16
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineHelperTests.cs

@ -24,7 +24,7 @@ public class JintScriptEngineHelperTests : IClassFixture<TranslationsFixture>
{ {
private readonly IHttpClientFactory httpClientFactory = A.Fake<IHttpClientFactory>(); private readonly IHttpClientFactory httpClientFactory = A.Fake<IHttpClientFactory>();
private readonly ITranslator translator = A.Fake<ITranslator>(); private readonly ITranslator translator = A.Fake<ITranslator>();
private readonly IChatBot chatBot = A.Fake<IChatBot>(); private readonly IChatAgent chatAgent = A.Fake<IChatAgent>();
private readonly JintScriptEngine sut; private readonly JintScriptEngine sut;
public JintScriptEngineHelperTests() public JintScriptEngineHelperTests()
@ -35,7 +35,7 @@ public class JintScriptEngineHelperTests : IClassFixture<TranslationsFixture>
new HttpJintExtension(httpClientFactory), new HttpJintExtension(httpClientFactory),
new StringJintExtension(), new StringJintExtension(),
new StringWordsJintExtension(), new StringWordsJintExtension(),
new StringAsyncJintExtension(translator, chatBot) new StringAsyncJintExtension(translator, chatAgent)
}; };
sut = new JintScriptEngine(new MemoryCache(Options.Create(new MemoryCacheOptions())), sut = new JintScriptEngine(new MemoryCache(Options.Create(new MemoryCacheOptions())),
@ -619,8 +619,8 @@ public class JintScriptEngineHelperTests : IClassFixture<TranslationsFixture>
[Fact] [Fact]
public async Task Should_generate_content() public async Task Should_generate_content()
{ {
A.CallTo(() => chatBot.AskQuestionAsync("prompt", A<CancellationToken>._)) A.CallTo(() => chatAgent.PromptAsync(A<string>._, "prompt", A<CancellationToken>._))
.Returns(new ChatBotResult { Choices = new List<string> { "Generated" } }); .Returns(ChatBotResponse.Success("Generated"));
var vars = new ScriptVars var vars = new ScriptVars
{ {
@ -635,6 +635,9 @@ public class JintScriptEngineHelperTests : IClassFixture<TranslationsFixture>
var actual = await sut.ExecuteAsync(vars, script); var actual = await sut.ExecuteAsync(vars, script);
Assert.Equal("Generated", actual.ToString()); Assert.Equal("Generated", actual.ToString());
A.CallTo(() => chatAgent.StopConversationAsync(A<string>._, A<CancellationToken>._))
.MustHaveHappened();
} }
[Theory] [Theory]
@ -657,7 +660,10 @@ public class JintScriptEngineHelperTests : IClassFixture<TranslationsFixture>
Assert.Equal(JsonValue.Null, actual); Assert.Equal(JsonValue.Null, actual);
A.CallTo(() => chatBot.AskQuestionAsync(A<string>._, A<CancellationToken>._)) A.CallTo(() => chatAgent.PromptAsync(A<string>._, A<string>._, A<CancellationToken>._))
.MustNotHaveHappened();
A.CallTo(() => chatAgent.StopConversationAsync(A<string>._, A<CancellationToken>._))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }

8
backend/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj

@ -16,19 +16,19 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="FakeItEasy" Version="8.1.0" /> <PackageReference Include="FakeItEasy" Version="8.1.0" />
<PackageReference Include="FluentAssertions" Version="6.12.0" /> <PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.139"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.145">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="NetTopologySuite.IO.GeoJSON4STJ" Version="4.0.0" /> <PackageReference Include="NetTopologySuite.IO.GeoJSON4STJ" Version="4.0.0" />
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.1.2" /> <PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.1.2" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="xunit" Version="2.6.6" /> <PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6"> <PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference> </PackageReference>

2
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs

@ -163,7 +163,7 @@ public abstract class GraphQLTestBase : IClassFixture<TranslationsFixture>
return A<Context>.That.Matches(x => return A<Context>.That.Matches(x =>
x.App == TestApp.Default && x.App == TestApp.Default &&
x.NoCleanup() && x.NoCleanup() &&
x.NoAssetEnrichment()); !x.NoAssetEnrichment());
} }
protected static Context MatchsContentContext() protected static Context MatchsContentContext()

16
backend/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj

@ -24,24 +24,24 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="FakeItEasy" Version="8.1.0" /> <PackageReference Include="FakeItEasy" Version="8.1.0" />
<PackageReference Include="FluentAssertions" Version="6.12.0" /> <PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="GraphQL" Version="7.7.2" /> <PackageReference Include="GraphQL" Version="7.8.0" />
<PackageReference Include="GraphQL.SystemTextJson" Version="7.7.2" /> <PackageReference Include="GraphQL.SystemTextJson" Version="7.8.0" />
<PackageReference Include="Lorem.Universal.Net" Version="4.0.80" /> <PackageReference Include="Lorem.Universal.Net" Version="4.0.80" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.139"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.145">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.1" /> <PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.1.0" /> <PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.1.0" />
<PackageReference Include="Squidex.YDotNet.Native" Version="0.2.9" /> <PackageReference Include="Squidex.YDotNet.Native" Version="0.2.9" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="System.Reactive.Linq" Version="6.0.0" /> <PackageReference Include="System.Reactive.Linq" Version="6.0.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="Verify.Xunit" Version="23.0.1" /> <PackageReference Include="Verify.Xunit" Version="23.2.2" />
<PackageReference Include="xunit" Version="2.6.6" /> <PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6"> <PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference> </PackageReference>

8
backend/tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj

@ -16,15 +16,15 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="FakeItEasy" Version="8.1.0" /> <PackageReference Include="FakeItEasy" Version="8.1.0" />
<PackageReference Include="FluentAssertions" Version="6.12.0" /> <PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.139"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.145">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="xunit" Version="2.6.6" /> <PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6"> <PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference> </PackageReference>

8
backend/tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj

@ -16,7 +16,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="FakeItEasy" Version="8.1.0" /> <PackageReference Include="FakeItEasy" Version="8.1.0" />
<PackageReference Include="FluentAssertions" Version="6.12.0" /> <PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.139"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.145">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
@ -24,12 +24,12 @@
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.1.2" /> <PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.1.2" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="xunit" Version="2.6.6" /> <PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6"> <PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference> </PackageReference>

8
backend/tests/Squidex.Web.Tests/Squidex.Web.Tests.csproj

@ -16,14 +16,14 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="FakeItEasy" Version="8.1.0" /> <PackageReference Include="FakeItEasy" Version="8.1.0" />
<PackageReference Include="FluentAssertions" Version="6.12.0" /> <PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.139"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.145">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="xunit" Version="2.6.6" /> <PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6"> <PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference> </PackageReference>

40
tools/TestSuite/TestSuite.ApiTests/AssetFormatTests.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Squidex.Assets;
using Squidex.ClientLibrary; using Squidex.ClientLibrary;
using TestSuite.Fixtures; using TestSuite.Fixtures;
@ -60,7 +61,7 @@ public class AssetFormatTests : IClassFixture<CreatedAppFixture>
[Fact] [Fact]
public async Task Should_upload_image_jpg_and_resize() public async Task Should_upload_image_jpg_and_resize()
{ {
var asset = await _.Client.Assets.UploadFileAsync("Assets/SampleImage_62kb.jpg", "image/jpg"); var asset = await _.Client.Assets.UploadFileAsync("Assets/SampleImage_62kb.jpg", "image/jpeg");
await AssertImageAsync(asset); await AssertImageAsync(asset);
@ -70,7 +71,7 @@ public class AssetFormatTests : IClassFixture<CreatedAppFixture>
[Fact] [Fact]
public async Task Should_upload_image_webp_and_resize() public async Task Should_upload_image_webp_and_resize()
{ {
var asset = await _.Client.Assets.UploadFileAsync("Assets/SampleImage_100kb.webp", "image/jpg"); var asset = await _.Client.Assets.UploadFileAsync("Assets/SampleImage_100kb.webp", "image/jpeg");
await AssertImageAsync(asset); await AssertImageAsync(asset);
} }
@ -78,7 +79,7 @@ public class AssetFormatTests : IClassFixture<CreatedAppFixture>
[Fact] [Fact]
public async Task Should_upload_image_tiff_and_resize() public async Task Should_upload_image_tiff_and_resize()
{ {
var asset = await _.Client.Assets.UploadFileAsync("Assets/SampleImage_400kb.tiff", "image/jpg"); var asset = await _.Client.Assets.UploadFileAsync("Assets/SampleImage_400kb.tiff", "image/jpeg");
await AssertImageAsync(asset); await AssertImageAsync(asset);
} }
@ -112,6 +113,25 @@ public class AssetFormatTests : IClassFixture<CreatedAppFixture>
await Verify(asset); await Verify(asset);
} }
[Fact]
public async Task Should_upload_image_and_remove_location()
{
var asset = await _.Client.Assets.UploadFileAsync("Assets/SampleImage_WithLocation.jpg", "image/jpeg");
var url = _.Client.GenerateImageUrl(asset.Id);
var httpClient = _.Client.CreateHttpClient();
var httpResonse = await httpClient.GetAsync(url);
await using (var stream = await httpResonse.Content.ReadAsStreamAsync())
{
var imageLoader = new ImageSharpThumbnailGenerator();
var imageInfo = await imageLoader.GetImageInfoAsync(stream, "image/jpeg");
Assert.False(imageInfo?.HasSensitiveMetadata);
}
}
private async Task AssertImageAsync(AssetDto asset) private async Task AssertImageAsync(AssetDto asset)
{ {
await Verify(asset); await Verify(asset);
@ -132,7 +152,7 @@ public class AssetFormatTests : IClassFixture<CreatedAppFixture>
[Fact] [Fact]
public async Task Should_fix_orientation() public async Task Should_fix_orientation()
{ {
var asset = await _.Client.Assets.UploadFileAsync("Assets/logo-wide-rotated.jpg", "image/jpg"); var asset = await _.Client.Assets.UploadFileAsync("Assets/logo-wide-rotated.jpg", "image/jpeg");
// Should parse image metadata and fix orientation. // Should parse image metadata and fix orientation.
Assert.True(asset.IsImage); Assert.True(asset.IsImage);
@ -218,10 +238,9 @@ public class AssetFormatTests : IClassFixture<CreatedAppFixture>
var url = $"{_.Client.GenerateImageUrl(imageId)}?width={width}&height={height}"; var url = $"{_.Client.GenerateImageUrl(imageId)}?width={width}&height={height}";
var httpClient = _.Client.CreateHttpClient(); var httpClient = _.Client.CreateHttpClient();
var httpResponse = await httpClient.GetAsync(url);
var response = await httpClient.GetAsync(url); await using (var stream = await httpResponse.Content.ReadAsStreamAsync())
await using (var stream = await response.Content.ReadAsStreamAsync())
{ {
var buffer = new MemoryStream(); var buffer = new MemoryStream();
@ -236,16 +255,15 @@ public class AssetFormatTests : IClassFixture<CreatedAppFixture>
var url = $"{_.Client.GenerateImageUrl(imageId)}?format={format}"; var url = $"{_.Client.GenerateImageUrl(imageId)}?format={format}";
var httpClient = _.Client.CreateHttpClient(); var httpClient = _.Client.CreateHttpClient();
var httpResponse = await httpClient.GetAsync(url);
var response = await httpClient.GetAsync(url); await using (var stream = await httpResponse.Content.ReadAsStreamAsync())
await using (var stream = await response.Content.ReadAsStreamAsync())
{ {
var buffer = new MemoryStream(); var buffer = new MemoryStream();
await stream.CopyToAsync(buffer); await stream.CopyToAsync(buffer);
return (buffer.Length, response.Content.Headers.ContentType?.ToString()); return (buffer.Length, httpResponse.Content.Headers.ContentType?.ToString());
} }
} }
} }

16
tools/TestSuite/TestSuite.ApiTests/AssetScriptingTests.cs

@ -31,7 +31,7 @@ public class AssetScriptingTests : IClassFixture<ClientFixture>
var scriptRequest = new UpdateAssetScriptsDto var scriptRequest = new UpdateAssetScriptsDto
{ {
Create = @" Create = @"
if (ctx.command.mimeType == 'image/jpg') { if (ctx.command.mimeType == 'image/jpeg') {
disallow('We do not use jpeg anymore.'); disallow('We do not use jpeg anymore.');
}" }"
}; };
@ -40,7 +40,7 @@ public class AssetScriptingTests : IClassFixture<ClientFixture>
// STEP 1: Upload jpeg. // STEP 1: Upload jpeg.
var ex = await Assert.ThrowsAnyAsync<SquidexException>(() => client.Assets.UploadFileAsync("Assets/logo-wide-rotated.jpg", "image/jpg")); var ex = await Assert.ThrowsAnyAsync<SquidexException>(() => client.Assets.UploadFileAsync("Assets/logo-wide-rotated.jpg", "image/jpeg"));
Assert.Contains("We do not use jpeg anymore.", ex.ToString(), StringComparison.Ordinal); Assert.Contains("We do not use jpeg anymore.", ex.ToString(), StringComparison.Ordinal);
} }
@ -64,7 +64,7 @@ public class AssetScriptingTests : IClassFixture<ClientFixture>
// STEP 1: Upload jpeg. // STEP 1: Upload jpeg.
var asset = await client.Assets.UploadFileAsync("Assets/logo-wide-rotated.jpg", "image/jpg"); var asset = await client.Assets.UploadFileAsync("Assets/logo-wide-rotated.jpg", "image/jpeg");
Assert.Equal("value1", asset.Metadata["key1"]); Assert.Equal("value1", asset.Metadata["key1"]);
Assert.Equal("value2", asset.Metadata["key2"]); Assert.Equal("value2", asset.Metadata["key2"]);
@ -81,7 +81,7 @@ public class AssetScriptingTests : IClassFixture<ClientFixture>
var scriptRequest = new UpdateAssetScriptsDto var scriptRequest = new UpdateAssetScriptsDto
{ {
Update = @" Update = @"
if (ctx.command.mimeType == 'image/jpg') { if (ctx.command.mimeType == 'image/jpeg') {
disallow('We do not use jpeg anymore.'); disallow('We do not use jpeg anymore.');
}" }"
}; };
@ -94,7 +94,7 @@ public class AssetScriptingTests : IClassFixture<ClientFixture>
// STEP 2: Upload jpeg. // STEP 2: Upload jpeg.
var ex = await Assert.ThrowsAnyAsync<SquidexException>(() => client.Assets.ReplaceFileAsync(asset_0.Id, "Assets/logo-wide-rotated.jpg", "image/jpg")); var ex = await Assert.ThrowsAnyAsync<SquidexException>(() => client.Assets.ReplaceFileAsync(asset_0.Id, "Assets/logo-wide-rotated.jpg", "image/jpeg"));
Assert.Contains("We do not use jpeg anymore.", ex.ToString(), StringComparison.Ordinal); Assert.Contains("We do not use jpeg anymore.", ex.ToString(), StringComparison.Ordinal);
} }
@ -120,7 +120,7 @@ public class AssetScriptingTests : IClassFixture<ClientFixture>
// STEP 2: Upload jpeg. // STEP 2: Upload jpeg.
var asset = await client.Assets.ReplaceFileAsync(asset_0.Id, "Assets/logo-wide-rotated.jpg", "image/jpg"); var asset = await client.Assets.ReplaceFileAsync(asset_0.Id, "Assets/logo-wide-rotated.jpg", "image/jpeg");
Assert.Equal("value1", asset.Metadata["key1"]); Assert.Equal("value1", asset.Metadata["key1"]);
Assert.Equal("value2", asset.Metadata["key2"]); Assert.Equal("value2", asset.Metadata["key2"]);
@ -135,7 +135,7 @@ public class AssetScriptingTests : IClassFixture<ClientFixture>
var scriptRequest = new UpdateAssetScriptsDto var scriptRequest = new UpdateAssetScriptsDto
{ {
Query = @" Query = @"
if (ctx.asset.mimeType == 'image/jpg') { if (ctx.asset.mimeType == 'image/jpeg') {
disallow('We do not use jpeg anymore.'); disallow('We do not use jpeg anymore.');
}" }"
}; };
@ -144,7 +144,7 @@ public class AssetScriptingTests : IClassFixture<ClientFixture>
// STEP 1. Upload initial jpg. // STEP 1. Upload initial jpg.
var ex = await Assert.ThrowsAnyAsync<SquidexException>(() => client.Assets.UploadFileAsync("Assets/logo-wide-rotated.jpg", "image/jpg")); var ex = await Assert.ThrowsAnyAsync<SquidexException>(() => client.Assets.UploadFileAsync("Assets/logo-wide-rotated.jpg", "image/jpeg"));
Assert.Contains("We do not use jpeg anymore.", ex.ToString(), StringComparison.Ordinal); Assert.Contains("We do not use jpeg anymore.", ex.ToString(), StringComparison.Ordinal);
} }

BIN
tools/TestSuite/TestSuite.ApiTests/Assets/SampleImage_100kb.webp

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 38 KiB

BIN
tools/TestSuite/TestSuite.ApiTests/Assets/SampleImage_WithLocation.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

4
tools/TestSuite/TestSuite.ApiTests/ContentLanguageTests.cs

@ -122,8 +122,8 @@ public class ContentLanguageTests : IClassFixture<ContentFixture>
httpClient.DefaultRequestHeaders.TryAddWithoutValidation(key, value); httpClient.DefaultRequestHeaders.TryAddWithoutValidation(key, value);
} }
var response = await httpClient.GetAsync(url); var httpResponse = await httpClient.GetAsync(url);
return (response.Headers.GetValues("ETag").FirstOrDefault(), response.Headers.Vary.ToString()); return (httpResponse.Headers.GetValues("ETag").FirstOrDefault(), httpResponse.Headers.Vary.ToString());
} }
} }

80
tools/TestSuite/TestSuite.ApiTests/GraphQLTests.cs

@ -7,6 +7,7 @@
using System.Net.Http.Json; using System.Net.Http.Json;
using FluentAssertions; using FluentAssertions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.ClientLibrary; using Squidex.ClientLibrary;
using Squidex.ClientLibrary.Utils; using Squidex.ClientLibrary.Utils;
@ -26,6 +27,71 @@ public sealed class GraphQLTests : IClassFixture<GraphQLFixture>
_ = fixture; _ = fixture;
} }
[Fact]
public async Task Should_query_assets()
{
var asset = await _.Client.Assets.UploadFileAsync("Assets/logo-squared.png", "image/png");
var query = new
{
query = @"
query FindAsset($id: String!) {
findAsset(id: $id) {
id
version
created
createdBy
createdByUser {
id
email
displayName
}
editToken
lastModified
lastModifiedBy
lastModifiedByUser {
id
email
displayName
}
url
thumbnailUrl
mimeType
fileName
fileHash
fileSize
fileVersion
isImage
isProtected
pixelWidth
pixelHeight
parentId
tags
type
metadataText
metadataPixelWidth: metadata(path: ""pixelWidth"")
metadataUnknown: metadata(path: ""unknown"")
metadata
slug
}
}",
variables = new
{
id = asset.Id,
}
};
var result = await _.Client.SharedDynamicContents.GraphQlAsync<JToken>(query);
var settings = new VerifySettings();
settings.IgnoreMember("editToken");
settings.IgnoreMember("thumbnailUrl");
settings.IgnoreMember("url");
settings.IgnoreMember("version");
await VerifyJson(JsonConvert.SerializeObject(result), settings);
}
[Fact] [Fact]
public async Task Should_query_json() public async Task Should_query_json()
{ {
@ -342,12 +408,13 @@ public sealed class GraphQLTests : IClassFixture<GraphQLFixture>
}" }"
}; };
var httpClient = _.Client.CreateHttpClient(); var url = _.Client.GenerateUrl($"api/content/{_.AppName}/graphql/batch");
// Create the request manually to check the content type. // Create the request manually to check the content type.
var response = await httpClient.PostAsync(_.Client.GenerateUrl($"api/content/{_.AppName}/graphql/batch"), query.ToContent(_.Client.Options)); var httpClient = _.Client.CreateHttpClient();
var httpResponse = await httpClient.PostAsync(url, query.ToContent(_.Client.Options));
Assert.Equal("application/json", response.Content.Headers.ContentType?.MediaType); Assert.Equal("application/json", httpResponse.Content.Headers.ContentType?.MediaType);
} }
[Fact] [Fact]
@ -445,10 +512,9 @@ public sealed class GraphQLTests : IClassFixture<GraphQLFixture>
}" }"
}; };
var httpClient = _.Client.CreateHttpClient();
// Create the request manually to check the headers. // Create the request manually to check the headers.
var response = await httpClient.PostAsJsonAsync($"api/content/{_.AppName}/graphql", query); var httpClient = _.Client.CreateHttpClient();
var httpResponse = await httpClient.PostAsJsonAsync($"api/content/{_.AppName}/graphql", query);
Assert.Equal(new[] Assert.Equal(new[]
{ {
@ -462,6 +528,6 @@ public sealed class GraphQLTests : IClassFixture<GraphQLFixture>
"X-ResolveFlow", "X-ResolveFlow",
"X-ResolveUrls", "X-ResolveUrls",
"X-Unpublished" "X-Unpublished"
}, response.Headers.Vary.Order().ToArray()); }, httpResponse.Headers.Vary.Order().ToArray());
} }
} }

7
tools/TestSuite/TestSuite.ApiTests/StatisticsTests.cs

@ -31,11 +31,10 @@ public class StatisticsTests : IClassFixture<CreatedAppFixture>
// STEP 2: Download log. // STEP 2: Download log.
var httpClient = _.Client.CreateHttpClient(); var httpClient = _.Client.CreateHttpClient();
var httpResponse = await httpClient.GetAsync(log.DownloadUrl);
var response = await httpClient.GetAsync(log.DownloadUrl); Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode);
Assert.Equal("text/csv", httpResponse.Content.Headers.GetValues("Content-Type").First());
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("text/csv", response.Content.Headers.GetValues("Content-Type").First());
} }
[Fact] [Fact]

20
tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj

@ -11,18 +11,19 @@
<PackageReference Include="FluentAssertions" Version="6.12.0" /> <PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="GraphQL.Client" Version="6.0.2" /> <PackageReference Include="GraphQL.Client" Version="6.0.2" />
<PackageReference Include="GraphQL.Client.Serializer.Newtonsoft" Version="6.0.2" /> <PackageReference Include="GraphQL.Client.Serializer.Newtonsoft" Version="6.0.2" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.139"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.145">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="NSwag.Core" Version="14.0.2" /> <PackageReference Include="NSwag.Core" Version="14.0.3" />
<PackageReference Include="PuppeteerSharp" Version="14.0.0" /> <PackageReference Include="PuppeteerSharp" Version="14.1.0" />
<PackageReference Include="Squidex.Assets" Version="6.3.7" /> <PackageReference Include="Squidex.Assets" Version="6.4.1" />
<PackageReference Include="Squidex.Assets.ImageSharp" Version="6.4.1" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="Verify.Xunit" Version="23.0.1" /> <PackageReference Include="Verify.Xunit" Version="23.2.2" />
<PackageReference Include="xunit" Version="2.6.6" /> <PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6"> <PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference> </PackageReference>
@ -76,6 +77,9 @@
<None Update="Assets\SampleImage_700kb.bmp"> <None Update="Assets\SampleImage_700kb.bmp">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Update="Assets\SampleImage_WithLocation.jpg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Assets\SampleJPGImage_50kbmb.jpg"> <None Update="Assets\SampleJPGImage_50kbmb.jpg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>

8
tools/TestSuite/TestSuite.ApiTests/Verify/AssetFormatTests.Should_fix_orientation.verified.txt

@ -2,12 +2,12 @@
Id: Guid_1, Id: Guid_1,
ParentId: Guid_Empty, ParentId: Guid_Empty,
FileName: logo-wide-rotated.jpg, FileName: logo-wide-rotated.jpg,
FileHash: TLNAlI4UM3i8O4IF/O7ZuH3PrI3+bsz4AsBt9NrEydI=, FileHash: XFoH3mp4QW9EIWjF50vJkm8IwnrjjO+ZiqV2GnH1GGw=,
IsProtected: false, IsProtected: false,
Slug: logo-wide-rotated.jpg, Slug: logo-wide-rotated.jpg,
MimeType: image/jpg, MimeType: image/jpeg,
FileType: jpg, FileType: jpg,
MetadataText: 600x135px, 15.1 kB, MetadataText: 600x135px, 15.2 kB,
Metadata: { Metadata: {
description: JFIF File, description: JFIF File,
imageQuality: 79, imageQuality: 79,
@ -19,7 +19,7 @@
image, image,
image/medium image/medium
], ],
FileSize: 15425, FileSize: 15598,
Type: Image, Type: Image,
Links: { Links: {
content: { content: {

8
tools/TestSuite/TestSuite.ApiTests/Verify/AssetFormatTests.Should_upload_image_jpg_and_resize.verified.txt

@ -2,12 +2,12 @@
Id: Guid_1, Id: Guid_1,
ParentId: Guid_Empty, ParentId: Guid_Empty,
FileName: SampleImage_62kb.jpg, FileName: SampleImage_62kb.jpg,
FileHash: hmGXmHleJqbU37bbPExix+Jbu36GNq6ZPf09Uw63t8w=, FileHash: ss/g3yq5dO3jYbWzJ9j2psJo1Nrk5TjSi78LJfCma38=,
IsProtected: false, IsProtected: false,
Slug: sampleimage-62kb.jpg, Slug: sampleimage-62kb.jpg,
MimeType: image/jpg, MimeType: image/jpeg,
FileType: jpg, FileType: jpg,
MetadataText: 600x400px, 62 kB, MetadataText: 600x400px, 59.2 kB,
Metadata: { Metadata: {
description: JFIF File, description: JFIF File,
imageQuality: 79, imageQuality: 79,
@ -19,7 +19,7 @@
image, image,
image/medium image/medium
], ],
FileSize: 63507, FileSize: 60603,
Type: Image, Type: Image,
Links: { Links: {
content: { content: {

2
tools/TestSuite/TestSuite.ApiTests/Verify/AssetFormatTests.Should_upload_image_tiff_and_resize.verified.txt

@ -5,7 +5,7 @@
FileHash: f+ZdTDOoK23jmThbfPhWhXL8Q9+KIQajHeFfT/MtWFU=, FileHash: f+ZdTDOoK23jmThbfPhWhXL8Q9+KIQajHeFfT/MtWFU=,
IsProtected: false, IsProtected: false,
Slug: sampleimage-400kb.tiff, Slug: sampleimage-400kb.tiff,
MimeType: image/jpg, MimeType: image/jpeg,
FileType: tiff, FileType: tiff,
MetadataText: 600x400px, 473.6 kB, MetadataText: 600x400px, 473.6 kB,
Metadata: { Metadata: {

8
tools/TestSuite/TestSuite.ApiTests/Verify/AssetFormatTests.Should_upload_image_webp_and_resize.verified.txt

@ -2,12 +2,12 @@
Id: Guid_1, Id: Guid_1,
ParentId: Guid_Empty, ParentId: Guid_Empty,
FileName: SampleImage_100kb.webp, FileName: SampleImage_100kb.webp,
FileHash: WwD409RVswvccG2YopnFqJzpWrCveLrd0Zj6Buw8Vao=, FileHash: fH0g7j4+h2vR6q0/E3BabgDqtFk0FnDfxT/AkuHKkf8=,
IsProtected: false, IsProtected: false,
Slug: sampleimage-100kb.webp, Slug: sampleimage-100kb.webp,
MimeType: image/jpg, MimeType: image/jpeg,
FileType: webp, FileType: webp,
MetadataText: 600x400px, 109.5 kB, MetadataText: 600x400px, 37.7 kB,
Metadata: { Metadata: {
pixelHeight: 400, pixelHeight: 400,
pixelWidth: 600 pixelWidth: 600
@ -17,7 +17,7 @@
image, image,
image/medium image/medium
], ],
FileSize: 112138, FileSize: 38580,
Type: Image, Type: Image,
Links: { Links: {
content: { content: {

44
tools/TestSuite/TestSuite.ApiTests/Verify/GraphQLTests.Should_query_assets.verified.txt

@ -0,0 +1,44 @@
{
findAsset: {
id: Guid_1,
created: DateTimeOffset_1,
createdBy: client:root,
createdByUser: {
id: root,
email: client:root,
displayName: root
},
lastModified: DateTimeOffset_1,
lastModifiedBy: client:root,
lastModifiedByUser: {
id: root,
email: client:root,
displayName: root
},
mimeType: image/png,
fileName: logo-squared.png,
fileHash: pAp0RDvipkoNCCTgey1HTJekRKKEWT6Ft5JdRLuAfAc=,
fileSize: 19430,
fileVersion: 0,
isImage: true,
isProtected: false,
pixelWidth: 600,
pixelHeight: 600,
parentId: Guid_Empty,
tags: [
type/png,
image,
image/medium
],
type: IMAGE,
metadataText: 600x600px, 19 kB,
metadataPixelWidth: 600,
metadataUnknown: null,
metadata: {
pixelWidth: 600,
pixelHeight: 600,
description: PNG File
},
slug: logo-squared.png
}
}
Loading…
Cancel
Save