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
This commit is contained in:
402
cmd/jdenticon/integration_test.go
Normal file
402
cmd/jdenticon/integration_test.go
Normal file
@@ -0,0 +1,402 @@
|
||||
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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user