//go:build llm_script package main import "testing" func TestSummarizeSignalEventTypes(t *testing.T) { events := []signalModelEvent{ {EventType: "new_model", ModelName: "A"}, {EventType: "new_model", ModelName: "B"}, {EventType: "price_cut", ModelName: "C"}, } counts := summarizeSignalEventTypes(events) if counts["new_model"] != 2 { t.Fatalf("new_model 计数错误: %d", counts["new_model"]) } if counts["price_cut"] != 1 { t.Fatalf("price_cut 计数错误: %d", counts["price_cut"]) } } func TestBuildSignalPageMode(t *testing.T) { if got := buildSignalPageMode(signalDailySignals{}, nil); got != "calm" { t.Fatalf("平静日 page_mode 错误: %q", got) } if got := buildSignalPageMode(signalDailySignals{NewModels: 2, PriceChanges: 1}, nil); got != "hot" { t.Fatalf("高变化日 page_mode 错误: %q", got) } if got := buildSignalPageMode(signalDailySignals{}, []signalModelEvent{{EventType: "official_release"}}); got != "hot" { t.Fatalf("官方发布日 page_mode 错误: %q", got) } } func TestBuildSignalPageModeTreatsVerifiedDiscoveryPromoAsHot(t *testing.T) { got := buildSignalPageMode(signalDailySignals{}, []signalModelEvent{{EventType: "promo_campaign", ModelName: "GPT-5.6"}}) if got != "hot" { t.Fatalf("已验证活动事件应触发 hot, got=%q", got) } } func TestFilterDiscoveryEventsDropsLeakAndCandidateOnly(t *testing.T) { events := []signalModelEvent{ {EventType: "official_release", ModelName: "GPT-5.6", Priority: 120}, {EventType: "leak_or_rumor", ModelName: "GPT-5.6", Priority: 200}, {EventType: "unknown", ModelName: "Mystery", Priority: 50}, } filtered := filterVerifiedDiscoverySignalEvents(events) if len(filtered) != 1 { t.Fatalf("期望仅保留 1 条正式事实事件, got=%d", len(filtered)) } if filtered[0].EventType != "official_release" { t.Fatalf("错误保留了非正式事件: %+v", filtered) } } func TestMergeVerifiedDiscoveryEventsPrefersNativeFact(t *testing.T) { native := []signalModelEvent{{ EventType: "official_release", ModelName: "GPT-5.6", ProviderName: "OpenAI", PrimarySource: "native_release", EvidenceDetail: "native evidence", Priority: 120, }} discovery := []signalModelEvent{{ EventType: "official_release", ModelName: "GPT-5.6", ProviderName: "OpenAI", PrimarySource: "discovery_release", EvidenceDetail: "discovery evidence", SourceKindLabel: "官方博客", Priority: 110, }} merged := mergeVerifiedDiscoveryEvents(native, discovery) if len(merged) != 1 { t.Fatalf("期望去重后只剩 1 条事件, got=%d", len(merged)) } if merged[0].PrimarySource != "native_release" { t.Fatalf("原生事实不应被 discovery 覆盖: %+v", merged[0]) } if merged[0].SourceKindLabel != "官方博客" { t.Fatalf("原生事实应补入 discovery 证据缺口: %+v", merged[0]) } } func TestMergeVerifiedDiscoveryEventsDropsUnverifiedPriceNarrative(t *testing.T) { native := []signalModelEvent{{EventType: "new_model", ModelName: "DeepSeek-V4-Flash", ProviderName: "DeepSeek", Priority: 80}} discovery := []signalModelEvent{{EventType: "leak_or_rumor", ModelName: "DeepSeek-V4-Flash", ProviderName: "DeepSeek", Priority: 130}} merged := mergeVerifiedDiscoveryEvents(native, discovery) if len(merged) != 1 || merged[0].EventType != "new_model" { t.Fatalf("非正式 discovery 事件不应进入正式快照: %+v", merged) } } func TestBuildDeepSeekNewsDriftEvent(t *testing.T) { row := officialImportSignatureAuditViewRow{ SourceKey: "deepseek_news_signature", Status: "drift_detected", StructureState: "changed", StructureChanged: true, DriftDetected: true, BaselineInitialized: false, StructureSHA256: "abc123", } event, ok := buildDeepSeekSignatureSignalEvent(row, deepseekSignatureEventConfig{ SourceKey: "deepseek_news_signature", ModelName: "DeepSeek 官方新闻页", SourceKindLabel: "官方新闻页结构变化", PrimaryURL: defaultDeepSeekNewsSignalURL, Audience: "a", EvidenceTemplate: "news drift %s %s", Baseline: "官方新闻页结构漂移", Summary: "summary", Priority: 117, }) if !ok { t.Fatal("期望为 drift 行生成正式信号事件") } if event.EventType != "official_release" { t.Fatalf("DeepSeek drift 应映射为 official_release, got=%q", event.EventType) } if event.ProviderName != "DeepSeek" || event.ModelName != "DeepSeek 官方新闻页" { t.Fatalf("DeepSeek drift 事件主体错误: %+v", event) } } func TestBuildDeepSeekPricingDriftEvent(t *testing.T) { row := officialImportSignatureAuditViewRow{ SourceKey: "deepseek_pricing_signature", Status: "drift_detected", StructureState: "changed", StructureChanged: true, DriftDetected: true, BaselineInitialized: false, StructureSHA256: "pricing123", } event, ok := buildDeepSeekSignatureSignalEvent(row, deepseekSignatureEventConfig{ SourceKey: "deepseek_pricing_signature", ModelName: "DeepSeek 官方价格页", SourceKindLabel: "官方价格页结构变化", PrimaryURL: defaultDeepSeekPricingSignalURL, Audience: "a", EvidenceTemplate: "pricing drift %s %s", Baseline: "官方价格页结构漂移", Summary: "pricing summary", Priority: 116, }) if !ok { t.Fatal("期望为 pricing drift 行生成正式信号事件") } if event.ModelName != "DeepSeek 官方价格页" || event.SourceKindLabel != "官方价格页结构变化" { t.Fatalf("pricing drift 事件映射错误: %+v", event) } } func TestBuildDeepSeekAPIPricingDriftEvent(t *testing.T) { row := officialImportSignatureAuditViewRow{ SourceKey: "deepseek_api_pricing_signature", Status: "drift_detected", StructureState: "changed", StructureChanged: true, DriftDetected: true, BaselineInitialized: false, StructureSHA256: "api123", } event, ok := buildDeepSeekSignatureSignalEvent(row, deepseekSignatureEventConfig{ SourceKey: "deepseek_api_pricing_signature", ModelName: "DeepSeek API 定价页", SourceKindLabel: "官方 API 定价页结构变化", PrimaryURL: defaultDeepSeekAPIPricingSignalURL, Audience: "a", EvidenceTemplate: "api drift %s %s", Baseline: "官方 API 定价页结构漂移", Summary: "api pricing summary", Priority: 115, }) if !ok { t.Fatal("期望为 api pricing drift 行生成正式信号事件") } if event.ModelName != "DeepSeek API 定价页" || event.SourceKindLabel != "官方 API 定价页结构变化" { t.Fatalf("api pricing drift 事件映射错误: %+v", event) } } func TestBuildDeepSeekNewsDriftEventSkipsBaselineOnly(t *testing.T) { row := officialImportSignatureAuditViewRow{ SourceKey: "deepseek_news_signature", Status: "baseline_initialized", StructureState: "initial", StructureChanged: false, DriftDetected: false, BaselineInitialized: true, StructureSHA256: "abc123", } if _, ok := buildDeepSeekSignatureSignalEvent(row, deepseekSignatureEventConfig{SourceKey: "deepseek_news_signature"}); ok { t.Fatal("baseline 初始化不应直接进入正式信号") } }