Files
cannamanage/docs/sprint-11/cannamanage-sprint11-plan.md
T
Patrick Plate 59b785b8ed
Deploy to Production / test (push) Failing after 1s
Deploy to Production / deploy (push) Has been skipped
test(sprint-11): centralize JaCoCo coverage rules and add bank import + finance test coverage
- pom.xml: introduce risk-tiered JaCoCo rules in parent POM
  - bundle: 80% line coverage
  - bankimport/finance packages: 90% (highest precision)
  - api.security: 85%
  - scheduler/notification: 70%
  - exclude entity/enums/dto/config from coverage measurement
  - add Surefire 3.5.2 plugin management
- cannamanage-service/pom.xml: remove obsolete module-local ComplianceService=100% rule
  (subsumed by parent package rules), add explicit jackson-databind dep so
  ByteBuddy can mock AuditService.METADATA_MAPPER
- Add AbstractServiceTest base class for service-layer tests
- Add FinanceServiceTest
- Add bankimport test suite:
  - Mt940ParserTest with malformed input fixtures
    (encoding, overflow, truncated, generic)
  - PaymentMatchingServiceTest with ParsedTransactionBuilder helper
  - CAMT.053 / Sparkasse MT940 sample fixtures
  - XXE attack fixtures (billion-laughs, SSRF, generic)
- docs/sprint-11/: analysis, plan, plan-review, testplan
2026-06-15 21:37:49 +02:00

48 KiB
Raw Blame History

Sprint 11 Implementation Plan — Backend Test Coverage (v3 — Easy Targets Added)

Date: 2026-06-15 Sprint Theme: Quality Foundation — Backend Test Coverage Author: Patrick Plate / Roo (Architect) Status: Draft v2 (Expanded targets: 80% coverage, ~250 new tests) Basis: cannamanage-sprint11-analysis.md


Phase 1: Test Infrastructure Setup

1.1 Add JaCoCo Maven Plugin

File: pom.xml (parent)

Add the JaCoCo plugin to the <build><plugins> section of the parent POM:

<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.8.12</version>
    <executions>
        <execution>
            <id>prepare-agent</id>
            <goals><goal>prepare-agent</goal></goals>
        </execution>
        <execution>
            <id>report</id>
            <phase>verify</phase>
            <goals><goal>report</goal></goals>
        </execution>
        <execution>
            <id>check</id>
            <phase>verify</phase>
            <goals><goal>check</goal></goals>
            <configuration>
                <rules>
                    <rule>
                        <element>BUNDLE</element>
                        <limits>
                            <limit>
                                <counter>LINE</counter>
                                <value>COVEREDRATIO</value>
                                <minimum>0.80</minimum>
                            </limit>
                        </limits>
                    </rule>
                </rules>
            </configuration>
        </execution>
    </executions>
    <configuration>
        <excludes>
            <exclude>**/entity/**</exclude>
            <exclude>**/enums/**</exclude>
            <exclude>**/dto/**</exclude>
            <exclude>**/config/**</exclude>
            <exclude>**/CannaManageApplication.*</exclude>
        </excludes>
    </configuration>
</plugin>

1.2 Add Maven Surefire Parallelization

File: pom.xml (parent)

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <forkCount>2</forkCount>
        <reuseForks>true</reuseForks>
    </configuration>
</plugin>

1.3 Create Abstract Unit Test Base

File: cannamanage-service/src/test/java/de/cannamanage/service/AbstractServiceTest.java

package de.cannamanage.service;

import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.UUID;

@ExtendWith(MockitoExtension.class)
public abstract class AbstractServiceTest {
    protected static final UUID TEST_CLUB_ID = UUID.fromString("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
    protected static final UUID TEST_MEMBER_ID = UUID.fromString("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb");
    protected static final UUID TEST_USER_ID = UUID.fromString("cccccccc-cccc-cccc-cccc-cccccccccccc");
    protected static final UUID TEST_STAFF_ID = UUID.fromString("dddddddd-dddd-dddd-dddd-dddddddddddd");
}

1.4 Create Test Data Fixtures

Files:

