feat: 完成模拟交易闭环、App 与 A 股行情接入
Checks / verify (push) Failing after 9m52s

统一单入口 Go 工程与前端依赖目录,增加后台供应商配置、候选审核、计划预算、成交冲正、持仓复盘和站内通知。接入策略版本、Python 回测、可配置 A 股公开行情及 zap 文件轮转日志,补充迁移、契约与验证。
This commit is contained in:
2026-10-08 22:58:52 +08:00
parent bffdb3858b
commit 92329ddce2
145 changed files with 18593 additions and 587 deletions
+42
View File
@@ -0,0 +1,42 @@
import {test} from "node:test"
import assert from "node:assert/strict"
import {ApiError,createClient,normalizeBaseURL} from "../src/lib/api.ts"
test("API URL does not carry credentials or route state",()=>{
assert.equal(normalizeBaseURL(" https://api.example.com/ "),"https://api.example.com")
for(const value of ["","ftp://api.example.com","https://user:secret@api.example.com","https://api.example.com?token=secret","https://api.example.com/path"]){assert.throws(()=>normalizeBaseURL(value),ApiError)}
})
test("App uses its bearer token and the server confirmed envelope",async()=>{
let observed:RequestInit|undefined
const transport:typeof fetch=async(input,options)=>{assert.equal(input,"https://api.example.com/api/v1/accounts");observed=options;return new Response(JSON.stringify({data:[{cash_balance:"100000.01"}],meta:{request_id:"test"}}),{status:200})}
const client=createClient("https://api.example.com","app-token",undefined,transport)
const result=await client<Array<{cash_balance:string}>>("/api/v1/accounts",{method:"POST",body:{mode:"simulation",initial_cash:"100000.01"}})
assert.equal(result[0].cash_balance,"100000.01")
assert.equal(new Headers(observed?.headers).get("Authorization"),"Bearer app-token")
assert.equal(observed?.credentials,"omit");assert.equal(observed?.redirect,"error")
assert.equal(JSON.parse(observed?.body as string).initial_cash,"100000.01")
})
test("401 expires the session, infrastructure failures do not",async()=>{
let expired=0
for(const status of [503,401]){
const client=createClient("https://api.example.com","token",()=>expired++,async()=>new Response(JSON.stringify({error:{code:"TEST_FAILURE",message:"服务错误"}}),{status}))
await assert.rejects(()=>client("/api/v1/me"),(e:unknown)=>e instanceof ApiError&&e.status===status)
}
assert.equal(expired,1)
})
test("stale responses cannot expire or populate a newer session",async()=>{
let expired=0
const client=createClient("https://api.example.com","previous-token",()=>expired++,async()=>new Response(JSON.stringify({data:{id:"old-user"}}),{status:200}),()=>false)
await assert.rejects(()=>client("/api/v1/me"),(e:unknown)=>e instanceof ApiError&&e.code==="SESSION_CHANGED")
assert.equal(expired,0)
})
test("broken proxy response and private transport errors are sanitized",async()=>{
const client=createClient("https://api.example.com",undefined,undefined,async()=>new Response("<html>proxy</html>",{status:502}))
await assert.rejects(()=>client("/api/v1/me"),(e:unknown)=>e instanceof ApiError&&e.code==="INVALID_RESPONSE")
const failed=createClient("https://api.example.com",undefined,undefined,async()=>{throw new Error("private request details")})
await assert.rejects(()=>failed("/api/v1/me"),(e:unknown)=>e instanceof ApiError&&e.code==="NETWORK_ERROR"&&!e.message.includes("private"))
})
test("caller cancellation stays distinguishable from a network failure",async()=>{
const controller=new AbortController();controller.abort()
const transport:typeof fetch=async(_url,options)=>{assert.equal(options?.signal?.aborted,true);throw new DOMException("Aborted","AbortError")}
await assert.rejects(()=>createClient("https://api.example.com",undefined,undefined,transport)("/api/v1/accounts",{signal:controller.signal}),(e:unknown)=>e instanceof DOMException&&e.name==="AbortError")
})
+11
View File
@@ -0,0 +1,11 @@
import test from "node:test"
import assert from "node:assert/strict"
import {publicationAvailable} from "../src/lib/candidates.ts"
test("withdrawn, superseded and expired candidate links remain unavailable",()=>{
const now=Date.now(),candidate={status:"PUBLISHED",is_active_publication:true,valid_until:new Date(now+1000).toISOString()}
assert.equal(publicationAvailable(candidate,now),true)
assert.equal(publicationAvailable({...candidate,status:"WITHDRAWN"},now),false)
assert.equal(publicationAvailable({...candidate,is_active_publication:false},now),false)
assert.equal(publicationAvailable(candidate,now+1000),false)
assert.equal(publicationAvailable({...candidate,valid_until:"invalid"},now),false)
})
+13
View File
@@ -0,0 +1,13 @@
import {test} from "node:test"
import assert from "node:assert/strict"
import {formatMoney,validInitialCash} from "../src/lib/money.ts"
test("cash formatting preserves cents without floating point",()=>{
assert.equal(formatMoney("100000.01"),"100,000.01")
assert.equal(formatMoney("9007199254740993.09"),"9,007,199,254,740,993.09")
assert.equal(formatMoney(null),"未知")
assert.equal(formatMoney("invalid"),"数据异常")
})
test("initial cash validates decimal limits exactly",()=>{
for(const value of ["0.01","100000.01","1000000000.00"])assert.equal(validInitialCash(value),true,value)
for(const value of ["0","0.00","-1","1e4","1.001","1000000000.01","",".1"," 10"]){assert.equal(validInitialCash(value),false,value)}
})
+20
View File
@@ -0,0 +1,20 @@
import {test} from "node:test"
import assert from "node:assert/strict"
import {createStorageQueue,parseSession} from "../src/lib/session-data.ts"
const valid={access_token:"a".repeat(43),expires_at:"2026-12-01T10:00:00Z",user:{id:"7f8fd1ca-30e8-4dfb-82a9-8655fbd354ac",display_name:"测试用户",status:"ACTIVE"}}
test("session restoration requires a valid app credential and active identity",()=>{
assert.deepEqual(parseSession(valid),valid)
for(const value of [null,{}, {...valid,access_token:"admin cookie"},{...valid,expires_at:"invalid"},{...valid,user:{...valid.user,status:"DISABLED"}},{...valid,user:{...valid.user,id:"other"}}])assert.equal(parseSession(value),null)
})
test("ordered secret storage prevents expiration cleanup from erasing a newer login",async()=>{
const persist=createStorageQueue();let stored="old-session";const operations:string[]=[]
let release:()=>void=()=>{}
const barrier=new Promise<void>(resolve=>{release=resolve})
const clear=persist(async()=>{await barrier;stored="";operations.push("clear")})
const login=persist(async()=>{stored="new-session";operations.push("save")})
release();await Promise.all([clear,login]);assert.deepEqual(operations,["clear","save"]);assert.equal(stored,"new-session")
})
test("a failed storage operation does not prevent later recovery",async()=>{
const persist=createStorageQueue();await assert.rejects(persist(async()=>{throw new Error("storage failure")}))
let recovered=false;await persist(async()=>{recovered=true});assert.equal(recovered,true)
})