Files
OpenFlare/backend/pkg/testhelper/test_helper.go
T
ryan 299ac30ee4 refactor(core): align with cordis spatiotemporal composability architecture
- Purify core micro-kernel by removing context hardcoded helpers and reverse dependencies
- Eliminate init() side effects in infra plugins with reversible lifecycle disposal
- Completely isolate plugins by removing cross-plugin imports and using core/contracts
- Introduce TaskService and RiskControlService contracts for unified cross-plugin APIs
- Regenerate Swagger documentation and update developer guide matrix
- Achieve 0 violations in check_cordis_architecture.sh and 100% test pass
2026-08-28 15:05:31 +08:00

334 lines
14 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
// Package testhelper 提供测试辅助工具
package testhelper
import (
"context"
"testing"
"time"
"github.com/alicebob/miniredis/v2"
"github.com/glebarez/sqlite"
"github.com/redis/go-redis/v9"
"github.com/redis/go-redis/v9/maintnotifications"
"gorm.io/gorm"
cachepkg "Wavelet/plugins/infra/cache"
db "Wavelet/plugins/infra/database"
)
// SystemConfig 测试用系统配置表
type SystemConfig struct {
Key string `gorm:"primaryKey;size:64;not null"`
Value string `gorm:"type:text;not null"`
Type string `gorm:"size:32;not null"`
Visibility string `gorm:"size:32;not null;default:'hidden'"`
Description string `gorm:"size:255"`
}
// TableName 返回测试配置表表名
func (SystemConfig) TableName() string {
return "w_system_configs"
}
type userHelper struct {
ID uint64 `gorm:"primaryKey;autoIncrement"`
Username string `gorm:"size:64;uniqueIndex;not null"`
Nickname string `gorm:"size:64;not null;default:''"`
Password string `gorm:"size:255;not null;default:''"`
Email string `gorm:"size:128;index;default:''"`
AvatarURL string `gorm:"size:255;default:''"`
IsAdmin bool `gorm:"default:false;not null"`
IsActive bool `gorm:"default:true;not null"`
NeedChangePassword bool `gorm:"default:false;not null"`
Bio string `gorm:"size:500;default:''"`
Phone string `gorm:"size:32;default:''"`
Gender string `gorm:"size:16;default:''"`
Website string `gorm:"size:255;default:''"`
Location string `gorm:"size:255;default:''"`
LastLoginAt time.Time
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
}
func (userHelper) TableName() string { return "w_users" }
type accessTokenHelper struct {
ID uint64 `gorm:"primaryKey;autoIncrement"`
UserID uint64 `gorm:"not null;index"`
Name string `gorm:"size:64;not null"`
TokenHash string `gorm:"size:64;uniqueIndex;not null"`
MaskedToken string `gorm:"size:32;not null"`
IsAdmin bool `gorm:"default:false;not null"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
}
func (accessTokenHelper) TableName() string { return "w_access_tokens" }
type authSourceHelper struct {
ID uint64 `gorm:"primaryKey;autoIncrement"`
Type string `gorm:"size:32;not null"`
Name string `gorm:"size:64;not null"`
Enabled bool `gorm:"default:false;not null"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
}
func (authSourceHelper) TableName() string { return "w_auth_sources" }
type externalAccountHelper struct {
ID uint64 `gorm:"primaryKey;autoIncrement"`
UserID uint64 `gorm:"not null;index"`
AuthSourceType string `gorm:"size:32;not null;index"`
ExternalID string `gorm:"size:128;not null;index"`
Username string `gorm:"size:128;default:''"`
Email string `gorm:"size:128;default:''"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
}
func (externalAccountHelper) TableName() string { return "w_external_accounts" }
type uploadHelper struct {
ID uint64 `gorm:"primaryKey;autoIncrement"`
UserID uint64 `gorm:"not null;index"`
FileName string `gorm:"size:255;not null"`
FilePath string `gorm:"size:500;not null"`
FileSize int64 `gorm:"not null"`
MimeType string `gorm:"size:128;not null"`
Extension string `gorm:"size:32;not null"`
Hash string `gorm:"size:64;index;not null;default:''"`
Type string `gorm:"size:50;not null;index"`
Status string `gorm:"size:20;not null;default:'pending'"`
AccessMode int `gorm:"not null;default:0"`
Metadata string `gorm:"type:text"`
CreatedAt time.Time `gorm:"autoCreateTime;index"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
}
func (uploadHelper) TableName() string { return "w_uploads" }
type uploadStatHelper struct {
ID uint64 `gorm:"primaryKey;autoIncrement"`
Dimension string `gorm:"size:32;not null;uniqueIndex:idx_stat_dimension_key"`
StatKey string `gorm:"size:100;not null;uniqueIndex:idx_stat_dimension_key"`
FileCount int64 `gorm:"not null;default:0"`
FileSize int64 `gorm:"not null;default:0"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
}
func (uploadStatHelper) TableName() string { return "w_upload_stats" }
type messageChannelHelper struct {
ID uint64 `gorm:"primaryKey;autoIncrement"`
Type string `gorm:"size:32;not null"`
Name string `gorm:"size:64;not null"`
OwnerScope string `gorm:"size:32;not null;default:'system'"`
OwnerID *uint64
Credentials string `gorm:"type:text;not null"`
Extra string `gorm:"type:text"`
Enabled bool `gorm:"default:false;not null"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
}
func (messageChannelHelper) TableName() string { return "w_message_channels" }
type messageBindingHelper struct {
ID uint64 `gorm:"primaryKey;autoIncrement"`
ChannelID uint64 `gorm:"not null;index"`
PlatformUserID string `gorm:"size:128;not null;index"`
UserID uint64 `gorm:"not null;index"`
CreatedAt time.Time `gorm:"autoCreateTime"`
}
func (messageBindingHelper) TableName() string { return "w_message_bindings" }
type messagePairingHelper struct {
ID uint64 `gorm:"primaryKey;autoIncrement"`
Code string `gorm:"size:32;uniqueIndex;not null"`
ChannelID uint64 `gorm:"not null;index"`
PlatformUserID string `gorm:"size:128;not null;index"`
UserID uint64 `gorm:"not null;index"`
ExpiresAt time.Time `gorm:"not null;index"`
CreatedAt time.Time `gorm:"autoCreateTime"`
}
func (messagePairingHelper) TableName() string { return "w_message_pairing_codes" }
type taskExecutionHelper struct {
ID uint64 `gorm:"primaryKey;autoIncrement"`
TaskID string `gorm:"size:128;uniqueIndex;not null"`
TaskType string `gorm:"size:64;index;not null"`
TaskName string `gorm:"size:128"`
Status string `gorm:"size:32;index;not null"`
Retryable bool `gorm:"not null;default:false"`
MaxRetry int `gorm:"not null;default:0"`
RetryCount int `gorm:"not null;default:0"`
Log string `gorm:"type:text"`
ErrorMessage string `gorm:"type:text"`
Result string `gorm:"type:text"`
StartedAt *time.Time `gorm:"index"`
FinishedAt *time.Time
Duration int64
Payload string `gorm:"type:text"`
TriggeredBy string `gorm:"size:32;not null;default:system"`
CreatedAt time.Time `gorm:"autoCreateTime;index"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
}
func (taskExecutionHelper) TableName() string {
return "w_task_executions"
}
type scheduleHelper struct {
ID uint64 `gorm:"primaryKey;autoIncrement"`
TaskType string `gorm:"size:64;uniqueIndex;not null"`
TaskName string `gorm:"size:128;not null"`
CronExpr string `gorm:"size:64;not null"`
Payload string `gorm:"type:text"`
Enabled bool `gorm:"default:true;not null"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
}
func (scheduleHelper) TableName() string { return "w_schedules" }
const (
configTypeSystem = "system"
configTypeBusiness = "business"
configValueTrue = "true"
configValueFalse = "false"
)
// SetupTestEnvironment initializes an in-memory SQLite DB, seeds default configurations,
// starts miniredis, and overrides the global db/Redis clients. It returns a cleanup function.
func SetupTestEnvironment(t *testing.T) (*gorm.DB, *miniredis.Miniredis, func()) {
sqliteDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
DisableForeignKeyConstraintWhenMigrating: true,
})
if err != nil {
t.Fatalf("failed to open in-memory SQLite db: %v", err)
}
if sqlDB, err := sqliteDB.DB(); err == nil {
sqlDB.SetMaxOpenConns(1)
}
// AutoMigrate all tables via internal test helpers to completely decouple testhelper from domain plugins
err = sqliteDB.AutoMigrate(
&userHelper{},
&accessTokenHelper{},
&authSourceHelper{},
&externalAccountHelper{},
&SystemConfig{},
&uploadHelper{},
&uploadStatHelper{},
&taskExecutionHelper{},
&scheduleHelper{},
&messageChannelHelper{},
&messageBindingHelper{},
&messagePairingHelper{},
)
if err != nil {
t.Fatalf("failed to auto migrate tables: %v", err)
}
mr, err := miniredis.Run()
if err != nil {
t.Fatalf("failed to start miniredis: %v", err)
}
redisClient := redis.NewClient(&redis.Options{
Addr: mr.Addr(),
MaintNotificationsConfig: &maintnotifications.Config{
Mode: maintnotifications.ModeDisabled,
},
})
db.SetDB(sqliteDB)
cachepkg.Redis = redisClient
seedDefaultConfigs(t, sqliteDB)
cleanup := func() {
runExtraCleanups()
_ = redisClient.Close()
mr.Close()
db.SetDB(nil)
cachepkg.Redis = nil
}
return sqliteDB, mr, cleanup
}
func getSeedConfigsPart1() []SystemConfig {
return []SystemConfig{
{Key: "upload_allowed_extensions", Value: `["jpg", "jpeg", "png", "gif", "webp", "txt", "pdf", "zip"]`, Type: configTypeSystem, Description: "允许上传的文件扩展名列表(JSON 字符串数组)"},
{Key: "site_name", Value: "Wavelet", Type: configTypeSystem, Description: "站点名称"},
{Key: "site_description", Value: "Lightweight and Modular Web Application Platform", Type: configTypeSystem, Description: "站点描述"},
{Key: "password_login_enabled", Value: configValueTrue, Type: configTypeSystem, Description: "是否开启账号密码登录(true/false)"},
{Key: "registration_enabled", Value: configValueTrue, Type: configTypeSystem, Description: "是否允许新用户注册(全局总开关,true/false)"},
{Key: "password_register_enabled", Value: configValueTrue, Type: configTypeSystem, Description: "是否允许账号密码注册(true/false)"},
{Key: "oidc_login_enabled", Value: configValueFalse, Type: configTypeSystem, Description: "是否开启 OIDC 登录(true/false)"},
{Key: "server_address", Value: "http://localhost:8000", Type: configTypeSystem, Description: "服务端访问地址(用于生成绝对路径链接,多个地址用英文逗号分隔)"},
{Key: "smtp_host", Value: "", Type: configTypeSystem, Description: "SMTP 服务器主机名或 IP"},
{Key: "smtp_port", Value: "587", Type: configTypeSystem, Description: "SMTP 服务器端口(标准 STARTTLS 为 587,SMTPS 为 465)"},
{Key: "smtp_username", Value: "", Type: configTypeSystem, Description: "SMTP 账户(如 sender@example.com)"},
{Key: "smtp_password", Value: "", Type: configTypeSystem, Description: "SMTP 访问凭证(授权码/密码)"},
{Key: "email_login_verification_enabled", Value: configValueFalse, Type: configTypeSystem, Description: "是否开启邮箱登录验证(true/false)"},
{Key: "email_register_verification_enabled", Value: configValueFalse, Type: configTypeSystem, Description: "是否开启邮箱注册验证(true/false)"},
{Key: "menu_display_config", Value: "{}", Type: configTypeSystem, Description: "目录显示配置(JSON 字符串,格式为 {url: enabled})"},
{Key: "search_engine_indexing_enabled", Value: configValueFalse, Type: configTypeSystem, Description: "是否允许搜索引擎检索"},
{Key: "file_access_whitelist", Value: `["avatar"]`, Type: configTypeSystem, Description: "免登录访问的文件业务类型白名单"},
{Key: "disk_cache_max_size_mb", Value: "100", Type: configTypeSystem, Description: "磁盘缓存最大空间大小 (MB)"},
{Key: "disk_cache_ttl_minutes", Value: "60", Type: configTypeSystem, Description: "磁盘缓存默认有效期 (分钟)"},
{Key: "disk_cache_lru_enabled", Value: configValueTrue, Type: configTypeSystem, Description: "是否启用 LRU 淘汰机制"},
{Key: "login_session_ttl_hours", Value: "0", Type: configTypeSystem, Description: "登录会话过期时间 (小时,0表示浏览器关闭后自动退出,-1表示永不过期)"},
{Key: "update_upstream_repository", Value: "Rain-kl/Wavelet", Type: configTypeSystem, Description: "GitHub Actions Release 上游仓库"},
{Key: "storage_config", Value: `{"driver":"local","local":{"root":"."},"s3":{"region":"us-east-1"},"r2":{"region":"auto"},"minio":{"region":"us-east-1","path_style":true},"oss":{},"webdav":{}}`, Type: configTypeSystem, Description: "文件存储驱动及连接配置(JSON)"},
{Key: "log_database", Value: "sqlite", Type: configTypeSystem, Description: "当前日志主库"},
{Key: "log_db_migration", Value: "", Type: configTypeSystem, Description: "日志库迁移冻结标记"},
{Key: "log_retention_days_postgres", Value: "30", Type: configTypeBusiness, Description: "PostgreSQL 用户访问日志保留天数"},
{Key: "log_retention_days_sqlite", Value: "30", Type: configTypeBusiness, Description: "SQLite 用户访问日志保留天数"},
{Key: "log_retention_days_clickhouse", Value: "30", Type: configTypeBusiness, Description: "ClickHouse 用户访问日志保留天数"},
}
}
func seedDefaultConfigs(t *testing.T, tx *gorm.DB) {
defaultConfigs := getSeedConfigsPart1()
if err := tx.Create(&defaultConfigs).Error; err != nil {
t.Fatalf("failed to seed default system configs: %v", err)
}
publicKeys := map[string]struct{}{
"upload_allowed_extensions": {},
"site_name": {},
"password_login_enabled": {},
"registration_enabled": {},
"password_register_enabled": {},
"oidc_login_enabled": {},
}
keys := make([]string, 0, len(publicKeys))
for key := range publicKeys {
keys = append(keys, key)
}
if err := tx.Model(&SystemConfig{}).
Where("key IN ?", keys).
Update("visibility", "visible").Error; err != nil {
t.Fatalf("failed to seed public system config visibility: %v", err)
}
for _, config := range defaultConfigs {
if _, ok := publicKeys[config.Key]; ok {
config.Visibility = "visible"
}
_ = cachepkg.HSetJSON(context.Background(), "system_configs", config.Key, &config)
}
}