Files
go-jdenticon/cmd/jdenticon/integration_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

403 lines
11 KiB
Go

package main
import (
"bytes"
"context"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/ungluedlabs/go-jdenticon/jdenticon"
)
// testBinaryName returns the correct test binary name for the current OS.
// On Windows, executables need the .exe extension.
func testBinaryName() string {
if runtime.GOOS == "windows" {
return "jdenticon-test.exe"
}
return "jdenticon-test"
}
// TestCLIVsLibraryOutputIdentical verifies that CLI generates identical output to the library API
func TestCLIVsLibraryOutputIdentical(t *testing.T) {
// Build the CLI binary first
tempDir, err := os.MkdirTemp("", "jdenticon-integration-test")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer os.RemoveAll(tempDir)
cliBinary := filepath.Join(tempDir, testBinaryName())
cmd := exec.Command("go", "build", "-o", cliBinary, ".")
if err := cmd.Run(); err != nil {
t.Fatalf("Failed to build CLI binary: %v", err)
}
testCases := []struct {
name string
input string
size int
cliArgs []string
configFunc func() jdenticon.Config
}{
{
name: "basic SVG generation",
input: "test@example.com",
size: 200,
cliArgs: []string{"generate", "test@example.com"},
configFunc: func() jdenticon.Config {
return jdenticon.DefaultConfig()
},
},
{
name: "custom size",
input: "test@example.com",
size: 128,
cliArgs: []string{"generate", "--size", "128", "test@example.com"},
configFunc: func() jdenticon.Config {
return jdenticon.DefaultConfig()
},
},
{
name: "custom padding",
input: "test@example.com",
size: 200,
cliArgs: []string{"generate", "--padding", "0.15", "test@example.com"},
configFunc: func() jdenticon.Config {
config := jdenticon.DefaultConfig()
config.Padding = 0.15
return config
},
},
{
name: "custom color saturation",
input: "test@example.com",
size: 200,
cliArgs: []string{"generate", "--color-saturation", "0.8", "test@example.com"},
configFunc: func() jdenticon.Config {
config := jdenticon.DefaultConfig()
config.ColorSaturation = 0.8
return config
},
},
{
name: "background color",
input: "test@example.com",
size: 200,
cliArgs: []string{"generate", "--bg-color", "#ffffff", "test@example.com"},
configFunc: func() jdenticon.Config {
config := jdenticon.DefaultConfig()
config.BackgroundColor = "#ffffff"
return config
},
},
{
name: "grayscale saturation",
input: "test@example.com",
size: 200,
cliArgs: []string{"generate", "--grayscale-saturation", "0.1", "test@example.com"},
configFunc: func() jdenticon.Config {
config := jdenticon.DefaultConfig()
config.GrayscaleSaturation = 0.1
return config
},
},
{
name: "custom lightness ranges",
input: "test@example.com",
size: 200,
cliArgs: []string{"generate", "--color-lightness", "0.3,0.7", "--grayscale-lightness", "0.2,0.8", "test@example.com"},
configFunc: func() jdenticon.Config {
config := jdenticon.DefaultConfig()
config.ColorLightnessRange = [2]float64{0.3, 0.7}
config.GrayscaleLightnessRange = [2]float64{0.2, 0.8}
return config
},
},
{
name: "hue restrictions",
input: "test@example.com",
size: 200,
cliArgs: []string{"generate", "--hue-restrictions", "0,120,240", "test@example.com"},
configFunc: func() jdenticon.Config {
config := jdenticon.DefaultConfig()
config.HueRestrictions = []float64{0, 120, 240}
return config
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Generate using library API
config := tc.configFunc()
librarySVG, err := jdenticon.ToSVGWithConfig(context.Background(), tc.input, tc.size, config)
if err != nil {
t.Fatalf("Library generation failed: %v", err)
}
// Generate using CLI
cmd := exec.Command(cliBinary, tc.cliArgs...)
var cliOutput bytes.Buffer
cmd.Stdout = &cliOutput
cmd.Stderr = &cliOutput
if err := cmd.Run(); err != nil {
t.Fatalf("CLI command failed: %v, output: %s", err, cliOutput.String())
}
cliSVG := cliOutput.String()
// Compare outputs
if cliSVG != librarySVG {
t.Errorf("CLI and library outputs differ")
t.Logf("Library output length: %d", len(librarySVG))
t.Logf("CLI output length: %d", len(cliSVG))
// Find the first difference
minLen := len(librarySVG)
if len(cliSVG) < minLen {
minLen = len(cliSVG)
}
for i := 0; i < minLen; i++ {
if librarySVG[i] != cliSVG[i] {
start := i - 20
if start < 0 {
start = 0
}
end := i + 20
if end > minLen {
end = minLen
}
t.Logf("First difference at position %d:", i)
t.Logf("Library: %q", librarySVG[start:end])
t.Logf("CLI: %q", cliSVG[start:end])
break
}
}
}
})
}
}
// TestCLIPNGVsLibraryOutputIdentical verifies PNG output consistency
func TestCLIPNGVsLibraryOutputIdentical(t *testing.T) {
// Build the CLI binary first
tempDir, err := os.MkdirTemp("", "jdenticon-png-integration-test")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer os.RemoveAll(tempDir)
cliBinary := filepath.Join(tempDir, testBinaryName())
cmd := exec.Command("go", "build", "-o", cliBinary, ".")
if err := cmd.Run(); err != nil {
t.Fatalf("Failed to build CLI binary: %v", err)
}
testCases := []struct {
name string
input string
size int
cliArgs []string
configFunc func() jdenticon.Config
}{
{
name: "basic PNG generation",
input: "test@example.com",
size: 64,
cliArgs: []string{"generate", "--format", "png", "--size", "64", "test@example.com"},
configFunc: func() jdenticon.Config {
return jdenticon.DefaultConfig()
},
},
{
name: "PNG with background",
input: "test@example.com",
size: 64,
cliArgs: []string{"generate", "--format", "png", "--size", "64", "--bg-color", "#ff0000", "test@example.com"},
configFunc: func() jdenticon.Config {
config := jdenticon.DefaultConfig()
config.BackgroundColor = "#ff0000"
return config
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Generate using library API
config := tc.configFunc()
libraryPNG, err := jdenticon.ToPNGWithConfig(context.Background(), tc.input, tc.size, config)
if err != nil {
t.Fatalf("Library PNG generation failed: %v", err)
}
// Generate using CLI
cmd := exec.Command(cliBinary, tc.cliArgs...)
var cliOutput bytes.Buffer
cmd.Stdout = &cliOutput
if err := cmd.Run(); err != nil {
t.Fatalf("CLI command failed: %v", err)
}
cliPNG := cliOutput.Bytes()
// Compare PNG outputs - they should be identical
if !bytes.Equal(cliPNG, libraryPNG) {
t.Errorf("CLI and library PNG outputs differ")
t.Logf("Library PNG size: %d bytes", len(libraryPNG))
t.Logf("CLI PNG size: %d bytes", len(cliPNG))
// Check PNG headers
if len(libraryPNG) >= 8 && len(cliPNG) >= 8 {
if !bytes.Equal(libraryPNG[:8], cliPNG[:8]) {
t.Logf("PNG headers differ")
t.Logf("Library: %v", libraryPNG[:8])
t.Logf("CLI: %v", cliPNG[:8])
}
}
}
})
}
}
// TestCLIBatchIntegration tests batch processing consistency
func TestCLIBatchIntegration(t *testing.T) {
// Build the CLI binary first
tempDir, err := os.MkdirTemp("", "jdenticon-batch-integration-test")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer os.RemoveAll(tempDir)
cliBinary := filepath.Join(tempDir, testBinaryName())
cmd := exec.Command("go", "build", "-o", cliBinary, ".")
if err := cmd.Run(); err != nil {
t.Fatalf("Failed to build CLI binary: %v", err)
}
// Create input file
inputFile := filepath.Join(tempDir, "inputs.txt")
inputs := []string{
"user1@example.com",
"user2@example.com",
"test-user",
}
inputContent := strings.Join(inputs, "\n")
if err := os.WriteFile(inputFile, []byte(inputContent), 0644); err != nil {
t.Fatalf("Failed to create input file: %v", err)
}
outputDir := filepath.Join(tempDir, "batch-output")
// Run batch command
cmd = exec.Command(cliBinary, "batch", inputFile, "--output-dir", outputDir)
if err := cmd.Run(); err != nil {
t.Fatalf("Batch command failed: %v", err)
}
// Verify each generated file matches library output
config := jdenticon.DefaultConfig()
for _, input := range inputs {
filename := sanitizeFilename(input) + ".svg"
filepath := filepath.Join(outputDir, filename)
// Read CLI-generated file
cliContent, err := os.ReadFile(filepath)
if err != nil {
t.Errorf("Failed to read CLI-generated file for %s: %v", input, err)
continue
}
// Generate using library
librarySVG, err := jdenticon.ToSVGWithConfig(context.Background(), input, 200, config)
if err != nil {
t.Errorf("Library generation failed for %s: %v", input, err)
continue
}
// Compare
if string(cliContent) != librarySVG {
t.Errorf("Batch file for %s differs from library output", input)
}
}
}
// TestCLIErrorHandling tests that CLI properly handles error cases
func TestCLIErrorHandling(t *testing.T) {
// Build the CLI binary first
tempDir, err := os.MkdirTemp("", "jdenticon-error-test")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer os.RemoveAll(tempDir)
cliBinary := filepath.Join(tempDir, testBinaryName())
cmd := exec.Command("go", "build", "-o", cliBinary, ".")
if err := cmd.Run(); err != nil {
t.Fatalf("Failed to build CLI binary: %v", err)
}
errorCases := []struct {
name string
args []string
expectError bool
}{
{
name: "no arguments",
args: []string{},
expectError: false, // Should show help, not error
},
{
name: "invalid format",
args: []string{"generate", "--format", "invalid", "test"},
expectError: true,
},
{
name: "negative size",
args: []string{"generate", "--size", "-1", "test"},
expectError: true,
},
{
name: "generate no arguments",
args: []string{"generate"},
expectError: true,
},
{
name: "batch missing output dir",
args: []string{"batch", "somefile.txt"},
expectError: true,
},
{
name: "batch missing input file",
args: []string{"batch", "nonexistent.txt", "--output-dir", "/tmp"},
expectError: true,
},
}
for _, tc := range errorCases {
t.Run(tc.name, func(t *testing.T) {
cmd := exec.Command(cliBinary, tc.args...)
var output bytes.Buffer
cmd.Stdout = &output
cmd.Stderr = &output
err := cmd.Run()
if tc.expectError && err == nil {
t.Errorf("Expected error but command succeeded. Output: %s", output.String())
} else if !tc.expectError && err != nil {
t.Errorf("Expected success but command failed: %v. Output: %s", err, output.String())
}
})
}
}