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:
66
backend/internal/learning/plan_builder_test.go
Normal file
66
backend/internal/learning/plan_builder_test.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package learning
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/gooseek/backend/internal/db"
|
||||
"github.com/gooseek/backend/internal/llm"
|
||||
)
|
||||
|
||||
func TestBuildPersonalPlanAppliesDefaults(t *testing.T) {
|
||||
course := &db.LearningCourse{
|
||||
Title: "Go Backend",
|
||||
ShortDescription: "Курс по backend-разработке на Go",
|
||||
Difficulty: "intermediate",
|
||||
DurationHours: 24,
|
||||
BaseOutline: json.RawMessage(`{"modules":[{"index":0,"title":"base","description":"base","skills":["go"],"estimated_hours":4,"practice_focus":"api"}]}`),
|
||||
}
|
||||
|
||||
client := &mockLLMClient{
|
||||
generateFunc: func(ctx context.Context, req llm.StreamRequest) (string, error) {
|
||||
return `{
|
||||
"modules": [
|
||||
{"index": 999, "title": "API design", "description": "design REST", "skills": ["http"], "estimated_hours": 6, "practice_focus": "build handlers", "task_count": 0},
|
||||
{"index": 999, "title": "DB layer", "description": "storage", "skills": ["sql"], "estimated_hours": 8, "practice_focus": "repository pattern", "task_count": 3}
|
||||
],
|
||||
"total_hours": 0,
|
||||
"difficulty_adjusted": "intermediate",
|
||||
"personalization_notes": "adapted"
|
||||
}`, nil
|
||||
},
|
||||
}
|
||||
|
||||
planJSON, err := BuildPersonalPlan(context.Background(), client, course, `{"level":"junior"}`)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildPersonalPlan error: %v", err)
|
||||
}
|
||||
|
||||
var plan PersonalPlan
|
||||
if err := json.Unmarshal(planJSON, &plan); err != nil {
|
||||
t.Fatalf("unmarshal plan: %v", err)
|
||||
}
|
||||
|
||||
if len(plan.Modules) != 2 {
|
||||
t.Fatalf("expected 2 modules, got %d", len(plan.Modules))
|
||||
}
|
||||
if plan.Modules[0].Index != 0 || plan.Modules[1].Index != 1 {
|
||||
t.Fatalf("module indexes were not normalized: %+v", plan.Modules)
|
||||
}
|
||||
if plan.Modules[0].TaskCount != 2 {
|
||||
t.Fatalf("expected default task_count=2, got %d", plan.Modules[0].TaskCount)
|
||||
}
|
||||
if plan.TotalHours != 14 {
|
||||
t.Fatalf("expected total_hours=14, got %d", plan.TotalHours)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTruncateStr(t *testing.T) {
|
||||
if got := truncateStr("abc", 5); got != "abc" {
|
||||
t.Fatalf("truncateStr should keep short string, got %q", got)
|
||||
}
|
||||
if got := truncateStr("abcdef", 3); got != "abc..." {
|
||||
t.Fatalf("truncateStr should truncate with ellipsis, got %q", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user