Files
bifrost/transports/bifrost-http/lib/pricing_integration_test.go
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

1078 lines
42 KiB
Go

package lib
// =============================================================================
// E2E Pricing Config Integration Tests — Issue #2312
//
// These tests simulate production scenarios end-to-end using:
// - Real SQLite config stores (not mocks)
// - Real log capture (not silent noop loggers)
// - The actual initFrameworkConfig code path
// - DB read → resolve → write lifecycle
//
// Run: go test ./transports/bifrost-http/lib/... -run TestPricingE2E -v
// =============================================================================
import (
"context"
"fmt"
"path/filepath"
"strings"
"sync"
"testing"
"time"
"github.com/maximhq/bifrost/core/schemas"
"github.com/maximhq/bifrost/framework"
configstore "github.com/maximhq/bifrost/framework/configstore"
configstoreTables "github.com/maximhq/bifrost/framework/configstore/tables"
"github.com/maximhq/bifrost/framework/modelcatalog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// =============================================================================
// Capturing logger — records every emitted log line for assertion
// =============================================================================
type capturingLogger struct {
mu sync.Mutex
logs []string
}
func newCapturingLogger() *capturingLogger { return &capturingLogger{} }
func (l *capturingLogger) append(level, msg string, args ...any) {
line := fmt.Sprintf("[%s] %s", level, fmt.Sprintf(msg, args...))
l.mu.Lock()
l.logs = append(l.logs, line)
l.mu.Unlock()
}
func (l *capturingLogger) Debug(msg string, args ...any) { l.append("DEBUG", msg, args...) }
func (l *capturingLogger) Info(msg string, args ...any) { l.append("INFO", msg, args...) }
func (l *capturingLogger) Warn(msg string, args ...any) { l.append("WARN", msg, args...) }
func (l *capturingLogger) Error(msg string, args ...any) { l.append("ERROR", msg, args...) }
func (l *capturingLogger) Fatal(msg string, args ...any) { l.append("FATAL", msg, args...) }
func (l *capturingLogger) SetLevel(_ schemas.LogLevel) {}
func (l *capturingLogger) SetOutputType(_ schemas.LoggerOutputType) {}
func (l *capturingLogger) LogHTTPRequest(_ schemas.LogLevel, _ string) schemas.LogEventBuilder {
return schemas.NoopLogEvent
}
// hasLog returns true if any captured line contains all provided substrings.
func (l *capturingLogger) hasLog(substrings ...string) bool {
l.mu.Lock()
defer l.mu.Unlock()
for _, line := range l.logs {
match := true
for _, s := range substrings {
if !strings.Contains(line, s) {
match = false
break
}
}
if match {
return true
}
}
return false
}
// dump returns all captured log lines joined for test failure output.
func (l *capturingLogger) dump() string {
l.mu.Lock()
defer l.mu.Unlock()
return strings.Join(l.logs, "\n")
}
// =============================================================================
// Helpers
// =============================================================================
// makeStore creates a real SQLite-backed configstore in a temp directory.
func makeStore(t *testing.T, dir string) configstore.ConfigStore {
t.Helper()
store, err := configstore.NewConfigStore(context.Background(), &configstore.Config{
Enabled: true,
Type: configstore.ConfigStoreTypeSQLite,
Config: &configstore.SQLiteConfig{Path: filepath.Join(dir, "config.db")},
}, &testLogger{})
require.NoError(t, err, "failed to create SQLite configstore")
t.Cleanup(func() { store.Close(context.Background()) })
return store
}
// resolveWithCapture calls ResolveFrameworkPricingConfig with a capturing logger
// installed, then restores the previous logger.
func resolveWithCapture(
t *testing.T,
log *capturingLogger,
dbConfig *configstoreTables.TableFrameworkConfig,
fileConfig *framework.FrameworkConfig,
) (*configstoreTables.TableFrameworkConfig, *modelcatalog.Config, bool) {
if t != nil {
t.Helper()
}
prev := getLogger()
SetLogger(log)
defer SetLogger(prev)
return ResolveFrameworkPricingConfig(dbConfig, fileConfig)
}
// initWithCapture calls initFrameworkConfig with a capturing logger installed.
func initWithCapture(
t *testing.T,
log *capturingLogger,
store configstore.ConfigStore,
frameworkCfg *framework.FrameworkConfig,
) *Config {
t.Helper()
cfg := &Config{ConfigStore: store}
configData := &ConfigData{FrameworkConfig: frameworkCfg}
prev := getLogger()
SetLogger(log)
defer SetLogger(prev)
initFrameworkConfig(context.Background(), cfg, configData)
return cfg
}
// getLogger returns the module-level logger var.
// This uses the same package-level var as SetLogger.
func getLogger() schemas.Logger { return logger }
// pt is a generic pointer helper.
func ptStr(s string) *string { return &s }
func ptI64(n int64) *int64 { return &n }
func ptF64(f float64) *float64 { return &f }
// defaultSyncSecs is the production default converted to seconds.
var defaultSyncSecs = int64(modelcatalog.DefaultSyncInterval.Seconds())
// =============================================================================
// STEP 2 — Baseline: no config.json, no DB → built-in defaults
// =============================================================================
func TestPricingE2E_Step2_Baseline(t *testing.T) {
log := newCapturingLogger()
tableOut, catalogOut, needsDBUpdate := resolveWithCapture(t, log, nil, nil)
// Values must equal built-in defaults.
require.Equal(t, modelcatalog.DefaultPricingURL, *tableOut.PricingURL,
"pricing_url must be the built-in default")
require.Equal(t, defaultSyncSecs, *tableOut.PricingSyncInterval,
"pricing_sync_interval must be the built-in default (%d s)", defaultSyncSecs)
require.Equal(t, *tableOut.PricingURL, *catalogOut.PricingURL)
require.Equal(t, *tableOut.PricingSyncInterval, *catalogOut.PricingSyncInterval)
// No DB update needed when there is no DB.
require.False(t, needsDBUpdate, "no DB update should be needed for a fully absent config")
// The resolution log must report both as "default".
assert.True(t, log.hasLog("resolved pricing config", "source: default", "source: default"),
"expected resolution log with default sources\n%s", log.dump())
t.Logf("PASS — baseline: url=%q interval=%d s (both default)", *tableOut.PricingURL, *tableOut.PricingSyncInterval)
}
// =============================================================================
// STEP 3 — File config: valid values override defaults
// =============================================================================
func TestPricingE2E_Step3_FileConfigValid(t *testing.T) {
log := newCapturingLogger()
fileConfig := &framework.FrameworkConfig{
Pricing: &modelcatalog.Config{
PricingURL: ptStr("https://example.com/pricing.json"),
PricingSyncInterval: ptI64(7200),
},
}
tableOut, catalogOut, needsDBUpdate := resolveWithCapture(t, log, nil, fileConfig)
require.Equal(t, "https://example.com/pricing.json", *tableOut.PricingURL,
"pricing_url must come from file config")
require.Equal(t, int64(7200), *tableOut.PricingSyncInterval,
"pricing_sync_interval must be 7200 s from file config")
require.Equal(t, *tableOut.PricingURL, *catalogOut.PricingURL)
require.Equal(t, *tableOut.PricingSyncInterval, *catalogOut.PricingSyncInterval)
require.False(t, needsDBUpdate, "no DB update needed — no DB row exists yet")
// No warnings about clamping or fallback should appear.
assert.False(t, log.hasLog("WARN"), "unexpected WARN in file-config-valid case\n%s", log.dump())
// Resolution log must reflect file source for both fields.
assert.True(t, log.hasLog("resolved pricing config", "source: file"),
"expected 'source: file' in resolution log\n%s", log.dump())
// Debug logs must show file values.
assert.True(t, log.hasLog("DEBUG", "pricing_url resolved from file"),
"expected debug log for url resolved from file\n%s", log.dump())
assert.True(t, log.hasLog("DEBUG", "pricing_sync_interval resolved from file"),
"expected debug log for interval resolved from file\n%s", log.dump())
t.Logf("PASS — file config: url=%q interval=%d s (both file)", *tableOut.PricingURL, *tableOut.PricingSyncInterval)
}
// =============================================================================
// STEP 4 — Minimum constraint validation
// =============================================================================
func TestPricingE2E_Step4A_TooLow_ClampsTo3600(t *testing.T) {
log := newCapturingLogger()
fileConfig := &framework.FrameworkConfig{
Pricing: &modelcatalog.Config{PricingSyncInterval: ptI64(100)},
}
tableOut, catalogOut, _ := resolveWithCapture(t, log, nil, fileConfig)
require.Equal(t, modelcatalog.MinimumPricingSyncIntervalSec, *tableOut.PricingSyncInterval,
"too-low interval must be clamped to minimum 3600 s")
require.Equal(t, *tableOut.PricingSyncInterval, *catalogOut.PricingSyncInterval)
assert.True(t, log.hasLog("WARN", "below minimum", "clamping to 3600"),
"expected WARN clamping message\n%s", log.dump())
t.Logf("PASS — too-low: 100 s clamped to %d s", *tableOut.PricingSyncInterval)
}
func TestPricingE2E_Step4B_Zero_IgnoredUseDefault(t *testing.T) {
log := newCapturingLogger()
fileConfig := &framework.FrameworkConfig{
Pricing: &modelcatalog.Config{PricingSyncInterval: ptI64(0)},
}
tableOut, _, _ := resolveWithCapture(t, log, nil, fileConfig)
require.Equal(t, defaultSyncSecs, *tableOut.PricingSyncInterval,
"zero interval must be ignored; default must be used")
assert.True(t, log.hasLog("WARN", "invalid"), "expected WARN about invalid zero value\n%s", log.dump())
assert.False(t, log.hasLog("source: file"), "interval source must NOT be 'file' for rejected zero\n%s", log.dump())
t.Logf("PASS — zero: ignored, default %d s applied", *tableOut.PricingSyncInterval)
}
func TestPricingE2E_Step4C_Negative_IgnoredUseDefault(t *testing.T) {
log := newCapturingLogger()
fileConfig := &framework.FrameworkConfig{
Pricing: &modelcatalog.Config{PricingSyncInterval: ptI64(-500)},
}
tableOut, _, _ := resolveWithCapture(t, log, nil, fileConfig)
require.Equal(t, defaultSyncSecs, *tableOut.PricingSyncInterval,
"negative interval must be ignored; default must be used")
assert.True(t, log.hasLog("WARN", "invalid"), "expected WARN about invalid negative value\n%s", log.dump())
t.Logf("PASS — negative: ignored, default %d s applied", *tableOut.PricingSyncInterval)
}
// =============================================================================
// STEP 5 — Env variable resolution
// =============================================================================
func TestPricingE2E_Step5A_ValidEnvVar(t *testing.T) {
t.Setenv("BIFROST_E2E_PRICING_URL", "https://env.example.com/pricing.json")
log := newCapturingLogger()
fileConfig := &framework.FrameworkConfig{
Pricing: &modelcatalog.Config{PricingURL: ptStr("env.BIFROST_E2E_PRICING_URL")},
}
tableOut, catalogOut, _ := resolveWithCapture(t, log, nil, fileConfig)
require.Equal(t, "https://env.example.com/pricing.json", *tableOut.PricingURL,
"env.VAR prefix must be resolved to env value")
require.Equal(t, *tableOut.PricingURL, *catalogOut.PricingURL)
assert.False(t, log.hasLog("WARN"), "no WARN expected for valid env var\n%s", log.dump())
t.Logf("PASS — valid env: resolved to %q", *tableOut.PricingURL)
}
func TestPricingE2E_Step5B_MissingEnvVar_PreservesLiteral(t *testing.T) {
log := newCapturingLogger()
rawURL := "env.BIFROST_E2E_PRICING_URL_DEFINITELY_NOT_SET_XYZ9999"
fileConfig := &framework.FrameworkConfig{
Pricing: &modelcatalog.Config{PricingURL: ptStr(rawURL)},
}
tableOut, catalogOut, _ := resolveWithCapture(t, log, nil, fileConfig)
// Must preserve the literal "env.*" string — NOT silently fall back to default URL.
require.Equal(t, rawURL, *tableOut.PricingURL,
"missing env var must preserve the original 'env.*' string, not silently default")
require.Equal(t, *tableOut.PricingURL, *catalogOut.PricingURL)
assert.True(t, log.hasLog("WARN", "env variable not found", rawURL),
"expected WARN about missing env var preserving original value\n%s", log.dump())
// The key requirement: NOT replaced with default URL
require.NotEqual(t, modelcatalog.DefaultPricingURL, *tableOut.PricingURL,
"must NOT silently fall back to built-in default URL")
t.Logf("PASS — missing env: preserved literal %q (not default URL)", *tableOut.PricingURL)
}
func TestPricingE2E_Step5C_EmbeddedEnvNotExpanded(t *testing.T) {
t.Setenv("BIFROST_E2E_HOST", "host.example.com")
log := newCapturingLogger()
// URL that has env.VAR embedded mid-string — NOT a full-string "env." prefix.
embeddedURL := "https://env.BIFROST_E2E_HOST/pricing.json"
fileConfig := &framework.FrameworkConfig{
Pricing: &modelcatalog.Config{PricingURL: ptStr(embeddedURL)},
}
tableOut, catalogOut, _ := resolveWithCapture(t, log, nil, fileConfig)
require.Equal(t, embeddedURL, *tableOut.PricingURL,
"URL that does not start with 'env.' must be treated as a literal — partial/embedded env refs are NOT expanded")
require.Equal(t, *tableOut.PricingURL, *catalogOut.PricingURL)
// No WARN expected — this is valid literal input.
assert.False(t, log.hasLog("WARN"), "no WARN expected for non-prefixed URL\n%s", log.dump())
// MUST NOT be expanded to host.example.com form.
require.NotContains(t, *tableOut.PricingURL, "host.example.com",
"embedded env ref must NOT be expanded")
t.Logf("PASS — embedded env: returned verbatim %q", *tableOut.PricingURL)
}
// =============================================================================
// STEP 6 — Database precedence (real SQLite)
// =============================================================================
func TestPricingE2E_Step6A_DBOverridesFile(t *testing.T) {
dir := t.TempDir()
store := makeStore(t, dir)
ctx := context.Background()
// Seed DB with specific values.
dbInterval := int64(3600)
dbURL := "https://db.example.com/pricing.json"
require.NoError(t, store.UpdateFrameworkConfig(ctx, &configstoreTables.TableFrameworkConfig{
PricingURL: &dbURL,
PricingSyncInterval: &dbInterval,
}))
log := newCapturingLogger()
// File wants 7200s and a different URL.
fileConfig := &framework.FrameworkConfig{
Pricing: &modelcatalog.Config{
PricingURL: ptStr("https://file.example.com/pricing.json"),
PricingSyncInterval: ptI64(7200),
},
}
dbConfig, err := store.GetFrameworkConfig(ctx)
require.NoError(t, err)
tableOut, catalogOut, _ := resolveWithCapture(t, log, dbConfig, fileConfig)
// DB must win over file for both fields.
require.Equal(t, dbURL, *tableOut.PricingURL, "DB url must override file url")
require.Equal(t, int64(3600), *tableOut.PricingSyncInterval, "DB interval must override file interval")
require.Equal(t, *tableOut.PricingURL, *catalogOut.PricingURL)
require.Equal(t, *tableOut.PricingSyncInterval, *catalogOut.PricingSyncInterval)
// Override log must show both the file value and the DB value.
assert.True(t, log.hasLog("INFO", "pricing_url overridden by DB"),
"expected override log for url\n%s", log.dump())
assert.True(t, log.hasLog("INFO", "pricing_sync_interval overridden by DB"),
"expected override log for interval\n%s", log.dump())
// Resolution log must report DB as source.
assert.True(t, log.hasLog("resolved pricing config", "source: db"),
"expected resolution log with 'source: db'\n%s", log.dump())
t.Logf("PASS — DB override: url=%q interval=%d s (both db)", *tableOut.PricingURL, *tableOut.PricingSyncInterval)
}
func TestPricingE2E_Step6B_DBCorruptedZero_FileWins_DBBackfilled(t *testing.T) {
dir := t.TempDir()
store := makeStore(t, dir)
ctx := context.Background()
// Simulate pre-fix bug: interval was serialized as nanoseconds → stored as 0.
corruptedInterval := int64(0)
dbURL := "https://db.example.com/pricing.json"
require.NoError(t, store.UpdateFrameworkConfig(ctx, &configstoreTables.TableFrameworkConfig{
PricingURL: &dbURL,
PricingSyncInterval: &corruptedInterval,
}))
log := newCapturingLogger()
fileConfig := &framework.FrameworkConfig{
Pricing: &modelcatalog.Config{
PricingSyncInterval: ptI64(7200),
},
}
dbConfig, err := store.GetFrameworkConfig(ctx)
require.NoError(t, err)
tableOut, catalogOut, needsDBUpdate := resolveWithCapture(t, log, dbConfig, fileConfig)
// Corrupted DB interval must be rejected; file must win.
require.Equal(t, int64(7200), *tableOut.PricingSyncInterval,
"file interval must win when DB has corrupted zero value")
require.Equal(t, int64(7200), *catalogOut.PricingSyncInterval)
// DB URL is valid and must still be used.
require.Equal(t, dbURL, *tableOut.PricingURL,
"valid DB url must still be respected even when interval is corrupted")
// DB must be flagged for backfill.
require.True(t, needsDBUpdate,
"needsDBUpdate must be true so the healed value is persisted to DB")
// WARN must explain the corruption.
assert.True(t, log.hasLog("WARN", "corrupted", "backfilling"),
"expected WARN about corrupted DB value and backfill\n%s", log.dump())
t.Logf("PASS — DB corrupted (0): file %d s wins, needsDBUpdate=true", *tableOut.PricingSyncInterval)
}
func TestPricingE2E_Step6C_DBNullFields_FileUsed_DBBackfilled(t *testing.T) {
dir := t.TempDir()
store := makeStore(t, dir)
ctx := context.Background()
// Insert a DB row with NULL fields (mimics an incomplete migration or first boot).
require.NoError(t, store.UpdateFrameworkConfig(ctx, &configstoreTables.TableFrameworkConfig{
PricingURL: nil,
PricingSyncInterval: nil,
}))
log := newCapturingLogger()
fileConfig := &framework.FrameworkConfig{
Pricing: &modelcatalog.Config{
PricingURL: ptStr("https://file.example.com/pricing.json"),
PricingSyncInterval: ptI64(3600),
},
}
dbConfig, err := store.GetFrameworkConfig(ctx)
require.NoError(t, err)
tableOut, catalogOut, needsDBUpdate := resolveWithCapture(t, log, dbConfig, fileConfig)
// File config must be used when DB fields are NULL.
require.Equal(t, "https://file.example.com/pricing.json", *tableOut.PricingURL,
"file url must be used when DB url field is NULL")
require.Equal(t, int64(3600), *tableOut.PricingSyncInterval,
"file interval must be used when DB interval field is NULL")
require.Equal(t, *tableOut.PricingURL, *catalogOut.PricingURL)
require.Equal(t, *tableOut.PricingSyncInterval, *catalogOut.PricingSyncInterval)
// DB must be scheduled for backfill.
require.True(t, needsDBUpdate,
"needsDBUpdate must be true so file values are persisted to DB")
t.Logf("PASS — DB NULL fields: file config used, needsDBUpdate=true")
}
// =============================================================================
// STEP 7 — modelcatalog.Init stores the resolved interval correctly
// (verifies the Init → getPricingSyncInterval path)
// =============================================================================
// noSyncFunc is a shouldSyncPricingFunc that prevents real HTTP requests during tests.
var noSyncFunc = func(_ context.Context) bool { return false }
func TestPricingE2E_Step7_RuntimeInterval_StoredCorrectly(t *testing.T) {
dir := t.TempDir()
store := makeStore(t, dir)
ctx := context.Background()
clg := newCapturingLogger()
prevLogger := getLogger()
SetLogger(clg)
defer SetLogger(prevLogger)
// Scenario A: interval=3600 s stored in modelcatalog.
// PricingURL is intentionally left nil so Init falls back to DefaultPricingURL.
// The gate is installed post-Init and only blocks the background ticker, not
// the cold-start sync inside Init — a fake URL here would 404 and fail Init.
syncSeconds := int64(3600)
cfg := &modelcatalog.Config{
PricingSyncInterval: &syncSeconds,
}
mc, err := modelcatalog.Init(ctx, cfg, store, clg)
require.NoError(t, err)
mc.SetShouldSyncGate(noSyncFunc)
defer mc.Cleanup()
// The startup Info log must reflect the correct duration.
assert.True(t, clg.hasLog("INFO", "pricing sync interval set to", "1h0m0s"),
"expected startup log: interval=1h0m0s\n%s", clg.dump())
// Verify the scheduler model: syncWorkerTickerPeriod (1h) is also logged.
assert.True(t, clg.hasLog("INFO", "scheduler checks every", "1h0m0s"),
"expected startup log: scheduler ticker=1h0m0s\n%s", clg.dump())
t.Logf("PASS — Init: interval=3600 s logged as 1h0m0s, ticker=1h0m0s")
}
func TestPricingE2E_Step7_RuntimeInterval_24h_Default(t *testing.T) {
dir := t.TempDir()
store := makeStore(t, dir)
ctx := context.Background()
clg := newCapturingLogger()
prevLogger := getLogger()
SetLogger(clg)
defer SetLogger(prevLogger)
// Nil PricingURL: defaults apply. noSyncFunc prevents real HTTP requests.
cfg := &modelcatalog.Config{}
mc, err := modelcatalog.Init(ctx, cfg, store, clg)
require.NoError(t, err)
mc.SetShouldSyncGate(noSyncFunc)
defer mc.Cleanup()
// Must show 24h default.
assert.True(t, clg.hasLog("INFO", "pricing sync interval set to", "24h0m0s"),
"expected startup log: interval=24h0m0s (default)\n%s", clg.dump())
t.Logf("PASS — Init: nil config → interval=24h0m0s (default)")
}
func TestPricingE2E_Step7_RuntimeInterval_BelowTickerHasNoEffect(t *testing.T) {
// Demonstrates the scheduler model:
// pricingSyncInterval < syncWorkerTickerPeriod (1h) is clamped to minimum 3600s
// by ResolveFrameworkPricingConfig BEFORE reaching Init, so pricingSyncInterval
// can never be set below 3600s (= syncWorkerTickerPeriod) in production.
// In that case the scheduler's effective check period IS the minimum.
log := newCapturingLogger()
tooLow := int64(100)
fileConfig := &framework.FrameworkConfig{
Pricing: &modelcatalog.Config{PricingSyncInterval: &tooLow},
}
tableOut, _, _ := resolveWithCapture(t, log, nil, fileConfig)
// After resolution, the value passed to Init will be 3600, not 100.
require.Equal(t, int64(3600), *tableOut.PricingSyncInterval,
"value reaching modelcatalog.Init must be >= 3600 s after resolution")
t.Logf("PASS — scheduler model: 100 s input always yields %d s at Init boundary", *tableOut.PricingSyncInterval)
}
// =============================================================================
// STEP 8 — Restart consistency: DB values survive restarts
// =============================================================================
func TestPricingE2E_Step8_RestartConsistency(t *testing.T) {
dir := t.TempDir()
store := makeStore(t, dir)
ctx := context.Background()
// ---------- First boot ----------
// File config sets interval=7200 s. No DB row yet.
// When dbConfig==nil, ResolveFrameworkPricingConfig returns needsDBUpdate=false because
// there is no existing DB row to backfill — initFrameworkConfig handles the initial row
// creation separately (via the frameworkConfigFromDB==nil check). We simulate that here
// by writing the resolved config to the store manually, as initFrameworkConfig would do.
log1 := newCapturingLogger()
fileConfig := &framework.FrameworkConfig{
Pricing: &modelcatalog.Config{
PricingURL: ptStr("https://file.example.com/pricing.json"),
PricingSyncInterval: ptI64(7200),
},
}
dbConfig, _ := store.GetFrameworkConfig(ctx)
require.Nil(t, dbConfig, "no DB row should exist before first boot")
tableOut1, _, needsWrite1 := resolveWithCapture(t, log1, dbConfig, fileConfig)
require.Equal(t, int64(7200), *tableOut1.PricingSyncInterval, "first boot: file config used")
// needsWrite1 is false when dbConfig==nil: no existing row to backfill.
// initFrameworkConfig creates the initial row via the frameworkConfigFromDB==nil branch.
require.False(t, needsWrite1, "needsDBUpdate is false for nil dbConfig (no existing row to patch)")
// Simulate the initial DB row creation that initFrameworkConfig performs.
require.NoError(t, store.UpdateFrameworkConfig(ctx, tableOut1))
// ---------- Second boot (restart) ----------
// File config still says 7200, but the DB already has 7200 written.
log2 := newCapturingLogger()
dbConfig2, err := store.GetFrameworkConfig(ctx)
require.NoError(t, err, "DB row must exist after first boot write")
require.NotNil(t, dbConfig2)
tableOut2, _, needsWrite2 := resolveWithCapture(t, log2, dbConfig2, fileConfig)
// DB should be authoritative — values match.
require.Equal(t, int64(7200), *tableOut2.PricingSyncInterval,
"second boot: DB value (7200 s) persists correctly")
require.Equal(t, "https://file.example.com/pricing.json", *tableOut2.PricingURL,
"second boot: DB url persists correctly")
// After a write where file==DB, no update is needed (no change in values).
require.False(t, needsWrite2,
"second boot: no DB update needed when DB already has the correct values")
// Source must be "db" on second boot.
assert.True(t, log2.hasLog("resolved pricing config", "source: db"),
"second boot must report 'source: db'\n%s", log2.dump())
// Override logs must NOT appear (file and DB are identical).
assert.False(t, log2.hasLog("overridden by DB"),
"no override log expected when file and DB values are identical\n%s", log2.dump())
t.Logf("PASS — restart consistency: DB values survive restart, source=db, no spurious override log")
}
func TestPricingE2E_Step8_RestartConsistency_DBWinsOverChangedFile(t *testing.T) {
dir := t.TempDir()
store := makeStore(t, dir)
ctx := context.Background()
// Seed DB with 3600 s (as if written by a previous boot or API call).
dbURL := "https://db.example.com/pricing.json"
require.NoError(t, store.UpdateFrameworkConfig(ctx, &configstoreTables.TableFrameworkConfig{
PricingURL: &dbURL,
PricingSyncInterval: ptI64(3600),
}))
log := newCapturingLogger()
// File now has DIFFERENT values (someone edited config.json after initial setup).
fileConfig := &framework.FrameworkConfig{
Pricing: &modelcatalog.Config{
PricingURL: ptStr("https://new-file.example.com/pricing.json"),
PricingSyncInterval: ptI64(86400),
},
}
dbConfig, err := store.GetFrameworkConfig(ctx)
require.NoError(t, err)
tableOut, _, _ := resolveWithCapture(t, log, dbConfig, fileConfig)
// DB is authoritative — file changes must NOT override.
require.Equal(t, dbURL, *tableOut.PricingURL,
"DB url must override changed file url after initial setup")
require.Equal(t, int64(3600), *tableOut.PricingSyncInterval,
"DB interval must override changed file interval after initial setup")
// Override logs must explain the discrepancy.
assert.True(t, log.hasLog("INFO", "pricing_url overridden by DB"),
"expected url override log\n%s", log.dump())
assert.True(t, log.hasLog("INFO", "pricing_sync_interval overridden by DB"),
"expected interval override log\n%s", log.dump())
t.Logf("PASS — DB wins over changed file config on restart")
}
// =============================================================================
// STEP 9 — Failure simulation
// =============================================================================
func TestPricingE2E_Step9A_InvalidURL_NotSilentlyReplaced(t *testing.T) {
log := newCapturingLogger()
// "invalid-url" has no "env." prefix — treated as a plain string.
// It is NOT a URL format error at this layer (config layer ≠ HTTP fetch layer).
// The invalid URL should be stored verbatim and fail visibly when fetched.
fileConfig := &framework.FrameworkConfig{
Pricing: &modelcatalog.Config{
PricingURL: ptStr("invalid-url"),
},
}
tableOut, catalogOut, _ := resolveWithCapture(t, log, nil, fileConfig)
// The resolver must NOT silently replace an unambiguous non-env string URL with
// the default — it is stored as-is so the HTTP fetch fails visibly.
require.Equal(t, "invalid-url", *tableOut.PricingURL,
"invalid URL must be stored verbatim — visible failure at fetch time, not silent fallback")
require.Equal(t, *tableOut.PricingURL, *catalogOut.PricingURL)
// No WARN expected at config resolution layer.
assert.False(t, log.hasLog("WARN"), "no WARN at config layer for a literal non-env URL\n%s", log.dump())
t.Logf("PASS — invalid URL preserved verbatim %q (will fail visibly at fetch)", *tableOut.PricingURL)
}
func TestPricingE2E_Step9B_MissingEnvURL_NotReplacedWithDefault(t *testing.T) {
// The most dangerous silent failure: "env.VAR" lookup fails → must NOT fall back
// to the built-in default URL silently. This would mask a misconfiguration.
log := newCapturingLogger()
rawURL := "env.BIFROST_E2E_PRICING_URL_NONEXISTENT_PRODUCTION_XYZ"
fileConfig := &framework.FrameworkConfig{
Pricing: &modelcatalog.Config{PricingURL: ptStr(rawURL)},
}
tableOut, _, _ := resolveWithCapture(t, log, nil, fileConfig)
require.Equal(t, rawURL, *tableOut.PricingURL,
"failed env lookup must preserve the original 'env.*' string — NOT default URL")
require.NotEqual(t, modelcatalog.DefaultPricingURL, *tableOut.PricingURL,
"CRITICAL: must NOT silently fall back to default URL on env lookup failure")
assert.True(t, log.hasLog("WARN", "env variable not found"),
"expected WARN about env lookup failure\n%s", log.dump())
t.Logf("PASS — missing env URL: preserved literal, NOT silently replaced with default")
}
// =============================================================================
// STEP 10 — Final assertions: no nil pointers, no panics, no silent fallback
// =============================================================================
func TestPricingE2E_Step10_NoNilPointers_AllInputCombinations(t *testing.T) {
type tc struct {
name string
db *configstoreTables.TableFrameworkConfig
file *framework.FrameworkConfig
}
cases := []tc{
{"nil/nil", nil, nil},
{"nil/empty-framework", nil, &framework.FrameworkConfig{}},
{"nil/empty-pricing", nil, &framework.FrameworkConfig{Pricing: &modelcatalog.Config{}}},
{"empty-db/nil", &configstoreTables.TableFrameworkConfig{}, nil},
{"empty-db/empty-file", &configstoreTables.TableFrameworkConfig{}, &framework.FrameworkConfig{}},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
log := newCapturingLogger()
tableOut, catalogOut, _ := resolveWithCapture(t, log, tc.db, tc.file)
require.NotNil(t, tableOut, "TableFrameworkConfig must never be nil")
require.NotNil(t, tableOut.PricingURL, "TableFrameworkConfig.PricingURL must never be nil")
require.NotNil(t, tableOut.PricingSyncInterval, "TableFrameworkConfig.PricingSyncInterval must never be nil")
require.NotNil(t, catalogOut, "modelcatalog.Config must never be nil")
require.NotNil(t, catalogOut.PricingURL, "modelcatalog.Config.PricingURL must never be nil")
require.NotNil(t, catalogOut.PricingSyncInterval, "modelcatalog.Config.PricingSyncInterval must never be nil")
// Resolution log must always be emitted — no silent resolution.
assert.True(t, log.hasLog("resolved pricing config"),
"resolution log must always be emitted\n%s", log.dump())
})
}
}
func TestPricingE2E_Step10_NoPanic_ConcurrentResolution(t *testing.T) {
// Ensure ResolveFrameworkPricingConfig is safe under concurrent calls.
// We use a single shared capturing logger (thread-safe) and call
// ResolveFrameworkPricingConfig directly — NOT via resolveWithCapture — to
// avoid racing on the package-level `logger` variable.
sharedLog := newCapturingLogger()
prev := getLogger()
SetLogger(sharedLog)
defer SetLogger(prev)
interval := int64(7200)
fileConfig := &framework.FrameworkConfig{
Pricing: &modelcatalog.Config{PricingSyncInterval: &interval},
}
var wg sync.WaitGroup
for i := 0; i < 50; i++ {
wg.Add(1)
go func() {
defer wg.Done()
tableOut, _, _ := ResolveFrameworkPricingConfig(nil, fileConfig)
if tableOut == nil || tableOut.PricingSyncInterval == nil {
t.Errorf("nil output under concurrent access")
}
}()
}
wg.Wait()
t.Logf("PASS — 50 concurrent calls completed without panic or nil output")
}
// =============================================================================
// STEP 10 — Full pipeline: initFrameworkConfig with real SQLite store
// =============================================================================
func TestPricingE2E_Step10_FullPipeline_WithRealSQLite(t *testing.T) {
dir := t.TempDir()
store := makeStore(t, dir)
ctx := context.Background()
log := newCapturingLogger()
// Boot 1: no DB, file has 7200 s.
cfg1 := &Config{ConfigStore: store}
fileConfig := &framework.FrameworkConfig{
Pricing: &modelcatalog.Config{
PricingURL: ptStr("https://file.example.com/pricing.json"),
PricingSyncInterval: ptI64(7200),
},
}
configData := &ConfigData{FrameworkConfig: fileConfig}
prev := getLogger()
SetLogger(log)
initFrameworkConfig(ctx, cfg1, configData)
SetLogger(prev)
if cfg1.ModelCatalog != nil {
t.Cleanup(func() { cfg1.ModelCatalog.Cleanup() })
}
require.NotNil(t, cfg1.FrameworkConfig, "FrameworkConfig must be populated after init")
require.NotNil(t, cfg1.FrameworkConfig.Pricing, "Pricing config must not be nil")
require.Equal(t, int64(7200), *cfg1.FrameworkConfig.Pricing.PricingSyncInterval,
"pricing_sync_interval must be 7200 s after file-based init")
// DB must have been backfilled.
dbRow, err := store.GetFrameworkConfig(ctx)
require.NoError(t, err, "DB must have a row after first init")
require.NotNil(t, dbRow, "DB row must not be nil")
require.NotNil(t, dbRow.PricingSyncInterval, "DB interval field must be populated")
require.Equal(t, int64(7200), *dbRow.PricingSyncInterval,
"DB must be backfilled with 7200 s after first init")
// Boot 2 (restart): same file config, DB has the values.
log2 := newCapturingLogger()
cfg2 := &Config{ConfigStore: store}
prev2 := getLogger()
SetLogger(log2)
initFrameworkConfig(ctx, cfg2, configData)
SetLogger(prev2)
if cfg2.ModelCatalog != nil {
t.Cleanup(func() { cfg2.ModelCatalog.Cleanup() })
}
require.Equal(t, int64(7200), *cfg2.FrameworkConfig.Pricing.PricingSyncInterval,
"pricing_sync_interval must be 7200 s on restart (DB authoritative)")
// Second boot must report DB as source.
assert.True(t, log2.hasLog("resolved pricing config", "source: db"),
"second boot must use DB as source\n%s", log2.dump())
// Boot 3: DB has 7200, file changed to 3600 — DB must still win.
log3 := newCapturingLogger()
cfg3 := &Config{ConfigStore: store}
changedFileConfig := &ConfigData{FrameworkConfig: &framework.FrameworkConfig{
Pricing: &modelcatalog.Config{
PricingURL: ptStr("https://new-file.example.com/pricing.json"),
PricingSyncInterval: ptI64(3600),
},
}}
prev3 := getLogger()
SetLogger(log3)
initFrameworkConfig(ctx, cfg3, changedFileConfig)
SetLogger(prev3)
if cfg3.ModelCatalog != nil {
t.Cleanup(func() { cfg3.ModelCatalog.Cleanup() })
}
require.Equal(t, int64(7200), *cfg3.FrameworkConfig.Pricing.PricingSyncInterval,
"DB (7200 s) must win over changed file config (3600 s) on third boot")
assert.True(t, log3.hasLog("INFO", "pricing_sync_interval overridden by DB"),
"expected override log on third boot\n%s", log3.dump())
t.Logf("PASS — full pipeline (3 boots): file→DB backfill→DB authoritative→DB wins over changed file")
}
// =============================================================================
// STEP 10 — Timing: modelcatalog.Init correctly converts seconds → Duration
// =============================================================================
func TestPricingE2E_Step10_SecondsToDurationConversion(t *testing.T) {
// Before the fix, PricingSyncInterval was *time.Duration and JSON `3600` was
// deserialized as 3600 nanoseconds → .Seconds() → 0 → ignored.
// This test proves the fix: 3600 (seconds) must produce a 1-hour duration.
dir := t.TempDir()
store := makeStore(t, dir)
ctx := context.Background()
clg := newCapturingLogger()
prev := getLogger()
SetLogger(clg)
defer SetLogger(prev)
syncSeconds := int64(3600)
cfg := &modelcatalog.Config{PricingSyncInterval: &syncSeconds}
// noSyncFunc prevents real HTTP requests to the pricing URL during this unit test.
mc, err := modelcatalog.Init(ctx, cfg, store, clg)
require.NoError(t, err)
mc.SetShouldSyncGate(noSyncFunc)
defer mc.Cleanup()
// The critical assertion: if the old *time.Duration bug were present,
// this would show "3.6µs" not "1h0m0s".
assert.True(t, clg.hasLog("INFO", "1h0m0s"),
"3600 seconds must produce 1h0m0s duration — not 3.6µs (the pre-fix bug)\n%s", clg.dump())
// Also verify 7200 → 2h (reuse the same store since we're only testing Init startup log)
clg2 := newCapturingLogger()
SetLogger(clg2)
syncSeconds2 := int64(7200)
cfg2 := &modelcatalog.Config{PricingSyncInterval: &syncSeconds2}
mc2, err := modelcatalog.Init(ctx, cfg2, store, clg2)
require.NoError(t, err)
mc2.SetShouldSyncGate(noSyncFunc)
defer mc2.Cleanup()
SetLogger(prev)
assert.True(t, clg2.hasLog("INFO", "2h0m0s"),
"7200 seconds must produce 2h0m0s duration\n%s", clg2.dump())
t.Logf("PASS — seconds→duration conversion: 3600→1h, 7200→2h (pre-fix bug would give 3.6µs/7.2µs)")
}
// =============================================================================
// Benchmark: ResolveFrameworkPricingConfig has negligible overhead
// =============================================================================
func BenchmarkResolveFrameworkPricingConfig(b *testing.B) {
// Set up a realistic scenario with both DB and file config populated.
dbURL := "https://db.example.com/pricing.json"
dbInterval := int64(3600)
dbConfig := &configstoreTables.TableFrameworkConfig{
ID: 1,
PricingURL: &dbURL,
PricingSyncInterval: &dbInterval,
}
fileConfig := &framework.FrameworkConfig{
Pricing: &modelcatalog.Config{
PricingURL: ptStr("https://file.example.com/pricing.json"),
PricingSyncInterval: ptI64(7200),
},
}
noop := &testLogger{}
SetLogger(noop)
b.ResetTimer()
for i := 0; i < b.N; i++ {
ResolveFrameworkPricingConfig(dbConfig, fileConfig)
}
}
// =============================================================================
// Summary helper test: print a human-readable scenario table
// =============================================================================
func TestPricingE2E_SummaryTable(t *testing.T) {
type scenario struct {
name string
expected string
testFunc func() (actual string, match bool)
}
defaultInterval := fmt.Sprintf("%d s", defaultSyncSecs)
scenarios := []scenario{
{
name: "Baseline (no config/DB)",
expected: fmt.Sprintf("url=%q interval=%s source=default", modelcatalog.DefaultPricingURL, defaultInterval),
testFunc: func() (string, bool) {
log := newCapturingLogger()
t, c, _ := resolveWithCapture(nil, log, nil, nil)
actual := fmt.Sprintf("url=%q interval=%d s source=%s", *t.PricingURL, *t.PricingSyncInterval,
func() string {
if log.hasLog("source: default") {
return "default"
}
return "other"
}())
_ = c
return actual, log.hasLog("source: default") && *t.PricingSyncInterval == defaultSyncSecs
},
},
{
name: "File config (7200 s)",
expected: "interval=7200 s source=file",
testFunc: func() (string, bool) {
log := newCapturingLogger()
fc := &framework.FrameworkConfig{Pricing: &modelcatalog.Config{PricingSyncInterval: ptI64(7200)}}
tab, _, _ := resolveWithCapture(nil, log, nil, fc)
return fmt.Sprintf("interval=%d s", *tab.PricingSyncInterval),
*tab.PricingSyncInterval == 7200
},
},
{
name: "Too low (100 s) → clamped",
expected: "interval=3600 s WARN=clamped",
testFunc: func() (string, bool) {
log := newCapturingLogger()
fc := &framework.FrameworkConfig{Pricing: &modelcatalog.Config{PricingSyncInterval: ptI64(100)}}
tab, _, _ := resolveWithCapture(nil, log, nil, fc)
return fmt.Sprintf("interval=%d s WARN=%v", *tab.PricingSyncInterval, log.hasLog("WARN", "clamping")),
*tab.PricingSyncInterval == 3600 && log.hasLog("WARN", "clamping")
},
},
{
name: "Zero → ignored",
expected: fmt.Sprintf("interval=%s WARN=invalid", defaultInterval),
testFunc: func() (string, bool) {
log := newCapturingLogger()
fc := &framework.FrameworkConfig{Pricing: &modelcatalog.Config{PricingSyncInterval: ptI64(0)}}
tab, _, _ := resolveWithCapture(nil, log, nil, fc)
return fmt.Sprintf("interval=%d s WARN=%v", *tab.PricingSyncInterval, log.hasLog("WARN")),
*tab.PricingSyncInterval == defaultSyncSecs && log.hasLog("WARN")
},
},
{
name: "Missing env var → literal preserved",
expected: "url=env.MISSING_XYZ (NOT default URL)",
testFunc: func() (string, bool) {
log := newCapturingLogger()
fc := &framework.FrameworkConfig{Pricing: &modelcatalog.Config{PricingURL: ptStr("env.MISSING_XYZ_123")}}
tab, _, _ := resolveWithCapture(nil, log, nil, fc)
return fmt.Sprintf("url=%q", *tab.PricingURL),
*tab.PricingURL == "env.MISSING_XYZ_123" && *tab.PricingURL != modelcatalog.DefaultPricingURL
},
},
}
t.Log("")
t.Log("╔══════════════════════════════════════════════════════════════════════════════╗")
t.Log("║ E2E PRICING CONFIG SCENARIO SUMMARY TABLE ║")
t.Log("╠══════════════════════════════════════════╦══════════════════╦═════════════╣")
t.Log("║ Scenario ║ Expected ║ Match? ║")
t.Log("╠══════════════════════════════════════════╬══════════════════╬═════════════╣")
allPass := true
for _, sc := range scenarios {
actual, match := sc.testFunc()
status := "✔ PASS"
if !match {
status = "✘ FAIL"
allPass = false
}
t.Logf("║ %-40s ║ %-16s ║ %-11s ║", sc.name, actual[:min(len(actual), 16)], status)
}
t.Log("╚══════════════════════════════════════════╩══════════════════╩═════════════╝")
t.Log("")
if !allPass {
t.Fail()
}
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
// Ensure time is imported (used in TestPricingE2E_Step7 indirectly via modelcatalog)
var _ = time.Second