返回 DeepSeek-Reasonix
costquote_order_test.go
根目录 / internal / boot / costquote_order_test.go
1 package boot
2
3 import (
4 "sync"
5 "testing"
6
7 "reasonix/internal/billing"
8 "reasonix/internal/event"
9 "reasonix/internal/provider"
10 )
11
12 // TestCostQuoteReachesInnerSinkBeforeRecording documents the required wrap
13 // order: CostQuote fills e.CostQuote before the frontend (and any recorder
14 // wrapped inside it) observe the Usage event.
15 func TestCostQuoteReachesInnerSinkBeforeRecording(t *testing.T) {
16 var (
17 mu sync.Mutex
18 sawQuote bool
19 sawOriginal string
20 )
21 // Spy stands in for stats.Recorder + frontend: production wraps
22 // CostQuote(Recorder(frontend)), so the inner sink always sees CostQuote.
23 inner := event.FuncSink(func(e event.Event) {
24 if e.Kind != event.Usage {
25 return
26 }
27 mu.Lock()
28 sawQuote = e.CostQuote != nil && !e.CostQuote.Original.IsZero()
29 if e.CostQuote != nil {
30 sawOriginal = e.CostQuote.Original.Amount
31 }
32 mu.Unlock()
33 })
34 quoted := event.NewCostQuoteSink(inner, &event.QuoteContext{DisplayCurrency: "USD"})
35 quoted.Emit(event.Event{
36 Kind: event.Usage,
37 ModelRef: "deepseek-flash/deepseek-v4-flash",
38 Usage: &provider.Usage{PromptTokens: 1_000_000, CompletionTokens: 0, TotalTokens: 1_000_000},
39 Pricing: &provider.Pricing{Input: 0.14, Output: 0.28, Currency: "$"},
40 })
41 mu.Lock()
42 ok, amount := sawQuote, sawOriginal
43 mu.Unlock()
44 if !ok {
45 t.Fatal("inner sink did not receive CostQuote")
46 }
47 if amount != "0.14" {
48 t.Fatalf("original amount = %q, want 0.14", amount)
49 }
50
51 // Official dual-table path: CNY list price values USD without FX.
52 var basis string
53 quoted2 := event.NewCostQuoteSink(event.FuncSink(func(e event.Event) {
54 if e.CostQuote != nil {
55 if v, ok := e.CostQuote.Valuations["USD"]; ok {
56 basis = v.Basis
57 }
58 }
59 }), &event.QuoteContext{DisplayCurrency: "USD"})
60 quoted2.Emit(event.Event{
61 Kind: event.Usage,
62 ModelRef: "deepseek-flash/deepseek-v4-flash",
63 Usage: &provider.Usage{PromptTokens: 1_000_000, CompletionTokens: 1_000_000, TotalTokens: 2_000_000},
64 Pricing: &provider.Pricing{CacheHit: 0.10, Input: 3, Output: 9, Currency: "¥"},
65 })
66 if basis != billing.BasisOfficialTable {
67 t.Fatalf("USD valuation basis = %q, want official_table", basis)
68 }
69 }
70
71 func TestCostQuoteUsesConfiguredBillingMode(t *testing.T) {
72 ctx := &event.QuoteContext{
73 DisplayCurrency: "CNY",
74 BillingModeForModel: func(modelRef string) string {
75 if modelRef == "custom/mimo-v2.5-pro" {
76 return billing.BillingModeSubscriptionEquivalent
77 }
78 return ""
79 },
80 }
81 var got string
82 sink := event.NewCostQuoteSink(event.FuncSink(func(e event.Event) {
83 if e.CostQuote != nil {
84 got = e.CostQuote.BillingMode
85 }
86 }), ctx)
87 sink.Emit(event.Event{
88 Kind: event.Usage, ModelRef: "custom/mimo-v2.5-pro",
89 Usage: &provider.Usage{PromptTokens: 1_000_000, TotalTokens: 1_000_000},
90 Pricing: &provider.Pricing{Input: 1, Currency: "CNY"},
91 })
92 if got != billing.BillingModeSubscriptionEquivalent {
93 t.Fatalf("billing mode = %q, want subscription_equivalent", got)
94 }
95 }
96
96 lines GO