- 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
403 lines
11 KiB
Go
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())
|
|
}
|
|
})
|
|
}
|
|
}
|