Skip to content

Commit

Permalink
Merge pull request #63 from TrueVote/AD-124
Browse files Browse the repository at this point in the history
AD-124 Adds check to determine if user already submitted ballot for an election
  • Loading branch information
morrisonbrett authored Sep 4, 2024
2 parents 75f77a5 + ae666b5 commit d200564
Show file tree
Hide file tree
Showing 16 changed files with 168 additions and 66 deletions.
38 changes: 22 additions & 16 deletions TrueVote.Api.Tests/Helpers/TestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,27 @@ public class TestHelper

public const string MockedTokenValue = "mocked_token_value";

public static ControllerContext AuthHelper(string userId)
{
// For endpoints that require Authorization [Authorize]
// Mock a user principal with desired claims
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, userId),
new Claim(ClaimTypes.Role, "User"), // Role
};
var identity = new ClaimsIdentity(claims, "TestAuthentication");
var principal = new ClaimsPrincipal(identity);

// Create context for controllers to attach to
var authControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext { User = principal }
};

return authControllerContext;
}

public TestHelper(ITestOutputHelper output)
{
// This will override the setup shims in Startup.cs
Expand Down Expand Up @@ -92,22 +113,7 @@ public TestHelper(ITestOutputHelper output)
_candidateApi = new Candidate(_logHelper.Object, _moqDataAccessor.mockCandidateContext.Object, _mockServiceBus.Object);
_timestampApi = new Timestamp(_logHelper.Object, _moqDataAccessor.mockTimestampContext.Object);
_queryService = new Query(_trueVoteDbContext);

// For endpoints that require Authorization [Authorize]
// Mock a user principal with desired claims
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, MoqData.MockUserData[0].UserId),
new Claim(ClaimTypes.Role, "User"), // Role
};
var identity = new ClaimsIdentity(claims, "TestAuthentication");
var principal = new ClaimsPrincipal(identity);

// Create context for controllers to attach to
_authControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext { User = principal }
};
_authControllerContext = AuthHelper(MoqData.MockUserData[0].UserId);
}
}
}
30 changes: 18 additions & 12 deletions TrueVote.Api.Tests/MoqData.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using MockQueryable.Moq;
using Moq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using TrueVote.Api.Interfaces;
using TrueVote.Api.Models;
Expand Down Expand Up @@ -96,8 +98,13 @@ public static class MoqData

public static List<UsedAccessCodeModel> MockUsedAccessCodeData => new()
{
new UsedAccessCodeModel { AccessCode = "accesscode1" },
new UsedAccessCodeModel { AccessCode = "accesscode2" },
new UsedAccessCodeModel { AccessCode = "accesscode1", DateCreated = createDate.Date },
new UsedAccessCodeModel { AccessCode = "accesscode2", DateCreated = createDate.Date },
};

public static List<ElectionUserBindingModel> MockElectionUserBindingsData => new()
{
new ElectionUserBindingModel { UserId = MockUserData[2].UserId, ElectionId = MockElectionData[0].ElectionId, DateCreated = createDate.Date },
};

public static BallotList MockBallotList => new()
Expand All @@ -119,6 +126,7 @@ public class MoqDataAccessor
public readonly Mock<MoqTrueVoteDbContext> mockFeedbacksContext;
public readonly Mock<MoqTrueVoteDbContext> mockElectionAccessCodeContext;
public readonly Mock<MoqTrueVoteDbContext> mockUsedAccessCodeContext;
public readonly Mock<MoqTrueVoteDbContext> mockElectionUserBindingsContext;

public Mock<DbSet<UserModel>> MockUserSet { get; private set; }
public Mock<DbSet<RaceModel>> MockRaceSet { get; private set; }
Expand All @@ -130,6 +138,7 @@ public class MoqDataAccessor
public Mock<DbSet<FeedbackModel>> MockFeedbackSet { get; private set; }
public Mock<DbSet<AccessCodeModel>> MockElectionAccessCodeSet { get; private set; }
public Mock<DbSet<UsedAccessCodeModel>> MockUsedAccessCodeSet { get; private set; }
public Mock<DbSet<ElectionUserBindingModel>> MockElectionUserBindingsSet { get; private set; }

