Files
MeBox/internal/service/auth_user_limits_test.go
T

193 lines
6.0 KiB
Go

package service
import (
"context"
"errors"
"fmt"
"testing"
"github.com/glebarez/sqlite"
"go.uber.org/zap"
"gorm.io/gorm"
"github.com/ShukeBta/MediaStationGo/internal/config"
"github.com/ShukeBta/MediaStationGo/internal/model"
"github.com/ShukeBta/MediaStationGo/internal/repository"
)
func newAuthTestServices(t *testing.T) (*repository.Container, *AuthService, *ProfileService, *PermissionService) {
t.Helper()
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
if err != nil {
t.Fatal(err)
}
if err := db.AutoMigrate(&model.User{}, &model.UserPermission{}, &model.RefreshToken{}, &model.TelegramBinding{}); err != nil {
t.Fatal(err)
}
repos := repository.New(db)
cfg := &config.Config{}
cfg.Secrets.JWTSecret = "test-secret"
log := zap.NewNop()
permissions := NewPermissionService(log, repos)
tokenSvc := NewTokenService(cfg, log, repos)
auth := NewAuthService(cfg, log, repos, tokenSvc, permissions)
profile := NewProfileService(log, repos)
return repos, auth, profile, permissions
}
func TestRegisterRejectsMoreThanTwentyUsers(t *testing.T) {
ctx := context.Background()
repos, auth, _, _ := newAuthTestServices(t)
for i := 0; i < MaxUsers; i++ {
if err := repos.User.Create(ctx, &model.User{
Username: fmt.Sprintf("user-%02d", i),
PasswordHash: "hash",
Role: "user",
Tier: "free",
}); err != nil {
t.Fatal(err)
}
}
_, _, err := auth.Register(ctx, "overflow", "password")
if !errors.Is(err, ErrUserLimitReached) {
t.Fatalf("expected ErrUserLimitReached, got %v", err)
}
}
func TestRegisterDefaultsAdultLibrariesHidden(t *testing.T) {
_, auth, _, _ := newAuthTestServices(t)
user, _, err := auth.Register(context.Background(), "viewer", "password")
if err != nil {
t.Fatalf("register: %v", err)
}
if !user.HideAdult {
t.Fatal("new users should hide adult libraries by default")
}
}
func TestDeletedUserCanBeRecreatedWithSameUsername(t *testing.T) {
ctx := context.Background()
repos, auth, _, _ := newAuthTestServices(t)
user, _, err := auth.Register(ctx, "viewer", "old-password")
if err != nil {
t.Fatalf("register old user: %v", err)
}
if err := repos.DB.Create(&model.TelegramBinding{
TelegramUserID: 10001,
TelegramName: "@viewer",
ChatID: 10001,
UserID: user.ID,
}).Error; err != nil {
t.Fatalf("create telegram binding: %v", err)
}
if err := repos.User.Delete(ctx, user.ID); err != nil {
t.Fatalf("delete user: %v", err)
}
next, _, err := auth.Register(ctx, "viewer", "new-password")
if err != nil {
t.Fatalf("register same username after delete: %v", err)
}
if next.ID == user.ID {
t.Fatal("recreated user should be a new account row")
}
if _, err := auth.Login(ctx, "viewer", "new-password"); err != nil {
t.Fatalf("login recreated user: %v", err)
}
var bindings int64
if err := repos.DB.Model(&model.TelegramBinding{}).Where("telegram_user_id = ?", 10001).Count(&bindings).Error; err != nil {
t.Fatalf("count bindings: %v", err)
}
if bindings != 0 {
t.Fatalf("deleted user telegram bindings should be removed, got %d", bindings)
}
}
func TestRegisterReleasesLegacySoftDeletedUsername(t *testing.T) {
ctx := context.Background()
repos, auth, _, _ := newAuthTestServices(t)
if err := repos.User.Create(ctx, &model.User{
Username: "legacy",
PasswordHash: "hash",
Role: "user",
Tier: "free",
}); err != nil {
t.Fatal(err)
}
legacy, err := repos.User.FindByUsername(ctx, "legacy")
if err != nil || legacy == nil {
t.Fatalf("find legacy user: %v", err)
}
if err := repos.DB.Delete(&model.User{}, "id = ?", legacy.ID).Error; err != nil {
t.Fatalf("legacy soft delete: %v", err)
}
if _, _, err := auth.Register(ctx, "legacy", "new-password"); err != nil {
t.Fatalf("register should release old soft-deleted username: %v", err)
}
}
func TestAdminResetPasswordAllowsLoginWithNewPassword(t *testing.T) {
ctx := context.Background()
_, auth, _, _ := newAuthTestServices(t)
user, _, err := auth.Register(ctx, "viewer", "old-password")
if err != nil {
t.Fatalf("register: %v", err)
}
if err := auth.ResetPassword(ctx, user.ID, "new-password"); err != nil {
t.Fatalf("reset password: %v", err)
}
if _, err := auth.Login(ctx, "viewer", "old-password"); !errors.Is(err, ErrInvalidCredentials) {
t.Fatalf("old password should fail, got %v", err)
}
if _, err := auth.Login(ctx, "viewer", "new-password"); err != nil {
t.Fatalf("new password should login: %v", err)
}
}
func TestDefaultPermissionsAreViewerOnly(t *testing.T) {
perms := DefaultPermissions("user-1")
if !perms.CanViewDashboard || !perms.CanPlayMedia || !perms.CanExternalPlayer {
t.Fatal("viewer defaults must allow library viewing, playback, and external players")
}
if perms.CanManageDownloads || perms.CanManageSubscriptions || perms.CanManageFiles ||
perms.CanEditMedia || perms.CanRescrape || perms.CanCaptureFrames ||
perms.CanManageSites || perms.CanManageUsers || perms.CanManageStrm {
t.Fatal("viewer defaults must not allow downloads, scraping, media edits, or file management")
}
}
func TestAdminEffectivePermissionsAreAllGranted(t *testing.T) {
ctx := context.Background()
repos, _, _, permissions := newAuthTestServices(t)
admin := &model.User{Username: "admin", PasswordHash: "hash", Role: "admin", Tier: "plus"}
if err := repos.User.Create(ctx, admin); err != nil {
t.Fatal(err)
}
perms, err := permissions.Effective(ctx, admin.ID)
if err != nil {
t.Fatal(err)
}
if !perms.CanEditMedia || !perms.CanRescrape || !perms.CanUseAI ||
!perms.CanCaptureFrames || !perms.CanManageUsers || !perms.CanAccessSettings {
t.Fatal("admin effective permissions must grant every advanced capability")
}
}
func TestDefaultAdminCannotBeDemoted(t *testing.T) {
ctx := context.Background()
repos, _, profile, _ := newAuthTestServices(t)
admin := &model.User{Username: "admin", PasswordHash: "hash", Role: "admin", Tier: "plus"}
if err := repos.User.Create(ctx, admin); err != nil {
t.Fatal(err)
}
_, err := profile.AdminUpdateRole(ctx, admin.ID, "user")
if err == nil {
t.Fatal("expected default admin demotion to be rejected")
}
}