diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-21 01:40:10 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-21 01:40:10 +0800 |
| commit | be1afd248e4cc9b03a1e21090602a945af6988b2 (patch) | |
| tree | d31bd6e6c1736ab4ad784b52ebc5010ade074439 /tests/FundLab.Api.Tests/ApiTests.fs | |
| parent | 2ada70d6467aec11b45328112d598454ca50f2d6 (diff) | |
| download | fund-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.fs | 66 |
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) |
