Browse Source

Language generation

pull/1/head
Sebastian 10 years ago
parent
commit
4d7e26491f
  1. 94
      src/Squidex.Core/Contents/ContentData.cs
  2. 17
      src/Squidex.Core/Contents/ContentFieldData.cs
  3. 66
      src/Squidex.Core/Schemas/Schema.cs
  4. 6
      src/Squidex.Core/project.json
  5. 2
      src/Squidex.Events/project.json
  6. 8
      src/Squidex.Infrastructure.MongoDb/project.json
  7. 8
      src/Squidex.Infrastructure.RabbitMq/project.json
  8. 28
      src/Squidex.Infrastructure/CollectionExtensions.cs
  9. 88
      src/Squidex.Infrastructure/Language.cs
  10. 198
      src/Squidex.Infrastructure/Languages.cs
  11. 4
      src/Squidex.Infrastructure/PropertyValue.cs
  12. 11
      src/Squidex.Infrastructure/project.json
  13. 4
      src/Squidex.Read.MongoDb/project.json
  14. 2
      src/Squidex.Read/project.json
  15. 2
      src/Squidex.Write/Apps/AppDomainObject.cs
  16. 14
      src/Squidex.Write/Contents/Commands/PatchContent.cs
  17. 7
      src/Squidex.Write/Contents/ContentCommandHandler.cs
  18. 48
      src/Squidex.Write/Contents/ContentDomainObject.cs
  19. 2
      src/Squidex.Write/project.json
  20. 2
      src/Squidex/Config/MyUrlsOptions.cs
  21. 1
      src/Squidex/Config/Web/WebUsages.cs
  22. 11
      src/Squidex/Controllers/ContentApi/ContentsController.cs
  23. 16
      src/Squidex/Controllers/ContentApi/Generator/SchemasSwaggerGenerator.cs
  24. 2
      src/Squidex/Controllers/ContentApi/Models/ContentDto.cs
  25. 63
      src/Squidex/Pipeline/SingleUrlsMiddleware.cs
  26. 4
      src/Squidex/Startup.cs
  27. 12
      src/Squidex/project.json
  28. 237
      tests/Squidex.Core.Tests/Contents/ContentDataTests.cs
  29. 4
      tests/Squidex.Core.Tests/Schemas/SchemaTests.cs
  30. 137
      tests/Squidex.Core.Tests/Schemas/SchemaValidationTests.cs
  31. 4
      tests/Squidex.Core.Tests/project.json
  32. 34
      tests/Squidex.Infrastructure.Tests/CollectionExtensionsTests.cs
  33. 27
      tests/Squidex.Infrastructure.Tests/LanguageTests.cs
  34. 4
      tests/Squidex.Infrastructure.Tests/project.json
  35. 4
      tests/Squidex.Read.Tests/MongoDb/Contents/ODataQueryTests.cs
  36. 4
      tests/Squidex.Read.Tests/project.json
  37. 2
      tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs
  38. 36
      tests/Squidex.Write.Tests/Apps/AppDomainObjectTests.cs
  39. 30
      tests/Squidex.Write.Tests/Contents/ContentCommandHandlerTests.cs
  40. 82
      tests/Squidex.Write.Tests/Contents/ContentDomainObjectTests.cs
  41. 4
      tests/Squidex.Write.Tests/project.json
  42. 66
      tools/GenerateLanguages/Program.cs
  43. 19
      tools/GenerateLanguages/project.json

94
src/Squidex.Core/Contents/ContentData.cs

@ -12,16 +12,22 @@ using System.Linq;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Core.Schemas; using Squidex.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
// ReSharper disable InvertIf
namespace Squidex.Core.Contents namespace Squidex.Core.Contents
{ {
public sealed class ContentData : Dictionary<string, ContentFieldData> public sealed class ContentData : Dictionary<string, ContentFieldData>, IEquatable<ContentData>
{ {
public ContentData() public ContentData()
: base(StringComparer.OrdinalIgnoreCase) : base(StringComparer.OrdinalIgnoreCase)
{ {
} }
public ContentData(IDictionary<string, ContentFieldData> copy)
: base(copy, StringComparer.OrdinalIgnoreCase)
{
}
public ContentData AddField(string fieldName, ContentFieldData data) public ContentData AddField(string fieldName, ContentFieldData data)
{ {
Guard.ValidPropertyName(fieldName, nameof(fieldName)); Guard.ValidPropertyName(fieldName, nameof(fieldName));
@ -31,7 +37,31 @@ namespace Squidex.Core.Contents
return this; return this;
} }
public ContentData ToNameModel(Schema schema) public ContentData MergeInto(ContentData other)
{
Guard.NotNull(other, nameof(other));
var result = new ContentData(this);
if (ReferenceEquals(other, this))
{
return result;
}
foreach (var otherValue in other)
{
var fieldValue = result.GetOrAdd(otherValue.Key, x => new ContentFieldData());
foreach (var value in otherValue.Value)
{
fieldValue[value.Key] = value.Value;
}
}
return result;
}
public ContentData ToIdModel(Schema schema)
{ {
Guard.NotNull(schema, nameof(schema)); Guard.NotNull(schema, nameof(schema));
@ -39,22 +69,20 @@ namespace Squidex.Core.Contents
foreach (var fieldValue in this) foreach (var fieldValue in this)
{ {
long fieldId;
Field field; Field field;
if (!long.TryParse(fieldValue.Key, out fieldId) || !schema.Fields.TryGetValue(fieldId, out field)) if (!schema.FieldsByName.TryGetValue(fieldValue.Key, out field))
{ {
continue; continue;
} }
result[field.Name] = fieldValue.Value; result[field.Id.ToString()] = fieldValue.Value;
} }
return result; return result;
} }
public ContentData ToIdModel(Schema schema) public ContentData ToNameModel(Schema schema)
{ {
Guard.NotNull(schema, nameof(schema)); Guard.NotNull(schema, nameof(schema));
@ -62,14 +90,16 @@ namespace Squidex.Core.Contents
foreach (var fieldValue in this) foreach (var fieldValue in this)
{ {
long fieldId;
Field field; Field field;
if (!schema.FieldsByName.TryGetValue(fieldValue.Key, out field)) if (!long.TryParse(fieldValue.Key, out fieldId) || !schema.Fields.TryGetValue(fieldId, out field))
{ {
continue; continue;
} }
result[field.Id.ToString()] = fieldValue.Value; result[field.Name] = fieldValue.Value;
} }
return result; return result;
@ -134,5 +164,51 @@ namespace Squidex.Core.Contents
return result; return result;
} }
public object ToLanguageModel(IReadOnlyCollection<Language> languagePreferences = null)
{
if (languagePreferences == null)
{
return this;
}
var result = new Dictionary<string, JToken>();
foreach (var fieldValue in this)
{
var fieldValues = fieldValue.Value;
foreach (var language in languagePreferences)
{
var languageCode = language.Iso2Code;
JToken value;
if (fieldValues.TryGetValue(languageCode, out value) && value != null)
{
result[fieldValue.Key] = value;
break;
}
}
}
return result;
}
public override bool Equals(object obj)
{
return Equals(obj as ContentData);
}
public bool Equals(ContentData other)
{
return other != null && (ReferenceEquals(this, other) || this.EqualsDictionary(other));
}
public override int GetHashCode()
{
return this.DictionaryHashCode();
}
} }
} }

17
src/Squidex.Core/Contents/ContentFieldData.cs

@ -13,7 +13,7 @@ using Squidex.Infrastructure;
namespace Squidex.Core.Contents namespace Squidex.Core.Contents
{ {
public sealed class ContentFieldData : Dictionary<string, JToken> public sealed class ContentFieldData : Dictionary<string, JToken>, IEquatable<ContentFieldData>
{ {
public ContentFieldData() public ContentFieldData()
: base(StringComparer.OrdinalIgnoreCase) : base(StringComparer.OrdinalIgnoreCase)
@ -35,5 +35,20 @@ namespace Squidex.Core.Contents
return this; return this;
} }
public override bool Equals(object obj)
{
return Equals(obj as ContentFieldData);
}
public bool Equals(ContentFieldData other)
{
return other != null && (ReferenceEquals(this, other) || this.EqualsDictionary(other));
}
public override int GetHashCode()
{
return this.DictionaryHashCode();
}
} }
} }

66
src/Squidex.Core/Schemas/Schema.cs