// https://docs.microsoft.com/en-us/ef/ef6/fundamentals/testing/mocking?redirectedfrom=MSDN
// https://github.com/romantitov/MockQueryable
Expand All @@ -145,27 +154,24 @@ public MoqDataAccessor()
MockFeedbackSet = MoqData.MockFeedbackData.AsQueryable().BuildMockDbSet();
MockElectionAccessCodeSet = MoqData.MockElectionAccessCodeData.AsQueryable().BuildMockDbSet();
MockUsedAccessCodeSet = MoqData.MockUsedAccessCodeData.AsQueryable().BuildMockDbSet();
MockElectionUserBindingsSet = MoqData.MockElectionUserBindingsData.AsQueryable().BuildMockDbSet();

mockUserContext = new Mock<MoqTrueVoteDbContext>();
mockUserContext.Setup(m => m.Feedbacks).Returns(MockFeedbackSet.Object);
mockUserContext.Setup(m => m.Users).Returns(MockUserSet.Object);
mockUserContext.Setup(m => m.EnsureCreatedAsync()).Returns(Task.FromResult(true));

mockElectionContext = new Mock<MoqTrueVoteDbContext>();
mockElectionContext.Setup(m => m.Elections).Returns(MockElectionSet.Object);
mockElectionContext.Setup(m => m.Races).Returns(MockRaceSet.Object);
mockElectionContext.Setup(m => m.Users).Returns(MockUserSet.Object);
mockElectionContext.Setup(m => m.ElectionAccessCodes).Returns(MockElectionAccessCodeSet.Object);
mockElectionContext.Setup(m => m.UsedAccessCodes).Returns(MockUsedAccessCodeSet.Object);
mockElectionContext.Setup(m => m.EnsureCreatedAsync()).Returns(Task.FromResult(true));

mockTimestampContext = new Mock<MoqTrueVoteDbContext>();
mockTimestampContext.Setup(m => m.Timestamps).Returns(MockTimestampSet.Object);
mockTimestampContext.Setup(m => m.EnsureCreatedAsync()).Returns(Task.FromResult(true));

mockBallotHashContext = new Mock<MoqTrueVoteDbContext>();
mockBallotHashContext.Setup(m => m.BallotHashes).Returns(MockBallotHashSet.Object);
mockBallotHashContext.Setup(m => m.EnsureCreatedAsync()).Returns(Task.FromResult(true));

mockBallotContext = new Mock<MoqTrueVoteDbContext>();
mockBallotContext.Setup(m => m.Elections).Returns(MockElectionSet.Object);
Expand All @@ -176,28 +182,26 @@ public MoqDataAccessor()
mockBallotContext.Setup(m => m.Timestamps).Returns(MockTimestampSet.Object);
mockBallotContext.Setup(m => m.ElectionAccessCodes).Returns(MockElectionAccessCodeSet.Object);
mockBallotContext.Setup(m => m.UsedAccessCodes).Returns(MockUsedAccessCodeSet.Object);
mockBallotContext.Setup(m => m.EnsureCreatedAsync()).Returns(Task.FromResult(true));
mockBallotContext.Setup(m => m.ElectionUserBindings).Returns(MockElectionUserBindingsSet.Object);

mockCandidateContext = new Mock<MoqTrueVoteDbContext>();
mockCandidateContext.Setup(m => m.Candidates).Returns(MockCandidateSet.Object);
mockCandidateContext.Setup(m => m.EnsureCreatedAsync()).Returns(Task.FromResult(true));

mockRaceContext = new Mock<MoqTrueVoteDbContext>();
mockRaceContext.Setup(m => m.Candidates).Returns(MockCandidateSet.Object);
mockRaceContext.Setup(m => m.Races).Returns(MockRaceSet.Object);
mockRaceContext.Setup(m => m.EnsureCreatedAsync()).Returns(Task.FromResult(true));

mockFeedbacksContext = new Mock<MoqTrueVoteDbContext>();
mockFeedbacksContext.Setup(m => m.Feedbacks).Returns(MockFeedbackSet.Object);
mockFeedbacksContext.Setup(m => m.EnsureCreatedAsync()).Returns(Task.FromResult(true));

mockElectionAccessCodeContext = new Mock<MoqTrueVoteDbContext>();
mockElectionAccessCodeContext.Setup(m => m.ElectionAccessCodes).Returns(MockElectionAccessCodeSet.Object);
mockElectionAccessCodeContext.Setup(m => m.EnsureCreatedAsync()).Returns(Task.FromResult(true));

