//go:build llm_script package main import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" ) func TestParseAzureOpenAIPricingCatalogBuildsRecords(t *testing.T) { raw, err := os.ReadFile(filepath.Join("testdata", "azure_openai_pricing_sample.json")) if err != nil { t.Fatalf("读取 fixture 失败: %v", err) } records, err := parseAzureOpenAIPricingCatalog(string(raw)) if err != nil { t.Fatalf("parseAzureOpenAIPricingCatalog 返回错误: %v", err) } if len(records) != 2 { t.Fatalf("期望 2 条 Azure OpenAI 价格记录,实际 %d", len(records)) } if records[0].InputPrice != 2.2 || records[0].OutputPrice != 8.8 { t.Fatalf("gpt-4.1 价格换算错误: %v / %v", records[0].InputPrice, records[0].OutputPrice) } if records[1].ModelName != "GPT-5" { t.Fatalf("第二条模型名错误: %q", records[1].ModelName) } } func TestRunAzureOpenAIPricingImportDryRunPrintsSummary(t *testing.T) { var out bytes.Buffer err := runAzureOpenAIPricingImport(azureOpenAIPricingImportConfig{ URL: defaultAzureOpenAIPricingURL, Fixture: filepath.Join("testdata", "azure_openai_pricing_sample.json"), DryRun: true, }, nil, &out) if err != nil { t.Fatalf("runAzureOpenAIPricingImport 返回错误: %v", err) } output := out.String() for _, want := range []string{ "source=azure-openai-pricing-import", "models=2", "operator=Microsoft Azure", "dry_run=true", } { if !strings.Contains(output, want) { t.Fatalf("输出缺少 %q,实际: %q", want, output) } } } func TestRunAzureOpenAIPricingImportDryRunFollowsNextPageLink(t *testing.T) { pageTwoPath := "/page-2" var server *httptest.Server server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") response := azureRetailPriceResponse{} switch r.URL.Path { case "/": response = azureRetailPriceResponse{ Items: []azureRetailPriceItem{ { CurrencyCode: "USD", UnitPrice: 0.0022, Location: "US East", MeterName: "gpt 4.1 Inp regnl Tokens", ProductName: "Azure OpenAI", SkuName: "gpt 4.1 Inp regnl", ServiceName: "Foundry Models", UnitOfMeasure: "1K", Type: "Consumption", ArmSkuName: "gpt 4.1 Inp regnl", }, { CurrencyCode: "USD", UnitPrice: 0.0088, Location: "US East", MeterName: "gpt 4.1 Outp regnl Tokens", ProductName: "Azure OpenAI", SkuName: "gpt 4.1 Outp regnl", ServiceName: "Foundry Models", UnitOfMeasure: "1K", Type: "Consumption", ArmSkuName: "gpt 4.1 Outp regnl", }, }, NextPageLink: server.URL + pageTwoPath, } case pageTwoPath: response = azureRetailPriceResponse{ Items: []azureRetailPriceItem{ { CurrencyCode: "USD", UnitPrice: 1.25, Location: "US West", MeterName: "GPT 5 inp Glbl 1M Tokens", ProductName: "Azure OpenAI GPT5", SkuName: "GPT 5 inp Glbl", ServiceName: "Foundry Models", UnitOfMeasure: "1M", Type: "Consumption", ArmSkuName: "GPT 5 inp Glbl", }, { CurrencyCode: "USD", UnitPrice: 10, Location: "US West", MeterName: "GPT 5 outpt Glbl 1M Tokens", ProductName: "Azure OpenAI GPT5", SkuName: "GPT 5 outpt Glbl", ServiceName: "Foundry Models", UnitOfMeasure: "1M", Type: "Consumption", ArmSkuName: "GPT 5 outpt Glbl", }, }, } default: t.Fatalf("unexpected path: %s", r.URL.Path) } if err := json.NewEncoder(w).Encode(response); err != nil { t.Fatalf("encode response: %v", err) } })) defer server.Close() var out bytes.Buffer err := runAzureOpenAIPricingImport(azureOpenAIPricingImportConfig{ URL: server.URL, DryRun: true, }, nil, &out) if err != nil { t.Fatalf("runAzureOpenAIPricingImport 返回错误: %v", err) } if !strings.Contains(out.String(), "models=2") { t.Fatalf("分页结果未聚合,输出: %q", out.String()) } }