package main import ( "errors" "flag" "os" "strings" "testing" "gorm.io/gorm" "goyco/cmd/goyco/commands" "goyco/internal/config" "goyco/internal/testutils" ) func TestLoadDotEnv(t *testing.T) { t.Run("no .env file", func(t *testing.T) { defer func() { if r := recover(); r != nil { t.Errorf("loadDotEnv panicked: %v", r) } }() loadDotEnv() }) } func TestNewFlagSet(t *testing.T) { tests := []struct { name string flagName string usage func() }{ { name: "with usage function", flagName: "test", usage: func() { _, _ = os.Stderr.WriteString("test usage") }, }, { name: "without usage function", flagName: "test2", usage: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { fs := newFlagSet(tt.flagName, tt.usage) if fs.Name() != tt.flagName { t.Errorf("expected flag set name %q, got %q", tt.flagName, fs.Name()) } if tt.usage != nil && fs.Usage == nil { t.Error("expected usage function to be set") } }) } } func TestParseCommand(t *testing.T) { tests := []struct { name string args []string context string expectError bool expectHelp bool }{ { name: "valid arguments", args: []string{"--help"}, context: "test", expectError: true, expectHelp: true, }, { name: "invalid flag", args: []string{"--invalid-flag"}, context: "test", expectError: true, expectHelp: false, }, { name: "empty arguments", args: []string{}, context: "test", expectError: false, expectHelp: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { fs := flag.NewFlagSet("test", flag.ContinueOnError) fs.SetOutput(os.Stderr) err := parseCommand(fs, tt.args, tt.context) if tt.expectError && err == nil { t.Error("expected error but got none") } if !tt.expectError && err != nil { t.Errorf("unexpected error: %v", err) } if tt.expectHelp && !errors.Is(err, commands.ErrHelpRequested) { t.Error("expected help requested error") } }) } } func TestPrintRootUsage(t *testing.T) { defer func() { if r := recover(); r != nil { t.Errorf("printRootUsage panicked: %v", r) } }() printRootUsage() } func TestPrintRunUsage(t *testing.T) { defer func() { if r := recover(); r != nil { t.Errorf("printRunUsage panicked: %v", r) } }() printRunUsage() } func TestDispatchCommand(t *testing.T) { t.Run("unknown command", func(t *testing.T) { cfg := testutils.NewTestConfig() err := dispatchCommand(cfg, "unknown", []string{}) if err == nil { t.Error("expected error for unknown command") } expectedErr := "unknown command: unknown" if err.Error() != expectedErr { t.Errorf("expected error %q, got %q", expectedErr, err.Error()) } }) t.Run("help command", func(t *testing.T) { cfg := testutils.NewTestConfig() err := dispatchCommand(cfg, "help", []string{}) if err != nil { t.Errorf("unexpected error for help command: %v", err) } }) t.Run("h command", func(t *testing.T) { cfg := testutils.NewTestConfig() err := dispatchCommand(cfg, "-h", []string{}) if err != nil { t.Errorf("unexpected error for -h command: %v", err) } }) t.Run("--help command", func(t *testing.T) { cfg := testutils.NewTestConfig() err := dispatchCommand(cfg, "--help", []string{}) if err != nil { t.Errorf("unexpected error for --help command: %v", err) } }) t.Run("post list with injected database", func(t *testing.T) { cfg := testutils.NewTestConfig() useInMemoryCommandsConnector(t) err := dispatchCommand(cfg, "post", []string{"list"}) if err != nil { t.Errorf("unexpected error for post list: %v", err) } }) } func TestHandleRunCommand(t *testing.T) { t.Run("help requested", func(t *testing.T) { cfg := testutils.NewTestConfig() err := handleRunCommand(cfg, []string{"--help"}) if err != nil { t.Errorf("unexpected error for help: %v", err) } }) t.Run("unexpected arguments", func(t *testing.T) { cfg := testutils.NewTestConfig() err := handleRunCommand(cfg, []string{"extra", "args"}) if err == nil { t.Error("expected error for unexpected arguments") } expectedErr := "unexpected arguments for run command" if err.Error() != expectedErr { t.Errorf("expected error %q, got %q", expectedErr, err.Error()) } }) } func TestRun(t *testing.T) { t.Run("no arguments", func(t *testing.T) { err := run([]string{}) if err != nil { t.Logf("Expected error in test environment: %v", err) } }) t.Run("help flag", func(t *testing.T) { err := run([]string{"--help"}) if err == nil { t.Error("expected config loading error in test environment") } }) t.Run("invalid flag", func(t *testing.T) { err := run([]string{"--invalid-flag"}) if err == nil { t.Error("expected error for invalid flag") } }) } func TestRunE2E_CommandParsing(t *testing.T) { setupTestEnv(t) t.Run("help command succeeds", func(t *testing.T) { err := run([]string{"help"}) if err != nil { t.Errorf("Expected help command to succeed, got error: %v", err) } }) t.Run("unknown command fails with error", func(t *testing.T) { err := run([]string{"unknown-command"}) if err == nil { t.Error("Expected error for unknown command") } if err != nil && !strings.Contains(err.Error(), "unknown command") { t.Errorf("Expected error about unknown command, got: %v", err) } }) t.Run("migrate command parses correctly", func(t *testing.T) { err := run([]string{"migrate", "up"}) if err != nil && strings.Contains(err.Error(), "unknown command") { t.Errorf("Expected migrate command to be recognized, got parsing error: %v", err) } }) t.Run("post command parses correctly", func(t *testing.T) { useInMemoryCommandsConnector(t) err := run([]string{"post", "list"}) if err != nil && strings.Contains(err.Error(), "unknown command") { t.Errorf("Expected post command to be recognized, got parsing error: %v", err) } }) } func TestRunE2E_ConfigurationLoading(t *testing.T) { t.Run("missing required env vars fails gracefully", func(t *testing.T) { originalDBPwd := os.Getenv("DB_PASSWORD") originalSMTPHost := os.Getenv("SMTP_HOST") originalSMTPFrom := os.Getenv("SMTP_FROM") originalAdminEmail := os.Getenv("ADMIN_EMAIL") originalJWTSecret := os.Getenv("JWT_SECRET") defer func() { if originalDBPwd != "" { _ = os.Setenv("DB_PASSWORD", originalDBPwd) } if originalSMTPHost != "" { _ = os.Setenv("SMTP_HOST", originalSMTPHost) } if originalSMTPFrom != "" { _ = os.Setenv("SMTP_FROM", originalSMTPFrom) } if originalAdminEmail != "" { _ = os.Setenv("ADMIN_EMAIL", originalAdminEmail) } if originalJWTSecret != "" { _ = os.Setenv("JWT_SECRET", originalJWTSecret) } }() _ = os.Unsetenv("DB_PASSWORD") _ = os.Unsetenv("SMTP_HOST") _ = os.Unsetenv("SMTP_FROM") _ = os.Unsetenv("ADMIN_EMAIL") _ = os.Unsetenv("JWT_SECRET") err := run([]string{"help"}) if err == nil { t.Error("Expected error when required env vars are missing") } if err != nil && !strings.Contains(err.Error(), "configuration") && !strings.Contains(err.Error(), "config") { t.Logf("Got error (may be expected): %v", err) } }) t.Run("valid configuration loads successfully", func(t *testing.T) { setupTestEnv(t) err := run([]string{"help"}) if err != nil { t.Errorf("Expected help command to succeed with valid config, got: %v", err) } }) } func TestRunE2E_ArgumentParsing(t *testing.T) { setupTestEnv(t) t.Run("root help flag", func(t *testing.T) { err := run([]string{"--help"}) if err != nil && !strings.Contains(err.Error(), "flag") { t.Logf("Got error (may be expected in test env): %v", err) } }) t.Run("command with help flag", func(t *testing.T) { err := run([]string{"migrate", "--help"}) if err != nil && strings.Contains(err.Error(), "unknown command") { t.Errorf("Expected migrate command to be recognized, got: %v", err) } }) t.Run("command with invalid arguments", func(t *testing.T) { err := run([]string{"run", "extra", "args"}) if err == nil { t.Error("Expected error for unexpected arguments") } if err != nil && !strings.Contains(err.Error(), "unexpected arguments") { t.Errorf("Expected error about unexpected arguments, got: %v", err) } }) } func setupTestEnv(t *testing.T) { t.Helper() t.Setenv("DB_PASSWORD", "test-password") t.Setenv("SMTP_HOST", "smtp.example.com") t.Setenv("SMTP_FROM", "test@example.com") t.Setenv("ADMIN_EMAIL", "admin@example.com") t.Setenv("JWT_SECRET", "test-jwt-secret-key-that-is-long-enough-for-validation-purposes") tmpDir := os.TempDir() t.Setenv("LOG_DIR", tmpDir) t.Setenv("PID_DIR", tmpDir) } func useInMemoryCommandsConnector(t *testing.T) { t.Helper() commands.SetDBConnector(func(_ *config.Config) (*gorm.DB, func() error, error) { db := testutils.NewTestDB(t) sqlDB, err := db.DB() if err != nil { t.Fatalf("failed to access underlying sql.DB: %v", err) } cleanup := func() error { return sqlDB.Close() } return db, cleanup, nil }) t.Cleanup(func() { commands.SetDBConnector(nil) }) }