Merge remote-tracking branch 'origin/dev-v7' into dev-v7.7

# Conflicts:
#	src/Umbraco.Core/UdiEntityType.cs
This commit is contained in:
Sebastiaan Janssen
2017-09-13 17:34:35 +02:00
9 changed files with 389 additions and 126 deletions

View File

@@ -32,7 +32,11 @@ namespace Umbraco.Core
public GuidUdi(Uri uriValue)
: base(uriValue)
{
Guid = Guid.Parse(uriValue.AbsolutePath.TrimStart('/'));
Guid guid;
if (Guid.TryParse(uriValue.AbsolutePath.TrimStart('/'), out guid) == false)
throw new FormatException("Url \"" + uriValue + "\" is not a guid entity id.");
Guid = guid;
}
/// <summary>
@@ -43,16 +47,17 @@ namespace Umbraco.Core
public new static GuidUdi Parse(string s)
{
var udi = Udi.Parse(s);
if (!(udi is GuidUdi))
if (udi is GuidUdi == false)
throw new FormatException("String \"" + s + "\" is not a guid entity id.");
return (GuidUdi)udi;
return (GuidUdi) udi;
}
public static bool TryParse(string s, out GuidUdi udi)
{
Udi tmp;
udi = null;
if (!TryParse(s, out tmp)) return false;
if (TryParse(s, out tmp) == false) return false;
udi = tmp as GuidUdi;
return udi != null;
}
@@ -75,10 +80,9 @@ namespace Umbraco.Core
get { return Guid == Guid.Empty; }
}
/// <inheritdoc/>
public GuidUdi EnsureClosed()
{
base.EnsureNotRoot();
EnsureNotRoot();
return this;
}
}

View File

@@ -0,0 +1,26 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Umbraco.Core.Serialization
{
public class KnownTypeUdiJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Udi).IsAssignableFrom(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jo = JToken.ReadFrom(reader);
var val = jo.ToObject<string>();
return val == null ? null : Udi.Parse(val, true);
}
}
}

View File

@@ -4,12 +4,11 @@ using Newtonsoft.Json.Linq;
namespace Umbraco.Core.Serialization
{
public class UdiJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Udi).IsAssignableFrom(objectType);
return typeof (Udi).IsAssignableFrom(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)

View File

@@ -57,17 +57,18 @@ namespace Umbraco.Core
public new static StringUdi Parse(string s)
{
var udi = Udi.Parse(s);
if (!(udi is StringUdi))
if (udi is StringUdi == false)
throw new FormatException("String \"" + s + "\" is not a string entity id.");
return (StringUdi)udi;
return (StringUdi) udi;
}
public static bool TryParse(string s, out StringUdi udi)
{
udi = null;
Udi tmp;
if (!TryParse(s, out tmp) || !(tmp is StringUdi)) return false;
udi = (StringUdi)tmp;
if (TryParse(s, out tmp) == false || tmp is StringUdi == false) return false;
udi = (StringUdi) tmp;
return true;
}
@@ -77,10 +78,9 @@ namespace Umbraco.Core
get { return Id == string.Empty; }
}
/// <inheritdoc/>
public StringUdi EnsureClosed()
{
base.EnsureNotRoot();
EnsureNotRoot();
return this;
}
}

View File

