1mod inner;
4pub mod store;
5
6use self::store::AtpSessionStore;
7use super::{
8 Agent, CloneWithProxy, Configure, SessionManager, inner::Wrapper,
9 utils::SessionWithEndpointStore,
10};
11use crate::{
12 client::com::atproto::Service,
13 did_doc::DidDocument,
14 types::{TryFromUnknown, string::Did},
15};
16use atrium_xrpc::{Error, HttpClient, OutputDataOrBytes, XrpcClient, XrpcRequest};
17use http::{Request, Response};
18use serde::{Serialize, de::DeserializeOwned};
19use std::{convert, fmt::Debug, ops::Deref, sync::Arc};
20
21pub type AtpSession = crate::com::atproto::server::create_session::Output;
23
24pub struct CredentialSession<S, T>
25where
26 S: AtpSessionStore + Send + Sync,
27 T: XrpcClient + Send + Sync,
28 S::Error: std::error::Error + Send + Sync + 'static,
29{
30 store: Arc<SessionWithEndpointStore<S, AtpSession>>,
31 inner: Arc<inner::Client<S, T>>,
32 atproto_service: Service<inner::Client<S, T>>,
33}
34
35impl<S, T> CredentialSession<S, T>
36where
37 S: AtpSessionStore + Send + Sync,
38 T: XrpcClient + Send + Sync,
39 S::Error: std::error::Error + Send + Sync + 'static,
40{
41 pub fn new(xrpc: T, store: S) -> Self {
42 let store = Arc::new(SessionWithEndpointStore::new(store, xrpc.base_uri()));
43 let inner = Arc::new(inner::Client::new(Arc::clone(&store), xrpc));
44 let atproto_service = Service::new(Arc::clone(&inner));
45 Self { store, inner, atproto_service }
46 }
47 pub async fn login(
49 &self,
50 identifier: impl AsRef<str>,
51 password: impl AsRef<str>,
52 ) -> Result<AtpSession, Error<crate::com::atproto::server::create_session::Error>> {
53 let result = self
54 .atproto_service
55 .server
56 .create_session(
57 crate::com::atproto::server::create_session::InputData {
58 allow_takendown: None,
59 auth_factor_token: None,
60 identifier: identifier.as_ref().into(),
61 password: password.as_ref().into(),
62 }
63 .into(),
64 )
65 .await?;
66 self.store.set(result.clone()).await.ok();
67 if let Some(did_doc) = result
68 .did_doc
69 .as_ref()
70 .and_then(|value| DidDocument::try_from_unknown(value.clone()).ok())
71 {
72 self.store.update_endpoint(&did_doc);
73 }
74 Ok(result)
75 }
76 pub async fn resume_session(
78 &self,
79 session: AtpSession,
80 ) -> Result<(), Error<crate::com::atproto::server::get_session::Error>> {
81 self.store.set(session.clone()).await.ok();
82 let result = self.atproto_service.server.get_session().await;
83 match result {
84 Ok(output) => {
85 assert_eq!(output.data.did, session.data.did);
86 if let Ok(Some(mut session)) = self.store.get().await {
87 session.did_doc = output.data.did_doc.clone();
88 session.email = output.data.email;
89 session.email_confirmed = output.data.email_confirmed;
90 session.handle = output.data.handle;
91 self.store.set(session).await.ok();
92 }
93 if let Some(did_doc) = output
94 .data
95 .did_doc
96 .as_ref()
97 .and_then(|value| DidDocument::try_from_unknown(value.clone()).ok())
98 {
99 self.store.update_endpoint(&did_doc);
100 }
101 Ok(())
102 }
103 Err(err) => {
104 self.store.clear().await.ok();
105 Err(err)
106 }
107 }
108 }
109 pub async fn get_session(&self) -> Option<AtpSession> {
111 self.store.get().await.ok().and_then(convert::identity)
112 }
113 pub async fn get_endpoint(&self) -> String {
115 self.store.get_endpoint()
116 }
117 pub async fn get_labelers_header(&self) -> Option<Vec<String>> {
119 self.inner.get_labelers_header().await
120 }
121 pub async fn get_proxy_header(&self) -> Option<String> {
123 self.inner.get_proxy_header().await
124 }
125}
126
127impl<S, T> HttpClient for CredentialSession<S, T>
128where
129 S: AtpSessionStore + Send + Sync,
130 T: XrpcClient + Send + Sync,
131 S::Error: std::error::Error + Send + Sync + 'static,
132{
133 async fn send_http(
134 &self,
135 request: Request<Vec<u8>>,
136 ) -> Result<Response<Vec<u8>>, Box<dyn std::error::Error + Send + Sync + 'static>> {
137 self.inner.send_http(request).await
138 }
139}
140
141impl<S, T> XrpcClient for CredentialSession<S, T>
142where
143 S: AtpSessionStore + Send + Sync,
144 T: XrpcClient + Send + Sync,
145 S::Error: std::error::Error + Send + Sync + 'static,
146{
147 fn base_uri(&self) -> String {
148 self.inner.base_uri()
149 }
150 async fn send_xrpc<P, I, O, E>(
151 &self,
152 request: &XrpcRequest<P, I>,
153 ) -> Result<OutputDataOrBytes<O>, Error<E>>
154 where
155 P: Serialize + Send + Sync,
156 I: Serialize + Send + Sync,
157 O: DeserializeOwned + Send + Sync,
158 E: DeserializeOwned + Send + Sync + Debug,
159 {
160 self.inner.send_xrpc(request).await
161 }
162}
163
164impl<S, T> SessionManager for CredentialSession<S, T>
165where
166 S: AtpSessionStore + Send + Sync,
167 T: XrpcClient + Send + Sync,
168 S::Error: std::error::Error + Send + Sync + 'static,
169{
170 async fn did(&self) -> Option<Did> {
171 self.store.get().await.ok().and_then(|session| session.map(|session| session.data.did))
172 }
173}
174
175impl<S, T> Configure for CredentialSession<S, T>
176where
177 S: AtpSessionStore + Send + Sync,
178 T: XrpcClient + Send + Sync,
179 S::Error: std::error::Error + Send + Sync + 'static,
180{
181 fn configure_endpoint(&self, endpoint: String) {
182 self.inner.configure_endpoint(endpoint);
183 }
184 fn configure_labelers_header(&self, labeler_dids: Option<Vec<(Did, bool)>>) {
185 self.inner.configure_labelers_header(labeler_dids);
186 }
187 fn configure_proxy_header(&self, did: Did, service_type: impl AsRef<str>) {
188 self.inner.configure_proxy_header(did, service_type);
189 }
190}
191
192impl<S, T> CloneWithProxy for CredentialSession<S, T>
193where
194 S: AtpSessionStore + Send + Sync,
195 S::Error: std::error::Error + Send + Sync + 'static,
196 T: XrpcClient + Send + Sync,
197{
198 fn clone_with_proxy(&self, did: Did, service_type: impl AsRef<str>) -> Self {
199 let inner = Arc::new(self.inner.clone_with_proxy(did, service_type));
200 let atproto_service = Service::new(Arc::clone(&inner));
201 Self { store: Arc::clone(&self.store), inner, atproto_service }
202 }
203}
204
205pub struct AtpAgent<S, T>
225where
226 S: AtpSessionStore + Send + Sync,
227 T: XrpcClient + Send + Sync,
228 S::Error: std::error::Error + Send + Sync + 'static,
229{
230 session_manager: Wrapper<CredentialSession<S, T>>,
231 inner: Agent<Wrapper<CredentialSession<S, T>>>,
232}
233
234impl<S, T> AtpAgent<S, T>
235where
236 S: AtpSessionStore + Send + Sync,
237 T: XrpcClient + Send + Sync,
238 S::Error: std::error::Error + Send + Sync + 'static,
239{
240 pub fn new(xrpc: T, store: S) -> Self {
242 let session_manager = Wrapper::new(CredentialSession::new(xrpc, store));
243 let inner = Agent::new(session_manager.clone());
244 Self { session_manager, inner }
245 }
246 pub async fn login(
248 &self,
249 identifier: impl AsRef<str>,
250 password: impl AsRef<str>,
251 ) -> Result<AtpSession, Error<crate::com::atproto::server::create_session::Error>> {
252 self.session_manager.login(identifier, password).await
253 }
254 pub async fn resume_session(
256 &self,
257 session: AtpSession,
258 ) -> Result<(), Error<crate::com::atproto::server::get_session::Error>> {
259 self.session_manager.resume_session(session).await
260 }
261 pub async fn get_session(&self) -> Option<AtpSession> {
263 self.session_manager.get_session().await
264 }
265 pub async fn get_endpoint(&self) -> String {
267 self.session_manager.get_endpoint().await
268 }
269 pub async fn get_labelers_header(&self) -> Option<Vec<String>> {
271 self.session_manager.get_labelers_header().await
272 }
273 pub async fn get_proxy_header(&self) -> Option<String> {
275 self.session_manager.get_proxy_header().await
276 }
277}
278
279impl<S, T> Deref for AtpAgent<S, T>
280where
281 S: AtpSessionStore + Send + Sync,
282 T: XrpcClient + Send + Sync,
283 S::Error: std::error::Error + Send + Sync + 'static,
284{
285 type Target = Agent<Wrapper<CredentialSession<S, T>>>;
286
287 fn deref(&self) -> &Self::Target {
288 &self.inner
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use super::store::MemorySessionStore;
295 use super::*;
296 use crate::{
297 agent::AtprotoServiceType,
298 com::atproto::server::create_session::OutputData,
299 did_doc::{DidDocument, Service, VerificationMethod},
300 types::TryIntoUnknown,
301 };
302 use atrium_xrpc::HttpClient;
303 use http::{HeaderMap, HeaderName, HeaderValue, Request, Response};
304 use std::collections::HashMap;
305 use tokio::sync::RwLock;
306 #[cfg(target_arch = "wasm32")]
307 use wasm_bindgen_test::wasm_bindgen_test;
308
309 #[derive(Default)]
310 struct MockResponses {
311 create_session: Option<crate::com::atproto::server::create_session::OutputData>,
312 get_session: Option<crate::com::atproto::server::get_session::OutputData>,
313 }
314
315 #[derive(Default)]
316 struct MockClient {
317 responses: MockResponses,
318 counts: Arc<RwLock<HashMap<String, usize>>>,
319 headers: Arc<RwLock<Vec<HeaderMap<HeaderValue>>>>,
320 }
321
322 impl HttpClient for MockClient {
323 async fn send_http(
324 &self,
325 request: Request<Vec<u8>>,
326 ) -> Result<Response<Vec<u8>>, Box<dyn std::error::Error + Send + Sync + 'static>> {
327 #[cfg(not(target_arch = "wasm32"))]
329 tokio::time::sleep(std::time::Duration::from_micros(10)).await;
330
331 self.headers.write().await.push(request.headers().clone());
332 let builder =
333 Response::builder().header(http::header::CONTENT_TYPE, "application/json");
334 let token = request
335 .headers()
336 .get(http::header::AUTHORIZATION)
337 .and_then(|value| value.to_str().ok())
338 .and_then(|value| value.split(' ').last());
339 if token == Some("expired") {
340 return Ok(builder.status(http::StatusCode::BAD_REQUEST).body(
341 serde_json::to_vec(&atrium_xrpc::error::ErrorResponseBody {
342 error: Some(String::from("ExpiredToken")),
343 message: Some(String::from("Token has expired")),
344 })?,
345 )?);
346 }
347 let mut body = Vec::new();
348 if let Some(nsid) = request.uri().path().strip_prefix("/xrpc/") {
349 *self.counts.write().await.entry(nsid.into()).or_default() += 1;
350 match nsid {
351 crate::com::atproto::server::create_session::NSID => {
352 if let Some(output) = &self.responses.create_session {
353 body.extend(serde_json::to_vec(output)?);
354 }
355 }
356 crate::com::atproto::server::get_session::NSID => {
357 if token == Some("access") {
358 if let Some(output) = &self.responses.get_session {
359 body.extend(serde_json::to_vec(output)?);
360 }
361 }
362 }
363 crate::com::atproto::server::refresh_session::NSID => {
364 if token == Some("refresh") {
365 body.extend(serde_json::to_vec(
366 &crate::com::atproto::server::refresh_session::OutputData {
367 access_jwt: String::from("access"),
368 active: None,
369 did: "did:web:example.com".parse().expect("valid"),
370 did_doc: None,
371 email: None,
372 email_auth_factor: None,
373 email_confirmed: None,
374 handle: "example.com".parse().expect("valid"),
375 refresh_jwt: String::from("refresh"),
376 status: None,
377 },
378 )?);
379 }
380 }
381 crate::com::atproto::server::describe_server::NSID => {
382 body.extend(serde_json::to_vec(
383 &crate::com::atproto::server::describe_server::OutputData {
384 available_user_domains: Vec::new(),
385 blob_upload_limit: None,
386 contact: None,
387 did: "did:web:example.com".parse().expect("valid"),
388 invite_code_required: None,
389 links: None,
390 phone_verification_required: None,
391 },
392 )?);
393 }
394 _ => {}
395 }
396 }
397 if body.is_empty() {
398 Ok(builder.status(http::StatusCode::UNAUTHORIZED).body(serde_json::to_vec(
399 &atrium_xrpc::error::ErrorResponseBody {
400 error: Some(String::from("AuthenticationRequired")),
401 message: Some(String::from("Invalid identifier or password")),
402 },
403 )?)?)
404 } else {
405 Ok(builder.status(http::StatusCode::OK).body(body)?)
406 }
407 }
408 }
409
410 impl XrpcClient for MockClient {
411 fn base_uri(&self) -> String {
412 "http://localhost:8080".into()
413 }
414 }
415
416 fn session_data() -> OutputData {
417 OutputData {
418 access_jwt: String::from("access"),
419 active: None,
420 did: "did:web:example.com".parse().expect("valid"),
421 did_doc: None,
422 email: None,
423 email_auth_factor: None,
424 email_confirmed: None,
425 handle: "example.com".parse().expect("valid"),
426 refresh_jwt: String::from("refresh"),
427 status: None,
428 }
429 }
430
431 #[tokio::test]
432 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
433 async fn test_new() {
434 let agent = AtpAgent::new(MockClient::default(), MemorySessionStore::default());
435 assert_eq!(agent.get_session().await, None);
436 }
437
438 #[tokio::test]
439 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
440 async fn test_login() {
441 let session_data = session_data();
442 {
444 let client = MockClient {
445 responses: MockResponses {
446 create_session: Some(crate::com::atproto::server::create_session::OutputData {
447 ..session_data.clone()
448 }),
449 ..Default::default()
450 },
451 ..Default::default()
452 };
453 let agent = AtpAgent::new(client, MemorySessionStore::default());
454 agent.login("test", "pass").await.expect("login should be succeeded");
455 assert_eq!(agent.get_session().await, Some(session_data.into()));
456 }
457 {
459 let client = MockClient {
460 responses: MockResponses { ..Default::default() },
461 ..Default::default()
462 };
463 let agent = AtpAgent::new(client, MemorySessionStore::default());
464 agent.login("test", "bad").await.expect_err("login should be failed");
465 assert_eq!(agent.get_session().await, None);
466 }
467 }
468
469 #[tokio::test]
470 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
471 async fn test_xrpc_get_session() {
472 let session_data = session_data();
473 let client = MockClient {
474 responses: MockResponses {
475 get_session: Some(crate::com::atproto::server::get_session::OutputData {
476 active: session_data.active,
477 did: session_data.did.clone(),
478 did_doc: session_data.did_doc.clone(),
479 email: session_data.email.clone(),
480 email_auth_factor: session_data.email_auth_factor,
481 email_confirmed: session_data.email_confirmed,
482 handle: session_data.handle.clone(),
483 status: session_data.status.clone(),
484 }),
485 ..Default::default()
486 },
487 ..Default::default()
488 };
489 let agent = AtpAgent::new(client, MemorySessionStore::default());
490 agent
491 .session_manager
492 .store
493 .set(session_data.clone().into())
494 .await
495 .expect("set session should be succeeded");
496 let output = agent
497 .api
498 .com
499 .atproto
500 .server
501 .get_session()
502 .await
503 .expect("get session should be succeeded");
504 assert_eq!(output.did.as_str(), "did:web:example.com");
505 }
506
507 #[tokio::test]
508 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
509 async fn test_xrpc_get_session_with_refresh() {
510 let mut session_data = session_data();
511 session_data.access_jwt = String::from("expired");
512 let client = MockClient {
513 responses: MockResponses {
514 get_session: Some(crate::com::atproto::server::get_session::OutputData {
515 active: session_data.active,
516 did: session_data.did.clone(),
517 did_doc: session_data.did_doc.clone(),
518 email: session_data.email.clone(),
519 email_auth_factor: session_data.email_auth_factor,
520 email_confirmed: session_data.email_confirmed,
521 handle: session_data.handle.clone(),
522 status: session_data.status.clone(),
523 }),
524 ..Default::default()
525 },
526 ..Default::default()
527 };
528 let agent = AtpAgent::new(client, MemorySessionStore::default());
529 agent
530 .session_manager
531 .store
532 .set(session_data.clone().into())
533 .await
534 .expect("set session should be succeeded");
535 let output = agent
536 .api
537 .com
538 .atproto
539 .server
540 .get_session()
541 .await
542 .expect("get session should be succeeded");
543 assert_eq!(output.did.as_str(), "did:web:example.com");
544 assert_eq!(
545 agent
546 .session_manager
547 .store
548 .get()
549 .await
550 .expect("session should be stored")
551 .map(|session| session.data.access_jwt),
552 Some("access".into())
553 );
554 }
555
556 #[cfg(not(target_arch = "wasm32"))]
557 #[tokio::test]
558 async fn test_xrpc_get_session_with_duplicated_refresh() {
559 let mut session_data = session_data();
560 session_data.access_jwt = String::from("expired");
561 let client = MockClient {
562 responses: MockResponses {
563 get_session: Some(crate::com::atproto::server::get_session::OutputData {
564 active: session_data.active,
565 did: session_data.did.clone(),
566 did_doc: session_data.did_doc.clone(),
567 email: session_data.email.clone(),
568 email_auth_factor: session_data.email_auth_factor,
569 email_confirmed: session_data.email_confirmed,
570 handle: session_data.handle.clone(),
571 status: session_data.status.clone(),
572 }),
573 ..Default::default()
574 },
575 ..Default::default()
576 };
577 let counts = Arc::clone(&client.counts);
578 let agent = Arc::new(AtpAgent::new(client, MemorySessionStore::default()));
579 agent
580 .session_manager
581 .store
582 .set(session_data.clone().into())
583 .await
584 .expect("set session should be succeeded");
585 let handles = (0..3).map(|_| {
586 let agent = Arc::clone(&agent);
587 tokio::spawn(async move { agent.api.com.atproto.server.get_session().await })
588 });
589 let results = futures::future::join_all(handles).await;
590 for result in &results {
591 let output = result
592 .as_ref()
593 .expect("task should be successfully executed")
594 .as_ref()
595 .expect("get session should be succeeded");
596 assert_eq!(output.did.as_str(), "did:web:example.com");
597 }
598 assert_eq!(
599 agent
600 .session_manager
601 .store
602 .get()
603 .await
604 .expect("session should be stored")
605 .map(|session| session.data.access_jwt),
606 Some("access".into())
607 );
608 assert_eq!(
609 counts.read().await.clone(),
610 HashMap::from_iter([
611 ("com.atproto.server.refreshSession".into(), 1),
612 ("com.atproto.server.getSession".into(), 3)
613 ])
614 );
615 }
616
617 #[tokio::test]
618 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
619 async fn test_resume_session() {
620 let session_data = session_data();
621 {
623 let client = MockClient {
624 responses: MockResponses {
625 get_session: Some(crate::com::atproto::server::get_session::OutputData {
626 active: session_data.active,
627 did: session_data.did.clone(),
628 did_doc: session_data.did_doc.clone(),
629 email: session_data.email.clone(),
630 email_auth_factor: session_data.email_auth_factor,
631 email_confirmed: session_data.email_confirmed,
632 handle: session_data.handle.clone(),
633 status: session_data.status.clone(),
634 }),
635 ..Default::default()
636 },
637 ..Default::default()
638 };
639 let agent = AtpAgent::new(client, MemorySessionStore::default());
640 assert_eq!(agent.get_session().await, None);
641 agent
642 .resume_session(
643 OutputData {
644 email: Some(String::from("test@example.com")),
645 ..session_data.clone()
646 }
647 .into(),
648 )
649 .await
650 .expect("resume_session should be succeeded");
651 assert_eq!(agent.get_session().await, Some(session_data.clone().into()));
652 }
653 {
655 let client = MockClient {
656 responses: MockResponses { ..Default::default() },
657 ..Default::default()
658 };
659 let agent = AtpAgent::new(client, MemorySessionStore::default());
660 assert_eq!(agent.get_session().await, None);
661 agent
662 .resume_session(session_data.clone().into())
663 .await
664 .expect_err("resume_session should be failed");
665 assert_eq!(agent.get_session().await, None);
666 }
667 }
668
669 #[tokio::test]
670 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
671 async fn test_resume_session_with_refresh() {
672 let session_data = session_data();
673 let client = MockClient {
674 responses: MockResponses {
675 get_session: Some(crate::com::atproto::server::get_session::OutputData {
676 active: session_data.active,
677 did: session_data.did.clone(),
678 did_doc: session_data.did_doc.clone(),
679 email: session_data.email.clone(),
680 email_auth_factor: session_data.email_auth_factor,
681 email_confirmed: session_data.email_confirmed,
682 handle: session_data.handle.clone(),
683 status: session_data.status.clone(),
684 }),
685 ..Default::default()
686 },
687 ..Default::default()
688 };
689 let agent = AtpAgent::new(client, MemorySessionStore::default());
690 agent
691 .resume_session(
692 OutputData { access_jwt: "expired".into(), ..session_data.clone() }.into(),
693 )
694 .await
695 .expect("resume_session should be succeeded");
696 assert_eq!(agent.get_session().await, Some(session_data.clone().into()));
697 }
698
699 #[tokio::test]
700 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
701 async fn test_login_with_diddoc() {
702 let session_data = session_data();
703 let did_doc = DidDocument {
704 context: None,
705 id: "did:plc:ewvi7nxzyoun6zhxrhs64oiz".into(),
706 also_known_as: Some(vec!["at://atproto.com".into()]),
707 verification_method: Some(vec![VerificationMethod {
708 id: "did:plc:ewvi7nxzyoun6zhxrhs64oiz#atproto".into(),
709 r#type: "Multikey".into(),
710 controller: "did:plc:ewvi7nxzyoun6zhxrhs64oiz".into(),
711 public_key_multibase: Some(
712 "zQ3shXjHeiBuRCKmM36cuYnm7YEMzhGnCmCyW92sRJ9pribSF".into(),
713 ),
714 }]),
715 service: Some(vec![Service {
716 id: "#atproto_pds".into(),
717 r#type: "AtprotoPersonalDataServer".into(),
718 service_endpoint: "https://bsky.social".into(),
719 }]),
720 };
721 {
723 let client = MockClient {
724 responses: MockResponses {
725 create_session: Some(crate::com::atproto::server::create_session::OutputData {
726 did_doc: Some(
727 did_doc
728 .clone()
729 .try_into_unknown()
730 .expect("failed to convert to unknown"),
731 ),
732 ..session_data.clone()
733 }),
734 ..Default::default()
735 },
736 ..Default::default()
737 };
738 let agent = AtpAgent::new(client, MemorySessionStore::default());
739 agent.login("test", "pass").await.expect("login should be succeeded");
740 assert_eq!(agent.get_endpoint().await, "https://bsky.social");
741 assert_eq!(agent.api.com.atproto.server.xrpc.base_uri(), "https://bsky.social");
742 }
743 {
745 let client = MockClient {
746 responses: MockResponses {
747 create_session: Some(crate::com::atproto::server::create_session::OutputData {
748 did_doc: Some(
749 DidDocument {
750 service: Some(vec![
751 Service {
752 id: "#pds".into(), r#type: "AtprotoPersonalDataServer".into(),
754 service_endpoint: "https://bsky.social".into(),
755 },
756 Service {
757 id: "#atproto_pds".into(),
758 r#type: "AtprotoPersonalDataServer".into(),
759 service_endpoint: "htps://bsky.social".into(), },
761 ]),
762 ..did_doc.clone()
763 }
764 .try_into_unknown()
765 .expect("failed to convert to unknown"),
766 ),
767 ..session_data.clone()
768 }),
769 ..Default::default()
770 },
771 ..Default::default()
772 };
773 let agent = AtpAgent::new(client, MemorySessionStore::default());
774 agent.login("test", "pass").await.expect("login should be succeeded");
775 assert_eq!(agent.get_endpoint().await, "http://localhost:8080");
777 assert_eq!(agent.api.com.atproto.server.xrpc.base_uri(), "http://localhost:8080");
778 }
779 }
780
781 #[tokio::test]
782 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
783 async fn test_configure_labelers_header() {
784 let client = MockClient::default();
785 let headers = Arc::clone(&client.headers);
786 let agent = AtpAgent::new(client, MemorySessionStore::default());
787
788 agent
789 .api
790 .com
791 .atproto
792 .server
793 .describe_server()
794 .await
795 .expect("describe_server should be succeeded");
796 assert_eq!(headers.read().await.last(), Some(&HeaderMap::new()));
797
798 agent.configure_labelers_header(Some(vec![(
799 "did:plc:test1".parse().expect("did should be valid"),
800 false,
801 )]));
802 agent
803 .api
804 .com
805 .atproto
806 .server
807 .describe_server()
808 .await
809 .expect("describe_server should be succeeded");
810 assert_eq!(
811 headers.read().await.last(),
812 Some(&HeaderMap::from_iter([(
813 HeaderName::from_static("atproto-accept-labelers"),
814 HeaderValue::from_static("did:plc:test1"),
815 )]))
816 );
817
818 agent.configure_labelers_header(Some(vec![
819 ("did:plc:test1".parse().expect("did should be valid"), true),
820 ("did:plc:test2".parse().expect("did should be valid"), false),
821 ]));
822 agent
823 .api
824 .com
825 .atproto
826 .server
827 .describe_server()
828 .await
829 .expect("describe_server should be succeeded");
830 assert_eq!(
831 headers.read().await.last(),
832 Some(&HeaderMap::from_iter([(
833 HeaderName::from_static("atproto-accept-labelers"),
834 HeaderValue::from_static("did:plc:test1;redact, did:plc:test2"),
835 )]))
836 );
837
838 assert_eq!(
839 agent.get_labelers_header().await,
840 Some(vec![String::from("did:plc:test1;redact"), String::from("did:plc:test2")])
841 );
842 }
843
844 #[tokio::test]
845 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
846 async fn test_configure_proxy_header() {
847 let client = MockClient::default();
848 let headers = Arc::clone(&client.headers);
849 let agent = AtpAgent::new(client, MemorySessionStore::default());
850
851 agent
852 .api
853 .com
854 .atproto
855 .server
856 .describe_server()
857 .await
858 .expect("describe_server should be succeeded");
859 assert_eq!(headers.read().await.last(), Some(&HeaderMap::new()));
860
861 agent.configure_proxy_header(
862 "did:plc:test1".parse().expect("did should be valid"),
863 AtprotoServiceType::AtprotoLabeler,
864 );
865 agent
866 .api
867 .com
868 .atproto
869 .server
870 .describe_server()
871 .await
872 .expect("describe_server should be succeeded");
873 assert_eq!(
874 headers.read().await.last(),
875 Some(&HeaderMap::from_iter([(
876 HeaderName::from_static("atproto-proxy"),
877 HeaderValue::from_static("did:plc:test1#atproto_labeler"),
878 ),]))
879 );
880
881 agent.configure_proxy_header(
882 "did:plc:test1".parse().expect("did should be valid"),
883 "atproto_labeler",
884 );
885 agent
886 .api
887 .com
888 .atproto
889 .server
890 .describe_server()
891 .await
892 .expect("describe_server should be succeeded");
893 assert_eq!(
894 headers.read().await.last(),
895 Some(&HeaderMap::from_iter([(
896 HeaderName::from_static("atproto-proxy"),
897 HeaderValue::from_static("did:plc:test1#atproto_labeler"),
898 ),]))
899 );
900
901 agent
902 .api_with_proxy(
903 "did:plc:test2".parse().expect("did should be valid"),
904 "atproto_labeler",
905 )
906 .com
907 .atproto
908 .server
909 .describe_server()
910 .await
911 .expect("describe_server should be succeeded");
912 assert_eq!(
913 headers.read().await.last(),
914 Some(&HeaderMap::from_iter([(
915 HeaderName::from_static("atproto-proxy"),
916 HeaderValue::from_static("did:plc:test2#atproto_labeler"),
917 ),]))
918 );
919
920 agent
921 .api
922 .com
923 .atproto
924 .server
925 .describe_server()
926 .await
927 .expect("describe_server should be succeeded");
928 assert_eq!(
929 headers.read().await.last(),
930 Some(&HeaderMap::from_iter([(
931 HeaderName::from_static("atproto-proxy"),
932 HeaderValue::from_static("did:plc:test1#atproto_labeler"),
933 ),]))
934 );
935
936 assert_eq!(
937 agent.get_proxy_header().await,
938 Some(String::from("did:plc:test1#atproto_labeler"))
939 );
940 }
941
942 #[tokio::test]
943 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
944 async fn test_agent_did() {
945 let session_data = session_data();
946 let client = MockClient { responses: MockResponses::default(), ..Default::default() };
947 let agent = AtpAgent::new(client, MemorySessionStore::default());
948 assert_eq!(agent.did().await, None);
949 agent
950 .session_manager
951 .store
952 .set(session_data.clone().into())
953 .await
954 .expect("set session should be succeeded");
955 assert_eq!(agent.did().await, Some(session_data.did));
956 }
957}