mockUsedAccessCodeContext = new Mock<MoqTrueVoteDbContext>();
mockUsedAccessCodeContext.Setup(m => m.UsedAccessCodes).Returns(MockUsedAccessCodeSet.Object);
mockUsedAccessCodeContext.Setup(m => m.EnsureCreatedAsync()).Returns(Task.FromResult(true));

mockElectionUserBindingsContext = new Mock<MoqTrueVoteDbContext>();
mockElectionUserBindingsContext.Setup(m => m.ElectionUserBindings).Returns(MockElectionUserBindingsSet.Object);

// Leaving commented code. This is for Mocking UTC time. Helpful for test consistency.
// var mockUtcNowProvider = new Mock<IUtcNowProvider>();
Expand All @@ -219,6 +223,7 @@ public class MoqTrueVoteDbContext : DbContext, ITrueVoteDbContext
public virtual DbSet<FeedbackModel> Feedbacks { get; set; }
public virtual DbSet<AccessCodeModel> ElectionAccessCodes { get; set; }
public virtual DbSet<UsedAccessCodeModel> UsedAccessCodes { get; set; }
public virtual DbSet<ElectionUserBindingModel> ElectionUserBindings { get; set; }

protected MoqDataAccessor _moqDataAccessor;

Expand All @@ -236,6 +241,7 @@ public MoqTrueVoteDbContext()
Feedbacks = _moqDataAccessor.MockFeedbackSet.Object;
ElectionAccessCodes = _moqDataAccessor.MockElectionAccessCodeSet.Object;
UsedAccessCodes = _moqDataAccessor.MockUsedAccessCodeSet.Object;
ElectionUserBindings = _moqDataAccessor.MockElectionUserBindingsSet.Object;
}