@@ -3,7 +3,6 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using Umbraco.Core.Deploy;
namespace Umbraco.Core
@@ -15,7 +14,15 @@ namespace Umbraco.Core
[TypeConverter(typeof(UdiTypeConverter))]
public abstract class Udi : IComparable<Udi>
{
private static readonly Lazy<Dictionary<string, UdiType>> UdiTypes;
// notes - see U4-10409
// if this class is used during application pre-start it cannot scans the assemblies,
// this is addressed by lazily-scanning, with the following caveats:
// - parsing a root udi still requires a scan and therefore still breaks
// - parsing an invalid udi ("umb://should-be-guid/<not-a-guid>") corrupts KnowUdiTypes
private static volatile bool _scanned;
private static readonly object ScanLocker = new object();
private static ConcurrentDictionary<string, UdiType> _udiTypes;
private static readonly ConcurrentDictionary<string, Udi> RootUdis = new ConcurrentDictionary<string, Udi>();
internal readonly Uri UriValue; // internal for UdiRange
@@ -42,50 +49,16 @@ namespace Umbraco.Core
static Udi()
{
UdiTypes = new Lazy<Dictionary<string, UdiType>>(() =>
{
var result = new Dictionary<string, UdiType>();
// known types:
foreach (var fi in typeof(Constants.UdiEntityType).GetFields(BindingFlags.Public | BindingFlags.Static))
{
// IsLiteral determines if its value is written at
// compile time and not changeable
// IsInitOnly determine if the field can be set
// in the body of the constructor
// for C# a field which is readonly keyword would have both true
// but a const field would have only IsLiteral equal to true
if (fi.IsLiteral && fi.IsInitOnly == false)
{
var udiType = fi.GetCustomAttribute<Constants.UdiTypeAttribute>();
// initialize with known (built-in) Udi types
// we will add scanned types later on
_udiTypes = new ConcurrentDictionary<string, UdiType>(Constants.UdiEntityType.GetTypes());
}
if (udiType == null)
throw new InvalidOperationException("All Constants listed in UdiEntityType must be attributed with " + typeof(Constants.UdiTypeAttribute));
result[fi.GetValue(null).ToString()] = udiType.UdiType;
}
}
// Scan for unknown UDI types
// there is no way we can get the "registered" service connectors, as registration
// happens in Deploy, not in Core, and the Udi class belongs to Core - therefore, we
// just pick every service connectors - just making sure that not two of them
// would register the same entity type, with different udi types (would not make
// much sense anyways).
var connectors = PluginManager.Current.ResolveTypes<IServiceConnector>();
foreach (var connector in connectors)
{
var attrs = connector.GetCustomAttributes<UdiDefinitionAttribute>(false);
foreach (var attr in attrs)
{
UdiType udiType;
if (result.TryGetValue(attr.EntityType, out udiType) && udiType != attr.UdiType)
throw new Exception(string.Format("Entity type \"{0}\" is declared by more than one IServiceConnector, with different UdiTypes.", attr.EntityType));
result[attr.EntityType] = attr.UdiType;
}
}
return result;
});
// for tests, totally unsafe
internal static void ResetUdiTypes()
{
_udiTypes = new ConcurrentDictionary<string, UdiType>(Constants.UdiEntityType.GetTypes());
_scanned = false;
}
/// <summary>
@@ -113,17 +86,65 @@ namespace Umbraco.Core
public static Udi Parse(string s)
{
Udi udi;
ParseInternal(s, false, out udi);
ParseInternal(s, false, false, out udi);
return udi;
}
public static bool TryParse(string s, out Udi udi)
/// <summary>
/// Converts the string representation of an entity identifier into the equivalent Udi instance.
/// </summary>
/// <param name="s">The string to convert.</param>
/// <param name="knownTypes">A value indicating whether to only deal with known types.</param>
/// <returns>An Udi instance that contains the value that was parsed.</returns>
/// <remarks>
/// <para>If <paramref name="knownTypes"/> is <c>true</c>, and the string could not be parsed because
/// the entity type was not known, the method succeeds but sets <c>udi</c>to an
/// <see cref="UnknownTypeUdi"/> value.</para>
/// <para>If <paramref name="knownTypes"/> is <c>true</c>, assemblies are not scanned for types,
/// and therefore only builtin types may be known. Unless scanning already took place.</para>
/// </remarks>
public static Udi Parse(string s, bool knownTypes)
{
return ParseInternal(s, true, out udi);
Udi udi;
ParseInternal(s, false, knownTypes, out udi);
return udi;
}
private static bool ParseInternal(string s, bool tryParse, out Udi udi)
/// <summary>
/// Converts the string representation of an entity identifier into the equivalent Udi instance.
/// </summary>
/// <param name="s">The string to convert.</param>
/// <param name="udi">An Udi instance that contains the value that was parsed.</param>
/// <returns>A boolean value indicating whether the string could be parsed.</returns>
public static bool TryParse(string s, out Udi udi)
{
return ParseInternal(s, true, false, out udi);
}
/// <summary>
/// Converts the string representation of an entity identifier into the equivalent Udi instance.
/// </summary>
/// <param name="s">The string to convert.</param>
/// <param name="knownTypes">A value indicating whether to only deal with known types.</param>
/// <param name="udi">An Udi instance that contains the value that was parsed.</param>
/// <returns>A boolean value indicating whether the string could be parsed.</returns>
/// <remarks>
/// <para>If <paramref name="knownTypes"/> is <c>true</c>, and the string could not be parsed because
/// the entity type was not known, the method returns <c>false</c> but still sets <c>udi</c>
/// to an <see cref="UnknownTypeUdi"/> value.</para>
/// <para>If <paramref name="knownTypes"/> is <c>true</c>, assemblies are not scanned for types,
/// and therefore only builtin types may be known. Unless scanning already took place.</para>
/// </remarks>
public static bool TryParse(string s, bool knownTypes, out Udi udi)
{
return ParseInternal(s, true, knownTypes, out udi);
}
private static bool ParseInternal(string s, bool tryParse, bool knownTypes, out Udi udi)
{
if (knownTypes == false)
EnsureScanForUdiTypes();
udi = null;
Uri uri;
@@ -136,12 +157,21 @@ namespace Umbraco.Core
var entityType = uri.Host;
UdiType udiType;
if (UdiTypes.Value.TryGetValue(entityType, out udiType) == false)
if (_udiTypes.TryGetValue(entityType, out udiType) == false)
{
if (knownTypes)
{
// not knowing the type is not an error
// just return the unknown type udi
udi = UnknownTypeUdi.Instance;
return false;
}
if (tryParse) return false;
throw new FormatException(string.Format("Unknown entity type \"{0}\".", entityType));
}
var path = uri.AbsolutePath.TrimStart('/');
if (udiType == UdiType.GuidUdi)
{
if (path == string.Empty)
@@ -158,21 +188,25 @@ namespace Umbraco.Core
udi = new GuidUdi(uri.Host, guid);
return true;
}
if (udiType == UdiType.StringUdi)
{
udi = path == string.Empty ? GetRootUdi(uri.Host) : new StringUdi(uri.Host, Uri.UnescapeDataString(path));
return true;
}
if (tryParse) return false;
throw new InvalidOperationException("Internal error.");
throw new InvalidOperationException(string.Format("Invalid udi type \"{0}\".", udiType));
}
private static Udi GetRootUdi(string entityType)
{
EnsureScanForUdiTypes();
return RootUdis.GetOrAdd(entityType, x =>
{
UdiType udiType;
if (UdiTypes.Value.TryGetValue(x, out udiType) == false)
if (_udiTypes.TryGetValue(x, out udiType) == false)
throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType));
return udiType == UdiType.StringUdi
? (Udi)new StringUdi(entityType, string.Empty)
@@ -180,6 +214,46 @@ namespace Umbraco.Core
});
}
/// <summary>
/// When required scan assemblies for known UDI types based on <see cref="IServiceConnector"/> instances
/// </summary>
/// <remarks>
/// This is only required when needing to resolve root udis
/// </remarks>
private static void EnsureScanForUdiTypes()
{
if (_scanned) return;
lock (ScanLocker)
{
// Scan for unknown UDI types
// there is no way we can get the "registered" service connectors, as registration
// happens in Deploy, not in Core, and the Udi class belongs to Core - therefore, we
// just pick every service connectors - just making sure that not two of them
// would register the same entity type, with different udi types (would not make
// much sense anyways).
var connectors = PluginManager.Current.ResolveTypes<IServiceConnector>();
var result = new Dictionary<string, UdiType>();
foreach (var connector in connectors)
{
var attrs = connector.GetCustomAttributes<UdiDefinitionAttribute>(false);
foreach (var attr in attrs)
{
UdiType udiType;
if (result.TryGetValue(attr.EntityType, out udiType) && udiType != attr.UdiType)
throw new Exception(string.Format("Entity type \"{0}\" is declared by more than one IServiceConnector, with different UdiTypes.", attr.EntityType));
result[attr.EntityType] = attr.UdiType;
}
}
// merge these into the known list
foreach (var item in result)
_udiTypes.TryAdd(item.Key, item.Value);
_scanned = true;
}
}
/// <summary>
/// Creates a root Udi for an entity type.
/// </summary>
@@ -199,13 +273,14 @@ namespace Umbraco.Core
public static Udi Create(string entityType, string id)
{
UdiType udiType;
if (UdiTypes.Value.TryGetValue(entityType, out udiType) == false)
if (_udiTypes.TryGetValue(entityType, out udiType) == false)
throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType), "entityType");
if (string.IsNullOrWhiteSpace(id))
throw new ArgumentException("Value cannot be null or whitespace.", "id");
if (udiType != UdiType.StringUdi)
throw new InvalidOperationException(string.Format("Entity type \"{0}\" does not have string udis.", entityType));
return new StringUdi(entityType, id);
}
@@ -218,24 +293,31 @@ namespace Umbraco.Core
public static Udi Create(string entityType, Guid id)
{
UdiType udiType;
if (UdiTypes.Value.TryGetValue(entityType, out udiType) == false)
if (_udiTypes.TryGetValue(entityType, out udiType) == false)
throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType), "entityType");
if (udiType != UdiType.GuidUdi)
throw new InvalidOperationException(string.Format("Entity type \"{0}\" does not have guid udis.", entityType));
throw new InvalidOperationException(string.Format("Entity type \"{0}\" does not have guid udis.", entityType));
if (id == default(Guid))
throw new ArgumentException("Cannot be an empty guid.", "id");
return new GuidUdi(entityType, id);
}
internal static Udi Create(Uri uri)
{
// if it's a know type go fast and use ctors
// else fallback to parsing the string (and guess the type)
UdiType udiType;
if (UdiTypes.Value.TryGetValue(uri.Host, out udiType) == false)
if (_udiTypes.TryGetValue(uri.Host, out udiType) == false)
throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", uri.Host), "uri");
if (udiType == UdiType.GuidUdi)
return new GuidUdi(uri);
if (udiType == UdiType.GuidUdi)
return new StringUdi(uri);
throw new ArgumentException(string.Format("Uri \"{0}\" is not a valid udi.", uri));
}
@@ -284,6 +366,19 @@ namespace Umbraco.Core
{
return (udi1 == udi2) == false;
}
}
private class UnknownTypeUdi : Udi
{
private UnknownTypeUdi()
: base("unknown", "umb://unknown/")
{ }
public static readonly UnknownTypeUdi Instance = new UnknownTypeUdi();
public override bool IsRoot
{
get { return false; }
}
}
}
}