@ -203,17 +203,77 @@ namespace Squidex.Core.Schemas
return schema; return schema;
} }
public async Task ValidatePartialAsync(ContentData data, IList<ValidationError> errors, HashSet<Language> languages)
{
Guard.NotNull(data, nameof(data));
Guard.NotNull(errors, nameof(errors));
foreach (var fieldData in data)
{
Field field;
if (!fieldsByName.TryGetValue(fieldData.Key, out field))
{
errors.Add(new ValidationError($"{fieldData.Key} is not a known field", fieldData.Key));
}
else
{
var fieldErrors = new List<string>();
if (field.RawProperties.IsLocalizable)
{
foreach (var languageValue in fieldData.Value)
{
Language language;
if (!Language.TryGetLanguage(languageValue.Key, out language))
{
fieldErrors.Add($"{field.Name} has an invalid language '{languageValue.Key}'");
}
else if (!languages.Contains(language))
{
fieldErrors.Add($"{field.Name} has an unsupported language '{languageValue.Key}'");
}
else
{
await field.ValidateAsync(languageValue.Value, fieldErrors, language);
}
}
}
else
{
if (fieldData.Value.Keys.Any(x => x != Language.Invariant.Iso2Code))
{
fieldErrors.Add($"{field.Name} can only contain a single entry for invariant language ({Language.Invariant.Iso2Code})");
}
JToken value;
if (fieldData.Value.TryGetValue(Language.Invariant.Iso2Code, out value))
{
await field.ValidateAsync(value, fieldErrors);
}
}
foreach (var error in fieldErrors)
{
errors.Add(new ValidationError(error, field.Name));
}
}
}
}
public async Task ValidateAsync(ContentData data, IList<ValidationError> errors, HashSet<Language> languages) public async Task ValidateAsync(ContentData data, IList<ValidationError> errors, HashSet<Language> languages)
{ {
Guard.NotNull(data, nameof(data)); Guard.NotNull(data, nameof(data));
Guard.NotNull(errors, nameof(errors)); Guard.NotNull(errors, nameof(errors));
Guard.NotEmpty(languages, nameof(languages)); Guard.NotEmpty(languages, nameof(languages));
foreach (var fieldValue in data) foreach (var fieldData in data)
{ {
if (!fieldsByName.ContainsKey(fieldValue.Key)) if (!fieldsByName.ContainsKey(fieldData.Key))
{ {
errors.Add(new ValidationError($"{fieldValue.Key} is not a known field", fieldValue.Key)); errors.Add(new ValidationError($"{fieldData.Key} is not a known field", fieldData.Key));
} }
} }

6
src/Squidex.Core/project.json

@ -1,10 +1,10 @@
{ {
"dependencies": { "dependencies": {
"NodaTime": "2.0.0-alpha20160729",
"Squidex.Infrastructure": "1.0.0-*", "Squidex.Infrastructure": "1.0.0-*",
"protobuf-net": "2.1.0", "protobuf-net": "2.1.0",
"NJsonSchema": "7.3.6214.20986", "System.Collections.Immutable": "1.3.1",
"System.Collections.Immutable": "1.3.1" "NodaTime": "2.0.0-beta20170123",
"NJsonSchema": "7.10.6235.25398"
}, },
"frameworks": { "frameworks": {
"netstandard1.6": { "netstandard1.6": {

2
src/Squidex.Events/project.json

@ -1,6 +1,6 @@
{ {
"dependencies": { "dependencies": {
"NodaTime": "2.0.0-alpha20160729", "NodaTime": "2.0.0-beta20170123",
"Squidex.Core": "1.0.0-*", "Squidex.Core": "1.0.0-*",
"Squidex.Infrastructure": "1.0.0-*" "Squidex.Infrastructure": "1.0.0-*"
}, },

8
src/Squidex.Infrastructure.MongoDb/project.json

@ -1,11 +1,11 @@
{ {
"version": "1.0.0-*", "version": "1.0.0-*",
"dependencies": { "dependencies": {
"Autofac": "4.2.1", "Autofac": "4.3.0",
"Microsoft.Extensions.Logging": "1.1.0", "Microsoft.Extensions.Logging": "1.1.0",
"MongoDB.Driver": "2.4.1", "MongoDB.Driver": "2.4.1",
"Newtonsoft.Json": "9.0.2-beta1", "Newtonsoft.Json": "9.0.2-beta2",
"NodaTime": "2.0.0-alpha20160729", "NodaTime": "2.0.0-beta20170123",
"Squidex.Infrastructure": "1.0.0-*", "Squidex.Infrastructure": "1.0.0-*",
"System.Linq": "4.3.0", "System.Linq": "4.3.0",
"System.Reactive": "3.1.1", "System.Reactive": "3.1.1",

8
src/Squidex.Infrastructure.RabbitMq/project.json

@ -1,10 +1,10 @@
{ {
"version": "1.0.0-*", "version": "1.0.0-*",
"dependencies": { "dependencies": {
"Autofac": "4.2.1", "Autofac": "4.3.0",
"Microsoft.Extensions.Logging": "1.1.0", "Microsoft.Extensions.Logging": "1.1.0",
"Newtonsoft.Json": "9.0.2-beta1", "Newtonsoft.Json": "9.0.2-beta2",
"NodaTime": "2.0.0-alpha20160729", "NodaTime": "2.0.0-beta20170123",
"RabbitMQ.Client": "5.0.0-pre2", "RabbitMQ.Client": "5.0.0-pre2",
"Squidex.Infrastructure": "1.0.0-*", "Squidex.Infrastructure": "1.0.0-*",
"System.Linq": "4.3.0", "System.Linq": "4.3.0",

28
src/Squidex.Infrastructure/CollectionExtensions.cs

@ -43,6 +43,8 @@ namespace Squidex.Infrastructure
public static int OrderedHashCode<T>(this IEnumerable<T> collection, IEqualityComparer<T> comparer) public static int OrderedHashCode<T>(this IEnumerable<T> collection, IEqualityComparer<T> comparer)
{ {
Guard.NotNull(comparer, nameof(comparer));
var hashCodes = collection.Where(x => x != null).Select(x => x.GetHashCode()).OrderBy(x => x).ToArray(); var hashCodes = collection.Where(x => x != null).Select(x => x.GetHashCode()).OrderBy(x => x).ToArray();
var hashCode = 17; var hashCode = 17;
@ -55,9 +57,33 @@ namespace Squidex.Infrastructure
return hashCode; return hashCode;
} }
public static int DictionaryHashCode<TKey, TValue>(this IDictionary<TKey, TValue> dictionary)
{
return DictionaryHashCode(dictionary, EqualityComparer<TValue>.Default);
}
public static int DictionaryHashCode<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, IEqualityComparer<TValue> comparer)
{
Guard.NotNull(comparer, nameof(comparer));
var hashCode = 17;
foreach (var kvp in dictionary.OrderBy(x => x.Key))
{
hashCode = hashCode * 23 + kvp.Key.GetHashCode();
if (kvp.Value != null)
{
hashCode = hashCode * 23 + comparer.GetHashCode(kvp.Value);
}
}
return hashCode;
}
public static bool EqualsDictionary<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dictionary, IDictionary<TKey, TValue> other) public static bool EqualsDictionary<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dictionary, IDictionary<TKey, TValue> other)
{ {
return Equals(dictionary, other) || (other != null && dictionary.Count == other.Count && !dictionary.Except(other).Any()); return other != null && dictionary.Count == other.Count && !dictionary.Except(other).Any();
} }
public static TValue GetOrDefault<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dictionary, TKey key) public static TValue GetOrDefault<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dictionary, TKey key)

88
src/Squidex.Infrastructure/Language.cs

@ -8,38 +8,27 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.Text.RegularExpressions;
using System.Reflection; // ReSharper disable ConvertIfStatementToReturnStatement
using System.Text;
namespace Squidex.Infrastructure namespace Squidex.Infrastructure
{ {
public sealed class Language public sealed partial class Language
{ {
private static readonly Regex CultureRegex = new Regex("([a-z]{2})(\\-[a-z]{2})?");
private readonly string iso2Code; private readonly string iso2Code;
private readonly string englishName; private readonly string englishName;
private static readonly Dictionary<string, Language> allLanguages = new Dictionary<string, Language>(); private static readonly Dictionary<string, Language> allLanguages = new Dictionary<string, Language>();
public static readonly Language Invariant = new Language("iv", "Invariant"); public static readonly Language Invariant = AddLanguage("iv", "Invariant");
static Language() private static Language AddLanguage(string iso2Code, string englishName)
{ {
var resourceAssembly = typeof(Language).GetTypeInfo().Assembly; var language = new Language(iso2Code, englishName);
var resourceStream = resourceAssembly.GetManifestResourceStream("Squidex.Infrastructure.language-codes.csv");
using (var reader = new StreamReader(resourceStream, Encoding.UTF8)) allLanguages[iso2Code] = language;
{
reader.ReadLine();
while (!reader.EndOfStream)
{
var languageLine = reader.ReadLine();
var languageIso2Code = languageLine.Substring(1, 2);
var languageEnglishName = languageLine.Substring(6, languageLine.Length - 7);
allLanguages[languageIso2Code] = new Language(languageIso2Code, languageEnglishName); return language;
}
}
} }
public static Language GetLanguage(string iso2Code) public static Language GetLanguage(string iso2Code)
@ -56,20 +45,6 @@ namespace Squidex.Infrastructure
} }
} }
public static bool TryGetLanguage(string iso2Code, out Language language)
{
Guard.NotNullOrEmpty(iso2Code, nameof(iso2Code));
return allLanguages.TryGetValue(iso2Code, out language);
}
public static bool IsValidLanguage(string iso2Code)
{
Guard.NotNullOrEmpty(iso2Code, nameof(iso2Code));
return allLanguages.ContainsKey(iso2Code);
}
public static IEnumerable<Language> AllLanguages public static IEnumerable<Language> AllLanguages
{ {
get { return allLanguages.Values; } get { return allLanguages.Values; }
@ -91,5 +66,50 @@ namespace Squidex.Infrastructure
this.englishName = englishName; this.englishName = englishName;
} }
public static bool IsValidLanguage(string iso2Code)
{
Guard.NotNullOrEmpty(iso2Code, nameof(iso2Code));
return allLanguages.ContainsKey(iso2Code);
}
public static bool TryGetLanguage(string iso2Code, out Language language)
{
Guard.NotNullOrEmpty(iso2Code, nameof(iso2Code));
return allLanguages.TryGetValue(iso2Code, out language);
}
public static Language TryParse(string input)
{
if (string.IsNullOrWhiteSpace(input))
{
return null;
}
input = input.Trim();
if (input.Length != 2)
{
var match = CultureRegex.Match(input);
if (!match.Success)
{
return null;
}
input = match.Groups[0].Value;
}
Language result;
if (TryGetLanguage(input.ToLowerInvariant(), out result))
{
return result;
}
return null;
}
} }
} }

198
src/Squidex.Infrastructure/Languages.cs

@ -0,0 +1,198 @@
// ==========================================================================
// Langauges.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Infrastructure
{
partial class Language
{
public static Language AA = AddLanguage("aa", "Afar");
public static Language AB = AddLanguage("ab", "Abkhazian");
public static Language AE = AddLanguage("ae", "Avestan");
public static Language AF = AddLanguage("af", "Afrikaans");
public static Language AK = AddLanguage("ak", "Akan");
public static Language AM = AddLanguage("am", "Amharic");
public static Language AN = AddLanguage("an", "Aragonese");
public static Language AR = AddLanguage("ar", "Arabic");
public static Language AS = AddLanguage("as", "Assamese");
public static Language AV = AddLanguage("av", "Avaric");
public static Language AY = AddLanguage("ay", "Aymara");
public static Language AZ = AddLanguage("az", "Azerbaijani");
public static Language BA = AddLanguage("ba", "Bashkir");
public static Language BE = AddLanguage("be", "Belarusian");
public static Language BG = AddLanguage("bg", "Bulgarian");
public static Language BH = AddLanguage("bh", "Bihari languages");
public static Language BI = AddLanguage("bi", "Bislama");
public static Language BM = AddLanguage("bm", "Bambara");
public static Language BN = AddLanguage("bn", "Bengali");
public static Language BO = AddLanguage("bo", "Tibetan");
public static Language BR = AddLanguage("br", "Breton");
public static Language BS = AddLanguage("bs", "Bosnian");
public static Language CA = AddLanguage("ca", "Catalan; Valencian");
public static Language CE = AddLanguage("ce", "Chechen");
public static Language CH = AddLanguage("ch", "Chamorro");
public static Language CO = AddLanguage("co", "Corsican");
public static Language CR = AddLanguage("cr", "Cree");
public static Language CS = AddLanguage("cs", "Czech");
public static Language CU = AddLanguage("cu", "Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic");
public static Language CV = AddLanguage("cv", "Chuvash");
public static Language CY = AddLanguage("cy", "Welsh");
public static Language DA = AddLanguage("da", "Danish");
public static Language DE = AddLanguage("de", "German");
public static Language DV = AddLanguage("dv", "Divehi; Dhivehi; Maldivian");
public static Language DZ = AddLanguage("dz", "Dzongkha");
public static Language EE = AddLanguage("ee", "Ewe");
public static Language EL = AddLanguage("el", "Greek, Modern (1453-)");
public static Language EN = AddLanguage("en", "English");
public static Language EO = AddLanguage("eo", "Esperanto");
public static Language ES = AddLanguage("es", "Spanish; Castilian");
public static Language ET = AddLanguage("et", "Estonian");
public static Language EU = AddLanguage("eu", "Basque");
public static Language FA = AddLanguage("fa", "Persian");
public static Language FF = AddLanguage("ff", "Fulah");
public static Language FI = AddLanguage("fi", "Finnish");
public static Language FJ = AddLanguage("fj", "Fijian");
public static Language FO = AddLanguage("fo", "Faroese");
public static Language FR = AddLanguage("fr", "French");
public static Language FY = AddLanguage("fy", "Western Frisian");
public static Language GA = AddLanguage("ga", "Irish");
public static Language GD = AddLanguage("gd", "Gaelic; Scottish Gaelic");
public static Language GL = AddLanguage("gl", "Galician");
public static Language GN = AddLanguage("gn", "Guarani");
public static Language GU = AddLanguage("gu", "Gujarati");
public static Language GV = AddLanguage("gv", "Manx");
public static Language HA = AddLanguage("ha", "Hausa");
public static Language HE = AddLanguage("he", "Hebrew");
public static Language HI = AddLanguage("hi", "Hindi");
public static Language HO = AddLanguage("ho", "Hiri Motu");
public static Language HR = AddLanguage("hr", "Croatian");
public static Language HT = AddLanguage("ht", "Haitian; Haitian Creole");
public static Language HU = AddLanguage("hu", "Hungarian");
public static Language HY = AddLanguage("hy", "Armenian");
public static Language HZ = AddLanguage("hz", "Herero");
public static Language IA = AddLanguage("ia", "Interlingua (International Auxiliary Language Association)");
public static Language ID = AddLanguage("id", "Indonesian");
public static Language IE = AddLanguage("ie", "Interlingue; Occidental");
public static Language IG = AddLanguage("ig", "Igbo");
public static Language II = AddLanguage("ii", "Sichuan Yi; Nuosu");
public static Language IK = AddLanguage("ik", "Inupiaq");
public static Language IO = AddLanguage("io", "Ido");
public static Language IS = AddLanguage("is", "Icelandic");
public static Language IT = AddLanguage("it", "Italian");
public static Language IU = AddLanguage("iu", "Inuktitut");
public static Language JA = AddLanguage("ja", "Japanese");
public static Language JV = AddLanguage("jv", "Javanese");
public static Language KA = AddLanguage("ka", "Georgian");
public static Language KG = AddLanguage("kg", "Kongo");
public static Language KI = AddLanguage("ki", "Kikuyu; Gikuyu");
public static Language KJ = AddLanguage("kj", "Kuanyama; Kwanyama");
public static Language KK = AddLanguage("kk", "Kazakh");
public static Language KL = AddLanguage("kl", "Kalaallisut; Greenlandic");
public static Language KM = AddLanguage("km", "Central Khmer");
public static Language KN = AddLanguage("kn", "Kannada");
public static Language KO = AddLanguage("ko", "Korean");
public static Language KR = AddLanguage("kr", "Kanuri");
public static Language KS = AddLanguage("ks", "Kashmiri");
public static Language KU = AddLanguage("ku", "Kurdish");
public static Language KV = AddLanguage("kv", "Komi");
public static Language KW = AddLanguage("kw", "Cornish");
public static Language KY = AddLanguage("ky", "Kirghiz; Kyrgyz");
public static Language LA = AddLanguage("la", "Latin");
public static Language LB = AddLanguage("lb", "Luxembourgish; Letzeburgesch");
public static Language LG = AddLanguage("lg", "Ganda");
public static Language LI = AddLanguage("li", "Limburgan; Limburger; Limburgish");
public static Language LN = AddLanguage("ln", "Lingala");
public static Language LO = AddLanguage("lo", "Lao");
public static Language LT = AddLanguage("lt", "Lithuanian");
public static Language LU = AddLanguage("lu", "Luba-Katanga");
public static Language LV = AddLanguage("lv", "Latvian");
public static Language MG = AddLanguage("mg", "Malagasy");
public static Language MH = AddLanguage("mh", "Marshallese");
public static Language MI = AddLanguage("mi", "Maori");
public static Language MK = AddLanguage("mk", "Macedonian");
public static Language ML = AddLanguage("ml", "Malayalam");
public static Language MN = AddLanguage("mn", "Mongolian");
public static Language MR = AddLanguage("mr", "Marathi");
public static Language MS = AddLanguage("ms", "Malay");
public static Language MT = AddLanguage("mt", "Maltese");
public static Language MY = AddLanguage("my", "Burmese");
public static Language NA = AddLanguage("na", "Nauru");
public static Language NB = AddLanguage("nb", "Bokmål, Norwegian; Norwegian Bokmål");
public static Language ND = AddLanguage("nd", "Ndebele, North; North Ndebele");
public static Language NE = AddLanguage("ne", "Nepali");
public static Language NG = AddLanguage("ng", "Ndonga");
public static Language NL = AddLanguage("nl", "Dutch; Flemish");
public static Language NN = AddLanguage("nn", "Norwegian Nynorsk; Nynorsk, Norwegian");
public static Language NO = AddLanguage("no", "Norwegian");
public static Language NR = AddLanguage("nr", "Ndebele, South; South Ndebele");
public static Language NV = AddLanguage("nv", "Navajo; Navaho");
public static Language NY = AddLanguage("ny", "Chichewa; Chewa; Nyanja");
public static Language OC = AddLanguage("oc", "Occitan (post 1500); Provençal");
public static Language OJ = AddLanguage("oj", "Ojibwa");
public static Language OM = AddLanguage("om", "Oromo");
public static Language OR = AddLanguage("or", "Oriya");
public static Language OS = AddLanguage("os", "Ossetian; Ossetic");
public static Language PA = AddLanguage("pa", "Panjabi; Punjabi");
public static Language PI = AddLanguage("pi", "Pali");
public static Language PL = AddLanguage("pl", "Polish");
public static Language PS = AddLanguage("ps", "Pushto; Pashto");
public static Language PT = AddLanguage("pt", "Portuguese");
public static Language QU = AddLanguage("qu", "Quechua");
public static Language RM = AddLanguage("rm", "Romansh");
public static Language RN = AddLanguage("rn", "Rundi");
public static Language RO = AddLanguage("ro", "Romanian; Moldavian; Moldovan");
public static Language RU = AddLanguage("ru", "Russian");
public static Language RW = AddLanguage("rw", "Kinyarwanda");
public static Language SA = AddLanguage("sa", "Sanskrit");
public static Language SC = AddLanguage("sc", "Sardinian");
public static Language SD = AddLanguage("sd", "Sindhi");
public static Language SE = AddLanguage("se", "Northern Sami");
public static Language SG = AddLanguage("sg", "Sango");
public static Language SI = AddLanguage("si", "Sinhala; Sinhalese");
public static Language SK = AddLanguage("sk", "Slovak");
public static Language SL = AddLanguage("sl", "Slovenian");
public static Language SM = AddLanguage("sm", "Samoan");
public static Language SN = AddLanguage("sn", "Shona");
public static Language SO = AddLanguage("so", "Somali");
public static Language SQ = AddLanguage("sq", "Albanian");
public static Language SR = AddLanguage("sr", "Serbian");
public static Language SS = AddLanguage("ss", "Swati");
public static Language ST = AddLanguage("st", "Sotho, Southern");
public static Language SU = AddLanguage("su", "Sundanese");
public static Language SV = AddLanguage("sv", "Swedish");
public static Language SW = AddLanguage("sw", "Swahili");
public static Language TA = AddLanguage("ta", "Tamil");
public static Language TE = AddLanguage("te", "Telugu");
public static Language TG = AddLanguage("tg", "Tajik");
public static Language TH = AddLanguage("th", "Thai");
public static Language TI = AddLanguage("ti", "Tigrinya");
public static Language TK = AddLanguage("tk", "Turkmen");
public static Language TL = AddLanguage("tl", "Tagalog");
public static Language TN = AddLanguage("tn", "Tswana");
public static Language TO = AddLanguage("to", "Tonga (Tonga Islands)");
public static Language TR = AddLanguage("tr", "Turkish");
public static Language TS = AddLanguage("ts", "Tsonga");
public static Language TT = AddLanguage("tt", "Tatar");
public static Language TW = AddLanguage("tw", "Twi");
public static Language TY = AddLanguage("ty", "Tahitian");
public static Language UG = AddLanguage("ug", "Uighur; Uyghur");
public static Language UK = AddLanguage("uk", "Ukrainian");
public static Language UR = AddLanguage("ur", "Urdu");
public static Language UZ = AddLanguage("uz", "Uzbek");
public static Language VE = AddLanguage("ve", "Venda");
public static Language VI = AddLanguage("vi", "Vietnamese");
public static Language VO = AddLanguage("vo", "Volapük");
public static Language WA = AddLanguage("wa", "Walloon");
public static Language WO = AddLanguage("wo", "Wolof");
public static Language XH = AddLanguage("xh", "Xhosa");
public static Language YI = AddLanguage("yi", "Yiddish");
public static Language YO = AddLanguage("yo", "Yoruba");
public static Language ZA = AddLanguage("za", "Zhuang; Chuang");
public static Language ZH = AddLanguage("zh", "Chinese");
public static Language ZU = AddLanguage("zu", "Zulu");
}
}

4
src/Squidex.Infrastructure/PropertyValue.cs

@ -127,12 +127,12 @@ namespace Squidex.Infrastructure
public Instant ToInstant(CultureInfo culture) public Instant ToInstant(CultureInfo culture)
{ {
return ToOrParseValue(culture, x => InstantPattern.GeneralPattern.Parse(x).Value); return ToOrParseValue(culture, x => InstantPattern.General.Parse(x).Value);
} }
public Instant? ToNullableInstant(CultureInfo culture) public Instant? ToNullableInstant(CultureInfo culture)
{ {
return ToNullableOrParseValue(culture, x => InstantPattern.GeneralPattern.Parse(x).Value); return ToNullableOrParseValue(culture, x => InstantPattern.General.Parse(x).Value);
} }
public Guid ToGuid(CultureInfo culture) public Guid ToGuid(CultureInfo culture)

11
src/Squidex.Infrastructure/project.json

@ -1,10 +1,10 @@
{ {
"version": "1.0.0-*", "version": "1.0.0-*",
"dependencies": { "dependencies": {
"Autofac": "4.2.1", "Autofac": "4.3.0",
"Microsoft.Extensions.Logging": "1.1.0", "Microsoft.Extensions.Logging": "1.1.0",
"Newtonsoft.Json": "9.0.2-beta1", "Newtonsoft.Json": "9.0.2-beta2",
"NodaTime": "2.0.0-alpha20160729", "NodaTime": "2.0.0-beta20170123",
"protobuf-net": "2.1.0", "protobuf-net": "2.1.0",
"System.Linq": "4.3.0", "System.Linq": "4.3.0",
"System.Reactive": "3.1.1", "System.Reactive": "3.1.1",
@ -19,11 +19,6 @@
"imports": "dnxcore50" "imports": "dnxcore50"
} }
}, },
"buildOptions": {
"embed": [
"*.csv"
]
},
"tooling": { "tooling": {
"defaultNamespace": "Squidex.Infrastructure" "defaultNamespace": "Squidex.Infrastructure"
} }

4
src/Squidex.Read.MongoDb/project.json

@ -1,7 +1,7 @@
{ {
"dependencies": { "dependencies": {
"Autofac": "4.2.1", "Autofac": "4.3.0",
"IdentityServer4": "1.0.0", "IdentityServer4": "1.0.2",
"Microsoft.AspNetCore.Identity": "1.1.0", "Microsoft.AspNetCore.Identity": "1.1.0",
"Microsoft.AspNetCore.Identity.MongoDB": "1.0.2", "Microsoft.AspNetCore.Identity.MongoDB": "1.0.2",
"MongoDB.Driver": "2.4.1", "MongoDB.Driver": "2.4.1",

2
src/Squidex.Read/project.json

@ -2,7 +2,7 @@
"dependencies": { "dependencies": {
"Microsoft.Extensions.Caching.Memory": "1.1.0", "Microsoft.Extensions.Caching.Memory": "1.1.0",
"MongoDB.Driver": "2.4.1", "MongoDB.Driver": "2.4.1",
"NodaTime": "2.0.0-alpha20160729", "NodaTime": "2.0.0-beta20170123",
"Squidex.Core": "1.0.0-*", "Squidex.Core": "1.0.0-*",
"Squidex.Events": "1.0.0-*", "Squidex.Events": "1.0.0-*",
"Squidex.Infrastructure": "1.0.0-*" "Squidex.Infrastructure": "1.0.0-*"

2
src/Squidex.Write/Apps/AppDomainObject.cs

@ -23,7 +23,7 @@ namespace Squidex.Write.Apps
{ {
public class AppDomainObject : DomainObject public class AppDomainObject : DomainObject
{ {
private static readonly Language DefaultLanguage = Language.GetLanguage("en"); private static readonly Language DefaultLanguage = Language.EN;
private readonly AppContributors contributors = new AppContributors(); private readonly AppContributors contributors = new AppContributors();
private readonly AppLanguages languages = new AppLanguages(); private readonly AppLanguages languages = new AppLanguages();
private readonly AppClients clients = new AppClients(); private readonly AppClients clients = new AppClients();

14
src/Squidex.Write/Contents/Commands/PatchContent.cs

@ -0,0 +1,14 @@
// ==========================================================================
// PatchContent.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Write.Contents.Commands
{
public class PatchContent : ContentDataCommand
{
}
}

7
src/Squidex.Write/Contents/ContentCommandHandler.cs

@ -57,6 +57,13 @@ namespace Squidex.Write.Contents
await handler.UpdateAsync<ContentDomainObject>(command, s => s.Update(command)); await handler.UpdateAsync<ContentDomainObject>(command, s => s.Update(command));
} }
protected async Task On(PatchContent command, CommandContext context)
{
await ValidateAsync(command, () => "Failed to patch content");
await handler.UpdateAsync<ContentDomainObject>(command, s => s.Patch(command));
}
protected Task On(PublishContent command, CommandContext context) protected Task On(PublishContent command, CommandContext context)
{ {
return handler.UpdateAsync<ContentDomainObject>(command, s => s.Publish(command)); return handler.UpdateAsync<ContentDomainObject>(command, s => s.Publish(command));

48
src/Squidex.Write/Contents/ContentDomainObject.cs

@ -7,6 +7,7 @@
// ========================================================================== // ==========================================================================
using System; using System;
using Squidex.Core.Contents;
using Squidex.Events.Contents; using Squidex.Events.Contents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS; using Squidex.Infrastructure.CQRS;
@ -22,6 +23,7 @@ namespace Squidex.Write.Contents
private bool isDeleted; private bool isDeleted;
private bool isCreated; private bool isCreated;
private bool isPublished; private bool isPublished;
private ContentData data;
public bool IsDeleted public bool IsDeleted
{ {
@ -41,6 +43,13 @@ namespace Squidex.Write.Contents
protected void On(ContentCreated @event) protected void On(ContentCreated @event)
{ {
isCreated = true; isCreated = true;
data = @event.Data;
}
protected void On(ContentUpdated @event)
{
data = @event.Data;
} }
protected void On(ContentPublished @event) protected void On(ContentPublished @event)
@ -69,46 +78,65 @@ namespace Squidex.Write.Contents
return this; return this;
} }
public ContentDomainObject Update(UpdateContent command) public ContentDomainObject Delete(DeleteContent command)
{ {
Guard.Valid(command, nameof(command), () => "Cannot update content"); Guard.NotNull(command, nameof(command));
VerifyCreatedAndNotDeleted(); VerifyCreatedAndNotDeleted();
RaiseEvent(SimpleMapper.Map(command, new ContentUpdated())); RaiseEvent(SimpleMapper.Map(command, new ContentDeleted()));
return this; return this;
} }
public ContentDomainObject Delete(DeleteContent command) public ContentDomainObject Publish(PublishContent command)
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
VerifyCreatedAndNotDeleted(); VerifyCreatedAndNotDeleted();
RaiseEvent(SimpleMapper.Map(command, new ContentDeleted())); RaiseEvent(SimpleMapper.Map(command, new ContentPublished()));
return this; return this;
} }
public ContentDomainObject Publish(PublishContent command) public ContentDomainObject Unpublish(UnpublishContent command)
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
VerifyCreatedAndNotDeleted(); VerifyCreatedAndNotDeleted();
RaiseEvent(SimpleMapper.Map(command, new ContentPublished())); RaiseEvent(SimpleMapper.Map(command, new ContentUnpublished()));
return this; return this;
} }
public ContentDomainObject Unpublish(UnpublishContent command) public ContentDomainObject Update(UpdateContent command)
{ {
Guard.NotNull(command, nameof(command)); Guard.Valid(command, nameof(command), () => "Cannot update content");
VerifyCreatedAndNotDeleted(); VerifyCreatedAndNotDeleted();
RaiseEvent(SimpleMapper.Map(command, new ContentUnpublished())); if (!command.Data.Equals(data))
{
RaiseEvent(SimpleMapper.Map(command, new ContentUpdated()));
}
return this;
}
public ContentDomainObject Patch(PatchContent command)
{
Guard.Valid(command, nameof(command), () => "Cannot patch content");
VerifyCreatedAndNotDeleted();
var newData = data.MergeInto(command.Data);
if (!newData.Equals(data))
{
RaiseEvent(new ContentUpdated { Data = newData });
}
return this; return this;
} }

2
src/Squidex.Write/project.json

@ -1,7 +1,7 @@
{ {
"dependencies": { "dependencies": {
"Microsoft.AspNetCore.Identity": "1.1.0", "Microsoft.AspNetCore.Identity": "1.1.0",
"NodaTime": "2.0.0-alpha20160729", "NodaTime": "2.0.0-beta20170123",
"Squidex.Core": "1.0.0-*", "Squidex.Core": "1.0.0-*",
"Squidex.Events": "1.0.0-*", "Squidex.Events": "1.0.0-*",
"Squidex.Infrastructure": "1.0.0-*", "Squidex.Infrastructure": "1.0.0-*",

2
src/Squidex/Config/MyUrlsOptions.cs

@ -12,6 +12,8 @@ namespace Squidex.Config
{ {
public sealed class MyUrlsOptions public sealed class MyUrlsOptions
{ {
public bool EnforceSSL { get; set; }
public string BaseUrl { get; set; } public string BaseUrl { get; set; }
public string BuildUrl(string path, bool trailingSlash = true) public string BuildUrl(string path, bool trailingSlash = true)

1
src/Squidex/Config/Web/WebUsages.cs

@ -22,6 +22,7 @@ namespace Squidex.Config.Web
{ {
OnPrepareResponse = context => OnPrepareResponse = context =>
{ {
context.Context.Request.GetTypedHeaders();
var response = context.Context.Response; var response = context.Context.Response;
var headers = response.GetTypedHeaders(); var headers = response.GetTypedHeaders();

11
src/Squidex/Controllers/ContentApi/ContentsController.cs

@ -130,6 +130,17 @@ namespace Squidex.Controllers.ContentApi
return NoContent(); return NoContent();
} }
[HttpPatch]
[Route("content/{app}/{name}/{id}")]
public async Task<IActionResult> PatchContent(Guid id, [FromBody] ContentData request)
{
var command = new PatchContent { AggregateId = id, Data = request };
await CommandBus.PublishAsync(command);
return NoContent();
}
[HttpPut] [HttpPut]
[Route("content/{app}/{name}/{id}/publish")] [Route("content/{app}/{name}/{id}/publish")]
public async Task<IActionResult> PublishContent(Guid id) public async Task<IActionResult> PublishContent(Guid id)

16
src/Squidex/Controllers/ContentApi/Generator/SchemasSwaggerGenerator.cs

@ -185,6 +185,7 @@ When you change the field to be localizable the value will become the value for
GenerateSchemaCreateOperation(schema, schemaName), GenerateSchemaCreateOperation(schema, schemaName),
GenerateSchemaGetOperation(schema, schemaName), GenerateSchemaGetOperation(schema, schemaName),
GenerateSchemaUpdateOperation(schema, schemaName), GenerateSchemaUpdateOperation(schema, schemaName),
GenerateSchemaPatchOperation(schema, schemaName),
GenerateSchemaPublishOperation(schema, schemaName), GenerateSchemaPublishOperation(schema, schemaName),
GenerateSchemaUnpublishOperation(schema, schemaName), GenerateSchemaUnpublishOperation(schema, schemaName),
GenerateSchemaDeleteOperation(schema, schemaName) GenerateSchemaDeleteOperation(schema, schemaName)
@ -257,6 +258,21 @@ When you change the field to be localizable the value will become the value for
}); });
} }
private SwaggerOperations GenerateSchemaPatchOperation(Schema schema, string schemaName)
{
return AddOperation(SwaggerOperationMethod.Patch, schemaName, $"{appBasePath}/{schema.Name}/{{id}}", operation =>
{
operation.Summary = $"Update a {schemaName} content element partially.";
var bodySchema = AppendSchema($"{schema.Name}Dto", schema.BuildSchema(languages, AppendSchema));
operation.Parameters.AddBodyParameter(bodySchema, "data", string.Format(BodyDescription, schemaName));
operation.Responses.Add("204",
new SwaggerResponse { Description = $"{schemaName} element updated." });
});
}
private SwaggerOperations GenerateSchemaPublishOperation(Schema schema, string schemaName) private SwaggerOperations GenerateSchemaPublishOperation(Schema schema, string schemaName)
{ {
return AddOperation(SwaggerOperationMethod.Put, schemaName, $"{appBasePath}/{schema.Name}/{{id}}/publish", operation => return AddOperation(SwaggerOperationMethod.Put, schemaName, $"{appBasePath}/{schema.Name}/{{id}}/publish", operation =>

2
src/Squidex/Controllers/ContentApi/Models/ContentDto.cs

@ -36,7 +36,7 @@ namespace Squidex.Controllers.ContentApi.Models
/// The data of the content item. /// The data of the content item.
/// </summary> /// </summary>
[Required] [Required]
public ContentData Data { get; set; } public object Data { get; set; }
/// <summary> /// <summary>
/// The date and time when the content element has been created. /// The date and time when the content element has been created.

63
src/Squidex/Pipeline/SingleUrlsMiddleware.cs

@ -0,0 +1,63 @@
// ==========================================================================
// SingleUrlsMiddleware.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Squidex.Config;
namespace Squidex.Pipeline
{
public sealed class SingleUrlsMiddleware
{
private readonly RequestDelegate next;
private readonly IOptions<MyUrlsOptions> urls;
private readonly ILogger<SingleUrlsMiddleware> logger;
public SingleUrlsMiddleware(RequestDelegate next, ILoggerFactory factory, IOptions<MyUrlsOptions> urls)
{
this.next = next;
this.urls = urls;
logger = factory.CreateLogger<SingleUrlsMiddleware>();
}
public async Task Invoke(HttpContext context)
{
if (!urls.Value.EnforceSSL)
{
await next(context);
}
else
{
var currentUrl = string.Concat(context.Request.Scheme, "://", context.Request.Host, context.Request.Path);
var hostName = context.Request.Host.ToString().ToLowerInvariant();
if (hostName.StartsWith("www."))
{
hostName = hostName.Substring(4);
}
var newUrl = string.Concat("https://", hostName, context.Request.Path);
if (!string.Equals(newUrl, currentUrl, StringComparison.OrdinalIgnoreCase))
{
logger.LogError("Invalid url: {0} instead {1}", currentUrl, newUrl);
context.Response.Redirect(newUrl + context.Request.QueryString, true);
}
else
{
await next(context);
}
}
}
}
}

4
src/Squidex/Startup.cs

@ -80,6 +80,7 @@ namespace Squidex
Configuration.GetSection("identity")); Configuration.GetSection("identity"));
var builder = new ContainerBuilder(); var builder = new ContainerBuilder();
builder.Populate(services);
builder.RegisterModule<InfrastructureModule>(); builder.RegisterModule<InfrastructureModule>();
builder.RegisterModule<MongoDbEventStoreModule>(); builder.RegisterModule<MongoDbEventStoreModule>();
builder.RegisterModule<MongoDbModule>(); builder.RegisterModule<MongoDbModule>();
@ -87,7 +88,6 @@ namespace Squidex
builder.RegisterModule<ReadModule>(); builder.RegisterModule<ReadModule>();
builder.RegisterModule<WebModule>(); builder.RegisterModule<WebModule>();
builder.RegisterModule<WriteModule>(); builder.RegisterModule<WriteModule>();
builder.Populate(services);
var container = builder.Build(); var container = builder.Build();
@ -104,6 +104,8 @@ namespace Squidex
loggerFactory.AddConsole(LogLevel.Debug); loggerFactory.AddConsole(LogLevel.Debug);
loggerFactory.AddDebug(); loggerFactory.AddDebug();
app.UseMiddleware<SingleUrlsMiddleware>();
MapAndUseIdentity(app); MapAndUseIdentity(app);
MapAndUseApi(app); MapAndUseApi(app);
MapAndUseFrontend(app); MapAndUseFrontend(app);

12
src/Squidex/project.json

@ -1,16 +1,16 @@
{ {
"dependencies": { "dependencies": {
"Autofac": "4.2.1", "Autofac": "4.3.0",
"Autofac.Extensions.DependencyInjection": "4.0.0", "Autofac.Extensions.DependencyInjection": "4.0.0",
"IdentityServer4": "1.0.0", "IdentityServer4": "1.0.2",
"IdentityServer4.AccessTokenValidation": "1.0.2", "IdentityServer4.AccessTokenValidation": "1.0.4",
"IdentityServer4.AspNetIdentity": "1.0.0", "IdentityServer4.AspNetIdentity": "1.0.0",
"Microsoft.AspNetCore.Authentication.Cookies": "1.1.0", "Microsoft.AspNetCore.Authentication.Cookies": "1.1.0",
"Microsoft.AspNetCore.Authentication.Google": "1.1.0", "Microsoft.AspNetCore.Authentication.Google": "1.1.0",
"Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.1.0", "Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.1.0",
"Microsoft.AspNetCore.Diagnostics": "1.1.0", "Microsoft.AspNetCore.Diagnostics": "1.1.0",
"Microsoft.AspNetCore.Identity": "1.1.0", "Microsoft.AspNetCore.Identity": "1.1.0",
"Microsoft.AspNetCore.Mvc": "1.1.0", "Microsoft.AspNetCore.Mvc": "1.1.1",
"Microsoft.AspNetCore.Razor.Tools": "1.1.0-preview4-final", "Microsoft.AspNetCore.Razor.Tools": "1.1.0-preview4-final",
"Microsoft.AspNetCore.Server.IISIntegration": "1.1.0", "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0",
"Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.1.0-preview4-final", "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.1.0-preview4-final",
@ -23,8 +23,8 @@
"Microsoft.Extensions.Logging.Debug": "1.1.0", "Microsoft.Extensions.Logging.Debug": "1.1.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0",
"MongoDB.Driver": "2.4.1", "MongoDB.Driver": "2.4.1",
"NJsonSchema": "7.3.6214.20986", "NJsonSchema": "7.10.6235.25398",
"NSwag.AspNetCore": "8.5.0", "NSwag.AspNetCore": "8.8.0",
"OpenCover": "4.6.519", "OpenCover": "4.6.519",
"ReportGenerator": "2.5.2", "ReportGenerator": "2.5.2",
"Squidex.Core": "1.0.0-*", "Squidex.Core": "1.0.0-*",

237
tests/Squidex.Core.Tests/Contents/ContentDataTests.cs

@ -7,7 +7,6 @@
// ========================================================================== // ==========================================================================
using System.Collections.Generic; using System.Collections.Generic;
using FluentAssertions;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Core.Schemas; using Squidex.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -26,8 +25,8 @@ namespace Squidex.Core.Contents
.AddOrUpdateField( .AddOrUpdateField(
new NumberField(3, "field3", new NumberFieldProperties { IsLocalizable = false })) new NumberField(3, "field3", new NumberFieldProperties { IsLocalizable = false }))
.HideField(3); .HideField(3);
private readonly Language[] languages = { Language.GetLanguage("de"), Language.GetLanguage("en") }; private readonly Language[] languages = { Language.DE, Language.EN };
private readonly Language masterLanguage = Language.GetLanguage("en"); private readonly Language masterLanguage = Language.EN;
[Fact] [Fact]
public void Should_convert_to_id_model() public void Should_convert_to_id_model()
@ -56,10 +55,10 @@ namespace Squidex.Core.Contents
.AddField("2", .AddField("2",
new ContentFieldData() new ContentFieldData()
.AddValue("iv", 3)); .AddValue("iv", 3));
actual.ShouldBeEquivalentTo(expected);
}
Assert.Equal(expected, actual);
}
[Fact] [Fact]
public void Should_convert_to_from_id_model() public void Should_convert_to_from_id_model()
{ {
@ -87,22 +86,18 @@ namespace Squidex.Core.Contents
.AddField("field2", .AddField("field2",
new ContentFieldData() new ContentFieldData()
.AddValue("iv", 3)); .AddValue("iv", 3));
actual.ShouldBeEquivalentTo(expected); Assert.Equal(expected, actual);
} }
[Fact] [Fact]
public void Should_cleanup_old_fields() public void Should_cleanup_old_fields()
{ {
var expected = var expected =
new Dictionary<string, Dictionary<string, JToken>> new ContentData()
{ .AddField("field1",
["field1"] = new Dictionary<string, JToken> new ContentFieldData()
{ .AddValue("en", "en_string")
["en"] = "en_string", .AddValue("de", "de_string"));
["de"] = "de_string"
}
};
var input = var input =
new ContentData() new ContentData()
@ -114,23 +109,19 @@ namespace Squidex.Core.Contents
.AddValue("en", "en_string") .AddValue("en", "en_string")
.AddValue("de", "de_string")); .AddValue("de", "de_string"));
var output = input.ToApiModel(schema, languages, masterLanguage); var actual = input.ToApiModel(schema, languages, masterLanguage);
output.ShouldBeEquivalentTo(expected); Assert.Equal(expected, actual);
} }
[Fact] [Fact]
public void Should_cleanup_old_languages() public void Should_cleanup_old_languages()
{ {
var expected = var expected =
new Dictionary<string, Dictionary<string, JToken>> new ContentData()
{ .AddField("field1",
["field1"] = new Dictionary<string, JToken> new ContentFieldData()
{ .AddValue("en", "en_string")
["en"] = "en_string", .AddValue("de", "de_string"));
["de"] = "de_string"
}
};
var input = var input =
new ContentData() new ContentData()
@ -140,22 +131,19 @@ namespace Squidex.Core.Contents
.AddValue("de", "de_string") .AddValue("de", "de_string")
.AddValue("it", "it_string")); .AddValue("it", "it_string"));
var output = input.ToApiModel(schema, languages, masterLanguage); var actual = input.ToApiModel(schema, languages, masterLanguage);
output.ShouldBeEquivalentTo(expected); Assert.Equal(expected, actual);
} }
[Fact] [Fact]
public void Should_provide_invariant_from_master_language() public void Should_provide_invariant_from_master_language()
{ {
var expected = var expected =
new Dictionary<string, Dictionary<string, JToken>> new ContentData()
{ .AddField("field2",
["field2"] = new Dictionary<string, JToken> new ContentFieldData()
{ .AddValue("iv", 3));
["iv"] = 3
}
};
var input = var input =
new ContentData() new ContentData()
@ -164,22 +152,19 @@ namespace Squidex.Core.Contents
.AddValue("de", 2) .AddValue("de", 2)
.AddValue("en", 3)); .AddValue("en", 3));
var output = input.ToApiModel(schema, languages, masterLanguage); var actual = input.ToApiModel(schema, languages, masterLanguage);
output.ShouldBeEquivalentTo(expected); Assert.Equal(expected, actual);
} }
[Fact] [Fact]
public void Should_provide_invariant_from_first_language() public void Should_provide_invariant_from_first_language()
{ {
var expected = var expected =
new Dictionary<string, Dictionary<string, JToken>> new ContentData()
{ .AddField("field2",
["field2"] = new Dictionary<string, JToken> new ContentFieldData()
{ .AddValue("iv", 2));
["iv"] = 2
}
};
var input = var input =
new ContentData() new ContentData()
@ -188,35 +173,167 @@ namespace Squidex.Core.Contents
.AddValue("de", 2) .AddValue("de", 2)
.AddValue("it", 3)); .AddValue("it", 3));
var output = input.ToApiModel(schema, languages, masterLanguage); var actual = input.ToApiModel(schema, languages, masterLanguage);
output.ShouldBeEquivalentTo(expected); Assert.Equal(expected, actual);
} }
[Fact] [Fact]
public void Should_not_include_hidden_field() public void Should_not_include_hidden_field()
{ {
var expected = var expected =
new Dictionary<string, Dictionary<string, JToken>> new ContentData()
{ .AddField("field2",
["field2"] = new Dictionary<string, JToken> new ContentFieldData()
{ .AddValue("iv", 5));
["iv"] = 2
}
};
var input = var input =
new ContentData() new ContentData()
.AddField("field2", .AddField("field2",
new ContentFieldData() new ContentFieldData()
.AddValue("iv", 2)) .AddValue("iv", 5))
.AddField("field3",
new ContentFieldData()
.AddValue("iv", 2));
var actual = input.ToApiModel(schema, languages, masterLanguage);
Assert.Equal(expected, actual);
}
[Fact]
public void Should_return_original_when_no_language_preferences_defined()
{
var data =
new ContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("iv", 1));
Assert.Same(data, data.ToLanguageModel());
}
[Fact]
public void Should_return_flat_list_when_languages_specified()
{
var data =
new ContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("de", 1)
.AddValue("en", 2))
.AddField("field2",
new ContentFieldData()
.AddValue("de", null)
.AddValue("en", 4))
.AddField("field3",
new ContentFieldData()
.AddValue("en", 6))
.AddField("field4",
new ContentFieldData()
.AddValue("it", 7));
var output = (Dictionary<string, JToken>)data.ToLanguageModel(new List<Language> { Language.DE, Language.EN });
var expected = new Dictionary<string, JToken>
{
{ "field1", 1 },
{ "field2", 4 },
{ "field3", 6 },
};
Assert.True(expected.EqualsDictionary(output));
}
[Fact]
public void Should_merge_two_data()
{
var lhs =
new ContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("iv", 1))
.AddField("field2",
new ContentFieldData()
.AddValue("de", 2));
var rhs =
new ContentData()
.AddField("field2",
new ContentFieldData()
.AddValue("en", 3))
.AddField("field3",
new ContentFieldData()
.AddValue("iv", 4));
var expected =
new ContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("iv", 1))
.AddField("field2",
new ContentFieldData()
.AddValue("de", 2)
.AddValue("en", 3))
.AddField("field3", .AddField("field3",
new ContentFieldData()
.AddValue("iv", 4));
var actual = lhs.MergeInto(rhs);
Assert.Equal(expected, actual);
}
[Fact]
public void Should_be_equal_when_data_have_same_structure()
{
var lhs =
new ContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("iv", 2))
.AddField("field2",
new ContentFieldData()
.AddValue("iv", 2));
var rhs =
new ContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("iv", 2))
.AddField("field2",
new ContentFieldData()
.AddValue("iv", 2));
Assert.True(lhs.Equals(rhs));
Assert.True(lhs.Equals((object)rhs));
Assert.Equal(lhs.GetHashCode(), rhs.GetHashCode());
}
[Fact]
public void Should_not_be_equal_when_data_have_not_same_structure()
{
var lhs =
new ContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("iv", 2))
.AddField("field2",
new ContentFieldData() new ContentFieldData()
.AddValue("iv", 2)); .AddValue("iv", 2));
var output = input.ToApiModel(schema, languages, masterLanguage); var rhs =
new ContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("en", 2))
.AddField("field3",
new ContentFieldData()
.AddValue("iv", 2));
output.ShouldBeEquivalentTo(expected); Assert.False(lhs.Equals(rhs));
Assert.False(lhs.Equals((object)rhs));
Assert.NotEqual(lhs.GetHashCode(), rhs.GetHashCode());
} }
} }
} }

4
tests/Squidex.Core.Tests/Schemas/SchemaTests.cs

@ -258,7 +258,7 @@ namespace Squidex.Core.Schemas
.AddOrUpdateField(new NumberField(4, "age", .AddOrUpdateField(new NumberField(4, "age",
new NumberFieldProperties { MinValue = 1, MaxValue = 10 })); new NumberFieldProperties { MinValue = 1, MaxValue = 10 }));
var languages = new HashSet<Language>(new[] { Language.GetLanguage("de"), Language.GetLanguage("en") }); var languages = new HashSet<Language>(new[] { Language.DE, Language.EN });
var json = schema.BuildSchema(languages, (n, s) => s).ToJson(); var json = schema.BuildSchema(languages, (n, s) => s).ToJson();
@ -279,7 +279,7 @@ namespace Squidex.Core.Schemas
.AddOrUpdateField(new NumberField(4, "age", .AddOrUpdateField(new NumberField(4, "age",
new NumberFieldProperties { MinValue = 1, MaxValue = 10 })); new NumberFieldProperties { MinValue = 1, MaxValue = 10 }));
var languages = new HashSet<Language>(new[] { Language.GetLanguage("de"), Language.GetLanguage("en") }); var languages = new HashSet<Language>(new[] { Language.DE, Language.EN });
var edmModel = schema.BuildEdmType(languages, x => x); var edmModel = schema.BuildEdmType(languages, x => x);

137
tests/Squidex.Core.Tests/Schemas/SchemaValidationTests.cs

@ -17,7 +17,7 @@ namespace Squidex.Core.Schemas
{ {
public class SchemaValidationTests public class SchemaValidationTests
{ {
private readonly HashSet<Language> languages = new HashSet<Language>(new[] { Language.GetLanguage("de"), Language.GetLanguage("en") }); private readonly HashSet<Language> languages = new HashSet<Language>(new[] { Language.DE, Language.EN });
private readonly List<ValidationError> errors = new List<ValidationError>(); private readonly List<ValidationError> errors = new List<ValidationError>();
private Schema sut = Schema.Create("my-name", new SchemaProperties()); private Schema sut = Schema.Create("my-name", new SchemaProperties());
@ -59,7 +59,7 @@ namespace Squidex.Core.Schemas
} }
[Fact] [Fact]
public async Task Should_add_error_non_localizable_field_contains_language() public async Task Should_add_error_non_localizable_data_field_contains_language()
{ {
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties())); sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties()));
@ -98,7 +98,7 @@ namespace Squidex.Core.Schemas
} }
[Fact] [Fact]
public async Task Should_add_error_if_required_field_is_not_in_bag() public async Task Should_add_error_if_required_data_field_is_not_in_bag()
{ {
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsRequired = true })); sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsRequired = true }));
@ -115,7 +115,7 @@ namespace Squidex.Core.Schemas
} }
[Fact] [Fact]
public async Task Should_add_error_if_value_contains_invalid_language() public async Task Should_add_error_if_data_contains_invalid_language()
{ {
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsLocalizable = true })); sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsLocalizable = true }));
@ -136,7 +136,7 @@ namespace Squidex.Core.Schemas
} }
[Fact] [Fact]
public async Task Should_add_error_if_value_contains_unsupported_language() public async Task Should_add_error_if_data_contains_unsupported_language()
{ {
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsLocalizable = true })); sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsLocalizable = true }));
@ -156,5 +156,132 @@ namespace Squidex.Core.Schemas
new ValidationError("my-field has an unsupported language 'it'", "my-field") new ValidationError("my-field has an unsupported language 'it'", "my-field")
}); });
} }
[Fact]
public async Task Should_add_error_if_validating_partial_data_with_unknown_field()
{
var data =
new ContentData()
.AddField("unknown",
new ContentFieldData());
await sut.ValidatePartialAsync(data, errors, languages);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("unknown is not a known field", "unknown")
});
}
[Fact]
public async Task Should_add_error_if_validating_partial_data_with_invalid_field()
{
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { MaxValue = 100 }));
var data =
new ContentData()
.AddField("my-field",
new ContentFieldData()
.SetValue(1000));
await sut.ValidatePartialAsync(data, errors, languages);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field must be less than '100'", "my-field")
});
}
[Fact]
public async Task Should_add_error_non_localizable_partial_data_field_contains_language()
{
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties()));
var data =
new ContentData()
.AddField("my-field",
new ContentFieldData()
.AddValue("es", 1)
.AddValue("it", 1));
await sut.ValidatePartialAsync(data, errors, languages);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field can only contain a single entry for invariant language (iv)", "my-field")
});
}
[Fact]
public async Task Should_not_add_error_if_validating_partial_data_with_invalid_localizable_field()
{
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsRequired = true, IsLocalizable = true }));
var data =
new ContentData();
await sut.ValidatePartialAsync(data, errors, languages);
Assert.Equal(0, errors.Count);
}
[Fact]
public async Task Should_not_add_error_if_required_partial_data_field_is_not_in_bag()
{
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsRequired = true }));
var data =
new ContentData();
await sut.ValidatePartialAsync(data, errors, languages);
Assert.Equal(0, errors.Count);
}
[Fact]
public async Task Should_add_error_if_partial_data_contains_invalid_language()
{
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsLocalizable = true }));
var data =
new ContentData()
.AddField("my-field",
new ContentFieldData()
.AddValue("de", 1)
.AddValue("xx", 1));
await sut.ValidatePartialAsync(data, errors, languages);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field has an invalid language 'xx'", "my-field")
});
}
[Fact]
public async Task Should_add_error_if_partial_data_contains_unsupported_language()
{
sut = sut.AddOrUpdateField(new NumberField(1, "my-field", new NumberFieldProperties { IsLocalizable = true }));
var data =
new ContentData()
.AddField("my-field",
new ContentFieldData()
.AddValue("es", 1)
.AddValue("it", 1));
await sut.ValidatePartialAsync(data, errors, languages);
errors.ShouldBeEquivalentTo(
new List<ValidationError>
{
new ValidationError("my-field has an unsupported language 'es'", "my-field"),
new ValidationError("my-field has an unsupported language 'it'", "my-field")
});
}
} }
} }

