From 62d8f635b02a33d08782a0939bfd64e83be3c8ad Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Thu, 13 Aug 2015 11:25:05 -0700 Subject: [PATCH] Track voice support on TS server. // FREEBIE --- pom.xml | 2 +- .../textsecuregcm/WhisperServerService.java | 4 ++++ .../controllers/AccountController.java | 23 ++++++++----------- .../controllers/FederationControllerV1.java | 2 +- .../entities/AccountAttributes.java | 14 +++++++---- .../textsecuregcm/entities/ClientContact.java | 17 ++++++++++---- .../federation/NonLimitedAccount.java | 2 +- .../providers/RedisHealthCheck.java | 6 +---- .../textsecuregcm/push/FeedbackHandler.java | 1 + .../textsecuregcm/storage/Account.java | 12 +++++++++- .../textsecuregcm/storage/Accounts.java | 2 -- .../storage/AccountsManager.java | 2 +- .../textsecuregcm/storage/Device.java | 14 ++++++++++- .../storage/DirectoryManager.java | 22 ++++++++++-------- .../workers/DirectoryUpdater.java | 3 +-- .../workers/TrimMessagesCommand.java | 3 --- .../controllers/AccountControllerTest.java | 2 ++ .../controllers/DeviceControllerTest.java | 2 +- .../controllers/FederatedControllerTest.java | 6 ++--- .../controllers/MessageControllerTest.java | 8 +++---- .../controllers/ReceiptControllerTest.java | 6 ++--- .../tests/entities/ClientContactTest.java | 12 ++++++---- .../tests/entities/PreKeyTest.java | 2 +- .../fixtures/contact.relay.voice.json | 5 ++++ 24 files changed, 108 insertions(+), 64 deletions(-) create mode 100644 src/test/resources/fixtures/contact.relay.voice.json diff --git a/pom.xml b/pom.xml index baf81f3f1..8669217a5 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ org.whispersystems.textsecure TextSecureServer - 0.71 + 0.75 0.9.0-rc3 diff --git a/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index ea1980034..74ce4e0a2 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -18,6 +18,8 @@ package org.whispersystems.textsecuregcm; import com.codahale.metrics.SharedMetricRegistries; import com.codahale.metrics.graphite.GraphiteReporter; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.DeserializationFeature; import com.google.common.base.Optional; import org.bouncycastle.jce.provider.BouncyCastleProvider; @@ -148,6 +150,8 @@ public class WhisperServerService extends Application getAuthenticatedDevice() { - return Optional.of(new Device(deviceId, null, null, null, null, null, null, null, false, 0, null, System.currentTimeMillis(), System.currentTimeMillis())); + return Optional.of(new Device(deviceId, null, null, null, null, null, null, null, false, 0, null, System.currentTimeMillis(), System.currentTimeMillis(), false)); } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/providers/RedisHealthCheck.java b/src/main/java/org/whispersystems/textsecuregcm/providers/RedisHealthCheck.java index 219eb86b6..5340ecf24 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/providers/RedisHealthCheck.java +++ b/src/main/java/org/whispersystems/textsecuregcm/providers/RedisHealthCheck.java @@ -31,9 +31,7 @@ public class RedisHealthCheck extends HealthCheck { @Override protected Result check() throws Exception { - Jedis client = clientPool.getResource(); - - try { + try (Jedis client = clientPool.getResource()) { client.set("HEALTH", "test"); if (!"test".equals(client.get("HEALTH"))) { @@ -41,8 +39,6 @@ public class RedisHealthCheck extends HealthCheck { } return Result.healthy(); - } finally { - clientPool.returnResource(client); } } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/push/FeedbackHandler.java b/src/main/java/org/whispersystems/textsecuregcm/push/FeedbackHandler.java index 52ac44051..e76c7bbcb 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/push/FeedbackHandler.java +++ b/src/main/java/org/whispersystems/textsecuregcm/push/FeedbackHandler.java @@ -82,6 +82,7 @@ public class FeedbackHandler implements Managed, Runnable { device.get().setGcmId(event.getCanonicalId()); } else { device.get().setGcmId(null); + device.get().setFetchesMessages(false); } accountsManager.update(account.get()); } diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java b/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java index 15fa31716..c9cee94ee 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/Account.java @@ -71,7 +71,7 @@ public class Account { } public void removeDevice(long deviceId) { - this.devices.remove(new Device(deviceId, null, null, null, null, null, null, null, false, 0, null, 0, 0)); + this.devices.remove(new Device(deviceId, null, null, null, null, null, null, null, false, 0, null, 0, 0, false)); } public Set getDevices() { @@ -92,6 +92,16 @@ public class Account { return Optional.absent(); } + public boolean isVoiceSupported() { + for (Device device : devices) { + if (device.isActive() && device.isVoiceSupported()) { + return true; + } + } + + return false; + } + public boolean isActive() { return getMasterDevice().isPresent() && diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/Accounts.java b/src/main/java/org/whispersystems/textsecuregcm/storage/Accounts.java index 085502419..13f260c47 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/Accounts.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/Accounts.java @@ -16,8 +16,6 @@ */ package org.whispersystems.textsecuregcm.storage; -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.skife.jdbi.v2.SQLStatement; diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java b/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java index 04a22f52f..7a552eb10 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java @@ -100,7 +100,7 @@ public class AccountsManager { private void updateDirectory(Account account) { if (account.isActive()) { byte[] token = Util.getContactToken(account.getNumber()); - ClientContact clientContact = new ClientContact(token, null); + ClientContact clientContact = new ClientContact(token, null, account.isVoiceSupported()); directory.add(clientContact); } else { directory.remove(account.getNumber()); diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/Device.java b/src/main/java/org/whispersystems/textsecuregcm/storage/Device.java index 97f524978..65e4a0ff6 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/Device.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/Device.java @@ -70,13 +70,16 @@ public class Device { @JsonProperty private long created; + @JsonProperty + private boolean voice; + public Device() {} public Device(long id, String name, String authToken, String salt, String signalingKey, String gcmId, String apnId, String voipApnId, boolean fetchesMessages, int registrationId, SignedPreKey signedPreKey, - long lastSeen, long created) + long lastSeen, long created, boolean voice) { this.id = id; this.name = name; @@ -91,6 +94,7 @@ public class Device { this.signedPreKey = signedPreKey; this.lastSeen = lastSeen; this.created = created; + this.voice = voice; } public String getApnId() { @@ -157,6 +161,14 @@ public class Device { this.name = name; } + public boolean isVoiceSupported() { + return voice; + } + + public void setVoiceSupported(boolean voice) { + this.voice = voice; + } + public void setAuthenticationCredentials(AuthenticationCredentials credentials) { this.authToken = credentials.getHashedAuthenticationToken(); this.salt = credentials.getSalt(); diff --git a/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryManager.java b/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryManager.java index bf40ed068..97e46ea0a 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryManager.java +++ b/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryManager.java @@ -61,9 +61,9 @@ public class DirectoryManager { } public void remove(byte[] token) { - Jedis jedis = redisPool.getResource(); - jedis.hdel(DIRECTORY_KEY, token); - redisPool.returnResource(jedis); + try (Jedis jedis = redisPool.getResource()) { + jedis.hdel(DIRECTORY_KEY, token); + } } public void remove(BatchOperationHandle handle, byte[] token) { @@ -72,7 +72,7 @@ public class DirectoryManager { } public void add(ClientContact contact) { - TokenValue tokenValue = new TokenValue(contact.getRelay()); + TokenValue tokenValue = new TokenValue(contact.getRelay(), contact.isVoice()); try (Jedis jedis = redisPool.getResource()) { jedis.hset(DIRECTORY_KEY, contact.getToken(), objectMapper.writeValueAsBytes(tokenValue)); @@ -84,7 +84,7 @@ public class DirectoryManager { public void add(BatchOperationHandle handle, ClientContact contact) { try { Pipeline pipeline = handle.pipeline; - TokenValue tokenValue = new TokenValue(contact.getRelay()); + TokenValue tokenValue = new TokenValue(contact.getRelay(), contact.isVoice()); pipeline.hset(DIRECTORY_KEY, contact.getToken(), objectMapper.writeValueAsBytes(tokenValue)); } catch (JsonProcessingException e) { @@ -106,7 +106,7 @@ public class DirectoryManager { } TokenValue tokenValue = objectMapper.readValue(result, TokenValue.class); - return Optional.of(new ClientContact(token, tokenValue.relay)); + return Optional.of(new ClientContact(token, tokenValue.relay, tokenValue.voice)); } catch (IOException e) { logger.warn("JSON Error", e); return Optional.absent(); @@ -133,7 +133,7 @@ public class DirectoryManager { try { if (pair.second().get() != null) { TokenValue tokenValue = objectMapper.readValue(pair.second().get(), TokenValue.class); - ClientContact clientContact = new ClientContact(pair.first(), tokenValue.relay); + ClientContact clientContact = new ClientContact(pair.first(), tokenValue.relay, tokenValue.voice); results.add(clientContact); } @@ -175,10 +175,14 @@ public class DirectoryManager { @JsonProperty(value = "r") private String relay; + @JsonProperty(value = "v") + private boolean voice; + public TokenValue() {} - public TokenValue(String relay) { + public TokenValue(String relay, boolean voice) { this.relay = relay; + this.voice = voice; } } @@ -201,7 +205,7 @@ public class DirectoryManager { } TokenValue tokenValue = objectMapper.readValue(result, TokenValue.class); - return Optional.of(new ClientContact(token, tokenValue.relay)); + return Optional.of(new ClientContact(token, tokenValue.relay, tokenValue.voice)); } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/workers/DirectoryUpdater.java b/src/main/java/org/whispersystems/textsecuregcm/workers/DirectoryUpdater.java index 7b5d3cf96..d674cb469 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/workers/DirectoryUpdater.java +++ b/src/main/java/org/whispersystems/textsecuregcm/workers/DirectoryUpdater.java @@ -26,7 +26,6 @@ import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.DirectoryManager; import org.whispersystems.textsecuregcm.storage.DirectoryManager.BatchOperationHandle; -import org.whispersystems.textsecuregcm.util.Base64; import org.whispersystems.textsecuregcm.util.Util; import java.io.IOException; @@ -73,7 +72,7 @@ public class DirectoryUpdater { for (Account account : accounts) { if (account.isActive()) { byte[] token = Util.getContactToken(account.getNumber()); - ClientContact clientContact = new ClientContact(token, null); + ClientContact clientContact = new ClientContact(token, null, account.isVoiceSupported()); directory.add(batchOperation, clientContact); contactsAdded++; diff --git a/src/main/java/org/whispersystems/textsecuregcm/workers/TrimMessagesCommand.java b/src/main/java/org/whispersystems/textsecuregcm/workers/TrimMessagesCommand.java index 727fa7ff2..979c794e4 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/workers/TrimMessagesCommand.java +++ b/src/main/java/org/whispersystems/textsecuregcm/workers/TrimMessagesCommand.java @@ -5,10 +5,7 @@ import org.skife.jdbi.v2.DBI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.WhisperServerConfiguration; -import org.whispersystems.textsecuregcm.storage.Accounts; -import org.whispersystems.textsecuregcm.storage.Keys; import org.whispersystems.textsecuregcm.storage.Messages; -import org.whispersystems.textsecuregcm.storage.PendingAccounts; import java.util.concurrent.TimeUnit; diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java index dae140b50..7c563bdd2 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java @@ -19,6 +19,7 @@ import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.MessagesManager; import org.whispersystems.textsecuregcm.storage.PendingAccountsManager; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; +import org.whispersystems.textsecuregcm.util.SystemMapper; import javax.ws.rs.client.Entity; import javax.ws.rs.core.MediaType; @@ -47,6 +48,7 @@ public class AccountControllerTest { public final ResourceTestRule resources = ResourceTestRule.builder() .addProvider(AuthHelper.getAuthFilter()) .addProvider(new AuthValueFactoryProvider.Binder()) + .setMapper(SystemMapper.getMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(new AccountController(pendingAccountsManager, accountsManager, diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java index 9f33cda73..9aa0752e2 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/DeviceControllerTest.java @@ -135,7 +135,7 @@ public class DeviceControllerTest { .target("/v1/devices/5678901") .request() .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, "password1")) - .put(Entity.entity(new AccountAttributes("keykeykeykey", false, 1234, "this is a really long name that is longer than 80 characters"), + .put(Entity.entity(new AccountAttributes("keykeykeykey", false, 1234, "this is a really long name that is longer than 80 characters", true), MediaType.APPLICATION_JSON_TYPE)); assertEquals(response.getStatus(), 422); diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/FederatedControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/FederatedControllerTest.java index 834141c72..9c1616bbe 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/FederatedControllerTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/FederatedControllerTest.java @@ -79,12 +79,12 @@ public class FederatedControllerTest { @Before public void setup() throws Exception { Set singleDeviceList = new HashSet() {{ - add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 111, null, System.currentTimeMillis(), System.currentTimeMillis())); + add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 111, null, System.currentTimeMillis(), System.currentTimeMillis(), false)); }}; Set multiDeviceList = new HashSet() {{ - add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 222, null, System.currentTimeMillis(), System.currentTimeMillis())); - add(new Device(2, null, "foo", "bar", "baz", "isgcm", null, null, false, 333, null, System.currentTimeMillis(), System.currentTimeMillis())); + add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 222, null, System.currentTimeMillis(), System.currentTimeMillis(), false)); + add(new Device(2, null, "foo", "bar", "baz", "isgcm", null, null, false, 333, null, System.currentTimeMillis(), System.currentTimeMillis(), false)); }}; Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, singleDeviceList); diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java index a8ae7823c..916c98d72 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/MessageControllerTest.java @@ -74,13 +74,13 @@ public class MessageControllerTest { @Before public void setup() throws Exception { Set singleDeviceList = new HashSet() {{ - add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 111, null, System.currentTimeMillis(), System.currentTimeMillis())); + add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 111, null, System.currentTimeMillis(), System.currentTimeMillis(), false)); }}; Set multiDeviceList = new HashSet() {{ - add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 222, new SignedPreKey(111, "foo", "bar"), System.currentTimeMillis(), System.currentTimeMillis())); - add(new Device(2, null, "foo", "bar", "baz", "isgcm", null, null, false, 333, new SignedPreKey(222, "oof", "rab"), System.currentTimeMillis(), System.currentTimeMillis())); - add(new Device(3, null, "foo", "bar", "baz", "isgcm", null, null, false, 444, null, System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31), System.currentTimeMillis())); + add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 222, new SignedPreKey(111, "foo", "bar"), System.currentTimeMillis(), System.currentTimeMillis(), false)); + add(new Device(2, null, "foo", "bar", "baz", "isgcm", null, null, false, 333, new SignedPreKey(222, "oof", "rab"), System.currentTimeMillis(), System.currentTimeMillis(), false)); + add(new Device(3, null, "foo", "bar", "baz", "isgcm", null, null, false, 444, null, System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31), System.currentTimeMillis(), false)); }}; Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, singleDeviceList); diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ReceiptControllerTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ReceiptControllerTest.java index 693812f48..bcc57690f 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ReceiptControllerTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/ReceiptControllerTest.java @@ -53,12 +53,12 @@ public class ReceiptControllerTest { @Before public void setup() throws Exception { Set singleDeviceList = new HashSet() {{ - add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 111, null, System.currentTimeMillis(), System.currentTimeMillis())); + add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 111, null, System.currentTimeMillis(), System.currentTimeMillis(), false)); }}; Set multiDeviceList = new HashSet() {{ - add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 222, null, System.currentTimeMillis(), System.currentTimeMillis())); - add(new Device(2, null, "foo", "bar", "baz", "isgcm", null, null, false, 333, null, System.currentTimeMillis(), System.currentTimeMillis())); + add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 222, null, System.currentTimeMillis(), System.currentTimeMillis(), false)); + add(new Device(2, null, "foo", "bar", "baz", "isgcm", null, null, false, 333, null, System.currentTimeMillis(), System.currentTimeMillis(), false)); }}; Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, singleDeviceList); diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/entities/ClientContactTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/entities/ClientContactTest.java index 4d4d4450c..c23f459f9 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/entities/ClientContactTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/entities/ClientContactTest.java @@ -14,9 +14,9 @@ public class ClientContactTest { @Test public void serializeToJSON() throws Exception { byte[] token = Util.getContactToken("+14152222222"); - ClientContact contact = new ClientContact(token, null); - ClientContact contactWithRelay = new ClientContact(token, "whisper"); - ClientContact contactWithRelaySms = new ClientContact(token, "whisper"); + ClientContact contact = new ClientContact(token, null, false); + ClientContact contactWithRelay = new ClientContact(token, "whisper", false); + ClientContact contactWithRelayVox = new ClientContact(token, "whisper", true); assertThat("Basic Contact Serialization works", asJson(contact), @@ -25,12 +25,16 @@ public class ClientContactTest { assertThat("Contact Relay Serialization works", asJson(contactWithRelay), is(equalTo(jsonFixture("fixtures/contact.relay.json")))); + + assertThat("Contact Relay Vox Serializaton works", + asJson(contactWithRelayVox), + is(equalTo(jsonFixture("fixtures/contact.relay.voice.json")))); } @Test public void deserializeFromJSON() throws Exception { ClientContact contact = new ClientContact(Util.getContactToken("+14152222222"), - "whisper"); + "whisper", false); assertThat("a ClientContact can be deserialized from JSON", fromJson(jsonFixture("fixtures/contact.relay.json"), ClientContact.class), diff --git a/src/test/java/org/whispersystems/textsecuregcm/tests/entities/PreKeyTest.java b/src/test/java/org/whispersystems/textsecuregcm/tests/entities/PreKeyTest.java index a77ef3ffb..fee62ab83 100644 --- a/src/test/java/org/whispersystems/textsecuregcm/tests/entities/PreKeyTest.java +++ b/src/test/java/org/whispersystems/textsecuregcm/tests/entities/PreKeyTest.java @@ -26,7 +26,7 @@ public class PreKeyTest { @Test public void deserializeFromJSONV() throws Exception { ClientContact contact = new ClientContact(Util.getContactToken("+14152222222"), - "whisper"); + "whisper", false); assertThat("a ClientContact can be deserialized from JSON", fromJson(jsonFixture("fixtures/contact.relay.json"), ClientContact.class), diff --git a/src/test/resources/fixtures/contact.relay.voice.json b/src/test/resources/fixtures/contact.relay.voice.json new file mode 100644 index 000000000..6368ffb89 --- /dev/null +++ b/src/test/resources/fixtures/contact.relay.voice.json @@ -0,0 +1,5 @@ +{ + "token" : "BQVVHxMt5zAFXA", + "voice" : true, + "relay" : "whisper" +} \ No newline at end of file