diff --git a/src/Orchard.Specs/Bindings/WebAppHosting.cs b/src/Orchard.Specs/Bindings/WebAppHosting.cs index c13091bf8..0658c2957 100644 --- a/src/Orchard.Specs/Bindings/WebAppHosting.cs +++ b/src/Orchard.Specs/Bindings/WebAppHosting.cs @@ -6,6 +6,8 @@ using System.Linq; using System.Reflection; using System.Web; using System.Web.Hosting; +using System.Xml.Linq; +using HtmlAgilityPack; using Orchard.Specs.Hosting; using Orchard.Specs.Util; using TechTalk.SpecFlow; @@ -16,6 +18,7 @@ namespace Orchard.Specs.Bindings { public class WebAppHosting { private WebHost _webHost; private RequestDetails _details; + private HtmlDocument _doc; private MessageSink _messages; [Given(@"I have a clean site")] @@ -54,20 +57,63 @@ namespace Orchard.Specs.Bindings { _webHost.CopyExtension("Core", moduleName); } + [Given(@"I am on ""(.*)""")] + public void GivenIAmOn(string urlPath) { + WhenIGoTo(urlPath); + } + + [When(@"I go to ""(.*)""")] public void WhenIGoTo(string urlPath) { _details = _webHost.SendRequest(urlPath); + _doc = new HtmlDocument(); + _doc.Load(new StringReader(_details.ResponseText)); } [When(@"I follow ""(.*)""")] public void WhenIFollow(string linkText) { - var doc = new HtmlAgilityPack.HtmlDocument(); - doc.Load(new StringReader(_details.ResponseText)); - var link = doc.DocumentNode.SelectNodes("//a").Single(elt => elt.InnerText == linkText); + var link = _doc.DocumentNode + .SelectNodes("//a") + .Single(elt => elt.InnerText == linkText); - WhenIGoTo(link.Attributes["href"].Value); + var urlPath = link.Attributes["href"].Value; + + WhenIGoTo(urlPath); } + [When(@"I fill in")] + public void WhenIFillIn(Table table) { + var inputs = _doc.DocumentNode + .SelectNodes("//input"); + + foreach (var row in table.Rows) { + var r = row; + var input = inputs.Single( + x => x.Attributes.Contains("name") && + x.Attributes["name"].Value == r["name"]); + input.Attributes.Add("value", row["value"]); + } + } + + [When(@"I hit ""(.*)""")] + public void WhenIHit(string submitText) { + var submit = _doc.DocumentNode + .SelectNodes("//input[@type='submit']") + .Single(elt => elt.GetAttributeValue("value", null) == submitText); + + var form = Form.LocateAround(submit); + var urlPath = form.Start.GetAttributeValue("action", _details.UrlPath); + var inputs = form.Children + .SelectMany(elt => elt.DescendantsAndSelf("input")) + .GroupBy(elt => elt.GetAttributeValue("name", elt.GetAttributeValue("id", "")), elt => elt.GetAttributeValue("value", "")) + .ToDictionary(elt => elt.Key, elt => (IEnumerable)elt); + + _details = _webHost.SendRequest(urlPath, inputs); + _doc = new HtmlDocument(); + _doc.Load(new StringReader(_details.ResponseText)); + } + + [Then(@"the status should be (.*) (.*)")] public void ThenTheStatusShouldBe(int statusCode, string statusDescription) { Assert.That(_details.StatusCode, Is.EqualTo(statusCode)); @@ -84,4 +130,51 @@ namespace Orchard.Specs.Bindings { ScenarioContext.Current.Pending(); } } + + public class Form { + public static Form LocateAround(HtmlNode cornerstone) { + foreach (var inspect in cornerstone.AncestorsAndSelf()) { + + var form = inspect.PreviousSiblingsAndSelf().FirstOrDefault( + n => n.NodeType == HtmlNodeType.Element && n.Name == "form"); + if (form == null) + continue; + + var endForm = inspect.NextSiblingsAndSelf().FirstOrDefault( + n => n.NodeType == HtmlNodeType.Text && n.InnerText == ""); + if (endForm == null) + continue; + + return new Form { + Start = form, + End = endForm, + Children = form.NextSibling.NextSiblingsAndSelf().TakeWhile(n => n != endForm).ToArray() + }; + } + + return null; + } + + + public HtmlNode Start { get; set; } + public HtmlNode End { get; set; } + public IEnumerable Children { get; set; } + } + + static class HtmlExtensions { + public static IEnumerable PreviousSiblingsAndSelf(this HtmlNode node) { + var scan = node; + while (scan != null) { + yield return scan; + scan = scan.PreviousSibling; + } + } + public static IEnumerable NextSiblingsAndSelf(this HtmlNode node) { + var scan = node; + while (scan != null) { + yield return scan; + scan = scan.NextSibling; + } + } + } } diff --git a/src/Orchard.Specs/Hosting/RequestDetails.cs b/src/Orchard.Specs/Hosting/RequestDetails.cs index 5a1a8419a..9071a5122 100644 --- a/src/Orchard.Specs/Hosting/RequestDetails.cs +++ b/src/Orchard.Specs/Hosting/RequestDetails.cs @@ -2,14 +2,15 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using HtmlAgilityPack; -namespace Orchard.Specs.Hosting -{ +namespace Orchard.Specs.Hosting { [Serializable] - public class RequestDetails - { + public class RequestDetails { + public string UrlPath { get; set; } public string Page { get; set; } public string Query { get; set; } + public byte[] PostData { get; set; } public int StatusCode { get; set; } public string StatusDescription { get; set; } diff --git a/src/Orchard.Specs/Hosting/RequestExtensions.cs b/src/Orchard.Specs/Hosting/RequestExtensions.cs index b53ec1dc7..7e8142dba 100644 --- a/src/Orchard.Specs/Hosting/RequestExtensions.cs +++ b/src/Orchard.Specs/Hosting/RequestExtensions.cs @@ -5,25 +5,30 @@ using System.Linq; using System.Text; using System.Web; using System.Web.Hosting; +using HtmlAgilityPack; using Orchard.Specs.Util; -namespace Orchard.Specs.Hosting -{ - public static class RequestExtensions - { - public static RequestDetails SendRequest(this WebHost webHost, string urlPath) - { +namespace Orchard.Specs.Hosting { + public static class RequestExtensions { + public static RequestDetails SendRequest(this WebHost webHost, string urlPath, IDictionary> postData) { var physicalPath = Bleroy.FluentPath.Path.Get(webHost.PhysicalDirectory); - var details = new RequestDetails - { + var details = new RequestDetails { + UrlPath = urlPath, Page = physicalPath .Combine(urlPath.TrimStart('/', '\\')) - .GetRelativePath(physicalPath) + .GetRelativePath(physicalPath), }; - webHost.Execute(() => - { + if (postData != null) { + var requestBodyText = postData + .SelectMany(kv=>kv.Value.Select(v=>new{k=kv.Key, v})) + .Select((kv, n) => new {p = HttpUtility.UrlEncode(kv.k) + "=" + HttpUtility.UrlEncode(kv.v), n}) + .Aggregate("", (a, x) => a + (x.n == 0 ? "" : "&") + x.p); + details.PostData = Encoding.Default.GetBytes(requestBodyText); + } + + webHost.Execute(() => { var output = new StringWriter(); HttpRuntime.ProcessRequest(new Worker(details, output)); details.ResponseText = output.ToString(); @@ -32,28 +37,59 @@ namespace Orchard.Specs.Hosting return details; } - class Worker : SimpleWorkerRequest - { + public static RequestDetails SendRequest(this WebHost webHost, string urlPath) { + return webHost.SendRequest(urlPath, null); + } + + class Worker : SimpleWorkerRequest { private readonly RequestDetails _details; private readonly TextWriter _output; public Worker(RequestDetails details, TextWriter output) - : base(details.Page, details.Query, output) - { + : base(details.Page, details.Query, output) { _details = details; _output = output; + PostContentType = "application/x-www-form-urlencoded"; } - public override void SendStatus(int statusCode, string statusDescription) - { + public string PostContentType { get; set; } + + + public override String GetHttpVerbName() { + if (_details.PostData == null) + return base.GetHttpVerbName(); + + return "POST"; + } + + public override string GetKnownRequestHeader(int index) { + if (index == HeaderContentLength) { + if (_details.PostData != null) + return _details.PostData.Length.ToString(); + } + else if (index == HeaderContentType) { + if (_details.PostData != null) + return PostContentType; + } + return base.GetKnownRequestHeader(index); + + } + + public override byte[] GetPreloadedEntityBody() { + if (_details.PostData != null) + return _details.PostData; + + return base.GetPreloadedEntityBody(); + } + + public override void SendStatus(int statusCode, string statusDescription) { _details.StatusCode = statusCode; _details.StatusDescription = statusDescription; base.SendStatus(statusCode, statusDescription); } - public override void SendResponseFromFile(string filename, long offset, long length) - { + public override void SendResponseFromFile(string filename, long offset, long length) { _output.Write(File.ReadAllText(filename)); } } diff --git a/src/Orchard.Specs/Setup.feature b/src/Orchard.Specs/Setup.feature index 82dff75fb..903a0d9b2 100644 --- a/src/Orchard.Specs/Setup.feature +++ b/src/Orchard.Specs/Setup.feature @@ -20,3 +20,25 @@ Scenario: Setup folder also shows setup form Then I should see "Welcome to Orchard" And I should see "Finish Setup" And the status should be 200 OK + +Scenario: Some of the initial form values are required + Given I have a clean site + And I have module "Orchard.Setup" + And I have theme "SafeMode" + When I go to "/Setup" + And I hit "Finish Setup" + Then I should see "Site name is required" + And I should see "Password is required" + +Scenario: Initializing the site + Given I have a clean site + And I have module "Orchard.Setup" + And I have theme "SafeMode" + And I am on "/Setup" + When I fill in + | name | value | + | SiteName | My Site | + | AdminPassword | 6655321 | + And I hit "Finish Setup" + Then I should see "Blah" + diff --git a/src/Orchard.Specs/Setup.feature.cs b/src/Orchard.Specs/Setup.feature.cs index 96284e425..46bb12738 100644 --- a/src/Orchard.Specs/Setup.feature.cs +++ b/src/Orchard.Specs/Setup.feature.cs @@ -1,103 +1,2 @@ -// ------------------------------------------------------------------------------ -// -// This code was generated by SpecFlow (http://www.specflow.org/). -// SpecFlow Version:1.2.0.0 -// Runtime Version:2.0.50727.4200 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// ------------------------------------------------------------------------------ -namespace Orchard.Specs -{ - using TechTalk.SpecFlow; - - - [NUnit.Framework.TestFixtureAttribute()] - [NUnit.Framework.DescriptionAttribute("Setup")] - public partial class SetupFeature - { - - private static TechTalk.SpecFlow.ITestRunner testRunner; - -#line 1 "Setup.feature" -#line hidden - - [NUnit.Framework.TestFixtureSetUpAttribute()] - public virtual void FeatureSetup() - { - testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner(); - TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "Setup", "In order to install orchard\r\nAs a new user\r\nI want to setup a new site from the d" + - "efault screen", ((string[])(null))); - testRunner.OnFeatureStart(featureInfo); - } - - [NUnit.Framework.TestFixtureTearDownAttribute()] - public virtual void FeatureTearDown() - { - testRunner.OnFeatureEnd(); - testRunner = null; - } - - public virtual void ScenarioSetup(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) - { - testRunner.OnScenarioStart(scenarioInfo); - } - - [NUnit.Framework.TearDownAttribute()] - public virtual void ScenarioTearDown() - { - testRunner.OnScenarioEnd(); - } - - [NUnit.Framework.TestAttribute()] - [NUnit.Framework.DescriptionAttribute("Root request shows setup form")] - public virtual void RootRequestShowsSetupForm() - { - TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Root request shows setup form", ((string[])(null))); -#line 6 -this.ScenarioSetup(scenarioInfo); -#line 7 - testRunner.Given("I have a clean site"); -#line 8 - testRunner.And("I have module \"Orchard.Setup\""); -#line 9 - testRunner.And("I have theme \"SafeMode\""); -#line 10 - testRunner.When("I go to \"/Default.aspx\""); -#line 11 - testRunner.Then("I should see \"Welcome to Orchard\""); -#line 12 - testRunner.And("I should see \"Finish Setup\""); -#line 13 - testRunner.And("the status should be 200 OK"); -#line hidden - testRunner.CollectScenarioErrors(); - } - - [NUnit.Framework.TestAttribute()] - [NUnit.Framework.DescriptionAttribute("Setup folder also shows setup form")] - public virtual void SetupFolderAlsoShowsSetupForm() - { - TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Setup folder also shows setup form", ((string[])(null))); -#line 15 -this.ScenarioSetup(scenarioInfo); -#line 16 - testRunner.Given("I have a clean site"); -#line 17 - testRunner.And("I have module \"Orchard.Setup\""); -#line 18 - testRunner.And("I have theme \"SafeMode\""); -#line 19 - testRunner.When("I go to \"/Setup\""); -#line 20 - testRunner.Then("I should see \"Welcome to Orchard\""); -#line 21 - testRunner.And("I should see \"Finish Setup\""); -#line 22 - testRunner.And("the status should be 200 OK"); -#line hidden - testRunner.CollectScenarioErrors(); - } - } -} +startIndex cannot be larger than length of string. +Parameter name: startIndex \ No newline at end of file diff --git a/src/Orchard.Web/Core/Themes/Module.txt b/src/Orchard.Web/Core/Themes/Module.txt index b6d2a2c96..4b65cc663 100644 --- a/src/Orchard.Web/Core/Themes/Module.txt +++ b/src/Orchard.Web/Core/Themes/Module.txt @@ -1,2 +1,2 @@ name: Themes -antiforgery: enabled \ No newline at end of file +antiforgery: enabled diff --git a/src/Orchard/Environment/Topology/Models/ShellTopology.cs b/src/Orchard/Environment/Topology/Models/ShellTopology.cs index 4cf51c77b..9e99ff500 100644 --- a/src/Orchard/Environment/Topology/Models/ShellTopology.cs +++ b/src/Orchard/Environment/Topology/Models/ShellTopology.cs @@ -5,6 +5,9 @@ using Orchard.Environment.Extensions; using Orchard.Environment.Extensions.Models; namespace Orchard.Environment.Topology.Models { + /// + /// The topology of the shell (humor) + /// public class ShellTopology { public IEnumerable Modules { get; set; } public IEnumerable Dependencies { get; set; } @@ -14,8 +17,7 @@ namespace Orchard.Environment.Topology.Models { public class ShellTopologyItem { public Type Type { get; set; } - public Feature Feature { get; set; } - public FeatureDescriptor FeatureDescriptor { get; set; } + public Feature Feature { get; set; } } public class ModuleTopology : ShellTopologyItem {