1
pub mod user {
2

            
3
    use crate::error::ServerError;
4
    use crate::user::User;
5
    use finance::tag::Tag;
6
    use sqlx::types::Uuid;
7

            
8
    /// Which join table to scope `list_tag_names` / `list_tag_values_for` to.
9
    #[derive(Debug, Clone, Copy)]
10
    pub enum TagScope {
11
        Transaction,
12
        Account,
13
        Split,
14
    }
15

            
16
    impl User {
17
128
        pub async fn create_tag(
18
128
            &self,
19
128
            name: String,
20
128
            value: String,
21
128
            description: Option<String>,
22
128
        ) -> Result<Uuid, ServerError> {
23
41
            if name.trim().is_empty() || value.trim().is_empty() {
24
                return Err(ServerError::Creation);
25
41
            }
26
41
            let mut conn = self.get_connection().await.map_err(|err| {
27
3
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
28
3
                ServerError::DB(err)
29
3
            })?;
30

            
31
38
            Tag {
32
38
                id: Uuid::new_v4(),
33
38
                tag_name: name,
34
38
                tag_value: value,
35
38
                description,
36
38
            }
37
38
            .commit(&mut *conn)
38
38
            .await
39
38
            .map_err(|err| {
40
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
41
                ServerError::Finance(err)
42
            })
43
41
        }
44

            
45
32
        pub async fn list_tags(&self) -> Result<Vec<Tag>, ServerError> {
46
3
            let mut conn = self.get_connection().await?;
47

            
48
2
            let tags = sqlx::query_file_as!(Tag, "sql/select/tags/all.sql")
49
2
                .fetch_all(&mut *conn)
50
2
                .await
51
2
                .map_err(|err| {
52
                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
53
                    ServerError::DB(crate::db::DBError::Sqlx(err))
54
                })?;
55

            
56
2
            Ok(tags)
57
3
        }
58

            
59
9
        pub async fn get_tag(&self, id: Uuid) -> Result<Tag, ServerError> {
60
9
            let mut conn = self.get_connection().await?;
61

            
62
9
            let tag = sqlx::query_file_as!(Tag, "sql/select/tags/by_id.sql", &id)
63
9
                .fetch_one(&mut *conn)
64
9
                .await
65
9
                .map_err(|err| {
66
2
                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
67
2
                    ServerError::DB(crate::db::DBError::Sqlx(err))
68
2
                })?;
69

            
70
7
            Ok(tag)
71
9
        }
72

            
73
63
        pub async fn update_tag(
74
63
            &self,
75
63
            id: Uuid,
76
63
            name: String,
77
63
            value: String,
78
63
            description: Option<String>,
79
63
        ) -> Result<(), ServerError> {
80
5
            let mut conn = self.get_connection().await?;
81

            
82
3
            sqlx::query_file!(
83
                "sql/update/tags/update.sql",
84
                &id,
85
                &name,
86
                &value,
87
3
                description.as_deref()
88
            )
89
3
            .execute(&mut *conn)
90
3
            .await
91
3
            .map_err(|err| {
92
1
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
93
1
                ServerError::DB(crate::db::DBError::Sqlx(err))
94
1
            })?;
95

            
96
2
            Ok(())
97
5
        }
98

            
99
        pub async fn get_transaction_tags(&self, tx_id: Uuid) -> Result<Vec<Tag>, ServerError> {
100
            let mut conn = self.get_connection().await?;
101

            
102
            let tags = sqlx::query_file_as!(Tag, "sql/select/tags/by_transaction.sql", &tx_id)
103
                .fetch_all(&mut *conn)
104
                .await
105
                .map_err(|err| {
106
                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
107
                    ServerError::DB(crate::db::DBError::Sqlx(err))
108
                })?;
109

            
110
            Ok(tags)
111
        }
112

            
113
37
        pub async fn create_transaction_tag(
114
37
            &self,
115
37
            tx_id: Uuid,
116
37
            name: String,
117
37
            value: String,
118
37
            description: Option<String>,
119
37
        ) -> Result<Uuid, ServerError> {
120
8
            let tag_id = self.create_tag(name, value, description).await?;
121
7
            let mut conn = self.get_connection().await?;
122
7
            sqlx::query_file!(
123
                "sql/insert/transaction_tags/transaction_tag.sql",
124
                &tx_id,
125
                &tag_id
126
            )
127
7
            .execute(&mut *conn)
128
7
            .await
129
7
            .map_err(|err| {
130
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
131
                ServerError::DB(crate::db::DBError::Sqlx(err))
132
            })?;
133
7
            Ok(tag_id)
134
8
        }
135

            
136
        /// Cascade-delete a tag: detach from every join table, then drop the
137
        /// row from `tags`. Used by user-initiated tag deletion in the UI;
138
        /// see `detach_*_tag` + `cleanup_orphan_tag` for the
139
        /// "remove from one entity, drop only if no longer referenced"
140
        /// pattern.
141
32
        pub async fn delete_tag(&self, id: Uuid) -> Result<(), ServerError> {
142
            use sqlx::Connection;
143
3
            let mut conn = self.get_connection().await?;
144
2
            let mut tx = conn.begin().await.map_err(|err| {
145
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
146
                ServerError::DB(crate::db::DBError::Sqlx(err))
147
            })?;
148
20
            for sql in [
149
2
                "DELETE FROM transaction_tags WHERE tag_id = $1",
150
2
                "DELETE FROM split_tags WHERE tag_id = $1",
151
2
                "DELETE FROM account_tags WHERE tag_id = $1",
152
2
                "DELETE FROM budget_tags WHERE tag_id = $1",
153
2
                "DELETE FROM commodity_tags WHERE tag_id = $1",
154
2
                "DELETE FROM price_tags WHERE tag_id = $1",
155
2
                "DELETE FROM book_tags WHERE tag_id = $1",
156
2
                "DELETE FROM artifact_tags WHERE tag_id = $1",
157
2
                "DELETE FROM tag_tags WHERE tagged_tag_id = $1 OR tagging_tag_id = $1",
158
2
                "DELETE FROM tags WHERE id = $1",
159
2
            ] {
160
20
                sqlx::query(sql)
161
20
                    .bind(id)
162
20
                    .execute(&mut *tx)
163
20
                    .await
164
20
                    .map_err(|err| {
165
                        log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
166
                        ServerError::DB(crate::db::DBError::Sqlx(err))
167
                    })?;
168
            }
169
2
            tx.commit().await.map_err(|err| {
170
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
171
                ServerError::DB(crate::db::DBError::Sqlx(err))
172
            })
173
3
        }
174

            
175
        pub async fn detach_transaction_tag(
176
            &self,
177
            tx_id: Uuid,
178
            tag_id: Uuid,
179
        ) -> Result<(), ServerError> {
180
            let mut conn = self.get_connection().await?;
181
            sqlx::query_file!("sql/delete/transaction_tags/by_pair.sql", &tx_id, &tag_id)
182
                .execute(&mut *conn)
183
                .await
184
                .map_err(|err| {
185
                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
186
                    ServerError::DB(crate::db::DBError::Sqlx(err))
187
                })?;
188
            Ok(())
189
        }
190

            
191
        pub async fn detach_split_tag(
192
            &self,
193
            split_id: Uuid,
194
            tag_id: Uuid,
195
        ) -> Result<(), ServerError> {
196
            let mut conn = self.get_connection().await?;
197
            sqlx::query_file!("sql/delete/split_tags/by_pair.sql", &split_id, &tag_id)
198
                .execute(&mut *conn)
199
                .await
200
                .map_err(|err| {
201
                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
202
                    ServerError::DB(crate::db::DBError::Sqlx(err))
203
                })?;
204
            Ok(())
205
        }
206

            
207
        pub async fn detach_account_tag(
208
            &self,
209
            account_id: Uuid,
210
            tag_id: Uuid,
211
        ) -> Result<(), ServerError> {
212
            let mut conn = self.get_connection().await?;
213
            sqlx::query_file!("sql/delete/account_tags/by_pair.sql", &account_id, &tag_id)
214
                .execute(&mut *conn)
215
                .await
216
                .map_err(|err| {
217
                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
218
                    ServerError::DB(crate::db::DBError::Sqlx(err))
219
                })?;
220
            Ok(())
221
        }
222

            
223
        pub async fn detach_commodity_tag(
224
            &self,
225
            commodity_id: Uuid,
226
            tag_id: Uuid,
227
        ) -> Result<(), ServerError> {
228
            let mut conn = self.get_connection().await?;
229
            sqlx::query_file!(
230
                "sql/delete/commodity_tags/by_pair.sql",
231
                &commodity_id,
232
                &tag_id
233
            )
234
            .execute(&mut *conn)
235
            .await
236
            .map_err(|err| {
237
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
238
                ServerError::DB(crate::db::DBError::Sqlx(err))
239
            })?;
240
            Ok(())
241
        }
242

            
243
        /// If `tag_id` is no longer referenced by any join table, delete the
244
        /// canonical row. Idempotent.
245
        pub async fn cleanup_orphan_tag(&self, tag_id: Uuid) -> Result<(), ServerError> {
246
            let mut conn = self.get_connection().await?;
247
            let row = sqlx::query_file!("sql/check/tags/is_orphaned.sql", &tag_id)
248
                .fetch_one(&mut *conn)
249
                .await
250
                .map_err(|err| {
251
                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
252
                    ServerError::DB(crate::db::DBError::Sqlx(err))
253
                })?;
254
            if row.is_orphaned.unwrap_or(false) {
255
                sqlx::query_file!("sql/delete/tags/by_id.sql", &tag_id)
256
                    .execute(&mut *conn)
257
                    .await
258
                    .map_err(|err| {
259
                        log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
260
                        ServerError::DB(crate::db::DBError::Sqlx(err))
261
                    })?;
262
            }
263
            Ok(())
264
        }
265

            
266
6
        pub async fn list_tag_names(&self, scope: TagScope) -> Result<Vec<String>, ServerError> {
267
6
            let mut conn = self.get_connection().await?;
268
6
            let result = match scope {
269
2
                TagScope::Transaction => sqlx::query_file!("sql/select/tags/transaction/names.sql")
270
2
                    .fetch_all(&mut *conn)
271
2
                    .await
272
2
                    .map(|rows| rows.into_iter().map(|r| r.tag_name).collect::<Vec<_>>()),
273
2
                TagScope::Account => sqlx::query_file!("sql/select/tags/account/names.sql")
274
2
                    .fetch_all(&mut *conn)
275
2
                    .await
276
2
                    .map(|rows| rows.into_iter().map(|r| r.tag_name).collect::<Vec<_>>()),
277
2
                TagScope::Split => sqlx::query_file!("sql/select/tags/split/names.sql")
278
2
                    .fetch_all(&mut *conn)
279
2
                    .await
280
2
                    .map(|rows| rows.into_iter().map(|r| r.tag_name).collect::<Vec<_>>()),
281
            };
282
6
            result.map_err(|err| {
283
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
284
                ServerError::DB(crate::db::DBError::Sqlx(err))
285
            })
286
6
        }
287

            
288
11
        pub async fn list_tag_values_for(
289
11
            &self,
290
11
            scope: TagScope,
291
11
            tag_name: &str,
292
11
        ) -> Result<Vec<String>, ServerError> {
293
11
            let mut conn = self.get_connection().await?;
294
11
            let result = match scope {
295
                TagScope::Transaction => {
296
4
                    sqlx::query_file!("sql/select/tags/transaction/values_by_name.sql", tag_name)
297
4
                        .fetch_all(&mut *conn)
298
4
                        .await
299
4
                        .map(|rows| rows.into_iter().map(|r| r.tag_value).collect::<Vec<_>>())
300
                }
301
                TagScope::Account => {
302
3
                    sqlx::query_file!("sql/select/tags/account/values_by_name.sql", tag_name)
303
3
                        .fetch_all(&mut *conn)
304
3
                        .await
305
3
                        .map(|rows| rows.into_iter().map(|r| r.tag_value).collect::<Vec<_>>())
306
                }
307
                TagScope::Split => {
308
4
                    sqlx::query_file!("sql/select/tags/split/values_by_name.sql", tag_name)
309
4
                        .fetch_all(&mut *conn)
310
4
                        .await
311
4
                        .map(|rows| rows.into_iter().map(|r| r.tag_value).collect::<Vec<_>>())
312
                }
313
            };
314
11
            result.map_err(|err| {
315
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
316
                ServerError::DB(crate::db::DBError::Sqlx(err))
317
            })
318
11
        }
319

            
320
2
        pub async fn list_transaction_tag_names(&self) -> Result<Vec<String>, ServerError> {
321
2
            self.list_tag_names(TagScope::Transaction).await
322
2
        }
323

            
324
4
        pub async fn list_transaction_tag_values(
325
4
            &self,
326
4
            tag_name: &str,
327
4
        ) -> Result<Vec<String>, ServerError> {
328
4
            self.list_tag_values_for(TagScope::Transaction, tag_name)
329
4
                .await
330
4
        }
331

            
332
22
        pub async fn create_split_tag(
333
22
            &self,
334
22
            split_id: Uuid,
335
22
            name: String,
336
22
            value: String,
337
22
            description: Option<String>,
338
22
        ) -> Result<Uuid, ServerError> {
339
22
            let tag_id = self.create_tag(name, value, description).await?;
340
22
            let mut conn = self.get_connection().await?;
341
22
            sqlx::query_file!("sql/insert/split_tags/split_tag.sql", &split_id, &tag_id)
342
22
                .execute(&mut *conn)
343
22
                .await
344
22
                .map_err(|err| {
345
                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
346
                    ServerError::DB(crate::db::DBError::Sqlx(err))
347
                })?;
348
22
            Ok(tag_id)
349
22
        }
350

            
351
        /// Upsert-style set: replace any existing (split, tag_name) link with
352
        /// the (tag_name, tag_value) pair from `t`. Mirrors `set_account_tag`
353
        /// — script-friendly idempotent set semantics.
354
180
        pub async fn set_split_tag(&self, split_id: Uuid, t: &Tag) -> Result<(), ServerError> {
355
150
            if t.tag_name.trim().is_empty() || t.tag_value.trim().is_empty() {
356
                return Err(ServerError::Creation);
357
150
            }
358
150
            let mut conn = self.get_connection().await.map_err(|err| {
359
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
360
                ServerError::DB(err)
361
            })?;
362

            
363
150
            sqlx::query_file!(
364
                "sql/set/splits/tag.sql",
365
                &split_id,
366
                &t.tag_name,
367
                &t.tag_value,
368
                t.description
369
            )
370
150
            .execute(&mut *conn)
371
150
            .await
372
150
            .map_err(|err| {
373
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
374
                ServerError::DB(crate::db::DBError::Sqlx(err))
375
            })?;
376

            
377
150
            Ok(())
378
150
        }
379

            
380
        /// Companion to `set_split_tag` for transactions.
381
30
        pub async fn set_transaction_tag(&self, tx_id: Uuid, t: &Tag) -> Result<(), ServerError> {
382
25
            if t.tag_name.trim().is_empty() || t.tag_value.trim().is_empty() {
383
                return Err(ServerError::Creation);
384
25
            }
385
25
            let mut conn = self.get_connection().await.map_err(|err| {
386
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
387
                ServerError::DB(err)
388
            })?;
389

            
390
25
            sqlx::query_file!(
391
                "sql/set/transactions/tag.sql",
392
                &tx_id,
393
                &t.tag_name,
394
                &t.tag_value,
395
                t.description
396
            )
397
25
            .execute(&mut *conn)
398
25
            .await
399
25
            .map_err(|err| {
400
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
401
                ServerError::DB(crate::db::DBError::Sqlx(err))
402
            })?;
403

            
404
25
            Ok(())
405
25
        }
406

            
407
        /// Read all tags attached to a single split. Parallel to
408
        /// `get_transaction_tags`; the script-side `get-split-tag` native
409
        /// looks up by name in the returned vec.
410
180
        pub async fn get_split_tags(&self, split_id: Uuid) -> Result<Vec<Tag>, ServerError> {
411
150
            let mut conn = self.get_connection().await?;
412

            
413
150
            let tags = sqlx::query_file_as!(Tag, "sql/select/tags/by_split.sql", &split_id)
414
150
                .fetch_all(&mut *conn)
415
150
                .await
416
150
                .map_err(|err| {
417
                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
418
                    ServerError::DB(crate::db::DBError::Sqlx(err))
419
                })?;
420

            
421
150
            Ok(tags)
422
150
        }
423

            
424
1
        pub async fn create_account_tag(
425
1
            &self,
426
1
            account_id: Uuid,
427
1
            name: String,
428
1
            value: String,
429
1
            description: Option<String>,
430
1
        ) -> Result<Uuid, ServerError> {
431
1
            let tag_id = self.create_tag(name, value, description).await?;
432
1
            let mut conn = self.get_connection().await?;
433
1
            sqlx::query_file!(
434
                "sql/insert/account_tags/account_tag.sql",
435
                &account_id,
436
                &tag_id
437
            )
438
1
            .execute(&mut *conn)
439
1
            .await
440
1
            .map_err(|err| {
441
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
442
                ServerError::DB(crate::db::DBError::Sqlx(err))
443
            })?;
444
1
            Ok(tag_id)
445
1
        }
446

            
447
2
        pub async fn list_account_tag_names(&self) -> Result<Vec<String>, ServerError> {
448
2
            self.list_tag_names(TagScope::Account).await
449
2
        }
450

            
451
3
        pub async fn list_account_tag_values(
452
3
            &self,
453
3
            tag_name: &str,
454
3
        ) -> Result<Vec<String>, ServerError> {
455
3
            self.list_tag_values_for(TagScope::Account, tag_name).await
456
3
        }
457

            
458
2
        pub async fn list_split_tag_names(&self) -> Result<Vec<String>, ServerError> {
459
2
            self.list_tag_names(TagScope::Split).await
460
2
        }
461

            
462
4
        pub async fn list_split_tag_values(
463
4
            &self,
464
4
            tag_name: &str,
465
4
        ) -> Result<Vec<String>, ServerError> {
466
4
            self.list_tag_values_for(TagScope::Split, tag_name).await
467
4
        }
468
    }
469

            
470
    #[cfg(test)]
471
    mod tag_tests {
472
        use super::*;
473
        use crate::db::DB_POOL;
474
        #[cfg(feature = "testlog")]
475
        use env_logger;
476
        use finance::{split::Split, transaction::Transaction};
477
        #[cfg(feature = "testlog")]
478
        use log;
479
        use sqlx::PgPool;
480
        use sqlx::types::chrono;
481

            
482
        use supp_macro::local_db_sqlx_test;
483
        use tokio::sync::OnceCell;
484

            
485
        /// Context for keeping environment intact
486
        static CONTEXT: OnceCell<()> = OnceCell::const_new();
487
        static USER: OnceCell<User> = OnceCell::const_new();
488

            
489
26
        async fn setup() {
490
26
            CONTEXT
491
26
                .get_or_init(|| async {
492
                    #[cfg(feature = "testlog")]
493
1
                    let _ = env_logger::builder()
494
1
                        .is_test(true)
495
1
                        .filter_level(log::LevelFilter::Trace)
496
1
                        .try_init();
497
2
                })
498
26
                .await;
499
26
            USER.get_or_init(|| async { User { id: Uuid::new_v4() } })
500
26
                .await;
501
26
        }
502

            
503
        #[local_db_sqlx_test]
504
        async fn test_tag_creation(pool: PgPool) -> Result<(), anyhow::Error> {
505
            let user = USER.get().unwrap();
506
            user.commit()
507
                .await
508
                .expect("Failed to commit user to database");
509

            
510
            let id = user
511
                .create_tag("testtag".to_string(), "testval".to_string(), None)
512
                .await?;
513

            
514
            let mut conn = user.get_connection().await?;
515
            let res = sqlx::query_file!("testdata/query_tag_by_id.sql", &id)
516
                .fetch_one(&mut *conn)
517
                .await?;
518

            
519
            assert_eq!(res.tag_name, "testtag".to_string());
520
            assert_eq!(res.tag_value, "testval".to_string());
521
            assert_eq!(res.description, None);
522
        }
523

            
524
        #[local_db_sqlx_test]
525
        async fn test_tag_creation_with_description(pool: PgPool) -> Result<(), anyhow::Error> {
526
            let user = USER.get().unwrap();
527
            user.commit()
528
                .await
529
                .expect("Failed to commit user to database");
530

            
531
            let id = user
532
                .create_tag(
533
                    "categorytag".to_string(),
534
                    "category1".to_string(),
535
                    Some("Test description".to_string()),
536
                )
537
                .await?;
538

            
539
            let tag = user.get_tag(id).await?;
540

            
541
            assert_eq!(tag.tag_name, "categorytag");
542
            assert_eq!(tag.tag_value, "category1");
543
            assert_eq!(tag.description, Some("Test description".to_string()));
544
        }
545

            
546
        #[local_db_sqlx_test]
547
        async fn test_list_tags(pool: PgPool) -> Result<(), anyhow::Error> {
548
            let user = USER.get().unwrap();
549
            user.commit()
550
                .await
551
                .expect("Failed to commit user to database");
552

            
553
            let id1 = user
554
                .create_tag("tag1".to_string(), "value1".to_string(), None)
555
                .await?;
556

            
557
            let id2 = user
558
                .create_tag(
559
                    "tag2".to_string(),
560
                    "value2".to_string(),
561
                    Some("desc".to_string()),
562
                )
563
                .await?;
564

            
565
            let tags = user.list_tags().await?;
566

            
567
            assert!(tags.len() >= 2);
568
1
            assert!(tags.iter().any(|t| t.id == id1));
569
2
            assert!(tags.iter().any(|t| t.id == id2));
570

            
571
1
            let tag1 = tags.iter().find(|t| t.id == id1).unwrap();
572
            assert_eq!(tag1.tag_name, "tag1");
573
            assert_eq!(tag1.tag_value, "value1");
574
            assert_eq!(tag1.description, None);
575

            
576
2
            let tag2 = tags.iter().find(|t| t.id == id2).unwrap();
577
            assert_eq!(tag2.tag_name, "tag2");
578
            assert_eq!(tag2.tag_value, "value2");
579
            assert_eq!(tag2.description, Some("desc".to_string()));
580
        }
581

            
582
        #[local_db_sqlx_test]
583
        async fn test_get_tag(pool: PgPool) -> Result<(), anyhow::Error> {
584
            let user = USER.get().unwrap();
585
            user.commit()
586
                .await
587
                .expect("Failed to commit user to database");
588

            
589
            let id = user
590
                .create_tag(
591
                    "gettag".to_string(),
592
                    "getvalue".to_string(),
593
                    Some("Get description".to_string()),
594
                )
595
                .await?;
596

            
597
            let tag = user.get_tag(id).await?;
598

            
599
            assert_eq!(tag.id, id);
600
            assert_eq!(tag.tag_name, "gettag");
601
            assert_eq!(tag.tag_value, "getvalue");
602
            assert_eq!(tag.description, Some("Get description".to_string()));
603
        }
604

            
605
        #[local_db_sqlx_test]
606
        async fn test_get_nonexistent_tag(pool: PgPool) -> Result<(), anyhow::Error> {
607
            let user = USER.get().unwrap();
608
            user.commit()
609
                .await
610
                .expect("Failed to commit user to database");
611

            
612
            let nonexistent_id = Uuid::new_v4();
613
            let result = user.get_tag(nonexistent_id).await;
614

            
615
            assert!(result.is_err());
616
        }
617

            
618
        #[local_db_sqlx_test]
619
        async fn test_update_tag(pool: PgPool) -> Result<(), anyhow::Error> {
620
            let user = USER.get().unwrap();
621
            user.commit()
622
                .await
623
                .expect("Failed to commit user to database");
624

            
625
            let id = user
626
                .create_tag("oldname".to_string(), "oldvalue".to_string(), None)
627
                .await?;
628

            
629
            user.update_tag(
630
                id,
631
                "newname".to_string(),
632
                "newvalue".to_string(),
633
                Some("Updated description".to_string()),
634
            )
635
            .await?;
636

            
637
            let tag = user.get_tag(id).await?;
638

            
639
            assert_eq!(tag.id, id);
640
            assert_eq!(tag.tag_name, "newname");
641
            assert_eq!(tag.tag_value, "newvalue");
642
            assert_eq!(tag.description, Some("Updated description".to_string()));
643
        }
644

            
645
        #[local_db_sqlx_test]
646
        async fn test_update_tag_remove_description(pool: PgPool) {
647
            let user = USER.get().unwrap();
648
            user.commit()
649
                .await
650
                .expect("Failed to commit user to database");
651

            
652
            let id = user
653
                .create_tag(
654
                    "tagname".to_string(),
655
                    "tagvalue".to_string(),
656
                    Some("Initial description".to_string()),
657
                )
658
                .await?;
659

            
660
            user.update_tag(id, "tagname".to_string(), "tagvalue".to_string(), None)
661
                .await?;
662

            
663
            let tag = user.get_tag(id).await?;
664

            
665
            assert_eq!(tag.description, None);
666
        }
667

            
668
        #[local_db_sqlx_test]
669
        async fn test_delete_tag(pool: PgPool) -> Result<(), anyhow::Error> {
670
            let user = USER.get().unwrap();
671
            user.commit()
672
                .await
673
                .expect("Failed to commit user to database");
674

            
675
            let id = user
676
                .create_tag("deletetag".to_string(), "deletevalue".to_string(), None)
677
                .await?;
678

            
679
            let tag = user.get_tag(id).await;
680
            assert!(tag.is_ok());
681

            
682
            user.delete_tag(id).await?;
683

            
684
            let result = user.get_tag(id).await;
685
            assert!(result.is_err());
686
        }
687

            
688
        #[local_db_sqlx_test]
689
        async fn test_delete_nonexistent_tag(pool: PgPool) -> Result<(), anyhow::Error> {
690
            let user = USER.get().unwrap();
691
            user.commit()
692
                .await
693
                .expect("Failed to commit user to database");
694

            
695
            let nonexistent_id = Uuid::new_v4();
696
            let result = user.delete_tag(nonexistent_id).await;
697

            
698
            assert!(result.is_ok());
699
        }
700

            
701
        #[local_db_sqlx_test]
702
        async fn test_list_tags_empty(pool: PgPool) -> Result<(), anyhow::Error> {
703
            let user = USER.get().unwrap();
704
            user.commit()
705
                .await
706
                .expect("Failed to commit user to database");
707

            
708
            let tags = user.list_tags().await?;
709

            
710
            assert!(
711
                tags.is_empty()
712
                    || tags.iter().all(|t| t.tag_name == "name"
713
                        || t.tag_name == "note"
714
                        || t.tag_name == "symbol")
715
            );
716
        }
717

            
718
        #[local_db_sqlx_test]
719
        async fn test_list_transaction_tag_names_empty(pool: PgPool) -> Result<(), anyhow::Error> {
720
            let user = USER.get().unwrap();
721
            user.commit()
722
                .await
723
                .expect("Failed to commit user to database");
724

            
725
            let names = user.list_transaction_tag_names().await?;
726

            
727
            assert!(names.is_empty());
728
        }
729

            
730
        #[local_db_sqlx_test]
731
        async fn test_list_transaction_tag_names_with_data(
732
            pool: PgPool,
733
        ) -> Result<(), anyhow::Error> {
734
            let user = USER.get().unwrap();
735
            user.commit()
736
                .await
737
                .expect("Failed to commit user to database");
738

            
739
            let commodity_id = user
740
                .create_commodity("USD".to_string(), "US Dollar".to_string())
741
                .await?
742
                .id;
743
            let acc1 = user.create_account("test_acc1", None).await?.id;
744
            let acc2 = user.create_account("test_acc2", None).await?.id;
745

            
746
            let tx = Transaction {
747
                id: sqlx::types::Uuid::new_v4(),
748
                post_date: chrono::Utc::now(),
749
                enter_date: chrono::Utc::now(),
750
            };
751

            
752
            let mut conn = user.get_connection().await?;
753
            let mut ticket = tx.enter(&mut *conn).await?;
754
            let split1 = Split {
755
                id: sqlx::types::Uuid::new_v4(),
756
                account_id: acc1,
757
                tx_id: tx.id,
758
                value_num: 100,
759
                value_denom: 1,
760
                commodity_id,
761
                reconcile_state: None,
762
                reconcile_date: None,
763
                lot_id: None,
764
            };
765
            let split2 = Split {
766
                id: sqlx::types::Uuid::new_v4(),
767
                account_id: acc2,
768
                tx_id: tx.id,
769
                value_num: -100,
770
                value_denom: 1,
771
                commodity_id,
772
                reconcile_state: None,
773
                reconcile_date: None,
774
                lot_id: None,
775
            };
776
            ticket.add_splits(&[&split1, &split2]).await?;
777
            ticket.commit().await?;
778

            
779
            user.create_transaction_tag(tx.id, "category".to_string(), "food".to_string(), None)
780
                .await?;
781
            user.create_transaction_tag(tx.id, "priority".to_string(), "high".to_string(), None)
782
                .await?;
783

            
784
            let names = user.list_transaction_tag_names().await?;
785

            
786
            assert_eq!(names.len(), 2);
787
            assert!(names.contains(&"category".to_string()));
788
            assert!(names.contains(&"priority".to_string()));
789
        }
790

            
791
        #[local_db_sqlx_test]
792
        async fn test_list_transaction_tag_values_empty(pool: PgPool) -> Result<(), anyhow::Error> {
793
            let user = USER.get().unwrap();
794
            user.commit()
795
                .await
796
                .expect("Failed to commit user to database");
797

            
798
            let values = user.list_transaction_tag_values("category").await?;
799

            
800
            assert!(values.is_empty());
801
        }
802

            
803
        #[local_db_sqlx_test]
804
        async fn test_list_transaction_tag_values_with_data(
805
            pool: PgPool,
806
        ) -> Result<(), anyhow::Error> {
807
            let user = USER.get().unwrap();
808
            user.commit()
809
                .await
810
                .expect("Failed to commit user to database");
811

            
812
            let commodity_id = user
813
                .create_commodity("USD".to_string(), "US Dollar".to_string())
814
                .await?
815
                .id;
816
            let acc1 = user.create_account("test_acc1", None).await?.id;
817
            let acc2 = user.create_account("test_acc2", None).await?.id;
818

            
819
            let tx1 = Transaction {
820
                id: sqlx::types::Uuid::new_v4(),
821
                post_date: chrono::Utc::now(),
822
                enter_date: chrono::Utc::now(),
823
            };
824
            let tx2 = Transaction {
825
                id: sqlx::types::Uuid::new_v4(),
826
                post_date: chrono::Utc::now(),
827
                enter_date: chrono::Utc::now(),
828
            };
829
            {
830
                let mut conn = user.get_connection().await?;
831
                let mut ticket1 = tx1.enter(&mut *conn).await?;
832
                let split1a = Split {
833
                    id: sqlx::types::Uuid::new_v4(),
834
                    account_id: acc1,
835
                    tx_id: tx1.id,
836
                    value_num: 100,
837
                    value_denom: 1,
838
                    commodity_id,
839
                    reconcile_state: None,
840
                    reconcile_date: None,
841
                    lot_id: None,
842
                };
843
                let split1b = Split {
844
                    id: sqlx::types::Uuid::new_v4(),
845
                    account_id: acc2,
846
                    tx_id: tx1.id,
847
                    value_num: -100,
848
                    value_denom: 1,
849
                    commodity_id,
850
                    reconcile_state: None,
851
                    reconcile_date: None,
852
                    lot_id: None,
853
                };
854
                ticket1.add_splits(&[&split1a, &split1b]).await?;
855
                ticket1.commit().await?;
856
            }
857
            {
858
                let mut conn = user.get_connection().await?;
859
                let mut ticket2 = tx2.enter(&mut *conn).await?;
860
                let split2a = Split {
861
                    id: sqlx::types::Uuid::new_v4(),
862
                    account_id: acc1,
863
                    tx_id: tx2.id,
864
                    value_num: 200,
865
                    value_denom: 1,
866
                    commodity_id,
867
                    reconcile_state: None,
868
                    reconcile_date: None,
869
                    lot_id: None,
870
                };
871
                let split2b = Split {
872
                    id: sqlx::types::Uuid::new_v4(),
873
                    account_id: acc2,
874
                    tx_id: tx2.id,
875
                    value_num: -200,
876
                    value_denom: 1,
877
                    commodity_id,
878
                    reconcile_state: None,
879
                    reconcile_date: None,
880
                    lot_id: None,
881
                };
882
                ticket2.add_splits(&[&split2a, &split2b]).await?;
883
                ticket2.commit().await?;
884
            }
885
            user.create_transaction_tag(tx1.id, "category".to_string(), "food".to_string(), None)
886
                .await?;
887
            user.create_transaction_tag(
888
                tx2.id,
889
                "category".to_string(),
890
                "transport".to_string(),
891
                None,
892
            )
893
            .await?;
894
            user.create_transaction_tag(tx1.id, "priority".to_string(), "high".to_string(), None)
895
                .await?;
896

            
897
            let category_values = user.list_transaction_tag_values("category").await?;
898

            
899
            assert_eq!(category_values.len(), 2);
900
            assert!(category_values.contains(&"food".to_string()));
901
            assert!(category_values.contains(&"transport".to_string()));
902

            
903
            let priority_values = user.list_transaction_tag_values("priority").await?;
904

            
905
            assert_eq!(priority_values.len(), 1);
906
            assert!(priority_values.contains(&"high".to_string()));
907
        }
908

            
909
        #[local_db_sqlx_test]
910
        async fn test_list_transaction_tag_values_nonexistent_name(
911
            pool: PgPool,
912
        ) -> Result<(), anyhow::Error> {
913
            let user = USER.get().unwrap();
914
            user.commit()
915
                .await
916
                .expect("Failed to commit user to database");
917

            
918
            let commodity_id = user
919
                .create_commodity("USD".to_string(), "US Dollar".to_string())
920
                .await?
921
                .id;
922
            let acc1 = user.create_account("test_acc1", None).await?.id;
923
            let acc2 = user.create_account("test_acc2", None).await?.id;
924

            
925
            let tx = Transaction {
926
                id: sqlx::types::Uuid::new_v4(),
927
                post_date: chrono::Utc::now(),
928
                enter_date: chrono::Utc::now(),
929
            };
930

            
931
            let mut conn = user.get_connection().await?;
932
            let mut ticket = tx.enter(&mut *conn).await?;
933
            let split1 = Split {
934
                id: sqlx::types::Uuid::new_v4(),
935
                account_id: acc1,
936
                tx_id: tx.id,
937
                value_num: 100,
938
                value_denom: 1,
939
                commodity_id,
940
                reconcile_state: None,
941
                reconcile_date: None,
942
                lot_id: None,
943
            };
944
            let split2 = Split {
945
                id: sqlx::types::Uuid::new_v4(),
946
                account_id: acc2,
947
                tx_id: tx.id,
948
                value_num: -100,
949
                value_denom: 1,
950
                commodity_id,
951
                reconcile_state: None,
952
                reconcile_date: None,
953
                lot_id: None,
954
            };
955
            ticket.add_splits(&[&split1, &split2]).await?;
956
            ticket.commit().await?;
957

            
958
            user.create_transaction_tag(tx.id, "category".to_string(), "food".to_string(), None)
959
                .await?;
960

            
961
            let values = user.list_transaction_tag_values("nonexistent").await?;
962

            
963
            assert!(values.is_empty());
964
        }
965

            
966
        #[local_db_sqlx_test]
967
        async fn test_create_split_tag(pool: PgPool) -> Result<(), anyhow::Error> {
968
            let user = USER.get().unwrap();
969
            user.commit()
970
                .await
971
                .expect("Failed to commit user to database");
972

            
973
            let commodity_id = user
974
                .create_commodity("USD".to_string(), "US Dollar".to_string())
975
                .await?
976
                .id;
977
            let acc1 = user.create_account("test_acc1", None).await?.id;
978
            let acc2 = user.create_account("test_acc2", None).await?.id;
979

            
980
            let tx = Transaction {
981
                id: sqlx::types::Uuid::new_v4(),
982
                post_date: chrono::Utc::now(),
983
                enter_date: chrono::Utc::now(),
984
            };
985

            
986
            let mut conn = user.get_connection().await?;
987
            let mut ticket = tx.enter(&mut *conn).await?;
988
            let split1 = Split {
989
                id: sqlx::types::Uuid::new_v4(),
990
                account_id: acc1,
991
                tx_id: tx.id,
992
                value_num: 100,
993
                value_denom: 1,
994
                commodity_id,
995
                reconcile_state: None,
996
                reconcile_date: None,
997
                lot_id: None,
998
            };
999
            let split2 = Split {
                id: sqlx::types::Uuid::new_v4(),
                account_id: acc2,
                tx_id: tx.id,
                value_num: -100,
                value_denom: 1,
                commodity_id,
                reconcile_state: None,
                reconcile_date: None,
                lot_id: None,
            };
            ticket.add_splits(&[&split1, &split2]).await?;
            ticket.commit().await?;
            let tag_id = user
                .create_split_tag(
                    split1.id,
                    "project".to_string(),
                    "nomisync".to_string(),
                    Some("Split tag for project tracking".to_string()),
                )
                .await?;
            let mut conn = user.get_connection().await?;
            let res = sqlx::query!(
                "SELECT tag_id FROM split_tags WHERE split_id = $1",
                &split1.id
            )
            .fetch_one(&mut *conn)
            .await?;
            assert_eq!(res.tag_id, tag_id);
            let tag = user.get_tag(tag_id).await?;
            assert_eq!(tag.tag_name, "project");
            assert_eq!(tag.tag_value, "nomisync");
            assert_eq!(
                tag.description,
                Some("Split tag for project tracking".to_string())
            );
        }
        #[local_db_sqlx_test]
        async fn test_list_split_tag_names_empty(pool: PgPool) -> Result<(), anyhow::Error> {
            let user = USER.get().unwrap();
            user.commit()
                .await
                .expect("Failed to commit user to database");
            let names = user.list_split_tag_names().await?;
            assert!(names.is_empty());
        }
        #[local_db_sqlx_test]
        async fn test_list_split_tag_names_with_data(pool: PgPool) -> Result<(), anyhow::Error> {
            let user = USER.get().unwrap();
            user.commit()
                .await
                .expect("Failed to commit user to database");
            let commodity_id = user
                .create_commodity("USD".to_string(), "US Dollar".to_string())
                .await?
                .id;
            let acc1 = user.create_account("test_acc1", None).await?.id;
            let acc2 = user.create_account("test_acc2", None).await?.id;
            let tx = Transaction {
                id: sqlx::types::Uuid::new_v4(),
                post_date: chrono::Utc::now(),
                enter_date: chrono::Utc::now(),
            };
            let mut conn = user.get_connection().await?;
            let mut ticket = tx.enter(&mut *conn).await?;
            let split1 = Split {
                id: sqlx::types::Uuid::new_v4(),
                account_id: acc1,
                tx_id: tx.id,
                value_num: 100,
                value_denom: 1,
                commodity_id,
                reconcile_state: None,
                reconcile_date: None,
                lot_id: None,
            };
            let split2 = Split {
                id: sqlx::types::Uuid::new_v4(),
                account_id: acc2,
                tx_id: tx.id,
                value_num: -100,
                value_denom: 1,
                commodity_id,
                reconcile_state: None,
                reconcile_date: None,
                lot_id: None,
            };
            ticket.add_splits(&[&split1, &split2]).await?;
            ticket.commit().await?;
            user.create_split_tag(
                split1.id,
                "project".to_string(),
                "nomisync".to_string(),
                None,
            )
            .await?;
            user.create_split_tag(
                split2.id,
                "department".to_string(),
                "engineering".to_string(),
                None,
            )
            .await?;
            let names = user.list_split_tag_names().await?;
            assert_eq!(names.len(), 2);
            assert!(names.contains(&"project".to_string()));
            assert!(names.contains(&"department".to_string()));
        }
        #[local_db_sqlx_test]
        async fn test_list_split_tag_values_empty(pool: PgPool) -> Result<(), anyhow::Error> {
            let user = USER.get().unwrap();
            user.commit()
                .await
                .expect("Failed to commit user to database");
            let values = user.list_split_tag_values("project").await?;
            assert!(values.is_empty());
        }
        #[local_db_sqlx_test]
        async fn test_list_split_tag_values_with_data(pool: PgPool) -> Result<(), anyhow::Error> {
            let user = USER.get().unwrap();
            user.commit()
                .await
                .expect("Failed to commit user to database");
            let commodity_id = user
                .create_commodity("USD".to_string(), "US Dollar".to_string())
                .await?
                .id;
            let acc1 = user.create_account("test_acc1", None).await?.id;
            let acc2 = user.create_account("test_acc2", None).await?.id;
            let tx1 = Transaction {
                id: sqlx::types::Uuid::new_v4(),
                post_date: chrono::Utc::now(),
                enter_date: chrono::Utc::now(),
            };
            let tx2 = Transaction {
                id: sqlx::types::Uuid::new_v4(),
                post_date: chrono::Utc::now(),
                enter_date: chrono::Utc::now(),
            };
            let split1_id = sqlx::types::Uuid::new_v4();
            let split2_id = sqlx::types::Uuid::new_v4();
            let split3_id = sqlx::types::Uuid::new_v4();
            let split4_id = sqlx::types::Uuid::new_v4();
            {
                let mut conn = user.get_connection().await?;
                let mut ticket1 = tx1.enter(&mut *conn).await?;
                let split1a = Split {
                    id: split1_id,
                    account_id: acc1,
                    tx_id: tx1.id,
                    value_num: 100,
                    value_denom: 1,
                    commodity_id,
                    reconcile_state: None,
                    reconcile_date: None,
                    lot_id: None,
                };
                let split1b = Split {
                    id: split2_id,
                    account_id: acc2,
                    tx_id: tx1.id,
                    value_num: -100,
                    value_denom: 1,
                    commodity_id,
                    reconcile_state: None,
                    reconcile_date: None,
                    lot_id: None,
                };
                ticket1.add_splits(&[&split1a, &split1b]).await?;
                ticket1.commit().await?;
            }
            {
                let mut conn = user.get_connection().await?;
                let mut ticket2 = tx2.enter(&mut *conn).await?;
                let split2a = Split {
                    id: split3_id,
                    account_id: acc1,
                    tx_id: tx2.id,
                    value_num: 200,
                    value_denom: 1,
                    commodity_id,
                    reconcile_state: None,
                    reconcile_date: None,
                    lot_id: None,
                };
                let split2b = Split {
                    id: split4_id,
                    account_id: acc2,
                    tx_id: tx2.id,
                    value_num: -200,
                    value_denom: 1,
                    commodity_id,
                    reconcile_state: None,
                    reconcile_date: None,
                    lot_id: None,
                };
                ticket2.add_splits(&[&split2a, &split2b]).await?;
                ticket2.commit().await?;
            }
            user.create_split_tag(
                split1_id,
                "project".to_string(),
                "nomisync".to_string(),
                None,
            )
            .await?;
            user.create_split_tag(
                split3_id,
                "project".to_string(),
                "website".to_string(),
                None,
            )
            .await?;
            user.create_split_tag(
                split2_id,
                "department".to_string(),
                "engineering".to_string(),
                None,
            )
            .await?;
            let project_values = user.list_split_tag_values("project").await?;
            assert_eq!(project_values.len(), 2);
            assert!(project_values.contains(&"nomisync".to_string()));
            assert!(project_values.contains(&"website".to_string()));
            let department_values = user.list_split_tag_values("department").await?;
            assert_eq!(department_values.len(), 1);
            assert!(department_values.contains(&"engineering".to_string()));
        }
        #[local_db_sqlx_test]
        async fn test_list_split_tag_values_nonexistent_name(
            pool: PgPool,
        ) -> Result<(), anyhow::Error> {
            let user = USER.get().unwrap();
            user.commit()
                .await
                .expect("Failed to commit user to database");
            let commodity_id = user
                .create_commodity("USD".to_string(), "US Dollar".to_string())
                .await?
                .id;
            let acc1 = user.create_account("test_acc1", None).await?.id;
            let acc2 = user.create_account("test_acc2", None).await?.id;
            let tx = Transaction {
                id: sqlx::types::Uuid::new_v4(),
                post_date: chrono::Utc::now(),
                enter_date: chrono::Utc::now(),
            };
            let mut conn = user.get_connection().await?;
            let mut ticket = tx.enter(&mut *conn).await?;
            let split1 = Split {
                id: sqlx::types::Uuid::new_v4(),
                account_id: acc1,
                tx_id: tx.id,
                value_num: 100,
                value_denom: 1,
                commodity_id,
                reconcile_state: None,
                reconcile_date: None,
                lot_id: None,
            };
            let split2 = Split {
                id: sqlx::types::Uuid::new_v4(),
                account_id: acc2,
                tx_id: tx.id,
                value_num: -100,
                value_denom: 1,
                commodity_id,
                reconcile_state: None,
                reconcile_date: None,
                lot_id: None,
            };
            ticket.add_splits(&[&split1, &split2]).await?;
            ticket.commit().await?;
            user.create_split_tag(
                split1.id,
                "project".to_string(),
                "nomisync".to_string(),
                None,
            )
            .await?;
            let values = user.list_split_tag_values("nonexistent").await?;
            assert!(values.is_empty());
        }
        #[local_db_sqlx_test]
        async fn test_create_account_tag(pool: PgPool) -> Result<(), anyhow::Error> {
            let user = USER.get().unwrap();
            user.commit()
                .await
                .expect("Failed to commit user to database");
            let acc = user.create_account("test_acc", None).await?;
            let tag_id = user
                .create_account_tag(
                    acc.id,
                    "category".to_string(),
                    "assets".to_string(),
                    Some("Account category".to_string()),
                )
                .await?;
            let mut conn = user.get_connection().await?;
            let res = sqlx::query!(
                "SELECT tag_id FROM account_tags WHERE account_id = $1 AND tag_id = $2",
                &acc.id,
                &tag_id
            )
            .fetch_one(&mut *conn)
            .await?;
            assert_eq!(res.tag_id, tag_id);
            let tag = user.get_tag(tag_id).await?;
            assert_eq!(tag.tag_name, "category");
            assert_eq!(tag.tag_value, "assets");
            assert_eq!(tag.description, Some("Account category".to_string()));
        }
        #[local_db_sqlx_test]
        async fn test_list_account_tag_names_empty(pool: PgPool) -> Result<(), anyhow::Error> {
            let user = USER.get().unwrap();
            user.commit()
                .await
                .expect("Failed to commit user to database");
            let names = user.list_account_tag_names().await?;
            assert!(names.is_empty());
        }
        #[local_db_sqlx_test]
        async fn test_list_account_tag_names_with_data(pool: PgPool) -> Result<(), anyhow::Error> {
            let user = USER.get().unwrap();
            user.commit()
                .await
                .expect("Failed to commit user to database");
            let acc1 = user.create_account("test_acc1", None).await?;
            let acc2 = user.create_account("test_acc2", None).await?;
            user.set_account_tag(
                &acc1,
                &Tag {
                    id: Uuid::new_v4(),
                    tag_name: "category".to_string(),
                    tag_value: "assets".to_string(),
                    description: None,
                },
            )
            .await?;
            user.set_account_tag(
                &acc2,
                &Tag {
                    id: Uuid::new_v4(),
                    tag_name: "priority".to_string(),
                    tag_value: "high".to_string(),
                    description: None,
                },
            )
            .await?;
            let names = user.list_account_tag_names().await?;
            assert!(names.contains(&"category".to_string()));
            assert!(names.contains(&"priority".to_string()));
        }
        #[local_db_sqlx_test]
        async fn test_list_account_tag_values_empty(pool: PgPool) -> Result<(), anyhow::Error> {
            let user = USER.get().unwrap();
            user.commit()
                .await
                .expect("Failed to commit user to database");
            let values = user.list_account_tag_values("category").await?;
            assert!(values.is_empty());
        }
        #[local_db_sqlx_test]
        async fn test_list_account_tag_values_with_data(pool: PgPool) -> Result<(), anyhow::Error> {
            let user = USER.get().unwrap();
            user.commit()
                .await
                .expect("Failed to commit user to database");
            let acc1 = user.create_account("test_acc1", None).await?;
            let acc2 = user.create_account("test_acc2", None).await?;
            user.set_account_tag(
                &acc1,
                &Tag {
                    id: Uuid::new_v4(),
                    tag_name: "category".to_string(),
                    tag_value: "assets".to_string(),
                    description: None,
                },
            )
            .await?;
            user.set_account_tag(
                &acc2,
                &Tag {
                    id: Uuid::new_v4(),
                    tag_name: "category".to_string(),
                    tag_value: "liabilities".to_string(),
                    description: None,
                },
            )
            .await?;
            let values = user.list_account_tag_values("category").await?;
            assert_eq!(values.len(), 2);
            assert!(values.contains(&"assets".to_string()));
            assert!(values.contains(&"liabilities".to_string()));
            let nonexistent = user.list_account_tag_values("nonexistent").await?;
            assert!(nonexistent.is_empty());
        }
    }
}