forked from niuniu/llm-intelligence
feat(phase1): OpenRouter采集器接入PostgreSQL,数据链路闭环
- 将 fetch_openrouter.go 的 summarize() 实现为 PostgreSQL upsert - 新增 -db 参数和 DATABASE_URL 环境变量支持 - 打通 models + model_prices 表的最小可运行链路 - 创建 llm_intelligence 数据库并运行 migration - 前端 Explorer 验证 T-3.2~T-3.5 全部通过 - 日报生成器正常产出 Markdown 和 latest_models.json
This commit is contained in:
98
scripts/fetch_openrouter_test.go
Normal file
98
scripts/fetch_openrouter_test.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test 1: parseModels 正确解析 name、context_length、capabilities、pricing input/prompt 和 output/completion
|
||||
func TestParseModels(t *testing.T) {
|
||||
// 从样例文件读取,而非内联 JSON
|
||||
samplePath := filepath.Join("testdata", "openrouter_models_sample.json")
|
||||
raw, err := os.ReadFile(samplePath)
|
||||
if err != nil {
|
||||
t.Fatalf("读取样例文件失败: %v", err)
|
||||
}
|
||||
|
||||
models, err := parseModels(raw)
|
||||
if err != nil {
|
||||
t.Fatalf("parseModels 失败: %v", err)
|
||||
}
|
||||
if len(models) != 3 {
|
||||
t.Fatalf("期望 3 条,实际 %d", len(models))
|
||||
}
|
||||
|
||||
// 第一条:完整字段
|
||||
m := models[0]
|
||||
if m.ID != "openai/gpt-4o" {
|
||||
t.Errorf("ID 错误: %s", m.ID)
|
||||
}
|
||||
if m.Name != "GPT-4o" {
|
||||
t.Errorf("Name 错误: %s", m.Name)
|
||||
}
|
||||
if m.ContextLength != 128000 {
|
||||
t.Errorf("ContextLength 错误: %d", m.ContextLength)
|
||||
}
|
||||
if len(m.Capabilities) != 3 {
|
||||
t.Errorf("Capabilities 长度错误: %d", len(m.Capabilities))
|
||||
}
|
||||
if m.Pricing.Input != 2.5 {
|
||||
t.Errorf("Pricing.Input 错误: %f", m.Pricing.Input)
|
||||
}
|
||||
if m.Pricing.Output != 10.0 {
|
||||
t.Errorf("Pricing.Output 错误: %f", m.Pricing.Output)
|
||||
}
|
||||
|
||||
// 第二条:pricing 用 prompt/completion 别名回退
|
||||
m2 := models[1]
|
||||
if m2.Pricing.Input != 0.1 {
|
||||
t.Errorf("Input 回退 prompt 失败: %f", m2.Pricing.Input)
|
||||
}
|
||||
if m2.Pricing.Output != 0.3 {
|
||||
t.Errorf("Output 回退 completion 失败: %f", m2.Pricing.Output)
|
||||
}
|
||||
|
||||
// 第三条:空 pricing
|
||||
m3 := models[2]
|
||||
if m3.Pricing.Input != 0 || m3.Pricing.Output != 0 {
|
||||
t.Errorf("空 pricing 未返回 0: input=%f output=%f", m3.Pricing.Input, m3.Pricing.Output)
|
||||
}
|
||||
}
|
||||
|
||||
// Test 2: run 无 API Key 时写入临时文件,JSON 含 total 和 models 字段
|
||||
func TestRunNoAPIKey(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
outPath := filepath.Join(tmpDir, "models.json")
|
||||
|
||||
cfg := Config{OutPath: outPath}
|
||||
err := run(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("run 失败: %v", err)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(outPath)
|
||||
if err != nil {
|
||||
t.Fatalf("读取输出文件失败: %v", err)
|
||||
}
|
||||
|
||||
var result map[string]any
|
||||
if err := json.Unmarshal(data, &result); err != nil {
|
||||
t.Fatalf("JSON 解析失败: %v", err)
|
||||
}
|
||||
|
||||
if _, ok := result["total"]; !ok {
|
||||
t.Error("JSON 缺少 total 字段")
|
||||
}
|
||||
if _, ok := result["models"]; !ok {
|
||||
t.Error("JSON 缺少 models 字段")
|
||||
}
|
||||
models, ok := result["models"].([]any)
|
||||
if !ok {
|
||||
t.Fatal("models 字段类型错误")
|
||||
}
|
||||
if len(models) == 0 {
|
||||
t.Error("models 为空")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user