Skip to content

[Unit Tests] Expand DAO test coverage for PlayerBoardState, LoadoutDisplay, PlayerLoadoutSelection, and PlayerStat - #323

Open
DiamondDagger590 wants to merge 2 commits into
recodefrom
claude/magical-cray-f29vuh
Open

[Unit Tests] Expand DAO test coverage for PlayerBoardState, LoadoutDisplay, PlayerLoadoutSelection, and PlayerStat#323
DiamondDagger590 wants to merge 2 commits into
recodefrom
claude/magical-cray-f29vuh

Conversation

@DiamondDagger590

@DiamondDagger590 DiamondDagger590 commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Summary

  • PlayerBoardStateDAO: Expanded from 4 to ~29 tests — covers all 8 public methods (attemptCreateTable, updateTable, saveState, deleteForPlayer, loadAcceptedForPlayer, updateStateByQuestInstanceUUID, bulkCancelExpiredBoardStates, countActiveQuestsFromBoard) plus AcceptedBoardEntry record, including multi-row iteration and SQL exception paths
  • LoadoutDisplayDAO: Expanded from 9 to ~22 tests — adds updateTable v1/v2 migration tests, saveAllLoadoutDisplays with exact statement count assertion, columnExists metadata/PRAGMA paths, material fallback when custom item is absent, and SQL exception paths
  • PlayerLoadoutSelectionDAO: Expanded from 8 to ~14 tests — adds attemptCreateTable (exists/create/exception), updateTable version tracking, and fixes the no-row test to exercise the catch-block path matching real JDBC behavior
  • PlayerStatDAO: Expanded from 7 to ~16 tests — adds attemptCreateTable (exists/create/exception/PK verification), saveStats batch (multi-entry, empty map, exception propagation, per-entry binding), and SQL exception paths for load methods

All tests use the established mock-based JDBC pattern (Mockito mocks for Connection, PreparedStatement, ResultSet) and follow the @Nested/@DisplayName/action_outcome_whenCondition naming conventions.

Test plan

  • Full test suite passes (./gradlew test — BUILD SUCCESSFUL)
  • Testing audit persona (persona-testing.mdc) reviewed — all findings addressed
  • JaCoCo coverage report generated

Generated by Claude Code

Summary by CodeRabbit

  • Tests
    • Expanded automated coverage for loadout displays, player loadouts, player statistics, and board state handling.
    • Added validation for table creation, schema updates, data saving, retrieval, and version management.
    • Improved coverage of empty results, missing optional values, database errors, and fallback behavior.
    • Added checks for SQL parameter binding, default values, and aggregate calculations.
    • Reorganized tests into clearer operation-based sections for easier maintenance.

claude added 2 commits July 29, 2026 09:25
…LoadoutSelection, and PlayerStat

Add comprehensive mock-based unit tests for four DAO classes that previously
had low coverage (34-56%). Tests cover attemptCreateTable, updateTable
migrations, CRUD operations, SQL exception handling, parameter binding,
and edge cases like invalid NamespacedKey parsing and minimum-value floors.

- PlayerBoardStateDAO: 4 → ~28 tests (all 8 public methods + AcceptedBoardEntry)
- LoadoutDisplayDAO: 9 → ~22 tests (updateTable migrations, saveAll, columnExists)
- PlayerLoadoutSelectionDAO: 8 → ~14 tests (attemptCreateTable, updateTable)
- PlayerStatDAO: 7 → ~16 tests (attemptCreateTable, saveStats batch, exceptions)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TEUot1ASeqchbi29i1ofUS
- Fix getActiveLoadout no-row test to exercise catch-block path by
  throwing SQLException from getInt after empty ResultSet
- Add multi-row iteration test for loadAcceptedForPlayer
- Assert exact statement count (4) in saveAllLoadoutDisplays test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TEUot1ASeqchbi29i1ofUS
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The DAO test suites were reorganized into nested operation groups and expanded with coverage for schema migration, JDBC parameter binding, nullable values, empty results, SQL exceptions, and CRUD behavior across loadout, stat, and board-state persistence.

Changes

DAO test coverage

