Add plan catalog and subscription schema support, seed baselines, and real importers for core domestic subscriptions plus stable official pricing sources. This commit also hardens the shared fetch layers so the importers can support live collection and database writes instead of relying on manual placeholders alone.
150 lines
7.2 KiB
Go
150 lines
7.2 KiB
Go
//go:build llm_script
|
||
|
||
package main
|
||
|
||
import (
|
||
"bytes"
|
||
"path/filepath"
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
func TestBuildPlanCatalogRows(t *testing.T) {
|
||
envelope, err := loadPlanCatalogSeeds([]string{
|
||
filepath.Join("..", "seeds", "plan_catalog_inventory_seed.json"),
|
||
filepath.Join("..", "seeds", "plan_catalog_inventory_seed_cn_vendors_top20.json"),
|
||
filepath.Join("..", "seeds", "plan_catalog_inventory_seed_cn_relays_top20plus.json"),
|
||
filepath.Join("..", "seeds", "plan_catalog_inventory_seed_web_research.json"),
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("loadPlanCatalogSeeds 失败: %v", err)
|
||
}
|
||
|
||
rows, err := buildPlanCatalogRows(envelope)
|
||
if err != nil {
|
||
t.Fatalf("buildPlanCatalogRows 失败: %v", err)
|
||
}
|
||
if len(rows) != 70 {
|
||
t.Fatalf("期望 70 条基础目录记录,实际 %d", len(rows))
|
||
}
|
||
|
||
foundVendorTop20 := false
|
||
foundRelayTop20Plus := false
|
||
wantImporterKeys := map[string]string{
|
||
"tencent-cloud-token-plan-enterprise-pro": "tencent_catalog",
|
||
"tencent-cloud-token-plan-enterprise-lite": "tencent_catalog",
|
||
"tencent-cloud-coding-plan": "tencent_catalog",
|
||
"aliyun-bailian-token-plan-team": "import_aliyun_subscription.go",
|
||
"aliyun-bailian-coding-plan": "import_aliyun_subscription.go",
|
||
"baidu-qianfan-token-benefit-pack": "import_baidu_subscription.go",
|
||
"baidu-qianfan-coding-plan": "import_baidu_subscription.go",
|
||
"zhipu-glm-coding-plan": "import_zhipu_coding_plan.go",
|
||
"minimax-token-plan": "import_minimax_subscription.go",
|
||
"volcengine-ark-coding-plan": "import_bytedance_subscription.go",
|
||
"huawei-cloud-maas-package-plan": "import_huawei_package.go",
|
||
"ctyun-token-plan": "import_ctyun_subscription.go",
|
||
"ctyun-coding-plan": "import_ctyun_subscription.go",
|
||
"cucloud-aicp-platform": "import_cucloud_catalog.go",
|
||
"cucloud-ai-app-platform": "import_cucloud_catalog.go",
|
||
"mobile-cloud-ai-market": "import_mobile_cloud_catalog.go",
|
||
"youdao-zhiyun-maas": "import_youdao_pricing.go",
|
||
"360-open-platform": "import_360_pricing.go",
|
||
"siliconflow-siliconcloud": "import_siliconflow_pricing.go",
|
||
"ppio-model-api": "import_ppio_pricing.go",
|
||
"ucloud-umodelverse": "import_ucloud_pricing.go",
|
||
"anthropic-api-payg": "import_catalog_seed_verification.go",
|
||
"xai-api-payg": "import_catalog_seed_verification.go",
|
||
"alibaba-qwen-api-payg": "import_catalog_seed_verification.go",
|
||
"tencent-hunyuan-api-payg": "import_catalog_seed_verification.go",
|
||
"huawei-pangu-api-payg": "import_catalog_seed_verification.go",
|
||
"baichuan-api-payg": "import_catalog_seed_verification.go",
|
||
"01ai-api-payg": "import_catalog_seed_verification.go",
|
||
"sensenova-api-payg": "import_catalog_seed_verification.go",
|
||
"xfyun-spark-api-payg": "import_catalog_seed_verification.go",
|
||
"360-zhinao-api-payg": "import_catalog_seed_verification.go",
|
||
"youdao-ziyue-api-payg": "import_catalog_seed_verification.go",
|
||
"modelbest-minicpm-api-payg": "import_catalog_seed_verification.go",
|
||
"baai-flagopen-api-payg": "import_catalog_seed_verification.go",
|
||
"skywork-api-payg": "import_catalog_seed_verification.go",
|
||
"infinigence-api-payg": "import_catalog_seed_verification.go",
|
||
"qingcloud-coreshub": "import_catalog_seed_verification.go",
|
||
"ksyun-xingliu-platform": "import_catalog_seed_verification.go",
|
||
"google-gemini-api-payg": "import_catalog_seed_verification.go",
|
||
"mistral-api-payg": "import_catalog_seed_verification.go",
|
||
"cohere-api-payg": "import_catalog_seed_verification.go",
|
||
"openrouter-api-payg": "fetch_openrouter.go",
|
||
"together-ai-api-payg": "import_catalog_seed_verification.go",
|
||
"fireworks-ai-api-payg": "import_catalog_seed_verification.go",
|
||
"deepinfra-api-payg": "import_catalog_seed_verification.go",
|
||
"groq-api-payg": "import_catalog_seed_verification.go",
|
||
"replicate-api-payg": "import_catalog_seed_verification.go",
|
||
"hyperbolic-api-payg": "import_catalog_seed_verification.go",
|
||
"novita-ai-api-payg": "import_catalog_seed_verification.go",
|
||
"azure-openai-service-payg": "import_azure_openai_pricing.go",
|
||
"amazon-bedrock-payg": "import_bedrock_pricing.go",
|
||
"google-vertex-ai-genai-payg": "import_vertex_pricing.go",
|
||
"cloudflare-workers-ai-payg": "import_cloudflare_pricing.go",
|
||
"baseten-inference-payg": "import_catalog_seed_verification.go",
|
||
"cerebras-inference-payg": "import_catalog_seed_verification.go",
|
||
"perplexity-agent-api-payg": "import_perplexity_pricing.go",
|
||
"sambanova-cloud-payg": "import_catalog_seed_verification.go",
|
||
"jdcloud-joybuilder-payg": "import_catalog_seed_verification.go",
|
||
}
|
||
for _, row := range rows {
|
||
if row.CatalogCode == "zhipu-glm-coding-plan" {
|
||
if row.CatalogSegment != "vendor_top20" || row.MarketRank != 5 {
|
||
t.Fatalf("智谱榜单字段错误: segment=%q rank=%d", row.CatalogSegment, row.MarketRank)
|
||
}
|
||
foundVendorTop20 = true
|
||
}
|
||
if row.CatalogCode == "ctyun-coding-plan" {
|
||
if row.CatalogSegment != "relay_top20plus" || row.MarketRank != 9 {
|
||
t.Fatalf("天翼云编码套餐榜单字段错误: segment=%q rank=%d", row.CatalogSegment, row.MarketRank)
|
||
}
|
||
foundRelayTop20Plus = true
|
||
}
|
||
if wantImporterKey, ok := wantImporterKeys[row.CatalogCode]; ok && row.ImporterKey != wantImporterKey {
|
||
t.Fatalf("%s importerKey 错误: got=%q want=%q", row.CatalogCode, row.ImporterKey, wantImporterKey)
|
||
}
|
||
}
|
||
if !foundVendorTop20 {
|
||
t.Fatalf("缺少 vendor_top20 覆盖记录")
|
||
}
|
||
if !foundRelayTop20Plus {
|
||
t.Fatalf("缺少 relay_top20plus 覆盖记录")
|
||
}
|
||
}
|
||
|
||
func TestRunPlanCatalogImportDryRunPrintsSummary(t *testing.T) {
|
||
var out bytes.Buffer
|
||
err := runPlanCatalogImport(importPlanCatalogConfig{
|
||
SeedPaths: strings.Join([]string{
|
||
filepath.Join("..", "seeds", "plan_catalog_inventory_seed.json"),
|
||
filepath.Join("..", "seeds", "plan_catalog_inventory_seed_cn_vendors_top20.json"),
|
||
filepath.Join("..", "seeds", "plan_catalog_inventory_seed_cn_relays_top20plus.json"),
|
||
filepath.Join("..", "seeds", "plan_catalog_inventory_seed_web_research.json"),
|
||
}, ","),
|
||
DryRun: true,
|
||
}, nil, &out)
|
||
if err != nil {
|
||
t.Fatalf("runPlanCatalogImport 失败: %v", err)
|
||
}
|
||
|
||
output := out.String()
|
||
for _, want := range []string{
|
||
"source=plan-catalog-import",
|
||
"rows=70",
|
||
"coding_plan:7",
|
||
"package_plan:1",
|
||
"pay_as_you_go:51",
|
||
"token_plan:7",
|
||
"unknown:4",
|
||
"confirmed:70",
|
||
"dry_run=true",
|
||
} {
|
||
if !strings.Contains(output, want) {
|
||
t.Fatalf("输出缺少 %q,实际: %q", want, output)
|
||
}
|
||
}
|
||
}
|