public virtual async Task<bool> EnsureCreatedAsync()
Expand Down
26 changes: 26 additions & 0 deletions TrueVote.Api.Tests/ServiceTests/BallotTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public async Task SubmitsBallot()
{
var baseBallotObj = new SubmitBallotModel { AccessCode = MoqData.MockElectionAccessCodeData[0].AccessCode, Election = MoqData.MockBallotData[1].Election };

_ballotApi.ControllerContext = _authControllerContext;
var ret = await _ballotApi.SubmitBallot(baseBallotObj);
Assert.NotNull(ret);
Assert.Equal(StatusCodes.Status201Created, ((IStatusCodeActionResult) ret).StatusCode);
Expand Down Expand Up @@ -113,6 +114,8 @@ public async Task HandlesSubmitBallotHashingError()
mockValidator.Setup(m => m.HashBallotAsync(It.IsAny<BallotModel>())).Throws(new Exception("Hash Ballot Exception"));

var ballotApi = new Ballot(_logHelper.Object, _moqDataAccessor.mockBallotContext.Object, mockValidator.Object, _mockServiceBus.Object, _mockRecursiveValidator.Object);

ballotApi.ControllerContext = _authControllerContext;
var ret = await ballotApi.SubmitBallot(baseBallotObj);
Assert.NotNull(ret);
Assert.Equal(StatusCodes.Status409Conflict, ((IStatusCodeActionResult) ret).StatusCode);
Expand Down Expand Up @@ -216,9 +219,12 @@ public async Task HandlesSubmitBallotDatabaseError()
mockBallotContext.Setup(m => m.Ballots).Returns(MockBallotSet.Object);
mockBallotContext.Setup(m => m.ElectionAccessCodes).Returns(_moqDataAccessor.MockElectionAccessCodeSet.Object);
mockBallotContext.Setup(m => m.UsedAccessCodes).Returns(_moqDataAccessor.MockUsedAccessCodeSet.Object);
mockBallotContext.Setup(m => m.ElectionUserBindings).Returns(_moqDataAccessor.MockElectionUserBindingsSet.Object);
mockBallotContext.Setup(m => m.SaveChangesAsync()).Throws(new Exception("DB Saving Changes Exception"));

var ballotApi = new Ballot(_logHelper.Object, mockBallotContext.Object, _validatorApi, _mockServiceBus.Object, _mockRecursiveValidator.Object);

ballotApi.ControllerContext = _authControllerContext;
var ret = await ballotApi.SubmitBallot(baseBallotObj);
Assert.NotNull(ret);
Assert.Equal(StatusCodes.Status409Conflict, ((IStatusCodeActionResult) ret).StatusCode);
Expand All @@ -236,6 +242,7 @@ public async Task HandlesSubmitBallotUnfoundAccessCode()
{
var baseBallotObj = new SubmitBallotModel { AccessCode = "blah", Election = MoqData.MockBallotData[1].Election };

_ballotApi.ControllerContext = _authControllerContext;
var ret = await _ballotApi.SubmitBallot(baseBallotObj);
Assert.NotNull(ret);
Assert.Equal(StatusCodes.Status404NotFound, ((IStatusCodeActionResult) ret).StatusCode);
Expand All @@ -254,6 +261,7 @@ public async Task HandlesSubmitBallotAlreadyUsedAccessCode()
{
var baseBallotObj = new SubmitBallotModel { AccessCode = MoqData.MockUsedAccessCodeData[0].AccessCode, Election = MoqData.MockBallotData[1].Election };

_ballotApi.ControllerContext = _authControllerContext;
var ret = await _ballotApi.SubmitBallot(baseBallotObj);
Assert.NotNull(ret);
Assert.Equal(StatusCodes.Status409Conflict, ((IStatusCodeActionResult) ret).StatusCode);
Expand All @@ -266,5 +274,23 @@ public async Task HandlesSubmitBallotAlreadyUsedAccessCode()
_logHelper.Verify(LogLevel.Information, Times.Exactly(1));
_logHelper.Verify(LogLevel.Debug, Times.Exactly(2));
}

[Fact]
public async Task HandlesSubmitBallotUserAlreadySubmitted()
{
var baseBallotObj = new SubmitBallotModel { AccessCode = MoqData.MockUsedAccessCodeData[0].AccessCode, Election = MoqData.MockBallotData[1].Election };

_ballotApi.ControllerContext = AuthHelper(MoqData.MockUserData[2].UserId);
var ret = await _ballotApi.SubmitBallot(baseBallotObj);
Assert.NotNull(ret);
Assert.Equal(StatusCodes.Status409Conflict, ((IStatusCodeActionResult) ret).StatusCode);

var val = (SecureString) (ret as ConflictObjectResult).Value;
Assert.NotNull(val);
Assert.Contains("Ballot already submitted", val.Value);

_logHelper.Verify(LogLevel.Information, Times.Exactly(1));
_logHelper.Verify(LogLevel.Debug, Times.Exactly(2));
}
}
}
1 change: 0 additions & 1 deletion TrueVote.Api.Tests/ServiceTests/UserTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,6 @@ public async Task SignInSuccessFoundUser()
var mockUserDataCollection = mockUserData;
var MockUserSet = DbMoqHelper.GetDbSet(mockUserDataQueryable);
mockUserContext.Setup(m => m.Users).Returns(MockUserSet.Object);
mockUserContext.Setup(m => m.EnsureCreatedAsync()).Returns(Task.FromResult(true));

// Simulate a client (e.g. TypeScript)
var content = new BaseUserModel
Expand Down
29 changes: 23 additions & 6 deletions TrueVote.Api.Tests/ServiceTests/ValidatorTest.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using Moq;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using TrueVote.Api.Models;
using TrueVote.Api.Services;
using TrueVote.Api.Tests.Helpers;
using Xunit;
Expand Down Expand Up @@ -48,11 +50,14 @@ public async Task HashesBallotThrowsStoreTimestampException()
var mockBallotContext = new Mock<MoqTrueVoteDbContext>();
var mockBallotDataQueryable = MoqData.MockBallotData.AsQueryable();
var mockBallotHashDataQueryable = MoqData.MockBallotHashData.AsQueryable();
var mockTimestampsDataQueryable = MoqData.MockTimestampData.AsQueryable();
var MockBallotSet = DbMoqHelper.GetDbSet(mockBallotDataQueryable);
var MockBallotHashSet = DbMoqHelper.GetDbSet(mockBallotHashDataQueryable);
var MockTimestampsSet = DbMoqHelper.GetDbSet(mockTimestampsDataQueryable);
mockBallotContext.Setup(m => m.Ballots).Returns(MockBallotSet.Object);
mockBallotContext.Setup(m => m.BallotHashes).Returns(MockBallotHashSet.Object);
mockBallotContext.Setup(m => m.EnsureCreatedAsync()).Throws(new Exception("Storing data exception"));
mockBallotContext.Setup(m => m.Timestamps).Returns(MockTimestampsSet.Object);
mockBallotContext.Setup(m => m.SaveChangesAsync()).Throws(new Exception("Storing data exception"));

