feat(user): synced appearance settings

This commit is contained in:
lobo committed 2026-09-24 15:06:16 +02:00
1 parent 973b980e05
commit 1d88c30d48
16 files changed
+268 -1

No files matched your search

@@ -6,6 +6,7 @@ import auth_service.shared.security.UserPrincipal;
import auth_service.modules.user.exception.DisplayNameAlreadyInUseException;
import auth_service.modules.user.exception.SteamIdAlreadyInUseException;
import auth_service.modules.user.exception.UserNotFoundException;
import auth_service.modules.user.model.Appearance;
import auth_service.modules.user.model.User;
import auth_service.modules.user.model.UserSettings;
import auth_service.modules.user.service.UserService;
@@ -401,4 +402,88 @@ class UserControllerTest extends AbstractWebMvcTest {
.andExpect(status().isOk())
.andExpect(jsonPath("$.settings.publicProfile").value(true));
}
@Test
void getCurrentUserV1_ShowsAppearance() throws Exception {
var email = "test@example.com";
var settings = UserSettings.builder().language("en").theme("dark")
.appearance(new Appearance("blue", "round", true, 620L)).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.appearance.accent").value("blue"))
.andExpect(jsonPath("$.settings.appearance.corners").value("round"))
.andExpect(jsonPath("$.settings.appearance.wideLayout").value(true))
.andExpect(jsonPath("$.settings.appearance.backgroundAppid").value(620));
}
@Test
void getCurrentUserV1_WithoutBackground_SendsNull() throws Exception {
var email = "test@example.com";
var settings = UserSettings.builder().language("en").theme("dark").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.appearance.accent").value("violet"))
.andExpect(jsonPath("$.settings.appearance.corners").value("default"))
.andExpect(jsonPath("$.settings.appearance.wideLayout").value(false))
.andExpect(jsonPath("$.settings.appearance.backgroundAppid").isEmpty());
}
@Test
void updateAppearance_StoresTheValues() throws Exception {
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(
new UserPrincipal("1", "test@example.com", null), null, Collections.emptyList()));
mockMvc.perform(put("/api/users/settings/appearance")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"accent\":\"green\",\"corners\":\"sharp\",\"wideLayout\":true,\"backgroundAppid\":620}"))
.andExpect(status().isNoContent());
verify(userService).updateAppearance(1L, new Appearance("green", "sharp", true, 620L));
}
@Test
void updateAppearance_MissingWideLayoutAndBackground_AreOffAndNone() throws Exception {
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(
new UserPrincipal("1", "test@example.com", null), null, Collections.emptyList()));
mockMvc.perform(put("/api/users/settings/appearance")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"accent\":\"violet\",\"corners\":\"default\"}"))
.andExpect(status().isNoContent());
verify(userService).updateAppearance(1L, new Appearance("violet", "default", false, null));
}
// 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\"}",
"{\"accent\":\"red;background:url(x)\",\"corners\":\"default\"}",
"{\"corners\":\"default\"}",
"{\"accent\":\"violet\",\"corners\":\"pointy\"}",
"{\"accent\":\"violet\"}",
"{\"accent\":\"violet\",\"corners\":\"default\",\"backgroundAppid\":0}",
"{\"accent\":\"violet\",\"corners\":\"default\",\"backgroundAppid\":-620}"
})
void updateAppearance_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/appearance")
.contentType(MediaType.APPLICATION_JSON)
.content(body))
.andExpect(status().isBadRequest());
verifyNoInteractions(userService);
}
}
@@ -1,6 +1,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.UserSettings;
import auth_service.modules.user.persistence.entities.UserEntity;
import auth_service.shared.test.AbstractPersistenceTest;
@@ -198,4 +199,62 @@ class UserRepositoryTest extends AbstractPersistenceTest {
assertTrue(userRepository.findByEmail("public@example.com").orElseThrow().getSettings().isPublicProfile());
}
@Test
void updateAppearance_WithoutSettingsRow_CreatesItAndFindByEmailReadsIt() {
var userEntity = entityManager.persist(UserEntity.builder()
.email("appearance@example.com")
.password("password")
.enabled(true)
.registrationDate(LocalDateTime.now())
.build());
entityManager.flush();
userRepository.updateAppearance(userEntity.getId(), new Appearance("orange", "round", true, 620L));
entityManager.flush();
entityManager.clear();
var settings = userRepository.findByEmail("appearance@example.com").orElseThrow().getSettings();
assertEquals(new Appearance("orange", "round", true, 620L), settings.getAppearance());
}
@Test
void updateSettings_KeepsTheAppearance() {
var userEntity = entityManager.persist(UserEntity.builder()
.email("keep@example.com")
.password("password")
.enabled(true)
.registrationDate(LocalDateTime.now())
.build());
entityManager.flush();
userRepository.updateAppearance(userEntity.getId(), new Appearance("pink", "sharp", false, 220L));
entityManager.flush();
userRepository.updateSettings(userEntity.getId(), null, null,
UserSettings.builder().language("de").theme("dark").build());
entityManager.flush();
entityManager.clear();
var settings = userRepository.findByEmail("keep@example.com").orElseThrow().getSettings();
assertEquals(new Appearance("pink", "sharp", false, 220L), settings.getAppearance());
}
@Test
void findByEmail_WithoutSettingsRow_HasTheDefaultAppearance() {
entityManager.persist(UserEntity.builder()
.email("defaults@example.com")
.password("password")
.enabled(true)
.registrationDate(LocalDateTime.now())
.build());
entityManager.flush();
assertEquals(Appearance.DEFAULT, userRepository.findByEmail("defaults@example.com").orElseThrow()
.getSettings().getAppearance());
}
@Test
void updateAppearance_UnknownUser() {
assertThrows(UserNotFoundException.class, () -> userRepository.updateAppearance(-1L, Appearance.DEFAULT));
}
}
@@ -3,6 +3,7 @@ package auth_service.modules.user.service;
import auth_service.modules.user.exception.DisplayNameAlreadyInUseException;
import auth_service.modules.user.exception.SteamIdAlreadyInUseException;
import auth_service.modules.user.exception.UserNotFoundException;
import auth_service.modules.user.model.Appearance;
import auth_service.modules.user.model.User;
import auth_service.modules.user.model.UserSettings;
import auth_service.modules.user.persistence.UserRepository;
@@ -131,4 +132,14 @@ class UserServiceTest extends AbstractUnitTest {
// Assert
verify(userRepository).updateSettings(1L, null, "Lobo", userSettings);
}
@Test
void updateAppearance_StoresItWithoutANewToken() {
var appearance = new Appearance("red", "round", false, null);
userService.updateAppearance(1L, appearance);
verify(userRepository).updateAppearance(1L, appearance);
verifyNoInteractions(tokenGenerator);
}
}