finance-query 2.5.1

A Rust library for querying financial data
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
//! FMP financial statement endpoints.

use serde::{Deserialize, Serialize};

use crate::adapters::common::encode_path_segment;
use crate::error::Result;

use super::models::Period;

// ============================================================================
// Response types
// ============================================================================

/// Income statement from FMP.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct IncomeStatement {
    /// Filing date.
    pub date: Option<String>,
    /// Ticker symbol.
    pub symbol: Option<String>,
    /// Reporting period (annual/quarter).
    #[serde(rename = "reportedCurrency")]
    pub reported_currency: Option<String>,
    /// CIK number.
    pub cik: Option<String>,
    /// Filing date.
    #[serde(rename = "fillingDate")]
    pub filling_date: Option<String>,
    /// Accepted date.
    #[serde(rename = "acceptedDate")]
    pub accepted_date: Option<String>,
    /// Calendar year.
    #[serde(rename = "calendarYear")]
    pub calendar_year: Option<String>,
    /// Fiscal period (e.g., "Q1", "FY").
    pub period: Option<String>,
    /// Total revenue.
    pub revenue: Option<f64>,
    /// Cost of revenue.
    #[serde(rename = "costOfRevenue")]
    pub cost_of_revenue: Option<f64>,
    /// Gross profit.
    #[serde(rename = "grossProfit")]
    pub gross_profit: Option<f64>,
    /// Gross profit ratio.
    #[serde(rename = "grossProfitRatio")]
    pub gross_profit_ratio: Option<f64>,
    /// Research and development expenses.
    #[serde(rename = "researchAndDevelopmentExpenses")]
    pub research_and_development_expenses: Option<f64>,
    /// General and administrative expenses.
    #[serde(rename = "generalAndAdministrativeExpenses")]
    pub general_and_administrative_expenses: Option<f64>,
    /// Selling and marketing expenses.
    #[serde(rename = "sellingAndMarketingExpenses")]
    pub selling_and_marketing_expenses: Option<f64>,
    /// Selling, general and administrative expenses.
    #[serde(rename = "sellingGeneralAndAdministrativeExpenses")]
    pub selling_general_and_administrative_expenses: Option<f64>,
    /// Other expenses.
    #[serde(rename = "otherExpenses")]
    pub other_expenses: Option<f64>,
    /// Operating expenses.
    #[serde(rename = "operatingExpenses")]
    pub operating_expenses: Option<f64>,
    /// Cost and expenses.
    #[serde(rename = "costAndExpenses")]
    pub cost_and_expenses: Option<f64>,
    /// Interest income.
    #[serde(rename = "interestIncome")]
    pub interest_income: Option<f64>,
    /// Interest expense.
    #[serde(rename = "interestExpense")]
    pub interest_expense: Option<f64>,
    /// Depreciation and amortization.
    #[serde(rename = "depreciationAndAmortization")]
    pub depreciation_and_amortization: Option<f64>,
    /// EBITDA.
    pub ebitda: Option<f64>,
    /// EBITDA ratio.
    #[serde(rename = "ebitdaratio")]
    pub ebitda_ratio: Option<f64>,
    /// Operating income.
    #[serde(rename = "operatingIncome")]
    pub operating_income: Option<f64>,
    /// Operating income ratio.
    #[serde(rename = "operatingIncomeRatio")]
    pub operating_income_ratio: Option<f64>,
    /// Total other income/expenses net.
    #[serde(rename = "totalOtherIncomeExpensesNet")]
    pub total_other_income_expenses_net: Option<f64>,
    /// Income before tax.
    #[serde(rename = "incomeBeforeTax")]
    pub income_before_tax: Option<f64>,
    /// Income before tax ratio.
    #[serde(rename = "incomeBeforeTaxRatio")]
    pub income_before_tax_ratio: Option<f64>,
    /// Income tax expense.
    #[serde(rename = "incomeTaxExpense")]
    pub income_tax_expense: Option<f64>,
    /// Net income.
    #[serde(rename = "netIncome")]
    pub net_income: Option<f64>,
    /// Net income ratio.
    #[serde(rename = "netIncomeRatio")]
    pub net_income_ratio: Option<f64>,
    /// Earnings per share (basic).
    pub eps: Option<f64>,
    /// Earnings per share (diluted).
    #[serde(rename = "epsdiluted")]
    pub eps_diluted: Option<f64>,
    /// Weighted average shares outstanding.
    #[serde(rename = "weightedAverageShsOut")]
    pub weighted_average_shs_out: Option<f64>,
    /// Weighted average shares outstanding (diluted).
    #[serde(rename = "weightedAverageShsOutDil")]
    pub weighted_average_shs_out_dil: Option<f64>,
    /// Link to SEC filing.
    pub link: Option<String>,
    /// Final link to filing.
    #[serde(rename = "finalLink")]
    pub final_link: Option<String>,
}

