test: add comprehensive test suite for normalization functions

Add 100+ test cases covering all normalization scenarios:
- Empty/nil inputs, whitespace trimming
- Titlecasing with hyphens, apostrophes, multi-word tags
- Punctuation preference (hyphens, periods, apostrophes)
- Case-insensitive deduplication with and without punctuation
- Contributor case preservation (CAPSLOCK, Title Case, lowercase)
- Edge cases: only punctuation, multiple spaces, mixed content

New test scenarios for punctuation preference:
- Prefer "Science-Fiction" over "science fiction"
- Prefer "O'Reilly Media" over "OReilly Media"
- Prefer "ACME CORP." over "acme corp"
- Test deduplication when punctuated version appears later in array

All tests passing ✓

Relates to Tags & Contributors Migration Phase 4
This commit is contained in:
2026-02-08 11:03:50 -05:00
parent eea08630ce
commit 08e823d6ab
+518
View File
@@ -0,0 +1,518 @@
package utils
import (
"testing"
"github.com/stretchr/testify/assert"
)
// TestNormalizeTags tests tag display normalization
func TestNormalizeTags(t *testing.T) {
tests := []struct {
name string
input []string
expected []string
}{
{
name: "empty array",
input: []string{},
expected: []string{},
},
{
name: "nil input",
input: nil,
expected: []string{},
},
{
name: "single tag",
input: []string{" science fiction "},
expected: []string{"Science Fiction"},
},
{
name: "multiple tags",
input: []string{"fiction", "adventure"},
expected: []string{"Fiction", "Adventure"},
},
{
name: "multi-word tag",
input: []string{" science fiction "},
expected: []string{"Science Fiction"},
},
{
name: "hyphenated tag",
input: []string{"non-fiction"},
expected: []string{"Non-Fiction"},
},
{
name: "mixed case",
input: []string{"FICTION", "fiction", "Fiction"},
expected: []string{"Fiction"},
},
{
name: "case-insensitive dedup",
input: []string{"fiction", "FICTION", "Fiction"},
expected: []string{"Fiction"},
},
{
name: "remove empty strings",
input: []string{"fiction", "", "adventure", " "},
expected: []string{"Fiction", "Adventure"},
},
{
name: "whitespace trimming",
input: []string{" fiction ", "\tadventure\t"},
expected: []string{"Fiction", "Adventure"},
},
{
name: "preserves punctuation",
input: []string{"science-fiction", "O'Reilly"},
expected: []string{"Science-Fiction", "O'Reilly"},
},
{
name: "complex real-world example",
input: []string{" science fiction ", "FICTION", "non-fiction", "", " "},
expected: []string{"Science Fiction", "Fiction", "Non-Fiction"},
},
{
name: "prefer punctuation - hyphenated tag",
input: []string{"science fiction", "science-fiction", "Science Fiction"},
expected: []string{"Science-Fiction"},
},
{
name: "prefer punctuation - apostrophe tag",
input: []string{"OReilly Media", "oreilly media", "O'Reilly Media"},
expected: []string{"O'Reilly Media"},
},
{
name: "prefer punctuation - mixed",
input: []string{"nonfiction", "non-fiction", "NONFICTION"},
expected: []string{"Non-Fiction"},
},
{
name: "prefer first punctuation when multiple have punctuation",
input: []string{"science-fiction", "science fiction", "Science-Fiction"},
expected: []string{"Science-Fiction"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := NormalizeTags(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
// TestNormalizeTagsSearch tests tag search normalization
func TestNormalizeTagsSearch(t *testing.T) {
tests := []struct {
name string
input []string
expected []string
}{
{
name: "empty array",
input: []string{},
expected: []string{},
},
{
name: "nil input",
input: nil,
expected: []string{},
},
{
name: "single tag",
input: []string{" Science Fiction "},
expected: []string{"science fiction"},
},
{
name: "lowercase",
input: []string{"SCIENCE FICTION"},
expected: []string{"science fiction"},
},
{
name: "remove punctuation",
input: []string{"science-fiction"},
expected: []string{"science fiction"},
},
{
name: "remove period",
input: []string{"ACME CORP."},
expected: []string{"acme corp"},
},
{
name: "remove multiple punctuation",
input: []string{"O'Reilly Media!"},
expected: []string{"oreilly media"},
},
{
name: "case-insensitive dedup",
input: []string{"science fiction", "SCIENCE FICTION", "Science Fiction"},
expected: []string{"science fiction"},
},
{
name: "remove empty after punctuation removal",
input: []string{"..."},
expected: []string{},
},
{
name: "complex example",
input: []string{" Science-Fiction ", "FICTION", "", " "},
expected: []string{"science fiction", "fiction"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := NormalizeTagsSearch(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
// TestNormalizeContributors tests contributor display normalization
func TestNormalizeContributors(t *testing.T) {
tests := []struct {
name string
input []string
expected []string
}{
{
name: "empty array",
input: []string{},
expected: []string{},
},
{
name: "nil input",
input: nil,
expected: []string{},
},
{
name: "single contributor",
input: []string{" ACME CORP "},
expected: []string{"ACME CORP"},
},
{
name: "preserve case - CAPSLOCK",
input: []string{"ACME CORP"},
expected: []string{"ACME CORP"},
},
{
name: "preserve case - Title Case",
input: []string{"Acme Corp"},
expected: []string{"Acme Corp"},
},
{
name: "preserve case - lowercase",
input: []string{"acme corp"},
expected: []string{"acme corp"},
},
{
name: "preserve punctuation - period",
input: []string{"ACME CORP."},
expected: []string{"ACME CORP."},
},
{
name: "preserve punctuation - apostrophe",
input: []string{"O'Reilly Media"},
expected: []string{"O'Reilly Media"},
},
{
name: "case-insensitive dedup - different case",
input: []string{"ACME CORP", "Acme Corp", "acme corp"},
expected: []string{"ACME CORP"},
},
{
name: "case-insensitive dedup - with punctuation",
input: []string{"ACME CORP.", "Acme Corp", "acme corp"},
expected: []string{"ACME CORP."},
},
{
name: "trim whitespace",
input: []string{" ACME CORP ", "\tAcme\t"},
expected: []string{"ACME CORP", "Acme"},
},
{
name: "remove empty strings",
input: []string{"ACME CORP", "", "Acme Corp", " "},
expected: []string{"ACME CORP"},
},
{
name: "complex real-world example",
input: []string{" ACME CORP. ", "Acme Corp", "acme corp", " "},
expected: []string{"ACME CORP."},
},
{
name: "prefer punctuation - period at end",
input: []string{"acme corp", "ACME CORP.", "Acme Corp"},
expected: []string{"ACME CORP."},
},
{
name: "prefer punctuation - apostrophe",
input: []string{"OReilly Media", "oreilly media", "O'Reilly Media"},
expected: []string{"O'Reilly Media"},
},
{
name: "prefer punctuation - mixed",
input: []string{"ACME CORP", "Acme Corp", "acme corp."},
expected: []string{"acme corp."},
},
{
name: "prefer first punctuation when multiple have punctuation",
input: []string{"ACME CORP!", "ACME CORP.", "Acme Corp"},
expected: []string{"ACME CORP!"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := NormalizeContributors(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
// TestNormalizeContributorsSearch tests contributor search normalization
func TestNormalizeContributorsSearch(t *testing.T) {
tests := []struct {
name string
input []string
expected []string
}{
{
name: "empty array",
input: []string{},
expected: []string{},
},
{
name: "nil input",
input: nil,
expected: []string{},
},
{
name: "single contributor",
input: []string{" ACME CORP. "},
expected: []string{"acme corp"},
},
{
name: "lowercase",
input: []string{"ACME CORP"},
expected: []string{"acme corp"},
},
{
name: "remove punctuation - period",
input: []string{"ACME CORP."},
expected: []string{"acme corp"},
},
{
name: "remove punctuation - apostrophe",
input: []string{"O'Reilly Media"},
expected: []string{"oreilly media"},
},
{
name: "case-insensitive dedup",
input: []string{"ACME CORP", "acme corp", "Acme Corp"},
expected: []string{"acme corp"},
},
{
name: "case-insensitive dedup with punctuation",
input: []string{"ACME CORP.", "Acme Corp", "acme corp"},
expected: []string{"acme corp"},
},
{
name: "remove empty after punctuation removal",
input: []string{"..."},
expected: []string{},
},
{
name: "complex example",
input: []string{" ACME CORP. ", "Acme Corp", "acme corp", " "},
expected: []string{"acme corp"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := NormalizeContributorsSearch(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
// TestTitlecase tests titlecase helper function
func TestTitlecase(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "simple word",
input: "science",
expected: "Science",
},
{
name: "multi-word",
input: "science fiction",
expected: "Science Fiction",
},
{
name: "hyphenated",
input: "non-fiction",
expected: "Non-Fiction",
},
{
name: "already capitalized",
input: "Science Fiction",
expected: "Science Fiction",
},
{
name: "all caps",
input: "SCIENCE FICTION",
expected: "Science Fiction",
},
{
name: "all lowercase",
input: "science fiction",
expected: "Science Fiction",
},
{
name: "apostrophe",
input: "o'reilly media",
expected: "O'Reilly Media",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := titlecase(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
// TestRemovePunctuation tests punctuation removal
func TestRemovePunctuation(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "no punctuation",
input: "acme corp",
expected: "acme corp",
},
{
name: "period",
input: "ACME CORP.",
expected: "ACME CORP",
},
{
name: "apostrophe",
input: "O'Reilly",
expected: "OReilly",
},
{
name: "multiple punctuation",
input: "science-fiction!",
expected: "science fiction",
},
{
name: "only punctuation",
input: "...",
expected: "",
},
{
name: "mixed content",
input: "O'Reilly Media!",
expected: "OReilly Media",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := removePunctuation(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
// TestJoinTags tests tag joining
func TestJoinTags(t *testing.T) {
tests := []struct {
name string
input []string
expected string
}{
{
name: "empty array",
input: []string{},
expected: "",
},
{
name: "single tag",
input: []string{"fiction"},
expected: "fiction",
},
{
name: "multiple tags",
input: []string{"fiction", "adventure"},
expected: "fiction, adventure",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := JoinTags(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
// TestSplitTags tests tag splitting and normalization
func TestSplitTags(t *testing.T) {
tests := []struct {
name string
input string
expected []string
}{
{
name: "empty string",
input: "",
expected: []string{},
},
{
name: "single tag",
input: "fiction",
expected: []string{"Fiction"},
},
{
name: "multiple tags comma separated",
input: "fiction, adventure",
expected: []string{"Fiction", "Adventure"},
},
{
name: "with spaces",
input: "fiction,adventure",
expected: []string{"Fiction", "Adventure"},
},
{
name: "with extra spaces",
input: " fiction , adventure ",
expected: []string{"Fiction", "Adventure"},
},
{
name: "deduplicates",
input: "fiction, FICTION, Fiction",
expected: []string{"Fiction"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := SplitTags(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}