1
use axum::{
2
    Router,
3
    body::Body,
4
    http::{Request, StatusCode},
5
    routing::{get, post},
6
};
7
use serde_json::json;
8
use tower::ServiceExt;
9

            
10
use crate::common::{create_mock_jwt_auth, create_mock_user, create_test_app_state};
11

            
12
#[tokio::test]
13
1
async fn test_transaction_create_without_auth() {
14
1
    let app_state = create_test_app_state().await;
15
1
    let app = Router::new()
16
1
        .route(
17
1
            "/transaction/create/submit",
18
1
            post(web::pages::transaction::create::submit::transaction_submit),
19
        )
20
1
        .with_state(app_state);
21

            
22
1
    let transaction_data = json!({
23
1
        "splits": [{
24
1
            "amount": "100.00",
25
1
            "amount_converted": "100.00",
26
1
            "from_account": "550e8400-e29b-41d4-a716-446655440000",
27
1
            "to_account": "650e8400-e29b-41d4-a716-446655440001",
28
1
            "from_commodity": "550e8400-e29b-41d4-a716-446655440002",
29
1
            "to_commodity": "550e8400-e29b-41d4-a716-446655440002"
30
        }],
31
1
        "note": "Test transaction",
32
1
        "date": "2023-01-01T12:00:00Z"
33
    });
34

            
35
1
    let response = app
36
1
        .oneshot(
37
1
            Request::builder()
38
1
                .method("POST")
39
1
                .uri("/transaction/create/submit")
40
1
                .header("content-type", "application/json")
41
1
                .body(Body::from(transaction_data.to_string()))
42
1
                .unwrap(),
43
1
        )
44
1
        .await
45
1
        .unwrap();
46

            
47
    // Should fail without authentication - expecting 401 or 500
48
1
    assert!(response.status().is_client_error() || response.status().is_server_error());
49
1
}
50

            
51
#[tokio::test]
52
1
async fn test_transaction_create_with_invalid_json() {
53
1
    let app_state = create_test_app_state().await;
54
1
    let app = Router::new()
55
1
        .route(
56
1
            "/transaction/create/submit",
57
1
            post(web::pages::transaction::create::submit::transaction_submit),
58
        )
59
1
        .with_state(app_state);
60

            
61
1
    let response = app
62
1
        .oneshot(
63
1
            Request::builder()
64
1
                .method("POST")
65
1
                .uri("/transaction/create/submit")
66
1
                .header("content-type", "application/json")
67
1
                .body(Body::from("invalid json"))
68
1
                .unwrap(),
69
1
        )
70
1
        .await
71
1
        .unwrap();
72

            
73
    // Should fail without authentication - expecting 401 or 500
74
1
    assert!(response.status().is_client_error() || response.status().is_server_error());
75
1
}
76

            
77
#[tokio::test]
78
1
async fn test_transaction_create_with_mock_auth() {
79
1
    let app_state = create_test_app_state().await;
80
1
    let mock_user = create_mock_user();
81
1
    let jwt_auth = create_mock_jwt_auth(mock_user);
82

            
83
1
    let app = Router::new()
84
1
        .route(
85
1
            "/transaction/create/submit",
86
1
            post(web::pages::transaction::create::submit::transaction_submit),
87
        )
88
1
        .layer(axum::middleware::from_fn_with_state(
89
1
            app_state.clone(),
90
1
            move |mut req: axum::http::Request<Body>, next: axum::middleware::Next| {
91
1
                let jwt_auth = jwt_auth.clone();
92
1
                async move {
93
1
                    req.extensions_mut().insert(jwt_auth);
94
1
                    next.run(req).await
95
1
                }
96
1
            },
97
        ))
98
1
        .with_state(app_state.clone());
99

            
100
1
    let transaction_data = json!({
101
1
        "splits": [{
102
1
            "amount": "100.00",
103
1
            "amount_converted": "100.00",
104
1
            "from_account": "550e8400-e29b-41d4-a716-446655440000",
105
1
            "to_account": "650e8400-e29b-41d4-a716-446655440001",
106
1
            "from_commodity": "550e8400-e29b-41d4-a716-446655440002",
107
1
            "to_commodity": "550e8400-e29b-41d4-a716-446655440002"
108
        }],
109
1
        "note": "Test transaction",
110
1
        "date": "2023-01-01T12:00:00Z"
111
    });
112

            
113
1
    let response = app
114
1
        .oneshot(
115
1
            Request::builder()
116
1
                .method("POST")
117
1
                .uri("/transaction/create/submit")
118
1
                .header("content-type", "application/json")
119
1
                .body(Body::from(transaction_data.to_string()))
120
1
                .unwrap(),
121
1
        )
122
1
        .await
123
1
        .unwrap();
124

            
125
    // This test verifies our CreateTransaction macro integration works
126
    // Even if it fails due to DB issues, it should not be a JSON parsing error
127
    // We expect either success (200) or a server error (500) due to missing DB
128
    // but NOT a client error (400) which would indicate API issues
129
1
    assert!(
130
1
        response.status().is_success() || response.status().is_server_error(),
131
        "Expected success or server error, got: {}",
132
        response.status()
133
    );
134

            
135
    // If we get a response, verify it has proper content type
136
1
    if let Some(content_type) = response.headers().get("content-type") {
137
1
        let content_type_str = content_type.to_str().unwrap_or("");
138
1
        // Should be text (for success message) or JSON (for error response)
139
1
        assert!(
140
1
            content_type_str.contains("text/") || content_type_str.contains("application/json"),
141
1
            "Unexpected content type: {content_type_str}"
142
1
        );
143
1
    }
144
1
}
145

            
146
#[tokio::test]
147
1
async fn test_transaction_create_empty_splits() {
148
1
    let app_state = create_test_app_state().await;
149
1
    let mock_user = create_mock_user();
150
1
    let jwt_auth = create_mock_jwt_auth(mock_user);
151

            
152
1
    let app = Router::new()
153
1
        .route(
154
1
            "/transaction/create/submit",
155
1
            post(web::pages::transaction::create::submit::transaction_submit),
156
        )
157
1
        .layer(axum::middleware::from_fn_with_state(
158
1
            app_state.clone(),
159
1
            move |mut req: axum::http::Request<Body>, next: axum::middleware::Next| {
160
1
                let jwt_auth = jwt_auth.clone();
161
1
                async move {
162
1
                    req.extensions_mut().insert(jwt_auth);
163
1
                    next.run(req).await
164
1
                }
165
1
            },
166
        ))
167
1
        .with_state(app_state.clone());
168

            
169
1
    let transaction_data = json!({
170
1
        "splits": [],
171
1
        "note": "Test transaction with no splits"
172
    });
173

            
174
1
    let response = app
175
1
        .oneshot(
176
1
            Request::builder()
177
1
                .method("POST")
178
1
                .uri("/transaction/create/submit")
179
1
                .header("content-type", "application/json")
180
1
                .body(Body::from(transaction_data.to_string()))
181
1
                .unwrap(),
182
1
        )
183
1
        .await
184
1
        .unwrap();
185

            
186
    // Should return 400 for empty splits array
187
1
    assert_eq!(response.status(), StatusCode::BAD_REQUEST);
188

            
189
    // Verify it returns JSON error response
190
1
    let body = axum::body::to_bytes(response.into_body(), usize::MAX)
191
1
        .await
192
1
        .unwrap();
193
1
    let body_str = String::from_utf8(body.to_vec()).unwrap();
194
1
    let json_response: serde_json::Value =
195
1
        serde_json::from_str(&body_str).expect("Response should be valid JSON");
196

            
197
    // Verify error structure
198
1
    assert_eq!(json_response["status"], "fail");
199
1
    assert!(json_response["message"].is_string());
200
1
    assert!(json_response["message"].as_str().unwrap().contains("split"));
201
1
}
202

            
203
#[tokio::test]
204
1
async fn test_transaction_create_invalid_amount() {
205
1
    let app_state = create_test_app_state().await;
206
1
    let mock_user = create_mock_user();
207
1
    let jwt_auth = create_mock_jwt_auth(mock_user);
208

            
209
1
    let app = Router::new()
210
1
        .route(
211
1
            "/transaction/create/submit",
212
1
            post(web::pages::transaction::create::submit::transaction_submit),
213
        )
214
1
        .layer(axum::middleware::from_fn_with_state(
215
1
            app_state.clone(),
216
1
            move |mut req: axum::http::Request<Body>, next: axum::middleware::Next| {
217
1
                let jwt_auth = jwt_auth.clone();
218
1
                async move {
219
1
                    req.extensions_mut().insert(jwt_auth);
220
1
                    next.run(req).await
221
1
                }
222
1
            },
223
        ))
224
1
        .with_state(app_state.clone());
225

            
226
1
    let transaction_data = json!({
227
1
        "splits": [{
228
1
            "amount": "invalid_amount",
229
1
            "amount_converted": "100.00",
230
1
            "from_account": "550e8400-e29b-41d4-a716-446655440000",
231
1
            "to_account": "650e8400-e29b-41d4-a716-446655440001",
232
1
            "from_commodity": "550e8400-e29b-41d4-a716-446655440002",
233
1
            "to_commodity": "550e8400-e29b-41d4-a716-446655440002"
234
        }],
235
1
        "note": "Test transaction with invalid amount"
236
    });
237

            
238
1
    let response = app
239
1
        .oneshot(
240
1
            Request::builder()
241
1
                .method("POST")
242
1
                .uri("/transaction/create/submit")
243
1
                .header("content-type", "application/json")
244
1
                .body(Body::from(transaction_data.to_string()))
245
1
                .unwrap(),
246
1
        )
247
1
        .await
248
1
        .unwrap();
249

            
250
    // Should return 400 for invalid amount
251
1
    assert_eq!(response.status(), StatusCode::BAD_REQUEST);
252

            
253
    // Verify it returns JSON error response
254
1
    let body = axum::body::to_bytes(response.into_body(), usize::MAX)
255
1
        .await
256
1
        .unwrap();
257
1
    let body_str = String::from_utf8(body.to_vec()).unwrap();
258
1
    let json_response: serde_json::Value =
259
1
        serde_json::from_str(&body_str).expect("Response should be valid JSON");
260

            
261
    // Verify error structure
262
1
    assert_eq!(json_response["status"], "fail");
263
1
    assert!(json_response["message"].is_string());
264
1
    assert!(
265
1
        json_response["message"]
266
1
            .as_str()
267
1
            .unwrap()
268
1
            .contains("Invalid amount")
269
1
    );
270
1
}
271

            
272
#[tokio::test]
273
1
async fn test_transaction_create_invalid_account_id() {
274
1
    let app_state = create_test_app_state().await;
275
1
    let mock_user = create_mock_user();
276
1
    let jwt_auth = create_mock_jwt_auth(mock_user);
277

            
278
1
    let app = Router::new()
279
1
        .route(
280
1
            "/transaction/create/submit",
281
1
            post(web::pages::transaction::create::submit::transaction_submit),
282
        )
283
1
        .layer(axum::middleware::from_fn_with_state(
284
1
            app_state.clone(),
285
1
            move |mut req: axum::http::Request<Body>, next: axum::middleware::Next| {
286
1
                let jwt_auth = jwt_auth.clone();
287
1
                async move {
288
1
                    req.extensions_mut().insert(jwt_auth);
289
1
                    next.run(req).await
290
1
                }
291
1
            },
292
        ))
293
1
        .with_state(app_state.clone());
294

            
295
1
    let transaction_data = json!({
296
1
        "splits": [{
297
1
            "amount": "100.00",
298
1
            "amount_converted": "100.00",
299
1
            "from_account": "invalid_uuid_format",
300
1
            "to_account": "650e8400-e29b-41d4-a716-446655440001",
301
1
            "from_commodity": "550e8400-e29b-41d4-a716-446655440002",
302
1
            "to_commodity": "550e8400-e29b-41d4-a716-446655440002"
303
        }],
304
1
        "note": "Test transaction with invalid account ID"
305
    });
306

            
307
1
    let response = app
308
1
        .oneshot(
309
1
            Request::builder()
310
1
                .method("POST")
311
1
                .uri("/transaction/create/submit")
312
1
                .header("content-type", "application/json")
313
1
                .body(Body::from(transaction_data.to_string()))
314
1
                .unwrap(),
315
1
        )
316
1
        .await
317
1
        .unwrap();
318

            
319
    // Should return 400 for invalid UUID format
320
1
    assert_eq!(response.status(), StatusCode::BAD_REQUEST);
321

            
322
    // Verify it returns JSON error response
323
1
    let body = axum::body::to_bytes(response.into_body(), usize::MAX)
324
1
        .await
325
1
        .unwrap();
326
1
    let body_str = String::from_utf8(body.to_vec()).unwrap();
327
1
    let json_response: serde_json::Value =
328
1
        serde_json::from_str(&body_str).expect("Response should be valid JSON");
329

            
330
    // Verify error structure
331
1
    assert_eq!(json_response["status"], "fail");
332
1
    assert!(json_response["message"].is_string());
333
1
    assert!(
334
1
        json_response["message"]
335
1
            .as_str()
336
1
            .unwrap()
337
1
            .contains("Invalid")
338
1
    );
339
1
}
340

            
341
#[tokio::test]
342
1
async fn test_transaction_create_negative_amount() {
343
1
    let app_state = create_test_app_state().await;
344
1
    let mock_user = create_mock_user();
345
1
    let jwt_auth = create_mock_jwt_auth(mock_user);
346

            
347
1
    let app = Router::new()
348
1
        .route(
349
1
            "/transaction/create/submit",
350
1
            post(web::pages::transaction::create::submit::transaction_submit),
351
        )
352
1
        .layer(axum::middleware::from_fn_with_state(
353
1
            app_state.clone(),
354
1
            move |mut req: axum::http::Request<Body>, next: axum::middleware::Next| {
355
1
                let jwt_auth = jwt_auth.clone();
356
1
                async move {
357
1
                    req.extensions_mut().insert(jwt_auth);
358
1
                    next.run(req).await
359
1
                }
360
1
            },
361
        ))
362
1
        .with_state(app_state.clone());
363

            
364
1
    let transaction_data = json!({
365
1
        "splits": [{
366
1
            "amount": "-50.00",
367
1
            "amount_converted": "-50.00",
368
1
            "from_account": "550e8400-e29b-41d4-a716-446655440000",
369
1
            "to_account": "650e8400-e29b-41d4-a716-446655440001",
370
1
            "from_commodity": "550e8400-e29b-41d4-a716-446655440002",
371
1
            "to_commodity": "550e8400-e29b-41d4-a716-446655440002"
372
        }],
373
1
        "note": "Test transaction with negative amount"
374
    });
375

            
376
1
    let response = app
377
1
        .oneshot(
378
1
            Request::builder()
379
1
                .method("POST")
380
1
                .uri("/transaction/create/submit")
381
1
                .header("content-type", "application/json")
382
1
                .body(Body::from(transaction_data.to_string()))
383
1
                .unwrap(),
384
1
        )
385
1
        .await
386
1
        .unwrap();
387

            
388
    // Should return 400 for negative amount
389
1
    assert_eq!(response.status(), StatusCode::BAD_REQUEST);
390

            
391
    // Verify it returns JSON error response
392
1
    let body = axum::body::to_bytes(response.into_body(), usize::MAX)
393
1
        .await
394
1
        .unwrap();
395
1
    let body_str = String::from_utf8(body.to_vec()).unwrap();
396
1
    let json_response: serde_json::Value =
397
1
        serde_json::from_str(&body_str).expect("Response should be valid JSON");
398

            
399
    // Verify error structure
400
1
    assert_eq!(json_response["status"], "fail");
401
1
    assert!(json_response["message"].is_string());
402
1
    assert!(
403
1
        json_response["message"]
404
1
            .as_str()
405
1
            .unwrap()
406
1
            .contains("positive")
407
1
    );
408
1
}
409

            
410
#[tokio::test]
411
1
async fn test_transaction_create_zero_amount() {
412
1
    let app_state = create_test_app_state().await;
413
1
    let mock_user = create_mock_user();
414
1
    let jwt_auth = create_mock_jwt_auth(mock_user);
415

            
416
1
    let app = Router::new()
417
1
        .route(
418
1
            "/transaction/create/submit",
419
1
            post(web::pages::transaction::create::submit::transaction_submit),
420
        )
421
1
        .layer(axum::middleware::from_fn_with_state(
422
1
            app_state.clone(),
423
1
            move |mut req: axum::http::Request<Body>, next: axum::middleware::Next| {
424
1
                let jwt_auth = jwt_auth.clone();
425
1
                async move {
426
1
                    req.extensions_mut().insert(jwt_auth);
427
1
                    next.run(req).await
428
1
                }
429
1
            },
430
        ))
431
1
        .with_state(app_state.clone());
432

            
433
1
    let transaction_data = json!({
434
1
        "splits": [{
435
1
            "amount": "0.00",
436
1
            "amount_converted": "0.00",
437
1
            "from_account": "550e8400-e29b-41d4-a716-446655440000",
438
1
            "to_account": "650e8400-e29b-41d4-a716-446655440001",
439
1
            "from_commodity": "550e8400-e29b-41d4-a716-446655440002",
440
1
            "to_commodity": "550e8400-e29b-41d4-a716-446655440002"
441
        }],
442
1
        "note": "Test transaction with zero amount"
443
    });
444

            
445
1
    let response = app
446
1
        .oneshot(
447
1
            Request::builder()
448
1
                .method("POST")
449
1
                .uri("/transaction/create/submit")
450
1
                .header("content-type", "application/json")
451
1
                .body(Body::from(transaction_data.to_string()))
452
1
                .unwrap(),
453
1
        )
454
1
        .await
455
1
        .unwrap();
456

            
457
    // Should return 400 for zero amount
458
1
    assert_eq!(response.status(), StatusCode::BAD_REQUEST);
459

            
460
    // Verify it returns JSON error response
461
1
    let body = axum::body::to_bytes(response.into_body(), usize::MAX)
462
1
        .await
463
1
        .unwrap();
464
1
    let body_str = String::from_utf8(body.to_vec()).unwrap();
465
1
    let json_response: serde_json::Value =
466
1
        serde_json::from_str(&body_str).expect("Response should be valid JSON");
467

            
468
    // Verify error structure
469
1
    assert_eq!(json_response["status"], "fail");
470
1
    assert!(json_response["message"].is_string());
471
1
    assert!(
472
1
        json_response["message"]
473
1
            .as_str()
474
1
            .unwrap()
475
1
            .contains("positive")
476
1
    );
477
1
}
478

            
479
#[tokio::test]
480
1
async fn test_transaction_create_with_note() {
481
1
    let app_state = create_test_app_state().await;
482
1
    let mock_user = create_mock_user();
483
1
    let jwt_auth = create_mock_jwt_auth(mock_user);
484

            
485
1
    let app = Router::new()
486
1
        .route(
487
1
            "/transaction/create/submit",
488
1
            post(web::pages::transaction::create::submit::transaction_submit),
489
        )
490
1
        .layer(axum::middleware::from_fn_with_state(
491
1
            app_state.clone(),
492
1
            move |mut req: axum::http::Request<Body>, next: axum::middleware::Next| {
493
1
                let jwt_auth = jwt_auth.clone();
494
1
                async move {
495
1
                    req.extensions_mut().insert(jwt_auth);
496
1
                    next.run(req).await
497
1
                }
498
1
            },
499
        ))
500
1
        .with_state(app_state.clone());
501

            
502
1
    let transaction_data = json!({
503
1
        "splits": [{
504
1
            "amount": "100.00",
505
1
            "amount_converted": "100.00",
506
1
            "from_account": "550e8400-e29b-41d4-a716-446655440000",
507
1
            "to_account": "650e8400-e29b-41d4-a716-446655440001",
508
1
            "from_commodity": "550e8400-e29b-41d4-a716-446655440002",
509
1
            "to_commodity": "550e8400-e29b-41d4-a716-446655440002"
510
        }],
511
1
        "note": "This is a test transaction with a note",
512
1
        "date": "2023-01-01T12:00:00Z"
513
    });
514

            
515
1
    let response = app
516
1
        .oneshot(
517
1
            Request::builder()
518
1
                .method("POST")
519
1
                .uri("/transaction/create/submit")
520
1
                .header("content-type", "application/json")
521
1
                .body(Body::from(transaction_data.to_string()))
522
1
                .unwrap(),
523
1
        )
524
1
        .await
525
1
        .unwrap();
526

            
527
    // Should succeed with auth (even if DB fails, it shouldn't be a parsing error)
528
1
    assert!(
529
1
        response.status().is_success() || response.status().is_server_error(),
530
1
        "Expected success or server error, got: {}",
531
1
        response.status()
532
1
    );
533
1
}
534

            
535
// ListTransactions web tests
536

            
537
#[tokio::test]
538
1
async fn test_transaction_list_page_without_auth() {
539
1
    let app_state = create_test_app_state().await;
540
1
    let app = Router::new()
541
1
        .route(
542
1
            "/transaction/list",
543
1
            get(web::pages::transaction::list::transaction_list_page),
544
        )
545
1
        .with_state(app_state);
546

            
547
1
    let response = app
548
1
        .oneshot(
549
1
            Request::builder()
550
1
                .method("GET")
551
1
                .uri("/transaction/list")
552
1
                .body(Body::empty())
553
1
                .unwrap(),
554
1
        )
555
1
        .await
556
1
        .unwrap();
557

            
558
    // Should succeed as this is just a static page
559
1
    assert_eq!(response.status(), StatusCode::OK);
560
1
}
561

            
562
#[tokio::test]
563
1
async fn test_transaction_table_without_auth() {
564
1
    let app_state = create_test_app_state().await;
565
1
    let app = Router::new()
566
1
        .route(
567
1
            "/transaction/table",
568
1
            get(web::pages::transaction::list::transaction_table),
569
        )
570
1
        .with_state(app_state);
571

            
572
1
    let response = app
573
1
        .oneshot(
574
1
            Request::builder()
575
1
                .method("GET")
576
1
                .uri("/transaction/table")
577
1
                .body(Body::empty())
578
1
                .unwrap(),
579
1
        )
580
1
        .await
581
1
        .unwrap();
582

            
583
    // Should fail without authentication - expecting 401 or 500
584
1
    assert!(response.status().is_client_error() || response.status().is_server_error());
585
1
}
586

            
587
#[tokio::test]
588
1
async fn test_transaction_table_with_mock_auth() {
589
1
    let app_state = create_test_app_state().await;
590
1
    let mock_user = create_mock_user();
591
1
    let jwt_auth = create_mock_jwt_auth(mock_user);
592

            
593
1
    let app = Router::new()
594
1
        .route(
595
1
            "/transaction/table",
596
1
            get(web::pages::transaction::list::transaction_table),
597
        )
598
1
        .layer(axum::middleware::from_fn_with_state(
599
1
            app_state.clone(),
600
1
            move |mut req: axum::http::Request<Body>, next: axum::middleware::Next| {
601
1
                let jwt_auth = jwt_auth.clone();
602
1
                async move {
603
1
                    req.extensions_mut().insert(jwt_auth);
604
1
                    next.run(req).await
605
1
                }
606
1
            },
607
        ))
608
1
        .with_state(app_state.clone());
609

            
610
1
    let response = app
611
1
        .oneshot(
612
1
            Request::builder()
613
1
                .method("GET")
614
1
                .uri("/transaction/table")
615
1
                .body(Body::empty())
616
1
                .unwrap(),
617
1
        )
618
1
        .await
619
1
        .unwrap();
620

            
621
    // This test verifies our ListTransactions macro integration works
622
    // Even if it fails due to DB issues, it should not be a parsing error
623
    // We expect either success (200) or a server error (500) due to missing DB
624
    // but NOT a client error (400) which would indicate API issues
625
1
    assert!(
626
1
        response.status().is_success() || response.status().is_server_error(),
627
        "Expected success or server error, got: {}",
628
        response.status()
629
    );
630

            
631
    // If we get a response, verify it has proper content type
632
1
    if let Some(content_type) = response.headers().get("content-type") {
633
1
        let content_type_str = content_type.to_str().unwrap_or("");
634
1
        // Should be HTML for the table template
635
1
        assert!(
636
1
            content_type_str.contains("text/html"),
637
1
            "Unexpected content type: {content_type_str}"
638
1
        );
639
1
    }
640
1
}
641

            
642
#[tokio::test]
643
1
async fn test_transaction_table_with_account_filter() {
644
1
    let app_state = create_test_app_state().await;
645
1
    let mock_user = create_mock_user();
646
1
    let jwt_auth = create_mock_jwt_auth(mock_user);
647

            
648
1
    let app = Router::new()
649
1
        .route(
650
1
            "/transaction/table",
651
1
            get(web::pages::transaction::list::transaction_table),
652
        )
653
1
        .layer(axum::middleware::from_fn_with_state(
654
1
            app_state.clone(),
655
1
            move |mut req: axum::http::Request<Body>, next: axum::middleware::Next| {
656
1
                let jwt_auth = jwt_auth.clone();
657
1
                async move {
658
1
                    req.extensions_mut().insert(jwt_auth);
659
1
                    next.run(req).await
660
1
                }
661
1
            },
662
        ))
663
1
        .with_state(app_state.clone());
664

            
665
    // Test with account filter parameter
666
1
    let response = app
667
1
        .oneshot(
668
1
            Request::builder()
669
1
                .method("GET")
670
1
                .uri("/transaction/table?account=550e8400-e29b-41d4-a716-446655440000")
671
1
                .body(Body::empty())
672
1
                .unwrap(),
673
1
        )
674
1
        .await
675
1
        .unwrap();
676

            
677
    // Should succeed with auth (even if DB fails, it shouldn't be a parsing error)
678
1
    assert!(
679
1
        response.status().is_success() || response.status().is_server_error(),
680
1
        "Expected success or server error, got: {}",
681
1
        response.status()
682
1
    );
683
1
}
684

            
685
#[tokio::test]
686
1
async fn test_transaction_table_with_invalid_account_filter() {
687
1
    let app_state = create_test_app_state().await;
688
1
    let mock_user = create_mock_user();
689
1
    let jwt_auth = create_mock_jwt_auth(mock_user);
690

            
691
1
    let app = Router::new()
692
1
        .route(
693
1
            "/transaction/table",
694
1
            get(web::pages::transaction::list::transaction_table),
695
        )
696
1
        .layer(axum::middleware::from_fn_with_state(
697
1
            app_state.clone(),
698
1
            move |mut req: axum::http::Request<Body>, next: axum::middleware::Next| {
699
1
                let jwt_auth = jwt_auth.clone();
700
1
                async move {
701
1
                    req.extensions_mut().insert(jwt_auth);
702
1
                    next.run(req).await
703
1
                }
704
1
            },
705
        ))
706
1
        .with_state(app_state.clone());
707

            
708
    // Test with invalid account UUID
709
1
    let response = app
710
1
        .oneshot(
711
1
            Request::builder()
712
1
                .method("GET")
713
1
                .uri("/transaction/table?account=invalid-uuid")
714
1
                .body(Body::empty())
715
1
                .unwrap(),
716
1
        )
717
1
        .await
718
1
        .unwrap();
719

            
720
    // Should return 400 for invalid UUID format
721
1
    assert!(
722
1
        response.status() == StatusCode::BAD_REQUEST || response.status().is_server_error(),
723
1
        "Expected bad request or server error, got: {}",
724
1
        response.status()
725
1
    );
726
1
}
727

            
728
#[tokio::test]
729
1
async fn test_transaction_table_empty_account_filter() {
730
1
    let app_state = create_test_app_state().await;
731
1
    let mock_user = create_mock_user();
732
1
    let jwt_auth = create_mock_jwt_auth(mock_user);
733

            
734
1
    let app = Router::new()
735
1
        .route(
736
1
            "/transaction/table",
737
1
            get(web::pages::transaction::list::transaction_table),
738
        )
739
1
        .layer(axum::middleware::from_fn_with_state(
740
1
            app_state.clone(),
741
1
            move |mut req: axum::http::Request<Body>, next: axum::middleware::Next| {
742
1
                let jwt_auth = jwt_auth.clone();
743
1
                async move {
744
1
                    req.extensions_mut().insert(jwt_auth);
745
1
                    next.run(req).await
746
1
                }
747
1
            },
748
        ))
749
1
        .with_state(app_state.clone());
750

            
751
    // Test with empty account parameter
752
1
    let response = app
753
1
        .oneshot(
754
1
            Request::builder()
755
1
                .method("GET")
756
1
                .uri("/transaction/table?account=")
757
1
                .body(Body::empty())
758
1
                .unwrap(),
759
1
        )
760
1
        .await
761
1
        .unwrap();
762

            
763
    // Empty string is not a valid UUID, so should return 400 or server error
764
1
    assert!(
765
1
        response.status() == StatusCode::BAD_REQUEST || response.status().is_server_error(),
766
1
        "Expected bad request or server error, got: {}",
767
1
        response.status()
768
1
    );
769
1
}
770

            
771
#[tokio::test]
772
1
async fn test_transaction_table_multiple_parameters() {
773
1
    let app_state = create_test_app_state().await;
774
1
    let mock_user = create_mock_user();
775
1
    let jwt_auth = create_mock_jwt_auth(mock_user);
776

            
777
1
    let app = Router::new()
778
1
        .route(
779
1
            "/transaction/table",
780
1
            get(web::pages::transaction::list::transaction_table),
781
        )
782
1
        .layer(axum::middleware::from_fn_with_state(
783
1
            app_state.clone(),
784
1
            move |mut req: axum::http::Request<Body>, next: axum::middleware::Next| {
785
1
                let jwt_auth = jwt_auth.clone();
786
1
                async move {
787
1
                    req.extensions_mut().insert(jwt_auth);
788
1
                    next.run(req).await
789
1
                }
790
1
            },
791
        ))
792
1
        .with_state(app_state.clone());
793

            
794
    // Test with multiple query parameters (only account should be used)
795
1
    let response = app
796
1
        .oneshot(
797
1
            Request::builder()
798
1
                .method("GET")
799
1
                .uri("/transaction/table?account=550e8400-e29b-41d4-a716-446655440000&other=value")
800
1
                .body(Body::empty())
801
1
                .unwrap(),
802
1
        )
803
1
        .await
804
1
        .unwrap();
805

            
806
    // Should succeed - extra parameters should be ignored
807
1
    assert!(
808
1
        response.status().is_success() || response.status().is_server_error(),
809
1
        "Expected success or server error, got: {}",
810
1
        response.status()
811
1
    );
812
1
}
813

            
814
// Pagination parameter acceptance tests
815

            
816
#[tokio::test]
817
1
async fn test_transaction_table_with_limit_param() {
818
1
    let app_state = create_test_app_state().await;
819
1
    let mock_user = create_mock_user();
820
1
    let jwt_auth = create_mock_jwt_auth(mock_user);
821

            
822
1
    let app = Router::new()
823
1
        .route(
824
1
            "/transaction/table",
825
1
            get(web::pages::transaction::list::transaction_table),
826
        )
827
1
        .layer(axum::middleware::from_fn_with_state(
828
1
            app_state.clone(),
829
1
            move |mut req: axum::http::Request<Body>, next: axum::middleware::Next| {
830
1
                let jwt_auth = jwt_auth.clone();
831
1
                async move {
832
1
                    req.extensions_mut().insert(jwt_auth);
833
1
                    next.run(req).await
834
1
                }
835
1
            },
836
        ))
837
1
        .with_state(app_state.clone());
838

            
839
1
    let response = app
840
1
        .oneshot(
841
1
            Request::builder()
842
1
                .method("GET")
843
1
                .uri("/transaction/table?limit=10")
844
1
                .body(Body::empty())
845
1
                .unwrap(),
846
1
        )
847
1
        .await
848
1
        .unwrap();
849

            
850
1
    assert!(
851
1
        response.status().is_success() || response.status().is_server_error(),
852
1
        "Expected success or server error for limit param, got: {}",
853
1
        response.status()
854
1
    );
855
1
}
856

            
857
#[tokio::test]
858
1
async fn test_transaction_table_with_page_param() {
859
1
    let app_state = create_test_app_state().await;
860
1
    let mock_user = create_mock_user();
861
1
    let jwt_auth = create_mock_jwt_auth(mock_user);
862

            
863
1
    let app = Router::new()
864
1
        .route(
865
1
            "/transaction/table",
866
1
            get(web::pages::transaction::list::transaction_table),
867
        )
868
1
        .layer(axum::middleware::from_fn_with_state(
869
1
            app_state.clone(),
870
1
            move |mut req: axum::http::Request<Body>, next: axum::middleware::Next| {
871
1
                let jwt_auth = jwt_auth.clone();
872
1
                async move {
873
1
                    req.extensions_mut().insert(jwt_auth);
874
1
                    next.run(req).await
875
1
                }
876
1
            },
877
        ))
878
1
        .with_state(app_state.clone());
879

            
880
1
    let response = app
881
1
        .oneshot(
882
1
            Request::builder()
883
1
                .method("GET")
884
1
                .uri("/transaction/table?page=2")
885
1
                .body(Body::empty())
886
1
                .unwrap(),
887
1
        )
888
1
        .await
889
1
        .unwrap();
890

            
891
1
    assert!(
892
1
        response.status().is_success() || response.status().is_server_error(),
893
1
        "Expected success or server error for page param, got: {}",
894
1
        response.status()
895
1
    );
896
1
}
897

            
898
#[tokio::test]
899
1
async fn test_transaction_table_with_date_from_param() {
900
1
    let app_state = create_test_app_state().await;
901
1
    let mock_user = create_mock_user();
902
1
    let jwt_auth = create_mock_jwt_auth(mock_user);
903

            
904
1
    let app = Router::new()
905
1
        .route(
906
1
            "/transaction/table",
907
1
            get(web::pages::transaction::list::transaction_table),
908
        )
909
1
        .layer(axum::middleware::from_fn_with_state(
910
1
            app_state.clone(),
911
1
            move |mut req: axum::http::Request<Body>, next: axum::middleware::Next| {
912
1
                let jwt_auth = jwt_auth.clone();
913
1
                async move {
914
1
                    req.extensions_mut().insert(jwt_auth);
915
1
                    next.run(req).await
916
1
                }
917
1
            },
918
        ))
919
1
        .with_state(app_state.clone());
920

            
921
1
    let response = app
922
1
        .oneshot(
923
1
            Request::builder()
924
1
                .method("GET")
925
1
                .uri("/transaction/table?date_from=2024-01-01")
926
1
                .body(Body::empty())
927
1
                .unwrap(),
928
1
        )
929
1
        .await
930
1
        .unwrap();
931

            
932
1
    assert!(
933
1
        response.status().is_success() || response.status().is_server_error(),
934
1
        "Expected success or server error for date_from param, got: {}",
935
1
        response.status()
936
1
    );
937
1
}
938

            
939
#[tokio::test]
940
1
async fn test_transaction_table_with_date_to_param() {
941
1
    let app_state = create_test_app_state().await;
942
1
    let mock_user = create_mock_user();
943
1
    let jwt_auth = create_mock_jwt_auth(mock_user);
944

            
945
1
    let app = Router::new()
946
1
        .route(
947
1
            "/transaction/table",
948
1
            get(web::pages::transaction::list::transaction_table),
949
        )
950
1
        .layer(axum::middleware::from_fn_with_state(
951
1
            app_state.clone(),
952
1
            move |mut req: axum::http::Request<Body>, next: axum::middleware::Next| {
953
1
                let jwt_auth = jwt_auth.clone();
954
1
                async move {
955
1
                    req.extensions_mut().insert(jwt_auth);
956
1
                    next.run(req).await
957
1
                }
958
1
            },
959
        ))
960
1
        .with_state(app_state.clone());
961

            
962
1
    let response = app
963
1
        .oneshot(
964
1
            Request::builder()
965
1
                .method("GET")
966
1
                .uri("/transaction/table?date_to=2024-12-31")
967
1
                .body(Body::empty())
968
1
                .unwrap(),
969
1
        )
970
1
        .await
971
1
        .unwrap();
972

            
973
1
    assert!(
974
1
        response.status().is_success() || response.status().is_server_error(),
975
1
        "Expected success or server error for date_to param, got: {}",
976
1
        response.status()
977
1
    );
978
1
}
979

            
980
#[tokio::test]
981
1
async fn test_transaction_table_with_all_pagination_params() {
982
1
    let app_state = create_test_app_state().await;
983
1
    let mock_user = create_mock_user();
984
1
    let jwt_auth = create_mock_jwt_auth(mock_user);
985

            
986
1
    let app = Router::new()
987
1
        .route(
988
1
            "/transaction/table",
989
1
            get(web::pages::transaction::list::transaction_table),
990
        )
991
1
        .layer(axum::middleware::from_fn_with_state(
992
1
            app_state.clone(),
993
1
            move |mut req: axum::http::Request<Body>, next: axum::middleware::Next| {
994
1
                let jwt_auth = jwt_auth.clone();
995
1
                async move {
996
1
                    req.extensions_mut().insert(jwt_auth);
997
1
                    next.run(req).await
998
1
                }
999
1
            },
        ))
1
        .with_state(app_state.clone());
1
    let response = app
1
        .oneshot(
1
            Request::builder()
1
                .method("GET")
1
                .uri("/transaction/table?limit=50&page=3&date_from=2024-01-01&date_to=2024-12-31")
1
                .body(Body::empty())
1
                .unwrap(),
1
        )
1
        .await
1
        .unwrap();
1
    assert!(
1
        response.status().is_success() || response.status().is_server_error(),
1
        "Expected success or server error for all pagination params, got: {}",
1
        response.status()
1
    );
1
}
#[tokio::test]
1
async fn test_transaction_table_with_pagination_and_account() {
1
    let app_state = create_test_app_state().await;
1
    let mock_user = create_mock_user();
1
    let jwt_auth = create_mock_jwt_auth(mock_user);
1
    let app = Router::new()
1
        .route(
1
            "/transaction/table",
1
            get(web::pages::transaction::list::transaction_table),
        )
1
        .layer(axum::middleware::from_fn_with_state(
1
            app_state.clone(),
1
            move |mut req: axum::http::Request<Body>, next: axum::middleware::Next| {
1
                let jwt_auth = jwt_auth.clone();
1
                async move {
1
                    req.extensions_mut().insert(jwt_auth);
1
                    next.run(req).await
1
                }
1
            },
        ))
1
        .with_state(app_state.clone());
1
    let response = app
1
        .oneshot(
1
            Request::builder()
1
                .method("GET")
1
                .uri("/transaction/table?account=550e8400-e29b-41d4-a716-446655440000&limit=20&page=1&date_from=2024-01-01&date_to=2024-12-31")
1
                .body(Body::empty())
1
                .unwrap(),
1
        )
1
        .await
1
        .unwrap();
1
    assert!(
1
        response.status().is_success() || response.status().is_server_error(),
1
        "Expected success or server error for pagination with account filter, got: {}",
1
        response.status()
1
    );
1
}
#[tokio::test]
1
async fn test_transaction_table_with_invalid_limit() {
1
    let app_state = create_test_app_state().await;
1
    let mock_user = create_mock_user();
1
    let jwt_auth = create_mock_jwt_auth(mock_user);
1
    let app = Router::new()
1
        .route(
1
            "/transaction/table",
1
            get(web::pages::transaction::list::transaction_table),
        )
1
        .layer(axum::middleware::from_fn_with_state(
1
            app_state.clone(),
1
            move |mut req: axum::http::Request<Body>, next: axum::middleware::Next| {
1
                let jwt_auth = jwt_auth.clone();
1
                async move {
1
                    req.extensions_mut().insert(jwt_auth);
1
                    next.run(req).await
1
                }
1
            },
        ))
1
        .with_state(app_state.clone());
1
    let response = app
1
        .oneshot(
1
            Request::builder()
1
                .method("GET")
1
                .uri("/transaction/table?limit=invalid")
1
                .body(Body::empty())
1
                .unwrap(),
1
        )
1
        .await
1
        .unwrap();
    // Invalid limit should cause parameter parsing error (400) or be handled gracefully
1
    assert!(
1
        response.status() == StatusCode::BAD_REQUEST || response.status().is_server_error(),
1
        "Expected bad request or server error for invalid limit, got: {}",
1
        response.status()
1
    );
1
}
#[tokio::test]
1
async fn test_transaction_table_with_invalid_page() {
1
    let app_state = create_test_app_state().await;
1
    let mock_user = create_mock_user();
1
    let jwt_auth = create_mock_jwt_auth(mock_user);
1
    let app = Router::new()
1
        .route(
1
            "/transaction/table",
1
            get(web::pages::transaction::list::transaction_table),
        )
1
        .layer(axum::middleware::from_fn_with_state(
1
            app_state.clone(),
1
            move |mut req: axum::http::Request<Body>, next: axum::middleware::Next| {
1
                let jwt_auth = jwt_auth.clone();
1
                async move {
1
                    req.extensions_mut().insert(jwt_auth);
1
                    next.run(req).await
1
                }
1
            },
        ))
1
        .with_state(app_state.clone());
1
    let response = app
1
        .oneshot(
1
            Request::builder()
1
                .method("GET")
1
                .uri("/transaction/table?page=not_a_number")
1
                .body(Body::empty())
1
                .unwrap(),
1
        )
1
        .await
1
        .unwrap();
    // Invalid page should cause parameter parsing error (400) or be handled gracefully
1
    assert!(
1
        response.status() == StatusCode::BAD_REQUEST || response.status().is_server_error(),
1
        "Expected bad request or server error for invalid page, got: {}",
1
        response.status()
1
    );
1
}