/// Balance sheet statement from FMP.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct BalanceSheet {
    /// Filing date.
    pub date: Option<String>,
    /// Ticker symbol.
    pub symbol: Option<String>,
    /// Reported currency.
    #[serde(rename = "reportedCurrency")]
    pub reported_currency: Option<String>,
    /// CIK number.
    pub cik: Option<String>,
    /// Filing date.
    #[serde(rename = "fillingDate")]
    pub filling_date: Option<String>,
    /// Accepted date.
    #[serde(rename = "acceptedDate")]
    pub accepted_date: Option<String>,
    /// Calendar year.
    #[serde(rename = "calendarYear")]
    pub calendar_year: Option<String>,
    /// Fiscal period.
    pub period: Option<String>,
    /// Cash and cash equivalents.
    #[serde(rename = "cashAndCashEquivalents")]
    pub cash_and_cash_equivalents: Option<f64>,
    /// Short-term investments.
    #[serde(rename = "shortTermInvestments")]
    pub short_term_investments: Option<f64>,
    /// Cash and short-term investments.
    #[serde(rename = "cashAndShortTermInvestments")]
    pub cash_and_short_term_investments: Option<f64>,
    /// Net receivables.
    #[serde(rename = "netReceivables")]
    pub net_receivables: Option<f64>,
    /// Inventory.
    pub inventory: Option<f64>,
    /// Other current assets.
    #[serde(rename = "otherCurrentAssets")]
    pub other_current_assets: Option<f64>,
    /// Total current assets.
    #[serde(rename = "totalCurrentAssets")]
    pub total_current_assets: Option<f64>,
    /// Property, plant and equipment net.
    #[serde(rename = "propertyPlantEquipmentNet")]
    pub property_plant_equipment_net: Option<f64>,
    /// Goodwill.
    pub goodwill: Option<f64>,
    /// Intangible assets.
    #[serde(rename = "intangibleAssets")]
    pub intangible_assets: Option<f64>,
    /// Goodwill and intangible assets.
    #[serde(rename = "goodwillAndIntangibleAssets")]
    pub goodwill_and_intangible_assets: Option<f64>,
    /// Long-term investments.
    #[serde(rename = "longTermInvestments")]
    pub long_term_investments: Option<f64>,
    /// Tax assets.
    #[serde(rename = "taxAssets")]
    pub tax_assets: Option<f64>,
    /// Other non-current assets.
    #[serde(rename = "otherNonCurrentAssets")]
    pub other_non_current_assets: Option<f64>,
    /// Total non-current assets.
    #[serde(rename = "totalNonCurrentAssets")]
    pub total_non_current_assets: Option<f64>,
    /// Other assets.
    #[serde(rename = "otherAssets")]
    pub other_assets: Option<f64>,
    /// Total assets.
    #[serde(rename = "totalAssets")]
    pub total_assets: Option<f64>,
    /// Account payables.
    #[serde(rename = "accountPayables")]
    pub account_payables: Option<f64>,
    /// Short-term debt.
    #[serde(rename = "shortTermDebt")]
    pub short_term_debt: Option<f64>,
    /// Tax payables.
    #[serde(rename = "taxPayables")]
    pub tax_payables: Option<f64>,
    /// Deferred revenue.
    #[serde(rename = "deferredRevenue")]
    pub deferred_revenue: Option<f64>,
    /// Other current liabilities.
    #[serde(rename = "otherCurrentLiabilities")]
    pub other_current_liabilities: Option<f64>,
    /// Total current liabilities.
    #[serde(rename = "totalCurrentLiabilities")]
    pub total_current_liabilities: Option<f64>,
    /// Long-term debt.
    #[serde(rename = "longTermDebt")]
    pub long_term_debt: Option<f64>,
    /// Deferred revenue non-current.
    #[serde(rename = "deferredRevenueNonCurrent")]
    pub deferred_revenue_non_current: Option<f64>,
    /// Deferred tax liabilities non-current.
    #[serde(rename = "deferredTaxLiabilitiesNonCurrent")]
    pub deferred_tax_liabilities_non_current: Option<f64>,
    /// Other non-current liabilities.
    #[serde(rename = "otherNonCurrentLiabilities")]
    pub other_non_current_liabilities: Option<f64>,
    /// Total non-current liabilities.
    #[serde(rename = "totalNonCurrentLiabilities")]
    pub total_non_current_liabilities: Option<f64>,
    /// Other liabilities.
    #[serde(rename = "otherLiabilities")]
    pub other_liabilities: Option<f64>,
    /// Capital lease obligations.
    #[serde(rename = "capitalLeaseObligations")]
    pub capital_lease_obligations: Option<f64>,
    /// Total liabilities.
    #[serde(rename = "totalLiabilities")]
    pub total_liabilities: Option<f64>,
    /// Preferred stock.
    #[serde(rename = "preferredStock")]
    pub preferred_stock: Option<f64>,
    /// Common stock.
    #[serde(rename = "commonStock")]
    pub common_stock: Option<f64>,
    /// Retained earnings.
    #[serde(rename = "retainedEarnings")]
    pub retained_earnings: Option<f64>,
    /// Accumulated other comprehensive income/loss.
    #[serde(rename = "accumulatedOtherComprehensiveIncomeLoss")]
    pub accumulated_other_comprehensive_income_loss: Option<f64>,
    /// Other total stockholders equity.
    #[serde(rename = "othertotalStockholdersEquity")]
    pub other_total_stockholders_equity: Option<f64>,
    /// Total stockholders equity.
    #[serde(rename = "totalStockholdersEquity")]
    pub total_stockholders_equity: Option<f64>,
    /// Total equity.
    #[serde(rename = "totalEquity")]
    pub total_equity: Option<f64>,
    /// Total liabilities and stockholders equity.
    #[serde(rename = "totalLiabilitiesAndStockholdersEquity")]
    pub total_liabilities_and_stockholders_equity: Option<f64>,
    /// Minority interest.
    #[serde(rename = "minorityInterest")]
    pub minority_interest: Option<f64>,
    /// Total liabilities and total equity.
    #[serde(rename = "totalLiabilitiesAndTotalEquity")]
    pub total_liabilities_and_total_equity: Option<f64>,
    /// Total investments.
    #[serde(rename = "totalInvestments")]
    pub total_investments: Option<f64>,
    /// Total debt.
    #[serde(rename = "totalDebt")]
    pub total_debt: Option<f64>,
    /// Net debt.
    #[serde(rename = "netDebt")]
    pub net_debt: Option<f64>,
    /// Link to SEC filing.
    pub link: Option<String>,
    /// Final link to filing.
    #[serde(rename = "finalLink")]
    pub final_link: Option<String>,
}

