投票发布功能
This commit is contained in:
1 parent
ae9e8e0c9d
commit
1e0652211b
24 files changed
+3633
-7
No files matched your search
@@ -5,6 +5,10 @@ using ElectionSystem.Api.Modules.Identity.Domain;
|
||||
using ElectionSystem.Api.Modules.Organization;
|
||||
using ElectionSystem.Api.Modules.Organization.Domain;
|
||||
using ElectionSystem.Api.Modules.Voters.Domain;
|
||||
using ElectionSystem.Api.Modules.Election.Domain;
|
||||
using ElectionSystem.Api.Modules.OfflineCounting.Domain;
|
||||
using ElectionSystem.Api.Modules.OfflineCounting;
|
||||
using ElectionSystem.Api.Modules.Results;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -100,6 +104,64 @@ public sealed class PersistenceModelTests
|
||||
Assert.False(CursorToken.TryDecode<TestCursor>("not-a-cursor", out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Offline_submissions_enforce_one_entry_per_clerk_and_do_not_store_voters()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<ElectionDbContext>().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options;
|
||||
using var context = new ElectionDbContext(options);
|
||||
var entity = context.Model.FindEntityType(typeof(OfflineCountSubmission))!;
|
||||
Assert.Contains(entity.GetIndexes(), x => x.IsUnique && x.Properties.Select(p => p.Name).SequenceEqual(new[] { nameof(OfflineCountSubmission.OfflineCountingSessionId), nameof(OfflineCountSubmission.EnteredByUserId) }));
|
||||
Assert.DoesNotContain(typeof(OfflineCountSubmission).GetProperties(), x => x.Name.Contains("Voter", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.DoesNotContain(typeof(OfflineCountTally).GetProperties(), x => x.Name.Contains("Voter", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Result_service_counts_confirmed_versions_only_and_keeps_abstention_invalid_and_missing_separate()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<ElectionDbContext>().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options;
|
||||
await using var context = new ElectionDbContext(options);
|
||||
var region = Region("11", "省", AdministrativeRegionLevel.Province);
|
||||
var election = new ElectionActivity { Name = "测试投票", RegionId = region.Id, Status = ElectionActivityStatus.Counting, VotingStartsAtUtc = DateTime.UtcNow.AddHours(-2), VotingEndsAtUtc = DateTime.UtcNow.AddHours(-1), CountingEndsAtUtc = DateTime.UtcNow.AddHours(1), CreatedByUserId = Guid.NewGuid() };
|
||||
var contest = new ElectionContest { ElectionActivityId = election.Id, Title = "主任", Type = ElectionContestType.CandidateElection, SortOrder = 1, Seats = 1, MaxSelections = 1 };
|
||||
var a = new ElectionOption { ElectionContestId = contest.Id, Label = "甲", SortOrder = 1 };
|
||||
var b = new ElectionOption { ElectionContestId = contest.Id, Label = "乙", SortOrder = 2 };
|
||||
var confirmed = new OfflineCountingSession { ElectionActivityId = election.Id, CountingStationId = Guid.NewGuid(), Status = OfflineCountingSessionStatus.Confirmed, OpenedByUserId = Guid.NewGuid() };
|
||||
var unconfirmed = new OfflineCountingSession { ElectionActivityId = election.Id, CountingStationId = Guid.NewGuid(), Status = OfflineCountingSessionStatus.NeedsReview, OpenedByUserId = Guid.NewGuid() };
|
||||
var good = new OfflineCountSubmission { OfflineCountingSessionId = confirmed.Id, EnteredByUserId = Guid.NewGuid() };
|
||||
var duplicateVerifiedEntry = new OfflineCountSubmission { OfflineCountingSessionId = confirmed.Id, EnteredByUserId = Guid.NewGuid(), SubmittedAtUtc = good.SubmittedAtUtc.AddSeconds(1) };
|
||||
var pending = new OfflineCountSubmission { OfflineCountingSessionId = unconfirmed.Id, EnteredByUserId = Guid.NewGuid() };
|
||||
context.AddRange(region, election, contest, a, b, confirmed, unconfirmed, good, duplicateVerifiedEntry, pending);
|
||||
context.AddRange(
|
||||
new OfflineCountTally { OfflineCountSubmissionId = good.Id, ElectionContestId = contest.Id, ElectionOptionId = a.Id, Votes = 18 },
|
||||
new OfflineCountTally { OfflineCountSubmissionId = good.Id, ElectionContestId = contest.Id, ElectionOptionId = b.Id, Votes = 12 },
|
||||
new OfflineCountTally { OfflineCountSubmissionId = good.Id, ElectionContestId = contest.Id, Abstentions = 3, InvalidBallots = 2, MissingSelections = 1 },
|
||||
new OfflineCountTally { OfflineCountSubmissionId = duplicateVerifiedEntry.Id, ElectionContestId = contest.Id, ElectionOptionId = a.Id, Votes = 18 },
|
||||
new OfflineCountTally { OfflineCountSubmissionId = duplicateVerifiedEntry.Id, ElectionContestId = contest.Id, ElectionOptionId = b.Id, Votes = 12 },
|
||||
new OfflineCountTally { OfflineCountSubmissionId = duplicateVerifiedEntry.Id, ElectionContestId = contest.Id, Abstentions = 3, InvalidBallots = 2, MissingSelections = 1 },
|
||||
new OfflineCountTally { OfflineCountSubmissionId = pending.Id, ElectionContestId = contest.Id, ElectionOptionId = a.Id, Votes = 99 });
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var result = await new ElectionResultService(context).BuildAsync(election.Id, CancellationToken.None);
|
||||
|
||||
var summary = Assert.Single(result.Contests);
|
||||
Assert.Equal(18, summary.Options.Single(x => x.OptionId == a.Id).Votes);
|
||||
Assert.Equal(30, summary.ValidVotes);
|
||||
Assert.Equal(3, summary.Abstentions);
|
||||
Assert.Equal(2, summary.InvalidBallots);
|
||||
Assert.Equal(1, summary.MissingSelections);
|
||||
Assert.Equal(new[] { a.Id }, summary.WinnerOptionIds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Missing_selections_are_calculated_from_counted_ballots_and_required_choices()
|
||||
{
|
||||
var contest = new ElectionContest { ElectionActivityId = Guid.NewGuid(), Title = "两席岗位", Type = ElectionContestType.CandidateElection, SortOrder = 1, Seats = 2, MinSelections = 2, MaxSelections = 2 };
|
||||
|
||||
var missing = OfflineCountingRules.CalculateMissingSelections(contest, countedBallots: 20, optionVotes: 33, abstentions: 2, invalidBallots: 1);
|
||||
|
||||
Assert.Equal(4, missing);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Four_level_filter_returns_selected_subtree_and_rejects_cross_branch_selection()
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user