refactor: Extract shared utilities and add validation layer
Part 1: Shared Utilities - Create internal/fileutil package with FindDataFile() and LoadJSON() - Create internal/lang package with language constants and validation - Eliminate 46 lines of code duplication between cv/loader.go and ui/loader.go - Simplify cv/loader.go from 69 to 36 lines (-48%) - Simplify ui/loader.go from 56 to 24 lines (-57%) Part 2: Validation Layer - Add comprehensive validation in internal/models/cv/validation.go - Validate Personal (name, email format, URLs) - Validate Experience (required fields, dates) - Validate Education (required fields) - Validate Skills (proficiency ranges 1-5, categories) - Validate Languages (proficiency levels 1-5) - Validate Projects (title, URLs) - Validate Meta (version, language) - Integrate validation into LoadCV() - automatic on load - Create ValidationError and ValidationErrors types for clear error reporting - Report all validation errors at once (better UX) Testing: - Add comprehensive tests for fileutil package (FindDataFile, LoadJSON) - Add tests for lang package (IsValid, Validate, All) - Add 280+ validation test cases covering edge cases - All tests pass with real CV data (cv-en.json, cv-es.json) - Fixed validation to allow both URLs and local paths for gitRepoUrl Documentation: - Create _go-learning/refactorings/002-shared-utilities-validation.md - Document architecture, benefits, testing, and interview talking points - Explain WHY decisions were made (DRY, type safety, data integrity) Benefits: - DRY: Single source of truth for utilities - Type safety: Language constants instead of magic strings - Data integrity: Validation catches errors at load time - Better errors: Clear messages showing all issues at once - Maintainability: Centralized utilities easier to update
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
package lang_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/juanatsap/cv-site/internal/lang"
|
||||
)
|
||||
|
||||
func TestIsValid(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
language string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "Valid - English",
|
||||
language: "en",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Valid - Spanish",
|
||||
language: "es",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid - French",
|
||||
language: "fr",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid - Empty",
|
||||
language: "",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid - Uppercase",
|
||||
language: "EN",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := lang.IsValid(tt.language); got != tt.want {
|
||||
t.Errorf("IsValid() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
language string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Valid - English",
|
||||
language: "en",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Valid - Spanish",
|
||||
language: "es",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid - French",
|
||||
language: "fr",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid - Empty",
|
||||
language: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := lang.Validate(tt.language)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if err != nil && tt.wantErr {
|
||||
// Check that error message includes supported languages
|
||||
errMsg := err.Error()
|
||||
if errMsg == "" {
|
||||
t.Error("Validate() error message is empty")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAll(t *testing.T) {
|
||||
all := lang.All()
|
||||
|
||||
if len(all) != 2 {
|
||||
t.Errorf("All() returned %d languages, want 2", len(all))
|
||||
}
|
||||
|
||||
// Check that it contains en and es
|
||||
hasEN := false
|
||||
hasES := false
|
||||
for _, l := range all {
|
||||
if l == "en" {
|
||||
hasEN = true
|
||||
}
|
||||
if l == "es" {
|
||||
hasES = true
|
||||
}
|
||||
}
|
||||
|
||||
if !hasEN {
|
||||
t.Error("All() missing 'en'")
|
||||
}
|
||||
if !hasES {
|
||||
t.Error("All() missing 'es'")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user