/// Cash flow statement from FMP.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct CashFlow {
    /// Filing date.
    pub date: Option<String>,
    /// Ticker symbol.
    pub symbol: Option<String>,
    /// Reported currency.
    #[serde(rename = "reportedCurrency")]
    pub reported_currency: Option<String>,
    /// CIK number.
    pub cik: Option<String>,
    /// Filing date.
    #[serde(rename = "fillingDate")]
    pub filling_date: Option<String>,
    /// Accepted date.
    #[serde(rename = "acceptedDate")]
    pub accepted_date: Option<String>,
    /// Calendar year.
    #[serde(rename = "calendarYear")]
    pub calendar_year: Option<String>,
    /// Fiscal period.
    pub period: Option<String>,
    /// Net income.
    #[serde(rename = "netIncome")]
    pub net_income: Option<f64>,
    /// Depreciation and amortization.
    #[serde(rename = "depreciationAndAmortization")]
    pub depreciation_and_amortization: Option<f64>,
    /// Deferred income tax.
    #[serde(rename = "deferredIncomeTax")]
    pub deferred_income_tax: Option<f64>,
    /// Stock-based compensation.
    #[serde(rename = "stockBasedCompensation")]
    pub stock_based_compensation: Option<f64>,
    /// Change in working capital.
    #[serde(rename = "changeInWorkingCapital")]
    pub change_in_working_capital: Option<f64>,
    /// Accounts receivables.
    #[serde(rename = "accountsReceivables")]
    pub accounts_receivables: Option<f64>,
    /// Inventory.
    pub inventory: Option<f64>,
    /// Accounts payables.
    #[serde(rename = "accountsPayables")]
    pub accounts_payables: Option<f64>,
    /// Other working capital.
    #[serde(rename = "otherWorkingCapital")]
    pub other_working_capital: Option<f64>,
    /// Other non-cash items.
    #[serde(rename = "otherNonCashItems")]
    pub other_non_cash_items: Option<f64>,
    /// Net cash provided by operating activities.
    #[serde(rename = "netCashProvidedByOperatingActivities")]
    pub net_cash_provided_by_operating_activities: Option<f64>,
    /// Investments in property, plant and equipment.
    #[serde(rename = "investmentsInPropertyPlantAndEquipment")]
    pub investments_in_property_plant_and_equipment: Option<f64>,
    /// Acquisitions net.
    #[serde(rename = "acquisitionsNet")]
    pub acquisitions_net: Option<f64>,
    /// Purchases of investments.
    #[serde(rename = "purchasesOfInvestments")]
    pub purchases_of_investments: Option<f64>,
    /// Sales/maturities of investments.
    #[serde(rename = "salesMaturitiesOfInvestments")]
    pub sales_maturities_of_investments: Option<f64>,
    /// Other investing activities.
    #[serde(rename = "otherInvestingActivites")]
    pub other_investing_activities: Option<f64>,
    /// Net cash used for investing activities.
    #[serde(rename = "netCashUsedForInvestingActivites")]
    pub net_cash_used_for_investing_activities: Option<f64>,
    /// Debt repayment.
    #[serde(rename = "debtRepayment")]
    pub debt_repayment: Option<f64>,
    /// Common stock issued.
    #[serde(rename = "commonStockIssued")]
    pub common_stock_issued: Option<f64>,
    /// Common stock repurchased.
    #[serde(rename = "commonStockRepurchased")]
    pub common_stock_repurchased: Option<f64>,
    /// Dividends paid.
    #[serde(rename = "dividendsPaid")]
    pub dividends_paid: Option<f64>,
    /// Other financing activities.
    #[serde(rename = "otherFinancingActivites")]
    pub other_financing_activities: Option<f64>,
    /// Net cash used/provided by financing activities.
    #[serde(rename = "netCashUsedProvidedByFinancingActivities")]
    pub net_cash_used_provided_by_financing_activities: Option<f64>,
    /// Effect of forex changes on cash.
    #[serde(rename = "effectOfForexChangesOnCash")]
    pub effect_of_forex_changes_on_cash: Option<f64>,
    /// Net change in cash.
    #[serde(rename = "netChangeInCash")]
    pub net_change_in_cash: Option<f64>,
    /// Cash at end of period.
    #[serde(rename = "cashAtEndOfPeriod")]
    pub cash_at_end_of_period: Option<f64>,
    /// Cash at beginning of period.
    #[serde(rename = "cashAtBeginningOfPeriod")]
    pub cash_at_beginning_of_period: Option<f64>,
    /// Operating cash flow.
    #[serde(rename = "operatingCashFlow")]
    pub operating_cash_flow: Option<f64>,
    /// Capital expenditure.
    #[serde(rename = "capitalExpenditure")]
    pub capital_expenditure: Option<f64>,
    /// Free cash flow.
    #[serde(rename = "freeCashFlow")]
    pub free_cash_flow: Option<f64>,
    /// Link to SEC filing.
    pub link: Option<String>,
    /// Final link to filing.
    #[serde(rename = "finalLink")]
    pub final_link: Option<String>,
}

