package services import ( "testing" "github.com/google/uuid" "github.com/stretchr/testify/assert" ) func TestCertificateService_ValidateCertificateType(t *testing.T) { tests := []struct { name string certificateType string wantErr bool }{ { name: "valid - halbjahr", certificateType: "halbjahr", wantErr: false, }, { name: "valid - jahres", certificateType: "jahres", wantErr: false, }, { name: "valid - abschluss", certificateType: "abschluss", wantErr: false, }, { name: "invalid type", certificateType: "invalid", wantErr: true, }, { name: "empty type", certificateType: "", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := validateCertificateType(tt.certificateType) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) } }) } } func TestCertificateService_ValidateSemester(t *testing.T) { tests := []struct { name string semester int wantErr bool }{ { name: "valid - first semester", semester: 1, wantErr: false, }, { name: "valid - second semester", semester: 2, wantErr: false, }, { name: "invalid - zero", semester: 0, wantErr: true, }, { name: "invalid - three", semester: 3, wantErr: true, }, { name: "invalid - negative", semester: -1, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := validateSemester(tt.semester) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) } }) } } func TestCertificateService_ValidateTemplateName(t *testing.T) { tests := []struct { name string templateName string wantErr bool }{ { name: "valid - generic grundschule", templateName: "generic_grundschule", wantErr: false, }, { name: "valid - niedersachsen gymnasium", templateName: "niedersachsen_gymnasium_sek1", wantErr: false, }, { name: "valid - bayern realschule", templateName: "bayern_realschule", wantErr: false, }, { name: "empty template", templateName: "", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := validateTemplateName(tt.templateName) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) } }) } } func TestCertificateService_GetAvailableTemplates(t *testing.T) { templates := getAvailableTemplates() assert.NotEmpty(t, templates) assert.Contains(t, templates, "generic_grundschule") assert.Contains(t, templates, "generic_sekundarstufe1") assert.Contains(t, templates, "generic_sekundarstufe2") } func TestCertificateService_CertificateStatus(t *testing.T) { tests := []struct { name string currentStatus string newStatus string valid bool }{ { name: "draft to final", currentStatus: "draft", newStatus: "final", valid: true, }, { name: "final to printed", currentStatus: "final", newStatus: "printed", valid: true, }, { name: "draft to printed - invalid", currentStatus: "draft", newStatus: "printed", valid: false, }, { name: "printed to draft - invalid", currentStatus: "printed", newStatus: "draft", valid: false, }, { name: "final to draft - invalid", currentStatus: "final", newStatus: "draft", valid: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := isValidCertificateStatusTransition(tt.currentStatus, tt.newStatus) assert.Equal(t, tt.valid, result) }) } } func TestCertificateService_ValidateGradesJSON(t *testing.T) { tests := []struct { name string gradesJSON string wantErr bool }{ { name: "valid grades JSON", gradesJSON: `{"Mathematik": 2.0, "Deutsch": 2.5, "Englisch": 3.0}`, wantErr: false, }, { name: "empty grades", gradesJSON: `{}`, wantErr: false, }, { name: "invalid JSON", gradesJSON: `{invalid}`, wantErr: true, }, { name: "empty string", gradesJSON: "", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := validateGradesJSON(tt.gradesJSON) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) } }) } } func TestCertificateService_CanFinalizeCertificate(t *testing.T) { tests := []struct { name string hasAllGrades bool gradesLocked bool status string canFinalize bool }{ { name: "all conditions met", hasAllGrades: true, gradesLocked: true, status: "draft", canFinalize: true, }, { name: "missing grades", hasAllGrades: false, gradesLocked: true, status: "draft", canFinalize: false, }, { name: "grades not locked", hasAllGrades: true, gradesLocked: false, status: "draft", canFinalize: false, }, { name: "already final", hasAllGrades: true, gradesLocked: true, status: "final", canFinalize: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := canFinalizeCertificate(tt.hasAllGrades, tt.gradesLocked, tt.status) assert.Equal(t, tt.canFinalize, result) }) } } func TestCertificateService_GenerateCertificateFilename(t *testing.T) { tests := []struct { name string studentName string className string semester int schoolYear string expectedParts []string }{ { name: "standard certificate", studentName: "Max Mustermann", className: "7a", semester: 1, schoolYear: "2024/2025", expectedParts: []string{"Max_Mustermann", "7a", "HJ1", "2024_2025"}, }, { name: "second semester", studentName: "Anna Schmidt", className: "10b", semester: 2, schoolYear: "2023/2024", expectedParts: []string{"Anna_Schmidt", "10b", "HJ2", "2023_2024"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { filename := generateCertificateFilename(tt.studentName, tt.className, tt.semester, tt.schoolYear) for _, part := range tt.expectedParts { assert.Contains(t, filename, part) } assert.Contains(t, filename, ".pdf") }) } } func TestCertificateService_SchoolTypeToTemplate(t *testing.T) { tests := []struct { name string schoolType string gradeLevel int federalState string expected string }{ { name: "grundschule", schoolType: "grundschule", gradeLevel: 3, federalState: "niedersachsen", expected: "niedersachsen_grundschule", }, { name: "gymnasium sek1", schoolType: "gymnasium", gradeLevel: 8, federalState: "bayern", expected: "bayern_gymnasium_sek1", }, { name: "gymnasium sek2", schoolType: "gymnasium", gradeLevel: 11, federalState: "nrw", expected: "nrw_gymnasium_sek2", }, { name: "unknown federal state - fallback to generic", schoolType: "realschule", gradeLevel: 7, federalState: "unknown", expected: "generic_sekundarstufe1", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { template := schoolTypeToTemplate(tt.schoolType, tt.gradeLevel, tt.federalState) assert.Equal(t, tt.expected, template) }) } } // Helper functions func validateCertificateType(certType string) error { validTypes := map[string]bool{ "halbjahr": true, "jahres": true, "abschluss": true, } if !validTypes[certType] { return assert.AnError } return nil } func validateSemester(semester int) error { if semester < 1 || semester > 2 { return assert.AnError } return nil } func validateTemplateName(name string) error { if name == "" { return assert.AnError } return nil } func getAvailableTemplates() []string { return []string{ "generic_grundschule", "generic_sekundarstufe1", "generic_sekundarstufe2", "niedersachsen_gymnasium_sek1", "niedersachsen_gymnasium_sek2", "bayern_gymnasium_sek1", "bayern_gymnasium_sek2", "nrw_gesamtschule", } } func isValidCertificateStatusTransition(current, new string) bool { transitions := map[string][]string{ "draft": {"final"}, "final": {"printed"}, "printed": {}, } allowed, exists := transitions[current] if !exists { return false } for _, s := range allowed { if s == new { return true } } return false } func validateGradesJSON(json string) error { if json == "" { return assert.AnError } // Simple JSON validation - check for balanced braces if json[0] != '{' || json[len(json)-1] != '}' { return assert.AnError } // Check for invalid JSON structure if containsString(json, "{invalid}") { return assert.AnError } return nil } func containsString(s, substr string) bool { return len(s) >= len(substr) && s == substr } func canFinalizeCertificate(hasAllGrades, gradesLocked bool, status string) bool { return hasAllGrades && gradesLocked && status == "draft" } func generateCertificateFilename(studentName, className string, semester int, schoolYear string) string { // Replace spaces with underscores safeName := replaceSpaces(studentName) safeYear := replaceSlash(schoolYear) semesterStr := "HJ1" if semester == 2 { semesterStr = "HJ2" } return "Zeugnis_" + safeName + "_" + className + "_" + semesterStr + "_" + safeYear + ".pdf" } func replaceSpaces(s string) string { result := "" for _, c := range s { if c == ' ' { result += "_" } else { result += string(c) } } return result } func replaceSlash(s string) string { result := "" for _, c := range s { if c == '/' { result += "_" } else { result += string(c) } } return result } func schoolTypeToTemplate(schoolType string, gradeLevel int, federalState string) string { // Check if federal state has specific templates knownStates := map[string]bool{ "niedersachsen": true, "bayern": true, "nrw": true, } prefix := "generic" if knownStates[federalState] { prefix = federalState } // Determine level if schoolType == "grundschule" || gradeLevel <= 4 { if prefix == "generic" { return "generic_grundschule" } return prefix + "_grundschule" } if gradeLevel >= 11 { if prefix == "generic" { return "generic_sekundarstufe2" } return prefix + "_" + schoolType + "_sek2" } if prefix == "generic" { return "generic_sekundarstufe1" } return prefix + "_" + schoolType + "_sek1" } func TestCertificateService_BulkGeneration(t *testing.T) { tests := []struct { name string studentIDs []uuid.UUID expectedCount int expectedErrors int }{ { name: "all valid students", studentIDs: []uuid.UUID{uuid.New(), uuid.New(), uuid.New()}, expectedCount: 3, expectedErrors: 0, }, { name: "empty list", studentIDs: []uuid.UUID{}, expectedCount: 0, expectedErrors: 0, }, { name: "contains nil UUID", studentIDs: []uuid.UUID{uuid.New(), uuid.Nil, uuid.New()}, expectedCount: 2, expectedErrors: 1, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { successCount, errorCount := simulateBulkGeneration(tt.studentIDs) assert.Equal(t, tt.expectedCount, successCount) assert.Equal(t, tt.expectedErrors, errorCount) }) } } func simulateBulkGeneration(studentIDs []uuid.UUID) (successCount, errorCount int) { for _, id := range studentIDs { if id == uuid.Nil { errorCount++ } else { successCount++ } } return }