refactor+test: extend StringNormalizer, migrate SystemAdministratorService, add SystemAdministratorServiceTest

- Add StringNormalizer.normalize() (trim + toLowerCase ROOT) for username normalization
- Migrate SystemAdministratorService private normalize() to use StringNormalizer.normalize()
- Remove now-unused Locale import from SystemAdministratorService
- Add StringNormalizerTest coverage for new normalize() method
- Add SystemAdministratorServiceTest with 10 unit tests covering all public methods
This commit is contained in:
2026-04-23 11:14:02 +02:00
parent 6dabdd5662
commit 4b5cb6023c
4 changed files with 126 additions and 2 deletions
@@ -2,8 +2,8 @@ package dev.kruhlmann.imgfloat.service;
import dev.kruhlmann.imgfloat.model.db.imgfloat.SystemAdministrator; import dev.kruhlmann.imgfloat.model.db.imgfloat.SystemAdministrator;
import dev.kruhlmann.imgfloat.repository.SystemAdministratorRepository; import dev.kruhlmann.imgfloat.repository.SystemAdministratorRepository;
import dev.kruhlmann.imgfloat.util.StringNormalizer;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -137,6 +137,6 @@ public class SystemAdministratorService {
} }
private String normalize(String username) { private String normalize(String username) {
return username.trim().toLowerCase(Locale.ROOT); return StringNormalizer.normalize(username);
} }
} }
@@ -16,4 +16,13 @@ public final class StringNormalizer {
public static String toLowerCaseRoot(String value) { public static String toLowerCaseRoot(String value) {
return value == null ? null : value.toLowerCase(Locale.ROOT); return value == null ? null : value.toLowerCase(Locale.ROOT);
} }
/**
* Returns {@code value.trim().toLowerCase(Locale.ROOT)}.
* Useful for normalizing user-supplied identifiers such as Twitch usernames.
* Returns {@code null} if {@code value} is {@code null}.
*/
public static String normalize(String value) {
return value == null ? null : value.trim().toLowerCase(Locale.ROOT);
}
} }
@@ -0,0 +1,104 @@
package dev.kruhlmann.imgfloat.service;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import dev.kruhlmann.imgfloat.model.db.imgfloat.SystemAdministrator;
import dev.kruhlmann.imgfloat.repository.SystemAdministratorRepository;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.core.env.Environment;
class SystemAdministratorServiceTest {
private SystemAdministratorRepository repo;
private Environment environment;
private SystemAdministratorService service;
@BeforeEach
void setup() {
repo = mock(SystemAdministratorRepository.class);
environment = mock(Environment.class);
when(environment.getProperty("IMGFLOAT_INITIAL_TWITCH_USERNAME_SYSADMIN")).thenReturn("admin");
when(environment.getProperty("org.springframework.boot.test.context.SpringBootTestContextBootstrapper"))
.thenReturn(null);
service = new SystemAdministratorService(repo, environment);
}
@Test
void isSysadminReturnsTrueForInitialSysadmin() {
assertThat(service.isSysadmin("admin")).isTrue();
assertThat(service.isSysadmin("ADMIN")).isTrue(); // case-insensitive
}
@Test
void isSysadminDelegatesToRepositoryForOtherUsers() {
when(repo.existsByTwitchUsername("other")).thenReturn(true);
assertThat(service.isSysadmin("other")).isTrue();
when(repo.existsByTwitchUsername("unknown")).thenReturn(false);
assertThat(service.isSysadmin("unknown")).isFalse();
}
@Test
void addSysadminSkipsIfAlreadyExists() {
when(repo.existsByTwitchUsername("newuser")).thenReturn(true);
service.addSysadmin("newuser");
verify(repo, never()).save(any());
}
@Test
void addSysadminSavesNewUser() {
when(repo.existsByTwitchUsername("newuser")).thenReturn(false);
service.addSysadmin("newuser");
verify(repo).save(any(SystemAdministrator.class));
}
@Test
void addSysadminThrowsForInitialSysadmin() {
assertThatThrownBy(() -> service.addSysadmin("admin"))
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining("initial");
}
@Test
void removeSysadminThrowsForInitialSysadmin() {
assertThatThrownBy(() -> service.removeSysadmin("admin"))
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining("initial");
}
@Test
void removeSysadminThrowsWhenUserDoesNotExist() {
when(repo.deleteByTwitchUsername("nonexistent")).thenReturn(0L);
assertThatThrownBy(() -> service.removeSysadmin("nonexistent"))
.isInstanceOf(IllegalArgumentException.class);
}
@Test
void removeSysadminDeletesExistingUser() {
when(repo.deleteByTwitchUsername("user")).thenReturn(1L);
service.removeSysadmin("user");
verify(repo).deleteByTwitchUsername("user");
}
@Test
void getInitialSysadminNormalizesUsername() {
when(environment.getProperty("IMGFLOAT_INITIAL_TWITCH_USERNAME_SYSADMIN")).thenReturn(" AdminUser ");
assertThat(service.getInitialSysadmin()).isEqualTo("adminuser");
}
@Test
void listSysadminsIncludesInitialSysadminFromEnvironment() {
SystemAdministrator persisted = new SystemAdministrator("other");
when(repo.findAllByOrderByTwitchUsernameAsc()).thenReturn(List.of(persisted));
List<String> admins = service.listSysadmins();
assertThat(admins).contains("admin", "other");
}
}
@@ -33,4 +33,15 @@ class StringNormalizerTest {
// Turkish locale would uppercase 'i' to 'İ' but ROOT locale must not // Turkish locale would uppercase 'i' to 'İ' but ROOT locale must not
assertThat(StringNormalizer.toLowerCaseRoot("TITLE")).isEqualTo("title"); assertThat(StringNormalizer.toLowerCaseRoot("TITLE")).isEqualTo("title");
} }
@Test
void normalizeTrimsAndLowercases() {
assertThat(StringNormalizer.normalize(" Hello ")).isEqualTo("hello");
assertThat(StringNormalizer.normalize("USER")).isEqualTo("user");
}
@Test
void normalizeReturnsNullForNull() {
assertThat(StringNormalizer.normalize(null)).isNull();
}
} }