feat: CI/CD pipeline + Learning/Medicine/Travel services
- Add Gitea Actions workflow for automated build & deploy - Add K8s manifests: webui, travel-svc, medicine-svc, sandbox-svc - Update kustomization for localhost:5000 registry - Add ingress for gooseek.ru and api.gooseek.ru - Learning cabinet with onboarding, courses, sandbox integration - Medicine service with symptom analysis and doctor matching - Travel service with itinerary planning - Server setup scripts (NVIDIA/CUDA, K3s, Gitea runner) Made-with: Cursor
This commit is contained in:
79
backend/internal/learning/course_autogen_test.go
Normal file
79
backend/internal/learning/course_autogen_test.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package learning
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gooseek/backend/internal/llm"
|
||||
)
|
||||
|
||||
func TestSanitizeSlug(t *testing.T) {
|
||||
got := sanitizeSlug(" Go Backend: Production _ Course ")
|
||||
if got != "go-backend-production-course" {
|
||||
t.Fatalf("sanitizeSlug result mismatch: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeDifficulty(t *testing.T) {
|
||||
if normalizeDifficulty("advanced") != "advanced" {
|
||||
t.Fatalf("expected advanced difficulty")
|
||||
}
|
||||
if normalizeDifficulty("unknown") != "intermediate" {
|
||||
t.Fatalf("expected fallback to intermediate")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateCourseArtifacts(t *testing.T) {
|
||||
outline := json.RawMessage(`{
|
||||
"modules": [
|
||||
{"index":0,"title":"m1","description":"d","skills":["a"],"estimated_hours":4,"practice_focus":"p"},
|
||||
{"index":1,"title":"m2","description":"d","skills":["a"],"estimated_hours":4,"practice_focus":"p"},
|
||||
{"index":2,"title":"m3","description":"d","skills":["a"],"estimated_hours":4,"practice_focus":"p"},
|
||||
{"index":3,"title":"m4","description":"d","skills":["a"],"estimated_hours":4,"practice_focus":"p"},
|
||||
{"index":4,"title":"m5","description":"d","skills":["a"],"estimated_hours":4,"practice_focus":"p"},
|
||||
{"index":5,"title":"m6","description":"d","skills":["a"],"estimated_hours":4,"practice_focus":"p"},
|
||||
{"index":6,"title":"m7","description":"d","skills":["a"],"estimated_hours":4,"practice_focus":"p"},
|
||||
{"index":7,"title":"m8","description":"d","skills":["a"],"estimated_hours":4,"practice_focus":"p"}
|
||||
]
|
||||
}`)
|
||||
landing := json.RawMessage(`{
|
||||
"hero_title":"Hero",
|
||||
"hero_subtitle":"Subtitle",
|
||||
"benefits":["b1","b2","b3"],
|
||||
"outcomes":["o1","o2"],
|
||||
"salary_range":"200k",
|
||||
"faq":[{"question":"q","answer":"a"}]
|
||||
}`)
|
||||
|
||||
err := validateCourseArtifacts("Course", "Это достаточно длинное описание для валидации курса в тесте.", outline, landing)
|
||||
if err != nil {
|
||||
t.Fatalf("validateCourseArtifacts unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateTextWithRetry(t *testing.T) {
|
||||
attempt := 0
|
||||
client := &mockLLMClient{
|
||||
generateFunc: func(ctx context.Context, req llm.StreamRequest) (string, error) {
|
||||
attempt++
|
||||
if attempt < 3 {
|
||||
return "", errors.New("temporary")
|
||||
}
|
||||
return "ok", nil
|
||||
},
|
||||
}
|
||||
|
||||
got, err := generateTextWithRetry(context.Background(), client, llm.StreamRequest{}, 3, time.Millisecond)
|
||||
if err != nil {
|
||||
t.Fatalf("generateTextWithRetry error: %v", err)
|
||||
}
|
||||
if got != "ok" {
|
||||
t.Fatalf("unexpected result: %q", got)
|
||||
}
|
||||
if attempt != 3 {
|
||||
t.Fatalf("expected 3 attempts, got %d", attempt)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user