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.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<string>)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 == "</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.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; }

View File

@@ -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<string, IEnumerable<string>> 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));
}
}

View File

@@ -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"

View File

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

View File

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

View File

@@ -5,6 +5,9 @@ using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Models;
namespace Orchard.Environment.Topology.Models {
/// <summary>
/// The topology of the shell (humor)
/// </summary>
public class ShellTopology {
public IEnumerable<ModuleTopology> Modules { get; set; }
public IEnumerable<DependencyTopology> 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 {