var validatorApi = new BallotValidator(_logHelper.Object, mockBallotContext.Object, _mockOpenTimestampsClient.Object, _mockServiceBus.Object);

Expand Down Expand Up @@ -103,10 +108,16 @@ public async Task HashesBallot()
public async Task StoreBallotHashAsyncThrowsException()
{
var mockBallotHashContext = new Mock<MoqTrueVoteDbContext>();
var mockBallotDataQueryable = MoqData.MockBallotData.AsQueryable();
var mockBallotHashDataQueryable = MoqData.MockBallotHashData.AsQueryable();
var mockTimestampsDataQueryable = MoqData.MockTimestampData.AsQueryable();
var MockBallotSet = DbMoqHelper.GetDbSet(mockBallotDataQueryable);
var MockBallotHashSet = DbMoqHelper.GetDbSet(mockBallotHashDataQueryable);
var MockTimestampsSet = DbMoqHelper.GetDbSet(mockTimestampsDataQueryable);
mockBallotHashContext.Setup(m => m.Ballots).Returns(MockBallotSet.Object);
mockBallotHashContext.Setup(m => m.BallotHashes).Returns(MockBallotHashSet.Object);
mockBallotHashContext.Setup(m => m.EnsureCreatedAsync()).Throws(new Exception("Storing data exception"));
mockBallotHashContext.Setup(m => m.Timestamps).Returns(MockTimestampsSet.Object);
mockBallotHashContext.Setup(m => m.BallotHashes.AddAsync(It.IsAny<BallotHashModel>(), It.IsAny<CancellationToken>())).Throws(new Exception("Storing data exception"));

var validatorApi = new BallotValidator(_logHelper.Object, mockBallotHashContext.Object, _mockOpenTimestampsClient.Object, _mockServiceBus.Object);

Expand All @@ -127,10 +138,16 @@ public async Task StoreBallotHashAsyncThrowsException()
public async Task StoreTimestampAsyncThrowsException()
{
var mockTimestampContext = new Mock<MoqTrueVoteDbContext>();
var mockTimestampDataQueryable = MoqData.MockTimestampData.AsQueryable();
var MockTimestampSet = DbMoqHelper.GetDbSet(mockTimestampDataQueryable);
mockTimestampContext.Setup(m => m.Timestamps).Returns(MockTimestampSet.Object);
mockTimestampContext.Setup(m => m.EnsureCreatedAsync()).Throws(new Exception("Storing data exception"));
var mockBallotDataQueryable = MoqData.MockBallotData.AsQueryable();
var mockBallotHashDataQueryable = MoqData.MockBallotHashData.AsQueryable();
var mockTimestampsDataQueryable = MoqData.MockTimestampData.AsQueryable();
var MockBallotSet = DbMoqHelper.GetDbSet(mockBallotDataQueryable);
var MockBallotHashSet = DbMoqHelper.GetDbSet(mockBallotHashDataQueryable);
var MockTimestampsSet = DbMoqHelper.GetDbSet(mockTimestampsDataQueryable);
mockTimestampContext.Setup(m => m.Ballots).Returns(MockBallotSet.Object);
mockTimestampContext.Setup(m => m.BallotHashes).Returns(MockBallotHashSet.Object);
mockTimestampContext.Setup(m => m.Timestamps).Returns(MockTimestampsSet.Object);
mockTimestampContext.Setup(m => m.Timestamps.AddAsync(It.IsAny<TimestampModel>(), It.IsAny<CancellationToken>())).Throws(new Exception("Storing data exception"));

var validatorApi = new BallotValidator(_logHelper.Object, mockTimestampContext.Object, _mockOpenTimestampsClient.Object, _mockServiceBus.Object);

Expand Down
4 changes: 2 additions & 2 deletions TrueVote.Api.Tests/TrueVote.Api.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
<PackageReference Include="EntityFramework" Version="6.5.1" />
<PackageReference Include="GraphQL.Client" Version="6.1.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.0" />
<PackageReference Include="MockQueryable.Moq" Version="7.0.2" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="MockQueryable.Moq" Version="7.0.3" />
<PackageReference Include="Moq" Version="4.20.71" />
<PackageReference Include="xunit" Version="2.9.0" />
<PackageReference Include="xunit.extensibility.execution" Version="2.9.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
Expand Down
Loading

0 comments on commit d200564

Please sign in to comment.