// ============================================================================
// Query functions
// ============================================================================

/// Fetch income statements for a symbol.
///
/// Returns quarterly or annual income statements. FMP returns an array.
pub async fn income_statement(
    symbol: &str,
    period: Period,
    limit: Option<u32>,
) -> Result<Vec<IncomeStatement>> {
    let client = super::build_client()?;
    let limit_str = limit.unwrap_or(4).to_string();
    client
        .get(
            &format!("/api/v3/income-statement/{}", encode_path_segment(symbol)),
            &[("period", period.as_str()), ("limit", &limit_str)],
        )
        .await
}

/// Fetch balance sheet statements for a symbol.
pub async fn balance_sheet(
    symbol: &str,
    period: Period,
    limit: Option<u32>,
) -> Result<Vec<BalanceSheet>> {
    let client = super::build_client()?;
    let limit_str = limit.unwrap_or(4).to_string();
    client
        .get(
            &format!(
                "/api/v3/balance-sheet-statement/{}",
                encode_path_segment(symbol)
            ),
            &[("period", period.as_str()), ("limit", &limit_str)],
        )
        .await
}

/// Fetch cash flow statements for a symbol.
pub async fn cash_flow(symbol: &str, period: Period, limit: Option<u32>) -> Result<Vec<CashFlow>> {
    let client = super::build_client()?;
    let limit_str = limit.unwrap_or(4).to_string();
    client
        .get(
            &format!(
                "/api/v3/cash-flow-statement/{}",
                encode_path_segment(symbol)
            ),
            &[("period", period.as_str()), ("limit", &limit_str)],
        )
        .await
}