4
tests/Squidex.Core.Tests/project.json

@ -12,7 +12,7 @@
"Moq": "4.6.38-alpha", "Moq": "4.6.38-alpha",
"Squidex.Core": "1.0.0-*", "Squidex.Core": "1.0.0-*",
"Squidex.Infrastructure": "1.0.0-*", "Squidex.Infrastructure": "1.0.0-*",
"xunit": "2.2.0-beta4-build3444" "xunit": "2.2.0-beta5-build3474"
}, },
"frameworks": { "frameworks": {
"netcoreapp1.1": { "netcoreapp1.1": {
@ -30,7 +30,7 @@
"defaultNamespace": "Squidex.Core" "defaultNamespace": "Squidex.Core"
}, },
"tools": { "tools": {
"Microsoft.DotNet.Watcher.Tools": "1.0.0-preview2-final" "Microsoft.DotNet.Watcher.Tools": "1.1.0-preview4-final"
}, },
"version": "1.0.0-*" "version": "1.0.0-*"
} }

34
tests/Squidex.Infrastructure.Tests/CollectionExtensionsTests.cs

@ -223,5 +223,39 @@ namespace Squidex.Infrastructure
Assert.False(lhs.EqualsDictionary(rhs)); Assert.False(lhs.EqualsDictionary(rhs));
} }
[Fact]
public void Dictionary_should_return_same_hashcode_for_equal_dictionaries()
{
var lhs = new Dictionary<int, int>
{
[1] = 1,
[2] = 2
};
var rhs = new Dictionary<int, int>
{
[1] = 1,
[2] = 2
};
Assert.Equal(lhs.DictionaryHashCode(), rhs.DictionaryHashCode());
}
[Fact]
public void Dictionary_should_return_different_hashcode_for_different_dictionaries()
{
var lhs = new Dictionary<int, int>
{
[1] = 1,
[2] = 2
};
var rhs = new Dictionary<int, int>
{
[1] = 1,
[3] = 3
};
Assert.NotEqual(lhs.DictionaryHashCode(), rhs.DictionaryHashCode());
}
} }
} }

