diff --git a/src/FunnelWeb.Extensions.CommentNotification/FunnelWeb.Extensions.CommentNotification.csproj b/src/FunnelWeb.Extensions.CommentNotification/FunnelWeb.Extensions.CommentNotification.csproj
index 7095bac5..9603d6a5 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.1.3\lib\net40\Autofac.dll
diff --git a/src/FunnelWeb.Extensions.CommentNotification/packages.config b/src/FunnelWeb.Extensions.CommentNotification/packages.config
index 842884bd..3d28ef54 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..afec4dc0 100644
--- a/src/FunnelWeb.Tests/App.config
+++ b/src/FunnelWeb.Tests/App.config
@@ -1,30 +1,62 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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/FunnelWeb.Tests.csproj b/src/FunnelWeb.Tests/FunnelWeb.Tests.csproj
index 3a1c3a2d..f0016db8 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,13 +35,16 @@
prompt4x86
+ false
-
- ..\packages\Autofac.2.6.3.862\lib\NET40\Autofac.dll
+
+ False
+ ..\packages\Autofac.3.1.3\lib\net40\Autofac.dll
-
- ..\packages\Autofac.2.6.3.862\lib\NET40\Autofac.Configuration.dll
+
+ False
+ ..\packages\Autofac.Mvc4.3.1.0\lib\net40\Autofac.Integration.Mvc.dllFalse
@@ -85,17 +90,13 @@
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
-
-
- True
- ..\packages\Microsoft.AspNet.Mvc.4.0.20710.0\lib\net40\System.Web.Mvc.dll
-
+ True..\packages\Microsoft.AspNet.Razor.2.0.20710.0\lib\net40\System.Web.Razor.dll
@@ -138,6 +139,7 @@
Properties\VersionInfo.cs
+
@@ -178,9 +180,12 @@
+
+
+
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