tank_tests/
user.rs

1#![allow(unused_imports)]
2use std::{
3    collections::{BTreeMap, HashSet},
4    sync::LazyLock,
5};
6use tank::{
7    Entity, Executor, Passive, expr,
8    stream::{StreamExt, TryStreamExt},
9};
10use time::macros::datetime;
11use tokio::sync::Mutex;
12use uuid::Uuid;
13
14#[derive(Entity, Debug, Clone)]
15// follower_count is PK to allow ordering for ScyllaDB / Cassandra
16#[tank(schema = "testing", name = "user_profiles", primary_key = (id, follower_count))]
17pub struct UserProfile {
18    #[tank(name = "user_id")]
19    pub id: Passive<Uuid>,
20    #[tank(unique, column_type = (mysql = "VARCHAR(128)"))]
21    pub username: String,
22    #[tank(unique, column_type = (mysql = "VARCHAR(128)"))]
23    pub email: String,
24    pub full_name: Option<String>,
25    #[tank(default = "0")]
26    pub follower_count: u32,
27    pub is_active: bool,
28    pub last_login: Option<time::PrimitiveDateTime>,
29    #[cfg(not(feature = "disable-maps"))]
30    pub preferences: Option<BTreeMap<String, String>>,
31}
32static MUTEX: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
33
34pub async fn users<E: Executor>(executor: &mut E) {
35    let _lock = MUTEX.lock().await;
36
37    // Cleanup
38    let result = UserProfile::drop_table(executor, true, false).await;
39    assert!(
40        result.is_ok(),
41        "Failed to UserProfile::drop_table: {:?}",
42        result.unwrap_err()
43    );
44
45    // Setup
46    let result = UserProfile::create_table(executor, false, true).await;
47    assert!(
48        result.is_ok(),
49        "Failed to UserProfile::create_table: {:?}",
50        result.unwrap_err()
51    );
52
53    // Insert
54    let users_to_create = vec![
55        UserProfile {
56            id: Uuid::parse_str("a1a1a1a1-a1a1-a1a1-a1a1-a1a1a1a1a1a1")
57                .unwrap()
58                .into(),
59            username: "alice".into(),
60            email: "alice@example.com".into(),
61            full_name: Some("Alice Wonderland".into()),
62            follower_count: 56,
63            is_active: true,
64            last_login: Some(datetime!(2025-07-15 10:00:00)),
65            #[cfg(not(feature = "disable-maps"))]
66            preferences: Some(BTreeMap::from_iter([("theme".into(), "dark".into())])),
67        },
68        UserProfile {
69            id: Uuid::parse_str("b2b2b2b2-b2b2-b2b2-b2b2-b2b2b2b2b2b2")
70                .unwrap()
71                .into(),
72            username: "bob".into(),
73            email: "bob@example.com".into(),
74            full_name: Some("Bob Builder".into()),
75            follower_count: 99,
76            is_active: false,
77            last_login: None,
78            #[cfg(not(feature = "disable-maps"))]
79            preferences: Some(BTreeMap::from_iter([("theme".into(), "light".into())])),
80        },
81        UserProfile {
82            id: Uuid::parse_str("c3c3c3c3-c3c3-c3c3-c3c3-c3c3c3c3c3c3")
83                .unwrap()
84                .into(),
85            username: "charlie".into(),
86            email: "charlie@example.com".into(),
87            full_name: None,
88            follower_count: 5000,
89            is_active: true,
90            last_login: Some(datetime!(2025-07-16 11:30:00)),
91            #[cfg(not(feature = "disable-maps"))]
92            preferences: None,
93        },
94        UserProfile {
95            id: Uuid::parse_str("d4d4d4d4-d4d4-d4d4-d4d4-d4d4d4d4d4d4")
96                .unwrap()
97                .into(),
98            username: "dean".into(),
99            email: "dean@example.com".into(),
100            full_name: Some("Dean Martin".into()),
101            follower_count: 15000,
102            is_active: true,
103            last_login: None,
104            #[cfg(not(feature = "disable-maps"))]
105            preferences: Some(BTreeMap::from_iter([(
106                "notifications".into(),
107                "off".into(),
108            )])),
109        },
110        UserProfile {
111            id: Uuid::parse_str("e5e5e5e5-e5e5-e5e5-e5e5-e5e5e5e5e5e5")
112                .unwrap()
113                .into(),
114            username: "eve".into(),
115            email: "eve@example.com".into(),
116            full_name: Some("Eve".into()),
117            follower_count: 1,
118            is_active: false,
119            last_login: Some(datetime!(2024-01-01 00:00:00)),
120            #[cfg(not(feature = "disable-maps"))]
121            preferences: None,
122        },
123    ];
124
125    let result = UserProfile::insert_many(executor, users_to_create.iter()).await;
126    assert!(
127        result.is_ok(),
128        "Failed to insert users: {:?}",
129        result.unwrap_err()
130    );
131    if let Some(affected) = result.unwrap().rows_affected {
132        assert_eq!(affected, 5);
133    }
134
135    // Find users with more than 1000 followers (should be 2: charlie, dean)
136    let popular_users = UserProfile::find_many(executor, &expr!(follower_count > 1000), None)
137        .try_collect::<Vec<_>>()
138        .await
139        .unwrap();
140    assert_eq!(popular_users.len(), 2);
141
142    // Find active users (should be 3: alice, charlie, dean)
143    let active_users = UserProfile::find_many(executor, &expr!(is_active == true), None)
144        .try_collect::<Vec<_>>()
145        .await
146        .unwrap();
147    assert_eq!(active_users.len(), 3);
148    let active_users = active_users
149        .into_iter()
150        .map(|u| u.username)
151        .collect::<HashSet<_>>();
152    assert_eq!(
153        active_users,
154        HashSet::from_iter(["alice".into(), "charlie".into(), "dean".into()])
155    );
156
157    // 4. Update a Bob
158    let mut bob = UserProfile::find_one(executor, &expr!(username == "bob"))
159        .await
160        .expect("Expected query to succeed")
161        .expect("Could not find bob ");
162    bob.is_active = true;
163    bob.full_name = Some("Robert Builder".into());
164    bob.last_login = Some(datetime!(2025-07-17 20:00:00));
165    let result = bob.save(executor).await;
166    assert!(
167        result.is_ok(),
168        "Failed to save Bob: {:?}",
169        result.unwrap_err()
170    );
171    let updated_bob = UserProfile::find_pk(executor, &bob.primary_key())
172        .await
173        .expect("Expected query to succeed")
174        .expect("Could not find bob ");
175    assert_eq!(updated_bob.is_active, true);
176    assert_eq!(updated_bob.full_name, Some("Robert Builder".into()));
177    assert!(updated_bob.last_login.is_some());
178
179    // There must be 4 active users
180    let active_users_after_update =
181        UserProfile::find_many(executor, &expr!(is_active == true), None)
182            .try_collect::<Vec<_>>()
183            .await
184            .unwrap();
185    assert_eq!(active_users_after_update.len(), 4);
186
187    // Find eve user and delete it.
188    let eve = UserProfile::find_one(executor, &expr!(username == "eve"))
189        .await
190        .expect("Expected query to succeed")
191        .expect("Could not find eve ");
192    let result = eve.delete(executor).await;
193    assert!(
194        result.is_ok(),
195        "Failed to delete Eve: {:?}",
196        result.unwrap_err()
197    );
198    let maybe_eve = UserProfile::find_pk(executor, &eve.primary_key())
199        .await
200        .expect("Expected query to succeed");
201    assert!(maybe_eve.is_none(), "Eve should have been deleted");
202
203    // There must be 5 total users
204    let total_users = UserProfile::find_many(executor, &true, None).count().await;
205    assert_eq!(total_users, 4, "There should be 4 users remaining");
206
207    // Delete all users who never logged in (only Dean)
208    let result = UserProfile::delete_many(executor, &expr!(last_login == NULL))
209        .await
210        .expect("Expected query to succeed");
211    if let Some(affected) = result.rows_affected {
212        assert_eq!(affected, 1, "Should have removed 1 rows");
213    }
214
215    // There must be 3 users left (alice, bob, charlie)
216    let final_users = UserProfile::find_many(executor, &true, None)
217        .try_collect::<Vec<_>>()
218        .await
219        .expect("Expected query to succeed");
220    assert_eq!(final_users.len(), 3);
221    let final_usernames = final_users
222        .into_iter()
223        .map(|u| u.username)
224        .collect::<HashSet<_>>();
225    assert_eq!(
226        final_usernames,
227        HashSet::from_iter(["alice".into(), "bob".into(), "charlie".into()])
228    );
229}