mirror of
https://github.com/shawnkim1997/All-in-one-Financial-Analysis.git
synced 2026-08-14 19:08:06 +00:00
Complete migration from Streamlit to Next.js 14 App Router + FastAPI backend. Frontend (Next.js 14): - 10 pages: Overview, Research, Valuation, Technical, Markets, Earnings, News, Portfolio, Filings, Settings - Terminal Noir dark theme with custom Tailwind config - TradingView Lightweight Charts for candlestick/volume - Valuation: DCF, Sensitivity Matrix, Monte Carlo, Tornado, Reverse DCF - Financial Statements table with YoY growth badges and margin rows - SEC EDGAR inline filing viewer with section tabs - News split-view with iframe article embedding - Technical Analysis with RSI, MACD, Bollinger, Fibonacci, Moving Averages - Earnings beat/miss visualization - AI Copilot chat panel with Gemini integration Backend (FastAPI): - 13 routers: market_data, financials, valuation, technical, earnings, insider, edgar, news, portfolio, analysis, chat, estimates, fx - Services: DCF engine, Monte Carlo simulation, sensitivity analysis, risk metrics, SEC parser, technical indicators - yfinance + yahooquery data sources with fallback pattern - SQLite caching layer Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
144 lines
4.0 KiB
Python
144 lines
4.0 KiB
Python
"""Tests for server.utils.safe_float -- edge cases and boundary conditions."""
|
|
|
|
import math
|
|
|
|
import pandas as pd
|
|
import pytest
|
|
|
|
from server.utils.safe_float import _safe_float, _na, _format_shares_display
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _safe_float
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestSafeFloat:
|
|
"""Exhaustive edge-case coverage for _safe_float."""
|
|
|
|
def test_none_returns_none(self):
|
|
assert _safe_float(None) is None
|
|
|
|
def test_nan_float_returns_none(self):
|
|
assert _safe_float(float("nan")) is None
|
|
|
|
def test_math_nan_returns_none(self):
|
|
assert _safe_float(math.nan) is None
|
|
|
|
def test_pandas_na_returns_none(self):
|
|
assert _safe_float(pd.NA) is None
|
|
|
|
def test_pandas_nat_returns_none(self):
|
|
assert _safe_float(pd.NaT) is None
|
|
|
|
def test_int_converts(self):
|
|
assert _safe_float(42) == 42.0
|
|
|
|
def test_float_passthrough(self):
|
|
assert _safe_float(3.14) == 3.14
|
|
|
|
def test_negative_float(self):
|
|
assert _safe_float(-9.99) == -9.99
|
|
|
|
def test_zero(self):
|
|
assert _safe_float(0) == 0.0
|
|
|
|
def test_string_numeric(self):
|
|
assert _safe_float("123.45") == 123.45
|
|
|
|
def test_string_negative(self):
|
|
assert _safe_float("-7.5") == -7.5
|
|
|
|
def test_string_non_numeric_returns_none(self):
|
|
assert _safe_float("hello") is None
|
|
|
|
def test_empty_string_returns_none(self):
|
|
assert _safe_float("") is None
|
|
|
|
def test_bool_true(self):
|
|
# bool is subclass of int; float(True) == 1.0
|
|
assert _safe_float(True) == 1.0
|
|
|
|
def test_bool_false(self):
|
|
assert _safe_float(False) == 0.0
|
|
|
|
def test_inf_positive(self):
|
|
result = _safe_float(float("inf"))
|
|
assert result == float("inf")
|
|
|
|
def test_inf_negative(self):
|
|
result = _safe_float(float("-inf"))
|
|
assert result == float("-inf")
|
|
|
|
def test_large_number(self):
|
|
assert _safe_float(1e18) == 1e18
|
|
|
|
def test_very_small_number(self):
|
|
assert _safe_float(1e-15) == pytest.approx(1e-15)
|
|
|
|
def test_object_returns_none(self):
|
|
assert _safe_float(object()) is None
|
|
|
|
def test_list_returns_none(self):
|
|
assert _safe_float([1, 2, 3]) is None
|
|
|
|
def test_dict_returns_none(self):
|
|
assert _safe_float({"a": 1}) is None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _na
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestNa:
|
|
"""Tests for the _na display helper."""
|
|
|
|
def test_none_returns_na_string(self):
|
|
assert _na(None) == "N/A"
|
|
|
|
def test_nan_returns_na_string(self):
|
|
assert _na(float("nan")) == "N/A"
|
|
|
|
def test_valid_float_passthrough(self):
|
|
assert _na(3.14) == 3.14
|
|
|
|
def test_zero_passthrough(self):
|
|
assert _na(0) == 0
|
|
|
|
def test_string_passthrough(self):
|
|
assert _na("hello") == "hello"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _format_shares_display
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestFormatSharesDisplay:
|
|
"""Tests for the _format_shares_display utility."""
|
|
|
|
def test_none_returns_na(self):
|
|
assert _format_shares_display(None) == "N/A"
|
|
|
|
def test_zero_returns_na(self):
|
|
assert _format_shares_display(0) == "N/A"
|
|
|
|
def test_negative_returns_na(self):
|
|
assert _format_shares_display(-100) == "N/A"
|
|
|
|
def test_billions(self):
|
|
assert _format_shares_display(15_420_000_000) == "15.42B Shares"
|
|
|
|
def test_millions(self):
|
|
assert _format_shares_display(1_200_000) == "1.20M Shares"
|
|
|
|
def test_thousands(self):
|
|
assert _format_shares_display(5_500) == "5.50K Shares"
|
|
|
|
def test_small_number(self):
|
|
assert _format_shares_display(42) == "42 Shares"
|
|
|
|
def test_exact_billion(self):
|
|
assert _format_shares_display(1_000_000_000) == "1.00B Shares"
|
|
|
|
def test_exact_million(self):
|
|
assert _format_shares_display(1_000_000) == "1.00M Shares"
|