// Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package install import ( "bytes" "context" "encoding/json" "mime/multipart" "net/http" "net/http/httptest" "os" "path/filepath" "testing" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/services/forms" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestRoutes(t *testing.T) { defer test.MockVariableValue(&setting.InstallLock, false)() r := Routes() assert.NotNil(t, r) w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/", nil) r.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Contains(t, w.Body.String(), `class="page-content install`) assert.Contains(t, w.Body.String(), `name="default_language"`) assert.Contains(t, w.Body.String(), `name="app_ini_file"`) w = httptest.NewRecorder() req = httptest.NewRequest(http.MethodGet, "/no-such", nil) r.ServeHTTP(w, req) assert.Equal(t, 404, w.Code) w = httptest.NewRecorder() req = httptest.NewRequest(http.MethodGet, "/assets/img/gitea.svg", nil) r.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) } func TestReorderInstallLanguages(t *testing.T) { defer test.MockVariableValue(&setting.Langs, []string{"en-US", "de-DE", "fr-FR"})() assert.Equal(t, "en-US", resolveInstallDefaultLanguage("")) assert.Equal(t, "en-US", resolveInstallDefaultLanguage("invalid")) assert.Equal(t, "fr-FR", resolveInstallDefaultLanguage("fr-FR")) assert.Equal(t, []string{"fr-FR", "en-US", "de-DE"}, reorderInstallLanguages("fr-FR")) assert.Equal(t, []string{"en-US", "de-DE", "fr-FR"}, reorderInstallLanguages("invalid")) } func TestInstallMailerDisplayName(t *testing.T) { assert.Equal(t, "gitSafe", installMailerDisplayName("gitSafe: for your code")) assert.Equal(t, "gitSafe", installMailerDisplayName("gitSafe for your code")) assert.Equal(t, "Gitea", installMailerDisplayName("")) } // start edit/add - by petru @ codex func TestIsRecoveryRequestAcceptsQueryFlag(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/?recovery=1", nil) assert.True(t, isRecoveryRequest(req)) } func TestRecoveryPageShowsBackupDiscoveryError(t *testing.T) { // start edit/add - by petru @ codex defer test.MockVariableValue(&setting.InstallLock, false)() tmpDir := t.TempDir() customConfDir := filepath.Join(tmpDir, "custom", "conf") require.NoError(t, os.MkdirAll(customConfDir, 0o755)) invalidBackupPath := filepath.Join(tmpDir, "invalid-backup-path") require.NoError(t, os.WriteFile(invalidBackupPath, []byte("not-a-directory"), 0o644)) oldCustomConf := setting.CustomConf defer func() { setting.CustomConf = oldCustomConf }() setting.CustomConf = filepath.Join(customConfDir, "app.ini") require.NoError(t, setting.SaveRecoveryConfig(&setting.RecoveryConfig{ Enabled: true, AllowedEmails: "admin@example.com", TokenSecret: "secret", TokenTTLMin: 15, BackupPath: invalidBackupPath, })) r := Routes() w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/?recovery=1", nil) r.ServeHTTP(w, WithRecoveryRequest(req)) assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "install.recovery_database_backup_discovery_failed") // end edit/add - by petru @ codex } func TestResolveInstallPageSiteNameKeepsImportedAppName(t *testing.T) { tmpDir := t.TempDir() oldCustomConf := setting.CustomConf defer func() { setting.CustomConf = oldCustomConf }() setting.CustomConf = filepath.Join(tmpDir, "custom", "conf", "app.ini") recoveryCfg := &setting.RecoveryConfig{ Enabled: true, AllowedEmails: "admin@example.com", TokenSecret: "secret", TokenTTLMin: 15, SMTPFrom: "gitSafe ", } require.NoError(t, setting.SaveRecoveryConfig(recoveryCfg)) form := &forms.InstallForm{ AppName: "Imported Safe: for your code", ImportedAppINI: true, } assert.Equal(t, "Imported Safe", resolveInstallPageSiteName(form, true)) assert.Equal(t, "Imported Safe: for your code", form.AppName) } func TestResolveInstallPageSiteNameUsesRecoveryFallbackForGenericAppName(t *testing.T) { tmpDir := t.TempDir() oldCustomConf := setting.CustomConf defer func() { setting.CustomConf = oldCustomConf }() setting.CustomConf = filepath.Join(tmpDir, "custom", "conf", "app.ini") recoveryCfg := &setting.RecoveryConfig{ Enabled: true, AllowedEmails: "admin@example.com", TokenSecret: "secret", TokenTTLMin: 15, SMTPFrom: "gitSafe ", } require.NoError(t, setting.SaveRecoveryConfig(recoveryCfg)) form := &forms.InstallForm{AppName: "Gitea: Git with a cup of tea"} assert.Equal(t, "gitSafe", resolveInstallPageSiteName(form, true)) assert.Equal(t, "gitSafe", form.AppName) } func TestImportedDBTypeKeepsImportedSQLite3Value(t *testing.T) { assert.Equal(t, "sqlite3", importedDBType("sqlite3", "mysql")) assert.Equal(t, "sqlite3", importedDBType("sqlite", "mysql")) assert.Equal(t, "postgres", importedDBType("postgresql", "mysql")) } func TestApplyBackupAppINIToInstallForm(t *testing.T) { // start edit/add - by petru @ codex cfg, err := setting.NewConfigProviderFromData(` APP_NAME = Restored Safe: for your code [database] DB_TYPE = sqlite3 PATH = /srv/gitea/data/gitea.db [server] ROOT_URL = http://localhost:3000/ [mailer] ENABLED = true SMTP_ADDR = smtp.example.com SMTP_PORT = 587 FROM = Restored Safe USER = smtp-user PASSWD = smtp-pass [security] INTERNAL_TOKEN = restored-internal `) require.NoError(t, err) form := &forms.InstallForm{ DbType: "mysql", BackupPath: "/srv/gitea/backups/db", BackupRestoreID: "20260524-010101.000000001", RecoveryMode: recoveryModeDatabaseBackup, ImportSensitiveSecrets: false, } applyBackupAppINIToInstallForm(form, cfg) assert.Equal(t, "Restored Safe: for your code", form.AppName) assert.Equal(t, "sqlite3", form.DbType) assert.Equal(t, "/srv/gitea/data/gitea.db", form.DbPath) assert.Equal(t, "http://localhost:3000/", form.AppURL) assert.Equal(t, "smtp.example.com", form.SMTPAddr) assert.Equal(t, "smtp-user", form.SMTPUser) assert.Equal(t, "restored-internal", form.ImportedInternalToken) assert.True(t, form.ImportSensitiveSecrets) assert.Equal(t, "/srv/gitea/backups/db", form.BackupPath) assert.Equal(t, "20260524-010101.000000001", form.BackupRestoreID) assert.Equal(t, recoveryModeDatabaseBackup, form.RecoveryMode) // end edit/add - by petru @ codex } func TestDetectInstallExistingDatabase(t *testing.T) { // start edit/add - by petru @ codex tmpDir := t.TempDir() ctx := context.Background() originalType := setting.Database.Type originalPath := setting.Database.Path originalHost := setting.Database.Host originalUser := setting.Database.User originalPasswd := setting.Database.Passwd originalName := setting.Database.Name originalSchema := setting.Database.Schema originalSSLMode := setting.Database.SSLMode originalLogSQL := setting.Database.LogSQL originalAllowSQLite3CreateForInstall := setting.AllowSQLite3CreateForInstall defer func() { setting.Database.Type = originalType setting.Database.Path = originalPath setting.Database.Host = originalHost setting.Database.User = originalUser setting.Database.Passwd = originalPasswd setting.Database.Name = originalName setting.Database.Schema = originalSchema setting.Database.SSLMode = originalSSLMode setting.Database.LogSQL = originalLogSQL setting.AllowSQLite3CreateForInstall = originalAllowSQLite3CreateForInstall db.UnsetDefaultEngine() }() setting.Database.Type = setting.DatabaseType("sqlite3") setting.Database.Path = filepath.Join(tmpDir, "gitea.db") setting.Database.Host = "" setting.Database.User = "" setting.Database.Passwd = "" setting.Database.Name = "" setting.Database.Schema = "" setting.Database.SSLMode = "" setting.Database.LogSQL = false setting.AllowSQLite3CreateForInstall = true require.NoError(t, db.InitEngine(ctx)) _, err := db.GetEngine(ctx).Exec("CREATE TABLE version (version INTEGER)") require.NoError(t, err) _, err = db.GetEngine(ctx).Exec("INSERT INTO version (version) VALUES (1)") require.NoError(t, err) _, err = db.GetEngine(ctx).Exec("CREATE TABLE user (id INTEGER PRIMARY KEY)") require.NoError(t, err) _, err = db.GetEngine(ctx).Exec("INSERT INTO user (id) VALUES (1)") require.NoError(t, err) db.UnsetDefaultEngine() existingDatabaseDetected, err := detectInstallExistingDatabase(ctx) require.NoError(t, err) assert.True(t, existingDatabaseDetected) // end edit/add - by petru @ codex } func TestShouldRestoreDatabaseFromBackup(t *testing.T) { // start edit/add - by petru @ codex assert.True(t, shouldRestoreDatabaseFromBackup(recoveryModeDatabaseBackup, false)) assert.False(t, shouldRestoreDatabaseFromBackup(recoveryModeDatabaseBackup, true)) assert.False(t, shouldRestoreDatabaseFromBackup(recoveryModeExistingDatabase, false)) // end edit/add - by petru @ codex } func TestShouldRequireBackupAppINISnapshot(t *testing.T) { // start edit/add - by petru @ codex assert.True(t, shouldRequireBackupAppINISnapshot(recoveryModeDatabaseBackup, true)) assert.False(t, shouldRequireBackupAppINISnapshot(recoveryModeDatabaseBackup, false)) assert.False(t, shouldRequireBackupAppINISnapshot(recoveryModeExistingDatabase, true)) // end edit/add - by petru @ codex } func TestMergeInstallRecoveryConfigSMTP(t *testing.T) { // start edit/add - by petru @ codex existing := &setting.RecoveryConfig{ SMTPAddr: "smtp.example.com", SMTPPort: "587", SMTPFrom: "gitSafe ", SMTPUser: "smtp-user", SMTPPasswd: "smtp-pass", } next := &setting.RecoveryConfig{ Enabled: true, AllowedEmails: "admin@example.com", } merged := mergeInstallRecoveryConfigSMTP(existing, next) require.NotNil(t, merged) assert.Equal(t, "smtp.example.com", merged.SMTPAddr) assert.Equal(t, "587", merged.SMTPPort) assert.Equal(t, "gitSafe ", merged.SMTPFrom) assert.Equal(t, "smtp-user", merged.SMTPUser) assert.Equal(t, "smtp-pass", merged.SMTPPasswd) // end edit/add - by petru @ codex } func TestImportAppINIInRecoveryKeepsImportedValues(t *testing.T) { defer test.MockVariableValue(&setting.InstallLock, false)() defer test.MockVariableValue(&setting.SupportedDatabaseTypes, []string{"mysql", "postgres", "sqlite3"})() defer test.MockVariableValue(&setting.AppName, "Gitea: Git with a cup of tea")() tmpDir := t.TempDir() oldCustomConf := setting.CustomConf defer func() { setting.CustomConf = oldCustomConf }() setting.CustomConf = filepath.Join(tmpDir, "custom", "conf", "app.ini") recoveryCfg := &setting.RecoveryConfig{ Enabled: true, AllowedEmails: "admin@example.com", TokenSecret: "secret", TokenTTLMin: 15, SMTPFrom: "gitSafe ", } require.NoError(t, setting.SaveRecoveryConfig(recoveryCfg)) var body bytes.Buffer writer := multipart.NewWriter(&body) fileWriter, err := writer.CreateFormFile("app_ini_file", "app.ini") require.NoError(t, err) _, err = fileWriter.Write([]byte(` APP_NAME = Imported Safe: for your code [database] DB_TYPE = sqlite3 PATH = ` + filepath.ToSlash(filepath.Join(tmpDir, "data", "gitea.db")) + ` [mailer] ENABLED = true SMTP_ADDR = smtp.example.com SMTP_PORT = 587 FROM = Imported Safe USER = smtp-user PASSWD = smtp-pass `)) require.NoError(t, err) require.NoError(t, writer.Close()) r := Routes() w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodPost, "/import_app_ini?recovery=1", &body) req.Header.Set("Content-Type", writer.FormDataContentType()) r.ServeHTTP(w, WithRecoveryRequest(req)) assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), `name="app_name" value="Imported Safe: for your code"`) assert.Contains(t, w.Body.String(), `id="db_type" name="db_type" value="sqlite3"`) assert.Contains(t, w.Body.String(), `name="smtp_addr" value="smtp.example.com"`) assert.Contains(t, w.Body.String(), `name="smtp_from_name" value="Imported Safe"`) assert.NotContains(t, w.Body.String(), `name="app_name" value="gitSafe"`) } // end edit/add - by petru @ codex func TestComposeInstallSMTPFrom(t *testing.T) { from, err := composeInstallSMTPFrom("gitSafe for your code", "", "noreply@example.com") require.NoError(t, err) assert.Equal(t, "gitSafe ", from) } func TestPopulateInstallFormFromConfig(t *testing.T) { defer test.MockVariableValue(&setting.SupportedDatabaseTypes, []string{"mysql", "postgres", "sqlite3"})() defer test.MockVariableValue(&setting.Langs, []string{"en-US", "de-DE", "fr-FR"})() cfg, err := setting.NewConfigProviderFromData(` APP_NAME = Imported Gitea RUN_USER = imported-user [database] DB_TYPE = postgres HOST = db.example.com:5432 USER = gitea PASSWD = secret NAME = giteadb SCHEMA = custom SSL_MODE = require [repository] ROOT = /srv/gitea/repos ALLOW_ADOPTION_OF_UNADOPTED_REPOSITORIES = false ALLOW_DELETE_OF_UNADOPTED_REPOSITORIES = true [server] DOMAIN = gitea.example.com SSH_PORT = 2222 HTTP_PORT = 4000 ROOT_URL = https://gitea.example.com/ [lfs] PATH = /srv/gitea/lfs [log] ROOT_PATH = /srv/gitea/log [mailer] ENABLED = true SMTP_ADDR = smtp.example.com SMTP_PORT = 587 FROM = Gitea USER = smtp-user PASSWD = smtp-pass [service] REGISTER_EMAIL_CONFIRM = true REGISTER_MANUAL_CONFIRM = false ENABLE_NOTIFY_MAIL = true ADMIN_CREATED_ACCOUNT_MODE = invite DISABLE_REGISTRATION = false ALLOW_ONLY_INTERNAL_REGISTRATION = true ALLOW_ONLY_EXTERNAL_REGISTRATION = false ENABLE_CAPTCHA = true REQUIRE_SIGNIN_VIEW = true DEFAULT_KEEP_EMAIL_PRIVATE = true DEFAULT_ALLOW_CREATE_ORGANIZATION = false DEFAULT_ENABLE_TIMETRACKING = false NO_REPLY_ADDRESS = noreply.example.com [openid] ENABLE_OPENID_SIGNIN = true ENABLE_OPENID_SIGNUP = true [security] ENABLE_UPDATE_CHECKER = true PASSWORD_HASH_ALGO = pbkdf2 [admin] ADMIN_MANAGEMENT_POLICY = super_admin_only [repository.release] MAX_FILES = 20 FILE_MAX_SIZE = 2048 [backup] PATH = /srv/gitea/backups/db RETENTION_COUNT = 9 COMPRESS = false INCLUDE_APP_INI_SNAPSHOT = false [cron.database_backup] ENABLED = true RUN_AT_START = true SCHEDULE = @every 12h [i18n] LANGS = de-DE,en-US `) require.NoError(t, err) form, curDBType := newInstallFormFromSettings() curDBType = populateInstallFormFromConfig(&form, cfg, curDBType) assert.Equal(t, "postgres", curDBType) assert.Equal(t, "postgres", form.DbType) assert.Equal(t, "Imported Gitea", form.AppName) assert.Equal(t, "imported-user", form.RunUser) assert.Equal(t, "db.example.com:5432", form.DbHost) assert.Equal(t, "gitea", form.DbUser) assert.Equal(t, "secret", form.DbPasswd) assert.Equal(t, "giteadb", form.DbName) assert.Equal(t, "custom", form.DbSchema) assert.Equal(t, "require", form.SSLMode) assert.Equal(t, "/srv/gitea/repos", form.RepoRootPath) assert.False(t, form.AllowAdoptionOfUnadoptedRepositories) assert.True(t, form.AllowDeleteOfUnadoptedRepositories) assert.Equal(t, "/srv/gitea/lfs", form.LFSRootPath) assert.Equal(t, "gitea.example.com", form.Domain) assert.Equal(t, 2222, form.SSHPort) assert.Equal(t, "4000", form.HTTPPort) assert.Equal(t, "https://gitea.example.com/", form.AppURL) assert.Equal(t, "/srv/gitea/log", form.LogRootPath) assert.Equal(t, "smtp.example.com", form.SMTPAddr) assert.Equal(t, "587", form.SMTPPort) assert.Equal(t, "Gitea ", form.SMTPFrom) assert.Equal(t, "Gitea", form.SMTPFromName) assert.Equal(t, "gitea@example.com", form.SMTPFromAddress) assert.Equal(t, "smtp-user", form.SMTPUser) assert.Equal(t, "smtp-pass", form.SMTPPasswd) assert.Equal(t, "de-DE", form.DefaultLanguage) assert.True(t, form.RegisterConfirm) assert.False(t, form.RegisterManualConfirm) assert.True(t, form.MailNotify) assert.Equal(t, "invite", form.AdminCreatedAccountMode) assert.Equal(t, "local_only", form.RegistrationMode) assert.False(t, form.EnableOpenIDSignIn) assert.False(t, form.EnableOpenIDSignUp) assert.True(t, form.EnableCaptcha) assert.True(t, form.RequireSignInView) assert.True(t, form.DefaultKeepEmailPrivate) assert.False(t, form.DefaultAllowCreateOrganization) assert.False(t, form.DefaultEnableTimetracking) assert.Equal(t, "noreply.example.com", form.NoReplyAddress) assert.True(t, form.EnableUpdateChecker) assert.Equal(t, "pbkdf2", form.PasswordAlgorithm) assert.Equal(t, "super_admin_only", form.AdminManagementPolicy) assert.EqualValues(t, 20, form.ReleaseMaxFiles) assert.EqualValues(t, 2048, form.ReleaseFileMaxSize) assert.True(t, form.BackupEnabled) assert.True(t, form.BackupRunAtStart) assert.Equal(t, "@every 12h", form.BackupSchedule) assert.Equal(t, "/srv/gitea/backups/db", form.BackupPath) assert.Equal(t, 9, form.BackupRetentionCount) assert.False(t, form.BackupCompress) assert.False(t, form.BackupIncludeAppINISnapshot) } // start edit/add - by petru @ codex func TestNewInstallFormFromSettingsBackupDefaults(t *testing.T) { cfg, err := setting.NewConfigProviderFromData("") require.NoError(t, err) defer test.MockVariableValue(&setting.CfgProvider, cfg)() form, _ := newInstallFormFromSettings() assert.True(t, form.BackupEnabled) assert.False(t, form.BackupRunAtStart) assert.Equal(t, "@daily", form.BackupSchedule) } func TestNewInstallFormFromSettingsRecoveryDefaults(t *testing.T) { oldCustomConf := setting.CustomConf defer func() { setting.CustomConf = oldCustomConf }() setting.CustomConf = filepath.Join(t.TempDir(), "custom", "conf", "app.ini") require.NoError(t, setting.SaveRecoveryConfig(&setting.RecoveryConfig{ Enabled: true, AllowedEmails: "admin@example.com,ops@example.com", SMTPAddr: "smtp.example.com", SMTPPort: "587", SMTPFrom: "Gitea ", SMTPUser: "smtp-user", SMTPPasswd: "smtp-pass", })) form, _ := newInstallFormFromSettings() assert.True(t, form.RecoveryEmailEnabled) assert.Equal(t, "admin@example.com,ops@example.com", form.RecoveryAllowedEmails) } func TestNewInstallFormFromSettingsDefaultsRecoveryEmailToEnabledOnFreshInstall(t *testing.T) { // edit/add - by petru @ codex oldCustomConf := setting.CustomConf defer func() { setting.CustomConf = oldCustomConf }() setting.CustomConf = filepath.Join(t.TempDir(), "custom", "conf", "app.ini") form, _ := newInstallFormFromSettings() assert.True(t, form.RecoveryEmailEnabled) } func TestDefaultInstallRecoveryAllowedEmailsUsesAdminEmailWhenEmpty(t *testing.T) { // edit/add - by petru @ codex form := &forms.InstallForm{AdminEmail: "admin@example.com"} assert.Equal(t, "admin@example.com", defaultInstallRecoveryAllowedEmails(form)) form.RecoveryAllowedEmails = "ops@example.com" assert.Equal(t, "", defaultInstallRecoveryAllowedEmails(form)) } func TestNormalizeInstallRecoveryEmails(t *testing.T) { normalized, err := normalizeInstallRecoveryEmails(" admin@example.com,\nops@example.com ; admin@example.com ") require.NoError(t, err) assert.Equal(t, "admin@example.com,ops@example.com", normalized) _, err = normalizeInstallRecoveryEmails("not-an-email") require.Error(t, err) } // end edit/add - by petru @ codex // start edit/add - by petru @ codex func TestApplyImportedAppINIRepositoryRecoveryDefaults(t *testing.T) { form := forms.InstallForm{ ImportedAppINI: true, RecoveryMode: recoveryModeRepositoryFileSystem, AllowAdoptionOfUnadoptedRepositories: false, AllowDeleteOfUnadoptedRepositories: false, } applyImportedAppINIRepositoryRecoveryDefaults(&form, false) assert.True(t, form.AllowAdoptionOfUnadoptedRepositories) assert.True(t, form.AllowDeleteOfUnadoptedRepositories) form = forms.InstallForm{ ImportedAppINI: true, RecoveryMode: recoveryModeRepositoryFileSystem, AllowAdoptionOfUnadoptedRepositories: false, AllowDeleteOfUnadoptedRepositories: false, } applyImportedAppINIRepositoryRecoveryDefaults(&form, true) assert.False(t, form.AllowAdoptionOfUnadoptedRepositories) assert.False(t, form.AllowDeleteOfUnadoptedRepositories) } func TestApplyImportedAppINIRepositoryRecoveryDefaultsWithoutFilesystemRecoveryMode(t *testing.T) { form := forms.InstallForm{ ImportedAppINI: true, RecoveryMode: recoveryModeExistingDatabase, AllowAdoptionOfUnadoptedRepositories: false, AllowDeleteOfUnadoptedRepositories: false, } applyImportedAppINIRepositoryRecoveryDefaults(&form, false) assert.False(t, form.AllowAdoptionOfUnadoptedRepositories) assert.False(t, form.AllowDeleteOfUnadoptedRepositories) } func TestHasInstallRepositoryFilesystem(t *testing.T) { tmpDir := t.TempDir() assert.False(t, hasInstallRepositoryFilesystem(tmpDir)) require.NoError(t, os.Mkdir(filepath.Join(tmpDir, "owner"), 0o755)) require.NoError(t, os.Mkdir(filepath.Join(tmpDir, "owner", "repo.git"), 0o755)) assert.True(t, hasInstallRepositoryFilesystem(tmpDir)) tmpDirFlat := t.TempDir() require.NoError(t, os.Mkdir(filepath.Join(tmpDirFlat, "repo.git"), 0o755)) assert.True(t, hasInstallRepositoryFilesystem(tmpDirFlat)) } func TestHasInstallRepositoryFilesystemRecoveryRequiresExistingOrManualAppINI(t *testing.T) { // edit/add - by petru @ codex tmpDir := t.TempDir() require.NoError(t, os.Mkdir(filepath.Join(tmpDir, "owner"), 0o755)) require.NoError(t, os.Mkdir(filepath.Join(tmpDir, "owner", "repo.git"), 0o755)) oldCustomConf := setting.CustomConf defer func() { setting.CustomConf = oldCustomConf }() setting.CustomConf = filepath.Join(tmpDir, "custom", "conf", "app.ini") form := &forms.InstallForm{RepoRootPath: tmpDir} assert.False(t, hasInstallRepositoryFilesystemRecovery(form)) form.ImportedAppINI = true assert.True(t, hasInstallRepositoryFilesystemRecovery(form)) form.BackupImportAppINI = true assert.False(t, hasInstallRepositoryFilesystemRecovery(form)) require.NoError(t, os.MkdirAll(filepath.Dir(setting.CustomConf), 0o755)) require.NoError(t, os.WriteFile(setting.CustomConf, []byte("APP_NAME = Test\n"), 0o644)) form.ImportedAppINI = false form.BackupImportAppINI = false assert.True(t, hasInstallRepositoryFilesystemRecovery(form)) } func TestNormalizeInstallRecoverySelectionStateForcesBundleRestoreModeAndSensitiveSecrets(t *testing.T) { // edit/add - by petru @ codex form := &forms.InstallForm{ ImportedAppINI: true, ImportSensitiveSecrets: false, BackupImportAppINI: true, BackupRestoreID: "20260524-010101.000000001", BackupRestoreDB: true, RecoveryMode: recoveryModeExistingDatabase, } normalizeInstallRecoverySelectionState(form, false) assert.True(t, form.ImportSensitiveSecrets) assert.Equal(t, recoveryModeDatabaseBackup, form.RecoveryMode) } func TestNormalizeInstallRecoverySelectionStateKeepsManualAppINIChoice(t *testing.T) { // edit/add - by petru @ codex form := &forms.InstallForm{ ImportedAppINI: true, ImportSensitiveSecrets: false, } normalizeInstallRecoverySelectionState(form, false) assert.False(t, form.ImportSensitiveSecrets) } func TestHasSelectedInstallDatabaseBackupSourceAcceptsUploadedFileWithoutBundle(t *testing.T) { // edit/add - by petru @ codex form := &forms.InstallForm{} assert.False(t, hasSelectedInstallDatabaseBackupSource(form, false)) assert.True(t, hasSelectedInstallDatabaseBackupSource(form, true)) form.BackupRestoreID = "20260524-010101.000000001" assert.True(t, hasSelectedInstallDatabaseBackupSource(form, false)) } func TestHasInstallRecoveryAppINIConfigSourceAcceptsManualImportWithoutBundleSnapshot(t *testing.T) { // edit/add - by petru @ codex form := &forms.InstallForm{ImportedAppINI: true} assert.True(t, hasInstallRecoveryAppINIConfigSource(form, nil)) form.BackupImportAppINI = true assert.False(t, hasInstallRecoveryAppINIConfigSource(form, nil)) } func TestRemoveInstallSQLiteDatabaseArtifacts(t *testing.T) { // edit/add - by petru @ codex tmpDir := t.TempDir() dbPath := filepath.Join(tmpDir, "gitea.db") for _, path := range []string{dbPath, dbPath + "-wal", dbPath + "-shm", dbPath + "-journal"} { require.NoError(t, os.WriteFile(path, []byte("x"), 0o644)) } require.NoError(t, removeInstallSQLiteDatabaseArtifacts(dbPath)) for _, path := range []string{dbPath, dbPath + "-wal", dbPath + "-shm", dbPath + "-journal"} { _, err := os.Stat(path) assert.ErrorIs(t, err, os.ErrNotExist) } } func TestResolveInstallBackupPath(t *testing.T) { defer test.MockVariableValue(&setting.AppWorkPath, "/srv/gitea")() assert.Equal(t, "", resolveInstallBackupPath("")) assert.Equal(t, "/srv/gitea/data/backups/db", resolveInstallBackupPath("data/backups/db")) assert.Equal(t, "/var/lib/gitea/backups", resolveInstallBackupPath("/var/lib/gitea/backups")) } func TestListInstallBackupChoices(t *testing.T) { tmpDir := t.TempDir() writeInstallBackupManifestForTest(t, tmpDir, "20260524-010101.000000001", "sqlite3", 100) writeInstallBackupManifestForTest(t, tmpDir, "20260524-010102.000000002", "mysql", 200) writeInstallBackupManifestForTest(t, tmpDir, "20260524-010103.000000003", "sqlite3", 300) choices, err := listInstallBackupChoices(tmpDir, "sqlite3") require.NoError(t, err) require.Len(t, choices, 2) assert.Equal(t, "20260524-010103.000000003", choices[0].ID) assert.Equal(t, "20260524-010101.000000001", choices[1].ID) assert.Equal(t, "20260524-010103.000000003 (sqlite3)", choices[0].Label) } func TestListInstallRecoveryBackupChoicesWithoutAppINIShowsAllBackups(t *testing.T) { // start edit/add - by petru @ codex tmpDir := t.TempDir() oldCustomConf := setting.CustomConf defer func() { setting.CustomConf = oldCustomConf }() setting.CustomConf = filepath.Join(tmpDir, "custom", "conf", "app.ini") writeInstallBackupManifestForTest(t, tmpDir, "20260524-010101.000000001", "sqlite3", 100) writeInstallBackupManifestForTest(t, tmpDir, "20260524-010102.000000002", "mysql", 200) form := &forms.InstallForm{ DbType: "mysql", BackupPath: tmpDir, } choices, err := listInstallRecoveryBackupChoices(form, true) require.NoError(t, err) require.Len(t, choices, 2) assert.Equal(t, "20260524-010102.000000002", choices[0].ID) assert.Equal(t, "20260524-010101.000000001", choices[1].ID) // end edit/add - by petru @ codex } func TestListInstallRecoveryBackupChoicesUsesRecoveryConfigBackupPathFallback(t *testing.T) { // start edit/add - by petru @ codex tmpDir := t.TempDir() emptyBackupDir := filepath.Join(tmpDir, "empty-backups") recoveryBackupDir := filepath.Join(tmpDir, "recovery-backups") require.NoError(t, os.MkdirAll(emptyBackupDir, 0o755)) require.NoError(t, os.MkdirAll(recoveryBackupDir, 0o755)) customConfDir := filepath.Join(tmpDir, "custom", "conf") require.NoError(t, os.MkdirAll(customConfDir, 0o755)) oldCustomConf := setting.CustomConf defer func() { setting.CustomConf = oldCustomConf }() setting.CustomConf = filepath.Join(customConfDir, "app.ini") require.NoError(t, os.WriteFile(setting.CustomConf, []byte("APP_NAME = Test\n"), 0o644)) require.NoError(t, setting.SaveRecoveryConfig(&setting.RecoveryConfig{ Enabled: true, AllowedEmails: "admin@example.com", BackupPath: recoveryBackupDir, })) writeInstallBackupManifestForTest(t, recoveryBackupDir, "20260524-010102.000000002", "mysql", 200) form := &forms.InstallForm{ DbType: "mysql", BackupPath: emptyBackupDir, } choices, err := listInstallRecoveryBackupChoices(form, true) require.NoError(t, err) require.Len(t, choices, 1) assert.Equal(t, "20260524-010102.000000002", choices[0].ID) // end edit/add - by petru @ codex } func TestListInstallRecoveryBackupChoicesWithAppINIStillShowsAllBackups(t *testing.T) { // start edit/add - by petru @ codex tmpDir := t.TempDir() customConfDir := filepath.Join(tmpDir, "custom", "conf") require.NoError(t, os.MkdirAll(customConfDir, 0o755)) oldCustomConf := setting.CustomConf defer func() { setting.CustomConf = oldCustomConf }() setting.CustomConf = filepath.Join(customConfDir, "app.ini") require.NoError(t, os.WriteFile(setting.CustomConf, []byte("APP_NAME = Test\n"), 0o644)) writeInstallBackupManifestForTest(t, tmpDir, "20260524-010101.000000001", "sqlite3", 100) writeInstallBackupManifestForTest(t, tmpDir, "20260524-010102.000000002", "mysql", 200) form := &forms.InstallForm{ DbType: "mysql", BackupPath: tmpDir, } choices, err := listInstallRecoveryBackupChoices(form, true) require.NoError(t, err) require.Len(t, choices, 2) assert.Equal(t, "20260524-010102.000000002", choices[0].ID) assert.Equal(t, "20260524-010101.000000001", choices[1].ID) // end edit/add - by petru @ codex } func TestNewInstallFormFromSettingsUsesRecoveryBackupPathWithoutAppINI(t *testing.T) { // start edit/add - by petru @ codex tmpDir := t.TempDir() oldCustomConf := setting.CustomConf oldBackup := setting.Backup defer func() { setting.CustomConf = oldCustomConf setting.Backup = oldBackup }() setting.CustomConf = filepath.Join(tmpDir, "custom", "conf", "app.ini") setting.Backup.Path = "data/backups/db" require.NoError(t, setting.SaveRecoveryConfig(&setting.RecoveryConfig{ Enabled: true, AllowedEmails: "admin@example.com", BackupPath: filepath.Join(tmpDir, "restores", "db"), RepoRootPath: filepath.Join(tmpDir, "restores", "repos"), })) form, _ := newInstallFormFromSettings() assert.Equal(t, filepath.Join(tmpDir, "restores", "db"), form.BackupPath) assert.Equal(t, filepath.Join(tmpDir, "restores", "repos"), form.RepoRootPath) // end edit/add - by petru @ codex } // end edit/add - by petru @ codex // start edit/add - by petru @ codex func writeInstallBackupManifestForTest(t *testing.T, rootPath, backupID, dbType string, createdUnix int64) { t.Helper() backupDir := filepath.Join(rootPath, backupID) require.NoError(t, os.MkdirAll(backupDir, 0o755)) manifestBytes, err := json.Marshal(map[string]any{ "schema_version": 1, "id": backupID, "created_unix": createdUnix, "db_type": dbType, "app_version": "dev", "app_name": "Test", "migration_version": 1, "file_name": "database.sql.gz", "file_sha256": "abc", "file_size": 1, "compressed": true, }) require.NoError(t, err) require.NoError(t, os.WriteFile(filepath.Join(backupDir, "manifest.json"), manifestBytes, 0o644)) } func writeInstallBackupManifestWithAppINIForTest(t *testing.T, rootPath, backupID, dbType string, createdUnix int64, appINIContent string) { // edit/add - by petru @ codex t.Helper() backupDir := filepath.Join(rootPath, backupID) require.NoError(t, os.MkdirAll(backupDir, 0o755)) manifestBytes, err := json.Marshal(map[string]any{ "schema_version": 1, "id": backupID, "created_unix": createdUnix, "db_type": dbType, "app_version": "dev", "app_name": "Test", "migration_version": 1, "file_name": "database.sql.gz", "file_sha256": "abc", "file_size": 1, "compressed": true, "app_ini_snapshot_file": "app.ini", }) require.NoError(t, err) require.NoError(t, os.WriteFile(filepath.Join(backupDir, "manifest.json"), manifestBytes, 0o644)) require.NoError(t, os.WriteFile(filepath.Join(backupDir, "app.ini"), []byte(appINIContent), 0o644)) } // end edit/add - by petru @ codex func TestPopulateInstallFormFromConfigReplacesSMTPFromSplitFields(t *testing.T) { cfg, err := setting.NewConfigProviderFromData(` APP_NAME = gitSafe: for your code [mailer] ENABLED = true SMTP_ADDR = smtp.example.com SMTP_PORT = 587 FROM = gitSafe `) require.NoError(t, err) form, curDBType := newInstallFormFromSettings() form.SMTPFrom = "Legacy " form.SMTPFromName = "Legacy" form.SMTPFromAddress = "legacy@example.com" populateInstallFormFromConfig(&form, cfg, curDBType) assert.Equal(t, "gitSafe ", form.SMTPFrom) assert.Equal(t, "gitSafe", form.SMTPFromName) assert.Equal(t, "noreply@example.com", form.SMTPFromAddress) } func TestPopulateInstallFormFromConfigWithSensitiveSecrets(t *testing.T) { cfg, err := setting.NewConfigProviderFromData(` [server] LFS_JWT_SECRET = lfs-secret [security] INTERNAL_TOKEN = internal-secret [oauth2] JWT_SECRET = oauth-secret `) require.NoError(t, err) form, curDBType := newInstallFormFromSettings() form.ImportSensitiveSecrets = true populateInstallFormFromConfig(&form, cfg, curDBType) assert.Equal(t, "lfs-secret", form.ImportedLFSJWTSecret) assert.Equal(t, "internal-secret", form.ImportedInternalToken) assert.Equal(t, "oauth-secret", form.ImportedOAuth2JWTSecret) } func TestPopulateInstallFormFromConfigWithSensitiveSecretURIs(t *testing.T) { tmpDir := t.TempDir() lfsSecretPath := filepath.Join(tmpDir, "lfs_secret") internalTokenPath := filepath.Join(tmpDir, "internal_token") oauthSecretPath := filepath.Join(tmpDir, "oauth_secret") require.NoError(t, os.WriteFile(lfsSecretPath, []byte("lfs-secret-uri\n"), 0o644)) require.NoError(t, os.WriteFile(internalTokenPath, []byte("internal-secret-uri\n"), 0o644)) require.NoError(t, os.WriteFile(oauthSecretPath, []byte("oauth-secret-uri\n"), 0o644)) cfg, err := setting.NewConfigProviderFromData(` [server] LFS_JWT_SECRET_URI = file:` + filepath.ToSlash(lfsSecretPath) + ` [security] INTERNAL_TOKEN_URI = file:` + filepath.ToSlash(internalTokenPath) + ` [oauth2] JWT_SECRET_URI = file:` + filepath.ToSlash(oauthSecretPath) + ` `) require.NoError(t, err) form, curDBType := newInstallFormFromSettings() form.ImportSensitiveSecrets = true populateInstallFormFromConfig(&form, cfg, curDBType) assert.Equal(t, "lfs-secret-uri", form.ImportedLFSJWTSecret) assert.Equal(t, "internal-secret-uri", form.ImportedInternalToken) assert.Equal(t, "oauth-secret-uri", form.ImportedOAuth2JWTSecret) } func TestPopulateInstallSensitiveSecretsFromConfigFillsMissingValuesOnly(t *testing.T) { tmpDir := t.TempDir() internalTokenPath := filepath.Join(tmpDir, "internal_token") require.NoError(t, os.WriteFile(internalTokenPath, []byte("internal-secret-uri\n"), 0o644)) cfg, err := setting.NewConfigProviderFromData(` [server] LFS_JWT_SECRET = lfs-secret [security] INTERNAL_TOKEN_URI = file:` + filepath.ToSlash(internalTokenPath) + ` [oauth2] JWT_SECRET = oauth-secret `) require.NoError(t, err) form := forms.InstallForm{ ImportSensitiveSecrets: true, ImportedLFSJWTSecret: "", ImportedInternalToken: "", ImportedOAuth2JWTSecret: "already-set", } populateInstallSensitiveSecretsFromConfig(&form, cfg) assert.Equal(t, "lfs-secret", form.ImportedLFSJWTSecret) assert.Equal(t, "internal-secret-uri", form.ImportedInternalToken) assert.Equal(t, "already-set", form.ImportedOAuth2JWTSecret) } func TestImportAppINIWithSensitiveSecrets(t *testing.T) { defer test.MockVariableValue(&setting.InstallLock, false)() var body bytes.Buffer writer := multipart.NewWriter(&body) require.NoError(t, writer.WriteField("import_sensitive_secrets", "on")) fileWriter, err := writer.CreateFormFile("app_ini_file", "app.ini") require.NoError(t, err) _, err = fileWriter.Write([]byte(` [server] LFS_JWT_SECRET = lfs-secret [security] INTERNAL_TOKEN = internal-secret [oauth2] JWT_SECRET = oauth-secret `)) require.NoError(t, err) require.NoError(t, writer.Close()) r := Routes() w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodPost, "/import_app_ini", &body) req.Header.Set("Content-Type", writer.FormDataContentType()) r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), `name="imported_lfs_jwt_secret" value="lfs-secret"`) assert.Contains(t, w.Body.String(), `name="imported_internal_token" value="internal-secret"`) assert.Contains(t, w.Body.String(), `name="imported_o_auth2_jwt_secret" value="oauth-secret"`) } // start edit/add - by petru @ codex func TestImportAppINIDefaultsSensitiveSecretsToEnabled(t *testing.T) { defer test.MockVariableValue(&setting.InstallLock, false)() var body bytes.Buffer writer := multipart.NewWriter(&body) fileWriter, err := writer.CreateFormFile("app_ini_file", "app.ini") require.NoError(t, err) _, err = fileWriter.Write([]byte(` [server] LFS_JWT_SECRET = lfs-secret [security] INTERNAL_TOKEN = internal-secret [oauth2] JWT_SECRET = oauth-secret `)) require.NoError(t, err) require.NoError(t, writer.Close()) r := Routes() w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodPost, "/import_app_ini", &body) req.Header.Set("Content-Type", writer.FormDataContentType()) r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), `id="import_sensitive_secrets" name="import_sensitive_secrets" type="checkbox" form="install-form" checked`) assert.Contains(t, w.Body.String(), `name="imported_lfs_jwt_secret" value="lfs-secret"`) assert.Contains(t, w.Body.String(), `name="imported_internal_token" value="internal-secret"`) assert.Contains(t, w.Body.String(), `name="imported_o_auth2_jwt_secret" value="oauth-secret"`) } func TestImportBackupAppINIInRecovery(t *testing.T) { // edit/add - by petru @ codex defer test.MockVariableValue(&setting.InstallLock, false)() tmpDir := t.TempDir() writeInstallBackupManifestWithAppINIForTest(t, tmpDir, "20260524-010101.000000001", "sqlite3", 100, ` APP_NAME = Imported Safe: for your code [database] DB_TYPE = sqlite3 PATH = `+filepath.ToSlash(filepath.Join(tmpDir, "data", "gitea.db"))+` [mailer] ENABLED = true SMTP_ADDR = smtp.example.com SMTP_PORT = 587 FROM = Imported Safe USER = smtp-user PASSWD = smtp-pass `) var body bytes.Buffer writer := multipart.NewWriter(&body) require.NoError(t, writer.WriteField("backup_path", tmpDir)) require.NoError(t, writer.WriteField("backup_restore_id", "20260524-010101.000000001")) require.NoError(t, writer.WriteField("backup_import_app_ini", "true")) require.NoError(t, writer.Close()) r := Routes() w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodPost, "/import_backup_app_ini?recovery=1", &body) req.Header.Set("Content-Type", writer.FormDataContentType()) r.ServeHTTP(w, WithRecoveryRequest(req)) assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), `name="app_name" value="Imported Safe: for your code"`) assert.Contains(t, w.Body.String(), `id="db_type" name="db_type" value="sqlite3"`) assert.Contains(t, w.Body.String(), `name="smtp_addr" value="smtp.example.com"`) assert.Contains(t, w.Body.String(), `name="smtp_from_name" value="Imported Safe"`) assert.Contains(t, w.Body.String(), `name="imported_app_ini" value="true"`) assert.Contains(t, w.Body.String(), `id="import_sensitive_secrets" name="import_sensitive_secrets" type="checkbox" form="install-form" checked`) assert.Contains(t, w.Body.String(), `id="install_backup_restore_id" name="backup_restore_id" value="20260524-010101.000000001"`) } // end edit/add - by petru @ codex func TestApplyInstallSensitiveSecretsToConfigPersistsImportedValues(t *testing.T) { tmpDir := t.TempDir() configPath := filepath.Join(tmpDir, "app.ini") defer test.MockVariableValue(&setting.InternalToken, "")() cfg, err := setting.NewConfigProviderFromData("") require.NoError(t, err) form := forms.InstallForm{ LFSRootPath: filepath.Join(tmpDir, "lfs"), ImportSensitiveSecrets: true, ImportedLFSJWTSecret: "lfs-secret", ImportedInternalToken: "internal-secret", ImportedOAuth2JWTSecret: "oauth-secret", } require.NoError(t, applyInstallSensitiveSecretsToConfig(cfg, &form)) require.NoError(t, cfg.SaveTo(configPath)) data, err := os.ReadFile(configPath) require.NoError(t, err) content := string(data) assert.Contains(t, content, "LFS_JWT_SECRET = lfs-secret") assert.Contains(t, content, "INTERNAL_TOKEN = internal-secret") assert.Contains(t, content, "JWT_SECRET = oauth-secret") } // start edit/add - by petru @ codex func TestApplyInstallSensitiveSecretsToConfigPreservesExistingLFSJWTSecretWithoutImport(t *testing.T) { tmpDir := t.TempDir() configPath := filepath.Join(tmpDir, "app.ini") cfg, err := setting.NewConfigProviderFromData(` [server] LFS_JWT_SECRET = existing-lfs-secret `) require.NoError(t, err) form := forms.InstallForm{ LFSRootPath: filepath.Join(tmpDir, "lfs"), ImportSensitiveSecrets: false, } require.NoError(t, applyInstallSensitiveSecretsToConfig(cfg, &form)) require.NoError(t, cfg.SaveTo(configPath)) data, err := os.ReadFile(configPath) require.NoError(t, err) content := string(data) assert.Contains(t, content, "LFS_JWT_SECRET = existing-lfs-secret") assert.NotContains(t, content, "LFS_JWT_SECRET_URI") } // end edit/add - by petru @ codex func TestMain(m *testing.M) { unittest.MainTest(m) }