Layer / File(s) Summary
Table creation and schema migration coverage
src/test/java/us/eunoians/mcrpg/database/table/*DAOTest.java
Tests cover table creation outcomes, schema-version short-circuits, index creation, and conditional column migration behavior.
Loadout display persistence and retrieval
src/test/java/us/eunoians/mcrpg/database/table/LoadoutDisplayDAOTest.java
Save, bulk-save, delete, and retrieval tests validate bindings, nullable fields, fallback values, empty results, and SQL failures.
Active loadout operations
src/test/java/us/eunoians/mcrpg/database/table/PlayerLoadoutSelectionDAOTest.java
Tests cover active-loadout defaults, value clamping, replacement writes, UUID binding, and version updates.
Player stat persistence and loading
src/test/java/us/eunoians/mcrpg/database/table/PlayerStatDAOTest.java
Single and bulk stat save/load tests cover valid data, invalid keys, empty results, parameter bindings, and SQL exceptions.
Player board state operations
src/test/java/us/eunoians/mcrpg/database/table/board/PlayerBoardStateDAOTest.java
Tests cover board-state CRUD, nullable fields, accepted-entry mapping, aggregate operations, SQL fallbacks, and record accessors.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • DiamondDagger590/McRPG#272: Adds related DAO unit-test coverage for PlayerLoadoutSelectionDAO, including active-loadout operations.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: expanded DAO unit test coverage for the four affected classes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/magical-cray-f29vuh

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.

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown

Claude finished @DiamondDagger590's task in 4m 59s —— View job


Review: 2 important, 1 nit

This PR only touches test files (LoadoutDisplayDAOTest, PlayerLoadoutSelectionDAOTest, PlayerStatDAOTest, PlayerBoardStateDAOTest), significantly expanding DAO coverage with @Nested/@DisplayName grouping per attemptCreateTable/updateTable/CRUD method. Ran the review-testing lens and independently verified each finding against the diff and the corresponding production DAO classes.

Important findings (also left as inline comments):

  • src/test/java/us/eunoians/mcrpg/database/table/LoadoutDisplayDAOTest.java:459-472 (and the analogous test just below it) — getLoadoutDisplay_returnsDisplay_whenRowExists / getLoadoutDisplay_returnsDisplay_whenDisplayNameIsNull stub display_item/display_name result-set columns but only assert result.isPresent(), never checking the returned LoadoutDisplay's actual item/name. A bug that swaps the two column reads or mis-wraps the item would still leave the Optional non-empty, so it wouldn't be caught. LoadoutDisplay already has a working equals() (backed by CustomItemWrapper.equals() + Optional<String> for the name) — assert on the actual value instead of just presence. Fix this →

  • src/test/java/us/eunoians/mcrpg/database/table/PlayerLoadoutSelectionDAOTest.java:79-91attemptCreateTable_referencesLoadoutInfoTable is named/@DisplayNamed "CREATE TABLE SQL references the loadout info table" but only verifies prepareStatement(contains("CREATE TABLE")), which is already covered by attemptCreateTable_returnsTrue_whenTableDoesNotExist. The test would still pass even if the FOREIGN KEY ... REFERENCES clause to the loadout info table were deleted, so it doesn't verify what its name claims. Assert on the FK content instead, e.g. contains("REFERENCES") or the actual table name. Fix this →

1 nit
  • src/test/java/us/eunoians/mcrpg/database/table/PlayerStatDAOTest.java:85-97attemptCreateTable_includesCompositePrimaryKey only checks the SQL contains the literal "PRIMARY KEY", not that it's composite over player_uuid/stat_key as the name implies — would equally pass for a single-column key. Match the full clause instead.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/test/java/us/eunoians/mcrpg/database/table/PlayerLoadoutSelectionDAOTest.java (1)

209-224: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

This test conflates two mutually exclusive paths.

If getActiveLoadout guards on next(), the getInt stub is never used (dead stubbing, and a failure under strict stubs); if it does not guard, the assertion only proves the exception fallback. Split into two tests: one with next() returning false and no getInt stub, and one asserting the SQLException fallback explicitly.

♻️ Suggested split
-        `@Test`
-        `@DisplayName`("returns 1 when no row exists and getInt throws after empty result set")
-        void getActiveLoadout_returnsOne_whenNoRowExists() throws SQLException {
+        `@Test`
+        `@DisplayName`("returns 1 when no row exists")
+        void getActiveLoadout_returnsOne_whenNoRowExists() throws SQLException {
             Connection mockConnection = mock(Connection.class);
             PreparedStatement mockStatement = mock(PreparedStatement.class);
             ResultSet mockResultSet = mock(ResultSet.class);
             when(mockConnection.prepareStatement(anyString())).thenReturn(mockStatement);
             when(mockStatement.executeQuery()).thenReturn(mockResultSet);
             when(mockResultSet.next()).thenReturn(false);
-            when(mockResultSet.getInt("active_loadout_id"))
-                    .thenThrow(new SQLException("ResultSet is empty"));
 
             int result = PlayerLoadoutSelectionDAO.getActiveLoadout(mockConnection, PLAYER_UUID);
 
             assertEquals(1, result);
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/test/java/us/eunoians/mcrpg/database/table/PlayerLoadoutSelectionDAOTest.java`
around lines 209 - 224, Split getActiveLoadout_returnsOne_whenNoRowExists into
two focused tests: keep the empty-result case with next() returning false and
remove the unused getInt stubbing, then add a separate test that makes next()
permit getInt execution, stubs getInt to throw SQLException, and asserts the
fallback value of 1.
src/test/java/us/eunoians/mcrpg/database/table/LoadoutDisplayDAOTest.java (1)

120-126: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Replace inline fully-qualified Mockito references with static imports. All four suites reference org.mockito.ArgumentMatchers.contains, org.mockito.Mockito.never, and org.mockito.Mockito.times by fully-qualified name inside method bodies, while the same classes are already statically imported for mock/when/verify.

  • src/test/java/us/eunoians/mcrpg/database/table/LoadoutDisplayDAOTest.java#L120-L126: add import static org.mockito.ArgumentMatchers.contains; and use bare contains(...) here and at lines 160, 179, 183, 214, 364, 366.
  • src/test/java/us/eunoians/mcrpg/database/table/PlayerLoadoutSelectionDAOTest.java#L90-L110: use the already-imported never() and a statically imported contains(...) here and at line 241.
  • src/test/java/us/eunoians/mcrpg/database/table/PlayerStatDAOTest.java#L96-L96: add the contains static import and use it here and at line 113.
  • src/test/java/us/eunoians/mcrpg/database/table/board/PlayerBoardStateDAOTest.java#L98-L116: statically import never and times and drop the org.mockito.Mockito. prefixes.

As per coding guidelines, "Do not write fully-qualified type references inline in method bodies (e.g., org.bukkit.Location loc = ...); all types must be imported at the top of the file".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/java/us/eunoians/mcrpg/database/table/LoadoutDisplayDAOTest.java`
around lines 120 - 126, Replace inline fully qualified Mockito calls with static
imports and bare calls. In
src/test/java/us/eunoians/mcrpg/database/table/LoadoutDisplayDAOTest.java:120-126
and its other listed usages (160, 179, 183, 214, 364, 366), statically import
contains and remove the ArgumentMatchers prefix; in
PlayerLoadoutSelectionDAOTest.java:90-110 and 241, use static never and
contains; in PlayerStatDAOTest.java:96 and 113, add and use static contains; in
PlayerBoardStateDAOTest.java:98-116, statically import never and times and
remove their Mockito prefixes.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/test/java/us/eunoians/mcrpg/database/table/LoadoutDisplayDAOTest.java`:
- Around line 29-33: Upgrade the Mockito test dependency from the legacy
mockito-inline 3.12.2 artifact to a Java 21–supported Mockito release in the
Gradle configuration, remove the mockito-inline dependency, and retain static
mocking through Mockito core’s mockStatic API used by LoadoutDisplayDAOTest.

In `@src/test/java/us/eunoians/mcrpg/database/table/PlayerStatDAOTest.java`:
- Line 50: Replace the hard-coded "mcrpg_player_stat" argument in all four
mockDatabase.tableExists stubs within PlayerStatDAOTest with
PlayerStatDAO.TABLE_NAME, preserving the existing stubbing behavior.

---

Nitpick comments:
In `@src/test/java/us/eunoians/mcrpg/database/table/LoadoutDisplayDAOTest.java`:
- Around line 120-126: Replace inline fully qualified Mockito calls with static
imports and bare calls. In
src/test/java/us/eunoians/mcrpg/database/table/LoadoutDisplayDAOTest.java:120-126
and its other listed usages (160, 179, 183, 214, 364, 366), statically import
contains and remove the ArgumentMatchers prefix; in
PlayerLoadoutSelectionDAOTest.java:90-110 and 241, use static never and
contains; in PlayerStatDAOTest.java:96 and 113, add and use static contains; in
PlayerBoardStateDAOTest.java:98-116, statically import never and times and
remove their Mockito prefixes.

In
`@src/test/java/us/eunoians/mcrpg/database/table/PlayerLoadoutSelectionDAOTest.java`:
- Around line 209-224: Split getActiveLoadout_returnsOne_whenNoRowExists into
two focused tests: keep the empty-result case with next() returning false and
remove the unused getInt stubbing, then add a separate test that makes next()
permit getInt execution, stubs getInt to throw SQLException, and asserts the
fallback value of 1.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Pro Plus

Run ID: 014b852a-70b3-4cef-9a23-4f336402aecd

📥 Commits

Reviewing files that changed from the base of the PR and between 71d4ebb and b64046d.

📒 Files selected for processing (4)
  • src/test/java/us/eunoians/mcrpg/database/table/LoadoutDisplayDAOTest.java
  • src/test/java/us/eunoians/mcrpg/database/table/PlayerLoadoutSelectionDAOTest.java
  • src/test/java/us/eunoians/mcrpg/database/table/PlayerStatDAOTest.java
  • src/test/java/us/eunoians/mcrpg/database/table/board/PlayerBoardStateDAOTest.java

Comment on lines +29 to +33
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

Mockito 3.12.2 mockito-inline Java 21 support minimum version

💡 Result:

Mockito 3.12.2 does not support Java 21 [1][2][3]. Official support for Java 21 was introduced in Mockito 5.x [2][4]. Mockito 3.x is a legacy version, and the project only supports the latest major version (currently 5.x) [4]. Attempts to use older versions like 3.12.2 on Java 21 will typically fail due to incompatibility with the underlying Byte Buddy library, which requires updates to recognize Java 21's bytecode version (65) [1][3]. If you are currently using Mockito 3.12.2 or any 4.x version and need to run tests on Java 21, you must upgrade to Mockito 5.x [2][4]. Starting with version 5.0.0, the "inline" mock maker is the default, so the separate mockito-inline artifact is no longer required and is effectively deprecated [5][6][4]. Additionally, please note that running Mockito on Java 21 may require explicit JVM configuration (such as adding -javaagent) due to increased restrictions on dynamic instrumentation in newer JDKs [7][6].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== java target and mockito dependency declarations =="
git ls-files | rg '(^|/)(build\.gradle|build\.gradle\.kts|pom\.xml|gradle\.properties|.*\.gradle)$' | while read -r f; do
  echo "--- $f"
  rg -n "java|sourceCompatibility|targetCompatibility|mockito|JUnit|org.mockito|bytebuddy|byte-buddy|source.*21|target.*21|java\s*[""']?21|target-version|jvm-target|properties.java.version|mocktools|inline" "$f" || true
done

echo
echo "== candidate test file relevant imports/usages =="
if [ -f src/test/java/us/eunoians/mcrpg/database/table/LoadoutDisplayDAOTest.java ]; then
  wc -l src/test/java/us/eunoians/mcrpg/database/table/LoadoutDisplayDAOTest.java
  sed -n '1,220p' src/test/java/us/eunoians/mcrpg/database/table/LoadoutDisplayDAOTest.java
fi

Repository: DiamondDagger590/McRPG

Length of output: 11446


Upgrade Mockito test dependencies before using mockStatic.

The project targets Java 21 while build.gradle.kts pins org.mockito:mockito-inline to 3.12.2; that Mockito release is legacy and does not support Java 21 static mocking. Move the static mocks to a supported Mockito Java 21 release and use the core mockStatic API instead of the deprecated mockito-inline artifact.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/java/us/eunoians/mcrpg/database/table/LoadoutDisplayDAOTest.java`
around lines 29 - 33, Upgrade the Mockito test dependency from the legacy
mockito-inline 3.12.2 artifact to a Java 21–supported Mockito release in the
Gradle configuration, remove the mockito-inline dependency, and retain static
mocking through Mockito core’s mockStatic API used by LoadoutDisplayDAOTest.

void attemptCreateTable_returnsFalse_whenTableExists() {
Connection mockConnection = mock(Connection.class);
Database mockDatabase = mock(Database.class);
when(mockDatabase.tableExists(mockConnection, "mcrpg_player_stat")).thenReturn(true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use PlayerStatDAO.TABLE_NAME instead of the literal "mcrpg_player_stat".

The sibling suites in this PR stub tableExists with the DAO's constant. With a literal, a rename of the constant leaves these stubs unmatched, so tableExists returns false by default and the tests keep "passing" while asserting nothing meaningful.

♻️ Proposed change (apply to all four stubs)
-            when(mockDatabase.tableExists(mockConnection, "mcrpg_player_stat")).thenReturn(true);
+            when(mockDatabase.tableExists(mockConnection, PlayerStatDAO.TABLE_NAME)).thenReturn(true);

Also applies to: 63-63, 77-77, 91-91

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/java/us/eunoians/mcrpg/database/table/PlayerStatDAOTest.java` at
line 50, Replace the hard-coded "mcrpg_player_stat" argument in all four
mockDatabase.tableExists stubs within PlayerStatDAOTest with
PlayerStatDAO.TABLE_NAME, preserving the existing stubbing behavior.

Comment on lines +459 to +472
@DisplayName("returns display when row exists")
void getLoadoutDisplay_returnsDisplay_whenRowExists() throws SQLException {
Connection mockConnection = mock(Connection.class);
PreparedStatement mockStatement = mock(PreparedStatement.class);
ResultSet mockResultSet = mock(ResultSet.class);
when(mockConnection.prepareStatement(anyString())).thenReturn(mockStatement);
when(mockStatement.executeQuery()).thenReturn(mockResultSet);
when(mockResultSet.next()).thenReturn(true);
when(mockResultSet.getString("display_item")).thenReturn("DIAMOND_SWORD");
when(mockResultSet.getString("display_name")).thenReturn("Battle Loadout");

Optional<LoadoutDisplay> result = LoadoutDisplayDAO.getLoadoutDisplay(mockConnection, PLAYER_UUID, 1);

assertTrue(result.isPresent());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important (testing): This test (and getLoadoutDisplay_returnsDisplay_whenDisplayNameIsNull below) stubs display_item/display_name on the result set but only asserts result.isPresent() — it never checks the returned LoadoutDisplay's actual item/name. A bug that swaps the two column reads or mis-wraps the item would still leave the Optional non-empty, so this test can't catch it.

LoadoutDisplay already has a working equals() (item + Optional<String> name), so assert on the value instead:

assertEquals(Optional.of(new LoadoutDisplay(new CustomItemWrapper("DIAMOND_SWORD"), "Battle Loadout")), result);

(and the analogous null-name assertion in the other test).

Comment on lines +79 to +91
@Test
@DisplayName("CREATE TABLE SQL references the loadout info table")
void attemptCreateTable_referencesLoadoutInfoTable() throws SQLException {
Connection mockConnection = mock(Connection.class);
Database mockDatabase = mock(Database.class);
PreparedStatement mockStatement = mock(PreparedStatement.class);
when(mockDatabase.tableExists(mockConnection, PlayerLoadoutSelectionDAO.TABLE_NAME)).thenReturn(false);
when(mockConnection.prepareStatement(anyString())).thenReturn(mockStatement);

PlayerLoadoutSelectionDAO.attemptCreateTable(mockConnection, mockDatabase);

verify(mockConnection).prepareStatement(org.mockito.ArgumentMatchers.contains("CREATE TABLE"));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important (testing): This test's @DisplayName claims "CREATE TABLE SQL references the loadout info table," but the assertion only checks contains("CREATE TABLE") — already covered by attemptCreateTable_returnsTrue_whenTableDoesNotExist above. It would still pass even if the FOREIGN KEY ... REFERENCES clause to the loadout info table were deleted entirely.

Assert on the FK content it claims to verify, e.g. contains("REFERENCES") or the actual loadout-info table name.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants