diff --git a/.gitignore b/.gitignore index 2f72fc4..2ac0d9e 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,7 @@ build/ *.pidb *.log *.scc +*.orig # Visual C++ cache files ipch/ @@ -75,6 +76,8 @@ _TeamCity* # NCrunch *.ncrunch* .*crunch*.local.xml +*nCrunchTemp* +_NCrunch # Installshield output folder [Ee]xpress/ @@ -154,3 +157,6 @@ $RECYCLE.BIN/ # Mac desktop service store files .DS_Store +/hacapp.web/_NCrunch_hacapp.web/StoredText +*.mdf +*.ldf diff --git a/hacapp.web/Hacapp.Data.Azure/Hacapp.Data.Azure.csproj b/hacapp.web/Hacapp.Data.Azure/Hacapp.Data.Azure.csproj new file mode 100644 index 0000000..232f3b0 --- /dev/null +++ b/hacapp.web/Hacapp.Data.Azure/Hacapp.Data.Azure.csproj @@ -0,0 +1,74 @@ + + + + + Debug + AnyCPU + {EC6BC7B1-C480-4F1D-B0D4-AFE13A90D11D} + Library + Properties + Hacapp.Data.Azure + Hacapp.Data.Azure + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Microsoft.Data.Edm.5.6.0\lib\net40\Microsoft.Data.Edm.dll + + + ..\packages\Microsoft.Data.OData.5.6.0\lib\net40\Microsoft.Data.OData.dll + + + ..\packages\Microsoft.Data.Services.Client.5.6.0\lib\net40\Microsoft.Data.Services.Client.dll + + + ..\packages\Newtonsoft.Json.5.0.8\lib\net45\Newtonsoft.Json.dll + + + + + ..\packages\System.Spatial.5.6.0\lib\net40\System.Spatial.dll + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hacapp.web/Hacapp.Data.Azure/Properties/AssemblyInfo.cs b/hacapp.web/Hacapp.Data.Azure/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..1ff383f --- /dev/null +++ b/hacapp.web/Hacapp.Data.Azure/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Hacapp.Data.Azure")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Hacapp.Data.Azure")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("6248e77c-a6c0-4a1d-a990-524ee180f910")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/hacapp.web/Hacapp.Data.Azure/Team/TeamTableEntity.cs b/hacapp.web/Hacapp.Data.Azure/Team/TeamTableEntity.cs new file mode 100644 index 0000000..67a2910 --- /dev/null +++ b/hacapp.web/Hacapp.Data.Azure/Team/TeamTableEntity.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Hacapp.Data.Azure.Team +{ + class TeamTableEntity + { + } +} diff --git a/hacapp.web/Hacapp.Data.Azure/packages.config b/hacapp.web/Hacapp.Data.Azure/packages.config new file mode 100644 index 0000000..9c529b0 --- /dev/null +++ b/hacapp.web/Hacapp.Data.Azure/packages.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/hacapp.web/Hacapp.Web.Models/App.config b/hacapp.web/Hacapp.Web.Models/App.config new file mode 100644 index 0000000..2fb423e --- /dev/null +++ b/hacapp.web/Hacapp.Web.Models/App.config @@ -0,0 +1,13 @@ + + + + +
+ + + + + + + + \ No newline at end of file diff --git a/hacapp.web/Hacapp.Web.Models/Hacapp.Web.Models.csproj b/hacapp.web/Hacapp.Web.Models/Hacapp.Web.Models.csproj new file mode 100644 index 0000000..7c60ada --- /dev/null +++ b/hacapp.web/Hacapp.Web.Models/Hacapp.Web.Models.csproj @@ -0,0 +1,80 @@ + + + + + Debug + AnyCPU + {E9399E7B-5C32-4B5E-B8F5-BE6CF8AAAF3E} + Library + Properties + Hacapp.Web.Models + Hacapp.Web.Models + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\EntityFramework.6.0.2\lib\net45\EntityFramework.dll + + + ..\packages\EntityFramework.6.0.2\lib\net45\EntityFramework.SqlServer.dll + + + ..\packages\Microsoft.AspNet.Identity.Core.1.0.0\lib\net45\Microsoft.AspNet.Identity.Core.dll + + + ..\packages\Microsoft.AspNet.Identity.EntityFramework.1.0.0\lib\net45\Microsoft.AspNet.Identity.EntityFramework.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hacapp.web/Hacapp.Web.Models/HomePageViewModel.cs b/hacapp.web/Hacapp.Web.Models/HomePageViewModel.cs new file mode 100644 index 0000000..d094c29 --- /dev/null +++ b/hacapp.web/Hacapp.Web.Models/HomePageViewModel.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace Hacapp.Web.Models +{ + /// + /// Model that contains all of the data that will be used on the homepage + /// + public class HomePageViewModel + { + /// + /// Collection of pending memberships for the teams that the current logged in user is the owner of. + /// + public List PendingMemberships { get; set; } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.web/Models/AccountViewModels.cs b/hacapp.web/Hacapp.Web.Models/Identity/AccountViewModels.cs similarity index 91% rename from hacapp.web/hacapp.web/Models/AccountViewModels.cs rename to hacapp.web/Hacapp.Web.Models/Identity/AccountViewModels.cs index e7ec899..067897a 100644 --- a/hacapp.web/hacapp.web/Models/AccountViewModels.cs +++ b/hacapp.web/Hacapp.Web.Models/Identity/AccountViewModels.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace hacapp.web.Models +namespace Hacapp.Web.Models.Identity { public class ExternalLoginConfirmationViewModel { @@ -60,4 +60,10 @@ public class RegisterViewModel [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] public string ConfirmPassword { get; set; } } + + public class ExternalLoginViewModel + { + public string Action { get; set; } + public string ReturnUrl { get; set; } + } } diff --git a/hacapp.web/Hacapp.Web.Models/Identity/UserViewModel.cs b/hacapp.web/Hacapp.Web.Models/Identity/UserViewModel.cs new file mode 100644 index 0000000..512e04b --- /dev/null +++ b/hacapp.web/Hacapp.Web.Models/Identity/UserViewModel.cs @@ -0,0 +1,11 @@ +namespace Hacapp.Web.Models.Identity +{ + public class UserViewModel + { + public string Id { get; set; } + + public string UserName { get; set; } + + public string PreferredEmail { get; set; } + } +} \ No newline at end of file diff --git a/hacapp.web/Hacapp.Web.Models/PendingMembershipModel.cs b/hacapp.web/Hacapp.Web.Models/PendingMembershipModel.cs new file mode 100644 index 0000000..3e691b4 --- /dev/null +++ b/hacapp.web/Hacapp.Web.Models/PendingMembershipModel.cs @@ -0,0 +1,28 @@ +namespace Hacapp.Web.Models +{ + /// + /// Model which represents a users pending membership to a team. + /// + public class PendingMembershipModel + { + /// + /// The id if the user that has the membership pending + /// + public string UserId { get; set; } + + /// + /// The display name for the user who has a pending membership + /// + public string UserName { get; set; } + + /// + /// The id of the team that the user has the pending membership for + /// + public int TeamId { get; set; } + + /// + /// The display name for the team that the user has a pending membership for + /// + public string TeamName { get; set; } + } +} \ No newline at end of file diff --git a/hacapp.web/Hacapp.Web.Models/Properties/AssemblyInfo.cs b/hacapp.web/Hacapp.Web.Models/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..009d086 --- /dev/null +++ b/hacapp.web/Hacapp.Web.Models/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Hacapp.Web.Models")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Hacapp.Web.Models")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("907f5f6d-84b3-4c8d-b495-726c16292fa2")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/hacapp.web/Hacapp.Web.Models/Team/AcceptNewMemberViewModel.cs b/hacapp.web/Hacapp.Web.Models/Team/AcceptNewMemberViewModel.cs new file mode 100644 index 0000000..20a77b8 --- /dev/null +++ b/hacapp.web/Hacapp.Web.Models/Team/AcceptNewMemberViewModel.cs @@ -0,0 +1,8 @@ +namespace Hacapp.Web.Models.Team +{ + public class AcceptNewMemberViewModel + { + public string UserIdToAccept { get; set; } + public int TeamId { get; set; } + } +} \ No newline at end of file diff --git a/hacapp.web/Hacapp.Web.Models/Team/CreateTeamViewModel.cs b/hacapp.web/Hacapp.Web.Models/Team/CreateTeamViewModel.cs new file mode 100644 index 0000000..9697cfc --- /dev/null +++ b/hacapp.web/Hacapp.Web.Models/Team/CreateTeamViewModel.cs @@ -0,0 +1,7 @@ +namespace Hacapp.Web.Models.Team +{ + public class CreateTeamViewModel + { + public string TeamName { get; set; } + } +} diff --git a/hacapp.web/Hacapp.Web.Models/Team/JoinTeamViewModel.cs b/hacapp.web/Hacapp.Web.Models/Team/JoinTeamViewModel.cs new file mode 100644 index 0000000..9401323 --- /dev/null +++ b/hacapp.web/Hacapp.Web.Models/Team/JoinTeamViewModel.cs @@ -0,0 +1,15 @@ +namespace Hacapp.Web.Models.Team +{ + /// + /// Model used when a user wants to join a team + /// + public class JoinTeamViewModel + { + /// + /// The id of the team to join + /// + public int TeamId { get; set; } + } + + +} \ No newline at end of file diff --git a/hacapp.web/Hacapp.Web.Models/Team/RemoveUserFromTeamViewModel.cs b/hacapp.web/Hacapp.Web.Models/Team/RemoveUserFromTeamViewModel.cs new file mode 100644 index 0000000..4efde95 --- /dev/null +++ b/hacapp.web/Hacapp.Web.Models/Team/RemoveUserFromTeamViewModel.cs @@ -0,0 +1,8 @@ +namespace Hacapp.Web.Models.Team +{ + public class RemoveUserFromTeamViewModel + { + public string UserId { get; set; } + public int TeamId { get; set; } + } +} \ No newline at end of file diff --git a/hacapp.web/Hacapp.Web.Models/Team/TeamDetailsViewModel.cs b/hacapp.web/Hacapp.Web.Models/Team/TeamDetailsViewModel.cs new file mode 100644 index 0000000..390be90 --- /dev/null +++ b/hacapp.web/Hacapp.Web.Models/Team/TeamDetailsViewModel.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using Hacapp.Web.Models.Identity; + +namespace Hacapp.Web.Models.Team +{ + /// + /// View model which represents the details of a team + /// + public class TeamDetailsViewModel + { + public int Id { get; set; } + + public string Name { get; set; } + + public UserViewModel Owner { get; set; } + + public bool IsEditable { get; set; } + + public List ConfirmedMembers { get; set; } + + public List PendingMembers { get; set; } + + public List SuspendedMembers { get; set; } + } +} \ No newline at end of file diff --git a/hacapp.web/Hacapp.Web.Models/Team/UpdateMembershipStatusViewModel.cs b/hacapp.web/Hacapp.Web.Models/Team/UpdateMembershipStatusViewModel.cs new file mode 100644 index 0000000..944133d --- /dev/null +++ b/hacapp.web/Hacapp.Web.Models/Team/UpdateMembershipStatusViewModel.cs @@ -0,0 +1,9 @@ +namespace Hacapp.Web.Models.Team +{ + public class UpdateMembershipStatusViewModel + { + public string IdOfUserToModify { get; set; } + public int TeamId { get; set; } + public UserMembershipStatus NewStatus { get; set; } + } +} \ No newline at end of file diff --git a/hacapp.web/Hacapp.Web.Models/Team/UserMembershipStatus.cs b/hacapp.web/Hacapp.Web.Models/Team/UserMembershipStatus.cs new file mode 100644 index 0000000..db3917b --- /dev/null +++ b/hacapp.web/Hacapp.Web.Models/Team/UserMembershipStatus.cs @@ -0,0 +1,10 @@ +namespace Hacapp.Web.Models.Team +{ + public enum UserMembershipStatus + { + Pending, + Confirmed, + Suspended, + Removed + } +} \ No newline at end of file diff --git a/hacapp.web/Hacapp.Web.Models/packages.config b/hacapp.web/Hacapp.Web.Models/packages.config new file mode 100644 index 0000000..c1eaef9 --- /dev/null +++ b/hacapp.web/Hacapp.Web.Models/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/hacapp.web/Haccap.Core.Tests/Commands/CommandAndQueryDispatcherTests.cs b/hacapp.web/Haccap.Core.Tests/Commands/CommandAndQueryDispatcherTests.cs new file mode 100644 index 0000000..9383d8c --- /dev/null +++ b/hacapp.web/Haccap.Core.Tests/Commands/CommandAndQueryDispatcherTests.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using hacapp.contracts.Commands; +using hacapp.core.Commands; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Haccapp.Core.Tests.Commands +{ + [TestClass] + public class CommandAndQueryDispatcherTests + { + [TestClass] + public class TheExecuteMethod + { + [TestMethod] + public void ShouldUseRegisteredCommandHandlerToExecuteCommand() + { + List queryHandlers = + Enumerable.Repeat(new TestQueryHandler1(string.Empty), 1) + .ToList(); + var mockHandler = new TestCommandHandler1(); + ICollection commandHandlers = new List + { + mockHandler + }; + var command = new TestCommand1(); + + var dispatcher = new CommandAndQueryDispatcher(commandHandlers, queryHandlers); + dispatcher.ExecuteCommand(command); + mockHandler.CommandExecuted.Should().Be(command); + } + + [TestMethod] + public void ShouldUseRegisteredCommandHandlerToExecuteCommandWhenMultipelHandlerExist() + { + List queryHandlers = + Enumerable.Repeat(new TestQueryHandler1(string.Empty), 1) + .ToList(); + var mockHandler = new TestCommandHandler1(); + var mockHandler2 = new TestCommandHandler2(); + ICollection commandHandlers = new List + { + mockHandler, + mockHandler2 + }; + var command = new TestCommand1(); + + var dispatcher = new CommandAndQueryDispatcher(commandHandlers, queryHandlers); + dispatcher.ExecuteCommand(command); + mockHandler.CommandExecuted.Should().Be(command); + mockHandler2.CommandExecuted.Should().BeNull(); + } + + [TestMethod] + public void ShouldThrowInvalidOperationExceptionWhenNoCommandHandlerIsRegistered() + { + List queryHandlers = + Enumerable.Repeat(new TestQueryHandler1(string.Empty), 1) + .ToList(); + var mockHandler = new TestCommandHandler1(); + ICollection commandHandlers = new List + { + mockHandler, + }; + var command = new TestCommand2(); + + var dispatcher = new CommandAndQueryDispatcher(commandHandlers, queryHandlers); + Action action = () => dispatcher.ExecuteCommand(command); + action.ShouldThrow(); + } + + [TestMethod] + public void ShouldUseRegisteredQueryHandlerToExecuteQuery() + { + const string expectedResult = "expectedResult"; + var mockHandler = new TestQueryHandler1(expectedResult); + ICollection commandHandlers = + Enumerable.Repeat(new TestCommandHandler1(), 1).ToList(); + ICollection queryHandlers = new List + { + mockHandler + }; + var query = new TestQuery1(); + + var dispatcher = new CommandAndQueryDispatcher(commandHandlers, queryHandlers); + string queryResult = dispatcher.ExecuteQuery(query); + mockHandler.QueryExecuted.Should().Be(query); + queryResult.Should().Be(expectedResult); + } + + [TestMethod] + public void ShouldUseRegisteredQueryHandlerToExecuteQueryWhenMultiple() + { + const string expectedResult = "expectedResult"; + const string expectedResult2 = "expectedResult2"; + var mockHandler = new TestQueryHandler1(expectedResult); + var mockHandler2 = new TestQueryHandler2(expectedResult2); + ICollection commandHandlers = + Enumerable.Repeat(new TestCommandHandler1(), 1).ToList(); + ICollection queryHandlers = new List + { + mockHandler, + mockHandler2 + }; + var query = new TestQuery2(); + + var dispatcher = new CommandAndQueryDispatcher(commandHandlers, queryHandlers); + string queryResult = dispatcher.ExecuteQuery(query); + mockHandler2.QueryExecuted.Should().Be(query); + queryResult.Should().Be(expectedResult2); + mockHandler.QueryExecuted.Should().BeNull(); + } + + [TestMethod] + public void ShouldThrowInvalidOperationExceptionWhenQueryHandlerIsNotRegistered() + { + const string expectedResult = "expectedResult"; + const string expectedResult2 = "expectedResult2"; + var mockHandler = new TestQueryHandler1(expectedResult); + ICollection commandHandlers = + Enumerable.Repeat(new TestCommandHandler1(), 1).ToList(); + ICollection queryHandlers = new List + { + mockHandler, + }; + var query = new TestQuery2(); + + var dispatcher = new CommandAndQueryDispatcher(commandHandlers, queryHandlers); + string queryResult; + Action action = () => queryResult = dispatcher.ExecuteQuery(query); + action.ShouldThrow(); + } + + private class TestQuery1 : IQuery + { + } + + private class TestQuery2 : IQuery + { + } + + private class TestQueryHandler1 : QueryHandlerBase where T : IQuery + { + private readonly TResult expectedResult; + + public TestQueryHandler1(TResult expectedResult) + { + this.expectedResult = expectedResult; + } + + public T QueryExecuted { get; private set; } + + public override TResult Execute(T query, ICommandAndQueryDispatcher dispatcher) + { + QueryExecuted = query; + return expectedResult; + } + } + + private class TestQueryHandler2 : QueryHandlerBase where T : IQuery + { + private readonly TResult expectedResult; + + public TestQueryHandler2(TResult expectedResult) + { + this.expectedResult = expectedResult; + } + + public T QueryExecuted { get; private set; } + + public override TResult Execute(T query, ICommandAndQueryDispatcher dispatcher) + { + QueryExecuted = query; + return expectedResult; + } + } + + #region TestCommands/Handlers + + private class TestCommand1 : ICommand + { + } + + private class TestCommand2 : ICommand + { + } + + private class TestCommandHandler1 : CommandHandlerBase where T : ICommand + { + private T commandExecuted; + + public T CommandExecuted + { + get { return commandExecuted; } + } + + public override void Execute(T command, ICommandAndQueryDispatcher dispatcher) + { + commandExecuted = command; + } + } + + private class TestCommandHandler2 : CommandHandlerBase where T : ICommand + { + private T commandExecuted; + + public T CommandExecuted + { + get { return commandExecuted; } + } + + public override void Execute(T command, ICommandAndQueryDispatcher dispatcher) + { + commandExecuted = command; + } + } + + #endregion + } + } +} \ No newline at end of file diff --git a/hacapp.web/Haccap.Core.Tests/Hacapp.Core.Tests.csproj b/hacapp.web/Haccap.Core.Tests/Hacapp.Core.Tests.csproj new file mode 100644 index 0000000..ecf50fa --- /dev/null +++ b/hacapp.web/Haccap.Core.Tests/Hacapp.Core.Tests.csproj @@ -0,0 +1,105 @@ + + + + Debug + AnyCPU + {474E3ED3-B365-4949-9FEF-5497AF8E3BB4} + Library + Properties + Hacapp.Core.Tests + Haccap.Core.Tests + v4.5 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\FluentAssertions.2.2.0.0\lib\net45\FluentAssertions.dll + + + ..\packages\Moq.4.1.1311.0615\lib\net40\Moq.dll + + + + + + + + + + + + + + + + + + + + + + + + + {0a01520a-83f2-41f3-b2de-b3aaa0a861b4} + Hacapp.Contracts + + + {38a957ed-6237-439d-93ac-220bfd7d0409} + Hacapp.Core + + + + + + + + + + False + + + False + + + False + + + False + + + + + + + + \ No newline at end of file diff --git a/hacapp.web/Haccap.Core.Tests/OptionalTests.cs b/hacapp.web/Haccap.Core.Tests/OptionalTests.cs new file mode 100644 index 0000000..eedc1f3 --- /dev/null +++ b/hacapp.web/Haccap.Core.Tests/OptionalTests.cs @@ -0,0 +1,183 @@ +using System; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Hacapp.Core.Tests +{ + [TestClass] + public class OptionalTests + { + [TestClass] + public class TheConstructorMethod : OptionalTests + { + [TestMethod] + public void ShouldThrowArgumentNullExceptionWhenConstructedWithNull() + { + //Arrange------------------------------------------------ + + //Act---------------------------------------------------- + Action action = () => new Optional(null); + //Assert------------------------------------------------- + action.ShouldThrow(); + } + } + + [TestClass] + public class TheEqualsMethod : OptionalTests + { + [TestMethod] + public void ShouldBeTrueWhenOptionalsAreSameInstance() + { + //Arrange------------------------------------------------ + var optional = new Optional(1); + Optional optional2 = optional; + //Act---------------------------------------------------- + + //Assert------------------------------------------------- + optional.Equals(optional2).Should().BeTrue(); + } + + [TestMethod] + public void ShouldBeTrueWhenOptionalsHaveValuesWhichAreEqual() + { + //Arrange------------------------------------------------ + var optional = new Optional(1); + var optional2 = new Optional(1); + //Act---------------------------------------------------- + + //Assert------------------------------------------------- + optional.Equals(optional2).Should().BeTrue(); + } + + [TestMethod] + public void ShouldBeTrueWhenOptionalsHaveNoValue() + { + //Arrange------------------------------------------------ + var optional = new Optional(); + var optional2 = new Optional(); + //Act---------------------------------------------------- + + //Assert------------------------------------------------- + optional.Equals(optional2).Should().BeTrue(); + } + + [TestMethod] + public void ShouldBeFalseWhenOptionalsAreDifferentValues() + { + //Arrange------------------------------------------------ + var optional = new Optional(1); + var optional2 = new Optional(2); + //Act---------------------------------------------------- + + //Assert------------------------------------------------- + optional.Equals(optional2).Should().BeFalse(); + } + } + + + [TestClass] + public class TheGetHashCodeMethod : OptionalTests + { + [TestMethod] + public void ShouldBeTrueWhenOptionalsAreSameInstance() + { + //Arrange------------------------------------------------ + var optional = new Optional(1); + Optional optional2 = optional; + //Act---------------------------------------------------- + + //Assert------------------------------------------------- + optional.GetHashCode().Should().Be(optional2.GetHashCode()); + } + + [TestMethod] + public void ShouldBeTrueWhenOptionalsHaveValuesWhichAreEqual() + { + //Arrange------------------------------------------------ + var optional = new Optional(1); + var optional2 = new Optional(1); + //Act---------------------------------------------------- + + //Assert------------------------------------------------- + optional.GetHashCode().Should().Be(optional2.GetHashCode()); + } + + [TestMethod] + public void ShouldBeFalseWhenOptionalsHaveNoValueAndAreDifferentInstances() + { + //Arrange------------------------------------------------ + var optional = new Optional(); + var optional2 = new Optional(); + //Act---------------------------------------------------- + + //Assert------------------------------------------------- + optional.GetHashCode().Should().NotBe(optional2.GetHashCode()); + } + + [TestMethod] + public void ShouldBeFalseWhenOptionalsAreDifferentValues() + { + //Arrange------------------------------------------------ + var optional = new Optional(1); + var optional2 = new Optional(2); + //Act---------------------------------------------------- + + //Assert------------------------------------------------- + optional.GetHashCode().Should().NotBe(optional2.GetHashCode()); + } + } + + [TestClass] + public class TheHasValueMethod : OptionalTests + { + [TestMethod] + public void ShouldBeFalseWhenDefaultConstructorIsUsed() + { + //Arrange + var optional = new Optional(); + //Act + + //Assert + optional.HasValue.Should().BeFalse(); + } + + [TestMethod] + public void ShouldBeTrueIfValueIsPassedToConstructor() + { + //Arrange + var optional = new Optional(1); + //Act + + //Assert + optional.HasValue.Should().BeTrue(); + } + } + + [TestClass] + public class TheValueMethod : OptionalTests + { + [TestMethod] + public void ShouldReturnValueIfOneExists() + { + //Arrange------------------------------------------------ + var optional = new Optional(1); + //Act---------------------------------------------------- + + //Assert------------------------------------------------- + optional.Value.Should().Be(1); + } + + [TestMethod] + public void ShouldThrowExceptionIfNoValueIsSet() + { + //Arrange------------------------------------------------ + var optional = new Optional(); + //Act---------------------------------------------------- + int value; + Action action = () => value = optional.Value; + //Assert------------------------------------------------- + action.ShouldThrow(); + } + } + } +} \ No newline at end of file diff --git a/hacapp.web/Haccap.Core.Tests/Properties/AssemblyInfo.cs b/hacapp.web/Haccap.Core.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..3139f4d --- /dev/null +++ b/hacapp.web/Haccap.Core.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,37 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Haccap.Core.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Haccap.Core.Tests")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("05404e9b-e8d5-48b4-a90e-686280c586a7")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + diff --git a/hacapp.web/Haccap.Core.Tests/packages.config b/hacapp.web/Haccap.Core.Tests/packages.config new file mode 100644 index 0000000..bc990d3 --- /dev/null +++ b/hacapp.web/Haccap.Core.Tests/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/hacapp.web/Haccapp.Model/App.config b/hacapp.web/Haccapp.Model/App.config new file mode 100644 index 0000000..6033f32 --- /dev/null +++ b/hacapp.web/Haccapp.Model/App.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/hacapp.web/Haccapp.Model/Domain/MembershipStatus.cs b/hacapp.web/Haccapp.Model/Domain/MembershipStatus.cs new file mode 100644 index 0000000..7923a17 --- /dev/null +++ b/hacapp.web/Haccapp.Model/Domain/MembershipStatus.cs @@ -0,0 +1,12 @@ +namespace Haccapp.Model.Domain +{ + /// + /// Enumeration representing the status of a users membership to a team + /// + public enum MembershipStatus + { + Pending, + Confimed, + Suspended + } +} \ No newline at end of file diff --git a/hacapp.web/Haccapp.Model/Domain/Team.cs b/hacapp.web/Haccapp.Model/Domain/Team.cs new file mode 100644 index 0000000..6220816 --- /dev/null +++ b/hacapp.web/Haccapp.Model/Domain/Team.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; +using Haccapp.Model.Identity; + +namespace Haccapp.Model.Domain +{ + /// + /// Represents a team in the domain model + /// + /// + /// A team is a collection of registered users who will work on the workflows defined for the by the members of + /// the team. + /// + public class Team + { + /// + /// Constructor required for the EntityFramework to create the object. + /// + [ExcludeFromCodeCoverage] + protected internal Team() + { + Members = new List(); + } + + public Team(string name, ApplicationUser owner) + { + Name = name; + Owner = owner; + Members = new List(); + } + + /// + /// The unique identifier for a team + /// + /// The will be a number greater than 0 + [Required] + public int Id { get; private set; } + + /// + /// The name of the team. This is unique within the owners teams + /// + [Required] + public string Name { get; private set; } + + /// + /// This is the user who owns the team + /// + [Required] + public ApplicationUser Owner { get; private set; } + + /// + /// Represents the additional members of the team. This may be empty if the owner is the only member of the team. + /// + [Required] + public List Members { get; set; } + } +} \ No newline at end of file diff --git a/hacapp.web/Haccapp.Model/Domain/TeamMembership.cs b/hacapp.web/Haccapp.Model/Domain/TeamMembership.cs new file mode 100644 index 0000000..db144b6 --- /dev/null +++ b/hacapp.web/Haccapp.Model/Domain/TeamMembership.cs @@ -0,0 +1,35 @@ +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; +using Haccapp.Model.Identity; + +namespace Haccapp.Model.Domain +{ + /// + /// Class representing the status of a member in a team. + /// + public class TeamMembership + { + /// + /// Constructor for the EntityFramework + /// + [ExcludeFromCodeCoverage] + protected internal TeamMembership() + { + } + + public TeamMembership(ApplicationUser user, MembershipStatus status) + { + User = user; + Status = status; + } + + [Required] + public ApplicationUser User { get; private set; } + + [Required] + public MembershipStatus Status { get; set; } + + [Required] + public int Id { get; private set; } + } +} \ No newline at end of file diff --git a/hacapp.web/Haccapp.Model/Haccapp.Model.csproj b/hacapp.web/Haccapp.Model/Haccapp.Model.csproj new file mode 100644 index 0000000..be56e60 --- /dev/null +++ b/hacapp.web/Haccapp.Model/Haccapp.Model.csproj @@ -0,0 +1,74 @@ + + + + + Debug + AnyCPU + {1F19B069-BBEF-4C41-B2A1-845893F87586} + Library + Properties + Haccapp.Model + Haccapp.Model + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\EntityFramework.6.0.2\lib\net45\EntityFramework.dll + + + ..\packages\EntityFramework.6.0.2\lib\net45\EntityFramework.SqlServer.dll + + + ..\packages\Microsoft.AspNet.Identity.Core.1.0.0\lib\net45\Microsoft.AspNet.Identity.Core.dll + + + ..\packages\Microsoft.AspNet.Identity.EntityFramework.1.0.0\lib\net45\Microsoft.AspNet.Identity.EntityFramework.dll + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hacapp.web/Haccapp.Model/Identity/ApplicationUser.cs b/hacapp.web/Haccapp.Model/Identity/ApplicationUser.cs new file mode 100644 index 0000000..ddfb169 --- /dev/null +++ b/hacapp.web/Haccapp.Model/Identity/ApplicationUser.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNet.Identity.EntityFramework; + +namespace Haccapp.Model.Identity +{ + // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more. + public class ApplicationUser : IdentityUser + { + [Display(Name = "Email address")] + //[Required(ErrorMessage = "The email address is required. This is where we will send you your notifications.")] + //[EmailAddress(ErrorMessage = "Invalid Email Address")] + public string PreferredEmailAddress { get; set; } + } +} \ No newline at end of file diff --git a/hacapp.web/Haccapp.Model/Identity/RoleNames.cs b/hacapp.web/Haccapp.Model/Identity/RoleNames.cs new file mode 100644 index 0000000..efc1afc --- /dev/null +++ b/hacapp.web/Haccapp.Model/Identity/RoleNames.cs @@ -0,0 +1,62 @@ +using System; + +namespace Haccapp.Model.Identity +{ + /// + /// Class which holds the definitions of the rolenames in the system. + /// + /// + /// Role names are held in a way that makes them strongly typed but also implicitly convertible to strings for + /// ease of use. + /// + public class RoleName + { + private const string TeamManagementIdentifier = "TeamManagement"; + private static readonly RoleName TeamManagementInstance = new RoleName(TeamManagementIdentifier); + private readonly string name; + + private RoleName(string name) + { + this.name = name; + } + + public static RoleName TeamManagement + { + get { return TeamManagementInstance; } + } + + public static implicit operator string(RoleName roleName) + { + return roleName.ToString(); + } + + public override string ToString() + { + return name; + } + + /// + /// Attempts to create a RoleName instance from the given string. + /// + /// + /// The name of the role to parse into a RoleName instance. This must map to a known role in the + /// system. + /// + /// + /// A RoleName instance, which will be the static instance of the RoleName which has the same idfentifier as the + /// given roleName. + /// + public RoleName Parse(string roleName) + { + switch (roleName) + { + case TeamManagementIdentifier: + return TeamManagement; + default: + throw new ArgumentException( + string.Format("The given roleName '{0}' did not map to a known rolename in the system.", + roleName)); + } + } + } +} \ No newline at end of file diff --git a/hacapp.web/Haccapp.Model/Properties/AssemblyInfo.cs b/hacapp.web/Haccapp.Model/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4850054 --- /dev/null +++ b/hacapp.web/Haccapp.Model/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Haccapp.Model")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Haccapp.Model")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("817fbd87-d274-4f29-b1be-6a21d6dcbcea")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/hacapp.web/Haccapp.Model/packages.config b/hacapp.web/Haccapp.Model/packages.config new file mode 100644 index 0000000..c1eaef9 --- /dev/null +++ b/hacapp.web/Haccapp.Model/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/hacapp.web/NCrunch.Generator.SpecflowPlugin/App.config b/hacapp.web/NCrunch.Generator.SpecflowPlugin/App.config new file mode 100644 index 0000000..8f6a4f7 --- /dev/null +++ b/hacapp.web/NCrunch.Generator.SpecflowPlugin/App.config @@ -0,0 +1,9 @@ + + + +
+ + + + + \ No newline at end of file diff --git a/hacapp.web/NCrunch.Generator.SpecflowPlugin/NCrunch.Generator.SpecflowPlugin.csproj b/hacapp.web/NCrunch.Generator.SpecflowPlugin/NCrunch.Generator.SpecflowPlugin.csproj new file mode 100644 index 0000000..5cc6f04 --- /dev/null +++ b/hacapp.web/NCrunch.Generator.SpecflowPlugin/NCrunch.Generator.SpecflowPlugin.csproj @@ -0,0 +1,71 @@ + + + + + Debug + AnyCPU + {9F70195C-261C-4AB6-8CE3-9A1E09F82E72} + Library + Properties + NCrunch.Generator.SpecflowPlugin + NCrunch.Generator.SpecflowPlugin + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + ..\packages\SpecFlow.1.9.0\lib\net35\TechTalk.SpecFlow.dll + + + ..\packages\SpecFlow.CustomPlugin.1.9.0\lib\net40\TechTalk.SpecFlow.Generator.dll + + + ..\packages\SpecFlow.CustomPlugin.1.9.0\lib\net40\TechTalk.SpecFlow.Parser.dll + + + ..\packages\SpecFlow.CustomPlugin.1.9.0\lib\net40\TechTalk.SpecFlow.Utils.dll + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hacapp.web/NCrunch.Generator.SpecflowPlugin/NCrunchAttributeGeneratorProvider.cs b/hacapp.web/NCrunch.Generator.SpecflowPlugin/NCrunchAttributeGeneratorProvider.cs new file mode 100644 index 0000000..36f4dca --- /dev/null +++ b/hacapp.web/NCrunch.Generator.SpecflowPlugin/NCrunchAttributeGeneratorProvider.cs @@ -0,0 +1,126 @@ +using System.CodeDom; +using System.Collections.Generic; +using System.Linq; +using TechTalk.SpecFlow.Generator; +using TechTalk.SpecFlow.Generator.UnitTestProvider; +using TechTalk.SpecFlow.Utils; + +namespace NCrunch.Generator.SpecflowPlugin +{ + public class NCrunchAttributeGeneratorProvider : IUnitTestGeneratorProvider + { + private static readonly string NCrunchAttributePrefix = "NCrunch.Framework"; + private static readonly string NCrunchExclusivelyUses = "NCrunch.Framework.ExclusivelyUsesAttribute"; + private readonly IUnitTestGeneratorProvider baseGeneratorProvider; + private readonly CodeDomHelper codeDomHelper; + + public NCrunchAttributeGeneratorProvider(CodeDomHelper codeDomHelper) + { + baseGeneratorProvider = new NUnitTestGeneratorProvider(codeDomHelper); + this.codeDomHelper = codeDomHelper; + } + + public void SetTestClass(TestClassGenerationContext generationContext, string featureTitle, + string featureDescription) + { + baseGeneratorProvider.SetTestClass(generationContext, featureTitle, featureDescription); + } + + public void SetTestClassCategories(TestClassGenerationContext generationContext, + IEnumerable featureCategories) + { + baseGeneratorProvider.SetTestClassCategories(generationContext, featureCategories); + } + + public void SetTestClassIgnore(TestClassGenerationContext generationContext) + { + baseGeneratorProvider.SetTestClassIgnore(generationContext); + } + + public void FinalizeTestClass(TestClassGenerationContext generationContext) + { + baseGeneratorProvider.FinalizeTestClass(generationContext); + } + + public void SetTestClassInitializeMethod(TestClassGenerationContext generationContext) + { + baseGeneratorProvider.SetTestClassInitializeMethod(generationContext); + } + + public void SetTestClassCleanupMethod(TestClassGenerationContext generationContext) + { + baseGeneratorProvider.SetTestClassCleanupMethod(generationContext); + } + + public void SetTestInitializeMethod(TestClassGenerationContext generationContext) + { + baseGeneratorProvider.SetTestInitializeMethod(generationContext); + } + + public void SetTestCleanupMethod(TestClassGenerationContext generationContext) + { + baseGeneratorProvider.SetTestCleanupMethod(generationContext); + } + + public void SetTestMethod(TestClassGenerationContext generationContext, CodeMemberMethod testMethod, + string scenarioTitle) + { + baseGeneratorProvider.SetTestMethod(generationContext, testMethod, scenarioTitle); + } + + public void SetTestMethodCategories(TestClassGenerationContext generationContext, CodeMemberMethod testMethod, + IEnumerable scenarioCategories) + { + baseGeneratorProvider.SetTestMethodCategories(generationContext, testMethod, + scenarioCategories.Where(category => !category.StartsWith(NCrunchAttributePrefix))); + foreach (string nCrunchCategories in scenarioCategories.Where(c => c.StartsWith(NCrunchAttributePrefix))) + { + string[] split = nCrunchCategories.Split('_'); + + string nCrunchAttributeIdentifier = split.First(); + + if (nCrunchAttributeIdentifier == NCrunchExclusivelyUses) + { + string nCrunchAttributeValue = split.Last(); + codeDomHelper.AddAttribute(testMethod, NCrunchExclusivelyUses, nCrunchAttributeValue); + } + } + } + + public void SetTestMethodIgnore(TestClassGenerationContext generationContext, CodeMemberMethod testMethod) + { + baseGeneratorProvider.SetTestMethodIgnore(generationContext, testMethod); + } + + public void SetRowTest(TestClassGenerationContext generationContext, CodeMemberMethod testMethod, + string scenarioTitle) + { + baseGeneratorProvider.SetRowTest(generationContext, testMethod, scenarioTitle); + } + + public void SetRow(TestClassGenerationContext generationContext, CodeMemberMethod testMethod, + IEnumerable arguments, + IEnumerable tags, bool isIgnored) + { + baseGeneratorProvider.SetRow(generationContext, testMethod, arguments, tags, isIgnored); + } + + public void SetTestMethodAsRow(TestClassGenerationContext generationContext, CodeMemberMethod testMethod, + string scenarioTitle, + string exampleSetName, string variantName, IEnumerable> arguments) + { + baseGeneratorProvider.SetTestMethodAsRow(generationContext, testMethod, scenarioTitle, exampleSetName, + variantName, arguments); + } + + public bool SupportsRowTests + { + get { return baseGeneratorProvider.SupportsRowTests; } + } + + public bool SupportsAsyncTests + { + get { return baseGeneratorProvider.SupportsAsyncTests; } + } + } +} \ No newline at end of file diff --git a/hacapp.web/NCrunch.Generator.SpecflowPlugin/NCrunchAttributes.cs b/hacapp.web/NCrunch.Generator.SpecflowPlugin/NCrunchAttributes.cs new file mode 100644 index 0000000..4e019ba --- /dev/null +++ b/hacapp.web/NCrunch.Generator.SpecflowPlugin/NCrunchAttributes.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +// ReSharper disable CheckNamespace +namespace NCrunch.Framework +// ReSharper restore CheckNamespace +{ + public abstract class ResourceUsageAttribute : Attribute + { + private readonly string[] resourceNames; + + public ResourceUsageAttribute(params string[] resourceName) + { + resourceNames = resourceName; + } + + public string[] ResourceNames + { + get { return resourceNames; } + } + } + + public class ExclusivelyUsesAttribute : ResourceUsageAttribute + { + public ExclusivelyUsesAttribute(params string[] resourceName) + : base(resourceName) { } + } + + public class InclusivelyUsesAttribute : ResourceUsageAttribute + { + public InclusivelyUsesAttribute(params string[] resourceName) + : base(resourceName) { } + } + + public class IsolatedAttribute : Attribute + { + } + + namespace NCrunch.Framework + { + public class SerialAttribute : Attribute + { + } + } + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Assembly, AllowMultiple = true)] + public class RequiresCapabilityAttribute : Attribute + { + public RequiresCapabilityAttribute(string capabilityName) + { + CapabilityName = capabilityName; + } + + public string CapabilityName { get; private set; } + } + + public class TimeoutAttribute : System.Attribute + { + private IDictionary properties; + + public TimeoutAttribute(int timeout) + { + properties = new Hashtable(); + properties["Timeout"] = timeout; + } + + public IDictionary Properties + { + get { return properties; } + } + } + + [AttributeUsage(AttributeTargets.Method + | AttributeTargets.Class + | AttributeTargets.Field + | AttributeTargets.Assembly, + AllowMultiple = true)] + public class CategoryAttribute: Attribute + { + public CategoryAttribute(string category) + { + Category = category; + } + + public string Category { get; private set; } + } +} \ No newline at end of file diff --git a/hacapp.web/NCrunch.Generator.SpecflowPlugin/NCrunchGeneratorPlugin.cs b/hacapp.web/NCrunch.Generator.SpecflowPlugin/NCrunchGeneratorPlugin.cs new file mode 100644 index 0000000..c07ebc9 --- /dev/null +++ b/hacapp.web/NCrunch.Generator.SpecflowPlugin/NCrunchGeneratorPlugin.cs @@ -0,0 +1,53 @@ +using BoDi; +using NCrunch.Generator.SpecflowPlugin; +using TechTalk.SpecFlow.Generator.Configuration; +using TechTalk.SpecFlow.Generator.Plugins; +using TechTalk.SpecFlow.Generator.UnitTestProvider; +using TechTalk.SpecFlow.Infrastructure; + +[assembly: GeneratorPlugin(typeof(NCrunchGeneratorPlugin))] + +namespace NCrunch.Generator.SpecflowPlugin +{ + /// + /// The CodedUI generator plugin. + /// + public class NCrunchGeneratorPlugin : IGeneratorPlugin + { + /// + /// The register dependencies. + /// + /// + /// The container. + /// + public void RegisterDependencies(ObjectContainer container) + { + } + + /// + /// The register customizations. + /// + /// + /// The container. + /// + /// + /// The generator configuration. + /// + public void RegisterCustomizations(ObjectContainer container, + SpecFlowProjectConfiguration generatorConfiguration) + { + container.RegisterTypeAs(); + //container.RegisterTypeAs(); + } + + /// + /// The register configuration defaults. + /// + /// + /// The spec flow configuration. + /// + public void RegisterConfigurationDefaults(SpecFlowProjectConfiguration specFlowConfiguration) + { + } + } +} \ No newline at end of file diff --git a/hacapp.web/NCrunch.Generator.SpecflowPlugin/Properties/AssemblyInfo.cs b/hacapp.web/NCrunch.Generator.SpecflowPlugin/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..6854b1f --- /dev/null +++ b/hacapp.web/NCrunch.Generator.SpecflowPlugin/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NCrunch.Generator.SpecflowPlugin")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("NCrunch.Generator.SpecflowPlugin")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("faa1b48e-c3b6-4162-bc7b-b05f5be86f60")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/hacapp.web/NCrunch.Generator.SpecflowPlugin/packages.config b/hacapp.web/NCrunch.Generator.SpecflowPlugin/packages.config new file mode 100644 index 0000000..c8e8c76 --- /dev/null +++ b/hacapp.web/NCrunch.Generator.SpecflowPlugin/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/hacapp.web/hacapp.contracts/Commands/CommandHandlerBase.cs b/hacapp.web/hacapp.contracts/Commands/CommandHandlerBase.cs new file mode 100644 index 0000000..2fc4b60 --- /dev/null +++ b/hacapp.web/hacapp.contracts/Commands/CommandHandlerBase.cs @@ -0,0 +1,21 @@ +namespace hacapp.contracts.Commands +{ + /// + /// Base class for command handlers which handles the genericless implementation of the Execute method + /// + /// The type of the command which the handler can execute + public abstract class CommandHandlerBase : ICommandHandler where TCommand : ICommand + { + public abstract void Execute(TCommand command, ICommandAndQueryDispatcher dispatcher); + + /// + /// This method simply casts the command to the expected type and calls the abstract execute method. + /// + /// The command to execute, whcih should be an object of type ICommand + /// + public void Execute(object command, ICommandAndQueryDispatcher dispatcher) + { + Execute((TCommand) command, dispatcher); + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.contracts/Commands/IAsyncQueryHandler.cs b/hacapp.web/hacapp.contracts/Commands/IAsyncQueryHandler.cs new file mode 100644 index 0000000..33e8c57 --- /dev/null +++ b/hacapp.web/hacapp.contracts/Commands/IAsyncQueryHandler.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; + +namespace hacapp.contracts.Commands +{ + public interface IAsyncQueryHandler : IQueryHandler where TQuery : IQuery + { + /// + /// Executes the query asyncromously and returns the a task which represents the result. Will throw an exception if the command could not be executed for any + /// reason. + /// + Task ExecuteAsync(TQuery query); + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.contracts/Commands/ICommand.cs b/hacapp.web/hacapp.contracts/Commands/ICommand.cs new file mode 100644 index 0000000..ac6dd95 --- /dev/null +++ b/hacapp.web/hacapp.contracts/Commands/ICommand.cs @@ -0,0 +1,14 @@ +namespace hacapp.contracts.Commands +{ + /// + /// Interface which represents a command which can be executed and returns nothing. + /// + /// + /// The command will represent only the data needed to execute a command. + /// The actual logic to execute a command is encapsulated in a command handler, of which there should be one per + /// command. + /// + public interface ICommand + { + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.contracts/Commands/ICommandAndQueryDispatcher.cs b/hacapp.web/hacapp.contracts/Commands/ICommandAndQueryDispatcher.cs new file mode 100644 index 0000000..031e03c --- /dev/null +++ b/hacapp.web/hacapp.contracts/Commands/ICommandAndQueryDispatcher.cs @@ -0,0 +1,22 @@ +namespace hacapp.contracts.Commands +{ + /// + /// Interface which defines the contract for dispatching and executing commands and queries + /// + public interface ICommandAndQueryDispatcher + { + /// + /// Executes a command + /// + /// The command to execute + void ExecuteCommand(ICommand command); + + /// + /// Executes a query and returns the result + /// + /// The type of the result of the query + /// The query to execute + /// The query that we want to execute + TResult ExecuteQuery(IQuery query); + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.contracts/Commands/ICommandHandler.cs b/hacapp.web/hacapp.contracts/Commands/ICommandHandler.cs new file mode 100644 index 0000000..6f74e02 --- /dev/null +++ b/hacapp.web/hacapp.contracts/Commands/ICommandHandler.cs @@ -0,0 +1,29 @@ +using System.Threading.Tasks; + +namespace hacapp.contracts.Commands +{ + /// + /// Interface for objects which will handle commands + /// + /// The type of the command the the implementation will handle + public interface ICommandHandler : ICommandHandler where TCommand : ICommand + { + /// + /// Executes the command. Will throw an exception if the command could not be executed for any reason. + /// + void Execute(TCommand command, ICommandAndQueryDispatcher dispatcher); + } + + /// + /// Genericless definition of the command handler to allow us to register the commands without knowing what the type of + /// command they handle is + /// + public interface ICommandHandler + { + /// + /// Executes the command. Will throw an exception if the command could not be executed for any reason. + /// + /// The return type of this method will always be a task + void Execute(object command, ICommandAndQueryDispatcher dispatcher); + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.contracts/Commands/ICommandWithResult.cs b/hacapp.web/hacapp.contracts/Commands/ICommandWithResult.cs new file mode 100644 index 0000000..f39a78a --- /dev/null +++ b/hacapp.web/hacapp.contracts/Commands/ICommandWithResult.cs @@ -0,0 +1,17 @@ +namespace hacapp.contracts.Commands +{ + /// + /// Interface which represents a command with a result. + /// + /// + /// A command which has a result should only be used when creating new data and the result of the command is the + /// new key for the object that the command created. + /// + public interface ICommandWithResult : ICommand + { + /// + /// The result of executing the command + /// + TResult Result { get; set; } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.contracts/Commands/IQuery.cs b/hacapp.web/hacapp.contracts/Commands/IQuery.cs new file mode 100644 index 0000000..1f8e113 --- /dev/null +++ b/hacapp.web/hacapp.contracts/Commands/IQuery.cs @@ -0,0 +1,10 @@ +namespace hacapp.contracts.Commands +{ + /// + /// Interface for a query which has a result. + /// + /// The type of the result of the execution of the query. + public interface IQuery + { + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.contracts/Commands/IQueryHandler.cs b/hacapp.web/hacapp.contracts/Commands/IQueryHandler.cs new file mode 100644 index 0000000..cd0b819 --- /dev/null +++ b/hacapp.web/hacapp.contracts/Commands/IQueryHandler.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; + +namespace hacapp.contracts.Commands +{ + public interface IQueryHandler : IQueryHandler where TQuery : IQuery + { + /// + /// Executes the query and returns the results. Will throw an exception if the command could not be executed for any + /// reason. + /// + TResult Execute(TQuery query, ICommandAndQueryDispatcher dispatcher); + } + + /// + /// Genericless instance of the IQueryHandler interface. This exists so that collections of IQueryHandlers can be + /// created and so we can dispatch the queries at runtime based on the type of the query + /// + public interface IQueryHandler + { + /// + /// Executes the query and returns the results. Will throw an exception if the command could not be executed for any + /// reason. + /// + /// + /// The return type of this method will always be a task whose result will be of the type of the result of the + /// query + /// + object Execute(object query, ICommandAndQueryDispatcher dispatcher); + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.contracts/Commands/QueryHandlerBase.cs b/hacapp.web/hacapp.contracts/Commands/QueryHandlerBase.cs new file mode 100644 index 0000000..842c8ea --- /dev/null +++ b/hacapp.web/hacapp.contracts/Commands/QueryHandlerBase.cs @@ -0,0 +1,21 @@ +using System.Threading.Tasks; + +namespace hacapp.contracts.Commands +{ + /// + /// Base implementation for the generic version of IQueryHandler interface which handlers the non generic version of + /// the execute method + /// + /// The type of the Query that the derived handler will handle. + /// The type of the result of the query that the derived handler will handle. + public abstract class QueryHandlerBase : IQueryHandler + where TQuery : IQuery + { + public object Execute(object query, ICommandAndQueryDispatcher dispatcher) + { + return Execute((TQuery) query, dispatcher); + } + + public abstract TResult Execute(TQuery query, ICommandAndQueryDispatcher dispatcher); + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.contracts/Fody.targets b/hacapp.web/hacapp.contracts/Fody.targets new file mode 100644 index 0000000..a668a51 --- /dev/null +++ b/hacapp.web/hacapp.contracts/Fody.targets @@ -0,0 +1,89 @@ + + + + + + $(NCrunchOriginalSolutionDir) + + + + + $(SolutionDir) + + + + + $(MSBuildProjectDirectory)\..\ + + + + + + + $(KeyOriginatorFile) + + + + + $(AssemblyOriginatorKeyFile) + + + + + + + + + + $(ProjectDir)$(IntermediateOutputPath) + Low + $(SignAssembly) + $(MSBuildThisFileDirectory) + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hacapp.web/hacapp.contracts/FodyWeavers.xml b/hacapp.web/hacapp.contracts/FodyWeavers.xml new file mode 100644 index 0000000..9d96e0a --- /dev/null +++ b/hacapp.web/hacapp.contracts/FodyWeavers.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/hacapp.web/hacapp.contracts/Properties/AssemblyInfo.cs b/hacapp.web/hacapp.contracts/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..7d54256 --- /dev/null +++ b/hacapp.web/hacapp.contracts/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("hacapp.contracts")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("hacapp.contracts")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("6fb5a745-454d-4cf9-803b-89ea3c2241f7")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/hacapp.web/hacapp.contracts/hacapp.contracts.csproj b/hacapp.web/hacapp.contracts/hacapp.contracts.csproj new file mode 100644 index 0000000..289a75c --- /dev/null +++ b/hacapp.web/hacapp.contracts/hacapp.contracts.csproj @@ -0,0 +1,74 @@ + + + + + Debug + AnyCPU + {0A01520A-83F2-41F3-B2DE-B3AAA0A861B4} + Library + Properties + hacapp.contracts + hacapp.contracts + v4.5 + 512 + ..\packages\Fody.1.19.1.0 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\NullGuard.Fody.1.1.0.0\Lib\portable-net4+sl4+wp7+win8+MonoAndroid16+MonoTouch40\NullGuard.dll + False + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hacapp.web/hacapp.contracts/packages.config b/hacapp.web/hacapp.contracts/packages.config new file mode 100644 index 0000000..5000761 --- /dev/null +++ b/hacapp.web/hacapp.contracts/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/hacapp.web/hacapp.core/App.config b/hacapp.web/hacapp.core/App.config new file mode 100644 index 0000000..1e94b1b --- /dev/null +++ b/hacapp.web/hacapp.core/App.config @@ -0,0 +1,17 @@ + + + + +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/hacapp.web/hacapp.core/Commands/CommandAndQueryDispatcher.cs b/hacapp.web/hacapp.core/Commands/CommandAndQueryDispatcher.cs new file mode 100644 index 0000000..bc99328 --- /dev/null +++ b/hacapp.web/hacapp.core/Commands/CommandAndQueryDispatcher.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using hacapp.contracts.Commands; + +namespace hacapp.core.Commands +{ + /// + /// The default implemenation of the ICommandAndQueryDispatcher interface. + /// + /// + /// Given a command or query this implementation looks up the appropriate handler for that command or query and + /// then invokes the command or query. + /// + public class CommandAndQueryDispatcher : ICommandAndQueryDispatcher + { + private readonly IDictionary commandHandlers = new Dictionary(); + private readonly IDictionary queryHandlers = new Dictionary(); + + public CommandAndQueryDispatcher(IEnumerable commandHandlers, + IEnumerable queryHandlers) + { + commandHandlers = commandHandlers.ToList(); + if (!commandHandlers.Any()) + { + throw new ArgumentException("No command handlers registered"); + } + + queryHandlers = queryHandlers.ToList(); + if (!queryHandlers.Any()) + { + throw new ArgumentException("No query handlers registered"); + } + + foreach (ICommandHandler commandHandler in commandHandlers) + { + this.commandHandlers.Add( + commandHandler.GetType().GetInterface("ICommandHandler`1").GenericTypeArguments[0], commandHandler); + } + + foreach (IQueryHandler queryHandler in queryHandlers) + { + this.queryHandlers.Add(queryHandler.GetType().GetInterface("IQueryHandler`2").GenericTypeArguments[0], + queryHandler); + } + } + + [DebuggerStepThrough] + public void ExecuteCommand(ICommand command) + { + ICommandHandler commandHandler; + if (commandHandlers.TryGetValue(command.GetType(), out commandHandler)) + { + commandHandler.Execute(command, this); + return; + } + + throw new InvalidOperationException( + "No command handler was found to be registered for the command with the type " + command.GetType()); + } + + [DebuggerStepThrough] + public TResult ExecuteQuery(IQuery query) + { + IQueryHandler queryHandler; + if (queryHandlers.TryGetValue(query.GetType(), out queryHandler)) + { + return (TResult) queryHandler.Execute(query, this); + } + + throw new InvalidOperationException( + "No query handler was found to be registered for the query with the type " + query.GetType()); + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.core/Commands/CommandExecutionException.cs b/hacapp.web/hacapp.core/Commands/CommandExecutionException.cs new file mode 100644 index 0000000..cb58f11 --- /dev/null +++ b/hacapp.web/hacapp.core/Commands/CommandExecutionException.cs @@ -0,0 +1,20 @@ +using System; +using System.Runtime.Serialization; + +namespace Hacapp.Core.Commands +{ + public class CommandExecutionException : Exception + { + public CommandExecutionException(string message) : base(message) + { + } + + public CommandExecutionException(string message, Exception innerException) : base(message, innerException) + { + } + + protected CommandExecutionException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.core/Commands/CommandExecutionExceptionMessages.cs b/hacapp.web/hacapp.core/Commands/CommandExecutionExceptionMessages.cs new file mode 100644 index 0000000..58a379f --- /dev/null +++ b/hacapp.web/hacapp.core/Commands/CommandExecutionExceptionMessages.cs @@ -0,0 +1,10 @@ +namespace Hacapp.Core.Commands +{ + public class CommandExecutionExceptionMessages + { + public static string TeamDoesNotExist(int teamId) + { + return string.Format("The team with the id {0} does not exist", teamId); + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.core/Commands/CreateNewTeamCommand.cs b/hacapp.web/hacapp.core/Commands/CreateNewTeamCommand.cs new file mode 100644 index 0000000..3d19594 --- /dev/null +++ b/hacapp.web/hacapp.core/Commands/CreateNewTeamCommand.cs @@ -0,0 +1,30 @@ +using hacapp.contracts.Commands; +using Haccapp.Model.Identity; + +namespace Hacapp.Core.Commands +{ + /// + /// Command which encapsulates the data required to create a new command + /// + public class CreateNewTeamCommand : ICommand + { + public CreateNewTeamCommand(string teamName, ApplicationUser teamOwnerId) + { + TeamName = new Optional(teamName); + TeamOwnerId = teamOwnerId; + } + + /// + /// Constructor for model binder to use + /// + public CreateNewTeamCommand() + { + } + + public Optional TeamName { get; private set; } + + public int TeamId { get; set; } + + public ApplicationUser TeamOwnerId { get; set; } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.core/Commands/DeleteTeamCommand.cs b/hacapp.web/hacapp.core/Commands/DeleteTeamCommand.cs new file mode 100644 index 0000000..8bce6d4 --- /dev/null +++ b/hacapp.web/hacapp.core/Commands/DeleteTeamCommand.cs @@ -0,0 +1,14 @@ +using hacapp.contracts.Commands; + +namespace Hacapp.Core.Commands +{ + public class DeleteTeamCommand : ICommand + { + public DeleteTeamCommand(int teamId) + { + TeamId = teamId; + } + + public int TeamId { get; private set; } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.core/Commands/JoinTeamCommand.cs b/hacapp.web/hacapp.core/Commands/JoinTeamCommand.cs new file mode 100644 index 0000000..971ac6f --- /dev/null +++ b/hacapp.web/hacapp.core/Commands/JoinTeamCommand.cs @@ -0,0 +1,16 @@ +using hacapp.contracts.Commands; + +namespace Hacapp.Core.Commands +{ + public class JoinTeamCommand : ICommand + { + public JoinTeamCommand(int teamId, string getUserId) + { + TeamId = teamId; + GetUserId = getUserId; + } + + public int TeamId { get; private set; } + public string GetUserId { get; private set; } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.core/Commands/RemoveUserFromTeamCommand.cs b/hacapp.web/hacapp.core/Commands/RemoveUserFromTeamCommand.cs new file mode 100644 index 0000000..81bf796 --- /dev/null +++ b/hacapp.web/hacapp.core/Commands/RemoveUserFromTeamCommand.cs @@ -0,0 +1,16 @@ +using hacapp.contracts.Commands; + +namespace Hacapp.Core.Commands +{ + public class RemoveUserFromTeamCommand : ICommand + { + public RemoveUserFromTeamCommand(string userId, int teamId) + { + UserId = userId; + TeamId = teamId; + } + + public string UserId { get; set; } + public int TeamId { get; set; } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.core/Commands/UpdateMembershipStatusCommand.cs b/hacapp.web/hacapp.core/Commands/UpdateMembershipStatusCommand.cs new file mode 100644 index 0000000..af029f3 --- /dev/null +++ b/hacapp.web/hacapp.core/Commands/UpdateMembershipStatusCommand.cs @@ -0,0 +1,19 @@ +using hacapp.contracts.Commands; +using Haccapp.Model.Domain; + +namespace Hacapp.Core.Commands +{ + public class UpdateMembershipStatusCommand : ICommand + { + public UpdateMembershipStatusCommand(string userIdToUpdate, int teamId, MembershipStatus newStatus) + { + UserIdToUpdate = userIdToUpdate; + TeamId = teamId; + NewStatus = newStatus; + } + + public string UserIdToUpdate { get; private set; } + public int TeamId { get; private set; } + public MembershipStatus NewStatus { get; private set; } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.core/Fody.targets b/hacapp.web/hacapp.core/Fody.targets new file mode 100644 index 0000000..a668a51 --- /dev/null +++ b/hacapp.web/hacapp.core/Fody.targets @@ -0,0 +1,89 @@ + + + + + + $(NCrunchOriginalSolutionDir) + + + + + $(SolutionDir) + + + + + $(MSBuildProjectDirectory)\..\ + + + + + + + $(KeyOriginatorFile) + + + + + $(AssemblyOriginatorKeyFile) + + + + + + + + + + $(ProjectDir)$(IntermediateOutputPath) + Low + $(SignAssembly) + $(MSBuildThisFileDirectory) + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hacapp.web/hacapp.core/FodyWeavers.xml b/hacapp.web/hacapp.core/FodyWeavers.xml new file mode 100644 index 0000000..9d96e0a --- /dev/null +++ b/hacapp.web/hacapp.core/FodyWeavers.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/hacapp.web/hacapp.core/Optional.cs b/hacapp.web/hacapp.core/Optional.cs new file mode 100644 index 0000000..d29a484 --- /dev/null +++ b/hacapp.web/hacapp.core/Optional.cs @@ -0,0 +1,69 @@ +using System; + +namespace Hacapp.Core +{ + public class Optional + { + private T value; + + public Optional() + { + HasValue = false; + } + + public Optional(T value) + { + if (value == null) + { + throw new ArgumentNullException("value", + "You cannot create an Optional with a null value for the T. " + + "If you want the optional value to be unspecified please use the default constructor."); + } + + HasValue = true; + Value = value; + } + + public T Value + { + get + { + if (HasValue) + { + return value; + } + + throw new InvalidOperationException( + "You attempted to access the value of an Optional when it has no value. " + + "Please check the value of HasValue before accessing the Value property and " + + "only attempt access if this property returns True"); + } + private set { this.value = value; } + } + + public bool HasValue { get; private set; } + + public override bool Equals(object obj) + { + if (obj is Optional) + { + return Equals((Optional) obj); + } + return false; + } + + public bool Equals(Optional other) + { + if (HasValue && other.HasValue) + { + return Equals(value, other.value); + } + return HasValue == other.HasValue; + } + + public override int GetHashCode() + { + return HasValue ? Value.GetHashCode() : base.GetHashCode(); + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.core/Properties/AssemblyInfo.cs b/hacapp.web/hacapp.core/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..59dc3ae --- /dev/null +++ b/hacapp.web/hacapp.core/Properties/AssemblyInfo.cs @@ -0,0 +1,37 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("hacapp.core")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("hacapp.core")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("9cafa160-3db3-4557-ac43-b49df88dadda")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: InternalsVisibleTo("Haccapp.Core.Tests")] diff --git a/hacapp.web/hacapp.core/Queries/GetAllTeamsQuery.cs b/hacapp.web/hacapp.core/Queries/GetAllTeamsQuery.cs new file mode 100644 index 0000000..cc88ce1 --- /dev/null +++ b/hacapp.web/hacapp.core/Queries/GetAllTeamsQuery.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using hacapp.contracts.Commands; +using Haccapp.Model.Domain; + +namespace Hacapp.Core.Queries +{ + public class GetAllTeamsQuery : IQuery> + { + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.core/Queries/GetPendingMembershipsForUsersTeamsQuery.cs b/hacapp.web/hacapp.core/Queries/GetPendingMembershipsForUsersTeamsQuery.cs new file mode 100644 index 0000000..8aa1911 --- /dev/null +++ b/hacapp.web/hacapp.core/Queries/GetPendingMembershipsForUsersTeamsQuery.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using hacapp.contracts.Commands; +using Hacapp.Core.Queries.Result; + +namespace Hacapp.Core.Queries +{ + public class GetPendingMembershipsForUsersTeamsQuery : IQuery> + { + public GetPendingMembershipsForUsersTeamsQuery(string userId) + { + UserId = userId; + } + + public string UserId { get; private set; } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.core/Queries/GetTeamByIdQuery.cs b/hacapp.web/hacapp.core/Queries/GetTeamByIdQuery.cs new file mode 100644 index 0000000..00f5ee2 --- /dev/null +++ b/hacapp.web/hacapp.core/Queries/GetTeamByIdQuery.cs @@ -0,0 +1,15 @@ +using hacapp.contracts.Commands; +using Haccapp.Model.Domain; + +namespace Hacapp.Core.Queries +{ + public class GetTeamByIdQuery : IQuery + { + public GetTeamByIdQuery(int teamId) + { + TeamId = teamId; + } + + public int TeamId { get; private set; } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.core/Queries/GetTeamsUserDoesNotBelongToQuery.cs b/hacapp.web/hacapp.core/Queries/GetTeamsUserDoesNotBelongToQuery.cs new file mode 100644 index 0000000..2f6c826 --- /dev/null +++ b/hacapp.web/hacapp.core/Queries/GetTeamsUserDoesNotBelongToQuery.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using hacapp.contracts.Commands; +using Haccapp.Model.Domain; + +namespace Hacapp.Core.Queries +{ + public class GetTeamsUserDoesNotBelongToQuery : IQuery> + { + public GetTeamsUserDoesNotBelongToQuery(string userId) + { + UserId = userId; + } + + public string UserId { get; private set; } + } + + +} \ No newline at end of file diff --git a/hacapp.web/hacapp.core/Queries/GetUserByIdQuery.cs b/hacapp.web/hacapp.core/Queries/GetUserByIdQuery.cs new file mode 100644 index 0000000..8c2b646 --- /dev/null +++ b/hacapp.web/hacapp.core/Queries/GetUserByIdQuery.cs @@ -0,0 +1,15 @@ +using hacapp.contracts.Commands; +using Haccapp.Model.Identity; + +namespace Hacapp.Core.Queries +{ + public class GetUserByIdQuery : IQuery + { + public GetUserByIdQuery(string getUserId) + { + GetUserId = getUserId; + } + + public string GetUserId { get; private set; } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.core/Queries/GetUserConfirmedTeamsQuery.cs b/hacapp.web/hacapp.core/Queries/GetUserConfirmedTeamsQuery.cs new file mode 100644 index 0000000..ccec654 --- /dev/null +++ b/hacapp.web/hacapp.core/Queries/GetUserConfirmedTeamsQuery.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using hacapp.contracts.Commands; +using Haccapp.Model.Domain; + +namespace Hacapp.Core.Queries +{ + public class GetUserConfirmedTeamsQuery : IQuery> + { + public GetUserConfirmedTeamsQuery(string userId) + { + UserId = userId; + } + + public string UserId { get; private set; } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.core/Queries/GetUserOwnedTeamsQuery.cs b/hacapp.web/hacapp.core/Queries/GetUserOwnedTeamsQuery.cs new file mode 100644 index 0000000..b562727 --- /dev/null +++ b/hacapp.web/hacapp.core/Queries/GetUserOwnedTeamsQuery.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using hacapp.contracts.Commands; +using Haccapp.Model.Domain; + +namespace Hacapp.Core.Queries +{ + public class GetUserOwnedTeamsQuery : IQuery> + { + public GetUserOwnedTeamsQuery(string userId) + { + UserId = userId; + } + + public string UserId { get; private set; } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.core/Queries/GetUserTeamsQuery.cs b/hacapp.web/hacapp.core/Queries/GetUserTeamsQuery.cs new file mode 100644 index 0000000..2d6bee0 --- /dev/null +++ b/hacapp.web/hacapp.core/Queries/GetUserTeamsQuery.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using hacapp.contracts.Commands; +using Haccapp.Model.Domain; + +namespace Hacapp.Core.Queries +{ + public class GetUserTeamsQuery : IQuery> + { + public GetUserTeamsQuery(string userId) + { + UserId = userId; + } + + public string UserId { get; private set; } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.core/Queries/Result/PendingMembershipResult.cs b/hacapp.web/hacapp.core/Queries/Result/PendingMembershipResult.cs new file mode 100644 index 0000000..adbb5e8 --- /dev/null +++ b/hacapp.web/hacapp.core/Queries/Result/PendingMembershipResult.cs @@ -0,0 +1,25 @@ +namespace Hacapp.Core.Queries.Result +{ + public class PendingMembershipResult + { + /// + /// The id if the user that has the membership pending + /// + public string UserId { get; set; } + + /// + /// The display name for the user who has a pending membership + /// + public string UserName { get; set; } + + /// + /// The id of the team that the user has the pending membership for + /// + public int TeamId { get; set; } + + /// + /// The display name for the team that the user has a pending membership for + /// + public string TeamName { get; set; } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.core/hacapp.core.csproj b/hacapp.web/hacapp.core/hacapp.core.csproj new file mode 100644 index 0000000..8e2de8f --- /dev/null +++ b/hacapp.web/hacapp.core/hacapp.core.csproj @@ -0,0 +1,107 @@ + + + + + Debug + AnyCPU + {38A957ED-6237-439D-93AC-220BFD7D0409} + Library + Properties + Hacapp.Core + hacapp.core + v4.5 + 512 + ..\packages\Fody.1.19.1.0 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\EntityFramework.6.0.2\lib\net45\EntityFramework.dll + + + ..\packages\EntityFramework.6.0.2\lib\net45\EntityFramework.SqlServer.dll + + + ..\packages\Microsoft.AspNet.Identity.Core.1.0.0\lib\net45\Microsoft.AspNet.Identity.Core.dll + + + ..\packages\Microsoft.AspNet.Identity.EntityFramework.1.0.0\lib\net45\Microsoft.AspNet.Identity.EntityFramework.dll + + + ..\packages\NullGuard.Fody.1.1.0.0\Lib\portable-net4+sl4+wp7+win8+MonoAndroid16+MonoTouch40\NullGuard.dll + False + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {0A01520A-83F2-41F3-B2DE-B3AAA0A861B4} + Hacapp.Contracts + + + {1F19B069-BBEF-4C41-B2A1-845893F87586} + Haccapp.Model + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hacapp.web/hacapp.core/packages.config b/hacapp.web/hacapp.core/packages.config new file mode 100644 index 0000000..d3dbec9 --- /dev/null +++ b/hacapp.web/hacapp.core/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/hacapp.web/hacapp.data/App.config b/hacapp.web/hacapp.data/App.config new file mode 100644 index 0000000..28cf065 --- /dev/null +++ b/hacapp.web/hacapp.data/App.config @@ -0,0 +1,20 @@ + + + + +
+ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hacapp.web/hacapp.data/ApplicationUserStore.cs b/hacapp.web/hacapp.data/ApplicationUserStore.cs new file mode 100644 index 0000000..37ee671 --- /dev/null +++ b/hacapp.web/hacapp.data/ApplicationUserStore.cs @@ -0,0 +1,18 @@ +using System.Data.Entity; +using Hacapp.Data.DataContexts; +using Haccapp.Model.Identity; +using Microsoft.AspNet.Identity.EntityFramework; + +namespace Hacapp.Data +{ + public class ApplicationUserStore : UserStore + { + private ApplicationUserStore() + { + } + + public ApplicationUserStore(ApplicationDb context) : base(context) + { + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.data/Commands/CreateNewTeamCommandHandler.cs b/hacapp.web/hacapp.data/Commands/CreateNewTeamCommandHandler.cs new file mode 100644 index 0000000..8a8c77c --- /dev/null +++ b/hacapp.web/hacapp.data/Commands/CreateNewTeamCommandHandler.cs @@ -0,0 +1,44 @@ +using System; +using System.Linq; +using hacapp.contracts.Commands; +using Hacapp.Core.Commands; +using Hacapp.Core.Queries; +using Hacapp.Data.DataContexts; +using Haccapp.Model.Domain; + +namespace Hacapp.Data.Commands +{ + /// + /// Handler for creating the creating new team command, which will add the team to table storage and associated + /// + public class CreateNewTeamCommandHandler : CommandHandlerBase + { + private readonly ApplicationDb db; + + public CreateNewTeamCommandHandler(ApplicationDb db) + { + this.db = db; + } + + public override void Execute(CreateNewTeamCommand command, ICommandAndQueryDispatcher dispatcher) + { + if (!command.TeamName.HasValue) + { + throw new ArgumentException("The team name was invalid, it cannot be blank."); + } + + var teamExistsQuery = new GetUserOwnedTeamsQuery(command.TeamOwnerId.Id); + if (dispatcher.ExecuteQuery(teamExistsQuery).Any(t => t.Name == command.TeamName.Value)) + { + //team with this name already exists, so we can't add a new one + throw new ArgumentException( + string.Format("The team name was invalid. A team called '{0}' already exists.", + command.TeamName.Value)); + } + var team = new Team(command.TeamName.Value, command.TeamOwnerId); + db.Teams.Add(team); + db.SaveChanges(); + command.TeamId = team.Id; + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.data/Commands/DeleteTeamCommandHandler.cs b/hacapp.web/hacapp.data/Commands/DeleteTeamCommandHandler.cs new file mode 100644 index 0000000..a471533 --- /dev/null +++ b/hacapp.web/hacapp.data/Commands/DeleteTeamCommandHandler.cs @@ -0,0 +1,47 @@ +using System.Threading; +using hacapp.contracts.Commands; +using Hacapp.Core.Commands; +using Hacapp.Core.Queries; +using Hacapp.Data.DataContexts; +using Hacapp.Data.Identity; +using Haccapp.Model.Domain; +using Haccapp.Model.Identity; +using Microsoft.AspNet.Identity; + +namespace Hacapp.Data.Commands +{ + internal class DeleteTeamCommandHandler : CommandHandlerBase + { + private readonly ApplicationDb db; + + public DeleteTeamCommandHandler(ApplicationDb db) + { + this.db = db; + } + + public override void Execute(DeleteTeamCommand command, ICommandAndQueryDispatcher dispatcher) + { + Team team = dispatcher.ExecuteQuery(new GetTeamByIdQuery(command.TeamId)); + if (CurrentUserCanDeleteTeams(team, dispatcher)) + { + db.Teams.Remove(team); + db.SaveChanges(); + } + } + + private static bool CurrentUserCanDeleteTeams(Team team, ICommandAndQueryDispatcher dispatcher) + { + if (!team.Owner.IsCurrentUser()) + { + ApplicationUser user = + dispatcher.ExecuteQuery(new GetUserByIdQuery(Thread.CurrentPrincipal.Identity.GetUserId())); + if (!user.IsInRole(RoleName.TeamManagement)) + { + return false; + } + } + + return true; + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.data/Commands/JoinATeamCommandHandler.cs b/hacapp.web/hacapp.data/Commands/JoinATeamCommandHandler.cs new file mode 100644 index 0000000..3e0d045 --- /dev/null +++ b/hacapp.web/hacapp.data/Commands/JoinATeamCommandHandler.cs @@ -0,0 +1,36 @@ +using hacapp.contracts.Commands; +using Hacapp.Core.Commands; +using Hacapp.Core.Queries; +using Hacapp.Data.DataContexts; +using Haccapp.Model.Domain; + +namespace Hacapp.Data.Commands +{ + internal class JoinATeamCommandHandler : CommandHandlerBase + { + private readonly ApplicationDb db; + + public JoinATeamCommandHandler(ApplicationDb db) + { + this.db = db; + } + + public override void Execute(JoinTeamCommand command, ICommandAndQueryDispatcher dispatcher) + { + var teamQuery = new GetTeamByIdQuery(command.TeamId); + Team team = dispatcher.ExecuteQuery(teamQuery); + if (UserIsNotAMemberOfTeam(command.GetUserId, team)) + { + var userQuery = new GetUserByIdQuery(command.GetUserId); + team.Members.Add(new TeamMembership(dispatcher.ExecuteQuery(userQuery), MembershipStatus.Pending)); + } + + db.SaveChanges(); + } + + private static bool UserIsNotAMemberOfTeam(string userId, Team team) + { + return !(team.Owner.Id == userId || team.Members.Exists(t => t.User.Id == userId)); + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.data/Commands/RemoveUserFromTeamCommandHandler.cs b/hacapp.web/hacapp.data/Commands/RemoveUserFromTeamCommandHandler.cs new file mode 100644 index 0000000..38a796b --- /dev/null +++ b/hacapp.web/hacapp.data/Commands/RemoveUserFromTeamCommandHandler.cs @@ -0,0 +1,42 @@ +using System.Linq; +using System.Threading; +using hacapp.contracts.Commands; +using Hacapp.Core.Commands; +using Hacapp.Core.Queries; +using Hacapp.Data.DataContexts; +using Hacapp.Data.Identity; +using Haccapp.Model.Domain; +using Microsoft.AspNet.Identity; + +namespace Hacapp.Data.Commands +{ + internal class RemoveUserFromTeamCommandHandler : CommandHandlerBase + { + private readonly ApplicationDb db; + + public RemoveUserFromTeamCommandHandler(ApplicationDb db) + { + this.db = db; + } + + public override void Execute(RemoveUserFromTeamCommand command, ICommandAndQueryDispatcher dispatcher) + { + Team team = dispatcher.ExecuteQuery(new GetTeamByIdQuery(command.TeamId)); + if (CurrentUserIsAllowedToRemoveTargetUser(command, team)) + { + TeamMembership membership = team.Members.SingleOrDefault(m => m.User.Id == command.UserId); + if (membership != null) + { + team.Members.Remove(membership); + } + + db.SaveChanges(); + } + } + + private static bool CurrentUserIsAllowedToRemoveTargetUser(RemoveUserFromTeamCommand command, Team team) + { + return team.Owner.IsCurrentUser() || Thread.CurrentPrincipal.Identity.GetUserId() == command.UserId; + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.data/Commands/UpdateMembershipStatusCommandHandler.cs b/hacapp.web/hacapp.data/Commands/UpdateMembershipStatusCommandHandler.cs new file mode 100644 index 0000000..f4246f8 --- /dev/null +++ b/hacapp.web/hacapp.data/Commands/UpdateMembershipStatusCommandHandler.cs @@ -0,0 +1,35 @@ +using System; +using System.Linq; +using hacapp.contracts.Commands; +using Hacapp.Core.Commands; +using Hacapp.Core.Queries; +using Hacapp.Data.DataContexts; +using Hacapp.Data.Identity; +using Haccapp.Model.Domain; + +namespace Hacapp.Data.Commands +{ + internal class UpdateMembershipStatusCommandHandler : CommandHandlerBase + { + private readonly ApplicationDb db; + + public UpdateMembershipStatusCommandHandler(ApplicationDb db) + { + this.db = db; + } + + public override void Execute(UpdateMembershipStatusCommand command, ICommandAndQueryDispatcher dispatcher) + { + var getTeamQuery = new GetTeamByIdQuery(command.TeamId); + Team team = dispatcher.ExecuteQuery(getTeamQuery); + if (!team.Owner.IsCurrentUser()) + { + throw new InvalidOperationException("Only the owner of the team can update the membership status"); + } + + TeamMembership teamMembership = team.Members.Single(m => m.User.Id == command.UserIdToUpdate); + teamMembership.Status = command.NewStatus; + db.SaveChanges(); + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.data/DataContexts/ApplicationDb.cs b/hacapp.web/hacapp.data/DataContexts/ApplicationDb.cs new file mode 100644 index 0000000..cd7faa7 --- /dev/null +++ b/hacapp.web/hacapp.data/DataContexts/ApplicationDb.cs @@ -0,0 +1,51 @@ +using System; +using System.Data.Entity; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Haccapp.Model.Domain; +using Haccapp.Model.Identity; +using Microsoft.AspNet.Identity.EntityFramework; + +namespace Hacapp.Data.DataContexts +{ + [ExcludeFromCodeCoverage] + public class ApplicationDb : IdentityDbContext + { + private static readonly Guid dbName = Guid.NewGuid(); + + private static readonly string NameOrConnectionString = + @"Server=(localdb)\v11.0;Integrated Security=true;Initial Catalog=" + dbName + + ";AttachDbFileName= e:\\localdb\\" + dbName + ".mdf"; + +#if NCRUNCH + public ApplicationDb() + : base(InitialCatalog.ToString()) + { + Configuration.LazyLoadingEnabled = false; + Database.Log = s => Log(s); + } +#else + public ApplicationDb() + : base("DefaultConnection") + { + Configuration.LazyLoadingEnabled = false; + Database.Log = s => Log(s); + } +#endif + + private void Log(string s) + { + Trace.WriteLine(s); + } + + /// + /// Property for updating the Teams + /// + public IDbSet Teams { get; set; } + + public static string InitialCatalog + { + get { return NameOrConnectionString; } + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.data/DataContexts/ApplicationMigrations/201401232042590_InitialCreate.Designer.cs b/hacapp.web/hacapp.data/DataContexts/ApplicationMigrations/201401232042590_InitialCreate.Designer.cs new file mode 100644 index 0000000..1053d66 --- /dev/null +++ b/hacapp.web/hacapp.data/DataContexts/ApplicationMigrations/201401232042590_InitialCreate.Designer.cs @@ -0,0 +1,29 @@ +// +namespace Hacapp.Data.DataContexts.ApplicationMigrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.0.0-20911")] + public sealed partial class InitialCreate : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(InitialCreate)); + + string IMigrationMetadata.Id + { + get { return "201401232042590_InitialCreate"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/hacapp.web/hacapp.data/DataContexts/ApplicationMigrations/201401232042590_InitialCreate.cs b/hacapp.web/hacapp.data/DataContexts/ApplicationMigrations/201401232042590_InitialCreate.cs new file mode 100644 index 0000000..d8cdc3a --- /dev/null +++ b/hacapp.web/hacapp.data/DataContexts/ApplicationMigrations/201401232042590_InitialCreate.cs @@ -0,0 +1,124 @@ +namespace Hacapp.Data.DataContexts.ApplicationMigrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class InitialCreate : DbMigration + { + public override void Up() + { + CreateTable( + "dbo.AspNetRoles", + c => new + { + Id = c.String(nullable: false, maxLength: 128), + Name = c.String(nullable: false), + }) + .PrimaryKey(t => t.Id); + + CreateTable( + "dbo.Teams", + c => new + { + Id = c.Int(nullable: false, identity: true), + Name = c.String(nullable: false), + Owner_Id = c.String(nullable: false, maxLength: 128), + }) + .PrimaryKey(t => t.Id) + .ForeignKey("dbo.AspNetUsers", t => t.Owner_Id, cascadeDelete: true) + .Index(t => t.Owner_Id); + + CreateTable( + "dbo.TeamMemberships", + c => new + { + Id = c.Int(nullable: false, identity: true), + Status = c.Int(nullable: false), + User_Id = c.String(nullable: false, maxLength: 128), + Team_Id = c.Int(), + }) + .PrimaryKey(t => t.Id) + .ForeignKey("dbo.AspNetUsers", t => t.User_Id, cascadeDelete: true) + .ForeignKey("dbo.Teams", t => t.Team_Id) + .Index(t => t.User_Id) + .Index(t => t.Team_Id); + + CreateTable( + "dbo.AspNetUsers", + c => new + { + Id = c.String(nullable: false, maxLength: 128), + UserName = c.String(), + PasswordHash = c.String(), + SecurityStamp = c.String(), + PreferredEmailAddress = c.String(), + Discriminator = c.String(nullable: false, maxLength: 128), + }) + .PrimaryKey(t => t.Id); + + CreateTable( + "dbo.AspNetUserClaims", + c => new + { + Id = c.Int(nullable: false, identity: true), + ClaimType = c.String(), + ClaimValue = c.String(), + User_Id = c.String(nullable: false, maxLength: 128), + }) + .PrimaryKey(t => t.Id) + .ForeignKey("dbo.AspNetUsers", t => t.User_Id, cascadeDelete: true) + .Index(t => t.User_Id); + + CreateTable( + "dbo.AspNetUserLogins", + c => new + { + UserId = c.String(nullable: false, maxLength: 128), + LoginProvider = c.String(nullable: false, maxLength: 128), + ProviderKey = c.String(nullable: false, maxLength: 128), + }) + .PrimaryKey(t => new { t.UserId, t.LoginProvider, t.ProviderKey }) + .ForeignKey("dbo.AspNetUsers", t => t.UserId, cascadeDelete: true) + .Index(t => t.UserId); + + CreateTable( + "dbo.AspNetUserRoles", + c => new + { + UserId = c.String(nullable: false, maxLength: 128), + RoleId = c.String(nullable: false, maxLength: 128), + }) + .PrimaryKey(t => new { t.UserId, t.RoleId }) + .ForeignKey("dbo.AspNetRoles", t => t.RoleId, cascadeDelete: true) + .ForeignKey("dbo.AspNetUsers", t => t.UserId, cascadeDelete: true) + .Index(t => t.RoleId) + .Index(t => t.UserId); + + } + + public override void Down() + { + DropForeignKey("dbo.Teams", "Owner_Id", "dbo.AspNetUsers"); + DropForeignKey("dbo.TeamMemberships", "Team_Id", "dbo.Teams"); + DropForeignKey("dbo.TeamMemberships", "User_Id", "dbo.AspNetUsers"); + DropForeignKey("dbo.AspNetUserClaims", "User_Id", "dbo.AspNetUsers"); + DropForeignKey("dbo.AspNetUserRoles", "UserId", "dbo.AspNetUsers"); + DropForeignKey("dbo.AspNetUserRoles", "RoleId", "dbo.AspNetRoles"); + DropForeignKey("dbo.AspNetUserLogins", "UserId", "dbo.AspNetUsers"); + DropIndex("dbo.Teams", new[] { "Owner_Id" }); + DropIndex("dbo.TeamMemberships", new[] { "Team_Id" }); + DropIndex("dbo.TeamMemberships", new[] { "User_Id" }); + DropIndex("dbo.AspNetUserClaims", new[] { "User_Id" }); + DropIndex("dbo.AspNetUserRoles", new[] { "UserId" }); + DropIndex("dbo.AspNetUserRoles", new[] { "RoleId" }); + DropIndex("dbo.AspNetUserLogins", new[] { "UserId" }); + DropTable("dbo.AspNetUserRoles"); + DropTable("dbo.AspNetUserLogins"); + DropTable("dbo.AspNetUserClaims"); + DropTable("dbo.AspNetUsers"); + DropTable("dbo.TeamMemberships"); + DropTable("dbo.Teams"); + DropTable("dbo.AspNetRoles"); + } + } +} diff --git a/hacapp.web/hacapp.data/DataContexts/ApplicationMigrations/201401232042590_InitialCreate.resx b/hacapp.web/hacapp.data/DataContexts/ApplicationMigrations/201401232042590_InitialCreate.resx new file mode 100644 index 0000000..6cc12e7 --- /dev/null +++ b/hacapp.web/hacapp.data/DataContexts/ApplicationMigrations/201401232042590_InitialCreate.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + H4sIAAAAAAAEAO1dyW7kthbdPyD/IGiVPCCW7WySRlUCx8OLkdhudDnZNmiJVRaepogst/1tWeST8gshNXIWNVSVqpONYUvkHQ+vOFxe//XHn4sfXuPIeYE5CtNk6Z6dnLoOTPw0CJPN0t3i9dffuj98/8V/FtdB/Or8Vrf7hrYjPRO0dJ8xzt55HvKfYQzQSRz6eYrSNT7x09gDQeqdn55+552deZCQcAktx1l82CY4jGHxB/nzMk18mOEtiO7SAEaoek7erAqqzj2IIcqAD5fuT8AHWXZyBTAofpC+GL5i5DoXUQiIPCsYrV0HJEmKASbSvvsVwRXO02SzysgDED2+ZZC0W4MIwUqLd21zW4VOz6lCXttxkEHcRlWi7DUxCn6j4hUKL93bABaPPqQRZFuStj/DN+4BefQ+TzOYk9Zw3fR3HY/v54kdm25MHyoCMSTOCQpc5w68/gKTDX4m+Dj/1nVuwlcY1E8qK/6ahAQ0pBPOt+TP+20UgacINu89I0/608CV/Dqe68JrzWs0+iME8f6NfZvgb84VKjA4XuE0h/+DCcwBhsF7gDHMkxYkczAy4XoPXsJNIbDA/w7GTyR+uM4HGBXv0XOYlcP1hNr8Y9PgJk9jCvnSF/Xzj6t0m/tUhVTx8hHkG4jtpXn4ROyol6V6LUhSPFXLUb5SSdELeJU6RJrPEYIrQmaLGhBSa7cK1y8HA4zEeb1HWz4fy3a8a4XXSh+LbUY5+yLLotAvJC3l+REgyNilNmnx0mjU9zlcwzyHwXUMwugiCHKI0AQDfZBarNiXEQg/y0haKEaZTWxlDaffQLTdAav+40hyrjSU1C2k0aRpNmpAGQbMZzVXovpN9Ck3M3oPEPqU5sFPAD3vnNkK+tuceI98BOJsj1gv0KeekwxGew3jDrTXg8JW1F/STZh0i1o0M4ratjCKyjTrKyol1i0pbWUUtGlglLNtpRJzUPwoVB8SRGhnOZAomxY8yOOXMKAWsOhRNyaS9A9VtWT7DleCmvtmz9ls58zHfVJ7jVzTJ1UxwEcPiaFbED1GBGUx5Ct8KGjX8h4OVoVTrMJs2dIQZosfJlC1rfouta3A3+Nj0CnlAORvYwb38pr0Ft1EYIMYB5KYEr0Rh7NrDt4eJZU6DsEkKOBRTOaX7qlkPq75ZZqswxgGTfszc/vVFmWEA9PhXNa81JF9eIFQ6oeFW3RfwY+K+fR1Ejh2wUm9ni0/r87dNsIhXQWTx0v3v5KGnWyaWYFm2SxwOHPFGPSQXMEIYuhc+OXW7yVAPgjkEUisF/BPPtBFN+UFIuIshHMQJliOcWHihxmIrPQQeluuUqh0DR/xzRUsYJFgK3/ZCKCN517DSzBdl6UWHgNDe3S2Yc0GNYoYpwZNGSiHQVMOkQomKgbzQqakxp6BKfnKhr968nAoXFoHTcVXbUe4PP6IKalxCFwebbxktjBsMKPavVODptzmHQhN1bbJvrHZw6CqkwSdmsZjhVZH4dSnhx1NZxIMA+mkYSZ2bM77TPrJh3+85URtTk9O5KmyhqTCVr2c0Vff8kzRKJpwwGjWtQsbwpnkrCBRrsNo7gaJbs0yhpHr6kmyFO2xgpjfY2wXdMp5lmQgngg1k4pIaW6Lzi1YdGRYOHUQlAKjST8m9PYg20WxF7F6R9pIsVrs9SDb5dl2OiQQZbBmELjaZWAam/fixLFgvfptdFRbTZoRWK93NYQlmtzoI817WovZODIbS7MU67EY02hUIcHSUvLyiyGrJDWFgWzQpFkT9FgVTGegQyCJPT4zW0o3S+0zT9UoVUdUS2MpZqY7tJYyRUU2Vef803oGyugifccMFjLNOXdsnjY/TG0X9Xyye0YpWKJbf2kCaW/LoapX6WgaxRUTy66pZX+l+cnkBL6uN+ObSWDzbuGVab/Vg4WnyQ9e3IEsC5MNky9cPXFWZbLw5der/mm5cUnD8zkoiVPWhhNOc7CBwlu6hR/AmzBHmCYpPwF6anAZxFIzqylvzYud+cruqqdLdWv6e9lDlzPNT5bl7YyK0g1RMKZ7IkXelML16u4OTd8GEcgVWTOXabSNE/0Wjb53mfvC9i+fyBQWniC/tAUj2UxazPAOsHJPOZ4mcY+cimzlFnW3f7A72Ig8mWN0qbrWLjIR2I2z6uNMlkL9bDYO478nk7hL3O8YHunU3XfjLU1iL0tQ00TJg1gjCIs9qeqIWNoFYilfhcjPwzhMALHELtDR4ctbRH9/WH/Z+e2isn91JC6lvOSI2T7tAQ4uO5PDBPemR3DgUzC5GMG/2jsYrDAwPwhII47fadvrcJsgGFcL6Mkdp7gn0Nt7Ghq7GcVM/j9LhHnck1aV4S8Rq57PEg3VNubkaFDk3vZGg4aGKSqLiNCd2+qpCJmuLLGOXF89TS59lf/wa3OBZ4KPHaxS1XmovdHRb7U6DTjq9BSWii5lZffu4/dkOiJ+tRk5JKhXXXsHbrr3JEVs09asbEGrsF+QUGdnMBIMFk6bJjL4m6SXS9xo6+135Q5076WwxuPm5a7CooZ95gP72rCpfjyObvfSLT3cdpjEteIm/EDLFaQm8qm4q78XZ3KLhFtELwI0OeYWik6DhepwwRYJVXObTUedqbmzhIGGLmlM5XzudGQm41g6IBGbNNOE5qBEOBBZVIcT3VVVpNOKsonr1BPNpbt6QxjGJSpWv0eXUUg+em2DO5CEa4jwY/p/mCzd89Ozc6Eky4DyKB5CQTTnGinJC8j9Z5DL935GlECpiX4Zg9evjrusSUgh0nndfk+2kim1QWSUO4+mxMdO3MFX9ShYDLjTPt4LMt3ms6kUbaIyF0ccdNqN547BNLRewCiCypoA40Q0lUoZRVnYv91PNJlBsZWdBBSpvsoo18g1VEaRmyZW/VuWgJds4sCmrDowGXVFUYF9wuBoruJPZnD+pv3Etp70EvRhrtYZMsfHXsIefWvvINeaj+tqnv1V5jIb/xC3ivUZ5SOvrI4CmOY0aOfXQD/nK8lzCmGHxtchApg1vmYTv3peLZ4TwKobdOOuNh8dxHQZN2qMaTbZ9wCyPtet54Qw8d7niOveR4GtznToOQLL6v75FBfOD4kEzQ2COSDAcHi7LwSYb+TPLaIMKAhwNNHDio/pyHdyyAhnr/VmonSrS/RYdSCuOp3sKlZQnrku3eApJb4uNzIuUHYPsfr2ssisxITEpHysIq68+tdR58CizIGOlelipM5w5RjTGq5KN9IaTn0p0cSsmvMZOVZtzGzV9427CyrY1VMw81bXGOisumBVdMHMWY3UQ1Zm0F5W7VeCwVzP4RhKL7Aelq9v9ikiYKxHcCxFFsbjYt+m2GEZhfHG4OKmJs13pnUSxhtC/YlU5UfOqhaCJOi8lBxX9WAar3aaaJoKB3KiHpm6Mv8kjUyaUbhpSdC00QT63KS1aXObrNN69ixIVDcRDtjuIAYBmdFe5DhcAx+T1z5EiK1YfE28G9wmD1ucbTFRmXg74g586RzcxL8o48DLvHjIipLQU6hAxAyJCvAh+XEbRm0h5BvFSaCGBJ3cV2kM1JeYpjNs3hpK92liSagyX7MmeYRxFhFi6CFZgReol63bhrzFFlch2ORkEl/RaPuTPwn8gvj1+78BOPvcYuZvAAA= + + + dbo + + \ No newline at end of file diff --git a/hacapp.web/hacapp.data/DataContexts/ApplicationMigrations/Configuration.cs b/hacapp.web/hacapp.data/DataContexts/ApplicationMigrations/Configuration.cs new file mode 100644 index 0000000..caf4ac6 --- /dev/null +++ b/hacapp.web/hacapp.data/DataContexts/ApplicationMigrations/Configuration.cs @@ -0,0 +1,71 @@ +using System; +using System.Data.Entity.Migrations; +using Haccapp.Model.Identity; +using Microsoft.AspNet.Identity; +using Microsoft.AspNet.Identity.EntityFramework; + +namespace Hacapp.Data.DataContexts.ApplicationMigrations +{ + public sealed class Configuration : DbMigrationsConfiguration + { + public Configuration() + { + AutomaticMigrationsEnabled = false; + MigrationsDirectory = @"DataContexts\ApplicationMigrations"; + } + + protected override void Seed(ApplicationDb context) + { + var roleManager = new RoleManager(new RoleStore(context)); + if (!roleManager.RoleExists(RoleName.TeamManagement)) + { + roleManager.Create(new IdentityRole(RoleName.TeamManagement)); + } + + const string siteOwnerId = "SiteOwner"; + var userManager = new UserManager(new UserStore(context)); + ApplicationUser sam = userManager.FindById(siteOwnerId); + if (sam == null) + { + sam = new ApplicationUser + { + Id = siteOwnerId, + PreferredEmailAddress = "samholder@gmail.com", + UserName = "SamHolder" + }; + IdentityResult identityResult = userManager.Create(sam); + if (identityResult.Succeeded) + { + IdentityResult result = userManager.AddLoginAsync(siteOwnerId, + new UserLoginInfo("Google", + "https://www.google.com/accounts/o8/id?id=AItOawllqzkU2MreNrjy29lR08H0AVzjd-Y4T6s")).Result; + if (!result.Succeeded) + { + throw new Exception("Problem setting up the db"); + } + + if (!userManager.AddToRoleAsync(siteOwnerId, RoleName.TeamManagement).Result.Succeeded) + { + throw new Exception("Unable to add sam to the role of site admin"); + } + } + else + { + throw new Exception("Problem creating the default user " + string.Join(",", identityResult.Errors)); + } + } + // This method will be called after migrating to the latest version. + + // You can use the DbSet.AddOrUpdate() helper extension method + // to avoid creating duplicate seed data. E.g. + // + // context.People.AddOrUpdate( + // p => p.FullName, + // new Person { FullName = "Andrew Peters" }, + // new Person { FullName = "Brice Lambson" }, + // new Person { FullName = "Rowan Miller" } + // ); + // + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.data/DataContexts/TestDbConfiguration.cs b/hacapp.web/hacapp.data/DataContexts/TestDbConfiguration.cs new file mode 100644 index 0000000..ba01f95 --- /dev/null +++ b/hacapp.web/hacapp.data/DataContexts/TestDbConfiguration.cs @@ -0,0 +1,14 @@ +using System.Data.Entity; +using System.Data.Entity.Infrastructure; + +namespace Hacapp.Data.DataContexts +{ + public class TestDbConfiguration : DbConfiguration + { + public TestDbConfiguration() + { + var localDbConnectionFactory = new LocalDbConnectionFactory("v11.0"); + SetDefaultConnectionFactory(localDbConnectionFactory); + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.data/Fody.targets b/hacapp.web/hacapp.data/Fody.targets new file mode 100644 index 0000000..a668a51 --- /dev/null +++ b/hacapp.web/hacapp.data/Fody.targets @@ -0,0 +1,89 @@ + + + + + + $(NCrunchOriginalSolutionDir) + + + + + $(SolutionDir) + + + + + $(MSBuildProjectDirectory)\..\ + + + + + + + $(KeyOriginatorFile) + + + + + $(AssemblyOriginatorKeyFile) + + + + + + + + + + $(ProjectDir)$(IntermediateOutputPath) + Low + $(SignAssembly) + $(MSBuildThisFileDirectory) + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hacapp.web/hacapp.data/FodyWeavers.xml b/hacapp.web/hacapp.data/FodyWeavers.xml new file mode 100644 index 0000000..9d96e0a --- /dev/null +++ b/hacapp.web/hacapp.data/FodyWeavers.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/hacapp.web/hacapp.data/Identity/ApplicationUserExtensions.cs b/hacapp.web/hacapp.data/Identity/ApplicationUserExtensions.cs new file mode 100644 index 0000000..292ca1d --- /dev/null +++ b/hacapp.web/hacapp.data/Identity/ApplicationUserExtensions.cs @@ -0,0 +1,20 @@ +using System.Linq; +using System.Threading; +using Haccapp.Model.Identity; +using Microsoft.AspNet.Identity; + +namespace Hacapp.Data.Identity +{ + public static class ApplicationUserExtensions + { + public static bool IsCurrentUser(this ApplicationUser user) + { + return user.Id == Thread.CurrentPrincipal.Identity.GetUserId(); + } + + public static bool IsInRole(this ApplicationUser user, RoleName roleName) + { + return user.Roles.Any(x => x.Role.Name == RoleName.TeamManagement); + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.data/Properties/AssemblyInfo.cs b/hacapp.web/hacapp.data/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f072703 --- /dev/null +++ b/hacapp.web/hacapp.data/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("hacapp.data")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("hacapp.data")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("3d66edf4-5f05-49bd-be94-ddc675f0c6b9")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/hacapp.web/hacapp.data/Query/GetAllTeamsQueryHandler.cs b/hacapp.web/hacapp.data/Query/GetAllTeamsQueryHandler.cs new file mode 100644 index 0000000..99b59dc --- /dev/null +++ b/hacapp.web/hacapp.data/Query/GetAllTeamsQueryHandler.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Data.Entity; +using System.Linq; +using hacapp.contracts.Commands; +using Hacapp.Core.Queries; +using Hacapp.Data.DataContexts; +using Haccapp.Model.Domain; + +namespace Hacapp.Data.Query +{ + public class GetAllTeamsQueryHandler : QueryHandlerBase> + { + private readonly ApplicationDb db; + + public GetAllTeamsQueryHandler(ApplicationDb db) + { + this.db = db; + } + + public override IEnumerable Execute(GetAllTeamsQuery query, ICommandAndQueryDispatcher dispatcher) + { + return db.Teams + .Include(x => x.Owner) + .Include(x => x.Members.Select(m => m.User)) + .ToList(); + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.data/Query/GetPendingMembershipsForUsersTeamsQueryHandler.cs b/hacapp.web/hacapp.data/Query/GetPendingMembershipsForUsersTeamsQueryHandler.cs new file mode 100644 index 0000000..07ef764 --- /dev/null +++ b/hacapp.web/hacapp.data/Query/GetPendingMembershipsForUsersTeamsQueryHandler.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Linq; +using hacapp.contracts.Commands; +using Hacapp.Core.Queries; +using Hacapp.Core.Queries.Result; +using Hacapp.Data.DataContexts; +using Haccapp.Model.Domain; + +namespace Hacapp.Data.Query +{ + public class GetPendingMembershipsForUsersTeamsQueryHandler : + QueryHandlerBase> + { + private readonly ApplicationDb db; + + public GetPendingMembershipsForUsersTeamsQueryHandler(ApplicationDb db) + { + this.db = db; + } + + public override IEnumerable Execute(GetPendingMembershipsForUsersTeamsQuery query, + ICommandAndQueryDispatcher dispatcher) + { + var pendingMemberships = + db.Teams.Where( + t => t.Owner.Id == query.UserId && t.Members.Any(m => m.Status == MembershipStatus.Pending)) + .SelectMany( + t => + t.Members.Where(m => m.Status == MembershipStatus.Pending) + .Select(m => new {UserId = m.User.Id, TeamId = t.Id, m.User.UserName, TeamName = t.Name})) + .ToList(); + + return + pendingMemberships.Select( + pendingMembership => + new PendingMembershipResult + { + UserId = pendingMembership.UserId, + TeamId = pendingMembership.TeamId, + UserName = pendingMembership.UserName, + TeamName = pendingMembership.TeamName + }).ToList(); + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.data/Query/GetTeamByIdQueryHandler.cs b/hacapp.web/hacapp.data/Query/GetTeamByIdQueryHandler.cs new file mode 100644 index 0000000..658f75a --- /dev/null +++ b/hacapp.web/hacapp.data/Query/GetTeamByIdQueryHandler.cs @@ -0,0 +1,33 @@ +using System.Data.Entity; +using System.Linq; +using hacapp.contracts.Commands; +using Hacapp.Core.Commands; +using Hacapp.Core.Queries; +using Hacapp.Data.DataContexts; +using Haccapp.Model.Domain; + +namespace Hacapp.Data.Query +{ + public class GetTeamByIdQueryHandler : QueryHandlerBase + { + private readonly ApplicationDb db; + + public GetTeamByIdQueryHandler(ApplicationDb db) + { + this.db = db; + } + + public override Team Execute(GetTeamByIdQuery query, ICommandAndQueryDispatcher dispatcher) + { + Team team = db.Teams + .Include(x => x.Owner) + .Include(x => x.Members.Select(m => m.User)) + .FirstOrDefault(t => t.Id == query.TeamId); + if (team == null) + { + throw new CommandExecutionException(CommandExecutionExceptionMessages.TeamDoesNotExist(query.TeamId)); + } + return team; + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.data/Query/GetTeamsUserDoesNotBelongToQueryHandler.cs b/hacapp.web/hacapp.data/Query/GetTeamsUserDoesNotBelongToQueryHandler.cs new file mode 100644 index 0000000..ee0fe9d --- /dev/null +++ b/hacapp.web/hacapp.data/Query/GetTeamsUserDoesNotBelongToQueryHandler.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Data.Entity; +using System.Linq; +using hacapp.contracts.Commands; +using Hacapp.Core.Queries; +using Hacapp.Data.DataContexts; +using Haccapp.Model.Domain; + +namespace Hacapp.Data.Query +{ + internal class GetTeamsUserDoesNotBelongToQueryHandler : + QueryHandlerBase> + { + private readonly ApplicationDb db; + + public GetTeamsUserDoesNotBelongToQueryHandler(ApplicationDb db) + { + this.db = db; + } + + public override IEnumerable Execute(GetTeamsUserDoesNotBelongToQuery query, + ICommandAndQueryDispatcher dispatcher) + { + return db.Teams + .Include(x => x.Owner) + .Include(x => x.Members.Select(m => m.User)) + .Where(x => x.Owner.Id != query.UserId + && + !x.Members.Select(m => m.User.Id) + .Contains(query.UserId)) + .ToList(); + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.data/Query/GetUserByIdQueryHandler.cs b/hacapp.web/hacapp.data/Query/GetUserByIdQueryHandler.cs new file mode 100644 index 0000000..3fe3fd2 --- /dev/null +++ b/hacapp.web/hacapp.data/Query/GetUserByIdQueryHandler.cs @@ -0,0 +1,23 @@ +using hacapp.contracts.Commands; +using Hacapp.Core.Queries; +using Hacapp.Data.DataContexts; +using Haccapp.Model; +using Haccapp.Model.Identity; + +namespace Hacapp.Data.Query +{ + public class GetUserByIdQueryHandler : QueryHandlerBase + { + private readonly ApplicationDb db; + + public GetUserByIdQueryHandler(ApplicationDb db) + { + this.db = db; + } + + public override ApplicationUser Execute(GetUserByIdQuery query, ICommandAndQueryDispatcher dispatcher) + { + return db.Users.Find(query.GetUserId); + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.data/Query/GetUserConfirmedTeamsQueryHandler.cs b/hacapp.web/hacapp.data/Query/GetUserConfirmedTeamsQueryHandler.cs new file mode 100644 index 0000000..536fd52 --- /dev/null +++ b/hacapp.web/hacapp.data/Query/GetUserConfirmedTeamsQueryHandler.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Data.Entity; +using System.Linq; +using hacapp.contracts.Commands; +using Hacapp.Core.Queries; +using Hacapp.Data.DataContexts; +using Haccapp.Model.Domain; + +namespace Hacapp.Data.Query +{ + public class GetUserConfirmedTeamsQueryHandler : QueryHandlerBase> + { + private readonly ApplicationDb db; + + public GetUserConfirmedTeamsQueryHandler(ApplicationDb db) + { + this.db = db; + } + + public override IEnumerable Execute(GetUserConfirmedTeamsQuery query, + ICommandAndQueryDispatcher dispatcher) + { + return db.Teams + .Include(x => x.Owner) + .Include(x => x.Members.Select(m => m.User)) + .Where( + x => + x.Owner.Id == query.UserId || + x.Members.Where(m => m.Status == MembershipStatus.Confimed) + .Select(m => m.User.Id) + .Contains(query.UserId)) + .ToList(); + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.data/Query/GetUserOwnedTeamsQueryHandler.cs b/hacapp.web/hacapp.data/Query/GetUserOwnedTeamsQueryHandler.cs new file mode 100644 index 0000000..7e511fb --- /dev/null +++ b/hacapp.web/hacapp.data/Query/GetUserOwnedTeamsQueryHandler.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Data.Entity; +using System.Linq; +using hacapp.contracts.Commands; +using Hacapp.Core.Queries; +using Hacapp.Data.DataContexts; +using Haccapp.Model.Domain; + +namespace Hacapp.Data.Query +{ + public class GetUserOwnedTeamsQueryHandler : QueryHandlerBase> + { + private readonly ApplicationDb db; + + public GetUserOwnedTeamsQueryHandler(ApplicationDb db) + { + this.db = db; + } + + public override IEnumerable Execute(GetUserOwnedTeamsQuery query, ICommandAndQueryDispatcher dispatcher) + { + return db.Teams + .Include(x => x.Owner) + .Include(x => x.Members.Select(m => m.User)) + .Where(x => x.Owner.Id == query.UserId).ToList(); + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.data/Query/GetUserTeamsQueryHandler.cs b/hacapp.web/hacapp.data/Query/GetUserTeamsQueryHandler.cs new file mode 100644 index 0000000..5e88df2 --- /dev/null +++ b/hacapp.web/hacapp.data/Query/GetUserTeamsQueryHandler.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Data.Entity; +using System.Linq; +using hacapp.contracts.Commands; +using Hacapp.Core.Queries; +using Hacapp.Data.DataContexts; +using Haccapp.Model.Domain; + +namespace Hacapp.Data.Query +{ + public class GetUserTeamsQueryHandler : QueryHandlerBase> + { + private readonly ApplicationDb db; + + public GetUserTeamsQueryHandler(ApplicationDb db) + { + this.db = db; + } + + public override IEnumerable Execute(GetUserTeamsQuery query, ICommandAndQueryDispatcher dispatcher) + { + return db.Teams + .Include(x => x.Owner) + .Include(x => x.Members.Select(m => m.User)) + .Where(x => x.Owner.Id == query.UserId + || + x.Members.Select(m => m.User.Id) + .Contains(query.UserId)).ToList(); + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.data/hacapp.data.csproj b/hacapp.web/hacapp.data/hacapp.data.csproj new file mode 100644 index 0000000..0195232 --- /dev/null +++ b/hacapp.web/hacapp.data/hacapp.data.csproj @@ -0,0 +1,125 @@ + + + + + Debug + AnyCPU + {2282989C-3FAB-404C-BF85-3BE9A5228E23} + Library + Properties + Hacapp.Data + Hacapp.Data + v4.5 + 512 + ..\packages\Fody.1.19.1.0 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\EntityFramework.6.0.0\lib\net45\EntityFramework.dll + + + ..\packages\EntityFramework.6.0.0\lib\net45\EntityFramework.SqlServer.dll + + + ..\packages\Microsoft.AspNet.Identity.Core.1.0.0\lib\net45\Microsoft.AspNet.Identity.Core.dll + + + ..\packages\Microsoft.AspNet.Identity.EntityFramework.1.0.0\lib\net45\Microsoft.AspNet.Identity.EntityFramework.dll + + + ..\packages\NullGuard.Fody.1.1.0.0\Lib\portable-net4+sl4+wp7+win8+MonoAndroid16+MonoTouch40\NullGuard.dll + False + + + + + + + + + + + + + + + + + + + + + + + 201401232042590_InitialCreate.cs + + + + + + + + + + + + + + + + + {0A01520A-83F2-41F3-B2DE-B3AAA0A861B4} + Hacapp.Contracts + + + {38A957ED-6237-439D-93AC-220BFD7D0409} + Hacapp.Core + + + {1f19b069-bbef-4c41-b2a1-845893f87586} + Haccapp.Model + + + + + + + + + + + + + + + + 201401232042590_InitialCreate.cs + + + + + + \ No newline at end of file diff --git a/hacapp.web/hacapp.data/packages.config b/hacapp.web/hacapp.data/packages.config new file mode 100644 index 0000000..ea3baa3 --- /dev/null +++ b/hacapp.web/hacapp.data/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/hacapp.web/hacapp.web.Tests/App.config b/hacapp.web/hacapp.web.Tests/App.config index 34eb768..88335b8 100644 --- a/hacapp.web/hacapp.web.Tests/App.config +++ b/hacapp.web/hacapp.web.Tests/App.config @@ -6,14 +6,13 @@
- +
+ - - - + @@ -22,13 +21,23 @@ - + + + + + - + + + + + + + diff --git a/hacapp.web/hacapp.web.Tests/Features/CurrentScenarioContext.cs b/hacapp.web/hacapp.web.Tests/Features/CurrentScenarioContext.cs new file mode 100644 index 0000000..a47eb15 --- /dev/null +++ b/hacapp.web/hacapp.web.Tests/Features/CurrentScenarioContext.cs @@ -0,0 +1,32 @@ +using System.Web.Mvc; +using hacapp.contracts.Commands; +using Hacapp.Data.DataContexts; +using hacapp.web.Controllers; +using Haccapp.Model.Identity; +using Microsoft.AspNet.Identity; +using Moq; + +namespace hacapp.web.Tests.Features +{ + /// + /// Class which represents the context in which the current scenario is executing. This is used to share state between + /// the various step definitions. + /// + public class CurrentScenarioContext + { + public ActionResult ActionResult { get; set; } + public TeamController TeamController { get; set; } + public HomeController HomeController { get; set; } + public ICommandAndQueryDispatcher Dispatcher { get; set; } + public UserManager UserManager { get; set; } + public ApplicationDb Db { get; set; } + public bool SkipBefore { get; set; } + public bool SkipAfter { get; set; } + + public void SetControllerContexts(Mock mockContext) + { + TeamController.ControllerContext = mockContext.Object; + HomeController.ControllerContext = mockContext.Object; + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.web.Tests/Features/HomePage/HomePage.feature b/hacapp.web/hacapp.web.Tests/Features/HomePage/HomePage.feature new file mode 100644 index 0000000..b28c02f --- /dev/null +++ b/hacapp.web/hacapp.web.Tests/Features/HomePage/HomePage.feature @@ -0,0 +1,14 @@ +Feature: HomePage + In order to user the application effectively + As a user + I want the home page to show the most relevant + +@mytag +Scenario: Pending memberships should be listed on the home page for the team owner + Given There is a registered user with id 'Bob' + And There is a registered user with id 'Tom' + And Team '1' is owned by 'Tom' + And 'Bob' has pending membership for Team '1' + And I am logged in as 'Tom' + When I visit the home page + Then I should see that 'Bob' has a pending membership for team '1' diff --git a/hacapp.web/hacapp.web.Tests/Features/HomePage/HomePage.feature.cs b/hacapp.web/hacapp.web.Tests/Features/HomePage/HomePage.feature.cs new file mode 100644 index 0000000..75ff2f3 --- /dev/null +++ b/hacapp.web/hacapp.web.Tests/Features/HomePage/HomePage.feature.cs @@ -0,0 +1,97 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by SpecFlow (http://www.specflow.org/). +// SpecFlow Version:1.9.0.77 +// SpecFlow Generator Version:1.9.0.0 +// Runtime Version:4.0.30319.34003 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +#region Designer generated code +#pragma warning disable +namespace hacapp.web.Tests.Features.HomePage +{ + using TechTalk.SpecFlow; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.0.77")] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [NUnit.Framework.TestFixtureAttribute()] + [NUnit.Framework.DescriptionAttribute("HomePage")] + public partial class HomePageFeature + { + + private static TechTalk.SpecFlow.ITestRunner testRunner; + +#line 1 "HomePage.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"), "HomePage", "In order to user the application effectively\nAs a user\nI want the home page to sh" + + "ow the most relevant", ProgrammingLanguage.CSharp, ((string[])(null))); + testRunner.OnFeatureStart(featureInfo); + } + + [NUnit.Framework.TestFixtureTearDownAttribute()] + public virtual void FeatureTearDown() + { + testRunner.OnFeatureEnd(); + testRunner = null; + } + + [NUnit.Framework.SetUpAttribute()] + public virtual void TestInitialize() + { + } + + [NUnit.Framework.TearDownAttribute()] + public virtual void ScenarioTearDown() + { + testRunner.OnScenarioEnd(); + } + + public virtual void ScenarioSetup(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) + { + testRunner.OnScenarioStart(scenarioInfo); + } + + public virtual void ScenarioCleanup() + { + testRunner.CollectScenarioErrors(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Pending memberships should be listed on the home page for the team owner")] + [NUnit.Framework.CategoryAttribute("mytag")] + public virtual void PendingMembershipsShouldBeListedOnTheHomePageForTheTeamOwner() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Pending memberships should be listed on the home page for the team owner", new string[] { + "mytag"}); +#line 7 +this.ScenarioSetup(scenarioInfo); +#line 8 + testRunner.Given("There is a registered user with id \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 9 + testRunner.And("There is a registered user with id \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 10 + testRunner.And("Team \'1\' is owned by \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 11 + testRunner.And("\'Bob\' has pending membership for Team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 12 + testRunner.And("I am logged in as \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 13 + testRunner.When("I visit the home page", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 14 + testRunner.Then("I should see that \'Bob\' has a pending membership for team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + this.ScenarioCleanup(); + } + } +} +#pragma warning restore +#endregion diff --git a/hacapp.web/hacapp.web.Tests/Features/HomePage/HomePageSteps.cs b/hacapp.web/hacapp.web.Tests/Features/HomePage/HomePageSteps.cs new file mode 100644 index 0000000..d2499cf --- /dev/null +++ b/hacapp.web/hacapp.web.Tests/Features/HomePage/HomePageSteps.cs @@ -0,0 +1,33 @@ +using System.Linq; +using System.Web.Mvc; +using FluentAssertions; +using Hacapp.Web.Models; +using hacapp.web.Tests.Features.Teams; +using TechTalk.SpecFlow; + +namespace hacapp.web.Tests.Features.HomePage +{ + [Binding] + public class HomePageSteps : CommonSteps + { + private readonly CurrentScenarioContext context; + + public HomePageSteps(CurrentScenarioContext context) + : base(context) + { + this.context = context; + } + + [Then(@"I should see that '(.*)' has a pending membership for team '(.*)'")] + public void ThenIShouldSeeThatHasAPendingMembershipForTeam(string userId, int teamId) + { + var viewResult = context.ActionResult as ViewResult; + viewResult.Should().NotBeNull(); + var model = viewResult.Model as HomePageViewModel; + model.Should().NotBeNull(); + model.PendingMemberships.Should().HaveCount(1); + model.PendingMemberships.First().TeamId.Should().Be(teamId); + model.PendingMemberships.First().UserId.Should().Be(userId); + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.web.Tests/Features/Login/UserLogin.feature b/hacapp.web/hacapp.web.Tests/Features/Login/UserLogin.feature new file mode 100644 index 0000000..48df102 --- /dev/null +++ b/hacapp.web/hacapp.web.Tests/Features/Login/UserLogin.feature @@ -0,0 +1,19 @@ +Feature: UserLogin + In order to ensure users log in when they visit + As a user + I want to be directed to the login page when I first visit + +Scenario: When user is not a member of any team then they should be redirected to join a team + Given There is a registered user with id 'Bob' + And I am logged in as 'Bob' + When I visit the home page + Then I Should be redirected to the team index page + +Scenario: Show the home page when I'm logged in + Given There is a registered user with id 'Bob' + And There is a registered user with id 'Tom' + And Team '1' is owned by 'Tom' + And 'Bob' is a member Team '1' + And I am logged in as 'Bob' + When I visit the home page + Then I Should see the home page diff --git a/hacapp.web/hacapp.web.Tests/UserLogin.feature.cs b/hacapp.web/hacapp.web.Tests/Features/Login/UserLogin.feature.cs similarity index 64% rename from hacapp.web/hacapp.web.Tests/UserLogin.feature.cs rename to hacapp.web/hacapp.web.Tests/Features/Login/UserLogin.feature.cs index 92899b1..fde36fd 100644 --- a/hacapp.web/hacapp.web.Tests/UserLogin.feature.cs +++ b/hacapp.web/hacapp.web.Tests/Features/Login/UserLogin.feature.cs @@ -3,7 +3,7 @@ // This code was generated by SpecFlow (http://www.specflow.org/). // SpecFlow Version:1.9.0.77 // SpecFlow Generator Version:1.9.0.0 -// Runtime Version:4.0.30319.18408 +// Runtime Version:4.0.30319.34003 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -11,7 +11,7 @@ // ------------------------------------------------------------------------------ #region Designer generated code #pragma warning disable -namespace hacapp.web.Tests +namespace hacapp.web.Tests.Features.Login { using TechTalk.SpecFlow; @@ -32,8 +32,8 @@ public partial class UserLoginFeature public virtual void FeatureSetup() { testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner(); - TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "UserLogin", "In order to ensure users log in when they visit\nAs a user\nI want to be directed t" + - "o the login page when I first visit", ProgrammingLanguage.CSharp, ((string[])(null))); + TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "UserLogin", "In order to ensure users log in when they visit\r\nAs a user\r\nI want to be directed" + + " to the login page when I first visit", ProgrammingLanguage.CSharp, ((string[])(null))); testRunner.OnFeatureStart(featureInfo); } @@ -66,18 +66,22 @@ public virtual void ScenarioCleanup() } [NUnit.Framework.TestAttribute()] - [NUnit.Framework.DescriptionAttribute("Redirect To Login When Not logged in")] - public virtual void RedirectToLoginWhenNotLoggedIn() + [NUnit.Framework.DescriptionAttribute("When user is not a member of any team then they should be redirected to join a te" + + "am")] + public virtual void WhenUserIsNotAMemberOfAnyTeamThenTheyShouldBeRedirectedToJoinATeam() { - TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Redirect To Login When Not logged in", ((string[])(null))); + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("When user is not a member of any team then they should be redirected to join a te" + + "am", ((string[])(null))); #line 6 this.ScenarioSetup(scenarioInfo); #line 7 - testRunner.Given("I am not logged in", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); + testRunner.Given("There is a registered user with id \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 8 - testRunner.When("I visit the home page", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); + testRunner.And("I am logged in as \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 9 - testRunner.Then("the login page should be displayed", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); + testRunner.When("I visit the home page", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 10 + testRunner.Then("I Should be redirected to the team index page", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } @@ -87,14 +91,22 @@ public virtual void RedirectToLoginWhenNotLoggedIn() public virtual void ShowTheHomePageWhenIMLoggedIn() { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Show the home page when I\'m logged in", ((string[])(null))); -#line 11 -this.ScenarioSetup(scenarioInfo); #line 12 - testRunner.Given("I am logged in", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +this.ScenarioSetup(scenarioInfo); #line 13 - testRunner.When("I visit the home page", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); + testRunner.Given("There is a registered user with id \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 14 - testRunner.Then("home page should be displayed", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); + testRunner.And("There is a registered user with id \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 15 + testRunner.And("Team \'1\' is owned by \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 16 + testRunner.And("\'Bob\' is a member Team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 17 + testRunner.And("I am logged in as \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 18 + testRunner.When("I visit the home page", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 19 + testRunner.Then("I Should see the home page", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } diff --git a/hacapp.web/hacapp.web.Tests/Features/Login/UserLoginSteps.cs b/hacapp.web/hacapp.web.Tests/Features/Login/UserLoginSteps.cs new file mode 100644 index 0000000..17d2812 --- /dev/null +++ b/hacapp.web/hacapp.web.Tests/Features/Login/UserLoginSteps.cs @@ -0,0 +1,48 @@ +using System.Web.Mvc; +using FluentAssertions; +using hacapp.web.Tests.Features.Teams; +using TechTalk.SpecFlow; + +namespace hacapp.web.Tests.Features.Login +{ + [Binding] + public class UserLoginSteps : CommonSteps + { + private readonly CurrentScenarioContext context; + + + public UserLoginSteps(CurrentScenarioContext context) : base(context) + { + this.context = context; + } + + [When(@"I visit the home page")] + public void WhenIVisitTheHomePage() + { + context.ActionResult = context.HomeController.Index(); + } + + [Then(@"home page should be displayed")] + public void ThenHomePageShouldBeDisplayed() + { + context.ActionResult.Should().BeOfType(); + var viewResult = context.ActionResult as ViewResult; + string message = viewResult.ViewBag.Message; + message.Should().Be("Welcome blah"); + } + + [Then(@"I Should be redirected to the team index page")] + public void ThenIShouldBeRedirectedToTheTeamIndexPage() + { + var redirectResult = context.ActionResult as RedirectToRouteResult; + redirectResult.RouteValues["action"].Should().Be(@"Index"); + } + + [Then(@"I Should see the home page")] + public void ThenIShouldSeeTheHomePage() + { + var viewResult = context.ActionResult as ViewResult; + viewResult.Should().NotBeNull(); + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.web.Tests/Features/Teams/CommonSteps.cs b/hacapp.web/hacapp.web.Tests/Features/Teams/CommonSteps.cs new file mode 100644 index 0000000..ba7d623 --- /dev/null +++ b/hacapp.web/hacapp.web.Tests/Features/Teams/CommonSteps.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Data.Entity; +using System.Linq; +using System.Security.Principal; +using System.Threading; +using System.Web.Mvc; +using hacapp.contracts.Commands; +using hacapp.core.Commands; +using Hacapp.Data; +using Hacapp.Data.Commands; +using Hacapp.Data.DataContexts; +using Hacapp.Data.DataContexts.ApplicationMigrations; +using Hacapp.Data.Query; +using hacapp.web.Controllers; +using Haccapp.Model.Identity; +using Microsoft.AspNet.Identity; +using Moq; +using SimpleInjector; +using TechTalk.SpecFlow; + +namespace hacapp.web.Tests.Features.Teams +{ + public abstract class CommonSteps + { + private readonly CurrentScenarioContext context; + private ApplicationDb db; + private ICommandAndQueryDispatcher dispatcher; + private UserManager userManager; + + public CommonSteps(CurrentScenarioContext context) + { + this.context = context; + } + + protected string CurrentLoggedInUserId { get; set; } + + [Before] + public void ScenarioSetup() + { + if (context.SkipBefore) + { + return; + } + + SetupContainer(); + CreateNewDb(); + context.SkipBefore = true; + } + + [After] + public void ScenarioDelete() + { + if (context.SkipAfter) + { + return; + } + + db.Database.Delete(); + db.Dispose(); + context.SkipAfter = true; + } + + private void CreateNewDb() + { + Database.SetInitializer(new MigrateDatabaseToLatestVersion()); + + if (db.Database.Exists()) + { + db.Database.Delete(); + } + + db.Database.Initialize(true); + } + + private void SetupContainer() + { + AutoMapperConfig.RegisterMappings(); + var container = new Container(); + + container.RegisterSingle(() => new ApplicationDb()); + container.RegisterSingle, ApplicationUserStore>(); + container.RegisterSingle>(); + + //Register all of our Command and Query handlers in the application + IEnumerable commandHandlerTypes = + typeof (CreateNewTeamCommandHandler).Assembly.GetTypes() + .Where(x => typeof (ICommandHandler).IsAssignableFrom(x)); + IEnumerable queryHandlerTypes = + typeof (GetAllTeamsQueryHandler).Assembly.GetTypes() + .Where(x => typeof (IQueryHandler).IsAssignableFrom(x)); + container.RegisterAll(commandHandlerTypes); + container.RegisterAll(queryHandlerTypes); + + //Register the command and query dispatcher + container.Register(); + + container.Verify(); + + dispatcher = container.GetInstance(); + db = container.GetInstance(); + userManager = container.GetInstance>(); + context.TeamController = new TeamController(dispatcher); + context.HomeController = new HomeController(dispatcher); + context.Dispatcher = dispatcher; + context.UserManager = userManager; + context.Db = db; + } + + protected Mock GetMockControllerContext(string principalUserId) + { + var mockContext = new Mock(); + IPrincipal genericPrincipal = TestHelper.GetPrincipal(principalUserId, principalUserId); + mockContext.SetupGet(p => p.HttpContext.User).Returns(genericPrincipal); + Thread.CurrentPrincipal = genericPrincipal; + return mockContext; + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.web.Tests/Features/Teams/CreateANewTeam.feature b/hacapp.web/hacapp.web.Tests/Features/Teams/CreateANewTeam.feature new file mode 100644 index 0000000..8cc79e8 --- /dev/null +++ b/hacapp.web/hacapp.web.Tests/Features/Teams/CreateANewTeam.feature @@ -0,0 +1,23 @@ +Feature: Create a new team + In order to allow new teams to be created + As a user + I want to be able to create new teams + +@NCrunch.Framework.ExclusivelyUsesAttribute_Database +Scenario: Happy path + Given There is a registered user with id 'Bob' + And I am logged in as 'Bob' + When I enter the full details for creating a team called 'BobsTeam' + Then a team called 'BobsTeam' should be created and 'Bob' should be owner of that team + And I should be redirected to the team details page + +@NCrunch.Framework.ExclusivelyUsesAttribute_Database +Scenario: Cannot Create team with samer name + Given There is a registered user with id 'Bob' + And I am logged in as 'Bob' + When I enter the full details for creating a team called 'BobsTeam' + And I enter the full details for creating a team called 'BobsTeam' + Then only one team called 'BobsTeam' should not be created + And I should be redirected to back to the create a team page + And there should be an error message + diff --git a/hacapp.web/hacapp.web.Tests/Features/Teams/CreateANewTeam.feature.cs b/hacapp.web/hacapp.web.Tests/Features/Teams/CreateANewTeam.feature.cs new file mode 100644 index 0000000..cd247c1 --- /dev/null +++ b/hacapp.web/hacapp.web.Tests/Features/Teams/CreateANewTeam.feature.cs @@ -0,0 +1,121 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by SpecFlow (http://www.specflow.org/). +// SpecFlow Version:1.9.0.77 +// SpecFlow Generator Version:1.9.0.0 +// Runtime Version:4.0.30319.34003 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +#region Designer generated code +#pragma warning disable +namespace hacapp.web.Tests.Features.Teams +{ + using TechTalk.SpecFlow; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.0.77")] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [NUnit.Framework.TestFixtureAttribute()] + [NUnit.Framework.DescriptionAttribute("Create a new team")] + public partial class CreateANewTeamFeature + { + + private static TechTalk.SpecFlow.ITestRunner testRunner; + +#line 1 "CreateANewTeam.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"), "Create a new team", "In order to allow new teams to be created\r\nAs a user\r\nI want to be able to create" + + " new teams", ProgrammingLanguage.CSharp, ((string[])(null))); + testRunner.OnFeatureStart(featureInfo); + } + + [NUnit.Framework.TestFixtureTearDownAttribute()] + public virtual void FeatureTearDown() + { + testRunner.OnFeatureEnd(); + testRunner = null; + } + + [NUnit.Framework.SetUpAttribute()] + public virtual void TestInitialize() + { + } + + [NUnit.Framework.TearDownAttribute()] + public virtual void ScenarioTearDown() + { + testRunner.OnScenarioEnd(); + } + + public virtual void ScenarioSetup(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) + { + testRunner.OnScenarioStart(scenarioInfo); + } + + public virtual void ScenarioCleanup() + { + testRunner.CollectScenarioErrors(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Happy path")] + [NCrunch.Framework.ExclusivelyUsesAttribute("Database")] + public virtual void HappyPath() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Happy path", new string[] { + "NCrunch.Framework.ExclusivelyUsesAttribute_Database"}); +#line 7 +this.ScenarioSetup(scenarioInfo); +#line 8 + testRunner.Given("There is a registered user with id \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 9 + testRunner.And("I am logged in as \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 10 + testRunner.When("I enter the full details for creating a team called \'BobsTeam\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 11 + testRunner.Then("a team called \'BobsTeam\' should be created and \'Bob\' should be owner of that team" + + "", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line 12 + testRunner.And("I should be redirected to the team details page", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line hidden + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Cannot Create team with samer name")] + [NCrunch.Framework.ExclusivelyUsesAttribute("Database")] + public virtual void CannotCreateTeamWithSamerName() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Cannot Create team with samer name", new string[] { + "NCrunch.Framework.ExclusivelyUsesAttribute_Database"}); +#line 15 +this.ScenarioSetup(scenarioInfo); +#line 16 + testRunner.Given("There is a registered user with id \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 17 + testRunner.And("I am logged in as \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 18 + testRunner.When("I enter the full details for creating a team called \'BobsTeam\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 19 + testRunner.And("I enter the full details for creating a team called \'BobsTeam\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 20 + testRunner.Then("only one team called \'BobsTeam\' should not be created", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line 21 + testRunner.And("I should be redirected to back to the create a team page", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 22 + testRunner.And("there should be an error message", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line hidden + this.ScenarioCleanup(); + } + } +} +#pragma warning restore +#endregion diff --git a/hacapp.web/hacapp.web.Tests/Features/Teams/DeleteTeam.feature b/hacapp.web/hacapp.web.Tests/Features/Teams/DeleteTeam.feature new file mode 100644 index 0000000..b55831c --- /dev/null +++ b/hacapp.web/hacapp.web.Tests/Features/Teams/DeleteTeam.feature @@ -0,0 +1,40 @@ +Feature: DeleteTeam + In order allow full control of teams + As a team owner + I want to be able to delete teams + +@mytag +Scenario: Owners should be able to delete their teams + Given There is a registered user with id 'Bob' + And Team '1' is owned by 'Bob' + And I am logged in as 'Bob' + When I delete team '1' + Then team '1' should not exist + And 'Bob' should not be a member of any teams + +Scenario: Non owners should not be able to delete their teams + Given There is a registered user with id 'Bob' + Given There is a registered user with id 'Tom' + And Team '1' is owned by 'Bob' + And I am logged in as 'Tom' + When I delete team '1' + Then a team called '1' should be created and 'Bob' should be owner of that team + +Scenario: Non owner members should not be able to delete their teams + Given There is a registered user with id 'Bob' + Given There is a registered user with id 'Tom' + And Team '1' is owned by 'Bob' + And 'Tom' is a member Team '1' + And I am logged in as 'Tom' + When I delete team '1' + Then a team called '1' should be created and 'Bob' should be owner of that team + +Scenario: Site admin should be able to delete any team + Given There is a registered user with id 'Bob' + And Team '1' is owned by 'Bob' + And There is a registered user with id 'Sam' + And 'Sam' is in the TeamManagement role + And I am logged in as 'Sam' + When I delete team '1' + Then team '1' should not exist + And 'Bob' should not be a member of any teams \ No newline at end of file diff --git a/hacapp.web/hacapp.web.Tests/Features/Teams/DeleteTeam.feature.cs b/hacapp.web/hacapp.web.Tests/Features/Teams/DeleteTeam.feature.cs new file mode 100644 index 0000000..dc8c2d1 --- /dev/null +++ b/hacapp.web/hacapp.web.Tests/Features/Teams/DeleteTeam.feature.cs @@ -0,0 +1,170 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by SpecFlow (http://www.specflow.org/). +// SpecFlow Version:1.9.0.77 +// SpecFlow Generator Version:1.9.0.0 +// Runtime Version:4.0.30319.34003 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +#region Designer generated code +#pragma warning disable +namespace hacapp.web.Tests.Features.Teams +{ + using TechTalk.SpecFlow; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.0.77")] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [NUnit.Framework.TestFixtureAttribute()] + [NUnit.Framework.DescriptionAttribute("DeleteTeam")] + public partial class DeleteTeamFeature + { + + private static TechTalk.SpecFlow.ITestRunner testRunner; + +#line 1 "DeleteTeam.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"), "DeleteTeam", "In order allow full control of teams\nAs a team owner\nI want to be able to delete " + + "teams", ProgrammingLanguage.CSharp, ((string[])(null))); + testRunner.OnFeatureStart(featureInfo); + } + + [NUnit.Framework.TestFixtureTearDownAttribute()] + public virtual void FeatureTearDown() + { + testRunner.OnFeatureEnd(); + testRunner = null; + } + + [NUnit.Framework.SetUpAttribute()] + public virtual void TestInitialize() + { + } + + [NUnit.Framework.TearDownAttribute()] + public virtual void ScenarioTearDown() + { + testRunner.OnScenarioEnd(); + } + + public virtual void ScenarioSetup(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) + { + testRunner.OnScenarioStart(scenarioInfo); + } + + public virtual void ScenarioCleanup() + { + testRunner.CollectScenarioErrors(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Owners should be able to delete their teams")] + [NUnit.Framework.CategoryAttribute("mytag")] + public virtual void OwnersShouldBeAbleToDeleteTheirTeams() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Owners should be able to delete their teams", new string[] { + "mytag"}); +#line 7 +this.ScenarioSetup(scenarioInfo); +#line 8 + testRunner.Given("There is a registered user with id \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 9 + testRunner.And("Team \'1\' is owned by \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 10 + testRunner.And("I am logged in as \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 11 + testRunner.When("I delete team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 12 + testRunner.Then("team \'1\' should not exist", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line 13 + testRunner.And("\'Bob\' should not be a member of any teams", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line hidden + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Non owners should not be able to delete their teams")] + public virtual void NonOwnersShouldNotBeAbleToDeleteTheirTeams() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Non owners should not be able to delete their teams", ((string[])(null))); +#line 15 +this.ScenarioSetup(scenarioInfo); +#line 16 + testRunner.Given("There is a registered user with id \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 17 + testRunner.Given("There is a registered user with id \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 18 + testRunner.And("Team \'1\' is owned by \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 19 + testRunner.And("I am logged in as \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 20 + testRunner.When("I delete team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 21 + testRunner.Then("a team called \'1\' should be created and \'Bob\' should be owner of that team", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Non owner members should not be able to delete their teams")] + public virtual void NonOwnerMembersShouldNotBeAbleToDeleteTheirTeams() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Non owner members should not be able to delete their teams", ((string[])(null))); +#line 23 +this.ScenarioSetup(scenarioInfo); +#line 24 + testRunner.Given("There is a registered user with id \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 25 + testRunner.Given("There is a registered user with id \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 26 + testRunner.And("Team \'1\' is owned by \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 27 + testRunner.And("\'Tom\' is a member Team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 28 + testRunner.And("I am logged in as \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 29 + testRunner.When("I delete team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 30 + testRunner.Then("a team called \'1\' should be created and \'Bob\' should be owner of that team", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Site admin should be able to delete any team")] + public virtual void SiteAdminShouldBeAbleToDeleteAnyTeam() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Site admin should be able to delete any team", ((string[])(null))); +#line 32 +this.ScenarioSetup(scenarioInfo); +#line 33 + testRunner.Given("There is a registered user with id \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 34 + testRunner.And("Team \'1\' is owned by \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 35 + testRunner.And("There is a registered user with id \'Sam\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 36 + testRunner.And("\'Sam\' is in the TeamManagement role", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 37 + testRunner.And("I am logged in as \'Sam\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 38 + testRunner.When("I delete team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 39 + testRunner.Then("team \'1\' should not exist", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line 40 + testRunner.And("\'Bob\' should not be a member of any teams", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line hidden + this.ScenarioCleanup(); + } + } +} +#pragma warning restore +#endregion diff --git a/hacapp.web/hacapp.web.Tests/Features/Teams/JoinATeam.feature b/hacapp.web/hacapp.web.Tests/Features/Teams/JoinATeam.feature new file mode 100644 index 0000000..2aaaab0 --- /dev/null +++ b/hacapp.web/hacapp.web.Tests/Features/Teams/JoinATeam.feature @@ -0,0 +1,91 @@ +Feature: JoinATeam + In order to allow people to join teams + As a user + I want to be see which teams I can join + +Scenario: User wants to choose a team to join + Given There is a registered user with id 'Bob' + And There is a registered user with id 'Tom' + And Team 'A' is owned by 'Tom' + And I am logged in as 'Bob' + When I vist the join a team page + Then The list of teams to join should contain Team 'A' + +Scenario: User wants to choose a team to join but should not see teams he is already a member of + Given There is a registered user with id 'Bob' + And There is a registered user with id 'Tom' + And Team 'B' is owned by 'Tom' + And Team 'A' is owned by 'Bob' + And I am logged in as 'Bob' + When I vist the join a team page + Then The list of teams to join should contain Team 'B' + And The list of teams to join should not contain Team 'A' + +Scenario: User tries to join a new team should have a pending membership + Given There is a registered user with id 'Bob' + And There is a registered user with id 'Tom' + And Team '1' is owned by 'Tom' + And I am logged in as 'Bob' + When I try and join team '1' + Then 'Bob' should have a pending membership for team '1' + And I should be redirected to my teams page + And Team '1' should be listed on my teams page + +Scenario: Pending memberships should be listed in team details view + Given There is a registered user with id 'Bob' + And There is a registered user with id 'Tom' + And Team '1' is owned by 'Tom' + And 'Bob' has pending membership for Team '1' + And I am logged in as 'Tom' + When I visit the team details page for team '1' + Then 'Bob' should be listed in the pending members + +Scenario: Confirmed memberships should be listed in team details view + Given There is a registered user with id 'Bob' + And There is a registered user with id 'Tom' + And Team '1' is owned by 'Tom' + And 'Bob' is a member Team '1' + And I am logged in as 'Tom' + When I visit the team details page for team '1' + Then 'Bob' should be listed in the confirmed members + + +Scenario: User wants to choose a team to join, but no teams exist so they should be shown view to create a team + Given There is a registered user with id 'Bob' + And I am logged in as 'Bob' + When I vist the join a team page + Then I should be shown the No Teams Exist view + +Scenario: Owners should not be able to join their own teams + Given There is a registered user with id 'Bob' + And There is a registered user with id 'Tom' + And Team '1' is owned by 'Tom' + And I am logged in as 'Tom' + When I try and join team '1' + Then 'Tom' should not have a pending membership for team '1' + And I should be redirected to my teams page + And Team '1' should be listed on my teams page + +Scenario: Owner accepts pending membership + Given There is a registered user with id 'Bob' + And There is a registered user with id 'Tom' + And Team '1' is owned by 'Tom' + And 'Bob' has pending membership for Team '1' + And I am logged in as 'Tom' + When I accept pending membership for 'Bob' to join team '1' + Then 'Bob' should be a full member of team '1' + +Scenario: Non owner cannot accept pending membership + Given There is a registered user with id 'Bob' + And There is a registered user with id 'Tom' + And Team '1' is owned by 'Tom' + And 'Bob' has pending membership for Team '1' + And I am logged in as 'Bob' + When I accept pending membership for 'Bob' to join team '1' + Then 'Bob' should have a pending membership for team '1' + And I should be redirected to team details page for team '1' + +Scenario: New users should be given the choice to create or join a new team + Given I am logged in as 'Bob' + When I go to the home page + Then I should the create or join view diff --git a/hacapp.web/hacapp.web.Tests/Features/Teams/JoinATeam.feature.cs b/hacapp.web/hacapp.web.Tests/Features/Teams/JoinATeam.feature.cs new file mode 100644 index 0000000..a85f4c2 --- /dev/null +++ b/hacapp.web/hacapp.web.Tests/Features/Teams/JoinATeam.feature.cs @@ -0,0 +1,316 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by SpecFlow (http://www.specflow.org/). +// SpecFlow Version:1.9.0.77 +// SpecFlow Generator Version:1.9.0.0 +// Runtime Version:4.0.30319.34003 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +#region Designer generated code +#pragma warning disable +namespace hacapp.web.Tests.Features.Teams +{ + using TechTalk.SpecFlow; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.0.77")] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [NUnit.Framework.TestFixtureAttribute()] + [NUnit.Framework.DescriptionAttribute("JoinATeam")] + public partial class JoinATeamFeature + { + + private static TechTalk.SpecFlow.ITestRunner testRunner; + +#line 1 "JoinATeam.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"), "JoinATeam", "In order to allow people to join teams\nAs a user\nI want to be see which teams I c" + + "an join", ProgrammingLanguage.CSharp, ((string[])(null))); + testRunner.OnFeatureStart(featureInfo); + } + + [NUnit.Framework.TestFixtureTearDownAttribute()] + public virtual void FeatureTearDown() + { + testRunner.OnFeatureEnd(); + testRunner = null; + } + + [NUnit.Framework.SetUpAttribute()] + public virtual void TestInitialize() + { + } + + [NUnit.Framework.TearDownAttribute()] + public virtual void ScenarioTearDown() + { + testRunner.OnScenarioEnd(); + } + + public virtual void ScenarioSetup(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) + { + testRunner.OnScenarioStart(scenarioInfo); + } + + public virtual void ScenarioCleanup() + { + testRunner.CollectScenarioErrors(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("User wants to choose a team to join")] + public virtual void UserWantsToChooseATeamToJoin() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("User wants to choose a team to join", ((string[])(null))); +#line 6 +this.ScenarioSetup(scenarioInfo); +#line 7 + testRunner.Given("There is a registered user with id \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 8 + testRunner.And("There is a registered user with id \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 9 + testRunner.And("Team \'A\' is owned by \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 10 + testRunner.And("I am logged in as \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 11 + testRunner.When("I vist the join a team page", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 12 + testRunner.Then("The list of teams to join should contain Team \'A\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("User wants to choose a team to join but should not see teams he is already a memb" + + "er of")] + public virtual void UserWantsToChooseATeamToJoinButShouldNotSeeTeamsHeIsAlreadyAMemberOf() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("User wants to choose a team to join but should not see teams he is already a memb" + + "er of", ((string[])(null))); +#line 14 +this.ScenarioSetup(scenarioInfo); +#line 15 + testRunner.Given("There is a registered user with id \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 16 + testRunner.And("There is a registered user with id \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 17 + testRunner.And("Team \'B\' is owned by \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 18 + testRunner.And("Team \'A\' is owned by \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 19 + testRunner.And("I am logged in as \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 20 + testRunner.When("I vist the join a team page", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 21 + testRunner.Then("The list of teams to join should contain Team \'B\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line 22 + testRunner.And("The list of teams to join should not contain Team \'A\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line hidden + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("User tries to join a new team should have a pending membership")] + public virtual void UserTriesToJoinANewTeamShouldHaveAPendingMembership() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("User tries to join a new team should have a pending membership", ((string[])(null))); +#line 24 +this.ScenarioSetup(scenarioInfo); +#line 25 + testRunner.Given("There is a registered user with id \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 26 + testRunner.And("There is a registered user with id \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 27 + testRunner.And("Team \'1\' is owned by \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 28 + testRunner.And("I am logged in as \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 29 + testRunner.When("I try and join team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 30 + testRunner.Then("\'Bob\' should have a pending membership for team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line 31 + testRunner.And("I should be redirected to my teams page", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 32 + testRunner.And("Team \'1\' should be listed on my teams page", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line hidden + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Pending memberships should be listed in team details view")] + public virtual void PendingMembershipsShouldBeListedInTeamDetailsView() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Pending memberships should be listed in team details view", ((string[])(null))); +#line 34 +this.ScenarioSetup(scenarioInfo); +#line 35 + testRunner.Given("There is a registered user with id \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 36 + testRunner.And("There is a registered user with id \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 37 + testRunner.And("Team \'1\' is owned by \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 38 + testRunner.And("\'Bob\' has pending membership for Team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 39 + testRunner.And("I am logged in as \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 40 + testRunner.When("I visit the team details page for team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 41 + testRunner.Then("\'Bob\' should be listed in the pending members", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Confirmed memberships should be listed in team details view")] + public virtual void ConfirmedMembershipsShouldBeListedInTeamDetailsView() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Confirmed memberships should be listed in team details view", ((string[])(null))); +#line 43 +this.ScenarioSetup(scenarioInfo); +#line 44 + testRunner.Given("There is a registered user with id \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 45 + testRunner.And("There is a registered user with id \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 46 + testRunner.And("Team \'1\' is owned by \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 47 + testRunner.And("\'Bob\' is a member Team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 48 + testRunner.And("I am logged in as \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 49 + testRunner.When("I visit the team details page for team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 50 + testRunner.Then("\'Bob\' should be listed in the confirmed members", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("User wants to choose a team to join, but no teams exist so they should be shown v" + + "iew to create a team")] + public virtual void UserWantsToChooseATeamToJoinButNoTeamsExistSoTheyShouldBeShownViewToCreateATeam() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("User wants to choose a team to join, but no teams exist so they should be shown v" + + "iew to create a team", ((string[])(null))); +#line 53 +this.ScenarioSetup(scenarioInfo); +#line 54 + testRunner.Given("There is a registered user with id \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 55 + testRunner.And("I am logged in as \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 56 + testRunner.When("I vist the join a team page", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 57 + testRunner.Then("I should be shown the No Teams Exist view", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Owners should not be able to join their own teams")] + public virtual void OwnersShouldNotBeAbleToJoinTheirOwnTeams() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Owners should not be able to join their own teams", ((string[])(null))); +#line 59 +this.ScenarioSetup(scenarioInfo); +#line 60 + testRunner.Given("There is a registered user with id \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 61 + testRunner.And("There is a registered user with id \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 62 + testRunner.And("Team \'1\' is owned by \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 63 + testRunner.And("I am logged in as \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 64 + testRunner.When("I try and join team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 65 + testRunner.Then("\'Tom\' should not have a pending membership for team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line 66 + testRunner.And("I should be redirected to my teams page", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 67 + testRunner.And("Team \'1\' should be listed on my teams page", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line hidden + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Owner accepts pending membership")] + public virtual void OwnerAcceptsPendingMembership() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Owner accepts pending membership", ((string[])(null))); +#line 69 +this.ScenarioSetup(scenarioInfo); +#line 70 + testRunner.Given("There is a registered user with id \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 71 + testRunner.And("There is a registered user with id \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 72 + testRunner.And("Team \'1\' is owned by \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 73 + testRunner.And("\'Bob\' has pending membership for Team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 74 + testRunner.And("I am logged in as \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 75 + testRunner.When("I accept pending membership for \'Bob\' to join team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 76 + testRunner.Then("\'Bob\' should be a full member of team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Non owner cannot accept pending membership")] + public virtual void NonOwnerCannotAcceptPendingMembership() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Non owner cannot accept pending membership", ((string[])(null))); +#line 78 +this.ScenarioSetup(scenarioInfo); +#line 79 + testRunner.Given("There is a registered user with id \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 80 + testRunner.And("There is a registered user with id \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 81 + testRunner.And("Team \'1\' is owned by \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 82 + testRunner.And("\'Bob\' has pending membership for Team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 83 + testRunner.And("I am logged in as \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 84 + testRunner.When("I accept pending membership for \'Bob\' to join team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 85 + testRunner.Then("\'Bob\' should have a pending membership for team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line 86 + testRunner.And("I should be redirected to team details page for team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line hidden + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("New users should be given the choice to create or join a new team")] + public virtual void NewUsersShouldBeGivenTheChoiceToCreateOrJoinANewTeam() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("New users should be given the choice to create or join a new team", ((string[])(null))); +#line 88 +this.ScenarioSetup(scenarioInfo); +#line 89 + testRunner.Given("I am logged in as \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 90 + testRunner.When("I go to the home page", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 91 + testRunner.Then("I should the create or join view", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + this.ScenarioCleanup(); + } + } +} +#pragma warning restore +#endregion diff --git a/hacapp.web/hacapp.web.Tests/Features/Teams/TeamDetails.feature b/hacapp.web/hacapp.web.Tests/Features/Teams/TeamDetails.feature new file mode 100644 index 0000000..2be7ec1 --- /dev/null +++ b/hacapp.web/hacapp.web.Tests/Features/Teams/TeamDetails.feature @@ -0,0 +1,29 @@ +Feature: TeamDetails + In order to know who is in and manage a team + as a user + I want to be able to view the details of a team + +Scenario: Users should be able to see the details of teams that they are member of + Given There is a registered user with id 'Bob' + And Team '1' is owned by 'Bob' + And I am logged in as 'Bob' + When I visit the details page for team '1' + Then I should see the details for the team '1' + +Scenario: Users should not be able to see the details of teams that they are not members of + Given There is a registered user with id 'Bob' + And There is a registered user with id 'Tom' + And Team '1' is owned by 'Bob' + And I am logged in as 'Tom' + When I visit the details page for team '1' + Then I should see a not authorized + +Scenario: Users should not be able to see the details of teams that they are pending members of + Given There is a registered user with id 'Bob' + And There is a registered user with id 'Tom' + And Team '1' is owned by 'Tom' + And 'Bob' has pending membership for Team '1' + And I am logged in as 'Bob' + When I visit the details page for team '1' + Then I should see a not authorized + diff --git a/hacapp.web/hacapp.web.Tests/Features/Teams/TeamDetails.feature.cs b/hacapp.web/hacapp.web.Tests/Features/Teams/TeamDetails.feature.cs new file mode 100644 index 0000000..94c47ae --- /dev/null +++ b/hacapp.web/hacapp.web.Tests/Features/Teams/TeamDetails.feature.cs @@ -0,0 +1,143 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by SpecFlow (http://www.specflow.org/). +// SpecFlow Version:1.9.0.77 +// SpecFlow Generator Version:1.9.0.0 +// Runtime Version:4.0.30319.34003 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +#region Designer generated code +#pragma warning disable +namespace hacapp.web.Tests.Features.Teams +{ + using TechTalk.SpecFlow; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.0.77")] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [NUnit.Framework.TestFixtureAttribute()] + [NUnit.Framework.DescriptionAttribute("TeamDetails")] + public partial class TeamDetailsFeature + { + + private static TechTalk.SpecFlow.ITestRunner testRunner; + +#line 1 "TeamDetails.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"), "TeamDetails", "In order to know who is in and manage a team\nas a user\nI want to be able to view " + + "the details of a team", ProgrammingLanguage.CSharp, ((string[])(null))); + testRunner.OnFeatureStart(featureInfo); + } + + [NUnit.Framework.TestFixtureTearDownAttribute()] + public virtual void FeatureTearDown() + { + testRunner.OnFeatureEnd(); + testRunner = null; + } + + [NUnit.Framework.SetUpAttribute()] + public virtual void TestInitialize() + { + } + + [NUnit.Framework.TearDownAttribute()] + public virtual void ScenarioTearDown() + { + testRunner.OnScenarioEnd(); + } + + public virtual void ScenarioSetup(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) + { + testRunner.OnScenarioStart(scenarioInfo); + } + + public virtual void ScenarioCleanup() + { + testRunner.CollectScenarioErrors(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Users should be able to see the details of teams that they are member of")] + public virtual void UsersShouldBeAbleToSeeTheDetailsOfTeamsThatTheyAreMemberOf() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Users should be able to see the details of teams that they are member of", ((string[])(null))); +#line 6 +this.ScenarioSetup(scenarioInfo); +#line 7 + testRunner.Given("There is a registered user with id \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 8 + testRunner.And("Team \'1\' is owned by \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 9 + testRunner.And("I am logged in as \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 10 + testRunner.When("I visit the details page for team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 11 + testRunner.Then("I should see the details for the team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Users should not be able to see the details of teams that they are not members of" + + "")] + public virtual void UsersShouldNotBeAbleToSeeTheDetailsOfTeamsThatTheyAreNotMembersOf() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Users should not be able to see the details of teams that they are not members of" + + "", ((string[])(null))); +#line 13 +this.ScenarioSetup(scenarioInfo); +#line 14 + testRunner.Given("There is a registered user with id \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 15 + testRunner.And("There is a registered user with id \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 16 + testRunner.And("Team \'1\' is owned by \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 17 + testRunner.And("I am logged in as \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 18 + testRunner.When("I visit the details page for team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 19 + testRunner.Then("I should see a not authorized", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Users should not be able to see the details of teams that they are pending member" + + "s of")] + public virtual void UsersShouldNotBeAbleToSeeTheDetailsOfTeamsThatTheyArePendingMembersOf() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Users should not be able to see the details of teams that they are pending member" + + "s of", ((string[])(null))); +#line 21 +this.ScenarioSetup(scenarioInfo); +#line 22 + testRunner.Given("There is a registered user with id \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 23 + testRunner.And("There is a registered user with id \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 24 + testRunner.And("Team \'1\' is owned by \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 25 + testRunner.And("\'Bob\' has pending membership for Team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 26 + testRunner.And("I am logged in as \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 27 + testRunner.When("I visit the details page for team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 28 + testRunner.Then("I should see a not authorized", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + this.ScenarioCleanup(); + } + } +} +#pragma warning restore +#endregion diff --git a/hacapp.web/hacapp.web.Tests/Features/Teams/TeamMembership.feature b/hacapp.web/hacapp.web.Tests/Features/Teams/TeamMembership.feature new file mode 100644 index 0000000..b81a31b --- /dev/null +++ b/hacapp.web/hacapp.web.Tests/Features/Teams/TeamMembership.feature @@ -0,0 +1,43 @@ +Feature: TeamMembership + In order to allow changes in team membership + as a team owner + I want to be able to manage who is a member of the team + +Scenario: Owners should be able to remove members of a team + Given There is a registered user with id 'Bob' + And There is a registered user with id 'Tom' + And Team '1' is owned by 'Tom' + And 'Bob' is a member Team '1' + And I am logged in as 'Tom' + When I remove 'Bob' from team '1' + Then 'Bob' should not be a member of team '1' any more + +Scenario: Owners should be not be able to remove themselves from a team + Given There is a registered user with id 'Bob' + And Team '1' is owned by 'Bob' + And I am logged in as 'Bob' + When I remove 'Bob' from team '1' + Then a team called '1' should be created and 'Bob' should be owner of that team + +Scenario: Non Owners should not be able to remove other members of a team + Given There is a registered user with id 'Bob' + And There is a registered user with id 'Tom' + And There is a registered user with id 'Jon' + And Team '1' is owned by 'Tom' + And 'Bob' is a member Team '1' + And 'Jon' is a member Team '1' + And I am logged in as 'Bob' + When I remove 'Jon' from team '1' + Then I should be redirected to team details page for team '1' + And 'Bob' should be a full member of team '1' + +Scenario: Non Owners should be able to remove themselves from a team + Given There is a registered user with id 'Bob' + And There is a registered user with id 'Tom' + And There is a registered user with id 'Jon' + And Team '1' is owned by 'Tom' + And 'Bob' is a member Team '1' + And 'Jon' is a member Team '1' + And I am logged in as 'Bob' + When I remove 'Bob' from team '1' + Then 'Bob' should not be a member of team '1' any more \ No newline at end of file diff --git a/hacapp.web/hacapp.web.Tests/Features/Teams/TeamMembership.feature.cs b/hacapp.web/hacapp.web.Tests/Features/Teams/TeamMembership.feature.cs new file mode 100644 index 0000000..3409d97 --- /dev/null +++ b/hacapp.web/hacapp.web.Tests/Features/Teams/TeamMembership.feature.cs @@ -0,0 +1,176 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by SpecFlow (http://www.specflow.org/). +// SpecFlow Version:1.9.0.77 +// SpecFlow Generator Version:1.9.0.0 +// Runtime Version:4.0.30319.34003 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +#region Designer generated code +#pragma warning disable +namespace hacapp.web.Tests.Features.Teams +{ + using TechTalk.SpecFlow; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.0.77")] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [NUnit.Framework.TestFixtureAttribute()] + [NUnit.Framework.DescriptionAttribute("TeamMembership")] + public partial class TeamMembershipFeature + { + + private static TechTalk.SpecFlow.ITestRunner testRunner; + +#line 1 "TeamMembership.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"), "TeamMembership", "In order to allow changes in team membership\nas a team owner\nI want to be able to" + + " manage who is a member of the team", ProgrammingLanguage.CSharp, ((string[])(null))); + testRunner.OnFeatureStart(featureInfo); + } + + [NUnit.Framework.TestFixtureTearDownAttribute()] + public virtual void FeatureTearDown() + { + testRunner.OnFeatureEnd(); + testRunner = null; + } + + [NUnit.Framework.SetUpAttribute()] + public virtual void TestInitialize() + { + } + + [NUnit.Framework.TearDownAttribute()] + public virtual void ScenarioTearDown() + { + testRunner.OnScenarioEnd(); + } + + public virtual void ScenarioSetup(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) + { + testRunner.OnScenarioStart(scenarioInfo); + } + + public virtual void ScenarioCleanup() + { + testRunner.CollectScenarioErrors(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Owners should be able to remove members of a team")] + public virtual void OwnersShouldBeAbleToRemoveMembersOfATeam() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Owners should be able to remove members of a team", ((string[])(null))); +#line 6 +this.ScenarioSetup(scenarioInfo); +#line 7 + testRunner.Given("There is a registered user with id \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 8 + testRunner.And("There is a registered user with id \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 9 + testRunner.And("Team \'1\' is owned by \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 10 + testRunner.And("\'Bob\' is a member Team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 11 + testRunner.And("I am logged in as \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 12 + testRunner.When("I remove \'Bob\' from team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 13 + testRunner.Then("\'Bob\' should not be a member of team \'1\' any more", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Owners should be not be able to remove themselves from a team")] + public virtual void OwnersShouldBeNotBeAbleToRemoveThemselvesFromATeam() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Owners should be not be able to remove themselves from a team", ((string[])(null))); +#line 15 +this.ScenarioSetup(scenarioInfo); +#line 16 + testRunner.Given("There is a registered user with id \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 17 + testRunner.And("Team \'1\' is owned by \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 18 + testRunner.And("I am logged in as \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 19 + testRunner.When("I remove \'Bob\' from team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 20 + testRunner.Then("a team called \'1\' should be created and \'Bob\' should be owner of that team", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Non Owners should not be able to remove other members of a team")] + public virtual void NonOwnersShouldNotBeAbleToRemoveOtherMembersOfATeam() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Non Owners should not be able to remove other members of a team", ((string[])(null))); +#line 22 +this.ScenarioSetup(scenarioInfo); +#line 23 + testRunner.Given("There is a registered user with id \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 24 + testRunner.And("There is a registered user with id \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 25 + testRunner.And("There is a registered user with id \'Jon\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 26 + testRunner.And("Team \'1\' is owned by \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 27 + testRunner.And("\'Bob\' is a member Team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 28 + testRunner.And("\'Jon\' is a member Team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 29 + testRunner.And("I am logged in as \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 30 + testRunner.When("I remove \'Jon\' from team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 31 + testRunner.Then("I should be redirected to team details page for team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line 32 + testRunner.And("\'Bob\' should be a full member of team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line hidden + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Non Owners should be able to remove themselves from a team")] + public virtual void NonOwnersShouldBeAbleToRemoveThemselvesFromATeam() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Non Owners should be able to remove themselves from a team", ((string[])(null))); +#line 34 +this.ScenarioSetup(scenarioInfo); +#line 35 + testRunner.Given("There is a registered user with id \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 36 + testRunner.And("There is a registered user with id \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 37 + testRunner.And("There is a registered user with id \'Jon\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 38 + testRunner.And("Team \'1\' is owned by \'Tom\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 39 + testRunner.And("\'Bob\' is a member Team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 40 + testRunner.And("\'Jon\' is a member Team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 41 + testRunner.And("I am logged in as \'Bob\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 42 + testRunner.When("I remove \'Bob\' from team \'1\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 43 + testRunner.Then("\'Bob\' should not be a member of team \'1\' any more", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + this.ScenarioCleanup(); + } + } +} +#pragma warning restore +#endregion diff --git a/hacapp.web/hacapp.web.Tests/Features/Teams/TeamSteps.cs b/hacapp.web/hacapp.web.Tests/Features/Teams/TeamSteps.cs new file mode 100644 index 0000000..f987fd3 --- /dev/null +++ b/hacapp.web/hacapp.web.Tests/Features/Teams/TeamSteps.cs @@ -0,0 +1,335 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Mvc; +using FluentAssertions; +using Hacapp.Core.Commands; +using Hacapp.Core.Queries; +using Hacapp.Web.Models.Team; +using Haccapp.Model.Domain; +using Haccapp.Model.Identity; +using Microsoft.AspNet.Identity; +using Moq; +using TechTalk.SpecFlow; + +namespace hacapp.web.Tests.Features.Teams +{ + [Binding] + public class TeamSteps : CommonSteps + { + private readonly CurrentScenarioContext context; + + public TeamSteps(CurrentScenarioContext context) : base(context) + { + this.context = context; + } + + [Given(@"There is a registered user with id '(.*)'")] + public void GivenThereIsARegisteredUserWithId(string userId) + { + var user = new ApplicationUser + { + UserName = userId, + Id = userId + }; + IdentityResult result = context.UserManager.Create(user); + result.Succeeded.Should().BeTrue(); + var info = new UserLoginInfo("test", "test"); + context.UserManager.AddLogin(user.Id, info); + } + + [Given(@"Team '(.*)' is owned by '(.*)'")] + public void GivenTeamIsOwnedBy(string teamId, string userId) + { + ControllerContext currentContext = context.TeamController.ControllerContext; + Mock mockContext = GetMockControllerContext(userId); + context.TeamController.ControllerContext = mockContext.Object; + context.TeamController.Create(new CreateTeamViewModel {TeamName = teamId}); + context.TeamController.ControllerContext = currentContext; + } + + [When(@"I visit the team details page for team '(.*)'")] + public void WhenIVisitTheTeamDetailsPageForTeam(int teamId) + { + context.ActionResult = context.TeamController.Details(teamId); + } + + [Then(@"'(.*)' should be listed in the pending members")] + public void ThenShouldBeListedInThePendingMembers(string userId) + { + var viewResult = context.ActionResult as ViewResult; + var teamDetailsModel = viewResult.Model as TeamDetailsViewModel; + teamDetailsModel.PendingMembers.Should().Contain(m => m.Id == userId); + } + + [Then(@"'(.*)' should be listed in the confirmed members")] + public void ThenShouldBeListedInTheConfirmedMembers(string userId) + { + var viewResult = context.ActionResult as ViewResult; + var teamDetailsModel = viewResult.Model as TeamDetailsViewModel; + teamDetailsModel.ConfirmedMembers.Should().Contain(m => m.Id == userId); + } + + [Given(@"I am logged in as '(.*)'")] + public void GivenIAmLoggedInAs(string userId) + { + Mock mockContext = GetMockControllerContext(userId); + context.SetControllerContexts(mockContext); + CurrentLoggedInUserId = userId; + } + + [Then(@"The list of teams to join should contain Team '(.*)'")] + public void ThenTheListOfTeamsToJoinShouldContainTeam(string teamName) + { + var viewResult = context.ActionResult as ViewResult; + viewResult.Model.As>().Should().Contain(t => t.Name == teamName); + } + + [Then(@"The list of teams to join should not contain Team '(.*)'")] + public void ThenTheListOfTeamsToJoinShouldNotContainTeam(string teamName) + { + var viewResult = context.ActionResult as ViewResult; + viewResult.Model.As>().Should().NotContain(t => t.Name == teamName); + } + + + [Then(@"a team called '(.*)' should be created and '(.*)' should be owner of that team")] + public void ThenANewTeamShouldBeCreatedAndTheUserShouldBeOwnerOfThatTeam(string teamName, string userId) + { + List createdTeams = context.Db.Teams.Where(t => t.Name == teamName).ToList(); + createdTeams.Should().HaveCount(1); + createdTeams.First().Owner.Id.Should().Be(userId); + } + + + [When(@"I enter the full details for creating a team called '(.*)'")] + public void WhenIEnterTheFullDetailsForCreatingATeamCalled(string teamName) + { + context.ActionResult = + context.TeamController.Create(new CreateTeamViewModel {TeamName = teamName}); + } + + [Then(@"I should be redirected to the team details page")] + public void ThenIShouldBeRedirectedToTheTeamDetailsPage() + { + context.ActionResult.Should().BeOfType(); + var redirectResult = context.ActionResult as RedirectToRouteResult; + redirectResult.RouteValues.Should().Contain("id", 1) + .And.Contain("action", "Details"); + } + + [Then(@"only one team called '(.*)' should not be created")] + public void ThenOnlyOneTeamCalledShouldNotBeCreated(string teamName) + { + List createdTeams = context.Db.Teams.Where(t => t.Name == teamName).ToList(); + createdTeams.Should().HaveCount(1); + } + + [Then(@"I should be redirected to back to the create a team page")] + public void ThenIShouldBeRedirectedToBackToTheCreateATeamPage() + { + context.ActionResult.Should().BeOfType(); + var viewResult = context.ActionResult as ViewResult; + } + + [Then(@"there should be an error message")] + public void ThenThereShouldBeAnErrorMessage() + { + context.ActionResult.Should().BeOfType(); + var viewResult = context.ActionResult as ViewResult; + ((string) viewResult.ViewBag.ErrorMessage).Should() + .Be(string.Format("The team name was invalid. A team called 'BobsTeam' already exists.")); + } + + [When(@"I vist the join a team page")] + public void WhenIVistTheJoinATeamPage() + { + context.ActionResult = context.TeamController.Join(null); + } + + [Then(@"I should be shown the No Teams Exist view")] + public void ThenIShouldBeShownTheNoTeamsExistView() + { + var viewResult = context.ActionResult as ViewResult; + viewResult.ViewName.Should().Be("NoTeamToJoin"); + } + + + [When(@"I try and join team '(.*)'")] + public void WhenITryAndJoinTeam(int teamId) + { + context.ActionResult = context.TeamController.Join(teamId); + } + + [Then(@"'(.*)' should have a pending membership for team '(.*)'")] + public void ThenIShouldHaveAPendingMembershipForTeam(string userId, int teamId) + { + var teamQuery = new GetTeamByIdQuery(teamId); + Team team = context.Dispatcher.ExecuteQuery(teamQuery); + team.Members.Any(x => x.User.Id == userId && x.Status == MembershipStatus.Pending).Should().BeTrue(); + } + + [Then(@"'(.*)' should not have a pending membership for team '(.*)'")] + public void ThenShouldNotHaveAPendingMembershipForTeam(string userId, int teamId) + { + var teamQuery = new GetTeamByIdQuery(teamId); + Team team = context.Dispatcher.ExecuteQuery(teamQuery); + team.Members.Any(x => x.User.Id == userId && x.Status == MembershipStatus.Pending).Should().BeFalse(); + } + + + [Then(@"Team '(.*)' should be listed on my teams page")] + public void ThenTeamShouldBeListedOnMyTeamsPage(int teamId) + { + context.ActionResult = context.TeamController.Index(); + var viewResult = context.ActionResult.As(); + var teams = viewResult.Model.As>(); + teams.Should() + .Contain( + t => + t.Id == teamId); + } + + [Then(@"I should be redirected to my teams page")] + public void ThenIShouldBeRedirectedMyTeamsPage() + { + var redirect = context.ActionResult as RedirectToRouteResult; + redirect.RouteValues["action"].Should().Be("Index"); + } + + [Then(@"I should be redirected to team details page for team '(.*)'")] + public void ThenIShouldBeRedirectedToTeamDetailsPageForTeam(int teamId) + { + var redirect = context.ActionResult as RedirectToRouteResult; + redirect.RouteValues["id"].Should().Be(teamId); + redirect.RouteValues["action"].Should().Be("Details"); + } + + + [Then(@"I should see a list of teams that I can join")] + public void ThenIShouldSeeAListOfTeamsThatICanJoin() + { + context.ActionResult.Should().BeOfType(); + var teams = context.ActionResult.As() + .Model.As>(); + teams + .Should() + .Contain(t => t.Id == 1 && t.Name == "team1") + .And.Contain(t => t.Id == 2 && t.Name == "team2") + .And.HaveCount(2); + } + + [Given(@"'(.*)' has pending membership for Team '(.*)'")] + public void GivenHasPendingMembershipForTeam(string userId, int teamId) + { + var command = new JoinTeamCommand(teamId, userId); + context.Dispatcher.ExecuteCommand(command); + } + + [When(@"I accept pending membership for '(.*)' to join team '(.*)'")] + public void WhenIAcceptPendingMembershipFor(string userId, int teamId) + { + context.ActionResult = context.TeamController.UpdateMembership(teamId, userId, + UserMembershipStatus.Confirmed.ToString()); + } + + [Then(@"'(.*)' should be a full member of team '(.*)'")] + public void ThenUserShouldBeFullMemberOfTeamBlah(string userId, int teamId) + { + var userTeamsQuery = new GetUserTeamsQuery(userId); + IEnumerable teams = context.Dispatcher.ExecuteQuery(userTeamsQuery); + teams.Should() + .Contain( + t => + t.Members.SingleOrDefault(m => m.User.Id == userId && m.Status == MembershipStatus.Confimed) != + null); + } + + [When(@"I visit the details page for team '(.*)'")] + public void WhenIVisitTheDetailsPageForTeam(int teamId) + { + context.ActionResult = context.TeamController.Details(teamId); + } + + [Then(@"I should see the details for the team '(.*)'")] + public void ThenIShouldSeeTheDetailsForTheTeam(int teamId) + { + var viewResult = context.ActionResult as ViewResult; + var model = viewResult.Model as TeamDetailsViewModel; + model.Id.Should().Be(teamId); + } + + [Then(@"I should see a not authorized")] + public void ThenIShouldSeeANotAuthorized() + { + var viewResult = context.ActionResult as ViewResult; + viewResult.Model.Should().BeNull(); + var errorMessage = viewResult.ViewBag.ErrorMessage as string; + errorMessage.Should().Be("You are not authorized to view this teams details"); + } + + [When(@"I go to the home page")] + public void WhenIGoToTheHomePage() + { + context.ActionResult = context.TeamController.Index(); + } + + [Then(@"I should the create or join view")] + public void ThenIShouldSeeTheCreateOrJoinView() + { + var viewResult = context.ActionResult as ViewResult; + viewResult.Model.Should().BeNull(); + viewResult.ViewName.Should().Be("IndexNoTeams"); + } + + [Given(@"'(.*)' is a member Team '(.*)'")] + public void GivenIsAMemberTeam(string userId, int teamId) + { + context.Dispatcher.ExecuteCommand(new JoinTeamCommand(teamId, userId)); + context.Dispatcher.ExecuteCommand(new UpdateMembershipStatusCommand(userId, teamId, + MembershipStatus.Confimed)); + } + + [When(@"I remove '(.*)' from team '(.*)'")] + public void WhenIRemoveFromTeam(string userId, int teamId) + { + context.ActionResult = + context.TeamController.UpdateMembership(teamId, userId, UserMembershipStatus.Removed.ToString()); + } + + [Then(@"'(.*)' should not be a member of team '(.*)' any more")] + public void ThenShouldNotBeAMemberOfTeamAnyMore(string userId, int teamId) + { + Team team = context.Dispatcher.ExecuteQuery(new GetTeamByIdQuery(teamId)); + team.Members.Should().NotContain(m => m.User.Id == userId); + } + + [When(@"I delete team '(.*)'")] + public void WhenIDeleteTeam(int teamId) + { + context.TeamController.Delete(teamId); + } + + [Then(@"team '(.*)' should not exist")] + public void ThenTeamShouldNotExist(int teamId) + { + Action action = () => context.Dispatcher.ExecuteQuery(new GetTeamByIdQuery(teamId)); + action.ShouldThrow() + .And.Message.Should() + .Be(CommandExecutionExceptionMessages.TeamDoesNotExist(teamId)); + } + + [Then(@"'(.*)' should not be a member of any teams")] + public void ThenShouldNotBeAMemberOfAnyTeams(string userId) + { + IEnumerable teams = context.Dispatcher.ExecuteQuery(new GetUserTeamsQuery(userId)); + teams.Should().HaveCount(0); + } + + [Given(@"'(.*)' is in the TeamManagement role")] + public void GivenIsAnAdministrator(string userId) + { + context.UserManager.AddToRoleAsync(userId, RoleName.TeamManagement).Result.Succeeded.Should().BeTrue(); + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.web.Tests/TestHelper.cs b/hacapp.web/hacapp.web.Tests/TestHelper.cs new file mode 100644 index 0000000..b5945d1 --- /dev/null +++ b/hacapp.web/hacapp.web.Tests/TestHelper.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Security.Claims; +using System.Security.Principal; + +namespace hacapp.web.Tests +{ + public class TestHelper + { + public static GenericPrincipal GetPrincipal() + { + const string username = "username"; + Guid userid = Guid.NewGuid(); + + return GetPrincipal(userid.ToString(), username); + } + + public static GenericPrincipal GetPrincipal(string userId, string userName) + { + var claims = new List + { + new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", userName), + new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", userId) + }; + var genericIdentity = new GenericIdentity(userName); + genericIdentity.AddClaims(claims); + var genericPrincipal = new GenericPrincipal(genericIdentity, new string[0]); + return genericPrincipal; + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.web.Tests/UserLogin.feature b/hacapp.web/hacapp.web.Tests/UserLogin.feature deleted file mode 100644 index 5acc1ac..0000000 --- a/hacapp.web/hacapp.web.Tests/UserLogin.feature +++ /dev/null @@ -1,14 +0,0 @@ -Feature: UserLogin - In order to ensure users log in when they visit - As a user - I want to be directed to the login page when I first visit - -Scenario: Redirect To Login When Not logged in - Given I am not logged in - When I visit the home page - Then the login page should be displayed - -Scenario: Show the home page when I'm logged in - Given I am logged in - When I visit the home page - Then home page should be displayed diff --git a/hacapp.web/hacapp.web.Tests/hacapp.web.Tests.csproj b/hacapp.web/hacapp.web.Tests/hacapp.web.Tests.csproj index 71fffe4..72af961 100644 --- a/hacapp.web/hacapp.web.Tests/hacapp.web.Tests.csproj +++ b/hacapp.web/hacapp.web.Tests/hacapp.web.Tests.csproj @@ -36,8 +36,24 @@ ..\packages\BuildingBlocks.Mvc.FluentAssertions.1.0.0.62\lib\net40\BuildingBlocks.Mvc.FluentAssertions.dll - - ..\packages\FluentAssertions.2.1.0.0\lib\net45\FluentAssertions.dll + + False + ..\packages\EntityFramework.6.0.2\lib\net45\EntityFramework.dll + + + ..\packages\EntityFramework.6.0.2\lib\net45\EntityFramework.SqlServer.dll + + + False + ..\packages\FluentAssertions.2.2.0.0\lib\net45\FluentAssertions.dll + + + False + ..\packages\Microsoft.AspNet.Identity.Core.1.0.0\lib\net45\Microsoft.AspNet.Identity.Core.dll + + + False + ..\packages\Microsoft.AspNet.Identity.EntityFramework.1.0.0\lib\net45\Microsoft.AspNet.Identity.EntityFramework.dll @@ -47,6 +63,13 @@ ..\packages\NUnit.2.6.3\lib\nunit.framework.dll + + False + ..\packages\SimpleInjector.2.4.0\lib\net45\SimpleInjector.dll + + + ..\packages\SimpleInjector.2.4.0\lib\net45\SimpleInjector.Diagnostics.dll + @@ -98,10 +121,45 @@ + + True + True + HomePage.feature + + + + + True + True + CreateANewTeam.feature + + + + True + True + DeleteTeam.feature + + + True + True + JoinATeam.feature + + + True + True + TeamDetails.feature + + + True + True + TeamMembership.feature + + - + + True True UserLogin.feature @@ -111,18 +169,69 @@ + + SpecFlowSingleFileGenerator + HomePage.feature.cs + + + SpecFlowSingleFileGenerator + CreateANewTeam.feature.cs + + + SpecFlowSingleFileGenerator + DeleteTeam.feature.cs + + + SpecFlowSingleFileGenerator + JoinATeam.feature.cs + + + SpecFlowSingleFileGenerator + TeamDetails.feature.cs + + + SpecFlowSingleFileGenerator + TeamMembership.feature.cs + - + SpecFlowSingleFileGenerator UserLogin.feature.cs - + + {0A01520A-83F2-41F3-B2DE-B3AAA0A861B4} + Hacapp.Contracts + + + {38A957ED-6237-439D-93AC-220BFD7D0409} + Hacapp.Core + + + {2282989C-3FAB-404C-BF85-3BE9A5228E23} + Hacapp.Data + + + {E9399E7B-5C32-4B5E-B8F5-BE6CF8AAAF3E} + Hacapp.Web.Models + + {D4792F90-73B2-47A4-92CF-4CFEF1CA2571} - hacapp.web + Hacapp.Web + + + {1f19b069-bbef-4c41-b2a1-845893f87586} + Haccapp.Model + + + {9f70195c-261c-4ab6-8ce3-9a1e09f82e72} + NCrunch.Generator.SpecflowPlugin + + + + + + + + + + + + + + + + + + + diff --git a/hacapp.web/hacapp.web/Areas/HelpPage/Views/_ViewStart.cshtml b/hacapp.web/hacapp.web/Areas/HelpPage/Views/_ViewStart.cshtml new file mode 100644 index 0000000..a925950 --- /dev/null +++ b/hacapp.web/hacapp.web/Areas/HelpPage/Views/_ViewStart.cshtml @@ -0,0 +1,4 @@ +@{ + // Change the Layout path below to blend the look and feel of the help page with your existing web pages. + Layout = "~/Areas/HelpPage/Views/Shared/_Layout.cshtml"; +} \ No newline at end of file diff --git a/hacapp.web/hacapp.web/Areas/HelpPage/XmlDocumentationProvider.cs b/hacapp.web/hacapp.web/Areas/HelpPage/XmlDocumentationProvider.cs new file mode 100644 index 0000000..7a42247 --- /dev/null +++ b/hacapp.web/hacapp.web/Areas/HelpPage/XmlDocumentationProvider.cs @@ -0,0 +1,143 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Web.Http.Controllers; +using System.Web.Http.Description; +using System.Xml.XPath; + +namespace hacapp.web.Areas.HelpPage +{ + /// + /// A custom that reads the API documentation from an XML documentation file. + /// + public class XmlDocumentationProvider : IDocumentationProvider + { + private XPathNavigator _documentNavigator; + private const string TypeExpression = "/doc/members/member[@name='T:{0}']"; + private const string MethodExpression = "/doc/members/member[@name='M:{0}']"; + private const string ParameterExpression = "param[@name='{0}']"; + + /// + /// Initializes a new instance of the class. + /// + /// The physical path to XML document. + public XmlDocumentationProvider(string documentPath) + { + if (documentPath == null) + { + throw new ArgumentNullException("documentPath"); + } + XPathDocument xpath = new XPathDocument(documentPath); + _documentNavigator = xpath.CreateNavigator(); + } + + public string GetDocumentation(HttpControllerDescriptor controllerDescriptor) + { + XPathNavigator typeNode = GetTypeNode(controllerDescriptor); + return GetTagValue(typeNode, "summary"); + } + + public virtual string GetDocumentation(HttpActionDescriptor actionDescriptor) + { + XPathNavigator methodNode = GetMethodNode(actionDescriptor); + return GetTagValue(methodNode, "summary"); + } + + public virtual string GetDocumentation(HttpParameterDescriptor parameterDescriptor) + { + ReflectedHttpParameterDescriptor reflectedParameterDescriptor = parameterDescriptor as ReflectedHttpParameterDescriptor; + if (reflectedParameterDescriptor != null) + { + XPathNavigator methodNode = GetMethodNode(reflectedParameterDescriptor.ActionDescriptor); + if (methodNode != null) + { + string parameterName = reflectedParameterDescriptor.ParameterInfo.Name; + XPathNavigator parameterNode = methodNode.SelectSingleNode(String.Format(CultureInfo.InvariantCulture, ParameterExpression, parameterName)); + if (parameterNode != null) + { + return parameterNode.Value.Trim(); + } + } + } + + return null; + } + + public string GetResponseDocumentation(HttpActionDescriptor actionDescriptor) + { + XPathNavigator methodNode = GetMethodNode(actionDescriptor); + return GetTagValue(methodNode, "returns"); + } + + private XPathNavigator GetMethodNode(HttpActionDescriptor actionDescriptor) + { + ReflectedHttpActionDescriptor reflectedActionDescriptor = actionDescriptor as ReflectedHttpActionDescriptor; + if (reflectedActionDescriptor != null) + { + string selectExpression = String.Format(CultureInfo.InvariantCulture, MethodExpression, GetMemberName(reflectedActionDescriptor.MethodInfo)); + return _documentNavigator.SelectSingleNode(selectExpression); + } + + return null; + } + + private static string GetMemberName(MethodInfo method) + { + string name = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", method.DeclaringType.FullName, method.Name); + ParameterInfo[] parameters = method.GetParameters(); + if (parameters.Length != 0) + { + string[] parameterTypeNames = parameters.Select(param => GetTypeName(param.ParameterType)).ToArray(); + name += String.Format(CultureInfo.InvariantCulture, "({0})", String.Join(",", parameterTypeNames)); + } + + return name; + } + + private static string GetTagValue(XPathNavigator parentNode, string tagName) + { + if (parentNode != null) + { + XPathNavigator node = parentNode.SelectSingleNode(tagName); + if (node != null) + { + return node.Value.Trim(); + } + } + + return null; + } + + private static string GetTypeName(Type type) + { + if (type.IsGenericType) + { + // Format the generic type name to something like: Generic{System.Int32,System.String} + Type genericType = type.GetGenericTypeDefinition(); + Type[] genericArguments = type.GetGenericArguments(); + string typeName = genericType.FullName; + + // Trim the generic parameter counts from the name + typeName = typeName.Substring(0, typeName.IndexOf('`')); + string[] argumentTypeNames = genericArguments.Select(t => GetTypeName(t)).ToArray(); + return String.Format(CultureInfo.InvariantCulture, "{0}{{{1}}}", typeName, String.Join(",", argumentTypeNames)); + } + + return type.FullName; + } + + private XPathNavigator GetTypeNode(HttpControllerDescriptor controllerDescriptor) + { + Type controllerType = controllerDescriptor.ControllerType; + string controllerTypeName = controllerType.FullName; + if (controllerType.IsNested) + { + // Changing the nested type name from OuterType+InnerType to OuterType.InnerType to match the XML documentation syntax. + controllerTypeName = controllerTypeName.Replace("+", "."); + } + string selectExpression = String.Format(CultureInfo.InvariantCulture, TypeExpression, controllerTypeName); + return _documentNavigator.SelectSingleNode(selectExpression); + } + } +} diff --git a/hacapp.web/hacapp.web/Controllers/AccountController.cs b/hacapp.web/hacapp.web/Controllers/AccountController.cs index 94f30ec..32363af 100644 --- a/hacapp.web/hacapp.web/Controllers/AccountController.cs +++ b/hacapp.web/hacapp.web/Controllers/AccountController.cs @@ -1,24 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; using System.Web; using System.Web.Mvc; +using Hacapp.Web.Models.Identity; +using Haccapp.Model.Identity; using Microsoft.AspNet.Identity; -using Microsoft.AspNet.Identity.EntityFramework; +using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin.Security; -using hacapp.web.Models; namespace hacapp.web.Controllers { [Authorize] public class AccountController : Controller { - public AccountController() - : this(new UserManager(new UserStore(new ApplicationDbContext()))) - { - } +// public AccountController() +// : this(new UserManager(new UserStore(new ApplicationDb()))) + // { +// } public AccountController(UserManager userManager) { @@ -45,16 +44,13 @@ public async Task Login(LoginViewModel model, string returnUrl) { if (ModelState.IsValid) { - var user = await UserManager.FindAsync(model.UserName, model.Password); + ApplicationUser user = await UserManager.FindAsync(model.UserName, model.Password); if (user != null) { await SignInAsync(user, model.RememberMe); return RedirectToLocal(returnUrl); } - else - { - ModelState.AddModelError("", "Invalid username or password."); - } + ModelState.AddModelError("", "Invalid username or password."); } // If we got this far, something failed, redisplay form @@ -78,17 +74,14 @@ public async Task Register(RegisterViewModel model) { if (ModelState.IsValid) { - var user = new ApplicationUser() { UserName = model.UserName }; - var result = await UserManager.CreateAsync(user, model.Password); + var user = new ApplicationUser {UserName = model.UserName}; + IdentityResult result = await UserManager.CreateAsync(user, model.Password); if (result.Succeeded) { - await SignInAsync(user, isPersistent: false); + await SignInAsync(user, false); return RedirectToAction("Index", "Home"); } - else - { - AddErrors(result); - } + AddErrors(result); } // If we got this far, something failed, redisplay form @@ -102,7 +95,10 @@ public async Task Register(RegisterViewModel model) public async Task Disassociate(string loginProvider, string providerKey) { ManageMessageId? message = null; - IdentityResult result = await UserManager.RemoveLoginAsync(User.Identity.GetUserId(), new UserLoginInfo(loginProvider, providerKey)); + IdentityResult result = + await + UserManager.RemoveLoginAsync(User.Identity.GetUserId(), + new UserLoginInfo(loginProvider, providerKey)); if (result.Succeeded) { message = ManageMessageId.RemoveLoginSuccess; @@ -111,7 +107,7 @@ public async Task Disassociate(string loginProvider, string provid { message = ManageMessageId.Error; } - return RedirectToAction("Manage", new { Message = message }); + return RedirectToAction("Manage", new {Message = message}); } // @@ -119,11 +115,15 @@ public async Task Disassociate(string loginProvider, string provid public ActionResult Manage(ManageMessageId? message) { ViewBag.StatusMessage = - message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed." - : message == ManageMessageId.SetPasswordSuccess ? "Your password has been set." - : message == ManageMessageId.RemoveLoginSuccess ? "The external login was removed." - : message == ManageMessageId.Error ? "An error has occurred." - : ""; + message == ManageMessageId.ChangePasswordSuccess + ? "Your password has been changed." + : message == ManageMessageId.SetPasswordSuccess + ? "Your password has been set." + : message == ManageMessageId.RemoveLoginSuccess + ? "The external login was removed." + : message == ManageMessageId.Error + ? "An error has occurred." + : ""; ViewBag.HasLocalPassword = HasPassword(); ViewBag.ReturnUrl = Url.Action("Manage"); return View(); @@ -142,15 +142,15 @@ public async Task Manage(ManageUserViewModel model) { if (ModelState.IsValid) { - IdentityResult result = await UserManager.ChangePasswordAsync(User.Identity.GetUserId(), model.OldPassword, model.NewPassword); + IdentityResult result = + await + UserManager.ChangePasswordAsync(User.Identity.GetUserId(), model.OldPassword, + model.NewPassword); if (result.Succeeded) { - return RedirectToAction("Manage", new { Message = ManageMessageId.ChangePasswordSuccess }); - } - else - { - AddErrors(result); + return RedirectToAction("Manage", new {Message = ManageMessageId.ChangePasswordSuccess}); } + AddErrors(result); } } else @@ -164,15 +164,13 @@ public async Task Manage(ManageUserViewModel model) if (ModelState.IsValid) { - IdentityResult result = await UserManager.AddPasswordAsync(User.Identity.GetUserId(), model.NewPassword); + IdentityResult result = + await UserManager.AddPasswordAsync(User.Identity.GetUserId(), model.NewPassword); if (result.Succeeded) { - return RedirectToAction("Manage", new { Message = ManageMessageId.SetPasswordSuccess }); - } - else - { - AddErrors(result); + return RedirectToAction("Manage", new {Message = ManageMessageId.SetPasswordSuccess}); } + AddErrors(result); } } @@ -188,7 +186,8 @@ public async Task Manage(ManageUserViewModel model) public ActionResult ExternalLogin(string provider, string returnUrl) { // Request a redirect to the external login provider - return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl })); + return new ChallengeResult(provider, + Url.Action("ExternalLoginCallback", "Account", new {ReturnUrl = returnUrl})); } // @@ -196,26 +195,24 @@ public ActionResult ExternalLogin(string provider, string returnUrl) [AllowAnonymous] public async Task ExternalLoginCallback(string returnUrl) { - var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(); + ExternalLoginInfo loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(); if (loginInfo == null) { return RedirectToAction("Login"); } // Sign in the user with this external login provider if the user already has a login - var user = await UserManager.FindAsync(loginInfo.Login); + ApplicationUser user = await UserManager.FindAsync(loginInfo.Login); if (user != null) { - await SignInAsync(user, isPersistent: false); + await SignInAsync(user, false); return RedirectToLocal(returnUrl); } - else - { - // If the user does not have an account, then prompt the user to create an account - ViewBag.ReturnUrl = returnUrl; - ViewBag.LoginProvider = loginInfo.Login.LoginProvider; - return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { UserName = loginInfo.DefaultUserName }); - } + // If the user does not have an account, then prompt the user to create an account + ViewBag.ReturnUrl = returnUrl; + ViewBag.LoginProvider = loginInfo.Login.LoginProvider; + return View("ExternalLoginConfirmation", + new ExternalLoginConfirmationViewModel {UserName = loginInfo.DefaultUserName}); } // @@ -232,17 +229,18 @@ public ActionResult LinkLogin(string provider) // GET: /Account/LinkLoginCallback public async Task LinkLoginCallback() { - var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(XsrfKey, User.Identity.GetUserId()); + ExternalLoginInfo loginInfo = + await AuthenticationManager.GetExternalLoginInfoAsync(XsrfKey, User.Identity.GetUserId()); if (loginInfo == null) { - return RedirectToAction("Manage", new { Message = ManageMessageId.Error }); + return RedirectToAction("Manage", new {Message = ManageMessageId.Error}); } - var result = await UserManager.AddLoginAsync(User.Identity.GetUserId(), loginInfo.Login); + IdentityResult result = await UserManager.AddLoginAsync(User.Identity.GetUserId(), loginInfo.Login); if (result.Succeeded) { return RedirectToAction("Manage"); } - return RedirectToAction("Manage", new { Message = ManageMessageId.Error }); + return RedirectToAction("Manage", new {Message = ManageMessageId.Error}); } // @@ -250,7 +248,8 @@ public async Task LinkLoginCallback() [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] - public async Task ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl) + public async Task ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, + string returnUrl) { if (User.Identity.IsAuthenticated) { @@ -260,19 +259,19 @@ public async Task ExternalLoginConfirmation(ExternalLoginConfirmat if (ModelState.IsValid) { // Get the information about the user from the external login provider - var info = await AuthenticationManager.GetExternalLoginInfoAsync(); + ExternalLoginInfo info = await AuthenticationManager.GetExternalLoginInfoAsync(); if (info == null) { return View("ExternalLoginFailure"); } - var user = new ApplicationUser() { UserName = model.UserName }; - var result = await UserManager.CreateAsync(user); + var user = new ApplicationUser {UserName = model.UserName}; + IdentityResult result = await UserManager.CreateAsync(user); if (result.Succeeded) { result = await UserManager.AddLoginAsync(user.Id, info.Login); if (result.Succeeded) { - await SignInAsync(user, isPersistent: false); + await SignInAsync(user, false); return RedirectToLocal(returnUrl); } } @@ -304,9 +303,9 @@ public ActionResult ExternalLoginFailure() [ChildActionOnly] public ActionResult RemoveAccountList() { - var linkedAccounts = UserManager.GetLogins(User.Identity.GetUserId()); + IList linkedAccounts = UserManager.GetLogins(User.Identity.GetUserId()); ViewBag.ShowRemoveButton = HasPassword() || linkedAccounts.Count > 1; - return (ActionResult)PartialView("_RemoveAccountPartial", linkedAccounts); + return PartialView("_RemoveAccountPartial", linkedAccounts); } protected override void Dispose(bool disposing) @@ -320,27 +319,34 @@ protected override void Dispose(bool disposing) } #region Helpers + // Used for XSRF protection when adding external logins + public enum ManageMessageId + { + ChangePasswordSuccess, + SetPasswordSuccess, + RemoveLoginSuccess, + Error + } + private const string XsrfKey = "XsrfId"; private IAuthenticationManager AuthenticationManager { - get - { - return HttpContext.GetOwinContext().Authentication; - } + get { return HttpContext.GetOwinContext().Authentication; } } private async Task SignInAsync(ApplicationUser user, bool isPersistent) { AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie); - var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie); - AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity); + ClaimsIdentity identity = + await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie); + AuthenticationManager.SignIn(new AuthenticationProperties {IsPersistent = isPersistent}, identity); } private void AddErrors(IdentityResult result) { - foreach (var error in result.Errors) + foreach (string error in result.Errors) { ModelState.AddModelError("", error); } @@ -348,7 +354,7 @@ private void AddErrors(IdentityResult result) private bool HasPassword() { - var user = UserManager.FindById(User.Identity.GetUserId()); + ApplicationUser user = UserManager.FindById(User.Identity.GetUserId()); if (user != null) { return user.PasswordHash != null; @@ -356,24 +362,13 @@ private bool HasPassword() return false; } - public enum ManageMessageId - { - ChangePasswordSuccess, - SetPasswordSuccess, - RemoveLoginSuccess, - Error - } - private ActionResult RedirectToLocal(string returnUrl) { if (Url.IsLocalUrl(returnUrl)) { return Redirect(returnUrl); } - else - { - return RedirectToAction("Index", "Home"); - } + return RedirectToAction("Index", "Home"); } private class ChallengeResult : HttpUnauthorizedResult @@ -395,7 +390,7 @@ public ChallengeResult(string provider, string redirectUri, string userId) public override void ExecuteResult(ControllerContext context) { - var properties = new AuthenticationProperties() { RedirectUri = RedirectUri }; + var properties = new AuthenticationProperties {RedirectUri = RedirectUri}; if (UserId != null) { properties.Dictionary[XsrfKey] = UserId; @@ -403,6 +398,7 @@ public override void ExecuteResult(ControllerContext context) context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider); } } + #endregion } } \ No newline at end of file diff --git a/hacapp.web/hacapp.web/Controllers/HomeController.cs b/hacapp.web/hacapp.web/Controllers/HomeController.cs index 3658e00..153ab25 100644 --- a/hacapp.web/hacapp.web/Controllers/HomeController.cs +++ b/hacapp.web/hacapp.web/Controllers/HomeController.cs @@ -1,24 +1,46 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using System.Web; using System.Web.Mvc; +using AutoMapper; +using hacapp.contracts.Commands; +using Hacapp.Core.Queries; +using Hacapp.Core.Queries.Result; +using Hacapp.Web.Models; +using Haccapp.Model.Domain; +using Microsoft.AspNet.Identity; namespace hacapp.web.Controllers { [Authorize] public class HomeController : Controller - { + { + private readonly ICommandAndQueryDispatcher dispatcher; + + public HomeController(ICommandAndQueryDispatcher dispatcher) + { + this.dispatcher = dispatcher; + } + public ActionResult Index() { - if (ControllerContext.HttpContext.Request.IsAuthenticated) + string userId = HttpContext.User.Identity.GetUserId(); + IEnumerable teams = + dispatcher.ExecuteQuery(new GetUserTeamsQuery(userId)); + if (!teams.Any()) { - ViewBag.Message = "Welcome back " + ControllerContext.HttpContext.User.Identity.Name; - return View(); + return RedirectToAction("Index", "Team"); } - ViewBag.Message = "Welcome to HACCAPP. Please log in to continue."; - return RedirectToAction("Account/Login"); + ViewBag.Message = "Welcome " + ControllerContext.HttpContext.User.Identity.Name; + IEnumerable pendingMemberships = + dispatcher.ExecuteQuery(new GetPendingMembershipsForUsersTeamsQuery(userId)); + var model = new HomePageViewModel + { + PendingMemberships = + Mapper.Map, List>(pendingMemberships) + }; + + return View(model); } [AllowAnonymous] diff --git a/hacapp.web/hacapp.web/Controllers/TeamController.cs b/hacapp.web/hacapp.web/Controllers/TeamController.cs new file mode 100644 index 0000000..41e3d3a --- /dev/null +++ b/hacapp.web/hacapp.web/Controllers/TeamController.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Mvc; +using AutoMapper; +using hacapp.contracts.Commands; +using Hacapp.Core.Commands; +using Hacapp.Core.Queries; +using Hacapp.Data.Identity; +using Hacapp.Web.Models.Team; +using Haccapp.Model.Domain; +using Haccapp.Model.Identity; +using Microsoft.AspNet.Identity; + +namespace hacapp.web.Controllers +{ + [Authorize] + public class TeamController : Controller + { + private readonly ICommandAndQueryDispatcher dispatcher; + + public TeamController(ICommandAndQueryDispatcher dispatcher) + { + this.dispatcher = dispatcher; + } + + // + // GET: /Team/ + public ActionResult Index() + { + IQuery> command = new GetUserTeamsQuery(HttpContext.User.Identity.GetUserId()); + IEnumerable teams = dispatcher.ExecuteQuery(command).ToList(); + if (teams.Any()) + { + List teamDetailsViewModels = + Mapper.Map, List>(teams); + ApplicationUser currentUser = + dispatcher.ExecuteQuery(new GetUserByIdQuery(HttpContext.User.Identity.GetUserId())); + foreach (TeamDetailsViewModel team in teamDetailsViewModels) + { + team.IsEditable = CurrentUserIsOwnerOrTeamManager(currentUser, team); + } + return View(teamDetailsViewModels); + } + + ViewBag.Message = "Welcome " + ControllerContext.HttpContext.User.Identity.Name; + return View("IndexNoTeams"); + } + + private bool CurrentUserIsOwnerOrTeamManager(ApplicationUser currentUser, TeamDetailsViewModel team) + { + return currentUser.IsInRole(RoleName.TeamManagement) || team.Owner.Id == currentUser.Id; + } + + // + // GET: /Team/Details/5 + public ActionResult Details(int id) + { + var query = new GetUserConfirmedTeamsQuery(HttpContext.User.Identity.GetUserId()); + Team team = dispatcher.ExecuteQuery(query).FirstOrDefault(t => t.Id == id); + if (team != null) + { + TeamDetailsViewModel teamViewModel = Mapper.Map(team); + ApplicationUser currentUser = + dispatcher.ExecuteQuery(new GetUserByIdQuery(HttpContext.User.Identity.GetUserId())); + teamViewModel.IsEditable = CurrentUserIsOwnerOrTeamManager(currentUser, teamViewModel); + return View(teamViewModel); + } + ViewBag.ErrorMessage = "You are not authorized to view this teams details"; + return View("NotAMemberOfATeam"); + } + + // + // GET: /Team/Create + public ActionResult Create() + { + return View(); + } + + // + // POST: /Team/Create + [HttpPost] + public ActionResult Create(CreateTeamViewModel viewModel) + { + if (ModelState.IsValid) + { + try + { + IQuery currentUser = new GetUserByIdQuery(HttpContext.User.Identity.GetUserId()); + var command = new CreateNewTeamCommand(viewModel.TeamName, dispatcher.ExecuteQuery(currentUser)); + dispatcher.ExecuteCommand(command); + + return RedirectToAction("Details", new {id = command.TeamId}); + } + catch (ArgumentException argumentException) + { + ViewBag.ErrorMessage = argumentException.Message; + return View(); + } + catch + { + return View(); + } + } + return View(); + } + + // GET: Team/Join/5 + public ActionResult Join(int? teamId) + { + if (teamId == null) + { + return ShowTeamsToJoin(); + } + + return JoinExistingTeam(teamId); + } + + private ActionResult JoinExistingTeam(int? teamId) + { + var command = new JoinTeamCommand(teamId.Value, HttpContext.User.Identity.GetUserId()); + dispatcher.ExecuteCommand(command); + return RedirectToAction("Index"); + } + + private ActionResult ShowTeamsToJoin() + { + var query = new GetTeamsUserDoesNotBelongToQuery(HttpContext.User.Identity.GetUserId()); + IEnumerable teams = dispatcher.ExecuteQuery(query).ToList(); + if (teams.Any()) + { + return View(Mapper.Map, List>(teams)); + } + + return View("NoTeamToJoin"); + } + + //GET: Team/5/UpdateMembership/1/NewStatus + public ActionResult UpdateMembership(int teamId, string idOfUserToUpdate, string newStatus) + { + if (ModelState.IsValid) + { + UserMembershipStatus newMembershipStatus; + if (!Enum.TryParse(newStatus, true, out newMembershipStatus)) + { + return RedirectToAction("Details", new {id = teamId}); + } + + ICommand command = CreateCommandToUpdateMembership(teamId, idOfUserToUpdate, newMembershipStatus); + + try + { + dispatcher.ExecuteCommand(command); + } + catch (InvalidOperationException exception) + { + ViewBag.ErrorMessage = exception.Message; + } + + return RedirectToAction("Details", new {id = teamId}); + } + + return RedirectToAction("Details", new {id = teamId}); + } + + private static ICommand CreateCommandToUpdateMembership(int teamId, string idOfUserToUpdate, + UserMembershipStatus newMembershipStatus) + { + ICommand command = null; + if (newMembershipStatus == UserMembershipStatus.Removed) + { + command = new RemoveUserFromTeamCommand(idOfUserToUpdate, teamId); + } + else + { + command = new UpdateMembershipStatusCommand(idOfUserToUpdate, teamId, + Mapper.Map( + newMembershipStatus)); + } + return command; + } + + + //POST: Team/5/Delete + [HttpPost] + public void Delete(int teamId) + { + dispatcher.ExecuteCommand(new DeleteTeamCommand(teamId)); + } + } +} \ No newline at end of file diff --git a/hacapp.web/hacapp.web/Fody.targets b/hacapp.web/hacapp.web/Fody.targets new file mode 100644 index 0000000..a668a51 --- /dev/null +++ b/hacapp.web/hacapp.web/Fody.targets @@ -0,0 +1,89 @@ + + + + + + $(NCrunchOriginalSolutionDir) + + + + + $(SolutionDir) + + + + + $(MSBuildProjectDirectory)\..\ + + + + + + + $(KeyOriginatorFile) + + + + + $(AssemblyOriginatorKeyFile) + + + + + + + + + + $(ProjectDir)$(IntermediateOutputPath) + Low + $(SignAssembly) + $(MSBuildThisFileDirectory) + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hacapp.web/hacapp.web/FodyWeavers.xml b/hacapp.web/hacapp.web/FodyWeavers.xml new file mode 100644 index 0000000..9d96e0a --- /dev/null +++ b/hacapp.web/hacapp.web/FodyWeavers.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/hacapp.web/hacapp.web/GlimpseSecurityPolicy.cs b/hacapp.web/hacapp.web/GlimpseSecurityPolicy.cs new file mode 100644 index 0000000..25992ef --- /dev/null +++ b/hacapp.web/hacapp.web/GlimpseSecurityPolicy.cs @@ -0,0 +1,32 @@ +/* +// Uncomment this class to provide custom runtime policy for Glimpse + +using Glimpse.AspNet.Extensions; +using Glimpse.Core.Extensibility; + +namespace hacapp.web +{ + public class GlimpseSecurityPolicy:IRuntimePolicy + { + public RuntimePolicy Execute(IRuntimePolicyContext policyContext) + { + // You can perform a check like the one below to control Glimpse's permissions within your application. + // More information about RuntimePolicies can be found at http://getglimpse.com/Help/Custom-Runtime-Policy + // var httpContext = policyContext.GetHttpContext(); + // if (!httpContext.User.IsInRole("Administrator")) + // { + // return RuntimePolicy.Off; + // } + + return RuntimePolicy.On; + } + + public RuntimeEvent ExecuteOn + { + // The RuntimeEvent.ExecuteResource is only needed in case you create a security policy + // Have a look at http://blog.getglimpse.com/2013/12/09/protect-glimpse-axd-with-your-custom-runtime-policy/ for more details + get { return RuntimeEvent.EndRequest | RuntimeEvent.ExecuteResource; } + } + } +} +*/ \ No newline at end of file diff --git a/hacapp.web/hacapp.web/Global.asax.cs b/hacapp.web/hacapp.web/Global.asax.cs index 9290aee..8df1557 100644 --- a/hacapp.web/hacapp.web/Global.asax.cs +++ b/hacapp.web/hacapp.web/Global.asax.cs @@ -1,21 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; +using System.Web; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; namespace hacapp.web { - public class MvcApplication : System.Web.HttpApplication + public class MvcApplication : HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); + SimpleInjectorConfig.RegisterComponents(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); + AutoMapperConfig.RegisterMappings(); } } -} +} \ No newline at end of file diff --git a/hacapp.web/hacapp.web/Models/IdentityModels.cs b/hacapp.web/hacapp.web/Models/IdentityModels.cs deleted file mode 100644 index 9f0a2a9..0000000 --- a/hacapp.web/hacapp.web/Models/IdentityModels.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.AspNet.Identity.EntityFramework; - -namespace hacapp.web.Models -{ - // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more. - public class ApplicationUser : IdentityUser - { - } - - public class ApplicationDbContext : IdentityDbContext - { - public ApplicationDbContext() - : base("DefaultConnection") - { - } - } -} \ No newline at end of file diff --git a/hacapp.web/hacapp.web/Startup.cs b/hacapp.web/hacapp.web/Startup.cs index a69f85a..44b989a 100644 --- a/hacapp.web/hacapp.web/Startup.cs +++ b/hacapp.web/hacapp.web/Startup.cs @@ -1,4 +1,5 @@ -using Microsoft.Owin; +using System.Web.Http; +using Microsoft.Owin; using Owin; [assembly: OwinStartupAttribute(typeof(hacapp.web.Startup))] @@ -9,6 +10,10 @@ public partial class Startup public void Configuration(IAppBuilder app) { ConfigureAuth(app); + +// var webApiConfiguration = new HttpConfiguration(); +// WebApiConfig.Register(webApiConfiguration); +// app.UseWebApi(webApiConfiguration); } } } diff --git a/hacapp.web/hacapp.web/Views/Account/ExternalLoginConfirmation.cshtml b/hacapp.web/hacapp.web/Views/Account/ExternalLoginConfirmation.cshtml index a4ec340..0f3428e 100644 --- a/hacapp.web/hacapp.web/Views/Account/ExternalLoginConfirmation.cshtml +++ b/hacapp.web/hacapp.web/Views/Account/ExternalLoginConfirmation.cshtml @@ -1,4 +1,4 @@ -@model hacapp.web.Models.ExternalLoginConfirmationViewModel +@model Hacapp.Web.Models.Identity.ExternalLoginConfirmationViewModel @{ ViewBag.Title = "Register"; } diff --git a/hacapp.web/hacapp.web/Views/Account/Login.cshtml b/hacapp.web/hacapp.web/Views/Account/Login.cshtml index df09903..03f4278 100644 --- a/hacapp.web/hacapp.web/Views/Account/Login.cshtml +++ b/hacapp.web/hacapp.web/Views/Account/Login.cshtml @@ -1,4 +1,5 @@ -@model hacapp.web.Models.LoginViewModel +@using Hacapp.Web.Models.Identity +@model Hacapp.Web.Models.Identity.LoginViewModel @{ ViewBag.Title = "Log in"; @@ -6,50 +7,9 @@

@ViewBag.Title.

-
-
- @using (Html.BeginForm("Login", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" })) - { - @Html.AntiForgeryToken() -

Use a local account to log in.

-
- @Html.ValidationSummary(true) -
- @Html.LabelFor(m => m.UserName, new { @class = "col-md-2 control-label" }) -
- @Html.TextBoxFor(m => m.UserName, new { @class = "form-control" }) - @Html.ValidationMessageFor(m => m.UserName) -
-
-
- @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" }) -
- @Html.PasswordFor(m => m.Password, new { @class = "form-control" }) - @Html.ValidationMessageFor(m => m.Password) -
-
-
-
-
- @Html.CheckBoxFor(m => m.RememberMe) - @Html.LabelFor(m => m.RememberMe) -
-
-
-
-
- -
-
-

- @Html.ActionLink("Register", "Register") if you don't have a local account. -

- } -
-
- @Html.Partial("_ExternalLoginsListPartial", new { Action = "ExternalLogin", ReturnUrl = ViewBag.ReturnUrl }) + @Html.Partial("_ExternalLoginsListPartial", new ExternalLoginViewModel { Action = "ExternalLogin", ReturnUrl = ViewBag.ReturnUrl })
diff --git a/hacapp.web/hacapp.web/Views/Account/Manage.cshtml b/hacapp.web/hacapp.web/Views/Account/Manage.cshtml index 238435f..b6053c1 100644 --- a/hacapp.web/hacapp.web/Views/Account/Manage.cshtml +++ b/hacapp.web/hacapp.web/Views/Account/Manage.cshtml @@ -1,4 +1,4 @@ -@using hacapp.web.Models; +@using Hacapp.Web.Models.Identity @using Microsoft.AspNet.Identity; @{ ViewBag.Title = "Manage Account"; @@ -20,7 +20,7 @@
@Html.Action("RemoveAccountList") - @Html.Partial("_ExternalLoginsListPartial", new { Action = "LinkLogin", ReturnUrl = ViewBag.ReturnUrl }) + @Html.Partial("_ExternalLoginsListPartial", new ExternalLoginViewModel { Action = "LinkLogin", ReturnUrl = ViewBag.ReturnUrl })
diff --git a/hacapp.web/hacapp.web/Views/Account/Register.cshtml b/hacapp.web/hacapp.web/Views/Account/Register.cshtml index 11d5adf..5fff1e5 100644 --- a/hacapp.web/hacapp.web/Views/Account/Register.cshtml +++ b/hacapp.web/hacapp.web/Views/Account/Register.cshtml @@ -1,4 +1,4 @@ -@model hacapp.web.Models.RegisterViewModel +@model Hacapp.Web.Models.Identity.RegisterViewModel @{ ViewBag.Title = "Register"; } diff --git a/hacapp.web/hacapp.web/Views/Account/_ChangePasswordPartial.cshtml b/hacapp.web/hacapp.web/Views/Account/_ChangePasswordPartial.cshtml index a9c68bf..9fb0b0e 100644 --- a/hacapp.web/hacapp.web/Views/Account/_ChangePasswordPartial.cshtml +++ b/hacapp.web/hacapp.web/Views/Account/_ChangePasswordPartial.cshtml @@ -1,5 +1,5 @@ @using Microsoft.AspNet.Identity -@model hacapp.web.Models.ManageUserViewModel +@model Hacapp.Web.Models.Identity.ManageUserViewModel

You're logged in as @User.Identity.GetUserName().

diff --git a/hacapp.web/hacapp.web/Views/Account/_ExternalLoginsListPartial.cshtml b/hacapp.web/hacapp.web/Views/Account/_ExternalLoginsListPartial.cshtml index 529e4c5..4ee5b46 100644 --- a/hacapp.web/hacapp.web/Views/Account/_ExternalLoginsListPartial.cshtml +++ b/hacapp.web/hacapp.web/Views/Account/_ExternalLoginsListPartial.cshtml @@ -1,4 +1,6 @@ -@using Microsoft.Owin.Security +@model Hacapp.Web.Models.Identity.ExternalLoginViewModel + +@using Microsoft.Owin.Security

Use another service to log in.


diff --git a/hacapp.web/hacapp.web/Views/Account/_SetPasswordPartial.cshtml b/hacapp.web/hacapp.web/Views/Account/_SetPasswordPartial.cshtml index 72ab633..29d469f 100644 --- a/hacapp.web/hacapp.web/Views/Account/_SetPasswordPartial.cshtml +++ b/hacapp.web/hacapp.web/Views/Account/_SetPasswordPartial.cshtml @@ -1,4 +1,4 @@ -@model hacapp.web.Models.ManageUserViewModel +@model Hacapp.Web.Models.Identity.ManageUserViewModel

You do not have a local username/password for this site. Add a local diff --git a/hacapp.web/hacapp.web/Views/Home/About.cshtml b/hacapp.web/hacapp.web/Views/Home/About.cshtml index 4b2d9e8..b4e99e2 100644 --- a/hacapp.web/hacapp.web/Views/Home/About.cshtml +++ b/hacapp.web/hacapp.web/Views/Home/About.cshtml @@ -4,4 +4,4 @@

@ViewBag.Title.

@ViewBag.Message

-

Use this area to provide additional information.

+

This application provides a workflow management tool, mainly for producing foodstuffs.

diff --git a/hacapp.web/hacapp.web/Views/Home/Contact.cshtml b/hacapp.web/hacapp.web/Views/Home/Contact.cshtml index 0f4327e..cc3cdd0 100644 --- a/hacapp.web/hacapp.web/Views/Home/Contact.cshtml +++ b/hacapp.web/hacapp.web/Views/Home/Contact.cshtml @@ -5,13 +5,9 @@

@ViewBag.Message

- One Microsoft Way
- Redmond, WA 98052-6399
- P: - 425.555.0100 +
- Support: Support@example.com
- Marketing: Marketing@example.com + Found a bug? Contact support: samholder@gmail.com
\ No newline at end of file diff --git a/hacapp.web/hacapp.web/Views/Home/CreateOrJoinTeam.cshtml b/hacapp.web/hacapp.web/Views/Home/CreateOrJoinTeam.cshtml new file mode 100644 index 0000000..1a8bb0b --- /dev/null +++ b/hacapp.web/hacapp.web/Views/Home/CreateOrJoinTeam.cshtml @@ -0,0 +1,5 @@ +

@ViewBag.Message

+

You have not yet created or joined a team. A team is a collection of 1 or more people who will participate in the management of the processes that are defined.

+

If you are the owner of the processes then you will want to create a new team. If you work for someone who uses this app to manage their processes then you will want to join a team.

+Create a new team +Join an existing team \ No newline at end of file diff --git a/hacapp.web/hacapp.web/Views/Home/Index.cshtml b/hacapp.web/hacapp.web/Views/Home/Index.cshtml index 48b89d6..7c7748d 100644 --- a/hacapp.web/hacapp.web/Views/Home/Index.cshtml +++ b/hacapp.web/hacapp.web/Views/Home/Index.cshtml @@ -1,31 +1,42 @@ -@{ +@using Hacapp.Web.Models.Team +@model Hacapp.Web.Models.HomePageViewModel + +@{ ViewBag.Title = "Home Page"; } -
-

ASP.NET

-

Blah2

-

Learn more »

-
+@if (Model.PendingMemberships.Any()) +{ +
+
+

Pending memberships

+ @foreach (var pendingMembership in Model.PendingMemberships) + { +
+
+ @pendingMembership.UserName want to join @pendingMembership.TeamName +
+
+ @Html.ActionLink("Confirm membership", "UpdateMembership", "Team", new { idOfUserToUpdate = @pendingMembership.UserId, teamId = @pendingMembership.TeamId, newStatus = UserMembershipStatus.Confirmed }, new { Class = "btn btn-sm btn-default" }) +
+
+ } +
+
+ +}
-

Getting started

-

- ASP.NET MVC gives you a powerful, patterns-based way to build dynamic websites that - enables a clean separation of concerns and gives you full control over markup - for enjoyable, agile development. -

-

Learn more »

-
-
-

Get more libraries

-

NuGet is a free Visual Studio extension that makes it easy to add, remove, and update libraries and tools in Visual Studio projects.

-

Learn more »

-
-
-

Web Hosting

-

You can easily find a web hosting company that offers the right mix of features and price for your applications.

-

Learn more »

+

Todays tasks

+
+

Sample task 1

+
+
+

Sample task 1

+
+
+

Sample task 1

+
\ No newline at end of file diff --git a/hacapp.web/hacapp.web/Views/Shared/_Layout.cshtml b/hacapp.web/hacapp.web/Views/Shared/_Layout.cshtml index ec482f6..be53f55 100644 --- a/hacapp.web/hacapp.web/Views/Shared/_Layout.cshtml +++ b/hacapp.web/hacapp.web/Views/Shared/_Layout.cshtml @@ -22,6 +22,8 @@