27
tests/Squidex.Infrastructure.Tests/LanguageTests.cs

@ -63,7 +63,7 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public void Should_serialize_and_deserialize_valid_language() public void Should_serialize_and_deserialize_valid_language()
{ {
var input = Tuple.Create(Language.GetLanguage("de")); var input = Tuple.Create(Language.DE);
var json = JsonConvert.SerializeObject(input, serializerSettings); var json = JsonConvert.SerializeObject(input, serializerSettings);
var output = JsonConvert.DeserializeObject<Tuple<Language>>(json, serializerSettings); var output = JsonConvert.DeserializeObject<Tuple<Language>>(json, serializerSettings);
@ -82,5 +82,30 @@ namespace Squidex.Infrastructure
Assert.Equal(key, language.Iso2Code); Assert.Equal(key, language.Iso2Code);
Assert.Equal(englishName, language.EnglishName); Assert.Equal(englishName, language.EnglishName);
} }
[Theory]
[InlineData("iv", "iv")]
[InlineData("en", "en")]
[InlineData("EN", "en")]
[InlineData("en ", "en")]
public void Should_parse_valid_languages(string input, string languageCode)
{
var language = Language.TryParse(input);
Assert.Equal(language, Language.GetLanguage(languageCode));
}
[Theory]
[InlineData("")]
[InlineData(" ")]
[InlineData("xx")]
[InlineData("invalid")]
[InlineData(null)]
public void Should_parse_invalid_languages(string input)
{
var language = Language.TryParse(input);
Assert.Null(language);
}
} }
} }

