package cv_test import ( "strings" "testing" "github.com/juanatsap/cv-site/internal/models/cv" ) func TestPersonal_Validate(t *testing.T) { tests := []struct { name string personal cv.Personal wantErr bool errField string }{ { name: "Valid - All fields correct", personal: cv.Personal{ Name: "John Doe", Email: "john@example.com", LinkedIn: "https://linkedin.com/in/johndoe", GitHub: "https://github.com/johndoe", Website: "https://johndoe.com", }, wantErr: false, }, { name: "Invalid - Empty name", personal: cv.Personal{ Name: "", Email: "john@example.com", }, wantErr: true, errField: "name", }, { name: "Invalid - Empty email", personal: cv.Personal{ Name: "John Doe", Email: "", }, wantErr: true, errField: "email", }, { name: "Invalid - Bad email format", personal: cv.Personal{ Name: "John Doe", Email: "not-an-email", }, wantErr: true, errField: "email", }, { name: "Invalid - Bad LinkedIn URL", personal: cv.Personal{ Name: "John Doe", Email: "john@example.com", LinkedIn: "not-a-url", }, wantErr: true, errField: "linkedin", }, { name: "Valid - Optional fields empty", personal: cv.Personal{ Name: "John Doe", Email: "john@example.com", }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := tt.personal.Validate() if (err != nil) != tt.wantErr { t.Errorf("Personal.Validate() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantErr && err != nil { errMsg := err.Error() if !strings.Contains(errMsg, tt.errField) { t.Errorf("Error should mention field '%s', got: %s", tt.errField, errMsg) } } }) } } func TestExperience_Validate(t *testing.T) { tests := []struct { name string experience cv.Experience wantErr bool errField string }{ { name: "Valid - Current position", experience: cv.Experience{ Position: "Software Engineer", Company: "Tech Corp", StartDate: "2020-01", Current: true, }, wantErr: false, }, { name: "Valid - Past position with end date", experience: cv.Experience{ Position: "Software Engineer", Company: "Tech Corp", StartDate: "2020-01", EndDate: "2023-12", Current: false, }, wantErr: false, }, { name: "Invalid - Empty position", experience: cv.Experience{ Position: "", Company: "Tech Corp", StartDate: "2020-01", }, wantErr: true, errField: "position", }, { name: "Invalid - Empty company", experience: cv.Experience{ Position: "Software Engineer", Company: "", StartDate: "2020-01", }, wantErr: true, errField: "company", }, { name: "Invalid - Missing end date for past position", experience: cv.Experience{ Position: "Software Engineer", Company: "Tech Corp", StartDate: "2020-01", Current: false, EndDate: "", }, wantErr: true, errField: "endDate", }, { name: "Invalid - Bad company URL", experience: cv.Experience{ Position: "Software Engineer", Company: "Tech Corp", StartDate: "2020-01", Current: true, CompanyURL: "not-a-url", }, wantErr: true, errField: "companyURL", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := tt.experience.Validate() if (err != nil) != tt.wantErr { t.Errorf("Experience.Validate() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantErr && err != nil { errMsg := err.Error() if !strings.Contains(errMsg, tt.errField) { t.Errorf("Error should mention field '%s', got: %s", tt.errField, errMsg) } } }) } } func TestEducation_Validate(t *testing.T) { tests := []struct { name string education cv.Education wantErr bool errField string }{ { name: "Valid - Complete education", education: cv.Education{ Degree: "Bachelor of Science", Institution: "University of Technology", StartDate: "2015-09", EndDate: "2019-06", }, wantErr: false, }, { name: "Invalid - Empty degree", education: cv.Education{ Degree: "", Institution: "University of Technology", StartDate: "2015-09", EndDate: "2019-06", }, wantErr: true, errField: "degree", }, { name: "Invalid - Empty institution", education: cv.Education{ Degree: "Bachelor of Science", Institution: "", StartDate: "2015-09", EndDate: "2019-06", }, wantErr: true, errField: "institution", }, { name: "Invalid - Missing start date", education: cv.Education{ Degree: "Bachelor of Science", Institution: "University of Technology", StartDate: "", EndDate: "2019-06", }, wantErr: true, errField: "startDate", }, { name: "Invalid - Missing end date", education: cv.Education{ Degree: "Bachelor of Science", Institution: "University of Technology", StartDate: "2015-09", EndDate: "", }, wantErr: true, errField: "endDate", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := tt.education.Validate() if (err != nil) != tt.wantErr { t.Errorf("Education.Validate() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantErr && err != nil { errMsg := err.Error() if !strings.Contains(errMsg, tt.errField) { t.Errorf("Error should mention field '%s', got: %s", tt.errField, errMsg) } } }) } } func TestSkills_Validate(t *testing.T) { tests := []struct { name string skills cv.Skills wantErr bool errMsg string }{ { name: "Valid - Complete skills", skills: cv.Skills{ Technical: []cv.SkillCategory{ { Category: "Backend", Proficiency: 4, Items: []string{"Go", "Python"}, Sidebar: "left", }, }, SoftSkills: []string{"Communication", "Leadership"}, }, wantErr: false, }, { name: "Invalid - Empty category name", skills: cv.Skills{ Technical: []cv.SkillCategory{ { Category: "", Proficiency: 4, Items: []string{"Go"}, }, }, }, wantErr: true, errMsg: "category name is required", }, { name: "Invalid - Proficiency too low", skills: cv.Skills{ Technical: []cv.SkillCategory{ { Category: "Backend", Proficiency: 0, Items: []string{"Go"}, }, }, }, wantErr: true, errMsg: "proficiency must be between 1 and 10", }, { name: "Invalid - Proficiency too high", skills: cv.Skills{ Technical: []cv.SkillCategory{ { Category: "Backend", Proficiency: 11, Items: []string{"Go"}, }, }, }, wantErr: true, errMsg: "proficiency must be between 1 and 10", }, { name: "Invalid - No skill items", skills: cv.Skills{ Technical: []cv.SkillCategory{ { Category: "Backend", Proficiency: 4, Items: []string{}, }, }, }, wantErr: true, errMsg: "at least one skill item is required", }, { name: "Invalid - Invalid sidebar value", skills: cv.Skills{ Technical: []cv.SkillCategory{ { Category: "Backend", Proficiency: 4, Items: []string{"Go"}, Sidebar: "middle", }, }, }, wantErr: true, errMsg: "sidebar must be", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := tt.skills.Validate() if (err != nil) != tt.wantErr { t.Errorf("Skills.Validate() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantErr && err != nil { errMsg := err.Error() if !strings.Contains(errMsg, tt.errMsg) { t.Errorf("Error should contain '%s', got: %s", tt.errMsg, errMsg) } } }) } } func TestLanguage_Validate(t *testing.T) { tests := []struct { name string language cv.Language wantErr bool errField string }{ { name: "Valid - Complete language", language: cv.Language{ Language: "English", Proficiency: "Native", Level: 5, }, wantErr: false, }, { name: "Invalid - Empty language name", language: cv.Language{ Language: "", Proficiency: "Native", Level: 5, }, wantErr: true, errField: "language", }, { name: "Invalid - Empty proficiency", language: cv.Language{ Language: "English", Proficiency: "", Level: 5, }, wantErr: true, errField: "proficiency", }, { name: "Invalid - Level too low", language: cv.Language{ Language: "English", Proficiency: "Beginner", Level: 0, }, wantErr: true, errField: "level", }, { name: "Invalid - Level too high", language: cv.Language{ Language: "English", Proficiency: "Native", Level: 6, }, wantErr: true, errField: "level", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := tt.language.Validate() if (err != nil) != tt.wantErr { t.Errorf("Language.Validate() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantErr && err != nil { errMsg := err.Error() if !strings.Contains(errMsg, tt.errField) { t.Errorf("Error should mention field '%s', got: %s", tt.errField, errMsg) } } }) } } func TestProject_Validate(t *testing.T) { tests := []struct { name string project cv.Project wantErr bool errMsg string }{ { name: "Valid - Complete project with URL", project: cv.Project{ Title: "My Project", URL: "https://project.com", GitRepoUrl: "https://github.com/user/project", Technologies: []string{"Go", "React"}, }, wantErr: false, }, { name: "Valid - Project with local git path", project: cv.Project{ Title: "Local Project", URL: "https://project.com", GitRepoUrl: "/Users/user/projects/myproject", Technologies: []string{"Go"}, }, wantErr: false, }, { name: "Invalid - Empty title", project: cv.Project{ Title: "", URL: "https://project.com", }, wantErr: true, errMsg: "title is required", }, { name: "Invalid - Bad project URL", project: cv.Project{ Title: "My Project", URL: "not-a-url", }, wantErr: true, errMsg: "url", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := tt.project.Validate() if (err != nil) != tt.wantErr { t.Errorf("Project.Validate() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantErr && err != nil { errMsg := err.Error() if !strings.Contains(errMsg, tt.errMsg) { t.Errorf("Error should contain '%s', got: %s", tt.errMsg, errMsg) } } }) } } func TestMeta_Validate(t *testing.T) { tests := []struct { name string meta cv.Meta wantErr bool errField string }{ { name: "Valid - Complete meta", meta: cv.Meta{ Version: "1.0", Language: "en", LastUpdated: "2024-01-01", }, wantErr: false, }, { name: "Invalid - Empty version", meta: cv.Meta{ Version: "", Language: "en", }, wantErr: true, errField: "version", }, { name: "Invalid - Empty language", meta: cv.Meta{ Version: "1.0", Language: "", }, wantErr: true, errField: "language", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := tt.meta.Validate() if (err != nil) != tt.wantErr { t.Errorf("Meta.Validate() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantErr && err != nil { errMsg := err.Error() if !strings.Contains(errMsg, tt.errField) { t.Errorf("Error should mention field '%s', got: %s", tt.errField, errMsg) } } }) } } func TestCV_Validate(t *testing.T) { t.Run("Valid - Complete CV", func(t *testing.T) { validCV := &cv.CV{ Personal: cv.Personal{ Name: "John Doe", Email: "john@example.com", }, Skills: cv.Skills{ Technical: []cv.SkillCategory{ { Category: "Backend", Proficiency: 4, Items: []string{"Go"}, }, }, }, Meta: cv.Meta{ Version: "1.0", Language: "en", }, } err := validCV.Validate() if err != nil { t.Errorf("CV.Validate() should not error on valid CV, got: %v", err) } }) t.Run("Invalid - Multiple validation errors", func(t *testing.T) { invalidCV := &cv.CV{ Personal: cv.Personal{ Name: "", // Invalid Email: "not-an-email", // Invalid }, Experience: []cv.Experience{ { Position: "", // Invalid Company: "Tech Corp", StartDate: "2020-01", }, }, Skills: cv.Skills{ Technical: []cv.SkillCategory{ { Category: "Backend", Proficiency: 11, // Invalid Items: []string{"Go"}, }, }, }, Meta: cv.Meta{ Version: "", // Invalid Language: "en", }, } err := invalidCV.Validate() if err == nil { t.Error("CV.Validate() should error on invalid CV") return } // Check that multiple errors are reported errMsg := err.Error() if !strings.Contains(errMsg, "name") { t.Error("Error should mention 'name' field") } if !strings.Contains(errMsg, "email") { t.Error("Error should mention 'email' field") } if !strings.Contains(errMsg, "position") { t.Error("Error should mention 'position' field") } if !strings.Contains(errMsg, "proficiency") { t.Error("Error should mention 'proficiency' field") } if !strings.Contains(errMsg, "version") { t.Error("Error should mention 'version' field") } }) }