feat(profile): store the public profile look

This commit is contained in:
lobo committed 2026-09-28 14:20:14 +02:00
1 parent f86f3c2e63
commit dd4fcd1546
27 files changed
+308 -15

No files matched your search

@@ -83,13 +83,14 @@ public class JwtService implements TokenGenerator, ProfileTicketIssuer, ServiceT
}
@Override
public String generateProfileTicket(long userId, List<String> profileStats) {
public String generateProfileTicket(long userId, List<String> profileStats, List<String> sections) {
return Jwts.builder()
.header().keyId(jwtConfig.getKid()).and()
.subject(PROFILE_TICKET_TYPE)
.claim(TYPE_CLAIM, PROFILE_TICKET_TYPE)
.claim("profileUserId", String.valueOf(userId))
.claim("profileStats", profileStats)
.claim("sections", sections)
.issuedAt(new Date(System.currentTimeMillis()))
.expiration(new Date(System.currentTimeMillis() + PROFILE_TICKET_EXPIRATION_MS))
.signWith(privateKey, Jwts.SIG.EdDSA)
@@ -1,6 +1,7 @@
package auth_service.modules.profile.api;
import auth_service.modules.profile.api.dto.PublicProfileResponse;
import auth_service.modules.profile.model.PublicProfile;
import auth_service.modules.profile.service.PublicProfileService;
import auth_service.shared.api.dto.response.ErrorResponse;
import lombok.RequiredArgsConstructor;
@@ -26,8 +27,13 @@ public class PublicProfileController {
public ResponseEntity<?> getPublicProfile(@PathVariable("displayName") String displayName) {
return publicProfileService.findPublicProfile(displayName)
.<ResponseEntity<?>>map(p -> ResponseEntity.ok(new PublicProfileResponse(
p.profile().displayName(), p.profile().memberSince(), p.ticket())))
p.profile().displayName(), p.profile().memberSince(), p.ticket(), style(p.profile().style()))))
.orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse("PROFILE_NOT_FOUND", "No public profile with this name")));
}
private static PublicProfileResponse.Style style(PublicProfile.Style style) {
return new PublicProfileResponse.Style(style.accent(), style.corners(), style.bannerAppid(),
style.backgroundAppid(), style.sections());
}
}
@@ -1,8 +1,16 @@
package auth_service.modules.profile.api.dto;
import java.time.LocalDate;
import java.util.List;
/**
* @param ticket for Core's public profile endpoint; valid for a few minutes
* @param style the look the owner picked for the profile
*/
public record PublicProfileResponse(String displayName, LocalDate memberSince, String ticket) {}
public record PublicProfileResponse(String displayName, LocalDate memberSince, String ticket, Style style) {
/**
* @param sections the parts of the profile in their order; a part not listed is hidden
*/
public record Style(String accent, String corners, Long bannerAppid, Long backgroundAppid, List<String> sections) {}
}
@@ -3,4 +3,8 @@ package auth_service.modules.profile.model;
import java.time.LocalDate;
import java.util.List;
public record PublicProfile(long userId, String displayName, LocalDate memberSince, List<String> profileStats) {}
public record PublicProfile(long userId, String displayName, LocalDate memberSince, List<String> profileStats,
Style style) {
public record Style(String accent, String corners, Long bannerAppid, Long backgroundAppid, List<String> sections) {}
}
@@ -21,14 +21,19 @@ public class PublicProfileRepository {
*/
public Optional<PublicProfile> findPublicByDisplayName(String displayName) {
return jdbc.query("""
SELECT u.id, u.display_name, u.registration_date, s.profile_stats
SELECT u.id, u.display_name, u.registration_date, s.profile_stats, s.profile_accent,
s.profile_corners, s.profile_banner_appid, s.profile_background_appid, s.profile_sections
FROM users u JOIN user_settings s ON s.user_id = u.id
WHERE LOWER(u.display_name) = LOWER(?) AND u.enabled AND u.deleted_at IS NULL AND s.public_profile
""",
(rs, row) -> new PublicProfile(rs.getLong("id"), rs.getString("display_name"),
rs.getTimestamp("registration_date") != null
? rs.getTimestamp("registration_date").toLocalDateTime().toLocalDate() : null,
List.of((String[]) rs.getArray("profile_stats").getArray())),
List.of((String[]) rs.getArray("profile_stats").getArray()),
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),
List.of((String[]) rs.getArray("profile_sections").getArray()))),
displayName).stream().findFirst();
}
}
@@ -24,6 +24,7 @@ public class PublicProfileService {
*/
public Optional<TicketedProfile> findPublicProfile(String displayName) {
return publicProfileRepository.findPublicByDisplayName(displayName)
.map(profile -> new TicketedProfile(profile, profileTicketIssuer.generateProfileTicket(profile.userId(), profile.profileStats())));
.map(profile -> new TicketedProfile(profile, profileTicketIssuer.generateProfileTicket(
profile.userId(), profile.profileStats(), profile.style().sections())));
}
}
@@ -3,6 +3,7 @@ package auth_service.modules.user.api;
import auth_service.shared.security.UserPrincipal;
import auth_service.modules.user.api.dto.request.AppearanceRequest;
import auth_service.modules.user.api.dto.request.ProfileStatsRequest;
import auth_service.modules.user.api.dto.request.ProfileStyleRequest;
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;
@@ -53,6 +54,13 @@ public class UserController {
return ResponseEntity.noContent().build();
}
@PutMapping(value = "/settings/profile-style", version = "1.0")
public ResponseEntity<Void> updateProfileStyle(@AuthenticationPrincipal UserPrincipal principal,
@Valid @RequestBody ProfileStyleRequest request) {
this.userService.updateProfileStyle(principal.getIdAsLong(), ApiMapper.toDomain(request));
return ResponseEntity.noContent().build();
}
@PutMapping(value = "/settings/appearance", version = "1.0")
public ResponseEntity<Void> updateAppearance(@AuthenticationPrincipal UserPrincipal principal,
@Valid @RequestBody AppearanceRequest request) {
@@ -8,10 +8,13 @@ import jakarta.validation.constraints.Positive;
* The frontend has a CSS preset for each allowed name.
*/
public record AppearanceRequest(
@NotNull @Pattern(regexp = "violet|blue|green|orange|pink|red") String accent,
@NotNull @Pattern(regexp = "sharp|default|round") String corners,
@NotNull @Pattern(regexp = AppearanceRequest.ACCENTS) String accent,
@NotNull @Pattern(regexp = AppearanceRequest.CORNERS) String corners,
Boolean wideLayout,
@Positive Long backgroundAppid,
@Positive Long bannerAppid,
Boolean showBanner) {
public static final String ACCENTS = "violet|blue|green|orange|pink|red";
public static final String CORNERS = "sharp|default|round";
}
@@ -0,0 +1,18 @@
package auth_service.modules.user.api.dto.request;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Positive;
import java.util.List;
/**
* @param sections the parts of the public profile in their order; a part not listed is hidden
*/
public record ProfileStyleRequest(
@NotNull @Pattern(regexp = AppearanceRequest.ACCENTS) String accent,
@NotNull @Pattern(regexp = AppearanceRequest.CORNERS) String corners,
@Positive Long bannerAppid,
@Positive Long backgroundAppid,
@NotNull List<@NotNull @Pattern(regexp = "SUMMARY|STATUS|PLAYING|FINISHED|STATS") String> sections) {
}
@@ -0,0 +1,7 @@
package auth_service.modules.user.api.dto.response;
import java.util.List;
public record ProfileStyleResponse(String accent, String corners, Long bannerAppid, Long backgroundAppid,
List<String> sections) {
}
@@ -6,5 +6,6 @@ import java.util.List;
@Builder
public record SettingsResponse(long id, boolean useDarkTheme, String language, boolean publicProfile,
AppearanceResponse appearance, List<String> profileStats) {
AppearanceResponse appearance, List<String> profileStats,
ProfileStyleResponse profileStyle) {
}
@@ -2,11 +2,14 @@ package auth_service.modules.user.api.mapper;
import auth_service.modules.user.api.dto.request.AppearanceRequest;
import auth_service.modules.user.api.dto.request.ProfileStyleRequest;
import auth_service.modules.user.api.dto.request.UserSettingsRequest;
import auth_service.modules.user.api.dto.response.AppearanceResponse;
import auth_service.modules.user.api.dto.response.ProfileStyleResponse;
import auth_service.modules.user.api.dto.response.SettingsResponse;
import auth_service.modules.user.api.dto.response.UserProfileResponse;
import auth_service.modules.user.model.Appearance;
import auth_service.modules.user.model.ProfileStyle;
import auth_service.modules.user.model.User;
import auth_service.modules.user.model.UserSettings;
import auth_service.shared.security.Role;
@@ -36,6 +39,7 @@ public abstract class ApiMapper {
.publicProfile(user.getSettings().isPublicProfile())
.appearance(toApi(user.getSettings().getAppearance()))
.profileStats(user.getSettings().getProfileStats())
.profileStyle(toApi(user.getSettings().getProfileStyle()))
.build())
.build();
}
@@ -45,6 +49,16 @@ public abstract class ApiMapper {
appearance.backgroundAppid(), appearance.bannerAppid(), appearance.showBanner());
}
private static ProfileStyleResponse toApi(ProfileStyle style) {
return new ProfileStyleResponse(style.accent(), style.corners(), style.bannerAppid(), style.backgroundAppid(),
style.sections());
}
public static ProfileStyle toDomain(ProfileStyleRequest request) {
return new ProfileStyle(request.accent(), request.corners(), request.bannerAppid(), request.backgroundAppid(),
request.sections());
}
public static Appearance toDomain(AppearanceRequest request) {
// A missing showBanner keeps the banner on, the default.
return new Appearance(request.accent(), request.corners(), Boolean.TRUE.equals(request.wideLayout()),
@@ -0,0 +1,26 @@
package auth_service.modules.user.model;
import java.util.List;
/**
* The look of the public profile, apart from the {@link Appearance} of the app.
*
* @param bannerAppid the Steam app whose art is the banner of the profile; null for none
* @param backgroundAppid the Steam app whose art fills the background of the profile; null for none
* @param sections the parts of the profile in their order; a part not listed is hidden
*/
public record ProfileStyle(String accent, String corners, Long bannerAppid, Long backgroundAppid,
List<String> sections) {
public static final List<String> ALL_SECTIONS = List.of("SUMMARY", "STATUS", "PLAYING", "FINISHED", "STATS");
public static final ProfileStyle DEFAULT = new ProfileStyle(Appearance.DEFAULT.accent(), Appearance.DEFAULT.corners(),
null, null, ALL_SECTIONS);
/**
* Keeps each section once, at its first place.
*/
public ProfileStyle {
sections = sections.stream().distinct().toList();
}
}
@@ -13,6 +13,8 @@ public class UserSettings {
private boolean publicProfile;
@Builder.Default
private Appearance appearance = Appearance.DEFAULT;
@Builder.Default
private ProfileStyle profileStyle = ProfileStyle.DEFAULT;
/** The stat groups of Core the public profile shows. */
@Builder.Default
private List<String> profileStats = List.of();
@@ -2,6 +2,7 @@ package auth_service.modules.user.persistence;
import auth_service.modules.user.model.Appearance;
import auth_service.modules.user.model.ProfileStyle;
import auth_service.modules.user.model.SteamIdHolder;
import auth_service.modules.user.model.User;
import auth_service.modules.user.model.UserSettings;
@@ -34,6 +35,8 @@ public interface UserRepository {
void updateProfileStats(long userId, List<String> groups);
void updateProfileStyle(long userId, ProfileStyle style);
Optional<SteamIdHolder> findSteamIdHolder(long steamId);
/**
@@ -2,6 +2,7 @@ package auth_service.modules.user.persistence;
import auth_service.modules.user.exception.UserNotFoundException;
import auth_service.modules.user.model.Appearance;
import auth_service.modules.user.model.ProfileStyle;
import auth_service.modules.user.model.SteamIdHolder;
import auth_service.modules.user.model.User;
import auth_service.modules.user.model.UserSettings;
@@ -103,6 +104,17 @@ public class UserRepositoryImpl implements UserRepository {
settingsOf(userId).setProfileStats(new ArrayList<>(groups));
}
@Transactional
@Override
public void updateProfileStyle(long userId, ProfileStyle style) {
UserSettingsEntity settingsEntity = settingsOf(userId);
settingsEntity.setProfileAccent(style.accent());
settingsEntity.setProfileCorners(style.corners());
settingsEntity.setProfileBannerAppid(style.bannerAppid());
settingsEntity.setProfileBackgroundAppid(style.backgroundAppid());
settingsEntity.setProfileSections(new ArrayList<>(style.sections()));
}
/**
* The settings row of the user, created with the defaults of the user_settings columns when there is none.
*/
@@ -1,6 +1,7 @@
package auth_service.modules.user.persistence.entities;
import auth_service.modules.user.model.Appearance;
import auth_service.modules.user.model.ProfileStyle;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
@@ -55,4 +56,20 @@ public class UserSettingsEntity {
@JdbcTypeCode(SqlTypes.ARRAY)
@Column(name = "profile_stats", nullable = false)
private List<String> profileStats = new ArrayList<>();
@Column(name = "profile_accent", nullable = false)
private String profileAccent = ProfileStyle.DEFAULT.accent();
@Column(name = "profile_corners", nullable = false)
private String profileCorners = ProfileStyle.DEFAULT.corners();
@Column(name = "profile_banner_appid")
private Long profileBannerAppid;
@Column(name = "profile_background_appid")
private Long profileBackgroundAppid;
@JdbcTypeCode(SqlTypes.ARRAY)
@Column(name = "profile_sections", nullable = false)
private List<String> profileSections = new ArrayList<>(ProfileStyle.ALL_SECTIONS);
}
@@ -1,6 +1,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.User;
import auth_service.modules.user.model.UserSettings;
import auth_service.modules.user.persistence.entities.UserEntity;
@@ -30,6 +31,10 @@ public abstract class EntityMapper {
entity.getSettings().isWideLayout(), entity.getSettings().getBackgroundAppid(),
entity.getSettings().getBannerAppid(), entity.getSettings().isShowBanner()))
.profileStats(List.copyOf(entity.getSettings().getProfileStats()))
.profileStyle(new ProfileStyle(entity.getSettings().getProfileAccent(),
entity.getSettings().getProfileCorners(), entity.getSettings().getProfileBannerAppid(),
entity.getSettings().getProfileBackgroundAppid(),
List.copyOf(entity.getSettings().getProfileSections())))
.build();
} else {
// Fallback default settings for users without an entry in the database
@@ -3,6 +3,7 @@ package auth_service.modules.user.service;
import auth_service.modules.user.exception.DisplayNameAlreadyInUseException;
import auth_service.modules.user.exception.UserNotFoundException;
import auth_service.modules.user.model.Appearance;
import auth_service.modules.user.model.ProfileStyle;
import auth_service.modules.user.model.SteamIdHolder;
import auth_service.modules.user.model.SteamLinkOutcome;
import auth_service.modules.user.model.User;
@@ -105,4 +106,11 @@ public class UserService {
public void updateProfileStats(long userId, List<String> groups) {
userRepository.updateProfileStats(userId, groups.stream().distinct().toList());
}
@RequiresRole(Role.USER)
@Transactional
@CacheEvict(value = "usersByEmail", allEntries = true)
public void updateProfileStyle(long userId, ProfileStyle style) {
userRepository.updateProfileStyle(userId, style);
}
}
@@ -9,6 +9,7 @@ import java.util.List;
public interface ProfileTicketIssuer {
/**
* @param profileStats the stat groups of Core the profile shows
* @param sections the parts of the profile Core sends data for
*/
String generateProfileTicket(long userId, List<String> profileStats);
String generateProfileTicket(long userId, List<String> profileStats, List<String> sections);
}
@@ -0,0 +1,10 @@
-- The look of the public profile, apart from the app appearance. A profile starts with the accent and corners the
-- user has in the app. The sections are the parts of the profile page in their order; a part not listed is hidden.
ALTER TABLE user_settings
ADD COLUMN profile_accent VARCHAR(16) NOT NULL DEFAULT 'violet',
ADD COLUMN profile_corners VARCHAR(16) NOT NULL DEFAULT 'default',
ADD COLUMN profile_banner_appid BIGINT,
ADD COLUMN profile_background_appid BIGINT,
ADD COLUMN profile_sections VARCHAR(16)[] NOT NULL DEFAULT '{SUMMARY,STATUS,PLAYING,FINISHED,STATS}';
UPDATE user_settings SET profile_accent = accent, profile_corners = corners;
@@ -110,7 +110,7 @@ class JwtServiceTest extends AbstractUnitTest {
@Test
void generateProfileTicket_NamesTheUserAndExpiresSoon() {
var ticket = jwtService.generateProfileTicket(42L, List.of());
var ticket = jwtService.generateProfileTicket(42L, List.of(), List.of());
var claims = Jwts.parser().verifyWith(publicKey).build().parseSignedClaims(ticket).getPayload();
assertEquals("public-profile", claims.get("typ", String.class));
@@ -122,15 +122,16 @@ class JwtServiceTest extends AbstractUnitTest {
@Test
void generateProfileTicket_CarriesTheSharedStatGroups() {
String ticket = jwtService.generateProfileTicket(42L, List.of("PILE", "TREND"));
String ticket = jwtService.generateProfileTicket(42L, List.of("PILE", "TREND"), List.of("PLAYING", "STATS"));
Claims claims = Jwts.parser().verifyWith(publicKey).build().parseSignedClaims(ticket).getPayload();
assertEquals(List.of("PILE", "TREND"), claims.get("profileStats", List.class));
assertEquals(List.of("PLAYING", "STATS"), claims.get("sections", List.class));
}
@Test
void parseLoginToken_ProfileTicketIsNoLoginToken() {
assertEquals(Optional.empty(), jwtService.parseLoginToken(jwtService.generateProfileTicket(42L, List.of())));
assertEquals(Optional.empty(), jwtService.parseLoginToken(jwtService.generateProfileTicket(42L, List.of(), List.of())));
}
@Test
@@ -84,6 +84,40 @@ class PublicProfileControllerTest extends AbstractIntegrationTest {
assertEquals(List.of("PILE", "TREND"), claims.get("profileStats", List.class));
}
@Test
void publicProfile_AnswersWithTheOwnersStyle() throws Exception {
jdbc.update("update user_settings set profile_accent = 'red', profile_corners = 'round', profile_banner_appid = 620, "
+ "profile_sections = '{PLAYING,SUMMARY}' where user_id = ?", publicUserId);
mockMvc.perform(get("/api/public/profiles/Lobo"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.style.accent").value("red"))
.andExpect(jsonPath("$.style.corners").value("round"))
.andExpect(jsonPath("$.style.bannerAppid").value(620))
.andExpect(jsonPath("$.style.backgroundAppid").isEmpty())
.andExpect(jsonPath("$.style.sections[0]").value("PLAYING"))
.andExpect(jsonPath("$.style.sections[1]").value("SUMMARY"));
}
@Test
void publicProfile_NewRowShowsEverySection() throws Exception {
mockMvc.perform(get("/api/public/profiles/Lobo"))
.andExpect(jsonPath("$.style.accent").value("violet"))
.andExpect(jsonPath("$.style.sections.length()").value(5));
}
@Test
void publicProfile_TicketCarriesTheShownSections() throws Exception {
jdbc.update("update user_settings set profile_sections = '{PLAYING}' where user_id = ?", publicUserId);
String body = mockMvc.perform(get("/api/public/profiles/Lobo")).andReturn().getResponse().getContentAsString();
String ticket = body.replaceAll(".*\"ticket\"\\s*:\\s*\"([^\"]+)\".*", "$1");
PublicKey key = KeyFactory.getInstance("Ed25519").generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey)));
Claims claims = Jwts.parser().verifyWith(key).build().parseSignedClaims(ticket).getPayload();
assertEquals(List.of("PLAYING"), claims.get("sections", List.class));
}
@Test
void publicProfile_NameIgnoresCase() throws Exception {
mockMvc.perform(get("/api/public/profiles/lOBO"))
@@ -1,5 +1,6 @@
package auth_service.modules.user.api;
import auth_service.modules.user.model.ProfileStyle;
import auth_service.configuration.security.JwtAuthenticationFilter;
import auth_service.configuration.security.JwtService;
import auth_service.shared.security.UserPrincipal;
@@ -390,6 +391,62 @@ class UserControllerTest extends AbstractWebMvcTest {
.andExpect(status().isBadRequest());
}
@Test
void getCurrentUserV1_ShowsTheProfileStyle() throws Exception {
String email = "test@example.com";
UserSettings settings = UserSettings.builder().language("en").theme("dark")
.profileStyle(new ProfileStyle("red", "sharp", 620L, null, List.of("PLAYING"))).build();
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(new UserPrincipal("1", email, null), null, Collections.emptyList())
);
when(userService.getUserByEmail(email)).thenReturn(User.builder().email(email).settings(settings).build());
mockMvc.perform(get("/api/users/me").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.settings.profileStyle.accent").value("red"))
.andExpect(jsonPath("$.settings.profileStyle.corners").value("sharp"))
.andExpect(jsonPath("$.settings.profileStyle.bannerAppid").value(620))
.andExpect(jsonPath("$.settings.profileStyle.backgroundAppid").isEmpty())
.andExpect(jsonPath("$.settings.profileStyle.sections[0]").value("PLAYING"));
}
@Test
void updateProfileStyle_StoresTheValues() throws Exception {
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(
new UserPrincipal("1", "test@example.com", null), null, Collections.emptyList()));
mockMvc.perform(put("/api/users/settings/profile-style")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"accent\":\"green\",\"corners\":\"round\",\"bannerAppid\":620,"
+ "\"sections\":[\"STATS\",\"SUMMARY\"]}"))
.andExpect(status().isNoContent());
verify(userService).updateProfileStyle(1L, new ProfileStyle("green", "round", 620L, null, List.of("STATS", "SUMMARY")));
}
// Each body differs from a valid one in one field only, so the 400 comes from that field's constraint.
@ParameterizedTest
@ValueSource(strings = {
"{\"accent\":\"hotpink\",\"corners\":\"default\",\"sections\":[]}",
"{\"accent\":\"violet\",\"corners\":\"pointy\",\"sections\":[]}",
"{\"accent\":\"violet\",\"corners\":\"default\",\"bannerAppid\":0,\"sections\":[]}",
"{\"accent\":\"violet\",\"corners\":\"default\",\"backgroundAppid\":-620,\"sections\":[]}",
"{\"accent\":\"violet\",\"corners\":\"default\",\"sections\":[\"NOTES\"]}",
"{\"accent\":\"violet\",\"corners\":\"default\",\"sections\":[null]}",
"{\"accent\":\"violet\",\"corners\":\"default\"}"
})
void updateProfileStyle_UnknownOrMissingValue_IsRejected(String body) throws Exception {
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(
new UserPrincipal("1", "test@example.com", null), null, Collections.emptyList()));
mockMvc.perform(put("/api/users/settings/profile-style")
.contentType(MediaType.APPLICATION_JSON)
.content(body))
.andExpect(status().isBadRequest());
verifyNoInteractions(userService);
}
@Test
void updateAppearance_StoresTheValues() throws Exception {
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(
@@ -0,0 +1,17 @@
package auth_service.modules.user.model;
import auth_service.shared.test.AbstractUnitTest;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
class ProfileStyleTest extends AbstractUnitTest {
@Test
void keepsEachSectionOnceAtItsFirstPlace() {
assertEquals(List.of("STATS", "PLAYING"),
new ProfileStyle("red", "round", 620L, null, List.of("STATS", "PLAYING", "STATS")).sections());
}
}
@@ -1,5 +1,6 @@
package auth_service.modules.user.persistence;
import auth_service.modules.user.model.ProfileStyle;
import auth_service.modules.user.exception.UserNotFoundException;
import auth_service.modules.user.model.Appearance;
import auth_service.modules.user.model.SteamIdHolder;
@@ -280,6 +281,26 @@ class UserRepositoryTest extends AbstractPersistenceTest {
.getSettings().getAppearance());
}
@Test
void updateProfileStyle_IsReadBackAndStartsWithTheDefaults() {
UserEntity userEntity = entityManager.persist(UserEntity.builder()
.email("style@example.com")
.password("password")
.enabled(true)
.registrationDate(LocalDateTime.now())
.build());
entityManager.flush();
assertEquals(ProfileStyle.DEFAULT, userRepository.findByEmail("style@example.com").orElseThrow()
.getSettings().getProfileStyle());
ProfileStyle style = new ProfileStyle("pink", "sharp", 620L, 1086940L, List.of("FINISHED", "SUMMARY"));
userRepository.updateProfileStyle(userEntity.getId(), style);
entityManager.flush();
entityManager.clear();
assertEquals(style, userRepository.findByEmail("style@example.com").orElseThrow().getSettings().getProfileStyle());
}
@Test
void updateProfileStats_IsReadBackAndKeptByUpdateSettings() {
UserEntity userEntity = entityManager.persist(UserEntity.builder()