Transmission #065: Characters API test wiring
PlayerRepository now takes PlayerBaseStatRepository and BaseStatRepository alongside DSLContext and InventoryRepository. CharactersApiImplTest constructs those repositories in @BeforeEach so unit tests match production wiring and ./gradlew clean build stays green.
Character stats on GET / POST + integration coverage
PlayerRepository.findById loads player_base_stat rows via PlayerBaseStatRepository; applyPatch carries forward existing.getBaseStats() so PATCH responses still include stats. PlayerModelMapper.fromModelToDTO maps internal base stats to OpenAPI Character.stats.
Integration tests (CharactersApiIntegrationTest) assert that creating a character without explicit stats seeds one row per catalog stat at level 0 (catalog compared via StatsApi), that explicit stats persist on GET, and that a name PATCH preserves stat count on response and reload.
PATCH stats — level updates wired end-to-end
PlayerRepository.applyPatch now syncs stat levels when the patched Character payload includes a non-empty stats list: it iterates each CharacterStat, calls PlayerBaseStatRepository.update per row, then reloads the full stat list from the DB for the response. Stats are untouched when the patch omits the field entirely.
A subtle fix accompanied this: the existing create path checked character.getStats() != null to decide whether to seed default stats, but the OpenAPI-generated Character initializes stats to an empty ArrayList, so that guard was always true and seeding never happened. The condition is now != null && !isEmpty().
DbTestDataPopulator.addBaseStats() seeds the 6 standard base stats so unit tests can exercise the create → patch round trip. CharactersApiImplTest calls it in @BeforeAll and adds patchCharacter_replaceStats_updatesLevels to confirm the patched level survives both the response and a follow-up GET. The same scenario is covered at the HTTP layer in CharactersApiIntegrationTest. All 25 integration tests and 115 unit tests pass.