1use crate::AdapterRecord;
2pub mod cookie_cache;
3mod request;
4use crate::config::AuthConfig;
5use crate::entity::{AuthSession, AuthUser};
6use crate::error::AuthResult;
7use crate::schema::AuthSchema;
8use crate::store::AuthStore;
9use crate::types::CreateSession;
10use chrono::Utc;
11pub use request::SessionRequest;
12use std::sync::Arc;
13
14#[derive(Clone, Copy, Debug)]
18pub struct SessionRefreshSuppressed;
19
20#[derive(Debug, Clone, Copy)]
22pub struct SessionReadOptions {
23 pub allow_refresh: bool,
24 pub cleanup_expired: bool,
25}
26
27impl Default for SessionReadOptions {
28 fn default() -> Self {
29 Self {
30 allow_refresh: true,
31 cleanup_expired: true,
32 }
33 }
34}
35
36pub struct SessionRead<T> {
38 pub session: Option<T>,
39 pub needs_refresh: bool,
40 pub refreshed: bool,
41}
42
43impl<T> SessionRead<T> {
44 const fn absent() -> Self {
45 Self {
46 session: None,
47 needs_refresh: false,
48 refreshed: false,
49 }
50 }
51}
52
53pub struct SessionManager<S: AuthSchema> {
55 config: Arc<AuthConfig>,
56 database: Arc<dyn AuthStore<S>>,
57}
58
59impl<S: AuthSchema> Clone for SessionManager<S> {
60 fn clone(&self) -> Self {
61 Self {
62 config: Arc::clone(&self.config),
63 database: Arc::clone(&self.database),
64 }
65 }
66}
67
68impl<S: AuthSchema> SessionManager<S> {
69 #[must_use]
70 pub fn new(config: Arc<AuthConfig>, database: Arc<dyn AuthStore<S>>) -> Self {
71 Self { config, database }
72 }
73
74 pub async fn create_session(
80 &self,
81 user: &impl AuthUser,
82 ip_address: Option<String>,
83 user_agent: Option<String>,
84 ) -> AuthResult<S::Session> {
85 self.database
86 .create_session(self.new_session(user, ip_address, user_agent))
87 .await
88 }
89
90 pub async fn create_session_record(
95 &self,
96 user: &impl AuthUser,
97 ip_address: Option<String>,
98 user_agent: Option<String>,
99 ) -> AuthResult<AdapterRecord<S::Session>> {
100 self.database
101 .create_session_record(self.new_session(user, ip_address, user_agent))
102 .await
103 }
104
105 fn new_session(
106 &self,
107 user: &impl AuthUser,
108 ip_address: Option<String>,
109 user_agent: Option<String>,
110 ) -> CreateSession {
111 CreateSession {
112 additional_fields: crate::field_policy::FieldValues::default(),
113 token: None,
114 active_team_id: None,
115 user_id: user.id().to_string(),
116 expires_at: Utc::now() + self.config.session.expires_in,
117 ip_address,
118 user_agent,
119 impersonated_by: None,
120 active_organization_id: None,
121 }
122 }
123
124 pub async fn get_session(&self, token: &str) -> AuthResult<Option<S::Session>> {
130 Ok(self
131 .read_session(token, SessionReadOptions::default())
132 .await?
133 .session)
134 }
135
136 pub async fn read_session(
143 &self,
144 token: &str,
145 options: SessionReadOptions,
146 ) -> AuthResult<SessionRead<S::Session>> {
147 let Some(session) = self.database.get_session(token).await? else {
148 return Ok(SessionRead::absent());
149 };
150 self.read_loaded_session(session, options).await
151 }
152
153 pub async fn read_loaded_session(
162 &self,
163 session: S::Session,
164 options: SessionReadOptions,
165 ) -> AuthResult<SessionRead<S::Session>> {
166 self.read_loaded(session, options, |token, expires_at| async move {
167 self.database.refresh_session(&token, expires_at).await
168 })
169 .await
170 }
171
172 pub async fn read_loaded_session_record(
175 &self,
176 session: AdapterRecord<S::Session>,
177 options: SessionReadOptions,
178 ) -> AuthResult<SessionRead<AdapterRecord<S::Session>>> {
179 self.read_loaded(session, options, |token, expires_at| async move {
180 self.database
181 .refresh_session_record(&token, expires_at)
182 .await
183 })
184 .await
185 }
186
187 async fn read_loaded<T, F, Fut>(
188 &self,
189 session: T,
190 options: SessionReadOptions,
191 refresh: F,
192 ) -> AuthResult<SessionRead<T>>
193 where
194 T: AuthSession,
195 F: FnOnce(String, chrono::DateTime<chrono::Utc>) -> Fut,
196 Fut: std::future::Future<Output = AuthResult<Option<T>>>,
197 {
198 let token = session.token();
199 let now = Utc::now();
200 if session.expires_at() < now || !session.active() {
201 if options.cleanup_expired {
202 self.database.delete_session(token).await?;
203 }
204 return Ok(SessionRead::absent());
205 }
206 let needs_refresh = !self.config.session.disable_session_refresh
207 && self.config.session.update_age.is_none_or(|age| {
208 session.expires_at() - self.config.session.expires_in + age <= now
209 });
210 if needs_refresh && options.allow_refresh {
211 let refreshed_session =
212 refresh(token.to_owned(), now + self.config.session.expires_in).await?;
213 let refreshed = refreshed_session.is_some();
214 return Ok(SessionRead {
215 session: refreshed_session,
216 needs_refresh,
217 refreshed,
218 });
219 }
220 Ok(SessionRead {
221 session: Some(session),
222 needs_refresh,
223 refreshed: false,
224 })
225 }
226
227 #[must_use]
231 pub fn request_disables_refresh(&self, request: &impl SessionRequest) -> bool {
232 request.session_query_truthy("disableRefresh") || self.has_dont_remember_cookie(request)
233 }
234
235 #[must_use]
237 pub fn has_dont_remember_cookie(&self, request: &impl SessionRequest) -> bool {
238 let name = crate::utils::cookie_utils::related_cookie_name(&self.config, "dont_remember");
239 request
240 .session_headers()
241 .get("cookie")
242 .is_some_and(|header| {
243 cookie::Cookie::split_parse(header)
244 .flatten()
245 .find(|cookie| cookie.name() == name)
246 .and_then(|cookie| {
247 crate::utils::cookie_utils::verify_cookie_value(
248 cookie.value(),
249 self.config.current_secret(),
250 )
251 })
252 .is_some_and(|value| !value.is_empty())
253 })
254 }
255
256 pub async fn delete_session(&self, token: &str) -> AuthResult<()> {
262 self.database.delete_session(token).await
263 }
264
265 pub async fn delete_user_sessions(&self, user_id: impl AsRef<str>) -> AuthResult<()> {
271 self.database.delete_user_sessions(user_id.as_ref()).await
272 }
273
274 pub async fn list_user_sessions(
280 &self,
281 user_id: impl AsRef<str>,
282 ) -> AuthResult<Vec<S::Session>> {
283 let sessions = self.database.get_user_sessions(user_id.as_ref()).await?;
284 let now = Utc::now();
285 Ok(sessions
286 .into_iter()
287 .filter(|session| session.expires_at() > now && session.active())
288 .collect())
289 }
290
291 pub async fn revoke_session(&self, token: &str) -> AuthResult<bool> {
297 if self.get_session(token).await?.is_none() {
298 return Ok(false);
299 }
300 self.delete_session(token).await?;
301 Ok(true)
302 }
303
304 pub async fn revoke_all_user_sessions(&self, user_id: impl AsRef<str>) -> AuthResult<usize> {
310 let user_id = user_id.as_ref();
311 let count = self.list_user_sessions(user_id).await?.len();
312 self.delete_user_sessions(user_id).await?;
313 Ok(count)
314 }
315
316 pub async fn revoke_other_user_sessions(
322 &self,
323 user_id: impl AsRef<str>,
324 current_token: &str,
325 ) -> AuthResult<usize> {
326 let sessions = self.list_user_sessions(user_id).await?;
327 let mut count = 0;
328
329 for session in sessions {
330 if session.token() != current_token {
331 self.delete_session(session.token()).await?;
332 count += 1;
333 }
334 }
335
336 Ok(count)
337 }
338
339 pub fn is_session_fresh(&self, session: &impl AuthSession) -> bool {
345 match self.config.session.fresh_age {
346 Some(fresh_age) if fresh_age != chrono::Duration::zero() => {
347 session.created_at() + fresh_age > Utc::now()
348 }
349 _ => true,
350 }
351 }
352
353 #[must_use]
355 pub fn validate_token_format(&self, token: &str) -> bool {
356 token.len() == 32 && token.bytes().all(|byte| byte.is_ascii_alphanumeric())
357 }
358
359 #[must_use]
364 pub fn extract_session_token(&self, req: &impl SessionRequest) -> Option<String> {
365 let header = req.session_headers().get("cookie")?;
366 header
367 .split(';')
368 .find_map(|pair| {
369 let (name, value) = pair.split_once('=')?;
370 (crate::utils::javascript::trim(name) == self.config.session.cookie_name)
371 .then(|| crate::utils::javascript::trim(value))
372 })
373 .map(|value| {
374 if value.starts_with('"') {
377 value.get(1..value.len().saturating_sub(1)).unwrap_or("")
378 } else {
379 value
380 }
381 })
382 .and_then(|value| {
383 crate::utils::cookie_utils::verify_cookie_value(value, self.config.current_secret())
384 })
385 .filter(|token| !token.is_empty())
386 }
387}
388
389impl<T> std::fmt::Debug for SessionRead<T> {
390 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
391 f.debug_struct("SessionRead").finish_non_exhaustive()
392 }
393}
394
395impl<S: AuthSchema> std::fmt::Debug for SessionManager<S> {
396 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
397 f.debug_struct("SessionManager").finish_non_exhaustive()
398 }
399}
400
401#[cfg(test)]
403mod tests {
404 use super::*;
405 use crate::entity::AuthSession;
406 use crate::test_store::{BundledSchema, test_config, test_database};
407 use crate::types::AuthRequest;
408 use crate::types::HttpMethod;
409 use crate::wire::SessionView;
410 use chrono::Duration;
411
412 fn test_manager() -> SessionManager<BundledSchema> {
413 let runtime = tokio::runtime::Runtime::new().expect("runtime should build");
414 SessionManager::new(test_config(), runtime.block_on(test_database()))
415 }
416
417 #[test]
419 fn valid_token_format() {
420 let mgr = test_manager();
421 let token = "abcdefghijklmnopqrstuvwxyz123456";
422 assert!(mgr.validate_token_format(token));
423 }
424
425 #[test]
427 fn invalid_token_non_alphanumeric() {
428 let mgr = test_manager();
429 assert!(!mgr.validate_token_format("abcdefghijklmnopqrstuvwxy_123456"));
430 }
431
432 #[test]
434 fn invalid_token_too_short() {
435 let mgr = test_manager();
436 assert!(!mgr.validate_token_format("session_short"));
437 }
438
439 #[test]
442 fn extract_rejects_bare_bearer() {
443 let mgr = test_manager();
444 let mut req = AuthRequest::new(HttpMethod::Get, "/test");
445 _ = req
446 .headers
447 .insert("authorization".into(), "Bearer my-token".into());
448 assert_eq!(mgr.extract_session_token(&req), None);
449 }
450
451 #[test]
452 fn extract_cookie_checks_signature_and_first_duplicate() {
453 let mgr = test_manager();
454 let signed = crate::utils::cookie_utils::sign_cookie_value("tok123", &mgr.config.secret);
455 let foreign =
456 crate::utils::cookie_utils::sign_cookie_value("tok123", "another-server-secret");
457 for (cookie, expected) in [
458 (
459 format!("better-auth.session_token={signed}; other=val"),
460 Some("tok123"),
461 ),
462 ("better-auth.session_token=tok123".to_owned(), None),
463 (format!("better-auth.session_token={foreign}"), None),
464 (
465 format!("better-auth.session_token={signed}; better-auth.session_token=invalid"),
466 Some("tok123"),
467 ),
468 (
469 format!("better-auth.session_token=invalid; better-auth.session_token={signed}"),
470 None,
471 ),
472 ] {
473 let mut req = AuthRequest::new(HttpMethod::Get, "/test");
474 _ = req.headers.insert("cookie".into(), cookie);
475 assert_eq!(mgr.extract_session_token(&req).as_deref(), expected);
476 }
477 }
478
479 #[test]
480 fn extract_ignores_bearer_when_signed_cookie_exists() {
481 let mgr = test_manager();
482 let mut req = AuthRequest::new(HttpMethod::Get, "/test");
483 _ = req
484 .headers
485 .insert("authorization".into(), "Bearer bearer-tok".into());
486 let signed =
487 crate::utils::cookie_utils::sign_cookie_value("cookie-tok", &mgr.config.secret);
488 _ = req.headers.insert(
489 "cookie".into(),
490 format!("better-auth.session_token={signed}"),
491 );
492 assert_eq!(mgr.extract_session_token(&req), Some("cookie-tok".into()));
493 }
494
495 #[test]
497 fn extract_returns_none_without_auth() {
498 let mgr = test_manager();
499 let req = AuthRequest::new(HttpMethod::Get, "/test");
500 assert_eq!(mgr.extract_session_token(&req), None);
501 }
502
503 #[test]
505 fn extract_skips_empty_cookie_value() {
506 let mgr = test_manager();
507 let mut req = AuthRequest::new(HttpMethod::Get, "/test");
508 _ = req
509 .headers
510 .insert("cookie".into(), "better-auth.session_token=".into());
511 assert_eq!(mgr.extract_session_token(&req), None);
512 }
513
514 #[test]
516 fn session_fresh_when_within_window() {
517 let mut config = AuthConfig::new("test-secret-min-32-chars-1234567");
518 config.session.fresh_age = Some(Duration::minutes(10));
519 let runtime = tokio::runtime::Runtime::new().expect("runtime should build");
520 let mgr = SessionManager::new(Arc::new(config), runtime.block_on(test_database()));
521
522 let session = SessionView {
524 omitted_fields: std::collections::BTreeSet::default(),
525 active_team_id: None,
526 extension_fields: std::collections::BTreeMap::default(),
527 id: "s1".into(),
528 expires_at: Utc::now() + Duration::hours(1),
529 token: "tok".into(),
530 created_at: Utc::now(),
531 updated_at: Utc::now(),
532 ip_address: None,
533 user_agent: None,
534 user_id: "u1".into(),
535 impersonated_by: None,
536 active_organization_id: None,
537 active: true,
538 };
539 assert!(mgr.is_session_fresh(&session));
540 }
541
542 #[test]
544 fn session_not_fresh_when_old() {
545 let mut config = AuthConfig::new("test-secret-min-32-chars-1234567");
546 config.session.fresh_age = Some(Duration::minutes(10));
547 let runtime = tokio::runtime::Runtime::new().expect("runtime should build");
548 let mgr = SessionManager::new(Arc::new(config), runtime.block_on(test_database()));
549
550 let session = SessionView {
551 omitted_fields: std::collections::BTreeSet::default(),
552 active_team_id: None,
553 extension_fields: std::collections::BTreeMap::default(),
554 id: "s1".into(),
555 expires_at: Utc::now() + Duration::hours(1),
556 token: "tok".into(),
557 created_at: Utc::now() - Duration::minutes(20),
558 updated_at: Utc::now(),
559 ip_address: None,
560 user_agent: None,
561 user_id: "u1".into(),
562 impersonated_by: None,
563 active_organization_id: None,
564 active: true,
565 };
566 assert!(!mgr.is_session_fresh(&session));
567 }
568
569 #[test]
571 fn disabled_freshness_allows_an_old_session() {
572 let mut config = (*test_config()).clone();
573 config.session.fresh_age = None;
574 let mgr = SessionManager::new(Arc::new(config), test_manager().database);
575 let session = SessionView {
576 omitted_fields: std::collections::BTreeSet::default(),
577 active_team_id: None,
578 extension_fields: std::collections::BTreeMap::default(),
579 id: "s1".into(),
580 expires_at: Utc::now() + Duration::hours(1),
581 token: "tok".into(),
582 created_at: Utc::now() - Duration::days(30),
583 updated_at: Utc::now(),
584 ip_address: None,
585 user_agent: None,
586 user_id: "u1".into(),
587 impersonated_by: None,
588 active_organization_id: None,
589 active: true,
590 };
591 assert!(mgr.is_session_fresh(&session));
592 }
593
594 #[tokio::test]
596 async fn create_and_get_session() {
597 let db = test_database().await;
598 let mgr = SessionManager::new(test_config(), Arc::clone(&db));
599
600 let user = db
602 .create_user(crate::types::CreateUser::new().with_email("test@test.com"))
603 .await
604 .unwrap();
605
606 let session = mgr.create_session(&user, None, None).await.unwrap();
607 let token = session.token().to_owned();
608
609 let retrieved = mgr.get_session(&token).await.unwrap();
610 assert!(retrieved.is_some());
611 }
612
613 #[tokio::test]
615 async fn refresh_returns_the_persisted_expiry() {
616 let db = test_database().await;
617 let mut config = AuthConfig::new("test-secret-min-32-chars-1234567");
618 config.session.update_age = None;
620 let mgr = SessionManager::new(Arc::new(config), Arc::clone(&db));
621
622 let user = db
623 .create_user(crate::types::CreateUser::new().with_email("refresh@test.com"))
624 .await
625 .unwrap();
626 let session = mgr.create_session(&user, None, None).await.unwrap();
627 let token = session.token().to_owned();
628
629 let stale = session.expires_at() - Duration::minutes(30);
631 db.update_session_expiry(&token, stale).await.unwrap();
632
633 let returned = mgr
634 .get_session(&token)
635 .await
636 .unwrap()
637 .expect("session should still be live");
638 let stored = db
639 .get_session(&token)
640 .await
641 .unwrap()
642 .expect("session should still be stored");
643
644 assert!(
645 returned.expires_at() > stale,
646 "refresh should have extended the expiry"
647 );
648 assert_eq!(
649 returned.expires_at(),
650 stored.expires_at(),
651 "returned session must reflect the persisted expiry, not the pre-refresh value"
652 );
653 }
654
655 #[test]
656 fn refresh_preferences_verify_the_first_cookie_and_use_javascript_truthiness() {
657 let manager = test_manager();
658 let name =
659 crate::utils::cookie_utils::related_cookie_name(&manager.config, "dont_remember");
660 let valid = crate::utils::cookie_utils::sign_cookie_value("true", &manager.config.secret);
661 let empty = crate::utils::cookie_utils::sign_cookie_value("", &manager.config.secret);
662 let wrong = crate::utils::cookie_utils::sign_cookie_value("true", "foreign-secret");
663 for (header, expected) in [
664 (format!("{name}={valid}"), true),
665 (format!("{name}={empty}"), false),
666 (format!("{name}={wrong}"), false),
667 (format!("{name}=invalid; {name}={valid}"), false),
668 (format!("{name}={valid}; {name}=invalid"), true),
669 ] {
670 let mut request = AuthRequest::new(HttpMethod::Get, "/get-session");
671 _ = request.headers.insert("cookie".into(), header);
672 assert_eq!(manager.request_disables_refresh(&request), expected);
673 }
674 for (value, expected) in [("", false), ("false", true), ("0", true), ("true", true)] {
675 let mut request = AuthRequest::new(HttpMethod::Get, "/get-session");
676 _ = request.query.insert("disableRefresh".into(), value.into());
677 assert_eq!(manager.request_disables_refresh(&request), expected);
678 }
679 }
680
681 #[tokio::test]
682 async fn recent_sessions_refresh_at_the_expiry_based_half_second_boundary() {
683 let db = test_database().await;
684 let config = test_config();
685 let manager = SessionManager::new(Arc::clone(&config), Arc::clone(&db));
686 let user = db
687 .create_user(crate::types::CreateUser::new().with_email("half-second@test.com"))
688 .await
689 .unwrap();
690 for difference in [Duration::milliseconds(500), Duration::milliseconds(-500)] {
691 let session = manager.create_session(&user, None, None).await.unwrap();
692 let expiry = Utc::now() + config.session.expires_in
693 - config.session.update_age.unwrap()
694 + difference;
695 db.update_session_expiry(session.token(), expiry)
696 .await
697 .unwrap();
698 let returned = manager.get_session(session.token()).await.unwrap().unwrap();
699 let stored = db.get_session(session.token()).await.unwrap().unwrap();
700 assert_eq!(returned.expires_at(), stored.expires_at());
701 assert_eq!(returned.token(), session.token());
702 if difference > Duration::zero() {
703 assert_eq!(stored.expires_at(), expiry);
704 } else {
705 assert!(stored.expires_at() > expiry + Duration::hours(23));
706 }
707 }
708 }
709
710 #[tokio::test]
712 async fn create_session_without_metadata_uses_empty_strings() {
713 let db = test_database().await;
714 let mgr = SessionManager::new(test_config(), Arc::clone(&db));
715
716 let user = db
717 .create_user(crate::types::CreateUser::new().with_email("test@test.com"))
718 .await
719 .unwrap();
720
721 let session = mgr.create_session(&user, None, None).await.unwrap();
722 assert_eq!(session.ip_address.as_deref(), Some(""));
723 assert_eq!(session.user_agent.as_deref(), Some(""));
724 }
725
726 #[tokio::test]
728 async fn delete_session_removes_it() {
729 let db = test_database().await;
730 let mgr = SessionManager::new(test_config(), Arc::clone(&db));
731
732 let user = db
733 .create_user(crate::types::CreateUser::new().with_email("test@test.com"))
734 .await
735 .unwrap();
736
737 let session = mgr.create_session(&user, None, None).await.unwrap();
738 let token = session.token().to_owned();
739
740 mgr.delete_session(&token).await.unwrap();
741 let retrieved = mgr.get_session(&token).await.unwrap();
742 assert!(retrieved.is_none());
743 }
744
745 #[tokio::test]
747 async fn revoke_session_returns_true_when_found() {
748 let db = test_database().await;
749 let mgr = SessionManager::new(test_config(), Arc::clone(&db));
750
751 let user = db
752 .create_user(crate::types::CreateUser::new().with_email("test@test.com"))
753 .await
754 .unwrap();
755
756 let session = mgr.create_session(&user, None, None).await.unwrap();
757 let result = mgr.revoke_session(session.token()).await.unwrap();
758 assert!(result);
759 }
760
761 #[tokio::test]
763 async fn revoke_session_returns_false_when_not_found() {
764 let mgr = SessionManager::new(test_config(), test_database().await);
765 let result = mgr.revoke_session("nonexistent-token").await.unwrap();
766 assert!(!result);
767 }
768
769 #[tokio::test]
771 async fn list_user_sessions_excludes_expired() {
772 let db = test_database().await;
773 let mgr = SessionManager::new(test_config(), Arc::clone(&db));
774
775 let user = db
776 .create_user(crate::types::CreateUser::new().with_email("test@test.com"))
777 .await
778 .unwrap();
779
780 _ = mgr.create_session(&user, None, None).await.unwrap();
782 _ = mgr.create_session(&user, None, None).await.unwrap();
783
784 let sessions = mgr.list_user_sessions(user.id()).await.unwrap();
785 assert_eq!(sessions.len(), 2);
786 }
787
788 #[tokio::test]
790 async fn revoke_all_user_sessions() {
791 let db = test_database().await;
792 let mgr = SessionManager::new(test_config(), Arc::clone(&db));
793
794 let user = db
795 .create_user(crate::types::CreateUser::new().with_email("test@test.com"))
796 .await
797 .unwrap();
798
799 _ = mgr.create_session(&user, None, None).await.unwrap();
800 _ = mgr.create_session(&user, None, None).await.unwrap();
801
802 let count = mgr.revoke_all_user_sessions(user.id()).await.unwrap();
803 assert_eq!(count, 2);
804
805 let sessions = mgr.list_user_sessions(user.id()).await.unwrap();
806 assert_eq!(sessions, Vec::<SessionView>::new());
807 }
808
809 #[tokio::test]
811 async fn revoke_other_sessions_keeps_current() {
812 let db = test_database().await;
813 let mgr = SessionManager::new(test_config(), Arc::clone(&db));
814
815 let user = db
816 .create_user(crate::types::CreateUser::new().with_email("test@test.com"))
817 .await
818 .unwrap();
819
820 let current = mgr.create_session(&user, None, None).await.unwrap();
821 _ = mgr.create_session(&user, None, None).await.unwrap();
822 _ = mgr.create_session(&user, None, None).await.unwrap();
823
824 let count = mgr
825 .revoke_other_user_sessions(user.id(), current.token())
826 .await
827 .unwrap();
828 assert_eq!(count, 2);
829
830 let remaining = mgr.list_user_sessions(user.id()).await.unwrap();
831 assert_eq!(remaining.len(), 1);
832 assert_eq!(
833 (*(remaining)
834 .first()
835 .expect("fixture contains the requested index"))
836 .token(),
837 current.token()
838 );
839 }
840}
841