Public Access
Compare commits
2
Commits
2b2f45b9af
...
e72222a37f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e72222a37f | ||
|
|
6b6ac44551 |
No files matched your search
@@ -39,8 +39,9 @@ To Be Played is split into three repositories:
|
||||
10 minutes, `typ=public-profile`) that Core accepts for the public library data only. The profile has its own
|
||||
look (accent, corners, banner and background from own game art) and a **tile layout** (`profile_tiles`: the shown
|
||||
tiles in order, each with a size S, M or L), apart from the app appearance; the public answer sends both as
|
||||
`style`. The ticket's claims come from the layout: `sections` names the shown parts (`STATS` when any `STATS_`
|
||||
tile is shown), `profileStats` the stat groups of the `STATS_` tiles. The user's dashboard layout
|
||||
`style`. The ticket also names the user's `role`, so Core can show the free looks of an admin. The ticket's
|
||||
claims come from the layout: `sections` names the shown parts (`STATS` when any `STATS_` tile is shown),
|
||||
`profileStats` the stat groups of the `STATS_` tiles. The user's dashboard layout
|
||||
(`dashboard_tiles`, NULL = never changed) is stored the same way. The ticket is not a login: every token names
|
||||
its kind in `aud` (`tbp-login`, `tbp-public-profile`, `tbp-internal`, `iss=tbp-auth`), and both services check it.
|
||||
A login's `sub` is the user id; the email is in no token. A token without `aud` is refused. The claims are pinned in
|
||||
@@ -78,6 +79,9 @@ To Be Played is split into three repositories:
|
||||
drafts). The dashboard shows the entries published after `user_settings.news_seen_at` once in a dialog; "Got it"
|
||||
sets the stamp to now (`PUT /api/news/seen`). A new user starts at the registration time. `/whats-new` lists all
|
||||
entries. Editing an entry does not make it unseen again. Create, update and delete are rows in `admin_audit`.
|
||||
- **Welcome page**: `user_settings.welcome_seen_at` is null until the user sees the welcome page; "Let's go" and "Skip"
|
||||
set it (`PUT /api/users/settings/welcome-seen`). V24 sets it for every existing user, so only users registered after
|
||||
the deploy get the page.
|
||||
- **Mails** from the platform mailbox: "a new registration waits" to the admin (`APP_MAIL_ADMIN_TO`, with the link
|
||||
to the admin page), and to the user "we got your registration", then "your account is active" or "your
|
||||
registration was denied", later "your account is blocked" and "your account will be deleted from <date>". The registration sends the browser language (`de` for a German browser, else `en`), which also becomes
|
||||
@@ -123,6 +127,7 @@ To Be Played is split into three repositories:
|
||||
| `PUT` | `/api/users/settings/appearance` | access token |
|
||||
| `PUT` | `/api/users/settings/profile-style` (`{accent, corners, bannerAppid, backgroundAppid}`; `204`) | access token |
|
||||
| `PUT` | `/api/users/settings/dashboard-tiles` (`{tiles: [{id, size}]}` in order, at most 20; `{tiles: null}` resets; `204`). Auth checks only the id shape `[A-Z_]{1,30}`: Core and the Frontend skip ids they do not know, and Core never shares the overview or the activity | access token |
|
||||
| `PUT` | `/api/users/settings/welcome-seen` (`204`; stamps `user_settings.welcome_seen_at`, sent as `settings.welcomeSeenAt` in `/me`, null until then) | access token |
|
||||
| `PUT` | `/api/users/settings/favorite-platform` (`{platform}`, free text up to 32, e.g. `PS5`; `null` clears it; `204`) | access token |
|
||||
| `PUT` | `/api/users/settings/profile-tiles` (`{tiles: [{id, size}]}`, e.g. `SUMMARY`, `STATUS`, `PLAYING`, `FINISHED`, `STATS_<GROUP>`; `204`) | access token |
|
||||
| `GET` | `/api/public/profiles/{displayName}` | public |
|
||||
|
||||
@@ -85,13 +85,14 @@ public class JwtService implements TokenGenerator, ProfileTicketIssuer, ServiceT
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateProfileTicket(long userId, List<String> profileStats, List<String> sections) {
|
||||
public String generateProfileTicket(long userId, Role role, List<String> profileStats, List<String> sections) {
|
||||
return Jwts.builder()
|
||||
.header().keyId(jwtConfig.getKid()).and()
|
||||
.issuer(ISSUER)
|
||||
.audience().add(PROFILE_TICKET_AUDIENCE).and()
|
||||
.subject(PROFILE_TICKET_SUBJECT)
|
||||
.claim("profileUserId", String.valueOf(userId))
|
||||
.claim(ROLE_CLAIM, role.name())
|
||||
.claim("profileStats", profileStats)
|
||||
.claim("sections", sections)
|
||||
.issuedAt(new Date(System.currentTimeMillis()))
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package auth_service.modules.profile.model;
|
||||
|
||||
import auth_service.shared.security.Role;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
public record PublicProfile(long userId, String displayName, LocalDate memberSince, Style style, List<Tile> tiles) {
|
||||
public record PublicProfile(long userId, Role role, String displayName, LocalDate memberSince, Style style, List<Tile> tiles) {
|
||||
|
||||
public record Style(String accent, String corners, Long bannerAppid, Long backgroundAppid) {}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package auth_service.modules.profile.repo;
|
||||
|
||||
import auth_service.modules.profile.model.PublicProfile;
|
||||
import auth_service.shared.account.ActiveUser;
|
||||
import auth_service.shared.security.Role;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@@ -30,12 +31,12 @@ public class PublicProfileRepository {
|
||||
*/
|
||||
public Optional<PublicProfile> findPublicByDisplayName(String displayName) {
|
||||
return jdbc.query("""
|
||||
SELECT u.id, u.display_name, u.registration_date, s.profile_accent, s.profile_corners,
|
||||
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
|
||||
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")),
|
||||
(rs, row) -> new PublicProfile(rs.getLong("id"), rs.getString("display_name"),
|
||||
(rs, row) -> new PublicProfile(rs.getLong("id"), Role.parse(rs.getString("role")), rs.getString("display_name"),
|
||||
rs.getTimestamp("registration_date") != null
|
||||
? rs.getTimestamp("registration_date").toLocalDateTime().toLocalDate() : null,
|
||||
new PublicProfile.Style(rs.getString("profile_accent"), rs.getString("profile_corners"),
|
||||
|
||||
@@ -42,6 +42,6 @@ public class PublicProfileService {
|
||||
if (!statGroups.isEmpty()) {
|
||||
sections.add("STATS");
|
||||
}
|
||||
return profileTicketIssuer.generateProfileTicket(profile.userId(), statGroups, sections);
|
||||
return profileTicketIssuer.generateProfileTicket(profile.userId(), profile.role(), statGroups, sections);
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,12 @@ public class UserController {
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@PutMapping(value = "/settings/welcome-seen")
|
||||
public ResponseEntity<Void> markWelcomeSeen(@AuthenticationPrincipal UserPrincipal principal) {
|
||||
this.userService.markWelcomeSeen(principal.getIdAsLong());
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@PutMapping(value = "/settings/profile-style")
|
||||
public ResponseEntity<Void> updateProfileStyle(@AuthenticationPrincipal UserPrincipal principal,
|
||||
@Valid @RequestBody ProfileStyleRequest request) {
|
||||
|
||||
@@ -2,11 +2,12 @@ package auth_service.modules.user.api.dto.response;
|
||||
|
||||
import lombok.Builder;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
@Builder
|
||||
public record SettingsResponse(long id, boolean useDarkTheme, String language, boolean publicProfile,
|
||||
AppearanceResponse appearance, ProfileStyleResponse profileStyle,
|
||||
List<TileResponse> dashboardTiles, List<TileResponse> profileTiles,
|
||||
String favoritePlatform) {
|
||||
String favoritePlatform, Instant welcomeSeenAt) {
|
||||
}
|
||||
@@ -48,6 +48,7 @@ public abstract class ApiMapper {
|
||||
: toApi(user.getSettings().getDashboardTiles()))
|
||||
.profileTiles(toApi(user.getSettings().getProfileTiles()))
|
||||
.favoritePlatform(user.getSettings().getFavoritePlatform())
|
||||
.welcomeSeenAt(user.getSettings().getWelcomeSeenAt())
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package auth_service.modules.user.model;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@@ -20,6 +21,8 @@ public class UserSettings {
|
||||
@Builder.Default
|
||||
private List<Tile> profileTiles = DEFAULT_PROFILE_TILES;
|
||||
private String favoritePlatform;
|
||||
/** Null: the welcome page was not seen yet. */
|
||||
private Instant welcomeSeenAt;
|
||||
|
||||
/** Also the column default of user_settings.profile_tiles. */
|
||||
public static final List<Tile> DEFAULT_PROFILE_TILES = List.of(new Tile("SUMMARY", "M"), new Tile("STATUS", "M"),
|
||||
|
||||
@@ -9,6 +9,7 @@ import lombok.Setter;
|
||||
import org.hibernate.annotations.JdbcTypeCode;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
@@ -72,4 +73,7 @@ public class UserSettingsEntity {
|
||||
@JdbcTypeCode(SqlTypes.ARRAY)
|
||||
@Column(name = "profile_tiles", nullable = false)
|
||||
private List<String> profileTiles;
|
||||
|
||||
@Column(name = "welcome_seen_at")
|
||||
private Instant welcomeSeenAt;
|
||||
}
|
||||
@@ -35,6 +35,7 @@ public abstract class EntityMapper {
|
||||
: settings.getDashboardTiles().stream().map(Tile::parse).toList())
|
||||
.profileTiles(settings.getProfileTiles().stream().map(Tile::parse).toList())
|
||||
.favoritePlatform(settings.getFavoritePlatform())
|
||||
.welcomeSeenAt(settings.getWelcomeSeenAt())
|
||||
.build();
|
||||
|
||||
return User.builder()
|
||||
@@ -73,5 +74,6 @@ public abstract class EntityMapper {
|
||||
: Tile.storedAll(settings.getDashboardTiles()));
|
||||
target.setProfileTiles(Tile.storedAll(settings.getProfileTiles()));
|
||||
target.setFavoritePlatform(settings.getFavoritePlatform());
|
||||
target.setWelcomeSeenAt(settings.getWelcomeSeenAt());
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import org.springframework.cache.annotation.Caching;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -128,6 +129,13 @@ public class UserService {
|
||||
user -> user.getSettings().setFavoritePlatform(platform == null ? null : platform.strip()));
|
||||
}
|
||||
|
||||
@RequiresRole(Role.USER)
|
||||
@Transactional
|
||||
@CacheEvict(value = "users", key = "#userId")
|
||||
public void markWelcomeSeen(long userId) {
|
||||
userRepository.update(userId, user -> user.getSettings().setWelcomeSeenAt(Instant.now()));
|
||||
}
|
||||
|
||||
@RequiresRole(Role.USER)
|
||||
@Transactional
|
||||
@CacheEvict(value = "users", key = "#userId")
|
||||
|
||||
@@ -11,5 +11,5 @@ 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, List<String> sections);
|
||||
String generateProfileTicket(long userId, Role role, List<String> profileStats, List<String> sections);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Null: the user has not seen the welcome page (item 20). Every existing user counts as seen, so only users
|
||||
-- registered after this migration get the page. The registration insert names its columns and leaves it null.
|
||||
ALTER TABLE user_settings ADD COLUMN welcome_seen_at TIMESTAMPTZ;
|
||||
UPDATE user_settings SET welcome_seen_at = NOW();
|
||||
@@ -113,7 +113,7 @@ class JwtServiceTest extends AbstractUnitTest {
|
||||
|
||||
@Test
|
||||
void generateProfileTicket_NamesTheUserAndExpiresSoon() {
|
||||
var ticket = jwtService.generateProfileTicket(42L, List.of(), List.of());
|
||||
var ticket = jwtService.generateProfileTicket(42L, Role.USER, List.of(), List.of());
|
||||
|
||||
var claims = Jwts.parser().verifyWith(publicKey).build().parseSignedClaims(ticket).getPayload();
|
||||
assertEquals(List.of("tbp-public-profile"), List.copyOf(claims.getAudience()));
|
||||
@@ -123,9 +123,17 @@ class JwtServiceTest extends AbstractUnitTest {
|
||||
assertTrue(secondsLeft > 0 && secondsLeft <= 600, "expires in " + secondsLeft + "s");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateProfileTicket_CarriesTheRole() {
|
||||
String ticket = jwtService.generateProfileTicket(42L, Role.PLATFORM_ADMIN, List.of(), List.of());
|
||||
|
||||
Claims claims = Jwts.parser().verifyWith(publicKey).build().parseSignedClaims(ticket).getPayload();
|
||||
assertEquals("PLATFORM_ADMIN", claims.get("role", String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateProfileTicket_CarriesTheSharedStatGroups() {
|
||||
String ticket = jwtService.generateProfileTicket(42L, List.of("PILE", "TREND"), List.of("PLAYING", "STATS"));
|
||||
String ticket = jwtService.generateProfileTicket(42L, Role.USER, 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));
|
||||
@@ -134,7 +142,7 @@ class JwtServiceTest extends AbstractUnitTest {
|
||||
|
||||
@Test
|
||||
void parseLoginToken_ProfileTicketIsNoLoginToken() {
|
||||
assertEquals(Optional.empty(), jwtService.parseLoginToken(jwtService.generateProfileTicket(42L, List.of(), List.of())));
|
||||
assertEquals(Optional.empty(), jwtService.parseLoginToken(jwtService.generateProfileTicket(42L, Role.USER, List.of(), List.of())));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -218,7 +226,7 @@ class JwtServiceTest extends AbstractUnitTest {
|
||||
assertEquals(ContractFixture.token("login"),
|
||||
payload(jwtService.generateToken("42", 76561198000000001L, Role.USER)));
|
||||
assertEquals(ContractFixture.token("publicProfile"),
|
||||
payload(jwtService.generateProfileTicket(42L, List.of("OVERVIEW"), List.of("SUMMARY"))));
|
||||
payload(jwtService.generateProfileTicket(42L, Role.USER, List.of("OVERVIEW"), List.of("SUMMARY"))));
|
||||
assertEquals(ContractFixture.token("service"), payload(jwtService.generateServiceToken()));
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ class PublicProfileControllerTest extends AbstractIntegrationTest {
|
||||
var claims = Jwts.parser().verifyWith(key).build().parseSignedClaims(ticket).getPayload();
|
||||
assertEquals(String.valueOf(publicUserId), claims.get("profileUserId", String.class));
|
||||
assertEquals(List.of(), claims.get("profileStats", List.class));
|
||||
assertEquals("USER", claims.get("role", String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -120,6 +121,7 @@ class PublicProfileControllerTest extends AbstractIntegrationTest {
|
||||
Claims claims = Jwts.parser().verifyWith(key).build().parseSignedClaims(ticket).getPayload();
|
||||
assertEquals(List.of("PLAYING"), claims.get("sections", List.class));
|
||||
assertEquals(List.of(), claims.get("profileStats", List.class));
|
||||
assertEquals("USER", claims.get("role", String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -33,9 +33,11 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.never;
|
||||
@@ -381,6 +383,40 @@ class UserControllerTest extends AbstractWebMvcTest {
|
||||
.andExpect(jsonPath("$.settings.favoritePlatform").value("PS5"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCurrentUser_SendsWelcomeSeenAtNullForANewUser() throws Exception {
|
||||
SecurityContextHolder.getContext().setAuthentication(
|
||||
new UsernamePasswordAuthenticationToken(new UserPrincipal("1", null), null, Collections.emptyList())
|
||||
);
|
||||
when(userService.getUser(1L)).thenReturn(User.builder().email("test@example.com")
|
||||
.settings(UserSettings.builder().language("en").theme("dark").build()).build());
|
||||
mockMvc.perform(get("/api/users/me").accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.settings.welcomeSeenAt").value(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCurrentUser_SendsWelcomeSeenAtAsIsoInstant() throws Exception {
|
||||
SecurityContextHolder.getContext().setAuthentication(
|
||||
new UsernamePasswordAuthenticationToken(new UserPrincipal("1", null), null, Collections.emptyList())
|
||||
);
|
||||
when(userService.getUser(1L)).thenReturn(User.builder().email("test@example.com")
|
||||
.settings(UserSettings.builder().language("en").theme("dark")
|
||||
.welcomeSeenAt(Instant.parse("2026-10-01T10:00:00Z")).build()).build());
|
||||
mockMvc.perform(get("/api/users/me").accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(jsonPath("$.settings.welcomeSeenAt").value("2026-10-01T10:00:00Z"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void markWelcomeSeen_Returns204() throws Exception {
|
||||
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(
|
||||
new UserPrincipal("1", null), null, Collections.emptyList()));
|
||||
|
||||
mockMvc.perform(put("/api/users/settings/welcome-seen")).andExpect(status().isNoContent());
|
||||
|
||||
verify(userService).markWelcomeSeen(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCurrentUser_NeverChangedDashboard_SendsNullTiles() throws Exception {
|
||||
String email = "test@example.com";
|
||||
|
||||
@@ -14,7 +14,9 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.jpa.test.autoconfigure.TestEntityManager;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -374,6 +376,28 @@ class UserRepositoryTest extends AbstractPersistenceTest {
|
||||
assertEquals(profile, findByEmail("longtile@example.com").orElseThrow().getSettings().getProfileTiles());
|
||||
}
|
||||
|
||||
@Test
|
||||
void markWelcomeSeen_IsNullForANewRowAndSurvivesASettingsSave() {
|
||||
UserEntity userEntity = persisted(UserEntity.builder()
|
||||
.email("welcome@example.com")
|
||||
.password("password")
|
||||
.enabled(true)
|
||||
.registrationDate(LocalDateTime.now())
|
||||
.build());
|
||||
assertNull(findByEmail("welcome@example.com").orElseThrow().getSettings().getWelcomeSeenAt());
|
||||
|
||||
Instant seen = Instant.now().truncatedTo(ChronoUnit.MILLIS);
|
||||
userRepository.update(userEntity.getId(), user -> user.getSettings().setWelcomeSeenAt(seen));
|
||||
entityManager.flush();
|
||||
entityManager.clear();
|
||||
assertEquals(seen, findByEmail("welcome@example.com").orElseThrow().getSettings().getWelcomeSeenAt());
|
||||
|
||||
updateSettings(userEntity.getId(), null, UserSettings.builder().language("de").theme("dark").build());
|
||||
entityManager.flush();
|
||||
entityManager.clear();
|
||||
assertEquals(seen, findByEmail("welcome@example.com").orElseThrow().getSettings().getWelcomeSeenAt());
|
||||
}
|
||||
|
||||
private void updateSettings(long userId, String displayName, UserSettings settings) {
|
||||
userRepository.update(userId, user -> {
|
||||
user.setDisplayName(displayName);
|
||||
|
||||
@@ -19,6 +19,8 @@ import org.mockito.InOrder;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
@@ -26,6 +28,7 @@ import java.util.function.Consumer;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
@@ -192,6 +195,14 @@ class UserServiceTest extends AbstractUnitTest {
|
||||
assertEquals("Switch", appliedChanges(1).getFirst().getSettings().getFavoritePlatform());
|
||||
}
|
||||
|
||||
@Test
|
||||
void markWelcomeSeen_StampsNow() {
|
||||
userService.markWelcomeSeen(1L);
|
||||
|
||||
Instant stamp = appliedChanges(1).getFirst().getSettings().getWelcomeSeenAt();
|
||||
assertTrue(Duration.between(stamp, Instant.now()).abs().getSeconds() < 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs each change user 1 got on a fresh user with default settings.
|
||||
*/
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"aud": ["tbp-public-profile"],
|
||||
"sub": "public-profile",
|
||||
"profileUserId": "42",
|
||||
"role": "USER",
|
||||
"profileStats": ["OVERVIEW"],
|
||||
"sections": ["SUMMARY"]
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user