bloop_server_framework/
test_utils.rs1use crate::achievement::AchievementContext;
2use crate::bloop::{Bloop, BloopProvider};
3use crate::evaluator::registration_number::RegistrationNumberProvider;
4use crate::nfc_uid::NfcUid;
5use crate::player::{PlayerInfo, PlayerMutator};
6use crate::trigger::TriggerRegistry;
7use chrono::DateTime;
8use std::collections::HashMap;
9use std::fmt::Debug;
10use std::sync::{Arc, OnceLock, RwLock};
11use std::time::Duration;
12use tokio::time::Instant;
13use uuid::Uuid;
14
15pub struct Utc;
18
19static BASE_INSTANT: OnceLock<Instant> = OnceLock::new();
20static BASE_UTC: OnceLock<DateTime<chrono::Utc>> = OnceLock::new();
21
22impl Utc {
25 pub fn now() -> DateTime<chrono::Utc> {
26 let base_instant = *BASE_INSTANT.get_or_init(Instant::now);
27 let base_utc = *BASE_UTC.get_or_init(|| DateTime::from_timestamp(0, 0).unwrap());
28
29 let elapsed = Instant::now()
30 .checked_duration_since(base_instant)
31 .unwrap_or(Duration::ZERO);
32
33 base_utc + chrono::Duration::from_std(elapsed).unwrap()
34 }
35}
36
37#[derive(Default, Debug)]
39pub struct MockPlayerBuilder {
40 nfc_uid: NfcUid,
41 bloops_count: usize,
42 registration_number: usize,
43 name: String,
44}
45
46impl MockPlayerBuilder {
47 pub fn new() -> Self {
49 Self {
50 nfc_uid: NfcUid::default(),
51 bloops_count: 0,
52 registration_number: 0,
53 name: "test".to_string(),
54 }
55 }
56
57 pub fn nfc_uid(mut self, nfc_uid: NfcUid) -> Self {
59 self.nfc_uid = nfc_uid;
60 self
61 }
62
63 pub fn bloops_count(mut self, bloops_count: usize) -> Self {
65 self.bloops_count = bloops_count;
66 self
67 }
68
69 pub fn registration_number(mut self, registration_number: usize) -> Self {
71 self.registration_number = registration_number;
72 self
73 }
74
75 pub fn name(mut self, name: impl Into<String>) -> Self {
77 self.name = name.into();
78 self
79 }
80
81 pub fn build(self) -> (Arc<RwLock<MockPlayer>>, Uuid) {
83 let id = Uuid::new_v4();
84
85 (
86 Arc::new(RwLock::new(MockPlayer {
87 id,
88 nfc_uid: self.nfc_uid,
89 name: self.name,
90 bloops_count: self.bloops_count,
91 awarded: HashMap::new(),
92 registration_number: self.registration_number,
93 })),
94 id,
95 )
96 }
97}
98
99#[derive(Debug)]
103pub struct MockPlayer {
104 pub id: Uuid,
105 pub nfc_uid: NfcUid,
106 pub name: String,
107 pub bloops_count: usize,
108 pub awarded: HashMap<Uuid, DateTime<chrono::Utc>>,
109 pub registration_number: usize,
110}
111
112impl MockPlayer {
113 pub fn builder() -> MockPlayerBuilder {
115 MockPlayerBuilder::new()
116 }
117}
118
119impl PlayerInfo for MockPlayer {
120 fn id(&self) -> Uuid {
121 self.id
122 }
123
124 fn nfc_uid(&self) -> NfcUid {
125 self.nfc_uid
126 }
127
128 fn total_bloops(&self) -> usize {
129 self.bloops_count
130 }
131
132 fn awarded_achievements(&self) -> &HashMap<Uuid, DateTime<chrono::Utc>> {
133 &self.awarded
134 }
135}
136
137impl PlayerMutator for MockPlayer {
138 fn increment_bloops(&mut self) {
139 self.bloops_count += 1;
140 }
141
142 fn add_awarded_achievement(&mut self, achievement_id: Uuid, awarded_at: DateTime<chrono::Utc>) {
143 self.awarded.insert(achievement_id, awarded_at);
144 }
145}
146
147impl RegistrationNumberProvider for MockPlayer {
148 fn registration_number(&self) -> usize {
149 self.registration_number
150 }
151}
152
153#[derive(Debug)]
158pub struct TestCtxBuilder<Player, State = (), Trigger = ()> {
159 pub current_bloop: Bloop<Player>,
160 pub state: State,
161 pub bloop_provider: BloopProvider<Player>,
162 pub trigger_registry: TriggerRegistry<Trigger>,
163}
164
165impl<Player> TestCtxBuilder<Player, (), ()> {
166 pub fn new(current_bloop: Bloop<Player>) -> Self {
168 Self {
169 current_bloop,
170 state: (),
171 trigger_registry: TriggerRegistry::new(HashMap::new()),
172 bloop_provider: BloopProvider::new(Duration::from_secs(1800)),
173 }
174 }
175}
176
177impl<Player, State, Trigger> TestCtxBuilder<Player, State, Trigger> {
178 pub fn bloops(mut self, bloops: Vec<Bloop<Player>>) -> Self {
180 self.bloop_provider = BloopProvider::with_bloops(Duration::from_secs(1800), bloops);
181 self
182 }
183
184 pub fn state<T: Default>(self, state: T) -> TestCtxBuilder<Player, T, Trigger> {
186 TestCtxBuilder {
187 current_bloop: self.current_bloop,
188 state,
189 bloop_provider: self.bloop_provider,
190 trigger_registry: self.trigger_registry,
191 }
192 }
193
194 pub fn trigger_registry<T>(
196 self,
197 trigger_registry: TriggerRegistry<T>,
198 ) -> TestCtxBuilder<Player, State, T> {
199 TestCtxBuilder {
200 current_bloop: self.current_bloop,
201 state: self.state,
202 bloop_provider: self.bloop_provider,
203 trigger_registry,
204 }
205 }
206
207 pub fn build(&mut self) -> AchievementContext<'_, Player, State, Trigger> {
209 AchievementContext::new(
210 &self.current_bloop,
211 &self.bloop_provider,
212 &self.state,
213 &mut self.trigger_registry,
214 )
215 }
216}