package testutil import ( "encoding/json" "testing" "time" "nixcn-cms/data" "nixcn-cms/internal/cryptography" "github.com/alicebob/miniredis/v2" "github.com/glebarez/sqlite" "github.com/google/uuid" "github.com/redis/go-redis/v9" "github.com/spf13/viper" "gorm.io/gorm" "gorm.io/gorm/logger" ) // TestAESKey is a 32-byte AES key used for client_secret_key in tests. const TestAESKey = "testkey1234567890123456789012345" // TestKYCAESKey is a 16-byte AES key used for kyc_info_key in tests. const TestKYCAESKey = "testkyckey123456" // TestClientID is the OAuth client ID used across tests. const TestClientID = "test-client" // TestClientSecret is the plaintext client secret used in tests. const TestClientSecret = "testsecret-32bytes-padded-here!!" // SetupTestDB initialises an in-memory SQLite database and assigns it to // data.Database. It auto-migrates all models including Agenda. func SetupTestDB(t *testing.T) { t.Helper() db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) if err != nil { t.Fatalf("SetupTestDB: open sqlite: %v", err) } if err := db.AutoMigrate( &data.User{}, &data.Event{}, &data.Attendance{}, &data.Client{}, &data.Kyc{}, &data.Agenda{}, ); err != nil { t.Fatalf("SetupTestDB: auto-migrate: %v", err) } data.Database = db t.Cleanup(func() { sqlDB, _ := db.DB() _ = sqlDB.Close() data.Database = nil }) } // SetupTestRedis starts an in-memory miniredis server and assigns it to // data.Redis. It returns the miniredis instance for time manipulation. func SetupTestRedis(t *testing.T) *miniredis.Miniredis { t.Helper() mr, err := miniredis.Run() if err != nil { t.Fatalf("SetupTestRedis: start miniredis: %v", err) } rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()}) data.Redis = rdb t.Cleanup(func() { _ = rdb.Close() mr.Close() data.Redis = nil }) return mr } // SetupViper configures the minimum viper keys needed by internal packages // and services during tests. func SetupViper(t *testing.T) { t.Helper() viper.Reset() viper.Set("server.application", "test-app") viper.Set("server.debug_mode", true) viper.Set("server.external_url", "http://localhost:8080") viper.Set("secrets.client_secret_key", TestAESKey) viper.Set("secrets.kyc_info_key", TestKYCAESKey) viper.Set("secrets.turnstile_secret", "test-turnstile-secret") viper.Set("ttl.auth_code_ttl", 10*time.Minute) viper.Set("ttl.access_ttl", 15*time.Minute) viper.Set("ttl.refresh_ttl", 7*24*time.Hour) viper.Set("ttl.checkin_code_ttl", 5*time.Minute) viper.Set("kyc.ali_access_key_id", "test-key-id") viper.Set("kyc.ali_access_key_secret", "test-key-secret") viper.Set("kyc.passport_reader_public_key", "test-pub") viper.Set("kyc.passport_reader_secret", "test-secret") t.Cleanup(func() { viper.Reset() }) } // Setup is a convenience that calls SetupViper, SetupTestDB, and // SetupTestRedis, returning the miniredis instance. func Setup(t *testing.T) *miniredis.Miniredis { t.Helper() SetupViper(t) SetupTestDB(t) return SetupTestRedis(t) } // SeedClient creates a test OAuth client in the database and returns it. func SeedClient(t *testing.T) *data.Client { t.Helper() ctx := t.Context() enc, err := cryptography.AESCBCEncrypt([]byte(TestClientSecret), []byte(TestAESKey)) if err != nil { t.Fatalf("SeedClient: encrypt secret: %v", err) } redirectURIs := []string{"http://localhost/callback"} urisJSON, _ := json.Marshal(redirectURIs) client := &data.Client{ UUID: uuid.New(), ClientId: TestClientID, ClientSecret: enc, ClientName: "Test Client", RedirectUri: urisJSON, } if err := data.Database.WithContext(ctx).Create(client).Error; err != nil { t.Fatalf("SeedClient: create: %v", err) } return client } // RandomEmail returns a unique email address for use in tests. func RandomEmail() string { return uuid.New().String() + "@test.com" } // SetupWithAuth is a convenience that calls Setup and SeedClient, then returns // the miniredis instance and the seeded client. func SetupWithAuth(t *testing.T) (*miniredis.Miniredis, *data.Client) { t.Helper() mr := Setup(t) client := SeedClient(t) return mr, client } // SeedUser creates a test user in the database and returns it. func SeedUser(t *testing.T, email string, permLevel uint) *data.User { t.Helper() ctx := t.Context() u := data.NewUser( data.WithEmail(email), data.WithUsername("user-"+uuid.New().String()[:8]), data.WithPermissionLevel(permLevel), data.WithNickname("Test User"), ) if err := u.Create(ctx); err != nil { t.Fatalf("SeedUser: create %q: %v", email, err) } return u }