diff --git a/src/main/java/auth_service/modules/profile/api/PublicProfileController.java b/src/main/java/auth_service/modules/profile/api/PublicProfileController.java index 93fdc40..94195aa 100644 --- a/src/main/java/auth_service/modules/profile/api/PublicProfileController.java +++ b/src/main/java/auth_service/modules/profile/api/PublicProfileController.java @@ -35,6 +35,7 @@ public class PublicProfileController { private static PublicProfileResponse.Style style(PublicProfile profile) { PublicProfile.Style style = profile.style(); return new PublicProfileResponse.Style(style.accent(), style.corners(), style.bannerAppid(), style.backgroundAppid(), - profile.tiles().stream().map(tile -> new PublicProfileResponse.Tile(tile.id(), tile.size())).toList()); + profile.tiles().stream().map(tile -> new PublicProfileResponse.Tile(tile.id(), tile.size())).toList(), + style.statCharts()); } } diff --git a/src/main/java/auth_service/modules/profile/api/dto/PublicProfileResponse.java b/src/main/java/auth_service/modules/profile/api/dto/PublicProfileResponse.java index 6893c9d..9b3c600 100644 --- a/src/main/java/auth_service/modules/profile/api/dto/PublicProfileResponse.java +++ b/src/main/java/auth_service/modules/profile/api/dto/PublicProfileResponse.java @@ -2,6 +2,7 @@ package auth_service.modules.profile.api.dto; import java.time.LocalDate; import java.util.List; +import java.util.Map; /** * @param ticket for Core's public profile endpoint; valid for a few minutes @@ -11,8 +12,10 @@ public record PublicProfileResponse(String displayName, LocalDate memberSince, S /** * @param tiles the shown parts of the profile in their order + * @param statCharts the owner's chart form per stat group */ - public record Style(String accent, String corners, Long bannerAppid, Long backgroundAppid, List tiles) {} + public record Style(String accent, String corners, Long bannerAppid, Long backgroundAppid, List tiles, + Map statCharts) {} public record Tile(String id, String size) {} } diff --git a/src/main/java/auth_service/modules/profile/model/PublicProfile.java b/src/main/java/auth_service/modules/profile/model/PublicProfile.java index ed24f66..12c5945 100644 --- a/src/main/java/auth_service/modules/profile/model/PublicProfile.java +++ b/src/main/java/auth_service/modules/profile/model/PublicProfile.java @@ -4,10 +4,11 @@ import auth_service.shared.security.Role; import java.time.LocalDate; import java.util.List; +import java.util.Map; public record PublicProfile(long userId, Role role, String displayName, LocalDate memberSince, Style style, List tiles) { - public record Style(String accent, String corners, Long bannerAppid, Long backgroundAppid) {} + public record Style(String accent, String corners, Long bannerAppid, Long backgroundAppid, Map statCharts) {} /** * @param id a part of the profile, or STATS_ and a stat group of Core diff --git a/src/main/java/auth_service/modules/profile/repo/PublicProfileRepository.java b/src/main/java/auth_service/modules/profile/repo/PublicProfileRepository.java index 5890951..880b44e 100644 --- a/src/main/java/auth_service/modules/profile/repo/PublicProfileRepository.java +++ b/src/main/java/auth_service/modules/profile/repo/PublicProfileRepository.java @@ -7,7 +7,9 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Optional; @Repository @@ -26,13 +28,25 @@ public class PublicProfileRepository { : new PublicProfile.Tile(stored.substring(0, colon), stored.substring(colon + 1)); } + // Stored as "GROUP:CHART", like user.model.StatCharts.parse, which this module may not use. + private static Map statCharts(String[] stored) { + Map charts = new LinkedHashMap<>(); + for (String entry : stored) { + int colon = entry.indexOf(':'); + if (colon >= 0) { + charts.put(entry.substring(0, colon), entry.substring(colon + 1)); + } + } + return charts; + } + /** * Only enabled, not deleted users who switched the public profile on. The name ignores case, like its unique index. */ public Optional findPublicByDisplayName(String displayName) { return jdbc.query(""" SELECT u.id, u.role, u.display_name, u.registration_date, s.profile_accent, s.profile_corners, - s.profile_banner_appid, s.profile_background_appid, s.profile_tiles + s.profile_banner_appid, s.profile_background_appid, s.profile_tiles, s.stat_charts FROM users u JOIN user_settings s ON s.user_id = u.id WHERE LOWER(u.display_name) = LOWER(?) AND %s AND s.public_profile """.formatted(ActiveUser.of("u")), @@ -41,7 +55,8 @@ public class PublicProfileRepository { ? rs.getTimestamp("registration_date").toLocalDateTime().toLocalDate() : null, new PublicProfile.Style(rs.getString("profile_accent"), rs.getString("profile_corners"), rs.getObject("profile_banner_appid", Long.class), - rs.getObject("profile_background_appid", Long.class)), + rs.getObject("profile_background_appid", Long.class), + statCharts((String[]) rs.getArray("stat_charts").getArray())), Arrays.stream((String[]) rs.getArray("profile_tiles").getArray()).map(PublicProfileRepository::tile).toList()), displayName).stream().findFirst(); } diff --git a/src/main/java/auth_service/modules/user/api/UserController.java b/src/main/java/auth_service/modules/user/api/UserController.java index ad5b852..e77e46d 100644 --- a/src/main/java/auth_service/modules/user/api/UserController.java +++ b/src/main/java/auth_service/modules/user/api/UserController.java @@ -6,6 +6,7 @@ import auth_service.modules.user.api.dto.request.DashboardTilesRequest; import auth_service.modules.user.api.dto.request.FavoritePlatformRequest; import auth_service.modules.user.api.dto.request.ProfileTilesRequest; import auth_service.modules.user.api.dto.request.ProfileStyleRequest; +import auth_service.modules.user.api.dto.request.StatChartRequest; import auth_service.modules.user.api.dto.request.UserSettingsRequest; import auth_service.modules.user.api.dto.response.UserProfileResponse; import auth_service.modules.user.api.mapper.ApiMapper; @@ -57,6 +58,13 @@ public class UserController { return ResponseEntity.noContent().build(); } + @PutMapping(value = "/settings/stat-chart") + public ResponseEntity updateStatChart(@AuthenticationPrincipal UserPrincipal principal, + @Valid @RequestBody StatChartRequest request) { + this.userService.updateStatChart(principal.getIdAsLong(), request.group(), request.chart()); + return ResponseEntity.noContent().build(); + } + @PutMapping(value = "/settings/favorite-platform") public ResponseEntity updateFavoritePlatform(@AuthenticationPrincipal UserPrincipal principal, @Valid @RequestBody FavoritePlatformRequest request) { diff --git a/src/main/java/auth_service/modules/user/api/dto/request/StatChartRequest.java b/src/main/java/auth_service/modules/user/api/dto/request/StatChartRequest.java new file mode 100644 index 0000000..78526a8 --- /dev/null +++ b/src/main/java/auth_service/modules/user/api/dto/request/StatChartRequest.java @@ -0,0 +1,21 @@ +package auth_service.modules.user.api.dto.request; + +import auth_service.modules.user.model.StatCharts; +import com.fasterxml.jackson.annotation.JsonIgnore; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; + +/** + * @param group a stat group of the Frontend + * @param chart the form for the group; null puts the group back to its default + */ +public record StatChartRequest(@NotNull @Pattern(regexp = "[A-Z_]{1,20}") String group, + @Pattern(regexp = "[A-Z]{1,10}") String chart) { + + @AssertTrue(message = "Unknown stat group or chart") + @JsonIgnore + public boolean isKnownChoice() { + return group == null || (chart == null ? StatCharts.knows(group) : StatCharts.allows(group, chart)); + } +} diff --git a/src/main/java/auth_service/modules/user/api/dto/response/SettingsResponse.java b/src/main/java/auth_service/modules/user/api/dto/response/SettingsResponse.java index e990ac3..b4b8e66 100644 --- a/src/main/java/auth_service/modules/user/api/dto/response/SettingsResponse.java +++ b/src/main/java/auth_service/modules/user/api/dto/response/SettingsResponse.java @@ -4,10 +4,11 @@ import lombok.Builder; import java.time.Instant; import java.util.List; +import java.util.Map; @Builder public record SettingsResponse(long id, boolean useDarkTheme, String language, boolean publicProfile, AppearanceResponse appearance, ProfileStyleResponse profileStyle, List dashboardTiles, List profileTiles, - String favoritePlatform, Instant welcomeSeenAt) { + Map statCharts, String favoritePlatform, Instant welcomeSeenAt) { } diff --git a/src/main/java/auth_service/modules/user/api/mapper/ApiMapper.java b/src/main/java/auth_service/modules/user/api/mapper/ApiMapper.java index f78e2aa..3949bcf 100644 --- a/src/main/java/auth_service/modules/user/api/mapper/ApiMapper.java +++ b/src/main/java/auth_service/modules/user/api/mapper/ApiMapper.java @@ -47,6 +47,7 @@ public abstract class ApiMapper { .dashboardTiles(user.getSettings().getDashboardTiles() == null ? null : toApi(user.getSettings().getDashboardTiles())) .profileTiles(toApi(user.getSettings().getProfileTiles())) + .statCharts(user.getSettings().getStatCharts()) .favoritePlatform(user.getSettings().getFavoritePlatform()) .welcomeSeenAt(user.getSettings().getWelcomeSeenAt()) .build()) diff --git a/src/main/java/auth_service/modules/user/model/StatCharts.java b/src/main/java/auth_service/modules/user/model/StatCharts.java new file mode 100644 index 0000000..94608f1 --- /dev/null +++ b/src/main/java/auth_service/modules/user/model/StatCharts.java @@ -0,0 +1,51 @@ +package auth_service.modules.user.model; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * The chart form a user picked per stat group. Keep the allowed forms in line with the Frontend + * {@code utils/statCharts.js}. + */ +public final class StatCharts { + + private static final Map> ALLOWED = Map.of( + "TREND", Set.of("LINE", "BARS"), + "FINISHED", Set.of("BARS", "LINE", "LIST"), + "TASTE", Set.of("BARS", "PIE", "DONUT", "LIST"), + "PLATFORMS", Set.of("BARS", "PIE", "DONUT", "LIST")); + + private StatCharts() { + } + + public static boolean knows(String group) { + return ALLOWED.containsKey(group); + } + + public static boolean allows(String group, String chart) { + return ALLOWED.getOrDefault(group, Set.of()).contains(chart); + } + + /** + * @param stored "GROUP:CHART" entries, as the column holds them; an entry without ":" is skipped + */ + public static Map parse(List stored) { + Map charts = new LinkedHashMap<>(); + for (String entry : stored) { + int colon = entry.indexOf(':'); + if (colon >= 0) { + charts.put(entry.substring(0, colon), entry.substring(colon + 1)); + } + } + return charts; + } + + public static ArrayList stored(Map charts) { + ArrayList entries = new ArrayList<>(); + charts.forEach((group, chart) -> entries.add(group + ":" + chart)); + return entries; + } +} diff --git a/src/main/java/auth_service/modules/user/model/UserSettings.java b/src/main/java/auth_service/modules/user/model/UserSettings.java index 44a1c26..9312878 100644 --- a/src/main/java/auth_service/modules/user/model/UserSettings.java +++ b/src/main/java/auth_service/modules/user/model/UserSettings.java @@ -5,6 +5,7 @@ import lombok.Data; import java.time.Instant; import java.util.List; +import java.util.Map; @Data @Builder @@ -20,6 +21,9 @@ public class UserSettings { private List dashboardTiles; @Builder.Default private List profileTiles = DEFAULT_PROFILE_TILES; + /** Stat group to chart form; a group not in it shows its default. */ + @Builder.Default + private Map statCharts = Map.of(); private String favoritePlatform; /** Null: the welcome page was not seen yet. */ private Instant welcomeSeenAt; diff --git a/src/main/java/auth_service/modules/user/persistence/entities/UserSettingsEntity.java b/src/main/java/auth_service/modules/user/persistence/entities/UserSettingsEntity.java index 429233d..65cebb6 100644 --- a/src/main/java/auth_service/modules/user/persistence/entities/UserSettingsEntity.java +++ b/src/main/java/auth_service/modules/user/persistence/entities/UserSettingsEntity.java @@ -77,6 +77,11 @@ public class UserSettingsEntity { @Column(name = "profile_tiles", nullable = false) private List profileTiles; + /** "GROUP:CHART" entries, see {@link auth_service.modules.user.model.StatCharts}. */ + @JdbcTypeCode(SqlTypes.ARRAY) + @Column(name = "stat_charts", nullable = false) + private List statCharts; + @Column(name = "welcome_seen_at") private Instant welcomeSeenAt; } diff --git a/src/main/java/auth_service/modules/user/persistence/mapper/EntityMapper.java b/src/main/java/auth_service/modules/user/persistence/mapper/EntityMapper.java index da986e6..3c6acef 100644 --- a/src/main/java/auth_service/modules/user/persistence/mapper/EntityMapper.java +++ b/src/main/java/auth_service/modules/user/persistence/mapper/EntityMapper.java @@ -2,6 +2,7 @@ package auth_service.modules.user.persistence.mapper; import auth_service.modules.user.model.Appearance; import auth_service.modules.user.model.ProfileStyle; +import auth_service.modules.user.model.StatCharts; import auth_service.modules.user.model.Tile; import auth_service.modules.user.model.User; import auth_service.modules.user.model.UserSettings; @@ -35,6 +36,7 @@ public abstract class EntityMapper { .dashboardTiles(settings.getDashboardTiles() == null ? null : settings.getDashboardTiles().stream().map(Tile::parse).toList()) .profileTiles(settings.getProfileTiles().stream().map(Tile::parse).toList()) + .statCharts(StatCharts.parse(settings.getStatCharts())) .favoritePlatform(settings.getFavoritePlatform()) .welcomeSeenAt(settings.getWelcomeSeenAt()) .build(); @@ -75,6 +77,7 @@ public abstract class EntityMapper { target.setDashboardTiles(settings.getDashboardTiles() == null ? null : Tile.storedAll(settings.getDashboardTiles())); target.setProfileTiles(Tile.storedAll(settings.getProfileTiles())); + target.setStatCharts(StatCharts.stored(settings.getStatCharts())); target.setFavoritePlatform(settings.getFavoritePlatform()); target.setWelcomeSeenAt(settings.getWelcomeSeenAt()); } diff --git a/src/main/java/auth_service/modules/user/service/UserService.java b/src/main/java/auth_service/modules/user/service/UserService.java index 28da290..964ceb5 100644 --- a/src/main/java/auth_service/modules/user/service/UserService.java +++ b/src/main/java/auth_service/modules/user/service/UserService.java @@ -23,7 +23,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.Instant; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Optional; @Slf4j @@ -126,6 +128,24 @@ public class UserService { userRepository.update(userId, user -> user.getSettings().setProfileTiles(Tile.distinct(tiles))); } + /** + * @param chart null puts the group back to its default form + */ + @RequiresRole(Role.USER) + @Transactional + @CacheEvict(value = "users", key = "#userId") + public void updateStatChart(long userId, String group, String chart) { + userRepository.update(userId, user -> { + Map charts = new LinkedHashMap<>(user.getSettings().getStatCharts()); + if (chart == null) { + charts.remove(group); + } else { + charts.put(group, chart); + } + user.getSettings().setStatCharts(charts); + }); + } + @RequiresRole(Role.USER) @Transactional @CacheEvict(value = "users", key = "#userId") diff --git a/src/main/resources/db/migration/V31__Stat_Charts.sql b/src/main/resources/db/migration/V31__Stat_Charts.sql new file mode 100644 index 0000000..e8665dc --- /dev/null +++ b/src/main/resources/db/migration/V31__Stat_Charts.sql @@ -0,0 +1,2 @@ +-- GROUP:CHART per changed stat group; a group not listed shows its default form +ALTER TABLE user_settings ADD COLUMN stat_charts VARCHAR(32)[] NOT NULL DEFAULT '{}'; diff --git a/src/test/java/auth_service/modules/profile/api/PublicProfileControllerTest.java b/src/test/java/auth_service/modules/profile/api/PublicProfileControllerTest.java index 18ba74a..c117aab 100644 --- a/src/test/java/auth_service/modules/profile/api/PublicProfileControllerTest.java +++ b/src/test/java/auth_service/modules/profile/api/PublicProfileControllerTest.java @@ -102,6 +102,23 @@ class PublicProfileControllerTest extends AbstractIntegrationTest { .andExpect(jsonPath("$.style.tiles[1].size").value("L")); } + @Test + void publicProfile_AnswersWithTheOwnersStatCharts() throws Exception { + jdbc.update("update user_settings set stat_charts = '{PLATFORMS:DONUT,TREND:BARS}' where user_id = ?", + publicUserId); + + mockMvc.perform(get("/api/public/profiles/Lobo")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.style.statCharts.PLATFORMS").value("DONUT")) + .andExpect(jsonPath("$.style.statCharts.TREND").value("BARS")); + } + + @Test + void publicProfile_NewRowHasNoStatCharts() throws Exception { + mockMvc.perform(get("/api/public/profiles/Lobo")) + .andExpect(jsonPath("$.style.statCharts").isEmpty()); + } + @Test void publicProfile_NewRowShowsTheDefaultTiles() throws Exception { mockMvc.perform(get("/api/public/profiles/Lobo")) diff --git a/src/test/java/auth_service/modules/user/api/UserControllerTest.java b/src/test/java/auth_service/modules/user/api/UserControllerTest.java index 01f6669..ac1fe9a 100644 --- a/src/test/java/auth_service/modules/user/api/UserControllerTest.java +++ b/src/test/java/auth_service/modules/user/api/UserControllerTest.java @@ -36,6 +36,7 @@ import org.springframework.test.web.servlet.MockMvc; import java.time.Instant; import java.util.Collections; import java.util.List; +import java.util.Map; import static org.hamcrest.Matchers.nullValue; import static org.mockito.ArgumentMatchers.*; @@ -369,7 +370,8 @@ class UserControllerTest extends AbstractWebMvcTest { String email = "test@example.com"; UserSettings settings = UserSettings.builder().language("en").theme("dark") .dashboardTiles(List.of(new Tile("STATS_TREND", "L"))) - .profileTiles(List.of(new Tile("PLAYING", "S"))).favoritePlatform("PS5").build(); + .profileTiles(List.of(new Tile("PLAYING", "S"))).favoritePlatform("PS5") + .statCharts(Map.of("PLATFORMS", "PIE")).build(); SecurityContextHolder.getContext().setAuthentication( new UsernamePasswordAuthenticationToken(new UserPrincipal("1", null), null, Collections.emptyList()) ); @@ -381,7 +383,8 @@ class UserControllerTest extends AbstractWebMvcTest { .andExpect(jsonPath("$.settings.dashboardTiles[0].size").value("L")) .andExpect(jsonPath("$.settings.profileTiles[0].id").value("PLAYING")) .andExpect(jsonPath("$.settings.profileTiles[0].size").value("S")) - .andExpect(jsonPath("$.settings.favoritePlatform").value("PS5")); + .andExpect(jsonPath("$.settings.favoritePlatform").value("PS5")) + .andExpect(jsonPath("$.settings.statCharts.PLATFORMS").value("PIE")); } @Test @@ -459,6 +462,47 @@ class UserControllerTest extends AbstractWebMvcTest { verify(userService).updateDashboardTiles(1L, null); } + @Test + void updateStatChart_StoresOrClearsTheChoice() throws Exception { + SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken( + new UserPrincipal("1", null), null, Collections.emptyList())); + + mockMvc.perform(put("/api/users/settings/stat-chart") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"group\":\"PLATFORMS\",\"chart\":\"PIE\"}")) + .andExpect(status().isNoContent()); + mockMvc.perform(put("/api/users/settings/stat-chart") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"group\":\"FINISHED\",\"chart\":null}")) + .andExpect(status().isNoContent()); + + verify(userService).updateStatChart(1L, "PLATFORMS", "PIE"); + verify(userService).updateStatChart(1L, "FINISHED", null); + } + + @ParameterizedTest + @ValueSource(strings = { + "{\"group\":\"TASTE\",\"chart\":\"LINE\"}", + "{\"group\":\"TOP_GAMES\",\"chart\":\"LIST\"}", + "{\"group\":\"TOP_GAMES\",\"chart\":null}", + "{\"group\":\"UNKNOWN\",\"chart\":\"BARS\"}", + "{\"group\":\"platforms\",\"chart\":\"PIE\"}", + "{\"group\":\"PLATFORMS\",\"chart\":\"RADAR\"}", + "{\"chart\":\"PIE\"}", + "{}" + }) + void updateStatChart_UnknownGroupOrChart_Is400(String body) throws Exception { + SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken( + new UserPrincipal("1", null), null, Collections.emptyList())); + + mockMvc.perform(put("/api/users/settings/stat-chart") + .contentType(MediaType.APPLICATION_JSON) + .content(body)) + .andExpect(status().isBadRequest()); + + verifyNoInteractions(userService); + } + @Test void updateFavoritePlatform_StoresOrClearsIt() throws Exception { SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken( diff --git a/src/test/java/auth_service/modules/user/model/StatChartsTest.java b/src/test/java/auth_service/modules/user/model/StatChartsTest.java new file mode 100644 index 0000000..f70dbe4 --- /dev/null +++ b/src/test/java/auth_service/modules/user/model/StatChartsTest.java @@ -0,0 +1,54 @@ +package auth_service.modules.user.model; + +import auth_service.shared.test.AbstractUnitTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class StatChartsTest extends AbstractUnitTest { + + @ParameterizedTest + @CsvSource({"TREND,LINE", "TREND,BARS", "FINISHED,BARS", "FINISHED,LINE", "FINISHED,LIST", "TASTE,BARS", + "TASTE,PIE", "TASTE,DONUT", "TASTE,LIST", "PLATFORMS,BARS", "PLATFORMS,PIE", "PLATFORMS,DONUT", + "PLATFORMS,LIST"}) + void allows_EachAllowedPair(String group, String chart) { + assertTrue(StatCharts.allows(group, chart)); + } + + @ParameterizedTest + @CsvSource({"TASTE,LINE", "TOP_GAMES,LIST", "UNKNOWN,BARS", "platforms,PIE", "PLATFORMS,pie", "PLATFORMS,RADAR", + "TREND,PIE"}) + void allows_OtherPairs_AreRefused(String group, String chart) { + assertFalse(StatCharts.allows(group, chart)); + } + + @Test + void knows_OnlyTheChartableGroups() { + assertTrue(StatCharts.knows("PLATFORMS")); + assertFalse(StatCharts.knows("TOP_GAMES")); + assertFalse(StatCharts.knows("platforms")); + } + + @Test + void parseAndStored_RoundTripKeepsTheOrder() { + List stored = List.of("TREND:BARS", "PLATFORMS:PIE", "FINISHED:LINE"); + + Map parsed = StatCharts.parse(stored); + + assertEquals(stored, List.copyOf(StatCharts.stored(parsed))); + assertEquals("PIE", parsed.get("PLATFORMS")); + assertEquals(List.of("TREND", "PLATFORMS", "FINISHED"), List.copyOf(parsed.keySet())); + } + + @Test + void parse_SkipsAnEntryWithoutColon() { + assertEquals(Map.of("TREND", "BARS"), StatCharts.parse(List.of("BROKEN", "TREND:BARS"))); + } +} diff --git a/src/test/java/auth_service/modules/user/persistence/UserRepositoryTest.java b/src/test/java/auth_service/modules/user/persistence/UserRepositoryTest.java index 3b40abd..26a8e28 100644 --- a/src/test/java/auth_service/modules/user/persistence/UserRepositoryTest.java +++ b/src/test/java/auth_service/modules/user/persistence/UserRepositoryTest.java @@ -17,7 +17,9 @@ import org.springframework.jdbc.core.JdbcTemplate; import java.time.Instant; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import static org.junit.jupiter.api.Assertions.*; @@ -318,6 +320,29 @@ class UserRepositoryTest extends AbstractPersistenceTest { assertEquals(profile, saved.getProfileTiles()); } + @Test + void updateStatCharts_StartEmptyAreReadBackAndKeptByUpdateSettings() { + UserEntity userEntity = persisted(UserEntity.builder() + .email("charts@example.com") + .password("password") + .enabled(true) + .registrationDate(LocalDateTime.now()) + .build()); + assertEquals(Map.of(), findByEmail("charts@example.com").orElseThrow().getSettings().getStatCharts()); + + Map charts = new LinkedHashMap<>(); + charts.put("TREND", "BARS"); + charts.put("PLATFORMS", "PIE"); + userRepository.update(userEntity.getId(), user -> user.getSettings().setStatCharts(charts)); + entityManager.flush(); + updateSettings(userEntity.getId(), null, + UserSettings.builder().language("de").theme("dark").build()); + entityManager.flush(); + entityManager.clear(); + + assertEquals(charts, findByEmail("charts@example.com").orElseThrow().getSettings().getStatCharts()); + } + @Test void updateFavoritePlatform_StoresAndClears() { UserEntity userEntity = persisted(UserEntity.builder() diff --git a/src/test/java/auth_service/modules/user/service/UserServiceTest.java b/src/test/java/auth_service/modules/user/service/UserServiceTest.java index 2ad9a0e..f00ae25 100644 --- a/src/test/java/auth_service/modules/user/service/UserServiceTest.java +++ b/src/test/java/auth_service/modules/user/service/UserServiceTest.java @@ -22,6 +22,7 @@ import org.mockito.Mock; import java.time.Duration; import java.time.Instant; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.function.Consumer; @@ -76,6 +77,31 @@ class UserServiceTest extends AbstractUnitTest { assertEquals(List.of(new Tile("SUMMARY", "M")), changed.get(1).getSettings().getProfileTiles()); } + @Test + void updateStatChart_PutsTheFormOfTheGroup() { + userService.updateStatChart(1L, "PLATFORMS", "PIE"); + userService.updateStatChart(1L, "TREND", "BARS"); + + List changed = appliedChanges(2); + assertEquals(Map.of("PLATFORMS", "PIE"), changed.get(0).getSettings().getStatCharts()); + assertEquals(Map.of("TREND", "BARS"), changed.get(1).getSettings().getStatCharts()); + } + + @Test + void updateStatChart_KeepsOtherGroupsAndNullRemovesTheEntry() { + UserSettings settings = UserSettings.builder().statCharts(Map.of("PLATFORMS", "PIE", "TREND", "BARS")).build(); + User user = User.builder().id(1L).settings(settings).build(); + userService.updateStatChart(1L, "PLATFORMS", null); + userService.updateStatChart(1L, "FINISHED", "LINE"); + + ArgumentCaptor> change = ArgumentCaptor.forClass(Consumer.class); + verify(userRepository, times(2)).update(eq(1L), change.capture()); + change.getAllValues().get(0).accept(user); + assertEquals(Map.of("TREND", "BARS"), user.getSettings().getStatCharts()); + change.getAllValues().get(1).accept(user); + assertEquals(Map.of("TREND", "BARS", "FINISHED", "LINE"), user.getSettings().getStatCharts()); + } + @Test void updateDashboardTiles_NullStaysNull() { userService.updateDashboardTiles(1L, null);