  • cannamanage-service/src/test/resources/bankimport/sample.mt940 — realistic MT940 file (Sparkasse format)
  • cannamanage-service/src/test/resources/bankimport/sample-real-sparkasse.mt940 — anonymized real Sparkasse statement
  • cannamanage-service/src/test/resources/bankimport/sample-camt053.xml — CAMT.053 XML
  • cannamanage-service/src/test/resources/bankimport/sample.csv — CSV bank statement
  • cannamanage-service/src/test/resources/bankimport/malformed.mt940 — broken MT940 for negative tests
  • cannamanage-service/src/test/resources/bankimport/malformed-truncated.mt940 — truncated mid-statement
  • cannamanage-service/src/test/resources/bankimport/malformed-encoding.mt940 — wrong encoding (UTF-8 instead of ISO-8859-1)
  • cannamanage-service/src/test/resources/bankimport/malformed-overflow.mt940 — amounts exceeding Long.MAX
  • cannamanage-service/src/test/resources/bankimport/xxe-attack.xml — XXE payload for security test
  • cannamanage-service/src/test/resources/bankimport/xxe-billion-laughs.xml — billion laughs DoS attack
  • cannamanage-service/src/test/resources/bankimport/xxe-ssrf.xml — SSRF via external entity

Phase 2: P1 — Financial/Compliance Tests (Priority Critical) — ~120 tests

2.1 FinanceServiceTest (~18 test methods)

File: cannamanage-service/src/test/java/de/cannamanage/service/FinanceServiceTest.java

# Method Scenario Expected
1 testCreateFeeSchedule_ValidInput_ReturnsSchedule Standard fee creation Schedule persisted with correct fields
2 testCreateFeeSchedule_DefaultFlag_ClearsOtherDefaults Creating default schedule Previous default becomes non-default
3 testCreateFeeSchedule_NullName_ThrowsException Missing required field IllegalArgumentException
4 testCreateFeeSchedule_NegativeAmount_ThrowsException Negative fee IllegalArgumentException
5 testCreateFeeSchedule_ZeroAmount_Allowed Free tier fee schedule Schedule with 0 amount persisted
6 testRecordPayment_ValidAmount_CreatesPaymentAndLedger Normal payment Payment CONFIRMED, LedgerEntry INCOME created
7 testRecordPayment_ZeroAmount_ThrowsException Edge case IllegalArgumentException
8 testRecordPayment_MaxIntAmount_Succeeds Boundary: Integer.MAX_VALUE cents Payment created without overflow
9 testRecordPayment_NullMemberId_ThrowsException Missing member IllegalArgumentException
10 testVoidPayment_ExistingPayment_MarksVoidedAndCreatesReversalLedger Voiding Payment VOIDED, reversal LedgerEntry created
11 testVoidPayment_AlreadyVoided_ThrowsException Double-void IllegalStateException
12 testVoidPayment_NonExistentId_ThrowsException Unknown payment NotFoundException
13 testGetMemberBalance_WithPaymentsAndFees_CalculatesCorrectly Balance calculation Sum of payments - sum of assigned fees
14 testGetMemberBalance_NoPayments_ReturnsNegative Unpaid fees Negative balance
15 testGetMemberBalance_OddCentSplit_NoRoundingLoss 1/3 split: 100 cents ÷ 3 No cent lost (33+33+34 or similar)
16 testGetOutstandingMembers_MixedBalances_ReturnsOnlyNegative Outstanding detection Only members with negative balance
17 testRecordExpense_ValidInput_CreatesLedgerEntry Expense recording LedgerEntry EXPENSE type
18 testGetLedgerEntries_Pagination_ReturnsPage Pagination Correct page size and order

2.2 PaymentMatchingServiceTest (~22 test methods)

File: cannamanage-service/src/test/java/de/cannamanage/service/bankimport/PaymentMatchingServiceTest.java

# Method Scenario Expected
1 testMatch_ExactMemberNumber_ScoresAbove90 Member# in Verwendungszweck MATCHED status, confidence ≥90
2 testMatch_AmountAndName_ScoresAbove60 Amount matches + name fuzzy SUGGESTED status
3 testMatch_NoMatch_ScoresBelow60 Random transaction UNMATCHED status
4 testMatch_IbanExactMatch_AddsPoints IBAN field matches member Higher confidence than without
5 testMatch_AmountTolerance20Percent_Matches Amount within ±20% Still scores amount points
6 testMatch_AmountExceeds20Percent_NoAmountScore Amount off by >20% Zero amount score component
7 testMatch_DoublePaymentSafety_DowngradesToSuggested Same member best for 2 txns Both downgraded to SUGGESTED
8 testMatch_GermanUmlauts_NormalizedComparison Name "Müller" vs "Mueller" Recognized as same name
9 testMatch_EmptyTransactionList_ReturnsEmpty No transactions Empty result list
10 testMatch_NoActiveMembers_AllUnmatched Club has no active members All UNMATCHED
11 testMatch_MemberNumberInReference_CaseInsensitive "M-001" vs "m-001" Case-insensitive match
12 testMatch_MultipleFeesForMember_UsesClosestAmount Member has 2 fee schedules Closest amount used for scoring
13 testMatch_BookingDateContext_UsesCorrectPeriod December txns imported in Jan Fee schedule for December used
14 testMatch_EarlyExit_SkipsExpensiveChecks Amount off + no member# Name/IBAN comparison skipped
15 testMatch_PartialMemberNumber_NoMatch "M-00" (partial) in reference Not matched as member#
16 testMatch_ConcurrentMatching_ThreadSafe 10 threads matching simultaneously No race conditions, consistent results
17 testMatch_100Transactions_CompletesUnder1Second Performance boundary <1000ms execution
18 testMatch_AmountExactlyAt20PercentBoundary_Included Boundary: exactly 20% off Still scores (inclusive boundary)
19 testMatch_AmountJustOver20PercentBoundary_Excluded Boundary: 20.01% off Zero amount score
20 testMatch_MemberNumberWithSpaces_Normalized "M - 001" in reference Whitespace stripped before matching
21 testMatch_NullReference_NoNpe Null Verwendungszweck UNMATCHED without NPE
22 testMatch_EmptyName_NoNameScore Blank debtor name Name component scores 0

2.3 Mt940ParserTest (~16 test methods)

File: cannamanage-service/src/test/java/de/cannamanage/service/bankimport/Mt940ParserTest.java

# Method Scenario Expected
1 testParse_ValidFile_ExtractsTransactions Standard MT940 Correct count and fields
2 testParse_CenturyBoundary_HandlesYear70Plus Year 70 → 1970 Correct century assignment
3 testParse_CenturyBoundary_HandlesYear69Below Year 69 → 2069 Correct century assignment
4 testParse_ProprietaryHeaders_SkipsGracefully Extra headers before :20: Parses without error
5 testParse_MalformedAmount_ThrowsParseException "ABC" as amount BankStatementParseException
6 testParse_MissingMandatoryField_ThrowsParseException No :60F: field BankStatementParseException
7 testParse_MultipleStatements_ParsesAll File with 3 statements 3 × n transactions
8 testParse_GermanDecimalFormat_HandlesComma "1.234,56" 123456 cents
9 testParse_CreditAndDebit_CorrectSign C and D indicators Positive/negative amounts
10 testParse_EmptyFile_ReturnsEmptyResult Empty input ParseResult with 0 transactions
11 testParse_TruncatedFile_ThrowsParseException File cut mid-statement BankStatementParseException with context
12 testParse_WrongEncoding_HandlesGracefully UTF-8 with BOM instead of ISO-8859-1 Either parses or throws meaningful error
13 testParse_AmountOverflow_ThrowsParseException Amount > Long.MAX_VALUE BankStatementParseException
14 testParse_RealSparkasseFormat_ExtractsCorrectly Real anonymized Sparkasse file All fields populated
15 testParse_DateFeb29LeapYear_Parses Leap year date 240229 Valid date
16 testParse_DateFeb29NonLeapYear_ThrowsException Non-leap year 250229 BankStatementParseException

2.4 Camt053ParserTest (~14 test methods)

File: cannamanage-service/src/test/java/de/cannamanage/service/bankimport/Camt053ParserTest.java

# Method Scenario Expected
1 testParse_ValidCamt053_ExtractsEntries Standard XML Correct entries extracted
2 testParse_XxeAttack_Rejected XXE entity in XML BankStatementParseException (not file disclosure)
3 testParse_XxeBillionLaughs_Rejected Billion laughs DoS BankStatementParseException within 1s
4 testParse_XxeSsrf_Rejected SSRF via external entity No HTTP request made
5 testParse_WrongNamespace_ThrowsException Unknown namespace BankStatementParseException
6 testParse_MultipleNtry_ParsesAll 5 Ntry elements 5 transactions
7 testParse_DebitCredit_CorrectSign DBIT/CRDT indicators Correct amount signs
8 testParse_MissingOptionalFields_Succeeds No RmtInf element Transaction with null reference
9 testParse_LargeFile_PerformanceOk 1000 entries Completes under 2 seconds
10 testParse_InvalidXml_ThrowsException Broken XML structure BankStatementParseException
11 testParse_EmptyDocument_ThrowsException Valid XML but no entries BankStatementParseException
12 testParse_DuplicateEntryId_AcceptsBoth Same NtryRef twice Both transactions returned
13 testParse_NegativeAmount_ThrowsException Negative Amt value BankStatementParseException
14 testParse_CurrencyNotEur_IncludesCurrency USD transactions Currency field populated

2.5 CsvBankParserTest (~10 test methods)

File: cannamanage-service/src/test/java/de/cannamanage/service/bankimport/CsvBankParserTest.java

# Method Scenario Expected
1 testParse_StandardCsv_ExtractsRows Normal CSV Correct transaction count
2 testParse_GermanNumberFormat_ParsesComma "1.234,56" 123456 cents
3 testParse_Iso88591Encoding_HandlesUmlauts Müller, Straße Names preserved correctly
4 testParse_CustomColumnMapping_RespectsConfig Non-default column order Fields mapped correctly
5 testParse_EmptyCsv_ReturnsEmpty Headers only, no data 0 transactions
6 testParse_MissingRequiredColumn_ThrowsException No amount column BankStatementParseException
7 testParse_QuotedFieldsWithCommas_ParsesCorrectly "Müller, Hans" as name Name includes comma
8 testParse_TrailingNewlines_IgnoresBlankRows Extra blank lines Only valid rows parsed
9 testParse_TabSeparated_DetectsDelimiter TSV instead of CSV Auto-detects tab separator
10 testParse_BomPrefix_StripsUtf8Bom UTF-8 BOM in first byte First column name parsed correctly

2.6 BankImportServiceTest (~14 test methods)

File: cannamanage-service/src/test/java/de/cannamanage/service/bankimport/BankImportServiceTest.java

# Method Scenario Expected
1 testUpload_NewFile_CreatesSession Valid MT940 upload Session UPLOADED, SHA-256 stored
2 testUpload_DuplicateFile_RejectsDuplicate Same SHA-256 twice Exception (GoBD)
3 testParse_UploadedSession_TransitionsToMatched Parse after upload Status → MATCHED
4 testConfirm_MatchedTransaction_CreatesPayment Admin confirms match Payment created in FinanceService
5 testConfirm_CompletedSession_ThrowsImmutable Modify after complete IllegalStateException (GoBD)
6 testComplete_AllConfirmed_SessionCompletes All txns resolved Status → COMPLETED
7 testComplete_UnresolvedTransactions_ThrowsException Some still UNMATCHED IllegalStateException
8 testReassign_SuggestedTransaction_UpdatesMatch Admin picks different member Match updated, still in session
9 testGetSessionHistory_MultipleClubs_OnlyOwn Cross-club query Only own club's sessions
10 testSplitTransaction_SingleTxn_CreatesTwo Split payment Two txns summing to original
11 testUpload_ConcurrentSameFile_OnlyOneSucceeds Race condition: same file One session, one DuplicateException
12 testConfirm_ThenUnconfirm_ReversesPayment Undo confirmation Payment voided, status reverts
13 testSessionExpiry_OldUploadedSession_CanStillBeProcessed Session created 30 days ago No timeout on processing
14 testUpload_EmptyFile_ThrowsException Zero-byte file BankStatementParseException

2.7 RetentionServiceTest (~10 test methods)

File: cannamanage-service/src/test/java/de/cannamanage/service/RetentionServiceTest.java

# Method Scenario Expected
1 testAnonymize_ExpiredMember_AnonymizesFields Retention period passed Name/email/phone = "ANONYMIZED"
2 testAnonymize_ActiveMember_Skipped Still active No changes
3 testAnonymize_RecentlyLeft_NotYetEligible Left 6 months ago (< retention) No changes
4 testDryRun_ReturnsCountWithoutChanging Dry-run mode Count returned, DB unchanged
5 testRetentionRules_DifferentCategories_DifferentPeriods Financial vs personal 10y vs 3y retention
6 testAnonymize_PreservesAuditLog After anonymization AuditEvent still references member UUID
7 testAnonymize_DistributionRecords_AmountsPreserved Financial data Amounts kept, personal data removed
8 testScheduledRun_ProcessesAllClubs Scheduler trigger All eligible clubs processed
9 testAnonymize_MemberLeftExactlyAtBoundary_NotEligible Left exactly 3 years ago today Not yet eligible (must be >3y)
10 testAnonymize_ConcurrentRun_Idempotent Two triggers at same time No double-anonymization

2.8 ReportGeneratorServiceTest (~8 test methods)

File: cannamanage-service/src/test/java/de/cannamanage/service/ReportGeneratorServiceTest.java

# Method Scenario Expected
1 testDispatch_ValidType_CallsCorrectGenerator MEMBER_LIST type MemberListRegistryGenerator invoked
2 testDispatch_UnknownType_ThrowsException Invalid type IllegalArgumentException
3 testDispatch_PdfFormat_ReturnsPdfBytes PDF format Non-empty byte array, PDF magic bytes
4 testDispatch_CsvFormat_ReturnsCsvString CSV format Valid CSV content
5 testDispatch_Concurrent_RateLimited 10 simultaneous requests Only N proceed, rest rejected
6 testDispatch_StoresGeneratedReport Any successful generation GeneratedReport entity persisted
7 testDispatch_NullClubId_ThrowsException Missing club context IllegalArgumentException
8 testDispatch_EveryReportType_HasGenerator Each enum value in ReportType No IllegalArgumentException for any type

2.9 EurReportGeneratorTest (~8 test methods)

File: cannamanage-service/src/test/java/de/cannamanage/service/report/EurReportGeneratorTest.java

# Method Scenario Expected
1 testGenerate_WithIncomeAndExpenses_CorrectSums Mixed ledger Income - Expenses = Profit/Loss
2 testGenerate_EmptyLedger_ZeroSums No entries in period All zeros
3 testGenerate_CrossYearEntries_OnlyRequestedYear Multi-year data Only specified year included
4 testGenerate_CategorizedExpenses_GroupedCorrectly Multiple expense categories Grouped by ExpenseCategory
5 testGenerate_VoidedPayments_ExcludeFromSums Voided entries Not counted in totals
6 testGenerate_PdfOutput_ValidPdf PDF format Starts with %PDF- magic bytes
7 testGenerate_LargeDataset_1000Entries_CompletesUnder3s Performance <3000ms
8 testGenerate_SingleCentEntry_NoRoundingError 1 cent income Shows as 0,01 €

2.10 AnnualAuthorityReportGeneratorTest (~8 test methods)

File: cannamanage-service/src/test/java/de/cannamanage/service/report/AnnualAuthorityReportGeneratorTest.java

# Method Scenario Expected
1 testGenerate_FullYear_IncludesAllDistributions Year of data All distributions in report
2 testGenerate_PerMemberTotals_CorrectAggregation Multiple distributions Summed per member correctly
3 testGenerate_ThcCbdBreakdown_Present THC/CBD data THC and CBD columns populated
4 testGenerate_InactiveMembersIncluded Left during year Still in annual report
5 testGenerate_EmptyYear_ProducesHeaderOnly No distributions Report with headers but no data rows
6 testGenerate_U21MembersMarked Members under 21 U21 flag present in report
7 testGenerate_QuotaBreachHighlighted Member exceeds 25g/day Highlighted in report
8 testGenerate_DecemberDistributionInCorrectYear Dec 31 distribution Included in correct reporting year

2.11 BankStatementParserServiceTest (~6 test methods) — NEW

File: cannamanage-service/src/test/java/de/cannamanage/service/bankimport/BankStatementParserServiceTest.java

# Method Scenario Expected
1 testDetectFormat_Mt940Extension_UsesMt940Parser file.mt940 MT940 parser selected
2 testDetectFormat_XmlExtension_UsesCamt053Parser file.xml CAMT053 parser selected
3 testDetectFormat_CsvExtension_UsesCsvParser file.csv CSV parser selected
4 testDetectFormat_UnknownExtension_ThrowsException file.pdf UnsupportedFormatException
5 testParse_DelegatesToCorrectParser_Mt940 MT940 content Mt940Parser.parse() called
6 testParse_DelegatesToCorrectParser_Camt053 CAMT XML content Camt053Parser.parse() called

2.12 ComplianceServiceTest Extensions (~10 test methods) — NEW

File: Extend existing cannamanage-service/src/test/java/de/cannamanage/service/ComplianceServiceTest.java

# Method Scenario Expected
1 testQuota_ExactlyAt25g_Allowed Boundary: exactly 25g daily No violation
2 testQuota_25g01_Rejected Boundary: 25.01g QuotaExceededException
3 testQuota_Monthly50g_ExactBoundary_Allowed Exactly 50g monthly No violation
4 testQuota_Monthly50g01_Rejected 50.01g monthly QuotaExceededException
5 testQuota_U21_10gDaily_Boundary U21 member at 10g No violation
6 testQuota_U21_10g01_Rejected U21 member at 10.01g QuotaExceededException
7 testQuota_U21_ThcLimit15Pct_Boundary THC exactly 15% for U21 No violation
8 testQuota_U21_ThcLimit15Pct01_Rejected THC 15.01% for U21 QuotaExceededException
9 testQuota_MultipleSameDayDistributions_Cumulative 3 distributions same day Cumulative check
10 testQuota_CrossMidnight_ResetsDailyCounter 23:59 + 00:01 Independent days

2.13 Report Generators Batch (~30 test methods) — NEW

Easy-win coverage for 14 report generator classes in cannamanage-service/src/main/java/de/cannamanage/service/report/. Each generator follows the same pattern: ReportGenerator<P extends ReportParameters> with a generate(Club, P) method producing PDF/CSV bytes. Tests verify (a) successful generation with valid inputs, (b) handling of empty data sets, (c) PDF/CSV header integrity where applicable.

Files: New test classes under cannamanage-service/src/test/java/de/cannamanage/service/report/

# Test Class # Tests Key Scenarios
1 KassenbuchExportGeneratorTest 3 Valid year, empty year, GoBD column order
2 DistributionLogGeneratorTest 2 Valid range, empty range
3 DestructionProtocolGeneratorTest 2 Valid records, no records → empty report
4 TransportCertificateGeneratorTest 2 Valid transport, INVALID status rejected
5 BeitragsbescheinigungGeneratorTest 2 Member with payments, member without payments
6 BestandsfuehrungGeneratorTest 2 Valid stock snapshot, empty stock
7 BoardChangeGeneratorTest 2 Term change captured, no changes → empty report
8 PreventionActivityReportGeneratorTest 2 Activity present, no activities → empty PDF
9 DsfaReportGeneratorTest 2 DSFA template renders, custom risks list
10 TomReportGeneratorTest 2 TOM categories rendered, control measures section present
11 VvtReportGeneratorTest 2 Processing activities listed, legal basis column populated
12 LoeschkonzeptGeneratorTest 2 Retention categories rendered, retention period column correct
13 BreachNotificationGeneratorTest 2 Severity HIGH renders, severity LOW renders
14 AuthorityExportServiceTest 3 Format=CSV, format=XML, invalid format rejected

All tests share a common pattern using AbstractReportGeneratorTest base class (new in Phase 1) which provides a stub Club, helper assertPdfHasHeader(byte[], String) and assertCsvHasColumns(byte[], String...).

2.14 Simple CRUD Service Tests (~18 test methods) — NEW

Coverage for thin CRUD-style services with minimal logic. Each test covers basic CRUD operations + tenant isolation + happy-path validation.

Files: New test classes under cannamanage-service/src/test/java/de/cannamanage/service/

# Test Class # Tests Key Scenarios
1 ConsentServiceTest 4 record consent, revoke consent, query active consents, tenant isolation
2 AuditServiceTest 4 log entry created, query by entity, query by date range, immutability check
3 NotificationPreferenceServiceTest 3 default prefs created, update prefs, query prefs
4 DsgvoServiceTest 4 export personal data, anonymize member, delete data subject, generate DSGVO report
5 ComplianceDashboardServiceTest 3 aggregate metrics, upcoming deadlines, overdue items

Phase 3: P2 — Core Business Logic Tests — ~82 tests

3.1 AssemblyServiceTest (~20 test methods)

File: cannamanage-service/src/test/java/de/cannamanage/service/AssemblyServiceTest.java

# Method Scenario Expected
1 testCreate_ValidInput_ReturnsAssembly Standard creation Assembly PLANNED status
2 testQuorumCheck_50PctPresent_HasQuorum Majority present quorumReached = true
3 testQuorumCheck_LessThan50Pct_NoQuorum Minority present quorumReached = false
4 testQuorumCheck_ExactlyHalf_NoQuorum 50% exactly (not majority) quorumReached = false
5 testVote_SimpleMajority_Passes >50% YES (VoteType.SIMPLE) VoteResult.PASSED
6 testVote_SimpleMajority_Fails ≤50% YES (VoteType.SIMPLE) VoteResult.FAILED
7 testVote_TwoThirdsMajority_RequiresHigherBar VoteType.TWO_THIRDS, 66% VoteResult.FAILED (need 67%+)
8 testVote_TwoThirdsMajority_ExactThreshold_Passes VoteType.TWO_THIRDS, exactly 67% VoteResult.PASSED
9 testVote_Unanimous_AllYes_Passes VoteType.UNANIMOUS, 100% VoteResult.PASSED
10 testVote_Unanimous_OneNo_Fails VoteType.UNANIMOUS, 99% yes VoteResult.FAILED
11 testVote_SecretBallot_NoVoterTracking VoteType with secret flag No voter→choice link stored
12 testVote_MemberNotAttending_CannotVote Non-attendee votes Exception
13 testVote_AlreadyVoted_CannotVoteAgain Double vote Exception
14 testVote_Abstention_DoesNotCountTowardsTotal ABSTAIN vote Not counted in yes/no ratio
15 testBoardTermTracking_TermExpired_FlaggedForElection Past end date Term status EXPIRED
16 testAddAgendaItem_DuringAssembly_Allowed Runtime addition Item added with sequence#
17 testComplete_WithProtocol_GeneratesPdf Assembly completion PDF protocol generated
18 testAttend_MaxCapacity_Rejected Optional max limit CapacityExceededException
19 testVote_AfterAssemblyCompleted_Rejected Vote on completed assembly IllegalStateException
20 testCancel_PlannedAssembly_NotifiesInvitees Cancel event All invitees notified

3.2 EventServiceTest (~14 test methods)

File: cannamanage-service/src/test/java/de/cannamanage/service/EventServiceTest.java

# Method Scenario Expected
1 testCreate_ValidEvent_Persists Standard creation Event saved
2 testRsvp_UnderMaxAttendees_Accepted Space available RSVP CONFIRMED
3 testRsvp_AtMaxAttendees_Waitlisted Full event RSVP WAITLISTED
4 testRsvp_CancelFreesSeat_WaitlistPromoted Cancel then promote Next waitlisted → CONFIRMED
5 testRecurring_Weekly_ExpandsCorrectly RecurrenceRule.WEEKLY, 4 weeks 4 event instances
6 testRecurring_Monthly_LastDay_HandlesShortMonths Monthly on 31st Feb gets 28th/29th
7 testRecurring_Biweekly_CorrectDates RecurrenceRule.BIWEEKLY Every 14 days
8 testRecurring_DstTransition_CorrectTime Summer→Winter time Event stays at local time
9 testIcalGeneration_ValidEvent_ReturnsIcalString iCal export Valid VCALENDAR/VEVENT
10 testDelete_WithRsvps_NotifiesAttendees Delete booked event Notification sent
11 testRsvp_SameMemberTwice_Idempotent Double RSVP Only one confirmation
12 testRecurring_CancelSingle_OthersPersist Cancel one instance Other instances unchanged
13 testRecurring_EditSeries_UpdatesAllFuture Edit series Future instances updated
14 testCreate_PastDate_ThrowsException Start date in past IllegalArgumentException

3.3 ForumServiceTest (~14 test methods)

File: cannamanage-service/src/test/java/de/cannamanage/service/ForumServiceTest.java

# Method Scenario Expected
1 testCreateTopic_ValidInput_Persists New topic Topic saved with createdAt
2 testEditTopic_WithinEditWindow_Succeeds Edit within 15min Content updated
3 testEditTopic_AfterEditWindow_Rejected Edit after 15min Exception
4 testEditTopic_ExactlyAtBoundary_Succeeds Edit at 14:59 Still allowed
5 testEditTopic_OneSecondAfterBoundary_Rejected Edit at 15:01 Exception
6 testReaction_Toggle_AddsThenRemoves React twice First adds, second removes
7 testReaction_DifferentTypes_Coexist LIKE + HELPFUL on same post Both stored independently
8 testReaction_SameTypeTwice_Idempotent LIKE → LIKE → LIKE Toggle: add, remove, add
9 testReport_NewTopic_CreatesReport Report content ForumReport PENDING
10 testReport_SameMemberTwice_OnlyOneReport Duplicate report Idempotent
11 testReply_ToExistingTopic_IncrementsCount Add reply Topic replyCount + 1
12 testDelete_ByAuthor_SoftDeletes Author deletes deletedAt set, content hidden
13 testDelete_ByModerator_SoftDeletes Moderator removes deletedAt set with moderator flag
14 testReply_ToDeletedTopic_ThrowsException Reply to soft-deleted Exception

3.4 InfoBoardServiceTest (~8 test methods)

File: cannamanage-service/src/test/java/de/cannamanage/service/InfoBoardServiceTest.java

# Method Scenario Expected
1 testCreate_ValidPost_Persists New post Post saved
2 testPin_Post_SetsFlag Pin action isPinned = true
3 testArchive_Post_SetsArchivedAt Archive action archivedAt set
4 testMarkRead_NewPost_TracksReading Read tracking PostReadStatus created
5 testMarkRead_AlreadyRead_Idempotent Double read No duplicate entry
6 testGetUnread_ReturnsOnlyNew Mixed read/unread Only unread posts returned
7 testPin_AlreadyPinned_Idempotent Double pin No error, still pinned
8 testGetAll_OrderedByPinnedFirst_ThenDate Mixed pinned/unpinned Pinned first, then by createdAt desc

3.5 NotificationDispatchServiceTest (~10 test methods)

File: cannamanage-service/src/test/java/de/cannamanage/service/NotificationDispatchServiceTest.java

# Method Scenario Expected
1 testDispatch_EmailEnabled_SendsEmail User prefers email EmailService called
2 testDispatch_PushEnabled_SendsPush User prefers push PushSender called
3 testDispatch_AllChannelsDisabled_OnlyInApp Opt-out all Only in-app notification
4 testDispatch_MultipleRecipients_FansOut 10 recipients 10 notifications created
5 testDispatch_PreferenceFiltering_RespectsType User disables EVENT type No notification for events
6 testDispatch_WebSocketPublish_AlwaysFires Any notification WebSocket event published
7 testDispatch_EmailFailure_DoesNotBlockPush Email throws Push still sent, in-app still created
8 testDispatch_NullRecipientList_ThrowsException No recipients IllegalArgumentException
9 testDispatch_DeduplicatesSameRecipient Same user listed twice Only one notification
10 testDispatch_Concurrent100Recipients_NoDeadlock Mass notify All 100 created without timeout

3.6 GrowCalendarServiceTest (~4 test methods) — NEW

File: cannamanage-service/src/test/java/de/cannamanage/service/GrowCalendarServiceTest.java

# Method Scenario Expected
1 testScheduleHarvest_FutureDates_Persists Valid harvest window Entry saved
2 testScheduleHarvest_PastDate_Rejected Date already passed IllegalArgumentException
3 testGetUpcoming_OnlyFuture_Returned Mix past/future Only future entries
4 testDelete_OwnEntry_Succeeds Creator deletes Entry removed

3.7 Schedulers Batch (~12 test methods) — NEW

Coverage for 4 @Scheduled services. Tests verify (a) scheduler triggers correctly with deterministic fixed clock, (b) handles empty input gracefully, (c) idempotency on re-execution.

Files: New test classes under cannamanage-service/src/test/java/de/cannamanage/service/

# Test Class # Tests Key Scenarios
1 BoardTermSchedulerTest 3 Expired terms flagged, future terms ignored, no terms → no-op
2 EventReminderSchedulerTest 3 Events tomorrow → reminder sent, events today not duplicated, no events → no-op
3 PaymentReminderSchedulerTest 3 Overdue payments → reminder, not-yet-due ignored, already-reminded skipped
4 TokenCleanupSchedulerTest (extensions) 3 Expired tokens removed, active tokens preserved, idempotent re-run

All scheduler tests use Clock.fixed(...) injection to make LocalDate.now() deterministic.


Phase 4: P3 — Security & Infrastructure Tests — ~46 tests

4.1 JwtServiceTest (~12 test methods)

File: cannamanage-api/src/test/java/de/cannamanage/api/security/JwtServiceTest.java

# Method Scenario Expected
1 testGenerateToken_ValidUser_ReturnsJwt Normal user Non-null JWT string
2 testValidateToken_ValidToken_ReturnsTrue Fresh token true
3 testValidateToken_ExpiredToken_ReturnsFalse Token past expiry false
4 testValidateToken_TamperedSignature_ReturnsFalse Modified token false
5 testValidateToken_TamperedPayload_ReturnsFalse Payload modified, signature untouched false
6 testValidateToken_NoneAlgorithm_ReturnsFalse alg:none attack false
7 testValidateToken_WrongSigningKey_ReturnsFalse Signed with different key false
8 testExtractUsername_ValidToken_ReturnsSubject Token with subject Correct username
9 testRefreshToken_ValidRefresh_ReturnsNewAccess Valid refresh token New access token
10 testRefreshToken_ExpiredRefresh_ThrowsException Expired refresh Exception
11 testGenerateToken_IncludesRoleClaim User with role Role in token claims
12 testValidateToken_EmptyString_ReturnsFalse Empty/null token false without exception

4.2 LoginRateLimiterTest (~8 test methods)

File: cannamanage-api/src/test/java/de/cannamanage/api/security/LoginRateLimiterTest.java

# Method Scenario Expected
1 testAllow_UnderLimit_Permits 1st attempt true
2 testAllow_AtLimit_Blocks 6th attempt (limit=5) false
3 testAllow_ExactlyAtLimit_StillAllowed 5th attempt (limit=5) true (boundary)
4 testAllow_OneOverLimit_Blocked 6th attempt false (boundary)
5 testReset_AfterBlock_AllowsAgain Successful login resets true again
6 testAllow_DifferentIps_IndependentCounters IP-A blocked, IP-B ok IP-B still allowed
7 testAllow_AfterWindowExpires_AllowsAgain Wait for window reset true
8 testAllow_IpV6_HandledCorrectly IPv6 address as key Independent counter

4.3 TenantFilterAspectTest (~8 test methods)

File: cannamanage-api/src/test/java/de/cannamanage/api/security/TenantFilterAspectTest.java

# Method Scenario Expected
1 testFilter_MatchingClubId_Passes User's own club data Data returned
2 testFilter_DifferentClubId_Blocked Other club's data AccessDeniedException
3 testFilter_SystemAdmin_BypassesFilter SYSTEM_ADMIN role Data returned regardless
4 testFilter_NullClubId_Blocked No club context AccessDeniedException
5 testFilter_FinanceController_EnforcedOnAllEndpoints /api/finance/* cross-club AccessDeniedException
6 testFilter_MemberController_EnforcedOnAllEndpoints /api/members/* cross-club AccessDeniedException
7 testFilter_DocumentController_EnforcedOnAllEndpoints /api/documents/* cross-club AccessDeniedException
8 testFilter_ReportController_EnforcedOnAllEndpoints /api/reports/* cross-club AccessDeniedException

4.4 DocumentServiceTest (~12 test methods)

File: cannamanage-service/src/test/java/de/cannamanage/service/DocumentServiceTest.java

# Method Scenario Expected
1 testUpload_ValidFile_Persists Normal PDF upload Document entity created
2 testUpload_FilenameSanitization_RemovesPathTraversal "../../../etc/passwd" Sanitized to "etc_passwd"
3 testUpload_FilenameSanitization_RemovesBackslash "..\..\windows\system" Sanitized
4 testUpload_FilenameSanitization_NullBytes "file%00.pdf" Null bytes stripped
5 testUpload_FilenameSanitization_UnicodeSlash Unicode path separators Stripped
6 testUpload_ExceedsSizeLimit_Rejected 51MB file (limit=50MB) FileTooLargeException
7 testUpload_ExactlyAtSizeLimit_Allowed 50MB file (boundary) Upload succeeds
8 testUpload_ZeroByteFile_Rejected Empty file IllegalArgumentException
9 testDownload_OwnClub_Allowed Tenant match File bytes returned
10 testDownload_OtherClub_Denied Tenant mismatch AccessDeniedException
11 testDelete_NonOwner_Denied Different user AccessDeniedException
12 testUpload_DangerousExtension_Rejected .exe, .sh, .bat InvalidFileTypeException

4.5 GlobalExceptionHandlerTest (~6 test methods) — NEW

File: cannamanage-api/src/test/java/de/cannamanage/api/exception/GlobalExceptionHandlerTest.java

# Method Scenario Expected
1 testHandleAccessDenied_Returns403_WithSafeBody AccessDeniedException thrown 403, no stack trace in body
2 testHandleValidation_Returns400_WithFieldErrors MethodArgumentNotValidException 400, field error map
3 testHandleQuotaExceeded_Returns422_WithCode QuotaExceededException 422, error code in body
4 testHandleEntityNotFound_Returns404 EntityNotFoundException 404, generic message
5 testHandleGenericException_Returns500_NoInternalDetails RuntimeException 500, no stack trace leaked
6 testHandleMaxUploadSize_Returns413 MaxUploadSizeExceededException 413 Payload Too Large

Tests use MockMvc standalone setup with @ControllerAdvice registered.


Phase 5: P4 — Integration Tests — ~29 tests

5.1 BankImportIntegrationTest (~7 test methods)

File: cannamanage-api/src/test/java/de/cannamanage/api/integration/BankImportIntegrationTest.java

Extends AbstractIntegrationTest. Full HTTP flow with real PostgreSQL.

# Method Scenario
1 testFullFlow_UploadMt940_MatchConfirmComplete Upload → parse → confirm → complete
2 testDuplicateUpload_SameFile_Rejected SHA-256 duplicate detection
3 testImmutability_CompleteSessionCannotBeModified GoBD enforcement
4 testReassign_ChangeMatch_UpdatesTransaction Manual match correction
5 testConcurrentUpload_SameFile_OnlyOneSucceeds Race condition handling
6 testFullFlow_Camt053_MatchConfirmComplete CAMT053 variant of flow 1
7 testSplit_ThenConfirmBoth_SessionCompletes Split + confirm lifecycle

5.2 FinanceIntegrationTest (~6 test methods)

File: cannamanage-api/src/test/java/de/cannamanage/api/integration/FinanceIntegrationTest.java

# Method Scenario
1 testFullFlow_CreateFee_AssignMember_RecordPayment_CheckBalance Fee → Assign → Pay → Balance
2 testVoidPayment_ReversesLedgerEntry Record + Void + verify Kassenbuch
3 testOutstandingMembers_CorrectFiltering Multiple members, mixed status
4 testConcurrentPayments_SameMember_CorrectBalance 5 simultaneous payments
5 testLedgerImmutability_CannotDeleteEntry Direct DELETE attempt
6 testEurReport_AfterPayments_ReflectsAll Full financial → EÜR

5.3 AssemblyIntegrationTest (~5 test methods)

File: cannamanage-api/src/test/java/de/cannamanage/api/integration/AssemblyIntegrationTest.java

# Method Scenario
1 testFullFlow_Create_Invite_Attend_Vote_Complete Full assembly lifecycle
2 testQuorum_InsufficientAttendees_VoteBlocked Quorum enforcement via API
3 testProtocolPdf_AfterComplete_Downloadable PDF generation + download
4 testConcurrentVotes_AllCounted 20 members vote simultaneously
5 testComplete_GeneratesProtocol_ThenImmutable Complete → no more edits

5.4 ReportIntegrationTest Extensions (~4 test methods)

File: Extend existing cannamanage-api/src/test/java/de/cannamanage/api/integration/ReportIntegrationTest.java

# Method Scenario
1 testEurReport_GenerateAndDownload_ValidPdf EÜR PDF download
2 testAnnualAuthority_GenerateAndDownload_ValidPdf Authority report PDF
3 testMemberList_GenerateAndDownload_ValidCsv Member list CSV
4 testConcurrentReportGeneration_NoMixup 3 reports simultaneously

5.5 MigrationIntegrationTest (~3 test methods) — NEW

File: cannamanage-api/src/test/java/de/cannamanage/api/integration/MigrationIntegrationTest.java

# Method Scenario
1 testFlywayMigration_AllMigrationsApply_NoErrors Fresh DB + all migrations
2 testFlywayMigration_Idempotent_SecondRunNoOp Double-run
3 testFlywayMigration_ForeignKeys_AllValid Constraint validation

5.6 SecurityConfigIntegrationTest (~4 test methods) — NEW

File: cannamanage-api/src/test/java/de/cannamanage/api/integration/SecurityConfigIntegrationTest.java

Extends AbstractIntegrationTest. Verifies Spring Security filter chain end-to-end.

# Method Scenario
1 testUnauthenticated_PublicEndpoint_Allowed GET /actuator/health
2 testUnauthenticated_ProtectedEndpoint_Returns401 GET /api/members without JWT
3 testAuthenticated_ProtectedEndpoint_Returns200 GET /api/members with valid JWT
4 testCorsHeaders_PresentOnOptions OPTIONS /api/members with Origin

Phase 6: Coverage Report & CI Enforcement

6.1 JaCoCo Report Verification

After all tests pass, run:

mvn verify -pl cannamanage-service,cannamanage-api

Expected output: HTML report at target/site/jacoco/index.html

6.2 Coverage Enforcement Configuration

The JaCoCo check goal (configured in Phase 1) will fail the build if:

  • Overall line coverage < 80%

Per-package enforcement:

  • de.cannamanage.service.bankimport90% minimum
  • de.cannamanage.service (finance-related) — 90% minimum
  • de.cannamanage.api.security80% minimum
  • de.cannamanage.service.* (business logic) — 75% minimum

6.3 Gitea Actions CI Integration (Stretch Goal)

File: .gitea/workflows/test.yml

name: Test & Coverage
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_DB: cannamanage_test
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
        ports:
          - 5432:5432
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: 17
      - run: mvn verify --batch-mode
      - uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: '**/target/site/jacoco/'

Summary: Test Count by Phase (v3 — Easy Targets Added)

Phase New Unit Tests New Integration Tests Total
Phase 1 (Infrastructure) 0 0 0
Phase 2 (Financial/Compliance + Reports + CRUD) ~168 0 168
Phase 3 (Core Business + Schedulers) ~82 0 82
Phase 4 (Security/Infra + GlobalExceptionHandler) ~46 0 46
Phase 5 (Integration + SecurityConfig) 0 ~29 29
Phase 6 (CI) 0 0 0
Total New ~296 ~29 ~325

Combined with existing 9 unit tests + 11 integration tests = ~345 backend tests total (from current ~20).

v2 → v3 Delta (+70 easy-win tests)

Section Count Rationale
2.13 Report Generators Batch (14 classes) +30 Each generator is small and follows identical pattern — cheap coverage
2.14 Simple CRUD Services (5 services) +18 Thin services with little logic — easy to mock and verify
3.7 Schedulers Batch (4 schedulers) +12 Pure @Scheduled methods — deterministic with Clock.fixed()
4.5 GlobalExceptionHandler +6 Critical for security (no stack-trace leakage) but trivial to test
5.6 SecurityConfigIntegrationTest +4 Single end-to-end happy/sad path verification
Total Delta +70

Coverage Targets (v3)

Category Target v2 v1
Overall line coverage ≥80% (now realistically achievable with +70 easy wins) 80% 60%
Financial (FinanceService, BankImport, Parsers, Matching) ≥90% 90% 80%
Compliance (Retention, ComplianceService, Reports) ≥90% (boosted by 14 generator tests) 90% 80%
Security (JWT, RateLimiter, Tenant, Document, GlobalExceptionHandler) ≥85% 80% 75%
Core Business (Assembly, Events, Forum, InfoBoard) ≥75% 75% 60%
Infrastructure (Notifications, Schedulers) ≥70% (boosted by 4 scheduler tests) 60% 50%

Key Expansions from v1 → v2

Area v1 v2 What was added
PaymentMatchingServiceTest 15 tests 22 tests Concurrent, boundary conditions (20% tolerance edge), null handling
Mt940ParserTest 10 tests 16 tests Truncated file, wrong encoding, overflow, real Sparkasse, leap year
Camt053ParserTest 8 tests 14 tests Billion laughs, SSRF, empty doc, duplicate ID, negative amount, currency
CsvBankParserTest 6 tests 10 tests Quoted fields, trailing newlines, tab-separated, BOM
BankImportServiceTest 10 tests 14 tests Concurrent upload, undo confirm, session expiry, empty file
AssemblyServiceTest 12 tests 20 tests Every VoteType, abstention, secret ballot, cancel, boundary quorum
EventServiceTest 8 tests 14 tests DST transition, biweekly, cancel single recurring, idempotent RSVP
ForumServiceTest 8 tests 14 tests Edit window boundary (exact second), reaction types, moderator delete
JwtServiceTest 8 tests 12 tests None algorithm, wrong key, tampered payload, empty string
LoginRateLimiterTest 5 tests 8 tests Boundary (exactly at/over limit), IPv6
TenantFilterAspectTest 4 tests 8 tests Cross-tenant on every controller type
DocumentServiceTest 6 tests 12 tests Every filename attack vector (backslash, null byte, unicode, extension)
Integration tests 12 tests 25 tests Concurrent ops, migration verification, full lifecycles
NEW: ComplianceServiceTest extensions 0 10 Every quota boundary (daily, monthly, U21, THC%)
NEW: BankStatementParserServiceTest 0 6 Format detection and delegation
NEW: MigrationIntegrationTest 0 3 Flyway migration verification