use std::{
convert::Infallible,
fmt::Debug,
marker::{Send, Sync},
time::Duration,
};
use mob_push::{
self,
http_client::{PushClient, PushRequestBuilder, PushResponse},
load_config_from_default,
push_notify::{
android::{sound::WarnSound, AndroidNotify, Badge, Image, NotifyStyle},
ios::{IosBadgeType, IosNotify, IosPushSound, IosRichTextType},
},
MobPusher, PushEntity, SubscribeFilter, UserMobId, UserSubscribeManage,
};
use tokio::time;
struct Client(reqwest::Client);
struct RequestBuilder(reqwest::RequestBuilder);
struct Response(reqwest::Response);
impl PushClient for Client {
type RequestBuilder = RequestBuilder;
type Error = reqwest::Error;
fn post(&self, url: impl Into<url::Url>) -> Self::RequestBuilder {
RequestBuilder(self.0.post(url.into()))
}
fn send_request<'life0,'async_trait>(&'life0 self,req: <Self::RequestBuilder as mob_push::http_client::PushRequestBuilder> ::Request,) -> core::pin::Pin<Box<dyn core::future::Future<Output = Result< <Self::RequestBuilder as mob_push::http_client::PushRequestBuilder> ::Response,Self::Error> > + core::marker::Send+'async_trait> >where 'life0:'async_trait,Self:'async_trait{
Box::pin(async {
let resp = self.0.execute(req).await?;
Ok(Response(resp))
})
}
}
impl PushRequestBuilder for RequestBuilder {
type Error = reqwest::Error;
type Request = reqwest::Request;
type Response = Response;
fn header(self, key: &'static str, value: &str) -> Self {
Self(self.0.header(key, value))
}
fn body(self, payload: Vec<u8>) -> Self {
Self(self.0.body(payload))
}
fn build(self) -> Result<Self::Request, Self::Error> {
self.0.build()
}
}
impl PushResponse for Response {
type Error = reqwest::Error;
fn status(&self) -> u16 {
self.0.status().as_u16()
}
fn bytes<'async_trait>(
self,
) -> core::pin::Pin<
Box<
dyn core::future::Future<Output = Result<Vec<u8>, Self::Error>>
+ core::marker::Send
+ 'async_trait,
>,
>
where
Self: 'async_trait,
{
Box::pin(async { self.0.bytes().await.map(Into::into) })
}
}
#[derive(Default)]
struct TestMsg {
android: Option<Box<dyn Fn(&mut AndroidNotify) -> &mut AndroidNotify + Sync + Send + 'static>>,
ios: Option<Box<dyn Fn(&mut IosNotify) -> &mut IosNotify + Sync + Send + 'static>>,
}
impl Debug for TestMsg {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TestMsg")
.field("android", &self.android.is_some())
.field("ios", &self.ios.is_some())
.finish()
}
}
impl TestMsg {
fn set_android<AN>(self, android: AN) -> TestMsg
where
AN: Fn(&mut AndroidNotify) -> &mut AndroidNotify + Sync + Send + 'static,
{
let android = Box::new(android);
TestMsg {
android: Some(android),
ios: self.ios,
}
}
fn set_ios<IN>(self, ios: IN) -> TestMsg
where
IN: Fn(&mut IosNotify) -> &mut IosNotify + Sync + Send + 'static,
{
let ios = Box::new(ios);
TestMsg {
android: self.android,
ios: Some(ios),
}
}
}
impl PushEntity for TestMsg {
type Resource = i32;
fn get_resource(&self) -> &Self::Resource {
&11
}
type Content = str;
fn get_send_content(&self) -> &Self::Content {
"小刻食堂测试信息-----------------------------------------------------------------------很长"
}
fn get_title(&self) -> std::borrow::Cow<'_, str> {
"新饼来袭".into()
}
fn android_notify(&self, notify: &mut AndroidNotify) {
if let Some(an) = &self.android {
an(notify);
}
}
fn ios_notify(&self, notify: &mut mob_push::push_notify::ios::IosNotify) {
if let Some(ios_notify) = &self.ios {
ios_notify(notify);
}
}
}
struct User {
mob_id: String,
}
impl UserMobId for User {
type MobId = String;
fn get_mob_id(&self) -> Self::MobId {
self.mob_id.clone()
}
}
struct Filter;
impl SubscribeFilter for Filter {
type Data = TestMsg;
type Err = Infallible;
fn filter(
&self,
input: impl Iterator<Item = Self::Data>,
) -> Result<Vec<Self::Data>, Self::Err> {
Ok(input
.filter(|data| data.get_resource() == &11 || data.get_resource() == &15)
.collect())
}
fn contains(&self, target: &<Self::Data as PushEntity>::Resource) -> Result<bool, Self::Err> {
Ok(target == &11 || target == &15)
}
}
struct Manage;
#[async_trait::async_trait]
impl UserSubscribeManage for Manage {
type UserIdentify = User;
type PushData = TestMsg;
type Filter = Filter;
type Err = Infallible;
async fn fetch_subscribe_filter(
&self,
_user_id: &Self::UserIdentify,
) -> Result<Self::Filter, Self::Err> {
Ok(Filter)
}
async fn check_subscribed(
&self,
_user_id: &Self::UserIdentify,
data_resource: &<Self::PushData as PushEntity>::Resource,
) -> Result<bool, Self::Err> {
let resp = Filter.contains(data_resource);
resp
}
async fn fetch_all_subscriber(
&self,
data_resource: &<Self::PushData as PushEntity>::Resource,
) -> Result<Vec<Self::UserIdentify>, Self::Err> {
let resp = if data_resource == &11 || data_resource == &15 {
vec![
User {
mob_id: "65l05lvwtep0fls".into(),
},
]
} else {
vec![]
};
Ok(resp)
}
}
fn test_pushing<F>(msg: F)
where
F: FnOnce() -> TestMsg,
{
let client = reqwest::Client::new();
load_config_from_default();
let (mob_push, sender, mut err_rx) = MobPusher::new(Client(client), Manage, 8);
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Rt start Error");
rt.block_on(async move {
let handle = tokio::spawn(mob_push.start_up());
sender.send(msg()).await.unwrap();
time::sleep(Duration::from_secs(1)).await;
drop(sender);
let err = time::timeout(Duration::from_millis(500), err_rx.recv()).await;
if let Ok(err) = err {
println!("{err:?}")
}
handle.abort();
})
}
#[test]
fn test_push() {
test_pushing(|| TestMsg::default());
}
#[test]
fn test_android_badge() {
test_pushing(|| TestMsg::default().set_android(|an| an.set_badge(Badge::Add(1))));
}
#[test]
fn test_android_warn() {
test_pushing(|| {
TestMsg::default().set_android(|an| an.set_warn(WarnSound::Vibration & WarnSound::Prompt))
});
}
#[test]
fn test_icon() {
test_pushing(|| {
TestMsg::default().set_android(|an| {
let _img = "https://static.mob.com/www_mob_com/.nuxt/dist/client/img/62893e6.png";
let icon = "https://www.mob.com/favicon.ico";
an.set_image(Image::new_image(icon))
})
});
}
#[test]
fn test_style() {
test_pushing(|| {
TestMsg::default().set_android(|an| {
let url = "https://i2.hdslb.com/bfs/archive/355b2e7886f337ff3d0951a057f0022be527f309.jpg@672w_378h_1c";
an
.set_notify_style(NotifyStyle::new_big_vision(url))
})
});
}
#[test]
fn test_android_notify_push() {
test_pushing(|| {
TestMsg::default().set_android(|an|{
an
.set_notify_style(NotifyStyle::new_big_vision("https://i0.hdslb.com/bfs/archive/94bdaa89d9e1775f04bdfb705512a61e5de70628.jpg@672w_378h_1c"))
.set_badge(Badge::new_add(1))
.set_sound("114514".into())
.set_warn(WarnSound::Prompt & WarnSound::IndicatorLight & WarnSound::Vibration)
})
})
}
#[test]
fn test_ios_badge() {
test_pushing(|| TestMsg::default().set_ios(|ios| ios.set_badge(IosBadgeType::Abs(12))));
}
#[test]
fn test_ios_subtitle() {
test_pushing(|| TestMsg::default().set_ios(|ios| ios.set_subtitle("小可试探副标题".into())))
}
#[test]
fn test_ios_no_sound() {
test_pushing(|| TestMsg::default().set_ios(|ios| ios.set_sound(IosPushSound::None)))
}
#[test]
fn test_ios_rich() {
let img = "https://i2.hdslb.com/bfs/archive/a995572283104e306e433240b47fba772c4ed3a0.jpg@672w_378h_1c";
test_pushing(|| {
TestMsg::default().set_ios(|ios| ios.set_rich_text(IosRichTextType::Picture(img.into())))
})
}