4
tests/Squidex.Infrastructure.Tests/project.json

@ -10,7 +10,7 @@
"dotnet-test-xunit": "2.2.0-preview2-build1029", "dotnet-test-xunit": "2.2.0-preview2-build1029",
"Moq": "4.6.38-alpha", "Moq": "4.6.38-alpha",
"Squidex.Infrastructure": "1.0.0-*", "Squidex.Infrastructure": "1.0.0-*",
"xunit": "2.2.0-beta4-build3444" "xunit": "2.2.0-beta5-build3474"
}, },
"frameworks": { "frameworks": {
"netcoreapp1.1": { "netcoreapp1.1": {
@ -28,7 +28,7 @@
"defaultNamespace": "Squidex.Core.Tests" "defaultNamespace": "Squidex.Core.Tests"
}, },
"tools": { "tools": {
"Microsoft.DotNet.Watcher.Tools": "1.0.0-preview2-final" "Microsoft.DotNet.Watcher.Tools": "1.1.0-preview4-final"
}, },
"version": "1.0.0-*" "version": "1.0.0-*"
} }

4
tests/Squidex.Read.Tests/MongoDb/Contents/ODataQueryTests.cs

@ -38,8 +38,8 @@ namespace Squidex.Read.MongoDb.Contents
private readonly IBsonSerializer<MongoContentEntity> serializer = BsonSerializer.SerializerRegistry.GetSerializer<MongoContentEntity>(); private readonly IBsonSerializer<MongoContentEntity> serializer = BsonSerializer.SerializerRegistry.GetSerializer<MongoContentEntity>();
private readonly HashSet<Language> languages = new HashSet<Language> private readonly HashSet<Language> languages = new HashSet<Language>
{ {
Language.GetLanguage("en"), Language.EN,
Language.GetLanguage("de") Language.DE
}; };
[Fact] [Fact]

