mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-30 06:16:37 +08:00
3d2038a251
Authenticate, CreateAuthSource, UpdateAuthSource and ToggleAuthSource claimed success with a nil record, so any test that reached them surfaced a nil pointer dereference instead of the actual cause. Full suite confirms no test relied on the silent behaviour.
300 lines
8.8 KiB
Go
300 lines
8.8 KiB
Go
// Copyright 2026 Arctel.net
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package shared
|
|
|
|
import (
|
|
"Wavelet/core/contracts"
|
|
"Wavelet/pkg/testhelper"
|
|
"bytes"
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// MockDBService is a mock implementation of contracts.DBService for unit testing.
|
|
type MockDBService struct {
|
|
DBInstance *gorm.DB
|
|
}
|
|
|
|
// GORM returns the underlying GORM instance.
|
|
func (m *MockDBService) GORM() *gorm.DB {
|
|
return m.DBInstance
|
|
}
|
|
|
|
// DB returns the GORM instance bound to context.
|
|
func (m *MockDBService) DB(ctx context.Context) *gorm.DB {
|
|
return m.DBInstance.WithContext(ctx)
|
|
}
|
|
|
|
// Named returns the named GORM instance.
|
|
func (m *MockDBService) Named(_ string) *gorm.DB {
|
|
return m.DBInstance
|
|
}
|
|
|
|
// MockCacheService is an in-memory mock implementation of contracts.CacheService for unit testing.
|
|
type MockCacheService struct {
|
|
mu sync.RWMutex
|
|
data map[string][]byte
|
|
}
|
|
|
|
// NewMockCacheService creates a new MockCacheService.
|
|
func NewMockCacheService() *MockCacheService {
|
|
return &MockCacheService{
|
|
data: make(map[string][]byte),
|
|
}
|
|
}
|
|
|
|
// Get retrieves a cached value.
|
|
func (m *MockCacheService) Get(_ context.Context, key string, val any) error {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
b, ok := m.data[key]
|
|
if !ok {
|
|
return contracts.ErrCacheMiss
|
|
}
|
|
return json.Unmarshal(b, val)
|
|
}
|
|
|
|
// Set stores a key-value pair in cache.
|
|
func (m *MockCacheService) Set(_ context.Context, key string, val any, _ time.Duration) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
b, err := json.Marshal(val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
m.data[key] = b
|
|
return nil
|
|
}
|
|
|
|
// Delete removes a key from cache.
|
|
func (m *MockCacheService) Delete(_ context.Context, key string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
delete(m.data, key)
|
|
return nil
|
|
}
|
|
|
|
// GetOrSet retrieves or populates a cache entry.
|
|
func (m *MockCacheService) GetOrSet(ctx context.Context, key string, target any, ttl time.Duration, loader func() (any, error)) error {
|
|
err := m.Get(ctx, key, target)
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
val, err := loader()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return m.Set(ctx, key, val, ttl)
|
|
}
|
|
|
|
// Invalidate invalidates a cache tag or prefix.
|
|
func (m *MockCacheService) Invalidate(_ context.Context, _ string) error {
|
|
return nil
|
|
}
|
|
|
|
// MockStorageService is an in-memory mock implementation of contracts.StorageService for unit testing.
|
|
type MockStorageService struct {
|
|
mu sync.RWMutex
|
|
objects map[string][]byte
|
|
}
|
|
|
|
// NewMockStorageService creates a new MockStorageService.
|
|
func NewMockStorageService() *MockStorageService {
|
|
return &MockStorageService{
|
|
objects: make(map[string][]byte),
|
|
}
|
|
}
|
|
|
|
// Put uploads an object into mock storage.
|
|
func (m *MockStorageService) Put(_ context.Context, key string, body io.Reader, _ int64, _ string) (contracts.StoragePutResult, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
data, err := io.ReadAll(body)
|
|
if err != nil {
|
|
return contracts.StoragePutResult{}, err
|
|
}
|
|
m.objects[key] = data
|
|
return contracts.StoragePutResult{Key: key, Bucket: "test-bucket"}, nil
|
|
}
|
|
|
|
// PutRaw seeds an object directly into the in-memory store (test helper, no I/O).
|
|
func (m *MockStorageService) PutRaw(key string, data []byte) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.objects[key] = data
|
|
}
|
|
|
|
// Get retrieves an object from mock storage.
|
|
func (m *MockStorageService) Get(_ context.Context, key string) (*contracts.StorageObject, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
data, ok := m.objects[key]
|
|
if ok {
|
|
return &contracts.StorageObject{
|
|
Key: key,
|
|
Body: io.NopCloser(bytes.NewReader(data)),
|
|
ContentLength: int64(len(data)),
|
|
ContentType: "application/octet-stream",
|
|
}, nil
|
|
}
|
|
return nil, gorm.ErrRecordNotFound
|
|
}
|
|
|
|
// Delete removes an object from mock storage.
|
|
func (m *MockStorageService) Delete(_ context.Context, key string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
delete(m.objects, key)
|
|
return nil
|
|
}
|
|
|
|
// Ingest handles programmatic file ingestion for mock storage.
|
|
func (m *MockStorageService) Ingest(_ context.Context, _ io.Reader, _ contracts.IngestOptions) (*contracts.IngestResult, error) {
|
|
return &contracts.IngestResult{ID: 1, Key: "test.png", Created: true, Stored: true}, nil
|
|
}
|
|
|
|
// MockAuthService is a mock implementation of contracts.AuthService for unit testing.
|
|
type MockAuthService struct {
|
|
DB *gorm.DB
|
|
}
|
|
|
|
// errMockNotImplemented marks a mock method no test exercises yet, so an
|
|
// accidental call reports this instead of dereferencing a nil result.
|
|
var errMockNotImplemented = errors.New("mock auth service: not implemented")
|
|
|
|
// RequireAuthMiddleware returns a dummy auth middleware.
|
|
func (a *MockAuthService) RequireAuthMiddleware() any {
|
|
return func(c *gin.Context) { c.Next() }
|
|
}
|
|
|
|
// RequireAdminMiddleware returns a dummy admin middleware.
|
|
func (a *MockAuthService) RequireAdminMiddleware() any {
|
|
return func(c *gin.Context) { c.Next() }
|
|
}
|
|
|
|
// DisallowTokenAuthMiddleware returns a dummy disallow token middleware.
|
|
func (a *MockAuthService) DisallowTokenAuthMiddleware() any {
|
|
return func(c *gin.Context) { c.Next() }
|
|
}
|
|
|
|
// GetCurrentUser returns the user associated with the request context.
|
|
func (a *MockAuthService) GetCurrentUser(ctx context.Context) (*contracts.UserDTO, error) {
|
|
if c, ok := ctx.(*gin.Context); ok {
|
|
authHeader := c.GetHeader("Authorization")
|
|
if strings.HasPrefix(authHeader, "Bearer ") {
|
|
tokenStr := strings.TrimPrefix(authHeader, "Bearer ")
|
|
tokenHash := fmt.Sprintf("%x", sha256.Sum256([]byte(tokenStr)))
|
|
var tokenRecord struct {
|
|
UserID uint64
|
|
}
|
|
if err := a.DB.Table("w_access_tokens").Where("token_hash = ?", tokenHash).First(&tokenRecord).Error; err == nil {
|
|
return &contracts.UserDTO{ID: tokenRecord.UserID, IsActive: true}, nil
|
|
}
|
|
}
|
|
}
|
|
return nil, errors.New("unauthorized")
|
|
}
|
|
|
|
// GetCurrentUserID returns the current user ID.
|
|
func (a *MockAuthService) GetCurrentUserID(ctx context.Context) (uint64, error) {
|
|
u, err := a.GetCurrentUser(ctx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return u.ID, nil
|
|
}
|
|
|
|
// VerifyToken verifies an access token.
|
|
func (a *MockAuthService) VerifyToken(_ context.Context, token string) (*contracts.UserDTO, error) {
|
|
tokenHash := fmt.Sprintf("%x", sha256.Sum256([]byte(token)))
|
|
var tokenRecord struct {
|
|
UserID uint64
|
|
}
|
|
if err := a.DB.Table("w_access_tokens").Where("token_hash = ?", tokenHash).First(&tokenRecord).Error; err == nil {
|
|
return &contracts.UserDTO{ID: tokenRecord.UserID, IsActive: true}, nil
|
|
}
|
|
return nil, errors.New("unauthorized")
|
|
}
|
|
|
|
// Authenticate verifies credentials.
|
|
func (a *MockAuthService) Authenticate(_ context.Context, _, _ string) (*contracts.UserDTO, error) {
|
|
return nil, errMockNotImplemented
|
|
}
|
|
|
|
// CreateSession creates a login session.
|
|
func (a *MockAuthService) CreateSession(_ context.Context, _ uint64, _ map[string]any) (string, error) {
|
|
return "test-session", nil
|
|
}
|
|
|
|
// RevokeToken revokes an access token.
|
|
func (a *MockAuthService) RevokeToken(_ context.Context, _ string) error {
|
|
return nil
|
|
}
|
|
|
|
// RevokeUserSessions revokes all sessions for a user.
|
|
func (a *MockAuthService) RevokeUserSessions(_ context.Context, _ uint64) error {
|
|
return nil
|
|
}
|
|
|
|
// InvalidateCachedUser invalidates cached user profile.
|
|
func (a *MockAuthService) InvalidateCachedUser(_ context.Context, _ uint64) {}
|
|
|
|
// InvalidateCachedToken invalidates cached access token.
|
|
func (a *MockAuthService) InvalidateCachedToken(_ context.Context, _ string) {}
|
|
|
|
// ListAuthSources lists configured authentication sources.
|
|
func (a *MockAuthService) ListAuthSources(_ context.Context) ([]contracts.AuthSourceViewDTO, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
// CreateAuthSource creates an authentication source.
|
|
func (a *MockAuthService) CreateAuthSource(_ context.Context, _ contracts.AuthSourceDTO) (*contracts.AuthSourceDTO, error) {
|
|
return nil, errMockNotImplemented
|
|
}
|
|
|
|
// UpdateAuthSource updates an authentication source.
|
|
func (a *MockAuthService) UpdateAuthSource(_ context.Context, _ uint64, _ contracts.AuthSourceDTO) (*contracts.AuthSourceDTO, error) {
|
|
return nil, errMockNotImplemented
|
|
}
|
|
|
|
// DeleteAuthSource deletes an authentication source.
|
|
func (a *MockAuthService) DeleteAuthSource(_ context.Context, _ uint64) error {
|
|
return nil
|
|
}
|
|
|
|
// ToggleAuthSource toggles an authentication source active state.
|
|
func (a *MockAuthService) ToggleAuthSource(_ context.Context, _ uint64) (*contracts.AuthSourceDTO, error) {
|
|
return nil, errMockNotImplemented
|
|
}
|
|
|
|
// SetupTestEnv initializes test helper environment and binds DB, Cache, Storage, Auth mocks to shared services.
|
|
func SetupTestEnv(t *testing.T) (*gorm.DB, func()) {
|
|
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
|
dbSvc := &MockDBService{DBInstance: dbConn}
|
|
cacheSvc := NewMockCacheService()
|
|
storageSvc := NewMockStorageService()
|
|
authSvc := &MockAuthService{DB: dbConn}
|
|
|
|
SetDBService(dbSvc)
|
|
SetCacheService(cacheSvc)
|
|
SetStorageService(storageSvc)
|
|
SetAuthService(authSvc)
|
|
|
|
return dbConn, func() {
|
|
ResetServices()
|
|
cleanup()
|
|
}
|
|
}
|