Use live_kit_client::TestServer in integration tests

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Antonio Scandurra 2022-10-19 16:35:34 +02:00
parent 288c039929
commit b6e5aa3bb0
9 changed files with 269 additions and 94 deletions

3
Cargo.lock generated
View file

@ -1066,6 +1066,7 @@ dependencies = [
"language", "language",
"lazy_static", "lazy_static",
"lipsum", "lipsum",
"live_kit_client",
"live_kit_server", "live_kit_server",
"log", "log",
"lsp", "lsp",
@ -3163,6 +3164,7 @@ name = "live_kit_client"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait",
"block", "block",
"byteorder", "byteorder",
"bytes 1.2.1", "bytes 1.2.1",
@ -3193,6 +3195,7 @@ name = "live_kit_server"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait",
"futures 0.3.24", "futures 0.3.24",
"hmac 0.12.1", "hmac 0.12.1",
"jwt", "jwt",

View file

@ -62,15 +62,17 @@ editor = { path = "../editor", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] } language = { path = "../language", features = ["test-support"] }
fs = { path = "../fs", features = ["test-support"] } fs = { path = "../fs", features = ["test-support"] }
git = { path = "../git", features = ["test-support"] } git = { path = "../git", features = ["test-support"] }
log = { version = "0.4.16", features = ["kv_unstable_serde"] } live_kit_client = { path = "../live_kit_client", features = ["test-support"] }
lsp = { path = "../lsp", features = ["test-support"] } lsp = { path = "../lsp", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] } project = { path = "../project", features = ["test-support"] }
rpc = { path = "../rpc", features = ["test-support"] } rpc = { path = "../rpc", features = ["test-support"] }
settings = { path = "../settings", features = ["test-support"] } settings = { path = "../settings", features = ["test-support"] }
theme = { path = "../theme" } theme = { path = "../theme" }
workspace = { path = "../workspace", features = ["test-support"] } workspace = { path = "../workspace", features = ["test-support"] }
ctor = "0.1" ctor = "0.1"
env_logger = "0.9" env_logger = "0.9"
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
util = { path = "../util" } util = { path = "../util" }
lazy_static = "1.4" lazy_static = "1.4"
serde_json = { version = "1.0", features = ["preserve_order"] } serde_json = { version = "1.0", features = ["preserve_order"] }

View file