View File

@@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using Umbraco.Core.Models;
namespace Umbraco.Core
{
public static partial class Constants
{
/// <summary>
@@ -13,87 +13,100 @@ namespace Umbraco.Core
/// but entity types are strings and so can be extended beyond what is defined here.</remarks>
public static class UdiEntityType
{
[UdiType(UdiType.Unknown)]
// note: const fields in this class MUST be consistent with what GetTypes returns
// this is validated by UdiTests.ValidateUdiEntityType
internal static Dictionary<string, UdiType> GetTypes()
{
return new Dictionary<string,UdiType>
{
{ Unknown, UdiType.Unknown },
{ AnyGuid, UdiType.GuidUdi },
{ Document, UdiType.GuidUdi },
{ DocumentBluePrint, UdiType.GuidUdi },
{ Media, UdiType.GuidUdi },
{ Member, UdiType.GuidUdi },
{ DictionaryItem, UdiType.GuidUdi },
{ Macro, UdiType.GuidUdi },
{ Template, UdiType.GuidUdi },
{ DocumentType, UdiType.GuidUdi },
{ DocumentTypeContainer, UdiType.GuidUdi },
{ DocumentTypeBluePrints, UdiType.GuidUdi },
{ MediaType, UdiType.GuidUdi },
{ MediaTypeContainer, UdiType.GuidUdi },
{ DataType, UdiType.GuidUdi },
{ DataTypeContainer, UdiType.GuidUdi },
{ MemberType, UdiType.GuidUdi },
{ MemberGroup, UdiType.GuidUdi },
{ RelationType, UdiType.GuidUdi },
{ FormsForm, UdiType.GuidUdi },
{ FormsPreValue, UdiType.GuidUdi },
{ FormsDataSource, UdiType.GuidUdi },
{ AnyString, UdiType.StringUdi},
{ Language, UdiType.StringUdi},
{ MacroScript, UdiType.StringUdi},
{ MediaFile, UdiType.StringUdi},
{ TemplateFile, UdiType.StringUdi},
{ Script, UdiType.StringUdi},
{ PartialView, UdiType.StringUdi},
{ PartialViewMacro, UdiType.StringUdi},
{ Stylesheet, UdiType.StringUdi},
{ UserControl, UdiType.StringUdi},
{ Xslt, UdiType.StringUdi},
};
}
public const string Unknown = "unknown";
// guid entity types
[UdiType(UdiType.GuidUdi)]
public const string AnyGuid = "any-guid"; // that one is for tests
[UdiType(UdiType.GuidUdi)]
public const string Document = "document";
[UdiType(UdiType.GuidUdi)]
public const string DocumentBluePrint = "document-blueprint";
[UdiType(UdiType.GuidUdi)]
public const string Media = "media";
[UdiType(UdiType.GuidUdi)]
public const string Member = "member";
[UdiType(UdiType.GuidUdi)]
public const string DictionaryItem = "dictionary-item";
[UdiType(UdiType.GuidUdi)]
public const string Macro = "macro";
[UdiType(UdiType.GuidUdi)]
public const string Template = "template";
[UdiType(UdiType.GuidUdi)]
public const string DocumentType = "document-type";
[UdiType(UdiType.GuidUdi)]
public const string DocumentTypeContainer = "document-type-container";
[UdiType(UdiType.GuidUdi)]
public const string DocumentTypeBluePrints = "document-type-blueprints";
[UdiType(UdiType.GuidUdi)]
public const string MediaType = "media-type";
[UdiType(UdiType.GuidUdi)]
public const string MediaTypeContainer = "media-type-container";
[UdiType(UdiType.GuidUdi)]
public const string DataType = "data-type";
[UdiType(UdiType.GuidUdi)]
public const string DataTypeContainer = "data-type-container";
[UdiType(UdiType.GuidUdi)]
public const string MemberType = "member-type";
[UdiType(UdiType.GuidUdi)]
public const string MemberGroup = "member-group";
[UdiType(UdiType.GuidUdi)]
public const string RelationType = "relation-type";
// forms
[UdiType(UdiType.GuidUdi)]
public const string FormsForm = "forms-form";
[UdiType(UdiType.GuidUdi)]
public const string FormsPreValue = "forms-prevalue";
[UdiType(UdiType.GuidUdi)]
public const string FormsDataSource = "forms-datasource";
// string entity types
[UdiType(UdiType.StringUdi)]
public const string AnyString = "any-string"; // that one is for tests
[UdiType(UdiType.StringUdi)]
public const string Language = "language";
[UdiType(UdiType.StringUdi)]
public const string MacroScript = "macroscript";
[UdiType(UdiType.StringUdi)]
public const string MediaFile = "media-file";
[UdiType(UdiType.StringUdi)]
public const string TemplateFile = "template-file";
[UdiType(UdiType.StringUdi)]
public const string Script = "script";
[UdiType(UdiType.StringUdi)]
public const string Stylesheet = "stylesheet";
[UdiType(UdiType.StringUdi)]
public const string PartialView = "partial-view";
[UdiType(UdiType.StringUdi)]
public const string PartialViewMacro = "partial-view-macro";
[UdiType(UdiType.StringUdi)]
public const string UserControl = "usercontrol";
[UdiType(UdiType.StringUdi)]
public const string Xslt = "xslt";
public static string FromUmbracoObjectType(UmbracoObjectTypes umbracoObjectType)
@@ -185,16 +198,5 @@ namespace Umbraco.Core
string.Format("EntityType \"{0}\" does not have a matching UmbracoObjectType.", entityType));
}
}
[AttributeUsage(AttributeTargets.Field)]
internal class UdiTypeAttribute : Attribute
{
public UdiType UdiType { get; private set; }
public UdiTypeAttribute(UdiType udiType)
{
UdiType = udiType;
}
}
}
}

