Skip to content

Commit 921c6f0

Browse files
committed
Add create collection and channel command handlers
1 parent c0890d1 commit 921c6f0

File tree

57 files changed

+1056
-92
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+1056
-92
lines changed

CodeSnippets/CommandHandler.snippet

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,14 @@
2121
</Literal>
2222
</Declarations>
2323
<Code Language="csharp">
24-
<![CDATA[[AutoConstructor]
24+
<![CDATA[using System;
25+
using System.Threading.Tasks;
26+
27+
using Fifthweek.Api.Core;
28+
using Fifthweek.Api.Identity.Shared.Membership;
29+
using Fifthweek.CodeGeneration;
30+
31+
[AutoConstructor]
2532
public partial class $CommandPrefix$CommandHandler : ICommandHandler<$CommandPrefix$Command>
2633
{
2734
private readonly IRequesterSecurity requesterSecurity;

CodeSnippets/TestClass.snippet

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
33
<CodeSnippet Format="1.0.0">
44
<Header>
5-
<Title>Test Class</Title>
5+
<Title>Test Class (Good One)</Title>
66
<Description>Test scaffolding</Description>
77
<Shortcut>testc</Shortcut>
88
</Header>
@@ -15,7 +15,14 @@
1515
</Literal>
1616
</Declarations>
1717
<Code Language="csharp">
18-
<![CDATA[[TestClass]
18+
<![CDATA[using System;
19+
using System.Threading.Tasks;
20+
21+
using Microsoft.VisualStudio.TestTools.UnitTesting;
22+
23+
using Moq;
24+
25+
[TestClass]
1926
public class $TargetType$Tests
2027
{
2128
private $TargetType$ target;
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
3+
<CodeSnippet Format="1.0.0">
4+
<Header>
5+
<Title>Test Class (Command Handler)</Title>
6+
<Description>Test scaffolding</Description>
7+
<Shortcut>testcc</Shortcut>
8+
</Header>
9+
<Snippet>
10+
<Declarations>
11+
<Literal>
12+
<ID>TargetType</ID>
13+
<ToolTip>Replace with target type.</ToolTip>
14+
<Default>Awesome</Default>
15+
</Literal>
16+
</Declarations>
17+
<Code Language="csharp">
18+
<![CDATA[using System;
19+
using System.Threading.Tasks;
20+
21+
using Fifthweek.Api.Core;
22+
using Fifthweek.Api.Identity.Shared.Membership;
23+
using Fifthweek.Api.Identity.Tests.Shared.Membership;
24+
using Fifthweek.Shared;
25+
26+
using Microsoft.VisualStudio.TestTools.UnitTesting;
27+
28+
using Moq;
29+
30+
[TestClass]
31+
public class $TargetType$Tests
32+
{
33+
private static readonly UserId UserId = new UserId(Guid.NewGuid());
34+
private static readonly Requester Requester = Requester.Authenticated(UserId);
35+
36+
private $TargetType$ target;
37+
38+
[TestInitialize]
39+
public void Initialize()
40+
{
41+
this.requesterSecurity = new Mock<IRequesterSecurity>();
42+
this.requesterSecurity.SetupFor(Requester);
43+
44+
this.target = new $TargetType$(this.requesterSecurity.Object);
45+
}
46+
47+
[TestMethod]
48+
[ExpectedException(typeof(ArgumentNullException))]
49+
public async Task ItShouldRequireCommand()
50+
{
51+
await this.target.HandleAsync(null);
52+
}
53+
54+
[TestMethod]
55+
[ExpectedException(typeof(UnauthorizedException))]
56+
public async Task ItShouldRequireUserIsAuthenticated()
57+
{
58+
await this.target.HandleAsync(new CreateCollectionCommand(Requester.Unauthenticated, CollectionId, ChannelId, Name));
59+
}
60+
61+
[TestMethod]
62+
public async Task ItShould()
63+
{
64+
65+
}
66+
}]]>
67+
</Code>
68+
</Snippet>
69+
</CodeSnippet>
70+
</CodeSnippets>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
3+
<CodeSnippet Format="1.0.0">
4+
<Header>
5+
<Title>Test Method (Database)</Title>
6+
<Description>Test scaffolding for database method</Description>
7+
<Shortcut>testd</Shortcut>
8+
</Header>
9+
<Snippet>
10+
<Code Language="csharp">
11+
<![CDATA[ [TestMethod]
12+
public async Task ItShouldHaveNoEffect()
13+
{
14+
await this.DatabaseTestAsync(async testDatabase =>
15+
{
16+
this.InitializeTarget(testDatabase.NewContext());
17+
await this.CreateEntitiesAsync(testDatabase);
18+
await testDatabase.TakeSnapshotAsync();
19+
20+
await this.target.ExecuteAsync();
21+
22+
return ExpectedSideEffects.None;
23+
});
24+
}
25+
26+
private async Task CreateEntitiesAsync(TestDatabaseContext testDatabase)
27+
{
28+
using (var databaseContext = testDatabase.NewContext())
29+
{
30+
31+
}
32+
}]]>
33+
</Code>
34+
</Snippet>
35+
</CodeSnippet>
36+
</CodeSnippets>

Fifthweek.Api.Accounts.Tests/Commands/UpdateAccountSettingsCommandHandlerTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public async Task WhenCalledWithValidData_ItShouldCallTheAccountRepository()
6666
Password,
6767
FileId);
6868

69-
this.fileSecurity.Setup(v => v.AssertUsageAllowedAsync(UserId, FileId))
69+
this.fileSecurity.Setup(v => v.AssertReferenceAllowedAsync(UserId, FileId))
7070
.Returns(Task.FromResult(0));
7171

7272
this.updateAccountSettings.Setup(
@@ -96,7 +96,7 @@ public async Task WhenCalledWithUnauthorizedFileId_ItShouldCallThrowAnUnauthroiz
9696
Password,
9797
FileId);
9898

99-
this.fileSecurity.Setup(v => v.AssertUsageAllowedAsync(UserId, FileId))
99+
this.fileSecurity.Setup(v => v.AssertReferenceAllowedAsync(UserId, FileId))
100100
.Throws(new UnauthorizedException());
101101

102102
await this.target.HandleAsync(command);

Fifthweek.Api.Accounts/Commands/UpdateAccountSettingsCommandHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public async Task HandleAsync(UpdateAccountSettingsCommand command)
2323

2424
if (command.NewProfileImageId != null)
2525
{
26-
await this.fileSecurity.AssertUsageAllowedAsync(userId, command.NewProfileImageId);
26+
await this.fileSecurity.AssertReferenceAllowedAsync(userId, command.NewProfileImageId);
2727
}
2828

2929
var result = await this.updateAccountSettings.ExecuteAsync(

Fifthweek.Api.Channels.Shared/IChannelSecurity.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ namespace Fifthweek.Api.Channels.Shared
66

77
public interface IChannelSecurity
88
{
9-
Task<bool> IsPostingAllowedAsync(UserId requester, ChannelId channelId);
9+
Task<bool> IsWriteAllowedAsync(UserId requester, ChannelId channelId);
1010

11-
Task AssertPostingAllowedAsync(UserId requester, ChannelId channelId);
11+
Task AssertWriteAllowedAsync(UserId requester, ChannelId channelId);
1212
}
1313
}

Fifthweek.Api.Channels.Tests/ChannelSecurityTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,19 @@ public async Task WhenAuthorizingPost_ItShouldAllowIfUserOwnsChannel()
3232
{
3333
this.channelOwnership.Setup(_ => _.IsOwnerAsync(UserId, ChannelId)).ReturnsAsync(true);
3434

35-
var result = await this.target.IsPostingAllowedAsync(UserId, ChannelId);
35+
var result = await this.target.IsWriteAllowedAsync(UserId, ChannelId);
3636

3737
Assert.IsTrue(result);
3838

39-
await this.target.AssertPostingAllowedAsync(UserId, ChannelId);
39+
await this.target.AssertWriteAllowedAsync(UserId, ChannelId);
4040
}
4141

4242
[TestMethod]
4343
public async Task WhenAuthorizingPost_ItShouldForbidIfUserDoesNotOwnChannel()
4444
{
4545
this.channelOwnership.Setup(_ => _.IsOwnerAsync(UserId, ChannelId)).ReturnsAsync(false);
4646

47-
var result = await this.target.IsPostingAllowedAsync(UserId, ChannelId);
47+
var result = await this.target.IsWriteAllowedAsync(UserId, ChannelId);
4848

4949
Assert.IsFalse(result);
5050

@@ -56,7 +56,7 @@ public async Task WhenAuthorizingPost_ItShouldForbidIfUserDoesNotOwnChannel2()
5656
{
5757
this.channelOwnership.Setup(_ => _.IsOwnerAsync(UserId, ChannelId)).ReturnsAsync(false);
5858

59-
await this.target.AssertPostingAllowedAsync(UserId, ChannelId);
59+
await this.target.AssertWriteAllowedAsync(UserId, ChannelId);
6060
}
6161
}
6262
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
namespace Fifthweek.Api.Channels.Tests.Commands
2+
{
3+
using System;
4+
using System.Threading.Tasks;
5+
6+
using Fifthweek.Api.Channels.Commands;
7+
using Fifthweek.Api.Channels.Shared;
8+
using Fifthweek.Api.Core;
9+
using Fifthweek.Api.Identity.Shared.Membership;
10+
using Fifthweek.Api.Identity.Tests.Shared.Membership;
11+
using Fifthweek.Api.Persistence;
12+
using Fifthweek.Api.Persistence.Tests.Shared;
13+
using Fifthweek.Api.Subscriptions.Shared;
14+
15+
using Microsoft.VisualStudio.TestTools.UnitTesting;
16+
17+
using Moq;
18+
19+
[TestClass]
20+
public class CreateChannelCommandHandlerTests : PersistenceTestsBase
21+
{
22+
private static readonly UserId UserId = new UserId(Guid.NewGuid());
23+
private static readonly Requester Requester = Requester.Authenticated(UserId);
24+
private static readonly ChannelId ChannelId = new ChannelId(Guid.NewGuid());
25+
private static readonly SubscriptionId SubscriptionId = new SubscriptionId(Guid.NewGuid());
26+
private static readonly ValidChannelName Name = ValidChannelName.Parse("Bat puns");
27+
private static readonly ValidChannelPriceInUsCentsPerWeek Price = ValidChannelPriceInUsCentsPerWeek.Parse(10);
28+
private static readonly CreateChannelCommand Command = new CreateChannelCommand(Requester, ChannelId, SubscriptionId, Name, Price);
29+
30+
private Mock<IRequesterSecurity> requesterSecurity;
31+
private Mock<ISubscriptionSecurity> subscriptionSecurity;
32+
private Mock<IFifthweekDbContext> databaseContext;
33+
private CreateChannelCommandHandler target;
34+
35+
[TestInitialize]
36+
public void Initialize()
37+
{
38+
this.requesterSecurity = new Mock<IRequesterSecurity>();
39+
this.requesterSecurity.SetupFor(Requester);
40+
this.subscriptionSecurity = new Mock<ISubscriptionSecurity>();
41+
42+
// Give potentially side-effective components strict mock behaviour.
43+
this.databaseContext = new Mock<IFifthweekDbContext>(MockBehavior.Strict);
44+
45+
this.InitializeTarget(this.databaseContext.Object);
46+
}
47+
48+
public void InitializeTarget(IFifthweekDbContext databaseContext)
49+
{
50+
this.target = new CreateChannelCommandHandler(this.requesterSecurity.Object, this.subscriptionSecurity.Object, databaseContext);
51+
}
52+
53+
[TestMethod]
54+
[ExpectedException(typeof(ArgumentNullException))]
55+
public async Task ItShouldRequireCommand()
56+
{
57+
await this.target.HandleAsync(null);
58+
}
59+
60+
[TestMethod]
61+
[ExpectedException(typeof(UnauthorizedException))]
62+
public async Task ItShouldRequireUserIsAuthenticated()
63+
{
64+
await this.target.HandleAsync(new CreateChannelCommand(Requester.Unauthenticated, ChannelId, SubscriptionId, Name, Price));
65+
}
66+
67+
[TestMethod]
68+
[ExpectedException(typeof(UnauthorizedException))]
69+
public async Task ItShouldRequireUserHasWriteAccessToSubscription()
70+
{
71+
this.subscriptionSecurity.Setup(_ => _.AssertWriteAllowedAsync(UserId, SubscriptionId)).Throws<UnauthorizedException>();
72+
73+
await this.target.HandleAsync(Command);
74+
}
75+
76+
[TestMethod]
77+
public async Task ItShouldBeIdempotent()
78+
{
79+
await this.DatabaseTestAsync(async testDatabase =>
80+
{
81+
this.InitializeTarget(testDatabase.NewContext());
82+
await this.CreateEntitiesAsync(testDatabase);
83+
await this.target.HandleAsync(Command);
84+
await testDatabase.TakeSnapshotAsync();
85+
86+
await this.target.HandleAsync(Command);
87+
88+
return ExpectedSideEffects.None;
89+
});
90+
}
91+
92+
[TestMethod]
93+
public async Task ItShouldCreateChannel()
94+
{
95+
await this.DatabaseTestAsync(async testDatabase =>
96+
{
97+
this.InitializeTarget(testDatabase.NewContext());
98+
await this.CreateEntitiesAsync(testDatabase);
99+
await testDatabase.TakeSnapshotAsync();
100+
101+
await this.target.HandleAsync(Command);
102+
103+
var expectedChannel = new Channel(
104+
ChannelId.Value,
105+
SubscriptionId.Value,
106+
null,
107+
Name.Value,
108+
Price.Value,
109+
default(DateTime));
110+
111+
return new ExpectedSideEffects
112+
{
113+
Insert = new WildcardEntity<Channel>(expectedChannel)
114+
{
115+
Expected = actual =>
116+
{
117+
expectedChannel.CreationDate = actual.CreationDate;
118+
return expectedChannel;
119+
}
120+
}
121+
};
122+
});
123+
}
124+
125+
private async Task CreateEntitiesAsync(TestDatabaseContext testDatabase)
126+
{
127+
using (var databaseContext = testDatabase.NewContext())
128+
{
129+
await databaseContext.CreateTestSubscriptionAsync(UserId.Value, SubscriptionId.Value);
130+
}
131+
}
132+
}
133+
}

Fifthweek.Api.Channels.Tests/Fifthweek.Api.Channels.Tests.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
<ItemGroup>
8484
<Compile Include="ChannelOwnershipTests.cs" />
8585
<Compile Include="ChannelSecurityTests.cs" />
86+
<Compile Include="Commands\CreateChannelCommandHandlerTests.cs" />
8687
<Compile Include="Controllers\ChannelControllerTests.cs" />
8788
<Compile Include="Properties\AssemblyInfo.cs" />
8889
<Compile Include="ValidChannelNameTests.cs" />
@@ -105,6 +106,10 @@
105106
<Project>{1B07E9CC-1B0E-49D7-96D6-0F8C58A7C0CA}</Project>
106107
<Name>Fifthweek.Api.Identity.Shared</Name>
107108
</ProjectReference>
109+
<ProjectReference Include="..\Fifthweek.Api.Identity.Tests.Shared\Fifthweek.Api.Identity.Tests.Shared.csproj">
110+
<Project>{45E1C6C7-845A-480F-BBBC-5DF3135D1E19}</Project>
111+
<Name>Fifthweek.Api.Identity.Tests.Shared</Name>
112+
</ProjectReference>
108113
<ProjectReference Include="..\Fifthweek.Api.Persistence.Tests.Shared\Fifthweek.Api.Persistence.Tests.Shared.csproj">
109114
<Project>{ECDE2C4E-7DD8-4210-8E09-D5762C88B3CB}</Project>
110115
<Name>Fifthweek.Api.Persistence.Tests.Shared</Name>

0 commit comments

Comments
 (0)