/// Fetch income statements as reported for a symbol.
pub async fn income_statement_as_reported(
    symbol: &str,
    period: Period,
    limit: Option<u32>,
) -> Result<Vec<serde_json::Value>> {
    let client = super::build_client()?;
    let limit_str = limit.unwrap_or(4).to_string();
    client
        .get(
            &format!(
                "/api/v3/income-statement-as-reported/{}",
                encode_path_segment(symbol)
            ),
            &[("period", period.as_str()), ("limit", &limit_str)],
        )
        .await
}

/// Fetch balance sheet statements as reported for a symbol.
pub async fn balance_sheet_as_reported(
    symbol: &str,
    period: Period,
    limit: Option<u32>,
) -> Result<Vec<serde_json::Value>> {
    let client = super::build_client()?;
    let limit_str = limit.unwrap_or(4).to_string();
    client
        .get(
            &format!(
                "/api/v3/balance-sheet-statement-as-reported/{}",
                encode_path_segment(symbol)
            ),
            &[("period", period.as_str()), ("limit", &limit_str)],
        )
        .await
}

/// Fetch cash flow statements as reported for a symbol.
pub async fn cash_flow_as_reported(
    symbol: &str,
    period: Period,
    limit: Option<u32>,
) -> Result<Vec<serde_json::Value>> {
    let client = super::build_client()?;
    let limit_str = limit.unwrap_or(4).to_string();
    client
        .get(
            &format!(
                "/api/v3/cash-flow-statement-as-reported/{}",
                encode_path_segment(symbol)
            ),
            &[("period", period.as_str()), ("limit", &limit_str)],
        )
        .await
}

