diff --git a/internal/commands/config/doctor.go b/internal/commands/config/doctor.go index 5d9e88b..ff43188 100644 --- a/internal/commands/config/doctor.go +++ b/internal/commands/config/doctor.go @@ -71,12 +71,18 @@ func (d *DoctorCommand) runHealthCheck(ctx context.Context, t *i18n.Translations } case checkStatusWarning: spinner.Warning(checkName) + if result.message != "" { + ui.PrintInfo(" " + result.message) + } warnings = append(warnings, result.message) if result.suggestion != "" { ui.PrintInfo(" → " + result.suggestion) } case checkStatusError: spinner.Error(checkName) + if result.message != "" { + ui.PrintInfo(" " + result.message) + } errors = append(errors, result.message) allPassed = false if result.suggestion != "" { @@ -271,12 +277,18 @@ func (d *DoctorCommand) printCommandStatus(command string, available bool, t *i1 fmt.Printf(" %s matecommit %-15s %s\n", status, command, statusMsg) } -func (d *DoctorCommand) checkGitUserName(ctx context.Context, t *i18n.Translations, _ *config.Config) checkResult { +func (d *DoctorCommand) checkGitUserName(ctx context.Context, t *i18n.Translations, cfg *config.Config) checkResult { cmd := exec.CommandContext(ctx, "git", "config", "user.name") output, err := cmd.Output() userName := strings.TrimSpace(string(output)) if err != nil || userName == "" { + if cfg != nil && cfg.GitFallback.UserName != "" { + return checkResult{ + status: checkStatusWarning, + message: fmt.Sprintf("%s (%s)", t.GetMessage("doctor.git_user_fallback", 0, nil), cfg.GitFallback.UserName), + } + } return checkResult{ status: checkStatusError, message: t.GetMessage("doctor.git_user_not_set", 0, nil), @@ -289,12 +301,18 @@ func (d *DoctorCommand) checkGitUserName(ctx context.Context, t *i18n.Translatio message: fmt.Sprintf("(%s)", userName), } } -func (d *DoctorCommand) checkGitUserEmail(ctx context.Context, t *i18n.Translations, _ *config.Config) checkResult { +func (d *DoctorCommand) checkGitUserEmail(ctx context.Context, t *i18n.Translations, cfg *config.Config) checkResult { cmd := exec.CommandContext(ctx, "git", "config", "user.email") output, err := cmd.Output() userEmail := strings.TrimSpace(string(output)) if err != nil || userEmail == "" { + if cfg != nil && cfg.GitFallback.UserEmail != "" { + return checkResult{ + status: checkStatusWarning, + message: fmt.Sprintf("%s (%s)", t.GetMessage("doctor.git_email_fallback", 0, nil), cfg.GitFallback.UserEmail), + } + } return checkResult{ status: checkStatusError, message: t.GetMessage("doctor.git_email_not_set", 0, nil), diff --git a/internal/commands/config/doctor_test.go b/internal/commands/config/doctor_test.go new file mode 100644 index 0000000..3cb8b82 --- /dev/null +++ b/internal/commands/config/doctor_test.go @@ -0,0 +1,99 @@ +package config + +import ( + "context" + "os" + "os/exec" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/thomas-vilte/matecommit/internal/config" + "github.com/thomas-vilte/matecommit/internal/i18n" +) + +// setupDoctorTestRepo creates a fresh git repo in a temp dir, with global +// git identity explicitly cleared for the duration of the test via +// GIT_CONFIG_GLOBAL/GIT_CONFIG_SYSTEM, so the checks can reliably exercise +// the "git config user.name/email is unset" path regardless of the host's +// real git configuration. +func setupDoctorTestRepo(t *testing.T) func() { + tmpDir, err := os.MkdirTemp("", "matecommit-doctor-test-*") + assert.NoError(t, err) + + originalDir, err := os.Getwd() + assert.NoError(t, err) + + assert.NoError(t, os.Chdir(tmpDir)) + assert.NoError(t, exec.Command("git", "init").Run()) + + emptyConfig := tmpDir + "/empty-gitconfig" + assert.NoError(t, os.WriteFile(emptyConfig, []byte{}, 0644)) + t.Setenv("GIT_CONFIG_GLOBAL", emptyConfig) + t.Setenv("GIT_CONFIG_SYSTEM", emptyConfig) + + return func() { + _ = os.Chdir(originalDir) + _ = os.RemoveAll(tmpDir) + } +} + +func newDoctorTestTranslations(t *testing.T) *i18n.Translations { + translations, err := i18n.NewTranslations("en", "../../i18n/locales") + assert.NoError(t, err) + return translations +} + +func TestDoctorCommand_checkGitUserName(t *testing.T) { + t.Run("errors when unset and no fallback configured", func(t *testing.T) { + trans := newDoctorTestTranslations(t) + cleanup := setupDoctorTestRepo(t) + defer cleanup() + + d := NewDoctorCommand() + result := d.checkGitUserName(context.Background(), trans, &config.Config{}) + + assert.Equal(t, checkStatusError, result.status) + assert.NotEmpty(t, result.suggestion) + }) + + t.Run("warns instead of erroring when a matecommit fallback identity is configured", func(t *testing.T) { + trans := newDoctorTestTranslations(t) + cleanup := setupDoctorTestRepo(t) + defer cleanup() + + d := NewDoctorCommand() + cfg := &config.Config{GitFallback: config.GitConfig{UserName: "Fallback Name"}} + result := d.checkGitUserName(context.Background(), trans, cfg) + + assert.Equal(t, checkStatusWarning, result.status) + assert.Contains(t, result.message, "Fallback Name") + }) + + t.Run("ok when git user.name is actually configured", func(t *testing.T) { + trans := newDoctorTestTranslations(t) + cleanup := setupDoctorTestRepo(t) + defer cleanup() + assert.NoError(t, exec.Command("git", "config", "user.name", "Real Name").Run()) + + d := NewDoctorCommand() + result := d.checkGitUserName(context.Background(), trans, &config.Config{}) + + assert.Equal(t, checkStatusOK, result.status) + assert.Contains(t, result.message, "Real Name") + }) +} + +func TestDoctorCommand_checkGitUserEmail(t *testing.T) { + t.Run("warns instead of erroring when a matecommit fallback identity is configured", func(t *testing.T) { + trans := newDoctorTestTranslations(t) + cleanup := setupDoctorTestRepo(t) + defer cleanup() + + d := NewDoctorCommand() + cfg := &config.Config{GitFallback: config.GitConfig{UserEmail: "fallback@example.com"}} + result := d.checkGitUserEmail(context.Background(), trans, cfg) + + assert.Equal(t, checkStatusWarning, result.status) + assert.Contains(t, result.message, "fallback@example.com") + }) +} diff --git a/internal/i18n/locales/active.en.toml b/internal/i18n/locales/active.en.toml index da20038..22d2b23 100644 --- a/internal/i18n/locales/active.en.toml +++ b/internal/i18n/locales/active.en.toml @@ -577,6 +577,8 @@ command_ready = "(ready)" command_unavailable = "(unavailable)" git_user_not_set = "Git user.name is not configured" git_email_not_set = "Git user.email is not configured" +git_user_fallback = "Git user.name not set, using matecommit fallback identity" +git_email_fallback = "Git user.email not set, using matecommit fallback identity" # Checks check_config_file = "Configuration file" diff --git a/internal/i18n/locales/active.es.toml b/internal/i18n/locales/active.es.toml index e40798a..2268487 100644 --- a/internal/i18n/locales/active.es.toml +++ b/internal/i18n/locales/active.es.toml @@ -517,29 +517,29 @@ ensure_modified_files = "Asegúrate de tener archivos modificados antes de gener run_config_init = "Ejecuta: matecommit config init" internal_error = "Error interno del sistema" error_saving_config = "Error guardando la configuración" -git_user_not_configured = "Git user.name is not configured" -git_email_not_configured = "Git user.email is not configured" -git_config_user_suggestion = "Run: git config --global user.name \"Your Name\"" -git_config_email_suggestion = "Run: git config --global user.email \"your@email.com\"" -not_in_git_repo = "You are not in a Git repository" -git_init_suggestion = "Run 'git init' to create a new repository or navigate to an existing one" +git_user_not_configured = "Git user.name no está configurado" +git_email_not_configured = "Git user.email no está configurado" +git_config_user_suggestion = "Ejecuta: git config --global user.name \"Tu Nombre\"" +git_config_email_suggestion = "Ejecuta: git config --global user.email \"tu@email.com\"" +not_in_git_repo = "No estás en un repositorio Git" +git_init_suggestion = "Ejecuta 'git init' para crear un repositorio nuevo o navega a uno existente" try_suggestion = "💡 Prueba: " # GitHub/VCS Errors -github_token_invalid = "GitHub token is invalid or expired" -github_token_suggestion = "Check your token at: matecommit config init" -github_insufficient_perms = "GitHub token has insufficient permissions" -github_perms_suggestion = "Ensure your token has the scopes: repo, write:org" -github_rate_limit = "GitHub API rate limit exceeded" -github_rate_limit_suggestion = "Wait a few minutes before trying again" -vcs_error = "VCS provider error" +github_token_invalid = "El token de GitHub es inválido o expiró" +github_token_suggestion = "Revisa tu token en: matecommit config init" +github_insufficient_perms = "El token de GitHub tiene permisos insuficientes" +github_perms_suggestion = "Asegúrate de que tu token tenga los scopes: repo, write:org" +github_rate_limit = "Se superó el límite de la API de GitHub" +github_rate_limit_suggestion = "Espera unos minutos antes de volver a intentar" +vcs_error = "Error del proveedor VCS" # Gemini/AI Errors -gemini_api_key_invalid = "Gemini API key is invalid" -gemini_api_key_suggestion = "Check your API key at: https://makersuite.google.com/app/apikey" -gemini_quota_exceeded = "Gemini API quota exceeded" -gemini_quota_suggestion = "Wait a few minutes or consider using a cheaper model" -update_failed = "Failed to update application" +gemini_api_key_invalid = "La API key de Gemini es inválida" +gemini_api_key_suggestion = "Revisa tu API key en: https://makersuite.google.com/app/apikey" +gemini_quota_exceeded = "Se superó la cuota de la API de Gemini" +gemini_quota_suggestion = "Espera unos minutos o considera usar un modelo más económico" +update_failed = "Error al actualizar la aplicación" # UI - Preview y confirmaciones [ui_preview] @@ -595,8 +595,10 @@ has_errors = "Configuración incompleta (hay errores)" available_commands = "Comandos disponibles:" command_ready = "(listo)" command_unavailable = "(no disponible)" -git_user_not_set = "Git user.name is not configured" -git_email_not_set = "Git user.email is not configured" +git_user_not_set = "Git user.name no está configurado" +git_email_not_set = "Git user.email no está configurado" +git_user_fallback = "Git user.name no está configurado, usando la identidad de respaldo de matecommit" +git_email_fallback = "Git user.email no está configurado, usando la identidad de respaldo de matecommit" # Checks check_config_file = "Archivo de configuración" @@ -606,8 +608,8 @@ check_ai_key = "API key de {{.Provider}}" check_ai_key_generic = "API key del proveedor de IA" check_github_token = "Token de GitHub" check_editor = "Editor configurado" -check_git_user_name = "Checking Git user.name" -check_git_user_email = "Checking Git user.email" +check_git_user_name = "Verificando Git user.name" +check_git_user_email = "Verificando Git user.email" gemini_key_invalid = "API key de Gemini inválida o sin permisos" gemini_not_configured = "API key de Gemini no configurada"