mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-05 07:26:36 +08:00
refactor(backend): rename OpenFlare directory to lowercase openflare
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package testhelper
|
||||
|
||||
// RegisterCleanup registers an extra cleanup hook invoked by SetupTestEnvironment.
|
||||
func RegisterCleanup(fn func()) {
|
||||
extraCleanups = append(extraCleanups, fn)
|
||||
}
|
||||
|
||||
var extraCleanups []func()
|
||||
|
||||
func runExtraCleanups() {
|
||||
for _, fn := range extraCleanups {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package testhelper
|
||||
|
||||
import (
|
||||
"Wavelet/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// NewTestGinEngine 创建带 ErrorHandlerMiddleware 的 Gin 引擎,与生产环境错误响应行为一致。
|
||||
func NewTestGinEngine(middlewares ...gin.HandlerFunc) *gin.Engine {
|
||||
gin.SetMode(gin.TestMode)
|
||||
r := gin.New()
|
||||
r.Use(response.ErrorHandlerMiddleware())
|
||||
for _, middleware := range middlewares {
|
||||
r.Use(middleware)
|
||||
}
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package testhelper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"Wavelet/core/contracts"
|
||||
"Wavelet/pkg/idgen"
|
||||
adminmodel "Wavelet/plugins/domain/admin/model"
|
||||
"Wavelet/plugins/infra/database"
|
||||
)
|
||||
|
||||
// NoopTaskService is a contracts.TaskService that records dispatch and ignores the rest.
|
||||
type NoopTaskService struct {
|
||||
LastType string
|
||||
LastPayload []byte
|
||||
}
|
||||
|
||||
var _ contracts.TaskService = (*NoopTaskService)(nil)
|
||||
|
||||
func (s *NoopTaskService) Dispatch(ctx context.Context, taskType string, payload []byte, triggeredBy string) (string, error) {
|
||||
s.LastType = taskType
|
||||
s.LastPayload = payload
|
||||
taskID := fmt.Sprintf("test-task-%d", time.Now().UnixNano())
|
||||
if conn := database.DB(ctx); conn != nil {
|
||||
_ = conn.Create(&adminmodel.TaskExecution{
|
||||
ID: idgen.NextUint64ID(),
|
||||
TaskID: taskID,
|
||||
TaskType: taskType,
|
||||
Status: adminmodel.TaskExecutionStatusPending,
|
||||
TriggeredBy: triggeredBy,
|
||||
Payload: string(payload),
|
||||
}).Error
|
||||
}
|
||||
return taskID, nil
|
||||
}
|
||||
func (s *NoopTaskService) Retry(context.Context, uint64) (string, error) { return "", nil }
|
||||
func (s *NoopTaskService) ListTasks() []contracts.TaskMetaDTO { return nil }
|
||||
func (s *NoopTaskService) GetTaskMeta(string) (contracts.TaskMetaDTO, bool) {
|
||||
return contracts.TaskMetaDTO{}, false
|
||||
}
|
||||
func (s *NoopTaskService) ValidatePayload(_ string, payload []byte) ([]byte, error) {
|
||||
return payload, nil
|
||||
}
|
||||
func (s *NoopTaskService) ReloadScheduler() error { return nil }
|
||||
func (s *NoopTaskService) AppendLog(context.Context, string, ...any) {}
|
||||
func (s *NoopTaskService) ListExecutions(context.Context, string, string, int, int) ([]contracts.TaskExecutionDTO, int64, error) {
|
||||
return nil, 0, nil
|
||||
}
|
||||
func (s *NoopTaskService) GetExecution(context.Context, uint64) (*contracts.TaskExecutionDTO, error) {
|
||||
return &contracts.TaskExecutionDTO{TaskID: "test-task"}, nil
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package testhelper
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"Wavelet/core/contracts"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// StubAuth is a contracts.AuthService that admits every request.
|
||||
type StubAuth struct {
|
||||
User *contracts.UserDTO
|
||||
Sources []contracts.AuthSourceViewDTO
|
||||
}
|
||||
|
||||
var _ contracts.AuthService = StubAuth{}
|
||||
|
||||
func passThrough() gin.HandlerFunc {
|
||||
return func(c *gin.Context) { c.Next() }
|
||||
}
|
||||
|
||||
func (s StubAuth) RequireAuthMiddleware() any { return passThrough() }
|
||||
func (s StubAuth) RequireAdminMiddleware() any { return passThrough() }
|
||||
func (s StubAuth) DisallowTokenAuthMiddleware() any {
|
||||
return passThrough()
|
||||
}
|
||||
|
||||
func (s StubAuth) GetCurrentUser(context.Context) (*contracts.UserDTO, error) {
|
||||
return s.User, nil
|
||||
}
|
||||
func (s StubAuth) GetCurrentUserID(context.Context) (uint64, error) {
|
||||
if s.User == nil {
|
||||
return 0, nil
|
||||
}
|
||||
return s.User.ID, nil
|
||||
}
|
||||
func (s StubAuth) VerifyToken(context.Context, string) (*contracts.UserDTO, error) {
|
||||
return s.User, nil
|
||||
}
|
||||
func (s StubAuth) CreateSession(context.Context, uint64, map[string]any) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
func (s StubAuth) RevokeToken(context.Context, string) error { return nil }
|
||||
func (s StubAuth) RevokeUserSessions(context.Context, uint64) error { return nil }
|
||||
func (s StubAuth) InvalidateCachedUser(context.Context, uint64) {}
|
||||
func (s StubAuth) InvalidateCachedToken(context.Context, string) {}
|
||||
func (s StubAuth) ListAuthSources(context.Context) ([]contracts.AuthSourceViewDTO, error) {
|
||||
return s.Sources, nil
|
||||
}
|
||||
func (s StubAuth) CreateAuthSource(context.Context, contracts.AuthSourceDTO) (*contracts.AuthSourceDTO, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (s StubAuth) UpdateAuthSource(context.Context, uint64, contracts.AuthSourceDTO) (*contracts.AuthSourceDTO, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (s StubAuth) DeleteAuthSource(context.Context, uint64) error { return nil }
|
||||
func (s StubAuth) ToggleAuthSource(context.Context, uint64) (*contracts.AuthSourceDTO, error) {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
// Copyright 2025 linux.do
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package testhelper 提供测试辅助工具
|
||||
package testhelper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics"
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
"Wavelet/openflare/plugins/server/kernel/repository/logstore"
|
||||
"Wavelet/pkg/idgen"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
configTypeSystem = "system"
|
||||
configTypeBusiness = "business"
|
||||
configValueTrue = "true"
|
||||
configValueFalse = "false"
|
||||
)
|
||||
|
||||
// SetupTestEnvironment initializes an in-memory SQLite DB and seeds default
|
||||
// configurations. Redis is no longer owned by OpenFlare.
|
||||
func SetupTestEnvironment(t *testing.T) (*gorm.DB, any, func()) {
|
||||
t.Helper()
|
||||
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)
|
||||
}
|
||||
|
||||
err = sqliteDB.AutoMigrate(
|
||||
&model.User{},
|
||||
&model.AuthSource{},
|
||||
&model.ExternalAccount{},
|
||||
&model.SystemConfig{},
|
||||
&model.Upload{},
|
||||
&model.UploadStat{},
|
||||
&model.TaskExecution{},
|
||||
&model.Template{},
|
||||
&model.AccessToken{},
|
||||
&model.Schedule{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to auto migrate tables: %v", err)
|
||||
}
|
||||
|
||||
db.SetDB(sqliteDB)
|
||||
if err := idgen.Init(1); err != nil {
|
||||
t.Fatalf("idgen.Init: %v", err)
|
||||
}
|
||||
seedDefaultConfigs(t, sqliteDB)
|
||||
repository.ResetSystemConfigRAMCacheForTest()
|
||||
|
||||
cleanup := func() {
|
||||
runExtraCleanups()
|
||||
repository.StopSystemConfigCacheListener()
|
||||
repository.ResetSystemConfigRAMCacheForTest()
|
||||
repository.SetAuthService(nil)
|
||||
repository.SetUserService(nil)
|
||||
db.SetDB(nil)
|
||||
}
|
||||
|
||||
return sqliteDB, nil, cleanup
|
||||
}
|
||||
|
||||
func getSeedConfigsPart1() []model.SystemConfig {
|
||||
return []model.SystemConfig{
|
||||
{Key: model.ConfigKeyUploadAllowedExtensions, Value: "jpg,png,webp", Type: configTypeSystem, Description: "允许上传的图片扩展名(逗号分隔)"},
|
||||
{Key: model.ConfigKeySiteName, Value: "OpenFlare", Type: configTypeSystem, Description: "系统平台的展示名称"},
|
||||
{Key: model.ConfigKeyPasswordLoginEnabled, Value: configValueTrue, Type: configTypeSystem},
|
||||
{Key: model.ConfigKeyRegistrationEnabled, Value: configValueFalse, Type: configTypeSystem},
|
||||
{Key: model.ConfigKeyPasswordRegisterEnabled, Value: configValueFalse, Type: configTypeSystem},
|
||||
{Key: model.ConfigKeyOIDCLoginEnabled, Value: configValueTrue, Type: configTypeSystem},
|
||||
{Key: model.ConfigKeyMaxAPIKeysPerUser, Value: "5", Type: "business"},
|
||||
{Key: model.ConfigKeyCapLoginEnabled, Value: configValueFalse, Type: configTypeSystem},
|
||||
{Key: model.ConfigKeyCapAutoSolve, Value: configValueTrue, Type: configTypeSystem},
|
||||
{Key: model.ConfigKeyCapChallengeCount, Value: "1", Type: configTypeSystem},
|
||||
{Key: model.ConfigKeyCapChallengeSize, Value: "32", Type: configTypeSystem},
|
||||
{Key: model.ConfigKeyCapChallengeDifficulty, Value: "4", Type: configTypeSystem},
|
||||
{Key: model.ConfigKeyCapChallengeTTL, Value: "600", Type: configTypeSystem},
|
||||
{Key: model.ConfigKeyCapTokenTTL, Value: "1200", Type: configTypeSystem},
|
||||
}
|
||||
}
|
||||
|
||||
func getSeedConfigsPart2() []model.SystemConfig {
|
||||
return []model.SystemConfig{
|
||||
{Key: model.ConfigKeyServerAddress, Value: "", Type: configTypeSystem},
|
||||
{Key: model.ConfigKeySMTPHost, Value: "", Type: configTypeSystem},
|
||||
{Key: model.ConfigKeySMTPPort, Value: "587", Type: configTypeSystem},
|
||||
{Key: model.ConfigKeySMTPUsername, Value: "", Type: configTypeSystem},
|
||||
{Key: model.ConfigKeySMTPPassword, Value: "", Type: configTypeSystem},
|
||||
{Key: model.ConfigKeyEmailLoginVerificationEnabled, Value: configValueFalse, Type: configTypeSystem},
|
||||
{Key: model.ConfigKeyEmailRegisterVerificationEnabled, Value: configValueFalse, Type: configTypeSystem},
|
||||
{Key: model.ConfigKeyMenuDisplayConfig, Value: "{}", Type: configTypeSystem},
|
||||
{Key: model.ConfigKeySearchEngineIndexingEnabled, Value: configValueFalse, Type: configTypeSystem},
|
||||
{Key: model.ConfigKeyFileAccessWhitelist, Value: `["avatar"]`, Type: configTypeSystem},
|
||||
{Key: model.ConfigKeyDiskCacheMaxSizeMB, Value: "100", Type: configTypeSystem},
|
||||
{Key: model.ConfigKeyDiskCacheTTLMinutes, Value: "60", Type: configTypeSystem},
|
||||
{Key: model.ConfigKeyDiskCacheLRUEnabled, Value: configValueTrue, Type: configTypeSystem},
|
||||
{Key: model.ConfigKeyLoginSessionTTLHours, Value: "0", Type: configTypeSystem},
|
||||
{Key: model.ConfigKeyUpdateUpstreamRepository, Value: "Rain-kl/OpenFlare", Type: configTypeSystem},
|
||||
{Key: model.ConfigKeyStorageConfig, 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},
|
||||
{Key: model.ConfigKeyRelayFRPSWebUIEnabled, Value: configValueFalse, Type: configTypeBusiness},
|
||||
{Key: model.ConfigKeyRelayFRPSWebUIPort, Value: "17500", Type: configTypeBusiness},
|
||||
{Key: model.ConfigKeyPagesMaxPackageSizeMB, Value: "100", Type: configTypeBusiness},
|
||||
{Key: model.ConfigKeyPagesMaxHistoryCount, Value: "20", Type: configTypeBusiness},
|
||||
{Key: model.ConfigKeyLogRetentionDaysPostgres, Value: "90", Type: configTypeBusiness},
|
||||
{Key: model.ConfigKeyLogRetentionDaysSQLite, Value: "90", Type: configTypeBusiness},
|
||||
{Key: model.ConfigKeyLogRetentionDaysClickHouse, Value: "90", Type: configTypeBusiness},
|
||||
}
|
||||
}
|
||||
|
||||
func seedDefaultConfigs(t *testing.T, tx *gorm.DB) {
|
||||
t.Helper()
|
||||
defaultConfigs := append(getSeedConfigsPart1(), getSeedConfigsPart2()...)
|
||||
if err := tx.Create(&defaultConfigs).Error; err != nil {
|
||||
t.Fatalf("failed to seed default system configs: %v", err)
|
||||
}
|
||||
|
||||
publicKeys := []string{
|
||||
model.ConfigKeyUploadAllowedExtensions,
|
||||
model.ConfigKeySiteName,
|
||||
model.ConfigKeyPasswordLoginEnabled,
|
||||
model.ConfigKeyRegistrationEnabled,
|
||||
model.ConfigKeyPasswordRegisterEnabled,
|
||||
model.ConfigKeyOIDCLoginEnabled,
|
||||
model.ConfigKeyMaxAPIKeysPerUser,
|
||||
model.ConfigKeyCapLoginEnabled,
|
||||
model.ConfigKeyCapAutoSolve,
|
||||
model.ConfigKeyEmailLoginVerificationEnabled,
|
||||
model.ConfigKeyEmailRegisterVerificationEnabled,
|
||||
model.ConfigKeyMenuDisplayConfig,
|
||||
model.ConfigKeySearchEngineIndexingEnabled,
|
||||
model.ConfigKeyFileAccessWhitelist,
|
||||
}
|
||||
if err := tx.Model(&model.SystemConfig{}).
|
||||
Where("key IN ?", publicKeys).
|
||||
Update("visibility", model.ConfigVisibilityVisible).Error; err != nil {
|
||||
t.Fatalf("failed to seed public system config visibility: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// SetupLogStoresForTest 将 logstore 指向测试已通过 db.SetDB 注入的 sqlite 库。
|
||||
func SetupLogStoresForTest(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
gdb := db.DB(context.Background())
|
||||
require.NoError(t, idgen.Init(1))
|
||||
require.NoError(t, gdb.AutoMigrate(
|
||||
&analyticsmodel.NodeAccessLog{},
|
||||
&analyticsmodel.UserAccessLog{},
|
||||
&analyticsmodel.NodeMetricSnapshot{},
|
||||
&analyticsmodel.NodeEdgeHealth{},
|
||||
&analyticsmodel.NodeObsFrps{},
|
||||
&analyticsmodel.NodeObsFrpc{},
|
||||
))
|
||||
|
||||
logstore.ResetForTest()
|
||||
logstore.SetConfigReader(func(_ context.Context, key string) (string, error) {
|
||||
if key == model.ConfigKeyLogDatabase {
|
||||
return "sqlite", nil
|
||||
}
|
||||
return "", nil
|
||||
})
|
||||
store, err := logstore.Active(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
logstore.SetAccessLogHooks(logstore.AccessLogHooks{
|
||||
QueueNodeAccessLogs: func(logs []analyticsmodel.NodeAccessLog) {
|
||||
if err := store.AccessLogs.BatchInsertNodeAccessLogs(context.Background(), logs); err != nil {
|
||||
t.Errorf("batch insert node access logs failed in test hook: %v", err)
|
||||
}
|
||||
},
|
||||
})
|
||||
logstore.SetObservabilityHooks(logstore.ObservabilityHooks{
|
||||
QueueMetricSnapshot: func(record analyticsmodel.NodeMetricSnapshot) {
|
||||
if err := store.Observability.BatchInsertNodeMetricSnapshots(context.Background(), []analyticsmodel.NodeMetricSnapshot{record}); err != nil {
|
||||
t.Errorf("batch insert node metric snapshots failed in test hook: %v", err)
|
||||
}
|
||||
},
|
||||
QueueEdgeHealth: func(record analyticsmodel.NodeEdgeHealth) {
|
||||
if err := store.Observability.BatchInsertNodeEdgeHealth(context.Background(), []analyticsmodel.NodeEdgeHealth{record}); err != nil {
|
||||
t.Errorf("batch insert node edge health failed in test hook: %v", err)
|
||||
}
|
||||
},
|
||||
QueueNodeObsFrps: func(record analyticsmodel.NodeObsFrps) {
|
||||
if err := store.Observability.BatchInsertNodeObsFrps(context.Background(), []analyticsmodel.NodeObsFrps{record}); err != nil {
|
||||
t.Errorf("batch insert node obs frps failed in test hook: %v", err)
|
||||
}
|
||||
},
|
||||
QueueNodeObsFrpc: func(record analyticsmodel.NodeObsFrpc) {
|
||||
if err := store.Observability.BatchInsertNodeObsFrpc(context.Background(), []analyticsmodel.NodeObsFrpc{record}); err != nil {
|
||||
t.Errorf("batch insert node obs frpc failed in test hook: %v", err)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
t.Cleanup(func() {
|
||||
logstore.SetAccessLogHooks(logstore.AccessLogHooks{})
|
||||
logstore.SetObservabilityHooks(logstore.ObservabilityHooks{})
|
||||
logstore.ResetForTest()
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user