Files
go-jdenticon/cmd/jdenticon/root_test.go
Kevin McIntyre d9e84812ff Initial release: Go Jdenticon library v0.1.0
- Core library with SVG and PNG generation
- CLI tool with generate and batch commands
- Cross-platform path handling for Windows compatibility
- Comprehensive test suite with integration tests
2026-01-03 23:41:48 -05:00

308 lines
8.3 KiB
Go

package main
import (
"os"
"testing"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
// TestRootCommand tests the basic structure and flags of the root command
func TestRootCommand(t *testing.T) {
// Reset viper for clean test state
viper.Reset()
tests := []struct {
name string
args []string
wantErr bool
validate func(t *testing.T, cmd *cobra.Command)
}{
{
name: "help flag",
args: []string{"--help"},
wantErr: false,
validate: func(t *testing.T, cmd *cobra.Command) {
// Help should be available
if !cmd.HasAvailableFlags() {
t.Error("Expected command to have available flags")
}
},
},
{
name: "size flag",
args: []string{"--size", "128"},
wantErr: false,
validate: func(t *testing.T, cmd *cobra.Command) {
if viper.GetInt("size") != 128 {
t.Errorf("Expected size=128, got %d", viper.GetInt("size"))
}
},
},
{
name: "format flag svg",
args: []string{"--format", "svg"},
wantErr: false,
validate: func(t *testing.T, cmd *cobra.Command) {
if viper.GetString("format") != "svg" {
t.Errorf("Expected format=svg, got %s", viper.GetString("format"))
}
},
},
{
name: "format flag png",
args: []string{"--format", "png"},
wantErr: false,
validate: func(t *testing.T, cmd *cobra.Command) {
if viper.GetString("format") != "png" {
t.Errorf("Expected format=png, got %s", viper.GetString("format"))
}
},
},
{
name: "invalid format",
args: []string{"--format", "invalid"},
wantErr: true,
validate: func(t *testing.T, cmd *cobra.Command) {
// Should not reach here on error
},
},
{
name: "padding flag",
args: []string{"--padding", "0.15"},
wantErr: false,
validate: func(t *testing.T, cmd *cobra.Command) {
if viper.GetFloat64("padding") != 0.15 {
t.Errorf("Expected padding=0.15, got %f", viper.GetFloat64("padding"))
}
},
},
{
name: "color-saturation flag",
args: []string{"--color-saturation", "0.8"},
wantErr: false,
validate: func(t *testing.T, cmd *cobra.Command) {
if viper.GetFloat64("color-saturation") != 0.8 {
t.Errorf("Expected color-saturation=0.8, got %f", viper.GetFloat64("color-saturation"))
}
},
},
{
name: "bg-color flag",
args: []string{"--bg-color", "#ffffff"},
wantErr: false,
validate: func(t *testing.T, cmd *cobra.Command) {
if viper.GetString("bg-color") != "#ffffff" {
t.Errorf("Expected bg-color=#ffffff, got %s", viper.GetString("bg-color"))
}
},
},
{
name: "hue-restrictions flag",
args: []string{"--hue-restrictions", "0,120,240"},
wantErr: false,
validate: func(t *testing.T, cmd *cobra.Command) {
hues := viper.GetStringSlice("hue-restrictions")
expected := []string{"0", "120", "240"}
if len(hues) != len(expected) {
t.Errorf("Expected %d hue restrictions, got %d", len(expected), len(hues))
}
for i, hue := range expected {
if i >= len(hues) || hues[i] != hue {
t.Errorf("Expected hue[%d]=%s, got %s", i, hue, hues[i])
}
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Reset viper for each test
viper.Reset()
// Create a fresh root command for each test
cmd := &cobra.Command{
Use: "jdenticon",
Short: "Generate identicons from any input string",
}
// Re-initialize flags
initTestFlags(cmd)
// Set args and execute
cmd.SetArgs(tt.args)
err := cmd.Execute()
if (err != nil) != tt.wantErr {
t.Errorf("Execute() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && tt.validate != nil {
tt.validate(t, cmd)
}
})
}
}
// initTestFlags initializes flags for testing (similar to root init())
func initTestFlags(cmd *cobra.Command) {
// Basic flags shared across commands
var formatFlag FormatFlag = FormatSVG // Default to SVG
cmd.PersistentFlags().IntP("size", "s", 200, "Size of the identicon in pixels")
cmd.PersistentFlags().VarP(&formatFlag, "format", "f", `Output format ("png" or "svg")`)
cmd.PersistentFlags().Float64P("padding", "p", 0.08, "Padding as percentage of icon size (0.0-0.5)")
// Color configuration flags
cmd.PersistentFlags().Float64("color-saturation", 0.5, "Saturation for colored shapes (0.0-1.0)")
cmd.PersistentFlags().Float64("grayscale-saturation", 0.0, "Saturation for grayscale shapes (0.0-1.0)")
cmd.PersistentFlags().String("bg-color", "", "Background color (hex format, e.g., #ffffff)")
// Advanced configuration flags
cmd.PersistentFlags().StringSlice("hue-restrictions", nil, "Restrict hues to specific degrees (0-360), e.g., --hue-restrictions=0,120,240")
cmd.PersistentFlags().String("color-lightness", "0.4,0.8", "Color lightness range as min,max (0.0-1.0)")
cmd.PersistentFlags().String("grayscale-lightness", "0.3,0.9", "Grayscale lightness range as min,max (0.0-1.0)")
cmd.PersistentFlags().Int("png-supersampling", 8, "PNG supersampling factor (1-16)")
// Bind flags to viper
viper.BindPFlag("size", cmd.PersistentFlags().Lookup("size"))
viper.BindPFlag("format", cmd.PersistentFlags().Lookup("format"))
viper.BindPFlag("padding", cmd.PersistentFlags().Lookup("padding"))
viper.BindPFlag("color-saturation", cmd.PersistentFlags().Lookup("color-saturation"))
viper.BindPFlag("grayscale-saturation", cmd.PersistentFlags().Lookup("grayscale-saturation"))
viper.BindPFlag("bg-color", cmd.PersistentFlags().Lookup("bg-color"))
viper.BindPFlag("hue-restrictions", cmd.PersistentFlags().Lookup("hue-restrictions"))
viper.BindPFlag("color-lightness", cmd.PersistentFlags().Lookup("color-lightness"))
viper.BindPFlag("grayscale-lightness", cmd.PersistentFlags().Lookup("grayscale-lightness"))
viper.BindPFlag("png-supersampling", cmd.PersistentFlags().Lookup("png-supersampling"))
}
// TestPopulateConfigFromFlags tests the configuration building logic
func TestPopulateConfigFromFlags(t *testing.T) {
tests := []struct {
name string
setup func()
wantErr bool
validate func(t *testing.T, config interface{}, size int)
}{
{
name: "default config",
setup: func() {
viper.Reset()
viper.Set("size", 200)
viper.Set("format", "svg")
viper.Set("png-supersampling", 8)
},
wantErr: false,
validate: func(t *testing.T, config interface{}, size int) {
if size != 200 {
t.Errorf("Expected size=200, got %d", size)
}
},
},
{
name: "custom config",
setup: func() {
viper.Reset()
viper.Set("size", 128)
viper.Set("padding", 0.12)
viper.Set("color-saturation", 0.7)
viper.Set("bg-color", "#000000")
viper.Set("png-supersampling", 8)
},
wantErr: false,
validate: func(t *testing.T, config interface{}, size int) {
if size != 128 {
t.Errorf("Expected size=128, got %d", size)
}
},
},
{
name: "invalid size",
setup: func() {
viper.Reset()
viper.Set("size", -1)
},
wantErr: true,
validate: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.setup()
config, size, err := populateConfigFromFlags()
if (err != nil) != tt.wantErr {
t.Errorf("populateConfigFromFlags() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && tt.validate != nil {
tt.validate(t, config, size)
}
})
}
}
// TestGetFormatFromViper tests format flag validation
func TestGetFormatFromViper(t *testing.T) {
tests := []struct {
name string
formatValue string
wantFormat FormatFlag
wantErr bool
}{
{
name: "svg format",
formatValue: "svg",
wantFormat: FormatSVG,
wantErr: false,
},
{
name: "png format",
formatValue: "png",
wantFormat: FormatPNG,
wantErr: false,
},
{
name: "invalid format",
formatValue: "invalid",
wantFormat: FormatSVG,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
viper.Reset()
viper.Set("format", tt.formatValue)
format, err := getFormatFromViper()
if (err != nil) != tt.wantErr {
t.Errorf("getFormatFromViper() error = %v, wantErr %v", err, tt.wantErr)
return
}
if format != tt.wantFormat {
t.Errorf("getFormatFromViper() = %v, want %v", format, tt.wantFormat)
}
})
}
}
// TestMain sets up and tears down for tests
func TestMain(m *testing.M) {
// Setup
code := m.Run()
// Teardown
viper.Reset()
os.Exit(code)
}