summaryrefslogtreecommitdiff
path: root/tests/FundLab.Api.Tests/ApiTests.fs
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-21 01:40:10 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-21 01:40:10 +0800
commitbe1afd248e4cc9b03a1e21090602a945af6988b2 (patch)
treed31bd6e6c1736ab4ad784b52ebc5010ade074439 /tests/FundLab.Api.Tests/ApiTests.fs
parent2ada70d6467aec11b45328112d598454ca50f2d6 (diff)
downloadfund-lab-be1afd248e4cc9b03a1e21090602a945af6988b2.tar.gz
feat(app): 接入 AKShare 行情采集、净值查询与前端净值曲线
[变更性质] - 本提交完成 3b 行情闭环:真实 AKShare 搜索与净值采集、PostgreSQL 持久化、鉴权 API 与 Fable 前端净值曲线展示。 [新增功能] - API 新增 /api/instruments/search、/api/instruments/{code}/nav 与 nav/refresh 接口,经 Bearer 鉴权调用 AKShare Python 采集器并幂等落库。 - 前端提供基金搜索、精确代码选择、历史净值刷新/重读与 SVG 折线图,token 变更即清空私有结果并失效在途请求。 [实现方案] - MarketData 以 requiredProperty 校验采集器 payload,净值观测按 (code, nav_date) 幂等 upsert 并保留来源与哈希。 - 前端以 requestId 序列守卫 SearchCompleted/SearchFailed/NavCompleted/NavFailed,TokenChanged 同时递增 searchSeq/navSeq 拒绝过期响应;边界解码兼容 F# option 的 {"case":"Some"} 线格式。 [影响范围] - 新增 FundLab.Web.Tests(边界解码、序列失效、图表纯函数共 8 项),扩展 API 测试至 20 项;vite dev 代理 /api 至本地 API。 - 真实 AKShare 端到端依赖本机 akshare 环境,由负责人另行验收;本提交不包含 docs/overnight-progress.md 的现有修改。
Diffstat (limited to 'tests/FundLab.Api.Tests/ApiTests.fs')
-rw-r--r--tests/FundLab.Api.Tests/ApiTests.fs66
1 files changed, 66 insertions, 0 deletions
diff --git a/tests/FundLab.Api.Tests/ApiTests.fs b/tests/FundLab.Api.Tests/ApiTests.fs
index a46d330..1aaab6b 100644
--- a/tests/FundLab.Api.Tests/ApiTests.fs
+++ b/tests/FundLab.Api.Tests/ApiTests.fs
@@ -2,6 +2,7 @@ namespace FundLab.Api.Tests
module ApiTests =
+ open System
open Xunit
open FundLab.Api
@@ -22,3 +23,68 @@ module ApiTests =
Assert.Contains("\"status\":\"ok\"", payload)
Assert.DoesNotContain("secret", payload)
+
+ [<Fact>]
+ let ``AKShare NAV payload keeps missing publication time empty`` () =
+ let payload =
+ """
+ {
+ "schema_version": "fund-lab.akshare.v1",
+ "operation": "nav",
+ "source": "akshare",
+ "source_revision": "akshare-1.18.96/eastmoney",
+ "collected_at": "2026-09-21T10:00:00Z",
+ "instrument": { "code": "000001" },
+ "observations": [
+ {
+ "nav_date": "2001-12-18",
+ "published_at": null,
+ "nav": "1.00000000",
+ "accumulated_nav": null,
+ "daily_return": "0.00"
+ }
+ ]
+ }
+ """
+
+ match MarketData.parseNavPayload payload with
+ | Ok parsed ->
+ let observation = parsed.Observations |> List.head
+ Assert.Equal("000001", parsed.Code)
+ Assert.Equal(DateOnly(2001, 12, 18), observation.NavDate)
+ Assert.Equal(None, observation.PublishedAt)
+ Assert.Equal(None, observation.AccumulatedNav)
+ Assert.Equal(Some 0.00m, observation.DailyReturn)
+ Assert.Equal(1.00000000m, observation.Nav)
+ | Error message ->
+ Assert.Fail(sprintf "expected valid NAV payload, got %s" message)
+
+ [<Fact>]
+ let ``AKShare search payload preserves exact fund code and source`` () =
+ let payload =
+ """
+ {
+ "schema_version": "fund-lab.akshare.v1",
+ "operation": "search",
+ "source": "akshare",
+ "source_revision": "akshare-1.18.96/eastmoney",
+ "collected_at": "2026-09-21T10:00:00Z",
+ "instruments": [
+ {
+ "code": "000001",
+ "name": "华夏成长混合",
+ "fund_type": "混合型-灵活"
+ }
+ ]
+ }
+ """
+
+ match MarketData.parseSearchPayload payload with
+ | Ok parsed ->
+ let instrument = parsed.Instruments |> List.head
+ Assert.Equal("000001", instrument.Code)
+ Assert.Equal("华夏成长混合", instrument.Name)
+ Assert.Equal(Some "混合型-灵活", instrument.FundType)
+ Assert.Equal("akshare", parsed.Source)
+ | Error message ->
+ Assert.Fail(sprintf "expected valid search payload, got %s" message)