diff --git a/README.md b/README.md
index 006fb44c..2fed51b8 100644
--- a/README.md
+++ b/README.md
@@ -52,4 +52,66 @@ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+New features in 2.5 (2015-03-30)
+==========================
+
+* Upgraded the project file to work with Visual Studio 2013 Community Editon
+* The HTML structure of the site mirrors the default WordPress structure, so we can apply WordPress themes to FunnelWeb now
+* Theming engine now supports both WordPress and Bootstrap themes
+* Updated the MVC framework to version 5
+* Included Bootstrap 3 and it's responsive layout (so it's mobile-friendly now)
+* Redesigned the administration page
+* ShowDown and WMD is replaced by a more recent PageDown
+* The HTML is more HTML5-compliant
+* Tested with both SQL and SQL CE databases
+
+Screenshots
+==========
+
+Admin
+--------
+
+
+
+WP-Flower theme
+-----------------------
+
+
+
+
+
+
+
+WP-Hostmarks theme
+----------------------------
+
+
+
+
+
+
+
+WP-Quark theme
+----------------------
+
+
+
+
+
+
+
+WP-Topmag theme
+-------------------------
+
+
+
+
+
+
+
+Further Information
+===============
+If you need some help with .NET, C#, MVC, Entity Framework, HTML5, CSS, JavaScript, jQuery or Bootstrap, you can contact me for one-on-one consultancy or live training on the [Kevin Sharp Traning website](http://www.kevinsharp.net).
diff --git a/docs/screenshots/admin-1.png b/docs/screenshots/admin-1.png
new file mode 100644
index 00000000..1c230666
Binary files /dev/null and b/docs/screenshots/admin-1.png differ
diff --git a/docs/screenshots/admin-2.png b/docs/screenshots/admin-2.png
new file mode 100644
index 00000000..be2d4460
Binary files /dev/null and b/docs/screenshots/admin-2.png differ
diff --git a/docs/screenshots/wp_flower-1.png b/docs/screenshots/wp_flower-1.png
new file mode 100644
index 00000000..7339a3cb
Binary files /dev/null and b/docs/screenshots/wp_flower-1.png differ
diff --git a/docs/screenshots/wp_flower-2.png b/docs/screenshots/wp_flower-2.png
new file mode 100644
index 00000000..9d2b7833
Binary files /dev/null and b/docs/screenshots/wp_flower-2.png differ
diff --git a/docs/screenshots/wp_flower-post-1.png b/docs/screenshots/wp_flower-post-1.png
new file mode 100644
index 00000000..afced774
Binary files /dev/null and b/docs/screenshots/wp_flower-post-1.png differ
diff --git a/docs/screenshots/wp_flower-post-2.png b/docs/screenshots/wp_flower-post-2.png
new file mode 100644
index 00000000..efc5d9eb
Binary files /dev/null and b/docs/screenshots/wp_flower-post-2.png differ
diff --git a/docs/screenshots/wp_flower-post-3.png b/docs/screenshots/wp_flower-post-3.png
new file mode 100644
index 00000000..175af657
Binary files /dev/null and b/docs/screenshots/wp_flower-post-3.png differ
diff --git a/docs/screenshots/wp_flower-post-4.png b/docs/screenshots/wp_flower-post-4.png
new file mode 100644
index 00000000..00f12c82
Binary files /dev/null and b/docs/screenshots/wp_flower-post-4.png differ
diff --git a/docs/screenshots/wp_hostmarks-1.png b/docs/screenshots/wp_hostmarks-1.png
new file mode 100644
index 00000000..4d0c9b53
Binary files /dev/null and b/docs/screenshots/wp_hostmarks-1.png differ
diff --git a/docs/screenshots/wp_hostmarks-2.png b/docs/screenshots/wp_hostmarks-2.png
new file mode 100644
index 00000000..7bf5f68f
Binary files /dev/null and b/docs/screenshots/wp_hostmarks-2.png differ
diff --git a/docs/screenshots/wp_hostmarks-post-1.png b/docs/screenshots/wp_hostmarks-post-1.png
new file mode 100644
index 00000000..27b18d60
Binary files /dev/null and b/docs/screenshots/wp_hostmarks-post-1.png differ
diff --git a/docs/screenshots/wp_hostmarks-post-2.png b/docs/screenshots/wp_hostmarks-post-2.png
new file mode 100644
index 00000000..1125a8ea
Binary files /dev/null and b/docs/screenshots/wp_hostmarks-post-2.png differ
diff --git a/docs/screenshots/wp_hostmarks-post-3.png b/docs/screenshots/wp_hostmarks-post-3.png
new file mode 100644
index 00000000..2b833d3e
Binary files /dev/null and b/docs/screenshots/wp_hostmarks-post-3.png differ
diff --git a/docs/screenshots/wp_hostmarks-post-4.png b/docs/screenshots/wp_hostmarks-post-4.png
new file mode 100644
index 00000000..60454ddd
Binary files /dev/null and b/docs/screenshots/wp_hostmarks-post-4.png differ
diff --git a/docs/screenshots/wp_quark-1.png b/docs/screenshots/wp_quark-1.png
new file mode 100644
index 00000000..3306f8cf
Binary files /dev/null and b/docs/screenshots/wp_quark-1.png differ
diff --git a/docs/screenshots/wp_quark-2.png b/docs/screenshots/wp_quark-2.png
new file mode 100644
index 00000000..4fcbb697
Binary files /dev/null and b/docs/screenshots/wp_quark-2.png differ
diff --git a/docs/screenshots/wp_quark-post-1.png b/docs/screenshots/wp_quark-post-1.png
new file mode 100644
index 00000000..7e126bac
Binary files /dev/null and b/docs/screenshots/wp_quark-post-1.png differ
diff --git a/docs/screenshots/wp_quark-post-2.png b/docs/screenshots/wp_quark-post-2.png
new file mode 100644
index 00000000..e1c46b96
Binary files /dev/null and b/docs/screenshots/wp_quark-post-2.png differ
diff --git a/docs/screenshots/wp_quark-post-3.png b/docs/screenshots/wp_quark-post-3.png
new file mode 100644
index 00000000..496a3477
Binary files /dev/null and b/docs/screenshots/wp_quark-post-3.png differ
diff --git a/docs/screenshots/wp_quark-post-4.png b/docs/screenshots/wp_quark-post-4.png
new file mode 100644
index 00000000..5ffa37cc
Binary files /dev/null and b/docs/screenshots/wp_quark-post-4.png differ
diff --git a/docs/screenshots/wp_topmag-1.png b/docs/screenshots/wp_topmag-1.png
new file mode 100644
index 00000000..43180a41
Binary files /dev/null and b/docs/screenshots/wp_topmag-1.png differ
diff --git a/docs/screenshots/wp_topmag-2.png b/docs/screenshots/wp_topmag-2.png
new file mode 100644
index 00000000..39f11daa
Binary files /dev/null and b/docs/screenshots/wp_topmag-2.png differ
diff --git a/docs/screenshots/wp_topmag-post-1.png b/docs/screenshots/wp_topmag-post-1.png
new file mode 100644
index 00000000..24212b9d
Binary files /dev/null and b/docs/screenshots/wp_topmag-post-1.png differ
diff --git a/docs/screenshots/wp_topmag-post-2.png b/docs/screenshots/wp_topmag-post-2.png
new file mode 100644
index 00000000..c06bc71c
Binary files /dev/null and b/docs/screenshots/wp_topmag-post-2.png differ
diff --git a/docs/screenshots/wp_topmag-post-3.png b/docs/screenshots/wp_topmag-post-3.png
new file mode 100644
index 00000000..6de61ce5
Binary files /dev/null and b/docs/screenshots/wp_topmag-post-3.png differ
diff --git a/docs/screenshots/wp_topmag-post-4.png b/docs/screenshots/wp_topmag-post-4.png
new file mode 100644
index 00000000..558fe523
Binary files /dev/null and b/docs/screenshots/wp_topmag-post-4.png differ
diff --git a/src/.nuget/NuGet.exe b/src/.nuget/NuGet.exe
index 79482f60..8dd7e45a 100644
Binary files a/src/.nuget/NuGet.exe and b/src/.nuget/NuGet.exe differ
diff --git a/src/.nuget/packages.config b/src/.nuget/packages.config
deleted file mode 100644
index 18e0682c..00000000
--- a/src/.nuget/packages.config
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/src/FunnelWeb.Extensions.CommentNotification/FunnelWeb.Extensions.CommentNotification.csproj b/src/FunnelWeb.Extensions.CommentNotification/FunnelWeb.Extensions.CommentNotification.csproj
index 7095bac5..19546178 100644
--- a/src/FunnelWeb.Extensions.CommentNotification/FunnelWeb.Extensions.CommentNotification.csproj
+++ b/src/FunnelWeb.Extensions.CommentNotification/FunnelWeb.Extensions.CommentNotification.csproj
@@ -11,9 +11,10 @@
PropertiesFunnelWeb.Extensions.CommentNotificationFunnelWeb.Extensions.CommentNotification
- v4.0
+ v4.5512..\
+ true
@@ -23,6 +24,7 @@
DEBUG;TRACEprompt4
+ falsepdbonly
@@ -31,13 +33,12 @@
TRACEprompt4
+ false
-
- ..\packages\Autofac.2.6.3.862\lib\NET40\Autofac.dll
-
-
- ..\packages\Autofac.2.6.3.862\lib\NET40\Autofac.Configuration.dll
+
+ False
+ ..\packages\Autofac.3.5.2\lib\net40\Autofac.dll
@@ -66,6 +67,7 @@
+
diff --git a/src/FunnelWeb.Extensions.CommentNotification/app.config b/src/FunnelWeb.Extensions.CommentNotification/app.config
new file mode 100644
index 00000000..6a81556d
--- /dev/null
+++ b/src/FunnelWeb.Extensions.CommentNotification/app.config
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/FunnelWeb.Extensions.CommentNotification/packages.config b/src/FunnelWeb.Extensions.CommentNotification/packages.config
index 842884bd..f9427518 100644
--- a/src/FunnelWeb.Extensions.CommentNotification/packages.config
+++ b/src/FunnelWeb.Extensions.CommentNotification/packages.config
@@ -1,4 +1,4 @@
-
+
\ No newline at end of file
diff --git a/src/FunnelWeb.Tests/App.config b/src/FunnelWeb.Tests/App.config
index c3131a17..bbf5d5b4 100644
--- a/src/FunnelWeb.Tests/App.config
+++ b/src/FunnelWeb.Tests/App.config
@@ -1,30 +1,102 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/FunnelWeb.Tests/Authentication/Internal/SqlFunnelWebMembershipTests.cs b/src/FunnelWeb.Tests/Authentication/Internal/SqlFunnelWebMembershipTests.cs
new file mode 100644
index 00000000..fd8706a7
--- /dev/null
+++ b/src/FunnelWeb.Tests/Authentication/Internal/SqlFunnelWebMembershipTests.cs
@@ -0,0 +1,18 @@
+using System.Diagnostics;
+using FunnelWeb.Authentication.Internal;
+using NUnit.Framework;
+
+namespace FunnelWeb.Tests.Authentication.Internal
+{
+ [TestFixture]
+ public class SqlFunnelWebMembershipTests
+ {
+ [Test]
+ public void HashPassword()
+ {
+ string hashPassword = SqlFunnelWebMembership.HashPassword("ThisIsMyPassword");
+ Debug.WriteLine(hashPassword);
+ Assert.AreEqual("30B8BD5829888900D15D2BBE6270D9BC65B0702F", hashPassword);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/FunnelWeb.Tests/DatabaseDeployer/DatabaseUpgradeDetectorTests.cs b/src/FunnelWeb.Tests/DatabaseDeployer/DatabaseUpgradeDetectorTests.cs
index 96aceff5..51a824cb 100644
--- a/src/FunnelWeb.Tests/DatabaseDeployer/DatabaseUpgradeDetectorTests.cs
+++ b/src/FunnelWeb.Tests/DatabaseDeployer/DatabaseUpgradeDetectorTests.cs
@@ -10,125 +10,127 @@
namespace FunnelWeb.Tests.DatabaseDeployer
{
- [TestFixture]
- public class DatabaseUpgradeDetectorTests
- {
- private DatabaseUpgradeDetector detector;
- private IConnectionStringSettings connectionString;
- private IApplicationDatabase applicationDatabase;
- private readonly List extensions = new List();
- private IDatabaseProvider databaseProvider;
-
- [SetUp]
- public void SetUp()
- {
- connectionString = Substitute.For();
- connectionString.Schema = "dbo";
- connectionString.DatabaseProvider = "sql";
- applicationDatabase = Substitute.For();
- databaseProvider = Substitute.For();
- detector = new DatabaseUpgradeDetector(connectionString, extensions, applicationDatabase, databaseProvider);
- }
-
- [Test]
- public void UpdateNeededIfDatabaseIsOffline()
- {
- DatabaseIsOnline(false);
-
- var needed = detector.UpdateNeeded();
- Assert.IsTrue(needed);
- }
-
- [Test]
- public void UpdateNeededIfMainDatabaseIsOld()
- {
- DatabaseIsOnline(true);
- CurrentSchemaVersionIs(10);
- RequiredApplicationVersionIs(20);
-
- var needed = detector.UpdateNeeded();
- Assert.IsTrue(needed);
- }
-
- [Test]
- public void UpdateNotNeededIfUpToDate()
- {
- DatabaseIsOnline(true);
- CurrentSchemaVersionIs(10);
- RequiredApplicationVersionIs(10);
-
- var needed = detector.UpdateNeeded();
- Assert.IsFalse(needed);
- }
-
- [Test]
- public void UpdateNeededIfExtensionsOld()
- {
- extensions.Add(new ScriptedExtension("XYZ", null, Substitute.For()));
-
- DatabaseIsOnline(true);
- CurrentSchemaVersionIs(10);
- RequiredApplicationVersionIs(10);
- CurrentExtensionVersionIs(1);
- RequiredExtensionVersionIs(4);
-
- var needed = detector.UpdateNeeded();
- Assert.IsTrue(needed);
- }
-
- [Test]
- public void UpdateNotNeededIfExtensionsUpToDate()
- {
- extensions.Add(new ScriptedExtension("XYZ", null, Substitute.For()));
-
- DatabaseIsOnline(true);
- CurrentSchemaVersionIs(10);
- RequiredApplicationVersionIs(10);
- CurrentExtensionVersionIs(4);
- RequiredExtensionVersionIs(4);
-
- var needed = detector.UpdateNeeded();
- Assert.IsFalse(needed);
- }
-
- #region Helpers
-
- private void DatabaseIsOnline(bool isItReally)
- {
- string message;
- databaseProvider
- .TryConnect(Arg.Any(), out message)
- .Returns(isItReally);
- }
-
- private void CurrentSchemaVersionIs(int version)
- {
- applicationDatabase
- .GetCoreExecutedScripts(Arg.Any>())
- .Returns(Enumerable.Range(1, version).Select(x => "Script" + x + ".sql").ToArray());
- }
-
- private void RequiredApplicationVersionIs(int version)
- {
- applicationDatabase
- .GetCoreRequiredScripts(Arg.Any>())
- .Returns(Enumerable.Range(1, version).Select(x => "Script" + x + ".sql").ToArray());
- }
-
- private void CurrentExtensionVersionIs(int version)
- {
- applicationDatabase
- .GetExtensionExecutedScripts(Arg.Any>(), Arg.Any())
- .Returns(Enumerable.Range(1, version).Select(x => "Script" + x + ".sql").ToArray());
- }
-
- private void RequiredExtensionVersionIs(int version)
- {
- applicationDatabase
- .GetExtensionRequiredScripts(Arg.Any>(), Arg.Any())
- .Returns(Enumerable.Range(1, version).Select(x => "Script" + x + ".sql").ToArray());
- }
-
- #endregion
- }
+ [TestFixture]
+ public class DatabaseUpgradeDetectorTests
+ {
+ private DatabaseUpgradeDetector detector;
+ private IConnectionStringSettings connectionString;
+ private IApplicationDatabase applicationDatabase;
+ private readonly List extensions = new List();
+ private IDatabaseProvider databaseProvider;
+ private IDatabaseConnectionDetector databaseConnectionDetector;
+
+ [SetUp]
+ public void SetUp()
+ {
+ connectionString = Substitute.For();
+ connectionString.Schema = "dbo";
+ connectionString.DatabaseProvider = "sql";
+ applicationDatabase = Substitute.For();
+ databaseProvider = Substitute.For();
+ databaseConnectionDetector = Substitute.For();
+ detector = new DatabaseUpgradeDetector(connectionString, extensions, applicationDatabase, databaseProvider, databaseConnectionDetector);
+ }
+
+ [Test]
+ public void UpdateNeededIfDatabaseIsOffline()
+ {
+ DatabaseIsOnline(false);
+
+ var needed = detector.UpdateNeeded();
+ Assert.IsTrue(needed);
+ }
+
+ [Test]
+ public void UpdateNeededIfMainDatabaseIsOld()
+ {
+ DatabaseIsOnline(true);
+ CurrentSchemaVersionIs(10);
+ RequiredApplicationVersionIs(20);
+
+ var needed = detector.UpdateNeeded();
+ Assert.IsTrue(needed);
+ }
+
+ [Test]
+ public void UpdateNotNeededIfUpToDate()
+ {
+ DatabaseIsOnline(true);
+ CurrentSchemaVersionIs(10);
+ RequiredApplicationVersionIs(10);
+
+ var needed = detector.UpdateNeeded();
+ Assert.IsFalse(needed);
+ }
+
+ [Test]
+ public void UpdateNeededIfExtensionsOld()
+ {
+ extensions.Add(new ScriptedExtension("XYZ", null, Substitute.For()));
+
+ DatabaseIsOnline(true);
+ CurrentSchemaVersionIs(10);
+ RequiredApplicationVersionIs(10);
+ CurrentExtensionVersionIs(1);
+ RequiredExtensionVersionIs(4);
+
+ var needed = detector.UpdateNeeded();
+ Assert.IsTrue(needed);
+ }
+
+ [Test]
+ public void UpdateNotNeededIfExtensionsUpToDate()
+ {
+ extensions.Add(new ScriptedExtension("XYZ", null, Substitute.For()));
+
+ DatabaseIsOnline(true);
+ CurrentSchemaVersionIs(10);
+ RequiredApplicationVersionIs(10);
+ CurrentExtensionVersionIs(4);
+ RequiredExtensionVersionIs(4);
+
+ var needed = detector.UpdateNeeded();
+ Assert.IsFalse(needed);
+ }
+
+ #region Helpers
+
+ private void DatabaseIsOnline(bool isItReally)
+ {
+ string message;
+ databaseProvider
+ .TryConnect(Arg.Any(), out message)
+ .Returns(isItReally);
+ }
+
+ private void CurrentSchemaVersionIs(int version)
+ {
+ applicationDatabase
+ .GetCoreExecutedScripts(Arg.Any>())
+ .Returns(Enumerable.Range(1, version).Select(x => "Script" + x + ".sql").ToArray());
+ }
+
+ private void RequiredApplicationVersionIs(int version)
+ {
+ applicationDatabase
+ .GetCoreRequiredScripts(Arg.Any>())
+ .Returns(Enumerable.Range(1, version).Select(x => "Script" + x + ".sql").ToArray());
+ }
+
+ private void CurrentExtensionVersionIs(int version)
+ {
+ applicationDatabase
+ .GetExtensionExecutedScripts(Arg.Any>(), Arg.Any())
+ .Returns(Enumerable.Range(1, version).Select(x => "Script" + x + ".sql").ToArray());
+ }
+
+ private void RequiredExtensionVersionIs(int version)
+ {
+ applicationDatabase
+ .GetExtensionRequiredScripts(Arg.Any>(), Arg.Any())
+ .Returns(Enumerable.Range(1, version).Select(x => "Script" + x + ".sql").ToArray());
+ }
+
+ #endregion
+ }
}
diff --git a/src/FunnelWeb.Tests/DatabaseDeployer/FunnelWebScriptProviderTests.cs b/src/FunnelWeb.Tests/DatabaseDeployer/FunnelWebScriptProviderTests.cs
index 934aabac..1fb61265 100644
--- a/src/FunnelWeb.Tests/DatabaseDeployer/FunnelWebScriptProviderTests.cs
+++ b/src/FunnelWeb.Tests/DatabaseDeployer/FunnelWebScriptProviderTests.cs
@@ -27,7 +27,7 @@ public void WhenDatabaseProviderSpecificScriptIsPresentIgnoreGeneric()
});
var scriptProvider = new FunnelWebScriptProvider(assembly, s=>s.StartsWith("Script"), "sqlce");
- var scripts = scriptProvider.GetScripts(()=>null).ToList();
+ var scripts = scriptProvider.GetScripts(null).ToList();
Assert.AreEqual("Script0001_sqlce.sql", scripts.Single(s => s.Name == "Script0001_sqlce.sql").Name);
Assert.AreEqual("Script0002.sql", scripts.Single(s => s.Name == "Script0002.sql").Name);
diff --git a/src/FunnelWeb.Tests/FunnelWeb.Tests.csproj b/src/FunnelWeb.Tests/FunnelWeb.Tests.csproj
index 3a1c3a2d..43a2317e 100644
--- a/src/FunnelWeb.Tests/FunnelWeb.Tests.csproj
+++ b/src/FunnelWeb.Tests/FunnelWeb.Tests.csproj
@@ -11,9 +11,10 @@
PropertiesFunnelWeb.TestsFunnelWeb.Tests
- v4.0
+ v4.5512..\
+ true
@@ -24,6 +25,7 @@
prompt4x86
+ falsepdbonly
@@ -33,29 +35,32 @@
prompt4x86
+ false
-
- ..\packages\Autofac.2.6.3.862\lib\NET40\Autofac.dll
+
+ False
+ ..\packages\Autofac.3.5.2\lib\net40\Autofac.dll
-
- ..\packages\Autofac.2.6.3.862\lib\NET40\Autofac.Configuration.dll
+
+ False
+ ..\packages\Autofac.Mvc5.3.3.3\lib\net45\Autofac.Integration.Mvc.dll
-
+ False
- ..\packages\dbup.2.0.113\lib\NET35\DbUp.dll
+ ..\packages\dbup.3.2.1\lib\NET35\DbUp.dll
-
+ False
- ..\packages\dbup-sqlce.2.0.113\lib\NET35\DbUp.SqlCe.dll
+ ..\packages\dbup-sqlce.3.2.1\lib\NET35\DbUp.SqlCe.dll
-
+ False
- ..\packages\FluentNHibernate.1.3.0.733\lib\FluentNHibernate.dll
+ ..\packages\FluentNHibernate.2.0.1.0\lib\net40\FluentNHibernate.dll
-
+ False
- ..\packages\Iesi.Collections.3.3.2.4000\lib\Net35\Iesi.Collections.dll
+ ..\packages\Iesi.Collections.4.0.1.4000\lib\net40\Iesi.Collections.dllFalse
@@ -67,13 +72,13 @@
True..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll
-
+ False
- ..\packages\NHibernate.3.3.2.4000\lib\Net35\NHibernate.dll
+ ..\packages\NHibernate.4.0.3.4000\lib\net40\NHibernate.dll
-
+ False
- ..\packages\NSubstitute.1.4.3.0\lib\NET40\NSubstitute.dll
+ ..\packages\NSubstitute.1.8.1.0\lib\net45\NSubstitute.dll..\..\lib\NUnit\nunit.framework.dll
@@ -85,32 +90,35 @@
True..\packages\Microsoft.SqlServer.Compact.4.0.8876.1\lib\net40\System.Data.SqlServerCe.dll
+
+ ..\packages\System.IdentityModel.Tokens.ValidatingIssuerNameRegistry.4.5.1\lib\net45\System.IdentityModel.Tokens.ValidatingIssuerNameRegistry.dll
+
-
- True
- ..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.Helpers.dll
+
+ False
+ ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll
-
- True
- ..\packages\Microsoft.AspNet.Mvc.4.0.20710.0\lib\net40\System.Web.Mvc.dll
+
+ False
+ ..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll
-
- True
- ..\packages\Microsoft.AspNet.Razor.2.0.20710.0\lib\net40\System.Web.Razor.dll
+
+ False
+ ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll
-
- True
- ..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.WebPages.dll
+
+ False
+ ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll
-
- True
- ..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.WebPages.Deployment.dll
+
+ False
+ ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll
-
- True
- ..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.WebPages.Razor.dll
+
+ False
+ ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll
@@ -138,6 +146,7 @@
Properties\VersionInfo.cs
+
@@ -178,9 +187,12 @@
+
+
+
diff --git a/src/FunnelWeb.Tests/Helpers/SqlCeTemporaryDatabase.cs b/src/FunnelWeb.Tests/Helpers/SqlCeTemporaryDatabase.cs
index 6225538d..6590353d 100644
--- a/src/FunnelWeb.Tests/Helpers/SqlCeTemporaryDatabase.cs
+++ b/src/FunnelWeb.Tests/Helpers/SqlCeTemporaryDatabase.cs
@@ -31,7 +31,7 @@ public SqlCeTemporaryDatabase()
databaseFile = Path.Combine(Path.GetTempPath(), "FunnelWeb.sdf");
connectionString = string.Format("Data Source={0}; Persist Security Info=False", databaseFile);
- database = new AdHocSqlRunner(() => new SqlCeConnection(connectionString), null);
+ database = new AdHocSqlRunner(() => new SqlCeConnection(connectionString).CreateCommand(), null);
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterInstance(this).As();
diff --git a/src/FunnelWeb.Tests/LifetimeJustifications.cs b/src/FunnelWeb.Tests/LifetimeJustifications.cs
index 0bbb18b3..43e15789 100644
--- a/src/FunnelWeb.Tests/LifetimeJustifications.cs
+++ b/src/FunnelWeb.Tests/LifetimeJustifications.cs
@@ -31,181 +31,185 @@
namespace FunnelWeb.Tests
{
- ///
- /// We often get subtle bugs introduced by changes to lifetime assumptions. The following test fixture
- /// is a cross-cutting fixture that explains the assumptions behind why every component has the lifetime
- /// it is given.
- ///
- /// If you want to change any of these lifetimes, please consider it very carefully, and test thoroughly.
- ///
- [TestFixture]
- public class LifetimeJustifications
- {
- private IContainer container;
-
- ///
- /// We could set up a re-usable class that calls this, but configuration is pretty important
- /// and I'd rather make it obvious in Global.asax.
- ///
- [SetUp]
- public void IntializeTheContainerJustLikeInGlobalAsax()
- {
- var builder = new ContainerBuilder();
-
- var routes = new RouteCollection();
-
- // FunnelWeb Database
- builder.RegisterModule(new DatabaseModule());
-
- //// FunnelWeb Core
- builder.RegisterModule(new SettingsModule("C:\\Foo\\My.config"));
- builder.RegisterModule(new TasksModule()); // HACK: Need a better way to enable the TasksModule to create lifetime scopes from the root
- builder.RegisterModule(new RepositoriesModule());
- builder.RegisterModule(new EventingModule());
- builder.RegisterModule(new ExtensionsModule("C:\\Foo", routes));
- builder.RegisterModule(new InternalProviderRegistrationModule());
-
- //// FunnelWeb Web
- builder.RegisterModule(new WebAbstractionsModule());
- builder.RegisterModule(new AuthenticationModule());
- builder.RegisterModule(new BindersModule(new ModelBinderDictionary()));
- builder.RegisterModule(new MimeSupportModule());
- builder.RegisterModule(new ThemesModule());
- builder.RegisterModule(new SpamModule());
- // We don't register the RoutesModule, because all it does is register MVC routes, not components
- // with lifetimes that we care about
-
- container = builder.Build();
- }
-
- [Test]
- public void ComponentsThatShouldBeSingletons()
- {
- // There is a very strong case for the following lifetimes to never be changed
- IsSingleton("There should only be one session factory in the application");
- IsSingleton("This component only executes SQL queries, and it takes the connection string as a parameter - therefore, only one instance should exist");
- IsSingleton("This component helps optimize performance by caching the result of whether the database is up to date. Making this component anything but singleton would make the caching benefit useless.");
-
- // The following are singletons just because there's no need to have more than one - if you have a good
- // reason feel free to change
- IsSingleton("This component uses the bootstrap settings to store the connection string. Since the bootstrap settings are opened/closed on the fly, there only needs to be one instance of this type.");
- IsSingleton("This component opens/closes the XML file on the fly; there's no need to have more than one.");
- IsSingleton("It just calls the registry/a static list - no need for more than one");
-
- IsSingleton>("This type extends the contains, no need to create multiple times");
- IsSingleton>("This type extends the contains, no need to create multiple times");
- IsSingleton("Settings provider provides caching, it depends on a factory to connect to the database which will be scoped to http request.");
- }
-
- [Test]
- public void ComponentsThatShouldBePerRequest()
- {
- // You will never have any possible reason to change this. Just don't. I kill you!
- PerLifetimeScope("There should be at most one session per request. Repositories depend on an ISession. Since a controller may use many repositories, all repositories need the same session.");
-
- // Per request just for performance, could otherwise be per dependency
- PerLifetimeScope("This component takes a list of handlers; to save finding them all each time we raise an event during a request, we build this once");
- PerLifetimeScope("Could be anything, but authentication should be done per request");
- PerLifetimeScope("Could be anything, but authentication should be done per request.");
- PerLifetimeScope("Could be anything, but authentication should be done per request.");
- PerLifetimeScope("The forms authenticator just calls into ASP.NET code - may as well re-use the same instance in a request.");
- PerLifetimeScope("The forms authenticator just calls into ASP.NET code - may as well re-use the same instance in a request.");
- PerLifetimeScope("The forms authenticator just calls into ASP.NET code - may as well re-use the same instance in a request.");
- PerLifetimeScope("Uses factories to get as session, so could be anything really.");
- PerLifetimeScope("Uses factories to get as session, so could be anything really.");
- PerLifetimeScope("Uses factories to get as session, so could be anything really.");
-
- // HTTP abstractions, therefore obviously per request
- PerLifetimeScope("Comes from HTTP context");
- PerLifetimeScope("Comes from HTTP context");
- PerLifetimeScope("Comes from HTTP context");
-
- // Repositories
- const string repositoriesReason = "Generally we're unlikely to resolve repositories more than once per request, and they are stateless anyway, but just in case let's re-use the same one";
- PerLifetimeScope(repositoriesReason);
- PerLifetimeScope(repositoriesReason);
- PerLifetimeScope(repositoriesReason);
- PerLifetimeScope(repositoriesReason);
- }
-
- [Test]
- public void ComponentsThatShouldBePerDependency()
- {
- // Could be per lifetime or per dependency (but definitely not singletons)
- IsPerDependency("Tasks are created in their own lifetime scope, but only one is ever created, therefore they should all be per dependency.");
- IsPerDependency("This component just scans files in a directory, lifetime doesn't matter. It does use HttpContext though, so it shouldn't be singleton");
- IsPerDependency("This component depends on settings (which depends on an ISession), so it definitely shouldn't be singleton");
- }
-
- [Test]
- public void AllComponentLifetimesHaveBeenJustified()
- {
- ComponentsThatShouldBeSingletons();
- ComponentsThatShouldBePerRequest();
- ComponentsThatShouldBePerDependency();
-
- var typesNotTested = container.ComponentRegistry.Registrations
- .SelectMany(x => x.Services.OfType().Select(t => new { t.ServiceType, Registration = x}) )
- .Where(x => !seenTypes.Any(s => s == x.ServiceType))
- .Where(x => !x.ServiceType.Namespace.StartsWith("Autofac")) // We don't care about internal Autofac types
- .Where(x => !x.ServiceType.FullName.Contains("IContainerAwareComponent")) // Some whacky Autofac thing I don't understand
- //new version of autofac seems to register a IEnumerable
- .Where(x => x.ServiceType.IsGenericType && !x.ServiceType.GetGenericArguments()[0].Namespace.StartsWith("Autofac"))
- .ToList();
-
- if (typesNotTested.Count == 0)
- return;
-
- Trace.WriteLine("Forgot to test assumptions around the following types:");
- foreach (var item in typesNotTested)
- {
- Trace.WriteLine(" - " + item.ServiceType.FullName + " (current lifetime: " + GetLifetime(item.Registration) + ")");
- }
-
- Assert.Fail("One or more types don't have tests regarding their lifetime. Please justify the lifetime of all components. See the trace output of this test to see the types that weren't tested.");
- }
-
- #region Helpers
-
- private void IsSingleton(string reason)
- {
- Is(reason, x => GetLifetime(x) == "Singleton");
- }
-
- private void PerLifetimeScope(string reason)
- {
- Is(reason, x => GetLifetime(x) == "PerLifetimeScope");
- }
-
- private void IsPerDependency(string reason)
- {
- Is(reason, x => GetLifetime(x) == "PerDependency");
- }
-
- private string GetLifetime(IComponentRegistration rego)
- {
- if (rego.Lifetime is RootScopeLifetime)
- {
- return "Singleton";
- }
- if (rego.Lifetime is CurrentScopeLifetime && rego.Sharing == InstanceSharing.Shared)
- {
- return "PerLifetimeScope";
- }
- return "PerDependency";
- }
-
- private void Is(string reason, Func predicate)
- {
- var regos = container.ComponentRegistry.RegistrationsFor(new TypedService(typeof(TService)));
- if (!regos.Any(predicate))
- Assert.Fail(string.Format("Component {0} is not registered correctly: {1}", typeof(TService).Name, reason));
-
- seenTypes.Add(typeof (TService));
- }
-
- private readonly HashSet seenTypes = new HashSet();
-
- #endregion
- }
-}
+ ///
+ /// We often get subtle bugs introduced by changes to lifetime assumptions. The following test fixture
+ /// is a cross-cutting fixture that explains the assumptions behind why every component has the lifetime
+ /// it is given.
+ ///
+ /// If you want to change any of these lifetimes, please consider it very carefully, and test thoroughly.
+ ///
+ [TestFixture]
+ public class LifetimeJustifications
+ {
+ private IContainer container;
+
+ ///
+ /// We could set up a re-usable class that calls this, but configuration is pretty important
+ /// and I'd rather make it obvious in Global.asax.
+ ///
+ [SetUp]
+ public void IntializeTheContainerJustLikeInGlobalAsax()
+ {
+ var builder = new ContainerBuilder();
+
+ var routes = new RouteCollection();
+
+ // FunnelWeb Database
+ builder.RegisterModule(new DatabaseModule());
+
+ //// FunnelWeb Core
+ builder.RegisterModule(new SettingsModule("C:\\Foo\\My.config"));
+ builder.RegisterModule(new TasksModule()); // HACK: Need a better way to enable the TasksModule to create lifetime scopes from the root
+ builder.RegisterModule(new RepositoriesModule());
+ builder.RegisterModule(new EventingModule());
+ builder.RegisterModule(new ExtensionsModule("C:\\Foo", routes));
+ builder.RegisterModule(new InternalProviderRegistrationModule());
+
+ //// FunnelWeb Web
+ builder.RegisterModule(new WebAbstractionsModule());
+ builder.RegisterModule(new AuthenticationModule());
+ builder.RegisterModule(new BindersModule(new ModelBinderDictionary()));
+ builder.RegisterModule(new MimeSupportModule());
+ builder.RegisterModule(new ThemesModule());
+ builder.RegisterModule(new SpamModule());
+ // We don't register the RoutesModule, because all it does is register MVC routes, not components
+ // with lifetimes that we care about
+
+ container = builder.Build();
+ }
+
+ [Test]
+ public void ComponentsThatShouldBeSingletons()
+ {
+ // There is a very strong case for the following lifetimes to never be changed
+ IsSingleton("There should only be one session factory in the application");
+ IsSingleton("This component only executes SQL queries, and it takes the connection string as a parameter - therefore, only one instance should exist");
+ IsSingleton("This component helps optimize performance by caching the result of whether the database is up to date. Making this component anything but singleton would make the caching benefit useless.");
+
+ // The following are singletons just because there's no need to have more than one - if you have a good
+ // reason feel free to change
+ IsSingleton("This component uses the bootstrap settings to store the connection string. Since the bootstrap settings are opened/closed on the fly, there only needs to be one instance of this type.");
+ IsSingleton("This component opens/closes the XML file on the fly; there's no need to have more than one.");
+ IsSingleton("It just calls the registry/a static list - no need for more than one");
+
+ IsSingleton>("This type extends the contains, no need to create multiple times");
+ IsSingleton>("This type extends the contains, no need to create multiple times");
+ IsSingleton("Settings provider provides caching, it depends on a factory to connect to the database which will be scoped to http request.");
+ }
+
+ [Test]
+ public void ComponentsThatShouldBePerRequest()
+ {
+ // You will never have any possible reason to change this. Just don't. I kill you!
+ PerLifetimeScope("There should be at most one session per request. Repositories depend on an ISession. Since a controller may use many repositories, all repositories need the same session.");
+
+ // Per request just for performance, could otherwise be per dependency
+ PerLifetimeScope("This component takes a list of handlers; to save finding them all each time we raise an event during a request, we build this once");
+ PerLifetimeScope("Could be anything, but authentication should be done per request");
+ PerLifetimeScope("Could be anything, but authentication should be done per request.");
+ PerLifetimeScope("Could be anything, but authentication should be done per request.");
+ PerLifetimeScope("The forms authenticator just calls into ASP.NET code - may as well re-use the same instance in a request.");
+ PerLifetimeScope("The forms authenticator just calls into ASP.NET code - may as well re-use the same instance in a request.");
+ PerLifetimeScope("The forms authenticator just calls into ASP.NET code - may as well re-use the same instance in a request.");
+ //PerLifetimeScope("Uses factories to get as session, so could be anything really.");
+ PerLifetimeScope("Uses factories to get as session, so could be anything really.");
+ //PerLifetimeScope("Uses factories to get as session, so could be anything really.");
+ PerLifetimeScope("Uses factories to get as session, so could be anything really.");
+ PerLifetimeScope("Uses factories to get as session, so could be anything really.");
+
+ // HTTP abstractions, therefore obviously per request
+ PerLifetimeScope("Comes from HTTP context");
+ PerLifetimeScope("Comes from HTTP context");
+ PerLifetimeScope("Comes from HTTP context");
+
+ // Repositories
+ const string repositoriesReason = "Generally we're unlikely to resolve repositories more than once per request, and they are stateless anyway, but just in case let's re-use the same one";
+ PerLifetimeScope(repositoriesReason);
+ PerLifetimeScope(repositoriesReason);
+ PerLifetimeScope(repositoriesReason);
+ PerLifetimeScope(repositoriesReason);
+ }
+
+ [Test]
+ public void ComponentsThatShouldBePerDependency()
+ {
+ // Could be per lifetime or per dependency (but definitely not singletons)
+ IsPerDependency("Tasks are created in their own lifetime scope, but only one is ever created, therefore they should all be per dependency.");
+ IsPerDependency("This component just scans files in a directory, lifetime doesn't matter. It does use HttpContext though, so it shouldn't be singleton");
+ IsPerDependency("This component depends on settings (which depends on an ISession), so it definitely shouldn't be singleton");
+ }
+
+ [Test]
+ public void AllComponentLifetimesHaveBeenJustified()
+ {
+ ComponentsThatShouldBeSingletons();
+ ComponentsThatShouldBePerRequest();
+ ComponentsThatShouldBePerDependency();
+
+ var typesNotTested = container.ComponentRegistry.Registrations
+ .SelectMany(x => x.Services.OfType().Select(t => new { t.ServiceType, Registration = x }))
+ .Where(x => seenTypes.All(s => s != x.ServiceType))
+ // ReSharper disable once PossibleNullReferenceException
+ .Where(x => !x.ServiceType.Namespace.StartsWith("Autofac")) // We don't care about internal Autofac types
+ .Where(x => !x.ServiceType.FullName.Contains("IContainerAwareComponent")) // Some whacky Autofac thing I don't understand
+ //new version of autofac seems to register a IEnumerable
+ // ReSharper disable once PossibleNullReferenceException
+ .Where(x => x.ServiceType.IsGenericType && !x.ServiceType.GetGenericArguments()[0].Namespace.StartsWith("Autofac"))
+ .ToList();
+
+ if (typesNotTested.Count == 0)
+ return;
+
+ Trace.WriteLine("Forgot to test assumptions around the following types:");
+ foreach (var item in typesNotTested)
+ {
+ Trace.WriteLine(" - " + item.ServiceType.FullName + " (current lifetime: " + GetLifetime(item.Registration) + ")");
+ }
+
+ Assert.Fail("One or more types don't have tests regarding their lifetime. Please justify the lifetime of all components. See the trace output of this test to see the types that weren't tested.");
+ }
+
+ private void IsSingleton(string reason)
+ {
+ Is(reason, x => GetLifetime(x) == "Singleton");
+ }
+
+ private void PerLifetimeScope(string reason)
+ {
+ Is(reason, x => GetLifetime(x) == "PerLifetimeScope");
+ }
+
+ private void IsPerDependency(string reason)
+ {
+ Is(reason, x => GetLifetime(x) == "PerDependency");
+ }
+
+ private string GetLifetime(IComponentRegistration rego)
+ {
+ if (rego.Lifetime is RootScopeLifetime)
+ {
+ return "Singleton";
+ }
+ if (rego.Lifetime is CurrentScopeLifetime && rego.Sharing == InstanceSharing.Shared)
+ {
+ return "PerLifetimeScope";
+ }
+ return "PerDependency";
+ }
+
+ //private void Is(string reason, Func predicate)
+ private void Is(string reason, Func predicate)
+ {
+ if (reason == null) throw new ArgumentNullException("reason");
+ if (predicate == null) throw new ArgumentNullException("predicate");
+ var regos = container.ComponentRegistry.RegistrationsFor(new TypedService(typeof(TService)));
+ if (!regos.Any(predicate))
+ {
+ Assert.Fail("Component {0} is not registered correctly: {1}", typeof(TService).Name, reason);
+ }
+ seenTypes.Add(typeof(TService));
+ }
+
+ private readonly HashSet seenTypes = new HashSet();
+ }
+}
\ No newline at end of file
diff --git a/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/AdminControllerTests.cs b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/AdminControllerTests.cs
index 8e6b36c3..fb01e1bc 100644
--- a/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/AdminControllerTests.cs
+++ b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/AdminControllerTests.cs
@@ -10,127 +10,130 @@
namespace FunnelWeb.Tests.Web.Areas.Admin.Controllers
{
- [TestFixture]
- public class AdminControllerTests : ControllerTests
- {
- protected AdminController Controller { get; set; }
- protected IAdminRepository AdminRepository { get; set; }
-
- [SetUp]
- public void SetUp()
- {
- Controller = new AdminController
- {
- AdminRepository = AdminRepository = Substitute.For(),
- ControllerContext = ControllerContext,
- Repository = Repository
- };
- }
-
- [Test]
- public void Index()
- {
- var result = (ViewResult)Controller.Index();
-
- Assert.That(result.ViewName, Is.EqualTo(string.Empty));
- }
-
- [Test]
- public void DeleteComment()
- {
- var entry = Substitute.For();
- entry.Id.Returns(3);
- Repository.Get(0).Returns(new Comment { Entry = entry });
- var result = (RedirectToRouteResult)Controller.DeleteComment(0);
-
- Assert.That(result.RouteValues["Action"], Is.EqualTo("Comments"));
- }
-
- [Test]
- public void DeleteAllSpam()
- {
- var result = (RedirectToRouteResult)Controller.DeleteAllSpam();
-
- Assert.That(result.RouteValues["Action"], Is.EqualTo("Comments"));
- }
-
- [Test]
- public void DeletePingback()
- {
- var result = (RedirectToRouteResult)Controller.DeletePingback(0);
-
- Assert.That(result.RouteValues["Action"], Is.EqualTo("Pingbacks"));
- }
-
- [Test]
- public void ToggleSpam()
- {
- var result = (RedirectToRouteResult)Controller.ToggleSpam(0);
-
- Assert.That(result.RouteValues["Action"], Is.EqualTo("Comments"));
- }
-
- [Test]
- public void ToggePingbackSpam()
- {
- var result = (RedirectToRouteResult)Controller.TogglePingbackSpam(0);
-
- Assert.That(result.RouteValues["Action"], Is.EqualTo("Pingbacks"));
- }
-
- [Test]
- public void DeleteCommentUpdatesCommentCount()
- {
- // arrange
- var entry = Substitute.For();
- entry.Id.Returns(3);
- Repository.Get(0).Returns(new Comment{ Entry = entry});
-
- // act
- Controller.DeleteComment(0);
-
- // assert
- AdminRepository.Received().UpdateCommentCountFor(3);
- }
-
- [Test]
- public void ToggleSpamUpdatesCommentCount()
- {
- // arrange
- var entry = Substitute.For();
- entry.Id.Returns(3);
- Repository.Get(0).Returns(new Comment { Entry = entry });
-
- // act
- Controller.ToggleSpam(0);
-
- // assert
- AdminRepository.Received().UpdateCommentCountFor(3);
- }
-
- [Test]
- public void DeleteAllSpamUpdatesCommentCount()
- {
- // arrange
- var entry = Substitute.For();
- entry.Id.Returns(3);
- var entry2 = Substitute.For();
- entry2.Id.Returns(4);
- var comments = new[]
- {
- new Comment {Entry = entry},
- new Comment {Entry = entry},
- new Comment {Entry = entry2}
- };
- Repository.Find(Arg.Any()).Returns(comments);
-
- // act
- Controller.DeleteAllSpam();
-
- // assert
- Assert.AreEqual(3, Repository.ReceivedCalls().Count(c=>c.GetMethodInfo().Name == "Remove"));
- AdminRepository.Received().UpdateCommentCountFor(3);
- AdminRepository.Received().UpdateCommentCountFor(4);
- }
- }
-}
+ [TestFixture]
+ public class AdminControllerTests : ControllerTests
+ {
+ protected AdminController Controller { get; set; }
+ protected IAdminRepository AdminRepository { get; set; }
+
+ [SetUp]
+ public void SetUp()
+ {
+ TestAuthenticationAndAuthorization.SetTestUserToCurrentPrincipal();
+ CustomResolver.Initiate();
+
+ Controller = new AdminController
+ {
+ AdminRepository = AdminRepository = Substitute.For(),
+ ControllerContext = ControllerContext,
+ Repository = Repository
+ };
+ }
+
+ [Test]
+ public void Index()
+ {
+ var result = (ViewResult)Controller.Index();
+
+ Assert.That(result.ViewName, Is.EqualTo(string.Empty));
+ }
+
+ [Test]
+ public void DeleteComment()
+ {
+ var entry = Substitute.For();
+ entry.Id.Returns(3);
+ Repository.Get(0).Returns(new Comment { Entry = entry });
+ var result = (RedirectToRouteResult)Controller.DeleteComment(0);
+
+ Assert.That(result.RouteValues["Action"], Is.EqualTo("Comments"));
+ }
+
+ [Test]
+ public void DeleteAllSpam()
+ {
+ var result = (RedirectToRouteResult)Controller.DeleteAllSpam();
+
+ Assert.That(result.RouteValues["Action"], Is.EqualTo("Comments"));
+ }
+
+ [Test]
+ public void DeletePingback()
+ {
+ var result = (RedirectToRouteResult)Controller.DeletePingback(0);
+
+ Assert.That(result.RouteValues["Action"], Is.EqualTo("Pingbacks"));
+ }
+
+ [Test]
+ public void ToggleSpam()
+ {
+ var result = (RedirectToRouteResult)Controller.ToggleSpam(0);
+
+ Assert.That(result.RouteValues["Action"], Is.EqualTo("Comments"));
+ }
+
+ [Test]
+ public void ToggePingbackSpam()
+ {
+ var result = (RedirectToRouteResult)Controller.TogglePingbackSpam(0);
+
+ Assert.That(result.RouteValues["Action"], Is.EqualTo("Pingbacks"));
+ }
+
+ [Test]
+ public void DeleteCommentUpdatesCommentCount()
+ {
+ // arrange
+ var entry = Substitute.For();
+ entry.Id.Returns(3);
+ Repository.Get(0).Returns(new Comment { Entry = entry });
+
+ // act
+ Controller.DeleteComment(0);
+
+ // assert
+ AdminRepository.Received().UpdateCommentCountFor(3);
+ }
+
+ [Test]
+ public void ToggleSpamUpdatesCommentCount()
+ {
+ // arrange
+ var entry = Substitute.For();
+ entry.Id.Returns(3);
+ Repository.Get(0).Returns(new Comment { Entry = entry });
+
+ // act
+ Controller.ToggleSpam(0);
+
+ // assert
+ AdminRepository.Received().UpdateCommentCountFor(3);
+ }
+
+ [Test]
+ public void DeleteAllSpamUpdatesCommentCount()
+ {
+ // arrange
+ var entry = Substitute.For();
+ entry.Id.Returns(3);
+ var entry2 = Substitute.For();
+ entry2.Id.Returns(4);
+ var comments = new[]
+ {
+ new Comment {Entry = entry},
+ new Comment {Entry = entry},
+ new Comment {Entry = entry2}
+ };
+ Repository.Find(Arg.Any()).Returns(comments);
+
+ // act
+ Controller.DeleteAllSpam();
+
+ // assert
+ Assert.AreEqual(3, Repository.ReceivedCalls().Count(c => c.GetMethodInfo().Name == "Remove"));
+ AdminRepository.Received().UpdateCommentCountFor(3);
+ AdminRepository.Received().UpdateCommentCountFor(4);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/CustomResolver.cs b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/CustomResolver.cs
new file mode 100644
index 00000000..2fd328ac
--- /dev/null
+++ b/src/FunnelWeb.Tests/Web/Areas/Admin/Controllers/CustomResolver.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Web.Mvc;
+using Autofac;
+using FunnelWeb.Web;
+
+namespace FunnelWeb.Tests.Web.Areas.Admin.Controllers
+{
+ public class CustomResolver : IDependencyResolver
+ {
+ private readonly IContainer container;
+
+ public CustomResolver(IContainer container)
+ {
+ this.container = container;
+ }
+
+ public object GetService(Type serviceType)
+ {
+ return container.Resolve(serviceType);
+ }
+
+ public IEnumerable