Files
bookhoard/internal/utils/isbn_test.go
john-okeefe 0f8db2ab07 Add ISBN-10 to ISBN-13 validation and conversion
Enhance NormalizeISBN to validate and convert ISBNs:
- Validate length (10 or 13 digits), return error if invalid
- Convert ISBN-10 to ISBN-13 by prefixing '978' and recalculating checksum
- Add NormalizeISBNSafe for backward compatibility in scanners

This ensures all ISBNs stored in database are valid ISBN-13 format.
2026-02-11 09:42:18 -05:00

244 lines
4.7 KiB
Go

package utils
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNormalizeISBN_ValidISBNs(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "ISBN-10 with hyphens",
input: "0-306-40615-2",
expected: "0306406152",
},
{
name: "ISBN-13 with hyphens",
input: "978-0-306-40615-7",
expected: "9780306406157",
},
{
name: "ISBN-10 with spaces",
input: "0 306 40615 2",
expected: "0306406152",
},
{
name: "ISBN-13 with spaces",
input: "978 0 306 40615 7",
expected: "9780306406157",
},
{
name: "ISBN-10 with hyphens and spaces",
input: "0-306-40615 2",
expected: "0306406152",
},
{
name: "ISBN-13 with hyphens and spaces",
input: "978-0-306 40615-7",
expected: "9780306406157",
},
{
name: "ISBN-10 clean",
input: "0306406152",
expected: "0306406152",
},
{
name: "ISBN-13 clean",
input: "9780306406157",
expected: "9780306406157",
},
{
name: "ISBN-10 with X",
input: "0-8044-2957-X",
expected: "080442957X",
},
{
name: "ISBN-10 with X and hyphens",
input: "0-8044-2957-X",
expected: "080442957X",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := NormalizeISBNSafe(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
func TestNormalizeISBN_EdgeCases(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "empty string",
input: "",
expected: "",
},
{
name: "only hyphens",
input: "---",
expected: "",
},
{
name: "only spaces",
input: " ",
expected: "",
},
{
name: "mixed hyphens and spaces",
input: "- - -",
expected: "",
},
{
name: "multiple consecutive hyphens",
input: "0--306--40615--2",
expected: "0306406152",
},
{
name: "multiple consecutive spaces",
input: "0 306 40615 2",
expected: "0306406152",
},
{
name: "tabs and newlines (treated as spaces)",
input: "0\t306\n40615\r2",
expected: "0306406152",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := NormalizeISBNSafe(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
func TestNormalizeISBN_SpecialCharacters(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "mixed dots and hyphens (hyphens removed, dots preserved)",
input: "978-0.306-40615.7",
expected: "9780.30640615.7",
},
{
name: "multiple spaces between groups",
input: "978 0 306 40615 7",
expected: "9780306406157",
},
{
name: "mixed hyphens and spaces",
input: "978-0 306-40615 7",
expected: "9780306406157",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := NormalizeISBNSafe(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
func TestNormalizeISBN_RealWorldExamples(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "real book ISBN-10",
input: "0-596-00965-X",
expected: "059600965X",
},
{
name: "real book ISBN-13",
input: "978-0-596-00965-2",
expected: "9780596009652",
},
{
name: "another real ISBN-10",
input: "1-4028-9462-7",
expected: "1402894627",
},
{
name: "another real ISBN-13",
input: "978-1-4028-9462-6",
expected: "9781402894626",
},
{
name: "popular programming book",
input: "978-0-13-595705-9",
expected: "9780135957059",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := NormalizeISBNSafe(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
func TestNormalizeISBN_DoesNotModifyValidISBNs(t *testing.T) {
validISBNs := []string{
"0306406152",
"9780306406157",
"080442957X",
"1234567890123",
}
for _, isbn := range validISBNs {
t.Run(isbn, func(t *testing.T) {
result := NormalizeISBNSafe(isbn)
assert.Equal(t, isbn, result, "Valid ISBN should not be modified")
})
}
}
func TestNormalizeISBN_PreservesX(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "X at end",
input: "0-8044-2957-X",
expected: "080442957X",
},
{
name: "lowercase x",
input: "0-8044-2957-x",
expected: "080442957x",
},
{
name: "X in middle (invalid but preserved)",
input: "0-8044-X-2957",
expected: "08044X2957",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := NormalizeISBNSafe(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}