mirror of https://github.com/Squidex/squidex.git
Browse Source
* Improve tests * Fixes for comments and more tests. * Add missing files. * Increase timeout. * More fixes * More tolerant tests.pull/902/head
committed by
GitHub
44 changed files with 973 additions and 212 deletions
@ -0,0 +1,14 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Infrastructure.Commands |
|||
{ |
|||
public sealed class DomainObjectCacheOptions |
|||
{ |
|||
public TimeSpan CacheDuration { get; set; } = TimeSpan.FromMinutes(10); |
|||
} |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.ClientLibrary.Management; |
|||
using TestSuite.Fixtures; |
|||
using Xunit; |
|||
|
|||
#pragma warning disable SA1300 // Element should begin with upper-case letter
|
|||
#pragma warning disable SA1507 // Code should not contain multiple blank lines in a row
|
|||
|
|||
namespace TestSuite.ApiTests |
|||
{ |
|||
public class CommentsTests : IClassFixture<CreatedAppFixture> |
|||
{ |
|||
private readonly string resource = Guid.NewGuid().ToString(); |
|||
|
|||
public CreatedAppFixture _ { get; } |
|||
|
|||
public CommentsTests(CreatedAppFixture fixture) |
|||
{ |
|||
_ = fixture; |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_make_watch_request() |
|||
{ |
|||
var result = await _.Comments.GetWatchingUsersAsync(_.AppName, resource); |
|||
|
|||
Assert.NotNull(result); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_create_comment() |
|||
{ |
|||
// STEP 1: Create the comment.
|
|||
var createRequest = new UpsertCommentDto { Text = resource }; |
|||
|
|||
await _.Comments.PostCommentAsync(_.AppName, resource, createRequest); |
|||
|
|||
|
|||
// STEP 2: Get comments
|
|||
var comments = await _.Comments.GetCommentsAsync(_.AppName, resource); |
|||
|
|||
Assert.Contains(comments.CreatedComments, x => x.Text == createRequest.Text); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_update_comment() |
|||
{ |
|||
// STEP 1: Create the comment.
|
|||
var createRequest = new UpsertCommentDto { Text = resource }; |
|||
|
|||
var comment = await _.Comments.PostCommentAsync(_.AppName, resource, createRequest); |
|||
|
|||
|
|||
// STEP 2: Update comment.
|
|||
var updateRequest = new UpsertCommentDto { Text = $"{resource}_Update" }; |
|||
|
|||
await _.Comments.PutCommentAsync(_.AppName, resource, comment.Id, updateRequest); |
|||
|
|||
|
|||
// STEP 3: Get comments since create.
|
|||
var comments = await _.Comments.GetCommentsAsync(_.AppName, resource, 0); |
|||
|
|||
Assert.Contains(comments.UpdatedComments, x => x.Text == updateRequest.Text); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_delete_comment() |
|||
{ |
|||
// STEP 1: Create the comment.
|
|||
var createRequest = new UpsertCommentDto { Text = resource }; |
|||
|
|||
var comment = await _.Comments.PostCommentAsync(_.AppName, resource, createRequest); |
|||
|
|||
|
|||
// STEP 2: Delete comment.
|
|||
await _.Comments.DeleteCommentAsync(_.AppName, resource, comment.Id); |
|||
|
|||
|
|||
// STEP 3: Get comments since create.
|
|||
var comments = await _.Comments.GetCommentsAsync(_.AppName, resource, 0); |
|||
|
|||
Assert.Contains(comment.Id, comments.DeletedComments); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,193 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.ClientLibrary.Management; |
|||
using TestSuite.Fixtures; |
|||
using TestSuite.Model; |
|||
using Xunit; |
|||
|
|||
#pragma warning disable SA1300 // Element should begin with upper-case letter
|
|||
#pragma warning disable SA1507 // Code should not contain multiple blank lines in a row
|
|||
|
|||
namespace TestSuite.ApiTests |
|||
{ |
|||
public class RuleRunnerTests : IClassFixture<ClientFixture>, IClassFixture<WebhookCatcherFixture> |
|||
{ |
|||
private readonly string appName = Guid.NewGuid().ToString(); |
|||
private readonly string schemaName = $"schema-{Guid.NewGuid()}"; |
|||
private readonly string contentString = Guid.NewGuid().ToString(); |
|||
private readonly WebhookCatcherClient webhookCatcher; |
|||
|
|||
public ClientFixture _ { get; } |
|||
|
|||
public RuleRunnerTests(ClientFixture fixture, WebhookCatcherFixture webhookCatcher) |
|||
{ |
|||
_ = fixture; |
|||
|
|||
this.webhookCatcher = webhookCatcher.Client; |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_run_rules() |
|||
{ |
|||
// STEP 0: Create app.
|
|||
await CreateAppAsync(); |
|||
|
|||
|
|||
// STEP 1: Start webhook session
|
|||
var (url, sessionId) = await webhookCatcher.CreateSessionAsync(); |
|||
|
|||
|
|||
// STEP 2: Create rule
|
|||
var createRule = new CreateRuleDto |
|||
{ |
|||
Action = new WebhookRuleActionDto |
|||
{ |
|||
Method = WebhookMethod.POST, |
|||
Payload = null, |
|||
PayloadType = null, |
|||
Url = new Uri(url) |
|||
}, |
|||
Trigger = new ContentChangedRuleTriggerDto |
|||
{ |
|||
HandleAll = true |
|||
} |
|||
}; |
|||
|
|||
var rule = await _.Rules.PostRuleAsync(appName, createRule); |
|||
|
|||
|
|||
// STEP 3: Create test content
|
|||
await CreateContentAsync(); |
|||
|
|||
// Get requests.
|
|||
var requests = await webhookCatcher.WaitForRequestsAsync(sessionId, TimeSpan.FromMinutes(2)); |
|||
|
|||
Assert.Contains(requests, x => x.Method == "POST" && x.Content.Contains(schemaName, StringComparison.OrdinalIgnoreCase)); |
|||
|
|||
|
|||
// STEP 4: Get events
|
|||
var eventsAll = await _.Rules.GetEventsAsync(appName, rule.Id); |
|||
var eventsRule = await _.Rules.GetEventsAsync(appName); |
|||
|
|||
Assert.Single(eventsAll.Items); |
|||
Assert.Single(eventsRule.Items); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_run_rule_manually() |
|||
{ |
|||
// STEP 0: Create app.
|
|||
await CreateAppAsync(); |
|||
|
|||
|
|||
// STEP 1: Start webhook session
|
|||
var (url, sessionId) = await webhookCatcher.CreateSessionAsync(); |
|||
|
|||
|
|||
// STEP 2: Create rule
|
|||
var createRule = new CreateRuleDto |
|||
{ |
|||
Action = new WebhookRuleActionDto |
|||
{ |
|||
Method = WebhookMethod.POST, |
|||
Payload = null, |
|||
PayloadType = null, |
|||
Url = new Uri(url) |
|||
}, |
|||
Trigger = new ManualRuleTriggerDto() |
|||
}; |
|||
|
|||
var rule = await _.Rules.PostRuleAsync(appName, createRule); |
|||
|
|||
|
|||
// STEP 3: Trigger rule
|
|||
await _.Rules.TriggerRuleAsync(appName, rule.Id); |
|||
|
|||
// Get requests.
|
|||
var requests = await webhookCatcher.WaitForRequestsAsync(sessionId, TimeSpan.FromSeconds(30)); |
|||
|
|||
Assert.Contains(requests, x => x.Method == "POST"); |
|||
|
|||
|
|||
// STEP 4: Get events
|
|||
var eventsAll = await _.Rules.GetEventsAsync(appName, rule.Id); |
|||
var eventsRule = await _.Rules.GetEventsAsync(appName); |
|||
|
|||
Assert.Single(eventsAll.Items); |
|||
Assert.Single(eventsRule.Items); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(true)] |
|||
[InlineData(false)] |
|||
public async Task Should_rerun_rules(bool fromSnapshots) |
|||
{ |
|||
// STEP 0: Create app.
|
|||
await CreateAppAsync(); |
|||
|
|||
|
|||
// STEP 1: Start webhook session
|
|||
var (url, sessionId) = await webhookCatcher.CreateSessionAsync(); |
|||
|
|||
|
|||
// STEP 2: Create disabled rule
|
|||
var createRule = new CreateRuleDto |
|||
{ |
|||
Action = new WebhookRuleActionDto |
|||
{ |
|||
Method = WebhookMethod.POST, |
|||
Payload = null, |
|||
PayloadType = null, |
|||
Url = new Uri(url) |
|||
}, |
|||
Trigger = new ContentChangedRuleTriggerDto |
|||
{ |
|||
HandleAll = true |
|||
} |
|||
}; |
|||
|
|||
var rule = await _.Rules.PostRuleAsync(appName, createRule); |
|||
|
|||
// Disable rule, so that we do not create the event from the rule itself.
|
|||
await _.Rules.DisableRuleAsync(appName, rule.Id); |
|||
|
|||
|
|||
// STEP 3: Create test content before rule
|
|||
await CreateContentAsync(); |
|||
|
|||
|
|||
// STEP 4: Run rule.
|
|||
await _.Rules.PutRuleRunAsync(appName, rule.Id, fromSnapshots); |
|||
|
|||
// Get requests.
|
|||
var requests = await webhookCatcher.WaitForRequestsAsync(sessionId, TimeSpan.FromSeconds(30)); |
|||
|
|||
Assert.Contains(requests, x => x.Method == "POST" && x.Content.Contains(schemaName, StringComparison.OrdinalIgnoreCase)); |
|||
} |
|||
|
|||
private async Task CreateContentAsync() |
|||
{ |
|||
await TestEntity.CreateSchemaAsync(_.Schemas, appName, schemaName); |
|||
|
|||
// Create a test content.
|
|||
var contents = _.ClientManager.CreateContentsClient<TestEntity, TestEntityData>(appName, schemaName); |
|||
|
|||
await contents.CreateAsync(new TestEntityData { String = contentString }); |
|||
} |
|||
|
|||
private async Task CreateAppAsync() |
|||
{ |
|||
var createRequest = new CreateAppDto |
|||
{ |
|||
Name = appName |
|||
}; |
|||
|
|||
await _.Apps.PostAppAsync(createRequest); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,162 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.ClientLibrary.Management; |
|||
using TestSuite.Fixtures; |
|||
using Xunit; |
|||
|
|||
#pragma warning disable SA1300 // Element should begin with upper-case letter
|
|||
#pragma warning disable SA1507 // Code should not contain multiple blank lines in a row
|
|||
|
|||
namespace TestSuite.ApiTests |
|||
{ |
|||
public class RuleTests : IClassFixture<ClientFixture> |
|||
{ |
|||
private readonly string appName = Guid.NewGuid().ToString(); |
|||
private readonly string ruleName = Guid.NewGuid().ToString(); |
|||
|
|||
public ClientFixture _ { get; } |
|||
|
|||
public RuleTests(ClientFixture fixture) |
|||
{ |
|||
_ = fixture; |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_create_rule() |
|||
{ |
|||
// STEP 0: Create app.
|
|||
await CreateAppAsync(); |
|||
|
|||
|
|||
// STEP 1: Create rule
|
|||
var createRule = new CreateRuleDto |
|||
{ |
|||
Action = new WebhookRuleActionDto |
|||
{ |
|||
Method = WebhookMethod.POST, |
|||
Payload = null, |
|||
PayloadType = null, |
|||
Url = new Uri("http://squidex.io") |
|||
}, |
|||
Trigger = new ContentChangedRuleTriggerDto |
|||
{ |
|||
HandleAll = true |
|||
} |
|||
}; |
|||
|
|||
var rule = await _.Rules.PostRuleAsync(appName, createRule); |
|||
|
|||
Assert.IsType<WebhookRuleActionDto>(rule.Action); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_update_rule() |
|||
{ |
|||
// STEP 0: Create app.
|
|||
await CreateAppAsync(); |
|||
|
|||
|
|||
// STEP 1: Create rule
|
|||
var createRequest = new CreateRuleDto |
|||
{ |
|||
Action = new WebhookRuleActionDto |
|||
{ |
|||
Method = WebhookMethod.POST, |
|||
Payload = null, |
|||
PayloadType = null, |
|||
Url = new Uri("http://squidex.io") |
|||
}, |
|||
Trigger = new ContentChangedRuleTriggerDto |
|||
{ |
|||
HandleAll = true |
|||
} |
|||
}; |
|||
|
|||
var rule_0 = await _.Rules.PostRuleAsync(appName, createRequest); |
|||
|
|||
|
|||
// STEP 2: Update rule
|
|||
var updateRequest = new UpdateRuleDto |
|||
{ |
|||
Name = ruleName |
|||
}; |
|||
|
|||
var rule_1 = await _.Rules.PutRuleAsync(appName, rule_0.Id, updateRequest); |
|||
|
|||
Assert.Equal(ruleName, rule_1.Name); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_delete_rule() |
|||
{ |
|||
// STEP 0: Create app.
|
|||
await CreateAppAsync(); |
|||
|
|||
|
|||
// STEP 1: Create rule
|
|||
var createRequest = new CreateRuleDto |
|||
{ |
|||
Action = new WebhookRuleActionDto |
|||
{ |
|||
Method = WebhookMethod.POST, |
|||
Payload = null, |
|||
PayloadType = null, |
|||
Url = new Uri("http://squidex.io") |
|||
}, |
|||
Trigger = new ContentChangedRuleTriggerDto |
|||
{ |
|||
HandleAll = true |
|||
} |
|||
}; |
|||
|
|||
var rule = await _.Rules.PostRuleAsync(appName, createRequest); |
|||
|
|||
|
|||
// STEP 2: Delete rule
|
|||
await _.Rules.DeleteRuleAsync(appName, rule.Id); |
|||
|
|||
var rules = await _.Rules.GetRulesAsync(appName); |
|||
|
|||
Assert.DoesNotContain(rules.Items, x => x.Id == rule.Id); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_get_actions() |
|||
{ |
|||
var actions = await _.Rules.GetActionsAsync(); |
|||
|
|||
Assert.NotEmpty(actions); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_get_event_schemas() |
|||
{ |
|||
var schema = await _.Rules.GetEventSchemaAsync("EnrichedContentEvent"); |
|||
|
|||
Assert.NotNull(schema); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_get_event_types() |
|||
{ |
|||
var eventTypes = await _.Rules.GetEventTypesAsync(); |
|||
|
|||
Assert.NotEmpty(eventTypes); |
|||
} |
|||
|
|||
private async Task CreateAppAsync() |
|||
{ |
|||
var createRequest = new CreateAppDto |
|||
{ |
|||
Name = appName |
|||
}; |
|||
|
|||
await _.Apps.PostAppAsync(createRequest); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.ClientLibrary.Management; |
|||
|
|||
namespace TestSuite |
|||
{ |
|||
public static class ClientExtensions |
|||
{ |
|||
public static async Task<BackupJobDto> WaitForBackupAsync(this IBackupsClient backupsClient, string app, TimeSpan timeout) |
|||
{ |
|||
try |
|||
{ |
|||
using var cts = new CancellationTokenSource(timeout); |
|||
|
|||
while (!cts.IsCancellationRequested) |
|||
{ |
|||
var backups = await backupsClient.GetBackupsAsync(app, cts.Token); |
|||
var backup = backups.Items.Find(x => x.Status == JobStatus.Completed || x.Status == JobStatus.Failed); |
|||
|
|||
if (backup != null) |
|||
{ |
|||
return backup; |
|||
} |
|||
|
|||
await Task.Delay(200, cts.Token); |
|||
} |
|||
} |
|||
catch (OperationCanceledException) |
|||
{ |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public static async Task<RestoreJobDto> WaitForRestoreAsync(this IBackupsClient backupsClient, Uri url, TimeSpan timeout) |
|||
{ |
|||
try |
|||
{ |
|||
using var cts = new CancellationTokenSource(timeout); |
|||
|
|||
while (!cts.IsCancellationRequested) |
|||
{ |
|||
var restore = await backupsClient.GetRestoreJobAsync(cts.Token); |
|||
|
|||
if (restore.Url == url && restore.Status is JobStatus.Completed or JobStatus.Failed) |
|||
{ |
|||
return restore; |
|||
} |
|||
|
|||
await Task.Delay(200, cts.Token); |
|||
} |
|||
} |
|||
catch (OperationCanceledException) |
|||
{ |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,117 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Net.Http.Json; |
|||
using System.Text; |
|||
using System.Text.Json.Serialization; |
|||
|
|||
#pragma warning disable MA0048 // File name must match type name
|
|||
|
|||
namespace TestSuite.Fixtures |
|||
{ |
|||
public sealed class WebhookSession |
|||
{ |
|||
public string Uuid { get; set; } |
|||
} |
|||
|
|||
public sealed class WebhookRequest |
|||
{ |
|||
[JsonPropertyName("uuid")] |
|||
public string Uuid { get; set; } |
|||
|
|||
[JsonPropertyName("method")] |
|||
public string Method { get; set; } |
|||
|
|||
[JsonPropertyName("content_base64")] |
|||
public string Content { get; set; } |
|||
} |
|||
|
|||
public sealed class WebhookCatcherClient |
|||
{ |
|||
private readonly HttpClient httpClient; |
|||
|
|||
public string EndpointHost { get; } |
|||
|
|||
public int EndpointPort { get; } |
|||
|
|||
public WebhookCatcherClient(string apiHost, int apiPort, string endpointHost, int endpointPort) |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(apiHost)) |
|||
{ |
|||
apiHost = "localhost"; |
|||
} |
|||
|
|||
if (string.IsNullOrWhiteSpace(endpointHost)) |
|||
{ |
|||
endpointHost = "localhost"; |
|||
} |
|||
|
|||
EndpointHost = endpointHost; |
|||
EndpointPort = endpointPort; |
|||
|
|||
httpClient = new HttpClient |
|||
{ |
|||
BaseAddress = new Uri($"http://{apiHost}:{apiPort}") |
|||
}; |
|||
} |
|||
|
|||
public async Task<(string, string)> CreateSessionAsync( |
|||
CancellationToken ct = default) |
|||
{ |
|||
var response = await httpClient.PostAsJsonAsync("/api/session", new { }, ct); |
|||
|
|||
response.EnsureSuccessStatusCode(); |
|||
|
|||
var responseObj = await response.Content.ReadFromJsonAsync<WebhookSession>(cancellationToken: ct); |
|||
|
|||
return ($"http://{EndpointHost}:{EndpointPort}/{responseObj.Uuid}", responseObj.Uuid); |
|||
} |
|||
|
|||
public async Task<WebhookRequest[]> GetRequestsAsync(string sessionId, |
|||
CancellationToken ct = default) |
|||
{ |
|||
var result = await httpClient.GetFromJsonAsync<WebhookRequest[]>($"/api/session/{sessionId}/requests", ct); |
|||
|
|||
foreach (var request in result) |
|||
{ |
|||
if (request.Content != null) |
|||
{ |
|||
request.Content = Encoding.Default.GetString(Convert.FromBase64String(request.Content)); |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
public async Task<WebhookRequest[]> WaitForRequestsAsync(string sessionId, TimeSpan timeout) |
|||
{ |
|||
var requests = Array.Empty<WebhookRequest>(); |
|||
|
|||
try |
|||
{ |
|||
using var cts = new CancellationTokenSource(timeout); |
|||
|
|||
while (!cts.IsCancellationRequested) |
|||
{ |
|||
requests = await GetRequestsAsync(sessionId, cts.Token); |
|||
|
|||
if (requests.Length > 0) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
await Task.Delay(50, cts.Token); |
|||
} |
|||
} |
|||
catch (OperationCanceledException) |
|||
{ |
|||
} |
|||
|
|||
return requests; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using TestSuite.Utils; |
|||
|
|||
namespace TestSuite.Fixtures |
|||
{ |
|||
public sealed class WebhookCatcherFixture |
|||
{ |
|||
public WebhookCatcherClient Client { get; } |
|||
|
|||
public WebhookCatcherFixture() |
|||
{ |
|||
Client = new WebhookCatcherClient( |
|||
TestHelpers.GetAndPrintValue("webhookcatcher:host:api", "localhost"), 1026, |
|||
TestHelpers.GetAndPrintValue("webhookcatcher:host:endpoint", "localhost"), 1026); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue