mirror of
https://github.com/AsamK/signal-cli.git
synced 2026-08-25 05:26:03 +00:00
Keep the unidentified socket alive while a JSON-RPC connection is open (including stdio mode) and while a D-Bus object is exported, instead of for the lifetime of the receive loop. The receive loop does not use the unauthenticated socket, so keeping it alive there was semantically wrong. This also covers --receive-mode=manual, where no receive loop runs butclients still send messages.
790 lines
23 KiB
Java
790 lines
23 KiB
Java
package org.asamk.signal.jsonrpc;
|
|
|
|
import org.asamk.signal.manager.Manager;
|
|
import org.asamk.signal.manager.MultiAccountManager;
|
|
import org.asamk.signal.manager.ProvisioningManager;
|
|
import org.asamk.signal.manager.RegistrationManager;
|
|
import org.asamk.signal.manager.api.CallInfo;
|
|
import org.asamk.signal.manager.api.CallOffer;
|
|
import org.asamk.signal.manager.api.Configuration;
|
|
import org.asamk.signal.manager.api.Device;
|
|
import org.asamk.signal.manager.api.DeviceLinkUrl;
|
|
import org.asamk.signal.manager.api.Group;
|
|
import org.asamk.signal.manager.api.GroupId;
|
|
import org.asamk.signal.manager.api.GroupInviteLinkUrl;
|
|
import org.asamk.signal.manager.api.Identity;
|
|
import org.asamk.signal.manager.api.IdentityVerificationCode;
|
|
import org.asamk.signal.manager.api.Message;
|
|
import org.asamk.signal.manager.api.MessageEnvelope;
|
|
import org.asamk.signal.manager.api.Pair;
|
|
import org.asamk.signal.manager.api.ReceiveConfig;
|
|
import org.asamk.signal.manager.api.Recipient;
|
|
import org.asamk.signal.manager.api.RecipientIdentifier;
|
|
import org.asamk.signal.manager.api.SendGroupMessageResults;
|
|
import org.asamk.signal.manager.api.SendMessageResult;
|
|
import org.asamk.signal.manager.api.SendMessageResults;
|
|
import org.asamk.signal.manager.api.StickerPack;
|
|
import org.asamk.signal.manager.api.StickerPackId;
|
|
import org.asamk.signal.manager.api.StickerPackUrl;
|
|
import org.asamk.signal.manager.api.TurnServer;
|
|
import org.asamk.signal.manager.api.TypingAction;
|
|
import org.asamk.signal.manager.api.UpdateGroup;
|
|
import org.asamk.signal.manager.api.UpdateProfile;
|
|
import org.asamk.signal.manager.api.UserStatus;
|
|
import org.asamk.signal.manager.api.UsernameLinkUrl;
|
|
import org.asamk.signal.manager.api.UsernameStatus;
|
|
import org.asamk.signal.output.JsonWriter;
|
|
import org.junit.jupiter.api.Test;
|
|
|
|
import java.io.File;
|
|
import java.io.InputStream;
|
|
import java.net.URI;
|
|
import java.time.Duration;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Optional;
|
|
import java.util.Queue;
|
|
import java.util.Set;
|
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
import java.util.function.Consumer;
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
|
/**
|
|
* Tests for the subscribeCallEvents / unsubscribeCallEvents JSON-RPC commands
|
|
* introduced in commit d1e93dd.
|
|
*/
|
|
class SubscribeCallEventsTest {
|
|
|
|
/**
|
|
* Feeds pre-configured JSON-RPC lines to the handler, then returns null to end.
|
|
*/
|
|
private static class LineFeeder {
|
|
|
|
private final Queue<String> lines = new ConcurrentLinkedQueue<>();
|
|
|
|
void addLine(String line) {
|
|
lines.add(line);
|
|
}
|
|
|
|
String getLine() {
|
|
return lines.poll();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Captures JSON-RPC responses written by the handler.
|
|
*/
|
|
private static class CapturingJsonWriter implements JsonWriter {
|
|
|
|
final List<Object> written = Collections.synchronizedList(new ArrayList<>());
|
|
|
|
@Override
|
|
public void write(final Object object) {
|
|
written.add(object);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Minimal Manager stub that tracks call event listener add/remove calls.
|
|
*/
|
|
private static class StubManager implements Manager {
|
|
|
|
final List<CallEventListener> listeners = new ArrayList<>();
|
|
final AtomicInteger addCount = new AtomicInteger(0);
|
|
final AtomicInteger removeCount = new AtomicInteger(0);
|
|
final String selfNumber;
|
|
|
|
StubManager(String selfNumber) {
|
|
this.selfNumber = selfNumber;
|
|
}
|
|
|
|
@Override
|
|
public void addCallEventListener(CallEventListener listener) {
|
|
addCount.incrementAndGet();
|
|
listeners.add(listener);
|
|
}
|
|
|
|
@Override
|
|
public void removeCallEventListener(CallEventListener listener) {
|
|
removeCount.incrementAndGet();
|
|
listeners.remove(listener);
|
|
}
|
|
|
|
@Override
|
|
public String getSelfNumber() {
|
|
return selfNumber;
|
|
}
|
|
|
|
// --- Stubs for remaining Manager interface methods ---
|
|
@Override
|
|
public Map<String, UserStatus> getUserStatus(Set<String> n) {
|
|
return Map.of();
|
|
}
|
|
|
|
@Override
|
|
public Map<String, UsernameStatus> getUsernameStatus(Set<String> u) {
|
|
return Map.of();
|
|
}
|
|
|
|
@Override
|
|
public void updateAccountAttributes(String d, Boolean u, Boolean dn, Boolean ns) {
|
|
}
|
|
|
|
@Override
|
|
public Configuration getConfiguration() {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void updateConfiguration(Configuration c) {
|
|
}
|
|
|
|
@Override
|
|
public void updateProfile(UpdateProfile u) {
|
|
}
|
|
|
|
@Override
|
|
public String getUsername() {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public UsernameLinkUrl getUsernameLink() {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void setUsername(String u) {
|
|
}
|
|
|
|
@Override
|
|
public void deleteUsername() {
|
|
}
|
|
|
|
@Override
|
|
public void startChangeNumber(String n, boolean v, String c) {
|
|
}
|
|
|
|
@Override
|
|
public void finishChangeNumber(String n, String v, String p) {
|
|
}
|
|
|
|
@Override
|
|
public void unregister() {
|
|
}
|
|
|
|
@Override
|
|
public void deleteAccount() {
|
|
}
|
|
|
|
@Override
|
|
public void submitRateLimitRecaptchaChallenge(String c, String cap) {
|
|
}
|
|
|
|
@Override
|
|
public List<Device> getLinkedDevices() {
|
|
return List.of();
|
|
}
|
|
|
|
@Override
|
|
public void updateLinkedDevice(int d, String n) {
|
|
}
|
|
|
|
@Override
|
|
public void removeLinkedDevices(int d) {
|
|
}
|
|
|
|
@Override
|
|
public void addDeviceLink(DeviceLinkUrl u) {
|
|
}
|
|
|
|
@Override
|
|
public void setRegistrationLockPin(Optional<String> p) {
|
|
}
|
|
|
|
@Override
|
|
public List<Group> getGroups() {
|
|
return List.of();
|
|
}
|
|
|
|
@Override
|
|
public List<Group> getGroups(Collection<GroupId> g) {
|
|
return List.of();
|
|
}
|
|
|
|
@Override
|
|
public SendGroupMessageResults quitGroup(GroupId g, Set<RecipientIdentifier.Single> a) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void deleteGroup(GroupId g) {
|
|
}
|
|
|
|
@Override
|
|
public Pair<GroupId, SendGroupMessageResults> createGroup(
|
|
String n,
|
|
Set<RecipientIdentifier.Single> m,
|
|
String a
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public SendGroupMessageResults updateGroup(GroupId g, UpdateGroup u) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public Pair<GroupId, SendGroupMessageResults> joinGroup(GroupInviteLinkUrl u) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public SendMessageResults sendTypingMessage(TypingAction a, Set<RecipientIdentifier> r) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public SendMessageResults sendReadReceipt(RecipientIdentifier.Single s, List<Long> m) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public SendMessageResults sendViewedReceipt(RecipientIdentifier.Single s, List<Long> m) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public SendMessageResults sendMessage(Message m, Set<RecipientIdentifier> r, boolean n) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public SendMessageResults sendEditMessage(Message m, Set<RecipientIdentifier> r, long t) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public SendMessageResults sendRemoteDeleteMessage(long t, Set<RecipientIdentifier> r) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public SendMessageResults sendMessageReaction(
|
|
String e,
|
|
boolean rm,
|
|
RecipientIdentifier.Single a,
|
|
long t,
|
|
Set<RecipientIdentifier> r,
|
|
boolean n,
|
|
boolean s
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public SendMessageResults sendAdminDelete(
|
|
RecipientIdentifier.Single a,
|
|
long t,
|
|
Set<RecipientIdentifier.Group> r,
|
|
boolean n,
|
|
boolean s
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public SendMessageResults sendPinMessage(
|
|
int d,
|
|
RecipientIdentifier.Single a,
|
|
long t,
|
|
Set<RecipientIdentifier> r,
|
|
boolean n,
|
|
boolean s
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public SendMessageResults sendUnpinMessage(
|
|
RecipientIdentifier.Single a,
|
|
long t,
|
|
Set<RecipientIdentifier> r,
|
|
boolean n,
|
|
boolean s
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public SendMessageResults sendPaymentNotificationMessage(byte[] r, String n, RecipientIdentifier.Single re) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void sendEndSessionMessage(Set<RecipientIdentifier.Single> r) {
|
|
}
|
|
|
|
@Override
|
|
public SendMessageResults sendMessageRequestResponse(
|
|
MessageEnvelope.Sync.MessageRequestResponse.Type t,
|
|
Set<RecipientIdentifier> r
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public SendMessageResults sendPollCreateMessage(
|
|
String q,
|
|
boolean a,
|
|
List<String> o,
|
|
Set<RecipientIdentifier> r,
|
|
boolean n
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public SendMessageResults sendPollVoteMessage(
|
|
RecipientIdentifier.Single a,
|
|
long t,
|
|
List<Integer> o,
|
|
int v,
|
|
Set<RecipientIdentifier> r,
|
|
boolean n
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public SendMessageResults sendPollTerminateMessage(long t, Set<RecipientIdentifier> r, boolean n) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void hideRecipient(RecipientIdentifier.Single r) {
|
|
}
|
|
|
|
@Override
|
|
public void deleteRecipient(RecipientIdentifier.Single r) {
|
|
}
|
|
|
|
@Override
|
|
public void deleteContact(RecipientIdentifier.Single r) {
|
|
}
|
|
|
|
@Override
|
|
public void setContactName(RecipientIdentifier.Single r, String g, String f, String ng, String nf, String n) {
|
|
}
|
|
|
|
@Override
|
|
public void setContactsBlocked(Collection<RecipientIdentifier.Single> r, boolean b) {
|
|
}
|
|
|
|
@Override
|
|
public void setGroupsBlocked(Collection<GroupId> g, boolean b) {
|
|
}
|
|
|
|
@Override
|
|
public void setExpirationTimer(RecipientIdentifier.Single r, int t) {
|
|
}
|
|
|
|
@Override
|
|
public StickerPackUrl uploadStickerPack(File p) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void installStickerPack(StickerPackUrl u) {
|
|
}
|
|
|
|
@Override
|
|
public List<StickerPack> getStickerPacks() {
|
|
return List.of();
|
|
}
|
|
|
|
@Override
|
|
public void requestAllSyncData() {
|
|
}
|
|
|
|
@Override
|
|
public void addReceiveHandler(ReceiveMessageHandler h, boolean w) {
|
|
}
|
|
|
|
@Override
|
|
public void removeReceiveHandler(ReceiveMessageHandler h) {
|
|
}
|
|
|
|
@Override
|
|
public boolean isReceiving() {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public void receiveMessages(Optional<Duration> t, Optional<Integer> m, ReceiveMessageHandler h) {
|
|
}
|
|
|
|
@Override
|
|
public void stopReceiveMessages() {
|
|
}
|
|
|
|
@Override
|
|
public void setReceiveConfig(ReceiveConfig r) {
|
|
}
|
|
|
|
@Override
|
|
public boolean isContactBlocked(RecipientIdentifier.Single r) {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public void sendContacts() {
|
|
}
|
|
|
|
@Override
|
|
public List<Recipient> getRecipients(
|
|
boolean o,
|
|
Optional<Boolean> b,
|
|
Collection<RecipientIdentifier.Single> a,
|
|
Optional<String> n
|
|
) {
|
|
return List.of();
|
|
}
|
|
|
|
@Override
|
|
public String getContactOrProfileName(RecipientIdentifier.Single r) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public Group getGroup(GroupId g) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public List<Identity> getIdentities() {
|
|
return List.of();
|
|
}
|
|
|
|
@Override
|
|
public List<Identity> getIdentities(RecipientIdentifier.Single r) {
|
|
return List.of();
|
|
}
|
|
|
|
@Override
|
|
public boolean trustIdentityVerified(RecipientIdentifier.Single r, IdentityVerificationCode v) {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean trustIdentityAllKeys(RecipientIdentifier.Single r) {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public void addAddressChangedListener(Runnable l) {
|
|
}
|
|
|
|
@Override
|
|
public void addClosedListener(Runnable l) {
|
|
}
|
|
|
|
@Override
|
|
public void addUnidentifiedKeepAlive(String token) {
|
|
}
|
|
|
|
@Override
|
|
public void removeUnidentifiedKeepAlive(String token) {
|
|
}
|
|
|
|
@Override
|
|
public InputStream retrieveAttachment(String id) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public InputStream retrieveContactAvatar(RecipientIdentifier.Single r) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public InputStream retrieveProfileAvatar(RecipientIdentifier.Single r) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public InputStream retrieveGroupAvatar(GroupId g) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public InputStream retrieveSticker(StickerPackId s, int i) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public CallInfo startCall(RecipientIdentifier.Single r) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public CallInfo acceptCall(long c) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void hangupCall(long c) {
|
|
}
|
|
|
|
@Override
|
|
public SendMessageResult rejectCall(long c) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public List<CallInfo> listActiveCalls() {
|
|
return List.of();
|
|
}
|
|
|
|
@Override
|
|
public void sendCallOffer(RecipientIdentifier.Single r, CallOffer o) {
|
|
}
|
|
|
|
@Override
|
|
public void sendCallAnswer(RecipientIdentifier.Single r, long c, byte[] a) {
|
|
}
|
|
|
|
@Override
|
|
public void sendIceUpdate(RecipientIdentifier.Single r, long c, List<byte[]> i) {
|
|
}
|
|
|
|
@Override
|
|
public void sendHangup(RecipientIdentifier.Single r, long c, MessageEnvelope.Call.Hangup.Type t) {
|
|
}
|
|
|
|
@Override
|
|
public void sendBusy(RecipientIdentifier.Single r, long c) {
|
|
}
|
|
|
|
@Override
|
|
public List<TurnServer> getTurnServerInfo() {
|
|
return List.of();
|
|
}
|
|
|
|
@Override
|
|
public void close() {
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Minimal MultiAccountManager stub for multi-account mode tests.
|
|
*/
|
|
private static class StubMultiAccountManager implements MultiAccountManager {
|
|
|
|
final List<Manager> managers;
|
|
final List<Consumer<Manager>> addedHandlers = new ArrayList<>();
|
|
|
|
StubMultiAccountManager(List<Manager> managers) {
|
|
this.managers = new ArrayList<>(managers);
|
|
}
|
|
|
|
@Override
|
|
public List<String> getAccountNumbers() {
|
|
return managers.stream().map(Manager::getSelfNumber).toList();
|
|
}
|
|
|
|
@Override
|
|
public List<Manager> getManagers() {
|
|
return managers;
|
|
}
|
|
|
|
@Override
|
|
public void addOnManagerAddedHandler(Consumer<Manager> handler) {
|
|
addedHandlers.add(handler);
|
|
}
|
|
|
|
@Override
|
|
public void addOnManagerRemovedHandler(Consumer<Manager> handler) {
|
|
}
|
|
|
|
@Override
|
|
public Manager getManager(String phoneNumber) {
|
|
return managers.stream().filter(m -> phoneNumber.equals(m.getSelfNumber())).findFirst().orElse(null);
|
|
}
|
|
|
|
@Override
|
|
public URI getNewProvisioningDeviceLinkUri() {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public ProvisioningManager getProvisioningManagerFor(URI u) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public RegistrationManager getNewRegistrationManager(String a) {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void close() {
|
|
}
|
|
}
|
|
|
|
private static String jsonRpcCall(int id, String method) {
|
|
return "{\"jsonrpc\":\"2.0\",\"id\":" + id + ",\"method\":\"" + method + "\"}";
|
|
}
|
|
|
|
private static String jsonRpcCall(int id, String method, String params) {
|
|
return "{\"jsonrpc\":\"2.0\",\"id\":" + id + ",\"method\":\"" + method + "\",\"params\":" + params + "}";
|
|
}
|
|
|
|
// --- Single-account mode tests ---
|
|
|
|
@Test
|
|
void callEventsNotSubscribedByDefault() {
|
|
var manager = new StubManager("+15551234567");
|
|
var feeder = new LineFeeder();
|
|
var writer = new CapturingJsonWriter();
|
|
|
|
// Send no subscribeCallEvents, just end the connection
|
|
var handler = new SignalJsonRpcDispatcherHandler(writer, feeder::getLine, true);
|
|
handler.handleConnection(manager);
|
|
|
|
// No listeners should have been added
|
|
assertEquals(0, manager.addCount.get(), "call events should not be auto-subscribed");
|
|
}
|
|
|
|
@Test
|
|
void subscribeCallEventsAddsListener() {
|
|
var manager = new StubManager("+15551234567");
|
|
var feeder = new LineFeeder();
|
|
var writer = new CapturingJsonWriter();
|
|
|
|
feeder.addLine(jsonRpcCall(1, "subscribeCallEvents"));
|
|
// null terminates the read loop
|
|
|
|
var handler = new SignalJsonRpcDispatcherHandler(writer, feeder::getLine, true);
|
|
handler.handleConnection(manager);
|
|
|
|
assertEquals(1, manager.addCount.get(), "subscribeCallEvents should add one listener");
|
|
// Cleanup in finally block should remove it
|
|
assertEquals(1, manager.removeCount.get(), "cleanup should remove the listener");
|
|
assertEquals(0, manager.listeners.size(), "no listeners should remain after cleanup");
|
|
}
|
|
|
|
@Test
|
|
void subscribeCallEventsCanBeCalledMultipleTimes() {
|
|
var manager = new StubManager("+15551234567");
|
|
var feeder = new LineFeeder();
|
|
var writer = new CapturingJsonWriter();
|
|
|
|
feeder.addLine(jsonRpcCall(1, "subscribeCallEvents"));
|
|
feeder.addLine(jsonRpcCall(2, "subscribeCallEvents"));
|
|
|
|
var handler = new SignalJsonRpcDispatcherHandler(writer, feeder::getLine, true);
|
|
handler.handleConnection(manager);
|
|
|
|
// The implementation allows multiple subscriptions, so two calls add two listeners
|
|
assertEquals(2, manager.addCount.get(), "multiple subscribeCallEvents should add multiple listeners");
|
|
}
|
|
|
|
@Test
|
|
void unsubscribeCallEventsRemovesListener() {
|
|
var manager = new StubManager("+15551234567");
|
|
var feeder = new LineFeeder();
|
|
var writer = new CapturingJsonWriter();
|
|
|
|
feeder.addLine(jsonRpcCall(1, "subscribeCallEvents"));
|
|
feeder.addLine(jsonRpcCall(2, "unsubscribeCallEvents", "{\"subscription\":0}"));
|
|
|
|
var handler = new SignalJsonRpcDispatcherHandler(writer, feeder::getLine, true);
|
|
handler.handleConnection(manager);
|
|
|
|
assertEquals(1, manager.addCount.get(), "should have subscribed once");
|
|
// removeCount: 1 from explicit unsubscribe. The finally block's unsubscribeAllCallEvents
|
|
// iterates an empty list so adds 0 more.
|
|
assertEquals(1, manager.removeCount.get(), "should have unsubscribed once");
|
|
assertEquals(0, manager.listeners.size());
|
|
}
|
|
|
|
@Test
|
|
void unsubscribeWithoutSubscribeIsNoOp() {
|
|
var manager = new StubManager("+15551234567");
|
|
var feeder = new LineFeeder();
|
|
var writer = new CapturingJsonWriter();
|
|
|
|
feeder.addLine(jsonRpcCall(1, "unsubscribeCallEvents"));
|
|
|
|
var handler = new SignalJsonRpcDispatcherHandler(writer, feeder::getLine, true);
|
|
handler.handleConnection(manager);
|
|
|
|
assertEquals(0, manager.addCount.get());
|
|
assertEquals(0, manager.removeCount.get());
|
|
}
|
|
|
|
// --- Multi-account mode tests ---
|
|
|
|
@Test
|
|
void multiAccountSubscribeCallEventsSubscribesAllManagers() {
|
|
var manager1 = new StubManager("+15551111111");
|
|
var manager2 = new StubManager("+15552222222");
|
|
var multi = new StubMultiAccountManager(List.of(manager1, manager2));
|
|
|
|
var feeder = new LineFeeder();
|
|
var writer = new CapturingJsonWriter();
|
|
|
|
feeder.addLine(jsonRpcCall(1, "subscribeCallEvents"));
|
|
|
|
var handler = new SignalJsonRpcDispatcherHandler(writer, feeder::getLine, true);
|
|
handler.handleConnection(multi);
|
|
|
|
assertEquals(1, manager1.addCount.get(), "manager1 should have one listener");
|
|
assertEquals(1, manager2.addCount.get(), "manager2 should have one listener");
|
|
// Registers onManagerAdded handlers for receive, call events, and keep-alive
|
|
assertEquals(3, multi.addedHandlers.size(), "should register onManagerAdded handlers");
|
|
}
|
|
|
|
@Test
|
|
void multiAccountUnsubscribeCallEventsCleansUpAll() {
|
|
var manager1 = new StubManager("+15551111111");
|
|
var manager2 = new StubManager("+15552222222");
|
|
var multi = new StubMultiAccountManager(List.of(manager1, manager2));
|
|
|
|
var feeder = new LineFeeder();
|
|
var writer = new CapturingJsonWriter();
|
|
|
|
feeder.addLine(jsonRpcCall(1, "subscribeCallEvents"));
|
|
feeder.addLine(jsonRpcCall(2, "unsubscribeCallEvents", "{\"subscription\":0}"));
|
|
|
|
var handler = new SignalJsonRpcDispatcherHandler(writer, feeder::getLine, true);
|
|
handler.handleConnection(multi);
|
|
|
|
assertEquals(1, manager1.addCount.get());
|
|
assertEquals(1, manager2.addCount.get());
|
|
assertEquals(1, manager1.removeCount.get(), "manager1 listener should be removed");
|
|
assertEquals(1, manager2.removeCount.get(), "manager2 listener should be removed");
|
|
}
|
|
|
|
@Test
|
|
void multiAccountCallEventsNotSubscribedByDefault() {
|
|
var manager1 = new StubManager("+15551111111");
|
|
var multi = new StubMultiAccountManager(List.of(manager1));
|
|
|
|
var feeder = new LineFeeder();
|
|
var writer = new CapturingJsonWriter();
|
|
|
|
var handler = new SignalJsonRpcDispatcherHandler(writer, feeder::getLine, true);
|
|
handler.handleConnection(multi);
|
|
|
|
assertEquals(0, manager1.addCount.get(), "call events should not be auto-subscribed in multi mode");
|
|
}
|
|
}
|