4
tests/Squidex.Read.Tests/project.json

@ -15,7 +15,7 @@
"Squidex.Infrastructure": "1.0.0-*", "Squidex.Infrastructure": "1.0.0-*",
"Squidex.Read": "1.0.0-*", "Squidex.Read": "1.0.0-*",
"Squidex.Read.MongoDb": "1.0.0-*", "Squidex.Read.MongoDb": "1.0.0-*",
"xunit": "2.2.0-beta4-build3444" "xunit": "2.2.0-beta5-build3474"
}, },
"frameworks": { "frameworks": {
"netcoreapp1.1": { "netcoreapp1.1": {
@ -33,7 +33,7 @@
"defaultNamespace": "Squidex.Read" "defaultNamespace": "Squidex.Read"
}, },
"tools": { "tools": {
"Microsoft.DotNet.Watcher.Tools": "1.0.0-preview2-final" "Microsoft.DotNet.Watcher.Tools": "1.1.0-preview4-final"
}, },
"version": "1.0.0-*" "version": "1.0.0-*"
} }

2
tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs

@ -33,7 +33,7 @@ namespace Squidex.Write.Apps
private readonly AppDomainObject app; private readonly AppDomainObject app;
private readonly RefToken subjectId = new RefToken("subject", Guid.NewGuid().ToString()); private readonly RefToken subjectId = new RefToken("subject", Guid.NewGuid().ToString());
private readonly DateTime expiresUtc = DateTime.UtcNow.AddYears(1); private readonly DateTime expiresUtc = DateTime.UtcNow.AddYears(1);
private readonly Language language = Language.GetLanguage("de"); private readonly Language language = Language.DE;
private readonly string contributorId = Guid.NewGuid().ToString(); private readonly string contributorId = Guid.NewGuid().ToString();
private readonly string clientSecret = Guid.NewGuid().ToString(); private readonly string clientSecret = Guid.NewGuid().ToString();
private readonly string clientName = "client"; private readonly string clientName = "client";

