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:
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user