Public Access
feat(account): push public profile flag and sections to Core
This commit is contained in:
1 parent
d924c09953
commit
d842d9e865
16 files changed
+234
-33
No files matched your search
@@ -397,8 +397,11 @@ sequenceDiagram
|
|||||||
Core keeps its own copy of the Steam ID and of the block. After a verified Steam link (also for the account that
|
Core keeps its own copy of the Steam ID and of the block. After a verified Steam link (also for the account that
|
||||||
loses a typed-in ID), an unlink, a block and an unblock, Auth sends the user's state after the commit
|
loses a typed-in ID), an unlink, a block and an unblock, Auth sends the user's state after the commit
|
||||||
(`AccountStatePublisher`), also when the display name, the public profile or "show me in owner lists" changes; the nightly
|
(`AccountStatePublisher`), also when the display name, the public profile or "show me in owner lists" changes; the nightly
|
||||||
cleanup sends every user. The state carries `listed` (active, public profile, opt-in and a display name: `ListedUser`),
|
cleanup sends every user. A change of the profile tiles sends it too. The state carries `listed` (active, public profile, opt-in and a display
|
||||||
which Core uses for its owner lists. The call runs on the task executor, so neither the answer nor a database
|
name: `ListedUser`), which Core uses for its owner lists, `publicProfile` (active, public profile and a display name:
|
||||||
|
`PublicUser`) and `profileSections` (the profile parts the user shows, by the names of the profile ticket:
|
||||||
|
`ProfileSections`), which Core uses to let others follow the user and to leave out in the follower note what the profile
|
||||||
|
hides (#27). The call runs on the task executor, so neither the answer nor a database
|
||||||
connection waits for Core; a failed call is tried again after 10 s, 1 min and 5 min (in memory), then it only warns.
|
connection waits for Core; a failed call is tried again after 10 s, 1 min and 5 min (in memory), then it only warns.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
@@ -406,8 +409,8 @@ sequenceDiagram
|
|||||||
participant A as Auth
|
participant A as Auth
|
||||||
participant C as Core
|
participant C as Core
|
||||||
A->>A: link / unlink / block / unblock / name change (commit)
|
A->>A: link / unlink / block / unblock / name change (commit)
|
||||||
A->>C: POST /internal/accounts/states {userId, steamId, displayName, blocked, roleChangedAt, listed}
|
A->>C: POST /internal/accounts/states {userId, steamId, displayName, blocked, roleChangedAt, listed, publicProfile, profileSections}
|
||||||
C->>C: blocked_user, listed_user, account_role_change, Steam ID in user_steam_sync, display name in account_name
|
C->>C: blocked_user, listed_user, public_user (+ sections), account_role_change, Steam ID in user_steam_sync, display name in account_name
|
||||||
Note over A,C: nightly 04:15
|
Note over A,C: nightly 04:15
|
||||||
A->>C: PUT /internal/accounts/states (every user)
|
A->>C: PUT /internal/accounts/states (every user)
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
package auth_service.modules.account.model;
|
package auth_service.modules.account.model;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* One user as Core copies it.
|
* One user as Core copies it.
|
||||||
*
|
*
|
||||||
* @param roleChangedAt null when the role never changed on the admin page
|
* @param roleChangedAt null when the role never changed on the admin page
|
||||||
* @param listed shown by name in Core's owner lists, see {@link auth_service.shared.account.ListedUser}
|
* @param listed shown by name in Core's owner lists, see {@link auth_service.shared.account.ListedUser}
|
||||||
|
* @param publicProfile may be followed in Core, see {@link auth_service.shared.account.PublicUser}
|
||||||
|
* @param profileSections the profile parts the user shows ({@link auth_service.shared.account.ProfileSections}),
|
||||||
|
* null without a settings row
|
||||||
*/
|
*/
|
||||||
public record CoreAccountState(long userId, Long steamId, String displayName, boolean blocked, Instant roleChangedAt,
|
public record CoreAccountState(long userId, Long steamId, String displayName, boolean blocked, Instant roleChangedAt,
|
||||||
boolean listed) {
|
boolean listed, boolean publicProfile, List<String> profileSections) {
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,8 @@ import auth_service.shared.account.ActiveAdmins;
|
|||||||
import auth_service.shared.account.ActiveUser;
|
import auth_service.shared.account.ActiveUser;
|
||||||
import auth_service.shared.account.EmailAddress;
|
import auth_service.shared.account.EmailAddress;
|
||||||
import auth_service.shared.account.ListedUser;
|
import auth_service.shared.account.ListedUser;
|
||||||
|
import auth_service.shared.account.ProfileSections;
|
||||||
|
import auth_service.shared.account.PublicUser;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.jdbc.core.RowMapper;
|
import org.springframework.jdbc.core.RowMapper;
|
||||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||||
@@ -12,9 +14,12 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
|||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.sql.Array;
|
||||||
|
import java.sql.SQLException;
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -115,10 +120,21 @@ public class AccountRepository {
|
|||||||
new CoreAccountState(rs.getLong("id"), rs.getObject("steam_id", Long.class), rs.getString("display_name"),
|
new CoreAccountState(rs.getLong("id"), rs.getObject("steam_id", Long.class), rs.getString("display_name"),
|
||||||
!rs.getBoolean("enabled"),
|
!rs.getBoolean("enabled"),
|
||||||
rs.getTimestamp("role_changed_at") == null ? null : rs.getTimestamp("role_changed_at").toInstant(),
|
rs.getTimestamp("role_changed_at") == null ? null : rs.getTimestamp("role_changed_at").toInstant(),
|
||||||
rs.getBoolean("listed"));
|
rs.getBoolean("listed"), rs.getBoolean("public_profile"), sections(rs.getArray("profile_tiles")));
|
||||||
|
|
||||||
|
// Stored as "ID:SIZE", see PublicProfileRepository.tile.
|
||||||
|
private static List<String> sections(Array storedTiles) throws SQLException {
|
||||||
|
if (storedTiles == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ProfileSections.of(Arrays.stream((String[]) storedTiles.getArray())
|
||||||
|
.map(stored -> stored.replaceFirst(":[^:]*$", ""))
|
||||||
|
.toList());
|
||||||
|
}
|
||||||
|
|
||||||
private static final String STATE_SELECT = "SELECT u.id, u.steam_id, u.display_name, u.enabled, u.role_changed_at, "
|
private static final String STATE_SELECT = "SELECT u.id, u.steam_id, u.display_name, u.enabled, u.role_changed_at, "
|
||||||
+ "COALESCE(" + ListedUser.of("u", "s") + ", FALSE) AS listed "
|
+ "COALESCE(" + ListedUser.of("u", "s") + ", FALSE) AS listed, "
|
||||||
|
+ "COALESCE(" + PublicUser.of("u", "s") + ", FALSE) AS public_profile, s.profile_tiles "
|
||||||
+ "FROM users u LEFT JOIN user_settings s ON s.user_id = u.id";
|
+ "FROM users u LEFT JOIN user_settings s ON s.user_id = u.id";
|
||||||
|
|
||||||
public Optional<CoreAccountState> findState(long userId) {
|
public Optional<CoreAccountState> findState(long userId) {
|
||||||
|
|||||||
@@ -2,16 +2,14 @@ package auth_service.modules.profile.service;
|
|||||||
|
|
||||||
import auth_service.modules.profile.model.PublicProfile;
|
import auth_service.modules.profile.model.PublicProfile;
|
||||||
import auth_service.modules.profile.repo.PublicProfileRepository;
|
import auth_service.modules.profile.repo.PublicProfileRepository;
|
||||||
|
import auth_service.shared.account.ProfileSections;
|
||||||
import auth_service.shared.security.ProfileTicketIssuer;
|
import auth_service.shared.security.ProfileTicketIssuer;
|
||||||
import auth_service.shared.security.Public;
|
import auth_service.shared.security.Public;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Public
|
@Public
|
||||||
@@ -20,8 +18,6 @@ public class PublicProfileService {
|
|||||||
|
|
||||||
public record TicketedProfile(PublicProfile profile, String ticket) {}
|
public record TicketedProfile(PublicProfile profile, String ticket) {}
|
||||||
|
|
||||||
private static final String STATS_PREFIX = "STATS_";
|
|
||||||
|
|
||||||
private final PublicProfileRepository publicProfileRepository;
|
private final PublicProfileRepository publicProfileRepository;
|
||||||
private final ProfileTicketIssuer profileTicketIssuer;
|
private final ProfileTicketIssuer profileTicketIssuer;
|
||||||
|
|
||||||
@@ -33,15 +29,9 @@ public class PublicProfileService {
|
|||||||
.map(profile -> new TicketedProfile(profile, ticket(profile)));
|
.map(profile -> new TicketedProfile(profile, ticket(profile)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Core knows sections and stat groups, not tiles: every STATS_ tile is one shared group of the STATS section.
|
|
||||||
private String ticket(PublicProfile profile) {
|
private String ticket(PublicProfile profile) {
|
||||||
Map<Boolean, List<String>> byStats = profile.tiles().stream().map(PublicProfile.Tile::id)
|
List<String> tileIds = profile.tiles().stream().map(PublicProfile.Tile::id).toList();
|
||||||
.collect(Collectors.partitioningBy(id -> id.startsWith(STATS_PREFIX)));
|
return profileTicketIssuer.generateProfileTicket(profile.userId(), profile.role(),
|
||||||
List<String> statGroups = byStats.get(true).stream().map(id -> id.substring(STATS_PREFIX.length())).toList();
|
ProfileSections.statGroups(tileIds), ProfileSections.of(tileIds));
|
||||||
List<String> sections = new ArrayList<>(byStats.get(false));
|
|
||||||
if (!statGroups.isEmpty()) {
|
|
||||||
sections.add("STATS");
|
|
||||||
}
|
|
||||||
return profileTicketIssuer.generateProfileTicket(profile.userId(), profile.role(), statGroups, sections);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,6 +130,7 @@ public class UserService {
|
|||||||
@CacheEvict(value = "users", key = "#userId")
|
@CacheEvict(value = "users", key = "#userId")
|
||||||
public void updateProfileTiles(long userId, List<Tile> tiles) {
|
public void updateProfileTiles(long userId, List<Tile> tiles) {
|
||||||
userRepository.update(userId, user -> user.getSettings().setProfileTiles(Tile.distinct(tiles)));
|
userRepository.update(userId, user -> user.getSettings().setProfileTiles(Tile.distinct(tiles)));
|
||||||
|
accountStatePublisher.publish(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ public final class ListedUser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String of(String user, String settings) {
|
public static String of(String user, String settings) {
|
||||||
return ActiveUser.of(user) + " AND " + settings + ".public_profile AND " + settings + ".show_in_owner_lists AND "
|
return PublicUser.of(user, settings) + " AND " + settings + ".show_in_owner_lists";
|
||||||
+ user + ".display_name IS NOT NULL";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package auth_service.shared.account;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The profile parts a user shows, by the names of the profile ticket. Core knows sections, not tiles: every
|
||||||
|
* {@code STATS_} tile is one group of the single STATS section.
|
||||||
|
*/
|
||||||
|
public final class ProfileSections {
|
||||||
|
|
||||||
|
private static final String STATS_PREFIX = "STATS_";
|
||||||
|
private static final String STATS = "STATS";
|
||||||
|
// The names Core stores; a tile id of any other kind is a user's own choice and says nothing to Core.
|
||||||
|
private static final Set<String> KNOWN = Set.of("SUMMARY", "STATUS", "PLAYING", "FINISHED", STATS);
|
||||||
|
|
||||||
|
private ProfileSections() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param tileIds the tile ids without their size
|
||||||
|
*/
|
||||||
|
public static List<String> of(List<String> tileIds) {
|
||||||
|
List<String> sections = new ArrayList<>(tileIds.stream().filter(KNOWN::contains).toList());
|
||||||
|
if (tileIds.stream().anyMatch(id -> id.startsWith(STATS_PREFIX)) && !sections.contains(STATS)) {
|
||||||
|
sections.add(STATS);
|
||||||
|
}
|
||||||
|
return sections;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param tileIds the tile ids without their size
|
||||||
|
* @return the stat groups of Core, one per {@code STATS_} tile
|
||||||
|
*/
|
||||||
|
public static List<String> statGroups(List<String> tileIds) {
|
||||||
|
return tileIds.stream().filter(id -> id.startsWith(STATS_PREFIX)).map(id -> id.substring(STATS_PREFIX.length()))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package auth_service.shared.account;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The rule Core uses to allow follows (#27): active, public profile on, with a display name. It matches
|
||||||
|
* {@code PublicProfileRepository.findPublicByDisplayName}. The settings alias may come from a LEFT JOIN: no settings
|
||||||
|
* row is not public.
|
||||||
|
*/
|
||||||
|
public final class PublicUser {
|
||||||
|
|
||||||
|
private PublicUser() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String of(String user, String settings) {
|
||||||
|
return ActiveUser.of(user) + " AND " + settings + ".public_profile AND " + user + ".display_name IS NOT NULL";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,9 +56,11 @@ class AccountRepositoryTest extends AbstractPersistenceTest {
|
|||||||
void findStates_CarryTheSteamIdTheDisplayNameAndTheBlock() {
|
void findStates_CarryTheSteamIdTheDisplayNameAndTheBlock() {
|
||||||
jdbc.update("update users set enabled = false, steam_id = 76561198000000001, display_name = 'Alice' where id = ?", alice);
|
jdbc.update("update users set enabled = false, steam_id = 76561198000000001, display_name = 'Alice' where id = ?", alice);
|
||||||
|
|
||||||
assertThat(repository.findState(alice)).contains(new CoreAccountState(alice, 76561198000000001L, "Alice", true, null, false));
|
assertThat(repository.findState(alice)).contains(new CoreAccountState(alice, 76561198000000001L, "Alice", true, null, false, false,
|
||||||
assertThat(repository.findAllStates()).containsExactly(new CoreAccountState(alice, 76561198000000001L, "Alice", true, null, false),
|
List.of("SUMMARY", "STATUS", "PLAYING", "FINISHED")));
|
||||||
new CoreAccountState(bob, null, null, false, null, false));
|
assertThat(repository.findAllStates()).containsExactly(new CoreAccountState(alice, 76561198000000001L, "Alice", true, null, false, false,
|
||||||
|
List.of("SUMMARY", "STATUS", "PLAYING", "FINISHED")),
|
||||||
|
new CoreAccountState(bob, null, null, false, null, false, false, List.of("SUMMARY", "STATUS", "PLAYING", "FINISHED")));
|
||||||
assertThat(repository.findState(-1L)).isEmpty();
|
assertThat(repository.findState(-1L)).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,4 +274,48 @@ class AccountRepositoryTest extends AbstractPersistenceTest {
|
|||||||
assertThat(activeTokens(alice)).isZero();
|
assertThat(activeTokens(alice)).isZero();
|
||||||
assertThat(activeTokens(bob)).isEqualTo(2);
|
assertThat(activeTokens(bob)).isEqualTo(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void findStates_PublicProfileOnlyForAnActivePublicUserWithAName_NoOptInNeeded() {
|
||||||
|
long open = insertUser("open@example.com");
|
||||||
|
jdbc.update("update users set display_name = 'Open' where id = ?", open);
|
||||||
|
jdbc.update("update user_settings set public_profile = true, show_in_owner_lists = false where user_id = ?", open);
|
||||||
|
assertThat(repository.findState(open)).get().extracting(CoreAccountState::publicProfile).isEqualTo(true);
|
||||||
|
|
||||||
|
String[] breakers = {"update users set enabled = false where id = ?",
|
||||||
|
"update users set deleted_at = now() where id = ?",
|
||||||
|
"update users set status = 'PENDING' where id = ?",
|
||||||
|
"update users set display_name = null where id = ?",
|
||||||
|
"update user_settings set public_profile = false where user_id = ?"};
|
||||||
|
for (String breaker : breakers) {
|
||||||
|
jdbc.update("update users set enabled = true, deleted_at = null, status = 'ACTIVE', display_name = 'Open' where id = ?", open);
|
||||||
|
jdbc.update("update user_settings set public_profile = true where user_id = ?", open);
|
||||||
|
jdbc.update(breaker, open);
|
||||||
|
|
||||||
|
assertThat(repository.findState(open)).as(breaker).get().extracting(CoreAccountState::publicProfile).isEqualTo(false);
|
||||||
|
assertThat(repository.findAllStates()).as(breaker).filteredOn(state -> state.userId() == open)
|
||||||
|
.extracting(CoreAccountState::publicProfile).containsExactly(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void findStates_ProfileSectionsAreTheTileIdsWithAllStatsTilesCollapsedToOne() {
|
||||||
|
jdbc.update("update user_settings set profile_tiles = '{SUMMARY:L,STATS_BACKLOG:M,PLAYING:S,STATS_PLAYTIME:M}' where user_id = ?",
|
||||||
|
alice);
|
||||||
|
|
||||||
|
assertThat(repository.findState(alice)).get().extracting(CoreAccountState::profileSections)
|
||||||
|
.isEqualTo(List.of("SUMMARY", "PLAYING", "STATS"));
|
||||||
|
assertThat(repository.findAllStates()).extracting(CoreAccountState::profileSections)
|
||||||
|
.containsExactly(List.of("SUMMARY", "PLAYING", "STATS"), List.of("SUMMARY", "STATUS", "PLAYING", "FINISHED"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void findStates_UserWithoutASettingsRow_HasNoSectionsAndIsNotPublic() {
|
||||||
|
jdbc.update("delete from user_settings where user_id = ?", alice);
|
||||||
|
|
||||||
|
assertThat(repository.findState(alice)).get().satisfies(state -> {
|
||||||
|
assertThat(state.profileSections()).isNull();
|
||||||
|
assertThat(state.publicProfile()).isFalse();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ import static org.mockito.Mockito.when;
|
|||||||
|
|
||||||
class AccountCleanupServiceTest extends AbstractUnitTest {
|
class AccountCleanupServiceTest extends AbstractUnitTest {
|
||||||
|
|
||||||
private static final List<CoreAccountState> STATES = List.of(new CoreAccountState(1L, 76561198000000001L, "lobo", true, null, false));
|
private static final List<CoreAccountState> STATES = List.of(new CoreAccountState(1L, 76561198000000001L, "lobo", true, null, false, false, null));
|
||||||
|
|
||||||
private static final Duration RETENTION = Duration.ofDays(30);
|
private static final Duration RETENTION = Duration.ofDays(30);
|
||||||
|
|
||||||
|
|||||||
@@ -73,8 +73,8 @@ class CoreAccountServiceTest extends AbstractUnitTest {
|
|||||||
expect("replaceStates");
|
expect("replaceStates");
|
||||||
|
|
||||||
service.applyStates(List.of(new CoreAccountState(42L, 76561198000000001L, "lobo", true,
|
service.applyStates(List.of(new CoreAccountState(42L, 76561198000000001L, "lobo", true,
|
||||||
Instant.parse("2026-10-04T12:00:00Z"), true)));
|
Instant.parse("2026-10-04T12:00:00Z"), true, true, List.of("SUMMARY", "STATUS", "PLAYING", "FINISHED"))));
|
||||||
service.replaceStates(List.of(new CoreAccountState(42L, null, "lobo", false, null, false)));
|
service.replaceStates(List.of(new CoreAccountState(42L, null, "lobo", false, null, false, false, List.of("SUMMARY", "STATUS", "PLAYING", "FINISHED"))));
|
||||||
|
|
||||||
core.verify();
|
core.verify();
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -35,7 +35,7 @@ import static org.mockito.Mockito.when;
|
|||||||
|
|
||||||
class CoreAccountStatePublisherTest extends AbstractUnitTest {
|
class CoreAccountStatePublisherTest extends AbstractUnitTest {
|
||||||
|
|
||||||
private static final CoreAccountState STATE = new CoreAccountState(7L, 76561198000000001L, "lobo", true, null, false);
|
private static final CoreAccountState STATE = new CoreAccountState(7L, 76561198000000001L, "lobo", true, null, false, false, null);
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private AccountRepository accountRepository;
|
private AccountRepository accountRepository;
|
||||||
@@ -89,7 +89,7 @@ class CoreAccountStatePublisherTest extends AbstractUnitTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void coreDownOnce_RetriesAfter10sAndSendsTheCurrentState() {
|
void coreDownOnce_RetriesAfter10sAndSendsTheCurrentState() {
|
||||||
CoreAccountState newer = new CoreAccountState(7L, 76561198000000001L, "lobo", true, null, true);
|
CoreAccountState newer = new CoreAccountState(7L, 76561198000000001L, "lobo", true, null, true, false, null);
|
||||||
when(accountRepository.findState(7L)).thenReturn(Optional.of(STATE), Optional.of(newer));
|
when(accountRepository.findState(7L)).thenReturn(Optional.of(STATE), Optional.of(newer));
|
||||||
doThrow(new RestClientException("Core down")).doNothing().when(coreAccountService).applyStates(any());
|
doThrow(new RestClientException("Core down")).doNothing().when(coreAccountService).applyStates(any());
|
||||||
ArgumentCaptor<Runnable> retry = ArgumentCaptor.forClass(Runnable.class);
|
ArgumentCaptor<Runnable> retry = ArgumentCaptor.forClass(Runnable.class);
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package auth_service.modules.profile.service;
|
||||||
|
|
||||||
|
import auth_service.modules.profile.model.PublicProfile;
|
||||||
|
import auth_service.modules.profile.repo.PublicProfileRepository;
|
||||||
|
import auth_service.shared.security.ProfileTicketIssuer;
|
||||||
|
import auth_service.shared.security.Role;
|
||||||
|
import auth_service.shared.test.AbstractUnitTest;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyList;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyLong;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
class PublicProfileServiceTest extends AbstractUnitTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private PublicProfileRepository publicProfileRepository;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ProfileTicketIssuer profileTicketIssuer;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private PublicProfileService service;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void findPublicProfile_TicketCarriesTheSectionsAndTheStatGroups() {
|
||||||
|
PublicProfile profile = new PublicProfile(7L, Role.USER, "lobo", null,
|
||||||
|
new PublicProfile.Style(null, null, null, null, Map.of()),
|
||||||
|
List.of(new PublicProfile.Tile("SUMMARY", "M"), new PublicProfile.Tile("STATS_BACKLOG", "M"),
|
||||||
|
new PublicProfile.Tile("STATS_PLAYTIME", "S")));
|
||||||
|
when(publicProfileRepository.findPublicByDisplayName("lobo")).thenReturn(Optional.of(profile));
|
||||||
|
when(profileTicketIssuer.generateProfileTicket(anyLong(), any(), anyList(), anyList())).thenReturn("ticket");
|
||||||
|
|
||||||
|
service.findPublicProfile("lobo");
|
||||||
|
|
||||||
|
verify(profileTicketIssuer).generateProfileTicket(7L, Role.USER, List.of("BACKLOG", "PLAYTIME"),
|
||||||
|
List.of("SUMMARY", "STATS"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,6 +77,13 @@ class UserServiceTest extends AbstractUnitTest {
|
|||||||
assertEquals(List.of(new Tile("SUMMARY", "M")), changed.get(1).getSettings().getProfileTiles());
|
assertEquals(List.of(new Tile("SUMMARY", "M")), changed.get(1).getSettings().getProfileTiles());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateProfileTiles_PublishesTheAccountStateOnce() {
|
||||||
|
userService.updateProfileTiles(1L, List.of(new Tile("SUMMARY", "M")));
|
||||||
|
|
||||||
|
verify(accountStatePublisher, times(1)).publish(1L);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void updateStatChart_PutsTheFormOfTheGroup() {
|
void updateStatChart_PutsTheFormOfTheGroup() {
|
||||||
userService.updateStatChart(1L, "PLATFORMS", "PIE");
|
userService.updateStatChart(1L, "PLATFORMS", "PIE");
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package auth_service.shared.account;
|
||||||
|
|
||||||
|
import auth_service.shared.test.AbstractUnitTest;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class ProfileSectionsTest extends AbstractUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void of_KeepsThePartsInOrder() {
|
||||||
|
assertThat(ProfileSections.of(List.of("SUMMARY", "PLAYING", "FINISHED"))).containsExactly("SUMMARY", "PLAYING", "FINISHED");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void of_CollapsesEveryStatsTileToOneStatsSectionAtTheEnd() {
|
||||||
|
assertThat(ProfileSections.of(List.of("STATS_A", "SUMMARY", "STATS_B"))).containsExactly("SUMMARY", "STATS");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void of_NoTilesNoSections() {
|
||||||
|
assertThat(ProfileSections.of(List.of())).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void of_DropsTileIdsThatCoreDoesNotKnow() {
|
||||||
|
assertThat(ProfileSections.of(List.of("SUMMARY", "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "OWN_TILE", "STATS")))
|
||||||
|
.containsExactly("SUMMARY", "STATS");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,12 +37,12 @@
|
|||||||
"applyStates": {
|
"applyStates": {
|
||||||
"method": "POST",
|
"method": "POST",
|
||||||
"path": "/internal/accounts/states",
|
"path": "/internal/accounts/states",
|
||||||
"request": {"accounts": [{"userId": 42, "steamId": 76561198000000001, "displayName": "lobo", "blocked": true, "roleChangedAt": "2026-10-04T12:00:00Z", "listed": true}]}
|
"request": {"accounts": [{"userId": 42, "steamId": 76561198000000001, "displayName": "lobo", "blocked": true, "roleChangedAt": "2026-10-04T12:00:00Z", "listed": true, "publicProfile": true, "profileSections": ["SUMMARY", "STATUS", "PLAYING", "FINISHED"]}]}
|
||||||
},
|
},
|
||||||
"replaceStates": {
|
"replaceStates": {
|
||||||
"method": "PUT",
|
"method": "PUT",
|
||||||
"path": "/internal/accounts/states",
|
"path": "/internal/accounts/states",
|
||||||
"request": {"accounts": [{"userId": 42, "steamId": null, "displayName": "lobo", "blocked": false, "roleChangedAt": null, "listed": false}]}
|
"request": {"accounts": [{"userId": 42, "steamId": null, "displayName": "lobo", "blocked": false, "roleChangedAt": null, "listed": false, "publicProfile": false, "profileSections": ["SUMMARY", "STATUS", "PLAYING", "FINISHED"]}]}
|
||||||
},
|
},
|
||||||
"purge": {
|
"purge": {
|
||||||
"method": "POST",
|
"method": "POST",
|
||||||
|
|||||||
Reference in new issue
Block a user