@ -47,7 +47,7 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
rc::Rc, rc::Rc,
sync::{ sync::{
atomic::{AtomicBool, Ordering::SeqCst}, atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst},
Arc, Arc,
}, },
time::Duration, time::Duration,
@ -6138,6 +6138,7 @@ struct TestServer {
connection_killers: Arc<Mutex<HashMap<PeerId, Arc<AtomicBool>>>>, connection_killers: Arc<Mutex<HashMap<PeerId, Arc<AtomicBool>>>>,
forbid_connections: Arc<AtomicBool>, forbid_connections: Arc<AtomicBool>,
_test_db: TestDb, _test_db: TestDb,
test_live_kit_server: Arc<live_kit_client::TestServer>,
} }
impl TestServer { impl TestServer {
@ -6145,8 +6146,18 @@ impl TestServer {
foreground: Rc<executor::Foreground>, foreground: Rc<executor::Foreground>,
background: Arc<executor::Background>, background: Arc<executor::Background>,
) -> Self { ) -> Self {
static NEXT_LIVE_KIT_SERVER_ID: AtomicUsize = AtomicUsize::new(0);
let test_db = TestDb::fake(background.clone()); let test_db = TestDb::fake(background.clone());
let app_state = Self::build_app_state(&test_db).await; let live_kit_server_id = NEXT_LIVE_KIT_SERVER_ID.fetch_add(1, SeqCst);
let live_kit_server = live_kit_client::TestServer::create(
format!("http://livekit.{}.test", live_kit_server_id),
format!("devkey-{}", live_kit_server_id),
format!("secret-{}", live_kit_server_id),
background.clone(),
)
.unwrap();
let app_state = Self::build_app_state(&test_db, &live_kit_server).await;
let peer = Peer::new(); let peer = Peer::new();
let notifications = mpsc::unbounded(); let notifications = mpsc::unbounded();
let server = Server::new(app_state.clone(), Some(notifications.0)); let server = Server::new(app_state.clone(), Some(notifications.0));
@ -6159,6 +6170,7 @@ impl TestServer {
connection_killers: Default::default(), connection_killers: Default::default(),
forbid_connections: Default::default(), forbid_connections: Default::default(),
_test_db: test_db, _test_db: test_db,
test_live_kit_server: live_kit_server,
} }
} }
@ -6354,10 +6366,13 @@ impl TestServer {
} }
} }
async fn build_app_state(test_db: &TestDb) -> Arc<AppState> { async fn build_app_state(
test_db: &TestDb,
fake_server: &live_kit_client::TestServer,
) -> Arc<AppState> {
Arc::new(AppState { Arc::new(AppState {
db: test_db.db().clone(), db: test_db.db().clone(),
live_kit_client: None, live_kit_client: Some(Arc::new(fake_server.create_api_client())),
api_token: Default::default(), api_token: Default::default(),
invite_link_prefix: Default::default(), invite_link_prefix: Default::default(),
}) })
@ -6390,6 +6405,7 @@ impl Deref for TestServer {
impl Drop for TestServer { impl Drop for TestServer {
fn drop(&mut self) { fn drop(&mut self) {
self.peer.reset(); self.peer.reset();
self.test_live_kit_server.teardown().unwrap();
} }
} }

View file

@ -41,7 +41,7 @@ pub struct AppState {
db: Arc<dyn Db>, db: Arc<dyn Db>,
api_token: String, api_token: String,
invite_link_prefix: String, invite_link_prefix: String,
live_kit_client: Option<live_kit_server::api::Client>, live_kit_client: Option<Arc<dyn live_kit_server::api::Client>>,
} }
impl AppState { impl AppState {
@ -53,11 +53,11 @@ impl AppState {
.zip(config.live_kit_key.as_ref()) .zip(config.live_kit_key.as_ref())
.zip(config.live_kit_secret.as_ref()) .zip(config.live_kit_secret.as_ref())
{ {
Some(live_kit_server::api::Client::new( Some(Arc::new(live_kit_server::api::LiveKitClient::new(
server.clone(), server.clone(),
key.clone(), key.clone(),
secret.clone(), secret.clone(),
)) )) as Arc<dyn live_kit_server::api::Client>)
} else { } else {
None None
}; };

View file

@ -12,7 +12,13 @@ doctest = false
name = "test_app" name = "test_app"
[features] [features]
test-support = ["collections/test-support", "gpui/test-support", "lazy_static", "live_kit_server"] test-support = [
"async-trait",
"collections/test-support",
"gpui/test-support",
"lazy_static",
"live_kit_server"
]
[dependencies] [dependencies]
collections = { path = "../collections", optional = true } collections = { path = "../collections", optional = true }
@ -21,6 +27,7 @@ live_kit_server = { path = "../live_kit_server", optional = true }
media = { path = "../media" } media = { path = "../media" }
anyhow = "1.0.38" anyhow = "1.0.38"
async-trait = { version = "0.1", optional = true }
core-foundation = "0.9.3" core-foundation = "0.9.3"
core-graphics = "0.22.3" core-graphics = "0.22.3"
futures = "0.3" futures = "0.3"
@ -30,11 +37,11 @@ parking_lot = "0.11.1"
[dev-dependencies] [dev-dependencies]
collections = { path = "../collections", features = ["test-support"] } collections = { path = "../collections", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] }
lazy_static = "1.4"
live_kit_server = { path = "../live_kit_server" } live_kit_server = { path = "../live_kit_server" }
media = { path = "../media" } media = { path = "../media" }
anyhow = "1.0.38" anyhow = "1.0.38"
async-trait = "0.1"
block = "0.1" block = "0.1"
bytes = "1.2" bytes = "1.2"
byteorder = "1.4" byteorder = "1.4"
@ -45,6 +52,7 @@ foreign-types = "0.3"
futures = "0.3" futures = "0.3"
hmac = "0.12" hmac = "0.12"
jwt = "0.16" jwt = "0.16"
lazy_static = "1.4"
log = { version = "0.4.16", features = ["kv_unstable_serde"] } log = { version = "0.4.16", features = ["kv_unstable_serde"] }
objc = "0.2" objc = "0.2"
parking_lot = "0.11.1" parking_lot = "0.11.1"

View file

@ -7,7 +7,10 @@ use gpui::{
Menu, MenuItem, ViewContext, Menu, MenuItem, ViewContext,
}; };
use live_kit_client::{LocalVideoTrack, RemoteVideoTrackUpdate, Room}; use live_kit_client::{LocalVideoTrack, RemoteVideoTrackUpdate, Room};
use live_kit_server::token::{self, VideoGrant}; use live_kit_server::{
api::Client,
token::{self, VideoGrant},
};
use log::LevelFilter; use log::LevelFilter;
use media::core_video::CVImageBuffer; use media::core_video::CVImageBuffer;
use postage::watch; use postage::watch;

View file

@ -1,35 +1,40 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use async_trait::async_trait;
use collections::HashMap; use collections::HashMap;
use futures::{channel::mpsc, future}; use futures::{channel::mpsc, future};
use gpui::executor::Background; use gpui::executor::Background;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use live_kit_server::token;
use media::core_video::CVImageBuffer; use media::core_video::CVImageBuffer;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{future::Future, sync::Arc}; use std::{future::Future, sync::Arc};
lazy_static! { lazy_static! {
static ref SERVERS: Mutex<HashMap<String, Arc<FakeServer>>> = Default::default(); static ref SERVERS: Mutex<HashMap<String, Arc<TestServer>>> = Default::default();
} }
pub struct FakeServer { pub struct TestServer {
url: String, pub url: String,
secret_key: String, pub api_key: String,
rooms: Mutex<HashMap<String, FakeServerRoom>>, pub secret_key: String,
rooms: Mutex<HashMap<String, TestServerRoom>>,
background: Arc<Background>, background: Arc<Background>,
} }
impl FakeServer { impl TestServer {
pub fn create( pub fn create(
url: String, url: String,
api_key: String,
secret_key: String, secret_key: String,
background: Arc<Background>, background: Arc<Background>,
) -> Result<Arc<FakeServer>> { ) -> Result<Arc<TestServer>> {
let mut servers = SERVERS.lock(); let mut servers = SERVERS.lock();
if servers.contains_key(&url) { if servers.contains_key(&url) {
Err(anyhow!("a server with url {:?} already exists", url)) Err(anyhow!("a server with url {:?} already exists", url))
} else { } else {
let server = Arc::new(FakeServer { let server = Arc::new(TestServer {
url: url.clone(), url: url.clone(),
api_key,
secret_key, secret_key,
rooms: Default::default(), rooms: Default::default(),
background, background,
@ -39,7 +44,7 @@ impl FakeServer {
} }
} }
fn get(url: &str) -> Result<Arc<FakeServer>> { fn get(url: &str) -> Result<Arc<TestServer>> {
Ok(SERVERS Ok(SERVERS
.lock() .lock()
.get(url) .get(url)
@ -55,33 +60,151 @@ impl FakeServer {
Ok(()) Ok(())
} }
pub fn create_api_client(&self) -> TestApiClient {
TestApiClient {
url: self.url.clone(),
}
}
async fn create_room(&self, room: String) -> Result<()> {
self.background.simulate_random_delay().await;
let mut server_rooms = self.rooms.lock();
if server_rooms.contains_key(&room) {
Err(anyhow!("room {:?} already exists", room))
} else {
server_rooms.insert(room, Default::default());
Ok(())
}
}
async fn delete_room(&self, room: String) -> Result<()> {
// TODO: clear state associated with all `Room`s.
self.background.simulate_random_delay().await;
let mut server_rooms = self.rooms.lock();
server_rooms
.remove(&room)
.ok_or_else(|| anyhow!("room {:?} does not exist", room))?;
Ok(())
}
async fn join_room(&self, token: String, client_room: Arc<Room>) -> Result<()> { async fn join_room(&self, token: String, client_room: Arc<Room>) -> Result<()> {
self.background.simulate_random_delay().await; self.background.simulate_random_delay().await;
let claims = live_kit_server::token::validate(&token, &self.secret_key)?; let claims = live_kit_server::token::validate(&token, &self.secret_key)?;
let identity = claims.sub.unwrap().to_string(); let identity = claims.sub.unwrap().to_string();
let room = claims.video.room.unwrap(); let room_name = claims.video.room.unwrap();
let mut server_rooms = self.rooms.lock(); let mut server_rooms = self.rooms.lock();
let room = server_rooms let room = server_rooms
.get_mut(&*room) .get_mut(&*room_name)
.ok_or_else(|| anyhow!("room {} does not exist", room))?; .ok_or_else(|| anyhow!("room {:?} does not exist", room_name))?;
room.clients.insert(identity, client_room); if room.clients.contains_key(&identity) {
Err(anyhow!(
"{:?} attempted to join room {:?} twice",
identity,
room_name
))
} else {
room.clients.insert(identity, client_room);
Ok(())
}
}
async fn leave_room(&self, token: String) -> Result<()> {
self.background.simulate_random_delay().await;
let claims = live_kit_server::token::validate(&token, &self.secret_key)?;
let identity = claims.sub.unwrap().to_string();
let room_name = claims.video.room.unwrap();
let mut server_rooms = self.rooms.lock();
let room = server_rooms
.get_mut(&*room_name)
.ok_or_else(|| anyhow!("room {} does not exist", room_name))?;
room.clients.remove(&identity).ok_or_else(|| {
anyhow!(
"{:?} attempted to leave room {:?} before joining it",
identity,
room_name
)
})?;
Ok(())
}
async fn remove_participant(&self, room_name: String, identity: String) -> Result<()> {
// TODO: clear state associated with the `Room`.
self.background.simulate_random_delay().await;
let mut server_rooms = self.rooms.lock();
let room = server_rooms
.get_mut(&room_name)
.ok_or_else(|| anyhow!("room {} does not exist", room_name))?;
room.clients.remove(&identity).ok_or_else(|| {
anyhow!(
"participant {:?} did not join room {:?}",
identity,
room_name
)
})?;
Ok(()) Ok(())
} }
} }
struct FakeServerRoom { #[derive(Default)]
struct TestServerRoom {
clients: HashMap<Sid, Arc<Room>>, clients: HashMap<Sid, Arc<Room>>,
} }
impl FakeServerRoom {} impl TestServerRoom {}
pub struct TestApiClient {
url: String,
}
#[async_trait]
impl live_kit_server::api::Client for TestApiClient {
fn url(&self) -> &str {
&self.url
}
async fn create_room(&self, name: String) -> Result<()> {
let server = TestServer::get(&self.url)?;
server.create_room(name).await?;
Ok(())
}
async fn delete_room(&self, name: String) -> Result<()> {
let server = TestServer::get(&self.url)?;
server.delete_room(name).await?;
Ok(())
}
async fn remove_participant(&self, room: String, identity: String) -> Result<()> {
let server = TestServer::get(&self.url)?;
server.remove_participant(room, identity).await?;
Ok(())
}
fn room_token(&self, room: &str, identity: &str) -> Result<String> {
let server = TestServer::get(&self.url)?;
token::create(
&server.api_key,
&server.secret_key,
Some(identity),
token::VideoGrant::to_join(room),
)
}
}
pub type Sid = String; pub type Sid = String;
pub struct Room; #[derive(Default)]
struct RoomState {
token: Option<String>,
}
#[derive(Default)]
pub struct Room(Mutex<RoomState>);
impl Room { impl Room {
pub fn new() -> Arc<Self> { pub fn new() -> Arc<Self> {
Arc::new(Self) Default::default()
} }
pub fn connect(self: &Arc<Self>, url: &str, token: &str) -> impl Future<Output = Result<()>> { pub fn connect(self: &Arc<Self>, url: &str, token: &str) -> impl Future<Output = Result<()>> {
@ -89,8 +212,9 @@ impl Room {
let url = url.to_string(); let url = url.to_string();
let token = token.to_string(); let token = token.to_string();
async move { async move {
let server = FakeServer::get(&url)?; let server = TestServer::get(&url)?;
server.join_room(token, this).await?; server.join_room(token.clone(), this.clone()).await?;
this.0.lock().token = Some(token);
Ok(()) Ok(())
} }
} }
@ -115,7 +239,14 @@ impl Room {
impl Drop for Room { impl Drop for Room {
fn drop(&mut self) { fn drop(&mut self) {
todo!() if let Some(token) = self.0.lock().token.take() {
if let Ok(server) = TestServer::get(&token) {
let background = server.background.clone();
background
.spawn(async move { server.leave_room(token).await.unwrap() })
.detach();
}
}
} }
} }

View file

@ -10,6 +10,7 @@ doctest = false
[dependencies] [dependencies]
anyhow = "1.0.38" anyhow = "1.0.38"
async-trait = "0.1"
futures = "0.3" futures = "0.3"
hmac = "0.12" hmac = "0.12"
log = "0.4" log = "0.4"

View file

@ -1,18 +1,28 @@
use crate::{proto, token}; use crate::{proto, token};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use async_trait::async_trait;
use prost::Message; use prost::Message;
use reqwest::header::CONTENT_TYPE; use reqwest::header::CONTENT_TYPE;
use std::{future::Future, sync::Arc}; use std::{future::Future, sync::Arc};
#[async_trait]
pub trait Client: Send + Sync {
fn url(&self) -> &str;
async fn create_room(&self, name: String) -> Result<()>;
async fn delete_room(&self, name: String) -> Result<()>;
async fn remove_participant(&self, room: String, identity: String) -> Result<()>;
fn room_token(&self, room: &str, identity: &str) -> Result<String>;
}
#[derive(Clone)] #[derive(Clone)]
pub struct Client { pub struct LiveKitClient {
http: reqwest::Client, http: reqwest::Client,
url: Arc<str>, url: Arc<str>,
key: Arc<str>, key: Arc<str>,
secret: Arc<str>, secret: Arc<str>,
} }
impl Client { impl LiveKitClient {
pub fn new(mut url: String, key: String, secret: String) -> Self { pub fn new(mut url: String, key: String, secret: String) -> Self {
if url.ends_with('/') { if url.ends_with('/') {
url.pop(); url.pop();
@ -26,67 +36,6 @@ impl Client {
} }
} }
pub fn url(&self) -> &str {
&self.url
}
pub fn create_room(&self, name: String) -> impl Future<Output = Result<proto::Room>> {
self.request(
"twirp/livekit.RoomService/CreateRoom",
token::VideoGrant {
room_create: Some(true),
..Default::default()
},
proto::CreateRoomRequest {
name,
..Default::default()
},
)
}
pub fn delete_room(&self, name: String) -> impl Future<Output = Result<()>> {
let response = self.request(
"twirp/livekit.RoomService/DeleteRoom",
token::VideoGrant {
room_create: Some(true),
..Default::default()
},
proto::DeleteRoomRequest { room: name },
);
async move {
let _: proto::DeleteRoomResponse = response.await?;
Ok(())
}
}
pub fn remove_participant(
&self,
room: String,
identity: String,
) -> impl Future<Output = Result<()>> {
let response = self.request(
"twirp/livekit.RoomService/RemoveParticipant",
token::VideoGrant::to_admin(&room),
proto::RoomParticipantIdentity {
room: room.clone(),
identity,
},
);
async move {
let _: proto::RemoveParticipantResponse = response.await?;
Ok(())
}
}
pub fn room_token(&self, room: &str, identity: &str) -> Result<String> {
token::create(
&self.key,
&self.secret,
Some(identity),
token::VideoGrant::to_join(room),
)
}
fn request<Req, Res>( fn request<Req, Res>(
&self, &self,
path: &str, path: &str,
@ -126,3 +75,65 @@ impl Client {
} }
} }
} }
#[async_trait]
impl Client for LiveKitClient {
fn url(&self) -> &str {
&self.url
}
async fn create_room(&self, name: String) -> Result<()> {
let x: proto::Room = self
.request(
"twirp/livekit.RoomService/CreateRoom",
token::VideoGrant {
room_create: Some(true),
..Default::default()
},
proto::CreateRoomRequest {
name,
..Default::default()
},
)
.await?;
dbg!(x);
Ok(())
}
async fn delete_room(&self, name: String) -> Result<()> {
let _: proto::DeleteRoomResponse = self
.request(
"twirp/livekit.RoomService/DeleteRoom",
token::VideoGrant {
room_create: Some(true),
..Default::default()
},
proto::DeleteRoomRequest { room: name },
)
.await?;
Ok(())
}
async fn remove_participant(&self, room: String, identity: String) -> Result<()> {
let _: proto::RemoveParticipantResponse = self
.request(
"twirp/livekit.RoomService/RemoveParticipant",
token::VideoGrant::to_admin(&room),
proto::RoomParticipantIdentity {
room: room.clone(),
identity,
},
)
.await?;
Ok(())
}
fn room_token(&self, room: &str, identity: &str) -> Result<String> {
token::create(
&self.key,
&self.secret,
Some(identity),
token::VideoGrant::to_join(room),
)
}
}