View File

@@ -62,8 +62,8 @@ namespace Umbraco.Core
{
Uri uri;
if (!Uri.IsWellFormedUriString(s, UriKind.Absolute)
|| !Uri.TryCreate(s, UriKind.Absolute, out uri))
if (Uri.IsWellFormedUriString(s, UriKind.Absolute) == false
|| Uri.TryCreate(s, UriKind.Absolute, out uri) == false)
{
//if (tryParse) return false;
throw new FormatException(string.Format("String \"{0}\" is not a valid udi range.", s));

View File

@@ -700,6 +700,7 @@
<Compile Include="Security\UmbracoEmailMessage.cs" />
<Compile Include="Security\UserAwareMembershipProviderPasswordHasher.cs" />
<Compile Include="SemVersionExtensions.cs" />
<Compile Include="Serialization\KnownTypeUdiJsonConverter.cs" />
<Compile Include="Serialization\NoTypeConverterJsonConverter.cs" />
<Compile Include="Serialization\StreamResultExtensions.cs" />
<Compile Include="Serialization\UdiJsonConverter.cs" />

View File

@@ -1,8 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Deploy;
using Umbraco.Core.Serialization;
namespace Umbraco.Tests
@@ -11,7 +14,7 @@ namespace Umbraco.Tests
public class UdiTests
{
[Test]
public void StringEntityCtorTest()
public void StringUdiCtorTest()
{
var udi = new StringUdi(Constants.UdiEntityType.AnyString, "test-id");
Assert.AreEqual(Constants.UdiEntityType.AnyString, udi.EntityType);
@@ -20,7 +23,7 @@ namespace Umbraco.Tests
}
[Test]
public void StringEntityParseTest()
public void StringUdiParseTest()
{
var udi = Udi.Parse("umb://" + Constants.UdiEntityType.AnyString + "/test-id");
Assert.AreEqual(Constants.UdiEntityType.AnyString, udi.EntityType);
@@ -29,6 +32,9 @@ namespace Umbraco.Tests
Assert.IsNotNull(stringEntityId);
Assert.AreEqual("test-id", stringEntityId.Id);
Assert.AreEqual("umb://" + Constants.UdiEntityType.AnyString + "/test-id", udi.ToString());
udi = Udi.Parse("umb://" + Constants.UdiEntityType.AnyString + "/DA845952BE474EE9BD6F6194272AC750");
Assert.IsInstanceOf<StringUdi>(udi);
}
[Test]
@@ -83,7 +89,7 @@ namespace Umbraco.Tests
}
[Test]
public void GuidEntityCtorTest()
public void GuidUdiCtorTest()
{
var guid = Guid.NewGuid();
var udi = new GuidUdi(Constants.UdiEntityType.AnyGuid, guid);
@@ -93,7 +99,7 @@ namespace Umbraco.Tests
}
[Test]
public void GuidEntityParseTest()
public void GuidUdiParseTest()
{
var guid = Guid.NewGuid();
var s = "umb://" + Constants.UdiEntityType.AnyGuid + "/" + guid.ToString("N");
@@ -148,9 +154,32 @@ namespace Umbraco.Tests
Assert.AreEqual(Constants.UdiEntityType.AnyGuid, udi.EntityType);
Assert.AreEqual(guid, ((GuidUdi)udi).Guid);
Assert.Throws<InvalidOperationException>(() => Udi.Create(Constants.UdiEntityType.AnyString, guid));
Assert.Throws<InvalidOperationException>(() => Udi.Create(Constants.UdiEntityType.AnyGuid, "foo"));
Assert.Throws<ArgumentException>(() => Udi.Create("barf", "foo"));
// *not* testing whether Udi.Create(type, invalidValue) throws
// because we don't throw anymore - see U4-10409
}
[Test]
public void RootUdiTest()
{
var stringUdi = new StringUdi(Constants.UdiEntityType.AnyString, string.Empty);
Assert.IsTrue(stringUdi.IsRoot);
Assert.AreEqual("umb://any-string/", stringUdi.ToString());
var guidUdi = new GuidUdi(Constants.UdiEntityType.AnyGuid, Guid.Empty);
Assert.IsTrue(guidUdi.IsRoot);
Assert.AreEqual("umb://any-guid/00000000000000000000000000000000", guidUdi.ToString());
var udi = Udi.Parse("umb://any-string/");
Assert.IsTrue(udi.IsRoot);
Assert.IsInstanceOf<StringUdi>(udi);
udi = Udi.Parse("umb://any-guid/00000000000000000000000000000000");
Assert.IsTrue(udi.IsRoot);
Assert.IsInstanceOf<GuidUdi>(udi);
udi = Udi.Parse("umb://any-guid/");
Assert.IsTrue(udi.IsRoot);
Assert.IsInstanceOf<GuidUdi>(udi);
}
[Test]
@@ -202,5 +231,112 @@ namespace Umbraco.Tests
Assert.AreEqual(string.Format("umb://any-guid/{0:N}", guid), drange.Udi.UriValue.ToString());
Assert.AreEqual(Constants.DeploySelector.ChildrenOfThis, drange.Selector);
}
[Test]
public void ValidateUdiEntityType()
{
var types = Constants.UdiEntityType.GetTypes();
foreach (var fi in typeof (Constants.UdiEntityType).GetFields(BindingFlags.Public | BindingFlags.Static))
{
// IsLiteral determines if its value is written at
// compile time and not changeable
// IsInitOnly determine if the field can be set
// in the body of the constructor
// for C# a field which is readonly keyword would have both true
// but a const field would have only IsLiteral equal to true
if (fi.IsLiteral && fi.IsInitOnly == false)
{
var value = fi.GetValue(null).ToString();
if (types.ContainsKey(value) == false)
Assert.Fail("Error in class Constants.UdiEntityType, type \"{0}\" is not declared by GetTypes.", value);
types.Remove(value);
}
}
Assert.AreEqual(0, types.Count, "Error in class Constants.UdiEntityType, GetTypes declares types that don't exist ({0}).", string.Join(",", types.Keys.Select(x => "\"" + x + "\"")));
}
[Test]
public void KnownTypes()
{
Udi udi;
// cannot parse an unknown type, udi is null
// this will scan
Assert.IsFalse(Udi.TryParse("umb://whatever/1234", out udi));
Assert.IsNull(udi);
Udi.ResetUdiTypes();
// unless we want to know
Assert.IsFalse(Udi.TryParse("umb://whatever/1234", true, out udi));
Assert.AreEqual(Constants.UdiEntityType.Unknown, udi.EntityType);
Assert.AreEqual("Umbraco.Core.Udi+UnknownTypeUdi", udi.GetType().FullName);
Udi.ResetUdiTypes();
// not known
Assert.IsFalse(Udi.TryParse("umb://foo/A87F65C8D6B94E868F6949BA92C93045", true, out udi));
Assert.AreEqual(Constants.UdiEntityType.Unknown, udi.EntityType);
Assert.AreEqual("Umbraco.Core.Udi+UnknownTypeUdi", udi.GetType().FullName);
// scanned
Assert.IsTrue(Udi.TryParse("umb://foo/A87F65C8D6B94E868F6949BA92C93045", out udi));
Assert.IsInstanceOf<GuidUdi>(udi);
// known
Assert.IsTrue(Udi.TryParse("umb://foo/A87F65C8D6B94E868F6949BA92C93045", true, out udi));
Assert.IsInstanceOf<GuidUdi>(udi);
// can get method for Deploy compatibility
var method = typeof (Udi).GetMethod("Parse", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof (string), typeof (bool) }, null);
Assert.IsNotNull(method);
}
[UdiDefinition("foo", UdiType.GuidUdi)]
public class FooConnector : IServiceConnector
{
public IArtifact GetArtifact(Udi udi)
{
throw new NotImplementedException();
}
public IArtifact GetArtifact(object entity)
{
throw new NotImplementedException();
}
public ArtifactDeployState ProcessInit(IArtifact art, IDeployContext context)
{
throw new NotImplementedException();
}
public void Process(ArtifactDeployState dart, IDeployContext context, int pass)
{
throw new NotImplementedException();
}
public void Explode(UdiRange range, List<Udi> udis)
{
throw new NotImplementedException();
}
public NamedUdiRange GetRange(Udi udi, string selector)
{
throw new NotImplementedException();
}
public NamedUdiRange GetRange(string entityType, string sid, string selector)
{
throw new NotImplementedException();
}
public bool Compare(IArtifact art1, IArtifact art2, ICollection<Difference> differences = null)
{
throw new NotImplementedException();
}
}
}
}