MOPC-App/docs/unified-architecture-redesign/11-testing-and-qa.md

14 KiB
Raw Permalink Blame History

Testing & QA

Overview

This document defines the test strategy for the redesigned competition system. It covers the test pyramid, specific test matrices for the Monaco competition flow and deliberation system, regression coverage, audit verification, and performance scenarios.


Test Pyramid

         ╱╲
         E2E ╲        ~10 tests — Full Monaco flow simulation
       ╱────────╲
      Integration╲    ~50 tests — Service + database interaction
     ╱──────────────╲
       Unit Tests    ╲  ~200+ tests — Pure logic, no I/O
   ╱────────────────────╲
Level What Tools Speed
Unit Pure functions: policy resolution, cap calculation, Borda count, tie-breaking logic, config validation Vitest < 1s each
Integration Service + Prisma: assignment algorithm with real DB, round transitions, deliberation vote aggregation Vitest + test DB < 5s each
E2E Full flow: create competition → run all 8 rounds → lock results Vitest + test DB + tRPC callers < 30s each

Monaco Flow Test Matrix

End-to-end tests that simulate the complete Monaco 2026 competition:

R1: Intake

Test Case Validates
Submit project before deadline Submission accepted, project created with category
Submit project after deadline (HARD policy) Submission rejected
Submit project after deadline (FLAG policy) Submission accepted, marked as Late
Submit with missing required doc Validation error returned
Admin uploads doc for applicant File associated, provenance recorded
Submission window not yet open Submission rejected

R2: AI Filtering

Test Case Validates
AI marks project eligible ProjectRoundState = ELIGIBLE
AI marks project ineligible ProjectRoundState = INELIGIBLE, reason stored
AI marks project for manual review ProjectRoundState = MANUAL_REVIEW
Admin overrides ineligible → eligible Override recorded in audit log
Admin overrides eligible → ineligible Override recorded in audit log
Filtering with no AI (deterministic rules only) Rules-based filtering works without AI

R3: Jury 1 Evaluation

Test Case Validates
Assignment respects hard cap Judge gets ≤ maxProjects
Assignment distributes overflow to soft cap judges Overflow distributed evenly, up to cap + buffer
Unassigned projects enter queue Remaining projects have reason codes
COI declared → project skipped COI judge doesn't get COI project
Judge submits score within window Score recorded
Judge submits score outside window Rejected
AI shortlist generated at round end Ranked recommendations exist per category
Admin overrides shortlist selection Override recorded, selected projects advance

R4: Semifinal Submission

Test Case Validates
Applicant uploads R2 docs Files accepted for correct slots
Applicant tries to edit R1 docs Rejected (read-only)
Admin replaces R1 doc Replacement accepted with provenance
Applicant downloads R1 doc Download succeeds

R5: Jury 2 Evaluation + Special Awards

Test Case Validates
Judge sees R1 + R2 docs clearly separated Both sets of docs returned with round labels
Award Mode A: projects filtered into award pool Eligible projects appear in award pool
Award Mode A: admin confirms pull-out Pull-out confirmed, project moved
Award Mode B: projects flagged but stay in main Project in both main and award evaluation
Award single-judge decision Single judge can select winner
Top N finalists selected (N configurable) Exactly N projects advance per category

R6: Mentoring

Test Case Validates
Mentor sends message to team Message delivered and visible
Team uploads file to workspace File visible to mentor + team + admin
Mentor comments on file Comment created with thread support
File promoted to official submission SubmissionPromotionEvent created with provenance
Non-mentored finalist has no workspace No workspace features visible

R7: Live Finals

Test Case Validates
Admin advances project cursor Current project updates for all Jury 3 members
Jury 3 member submits live score Score recorded within voting window
Audience vote submitted Vote counted
Audience vote outside window Rejected
Jury 3 sees prior jury data (when enabled) Prior jury scores/feedback visible
Jury 3 doesn't see prior jury data (when disabled) Prior jury data hidden

R8: Deliberation

