Incremental work towards getting SpecFlow form "POST"

--HG--
branch : dev
This commit is contained in:
Louis DeJardin
2010-04-20 14:13:14 -07:00
parent 9495f5c557
commit 5b3c213de4
7 changed files with 186 additions and 133 deletions

View File

@@ -6,6 +6,8 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Web; using System.Web;
using System.Web.Hosting; using System.Web.Hosting;
using System.Xml.Linq;
using HtmlAgilityPack;
using Orchard.Specs.Hosting; using Orchard.Specs.Hosting;
using Orchard.Specs.Util; using Orchard.Specs.Util;
using TechTalk.SpecFlow; using TechTalk.SpecFlow;
@@ -16,6 +18,7 @@ namespace Orchard.Specs.Bindings {
public class WebAppHosting { public class WebAppHosting {
private WebHost _webHost; private WebHost _webHost;
private RequestDetails _details; private RequestDetails _details;
private HtmlDocument _doc;
private MessageSink _messages; private MessageSink _messages;
[Given(@"I have a clean site")] [Given(@"I have a clean site")]
@@ -54,20 +57,63 @@ namespace Orchard.Specs.Bindings {
_webHost.CopyExtension("Core", moduleName); _webHost.CopyExtension("Core", moduleName);
} }
[Given(@"I am on ""(.*)""")]
public void GivenIAmOn(string urlPath) {
WhenIGoTo(urlPath);
}
[When(@"I go to ""(.*)""")] [When(@"I go to ""(.*)""")]
public void WhenIGoTo(string urlPath) { public void WhenIGoTo(string urlPath) {
_details = _webHost.SendRequest(urlPath); _details = _webHost.SendRequest(urlPath);
_doc = new HtmlDocument();
_doc.Load(new StringReader(_details.ResponseText));
} }
[When(@"I follow ""(.*)""")] [When(@"I follow ""(.*)""")]
public void WhenIFollow(string linkText) { public void WhenIFollow(string linkText) {
var doc = new HtmlAgilityPack.HtmlDocument(); var link = _doc.DocumentNode
doc.Load(new StringReader(_details.ResponseText)); .SelectNodes("//a")
var link = doc.DocumentNode.SelectNodes("//a").Single(elt => elt.InnerText == linkText); .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<string>)elt);
_details = _webHost.SendRequest(urlPath, inputs);
_doc = new HtmlDocument();
_doc.Load(new StringReader(_details.ResponseText));
}
[Then(@"the status should be (.*) (.*)")] [Then(@"the status should be (.*) (.*)")]
public void ThenTheStatusShouldBe(int statusCode, string statusDescription) { public void ThenTheStatusShouldBe(int statusCode, string statusDescription) {
Assert.That(_details.StatusCode, Is.EqualTo(statusCode)); Assert.That(_details.StatusCode, Is.EqualTo(statusCode));
@@ -84,4 +130,51 @@ namespace Orchard.Specs.Bindings {
ScenarioContext.Current.Pending(); 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 == "</form>");
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<HtmlNode> Children { get; set; }
}
static class HtmlExtensions {
public static IEnumerable<HtmlNode> PreviousSiblingsAndSelf(this HtmlNode node) {
var scan = node;
while (scan != null) {
yield return scan;
scan = scan.PreviousSibling;
}
}
public static IEnumerable<HtmlNode> NextSiblingsAndSelf(this HtmlNode node) {
var scan = node;
while (scan != null) {
yield return scan;
scan = scan.NextSibling;
}
}
}
} }

View File

@@ -2,14 +2,15 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using HtmlAgilityPack;
namespace Orchard.Specs.Hosting namespace Orchard.Specs.Hosting {
{
[Serializable] [Serializable]
public class RequestDetails public class RequestDetails {
{ public string UrlPath { get; set; }
public string Page { get; set; } public string Page { get; set; }
public string Query { get; set; } public string Query { get; set; }
public byte[] PostData { get; set; }
public int StatusCode { get; set; } public int StatusCode { get; set; }
public string StatusDescription { get; set; } public string StatusDescription { get; set; }

View File

@@ -5,25 +5,30 @@ using System.Linq;
using System.Text; using System.Text;
using System.Web; using System.Web;
using System.Web.Hosting; using System.Web.Hosting;
using HtmlAgilityPack;
using Orchard.Specs.Util; using Orchard.Specs.Util;
namespace Orchard.Specs.Hosting namespace Orchard.Specs.Hosting {
{ public static class RequestExtensions {
public static class RequestExtensions public static RequestDetails SendRequest(this WebHost webHost, string urlPath, IDictionary<string, IEnumerable<string>> postData) {
{
public static RequestDetails SendRequest(this WebHost webHost, string urlPath)
{
var physicalPath = Bleroy.FluentPath.Path.Get(webHost.PhysicalDirectory); var physicalPath = Bleroy.FluentPath.Path.Get(webHost.PhysicalDirectory);
var details = new RequestDetails var details = new RequestDetails {
{ UrlPath = urlPath,
Page = physicalPath Page = physicalPath
.Combine(urlPath.TrimStart('/', '\\')) .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(); var output = new StringWriter();
HttpRuntime.ProcessRequest(new Worker(details, output)); HttpRuntime.ProcessRequest(new Worker(details, output));
details.ResponseText = output.ToString(); details.ResponseText = output.ToString();
@@ -32,28 +37,59 @@ namespace Orchard.Specs.Hosting
return details; 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 RequestDetails _details;
private readonly TextWriter _output; private readonly TextWriter _output;
public Worker(RequestDetails details, TextWriter output) public Worker(RequestDetails details, TextWriter output)
: base(details.Page, details.Query, output) : base(details.Page, details.Query, output) {
{
_details = details; _details = details;
_output = output; _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.StatusCode = statusCode;
_details.StatusDescription = statusDescription; _details.StatusDescription = statusDescription;
base.SendStatus(statusCode, 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)); _output.Write(File.ReadAllText(filename));
} }
} }

View File

@@ -20,3 +20,25 @@ Scenario: Setup folder also shows setup form
Then I should see "Welcome to Orchard" Then I should see "Welcome to Orchard"
And I should see "Finish Setup" And I should see "Finish Setup"
And the status should be 200 OK 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"

View File

@@ -1,103 +1,2 @@
// ------------------------------------------------------------------------------ startIndex cannot be larger than length of string.
// <auto-generated> Parameter name: startIndex
// 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.
// </auto-generated>
// ------------------------------------------------------------------------------
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();
}
}
}

View File

@@ -1,2 +1,2 @@
name: Themes name: Themes
antiforgery: enabled antiforgery: enabled

View File

@@ -5,6 +5,9 @@ using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Models; using Orchard.Environment.Extensions.Models;
namespace Orchard.Environment.Topology.Models { namespace Orchard.Environment.Topology.Models {
/// <summary>
/// The topology of the shell (humor)
/// </summary>
public class ShellTopology { public class ShellTopology {
public IEnumerable<ModuleTopology> Modules { get; set; } public IEnumerable<ModuleTopology> Modules { get; set; }
public IEnumerable<DependencyTopology> Dependencies { get; set; } public IEnumerable<DependencyTopology> Dependencies { get; set; }
@@ -14,8 +17,7 @@ namespace Orchard.Environment.Topology.Models {
public class ShellTopologyItem { public class ShellTopologyItem {
public Type Type { get; set; } public Type Type { get; set; }
public Feature Feature { get; set; } public Feature Feature { get; set; }
public FeatureDescriptor FeatureDescriptor { get; set; }
} }
public class ModuleTopology : ShellTopologyItem { public class ModuleTopology : ShellTopologyItem {