using System; using System.Collections.Generic; using Autofac; using JetBrains.Annotations; using Moq; using NUnit.Framework; using Orchard.ContentManagement; using Orchard.ContentManagement.Aspects; using Orchard.ContentManagement.Handlers; using Orchard.ContentManagement.MetaData; using Orchard.ContentManagement.Records; using Orchard.Core.Common.Models; using Orchard.Core.Routable; using Orchard.Core.Routable.Handlers; using Orchard.Core.Routable.Models; using Orchard.Core.Routable.Services; using Orchard.Data; using Orchard.DisplayManagement; using Orchard.DisplayManagement.Descriptors; using Orchard.DisplayManagement.Implementation; using Orchard.Environment; using Orchard.Environment.Extensions; using Orchard.Mvc; using Orchard.Security; using Orchard.Tests.Modules; using System.Web.Mvc; using System.Web.Routing; using Orchard.Tests.Stubs; using Orchard.UI.Notify; namespace Orchard.Core.Tests.Routable.Services { [TestFixture] public class RoutableServiceTests : DatabaseEnabledTestsBase { [SetUp] public override void Init() { base.Init(); _routableService = _container.Resolve(); } public override void Register(ContainerBuilder builder) { builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterInstance(new Mock().Object); builder.RegisterInstance(new Mock().Object); builder.RegisterInstance(new Mock().Object); builder.RegisterInstance(new Mock().Object); builder.RegisterInstance(new Mock().Object); builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); 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(); builder.RegisterType().As(); builder.RegisterType().As(); } private IRoutableService _routableService; [Test] public void InvalidCharactersShouldBeReplacedByADash() { var contentManager = _container.Resolve(); var thing = contentManager.Create("thing", t => { t.As().Record = new RoutePartRecord(); t.Title = "Please do not use any of the following characters in your permalink: \":\", \"?\", \"#\", \"[\", \"]\", \"@\", \"!\", \"$\", \"&\", \"'\", \"(\", \")\", \"*\", \"+\", \",\", \";\", \"=\", \"\"\", \"<\", \">\""; }); _routableService.FillSlugFromTitle(thing.As()); Assert.That(thing.Slug, Is.EqualTo("please-do-not-use-any-of-the-following-characters-in-your-permalink")); } [Test] public void SpacesSlugShouldBeTreatedAsEmpty() { var contentManager = _container.Resolve(); var thing = contentManager.Create("thing", t => { t.As().Record = new RoutePartRecord(); t.Title = "My Title"; t.Slug = " "; }); _routableService.FillSlugFromTitle(thing.As()); Assert.That(thing.Slug, Is.EqualTo("my-title")); } [Test] public void SlashInSlugIsAllowed() { Assert.That(_routableService.IsSlugValid("some/page"), Is.True); } [Test] public void DotsAroundSlugAreAllowed() { Assert.That(_routableService.IsSlugValid(".slug"), Is.False); Assert.That(_routableService.IsSlugValid("slug."), Is.False); Assert.That(_routableService.IsSlugValid("slug.slug"), Is.True); } [Test] public void EmptySlugsShouldBeConsideredValid() { // so that automatic generation on Publish occurs Assert.That(_routableService.IsSlugValid(null), Is.True); Assert.That(_routableService.IsSlugValid(String.Empty), Is.True); Assert.That(_routableService.IsSlugValid(" "), Is.True); } [Test] public void InvalidCharacterShouldBeRefusedInSlugs() { Assert.That(_routableService.IsSlugValid("aaaa-_aaaa"), Is.True); foreach (var c in @":?#[]@!$&'()*+,;= ") { Assert.That(_routableService.IsSlugValid("a" + c + "b"), Is.False); } } [Test] public void VeryLongStringTruncatedTo1000Chars() { var veryVeryLongTitle = "this is a very long title..."; for (var i = 0; i < 100; i++) veryVeryLongTitle += "aaaaaaaaaa"; var thing = CreateRoutePartFromScratch(veryVeryLongTitle); _routableService.FillSlugFromTitle(thing); Assert.That(veryVeryLongTitle.Length, Is.AtLeast(1001)); Assert.That(thing.Slug.Length, Is.EqualTo(1000)); } [Test] public void NoExistingLikeSlugsGeneratesSameSlug() { string slug = _routableService.GenerateUniqueSlug(CreateRoutePartFromScratch("woohoo"), null); Assert.That(slug, Is.EqualTo("woohoo")); } [Test] public void ExistingSingleLikeSlugThatsAConflictGeneratesADash2() { string slug = _routableService.GenerateUniqueSlug(CreateRoutePartFromScratch("woohoo"), new List { "woohoo" }); Assert.That(slug, Is.EqualTo("woohoo-2")); } [Test] public void ExistingSingleLikeSlugThatsNotAConflictGeneratesSameSlug() { string slug = _routableService.GenerateUniqueSlug(CreateRoutePartFromScratch("woohoo"), new List { "woohoo-2" }); Assert.That(slug, Is.EqualTo("woohoo")); } [Test] public void ExistingLikeSlugsWithAConflictGeneratesADashVNext() { string slug = _routableService.GenerateUniqueSlug(CreateRoutePartFromScratch("woohoo"), new List { "woohoo", "woohoo-2" }); Assert.That(slug, Is.EqualTo("woohoo-3")); } [Test] public void ExistingSlugsWithVersionGapsAndNoMatchGeneratesSameSlug() { string slug = _routableService.GenerateUniqueSlug(CreateRoutePartFromScratch("woohoo"), new List { "woohoo-2", "woohoo-4", "woohoo-5" }); Assert.That(slug, Is.EqualTo("woohoo")); } [Test] public void ExistingSlugsWithVersionGapsAndAMatchGeneratesADash2() { string slug = _routableService.GenerateUniqueSlug(CreateRoutePartFromScratch("woohoo-2"), new List { "woohoo-2", "woohoo-4", "woohoo-5" }); Assert.That(slug, Is.EqualTo("woohoo-2-2")); } [Test] public void SlugIsGeneratedLowerCased() { var thing = CreateRoutePartFromScratch("This Is Some Interesting Title"); _routableService.FillSlugFromTitle(thing); Assert.That(thing.Slug, Is.EqualTo("this-is-some-interesting-title")); } [Test] public void SlugInConflictWithAnExistingItemsPathIsVersioned() { CreateRoutePartFromScratch("bar", "bar", "foo"); var thing2 = CreateRoutePartFromScratch("fooslashbar", "foo/bar"); Assert.That(thing2.Path, Is.EqualTo("foo/bar-2")); } [Test] public void GeneratedSlugInConflictInSameContaierPathIsVersioned() { var thing1 = CreateRoutePartFromScratch("Foo", "", "bar"); var thing2 = CreateRoutePartWithExistingContainer("Foo", thing1.As().Container); Assert.That(thing2.Path, Is.EqualTo("bar/foo-2")); Assert.That(thing2.Slug, Is.EqualTo("foo-2")); } [Test] public void GivenSlugInConflictInSameContaierPathIsVersioned() { var thing1 = CreateRoutePartFromScratch("Hi", "foo", "bar"); var thing2 = CreateRoutePartWithExistingContainer("There", thing1.As().Container, "foo"); Assert.That(thing2.Path, Is.EqualTo("bar/foo-2")); Assert.That(thing2.Slug, Is.EqualTo("foo-2")); } [Test] public void GeneratedSlugInConflictInDifferentContaierPathIsNotVersioned() { var thing1 = CreateRoutePartFromScratch("Foo", "", "rab"); var thing2 = CreateRoutePartFromScratch("Foo", "", "bar"); Assert.That(thing1.Path, Is.EqualTo("rab/foo")); Assert.That(thing2.Path, Is.EqualTo("bar/foo")); Assert.That(thing1.Slug, Is.EqualTo("foo")); Assert.That(thing2.Slug, Is.EqualTo("foo")); } private RoutePart CreateRoutePartWithExistingContainer(string title, IContent container, string slug = "") { var contentManager = _container.Resolve(); return contentManager.Create("thing", t => { t.As().Record = new RoutePartRecord(); t.Title = title; if (!string.IsNullOrWhiteSpace(slug)) t.As().Slug = slug; if (container != null) t.As().Container = container; }).As(); } private RoutePart CreateRoutePartFromScratch(string title, string slug = "", string containerPath = "") { var contentManager = _container.Resolve(); return contentManager.Create("thing", t => { t.As().Record = new RoutePartRecord(); if (!string.IsNullOrWhiteSpace(slug)) t.As().Slug = slug; t.Title = title; if (!string.IsNullOrWhiteSpace(containerPath)) { t.As().Container = contentManager.Create("thing", tt => { tt.As().Path = containerPath; tt.As().Slug = containerPath; tt.As().Title = "Test Container"; }); } }).As(); } protected override IEnumerable DatabaseTypes { get { return new[] { typeof(RoutePartRecord), typeof(ContentTypeRecord), typeof(ContentItemRecord), typeof(ContentItemVersionRecord), typeof(CommonPartRecord), typeof(CommonPartVersionRecord), }; } } [UsedImplicitly] public class ThingHandler : ContentHandler { public ThingHandler() { Filters.Add(new ActivatingFilter("thing")); Filters.Add(new ActivatingFilter>("thing")); Filters.Add(new ActivatingFilter("thing")); Filters.Add(new ActivatingFilter("thing")); } } public class Thing : ContentPart { 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; } } } [UsedImplicitly] public class StuffHandler : ContentHandler { public StuffHandler() { Filters.Add(new ActivatingFilter("stuff")); Filters.Add(new ActivatingFilter>("stuff")); Filters.Add(new ActivatingFilter("stuff")); Filters.Add(new ActivatingFilter("stuff")); } } public class Stuff : ContentPart { 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; } } } } }