Test Case Validates
SINGLE_WINNER_VOTE: juror picks winner Vote recorded
SINGLE_WINNER_VOTE: tally produces ranking Most votes = rank 1, others by vote count
FULL_RANKING: juror submits ordinal ranks All ranks recorded
FULL_RANKING: Borda count aggregation Correct Borda scores computed
Tie detected → runoff initiated Session status = RUNOFF, new vote round
Tie detected → admin breaks tie Admin decision recorded
Admin overrides entire result Override result stored, flag set
Admin locks result ResultLock created with snapshot
Super-admin unlocks result ResultUnlockEvent created with reason
Non-super-admin tries to unlock Rejected

Deliberation-Specific Test Matrix

Detailed tests for the deliberation subsystem:

SINGLE_WINNER_VOTE Mode

Scenario Expected Outcome
3 jurors, unanimous pick Winner = picked project, rank 1
3 jurors, split vote (2-1) Winner = project with 2 votes
3 jurors, three-way tie (1-1-1) Tie-break method triggered
Juror submits vote, then changes Latest vote overwrites (within window)
Voting window closes No more votes accepted

FULL_RANKING Mode

Scenario Expected Outcome
3 jurors rank 5 projects Borda count: 1st=5pts, 2nd=4pts, ... 5th=1pt
Clear winner (highest Borda) Rank 1 = highest Borda score
Two projects tied on Borda Tie-break method triggered
Juror ranks only top 3 (partial) Unranked projects get 0 points from this juror

Tie-Breaking

Method Test Case Expected
RUNOFF Two projects tied → runoff vote New voting round with only tied projects
RUNOFF Runoff still tied → second runoff Session supports multiple runoff rounds
ADMIN_DECIDES Tie detected → admin picks winner Admin decision recorded, tie resolved
SCORE_FALLBACK Tie → fall back to Jury 3 scores Higher Jury 3 score wins

Participant Management

Scenario Expected
Juror marked ABSENT_EXCUSED Doesn't count toward quorum
Juror REPLACED Replacement can vote, original cannot
Quorum not met Session cannot proceed to VOTING

Invite/Onboarding Test Matrix

Test Case Validates
Admin invites judge to Jury 1 JuryGroupMember created
Judge accepts invite and onboards Member status active, self-service values set
Judge adjusts cap during onboarding selfServiceCap updated
Judge adjusts ratio during onboarding selfServiceBias updated
Self-service disabled → fields hidden No self-service options shown
Admin overrides self-service values Override takes precedence
Invite with pre-assignment intent AssignmentIntent record created
Invite with pre-assignment + COI conflict Intent stays PENDING, reason INTENT_BLOCKED
Judge removed from group with pending intent Intent status → CANCELLED
Round completes with unmatched intent Intent status → EXPIRED

Config Schema Validation Test Matrix

All 7 round-type Zod schemas must validate both correct and malformed input:

Schema Valid Input Test Invalid Input Test
IntakeConfigSchema Valid with deadlinePolicy: "FLAG", maxFileSizeMB: 50 Rejects negative maxFileSizeMB, unknown deadlinePolicy
FilteringConfigSchema Valid with aiEnabled: true, autoAdvanceEligible: false Rejects autoAdvanceEligible without aiEnabled set
EvaluationConfigSchema Valid with rubric, requireFeedback: true, feedbackMinLength: 50 Rejects feedbackMinLength < 0, missing rubric
SubmissionConfigSchema Valid with doc slot requirements, deadlinePolicy: "HARD" Rejects empty requiredDocSlots, invalid MIME types
MentoringConfigSchema Valid with eligibility: "all_advancing", filePromotionEnabled: true Rejects unknown eligibility value
LiveFinalConfigSchema Valid with audienceVoteWeight: 0.3, presentationDurationMinutes: 15 Rejects audienceVoteWeight > 1, negative duration
DeliberationConfigSchema Valid with mode: "FULL_RANKING", topN: 3, tieBreakMethod: "RUNOFF" Rejects topN < 1, unknown mode

Edge cases (all schemas):

