Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions internal/commands/config/doctor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 != "" {
Expand Down Expand Up @@ -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),
Expand All @@ -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),
Expand Down
99 changes: 99 additions & 0 deletions internal/commands/config/doctor_test.go
Original file line number Diff line number Diff line change
@@ -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")
})
}
2 changes: 2 additions & 0 deletions internal/i18n/locales/active.en.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
46 changes: 24 additions & 22 deletions internal/i18n/locales/active.es.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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"
Expand All @@ -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"
Expand Down
Loading