diff --git a/symphony-bdk-core/build.gradle b/symphony-bdk-core/build.gradle
index 1d857c67c..1fac3f7f2 100644
--- a/symphony-bdk-core/build.gradle
+++ b/symphony-bdk-core/build.gradle
@@ -81,20 +81,25 @@ dependencies {
// OpenAPI code generation
def apiBaseUrl = "https://raw.githubusercontent.com/finos/symphony-api-spec/a0b152881a0405b7510cfc7e3a9698e8cb8c566c"
+
+def apiBaseUrlOverrides = [
+ Streams: "https://raw.githubusercontent.com/Tahak-symphony/symphony-api-spec/BE-9867_user_streams"
+]
def generatedFolder = "${layout.buildDirectory.get()}/generated/openapi"
def apisToGenerate = [
Agent: 'agent/agent-api-public-deprecated.yaml',
Pod : 'pod/pod-api-public-deprecated.yaml',
Auth : 'authenticator/authenticator-api-public-deprecated.yaml',
Login: 'login/login-api-public.yaml',
- Users: 'users/users-api-public.yaml'
+ Users: 'users/users-api-public.yaml',
+ Streams: 'streams/streams-api-public.yaml'
]
sourceSets.main.java.srcDirs += "$generatedFolder/src/main/java"
apisToGenerate.each { api, path ->
def downloadTask = tasks.register("download$api", Download) {
- src "$apiBaseUrl/$path"
+ src "${apiBaseUrlOverrides.getOrDefault(api, apiBaseUrl)}/$path"
dest "${layout.buildDirectory.get()}/openapi/$api/${path.split("/")[1]}"
overwrite false
useETag "all"
diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/ServiceFactory.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/ServiceFactory.java
index 94c22876d..adb70864e 100644
--- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/ServiceFactory.java
+++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/ServiceFactory.java
@@ -46,6 +46,7 @@
import com.symphony.bdk.gen.api.SessionApi;
import com.symphony.bdk.gen.api.ShareApi;
import com.symphony.bdk.gen.api.SignalsApi;
+import com.symphony.bdk.gen.api.StreamSearchApi;
import com.symphony.bdk.gen.api.StreamsApi;
import com.symphony.bdk.gen.api.SystemApi;
import com.symphony.bdk.gen.api.UserApi;
@@ -77,6 +78,7 @@ class ServiceFactory {
private final ApiClient podClient;
private final ApiClient usersClient;
private final ApiClient agentClient;
+ private final ApiClient streamClient;
private final ApiClient datafeedAgentClient;
private final ApiClient datahoseAgentClient;
private final AuthSession authSession;
@@ -103,6 +105,7 @@ public ServiceFactory(
this.podClient = apiClientFactory.getPodClient();
this.usersClient = apiClientFactory.getUsersClient();
this.agentClient = apiClientFactory.getAgentClient();
+ this.streamClient = apiClientFactory.getStreamClient();
this.datafeedAgentClient = apiClientFactory.getDatafeedAgentClient();
this.datahoseAgentClient = apiClientFactory.getDatahoseAgentClient();
this.authSession = authSession;
@@ -165,7 +168,7 @@ public ClientDataService getClientDataService() {
*/
public StreamService getStreamService() {
return new StreamService(new StreamsApi(podClient), new RoomMembershipApi(podClient), new ShareApi(agentClient),
- new MessagesApi(agentClient), authSession, retryBuilder);
+ new MessagesApi(agentClient), new StreamSearchApi(streamClient), authSession, retryBuilder);
}
public DisclaimerService getDisclaimerService() {
diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/client/ApiClientFactory.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/client/ApiClientFactory.java
index 07c1ba682..c8dd9dc45 100644
--- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/client/ApiClientFactory.java
+++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/client/ApiClientFactory.java
@@ -31,6 +31,7 @@
public class ApiClientFactory {
private static final String USERS_CONTEXT_PATH = "";
+ private static final String STREAMS_CONTEXT_PATH = "";
private static final String LOGIN_CONTEXT_PATH = "/login";
private static final String POD_CONTEXT_PATH = "/pod";
private static final String AGENT_CONTEXT_PATH = "/agent";
@@ -83,6 +84,10 @@ public ApiClient getUsersClient() {
return buildClient(USERS_CONTEXT_PATH, this.config.getPod());
}
+ public ApiClient getStreamClient() {
+ return buildClient(STREAMS_CONTEXT_PATH, this.config.getPod());
+ }
+
/**
* Returns a fully initialized {@link ApiClient} for KeyManager API.
*
diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/stream/OboStreamService.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/stream/OboStreamService.java
index 984a0ce4a..abf2e0473 100644
--- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/stream/OboStreamService.java
+++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/stream/OboStreamService.java
@@ -7,15 +7,18 @@
import com.symphony.bdk.gen.api.model.Stream;
import com.symphony.bdk.gen.api.model.StreamAttributes;
import com.symphony.bdk.gen.api.model.StreamFilter;
+import com.symphony.bdk.gen.api.model.StreamArrayResponse;
+import com.symphony.bdk.gen.api.model.StreamResponse;
import com.symphony.bdk.gen.api.model.V2Message;
import com.symphony.bdk.gen.api.model.V2RoomSearchCriteria;
import com.symphony.bdk.gen.api.model.V2StreamAttributes;
import com.symphony.bdk.gen.api.model.V3RoomAttributes;
import com.symphony.bdk.gen.api.model.V3RoomDetail;
import com.symphony.bdk.gen.api.model.V3RoomSearchResults;
-import org.apiguardian.api.API;
+import org.apiguardian.api.API;
import org.jspecify.annotations.Nullable;
+
import java.util.List;
/**
@@ -31,7 +34,7 @@ public interface OboStreamService {
* @return The created IM
* @see Create IM or MIM
*/
- Stream create(Long... uids);
+ Stream create(Long... uids);
/**
* Create a new single or multi party instant message conversation between the caller and specified users.
@@ -65,7 +68,7 @@ public interface OboStreamService {
/**
* {@link StreamService#getStream(String)}
*
- * @param streamId The stream id.
+ * @param streamId The stream id.
* @return The information about the stream with the given id.
* @see Stream Info V2
*/
@@ -83,7 +86,7 @@ public interface OboStreamService {
/**
* {@link StreamService#listStreams(StreamFilter)}
*
- * @param filter The stream searching criteria.
+ * @param filter The stream searching criteria.
* @return The list of streams retrieved according to the searching criteria.
* @see List Streams
*/
@@ -92,8 +95,8 @@ public interface OboStreamService {
/**
* {@link StreamService#listStreams(StreamFilter, PaginationAttribute)}
*
- * @param filter The stream searching criteria.
- * @param pagination The skip and limit for pagination.
+ * @param filter The stream searching criteria.
+ * @param pagination The skip and limit for pagination.
* @return The list of streams retrieved according to the searching criteria.
* @see List Streams
*/
@@ -112,8 +115,8 @@ public interface OboStreamService {
/**
* {@link StreamService#listAllStreams(StreamFilter, StreamPaginationAttribute)}
*
- * @param filter The stream searching criteria.
- * @param pagination The chunkSize and totalSize for pagination.
+ * @param filter The stream searching criteria.
+ * @param pagination The chunkSize and totalSize for pagination.
* @return a {@link java.util.stream.Stream} of matching streams according to the searching criteria.
* @see List Streams
*/
@@ -175,8 +178,8 @@ java.util.stream.Stream searchAllRooms(V2RoomSearchCriteria query,
/**
* {@link StreamService#addMemberToRoom(Long, String)}
*
- * @param userId The id of the user to be added to the given room
- * @param roomId The room id
+ * @param userId The id of the user to be added to the given room
+ * @param roomId The room id
* @see Add Member
*/
void addMemberToRoom(Long userId, String roomId);
@@ -184,8 +187,8 @@ java.util.stream.Stream searchAllRooms(V2RoomSearchCriteria query,
/**
* {@link StreamService#removeMemberFromRoom(Long, String)}
*
- * @param userId The id of the user to be removed from the given room
- * @param roomId The room id
+ * @param userId The id of the user to be removed from the given room
+ * @param roomId The room id
* @see Remove Member
*/
void removeMemberFromRoom(Long userId, String roomId);
@@ -193,8 +196,8 @@ java.util.stream.Stream searchAllRooms(V2RoomSearchCriteria query,
/**
* {@link StreamService#share(String, ShareContent)}
*
- * @param streamId The stream id.
- * @param content The third-party {@link ShareContent} to be shared.
+ * @param streamId The stream id.
+ * @param content The third-party {@link ShareContent} to be shared.
* @return Message contains share content
* @see Share
*/
@@ -203,8 +206,8 @@ java.util.stream.Stream searchAllRooms(V2RoomSearchCriteria query,
/**
* {@link StreamService#promoteUserToRoomOwner(Long, String)}
*
- * @param userId The id of the user to be promoted to room owner.
- * @param roomId The room id.
+ * @param userId The id of the user to be promoted to room owner.
+ * @param roomId The room id.
* @see Promote Owner
*/
void promoteUserToRoomOwner(Long userId, String roomId);
@@ -212,8 +215,8 @@ java.util.stream.Stream searchAllRooms(V2RoomSearchCriteria query,
/**
* {@link StreamService#demoteUserToRoomParticipant(Long, String)}
*
- * @param userId The id of the user to be demoted to room participant.
- * @param roomId The room id.
+ * @param userId The id of the user to be demoted to room participant.
+ * @param roomId The room id.
* @see Demote Owner
*/
void demoteUserToRoomParticipant(Long userId, String roomId);
@@ -230,13 +233,58 @@ java.util.stream.Stream searchAllRooms(V2RoomSearchCriteria query,
/**
* Return a representation of the conversation in Markdown format
* Returned data includes :
- * - stream title
- * - stream properties
- * - stream members
- * - stream last messages
+ * - stream title
+ * - stream properties
+ * - stream members
+ * - stream last messages
*
* @param streamId
* @return A String containing markdown representation of the conversation
*/
String getStreamContext(String streamId);
+
+ /**
+ * Lists the authenticated user's active chat streams matching specified filtering criteria.
+ *
+ * @param limit The maximum number of streams to return (maximum 100).
+ * @param includeHidden Whether to include hidden streams in the results.
+ * @param onlyUnread If true, only returns streams that contain unread messages.
+ * @param channel Filter streams by channel type (e.g. SYMPHONY, WHATSAPP, WCHT...).
+ * @param external Filter streams to include external chats.
+ * @param internal Filter streams to include internal chats.
+ * @param includeAgentStreams Whether to include AI_AGENT subtype streams in the results.
+ * @param cursor Opaque pagination cursor returned as nextCursor by a previous call.
+ * @return A {@link StreamArrayResponse} containing the list of active chat streams and next cursor.
+ * @see List User Streams
+ */
+ StreamArrayResponse getUserStreams(@Nullable Integer limit,
+ @Nullable Boolean includeHidden,
+ @Nullable Boolean onlyUnread,
+ @Nullable String channel,
+ @Nullable Boolean external,
+ @Nullable Boolean internal,
+ @Nullable Boolean includeAgentStreams,
+ @Nullable String cursor);
+
+ /**
+ * Retrieve streams for a list of stream IDs via query parameters matching specified filtering criteria.
+ *
+ * @param streamIds List of stream IDs (maximum 50 allowed).
+ * @param includeHidden Whether to include hidden streams in the results.
+ * @param onlyUnread If true, only returns streams that contain unread messages.
+ * @param channel Filter streams by channel type (e.g. SYMPHONY, WHATSAPP, WCHT...).
+ * @param external Filter streams to include external chats.
+ * @param internal Filter streams to include internal chats.
+ * @param includeAgentStreams Whether to include AI_AGENT subtype streams in the results.
+ * @return A list of {@link StreamResponse} matching the provided IDs and criteria.
+ * @see Search Streams
+ */
+ List searchStreams(List streamIds,
+ @Nullable Boolean includeHidden,
+ @Nullable Boolean onlyUnread,
+ @Nullable String channel,
+ @Nullable Boolean external,
+ @Nullable Boolean internal,
+ @Nullable Boolean includeAgentStreams);
+
}
diff --git a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/stream/StreamService.java b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/stream/StreamService.java
index 95125d647..6ffcddb7a 100644
--- a/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/stream/StreamService.java
+++ b/symphony-bdk-core/src/main/java/com/symphony/bdk/core/service/stream/StreamService.java
@@ -15,11 +15,14 @@
import com.symphony.bdk.gen.api.MessagesApi;
import com.symphony.bdk.gen.api.RoomMembershipApi;
import com.symphony.bdk.gen.api.ShareApi;
+import com.symphony.bdk.gen.api.StreamSearchApi;
import com.symphony.bdk.gen.api.StreamsApi;
import com.symphony.bdk.gen.api.model.MemberInfo;
import com.symphony.bdk.gen.api.model.RoomDetail;
import com.symphony.bdk.gen.api.model.ShareContent;
import com.symphony.bdk.gen.api.model.Stream;
+import com.symphony.bdk.gen.api.model.StreamArrayResponse;
+import com.symphony.bdk.gen.api.model.StreamResponse;
import com.symphony.bdk.gen.api.model.StreamAttributes;
import com.symphony.bdk.gen.api.model.StreamFilter;
import com.symphony.bdk.gen.api.model.UserId;
@@ -67,28 +70,31 @@ public class StreamService implements OboStreamService, OboService retryBuilder;
public StreamService(StreamsApi streamsApi, RoomMembershipApi membershipApi, ShareApi shareApi,
- MessagesApi messagesApi,
+ MessagesApi messagesApi, StreamSearchApi streamSearchApi,
AuthSession authSession, RetryWithRecoveryBuilder> retryBuilder) {
this.streamsApi = streamsApi;
this.roomMembershipApi = membershipApi;
this.shareApi = shareApi;
this.messagesApi = messagesApi;
+ this.streamSearchApi = streamSearchApi;
this.authSession = authSession;
this.retryBuilder = RetryWithRecoveryBuilder.copyWithoutRecoveryStrategies(retryBuilder)
.recoveryStrategy(ApiException::isUnauthorized, authSession::refresh);
}
public StreamService(StreamsApi streamsApi, RoomMembershipApi membershipApi, ShareApi shareApi,
- MessagesApi messagesApi,
+ MessagesApi messagesApi, StreamSearchApi streamSearchApi,
RetryWithRecoveryBuilder> retryBuilder) {
this.streamsApi = streamsApi;
this.roomMembershipApi = membershipApi;
this.shareApi = shareApi;
this.messagesApi = messagesApi;
+ this.streamSearchApi = streamSearchApi;
this.authSession = null;
this.retryBuilder = RetryWithRecoveryBuilder.copyWithoutRecoveryStrategies(retryBuilder);
}
@@ -98,7 +104,7 @@ public StreamService(StreamsApi streamsApi, RoomMembershipApi membershipApi, Sha
*/
@Override
public OboStreamService obo(AuthSession oboSession) {
- return new StreamService(streamsApi, roomMembershipApi, shareApi, messagesApi, oboSession, retryBuilder);
+ return new StreamService(streamsApi, roomMembershipApi, shareApi, messagesApi, streamSearchApi, oboSession, retryBuilder);
}
/**
@@ -509,6 +515,28 @@ public String getStreamContext(String streamId) {
() -> messagesApi.v1StreamContext(toUrlSafeIdIfNeeded(streamId), null, null, null, null, authSession.getSessionToken(), authSession.getKeyManagerToken()));
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public StreamArrayResponse getUserStreams(@Nullable Integer limit, @Nullable Boolean includeHidden,
+ @Nullable Boolean onlyUnread, @Nullable String channel, @Nullable Boolean external, @Nullable Boolean internal,
+ @Nullable Boolean includeAgentStreams, @Nullable String cursor) {
+ return executeAndRetry("getUserStreams", streamSearchApi.getApiClient().getBasePath(),
+ () -> streamSearchApi.listUserStreams(authSession.getSessionToken(), limit, includeHidden, onlyUnread, channel, external, internal, includeAgentStreams, cursor));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List searchStreams(List streamIds, @Nullable Boolean includeHidden,
+ @Nullable Boolean onlyUnread, @Nullable String channel, @Nullable Boolean external, @Nullable Boolean internal,
+ @Nullable Boolean includeAgentStreams) {
+ return executeAndRetry("searchStreams", streamSearchApi.getApiClient().getBasePath(),
+ () -> streamSearchApi.getStreamsSearch(authSession.getSessionToken(), streamIds, includeHidden, onlyUnread, channel, external, internal, includeAgentStreams));
+ }
+
private T executeAndRetry(String name, String address, SupplierWithApiException supplier) {
checkAuthSession(authSession);
return RetryWithRecovery.executeAndRetry(retryBuilder, name, address, supplier);
diff --git a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/ServiceFactoryTest.java b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/ServiceFactoryTest.java
index 1ab7f1545..82f5ec0ea 100644
--- a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/ServiceFactoryTest.java
+++ b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/ServiceFactoryTest.java
@@ -58,6 +58,7 @@ void setUp() throws BdkConfigException {
when(this.apiClientFactory.getPodClient()).thenReturn(mPodClient);
when(this.apiClientFactory.getUsersClient()).thenReturn(mock(ApiClient.class));
when(this.apiClientFactory.getAgentClient()).thenReturn(mock(ApiClient.class));
+ when(this.apiClientFactory.getStreamClient()).thenReturn(mock(ApiClient.class));
ApiClient datafeedAgentClient = mock(ApiClient.class);
when(datafeedAgentClient.getBasePath()).thenReturn("/agent");
@@ -83,6 +84,7 @@ void getClientDataServiceTest() {
void getStreamServiceTest() {
StreamService streamService = this.serviceFactory.getStreamService();
assertNotNull(streamService);
+ verify(this.apiClientFactory).getStreamClient();
}
@Test
diff --git a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/client/ApiClientFactoryTest.java b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/client/ApiClientFactoryTest.java
index 4a6c2ab6a..698184444 100644
--- a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/client/ApiClientFactoryTest.java
+++ b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/client/ApiClientFactoryTest.java
@@ -46,6 +46,13 @@ void testGetUsersClient() {
assertEquals("https://pod-host:443", usersClient.getBasePath());
}
+ @Test
+ void testGetStreamClient() {
+ final ApiClient streamClient = this.factory.getStreamClient();
+ assertEquals(ApiClientJersey2.class, streamClient.getClass());
+ assertEquals("https://pod-host:443", streamClient.getBasePath());
+ }
+
@Test
void testGetRelayClient() {
final ApiClient relayClient = this.factory.getRelayClient();
diff --git a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/service/stream/StreamServiceTest.java b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/service/stream/StreamServiceTest.java
index f96a568d0..52dccac8a 100644
--- a/symphony-bdk-core/src/test/java/com/symphony/bdk/core/service/stream/StreamServiceTest.java
+++ b/symphony-bdk-core/src/test/java/com/symphony/bdk/core/service/stream/StreamServiceTest.java
@@ -3,6 +3,7 @@
import static com.symphony.bdk.core.util.IdUtil.fromUrlSafeId;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -22,11 +23,14 @@
import com.symphony.bdk.gen.api.RoomMembershipApi;
import com.symphony.bdk.gen.api.ShareApi;
import com.symphony.bdk.gen.api.MessagesApi;
+import com.symphony.bdk.gen.api.StreamSearchApi;
import com.symphony.bdk.gen.api.StreamsApi;
import com.symphony.bdk.gen.api.model.MemberInfo;
import com.symphony.bdk.gen.api.model.RoomDetail;
import com.symphony.bdk.gen.api.model.ShareContent;
import com.symphony.bdk.gen.api.model.Stream;
+import com.symphony.bdk.gen.api.model.StreamArrayResponse;
+import com.symphony.bdk.gen.api.model.StreamResponse;
import com.symphony.bdk.gen.api.model.StreamAttributes;
import com.symphony.bdk.gen.api.model.StreamFilter;
import com.symphony.bdk.gen.api.model.StreamType;
@@ -80,6 +84,8 @@ public class StreamServiceTest {
private static final String V3_ROOM_UPDATE = "/pod/v3/room/{id}/update";
private static final String V3_SHARE = "/agent/v3/stream/{sid}/share";
private static final String V1_STREAM_CONTEXT = "/agent/v1/stream/{sid}/context";
+ private static final String V4_USER_STREAMS = "/v4/streams/user";
+ private static final String V4_SEARCH_STREAMS = "/v4/streams/search";
private StreamService service;
private MockApiClient mockApiClient;
@@ -87,6 +93,7 @@ public class StreamServiceTest {
private AuthSession authSession;
private StreamsApi streamsApi;
private MessagesApi messagesApi;
+ private StreamSearchApi streamSearchApi;
private ShareApi shareApi;
@BeforeEach
@@ -95,13 +102,15 @@ void setUp() {
this.authSession = mock(AuthSession.class);
ApiClient podClient = mockApiClient.getApiClient("/pod");
ApiClient agentClient = mockApiClient.getApiClient("/agent");
+ ApiClient streamClient = mockApiClient.getApiClient("");
this.spyRoomMembershipApi = spy(new RoomMembershipApi(podClient));
this.streamsApi = spy(new StreamsApi(podClient));
this.shareApi = new ShareApi(agentClient);
this.messagesApi = new MessagesApi(agentClient);
+ this.streamSearchApi = spy(new StreamSearchApi(streamClient));
this.service = new StreamService(this.streamsApi, this.spyRoomMembershipApi, this.shareApi, this.messagesApi,
- this.authSession, new RetryWithRecoveryBuilder<>());
+ this.streamSearchApi, this.authSession, new RetryWithRecoveryBuilder<>());
when(authSession.getSessionToken()).thenReturn("1234");
when(authSession.getKeyManagerToken()).thenReturn("1234");
@@ -110,7 +119,7 @@ void setUp() {
@Test
void nonOboEndpointShouldThrowExceptionInOboMode() {
this.service = new StreamService(this.streamsApi, this.spyRoomMembershipApi, this.shareApi, this.messagesApi,
- new RetryWithRecoveryBuilder<>());
+ this.streamSearchApi, new RetryWithRecoveryBuilder<>());
assertThrows(IllegalStateException.class, () -> this.service.getStream(""));
}
@@ -121,7 +130,7 @@ void getStreamInOboMode() throws IOException {
JsonHelper.readFromClasspath("/stream/v2_stream_attributes.json"));
this.service = new StreamService(this.streamsApi, this.spyRoomMembershipApi, this.shareApi, this.messagesApi,
- new RetryWithRecoveryBuilder<>());
+ this.streamSearchApi, new RetryWithRecoveryBuilder<>());
V2StreamAttributes stream = this.service.obo(this.authSession).getStream("p9B316LKDto7iOECc8Xuz3qeWsc0bdA");
assertEquals("p9B316LKDto7iOECc8Xuz3qeWsc0bdA", stream.getId());
@@ -524,7 +533,7 @@ void getStreamContextInOboMode() {
this.mockApiClient.onGet(V1_STREAM_CONTEXT.replace("{sid}", "p9B316LKDto7iOECc8Xuz3qeWsc0bdA"), "\"Markdown Context\"");
this.service = new StreamService(this.streamsApi, this.spyRoomMembershipApi, this.shareApi, this.messagesApi,
- new RetryWithRecoveryBuilder<>());
+ this.streamSearchApi, new RetryWithRecoveryBuilder<>());
String context = this.service.obo(this.authSession).getStreamContext("p9B316LKDto7iOECc8Xuz3qeWsc0bdA");
assertEquals("Markdown Context", context);
@@ -537,6 +546,96 @@ void getStreamContextTestFailed() {
assertThrows(ApiRuntimeException.class, () -> this.service.getStreamContext("p9B316LKDto7iOECc8Xuz3qeWsc0bdA"));
}
+ @Test
+ void getUserStreamsTest() throws ApiException {
+ this.mockApiClient.onGet(V4_USER_STREAMS,
+ "{\"streams\": [{\"id\": \"p9B316LKDto7iOECc8Xuz3qeWsc0bdA\", \"active\": true}], \"nextCursor\": \"cursor123\"}");
+
+ StreamArrayResponse response = this.service.getUserStreams(10, false, true, "WHATSAPP", true, false, true, "cursor000");
+
+ assertNotNull(response);
+ assertEquals("cursor123", response.getNextCursor());
+ assertEquals(1, response.getStreams().size());
+ assertEquals("p9B316LKDto7iOECc8Xuz3qeWsc0bdA", response.getStreams().get(0).getId());
+ assertTrue(response.getStreams().get(0).getActive());
+ verify(this.streamSearchApi).listUserStreams(eq("1234"), eq(10), eq(false), eq(true), eq("WHATSAPP"), eq(true), eq(false), eq(true), eq("cursor000"));
+ }
+
+ @Test
+ void getUserStreamsInOboMode() throws ApiException {
+ this.mockApiClient.onGet(V4_USER_STREAMS,
+ "{\"streams\": [{\"id\": \"p9B316LKDto7iOECc8Xuz3qeWsc0bdA\", \"active\": true}], \"nextCursor\": \"cursor123\"}");
+
+ this.service = new StreamService(this.streamsApi, this.spyRoomMembershipApi, this.shareApi, this.messagesApi,
+ this.streamSearchApi, new RetryWithRecoveryBuilder<>());
+ StreamArrayResponse response = this.service.obo(this.authSession).getUserStreams(null, null, null, null, null, null, null, null);
+
+ assertNotNull(response);
+ assertEquals("cursor123", response.getNextCursor());
+ assertEquals(1, response.getStreams().size());
+ assertEquals("p9B316LKDto7iOECc8Xuz3qeWsc0bdA", response.getStreams().get(0).getId());
+ verify(this.streamSearchApi).listUserStreams(eq("1234"), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null));
+ }
+
+ @Test
+ void getUserStreamsTestFailed() {
+ this.mockApiClient.onGet(400, V4_USER_STREAMS, "{}");
+
+ assertThrows(ApiRuntimeException.class, () -> this.service.getUserStreams(10, false, true, "WHATSAPP", true, false, true, "cursor000"));
+ }
+
+ @Test
+ void getUserStreamsShouldThrowExceptionWhenNoSession() {
+ this.service = new StreamService(this.streamsApi, this.spyRoomMembershipApi, this.shareApi, this.messagesApi,
+ this.streamSearchApi, new RetryWithRecoveryBuilder<>());
+
+ assertThrows(IllegalStateException.class, () -> this.service.getUserStreams(null, null, null, null, null, null, null, null));
+ }
+
+ @Test
+ void searchStreamsTest() throws ApiException {
+ this.mockApiClient.onGet(V4_SEARCH_STREAMS,
+ "[{\"id\": \"p9B316LKDto7iOECc8Xuz3qeWsc0bdA\", \"active\": true}]");
+
+ List response = this.service.searchStreams(List.of("p9B316LKDto7iOECc8Xuz3qeWsc0bdA"), false, true, "WHATSAPP", true, false, true);
+
+ assertNotNull(response);
+ assertEquals(1, response.size());
+ assertEquals("p9B316LKDto7iOECc8Xuz3qeWsc0bdA", response.get(0).getId());
+ assertTrue(response.get(0).getActive());
+ verify(this.streamSearchApi).getStreamsSearch(eq("1234"), eq(List.of("p9B316LKDto7iOECc8Xuz3qeWsc0bdA")), eq(false), eq(true), eq("WHATSAPP"), eq(true), eq(false), eq(true));
+ }
+
+ @Test
+ void searchStreamsInOboMode() throws ApiException {
+ this.mockApiClient.onGet(V4_SEARCH_STREAMS,
+ "[{\"id\": \"p9B316LKDto7iOECc8Xuz3qeWsc0bdA\", \"active\": true}]");
+
+ this.service = new StreamService(this.streamsApi, this.spyRoomMembershipApi, this.shareApi, this.messagesApi,
+ this.streamSearchApi, new RetryWithRecoveryBuilder<>());
+ List response = this.service.obo(this.authSession).searchStreams(List.of("p9B316LKDto7iOECc8Xuz3qeWsc0bdA"), null, null, null, null, null, null);
+
+ assertNotNull(response);
+ assertEquals(1, response.size());
+ assertEquals("p9B316LKDto7iOECc8Xuz3qeWsc0bdA", response.get(0).getId());
+ verify(this.streamSearchApi).getStreamsSearch(eq("1234"), eq(List.of("p9B316LKDto7iOECc8Xuz3qeWsc0bdA")), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null));
+ }
+
+ @Test
+ void searchStreamsTestFailed() {
+ this.mockApiClient.onGet(400, V4_SEARCH_STREAMS, "{}");
+
+ assertThrows(ApiRuntimeException.class, () -> this.service.searchStreams(List.of("p9B316LKDto7iOECc8Xuz3qeWsc0bdA"), false, true, "WHATSAPP", true, false, true));
+ }
+
+ @Test
+ void searchStreamsShouldThrowExceptionWhenNoSession() {
+ this.service = new StreamService(this.streamsApi, this.spyRoomMembershipApi, this.shareApi, this.messagesApi,
+ this.streamSearchApi, new RetryWithRecoveryBuilder<>());
+
+ assertThrows(IllegalStateException.class, () -> this.service.searchStreams(List.of("p9B316LKDto7iOECc8Xuz3qeWsc0bdA"), null, null, null, null, null, null));
+ }
+
@Test
void createAdminIMorMIMTest() {
this.mockApiClient.onPost(V1_IM_CREATE_ADMIN, "{\n\"id\": \"xhGxbTcvTDK6EIMMrwdOrX___quztr2HdA\"\n}");
diff --git a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkApiClientsConfig.java b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkApiClientsConfig.java
index 06785ac59..4a3799e8d 100644
--- a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkApiClientsConfig.java
+++ b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkApiClientsConfig.java
@@ -1,5 +1,6 @@
package com.symphony.bdk.spring.config;
+import com.symphony.bdk.gen.api.StreamSearchApi;
import com.symphony.bdk.http.api.ApiClient;
import com.symphony.bdk.gen.api.AppEntitlementApi;
import com.symphony.bdk.gen.api.ApplicationApi;
@@ -194,6 +195,12 @@ public StreamsApi streamsApi(@Qualifier("podApiClient") ApiClient podApiClient)
return new StreamsApi(podApiClient);
}
+ @Bean
+ @ConditionalOnMissingBean
+ public StreamSearchApi streamSearchApi(@Qualifier("streamApiClient") ApiClient streamApiClient) {
+ return new StreamSearchApi(streamApiClient);
+ }
+
@Bean
@ConditionalOnMissingBean
public SystemApi systemApi(@Qualifier("agentApiClient") ApiClient agentApiClient) {
diff --git a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkCoreConfig.java b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkCoreConfig.java
index 551eb501c..b37e57fc0 100644
--- a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkCoreConfig.java
+++ b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkCoreConfig.java
@@ -59,6 +59,14 @@ public ApiClient agentApiClient(ApiClientFactory apiClientFactory, Optional botSession,
+ BdkConfig config) {
+ ApiClient client = apiClientFactory.getStreamClient();
+ addCommonJwtConfig(client, botSession, config);
+ return client;
+ }
+
@Bean(name = "datafeedAgentApiClient")
public ApiClient datafeedAgentApiClient(ApiClientFactory apiClientFactory, Optional botSession,
BdkConfig config) {
diff --git a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkOboServiceConfig.java b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkOboServiceConfig.java
index fb364856d..96d737c7d 100644
--- a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkOboServiceConfig.java
+++ b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkOboServiceConfig.java
@@ -27,6 +27,7 @@
import com.symphony.bdk.gen.api.SessionApi;
import com.symphony.bdk.gen.api.ShareApi;
import com.symphony.bdk.gen.api.SignalsApi;
+import com.symphony.bdk.gen.api.StreamSearchApi;
import com.symphony.bdk.gen.api.StreamsApi;
import com.symphony.bdk.gen.api.UserApi;
import com.symphony.bdk.gen.api.UsersApi;
@@ -76,8 +77,8 @@ public SessionService oboSessionService(SessionApi sessionApi, BdkConfig config)
@Bean
@ConditionalOnMissingBean
public StreamService oboStreamService(StreamsApi streamsApi, RoomMembershipApi roomMembershipApi, ShareApi shareApi,
- MessagesApi messagesApi, BdkConfig config) {
- return new StreamService(streamsApi, roomMembershipApi, shareApi, messagesApi, new RetryWithRecoveryBuilder<>().retryConfig(config.getRetry()));
+ MessagesApi messagesApi, StreamSearchApi streamSearchApi, BdkConfig config) {
+ return new StreamService(streamsApi, roomMembershipApi, shareApi, messagesApi, streamSearchApi, new RetryWithRecoveryBuilder<>().retryConfig(config.getRetry()));
}
@Bean
diff --git a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkServiceConfig.java b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkServiceConfig.java
index 415122450..552a06d68 100644
--- a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkServiceConfig.java
+++ b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/main/java/com/symphony/bdk/spring/config/BdkServiceConfig.java
@@ -31,6 +31,7 @@
import com.symphony.bdk.gen.api.SessionApi;
import com.symphony.bdk.gen.api.ShareApi;
import com.symphony.bdk.gen.api.SignalsApi;
+import com.symphony.bdk.gen.api.StreamSearchApi;
import com.symphony.bdk.gen.api.StreamsApi;
import com.symphony.bdk.gen.api.SystemApi;
import com.symphony.bdk.gen.api.UserApi;
@@ -62,8 +63,8 @@ public SessionService sessionService(SessionApi sessionApi, AuthSession botSessi
@Bean
@ConditionalOnMissingBean
public StreamService streamService(StreamsApi streamsApi, RoomMembershipApi roomMembershipApi, ShareApi shareApi, MessagesApi messagesApi,
- AuthSession botSession, BdkConfig config) {
- return new StreamService(streamsApi, roomMembershipApi, shareApi, messagesApi, botSession, new RetryWithRecoveryBuilder<>().retryConfig(config.getRetry()));
+ StreamSearchApi streamSearchApi, AuthSession botSession, BdkConfig config) {
+ return new StreamService(streamsApi, roomMembershipApi, shareApi, messagesApi, streamSearchApi, botSession, new RetryWithRecoveryBuilder<>().retryConfig(config.getRetry()));
}
@Bean
diff --git a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/test/java/com/symphony/bdk/spring/SymphonyBdkAutoConfigurationTest.java b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/test/java/com/symphony/bdk/spring/SymphonyBdkAutoConfigurationTest.java
index 7bc1eac1f..def976520 100644
--- a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/test/java/com/symphony/bdk/spring/SymphonyBdkAutoConfigurationTest.java
+++ b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/test/java/com/symphony/bdk/spring/SymphonyBdkAutoConfigurationTest.java
@@ -13,6 +13,7 @@
import com.symphony.bdk.core.service.datafeed.impl.DatafeedLoopV2;
import com.symphony.bdk.core.service.datafeed.impl.DatahoseLoopImpl;
import com.symphony.bdk.core.service.health.HealthService;
+import com.symphony.bdk.gen.api.StreamSearchApi;
import com.symphony.bdk.gen.api.SystemApi;
import com.symphony.bdk.http.api.ApiClient;
import com.symphony.bdk.spring.annotation.SlashAnnotationProcessor;
@@ -64,6 +65,8 @@ void shouldLoadContextWithSuccess() {
assertThat(context).hasSingleBean(HealthService.class);
assertThat(context).hasSingleBean(DatafeedAsyncLauncherService.class);
assertThat(context).hasSingleBean(RealTimeEventsDispatcher.class);
+ assertThat(context).hasSingleBean(StreamSearchApi.class);
+ assertThat(context).hasBean("streamApiClient");
// verify datahose not enabled by default
assertThat(context).doesNotHaveBean(DatahoseLoopImpl.class);
@@ -237,6 +240,11 @@ void shouldAddAuthenticationIfCommonJwtEnabled() {
ApiClient podClient = (ApiClient) context.getBean("podApiClient");
assertThat(podClient.getAuthentications()).isNotEmpty();
assertThat(podClient.getAuthentications()).containsKey("bearerAuth");
+
+ assertThat(context).hasBean("streamApiClient");
+ ApiClient streamClient = (ApiClient) context.getBean("streamApiClient");
+ assertThat(streamClient.getAuthentications()).isNotEmpty();
+ assertThat(streamClient.getAuthentications()).containsKey("bearerAuth");
});
}
diff --git a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/test/java/com/symphony/bdk/spring/config/BdkCoreConfigTest.java b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/test/java/com/symphony/bdk/spring/config/BdkCoreConfigTest.java
index f9c7aec99..b9d107fd0 100644
--- a/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/test/java/com/symphony/bdk/spring/config/BdkCoreConfigTest.java
+++ b/symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/src/test/java/com/symphony/bdk/spring/config/BdkCoreConfigTest.java
@@ -76,6 +76,19 @@ void shouldCreatePodClient() {
when(factory.getPodClient()).thenReturn(mock(ApiClient.class));
assertNotNull(config.podApiClient(factory, Optional.ofNullable(authSession), bdkConfig));
}
+
+ @Test
+ void shouldCreateStreamApiClient() {
+ final BdkCoreConfig config = new BdkCoreConfig();
+ final BdkConfig bdkConfig = new BdkConfig();
+ BdkCommonJwtConfig bdkCommonJwtConfig = new BdkCommonJwtConfig();
+ bdkCommonJwtConfig.setEnabled(true);
+ bdkConfig.setCommonJwt(bdkCommonJwtConfig);
+ final ApiClientFactory factory = mock(ApiClientFactory.class);
+ final AuthSession authSession = mock(AuthSession.class);
+ when(factory.getStreamClient()).thenReturn(mock(ApiClient.class));
+ assertNotNull(config.streamApiClient(factory, Optional.ofNullable(authSession), bdkConfig));
+ }
@Test
void shouldCreateKeyAuthApiClient() {
final BdkCoreConfig config = new BdkCoreConfig();