36
tests/Squidex.Write.Tests/Apps/AppDomainObjectTests.cs

@ -63,8 +63,8 @@ namespace Squidex.Write.Apps
{ {
new AppCreated { Name = TestName }, new AppCreated { Name = TestName },
new AppContributorAssigned { ContributorId = user.Identifier, Permission = PermissionLevel.Owner }, new AppContributorAssigned { ContributorId = user.Identifier, Permission = PermissionLevel.Owner },
new AppLanguageAdded { Language = Language.GetLanguage("en") }, new AppLanguageAdded { Language = Language.EN },
new AppMasterLanguageSet { Language = Language.GetLanguage("en") } new AppMasterLanguageSet { Language = Language.EN }
}); });
} }
@ -290,7 +290,7 @@ namespace Squidex.Write.Apps
[Fact] [Fact]
public void AddLanguage_should_throw_if_not_created() public void AddLanguage_should_throw_if_not_created()
{ {
Assert.Throws<DomainException>(() => sut.AddLanguage(new AddLanguage { Language = Language.GetLanguage("de") })); Assert.Throws<DomainException>(() => sut.AddLanguage(new AddLanguage { Language = Language.DE }));
} }
[Fact] [Fact]
@ -306,7 +306,7 @@ namespace Squidex.Write.Apps
{ {
CreateApp(); CreateApp();
Assert.Throws<ValidationException>(() => sut.AddLanguage(new AddLanguage { Language = Language.GetLanguage("en") })); Assert.Throws<ValidationException>(() => sut.AddLanguage(new AddLanguage { Language = Language.EN }));
} }
[Fact] [Fact]
@ -314,20 +314,20 @@ namespace Squidex.Write.Apps
{ {
CreateApp(); CreateApp();
sut.AddLanguage(new AddLanguage { Language = Language.GetLanguage("de") }); sut.AddLanguage(new AddLanguage { Language = Language.DE });
sut.GetUncomittedEvents().Select(x => x.Payload).ToArray() sut.GetUncomittedEvents().Select(x => x.Payload).ToArray()
.ShouldBeEquivalentTo( .ShouldBeEquivalentTo(
new IEvent[] new IEvent[]
{ {
new AppLanguageAdded { Language = Language.GetLanguage("de") } new AppLanguageAdded { Language = Language.DE }
}); });
} }
[Fact] [Fact]
public void RemoveLanguage_should_throw_if_not_created() public void RemoveLanguage_should_throw_if_not_created()
{ {
Assert.Throws<DomainException>(() => sut.RemoveLanguage(new RemoveLanguage { Language = Language.GetLanguage("en") })); Assert.Throws<DomainException>(() => sut.RemoveLanguage(new RemoveLanguage { Language = Language.EN }));
} }
[Fact] [Fact]
@ -343,7 +343,7 @@ namespace Squidex.Write.Apps
{ {
CreateApp(); CreateApp();
Assert.Throws<DomainObjectNotFoundException>(() => sut.RemoveLanguage(new RemoveLanguage { Language = Language.GetLanguage("de") })); Assert.Throws<DomainObjectNotFoundException>(() => sut.RemoveLanguage(new RemoveLanguage { Language = Language.DE }));
} }
[Fact] [Fact]
@ -351,7 +351,7 @@ namespace Squidex.Write.Apps
{ {
CreateApp(); CreateApp();
Assert.Throws<ValidationException>(() => sut.RemoveLanguage(new RemoveLanguage { Language = Language.GetLanguage("en") })); Assert.Throws<ValidationException>(() => sut.RemoveLanguage(new RemoveLanguage { Language = Language.EN }));
} }
[Fact] [Fact]
@ -359,21 +359,21 @@ namespace Squidex.Write.Apps
{ {
CreateApp(); CreateApp();
sut.AddLanguage(new AddLanguage { Language = Language.GetLanguage("de") }); sut.AddLanguage(new AddLanguage { Language = Language.DE });
sut.RemoveLanguage(new RemoveLanguage { Language = Language.GetLanguage("de") }); sut.RemoveLanguage(new RemoveLanguage { Language = Language.DE });
sut.GetUncomittedEvents().Select(x => x.Payload).Skip(1).ToArray() sut.GetUncomittedEvents().Select(x => x.Payload).Skip(1).ToArray()
.ShouldBeEquivalentTo( .ShouldBeEquivalentTo(
new IEvent[] new IEvent[]
{ {
new AppLanguageRemoved { Language = Language.GetLanguage("de") } new AppLanguageRemoved { Language = Language.DE }
}); });
} }
[Fact] [Fact]
public void SetMasterLanguage_should_throw_if_not_created() public void SetMasterLanguage_should_throw_if_not_created()
{ {
Assert.Throws<DomainException>(() => sut.SetMasterLanguage(new SetMasterLanguage { Language = Language.GetLanguage("en") })); Assert.Throws<DomainException>(() => sut.SetMasterLanguage(new SetMasterLanguage { Language = Language.EN }));
} }
[Fact] [Fact]
@ -389,7 +389,7 @@ namespace Squidex.Write.Apps
{ {
CreateApp(); CreateApp();
Assert.Throws<DomainObjectNotFoundException>(() => sut.SetMasterLanguage(new SetMasterLanguage { Language = Language.GetLanguage("de") })); Assert.Throws<DomainObjectNotFoundException>(() => sut.SetMasterLanguage(new SetMasterLanguage { Language = Language.DE }));
} }
[Fact] [Fact]
@ -397,7 +397,7 @@ namespace Squidex.Write.Apps
{ {
CreateApp(); CreateApp();
Assert.Throws<ValidationException>(() => sut.SetMasterLanguage(new SetMasterLanguage { Language = Language.GetLanguage("en") })); Assert.Throws<ValidationException>(() => sut.SetMasterLanguage(new SetMasterLanguage { Language = Language.EN }));
} }
[Fact] [Fact]
@ -405,14 +405,14 @@ namespace Squidex.Write.Apps
{ {
CreateApp(); CreateApp();
sut.AddLanguage(new AddLanguage { Language = Language.GetLanguage("de") }); sut.AddLanguage(new AddLanguage { Language = Language.DE });
sut.SetMasterLanguage(new SetMasterLanguage { Language = Language.GetLanguage("de") }); sut.SetMasterLanguage(new SetMasterLanguage { Language = Language.DE });
sut.GetUncomittedEvents().Select(x => x.Payload).Skip(1).ToArray() sut.GetUncomittedEvents().Select(x => x.Payload).Skip(1).ToArray()
.ShouldBeEquivalentTo( .ShouldBeEquivalentTo(
new IEvent[] new IEvent[]
{ {
new AppMasterLanguageSet { Language = Language.GetLanguage("de") } new AppMasterLanguageSet { Language = Language.DE }
}); });
} }

