From 4b6c6c70f1e746e1f3693e958a92ea380e50462c Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 12 Jan 2020 20:14:29 +0100 Subject: [PATCH] Generate test data. --- cli/Squidex.CLI/.editorconfig | 4 + .../Squidex.CLI.Tests/LoremIpsumTests.cs | 45 ++ .../Squidex.CLI.Tests.csproj | 2 +- .../TestDataGeneratorTests.cs | 536 ++++++++++++++++++ cli/Squidex.CLI/Squidex.CLI.sln | 7 +- .../Squidex.CLI/Commands/App_Backup.cs | 2 +- .../Squidex.CLI/Commands/App_Content.cs | 94 ++- .../Squidex.CLI/Commands/App_Schemas.cs | 6 +- .../Implementation/Json2SquidexConverter.cs | 78 +-- .../Commands/Implementation/LoremIpsum.cs | 124 ++++ .../Implementation/TestDataGenerator.cs | 256 +++++++++ .../Configuration/ConfigurationService.cs | 2 +- .../Configuration/IConfigurationService.cs | 2 +- .../Squidex.CLI/Squidex.CLI.csproj | 12 +- 14 files changed, 1108 insertions(+), 62 deletions(-) create mode 100644 cli/Squidex.CLI/.editorconfig create mode 100644 cli/Squidex.CLI/Squidex.CLI.Tests/LoremIpsumTests.cs create mode 100644 cli/Squidex.CLI/Squidex.CLI.Tests/TestDataGeneratorTests.cs create mode 100644 cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/LoremIpsum.cs create mode 100644 cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/TestDataGenerator.cs diff --git a/cli/Squidex.CLI/.editorconfig b/cli/Squidex.CLI/.editorconfig new file mode 100644 index 00000000..a2963d8f --- /dev/null +++ b/cli/Squidex.CLI/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] + +# IDE0063: Use simple 'using' statement +csharp_prefer_simple_using_statement = true:none diff --git a/cli/Squidex.CLI/Squidex.CLI.Tests/LoremIpsumTests.cs b/cli/Squidex.CLI/Squidex.CLI.Tests/LoremIpsumTests.cs new file mode 100644 index 00000000..a6f91b7c --- /dev/null +++ b/cli/Squidex.CLI/Squidex.CLI.Tests/LoremIpsumTests.cs @@ -0,0 +1,45 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.CLI.Commands.Implementation; +using Xunit; + +namespace Squidex.CLI.Tests +{ + public class LoremIpsumTests + { + [Fact] + public void Should_generate_single_character() + { + var result = LoremIpsum.Text(1, false); + + Assert.Equal("l", result); + } + + [Fact] + public void Should_generate_html_text() + { + for (var i = 0; i < 5000; i++) + { + var result = LoremIpsum.Text(i, true); + + Assert.NotNull(result); + } + } + + [Fact] + public void Should_generate_text() + { + for (var i = 0; i < 5000; i++) + { + var result = LoremIpsum.Text(i, false); + + Assert.NotNull(result); + } + } + } +} diff --git a/cli/Squidex.CLI/Squidex.CLI.Tests/Squidex.CLI.Tests.csproj b/cli/Squidex.CLI/Squidex.CLI.Tests/Squidex.CLI.Tests.csproj index 22df4424..13f2d8c0 100644 --- a/cli/Squidex.CLI/Squidex.CLI.Tests/Squidex.CLI.Tests.csproj +++ b/cli/Squidex.CLI/Squidex.CLI.Tests/Squidex.CLI.Tests.csproj @@ -3,7 +3,7 @@ netcoreapp3.0 - + diff --git a/cli/Squidex.CLI/Squidex.CLI.Tests/TestDataGeneratorTests.cs b/cli/Squidex.CLI/Squidex.CLI.Tests/TestDataGeneratorTests.cs new file mode 100644 index 00000000..23bd2f38 --- /dev/null +++ b/cli/Squidex.CLI/Squidex.CLI.Tests/TestDataGeneratorTests.cs @@ -0,0 +1,536 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json.Linq; +using Squidex.CLI.Commands.Implementation; +using Squidex.ClientLibrary.Management; +using Xunit; + +namespace Squidex.CLI.Tests +{ + public class TestDataGeneratorTests + { + [Fact] + public void Should_generate_datetime_between_min_and_max() + { + var minValue = DateTime.UtcNow.AddDays(12); + var maxValue = minValue.AddDays(10); + + var values = + CreateManyScalars( + new DateTimeFieldPropertiesDto + { + Editor = DateTimeFieldEditor.DateTime, + MinValue = minValue, + MaxValue = maxValue + }); + + var dates = values.Select(x => (DateTime)x); + + var min = dates.Min(); + var max = dates.Max(); + + Assert.True(dates.All(x => x.Kind == DateTimeKind.Utc)); + } + + [Fact] + public void Should_generate_datetime_before_max() + { + var minValue = DateTime.UtcNow.AddDays(-10); + var maxValue = minValue.AddDays(30); + + var values = + CreateManyScalars( + new DateTimeFieldPropertiesDto + { + Editor = DateTimeFieldEditor.DateTime, + MinValue = null, + MaxValue = maxValue + }); + + var dates = values.Select(x => (DateTime)x); + + var min = dates.Min(); + var max = dates.Max(); + + Assert.True(dates.All(x => x.Kind == DateTimeKind.Utc)); + } + + [Fact] + public void Should_generate_datetime_after_min() + { + var minValue = DateTime.UtcNow.AddDays(-10); + var maxValue = minValue.AddDays(30); + + var values = + CreateManyScalars( + new DateTimeFieldPropertiesDto + { + Editor = DateTimeFieldEditor.DateTime, + MinValue = minValue, + MaxValue = null + }); + + var dates = values.Select(x => (DateTime)x); + + var min = dates.Min(); + var max = dates.Max(); + + Assert.True(dates.All(x => x.Kind == DateTimeKind.Utc)); + } + + [Fact] + public void Should_generate_datetime_with_default_range() + { + var minValue = DateTime.UtcNow; + var maxValue = minValue.AddDays(30); + + var values = + CreateManyScalars( + new DateTimeFieldPropertiesDto + { + Editor = DateTimeFieldEditor.DateTime + }); + + var dates = values.Select(x => (DateTime)x); + + var min = dates.Min(); + var max = dates.Max(); + + Assert.True(dates.All(x => x.Kind == DateTimeKind.Utc)); + } + + [Fact] + public void Should_generate_date_with_default_range() + { + var minValue = DateTime.UtcNow; + var maxValue = minValue.AddDays(30); + + var values = + CreateManyScalars( + new DateTimeFieldPropertiesDto()); + + var dates = values.Select(x => (DateTime)x); + + var min = dates.Min(); + var max = dates.Max(); + + Assert.True(min >= minValue.Date); + Assert.True(max <= maxValue.Date); + + Assert.True(dates.All(x => x.TimeOfDay == TimeSpan.Zero && x.Kind == DateTimeKind.Utc)); + } + + [Fact] + public void Should_generate_booleans_from_required_field() + { + var values = + CreateManyScalars( + new BooleanFieldPropertiesDto + { + IsRequired = true + }); + + Assert.Equal(2, values.Count); + Assert.Contains(true, values); + Assert.Contains(false, values); + } + + [Fact] + public void Should_generate_booleans() + { + var values = + CreateManyScalars( + new BooleanFieldPropertiesDto()); + + Assert.Equal(3, values.Count); + Assert.Contains(null, values); + Assert.Contains(true, values); + Assert.Contains(false, values); + } + + [Fact] + public void Should_generate_numbers_from_allowed_values() + { + var values = + CreateManyScalars( + new NumberFieldPropertiesDto + { + AllowedValues = new List { 13, 27, 42 } + }); + + Assert.Equal(3, values.Count); + Assert.Contains(13.0, values); + Assert.Contains(27.0, values); + Assert.Contains(42.0, values); + } + + [Fact] + public void Should_generate_numbers_between_min_and_max() + { + var values = + CreateManyScalars( + new NumberFieldPropertiesDto + { + MinValue = 400, + MaxValue = 550 + }); + + var numbers = values.Select(x => (double)x); + + var min = numbers.Min(); + var max = numbers.Max(); + + Assert.True(min >= 400); + Assert.True(max <= 550); + } + + [Fact] + public void Should_generate_numbers_lower_than_max() + { + var values = + CreateManyScalars( + new NumberFieldPropertiesDto + { + MaxValue = 550 + }); + + var numbers = values.Select(x => (double)x); + + var min = numbers.Min(); + var max = numbers.Max(); + + Assert.True(min >= 450); + Assert.True(max <= 550); + } + + [Fact] + public void Should_generate_numbers_greater_than_min() + { + var values = + CreateManyScalars( + new NumberFieldPropertiesDto + { + MinValue = 120 + }); + + var numbers = values.Select(x => (double)x); + + var min = numbers.Min(); + var max = numbers.Max(); + + Assert.True(min >= 120); + Assert.True(max <= 220); + } + + [Fact] + public void Should_generate_numbers_with_default_range() + { + var values = + CreateManyScalars( + new NumberFieldPropertiesDto()); + + var numbers = values.Select(x => (double)x); + + var min = numbers.Min(); + var max = numbers.Max(); + + Assert.True(min >= 0); + Assert.True(max <= 100); + } + + [Fact] + public void Should_generate_tags_with_allowed_values() + { + var values = + CreateManyStringTags( + new TagsFieldPropertiesDto + { + AllowedValues = new List { "foo", "bar" } + }); + + var distinct = values.SelectMany(x => x).Distinct().ToList(); + + Assert.Equal(2, distinct.Count); + Assert.Contains("foo", distinct); + Assert.Contains("bar", distinct); + } + + [Fact] + public void Should_generate_tags_between_min_and_max_items() + { + var values = + CreateManyStringTags( + new TagsFieldPropertiesDto + { + MinItems = 10, + MaxItems = 30 + }); + + var min = values.Min(x => x.Count); + var max = values.Max(x => x.Count); + + Assert.True(min >= 10); + Assert.True(max <= 30); + } + + [Fact] + public void Should_generate_tags_between_with_less_than_max() + { + var values = + CreateManyStringTags( + new TagsFieldPropertiesDto + { + MaxItems = 22 + }); + + var min = values.Min(x => x.Count); + var max = values.Max(x => x.Count); + + Assert.True(min >= 18); + Assert.True(max <= 22); + } + + [Fact] + public void Should_generate_tags_between_with_more_than_min() + { + var values = + CreateManyStringTags( + new TagsFieldPropertiesDto + { + MinItems = 44 + }); + + var min = values.Min(x => x.Count); + var max = values.Max(x => x.Count); + + Assert.True(min >= 44); + Assert.True(max <= 48); + } + + [Fact] + public void Should_generate_tags_between_with_default_sizes() + { + var values = + CreateManyStringTags( + new TagsFieldPropertiesDto()); + + var min = values.Min(x => x.Count); + var max = values.Max(x => x.Count); + + Assert.True(min >= 1); + Assert.True(max <= 5); + } + + [Fact] + public void Should_generate_string_with_allowed_values() + { + var values = + CreateManyScalars( + new StringFieldPropertiesDto + { + AllowedValues = new List { "foo", "bar" } + }); + + Assert.Equal(2, values.Count); + Assert.Contains("foo", values); + Assert.Contains("bar", values); + } + + [Fact] + public void Should_generate_string_as_color() + { + var values = + CreateManyScalars( + new StringFieldPropertiesDto + { + Editor = StringFieldEditor.Color + }); + + var strings = values.Select(x => (string)x); + + Assert.True(strings.All(x => x.StartsWith("#", StringComparison.OrdinalIgnoreCase) && x.Length == 7)); + } + + [Fact] + public void Should_generate_string_equals_to_max_length() + { + var values = + CreateManyScalars( + new StringFieldPropertiesDto + { + MaxLength = 20 + }); + + var strings = values.Select(x => (string)x); + + Assert.Equal(new int[] { 20 }, strings.Select(x => x.Length).Distinct().ToArray()); + } + + [Fact] + public void Should_generate_string_equals_to_short_max_length() + { + var values = + CreateManyScalars( + new StringFieldPropertiesDto + { + MaxLength = 2 + }); + + var strings = values.Select(x => (string)x); + + Assert.Equal(new int[] { 2 }, strings.Select(x => x.Length).Distinct().ToArray()); + } + + [Fact] + public void Should_generate_string_equals_default_max_length() + { + var values = + CreateManyScalars( + new StringFieldPropertiesDto + { + Editor = StringFieldEditor.Input + }); + + var strings = values.Select(x => (string)x); + + Assert.Equal(new int[] { 100 }, strings.Select(x => x.Length).Distinct().ToArray()); + } + + [Fact] + public void Should_generate_string_equals_default_html_max_length() + { + var values = + CreateManyScalars( + new StringFieldPropertiesDto + { + Editor = StringFieldEditor.Markdown + }); + + var strings = values.Select(x => (string)x); + + Assert.Equal(new int[] { 1000 }, strings.Select(x => x.Length).Distinct().ToArray()); + } + + [Fact] + public void Should_generate_string_equals_default_markdown_max_length() + { + var values = + CreateManyScalars( + new StringFieldPropertiesDto + { + Editor = StringFieldEditor.Markdown + }); + + var strings = values.Select(x => (string)x); + + Assert.Equal(new int[] { 1000 }, strings.Select(x => x.Length).Distinct().ToArray()); + } + + [Fact] + public void Should_generate_slugify_text() + { + var values = + CreateManyScalars( + new StringFieldPropertiesDto + { + MaxLength = 20, + MinLength = 20, + Editor = StringFieldEditor.Slug + }); + + var strings = values.Select(x => (string)x).Distinct(); + + Assert.Equal(new string[] { "lorem-ipsum-dolorxxx" }, strings); + } + + [Fact] + public void Should_generate_json() + { + var values = + CreateValue( + new JsonFieldPropertiesDto()); + + var obj = values as JObject; + + Assert.NotNull(obj); + Assert.Single(obj); + Assert.True(obj.ContainsKey("value")); + } + + [Fact] + public void Should_generate_geolocation() + { + var values = + CreateValue( + new GeolocationFieldPropertiesDto()); + + var obj = values as JObject; + + Assert.NotNull(obj); + Assert.Equal(2, obj.Count); + Assert.True(obj.ContainsKey("latitude")); + Assert.True(obj.ContainsKey("longitude")); + } + + private HashSet> CreateManyStringTags(FieldPropertiesDto field) + { + var values = new HashSet>(); + + for (var i = 0; i < 1000; i++) + { + values.Add(CreateValue(field).ToObject>()); + } + + return values; + } + + private HashSet CreateManyScalars(FieldPropertiesDto field) + { + var values = new HashSet(); + + for (var i = 0; i < 1000; i++) + { + values.Add(CreateScalar(field)); + } + + return values; + } + + private object CreateScalar(FieldPropertiesDto field) + { + var value = CreateValue(field); + + return (value as JValue)?.Value; + } + + private JToken CreateValue(FieldPropertiesDto field) + { + var schema = new SchemaDetailsDto + { + Fields = new List + { + new FieldDto + { + Name = "field", + Properties = field, + Partitioning = "invariant" + } + } + }; + + var sut = new TestDataGenerator(schema, null); + + var data = sut.GenerateTestData(); + + return data["field"]["iv"]; + } + } +} diff --git a/cli/Squidex.CLI/Squidex.CLI.sln b/cli/Squidex.CLI/Squidex.CLI.sln index 297e38f8..f1d5ca61 100644 --- a/cli/Squidex.CLI/Squidex.CLI.sln +++ b/cli/Squidex.CLI/Squidex.CLI.sln @@ -5,7 +5,12 @@ VisualStudioVersion = 16.0.28803.202 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.CLI", "Squidex.CLI\Squidex.CLI.csproj", "{2D435C5F-73FB-47F6-BEDA-49E9F9D8E9F7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Squidex.CLI.Tests", "Squidex.CLI.Tests\Squidex.CLI.Tests.csproj", "{3DCBE36E-07EE-4EC2-81F9-8D471DD89896}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.CLI.Tests", "Squidex.CLI.Tests\Squidex.CLI.Tests.csproj", "{3DCBE36E-07EE-4EC2-81F9-8D471DD89896}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E53F82E6-7123-42D7-825F-F84EF545323C}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/App_Backup.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/App_Backup.cs index 18664560..4d21a4b8 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Commands/App_Backup.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/App_Backup.cs @@ -31,7 +31,7 @@ public sealed class Backup [ApplicationMetadata(Name = "create", Description = "Create and download an backup.")] public async Task Create(CreateArguments arguments) { - var (app, service) = Configuration.GetClient(); + var (app, service) = Configuration.Setup(); var backupStarted = DateTime.UtcNow.AddMinutes(-5); var backupsClient = service.CreateBackupsClient(); diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/App_Content.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/App_Content.cs index 4c364df0..0f94aee7 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Commands/App_Content.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/App_Content.cs @@ -33,6 +33,40 @@ public sealed class Content [InjectProperty] public IConfigurationService Configuration { get; set; } + [ApplicationMetadata(Name = "test-data", Description = "Generates test data.")] + public async Task TestData(TestDataArguments arguments) + { + var (app, service) = Configuration.Setup(); + + var taskForSchema = service.CreateSchemasClient().GetSchemaAsync(app, arguments.Schema); + var taskForLanguages = service.CreateAppsClient().GetLanguagesAsync(app); + + await Task.WhenAll( + taskForSchema, + taskForLanguages); + + var datas = new List(); + + if (arguments.Count > 0) + { + var generator = new TestDataGenerator(taskForSchema.Result, taskForLanguages.Result); + + for (var i = 0; i < arguments.Count; i++) + { + datas.Add(generator.GenerateTestData()); + } + } + + if (!string.IsNullOrWhiteSpace(arguments.File)) + { + File.AppendAllText(arguments.File, datas.JsonPrettyString()); + } + else + { + await ImportAsync(arguments, service, datas); + } + } + [ApplicationMetadata(Name = "import", Description = "Import the content to a schema.", ExtendedHelpText = @"Use the following format to define fields from the CSV/JSON file: @@ -42,7 +76,9 @@ public sealed class Content ")] public async Task Import(ImportArguments arguments) { - if (Format.JSON.Equals(arguments.Format)) + var (_, service) = Configuration.Setup(); + + if (arguments.Format == Format.JSON) { var converter = new Json2SquidexConverter(arguments.Fields); @@ -54,7 +90,7 @@ public async Task Import(ImportArguments arguments) { var datas = converter.ReadAll(reader); - await ImportAsync(arguments, datas); + await ImportAsync(arguments, service, datas); } } } @@ -76,7 +112,7 @@ public async Task Import(ImportArguments arguments) { var datas = converter.ReadAll(reader); - await ImportAsync(arguments, datas); + await ImportAsync(arguments, service, datas); } } } @@ -101,7 +137,9 @@ public async Task Export(ExportArguments arguments) { var ctx = QueryContext.Default.Unpublished(arguments.Unpublished); - var client = Configuration.GetClient().Client.GetClient(arguments.Schema); + var (_, service) = Configuration.Setup(); + + var client = service.GetClient(arguments.Schema); if (arguments.Format == Format.JSON) { @@ -210,9 +248,9 @@ await ExportAsync(arguments, entity => } } - private async Task ImportAsync(ImportArguments arguments, IEnumerable datas) + private async Task ImportAsync(IImortArgumentBase arguments, SquidexClientManager service, IEnumerable datas) { - var client = Configuration.GetClient().Client.GetClient(arguments.Schema); + var client = service.GetClient(arguments.Schema); var totalWritten = 0; @@ -237,7 +275,9 @@ private async Task ExportAsync(ExportArguments arguments, Action ha { var ctx = QueryContext.Default.Unpublished(arguments.Unpublished); - var client = Configuration.GetClient().Client.GetClient(arguments.Schema); + var (_, service) = Configuration.Setup(); + + var client = service.GetClient(arguments.Schema); var total = 0L; var totalRead = 0; @@ -293,8 +333,15 @@ public enum Format JSON } + public interface IImortArgumentBase + { + string Schema { get; } + + bool Unpublished { get; } + } + [Validator(typeof(ImportArgumentsValidator))] - public sealed class ImportArguments : IArgumentModel + public sealed class ImportArguments : IImortArgumentBase, IArgumentModel { [Argument(Name = "schema", Description = "The name of the schema.")] public string Schema { get; set; } @@ -302,12 +349,12 @@ public sealed class ImportArguments : IArgumentModel [Argument(Name = "file", Description = "The path to the file.")] public string File { get; set; } - [Option(LongName = "fields", Description = "Comma separated list of fields to import.")] - public string Fields { get; set; } - [Option(LongName = "unpublished", ShortName = "u", Description = "Import unpublished content.")] public bool Unpublished { get; set; } + [Option(LongName = "fields", Description = "Comma separated list of fields to import.")] + public string Fields { get; set; } + [Option(LongName = "delimiter", Description = "The csv delimiter.")] public string Delimiter { get; set; } = ";"; @@ -368,6 +415,31 @@ public ExportArgumentsValidator() } } } + + [Validator(typeof(TestDataArgumentsValidator))] + public sealed class TestDataArguments : IImortArgumentBase, IArgumentModel + { + [Argument(Name = "schema", Description = "The name of the schema.")] + public string Schema { get; set; } + + [Option(LongName = "unpublished", ShortName = "u", Description = "Import unpublished content.")] + public bool Unpublished { get; set; } + + [Option(LongName = "count", ShortName = "c", Description = "The number of items.")] + public int Count { get; set; } = 10; + + [Option(LongName = "file", Description = "The optional path to the file.")] + public string File { get; set; } + + public sealed class TestDataArgumentsValidator : AbstractValidator + { + public TestDataArgumentsValidator() + { + RuleFor(x => x.Schema).NotEmpty(); + RuleFor(x => x.Count).GreaterThan(0); + } + } + } } } } \ No newline at end of file diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/App_Schemas.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/App_Schemas.cs index 09b26302..d76fe3fa 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Commands/App_Schemas.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/App_Schemas.cs @@ -36,7 +36,7 @@ public sealed class Schemas [ApplicationMetadata(Name = "list", Description = "List all schemas.")] public async Task List(ListArguments arguments) { - var (app, service) = Configuration.GetClient(); + var (app, service) = Configuration.Setup(); var schemasClient = service.CreateSchemasClient(); var schemas = await schemasClient.GetSchemasAsync(app); @@ -61,7 +61,7 @@ public async Task List(ListArguments arguments) [ApplicationMetadata(Name = "get", Description = "Get a schema by name.")] public async Task Get(GetArguments arguments) { - var (app, service) = Configuration.GetClient(); + var (app, service) = Configuration.Setup(); var schemasClient = service.CreateSchemasClient(); var schema = await schemasClient.GetSchemaAsync(app, arguments.Name); @@ -83,7 +83,7 @@ public async Task Get(GetArguments arguments) [ApplicationMetadata(Name = "sync", Description = "Sync the schema.")] public async Task Sync(SyncArguments arguments) { - var (app, service) = Configuration.GetClient(); + var (app, service) = Configuration.Setup(); var schemasClient = service.CreateSchemasClient(); diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Json2SquidexConverter.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Json2SquidexConverter.cs index 340b032b..671908e4 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Json2SquidexConverter.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/Json2SquidexConverter.cs @@ -66,45 +66,6 @@ private void SetValue(DummyData data, JToken value, JsonPath path) data[path[0].Key] = property; } - object AddElement(object parent, string key, int index, JToken currentValue, bool merge) - { - if (index >= 0) - { - if (parent is JArray array) - { - while (array.Count < index + 1) - { - array.Add(null); - } - - if (merge && array[index].Type == currentValue.Type) - { - return array[index]; - } - - array[index] = currentValue; - - return currentValue; - } - } - else - { - if (parent is IDictionary obj) - { - if (merge && obj.TryGetValue(key, out var temp) && temp.Type == currentValue.Type) - { - return temp; - } - - obj[key] = currentValue; - - return currentValue; - } - } - - throw new SquidexException("Invalid json mapping."); - } - object container = property; for (var i = 1; i < path.Count; i++) @@ -130,5 +91,44 @@ object AddElement(object parent, string key, int index, JToken currentValue, boo } } } + + private static object AddElement(object parent, string key, int index, JToken currentValue, bool merge) + { + if (index >= 0) + { + if (parent is JArray array) + { + while (array.Count < index + 1) + { + array.Add(null); + } + + if (merge && array[index].Type == currentValue.Type) + { + return array[index]; + } + + array[index] = currentValue; + + return currentValue; + } + } + else + { + if (parent is IDictionary obj) + { + if (merge && obj.TryGetValue(key, out var temp) && temp.Type == currentValue.Type) + { + return temp; + } + + obj[key] = currentValue; + + return currentValue; + } + } + + throw new SquidexException("Invalid json mapping."); + } } } diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/LoremIpsum.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/LoremIpsum.cs new file mode 100644 index 00000000..6bdb0ce6 --- /dev/null +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/LoremIpsum.cs @@ -0,0 +1,124 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Text; + +namespace Squidex.CLI.Commands.Implementation +{ + public static class LoremIpsum + { + private const int SentencesPerParagraph = 3; + + private static readonly string[] Words = + { + "lorem", + "ipsum", + "dolor", + "sit", + "amet", + "consectetuer", + "adipiscing", + "elit", + "sed", + "diam", + "nonummy", + "nibh", + "euismod", + "tincidunt", + "ut", + "laoreet", + "dolore", + "magna", + "aliquam", + "erat" + }; + + public static string GetWord(Random random) + { + return Words[random.Next(0, Words.Length)]; + } + + public static string Text(int maxCharacters, bool html) + { + var sb = new StringBuilder(); + + var nextWord = new StringBuilder(); + + var sentences = 0; + + for (var i = 0; i < Words.Length; i++) + { + var word = Words[i]; + + if (sb.Length > 0) + { + nextWord.Append(" "); + } + + nextWord.Append(word); + + if (sb.Length + nextWord.Length < maxCharacters) + { + sb.Append(nextWord); + + nextWord.Clear(); + } + else + { + break; + } + + if (i == Words.Length - 1) + { + sentences++; + + var left = maxCharacters - 1; + + void Append(string value) + { + if (left > value.Length) + { + sb.Append(value); + left -= value.Length; + } + } + + Append("."); + + if (sentences % SentencesPerParagraph == 0) + { + if (html) + { + Append("
"); + Append("
"); + } + else + { + Append("\n"); + Append("\n"); + } + } + + i = -1; + } + } + + if (sb.Length == 0) + { + return Words[0].Substring(0, maxCharacters); + } + + while (sb.Length < maxCharacters) + { + sb.Append("."); + } + + return sb.ToString(); + } + } +} diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/TestDataGenerator.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/TestDataGenerator.cs new file mode 100644 index 00000000..8cbc4a95 --- /dev/null +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/Implementation/TestDataGenerator.cs @@ -0,0 +1,256 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json.Linq; +using Slugify; +using Squidex.ClientLibrary.Management; + +namespace Squidex.CLI.Commands.Implementation +{ + public sealed class TestDataGenerator + { + private readonly SlugHelper slugify = new SlugHelper(); + private readonly SchemaDetailsDto schema; + private readonly AppLanguagesDto languages; + private readonly Random random = new Random(); + + public TestDataGenerator(SchemaDetailsDto schema, AppLanguagesDto languages) + { + this.schema = schema; + this.languages = languages; + } + + public DummyData GenerateTestData() + { + var data = new DummyData(); + + foreach (var field in schema.Fields) + { + var fieldData = new Dictionary(); + + if (field.Partitioning == "invariant") + { + var value = GenerateValue(field); + + fieldData["iv"] = value; + } + else + { + foreach (var language in languages.Items) + { + var value = GenerateValue(field); + + fieldData[language.Iso2Code] = value; + } + } + + data.Add(field.Name, fieldData); + } + + return data; + } + + private JToken GenerateValue(FieldDto field) + { + switch (field.Properties) + { + case BooleanFieldPropertiesDto booleanField: + { + if (booleanField.IsRequired) + { + var value = random.Next(2); + + return value == 1; + } + else + { + var value = random.Next(3); + + switch (value) + { + case 1: + return true; + case 2: + return false; + default: + return null; + } + } + } + + case DateTimeFieldPropertiesDto dateTimeField: + { + var min = DateTimeOffset.UtcNow; + var max = DateTimeOffset.UtcNow.AddDays(30); + + if (dateTimeField.MinValue.HasValue && dateTimeField.MaxValue.HasValue) + { + min = dateTimeField.MinValue.Value; + max = dateTimeField.MaxValue.Value; + } + else if (dateTimeField.MinValue.HasValue) + { + min = dateTimeField.MinValue.Value; + max = min.AddDays(30); + } + else if (dateTimeField.MaxValue.HasValue) + { + max = dateTimeField.MaxValue.Value; + min = max.AddDays(-30); + } + + var range = max - min; + + var result = min.AddMinutes(random.Next(0, (int)range.TotalMinutes)).UtcDateTime; + + if (dateTimeField.Editor == DateTimeFieldEditor.Date) + { + result = result.Date; + } + + return result; + } + + case GeolocationFieldPropertiesDto _: + { + var lat = random.Next(-90, 90); + var lon = random.Next(-180, 180); + + return new JObject( + new JProperty("latitude", + lat), + new JProperty("longitude", + lon)); + } + + case JsonFieldPropertiesDto _: + { + return new JObject( + new JProperty("value", + LoremIpsum.GetWord(random))); + } + + case NumberFieldPropertiesDto numberField: + { + if (numberField.AllowedValues?.Count > 0) + { + return GetRandomValue(numberField.AllowedValues); + } + + var value = GetRandom(numberField.MinValue, numberField.MaxValue, 0, 100); + + return Math.Round(value, 2); + } + + case StringFieldPropertiesDto stringField: + { + if (!string.IsNullOrWhiteSpace(stringField.Pattern)) + { + throw new NotSupportedException("Patterns validation for string fields are not supported."); + } + + if (stringField.AllowedValues?.Count > 0) + { + return GetRandomValue(stringField.AllowedValues); + } + + if (stringField.Editor == StringFieldEditor.Color) + { + var r = random.Next(0, 0xFF); + var g = random.Next(0, 0xFF); + var b = random.Next(0, 0xFF); + + return $"#{r:x2}{g:x2}{b:x2}"; + } + + var max = 100; + + if (stringField.MaxLength.HasValue) + { + max = stringField.MaxLength.Value; + } + else if (stringField.Editor == StringFieldEditor.RichText || stringField.Editor == StringFieldEditor.Markdown) + { + max = 1000; + } + + var result = LoremIpsum.Text(max, stringField.Editor == StringFieldEditor.RichText); + + if (stringField.Editor == StringFieldEditor.Slug) + { + result = slugify.GenerateSlug(result).Replace(".", "x"); + } + + return result; + } + + case TagsFieldPropertiesDto tagsField: + { + var items = (int)GetRandom(tagsField.MinItems, tagsField.MaxItems, 1, 5); + + var result = new JArray(); + + for (var i = 0; i < items; i++) + { + string value; + + if (tagsField.AllowedValues?.Count > 0) + { + value = GetRandomValue(tagsField.AllowedValues); + } + else + { + value = LoremIpsum.GetWord(random); + } + + result.Add(value); + } + + return result; + } + } + + throw new NotSupportedException($"Field type {field.Properties.GetType().Name} for field '{field.Name}' is not supported."); + } + + private T GetRandomValue(ICollection source) + { + return source.ElementAt(random.Next(0, source.Count)); + } + + private double GetRandom(double? minValue, double? maxValue, double defaultMin, double defaultMax) + { + var min = defaultMin; + var max = defaultMax; + + var defaultRange = (defaultMax - defaultMin); + + if (minValue.HasValue && maxValue.HasValue) + { + min = minValue.Value; + max = maxValue.Value; + } + else if (minValue.HasValue) + { + min = minValue.Value; + max = min + defaultRange; + } + else if (maxValue.HasValue) + { + max = maxValue.Value; + min = max - defaultRange; + } + + var value = random.NextDouble(); + + return min + (value * (max - min)); + } + } +} diff --git a/cli/Squidex.CLI/Squidex.CLI/Configuration/ConfigurationService.cs b/cli/Squidex.CLI/Squidex.CLI/Configuration/ConfigurationService.cs index 773c2fba..0eb971c6 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Configuration/ConfigurationService.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Configuration/ConfigurationService.cs @@ -140,7 +140,7 @@ public void UseAppInSession(string entry) sessionApp = entry; } - public (string App, SquidexClientManager Client) GetClient() + public (string App, SquidexClientManager Client) Setup() { if (!string.IsNullOrWhiteSpace(sessionApp) && configuration.Apps.TryGetValue(sessionApp, out var app)) { diff --git a/cli/Squidex.CLI/Squidex.CLI/Configuration/IConfigurationService.cs b/cli/Squidex.CLI/Squidex.CLI/Configuration/IConfigurationService.cs index 36ece062..f11a2d52 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Configuration/IConfigurationService.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Configuration/IConfigurationService.cs @@ -23,6 +23,6 @@ public interface IConfigurationService void UseAppInSession(string entry); - (string App, SquidexClientManager Client) GetClient(); + (string App, SquidexClientManager Client) Setup(); } } diff --git a/cli/Squidex.CLI/Squidex.CLI/Squidex.CLI.csproj b/cli/Squidex.CLI/Squidex.CLI/Squidex.CLI.csproj index 9ceca586..4f0542c2 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Squidex.CLI.csproj +++ b/cli/Squidex.CLI/Squidex.CLI/Squidex.CLI.csproj @@ -4,7 +4,7 @@ sq netcoreapp3.0 true - 4.0 + 4.1 true sq sebastianstehle @@ -17,13 +17,14 @@ https://github.com/Squidex/squidex/ - + - + - + + @@ -32,4 +33,7 @@ + + +