Files
OpenFlare/internal/apps/openflare/option/logics_test.go
T
ryan 7d71f1e4e1 feat(log): decouple log storage from ClickHouse with switchable logstore
- New internal/repository/logstore abstraction: exported domain interfaces
  (AccessLogStore/ObservabilityStore/UserAccessLogStore/StatusStore),
  config-driven provider (Active/Build/Migrating/SetConfigReader), GORM
  implementation for PostgreSQL/SQLite (incl. hourly rollups computed in
  real time, migration listers, PG partition maintenance), and a ClickHouse
  wrapper preserving the native batch path; repository facade delegates to
  logstore; import-lint test enforces apps never import analyticsrepo.
- ClickHouse is now optional: the log DB is either the main DB (postgres
  when database.enabled, else sqlite) or clickhouse; boot validation +
  first-run seed; log_database / log_db_migration are protected keys.
- New user task 切换日志数据库 (of_log_db_switch): freeze log writes,
  drain batch writers, copy all 6 raw log tables by id (preserving IDs)
  with target-partition pre-creation for PG, flip log_database on success,
  clear the freeze flag on failure.
- Per-store retention (log_retention_days_*) with expiry cleanup folded
  into the daily system_cleanup task; legacy database_auto_cleanup_* and
  of_database_auto_cleanup decommissioned.
- goose migrations: 6 log tables in PG (2 monthly-partitioned) + SQLite,
  retention config seeds, schedule cleanup; GET
  /api/v1/admin/status/log-database endpoint; frontend retention settings,
  switch-task UI and status badge; changelog and docs updated.

docs(plan): log database decoupling implementation plan

docs(design): log database decoupling design (ClickHouse optional)
2026-08-08 19:43:01 +08:00

119 lines
3.1 KiB
Go

// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package option
import (
"context"
"testing"
db "github.com/Rain-kl/Wavelet/internal/infra/persistence"
"github.com/Rain-kl/Wavelet/internal/model"
"github.com/Rain-kl/Wavelet/internal/repository"
"github.com/glebarez/sqlite"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/gorm"
)
func setupOptionTestDB(t *testing.T) func() {
t.Helper()
sqliteDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
DisableForeignKeyConstraintWhenMigrating: true,
})
require.NoError(t, err)
require.NoError(t, sqliteDB.AutoMigrate(&model.SystemConfig{}))
db.SetDB(sqliteDB)
// 预填充一些业务配置用于测试
seedConfigs := []model.SystemConfig{
{Key: "geoip_provider", Value: "ipinfo", Type: "business", Visibility: 0},
{Key: "uptime_kuma_password", Value: "secret-pwd", Type: "business", Visibility: 0},
}
for _, cfg := range seedConfigs {
require.NoError(t, sqliteDB.Create(&cfg).Error)
}
return func() {
db.SetDB(nil)
}
}
// setTestConfig 设置测试配置的辅助函数
func setTestConfig(t *testing.T, ctx context.Context, key, value string) {
t.Helper()
require.NoError(t, db.DB(ctx).Model(&model.SystemConfig{}).Where("key = ?", key).Update("value", value).Error)
}
func TestListOptionsFiltersSecretKeys(t *testing.T) {
cleanup := setupOptionTestDB(t)
defer cleanup()
ctx := context.Background()
options, err := listOptions(ctx)
require.NoError(t, err)
keys := make(map[string]string, len(options))
for _, option := range options {
keys[option.Key] = option.Value
}
// geoip_provider 应该出现在列表中
assert.Equal(t, "ipinfo", keys["geoip_provider"])
// 敏感配置(密码)应该被过滤掉
assert.NotContains(t, keys, "uptime_kuma_password")
}
func TestUpdateOptionPersistsToSystemConfig(t *testing.T) {
cleanup := setupOptionTestDB(t)
defer cleanup()
ctx := context.Background()
err := updateOption(ctx, model.OpenFlareOption{
Key: model.ConfigKeyGeoIPProvider,
Value: "mmdb",
})
require.NoError(t, err)
// 验证配置已写入 SystemConfig
config, err := repository.GetSystemConfigByKey(ctx, model.ConfigKeyGeoIPProvider)
require.NoError(t, err)
assert.Equal(t, "mmdb", config.Value)
}
func TestUpdateOpenRestyOptionPersistsToSystemConfig(t *testing.T) {
cleanup := setupOptionTestDB(t)
defer cleanup()
ctx := context.Background()
require.NoError(t, db.DB(ctx).Create(&model.SystemConfig{
Key: model.ConfigKeyOpenRestyEventsUse,
Value: "epoll",
Type: "business",
Visibility: 0,
}).Error)
err := updateOption(ctx, model.OpenFlareOption{
Key: model.ConfigKeyOpenRestyEventsUse,
Value: "kqueue",
})
require.NoError(t, err)
config, err := repository.GetSystemConfigByKey(ctx, model.ConfigKeyOpenRestyEventsUse)
require.NoError(t, err)
assert.Equal(t, "kqueue", config.Value)
}
func TestLookupGeoIPDisabledProvider(t *testing.T) {
cleanup := setupOptionTestDB(t)
defer cleanup()
ctx := context.Background()
view, err := lookupGeoIP(ctx, "disabled", "8.8.8.8")
require.NoError(t, err)
assert.Equal(t, "disabled", view.Provider)
assert.Equal(t, "8.8.8.8", view.IP)
}