package bootstrap_test import ( "context" "log/slog" "path/filepath" "testing" "glchat/internal/auth" "glchat/internal/bootstrap" "glchat/internal/config" "glchat/internal/database" "glchat/internal/permissions" "glchat/internal/store" ) func newRunner(t *testing.T) (*bootstrap.Runner, *store.Store, *auth.Service) { t.Helper() ctx := context.Background() db, err := database.Open(ctx, database.Options{ Path: filepath.Join(t.TempDir(), "glchat.db"), ReadPool: 2, Migrate: true, }) if err != nil { t.Fatalf("open database: %v", err) } t.Cleanup(func() { _ = db.Close() }) st := store.New(db) logger := slog.New(slog.DiscardHandler) cfg := config.Config{ SessionPepper: "bootstrap-pepper", MasterKey: "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff", Argon2MemoryKiB: 1024, Argon2Iterations: 1, Argon2Parallelism: 1, } authService, err := auth.New(ctx, cfg, st, logger) if err != nil { t.Fatalf("auth.New: %v", err) } return bootstrap.New(st, authService, logger), st, authService } func TestBootstrapCreatesAdminAndMainGuild(t *testing.T) { ctx := context.Background() runner, st, _ := newRunner(t) result, err := runner.Run(ctx, bootstrap.Options{ Email: "owner@example.com", Password: "correct-horse-battery", MainGuild: "Главный сервер", }) if err != nil { t.Fatalf("Run: %v", err) } if !result.CreatedUser || !result.CreatedGuild { t.Fatalf("unexpected result: %+v", result) } admin, err := st.GetUser(ctx, result.UserID) if err != nil { t.Fatalf("GetUser: %v", err) } if !admin.IsInstanceAdmin { t.Fatal("bootstrap admin must have the instance admin flag") } if len(admin.Badges) == 0 || admin.Badges[0] != "instance_admin" { t.Fatalf("admin badge is missing: %+v", admin.Badges) } settings, err := st.InstanceSettings(ctx) if err != nil { t.Fatalf("InstanceSettings: %v", err) } if settings.MainGuildID != result.GuildID { t.Fatalf("main guild id = %d, want %d", settings.MainGuildID, result.GuildID) } roles, err := st.ListGuildRoles(ctx, result.GuildID) if err != nil { t.Fatalf("ListGuildRoles: %v", err) } if len(roles) != 2 { t.Fatalf("main guild must have 2 default roles, got %d", len(roles)) } var defaultRole *store.Role for i := range roles { if roles[i].IsDefault { defaultRole = &roles[i] } } if defaultRole == nil { t.Fatal("default role is missing") } if permissions.Permission(defaultRole.Permissions) != permissions.DefaultUserPermissions { t.Fatalf("default role permissions = %s", permissions.Permission(defaultRole.Permissions)) } // Владелец — участник с ролью администратора. memberRoles, err := st.MemberRoles(ctx, result.GuildID, result.UserID) if err != nil { t.Fatalf("MemberRoles: %v", err) } found := false for _, role := range memberRoles { if role.ID == result.AdminRoleID { found = true } } if !found { t.Fatal("owner must receive the administrator role") } // Общая текстовая комната создана. channels, err := st.ListGuildChannels(ctx, result.GuildID) if err != nil { t.Fatalf("ListGuildChannels: %v", err) } if len(channels) != 1 || channels[0].Name != "общий" { t.Fatalf("unexpected channels: %+v", channels) } } func TestBootstrapIsIdempotent(t *testing.T) { ctx := context.Background() runner, st, authService := newRunner(t) options := bootstrap.Options{Email: "owner@example.com", Password: "correct-horse-battery"} first, err := runner.Run(ctx, options) if err != nil { t.Fatalf("first Run: %v", err) } second, err := runner.Run(ctx, options) if err != nil { t.Fatalf("second Run: %v", err) } if second.GuildID != first.GuildID { t.Fatalf("second run created another guild: %d != %d", second.GuildID, first.GuildID) } if second.CreatedUser || second.CreatedGuild { t.Fatalf("second run must not create anything: %+v", second) } guilds, err := st.ListAllGuilds(ctx) if err != nil { t.Fatalf("ListAllGuilds: %v", err) } if len(guilds) != 1 { t.Fatalf("guild count = %d, want 1", len(guilds)) } // Новый пользователь может войти с заданным паролем. if _, _, _, err := authService.Login(ctx, auth.LoginInput{ Email: "owner@example.com", Password: "correct-horse-battery", }); err == nil { t.Fatal("instance admin without 2FA must not be able to log in") } } func TestNewUserJoinsMainGuild(t *testing.T) { ctx := context.Background() runner, st, authService := newRunner(t) result, err := runner.Run(ctx, bootstrap.Options{Email: "owner@example.com", Password: "correct-horse-battery"}) if err != nil { t.Fatalf("Run: %v", err) } user, _, _, err := authService.Register(ctx, auth.RegisterInput{ Username: "newcomer", Email: "newcomer@example.com", Password: "correct-horse-battery", }) if err != nil { t.Fatalf("Register: %v", err) } guilds, err := st.ListGuildsForUser(ctx, user.ID) if err != nil { t.Fatalf("ListGuildsForUser: %v", err) } if len(guilds) != 1 || guilds[0].ID != result.GuildID { t.Fatalf("newcomer must join the main guild: %+v", guilds) } roles, err := st.MemberRoles(ctx, result.GuildID, user.ID) if err != nil { t.Fatalf("MemberRoles: %v", err) } if len(roles) != 1 || !roles[0].IsDefault { t.Fatalf("newcomer must get the default role: %+v", roles) } if !permissions.Permission(roles[0].Permissions).Has(permissions.ViewGuild) { t.Fatal("default role must allow viewing the guild") } }