1414 * limitations under the License.
1515 */
1616
17- // / @file test_ffi_client.cpp
18- // / @brief Unit tests for FfiClient (the internal singleton that bridges the
19- // / C++ SDK to the Rust FFI runtime).
20- // /
21- // / Scope:
22- // / - Singleton identity
23- // / - initialize() / shutdown() / isInitialized() state machine
24- // / - AddListener() / RemoveListener() lifecycle and ID uniqueness
25- // / - sendRequest() invariants that are reachable without a live FFI
26- // / roundtrip (empty-serialization failure path)
27- // /
28- // / Out of scope (covered by integration tests with a live Rust runtime):
29- // / - Real sendRequest()/sendRequestAsync() responses
30- // / - Listener invocation from the FFI callback thread
31- // /
32- // / DISABLED_ tests in this file are stubs for the "isInitialized() gate"
33- // / design proposal (see project notes). Strip the DISABLED_ prefix once
34- // / the corresponding gate lands in production.
35-
3617#include < gtest/gtest.h>
3718#include < livekit/livekit.h>
3819
@@ -139,7 +120,7 @@ TEST_F(FfiClientTest, AddListenerReturnsUniqueIds) {
139120}
140121
141122TEST_F (FfiClientTest, RemoveListenerWithUnknownIdIsSafe) {
142- EXPECT_NO_THROW (FfiClient::instance ().RemoveListener (/* never registered= */ 424242 ));
123+ EXPECT_NO_THROW (FfiClient::instance ().RemoveListener (424242 ));
143124}
144125
145126TEST_F (FfiClientTest, RemoveListenerIsIdempotent) {
@@ -160,7 +141,7 @@ TEST_F(FfiClientTest, ListenerRegistrationSurvivesShutdownReinitCycle) {
160141}
161142
162143// ---------------------------------------------------------------------------
163- // sendRequest() — invariants reachable without a live FFI
144+ // These tests ensure FfiClient methods throw in various error conditions
164145// ---------------------------------------------------------------------------
165146
166147TEST_F (FfiClientTest, SendRequestThrowsOnEmptyRequest) {
@@ -171,18 +152,7 @@ TEST_F(FfiClientTest, SendRequestThrowsOnEmptyRequest) {
171152 EXPECT_THROW (FfiClient::instance ().sendRequest (req), std::runtime_error);
172153}
173154
174- TEST_F (FfiClientTest, SendRequestWhenNotInitialized) {
175- ASSERT_FALSE (FfiClient::instance ().isInitialized ());
176-
177- proto::FfiRequest req;
178- // Populate any oneof so we get past the empty-bytes serialization check
179- // and hit the proposed init gate instead.
180- (void )req.mutable_dispose ();
181-
182- // EXPECT_THROW(FfiClient::instance().sendRequest(req), std::runtime_error);
183- }
184-
185- TEST_F (FfiClientTest, DISABLED_SendRequestThrowsAfterShutdown) {
155+ TEST_F (FfiClientTest, SendRequestThrowsAfterShutdown) {
186156 ASSERT_TRUE (FfiClient::instance ().initialize (false ));
187157 FfiClient::instance ().shutdown ();
188158 ASSERT_FALSE (FfiClient::instance ().isInitialized ());
@@ -193,146 +163,100 @@ TEST_F(FfiClientTest, DISABLED_SendRequestThrowsAfterShutdown) {
193163 EXPECT_THROW (FfiClient::instance ().sendRequest (req), std::runtime_error);
194164}
195165
196- // ---------------------------------------------------------------------------
197- // PROPOSED: *Async helper isInitialized() gates (Tier 1 #3)
198- //
199- // Each async helper (connectAsync, publishTrackAsync, captureAudioFrameAsync,
200- // publishDataAsync, performRpcAsync, publishDataTrackAsync, etc.) should
201- // surface "not initialized" through its return channel:
202- // - std::future<T> helpers: the future should resolve with an exception
203- // (fut.get() throws std::runtime_error).
204- // - Result<T, E> helpers (subscribeDataTrack, publishDataTrackAsync's
205- // Result payload): the Result should be a failure with an appropriate
206- // error code.
207- //
208- // Each stub below pins down the *contract* for one helper. They are kept
209- // DISABLED for the same reason as sendRequest above: without the gate, the
210- // helper either calls into Rust unsafely or registers a pending waiter that
211- // will never complete.
212- //
213- // As you add each gate, strip the DISABLED_ prefix one test at a time.
214- // ---------------------------------------------------------------------------
215-
216- TEST_F (FfiClientTest, DISABLED_ConnectAsyncFailsWhenNotInitialized) {
166+ TEST_F (FfiClientTest, NotInitialized_ConnectAsyncThrows) {
217167 ASSERT_FALSE (FfiClient::instance ().isInitialized ());
218168
219169 RoomOptions options;
220- auto fut = FfiClient::instance ().connectAsync (" wss://localhost:7880" , " fake-token" , options);
221- EXPECT_THROW (fut.get (), std::runtime_error);
170+ EXPECT_THROW (FfiClient::instance ().connectAsync (" wss://localhost:7880" , " fake-token" , options), std::runtime_error);
222171}
223172
224- TEST_F (FfiClientTest, DISABLED_PublishTrackAsyncFailsWhenNotInitialized ) {
173+ TEST_F (FfiClientTest, NotInitialized_PublishTrackAsyncThrows ) {
225174 ASSERT_FALSE (FfiClient::instance ().isInitialized ());
226175
227- // Handle values are placeholders — the gate must fail before they are
228- // ever forwarded to Rust.
229- // TrackPublishOptions opts;
230- // auto fut = FfiClient::instance().publishTrackAsync(/*participant=*/1, /*track=*/2, opts);
231- // EXPECT_THROW(fut.get(), std::runtime_error);
232- GTEST_SKIP () << " TODO: enable once publishTrackAsync gate lands" ;
176+ TrackPublishOptions options;
177+ EXPECT_THROW (FfiClient::instance ().publishTrackAsync (1 , 2 , options), std::runtime_error);
233178}
234179
235- TEST_F (FfiClientTest, DISABLED_UnpublishTrackAsyncFailsWhenNotInitialized ) {
180+ TEST_F (FfiClientTest, NotInitialized_UnpublishTrackAsyncThrows ) {
236181 ASSERT_FALSE (FfiClient::instance ().isInitialized ());
237- // auto fut = FfiClient::instance().unpublishTrackAsync(/*participant=*/1, "sid", true);
238- // EXPECT_THROW(fut.get(), std::runtime_error);
239- GTEST_SKIP () << " TODO: enable once unpublishTrackAsync gate lands" ;
182+
183+ EXPECT_THROW (FfiClient::instance ().unpublishTrackAsync (1 , " sid" , true ), std::runtime_error);
240184}
241185
242- TEST_F (FfiClientTest, DISABLED_PublishDataAsyncFailsWhenNotInitialized ) {
186+ TEST_F (FfiClientTest, NotInitialized_PublishDataAsyncThrows ) {
243187 ASSERT_FALSE (FfiClient::instance ().isInitialized ());
244- // const std::uint8_t payload[1] = {0};
245- // auto fut = FfiClient::instance().publishDataAsync(
246- // /*participant=*/1, payload, 1, /*reliable=*/true, /*destinations=*/{}, /*topic=*/"");
247- // EXPECT_THROW(fut.get(), std::runtime_error);
248- GTEST_SKIP () << " TODO: enable once publishDataAsync gate lands" ;
188+
189+ const std::uint8_t payload[1 ] = {0 };
190+ EXPECT_THROW (FfiClient::instance ().publishDataAsync (1 , payload, 1 , true , {}, " " ), std::runtime_error);
249191}
250192
251- TEST_F (FfiClientTest, DISABLED_PublishSipDtmfAsyncFailsWhenNotInitialized ) {
193+ TEST_F (FfiClientTest, NotInitialized_PublishSipDtmfAsyncThrows ) {
252194 ASSERT_FALSE (FfiClient::instance ().isInitialized ());
253- // auto fut = FfiClient::instance().publishSipDtmfAsync(/*participant=*/1, /*code=*/1, "1", {});
254- // EXPECT_THROW(fut.get(), std::runtime_error);
255- GTEST_SKIP () << " TODO: enable once publishSipDtmfAsync gate lands" ;
195+
196+ EXPECT_THROW (FfiClient::instance ().publishSipDtmfAsync (1 , 1 , " 1" , {}), std::runtime_error);
256197}
257198
258- TEST_F (FfiClientTest, DISABLED_SetLocalMetadataAsyncFailsWhenNotInitialized ) {
199+ TEST_F (FfiClientTest, NotInitialized_SetLocalMetadataAsyncThrows ) {
259200 ASSERT_FALSE (FfiClient::instance ().isInitialized ());
260- // auto fut = FfiClient::instance().setLocalMetadataAsync(/*participant=*/1, "metadata");
261- // EXPECT_THROW(fut.get(), std::runtime_error);
262- GTEST_SKIP () << " TODO: enable once setLocalMetadataAsync gate lands" ;
201+
202+ EXPECT_THROW (FfiClient::instance ().setLocalMetadataAsync (1 , " metadata" ), std::runtime_error);
263203}
264204
265- TEST_F (FfiClientTest, DISABLED_CaptureAudioFrameAsyncFailsWhenNotInitialized ) {
205+ TEST_F (FfiClientTest, NotInitialized_CaptureAudioFrameAsyncThrows ) {
266206 ASSERT_FALSE (FfiClient::instance ().isInitialized ());
267- // proto::AudioFrameBufferInfo buf;
268- // auto fut = FfiClient::instance().captureAudioFrameAsync(/*source=*/1, buf);
269- // EXPECT_THROW(fut.get(), std::runtime_error);
270- GTEST_SKIP () << " TODO: enable once captureAudioFrameAsync gate lands" ;
207+
208+ proto::AudioFrameBufferInfo buf;
209+ EXPECT_THROW (FfiClient::instance ().captureAudioFrameAsync (1 , buf), std::runtime_error);
271210}
272211
273- TEST_F (FfiClientTest, DISABLED_PerformRpcAsyncFailsWhenNotInitialized ) {
212+ TEST_F (FfiClientTest, NotInitialized_PerformRpcAsyncThrows ) {
274213 ASSERT_FALSE (FfiClient::instance ().isInitialized ());
275- // auto fut = FfiClient::instance().performRpcAsync(
276- // /*participant=*/1, "dest", "method", "payload", std::nullopt);
277- // EXPECT_THROW(fut.get(), std::runtime_error);
278- GTEST_SKIP () << " TODO: enable once performRpcAsync gate lands" ;
214+
215+ EXPECT_THROW (FfiClient::instance ().performRpcAsync (1 , " dest" , " method" , " payload" , std::nullopt ), std::runtime_error);
279216}
280217
281- TEST_F (FfiClientTest, DISABLED_GetTrackStatsAsyncFailsWhenNotInitialized ) {
218+ TEST_F (FfiClientTest, NotInitialized_GetTrackStatsAsyncThrows ) {
282219 ASSERT_FALSE (FfiClient::instance ().isInitialized ());
283- // auto fut = FfiClient::instance().getTrackStatsAsync(/*track=*/1);
284- // EXPECT_THROW(fut.get(), std::runtime_error);
285- GTEST_SKIP () << " TODO: enable once getTrackStatsAsync gate lands" ;
220+
221+ EXPECT_THROW (FfiClient::instance ().getTrackStatsAsync (1 ), std::runtime_error);
286222}
287223
288- TEST_F (FfiClientTest, DISABLED_PublishDataTrackAsyncReturnsFailureWhenNotInitialized ) {
224+ TEST_F (FfiClientTest, NotInitialized_PublishDataTrackAsyncFails ) {
289225 ASSERT_FALSE (FfiClient::instance ().isInitialized ());
290226
291- // publishDataTrackAsync's future yields a Result<..., PublishDataTrackError>,
292- // so the convention here is "failure result" rather than a thrown exception.
293- //
294- // auto fut = FfiClient::instance().publishDataTrackAsync(/*participant=*/1, "name");
295- // auto result = fut.get();
296- // EXPECT_FALSE(result.ok());
297- // EXPECT_EQ(result.error().code, PublishDataTrackErrorCode::INVALID_HANDLE); // or NOT_INITIALIZED
298- GTEST_SKIP () << " TODO: enable once publishDataTrackAsync gate lands; "
299- " decide between INVALID_HANDLE and a new NOT_INITIALIZED code" ;
227+ auto fut_result = FfiClient::instance ().publishDataTrackAsync (1 , " name" );
228+ auto result = fut_result.get ();
229+ EXPECT_FALSE (result.ok ());
230+ EXPECT_EQ (result.error ().code , PublishDataTrackErrorCode::INTERNAL);
300231}
301232
302- TEST_F (FfiClientTest, DISABLED_SubscribeDataTrackReturnsFailureWhenNotInitialized ) {
233+ TEST_F (FfiClientTest, NotInitialized_SubscribeDataTrackFails ) {
303234 ASSERT_FALSE (FfiClient::instance ().isInitialized ());
304235
305- // subscribeDataTrack returns Result<..., SubscribeDataTrackError> directly.
306- //
307- // auto result = FfiClient::instance().subscribeDataTrack(/*track=*/1);
308- // EXPECT_FALSE(result.ok());
309- // EXPECT_EQ(result.error().code, SubscribeDataTrackErrorCode::INVALID_HANDLE); // or NOT_INITIALIZED
310- GTEST_SKIP () << " TODO: enable once subscribeDataTrack gate lands; "
311- " decide between INVALID_HANDLE and a new NOT_INITIALIZED code" ;
236+ auto result = FfiClient::instance ().subscribeDataTrack (1 );
237+ EXPECT_FALSE (result.ok ());
238+ EXPECT_EQ (result.error ().code , SubscribeDataTrackErrorCode::INTERNAL);
312239}
313240
314- TEST_F (FfiClientTest, DISABLED_SendStreamHeaderAsyncFailsWhenNotInitialized ) {
241+ TEST_F (FfiClientTest, NotInitialized_SendStreamHeaderAsyncThrows ) {
315242 ASSERT_FALSE (FfiClient::instance ().isInitialized ());
316- // proto::DataStream::Header header;
317- // auto fut = FfiClient::instance().sendStreamHeaderAsync(/*participant=*/1, header, {}, "sender");
318- // EXPECT_THROW(fut.get(), std::runtime_error);
319- GTEST_SKIP () << " TODO: enable once sendStreamHeaderAsync gate lands" ;
243+
244+ proto::DataStream::Header header;
245+ EXPECT_THROW (FfiClient::instance ().sendStreamHeaderAsync (1 , header, {}, " sender" ), std::runtime_error);
320246}
321247
322- TEST_F (FfiClientTest, DISABLED_SendStreamChunkAsyncFailsWhenNotInitialized ) {
248+ TEST_F (FfiClientTest, NotInitialized_SendStreamChunkAsyncThrows ) {
323249 ASSERT_FALSE (FfiClient::instance ().isInitialized ());
324- // proto::DataStream::Chunk chunk;
325- // auto fut = FfiClient::instance().sendStreamChunkAsync(/*participant=*/1, chunk, {}, "sender");
326- // EXPECT_THROW(fut.get(), std::runtime_error);
327- GTEST_SKIP () << " TODO: enable once sendStreamChunkAsync gate lands" ;
250+
251+ proto::DataStream::Chunk chunk;
252+ EXPECT_THROW (FfiClient::instance ().sendStreamChunkAsync (1 , chunk, {}, " sender" ), std::runtime_error);
328253}
329254
330- TEST_F (FfiClientTest, DISABLED_SendStreamTrailerAsyncFailsWhenNotInitialized ) {
255+ TEST_F (FfiClientTest, NotInitialized_SendStreamTrailerAsyncThrows ) {
331256 ASSERT_FALSE (FfiClient::instance ().isInitialized ());
332- // proto::DataStream::Trailer trailer;
333- // auto fut = FfiClient::instance().sendStreamTrailerAsync(/*participant=*/1, trailer, "sender");
334- // EXPECT_THROW(fut.get(), std::runtime_error);
335- GTEST_SKIP () << " TODO: enable once sendStreamTrailerAsync gate lands" ;
257+
258+ proto::DataStream::Trailer trailer;
259+ EXPECT_THROW (FfiClient::instance ().sendStreamTrailerAsync (1 , trailer, " sender" ), std::runtime_error);
336260}
337261
338262} // namespace livekit::test
0 commit comments