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
|
namespace FundLab.Api.Tests
module ApiTests =
open System
open Xunit
open FundLab.Api
[<Theory>]
[<InlineData("")>]
[<InlineData("Basic secret")>]
[<InlineData("Bearer wrong")>]
let ``missing or wrong bearer token is unauthorized`` header =
Assert.Equal(AuthDecision.Unauthorized, Authentication.authorize "secret" header)
[<Fact>]
let ``matching bearer token is authorized`` () =
Assert.Equal(AuthDecision.Authorized, Authentication.authorize "secret" "Bearer secret")
[<Fact>]
let ``health payload is non-sensitive`` () =
let payload = Health.payload "fund-lab-api"
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)
|