Skip to content

feat(jans-fido2): add JUnit 5 coverage for Fido2UserMetrics entity be…#14137

Draft
imran-ishaq wants to merge 2 commits into
mainfrom
jans-fido2-model-user-metrics-entity-tests
Draft

feat(jans-fido2): add JUnit 5 coverage for Fido2UserMetrics entity be…#14137
imran-ishaq wants to merge 2 commits into
mainfrom
jans-fido2-model-user-metrics-entity-tests

Conversation

@imran-ishaq
Copy link
Copy Markdown
Contributor

@imran-ishaq imran-ishaq commented May 22, 2026

Prepare


Description

Adds JUnit 5 test coverage for Fido2UserMetrics, the per-user ORM entity backing every FIDO2 user-adoption metric stored under ou=fido2-user-metrics,o=jans. This is the first PR in the FIDO2 metrics test rollout that exercises real computed behavior — counter mutations with timestamp side effects, null-safe ratio computations, threshold-based bucketing for engagement (HIGH/MEDIUM/LOW at 50 / 10) and adoption (NEW/LEARNING/ADOPTED/EXPERT), 30-day window logic for new/active user classification, and the custom equals/hashCode identity-tuple contract used by collections/dedup in Fido2UserMetricsService. 19 grouped @Test methods, runs in ~0.151s. Test-only change; no production code or pom.xml changes. Step 5 of the FIDO2 metrics test rollout.

Target issue

Fido2UserMetrics (jans-fido2/model/src/main/java/io/jans/fido2/model/metric/Fido2UserMetrics.java) is the per-user ORM entity backing every FIDO2 user-adoption metric stored under ou=fido2-user-metrics,o=jans. Unlike the earlier DTOs in this rollout, this class carries real computed logic that downstream services and the REST analytics endpoints depend on:

  • incrementRegistrations(boolean), incrementAuthentications(boolean), incrementFallbackEvents() — counter mutations with side effects on lastActivityDate / lastUpdated.
  • getRegistrationSuccessRate(), getAuthenticationSuccessRate(), getOverallSuccessRate(), getFallbackRate() — null-safe ratio computations. Without guards, Double.NaN / Double.POSITIVE_INFINITY would leak into stored aggregates and through the JSON analytics responses.
  • updateEngagementLevel() — threshold-based bucketing into HIGH (≥50 ops), MEDIUM (≥10), LOW (otherwise). An off-by-one mistake at the boundaries silently re-buckets every user.
  • updateAdoptionStage() — threshold-based bucketing into NEW (0 regs), LEARNING (1), ADOPTED (2–5), EXPERT (>5).
  • isNewUser(), isActiveUser() — 30-day window logic anchored to System.currentTimeMillis(), with null-date guards.
  • Custom equals(Object) / hashCode() based on the (userId, username, firstRegistrationDate) triple — used by collections/dedup in Fido2UserMetricsService.
  • Two constructors with non-trivial initialization: the no-arg constructor zeroes counters, sets isActive=true, and stamps lastUpdated; the 2-arg constructor additionally assigns a random UUID to id.

None of this is tested today. This is the first step in the rollout that exercises real computation rather than getter/setter wiring, and the per-method consequences of a silent regression are large: corrupted stored aggregates, NaN leakage through the JSON API, mis-bucketed engagement reports, and broken collection/dedup semantics in Fido2UserMetricsService.

This PR is step 5 of the FIDO2 metrics test rollout.

closes #14133

Implementation Details

Adds a single new JUnit 5 test class — jans-fido2/model/src/test/java/io/jans/fido2/model/metric/Fido2UserMetricsTest.java — with 19 grouped @Test methods. Coverage map:

