Add test for round-trip AccountsManager JSON serialization

This commit is contained in:
Chris Eager 2023-08-23 14:42:02 -05:00 committed by Chris Eager
parent 708f23a2ee
commit f181397664
4 changed files with 104 additions and 7 deletions

View File

@ -915,7 +915,7 @@ public class AccountsManager {
private void redisSet(Account account) {
try (Timer.Context ignored = redisSetTimer.time()) {
final String accountJson = ACCOUNT_REDIS_JSON_WRITER.writeValueAsString(account);
final String accountJson = writeRedisAccountJson(account);
cacheCluster.useCluster(connection -> {
final RedisAdvancedClusterCommands<String, String> commands = connection.sync();
@ -936,7 +936,7 @@ public class AccountsManager {
final String accountJson;
try {
accountJson = ACCOUNT_REDIS_JSON_WRITER.writeValueAsString(account);
accountJson = writeRedisAccountJson(account);
} catch (final JsonProcessingException e) {
throw new UncheckedIOException(e);
}
@ -1047,7 +1047,8 @@ public class AccountsManager {
.toCompletableFuture();
}
private static Optional<Account> parseAccountJson(@Nullable final String accountJson, final UUID uuid) {
@VisibleForTesting
static Optional<Account> parseAccountJson(@Nullable final String accountJson, final UUID uuid) {
try {
if (StringUtils.isNotBlank(accountJson)) {
Account account = SystemMapper.jsonMapper().readValue(accountJson, Account.class);
@ -1067,6 +1068,11 @@ public class AccountsManager {
}
}
@VisibleForTesting
static String writeRedisAccountJson(final Account account) throws JsonProcessingException {
return ACCOUNT_REDIS_JSON_WRITER.writeValueAsString(account);
}
private void redisDelete(final Account account) {
try (final Timer.Context ignored = redisDeleteTimer.time()) {
cacheCluster.useCluster(connection -> {

View File

@ -31,6 +31,8 @@ import static org.mockito.Mockito.when;
import io.lettuce.core.RedisException;
import io.lettuce.core.cluster.api.async.RedisAdvancedClusterAsyncCommands;
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.time.Duration;
import java.util.ArrayList;
@ -39,6 +41,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
@ -1328,6 +1331,46 @@ class AccountsManagerTest {
assertThrows(AssertionError.class, () -> accountsManager.update(account, a -> a.setUsernameHash(USERNAME_HASH_1)));
}
@Test
void testJsonRoundTripSerialization() throws Exception {
String originalJson;
try (InputStream inputStream = getClass().getResourceAsStream(
"AccountsManagerTest-testJsonRoundTripSerialization.json")) {
Objects.requireNonNull(inputStream);
originalJson = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
}
final Account originalAccount = AccountsManager.parseAccountJson(originalJson,
UUID.fromString("111111-1111-1111-1111-111111111111")).orElseThrow();
final String serialized = AccountsManager.writeRedisAccountJson(originalAccount);
final Account parsedAccount = AccountsManager.parseAccountJson(serialized, originalAccount.getUuid()).orElseThrow();
assertEquals(originalAccount.getUuid(), parsedAccount.getUuid());
assertEquals(originalAccount.getPhoneNumberIdentifier(), parsedAccount.getPhoneNumberIdentifier());
assertEquals(originalAccount.getIdentityKey(IdentityType.ACI), parsedAccount.getIdentityKey(IdentityType.ACI));
assertEquals(originalAccount.getIdentityKey(IdentityType.PNI), parsedAccount.getIdentityKey(IdentityType.PNI));
assertEquals(originalAccount.getNumber(), parsedAccount.getNumber());
assertArrayEquals(originalAccount.getUnidentifiedAccessKey().orElseThrow(),
parsedAccount.getUnidentifiedAccessKey().orElseThrow());
assertEquals(originalAccount.isDiscoverableByPhoneNumber(), parsedAccount.isDiscoverableByPhoneNumber());
assertEquals(originalAccount.isUnrestrictedUnidentifiedAccess(), parsedAccount.isUnrestrictedUnidentifiedAccess());
assertEquals(originalAccount.getDevices().size(), parsedAccount.getDevices().size());
final Device originalDevice = originalAccount.getMasterDevice().orElseThrow();
final Device parsedDevice = parsedAccount.getMasterDevice().orElseThrow();
assertEquals(originalDevice.getId(), parsedDevice.getId());
assertEquals(originalDevice.getSignedPreKey(IdentityType.ACI), parsedDevice.getSignedPreKey(IdentityType.ACI));
assertEquals(originalDevice.getSignedPreKey(IdentityType.PNI), parsedDevice.getSignedPreKey(IdentityType.PNI));
assertEquals(originalDevice.getRegistrationId(), parsedDevice.getRegistrationId());
assertEquals(originalDevice.getPhoneNumberIdentityRegistrationId(),
parsedDevice.getPhoneNumberIdentityRegistrationId());
assertEquals(originalDevice.getCapabilities(), parsedDevice.getCapabilities());
assertEquals(originalDevice.getFetchesMessages(), parsedDevice.getFetchesMessages());
}
private void setReservationHash(final Account account, final byte[] reservedUsernameHash) {
account.setReservedUsernameHash(reservedUsernameHash);
}

View File

@ -183,13 +183,13 @@ class AccountsTest {
accounts.create(account);
final UUID linkHandle = UUID.randomUUID();
final byte[] encruptedUsername = RandomUtils.nextBytes(32);
accountsManager.update(account, a -> a.setUsernameLinkDetails(linkHandle, encruptedUsername));
final byte[] encryptedUsername = RandomUtils.nextBytes(32);
accountsManager.update(account, a -> a.setUsernameLinkDetails(linkHandle, encryptedUsername));
final Optional<Account> maybeAccount = accountsManager.getByUsernameLinkHandle(linkHandle);
assertTrue(maybeAccount.isPresent());
assertTrue(maybeAccount.get().getEncryptedUsername().isPresent());
assertArrayEquals(encruptedUsername, maybeAccount.get().getEncryptedUsername().get());
assertArrayEquals(encryptedUsername, maybeAccount.get().getEncryptedUsername().get());
// making some unrelated change and updating account to check that username link data is still there
final Optional<Account> accountToChange = accountsManager.getByAccountIdentifier(account.getUuid());
@ -198,7 +198,7 @@ class AccountsTest {
final Optional<Account> accountAfterChange = accountsManager.getByUsernameLinkHandle(linkHandle);
assertTrue(accountAfterChange.isPresent());
assertTrue(accountAfterChange.get().getEncryptedUsername().isPresent());
assertArrayEquals(encruptedUsername, accountAfterChange.get().getEncryptedUsername().get());
assertArrayEquals(encryptedUsername, accountAfterChange.get().getEncryptedUsername().get());
// now deleting the link
final Optional<Account> accountToDeleteLink = accountsManager.getByAccountIdentifier(account.getUuid());

View File

@ -0,0 +1,48 @@
{
"number": "+14152222222",
"usernameHash": null,
"reservedUsernameHash": null,
"usernameLinkHandle": null,
"devices": [
{
"id": 1,
"name": null,
"authToken": null,
"salt": null,
"gcmId": null,
"apnId": null,
"voipApnId": null,
"pushTimestamp": 0,
"uninstalledFeedback": 0,
"fetchesMessages": true,
"registrationId": 1,
"signedPreKey": {
"keyId": 1,
"publicKey": "BerKjYSh1PdniL5bhI9kwbH/Et3mx/8CypR1TYo/+d5o",
"signature": "iK2yJkl0l6qe58Fy1dVo31X5sp6EiXSS5FZfa3W//E+Abylfa6ZRmM97CzTdXNu2DjgxZYF43G6HfJ49+99hgg"
},
"lastSeen" : 1692748800000,
"created" : 1692718240137,
"userAgent": null,
"capabilities": null,
"pniRegistrationId": 2,
"pniSignedPreKey": {
"keyId": 2,
"publicKey": "BXcLL1VLft3tUnr/5UIW5Q0Hsr8/Az0CGJ+EuFqiXCYc",
"signature": "YoKqyeOCHC0E9mqMoc1UPeyuLqGc8nvY+3D3YX5HC1bhxS48ZLYo40xql51A2CpIBqVmA+2gV3PXCV1Yhq4UAQ"
}
}
],
"identityKey": "BaMV4k/+jSn7jmHnRAPvfc7XBZOcayrhOmHFbGJwMyFS",
"badges": [],
"registrationLock": null,
"registrationLockSalt": null,
"version": 0,
"pni": "22222222-2222-2222-2222-222222222222",
"eu": null,
"pniIdentityKey": "Bc0Myhpf2D+iCgUfIs+UStgffR/VGQRfP9mwFHI4U2x4",
"cpv": null,
"uak": "p5uWNi83Muqsd16PLi0/tQ==",
"uua": true,
"inCds": true
}