From b002ac8600e5b58f40cee5336382bd6db0c79248 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Tue, 12 Nov 2024 08:45:58 +0100 Subject: [PATCH] Add UDI support for relation, user and user-group entity types (#17490) * Add GetUdi() extension methods for IUser and IUserGroup * Add GetUdi() extension methods for IRelation * Move GetUdi() extension methods for Script and Stylesheet to interface types * Register relation, user and user-group as known UDI types * Obsolete UdiParserServiceConnectors * Use IScript and IStylesheet types in tests * Fix UdiTests after obsoleting UdiParserServiceConnectors --- src/Umbraco.Core/Constants-UdiEntityType.cs | 56 ++++----- .../Extensions/UdiGetterExtensions.cs | 106 ++++++++++++----- src/Umbraco.Core/UdiParser.cs | 34 +++--- .../UdiParserServiceConnectors.cs | 3 + .../Builders/UserGroupBuilder.cs | 15 ++- .../Umbraco.Core/CoreThings/UdiTests.cs | 37 ------ .../Extensions/UdiGetterExtensionsTests.cs | 108 ++++++++++++------ 7 files changed, 212 insertions(+), 147 deletions(-) diff --git a/src/Umbraco.Core/Constants-UdiEntityType.cs b/src/Umbraco.Core/Constants-UdiEntityType.cs index 81e64b534c..9c2a25b314 100644 --- a/src/Umbraco.Core/Constants-UdiEntityType.cs +++ b/src/Umbraco.Core/Constants-UdiEntityType.cs @@ -17,48 +17,42 @@ public static partial class Constants // need to keep it around in a field nor to make it readonly public const string Unknown = "unknown"; - // guid entity types + // GUID entity types public const string AnyGuid = "any-guid"; // that one is for tests - - public const string Element = "element"; - public const string Document = "document"; - - public const string DocumentBlueprint = "document-blueprint"; - - public const string Media = "media"; - public const string Member = "member"; - - public const string DictionaryItem = "dictionary-item"; - public const string Template = "template"; - - public const string DocumentType = "document-type"; - public const string DocumentTypeContainer = "document-type-container"; - - public const string DocumentBlueprintContainer = "document-blueprint-container"; - public const string MediaType = "media-type"; - public const string MediaTypeContainer = "media-type-container"; public const string DataType = "data-type"; public const string DataTypeContainer = "data-type-container"; - public const string MemberType = "member-type"; + public const string DictionaryItem = "dictionary-item"; + public const string Document = "document"; + public const string DocumentBlueprint = "document-blueprint"; + public const string DocumentBlueprintContainer = "document-blueprint-container"; + public const string DocumentType = "document-type"; + public const string DocumentTypeContainer = "document-type-container"; + public const string Element = "element"; + public const string Media = "media"; + public const string MediaType = "media-type"; + public const string MediaTypeContainer = "media-type-container"; + public const string Member = "member"; public const string MemberGroup = "member-group"; - + public const string MemberType = "member-type"; + public const string Relation = "relation"; public const string RelationType = "relation-type"; - + public const string Template = "template"; + public const string User = "user"; + public const string UserGroup = "user-group"; public const string Webhook = "webhook"; - // forms - public const string FormsForm = "forms-form"; - public const string FormsPreValue = "forms-prevalue"; - public const string FormsDataSource = "forms-datasource"; - - // string entity types + // String entity types public const string AnyString = "any-string"; // that one is for tests - public const string Language = "language"; public const string MediaFile = "media-file"; - public const string TemplateFile = "template-file"; + public const string PartialView = "partial-view"; public const string Script = "script"; public const string Stylesheet = "stylesheet"; - public const string PartialView = "partial-view"; + public const string TemplateFile = "template-file"; + + // Forms entity types + public const string FormsDataSource = "forms-datasource"; + public const string FormsForm = "forms-form"; + public const string FormsPreValue = "forms-prevalue"; } } diff --git a/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs b/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs index 66c5002604..a5adc0de2a 100644 --- a/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs +++ b/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs @@ -4,6 +4,7 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Membership; namespace Umbraco.Extensions; @@ -27,8 +28,6 @@ public static class UdiGetterExtensions { // Concrete types EntityContainer container => container.GetUdi(), - Script script => script.GetUdi(), - Stylesheet stylesheet => stylesheet.GetUdi(), // Interfaces IContentBase contentBase => contentBase.GetUdi(), IContentTypeComposition contentTypeComposition => contentTypeComposition.GetUdi(), @@ -37,8 +36,13 @@ public static class UdiGetterExtensions ILanguage language => language.GetUdi(), IMemberGroup memberGroup => memberGroup.GetUdi(), IPartialView partialView => partialView.GetUdi(), + IRelation relation => relation.GetUdi(), IRelationType relationType => relationType.GetUdi(), + IScript script => script.GetUdi(), + IStylesheet stylesheet => stylesheet.GetUdi(), ITemplate template => template.GetUdi(), + IUser user => user.GetUdi(), + IUserGroup userGroup => userGroup.GetUdi(), IWebhook webhook => webhook.GetUdi(), _ => throw new NotSupportedException($"Entity type {entity.GetType().FullName} is not supported."), }; @@ -80,34 +84,6 @@ public static class UdiGetterExtensions return new GuidUdi(entityType, entity.Key).EnsureClosed(); } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static StringUdi GetUdi(this Script entity) - { - ArgumentNullException.ThrowIfNull(entity); - - return GetUdiFromPath(Constants.UdiEntityType.Script, entity.Path); - } - - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// - /// The entity identifier of the entity. - /// - public static StringUdi GetUdi(this Stylesheet entity) - { - ArgumentNullException.ThrowIfNull(entity); - - return GetUdiFromPath(Constants.UdiEntityType.Stylesheet, entity.Path); - } - /// /// Gets the entity identifier of the entity. /// @@ -304,6 +280,20 @@ public static class UdiGetterExtensions return GetUdiFromPath(Constants.UdiEntityType.PartialView, entity.Path); } + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static GuidUdi GetUdi(this IRelation entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return new GuidUdi(Constants.UdiEntityType.Relation, entity.Key).EnsureClosed(); + } + /// /// Gets the entity identifier of the entity. /// @@ -318,6 +308,34 @@ public static class UdiGetterExtensions return new GuidUdi(Constants.UdiEntityType.RelationType, entity.Key).EnsureClosed(); } + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static StringUdi GetUdi(this IScript entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return GetUdiFromPath(Constants.UdiEntityType.Script, entity.Path); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static StringUdi GetUdi(this IStylesheet entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return GetUdiFromPath(Constants.UdiEntityType.Stylesheet, entity.Path); + } + /// /// Gets the entity identifier of the entity. /// @@ -332,6 +350,34 @@ public static class UdiGetterExtensions return new GuidUdi(Constants.UdiEntityType.Template, entity.Key).EnsureClosed(); } + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static GuidUdi GetUdi(this IUser entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return new GuidUdi(Constants.UdiEntityType.User, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// + /// The entity identifier of the entity. + /// + public static GuidUdi GetUdi(this IUserGroup entity) + { + ArgumentNullException.ThrowIfNull(entity); + + return new GuidUdi(Constants.UdiEntityType.UserGroup, entity.Key).EnsureClosed(); + } + /// /// Gets the entity identifier of the entity. /// diff --git a/src/Umbraco.Core/UdiParser.cs b/src/Umbraco.Core/UdiParser.cs index 24ee238a69..ed1f16ae28 100644 --- a/src/Umbraco.Core/UdiParser.cs +++ b/src/Umbraco.Core/UdiParser.cs @@ -200,34 +200,40 @@ public sealed class UdiParser new() { { Constants.UdiEntityType.Unknown, UdiType.Unknown }, + // GUID UDI types { Constants.UdiEntityType.AnyGuid, UdiType.GuidUdi }, - { Constants.UdiEntityType.Element, UdiType.GuidUdi }, + { Constants.UdiEntityType.DataType, UdiType.GuidUdi }, + { Constants.UdiEntityType.DataTypeContainer, UdiType.GuidUdi }, + { Constants.UdiEntityType.DictionaryItem, UdiType.GuidUdi }, { Constants.UdiEntityType.Document, UdiType.GuidUdi }, { Constants.UdiEntityType.DocumentBlueprint, UdiType.GuidUdi }, { Constants.UdiEntityType.DocumentBlueprintContainer, UdiType.GuidUdi }, - { Constants.UdiEntityType.Media, UdiType.GuidUdi }, - { Constants.UdiEntityType.Member, UdiType.GuidUdi }, - { Constants.UdiEntityType.DictionaryItem, UdiType.GuidUdi }, - { Constants.UdiEntityType.Template, UdiType.GuidUdi }, { Constants.UdiEntityType.DocumentType, UdiType.GuidUdi }, { Constants.UdiEntityType.DocumentTypeContainer, UdiType.GuidUdi }, + { Constants.UdiEntityType.Element, UdiType.GuidUdi }, + { Constants.UdiEntityType.Media, UdiType.GuidUdi }, { Constants.UdiEntityType.MediaType, UdiType.GuidUdi }, { Constants.UdiEntityType.MediaTypeContainer, UdiType.GuidUdi }, - { Constants.UdiEntityType.DataType, UdiType.GuidUdi }, - { Constants.UdiEntityType.DataTypeContainer, UdiType.GuidUdi }, - { Constants.UdiEntityType.MemberType, UdiType.GuidUdi }, + { Constants.UdiEntityType.Member, UdiType.GuidUdi }, { Constants.UdiEntityType.MemberGroup, UdiType.GuidUdi }, + { Constants.UdiEntityType.MemberType, UdiType.GuidUdi }, + { Constants.UdiEntityType.Relation, UdiType.GuidUdi }, { Constants.UdiEntityType.RelationType, UdiType.GuidUdi }, - { Constants.UdiEntityType.FormsForm, UdiType.GuidUdi }, - { Constants.UdiEntityType.FormsPreValue, UdiType.GuidUdi }, - { Constants.UdiEntityType.FormsDataSource, UdiType.GuidUdi }, + { Constants.UdiEntityType.Template, UdiType.GuidUdi }, + { Constants.UdiEntityType.User, UdiType.GuidUdi }, + { Constants.UdiEntityType.UserGroup, UdiType.GuidUdi }, + { Constants.UdiEntityType.Webhook, UdiType.GuidUdi }, + // String UDI types { Constants.UdiEntityType.AnyString, UdiType.StringUdi }, { Constants.UdiEntityType.Language, UdiType.StringUdi }, { Constants.UdiEntityType.MediaFile, UdiType.StringUdi }, - { Constants.UdiEntityType.TemplateFile, UdiType.StringUdi }, - { Constants.UdiEntityType.Script, UdiType.StringUdi }, { Constants.UdiEntityType.PartialView, UdiType.StringUdi }, + { Constants.UdiEntityType.Script, UdiType.StringUdi }, { Constants.UdiEntityType.Stylesheet, UdiType.StringUdi }, - { Constants.UdiEntityType.Webhook, UdiType.GuidUdi }, + { Constants.UdiEntityType.TemplateFile, UdiType.StringUdi }, + // Forms UDI types + { Constants.UdiEntityType.FormsDataSource, UdiType.GuidUdi }, + { Constants.UdiEntityType.FormsForm, UdiType.GuidUdi }, + { Constants.UdiEntityType.FormsPreValue, UdiType.GuidUdi }, }; } diff --git a/src/Umbraco.Core/UdiParserServiceConnectors.cs b/src/Umbraco.Core/UdiParserServiceConnectors.cs index 4c307435de..465431476e 100644 --- a/src/Umbraco.Core/UdiParserServiceConnectors.cs +++ b/src/Umbraco.Core/UdiParserServiceConnectors.cs @@ -4,6 +4,7 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Core; +[Obsolete("This class will be removed in a future version.")] public static class UdiParserServiceConnectors { private static readonly object ScanLocker = new(); @@ -19,6 +20,7 @@ public static class UdiParserServiceConnectors /// Scan for deploy in assemblies for known UDI types. /// /// + [Obsolete("Use UdiParser.RegisterUdiType() instead. This method will be removed in a future version.")] public static void ScanDeployServiceConnectorsForUdiTypes(TypeLoader typeLoader) { if (typeLoader is null) @@ -72,6 +74,7 @@ public static class UdiParserServiceConnectors /// Registers a single to add it's UDI type. /// /// + [Obsolete("Use UdiParser.RegisterUdiType() instead. This method will be removed in a future version.")] public static void RegisterServiceConnector() where T : IServiceConnector { diff --git a/tests/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs b/tests/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs index 97288789b7..c84b118abe 100644 --- a/tests/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs +++ b/tests/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs @@ -1,8 +1,6 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; -using System.Linq; using Moq; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Strings; @@ -22,6 +20,7 @@ public class UserGroupBuilder : UserGroupBuilder public class UserGroupBuilder : ChildBuilderBase, IWithIdBuilder, + IWithKeyBuilder, IWithIconBuilder, IWithAliasBuilder, IWithNameBuilder @@ -30,6 +29,7 @@ public class UserGroupBuilder private IEnumerable _allowedSections = Enumerable.Empty(); private string _icon; private int? _id; + private Guid? _key; private string _name; private ISet _permissions = new HashSet(); private int? _startContentId; @@ -60,6 +60,12 @@ public class UserGroupBuilder set => _id = value; } + Guid? IWithKeyBuilder.Key + { + get => _key; + set => _key = value; + } + string IWithNameBuilder.Name { get => _name; @@ -117,11 +123,13 @@ public class UserGroupBuilder x.StartContentId == userGroup.StartContentId && x.StartMediaId == userGroup.StartMediaId && x.AllowedSections == userGroup.AllowedSections && - x.Id == userGroup.Id); + x.Id == userGroup.Id && + x.Key == userGroup.Key); public override IUserGroup Build() { var id = _id ?? 0; + var key = _key ?? Guid.NewGuid(); var name = _name ?? "TestUserGroup" + _suffix; var alias = _alias ?? "testUserGroup" + _suffix; var userCount = _userCount ?? 0; @@ -134,6 +142,7 @@ public class UserGroupBuilder var userGroup = new UserGroup(shortStringHelper, userCount, alias, name, icon) { Id = id, + Key = key, StartContentId = startContentId, StartMediaId = startMediaId }; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UdiTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UdiTests.cs index 03d086b4a6..1351bc57a0 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UdiTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UdiTests.cs @@ -293,42 +293,5 @@ public class UdiTests Assert.IsFalse(UdiParser.TryParse("umb://foo/A87F65C8D6B94E868F6949BA92C93045", true, out udi)); Assert.AreEqual(Constants.UdiEntityType.Unknown, udi.EntityType); Assert.AreEqual("Umbraco.Cms.Core.UnknownTypeUdi", udi.GetType().FullName); - - // scanned - UdiParserServiceConnectors - .RegisterServiceConnector< - FooConnector>(); // this is the equivalent of scanning but we'll just manually register this one - Assert.IsTrue(UdiParser.TryParse("umb://foo/A87F65C8D6B94E868F6949BA92C93045", out udi)); - Assert.IsInstanceOf(udi); - - // known - Assert.IsTrue(UdiParser.TryParse("umb://foo/A87F65C8D6B94E868F6949BA92C93045", true, out udi)); - Assert.IsInstanceOf(udi); - - // can get method for Deploy compatibility - var method = typeof(UdiParser).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 Task GetArtifactAsync(Udi udi, IContextCache contextCache, CancellationToken cancellationToken = default) => throw new NotImplementedException(); - - public Task GetArtifactAsync(object entity, IContextCache contextCache, CancellationToken cancellationToken = default) => throw new NotImplementedException(); - - public Task ProcessInitAsync(IArtifact artifact, IDeployContext context, CancellationToken cancellationToken = default) => throw new NotImplementedException(); - - public Task ProcessAsync(ArtifactDeployState state, IDeployContext context, int pass, - CancellationToken cancellationToken = default) => - throw new NotImplementedException(); - - public IAsyncEnumerable ExpandRangeAsync(UdiRange range, CancellationToken cancellationToken = default) => throw new NotImplementedException(); - - public Task GetRangeAsync(Udi udi, string selector, CancellationToken cancellationToken = default) => throw new NotImplementedException(); - - public Task GetRangeAsync(string entityType, string sid, string selector, CancellationToken cancellationToken = default) => throw new NotImplementedException(); - - public bool Compare(IArtifact? art1, IArtifact? art2, ICollection? differences = null) => throw new NotImplementedException(); } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UdiGetterExtensionsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UdiGetterExtensionsTests.cs index f5a5e79234..476fefec66 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UdiGetterExtensionsTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UdiGetterExtensionsTests.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Builders.Extensions; using Umbraco.Extensions; @@ -32,38 +33,6 @@ public class UdiGetterExtensionsTests Assert.AreEqual(expected, udi.ToString()); } - [TestCase("script.js", "umb://script/script.js")] - [TestCase("editor\\script.js", "umb://script/editor/script.js")] - [TestCase("editor/script.js", "umb://script/editor/script.js")] - public void GetUdiForScript(string path, string expected) - { - Script entity = new ScriptBuilder() - .WithPath(path) - .Build(); - - Udi udi = entity.GetUdi(); - Assert.AreEqual(expected, udi.ToString()); - - udi = ((IEntity)entity).GetUdi(); - Assert.AreEqual(expected, udi.ToString()); - } - - [TestCase("style.css", "umb://stylesheet/style.css")] - [TestCase("editor\\style.css", "umb://stylesheet/editor/style.css")] - [TestCase("editor/style.css", "umb://stylesheet/editor/style.css")] - public void GetUdiForStylesheet(string path, string expected) - { - Stylesheet entity = new StylesheetBuilder() - .WithPath(path) - .Build(); - - Udi udi = entity.GetUdi(); - Assert.AreEqual(expected, udi.ToString()); - - udi = ((IEntity)entity).GetUdi(); - Assert.AreEqual(expected, udi.ToString()); - } - [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", false, "umb://document/6ad82c70685c4e049b36d81bd779d16f")] [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", true, "umb://document-blueprint/6ad82c70685c4e049b36d81bd779d16f")] public void GetUdiForContent(Guid key, bool blueprint, string expected) @@ -241,6 +210,21 @@ public class UdiGetterExtensionsTests Assert.AreEqual(expected, udi.ToString()); } + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://relation/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForRelation(Guid key, string expected) + { + IRelation entity = new RelationBuilder() + .WithKey(key) + .AddRelationType().Done() + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://relation-type/6ad82c70685c4e049b36d81bd779d16f")] public void GetUdiForRelationType(Guid key, string expected) { @@ -255,6 +239,38 @@ public class UdiGetterExtensionsTests Assert.AreEqual(expected, udi.ToString()); } + [TestCase("script.js", "umb://script/script.js")] + [TestCase("editor\\script.js", "umb://script/editor/script.js")] + [TestCase("editor/script.js", "umb://script/editor/script.js")] + public void GetUdiForScript(string path, string expected) + { + IScript entity = new ScriptBuilder() + .WithPath(path) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("style.css", "umb://stylesheet/style.css")] + [TestCase("editor\\style.css", "umb://stylesheet/editor/style.css")] + [TestCase("editor/style.css", "umb://stylesheet/editor/style.css")] + public void GetUdiForStylesheet(string path, string expected) + { + IStylesheet entity = new StylesheetBuilder() + .WithPath(path) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://template/6ad82c70685c4e049b36d81bd779d16f")] public void GetUdiForTemplate(Guid key, string expected) { @@ -269,6 +285,34 @@ public class UdiGetterExtensionsTests Assert.AreEqual(expected, udi.ToString()); } + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://user/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForUser(Guid key, string expected) + { + IUser entity = new UserBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://user-group/6ad82c70685c4e049b36d81bd779d16f")] + public void GetUdiForUserGroup(Guid key, string expected) + { + IUserGroup entity = new UserGroupBuilder() + .WithKey(key) + .Build(); + + Udi udi = entity.GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + + udi = ((IEntity)entity).GetUdi(); + Assert.AreEqual(expected, udi.ToString()); + } + [TestCase("6ad82c70-685c-4e04-9b36-d81bd779d16f", "umb://webhook/6ad82c70685c4e049b36d81bd779d16f")] public void GetUdiForWebhook(Guid key, string expected) {