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();