修项目成本按天接口在 Postgres 上的 Decimal 序列化崩溃

2026-10-26 项目成本总览实弹:总览一次通过,但 ?day= 请求让
服务端请求线程直接死掉、客户端只看到连接断开。日志根因:
Postgres 的 SUM(bigint) 返回 NUMERIC,psycopg2 映成 Decimal,
按天分支把 SUM 原值塞进 JSON 时 json.dumps 抛 TypeError。
这是按天路径的潜 bug(此前从未在生产 PG 上跑通过按天查询),
不是 PR #45 新加列引入的,但本次实弹把它暴露了出来。
修复:按天两分支与模型总览的数值全部 int() 转换;_send_json
加 Decimal 兜底(整数转 int、非整数转 float),防止同类泄漏
再把请求线程拖死。单测:按天端点 + _json_default。
This commit is contained in:
host committed 2026-10-26 23:17:25 +08:00
1 parent 70177989f2
commit e384245722
2 files changed
+82 -15

No files matched your search

+49
View File
@@ -196,6 +196,55 @@ class EndpointsTestCase(unittest.TestCase):
self.assertEqual(status, 200)
self.assertEqual(body["overall"]["turns"], 2)
def test_project_cost_daily(self):
from datetime import datetime, timezone
status, task = self._req(
"POST", "/tasks",
{"project_id": self._pid(), "prompt": "做加法"}, self.owner_key)
self.assertEqual(status, 201)
store = CostStore(self.db_path)
try:
store.record(task["id"], self._pid(), "gpt-6.1-sol",
input_tokens=100, output_tokens=50,
cached_input_tokens=40, usd_micros=1234,
source="actual", fallback_count=1)
finally:
store._conn.close()
today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
status, body = self._req(
"GET", "/projects/%s/cost?day=%s" % (self._pid(), today),
key=self.owner_key)
self.assertEqual(status, 200)
self.assertEqual(len(body["daily"]), 1)
row = body["daily"][0]
self.assertEqual(row["day"], today)
self.assertEqual(row["total_tokens"], 150)
self.assertEqual(row["usd_micros"], 1234)
self.assertEqual(row["turns"], 1)
self.assertEqual(row["fallback_count"], 1)
self.assertEqual(row["cached_input_tokens"], 40)
self.assertEqual(row["unpriced_actual_tokens"], 0)
status, body = self._req(
"GET", "/projects/%s/cost?days=7" % self._pid(),
key=self.owner_key)
self.assertEqual(status, 200)
self.assertEqual(
[r for r in body["daily"] if r["day"] == today][0]["turns"], 1)
status, body = self._req(
"GET", "/projects/%s/cost?day=2000-01-01" % self._pid(),
key=self.owner_key)
self.assertEqual(status, 200)
self.assertEqual(body["daily"], [])
def test_json_default_decimal(self):
from decimal import Decimal
from cloud.server import _json_default
self.assertEqual(_json_default(Decimal("10424")), 10424)
self.assertIsInstance(_json_default(Decimal("10424")), int)
self.assertEqual(_json_default(Decimal("1.5")), 1.5)
with self.assertRaises(TypeError):
_json_default(object())
# -- 模型总览 -------------------------------------------------------
def test_models_overview_isolated(self):
status, task = self._req(