Test Case Expected
Empty object Defaults applied, valid
Extra unknown fields Stripped (Zod .strip())
null config Rejected — config is required
Partial config (some fields) Missing fields get defaults

Assignment Intent Lifecycle Tests

Test Case Expected
Create intent at invite time AssignmentIntent created with status PENDING, source INVITE
Algorithm runs, intent matchable Intent transitions to HONORED, Assignment record created
Algorithm runs, intent blocked by COI Intent stays PENDING, unassigned queue entry with INTENT_BLOCKED
Algorithm runs, intent blocked by cap Intent stays PENDING, unassigned queue entry with INTENT_BLOCKED
Admin reassigns project to different judge Intent transitions to OVERRIDDEN, audit logged
Judge removed from JuryGroup Intent transitions to CANCELLED
Round closes with pending intent Intent transitions to EXPIRED (batch update)
Admin explicitly cancels intent Intent transitions to CANCELLED
Intent already HONORED → no further transitions Terminal state enforced
Intent already EXPIRED → no further transitions Terminal state enforced

Regression Coverage

Existing test files that need updates for the redesign:

Test File Required Updates
tests/unit/stage-engine.test.ts Rename to round-engine, update all type references
tests/unit/stage-filtering.test.ts Update to use RoundType.FILTERING, Competition model
tests/unit/stage-assignment.test.ts Add JuryGroup-based assignment tests, cap mode tests
tests/integration/pipeline-crud.test.ts Rename to competition-crud, test Competition/Round CRUD
tests/integration/evaluation-flow.test.ts Update to use Round model, multi-round doc visibility

New test files to create:

Test File Coverage
tests/unit/policy-resolution.test.ts 5-layer policy precedence
tests/unit/borda-count.test.ts Borda count aggregation
tests/unit/deliberation-tally.test.ts Vote tallying for both modes
tests/unit/config-validation.test.ts All 7 Zod schema validations
tests/integration/deliberation-flow.test.ts Full deliberation lifecycle
tests/integration/mentor-workspace.test.ts Messaging, files, comments, promotion
tests/integration/submission-windows.test.ts Multi-round doc lifecycle
tests/e2e/monaco-full-flow.test.ts Complete 8-round simulation

Audit Completeness Checks

Every critical operation must emit an audit record to DecisionAuditLog:

Operation Must Audit
Admin overrides eligibility Yes — who, when, project, old→new status
Admin overrides assignment Yes — who, when, judge, project, reason
Admin overrides shortlist selection Yes — who, when, selected projects
Admin overrides deliberation result Yes — who, when, reason, original→override
Result lock Yes — who, when, snapshot
Result unlock Yes — who, when, reason (super-admin only)
File promotion Yes — who, when, source file, target slot
Award pull-out confirmation Yes — who, when, projects pulled
Judge cap/ratio override Yes — who, when, old→new values

Test: For each operation above, verify an audit record exists with all required fields.


Performance & Capacity Scenarios

Scenario Target Test Method
Bulk intake: 500 projects submitted in 1 hour All submissions processed, no timeouts Load test with concurrent submissions
Assignment: 500 projects across 30 judges Assignment completes < 30s Integration test with timing
Live voting: 50 audience members voting simultaneously All votes recorded, no conflicts Concurrent request test
Deliberation: 15 jurors submitting rankings Aggregation completes < 5s Integration test with timing
Multi-round doc query: judge views 3 rounds of docs Response < 2s Query performance test

Test Data Factories

All tests use factory functions (see tests/helpers.ts):

// New factories needed
createTestCompetition(overrides?)
createTestRound(competitionId, type, overrides?)
createTestJuryGroup(competitionId, overrides?)
createTestJuryGroupMember(juryGroupId, userId, overrides?)
createTestSubmissionWindow(competitionId, roundId, overrides?)
createTestDeliberationSession(competitionId, roundId, overrides?)
createTestDeliberationVote(sessionId, juryMemberId, overrides?)
createTestSpecialAward(competitionId, overrides?)
createTestMentorFile(projectId, mentorId, overrides?)

See 09-implementation-roadmap.md Phase 0 for factory creation timeline.