mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-14 19:04:51 +08:00
Incremental work towards getting SpecFlow form "POST"
--HG-- branch : dev
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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; }
|
||||||
|
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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"
|
||||||
|
|
||||||
|
105
src/Orchard.Specs/Setup.feature.cs
generated
105
src/Orchard.Specs/Setup.feature.cs
generated
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,2 +1,2 @@
|
|||||||
name: Themes
|
name: Themes
|
||||||
antiforgery: enabled
|
antiforgery: enabled
|
||||||
|
@@ -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 {
|
||||||
|
Reference in New Issue
Block a user