30
tests/Squidex.Write.Tests/Contents/ContentCommandHandlerTests.cs

@ -47,7 +47,7 @@ namespace Squidex.Write.Contents
sut = new ContentCommandHandler(Handler, appProvider.Object, schemaProvider.Object); sut = new ContentCommandHandler(Handler, appProvider.Object, schemaProvider.Object);
appEntity.Setup(x => x.Languages).Returns(new[] { Language.GetLanguage("de") }); appEntity.Setup(x => x.Languages).Returns(new[] { Language.DE });
appProvider.Setup(x => x.FindAppByIdAsync(appId)).Returns(Task.FromResult(appEntity.Object)); appProvider.Setup(x => x.FindAppByIdAsync(appId)).Returns(Task.FromResult(appEntity.Object));
schemaEntity.Setup(x => x.Schema).Returns(schema); schemaEntity.Setup(x => x.Schema).Returns(schema);
@ -108,6 +108,34 @@ namespace Squidex.Write.Contents
}); });
} }
[Fact]
public async Task Patch_should_throw_exception_if_data_is_not_valid()
{
CreateContent();
var command = new PatchContent { AggregateId = Id, AppId = appId, SchemaId = schemaId, Data = new ContentData() };
var context = new CommandContext(command);
await TestUpdate(content, async _ =>
{
await Assert.ThrowsAsync<ValidationException>(() => sut.HandleAsync(context));
}, false);
}
[Fact]
public async Task Patch_should_update_domain_object()
{
CreateContent();
var command = new PatchContent { AggregateId = Id, AppId = appId, SchemaId = schemaId, Data = data };
var context = new CommandContext(command);
await TestUpdate(content, async _ =>
{
await sut.HandleAsync(context);
});
}
[Fact] [Fact]
public async Task Publish_should_publish_domain_object() public async Task Publish_should_publish_domain_object()
{ {

82
tests/Squidex.Write.Tests/Contents/ContentDomainObjectTests.cs

@ -25,7 +25,16 @@ namespace Squidex.Write.Contents
{ {
private readonly Guid appId = Guid.NewGuid(); private readonly Guid appId = Guid.NewGuid();
private readonly ContentDomainObject sut; private readonly ContentDomainObject sut;
private readonly ContentData data = new ContentData(); private readonly ContentData data =
new ContentData()
.AddField("field1",
new ContentFieldData()
.AddValue("iv", 1));
private readonly ContentData otherData =
new ContentData()
.AddField("field2",
new ContentFieldData()
.AddValue("iv", 2));
public ContentDomainObjectTests() public ContentDomainObjectTests()
{ {
@ -86,17 +95,79 @@ namespace Squidex.Write.Contents
public void Update_should_create_events() public void Update_should_create_events()
{ {
CreateContent(); CreateContent();
UpdateContent();
sut.Update(new UpdateContent { Data = otherData });
sut.GetUncomittedEvents().Select(x => x.Payload).ToArray()
.ShouldBeEquivalentTo(
new IEvent[]
{
new ContentUpdated { Data = otherData }
});
}
[Fact]
public void Update_should_not_create_event_for_same_data()
{
CreateContent();
UpdateContent();
sut.Update(new UpdateContent { Data = data }); sut.Update(new UpdateContent { Data = data });
sut.GetUncomittedEvents().Select(x => x.Payload).ToArray().ShouldBeEquivalentTo(new IEvent[0]);
}
[Fact]
public void Patch_should_throw_if_not_created()
{
Assert.Throws<DomainException>(() => sut.Patch(new PatchContent { Data = data }));
}
[Fact]
public void Patch_should_throw_if_schema_is_deleted()
{
CreateContent();
DeleteContent();
Assert.Throws<ValidationException>(() => sut.Patch(new PatchContent()));
}
[Fact]
public void Patch_should_throw_if_command_is_not_valid()
{
CreateContent();
Assert.Throws<ValidationException>(() => sut.Patch(new PatchContent()));
}
[Fact]
public void Patch_should_create_events()
{
CreateContent();
UpdateContent();
sut.Patch(new PatchContent { Data = otherData });
sut.GetUncomittedEvents().Select(x => x.Payload).ToArray() sut.GetUncomittedEvents().Select(x => x.Payload).ToArray()
.ShouldBeEquivalentTo( .ShouldBeEquivalentTo(
new IEvent[] new IEvent[]
{ {
new ContentUpdated { Data = data } new ContentUpdated { Data = data.MergeInto(otherData) }
}); });
} }
[Fact]
public void Patch_should_not_create_event_for_same_data()
{
CreateContent();
UpdateContent();
sut.Patch(new PatchContent { Data = data });
sut.GetUncomittedEvents().Select(x => x.Payload).ToArray().Should().BeEmpty();
}
[Fact] [Fact]
public void Publish_should_throw_if_not_created() public void Publish_should_throw_if_not_created()
{ {
@ -201,6 +272,13 @@ namespace Squidex.Write.Contents
((IAggregate)sut).ClearUncommittedEvents(); ((IAggregate)sut).ClearUncommittedEvents();
} }
private void UpdateContent()
{
sut.Update(new UpdateContent { Data = data, AppId = appId });
((IAggregate)sut).ClearUncommittedEvents();
}
private void PublishContent() private void PublishContent()
{ {
sut.Publish(new PublishContent()); sut.Publish(new PublishContent());

4
tests/Squidex.Write.Tests/project.json

@ -13,7 +13,7 @@
"Squidex.Core": "1.0.0-*", "Squidex.Core": "1.0.0-*",
"Squidex.Infrastructure": "1.0.0-*", "Squidex.Infrastructure": "1.0.0-*",
"Squidex.Write": "1.0.0-*", "Squidex.Write": "1.0.0-*",
"xunit": "2.2.0-beta4-build3444" "xunit": "2.2.0-beta5-build3474"
}, },
"frameworks": { "frameworks": {
"netcoreapp1.1": { "netcoreapp1.1": {
@ -31,7 +31,7 @@
"defaultNamespace": "Squidex.Write" "defaultNamespace": "Squidex.Write"
}, },
"tools": { "tools": {
"Microsoft.DotNet.Watcher.Tools": "1.0.0-preview2-final" "Microsoft.DotNet.Watcher.Tools": "1.1.0-preview4-final"
}, },
"version": "1.0.0-*" "version": "1.0.0-*"
} }

66
tools/GenerateLanguages/Program.cs

@ -0,0 +1,66 @@
// ==========================================================================
// Program.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
var languageCodesFile = new FileInfo("../../src/Squidex.Infrastructure/language-codes.csv");
var languageFile = Path.Combine(languageCodesFile.DirectoryName, "Languages.cs");
var resourceStream = new FileStream(languageCodesFile.FullName, FileMode.Open);
var writer = new StringWriter();
writer.WriteLine("// ==========================================================================");
writer.WriteLine("// Langauges.cs");
writer.WriteLine("// Squidex Headless CMS");
writer.WriteLine("// ==========================================================================");
writer.WriteLine("// Copyright (c) Squidex Group");
writer.WriteLine("// All rights reserved.");
writer.WriteLine("// ==========================================================================");
writer.WriteLine();
writer.WriteLine("namespace Squidex.Infrastructure");
writer.WriteLine("{");
writer.WriteLine(" partial class Language");
writer.WriteLine(" {");
var uniqueCodes = new HashSet<string>(new [] { "iv" });
using (var reader = new StreamReader(resourceStream, Encoding.UTF8))
{
reader.ReadLine();
while (!reader.EndOfStream)
{
var languageLine = reader.ReadLine();
var languageIso2Code = languageLine.Substring(1, 2);
var languageEnglishName = languageLine.Substring(6, languageLine.Length - 7);
if (!uniqueCodes.Add(languageIso2Code))
{
Console.WriteLine("Languages contains duplicate {0}", languageIso2Code);
}
writer.WriteLine(" public static Language {0} = AddLanguage(\"{1}\", \"{2}\");", languageIso2Code.ToUpperInvariant(), languageIso2Code, languageEnglishName);
}
}
writer.WriteLine(" }");
writer.WriteLine("}");
File.WriteAllText(languageFile, writer.ToString());
}
}
}

19
tools/GenerateLanguages/project.json

@ -0,0 +1,19 @@
{
"version": "1.0.0-*",
"buildOptions": {
"debugType": "portable",
"emitEntryPoint": true
},
"dependencies": {},
"frameworks": {
"netcoreapp1.1": {
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.1.0"
}
},
"imports": "dnxcore50"
}
}
}
Loading…
Cancel
Save