package handlers import ( "bytes" "context" "encoding/json" "net/http" "net/http/httptest" "testing" "time" "github.com/gin-gonic/gin" "gitea.d3m0k1d.ru/d3m0k1d/rostpoliplast/backend/internal/storage" ) type testRepo struct { baleTypes []storage.BaleType bales []storage.Bale stages []storage.LineStage readings []storage.SensorReading events []storage.ProductionEvent stats *storage.LineStats } func (r *testRepo) GetBales(ctx context.Context) ([]storage.Bale, error) { return r.bales, nil } func (r *testRepo) GetBaleByID(ctx context.Context, id string) (*storage.Bale, error) { for _, b := range r.bales { if b.ID == 1 { return &b, nil } } return nil, nil } func (r *testRepo) CreateBale(ctx context.Context, input storage.Bale) (*storage.Bale, error) { b := storage.Bale{ID: 1, TypeID: input.TypeID, Timestamp: time.Now()} r.bales = append(r.bales, b) return &b, nil } func (r *testRepo) UpdateBale(ctx context.Context, id string, input storage.Bale) (*storage.Bale, error) { return &input, nil } func (r *testRepo) DeleteBale(ctx context.Context, id string) error { return nil } func (r *testRepo) GetBaleTypeByType(ctx context.Context, typeName string) (*storage.BaleType, error) { for _, bt := range r.baleTypes { if bt.Type == typeName { return &bt, nil } } return nil, nil } func (r *testRepo) GetBaleTypes(ctx context.Context) ([]storage.BaleType, error) { return r.baleTypes, nil } func (r *testRepo) GetBaleTypeByID(ctx context.Context, id int) (*storage.BaleType, error) { for _, bt := range r.baleTypes { if bt.ID == id { return &bt, nil } } return nil, nil } func (r *testRepo) CreateBaleType(ctx context.Context, input storage.BaleType) (*storage.BaleType, error) { bt := storage.BaleType{ID: 1, Type: input.Type, Weight: input.Weight} r.baleTypes = append(r.baleTypes, bt) return &bt, nil } func (r *testRepo) UpdateBaleType(ctx context.Context, id int, input storage.BaleType) (*storage.BaleType, error) { return &input, nil } func (r *testRepo) DeleteBaleType(ctx context.Context, id int) error { return nil } func (r *testRepo) GetLineStages(ctx context.Context) ([]storage.LineStage, error) { return r.stages, nil } func (r *testRepo) GetLineStageByID(ctx context.Context, id int) (*storage.LineStage, error) { for _, s := range r.stages { if s.ID == id { return &s, nil } } return nil, nil } func (r *testRepo) CreateLineStage(ctx context.Context, input storage.LineStage) (*storage.LineStage, error) { s := storage.LineStage{ID: 1, Name: input.Name, Order: input.Order} r.stages = append(r.stages, s) return &s, nil } func (r *testRepo) UpdateLineStage(ctx context.Context, id int, input storage.LineStage) (*storage.LineStage, error) { return &input, nil } func (r *testRepo) DeleteLineStage(ctx context.Context, id int) error { return nil } func (r *testRepo) CreateSensorReading(ctx context.Context, input storage.SensorReading) (*storage.SensorReading, error) { s := storage.SensorReading{ID: 1, StageID: input.StageID, Value: input.Value} r.readings = append(r.readings, s) return &s, nil } func (r *testRepo) GetSensorReadingsByStage(ctx context.Context, stageID int) ([]storage.SensorReading, error) { var result []storage.SensorReading for _, s := range r.readings { if s.StageID == stageID { result = append(result, s) } } return result, nil } func (r *testRepo) CreateProductionEvent(ctx context.Context, input storage.ProductionEvent) (*storage.ProductionEvent, error) { e := storage.ProductionEvent{ID: 1, StageID: input.StageID, EventType: input.EventType} r.events = append(r.events, e) return &e, nil } func (r *testRepo) GetProductionEventsByStage(ctx context.Context, stageID int) ([]storage.ProductionEvent, error) { var result []storage.ProductionEvent for _, e := range r.events { if e.StageID == stageID { result = append(result, e) } } return result, nil } func (r *testRepo) GetLineStats(ctx context.Context) (*storage.LineStats, error) { if r.stats != nil { return r.stats, nil } return &storage.LineStats{TotalItems: 42, RejectedItems: 2, Status: "running"}, nil } func setupRouter(repo storage.RepositoryInterface) *gin.Engine { gin.SetMode(gin.TestMode) r := gin.New() v1 := r.Group("/api/v1") lineH := &LineHandlers{Repo: repo} baleH := &BaleHandlers{Repo: repo, MQ: nil} baleTypeH := &BaleTypeHandlers{Repo: repo} lineH.RegisterRoutes(v1) baleH.RegisterRoutes(v1) baleTypeH.RegisterRoutes(v1) return r } func TestGetBales(t *testing.T) { repo := &testRepo{ bales: []storage.Bale{{ID: 1, TypeID: 1, Type: "test", Timestamp: time.Now()}}, } r := setupRouter(repo) req := httptest.NewRequest("GET", "/api/v1/bales", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Errorf("expected %d, got %d", http.StatusOK, w.Code) } var resp []storage.Bale if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil { t.Fatalf("failed to unmarshal response: %v", err) } if len(resp) != 1 { t.Errorf("expected 1 bale, got %d", len(resp)) } } func TestCreateBale(t *testing.T) { repo := &testRepo{} r := setupRouter(repo) body := bytes.NewBufferString(`{"typeId": 1}`) req := httptest.NewRequest("POST", "/api/v1/bales", body) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusCreated { t.Errorf("expected %d, got %d", http.StatusCreated, w.Code) } var resp storage.Bale if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil { t.Fatalf("failed to unmarshal response: %v", err) } if resp.TypeID != 1 { t.Errorf("expected typeId 1, got %d", resp.TypeID) } } func TestGetBalesWithTypeQuery(t *testing.T) { repo := &testRepo{ baleTypes: []storage.BaleType{{ID: 1, Type: "standard"}}, } r := setupRouter(repo) req := httptest.NewRequest("POST", "/api/v1/bales?type=standard", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusCreated { t.Errorf("expected %d, got %d", http.StatusCreated, w.Code) } } func TestGetBaleTypes(t *testing.T) { repo := &testRepo{ baleTypes: []storage.BaleType{{ID: 1, Type: "PET", Weight: 10.0}}, } r := setupRouter(repo) req := httptest.NewRequest("GET", "/api/v1/bale-types", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Errorf("expected %d, got %d", http.StatusOK, w.Code) } var resp []storage.BaleType if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil { t.Fatalf("failed to unmarshal response: %v", err) } if len(resp) != 1 { t.Errorf("expected 1 baleType, got %d", len(resp)) } if resp[0].Type != "PET" { t.Errorf("expected type 'PET', got '%s'", resp[0].Type) } } func TestCreateBaleType(t *testing.T) { repo := &testRepo{} r := setupRouter(repo) body := bytes.NewBufferString(`{"type": "HDPE", "weight": 15.5, "height": 100, "width": 80, "length": 120}`) req := httptest.NewRequest("POST", "/api/v1/bale-types", body) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusCreated { t.Errorf("expected %d, got %d", http.StatusCreated, w.Code) } } func TestGetLineStages(t *testing.T) { repo := &testRepo{ stages: []storage.LineStage{ {ID: 1, Name: "Разгрузка", Order: 1, Equipment: "Рампа", MQTTTopic: "/sensor/weight"}, {ID: 2, Name: "QR Сортировка", Order: 2, Equipment: "Camera", MQTTTopic: "/qr/result"}, }, } r := setupRouter(repo) req := httptest.NewRequest("GET", "/api/v1/line/stages", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Errorf("expected %d, got %d", http.StatusOK, w.Code) } var resp []storage.LineStage if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil { t.Fatalf("failed to unmarshal response: %v", err) } if len(resp) != 2 { t.Errorf("expected 2 stages, got %d", len(resp)) } } func TestCreateLineStage(t *testing.T) { repo := &testRepo{} r := setupRouter(repo) body := bytes.NewBufferString(`{"name": "Тестовый этап", "description": "Описание", "order": 99, "equipment": "Оборудование", "mqtt_topic": "/test/topic"}`) req := httptest.NewRequest("POST", "/api/v1/line/stages", body) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusCreated { t.Errorf("expected %d, got %d", http.StatusCreated, w.Code) } } func TestCreateSensorReading(t *testing.T) { repo := &testRepo{} r := setupRouter(repo) body := bytes.NewBufferString(`{"stage_id": 1, "sensor": "vibration", "value": 42.5, "unit": "mm/s"}`) req := httptest.NewRequest("POST", "/api/v1/sensors/readings", body) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusCreated { t.Errorf("expected %d, got %d", http.StatusCreated, w.Code) } } func TestGetSensorReadingsByStage(t *testing.T) { repo := &testRepo{ readings: []storage.SensorReading{ {ID: 1, StageID: 1, Sensor: "vibration", Value: 42.5, Unit: "mm/s", Timestamp: time.Now()}, }, } r := setupRouter(repo) req := httptest.NewRequest("GET", "/api/v1/sensors/readings/stage/1", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Errorf("expected %d, got %d", http.StatusOK, w.Code) } } func TestCreateProductionEvent(t *testing.T) { repo := &testRepo{} r := setupRouter(repo) body := bytes.NewBufferString(`{"stage_id": 1, "event_type": "item_processed", "data": "success"}`) req := httptest.NewRequest("POST", "/api/v1/events", body) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusCreated { t.Errorf("expected %d, got %d", http.StatusCreated, w.Code) t.Logf("Response: %s", w.Body.String()) } } func TestGetProductionEventsByStage(t *testing.T) { repo := &testRepo{ events: []storage.ProductionEvent{ {ID: 1, StageID: 1, EventType: "item_processed", Data: "success", Timestamp: time.Now()}, }, } r := setupRouter(repo) req := httptest.NewRequest("GET", "/api/v1/events/stage/1", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Errorf("expected %d, got %d", http.StatusOK, w.Code) } } func TestGetLineStats(t *testing.T) { repo := &testRepo{ stats: &storage.LineStats{ TotalItems: 100, RejectedItems: 5, AvgSpeed: 1.5, CurrentStage: "Экструзия", Status: "running", }, } r := setupRouter(repo) req := httptest.NewRequest("GET", "/api/v1/line/stats", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Errorf("expected %d, got %d", http.StatusOK, w.Code) } var resp storage.LineStats if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil { t.Fatalf("failed to unmarshal response: %v", err) } if resp.TotalItems != 100 { t.Errorf("expected totalItems 100, got %d", resp.TotalItems) } if resp.RejectedItems != 5 { t.Errorf("expected rejectedItems 5, got %d", resp.RejectedItems) } if resp.Status != "running" { t.Errorf("expected status 'running', got '%s'", resp.Status) } } func TestUpdateBaleType(t *testing.T) { repo := &testRepo{} r := setupRouter(repo) body := bytes.NewBufferString(`{"type": "UPDATED", "weight": 20.0}`) req := httptest.NewRequest("PUT", "/api/v1/bale-types/1", body) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Errorf("expected %d, got %d", http.StatusOK, w.Code) } } func TestDeleteBaleType(t *testing.T) { repo := &testRepo{} r := setupRouter(repo) req := httptest.NewRequest("DELETE", "/api/v1/bale-types/1", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Errorf("expected %d, got %d", http.StatusOK, w.Code) } } func TestInvalidIDReturns400(t *testing.T) { repo := &testRepo{} r := setupRouter(repo) req := httptest.NewRequest("GET", "/api/v1/line/stages/abc", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusBadRequest { t.Errorf("expected %d, got %d", http.StatusBadRequest, w.Code) } } func TestHealthEndpoint(t *testing.T) { gin.SetMode(gin.TestMode) r := gin.New() r.GET("/health", func(c *gin.Context) { c.JSON(200, gin.H{"status": "ok"}) }) req := httptest.NewRequest("GET", "/health", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Errorf("expected %d, got %d", http.StatusOK, w.Code) } }