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
3
async fn test_transaction_create_without_auth() {
14
2
    let app_state = create_test_app_state().await;
15
2
    let app = Router::new()
16
2
        .route(
17
2
            "/transaction/create/submit",
18
2
            post(web::pages::transaction::create::submit::transaction_submit),
19
        )
20
2
        .with_state(app_state);
21

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

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

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

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

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

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

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

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

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

            
113
2
    let response = app
114
2
        .oneshot(
115
2
            Request::builder()
116
2
                .method("POST")
117
2
                .uri("/transaction/create/submit")
118
2
                .header("content-type", "application/json")
119
2
                .body(Body::from(transaction_data.to_string()))
120
2
                .unwrap(),
121
2
        )
122
2
        .await
123
2
        .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
2
    assert!(
130
2
        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
3
    if let Some(content_type) = response.headers().get("content-type") {
137
3
        let content_type_str = content_type.to_str().unwrap_or("");
138
2
        // Should be text (for success message) or JSON (for error response)
139
3
        assert!(
140
3
            content_type_str.contains("text/") || content_type_str.contains("application/json"),
141
2
            "Unexpected content type: {}",
142
2
            content_type_str
143
2
        );
144
2
    }
145
2
}
146

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
536
// ListTransactions web tests
537

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

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

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

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

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

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

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

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

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

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

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

            
644
#[tokio::test]
645
3
async fn test_transaction_table_with_account_filter() {
646
2
    let app_state = create_test_app_state().await;
647
2
    let mock_user = create_mock_user();
648
2
    let jwt_auth = create_mock_jwt_auth(mock_user);
649

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

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

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

            
687
#[tokio::test]
688
3
async fn test_transaction_table_with_invalid_account_filter() {
689
2
    let app_state = create_test_app_state().await;
690
2
    let mock_user = create_mock_user();
691
2
    let jwt_auth = create_mock_jwt_auth(mock_user);
692

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

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

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

            
730
#[tokio::test]
731
3
async fn test_transaction_table_empty_account_filter() {
732
2
    let app_state = create_test_app_state().await;
733
2
    let mock_user = create_mock_user();
734
2
    let jwt_auth = create_mock_jwt_auth(mock_user);
735

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

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

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

            
773
#[tokio::test]
774
3
async fn test_transaction_table_multiple_parameters() {
775
2
    let app_state = create_test_app_state().await;
776
2
    let mock_user = create_mock_user();
777
2
    let jwt_auth = create_mock_jwt_auth(mock_user);
778

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

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

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