Behavior Test method
Default constructor initial state (counters zeroed, isActive=true, lastUpdated ~now) testDefaultConstructorInitialState
2-arg constructor (userId/username + UUID-parseable id + inherits defaults) testTwoArgConstructor
incrementRegistrations(true) testIncrementRegistrationsSuccess
incrementRegistrations(false) testIncrementRegistrationsFailure
incrementAuthentications(true/false) testIncrementAuthenticationsSuccessAndFailure
incrementFallbackEvents() testIncrementFallbackEvents
getRegistrationSuccessRate happy path testGetRegistrationSuccessRateHappyPath
getRegistrationSuccessRate null + zero guards testGetRegistrationSuccessRateZeroAndNullGuards
getAuthenticationSuccessRate happy path + guards testGetAuthenticationSuccessRateHappyPathAndGuards
getOverallSuccessRate cross-counter math (4 regs/6 auths → 0.5) testGetOverallSuccessRateHappyPath
getOverallSuccessRate null/zero/one-side-null guards testGetOverallSuccessRateNullAndZeroGuards
getFallbackRate happy path + null totals + null fallback testGetFallbackRateHappyPathAndGuards
updateEngagementLevel boundary at 50 (49→MEDIUM, 50→HIGH) testUpdateEngagementLevelBoundary50
updateEngagementLevel boundary at 10 (9→LOW, 10→MEDIUM) testUpdateEngagementLevelBoundary10
updateEngagementLevel null totals → LOW testUpdateEngagementLevelNullTotals
updateAdoptionStage full ladder (0→NEW, 1→LEARNING, 5→ADOPTED, 6→EXPERT, null→NEW) testUpdateAdoptionStageBoundaries
isNewUser 29d → true, 31d → false, null → false testIsNewUser
isActiveUser 29d → true, 31d → false, null → false testIsActiveUser
equals/hashCode identity-tuple contract testEqualsContractForIdentityTuple

Design choices worth flagging:

  • Boundary tests, not just happy paths. The entire risk profile here is off-by-one in the threshold ladders (> vs >= regressions on 50 and 10 for engagement; 0/1/5/6 transitions for adoption) and NaN leakage from unguarded division. Happy paths catch neither — so each threshold ladder is tested at the value above and below the boundary, plus null inputs.
  • No Thread.sleep and no clock injection. Timestamp side-effect assertions use a small wall-clock window pattern: capture before = System.currentTimeMillis() before the call, after = System.currentTimeMillis() after, assert lastUpdated.getTime() falls in [before, after]. isNewUser / isActiveUser use fixed Date offsets relative to System.currentTimeMillis() (29-day / 31-day) rather than wallclock comparisons inside the assertion. Robust without flakiness.
  • equals/hashCode contract coverage spans: reflexive (x.equals(x)), null (x.equals(null) == false), wrong-type (x.equals("string") == false), all three identity-tuple-diff cases (different userId, different username, different firstRegistrationDate), AND the inverse — unrelated fields like counters and engagementLevel differing must preserve equality. This is exactly what the Fido2UserMetricsService dedup invariant depends on.
  • Coverage of the cross-counter math in getOverallSuccessRate / getFallbackRate includes the one-side-null case, where the null side must be treated as 0 and the other side drives the rate — a subtle path that a happy-path-only test would miss entirely.

Scope:

  • Test-only addition under src/test.
  • No production-code changes under src/main.
  • No pom.xml changes (jans-orm-model, which provides the Entry superclass, is already on the model module's compile classpath).

Local verification: mvn -pl model -am test from jans-fido2/ — 19 tests, 0 failures, 0 errors, runtime ~0.151s.


Test and Document the changes

  • Static code analysis has been run locally and issues have been fixed
  • Relevant unit and integration tests have been added/updated
  • Relevant documentation has been updated if any (i.e. user guides, installation and configuration guides, technical design docs etc)

Please check the below before submitting your PR. The PR will not be merged if there are no commits that start with docs: to indicate documentation changes or if the below checklist is not selected.

  • I confirm that there is no impact on the docs due to the code changes in this PR.

…havior

Signed-off-by: imran <imranishaq7071@gmail.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 22, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f3f294b5-d1cc-4bfa-aef2-a825a624a70c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jans-fido2-model-user-metrics-entity-tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@mo-auto
Copy link
Copy Markdown
Member

mo-auto commented May 22, 2026

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@mo-auto mo-auto added comp-jans-fido2 Component affected by issue or PR kind-feature Issue or PR is a new feature request labels May 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp-jans-fido2 Component affected by issue or PR kind-feature Issue or PR is a new feature request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(jans-fido2): add unit tests for Fido2UserMetrics computed logic

4 participants