Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website, Klausur-Service, School-Service, Voice-Service, Geo-Service, BreakPilot Drive, Agent-Core Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
564 lines
12 KiB
Go
564 lines
12 KiB
Go
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
|
|
}
|