From 2647c1f8d445e44df1ba0ad2fed02d0adef17ece Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Tue, 20 Apr 2010 17:59:09 -0700 Subject: [PATCH] Added unit tests on slugs generation unicity among the sae content types --- .../Common/Services/RoutableServiceTests.cs | 89 +++++++++++++++++++ .../Orchard.Core.Tests.csproj | 3 + .../Core/Common/Drivers/RoutableDriver.cs | 8 +- .../Common/Handlers/RoutableAspectHandler.cs | 2 +- .../Core/Common/Services/IRoutableService.cs | 3 +- .../Core/Common/Services/RoutableService.cs | 20 ++--- 6 files changed, 110 insertions(+), 15 deletions(-) diff --git a/src/Orchard.Core.Tests/Common/Services/RoutableServiceTests.cs b/src/Orchard.Core.Tests/Common/Services/RoutableServiceTests.cs index 5a3588b21..b587f0c54 100644 --- a/src/Orchard.Core.Tests/Common/Services/RoutableServiceTests.cs +++ b/src/Orchard.Core.Tests/Common/Services/RoutableServiceTests.cs @@ -10,6 +10,10 @@ using Orchard.ContentManagement.Records; using Orchard.Core.Common.Models; using Orchard.Core.Common.Services; using Orchard.Tests.Modules; +using Orchard.Core.Common.Handlers; +using System.Web.Mvc; +using System.Web.Routing; +using Orchard.Tests.Stubs; namespace Orchard.Core.Tests.Common.Services { [TestFixture] @@ -23,7 +27,13 @@ namespace Orchard.Core.Tests.Common.Services { public override void Register(ContainerBuilder builder) { builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As(); + + builder.RegisterType().As(); + builder.RegisterInstance(new UrlHelper(new RequestContext(new StubHttpContext("~/"), new RouteData()))).As(); + builder.RegisterType().As(); + } private IRoutableService _routableService; @@ -111,6 +121,47 @@ namespace Orchard.Core.Tests.Common.Services { Assert.That(thing.Slug, Is.EqualTo("this-is-some-interesting-title")); } + [Test] + public void GeneratedSlugsShouldBeUniqueAmongContentType() + { + var contentManager = _container.Resolve(); + + var thing1 = contentManager.Create(ThingDriver.ContentType.Name, t => + { + t.As().Record = new RoutableRecord(); + t.Title = "This Is Some Interesting Title"; + }); + + var thing2 = contentManager.Create(ThingDriver.ContentType.Name , t => + { + t.As().Record = new RoutableRecord(); + t.Title = "This Is Some Interesting Title"; + }); + + Assert.AreNotEqual(thing1.Slug, thing2.Slug); + } + + [Test] + public void SlugsCanBeDuplicatedAccrossContentTypes() + { + var contentManager = _container.Resolve(); + + var thing = contentManager.Create(ThingDriver.ContentType.Name, t => + { + t.As().Record = new RoutableRecord(); + t.Title = "This Is Some Interesting Title"; + }); + + var stuff = contentManager.Create(StuffDriver.ContentType.Name, s => + { + s.As().Record = new RoutableRecord(); + s.Title = "This Is Some Interesting Title"; + }); + + Assert.AreEqual(thing.Slug, stuff.Slug); + } + + protected override IEnumerable DatabaseTypes { get { return new[] { @@ -155,5 +206,43 @@ namespace Orchard.Core.Tests.Common.Services { DisplayName = "Thing" }; } + + [UsedImplicitly] + public class StuffHandler : ContentHandler + { + public StuffHandler() + { + Filters.Add(new ActivatingFilter(StuffDriver.ContentType.Name)); + Filters.Add(new ActivatingFilter>(StuffDriver.ContentType.Name)); + Filters.Add(new ActivatingFilter(StuffDriver.ContentType.Name)); + Filters.Add(new ActivatingFilter(StuffDriver.ContentType.Name)); + } + } + + public class Stuff : ContentPart + { + public int Id { get { return ContentItem.Id; } } + + public string Title + { + get { return this.As().Title; } + set { this.As().Title = value; } + } + + public string Slug + { + get { return this.As().Slug; } + set { this.As().Slug = value; } + } + } + + public class StuffDriver : ContentItemDriver + { + public readonly static ContentType ContentType = new ContentType + { + Name = "stuff", + DisplayName = "Stuff" + }; + } } } \ No newline at end of file diff --git a/src/Orchard.Core.Tests/Orchard.Core.Tests.csproj b/src/Orchard.Core.Tests/Orchard.Core.Tests.csproj index 490fa4e25..7eb333c40 100644 --- a/src/Orchard.Core.Tests/Orchard.Core.Tests.csproj +++ b/src/Orchard.Core.Tests/Orchard.Core.Tests.csproj @@ -60,6 +60,9 @@ ..\..\lib\sqlite\System.Data.SQLite.DLL True + + 3.5 + False ..\..\lib\aspnetmvc\System.Web.Mvc.dll diff --git a/src/Orchard.Web/Core/Common/Drivers/RoutableDriver.cs b/src/Orchard.Web/Core/Common/Drivers/RoutableDriver.cs index 55f46c473..1abaf162e 100644 --- a/src/Orchard.Web/Core/Common/Drivers/RoutableDriver.cs +++ b/src/Orchard.Web/Core/Common/Drivers/RoutableDriver.cs @@ -5,6 +5,7 @@ using Orchard.Core.Common.Models; using Orchard.Core.Common.ViewModels; using Orchard.Core.Common.Services; using Orchard.Localization; +using Orchard.UI.Notify; namespace Orchard.Core.Common.Drivers { [UsedImplicitly] @@ -40,7 +41,12 @@ namespace Orchard.Core.Common.Drivers { updater.AddModelError("Routable.Slug", T("Please do not use any of the following characters in your slugs: \"/\", \":\", \"?\", \"#\", \"[\", \"]\", \"@\", \"!\", \"$\", \"&\", \"'\", \"(\", \")\", \"*\", \"+\", \",\", \";\", \"=\". No spaces are allowed (please use dashes or underscores instead).").ToString()); } - _routableService.ProcessSlug(part); + string originalSlug = part.Slug; + if(!_routableService.ProcessSlug(part)) { + _services.Notifier.Warning(T("Slugs in conflict. \"{0}\" is already set for a previously created {2} so now it has the slug \"{1}\"", + originalSlug, part.Slug, part.ContentItem.ContentType)); + } + return ContentPartTemplate(model, TemplateName, Prefix).Location("primary", "before.5"); } diff --git a/src/Orchard.Web/Core/Common/Handlers/RoutableAspectHandler.cs b/src/Orchard.Web/Core/Common/Handlers/RoutableAspectHandler.cs index b1e4244a0..8f654378b 100644 --- a/src/Orchard.Web/Core/Common/Handlers/RoutableAspectHandler.cs +++ b/src/Orchard.Web/Core/Common/Handlers/RoutableAspectHandler.cs @@ -34,7 +34,7 @@ namespace Orchard.Core.Common.Handlers { routable.ContentItemBasePath = url; }); - OnPublished((context, bp) => routableService.ProcessSlug(bp)); + OnCreated((context, ra) => routableService.ProcessSlug(ra)); } diff --git a/src/Orchard.Web/Core/Common/Services/IRoutableService.cs b/src/Orchard.Web/Core/Common/Services/IRoutableService.cs index deb7b8476..65f0fac1a 100644 --- a/src/Orchard.Web/Core/Common/Services/IRoutableService.cs +++ b/src/Orchard.Web/Core/Common/Services/IRoutableService.cs @@ -21,7 +21,8 @@ namespace Orchard.Core.Common.Services { /// /// Defines the slug of a RoutableAspect and validate its unicity /// - void ProcessSlug(RoutableAspect part); + /// True if the slug has been created, False if a conflict occured + bool ProcessSlug(RoutableAspect part); } } \ No newline at end of file diff --git a/src/Orchard.Web/Core/Common/Services/RoutableService.cs b/src/Orchard.Web/Core/Common/Services/RoutableService.cs index 1ebad4869..5b901ae14 100644 --- a/src/Orchard.Web/Core/Common/Services/RoutableService.cs +++ b/src/Orchard.Web/Core/Common/Services/RoutableService.cs @@ -11,14 +11,10 @@ using Orchard.UI.Notify; namespace Orchard.Core.Common.Services { [UsedImplicitly] public class RoutableService : IRoutableService { - private readonly IOrchardServices _services; private readonly IContentManager _contentManager; - private Localizer T { get; set; } - public RoutableService(IOrchardServices services, IContentManager contentManager) { - _services = services; + public RoutableService(IContentManager contentManager) { _contentManager = contentManager; - T = NullLocalizer.Instance; } public void FillSlug(TModel model) where TModel : RoutableAspect { @@ -71,10 +67,9 @@ namespace Orchard.Core.Common.Services { { return _contentManager.Query(contentType).Join() - .Where(rr => rr.Slug.StartsWith(slug, StringComparison.OrdinalIgnoreCase)) .List() - .Cast() - .Select(i => i.Slug) + .Select(i => i.As().Slug) + .Where(rr => rr.StartsWith(slug, StringComparison.OrdinalIgnoreCase)) // todo: for some reason the filter doesn't work within the query, even without StringComparison or StartsWith .ToArray(); } @@ -82,13 +77,13 @@ namespace Orchard.Core.Common.Services { return slug == null || String.IsNullOrEmpty(slug.Trim()) || !Regex.IsMatch(slug, @"^[^/:?#\[\]@!$&'()*+,;=\s]+$"); } - public void ProcessSlug(RoutableAspect part) + public bool ProcessSlug(RoutableAspect part) { FillSlug(part); if (string.IsNullOrEmpty(part.Slug)) { - return; + return true; } var slugsLikeThis = GetSimilarSlugs(part.ContentItem.ContentType, part.Slug); @@ -101,10 +96,11 @@ namespace Orchard.Core.Common.Services { part.Slug = GenerateUniqueSlug(part.Slug, slugsLikeThis); if (originalSlug != part.Slug) { - _services.Notifier.Warning(T("Slugs in conflict. \"{0}\" is already set for a previously created {2} so now it has the slug \"{1}\"", - originalSlug, part.Slug, part.ContentItem.ContentType)); + return false; } } + + return true; } } } \ No newline at end of file