chorus 0.20.0

A library for interacting with multiple Spacebar-compatible Instances at once.
Documentation
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

mod common;

use std::sync::Arc;
use std::time::Duration;

use async_trait::async_trait;
use chorus::errors::GatewayError;
use chorus::gateway::*;
use chorus::types::{
    self, Channel, ChannelCreateSchema, ChannelModifySchema, IntoShared, RoleCreateModifySchema,
    RoleObject,
};
use pubserve::Subscriber;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;
#[cfg(target_arch = "wasm32")]
wasm_bindgen_test_configure!(run_in_browser);

#[cfg(not(target_arch = "wasm32"))]
use tokio::time::sleep;
#[cfg(target_arch = "wasm32")]
use wasmtimer::tokio::sleep;

#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
/// Tests establishing a connection (hello and heartbeats) on the local gateway;
async fn test_gateway_establish() {
    let bundle = common::setup().await;

    let _: GatewayHandle = Gateway::spawn(&bundle.urls.wss, GatewayOptions::default())
        .await
        .unwrap();
    common::teardown(bundle).await
}

#[derive(Debug)]
struct GatewayErrorObserver {
    channel: tokio::sync::mpsc::Sender<GatewayError>,
}

#[async_trait]
impl Subscriber<GatewayError> for GatewayErrorObserver {
    async fn update(&self, data: &GatewayError) {
        self.channel.send(data.clone()).await.unwrap();
    }
}

#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
/// Tests establishing a connection and authenticating
async fn test_gateway_authenticate() {
    let bundle = common::setup().await;

    let gateway: GatewayHandle = Gateway::spawn(&bundle.urls.wss, GatewayOptions::default())
        .await
        .unwrap();

    let mut identify = types::GatewayIdentifyPayload::common();
    identify.token = bundle.user.token.clone();

    match gateway.identify(identify).await {
        Ok(_) => {}
        Err(e) => {
            println!("Failed to identify with error {}, failing", e);
            assert!(false);
        }
    }

    common::teardown(bundle).await
}

#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
/// Tests establishing a connection and receiving errors
async fn test_gateway_errors() {
    let bundle = common::setup().await;

    // FIXME: Without this, this test does not work
    //
    // Can WASM handle multiple gateway existing simulatinously?
    // This reminds of when my very old laptop couldn't handle multiple connections
    // with the ammount of tasks
    //
    // Anyway, if you have a free weekend to spend debugging wasm, you're welcome to have a crack
    // at this
    bundle.user.gateway.close().await;

    let gateway: GatewayHandle = Gateway::spawn(&bundle.urls.wss, GatewayOptions::default())
        .await
        .unwrap();

    // First we'll authenticate, then authenticate again to get AlreadyAuthenticated

    let (error_send, mut error_receive) = tokio::sync::mpsc::channel(1);

    let observer = Arc::new(GatewayErrorObserver {
        channel: error_send,
    });

    gateway.events.lock().await.error.subscribe(observer);

    let mut identify = types::GatewayIdentifyPayload::common();
    identify.token = bundle.user.token.clone();

    match gateway.identify(identify.clone()).await {
        Ok(_) => {}
        Err(e) => {
            println!("Failed to identify with error {}, failing", e);
            assert!(false);
        }
    }

    // Identify again, so we should receive already authenticated
    gateway.send_identify(identify).await;

    tokio::select! {
        // Fail, we timed out waiting for the error
        () = sleep(Duration::from_secs(20)) => {
            assert!(false);
        }
        // Success, we have received it
        Some(error) = error_receive.recv() => {
                assert_eq!(error, GatewayError::AlreadyAuthenticated);
          }
    }

    common::teardown(bundle).await;
}

#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn test_self_updating_structs() {
    // PRETTYFYME: This test is a bit of a mess, but it works. Ideally, each self-updating struct
    // would have its own test.
    let mut bundle = common::setup().await;

    let received_channel = bundle
        .user
        .gateway
        .observe_and_into_inner(bundle.channel.clone())
        .await;

    assert_eq!(received_channel, bundle.channel.read().unwrap().clone());

    let modify_schema = ChannelModifySchema {
        name: Some("selfupdating".to_string()),
        ..Default::default()
    };
    received_channel
        .modify(modify_schema, None, &mut bundle.user)
        .await
        .unwrap();
    assert_eq!(
        bundle
            .user
            .gateway
            .observe_and_into_inner(bundle.channel.clone())
            .await
            .name
            .unwrap(),
        "selfupdating".to_string()
    );

    let guild = bundle
        .user
        .gateway
        .observe_and_into_inner(bundle.guild.clone())
        .await;
    assert!(guild.channels.is_empty());

    Channel::create(
        &mut bundle.user,
        guild.id,
        None,
        ChannelCreateSchema {
            name: "selfupdating2".to_string(),
            channel_type: Some(types::ChannelType::GuildText),
            ..Default::default()
        },
    )
    .await
    .unwrap();

    let guild = bundle
        .user
        .gateway
        .observe_and_into_inner(guild.into_shared())
        .await;
    assert!(!guild.channels.is_empty());
    assert_eq!(guild.channels.len(), 1);

    common::teardown(bundle).await
}

#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn test_recursive_self_updating_structs() {
    // Setup
    let mut bundle = common::setup().await;
    let guild = bundle.guild.clone();
    // Observe Guild, make sure it has no channels
    let guild = bundle.user.gateway.observe(guild.clone()).await;
    let inner_guild = guild.read().unwrap().clone();
    assert!(inner_guild.roles.is_empty());
    // Create Role
    let permissions = types::PermissionFlags::CONNECT | types::PermissionFlags::MANAGE_EVENTS;
    let mut role_create_schema: types::RoleCreateModifySchema = RoleCreateModifySchema {
        name: Some("cool person".to_string()),
        permissions: Some(permissions),
        hoist: Some(true),
        icon: None,
        unicode_emoji: Some("".to_string()),
        mentionable: Some(true),
        position: None,
        color: None,
    };
    let guild_id = inner_guild.id;
    let role = RoleObject::create(&mut bundle.user, guild_id, role_create_schema.clone())
        .await
        .unwrap();
    // Watch role;
    bundle
        .user
        .gateway
        .observe(role.clone().into_shared())
        .await;
    // Update Guild and check for Guild
    let inner_guild = guild.read().unwrap().clone();
    assert!(!inner_guild.roles.is_empty());
    // Update the Role
    role_create_schema.name = Some("yippieee".to_string());
    RoleObject::modify(&mut bundle.user, guild_id, role.id, role_create_schema)
        .await
        .unwrap();
    let role_inner = bundle
        .user
        .gateway
        .observe_and_into_inner(role.clone().into_shared())
        .await;
    assert_eq!(role_inner.name, "yippieee");
    // Check if the change propagated
    let guild = bundle.user.gateway.observe(bundle.guild.clone()).await;
    let inner_guild = guild.read().unwrap().clone();
    let guild_roles = inner_guild.roles;
    let guild_role_inner = guild_roles.first().unwrap().read().unwrap().clone();
    assert_eq!(guild_role_inner.name, "yippieee".to_string());
    common::teardown(bundle).await;
}