From 06f8089f5a6d2b9872098d542f436e2d0ae4fa84 Mon Sep 17 00:00:00 2001 From: lobo Date: Wed, 30 Sep 2026 15:00:47 +0200 Subject: [PATCH] feat(account): tell Core about Steam ID and block changes Sent on the task executor after the commit, and every user again in the nightly cleanup. A failed call only warns. --- readme.md | 19 +++++ .../account/model/CoreAccountState.java | 7 ++ .../account/repo/AccountRepository.java | 16 ++++ .../service/AccountCleanupService.java | 13 ++- .../account/service/CoreAccountService.java | 14 ++++ .../service/CoreAccountStatePublisher.java | 55 +++++++++++++ .../admin/service/AdminUserService.java | 3 + .../modules/user/service/UserService.java | 5 ++ .../shared/account/AccountStatePublisher.java | 9 ++ .../account/repo/AccountRepositoryTest.java | 11 +++ .../service/AccountCleanupServiceTest.java | 17 ++++ .../service/CoreAccountServiceTest.java | 12 +++ .../CoreAccountStatePublisherTest.java | 82 +++++++++++++++++++ .../admin/service/AdminUserServiceTest.java | 7 +- .../modules/user/service/UserServiceTest.java | 9 ++ .../contract/auth-core-contract.json | 10 +++ 16 files changed, 286 insertions(+), 3 deletions(-) create mode 100644 src/main/java/auth_service/modules/account/model/CoreAccountState.java create mode 100644 src/main/java/auth_service/modules/account/service/CoreAccountStatePublisher.java create mode 100644 src/main/java/auth_service/shared/account/AccountStatePublisher.java create mode 100644 src/test/java/auth_service/modules/account/service/CoreAccountStatePublisherTest.java diff --git a/readme.md b/readme.md index e5c8e11..0ed80f1 100644 --- a/readme.md +++ b/readme.md @@ -221,6 +221,7 @@ sequenceDiagram Note over A: an old access token gets 401 within 30 s (activeUsers cache) Note over A,C: nightly 04:15 A->>C: PUT /internal/accounts/deleted (all flagged users) + A->>C: PUT /internal/accounts/states (every user) A->>C: POST /internal/accounts/purge (flagged 30+ days) A->>A: DELETE users (only when Core answered and no dry run) A->>C: POST /internal/accounts/prune (all user ids) @@ -229,6 +230,24 @@ sequenceDiagram A failed call to Core does not fail the delete: the next night sends the whole flagged set again. Undo within 30 days is `UPDATE users SET deleted_at = NULL` (see the Deployment README). +### Steam ID and block sync to Core + +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 +(`AccountStatePublisher`); the nightly cleanup sends every user. The call runs on the task executor, so neither the answer +nor a database connection waits for Core; a failed call only warns. + +```mermaid +sequenceDiagram + participant A as Auth + participant C as Core + A->>A: link / unlink / block / unblock (commit) + A->>C: POST /internal/accounts/states {userId, steamId, blocked} + C->>C: blocked_user, Steam ID in user_steam_sync + Note over A,C: nightly 04:15 + A->>C: PUT /internal/accounts/states (every user) +``` + ## Run locally ### Local development without keys (profile `local`) diff --git a/src/main/java/auth_service/modules/account/model/CoreAccountState.java b/src/main/java/auth_service/modules/account/model/CoreAccountState.java new file mode 100644 index 0000000..4fde3cc --- /dev/null +++ b/src/main/java/auth_service/modules/account/model/CoreAccountState.java @@ -0,0 +1,7 @@ +package auth_service.modules.account.model; + +/** + * One user as Core copies it. + */ +public record CoreAccountState(long userId, Long steamId, boolean blocked) { +} diff --git a/src/main/java/auth_service/modules/account/repo/AccountRepository.java b/src/main/java/auth_service/modules/account/repo/AccountRepository.java index 1ea82ec..dba245d 100644 --- a/src/main/java/auth_service/modules/account/repo/AccountRepository.java +++ b/src/main/java/auth_service/modules/account/repo/AccountRepository.java @@ -1,6 +1,8 @@ package auth_service.modules.account.repo; +import auth_service.modules.account.model.CoreAccountState; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Repository; @@ -56,6 +58,20 @@ public class AccountRepository { Timestamp.from(cutoff)); } + private static final RowMapper STATE_ROW = (rs, row) -> + new CoreAccountState(rs.getLong("id"), rs.getObject("steam_id", Long.class), !rs.getBoolean("enabled")); + + public Optional findState(long userId) { + return jdbc.query("SELECT id, steam_id, enabled FROM users WHERE id = ?", STATE_ROW, userId).stream().findFirst(); + } + + /** + * Every user, also the flagged ones. + */ + public List findAllStates() { + return jdbc.query("SELECT id, steam_id, enabled FROM users ORDER BY id", STATE_ROW); + } + /** * Also the flagged users: their rows in Core are no orphans yet. */ diff --git a/src/main/java/auth_service/modules/account/service/AccountCleanupService.java b/src/main/java/auth_service/modules/account/service/AccountCleanupService.java index 7025dc7..62dd5c1 100644 --- a/src/main/java/auth_service/modules/account/service/AccountCleanupService.java +++ b/src/main/java/auth_service/modules/account/service/AccountCleanupService.java @@ -12,8 +12,8 @@ import java.time.Instant; import java.util.List; /** - * Nightly, after Core's sync: tells Core who is flagged, removes users flagged longer than the retention (Core - * first, then Auth), and lets Core remove rows of users Auth does not know. + * Nightly, after Core's sync: tells Core who is flagged and every user's Steam ID and block, removes users flagged + * longer than the retention (Core first, then Auth), and lets Core remove rows of users Auth does not know. */ @Service @Slf4j @@ -41,10 +41,19 @@ public class AccountCleanupService { log.warn("Account cleanup stopped: Core did not take the flagged users: {}", e.getMessage()); return; } + sendStates(); purgeDue(); pruneOrphans(); } + private void sendStates() { + try { + coreAccountService.replaceStates(accountRepository.findAllStates()); + } catch (RuntimeException e) { + log.warn("Account cleanup: Core did not take the Steam IDs and blocks: {}", e.getMessage()); + } + } + private void purgeDue() { List due = accountRepository.findFlaggedBefore(Instant.now().minus(retention)); if (due.isEmpty()) { diff --git a/src/main/java/auth_service/modules/account/service/CoreAccountService.java b/src/main/java/auth_service/modules/account/service/CoreAccountService.java index 7ff1d03..d7516a2 100644 --- a/src/main/java/auth_service/modules/account/service/CoreAccountService.java +++ b/src/main/java/auth_service/modules/account/service/CoreAccountService.java @@ -1,5 +1,6 @@ package auth_service.modules.account.service; +import auth_service.modules.account.model.CoreAccountState; import auth_service.modules.account.model.CoreCleanupResult; import auth_service.shared.security.ServiceTokenIssuer; import org.springframework.beans.factory.annotation.Autowired; @@ -52,6 +53,19 @@ public class CoreAccountService { .body(Map.of("userIds", userIds)).retrieve().toBodilessEntity(); } + public void applyStates(List states) { + restClient.post().uri("/internal/accounts/states").headers(this::authorize) + .body(Map.of("accounts", states)).retrieve().toBodilessEntity(); + } + + /** + * After this, Core blocks exactly the blocked users of the list. + */ + public void replaceStates(List states) { + restClient.put().uri("/internal/accounts/states").headers(this::authorize) + .body(Map.of("accounts", states)).retrieve().toBodilessEntity(); + } + /** * @param dryRun Core only counts; Core also counts when its own dry run is on */ diff --git a/src/main/java/auth_service/modules/account/service/CoreAccountStatePublisher.java b/src/main/java/auth_service/modules/account/service/CoreAccountStatePublisher.java new file mode 100644 index 0000000..f085f70 --- /dev/null +++ b/src/main/java/auth_service/modules/account/service/CoreAccountStatePublisher.java @@ -0,0 +1,55 @@ +package auth_service.modules.account.service; + +import auth_service.modules.account.repo.AccountRepository; +import auth_service.shared.account.AccountStatePublisher; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +import java.util.List; +import java.util.concurrent.Executor; + +/** + * Sends on the task executor: in afterCommit the transaction still holds its database connection, and a slow Core + * (up to 13 s) would keep it from the pool. + */ +@Service +@Slf4j +public class CoreAccountStatePublisher implements AccountStatePublisher { + + private final AccountRepository accountRepository; + private final CoreAccountService coreAccountService; + private final Executor executor; + + public CoreAccountStatePublisher(AccountRepository accountRepository, CoreAccountService coreAccountService, + @Qualifier("applicationTaskExecutor") Executor executor) { + this.accountRepository = accountRepository; + this.coreAccountService = coreAccountService; + this.executor = executor; + } + + @Override + public void publish(long userId) { + if (TransactionSynchronizationManager.isSynchronizationActive()) { + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + @Override + public void afterCommit() { + executor.execute(() -> send(userId)); + } + }); + } else { + executor.execute(() -> send(userId)); + } + } + + private void send(long userId) { + try { + accountRepository.findState(userId).ifPresent(state -> coreAccountService.applyStates(List.of(state))); + } catch (RuntimeException e) { + log.warn("Core did not take the state of user {}, the nightly cleanup sends it again: {}", userId, + e.getMessage()); + } + } +} diff --git a/src/main/java/auth_service/modules/admin/service/AdminUserService.java b/src/main/java/auth_service/modules/admin/service/AdminUserService.java index 51d18e1..647eb12 100644 --- a/src/main/java/auth_service/modules/admin/service/AdminUserService.java +++ b/src/main/java/auth_service/modules/admin/service/AdminUserService.java @@ -6,6 +6,7 @@ import auth_service.modules.admin.model.AdminUserPage; import auth_service.modules.admin.model.TargetState; import auth_service.modules.admin.repo.AdminUserRepository; import auth_service.shared.account.AccountDeleter; +import auth_service.shared.account.AccountStatePublisher; import auth_service.shared.audit.AdminAuditLog; import auth_service.shared.security.RequiresRole; import auth_service.shared.security.Role; @@ -29,6 +30,7 @@ public class AdminUserService { private final AdminUserRepository adminUserRepository; private final AdminAuditLog adminAuditLog; private final AccountDeleter accountDeleter; + private final AccountStatePublisher accountStatePublisher; public AdminUserPage findUsers(String query, int page, int size) { String search = query == null ? "" : query.trim(); @@ -48,6 +50,7 @@ public class AdminUserService { throw new AdminActionRefusedException("ALREADY_DELETED", "The account is already deleted"); } adminUserRepository.setBlocked(targetUserId, blocked, Instant.now()); + accountStatePublisher.publish(targetUserId); adminAuditLog.record(actorUserId, blocked ? "BLOCK" : "UNBLOCK", targetUserId, null); log.info("Admin {} {} user {}", actorUserId, blocked ? "blocked" : "unblocked", targetUserId); } diff --git a/src/main/java/auth_service/modules/user/service/UserService.java b/src/main/java/auth_service/modules/user/service/UserService.java index e38c278..804c9bb 100644 --- a/src/main/java/auth_service/modules/user/service/UserService.java +++ b/src/main/java/auth_service/modules/user/service/UserService.java @@ -10,6 +10,7 @@ import auth_service.modules.user.model.SteamLinkOutcome; import auth_service.modules.user.model.User; import auth_service.modules.user.model.UserSettings; import auth_service.modules.user.persistence.UserRepository; +import auth_service.shared.account.AccountStatePublisher; import auth_service.shared.security.RequiresRole; import auth_service.shared.security.Role; import auth_service.shared.security.TokenGenerator; @@ -31,6 +32,7 @@ public class UserService { private final UserRepository userRepository; private final TokenGenerator tokenGenerator; + private final AccountStatePublisher accountStatePublisher; @RequiresRole(Role.USER) @Cacheable(value = "users", key = "#userId") @@ -74,8 +76,10 @@ public class UserService { } log.info("Steam ID taken from user {}, who had typed it in, for user {}", holder.get().userId(), userId); userRepository.clearSteamId(holder.get().userId()); + accountStatePublisher.publish(holder.get().userId()); } userRepository.setVerifiedSteamId(userId, steamId); + accountStatePublisher.publish(userId); log.info("Steam ID verified for user {}", userId); return SteamLinkOutcome.LINKED; } @@ -89,6 +93,7 @@ public class UserService { @CacheEvict(value = "publicStats", allEntries = true)}) public String unlinkSteamId(long userId, Role role) { userRepository.clearSteamId(userId); + accountStatePublisher.publish(userId); log.info("Steam ID unlinked for user {}", userId); return tokenGenerator.generateToken(String.valueOf(userId), null, role); } diff --git a/src/main/java/auth_service/shared/account/AccountStatePublisher.java b/src/main/java/auth_service/shared/account/AccountStatePublisher.java new file mode 100644 index 0000000..b74c5dd --- /dev/null +++ b/src/main/java/auth_service/shared/account/AccountStatePublisher.java @@ -0,0 +1,9 @@ +package auth_service.shared.account; + +/** + * Port for a change Core copies: the Steam ID or the block of a user. Sends after the commit; a failure only warns, + * because the nightly cleanup sends every user again. + */ +public interface AccountStatePublisher { + void publish(long userId); +} diff --git a/src/test/java/auth_service/modules/account/repo/AccountRepositoryTest.java b/src/test/java/auth_service/modules/account/repo/AccountRepositoryTest.java index b43967f..db25412 100644 --- a/src/test/java/auth_service/modules/account/repo/AccountRepositoryTest.java +++ b/src/test/java/auth_service/modules/account/repo/AccountRepositoryTest.java @@ -1,5 +1,6 @@ package auth_service.modules.account.repo; +import auth_service.modules.account.model.CoreAccountState; import auth_service.shared.test.AbstractPersistenceTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -49,6 +50,16 @@ class AccountRepositoryTest extends AbstractPersistenceTest { bob = insertUser("bob@example.com"); } + @Test + void findStates_CarryTheSteamIdAndTheBlock() { + jdbc.update("update users set enabled = false, steam_id = 76561198000000001 where id = ?", alice); + + assertThat(repository.findState(alice)).contains(new CoreAccountState(alice, 76561198000000001L, true)); + assertThat(repository.findAllStates()).containsExactly(new CoreAccountState(alice, 76561198000000001L, true), + new CoreAccountState(bob, null, false)); + assertThat(repository.findState(-1L)).isEmpty(); + } + @Test void flagDeleted_SetsTheFlagAndRevokesOnlyThatUsersTokens() { assertThat(repository.flagDeleted(alice, NOW)).isEqualTo(1); diff --git a/src/test/java/auth_service/modules/account/service/AccountCleanupServiceTest.java b/src/test/java/auth_service/modules/account/service/AccountCleanupServiceTest.java index a1e6731..fa9e109 100644 --- a/src/test/java/auth_service/modules/account/service/AccountCleanupServiceTest.java +++ b/src/test/java/auth_service/modules/account/service/AccountCleanupServiceTest.java @@ -1,5 +1,6 @@ package auth_service.modules.account.service; +import auth_service.modules.account.model.CoreAccountState; import auth_service.modules.account.model.CoreCleanupResult; import auth_service.modules.account.repo.AccountRepository; import auth_service.shared.test.AbstractUnitTest; @@ -24,6 +25,8 @@ import static org.mockito.Mockito.when; class AccountCleanupServiceTest extends AbstractUnitTest { + private static final List STATES = List.of(new CoreAccountState(1L, 76561198000000001L, true)); + private static final Duration RETENTION = Duration.ofDays(30); @Mock @@ -45,6 +48,7 @@ class AccountCleanupServiceTest extends AbstractUnitTest { lenient().when(accountRepository.findFlaggedIds()).thenReturn(List.of(7L, 8L)); lenient().when(accountRepository.findFlaggedBefore(any())).thenReturn(List.of(7L)); lenient().when(accountRepository.findAllIds()).thenReturn(List.of(1L, 7L, 8L)); + lenient().when(accountRepository.findAllStates()).thenReturn(STATES); } @Test @@ -55,9 +59,22 @@ class AccountCleanupServiceTest extends AbstractUnitTest { service(false).runNightly(); verify(coreAccountService).replaceDeleted(List.of(7L, 8L)); + verify(coreAccountService).replaceStates(STATES); verify(accountRepository).deleteUsers(List.of(7L)); } + @Test + void runNightly_AFailedStateCallStillPurgesAndPrunes() { + doThrow(new RestClientException("Core too old")).when(coreAccountService).replaceStates(any()); + when(coreAccountService.purge(eq(List.of(7L)), anyBoolean())).thenReturn(core(false)); + when(coreAccountService.prune(eq(List.of(1L, 7L, 8L)), anyBoolean())).thenReturn(core(false)); + + service(false).runNightly(); + + verify(accountRepository).deleteUsers(List.of(7L)); + verify(coreAccountService).prune(List.of(1L, 7L, 8L), false); + } + @Test void runNightly_InDryRunAsksCoreForADryRunAndDeletesNothingInAuth() { when(coreAccountService.purge(eq(List.of(7L)), anyBoolean())).thenReturn(core(true)); diff --git a/src/test/java/auth_service/modules/account/service/CoreAccountServiceTest.java b/src/test/java/auth_service/modules/account/service/CoreAccountServiceTest.java index 9a4724f..87a51fb 100644 --- a/src/test/java/auth_service/modules/account/service/CoreAccountServiceTest.java +++ b/src/test/java/auth_service/modules/account/service/CoreAccountServiceTest.java @@ -1,5 +1,6 @@ package auth_service.modules.account.service; +import auth_service.modules.account.model.CoreAccountState; import auth_service.modules.account.model.CoreCleanupResult; import auth_service.shared.security.ServiceTokenIssuer; import auth_service.shared.test.AbstractUnitTest; @@ -65,6 +66,17 @@ class CoreAccountServiceTest extends AbstractUnitTest { core.verify(); } + @Test + void applyAndReplaceStates_SendTheContractBodies() { + expect("applyStates"); + expect("replaceStates"); + + service.applyStates(List.of(new CoreAccountState(42L, 76561198000000001L, true))); + service.replaceStates(List.of(new CoreAccountState(42L, null, false))); + + core.verify(); + } + @Test void purgeAndPrune_ReadTheContractResult() { expect("purge"); diff --git a/src/test/java/auth_service/modules/account/service/CoreAccountStatePublisherTest.java b/src/test/java/auth_service/modules/account/service/CoreAccountStatePublisherTest.java new file mode 100644 index 0000000..7c2a9db --- /dev/null +++ b/src/test/java/auth_service/modules/account/service/CoreAccountStatePublisherTest.java @@ -0,0 +1,82 @@ +package auth_service.modules.account.service; + +import auth_service.modules.account.model.CoreAccountState; +import auth_service.modules.account.repo.AccountRepository; +import auth_service.shared.test.AbstractUnitTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.web.client.RestClientException; + +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class CoreAccountStatePublisherTest extends AbstractUnitTest { + + private static final CoreAccountState STATE = new CoreAccountState(7L, 76561198000000001L, true); + + @Mock + private AccountRepository accountRepository; + + @Mock + private CoreAccountService coreAccountService; + + private CoreAccountStatePublisher publisher; + + // Runs each task at once, so the test sees the call. + @BeforeEach + void setUp() { + publisher = new CoreAccountStatePublisher(accountRepository, coreAccountService, Runnable::run); + } + + @Test + void withoutATransaction_SendsAtOnce() { + when(accountRepository.findState(7L)).thenReturn(Optional.of(STATE)); + + publisher.publish(7L); + + verify(coreAccountService).applyStates(List.of(STATE)); + } + + @Test + void inATransaction_SendsOnlyAfterTheCommit() { + when(accountRepository.findState(7L)).thenReturn(Optional.of(STATE)); + TransactionSynchronizationManager.initSynchronization(); + try { + publisher.publish(7L); + verify(coreAccountService, never()).applyStates(any()); + + TransactionSynchronizationManager.getSynchronizations().forEach(TransactionSynchronization::afterCommit); + } finally { + TransactionSynchronizationManager.clearSynchronization(); + } + + verify(coreAccountService).applyStates(List.of(STATE)); + } + + @Test + void coreDown_OnlyWarns() { + when(accountRepository.findState(7L)).thenReturn(Optional.of(STATE)); + doThrow(new RestClientException("Core down")).when(coreAccountService).applyStates(any()); + + assertDoesNotThrow(() -> publisher.publish(7L)); + } + + @Test + void unknownUser_SendsNothing() { + when(accountRepository.findState(7L)).thenReturn(Optional.empty()); + + publisher.publish(7L); + + verify(coreAccountService, never()).applyStates(any()); + } +} diff --git a/src/test/java/auth_service/modules/admin/service/AdminUserServiceTest.java b/src/test/java/auth_service/modules/admin/service/AdminUserServiceTest.java index 2036e38..2bfe5bb 100644 --- a/src/test/java/auth_service/modules/admin/service/AdminUserServiceTest.java +++ b/src/test/java/auth_service/modules/admin/service/AdminUserServiceTest.java @@ -5,6 +5,7 @@ import auth_service.modules.admin.exception.TargetUserNotFoundException; import auth_service.modules.admin.model.TargetState; import auth_service.modules.admin.repo.AdminUserRepository; import auth_service.shared.account.AccountDeleter; +import auth_service.shared.account.AccountStatePublisher; import auth_service.shared.audit.AdminAuditLog; import auth_service.shared.security.Role; import auth_service.shared.test.AbstractUnitTest; @@ -39,11 +40,14 @@ class AdminUserServiceTest extends AbstractUnitTest { @Mock private AccountDeleter accountDeleter; + @Mock + private AccountStatePublisher accountStatePublisher; + private AdminUserService service; @BeforeEach void setUp() { - service = new AdminUserService(adminUserRepository, adminAuditLog, accountDeleter); + service = new AdminUserService(adminUserRepository, adminAuditLog, accountDeleter, accountStatePublisher); } private void target(Role role, boolean deleted) { @@ -67,6 +71,7 @@ class AdminUserServiceTest extends AbstractUnitTest { verify(adminUserRepository).setBlocked(eq(TARGET), eq(true), any()); verify(adminAuditLog).record(ADMIN, "BLOCK", TARGET, null); + verify(accountStatePublisher).publish(TARGET); } @Test diff --git a/src/test/java/auth_service/modules/user/service/UserServiceTest.java b/src/test/java/auth_service/modules/user/service/UserServiceTest.java index aed151d..aa87049 100644 --- a/src/test/java/auth_service/modules/user/service/UserServiceTest.java +++ b/src/test/java/auth_service/modules/user/service/UserServiceTest.java @@ -10,6 +10,7 @@ import auth_service.modules.user.model.SteamLinkOutcome; import auth_service.modules.user.model.User; import auth_service.modules.user.model.UserSettings; import auth_service.modules.user.persistence.UserRepository; +import auth_service.shared.account.AccountStatePublisher; import auth_service.shared.security.TokenGenerator; import auth_service.shared.test.AbstractUnitTest; import org.junit.jupiter.api.Test; @@ -41,6 +42,9 @@ class UserServiceTest extends AbstractUnitTest { @Mock private TokenGenerator tokenGenerator; + @Mock + private AccountStatePublisher accountStatePublisher; + @InjectMocks private UserService userService; @@ -124,6 +128,7 @@ class UserServiceTest extends AbstractUnitTest { assertEquals(SteamLinkOutcome.LINKED, userService.linkVerifiedSteamId(1L, STEAM_ID)); verify(userRepository).setVerifiedSteamId(1L, STEAM_ID); + verify(accountStatePublisher).publish(1L); } @Test @@ -145,6 +150,8 @@ class UserServiceTest extends AbstractUnitTest { InOrder order = inOrder(userRepository); order.verify(userRepository).clearSteamId(2L); order.verify(userRepository).setVerifiedSteamId(1L, STEAM_ID); + verify(accountStatePublisher).publish(2L); + verify(accountStatePublisher).publish(1L); } @Test @@ -155,6 +162,7 @@ class UserServiceTest extends AbstractUnitTest { verify(userRepository, never()).clearSteamId(anyLong()); verify(userRepository, never()).setVerifiedSteamId(anyLong(), anyLong()); + verifyNoInteractions(accountStatePublisher); } @Test @@ -164,6 +172,7 @@ class UserServiceTest extends AbstractUnitTest { assertEquals("token-without-steam", userService.unlinkSteamId(1L, Role.USER)); verify(userRepository).clearSteamId(1L); + verify(accountStatePublisher).publish(1L); } @Test diff --git a/src/test/resources/contract/auth-core-contract.json b/src/test/resources/contract/auth-core-contract.json index d83d832..138efa1 100644 --- a/src/test/resources/contract/auth-core-contract.json +++ b/src/test/resources/contract/auth-core-contract.json @@ -55,6 +55,16 @@ "path": "/internal/accounts/deleted", "request": {"userIds": []} }, + "applyStates": { + "method": "POST", + "path": "/internal/accounts/states", + "request": {"accounts": [{"userId": 42, "steamId": 76561198000000001, "blocked": true}]} + }, + "replaceStates": { + "method": "PUT", + "path": "/internal/accounts/states", + "request": {"accounts": [{"userId": 42, "steamId": null, "blocked": false}]} + }, "purge": { "method": "POST", "path": "/internal/accounts/purge",