feat(audit): add pricing signature guards and reporting
Add snapshot, signature, and drift guard support for Vertex AI, Cloudflare Workers AI, and Perplexity API, backed by a queryable audit table and recent-window view. This commit also wires the audit query layer into daily signal materialization and report generation so structure drift becomes a first-class signal instead of a log-only artifact.
This commit is contained in:
102
scripts/perplexity_pricing_signature_guard_test.go
Normal file
102
scripts/perplexity_pricing_signature_guard_test.go
Normal file
@@ -0,0 +1,102 @@
|
||||
//go:build llm_script
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRunPerplexityPricingSignatureGuardInitializesBaseline(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
baselinePath := filepath.Join(tempDir, "baseline.signature.json")
|
||||
|
||||
result, err := runPerplexityPricingSignatureGuard(perplexityPricingSignatureGuardConfig{
|
||||
URL: defaultPerplexityPricingFetchURL,
|
||||
Fixture: filepath.Join("testdata", "perplexity_pricing_sample.md"),
|
||||
SnapshotDir: tempDir,
|
||||
BaselinePath: baselinePath,
|
||||
Timeout: time.Second,
|
||||
AllowBootstrap: true,
|
||||
}, time.Date(2026, 5, 15, 20, 40, 0, 0, time.FixedZone("CST", 8*3600)))
|
||||
if err != nil {
|
||||
t.Fatalf("runPerplexityPricingSignatureGuard 返回错误: %v", err)
|
||||
}
|
||||
if !result.BaselineInitialized {
|
||||
t.Fatalf("期望初始化 baseline")
|
||||
}
|
||||
if result.DriftDetected {
|
||||
t.Fatalf("首次初始化不应判定为漂移")
|
||||
}
|
||||
if _, err := os.Stat(baselinePath); err != nil {
|
||||
t.Fatalf("baseline 未写入: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunPerplexityPricingSignatureGuardDetectsDrift(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
baselinePath := filepath.Join(tempDir, "baseline.signature.json")
|
||||
|
||||
_, err := runPerplexityPricingSignatureGuard(perplexityPricingSignatureGuardConfig{
|
||||
URL: defaultPerplexityPricingFetchURL,
|
||||
Fixture: filepath.Join("testdata", "perplexity_pricing_sample.md"),
|
||||
SnapshotDir: tempDir,
|
||||
BaselinePath: baselinePath,
|
||||
Timeout: time.Second,
|
||||
AllowBootstrap: true,
|
||||
}, time.Date(2026, 5, 15, 20, 41, 0, 0, time.FixedZone("CST", 8*3600)))
|
||||
if err != nil {
|
||||
t.Fatalf("初始化 baseline 失败: %v", err)
|
||||
}
|
||||
|
||||
driftFixture := "# Models\n\n| Name | Pricing |\n| --- | --- |\n| sonar | $1 |\n"
|
||||
driftPath := filepath.Join(tempDir, "perplexity-drift.md")
|
||||
if err := os.WriteFile(driftPath, []byte(driftFixture), 0o644); err != nil {
|
||||
t.Fatalf("写入 drift fixture 失败: %v", err)
|
||||
}
|
||||
|
||||
result, err := runPerplexityPricingSignatureGuard(perplexityPricingSignatureGuardConfig{
|
||||
URL: defaultPerplexityPricingFetchURL,
|
||||
Fixture: driftPath,
|
||||
SnapshotDir: tempDir,
|
||||
BaselinePath: baselinePath,
|
||||
Timeout: time.Second,
|
||||
AllowBootstrap: false,
|
||||
}, time.Date(2026, 5, 15, 20, 42, 0, 0, time.FixedZone("CST", 8*3600)))
|
||||
if err == nil {
|
||||
t.Fatalf("期望结构漂移时报错")
|
||||
}
|
||||
if !result.DriftDetected {
|
||||
t.Fatalf("期望 driftDetected=true")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "perplexity pricing structure drift detected") {
|
||||
t.Fatalf("期望返回 drift 错误,实际: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatPerplexityPricingSignatureGuardSummary(t *testing.T) {
|
||||
result := perplexityPricingSignatureGuardResult{
|
||||
SnapshotPath: "/tmp/perplexity.md",
|
||||
SignaturePath: "/tmp/perplexity.signature.json",
|
||||
BaselinePath: "/tmp/baseline.signature.json",
|
||||
DriftDetected: false,
|
||||
BaselineInitialized: true,
|
||||
CurrentSignature: markdownPricingStructureSignature{
|
||||
StructureSHA256: "abc123",
|
||||
},
|
||||
}
|
||||
summary := formatPerplexityPricingSignatureGuardSummary(result)
|
||||
for _, want := range []string{
|
||||
"source=perplexity-pricing-signature-guard",
|
||||
"drift=false",
|
||||
"baseline_initialized=true",
|
||||
"structure_sha256=abc123",
|
||||
} {
|
||||
if !strings.Contains(summary, want) {
|
||||
t.Fatalf("summary 缺少 %q,实际: %q", want, summary)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user