/// Fetch full financial statement as reported for a symbol.
pub async fn full_financial_statement(
    symbol: &str,
    period: Period,
) -> Result<Vec<serde_json::Value>> {
    let client = super::build_client()?;
    client
        .get(
            &format!(
                "/api/v3/financial-statement-full-as-reported/{}",
                encode_path_segment(symbol)
            ),
            &[("period", period.as_str())],
        )
        .await
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_income_statement_mock() {
        let mut server = mockito::Server::new_async().await;
        let _mock = server
            .mock("GET", "/api/v3/income-statement/AAPL")
            .match_query(mockito::Matcher::AllOf(vec![
                mockito::Matcher::UrlEncoded("apikey".into(), "test-key".into()),
                mockito::Matcher::UrlEncoded("period".into(), "quarter".into()),
                mockito::Matcher::UrlEncoded("limit".into(), "2".into()),
            ]))
            .with_status(200)
            .with_body(
                serde_json::json!([{
                    "date": "2024-03-30",
                    "symbol": "AAPL",
                    "reportedCurrency": "USD",
                    "calendarYear": "2024",
                    "period": "Q2",
                    "revenue": 90753000000.0,
                    "costOfRevenue": 49141000000.0,
                    "grossProfit": 41612000000.0,
                    "netIncome": 23636000000.0,
                    "eps": 1.53,
                    "epsdiluted": 1.52
                }])
                .to_string(),
            )
            .create_async()
            .await;

        let client = super::super::build_test_client(&server.url()).unwrap();
        let result: Vec<IncomeStatement> = client
            .get(
                "/api/v3/income-statement/AAPL",
                &[("period", "quarter"), ("limit", "2")],
            )
            .await
            .unwrap();

        assert_eq!(result.len(), 1);
        assert_eq!(result[0].symbol.as_deref(), Some("AAPL"));
        assert_eq!(result[0].revenue, Some(90753000000.0));
        assert_eq!(result[0].eps, Some(1.53));
    }

    #[tokio::test]
    async fn test_balance_sheet_mock() {
        let mut server = mockito::Server::new_async().await;
        let _mock = server
            .mock("GET", "/api/v3/balance-sheet-statement/AAPL")
            .match_query(mockito::Matcher::AllOf(vec![
                mockito::Matcher::UrlEncoded("apikey".into(), "test-key".into()),
                mockito::Matcher::UrlEncoded("period".into(), "annual".into()),
                mockito::Matcher::UrlEncoded("limit".into(), "1".into()),
            ]))
            .with_status(200)
            .with_body(
                serde_json::json!([{
                    "date": "2024-09-28",
                    "symbol": "AAPL",
                    "totalAssets": 364980000000.0,
                    "totalLiabilities": 308030000000.0,
                    "totalStockholdersEquity": 56950000000.0,
                    "cashAndCashEquivalents": 29943000000.0
                }])
                .to_string(),
            )
            .create_async()
            .await;

        let client = super::super::build_test_client(&server.url()).unwrap();
        let result: Vec<BalanceSheet> = client
            .get(
                "/api/v3/balance-sheet-statement/AAPL",
                &[("period", "annual"), ("limit", "1")],
            )
            .await
            .unwrap();

        assert_eq!(result.len(), 1);
        assert_eq!(result[0].total_assets, Some(364980000000.0));
        assert_eq!(result[0].total_stockholders_equity, Some(56950000000.0));
    }
}