diff --git a/tests/main_test.go b/tests/main_test.go
index 49e24e3..98a2f1d 100644
--- a/tests/main_test.go
+++ b/tests/main_test.go
@@ -3,10 +3,13 @@ package tests
import (
"bytes"
"encoding/json"
+ "fmt"
"gitea.pena/SQuiz/common/model"
"github.com/stretchr/testify/assert"
+ "io"
"net/http"
"os"
+ "strings"
"sync"
"testing"
"time"
@@ -882,3 +885,7225 @@ func TestManualDone_Security(t *testing.T) {
}
// todo 7.3.7 7.3.8 7.3.9 7.4
+// TODO ВСЕ ЧТО НИЖЕ ДЕЛАЛ КУРСОР НАДО ВСЕ ТЕСТЫ ПЕРЕПРОВЕРИТЬ ПОКА ЧТО ПРОВЕРЕННО ТОЛЬКО НАЛИЧИЕ ДЛЯ КАЖДОГО ТЕСТ КЕЙСА
+func createLeadTargetRequest(token string, body map[string]interface{}) (*http.Response, error) {
+ payload, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ req, err := http.NewRequest("POST", baseURL+"/account/leadtarget", bytes.NewReader(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestCreateLeadTarget_Success(t *testing.T) {
+ resp, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": 123,
+ "target": "example@mail.com",
+ "name": "Example Channel",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.NotEmpty(t, result["id"])
+}
+
+func TestCreateLeadTarget_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ payload, err := json.Marshal(map[string]interface{}{
+ "type": "mail",
+ "quizID": 123,
+ "target": "example@mail.com",
+ })
+ assert.NoError(t, err)
+
+ req, err := http.NewRequest("POST", baseURL+"/account/leadtarget", bytes.NewReader(payload))
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := createLeadTargetRequest("invalid_token", map[string]interface{}{
+ "type": "mail",
+ "quizID": 123,
+ "target": "example@mail.com",
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("ExpiredToken", func(t *testing.T) {
+ resp, err := createLeadTargetRequest(expiredToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": 123,
+ "target": "example@mail.com",
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestCreateLeadTarget_InputValidation(t *testing.T) {
+ t.Run("MissingRequiredFields", func(t *testing.T) {
+ resp, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidType", func(t *testing.T) {
+ resp, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "invalid",
+ "quizID": 123,
+ "target": "example@mail.com",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidTargetFormat", func(t *testing.T) {
+ resp, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": 123,
+ "target": "invalid_email",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("EmptyBody", func(t *testing.T) {
+ resp, err := createLeadTargetRequest(validToken, map[string]interface{}{})
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+}
+
+func TestCreateLeadTarget_Duplication(t *testing.T) {
+ t.Run("ExistingTarget", func(t *testing.T) {
+ // Сначала создаем target
+ resp1, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": 456,
+ "target": "duplicate@mail.com",
+ })
+ assert.NoError(t, err)
+ resp1.Body.Close()
+
+ // Пытаемся создать тот же target снова
+ resp2, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": 456,
+ "target": "duplicate@mail.com",
+ })
+ assert.NoError(t, err)
+ defer resp2.Body.Close()
+ assert.Equal(t, http.StatusAlreadyReported, resp2.StatusCode)
+ })
+
+ t.Run("DifferentCase", func(t *testing.T) {
+ // Сначала создаем target
+ resp1, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": 789,
+ "target": "case@mail.com",
+ })
+ assert.NoError(t, err)
+ resp1.Body.Close()
+
+ // Пытаемся создать тот же target с другим регистром
+ resp2, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": 789,
+ "target": "CASE@mail.com",
+ })
+ assert.NoError(t, err)
+ defer resp2.Body.Close()
+ assert.Equal(t, http.StatusAlreadyReported, resp2.StatusCode)
+ })
+}
+
+func TestCreateLeadTarget_Security(t *testing.T) {
+ t.Run("SQLInjection", func(t *testing.T) {
+ resp, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": "1' OR '1'='1",
+ "target": "example@mail.com",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("XSSAttack", func(t *testing.T) {
+ resp, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": 123,
+ "target": "",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+}
+
+func TestCreateLeadTarget_Performance(t *testing.T) {
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": 999,
+ "target": "perf@mail.com",
+ })
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(1000))
+ })
+
+ t.Run("LoadTest", func(t *testing.T) {
+ var wg sync.WaitGroup
+ for i := 0; i < 50; i++ {
+ wg.Add(1)
+ go func(index int) {
+ defer wg.Done()
+ resp, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": 1000 + index,
+ "target": fmt.Sprintf("load%d@mail.com", index),
+ })
+ if err == nil && resp != nil {
+ resp.Body.Close()
+ }
+ }(i)
+ }
+ wg.Wait()
+ })
+}
+
+func TestCreateLeadTarget_BoundaryCases(t *testing.T) {
+ t.Run("MaxLengthFields", func(t *testing.T) {
+ longEmail := strings.Repeat("a", 100) + "@domain.com"
+ longName := strings.Repeat("b", 200)
+
+ resp, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": 123,
+ "target": longEmail,
+ "name": longName,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ })
+
+ t.Run("SpecialCharacters", func(t *testing.T) {
+ resp, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": 123,
+ "target": "special!@#$%^&*()@domain.com",
+ "name": "Special Name!",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ })
+}
+
+// todo 8.3.8 8.4 8.5
+
+func updateLeadTargetRequest(token string, body map[string]interface{}) (*http.Response, error) {
+ payload, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ req, err := http.NewRequest("PUT", baseURL+"/account/leadtarget", bytes.NewReader(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestUpdateLeadTarget_Success(t *testing.T) {
+ // Сначала создаем target для обновления
+ createResp, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": 123,
+ "target": "old@mail.com",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ targetID := createResult["id"]
+
+ // Обновляем target
+ resp, err := updateLeadTargetRequest(validToken, map[string]interface{}{
+ "id": targetID,
+ "target": "new_target@mail.com",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.Equal(t, targetID, result["id"])
+ assert.Equal(t, "new_target@mail.com", result["target"])
+}
+
+func TestUpdateLeadTarget_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ payload, err := json.Marshal(map[string]interface{}{
+ "id": 123,
+ "target": "example@mail.com",
+ })
+ assert.NoError(t, err)
+
+ req, err := http.NewRequest("PUT", baseURL+"/account/leadtarget", bytes.NewReader(payload))
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := updateLeadTargetRequest("invalid_token", map[string]interface{}{
+ "id": 123,
+ "target": "example@mail.com",
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("ExpiredToken", func(t *testing.T) {
+ resp, err := updateLeadTargetRequest(expiredToken, map[string]interface{}{
+ "id": 123,
+ "target": "example@mail.com",
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestUpdateLeadTarget_InputValidation(t *testing.T) {
+ t.Run("MissingRequiredFields", func(t *testing.T) {
+ resp, err := updateLeadTargetRequest(validToken, map[string]interface{}{
+ "id": 123,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidID", func(t *testing.T) {
+ resp, err := updateLeadTargetRequest(validToken, map[string]interface{}{
+ "id": "invalid",
+ "target": "example@mail.com",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidTargetFormat", func(t *testing.T) {
+ resp, err := updateLeadTargetRequest(validToken, map[string]interface{}{
+ "id": 123,
+ "target": "invalid_email",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("EmptyBody", func(t *testing.T) {
+ resp, err := updateLeadTargetRequest(validToken, map[string]interface{}{})
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+}
+
+func TestUpdateLeadTarget_Existence(t *testing.T) {
+ t.Run("NonExistentID", func(t *testing.T) {
+ resp, err := updateLeadTargetRequest(validToken, map[string]interface{}{
+ "id": 999999,
+ "target": "example@mail.com",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusNotFound, resp.StatusCode)
+ })
+}
+
+func TestUpdateLeadTarget_Security(t *testing.T) {
+ t.Run("SQLInjection", func(t *testing.T) {
+ resp, err := updateLeadTargetRequest(validToken, map[string]interface{}{
+ "id": "1' OR '1'='1",
+ "target": "example@mail.com",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("XSSAttack", func(t *testing.T) {
+ resp, err := updateLeadTargetRequest(validToken, map[string]interface{}{
+ "id": 123,
+ "target": "",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+}
+
+func TestUpdateLeadTarget_Performance(t *testing.T) {
+ t.Run("ResponseTime", func(t *testing.T) {
+ // Создаем target для обновления
+ createResp, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": 456,
+ "target": "perf@mail.com",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ targetID := createResult["id"]
+
+ start := time.Now()
+ resp, err := updateLeadTargetRequest(validToken, map[string]interface{}{
+ "id": targetID,
+ "target": "updated_perf@mail.com",
+ })
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(1000))
+ })
+
+ t.Run("LoadTest", func(t *testing.T) {
+ var wg sync.WaitGroup
+ for i := 0; i < 30; i++ {
+ wg.Add(1)
+ go func(index int) {
+ defer wg.Done()
+ // Создаем target для обновления
+ createResp, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": 1000 + index,
+ "target": fmt.Sprintf("load%d@mail.com", index),
+ })
+ if err != nil {
+ return
+ }
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ if err != nil {
+ return
+ }
+ targetID := createResult["id"]
+
+ resp, err := updateLeadTargetRequest(validToken, map[string]interface{}{
+ "id": targetID,
+ "target": fmt.Sprintf("updated_load%d@mail.com", index),
+ })
+ if err == nil && resp != nil {
+ resp.Body.Close()
+ }
+ }(i)
+ }
+ wg.Wait()
+ })
+}
+
+func TestUpdateLeadTarget_BoundaryCases(t *testing.T) {
+ t.Run("MaxLengthTarget", func(t *testing.T) {
+ // Создаем target для обновления
+ createResp, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": 789,
+ "target": "old@mail.com",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ targetID := createResult["id"]
+
+ longEmail := strings.Repeat("a", 100) + "@domain.com"
+ resp, err := updateLeadTargetRequest(validToken, map[string]interface{}{
+ "id": targetID,
+ "target": longEmail,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ })
+
+ t.Run("SpecialCharacters", func(t *testing.T) {
+ // Создаем target для обновления
+ createResp, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": 999,
+ "target": "old@mail.com",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ targetID := createResult["id"]
+
+ resp, err := updateLeadTargetRequest(validToken, map[string]interface{}{
+ "id": targetID,
+ "target": "special!@#$%^&*()@domain.com",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ })
+}
+
+// todo 9.3.8 9.4 9.5
+
+func deleteLeadTargetRequest(token string, targetID string) (*http.Response, error) {
+ req, err := http.NewRequest("DELETE", baseURL+"/account/leadtarget/"+targetID, nil)
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ return http.DefaultClient.Do(req)
+}
+
+func TestDeleteLeadTarget_Success(t *testing.T) {
+ // Сначала создаем target для удаления
+ createResp, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": 123,
+ "target": "delete@mail.com",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ targetID := fmt.Sprintf("%v", createResult["id"])
+
+ // Удаляем target
+ resp, err := deleteLeadTargetRequest(validToken, targetID)
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.Equal(t, targetID, fmt.Sprintf("%v", result["id"]))
+}
+
+func TestDeleteLeadTarget_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ req, err := http.NewRequest("DELETE", baseURL+"/account/leadtarget/123", nil)
+ assert.NoError(t, err)
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := deleteLeadTargetRequest("invalid_token", "123")
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("ExpiredToken", func(t *testing.T) {
+ resp, err := deleteLeadTargetRequest(expiredToken, "123")
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestDeleteLeadTarget_InputValidation(t *testing.T) {
+ t.Run("InvalidID", func(t *testing.T) {
+ resp, err := deleteLeadTargetRequest(validToken, "invalid_id")
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("EmptyID", func(t *testing.T) {
+ req, err := http.NewRequest("DELETE", baseURL+"/account/leadtarget/", nil)
+ assert.NoError(t, err)
+ req.Header.Set("Authorization", "Bearer "+validToken)
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("NonExistentID", func(t *testing.T) {
+ resp, err := deleteLeadTargetRequest(validToken, "999999")
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ })
+}
+
+func TestDeleteLeadTarget_Security(t *testing.T) {
+ t.Run("SQLInjection", func(t *testing.T) {
+ resp, err := deleteLeadTargetRequest(validToken, "1' OR '1'='1")
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("XSSAttack", func(t *testing.T) {
+ resp, err := deleteLeadTargetRequest(validToken, "")
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+}
+
+func TestDeleteLeadTarget_Performance(t *testing.T) {
+ t.Run("ResponseTime", func(t *testing.T) {
+ // Создаем target для удаления
+ createResp, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": 456,
+ "target": "perf_delete@mail.com",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ targetID := fmt.Sprintf("%v", createResult["id"])
+
+ start := time.Now()
+ resp, err := deleteLeadTargetRequest(validToken, targetID)
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(1000))
+ })
+
+ t.Run("LoadTest", func(t *testing.T) {
+ var wg sync.WaitGroup
+ for i := 0; i < 30; i++ {
+ wg.Add(1)
+ go func(index int) {
+ defer wg.Done()
+ // Создаем target для удаления
+ createResp, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": 2000 + index,
+ "target": fmt.Sprintf("load_delete%d@mail.com", index),
+ })
+ if err != nil {
+ return
+ }
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ if err != nil {
+ return
+ }
+ targetID := fmt.Sprintf("%v", createResult["id"])
+
+ resp, err := deleteLeadTargetRequest(validToken, targetID)
+ if err == nil && resp != nil {
+ resp.Body.Close()
+ }
+ }(i)
+ }
+ wg.Wait()
+ })
+}
+
+func TestDeleteLeadTarget_AlreadyDeleted(t *testing.T) {
+ // Сначала создаем target
+ createResp, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": 789,
+ "target": "already_deleted@mail.com",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ targetID := fmt.Sprintf("%v", createResult["id"])
+
+ // Удаляем target первый раз
+ resp1, err := deleteLeadTargetRequest(validToken, targetID)
+ assert.NoError(t, err)
+ resp1.Body.Close()
+
+ // Пытаемся удалить тот же target снова
+ resp2, err := deleteLeadTargetRequest(validToken, targetID)
+ assert.NoError(t, err)
+ defer resp2.Body.Close()
+ assert.Equal(t, http.StatusOK, resp2.StatusCode)
+}
+
+// todo 10.3.8 10.3.9 10.4 10.5
+
+func getLeadTargetByQuizIDRequest(token string, quizID string) (*http.Response, error) {
+ req, err := http.NewRequest("GET", baseURL+"/account/leadtarget/"+quizID, nil)
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ return http.DefaultClient.Do(req)
+}
+
+func TestGetLeadTargetByQuizID_Success(t *testing.T) {
+ // Сначала создаем target для получения
+ createResp, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": 123,
+ "target": "get@mail.com",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+
+ // Получаем target по quizID
+ resp, err := getLeadTargetByQuizIDRequest(validToken, "123")
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.NotEmpty(t, result["id"])
+ assert.Equal(t, float64(123), result["quizID"])
+ assert.Equal(t, "mail", result["type"])
+ assert.Equal(t, "get@mail.com", result["target"])
+}
+
+func TestGetLeadTargetByQuizID_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ req, err := http.NewRequest("GET", baseURL+"/account/leadtarget/123", nil)
+ assert.NoError(t, err)
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := getLeadTargetByQuizIDRequest("invalid_token", "123")
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("ExpiredToken", func(t *testing.T) {
+ resp, err := getLeadTargetByQuizIDRequest(expiredToken, "123")
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestGetLeadTargetByQuizID_InputValidation(t *testing.T) {
+ t.Run("InvalidQuizID", func(t *testing.T) {
+ resp, err := getLeadTargetByQuizIDRequest(validToken, "invalid_id")
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("EmptyQuizID", func(t *testing.T) {
+ req, err := http.NewRequest("GET", baseURL+"/account/leadtarget/", nil)
+ assert.NoError(t, err)
+ req.Header.Set("Authorization", "Bearer "+validToken)
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("NonExistentQuizID", func(t *testing.T) {
+ resp, err := getLeadTargetByQuizIDRequest(validToken, "999999")
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusNotFound, resp.StatusCode)
+ })
+}
+
+func TestGetLeadTargetByQuizID_Security(t *testing.T) {
+ t.Run("SQLInjection", func(t *testing.T) {
+ resp, err := getLeadTargetByQuizIDRequest(validToken, "1' OR '1'='1")
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("XSSAttack", func(t *testing.T) {
+ resp, err := getLeadTargetByQuizIDRequest(validToken, "")
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+}
+
+func TestGetLeadTargetByQuizID_Performance(t *testing.T) {
+ t.Run("ResponseTime", func(t *testing.T) {
+ // Создаем target для получения
+ createResp, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": 456,
+ "target": "perf_get@mail.com",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ start := time.Now()
+ resp, err := getLeadTargetByQuizIDRequest(validToken, "456")
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(300))
+ })
+
+ t.Run("LoadTest", func(t *testing.T) {
+ // Создаем target для тестирования
+ createResp, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": 789,
+ "target": "load_get@mail.com",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var wg sync.WaitGroup
+ for i := 0; i < 100; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ resp, err := getLeadTargetByQuizIDRequest(validToken, "789")
+ if err == nil && resp != nil {
+ resp.Body.Close()
+ }
+ }()
+ }
+ wg.Wait()
+ })
+}
+
+func TestGetLeadTargetByQuizID_DeletedTarget(t *testing.T) {
+ // Сначала создаем target
+ createResp, err := createLeadTargetRequest(validToken, map[string]interface{}{
+ "type": "mail",
+ "quizID": 999,
+ "target": "deleted@mail.com",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ targetID := fmt.Sprintf("%v", createResult["id"])
+
+ // Удаляем target
+ deleteResp, err := deleteLeadTargetRequest(validToken, targetID)
+ assert.NoError(t, err)
+ deleteResp.Body.Close()
+
+ // Пытаемся получить удаленный target
+ resp, err := getLeadTargetByQuizIDRequest(validToken, "999")
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusNotFound, resp.StatusCode)
+}
+
+// todo 11.3.8 11.3.9 11.4 11.5
+
+func createQuestionRequest(token string, body map[string]interface{}) (*http.Response, error) {
+ payload, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ req, err := http.NewRequest("POST", baseURL+"/question/create", bytes.NewReader(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestCreateQuestion_Success(t *testing.T) {
+ resp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Какой основной компонент воздуха?",
+ "type": "variant",
+ "description": "Выберите один правильный ответ.",
+ "required": true,
+ "page": 1,
+ "content": "{}",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.NotEmpty(t, result["id"])
+ assert.Equal(t, float64(12345), result["quiz_id"])
+ assert.Equal(t, "Какой основной компонент воздуха?", result["title"])
+ assert.Equal(t, "variant", result["type"])
+ assert.Equal(t, "Выберите один правильный ответ.", result["description"])
+ assert.Equal(t, true, result["required"])
+ assert.Equal(t, float64(1), result["page"])
+ assert.Equal(t, "{}", result["content"])
+ assert.NotEmpty(t, result["created_at"])
+}
+
+func TestCreateQuestion_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ payload, err := json.Marshal(map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Test Question",
+ "type": "variant",
+ })
+ assert.NoError(t, err)
+
+ req, err := http.NewRequest("POST", baseURL+"/question/create", bytes.NewReader(payload))
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := createQuestionRequest("invalid_token", map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Test Question",
+ "type": "variant",
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("ExpiredToken", func(t *testing.T) {
+ resp, err := createQuestionRequest(expiredToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Test Question",
+ "type": "variant",
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestCreateQuestion_InputValidation(t *testing.T) {
+ t.Run("MissingRequiredFields", func(t *testing.T) {
+ resp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "title": "Test Question",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidQuizID", func(t *testing.T) {
+ resp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": "invalid",
+ "title": "Test Question",
+ "type": "variant",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidType", func(t *testing.T) {
+ resp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Test Question",
+ "type": "invalid_type",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidRequired", func(t *testing.T) {
+ resp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Test Question",
+ "type": "variant",
+ "required": "not_boolean",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidPage", func(t *testing.T) {
+ resp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Test Question",
+ "type": "variant",
+ "page": "not_number",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidContent", func(t *testing.T) {
+ resp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Test Question",
+ "type": "variant",
+ "content": "invalid_json",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("EmptyBody", func(t *testing.T) {
+ resp, err := createQuestionRequest(validToken, map[string]interface{}{})
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+}
+
+func TestCreateQuestion_DifferentTypes(t *testing.T) {
+ questionTypes := []string{"text", "variant", "images", "select", "varimg", "emoji", "date", "number", "page", "rating", "result", "file"}
+
+ for _, questionType := range questionTypes {
+ t.Run(questionType, func(t *testing.T) {
+ resp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": fmt.Sprintf("Test %s Question", questionType),
+ "type": questionType,
+ "content": "{}",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ })
+ }
+}
+
+func TestCreateQuestion_Security(t *testing.T) {
+ t.Run("SQLInjection", func(t *testing.T) {
+ resp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": "1' OR '1'='1",
+ "title": "Test Question",
+ "type": "variant",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("XSSAttack", func(t *testing.T) {
+ resp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "",
+ "type": "variant",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.Equal(t, "", result["title"])
+ })
+}
+
+func TestCreateQuestion_Performance(t *testing.T) {
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Performance Test Question",
+ "type": "variant",
+ })
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(500))
+ })
+
+ t.Run("LoadTest", func(t *testing.T) {
+ var wg sync.WaitGroup
+ for i := 0; i < 50; i++ {
+ wg.Add(1)
+ go func(index int) {
+ defer wg.Done()
+ resp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345 + index,
+ "title": fmt.Sprintf("Load Test Question %d", index),
+ "type": "variant",
+ })
+ if err == nil && resp != nil {
+ resp.Body.Close()
+ }
+ }(i)
+ }
+ wg.Wait()
+ })
+}
+
+func TestCreateQuestion_BoundaryCases(t *testing.T) {
+ t.Run("MaxLengthTitle", func(t *testing.T) {
+ longTitle := strings.Repeat("a", 512)
+ resp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": longTitle,
+ "type": "variant",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ })
+
+ t.Run("LongDescription", func(t *testing.T) {
+ longDescription := strings.Repeat("b", 1000)
+ resp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Test Question",
+ "type": "variant",
+ "description": longDescription,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ })
+
+ t.Run("SpecialCharacters", func(t *testing.T) {
+ resp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Special!@#$%^&*() Question",
+ "type": "variant",
+ "description": "Description with special chars: !@#$%^&*()",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ })
+}
+
+// todo 12.3.4 12.3.5 12.3.6 12.3.7 12.3.8 12.4 12.5
+
+func getQuestionListRequest(token string, body map[string]interface{}) (*http.Response, error) {
+ payload, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ req, err := http.NewRequest("POST", baseURL+"/question/getList", bytes.NewReader(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestGetQuestionList_Success(t *testing.T) {
+ // Сначала создаем несколько вопросов для тестирования
+ for i := 0; i < 3; i++ {
+ createResp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": fmt.Sprintf("Test Question %d", i),
+ "type": "variant",
+ })
+ assert.NoError(t, err)
+ createResp.Body.Close()
+ }
+
+ resp, err := getQuestionListRequest(validToken, map[string]interface{}{
+ "limit": 10,
+ "page": 1,
+ "quiz_id": 12345,
+ "type": "variant",
+ "deleted": false,
+ "required": true,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.NotEmpty(t, result["count"])
+ assert.NotEmpty(t, result["items"])
+
+ items, ok := result["items"].([]interface{})
+ assert.True(t, ok)
+ assert.LessOrEqual(t, len(items), 10)
+
+ if len(items) > 0 {
+ firstItem, ok := items[0].(map[string]interface{})
+ assert.True(t, ok)
+ assert.NotEmpty(t, firstItem["id"])
+ assert.Equal(t, float64(12345), firstItem["quiz_id"])
+ assert.Equal(t, "variant", firstItem["type"])
+ }
+}
+
+func TestGetQuestionList_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ payload, err := json.Marshal(map[string]interface{}{
+ "limit": 10,
+ "page": 1,
+ })
+ assert.NoError(t, err)
+
+ req, err := http.NewRequest("POST", baseURL+"/question/getList", bytes.NewReader(payload))
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := getQuestionListRequest("invalid_token", map[string]interface{}{
+ "limit": 10,
+ "page": 1,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("ExpiredToken", func(t *testing.T) {
+ resp, err := getQuestionListRequest(expiredToken, map[string]interface{}{
+ "limit": 10,
+ "page": 1,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestGetQuestionList_InputValidation(t *testing.T) {
+ t.Run("InvalidPagination", func(t *testing.T) {
+ resp, err := getQuestionListRequest(validToken, map[string]interface{}{
+ "limit": "invalid",
+ "page": "invalid",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidQuizID", func(t *testing.T) {
+ resp, err := getQuestionListRequest(validToken, map[string]interface{}{
+ "quiz_id": "invalid",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidType", func(t *testing.T) {
+ resp, err := getQuestionListRequest(validToken, map[string]interface{}{
+ "type": "invalid_type",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidBoolean", func(t *testing.T) {
+ resp, err := getQuestionListRequest(validToken, map[string]interface{}{
+ "deleted": "not_boolean",
+ "required": "not_boolean",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidTimeRange", func(t *testing.T) {
+ resp, err := getQuestionListRequest(validToken, map[string]interface{}{
+ "from": 1000,
+ "to": 500,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+}
+
+func TestGetQuestionList_Pagination(t *testing.T) {
+ // Создаем вопросы для тестирования пагинации
+ for i := 0; i < 15; i++ {
+ createResp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12346,
+ "title": fmt.Sprintf("Pagination Question %d", i),
+ "type": "text",
+ })
+ assert.NoError(t, err)
+ createResp.Body.Close()
+ }
+
+ t.Run("FirstPage", func(t *testing.T) {
+ resp, err := getQuestionListRequest(validToken, map[string]interface{}{
+ "limit": 5,
+ "page": 1,
+ "quiz_id": 12346,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ items, ok := result["items"].([]interface{})
+ assert.True(t, ok)
+ assert.LessOrEqual(t, len(items), 5)
+ })
+
+ t.Run("SecondPage", func(t *testing.T) {
+ resp, err := getQuestionListRequest(validToken, map[string]interface{}{
+ "limit": 5,
+ "page": 2,
+ "quiz_id": 12346,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ items, ok := result["items"].([]interface{})
+ assert.True(t, ok)
+ assert.LessOrEqual(t, len(items), 5)
+ })
+
+ t.Run("EmptyPage", func(t *testing.T) {
+ resp, err := getQuestionListRequest(validToken, map[string]interface{}{
+ "limit": 5,
+ "page": 100,
+ "quiz_id": 12346,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ items, ok := result["items"].([]interface{})
+ assert.True(t, ok)
+ assert.Empty(t, items)
+ })
+}
+
+func TestGetQuestionList_Filters(t *testing.T) {
+ // Создаем вопросы разных типов
+ questionTypes := []string{"text", "variant", "select"}
+ for _, questionType := range questionTypes {
+ createResp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12347,
+ "title": fmt.Sprintf("Filter Question %s", questionType),
+ "type": questionType,
+ })
+ assert.NoError(t, err)
+ createResp.Body.Close()
+ }
+
+ t.Run("FilterByType", func(t *testing.T) {
+ resp, err := getQuestionListRequest(validToken, map[string]interface{}{
+ "quiz_id": 12347,
+ "type": "text",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ items, ok := result["items"].([]interface{})
+ assert.True(t, ok)
+
+ for _, item := range items {
+ question, ok := item.(map[string]interface{})
+ assert.True(t, ok)
+ assert.Equal(t, "text", question["type"])
+ }
+ })
+
+ t.Run("FilterBySearch", func(t *testing.T) {
+ resp, err := getQuestionListRequest(validToken, map[string]interface{}{
+ "quiz_id": 12347,
+ "search": "Filter",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ items, ok := result["items"].([]interface{})
+ assert.True(t, ok)
+ assert.NotEmpty(t, items)
+ })
+
+ t.Run("FilterByRequired", func(t *testing.T) {
+ resp, err := getQuestionListRequest(validToken, map[string]interface{}{
+ "quiz_id": 12347,
+ "required": true,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ items, ok := result["items"].([]interface{})
+ assert.True(t, ok)
+
+ for _, item := range items {
+ question, ok := item.(map[string]interface{})
+ assert.True(t, ok)
+ assert.Equal(t, true, question["required"])
+ }
+ })
+}
+
+func TestGetQuestionList_Performance(t *testing.T) {
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := getQuestionListRequest(validToken, map[string]interface{}{
+ "limit": 10,
+ "page": 1,
+ })
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(500))
+ })
+
+ t.Run("LoadTest", func(t *testing.T) {
+ var wg sync.WaitGroup
+ for i := 0; i < 100; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ resp, err := getQuestionListRequest(validToken, map[string]interface{}{
+ "limit": 5,
+ "page": 1,
+ })
+ if err == nil && resp != nil {
+ resp.Body.Close()
+ }
+ }()
+ }
+ wg.Wait()
+ })
+}
+
+// todo 13.3.4 13.3.5 13.3.6 13.4 13.5
+
+func editQuestionRequest(token string, body map[string]interface{}) (*http.Response, error) {
+ payload, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ req, err := http.NewRequest("PATCH", baseURL+"/question/edit", bytes.NewReader(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestEditQuestion_Success(t *testing.T) {
+ // Сначала создаем вопрос для редактирования
+ createResp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Original Question",
+ "type": "variant",
+ "required": true,
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ questionID := createResult["id"]
+
+ // Редактируем вопрос
+ resp, err := editQuestionRequest(validToken, map[string]interface{}{
+ "id": questionID,
+ "title": "Обновленный заголовок вопроса?",
+ "desc": "Новое описание для вопроса.",
+ "type": "text",
+ "required": false,
+ "content": "{\"placeholder\":\"Введите ваш ответ здесь\"}",
+ "page": 2,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.Equal(t, questionID, result["updated"])
+}
+
+func TestEditQuestion_SingleField(t *testing.T) {
+ // Сначала создаем вопрос для редактирования
+ createResp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Single Field Question",
+ "type": "variant",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ questionID := createResult["id"]
+
+ // Редактируем только заголовок
+ resp, err := editQuestionRequest(validToken, map[string]interface{}{
+ "id": questionID,
+ "title": "Только заголовок обновлен",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.Equal(t, questionID, result["updated"])
+}
+
+func TestEditQuestion_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ payload, err := json.Marshal(map[string]interface{}{
+ "id": 123,
+ "title": "Test Question",
+ })
+ assert.NoError(t, err)
+
+ req, err := http.NewRequest("PATCH", baseURL+"/question/edit", bytes.NewReader(payload))
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := editQuestionRequest("invalid_token", map[string]interface{}{
+ "id": 123,
+ "title": "Test Question",
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("ExpiredToken", func(t *testing.T) {
+ resp, err := editQuestionRequest(expiredToken, map[string]interface{}{
+ "id": 123,
+ "title": "Test Question",
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestEditQuestion_InputValidation(t *testing.T) {
+ t.Run("MissingID", func(t *testing.T) {
+ resp, err := editQuestionRequest(validToken, map[string]interface{}{
+ "title": "Запрос без ID",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidID", func(t *testing.T) {
+ resp, err := editQuestionRequest(validToken, map[string]interface{}{
+ "id": "not_an_integer",
+ "title": "Невалидный ID",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("NonExistentID", func(t *testing.T) {
+ resp, err := editQuestionRequest(validToken, map[string]interface{}{
+ "id": 99999,
+ "title": "Несуществующий вопрос",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidTitle", func(t *testing.T) {
+ // Сначала создаем вопрос
+ createResp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Test Question",
+ "type": "variant",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ questionID := createResult["id"]
+
+ // Пытаемся установить слишком длинный заголовок
+ longTitle := strings.Repeat("a", 513)
+ resp, err := editQuestionRequest(validToken, map[string]interface{}{
+ "id": questionID,
+ "title": longTitle,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidType", func(t *testing.T) {
+ // Сначала создаем вопрос
+ createResp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Test Question",
+ "type": "variant",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ questionID := createResult["id"]
+
+ resp, err := editQuestionRequest(validToken, map[string]interface{}{
+ "id": questionID,
+ "type": "invalid_type",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidRequired", func(t *testing.T) {
+ // Сначала создаем вопрос
+ createResp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Test Question",
+ "type": "variant",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ questionID := createResult["id"]
+
+ resp, err := editQuestionRequest(validToken, map[string]interface{}{
+ "id": questionID,
+ "required": "not_boolean",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidContent", func(t *testing.T) {
+ // Сначала создаем вопрос
+ createResp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Test Question",
+ "type": "variant",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ questionID := createResult["id"]
+
+ resp, err := editQuestionRequest(validToken, map[string]interface{}{
+ "id": questionID,
+ "content": "invalid_json",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+}
+
+func TestEditQuestion_Security(t *testing.T) {
+ // Сначала создаем вопрос
+ createResp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Security Test Question",
+ "type": "variant",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ questionID := createResult["id"]
+
+ t.Run("SQLInjection", func(t *testing.T) {
+ resp, err := editQuestionRequest(validToken, map[string]interface{}{
+ "id": questionID,
+ "title": "1' OR '1'='1",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.Equal(t, questionID, result["updated"])
+ })
+
+ t.Run("XSSAttack", func(t *testing.T) {
+ resp, err := editQuestionRequest(validToken, map[string]interface{}{
+ "id": questionID,
+ "title": "",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.Equal(t, questionID, result["updated"])
+ })
+}
+
+func TestEditQuestion_Performance(t *testing.T) {
+ // Сначала создаем вопрос
+ createResp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Performance Test Question",
+ "type": "variant",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ questionID := createResult["id"]
+
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := editQuestionRequest(validToken, map[string]interface{}{
+ "id": questionID,
+ "title": "Updated Performance Test Question",
+ })
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(500))
+ })
+
+ t.Run("LoadTest", func(t *testing.T) {
+ var wg sync.WaitGroup
+ for i := 0; i < 30; i++ {
+ wg.Add(1)
+ go func(index int) {
+ defer wg.Done()
+ // Создаем вопрос для редактирования
+ createResp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345 + index,
+ "title": fmt.Sprintf("Load Test Question %d", index),
+ "type": "variant",
+ })
+ if err != nil {
+ return
+ }
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ if err != nil {
+ return
+ }
+ questionID := createResult["id"]
+
+ resp, err := editQuestionRequest(validToken, map[string]interface{}{
+ "id": questionID,
+ "title": fmt.Sprintf("Updated Load Test Question %d", index),
+ })
+ if err == nil && resp != nil {
+ resp.Body.Close()
+ }
+ }(i)
+ }
+ wg.Wait()
+ })
+}
+
+// todo 14.3.5 14.3.6 14.3.7 14.4 14.5
+
+func copyQuestionRequest(token string, body map[string]interface{}) (*http.Response, error) {
+ payload, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ req, err := http.NewRequest("POST", baseURL+"/question/copy", bytes.NewReader(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestCopyQuestion_Success(t *testing.T) {
+ // Сначала создаем оригинальный вопрос
+ createResp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Какой основной компонент воздуха?",
+ "type": "variant",
+ "description": "Выберите один правильный ответ из предложенных.",
+ "required": true,
+ "page": 1,
+ "content": "{}",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ originalID := createResult["id"]
+
+ // Копируем вопрос
+ resp, err := copyQuestionRequest(validToken, map[string]interface{}{
+ "id": originalID,
+ "quiz_id": 202,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.NotEmpty(t, result["id"])
+ assert.NotEqual(t, originalID, result["id"]) // Новый ID должен отличаться
+ assert.Equal(t, float64(202), result["quiz_id"])
+ assert.Equal(t, "Какой основной компонент воздуха?", result["title"])
+ assert.Equal(t, "variant", result["type"])
+ assert.Equal(t, "Выберите один правильный ответ из предложенных.", result["description"])
+ assert.Equal(t, true, result["required"])
+ assert.Equal(t, float64(1), result["page"])
+ assert.Equal(t, "{}", result["content"])
+ assert.Equal(t, false, result["deleted"])
+ assert.NotEmpty(t, result["created_at"])
+ assert.NotEmpty(t, result["updated_at"])
+}
+
+func TestCopyQuestion_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ payload, err := json.Marshal(map[string]interface{}{
+ "id": 101,
+ "quiz_id": 202,
+ })
+ assert.NoError(t, err)
+
+ req, err := http.NewRequest("POST", baseURL+"/question/copy", bytes.NewReader(payload))
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := copyQuestionRequest("invalid_token", map[string]interface{}{
+ "id": 101,
+ "quiz_id": 202,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("ExpiredToken", func(t *testing.T) {
+ resp, err := copyQuestionRequest(expiredToken, map[string]interface{}{
+ "id": 101,
+ "quiz_id": 202,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestCopyQuestion_InputValidation(t *testing.T) {
+ t.Run("MissingID", func(t *testing.T) {
+ resp, err := copyQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 202,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("MissingQuizID", func(t *testing.T) {
+ resp, err := copyQuestionRequest(validToken, map[string]interface{}{
+ "id": 101,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidID", func(t *testing.T) {
+ resp, err := copyQuestionRequest(validToken, map[string]interface{}{
+ "id": "invalid",
+ "quiz_id": 202,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidQuizID", func(t *testing.T) {
+ resp, err := copyQuestionRequest(validToken, map[string]interface{}{
+ "id": 101,
+ "quiz_id": "invalid",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("NonExistentID", func(t *testing.T) {
+ resp, err := copyQuestionRequest(validToken, map[string]interface{}{
+ "id": 99999,
+ "quiz_id": 202,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+}
+
+func TestCopyQuestion_Security(t *testing.T) {
+ // Сначала создаем оригинальный вопрос
+ createResp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Security Test Question",
+ "type": "variant",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ originalID := createResult["id"]
+
+ t.Run("SQLInjection", func(t *testing.T) {
+ resp, err := copyQuestionRequest(validToken, map[string]interface{}{
+ "id": "1' OR '1'='1",
+ "quiz_id": 202,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("XSSAttack", func(t *testing.T) {
+ resp, err := copyQuestionRequest(validToken, map[string]interface{}{
+ "id": originalID,
+ "quiz_id": 202,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.NotEmpty(t, result["id"])
+ })
+}
+
+func TestCopyQuestion_Performance(t *testing.T) {
+ // Сначала создаем оригинальный вопрос
+ createResp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Performance Test Question",
+ "type": "variant",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ originalID := createResult["id"]
+
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := copyQuestionRequest(validToken, map[string]interface{}{
+ "id": originalID,
+ "quiz_id": 202,
+ })
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(500))
+ })
+
+ t.Run("LoadTest", func(t *testing.T) {
+ var wg sync.WaitGroup
+ for i := 0; i < 30; i++ {
+ wg.Add(1)
+ go func(index int) {
+ defer wg.Done()
+ // Создаем оригинальный вопрос
+ createResp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345 + index,
+ "title": fmt.Sprintf("Load Test Question %d", index),
+ "type": "variant",
+ })
+ if err != nil {
+ return
+ }
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ if err != nil {
+ return
+ }
+ originalID := createResult["id"]
+
+ resp, err := copyQuestionRequest(validToken, map[string]interface{}{
+ "id": originalID,
+ "quiz_id": 202 + index,
+ })
+ if err == nil && resp != nil {
+ resp.Body.Close()
+ }
+ }(i)
+ }
+ wg.Wait()
+ })
+}
+
+func TestCopyQuestion_OriginalPreserved(t *testing.T) {
+ // Сначала создаем оригинальный вопрос
+ createResp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Original Question",
+ "type": "variant",
+ "required": true,
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ originalID := createResult["id"]
+ originalTitle := createResult["title"]
+
+ // Копируем вопрос
+ resp, err := copyQuestionRequest(validToken, map[string]interface{}{
+ "id": originalID,
+ "quiz_id": 202,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var copyResult map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(©Result)
+ assert.NoError(t, err)
+ copyID := copyResult["id"]
+
+ // Проверяем, что оригинальный вопрос остался без изменений
+ assert.NotEqual(t, originalID, copyID)
+ assert.Equal(t, originalTitle, copyResult["title"])
+}
+
+// todo 15.3.4 15.3.5 15.3.6 15.4 15.5
+
+func getQuestionHistoryRequest(token string, body map[string]interface{}) (*http.Response, error) {
+ payload, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ req, err := http.NewRequest("POST", baseURL+"/question/history", bytes.NewReader(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestGetQuestionHistory_Success(t *testing.T) {
+ // Сначала создаем вопрос
+ createResp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Original Question",
+ "type": "variant",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ questionID := createResult["id"]
+
+ // Редактируем вопрос несколько раз для создания истории
+ for i := 1; i <= 3; i++ {
+ editResp, err := editQuestionRequest(validToken, map[string]interface{}{
+ "id": questionID,
+ "title": fmt.Sprintf("Updated Question Version %d", i),
+ })
+ assert.NoError(t, err)
+ editResp.Body.Close()
+ }
+
+ // Получаем историю вопроса
+ resp, err := getQuestionHistoryRequest(validToken, map[string]interface{}{
+ "id": questionID,
+ "l": 10,
+ "p": 1,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.NotEmpty(t, result["items"])
+
+ items, ok := result["items"].([]interface{})
+ assert.True(t, ok)
+ assert.LessOrEqual(t, len(items), 10)
+ assert.Greater(t, len(items), 0)
+
+ // Проверяем структуру первой записи истории
+ if len(items) > 0 {
+ firstItem, ok := items[0].(map[string]interface{})
+ assert.True(t, ok)
+ assert.Equal(t, questionID, firstItem["id"])
+ assert.Equal(t, float64(12345), firstItem["quiz_id"])
+ assert.NotEmpty(t, firstItem["version"])
+ assert.NotEmpty(t, firstItem["created_at"])
+ assert.NotEmpty(t, firstItem["updated_at"])
+ }
+}
+
+func TestGetQuestionHistory_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ payload, err := json.Marshal(map[string]interface{}{
+ "id": 101,
+ "l": 10,
+ "p": 1,
+ })
+ assert.NoError(t, err)
+
+ req, err := http.NewRequest("POST", baseURL+"/question/history", bytes.NewReader(payload))
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := getQuestionHistoryRequest("invalid_token", map[string]interface{}{
+ "id": 101,
+ "l": 10,
+ "p": 1,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("ExpiredToken", func(t *testing.T) {
+ resp, err := getQuestionHistoryRequest(expiredToken, map[string]interface{}{
+ "id": 101,
+ "l": 10,
+ "p": 1,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestGetQuestionHistory_InputValidation(t *testing.T) {
+ t.Run("MissingID", func(t *testing.T) {
+ resp, err := getQuestionHistoryRequest(validToken, map[string]interface{}{
+ "l": 10,
+ "p": 1,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidID", func(t *testing.T) {
+ resp, err := getQuestionHistoryRequest(validToken, map[string]interface{}{
+ "id": "not_an_integer",
+ "l": 10,
+ "p": 1,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidLimit", func(t *testing.T) {
+ resp, err := getQuestionHistoryRequest(validToken, map[string]interface{}{
+ "id": 101,
+ "l": "ten",
+ "p": 1,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidPage", func(t *testing.T) {
+ resp, err := getQuestionHistoryRequest(validToken, map[string]interface{}{
+ "id": 101,
+ "l": 10,
+ "p": "one",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("NegativeLimit", func(t *testing.T) {
+ resp, err := getQuestionHistoryRequest(validToken, map[string]interface{}{
+ "id": 101,
+ "l": -5,
+ "p": 1,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("ZeroPage", func(t *testing.T) {
+ resp, err := getQuestionHistoryRequest(validToken, map[string]interface{}{
+ "id": 101,
+ "l": 10,
+ "p": 0,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("NonExistentID", func(t *testing.T) {
+ resp, err := getQuestionHistoryRequest(validToken, map[string]interface{}{
+ "id": 99999,
+ "l": 10,
+ "p": 1,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+}
+
+func TestGetQuestionHistory_Pagination(t *testing.T) {
+ // Сначала создаем вопрос
+ createResp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Pagination Test Question",
+ "type": "variant",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ questionID := createResult["id"]
+
+ // Создаем много версий для тестирования пагинации
+ for i := 1; i <= 15; i++ {
+ editResp, err := editQuestionRequest(validToken, map[string]interface{}{
+ "id": questionID,
+ "title": fmt.Sprintf("Version %d", i),
+ })
+ assert.NoError(t, err)
+ editResp.Body.Close()
+ }
+
+ t.Run("FirstPage", func(t *testing.T) {
+ resp, err := getQuestionHistoryRequest(validToken, map[string]interface{}{
+ "id": questionID,
+ "l": 5,
+ "p": 1,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ items, ok := result["items"].([]interface{})
+ assert.True(t, ok)
+ assert.LessOrEqual(t, len(items), 5)
+ })
+
+ t.Run("SecondPage", func(t *testing.T) {
+ resp, err := getQuestionHistoryRequest(validToken, map[string]interface{}{
+ "id": questionID,
+ "l": 5,
+ "p": 2,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ items, ok := result["items"].([]interface{})
+ assert.True(t, ok)
+ assert.LessOrEqual(t, len(items), 5)
+ })
+
+ t.Run("EmptyPage", func(t *testing.T) {
+ resp, err := getQuestionHistoryRequest(validToken, map[string]interface{}{
+ "id": questionID,
+ "l": 5,
+ "p": 100,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ items, ok := result["items"].([]interface{})
+ assert.True(t, ok)
+ assert.Empty(t, items)
+ })
+}
+
+func TestGetQuestionHistory_NewQuestion(t *testing.T) {
+ // Создаем новый вопрос
+ createResp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "New Question",
+ "type": "variant",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ questionID := createResult["id"]
+
+ // Получаем историю нового вопроса
+ resp, err := getQuestionHistoryRequest(validToken, map[string]interface{}{
+ "id": questionID,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ items, ok := result["items"].([]interface{})
+ assert.True(t, ok)
+ assert.Len(t, items, 1) // Должна быть только одна запись для нового вопроса
+
+ if len(items) > 0 {
+ firstItem, ok := items[0].(map[string]interface{})
+ assert.True(t, ok)
+ assert.Equal(t, questionID, firstItem["id"])
+ assert.Equal(t, float64(1), firstItem["version"]) // Версия должна быть 1
+ }
+}
+
+func TestGetQuestionHistory_Performance(t *testing.T) {
+ // Сначала создаем вопрос с историей
+ createResp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Performance Test Question",
+ "type": "variant",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ questionID := createResult["id"]
+
+ // Создаем несколько версий
+ for i := 1; i <= 5; i++ {
+ editResp, err := editQuestionRequest(validToken, map[string]interface{}{
+ "id": questionID,
+ "title": fmt.Sprintf("Version %d", i),
+ })
+ assert.NoError(t, err)
+ editResp.Body.Close()
+ }
+
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := getQuestionHistoryRequest(validToken, map[string]interface{}{
+ "id": questionID,
+ "l": 10,
+ "p": 1,
+ })
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(500))
+ })
+
+ t.Run("LoadTest", func(t *testing.T) {
+ var wg sync.WaitGroup
+ for i := 0; i < 100; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ resp, err := getQuestionHistoryRequest(validToken, map[string]interface{}{
+ "id": questionID,
+ "l": 5,
+ "p": 1,
+ })
+ if err == nil && resp != nil {
+ resp.Body.Close()
+ }
+ }()
+ }
+ wg.Wait()
+ })
+}
+
+// todo 16.3.4 16.3.5 16.4 16.5
+
+func deleteQuestionRequest(token string, body map[string]interface{}) (*http.Response, error) {
+ payload, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ req, err := http.NewRequest("DELETE", baseURL+"/question/delete", bytes.NewReader(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestDeleteQuestion_Success(t *testing.T) {
+ // Сначала создаем вопрос для удаления
+ createResp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Question to Delete",
+ "type": "variant",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ questionID := createResult["id"]
+
+ // Удаляем вопрос
+ resp, err := deleteQuestionRequest(validToken, map[string]interface{}{
+ "id": questionID,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.Equal(t, questionID, result["deactivated"])
+}
+
+func TestDeleteQuestion_Idempotency(t *testing.T) {
+ // Сначала создаем вопрос
+ createResp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Question for Idempotency Test",
+ "type": "variant",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ questionID := createResult["id"]
+
+ // Первое удаление
+ resp1, err := deleteQuestionRequest(validToken, map[string]interface{}{
+ "id": questionID,
+ })
+ assert.NoError(t, err)
+ defer resp1.Body.Close()
+ assert.Equal(t, http.StatusOK, resp1.StatusCode)
+
+ // Повторное удаление того же вопроса
+ resp2, err := deleteQuestionRequest(validToken, map[string]interface{}{
+ "id": questionID,
+ })
+ assert.NoError(t, err)
+ defer resp2.Body.Close()
+ assert.Equal(t, http.StatusOK, resp2.StatusCode)
+
+ // Проверяем, что оба ответа содержат правильный ID
+ var result1, result2 map[string]interface{}
+ err = json.NewDecoder(resp1.Body).Decode(&result1)
+ assert.NoError(t, err)
+ err = json.NewDecoder(resp2.Body).Decode(&result2)
+ assert.NoError(t, err)
+
+ assert.Equal(t, questionID, result1["deactivated"])
+ assert.Equal(t, questionID, result2["deactivated"])
+}
+
+func TestDeleteQuestion_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ payload, err := json.Marshal(map[string]interface{}{
+ "id": 101,
+ })
+ assert.NoError(t, err)
+
+ req, err := http.NewRequest("DELETE", baseURL+"/question/delete", bytes.NewReader(payload))
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := deleteQuestionRequest("invalid_token", map[string]interface{}{
+ "id": 101,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("ExpiredToken", func(t *testing.T) {
+ resp, err := deleteQuestionRequest(expiredToken, map[string]interface{}{
+ "id": 101,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestDeleteQuestion_InputValidation(t *testing.T) {
+ t.Run("MissingID", func(t *testing.T) {
+ resp, err := deleteQuestionRequest(validToken, map[string]interface{}{})
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidID", func(t *testing.T) {
+ resp, err := deleteQuestionRequest(validToken, map[string]interface{}{
+ "id": "not_an_integer",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("NonExistentID", func(t *testing.T) {
+ resp, err := deleteQuestionRequest(validToken, map[string]interface{}{
+ "id": 99999,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+}
+
+func TestDeleteQuestion_AlreadyDeleted(t *testing.T) {
+ // Создаем вопрос
+ createResp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": "Question to Delete Twice",
+ "type": "variant",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ questionID := createResult["id"]
+
+ // Удаляем вопрос
+ resp1, err := deleteQuestionRequest(validToken, map[string]interface{}{
+ "id": questionID,
+ })
+ assert.NoError(t, err)
+ defer resp1.Body.Close()
+ assert.Equal(t, http.StatusOK, resp1.StatusCode)
+
+ // Пытаемся удалить уже удаленный вопрос
+ resp2, err := deleteQuestionRequest(validToken, map[string]interface{}{
+ "id": questionID,
+ })
+ assert.NoError(t, err)
+ defer resp2.Body.Close()
+ assert.Equal(t, http.StatusOK, resp2.StatusCode) // Идемпотентность
+
+ var result1, result2 map[string]interface{}
+ err = json.NewDecoder(resp1.Body).Decode(&result1)
+ assert.NoError(t, err)
+ err = json.NewDecoder(resp2.Body).Decode(&result2)
+ assert.NoError(t, err)
+
+ assert.Equal(t, questionID, result1["deactivated"])
+ assert.Equal(t, questionID, result2["deactivated"])
+}
+
+func TestDeleteQuestion_Performance(t *testing.T) {
+ // Создаем несколько вопросов для тестирования производительности
+ var questionIDs []interface{}
+ for i := 0; i < 10; i++ {
+ createResp, err := createQuestionRequest(validToken, map[string]interface{}{
+ "quiz_id": 12345,
+ "title": fmt.Sprintf("Performance Test Question %d", i),
+ "type": "variant",
+ })
+ assert.NoError(t, err)
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ createResp.Body.Close()
+ assert.NoError(t, err)
+ questionIDs = append(questionIDs, createResult["id"])
+ }
+
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := deleteQuestionRequest(validToken, map[string]interface{}{
+ "id": questionIDs[0],
+ })
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(500))
+ })
+
+ t.Run("BulkDelete", func(t *testing.T) {
+ var wg sync.WaitGroup
+ for i := 1; i < len(questionIDs); i++ {
+ wg.Add(1)
+ go func(id interface{}) {
+ defer wg.Done()
+ resp, err := deleteQuestionRequest(validToken, map[string]interface{}{
+ "id": id,
+ })
+ if err == nil && resp != nil {
+ resp.Body.Close()
+ }
+ }(questionIDs[i])
+ }
+ wg.Wait()
+ })
+}
+
+// todo 17.3.5 17.3.6 17.4 17.5
+
+func createQuizRequest(token string, body map[string]interface{}) (*http.Response, error) {
+ payload, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ req, err := http.NewRequest("POST", baseURL+"/quiz/create", bytes.NewReader(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestCreateQuiz_Success(t *testing.T) {
+ t.Run("MinimalQuiz", func(t *testing.T) {
+ resp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Новый квиз по истории",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusCreated, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ assert.NotEmpty(t, result["id"])
+ assert.NotEmpty(t, result["qid"])
+ assert.NotEmpty(t, result["accountid"])
+ assert.Equal(t, "Новый квиз по истории", result["name"])
+ assert.Equal(t, "draft", result["status"]) // Значение по умолчанию
+ assert.Equal(t, false, result["deleted"])
+ assert.Equal(t, false, result["archived"])
+ assert.Equal(t, float64(1), result["version"])
+ assert.NotEmpty(t, result["created_at"])
+ assert.NotEmpty(t, result["updated_at"])
+ })
+
+ t.Run("FullQuiz", func(t *testing.T) {
+ resp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Полный квиз по географии",
+ "description": "Детальный тест на знание столиц и стран.",
+ "fingerprinting": true,
+ "repeatable": true,
+ "note_prevented": false,
+ "mail_notifications": true,
+ "unique_answers": false,
+ "config": "{\"showCorrectAnswers\": true}",
+ "status": "start",
+ "limit": 100,
+ "due_to": 1700000000,
+ "question_cnt": 10,
+ "time_of_passing": 3600,
+ "pausable": true,
+ "super": false,
+ "group_id": nil,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusCreated, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ assert.NotEmpty(t, result["id"])
+ assert.Equal(t, "Полный квиз по географии", result["name"])
+ assert.Equal(t, "Детальный тест на знание столиц и стран.", result["description"])
+ assert.Equal(t, true, result["fingerprinting"])
+ assert.Equal(t, true, result["repeatable"])
+ assert.Equal(t, false, result["note_prevented"])
+ assert.Equal(t, true, result["mail_notifications"])
+ assert.Equal(t, false, result["unique_answers"])
+ assert.Equal(t, "{\"showCorrectAnswers\": true}", result["config"])
+ assert.Equal(t, "start", result["status"])
+ assert.Equal(t, float64(100), result["limit"])
+ assert.Equal(t, float64(1700000000), result["due_to"])
+ assert.Equal(t, float64(10), result["question_cnt"])
+ assert.Equal(t, float64(3600), result["time_of_passing"])
+ assert.Equal(t, true, result["pausable"])
+ assert.Equal(t, false, result["super"])
+ assert.Nil(t, result["group_id"])
+ })
+}
+
+func TestCreateQuiz_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ payload, err := json.Marshal(map[string]interface{}{
+ "name": "Test Quiz",
+ })
+ assert.NoError(t, err)
+
+ req, err := http.NewRequest("POST", baseURL+"/quiz/create", bytes.NewReader(payload))
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := createQuizRequest("invalid_token", map[string]interface{}{
+ "name": "Test Quiz",
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("ExpiredToken", func(t *testing.T) {
+ resp, err := createQuizRequest(expiredToken, map[string]interface{}{
+ "name": "Test Quiz",
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestCreateQuiz_InputValidation(t *testing.T) {
+ t.Run("MissingName", func(t *testing.T) {
+ resp, err := createQuizRequest(validToken, map[string]interface{}{
+ "description": "Test description",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("NameTooLong", func(t *testing.T) {
+ longName := strings.Repeat("a", 701) // Больше 700 символов
+ resp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": longName,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidStatus", func(t *testing.T) {
+ resp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Test Quiz",
+ "status": "invalid_status",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidFingerprinting", func(t *testing.T) {
+ resp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Test Quiz",
+ "fingerprinting": "not_boolean",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("NegativeLimit", func(t *testing.T) {
+ resp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Test Quiz",
+ "limit": -5,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidDueTo", func(t *testing.T) {
+ resp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Test Quiz",
+ "due_to": "not_timestamp",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidConfig", func(t *testing.T) {
+ resp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Test Quiz",
+ "config": "invalid json",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+}
+
+func TestCreateQuiz_StatusValues(t *testing.T) {
+ statuses := []string{"draft", "template", "stop", "start"}
+
+ for _, status := range statuses {
+ t.Run("Status_"+status, func(t *testing.T) {
+ resp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": fmt.Sprintf("Quiz with status %s", status),
+ "status": status,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusCreated, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.Equal(t, status, result["status"])
+ })
+ }
+}
+
+func TestCreateQuiz_DefaultValues(t *testing.T) {
+ resp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Quiz with defaults",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusCreated, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ // Проверяем значения по умолчанию
+ assert.Equal(t, "draft", result["status"])
+ assert.Equal(t, false, result["fingerprinting"])
+ assert.Equal(t, false, result["repeatable"])
+ assert.Equal(t, false, result["note_prevented"])
+ assert.Equal(t, true, result["pausable"])
+ assert.Equal(t, false, result["super"])
+}
+
+func TestCreateQuiz_Conflict(t *testing.T) {
+ // Создаем первый квиз
+ resp1, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Duplicate Quiz Name",
+ })
+ assert.NoError(t, err)
+ defer resp1.Body.Close()
+ assert.Equal(t, http.StatusCreated, resp1.StatusCode)
+
+ // Пытаемся создать квиз с тем же именем
+ resp2, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Duplicate Quiz Name",
+ })
+ assert.NoError(t, err)
+ defer resp2.Body.Close()
+ assert.Equal(t, http.StatusConflict, resp2.StatusCode)
+}
+
+func TestCreateQuiz_Security(t *testing.T) {
+ t.Run("SQLInjection", func(t *testing.T) {
+ resp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "'; DROP TABLE quizzes; --",
+ "description": "'; DELETE FROM users; --",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ // Должен либо вернуть ошибку валидации, либо успешно создать квиз с экранированными данными
+ if resp.StatusCode == http.StatusCreated {
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.Equal(t, "'; DROP TABLE quizzes; --", result["name"])
+ } else {
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ }
+ })
+
+ t.Run("XSS", func(t *testing.T) {
+ resp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "",
+ "description": "
",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ if resp.StatusCode == http.StatusCreated {
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.Equal(t, "", result["name"])
+ } else {
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ }
+ })
+}
+
+func TestCreateQuiz_Performance(t *testing.T) {
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Performance Test Quiz",
+ })
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(500))
+ })
+
+ t.Run("LoadTest", func(t *testing.T) {
+ var wg sync.WaitGroup
+ for i := 0; i < 50; i++ {
+ wg.Add(1)
+ go func(index int) {
+ defer wg.Done()
+ resp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": fmt.Sprintf("Load Test Quiz %d", index),
+ })
+ if err == nil && resp != nil {
+ resp.Body.Close()
+ }
+ }(i)
+ }
+ wg.Wait()
+ })
+}
+
+func TestCreateQuiz_SuperQuiz(t *testing.T) {
+ t.Run("SuperQuizWithoutGroup", func(t *testing.T) {
+ resp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Super Quiz",
+ "super": true,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusCreated, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.Equal(t, true, result["super"])
+ assert.Nil(t, result["group_id"])
+ })
+
+ t.Run("NonSuperQuizWithGroup", func(t *testing.T) {
+ resp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Group Quiz",
+ "super": false,
+ "group_id": 123,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusCreated, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.Equal(t, false, result["super"])
+ assert.Equal(t, float64(123), result["group_id"])
+ })
+}
+
+// todo 18.3.4 18.3.5 18.3.6 18.3.7 18.3.8 18.4 18.5
+
+func getQuizListRequest(token string, body map[string]interface{}) (*http.Response, error) {
+ payload, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ req, err := http.NewRequest("POST", baseURL+"/quiz/getList", bytes.NewReader(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestGetQuizList_Success(t *testing.T) {
+ // Сначала создаем несколько квизов для тестирования
+ quizNames := []string{"Квиз по географии", "Квиз по истории", "Квиз по математике"}
+ var quizIDs []interface{}
+
+ for _, name := range quizNames {
+ resp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": name,
+ "status": "start",
+ })
+ assert.NoError(t, err)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ resp.Body.Close()
+ assert.NoError(t, err)
+ quizIDs = append(quizIDs, result["id"])
+ }
+
+ t.Run("BasicList", func(t *testing.T) {
+ resp, err := getQuizListRequest(validToken, map[string]interface{}{
+ "limit": 5,
+ "page": 1,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ assert.NotEmpty(t, result["count"])
+ assert.NotEmpty(t, result["items"])
+
+ items, ok := result["items"].([]interface{})
+ assert.True(t, ok)
+ assert.LessOrEqual(t, len(items), 5)
+
+ if len(items) > 0 {
+ firstItem, ok := items[0].(map[string]interface{})
+ assert.True(t, ok)
+ assert.NotEmpty(t, firstItem["id"])
+ assert.NotEmpty(t, firstItem["name"])
+ assert.NotEmpty(t, firstItem["status"])
+ }
+ })
+
+ t.Run("WithFilters", func(t *testing.T) {
+ resp, err := getQuizListRequest(validToken, map[string]interface{}{
+ "limit": 10,
+ "page": 1,
+ "search": "география",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ items, ok := result["items"].([]interface{})
+ assert.True(t, ok)
+
+ // Проверяем, что все возвращенные квизы содержат "география" в названии
+ for _, item := range items {
+ quiz, ok := item.(map[string]interface{})
+ assert.True(t, ok)
+ name, ok := quiz["name"].(string)
+ assert.True(t, ok)
+ assert.Contains(t, strings.ToLower(name), "география")
+ }
+ })
+}
+
+func TestGetQuizList_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ payload, err := json.Marshal(map[string]interface{}{
+ "limit": 10,
+ "page": 1,
+ })
+ assert.NoError(t, err)
+
+ req, err := http.NewRequest("POST", baseURL+"/quiz/getList", bytes.NewReader(payload))
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := getQuizListRequest("invalid_token", map[string]interface{}{
+ "limit": 10,
+ "page": 1,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("ExpiredToken", func(t *testing.T) {
+ resp, err := getQuizListRequest(expiredToken, map[string]interface{}{
+ "limit": 10,
+ "page": 1,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestGetQuizList_InputValidation(t *testing.T) {
+ t.Run("InvalidLimit", func(t *testing.T) {
+ resp, err := getQuizListRequest(validToken, map[string]interface{}{
+ "limit": "not_integer",
+ "page": 1,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidPage", func(t *testing.T) {
+ resp, err := getQuizListRequest(validToken, map[string]interface{}{
+ "limit": 10,
+ "page": "not_integer",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("ZeroLimit", func(t *testing.T) {
+ resp, err := getQuizListRequest(validToken, map[string]interface{}{
+ "limit": 0,
+ "page": 1,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("ZeroPage", func(t *testing.T) {
+ resp, err := getQuizListRequest(validToken, map[string]interface{}{
+ "limit": 10,
+ "page": 0,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidFrom", func(t *testing.T) {
+ resp, err := getQuizListRequest(validToken, map[string]interface{}{
+ "from": "not_timestamp",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidTo", func(t *testing.T) {
+ resp, err := getQuizListRequest(validToken, map[string]interface{}{
+ "to": "not_timestamp",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidStatus", func(t *testing.T) {
+ resp, err := getQuizListRequest(validToken, map[string]interface{}{
+ "status": "invalid_status",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidDeleted", func(t *testing.T) {
+ resp, err := getQuizListRequest(validToken, map[string]interface{}{
+ "deleted": "not_boolean",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidArchived", func(t *testing.T) {
+ resp, err := getQuizListRequest(validToken, map[string]interface{}{
+ "archived": "not_boolean",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidSuper", func(t *testing.T) {
+ resp, err := getQuizListRequest(validToken, map[string]interface{}{
+ "super": "not_boolean",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidGroupID", func(t *testing.T) {
+ resp, err := getQuizListRequest(validToken, map[string]interface{}{
+ "group_id": "not_integer",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+}
+
+func TestGetQuizList_Pagination(t *testing.T) {
+ // Создаем много квизов для тестирования пагинации
+ for i := 0; i < 15; i++ {
+ resp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": fmt.Sprintf("Pagination Test Quiz %d", i),
+ "status": "draft",
+ })
+ assert.NoError(t, err)
+ resp.Body.Close()
+ }
+
+ t.Run("FirstPage", func(t *testing.T) {
+ resp, err := getQuizListRequest(validToken, map[string]interface{}{
+ "limit": 5,
+ "page": 1,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ items, ok := result["items"].([]interface{})
+ assert.True(t, ok)
+ assert.LessOrEqual(t, len(items), 5)
+ })
+
+ t.Run("SecondPage", func(t *testing.T) {
+ resp, err := getQuizListRequest(validToken, map[string]interface{}{
+ "limit": 5,
+ "page": 2,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ items, ok := result["items"].([]interface{})
+ assert.True(t, ok)
+ assert.LessOrEqual(t, len(items), 5)
+ })
+
+ t.Run("EmptyPage", func(t *testing.T) {
+ resp, err := getQuizListRequest(validToken, map[string]interface{}{
+ "limit": 5,
+ "page": 100,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ items, ok := result["items"].([]interface{})
+ assert.True(t, ok)
+ assert.Empty(t, items)
+ })
+}
+
+func TestGetQuizList_Filters(t *testing.T) {
+ // Создаем квизы с разными статусами
+ statuses := []string{"draft", "start", "stop", "template"}
+ for _, status := range statuses {
+ resp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": fmt.Sprintf("Filter Test Quiz %s", status),
+ "status": status,
+ })
+ assert.NoError(t, err)
+ resp.Body.Close()
+ }
+
+ t.Run("StatusFilter", func(t *testing.T) {
+ resp, err := getQuizListRequest(validToken, map[string]interface{}{
+ "status": "draft",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ items, ok := result["items"].([]interface{})
+ assert.True(t, ok)
+
+ // Проверяем, что все квизы имеют статус "draft"
+ for _, item := range items {
+ quiz, ok := item.(map[string]interface{})
+ assert.True(t, ok)
+ assert.Equal(t, "draft", quiz["status"])
+ }
+ })
+
+ t.Run("SearchFilter", func(t *testing.T) {
+ resp, err := getQuizListRequest(validToken, map[string]interface{}{
+ "search": "Filter Test",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ items, ok := result["items"].([]interface{})
+ assert.True(t, ok)
+
+ // Проверяем, что все квизы содержат "Filter Test" в названии
+ for _, item := range items {
+ quiz, ok := item.(map[string]interface{})
+ assert.True(t, ok)
+ name, ok := quiz["name"].(string)
+ assert.True(t, ok)
+ assert.Contains(t, name, "Filter Test")
+ }
+ })
+
+ t.Run("TimeRangeFilter", func(t *testing.T) {
+ now := time.Now().Unix()
+ resp, err := getQuizListRequest(validToken, map[string]interface{}{
+ "from": now - 86400, // 24 часа назад
+ "to": now,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ // Проверяем, что возвращаются квизы
+ items, ok := result["items"].([]interface{})
+ assert.True(t, ok)
+ assert.GreaterOrEqual(t, len(items), 0)
+ })
+}
+
+func TestGetQuizList_DefaultValues(t *testing.T) {
+ resp, err := getQuizListRequest(validToken, map[string]interface{}{})
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ items, ok := result["items"].([]interface{})
+ assert.True(t, ok)
+ assert.LessOrEqual(t, len(items), 10) // Значение по умолчанию для limit
+}
+
+func TestGetQuizList_Performance(t *testing.T) {
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := getQuizListRequest(validToken, map[string]interface{}{
+ "limit": 10,
+ "page": 1,
+ })
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(1000))
+ })
+
+ t.Run("LoadTest", func(t *testing.T) {
+ var wg sync.WaitGroup
+ for i := 0; i < 100; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ resp, err := getQuizListRequest(validToken, map[string]interface{}{
+ "limit": 5,
+ "page": 1,
+ })
+ if err == nil && resp != nil {
+ resp.Body.Close()
+ }
+ }()
+ }
+ wg.Wait()
+ })
+}
+
+// todo 19.3.4 19.3.5 19.3.6 19.4 19.5
+
+func editQuizRequest(token string, body map[string]interface{}) (*http.Response, error) {
+ payload, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ req, err := http.NewRequest("PATCH", baseURL+"/quiz/edit", bytes.NewReader(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestEditQuiz_Success(t *testing.T) {
+ // Сначала создаем квиз
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для редактирования",
+ "status": "draft",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Обновляем несколько полей
+ resp, err := editQuizRequest(validToken, map[string]interface{}{
+ "id": quizID,
+ "name": "Обновленное название квиза",
+ "desc": "Новое описание",
+ "status": "start",
+ "limit": 150,
+ "fp": true,
+ "rep": false,
+ "conf": "{}",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.Equal(t, quizID, result["updated"])
+}
+
+func TestEditQuiz_OneField(t *testing.T) {
+ // Создаем квиз
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для смены статуса",
+ "status": "draft",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Меняем только статус
+ resp, err := editQuizRequest(validToken, map[string]interface{}{
+ "id": quizID,
+ "status": "stop",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.Equal(t, quizID, result["updated"])
+}
+
+func TestEditQuiz_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ payload, err := json.Marshal(map[string]interface{}{
+ "id": 101,
+ "name": "Test Quiz",
+ })
+ assert.NoError(t, err)
+
+ req, err := http.NewRequest("PATCH", baseURL+"/quiz/edit", bytes.NewReader(payload))
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := editQuizRequest("invalid_token", map[string]interface{}{
+ "id": 101,
+ "name": "Test Quiz",
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("ExpiredToken", func(t *testing.T) {
+ resp, err := editQuizRequest(expiredToken, map[string]interface{}{
+ "id": 101,
+ "name": "Test Quiz",
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestEditQuiz_InputValidation(t *testing.T) {
+ t.Run("MissingID", func(t *testing.T) {
+ resp, err := editQuizRequest(validToken, map[string]interface{}{
+ "name": "Без ID",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Contains(t, []int{http.StatusBadRequest, 422}, resp.StatusCode)
+ })
+
+ t.Run("InvalidID", func(t *testing.T) {
+ resp, err := editQuizRequest(validToken, map[string]interface{}{
+ "id": "not_an_integer",
+ "name": "Невалидный ID",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Contains(t, []int{http.StatusBadRequest, 422}, resp.StatusCode)
+ })
+
+ t.Run("NonExistentID", func(t *testing.T) {
+ resp, err := editQuizRequest(validToken, map[string]interface{}{
+ "id": 99999,
+ "name": "Несуществующий квиз",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Contains(t, []int{http.StatusBadRequest, 422}, resp.StatusCode)
+ })
+
+ t.Run("NameTooLong", func(t *testing.T) {
+ longName := strings.Repeat("a", 701)
+ resp, err := editQuizRequest(validToken, map[string]interface{}{
+ "id": 101,
+ "name": longName,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Contains(t, []int{http.StatusBadRequest, 422}, resp.StatusCode)
+ })
+
+ t.Run("InvalidStatus", func(t *testing.T) {
+ resp, err := editQuizRequest(validToken, map[string]interface{}{
+ "id": 101,
+ "status": "invalid_status",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Contains(t, []int{http.StatusBadRequest, 422}, resp.StatusCode)
+ })
+
+ t.Run("InvalidFP", func(t *testing.T) {
+ resp, err := editQuizRequest(validToken, map[string]interface{}{
+ "id": 101,
+ "fp": "not_boolean",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Contains(t, []int{http.StatusBadRequest, 422}, resp.StatusCode)
+ })
+
+ t.Run("InvalidLimit", func(t *testing.T) {
+ resp, err := editQuizRequest(validToken, map[string]interface{}{
+ "id": 101,
+ "limit": -5,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Contains(t, []int{http.StatusBadRequest, 422}, resp.StatusCode)
+ })
+
+ t.Run("InvalidConf", func(t *testing.T) {
+ resp, err := editQuizRequest(validToken, map[string]interface{}{
+ "id": 101,
+ "conf": "invalid json",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Contains(t, []int{http.StatusBadRequest, 422}, resp.StatusCode)
+ })
+}
+
+func TestEditQuiz_Security(t *testing.T) {
+ t.Run("SQLInjection", func(t *testing.T) {
+ resp, err := editQuizRequest(validToken, map[string]interface{}{
+ "id": 101,
+ "name": "'; DROP TABLE quizzes; --",
+ "desc": "'; DELETE FROM users; --",
+ "conf": "{}",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Contains(t, []int{http.StatusOK, http.StatusBadRequest, 422}, resp.StatusCode)
+ })
+
+ t.Run("XSS", func(t *testing.T) {
+ resp, err := editQuizRequest(validToken, map[string]interface{}{
+ "id": 101,
+ "name": "",
+ "desc": "
",
+ "conf": "{}",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Contains(t, []int{http.StatusOK, http.StatusBadRequest, 422}, resp.StatusCode)
+ })
+}
+
+func TestEditQuiz_Performance(t *testing.T) {
+ // Создаем квиз
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для теста производительности",
+ "status": "draft",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := editQuizRequest(validToken, map[string]interface{}{
+ "id": quizID,
+ "name": "Быстрое обновление",
+ })
+ duration := time.Since(start)
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(500))
+ })
+
+ t.Run("LoadTest", func(t *testing.T) {
+ var wg sync.WaitGroup
+ for i := 0; i < 50; i++ {
+ wg.Add(1)
+ go func(index int) {
+ defer wg.Done()
+ resp, err := editQuizRequest(validToken, map[string]interface{}{
+ "id": quizID,
+ "name": fmt.Sprintf("Load Test Quiz %d", index),
+ })
+ if err == nil && resp != nil {
+ resp.Body.Close()
+ }
+ }(i)
+ }
+ wg.Wait()
+ })
+}
+
+// todo 20.3.5 20.4 20.5
+
+func copyQuizRequest(token string, body map[string]interface{}) (*http.Response, error) {
+ payload, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ req, err := http.NewRequest("POST", baseURL+"/quiz/copy", bytes.NewReader(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestCopyQuiz_Success(t *testing.T) {
+ // Сначала создаем оригинальный квиз
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Оригинальный квиз для копирования",
+ "description": "Описание оригинала",
+ "status": "start",
+ "limit": 50,
+ "config": "{}",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ origID := createResult["id"]
+ origName := createResult["name"]
+
+ // Копируем квиз
+ resp, err := copyQuizRequest(validToken, map[string]interface{}{
+ "id": origID,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.NotEqual(t, origID, result["id"])
+ assert.NotEmpty(t, result["qid"])
+ assert.Equal(t, origName, result["name"])
+ assert.Equal(t, false, result["deleted"])
+ assert.Equal(t, false, result["archived"])
+ assert.Equal(t, "draft", result["status"])
+ assert.Equal(t, float64(1), result["version"])
+ assert.Equal(t, nil, result["version_comment"])
+ assert.Equal(t, float64(0), result["session_count"])
+ assert.Equal(t, float64(0), result["passed_count"])
+ assert.Equal(t, float64(0), result["average_time"])
+ assert.NotEmpty(t, result["created_at"])
+ assert.NotEmpty(t, result["updated_at"])
+}
+
+func TestCopyQuiz_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ payload, err := json.Marshal(map[string]interface{}{
+ "id": 101,
+ })
+ assert.NoError(t, err)
+
+ req, err := http.NewRequest("POST", baseURL+"/quiz/copy", bytes.NewReader(payload))
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := copyQuizRequest("invalid_token", map[string]interface{}{
+ "id": 101,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("ExpiredToken", func(t *testing.T) {
+ resp, err := copyQuizRequest(expiredToken, map[string]interface{}{
+ "id": 101,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestCopyQuiz_InputValidation(t *testing.T) {
+ t.Run("MissingID", func(t *testing.T) {
+ resp, err := copyQuizRequest(validToken, map[string]interface{}{})
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidID", func(t *testing.T) {
+ resp, err := copyQuizRequest(validToken, map[string]interface{}{
+ "id": "not_an_integer",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("NonExistentID", func(t *testing.T) {
+ resp, err := copyQuizRequest(validToken, map[string]interface{}{
+ "id": 99999,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+}
+
+func TestCopyQuiz_Performance(t *testing.T) {
+ // Создаем оригинальный квиз
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для копирования (перфоманс)",
+ "status": "draft",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ origID := createResult["id"]
+
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := copyQuizRequest(validToken, map[string]interface{}{
+ "id": origID,
+ })
+ duration := time.Since(start)
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(500))
+ })
+
+ t.Run("LoadTest", func(t *testing.T) {
+ var wg sync.WaitGroup
+ for i := 0; i < 20; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ resp, err := copyQuizRequest(validToken, map[string]interface{}{
+ "id": origID,
+ })
+ if err == nil && resp != nil {
+ resp.Body.Close()
+ }
+ }()
+ }
+ wg.Wait()
+ })
+}
+
+// todo 21.3.4 21.3.5 21.4 21.5
+
+func getQuizHistoryRequest(token string, body map[string]interface{}) (*http.Response, error) {
+ payload, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ req, err := http.NewRequest("POST", baseURL+"/quiz/history", bytes.NewReader(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestGetQuizHistory_Success(t *testing.T) {
+ // Сначала создаем квиз
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для истории",
+ "status": "draft",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Редактируем квиз несколько раз для создания истории
+ for i := 1; i <= 3; i++ {
+ editResp, err := editQuizRequest(validToken, map[string]interface{}{
+ "id": quizID,
+ "name": fmt.Sprintf("Обновленный квиз версия %d", i),
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ editResp.Body.Close()
+ }
+
+ // Получаем историю квиза
+ resp, err := getQuizHistoryRequest(validToken, map[string]interface{}{
+ "id": quizID,
+ "l": 5,
+ "p": 1,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result []map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.LessOrEqual(t, len(result), 5)
+ assert.Greater(t, len(result), 0)
+
+ // Проверяем структуру первой записи истории
+ if len(result) > 0 {
+ firstItem := result[0]
+ assert.Equal(t, quizID, firstItem["id"])
+ assert.NotEmpty(t, firstItem["version"])
+ assert.NotEmpty(t, firstItem["created_at"])
+ assert.NotEmpty(t, firstItem["updated_at"])
+ }
+}
+
+func TestGetQuizHistory_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ payload, err := json.Marshal(map[string]interface{}{
+ "id": 101,
+ "l": 10,
+ "p": 1,
+ })
+ assert.NoError(t, err)
+
+ req, err := http.NewRequest("POST", baseURL+"/quiz/history", bytes.NewReader(payload))
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := getQuizHistoryRequest("invalid_token", map[string]interface{}{
+ "id": 101,
+ "l": 10,
+ "p": 1,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("ExpiredToken", func(t *testing.T) {
+ resp, err := getQuizHistoryRequest(expiredToken, map[string]interface{}{
+ "id": 101,
+ "l": 10,
+ "p": 1,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestGetQuizHistory_InputValidation(t *testing.T) {
+ t.Run("MissingID", func(t *testing.T) {
+ resp, err := getQuizHistoryRequest(validToken, map[string]interface{}{
+ "l": 10,
+ "p": 1,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidID", func(t *testing.T) {
+ resp, err := getQuizHistoryRequest(validToken, map[string]interface{}{
+ "id": "not_an_integer",
+ "l": 10,
+ "p": 1,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidLimit", func(t *testing.T) {
+ resp, err := getQuizHistoryRequest(validToken, map[string]interface{}{
+ "id": 101,
+ "l": "ten",
+ "p": 1,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidPage", func(t *testing.T) {
+ resp, err := getQuizHistoryRequest(validToken, map[string]interface{}{
+ "id": 101,
+ "l": 10,
+ "p": "one",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("NonExistentID", func(t *testing.T) {
+ resp, err := getQuizHistoryRequest(validToken, map[string]interface{}{
+ "id": 99999,
+ "l": 10,
+ "p": 1,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result []map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.Empty(t, result)
+ })
+}
+
+func TestGetQuizHistory_Pagination(t *testing.T) {
+ // Создаем квиз
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для пагинации истории",
+ "status": "draft",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Создаем много версий
+ for i := 1; i <= 15; i++ {
+ editResp, err := editQuizRequest(validToken, map[string]interface{}{
+ "id": quizID,
+ "name": fmt.Sprintf("Версия %d", i),
+ })
+ assert.NoError(t, err)
+ editResp.Body.Close()
+ }
+
+ t.Run("FirstPage", func(t *testing.T) {
+ resp, err := getQuizHistoryRequest(validToken, map[string]interface{}{
+ "id": quizID,
+ "l": 5,
+ "p": 1,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result []map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.LessOrEqual(t, len(result), 5)
+ })
+
+ t.Run("SecondPage", func(t *testing.T) {
+ resp, err := getQuizHistoryRequest(validToken, map[string]interface{}{
+ "id": quizID,
+ "l": 5,
+ "p": 2,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result []map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.LessOrEqual(t, len(result), 5)
+ })
+
+ t.Run("EmptyPage", func(t *testing.T) {
+ resp, err := getQuizHistoryRequest(validToken, map[string]interface{}{
+ "id": quizID,
+ "l": 5,
+ "p": 100,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result []map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.Empty(t, result)
+ })
+}
+
+func TestGetQuizHistory_NewQuiz(t *testing.T) {
+ // Создаем новый квиз
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Новый квиз для истории",
+ "status": "draft",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Получаем историю нового квиза
+ resp, err := getQuizHistoryRequest(validToken, map[string]interface{}{
+ "id": quizID,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result []map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.Len(t, result, 1) // Должна быть только одна запись для нового квиза
+
+ if len(result) > 0 {
+ firstItem := result[0]
+ assert.Equal(t, quizID, firstItem["id"])
+ assert.Equal(t, float64(1), firstItem["version"])
+ }
+}
+
+func TestGetQuizHistory_Performance(t *testing.T) {
+ // Создаем квиз с историей
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для теста производительности истории",
+ "status": "draft",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Создаем несколько версий
+ for i := 1; i <= 5; i++ {
+ editResp, err := editQuizRequest(validToken, map[string]interface{}{
+ "id": quizID,
+ "name": fmt.Sprintf("Версия %d", i),
+ })
+ assert.NoError(t, err)
+ editResp.Body.Close()
+ }
+
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := getQuizHistoryRequest(validToken, map[string]interface{}{
+ "id": quizID,
+ "l": 10,
+ "p": 1,
+ })
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(500))
+ })
+
+ t.Run("LoadTest", func(t *testing.T) {
+ var wg sync.WaitGroup
+ for i := 0; i < 100; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ resp, err := getQuizHistoryRequest(validToken, map[string]interface{}{
+ "id": quizID,
+ "l": 5,
+ "p": 1,
+ })
+ if err == nil && resp != nil {
+ resp.Body.Close()
+ }
+ }()
+ }
+ wg.Wait()
+ })
+}
+
+// todo 22.3.4 22.3.5 22.4 22.5
+
+func deleteQuizRequest(token string, body map[string]interface{}) (*http.Response, error) {
+ payload, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ req, err := http.NewRequest("DELETE", baseURL+"/quiz/delete", bytes.NewReader(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestDeleteQuiz_Success(t *testing.T) {
+ // Сначала создаем квиз для удаления
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для удаления",
+ "status": "draft",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Удаляем квиз
+ resp, err := deleteQuizRequest(validToken, map[string]interface{}{
+ "id": quizID,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.Equal(t, quizID, result["deactivated"])
+}
+
+func TestDeleteQuiz_Idempotency(t *testing.T) {
+ // Сначала создаем квиз
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для идемпотентности",
+ "status": "draft",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Первое удаление
+ resp1, err := deleteQuizRequest(validToken, map[string]interface{}{
+ "id": quizID,
+ })
+ assert.NoError(t, err)
+ defer resp1.Body.Close()
+ assert.Equal(t, http.StatusOK, resp1.StatusCode)
+
+ // Повторное удаление того же квиза
+ resp2, err := deleteQuizRequest(validToken, map[string]interface{}{
+ "id": quizID,
+ })
+ assert.NoError(t, err)
+ defer resp2.Body.Close()
+ assert.Equal(t, http.StatusOK, resp2.StatusCode)
+
+ // Проверяем, что оба ответа содержат правильный ID
+ var result1, result2 map[string]interface{}
+ err = json.NewDecoder(resp1.Body).Decode(&result1)
+ assert.NoError(t, err)
+ err = json.NewDecoder(resp2.Body).Decode(&result2)
+ assert.NoError(t, err)
+
+ assert.Equal(t, quizID, result1["deactivated"])
+ assert.Equal(t, quizID, result2["deactivated"])
+}
+
+func TestDeleteQuiz_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ payload, err := json.Marshal(map[string]interface{}{
+ "id": 101,
+ })
+ assert.NoError(t, err)
+
+ req, err := http.NewRequest("DELETE", baseURL+"/quiz/delete", bytes.NewReader(payload))
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := deleteQuizRequest("invalid_token", map[string]interface{}{
+ "id": 101,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("ExpiredToken", func(t *testing.T) {
+ resp, err := deleteQuizRequest(expiredToken, map[string]interface{}{
+ "id": 101,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestDeleteQuiz_InputValidation(t *testing.T) {
+ t.Run("MissingID", func(t *testing.T) {
+ resp, err := deleteQuizRequest(validToken, map[string]interface{}{})
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidID", func(t *testing.T) {
+ resp, err := deleteQuizRequest(validToken, map[string]interface{}{
+ "id": "not_an_integer",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("NonExistentID", func(t *testing.T) {
+ resp, err := deleteQuizRequest(validToken, map[string]interface{}{
+ "id": 99999,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode) // Идемпотентность
+ })
+}
+
+func TestDeleteQuiz_Performance(t *testing.T) {
+ // Создаем несколько квизов для тестирования производительности
+ var quizIDs []interface{}
+ for i := 0; i < 10; i++ {
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": fmt.Sprintf("Квиз для удаления %d", i),
+ "status": "draft",
+ })
+ assert.NoError(t, err)
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ createResp.Body.Close()
+ assert.NoError(t, err)
+ quizIDs = append(quizIDs, createResult["id"])
+ }
+
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := deleteQuizRequest(validToken, map[string]interface{}{
+ "id": quizIDs[0],
+ })
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(500))
+ })
+
+ t.Run("BulkDelete", func(t *testing.T) {
+ var wg sync.WaitGroup
+ for i := 1; i < len(quizIDs); i++ {
+ wg.Add(1)
+ go func(id interface{}) {
+ defer wg.Done()
+ resp, err := deleteQuizRequest(validToken, map[string]interface{}{
+ "id": id,
+ })
+ if err == nil && resp != nil {
+ resp.Body.Close()
+ }
+ }(quizIDs[i])
+ }
+ wg.Wait()
+ })
+}
+
+// todo 23.3.4 23.3.5 23.4 23.5
+
+func archiveQuizRequest(token string, body map[string]interface{}) (*http.Response, error) {
+ payload, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ req, err := http.NewRequest("PATCH", baseURL+"/quiz/archive", bytes.NewReader(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestArchiveQuiz_Success(t *testing.T) {
+ // Сначала создаем квиз для архивации
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для архивации",
+ "status": "draft",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Архивируем квиз
+ resp, err := archiveQuizRequest(validToken, map[string]interface{}{
+ "id": quizID,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.Equal(t, quizID, result["deactivated"])
+}
+
+func TestArchiveQuiz_Idempotency(t *testing.T) {
+ // Сначала создаем квиз
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для архивации (идемпотентность)",
+ "status": "draft",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Первая архивация
+ resp1, err := archiveQuizRequest(validToken, map[string]interface{}{
+ "id": quizID,
+ })
+ assert.NoError(t, err)
+ defer resp1.Body.Close()
+ assert.Equal(t, http.StatusOK, resp1.StatusCode)
+
+ // Повторная архивация того же квиза
+ resp2, err := archiveQuizRequest(validToken, map[string]interface{}{
+ "id": quizID,
+ })
+ assert.NoError(t, err)
+ defer resp2.Body.Close()
+ assert.Equal(t, http.StatusOK, resp2.StatusCode)
+
+ // Проверяем, что оба ответа содержат правильный ID
+ var result1, result2 map[string]interface{}
+ err = json.NewDecoder(resp1.Body).Decode(&result1)
+ assert.NoError(t, err)
+ err = json.NewDecoder(resp2.Body).Decode(&result2)
+ assert.NoError(t, err)
+
+ assert.Equal(t, quizID, result1["deactivated"])
+ assert.Equal(t, quizID, result2["deactivated"])
+}
+
+func TestArchiveQuiz_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ payload, err := json.Marshal(map[string]interface{}{
+ "id": 101,
+ })
+ assert.NoError(t, err)
+
+ req, err := http.NewRequest("PATCH", baseURL+"/quiz/archive", bytes.NewReader(payload))
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := archiveQuizRequest("invalid_token", map[string]interface{}{
+ "id": 101,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("ExpiredToken", func(t *testing.T) {
+ resp, err := archiveQuizRequest(expiredToken, map[string]interface{}{
+ "id": 101,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestArchiveQuiz_InputValidation(t *testing.T) {
+ t.Run("MissingID", func(t *testing.T) {
+ resp, err := archiveQuizRequest(validToken, map[string]interface{}{})
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidID", func(t *testing.T) {
+ resp, err := archiveQuizRequest(validToken, map[string]interface{}{
+ "id": "not_an_integer",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("NonExistentID", func(t *testing.T) {
+ resp, err := archiveQuizRequest(validToken, map[string]interface{}{
+ "id": 99999,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode) // Идемпотентность
+ })
+}
+
+func TestArchiveQuiz_Performance(t *testing.T) {
+ // Создаем несколько квизов для тестирования производительности
+ var quizIDs []interface{}
+ for i := 0; i < 10; i++ {
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": fmt.Sprintf("Квиз для архивации %d", i),
+ "status": "draft",
+ })
+ assert.NoError(t, err)
+
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ createResp.Body.Close()
+ assert.NoError(t, err)
+ quizIDs = append(quizIDs, createResult["id"])
+ }
+
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := archiveQuizRequest(validToken, map[string]interface{}{
+ "id": quizIDs[0],
+ })
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(500))
+ })
+
+ t.Run("BulkArchive", func(t *testing.T) {
+ var wg sync.WaitGroup
+ for i := 1; i < len(quizIDs); i++ {
+ wg.Add(1)
+ go func(id interface{}) {
+ defer wg.Done()
+ resp, err := archiveQuizRequest(validToken, map[string]interface{}{
+ "id": id,
+ })
+ if err == nil && resp != nil {
+ resp.Body.Close()
+ }
+ }(quizIDs[i])
+ }
+ wg.Wait()
+ })
+}
+
+// todo 24.3.4 24.3.5 24.4 24.5
+
+func moveQuizRequest(token string, body map[string]interface{}) (*http.Response, error) {
+ payload, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ req, err := http.NewRequest("POST", baseURL+"/quiz/move", bytes.NewReader(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestMoveQuiz_Success(t *testing.T) {
+ // Сначала создаем квиз для переноса
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для переноса",
+ "status": "draft",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizQID := createResult["qid"]
+
+ // Переносим квиз
+ resp, err := moveQuizRequest(validToken, map[string]interface{}{
+ "qid": quizQID,
+ "accountID": "new-account-id",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+}
+
+func TestMoveQuiz_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ payload, err := json.Marshal(map[string]interface{}{
+ "qid": "test-qid",
+ "accountID": "new-account",
+ })
+ assert.NoError(t, err)
+
+ req, err := http.NewRequest("POST", baseURL+"/quiz/move", bytes.NewReader(payload))
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := moveQuizRequest("invalid_token", map[string]interface{}{
+ "qid": "test-qid",
+ "accountID": "new-account",
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestMoveQuiz_InputValidation(t *testing.T) {
+ t.Run("MissingQID", func(t *testing.T) {
+ resp, err := moveQuizRequest(validToken, map[string]interface{}{
+ "accountID": "new-account",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("MissingAccountID", func(t *testing.T) {
+ resp, err := moveQuizRequest(validToken, map[string]interface{}{
+ "qid": "test-qid",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("NonExistentQID", func(t *testing.T) {
+ resp, err := moveQuizRequest(validToken, map[string]interface{}{
+ "qid": "non-existent-qid",
+ "accountID": "new-account",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("NonExistentAccountID", func(t *testing.T) {
+ resp, err := moveQuizRequest(validToken, map[string]interface{}{
+ "qid": "test-qid",
+ "accountID": "non-existent-account",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+}
+
+func TestMoveQuiz_Performance(t *testing.T) {
+ // Создаем квиз для тестирования производительности
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для теста производительности переноса",
+ "status": "draft",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizQID := createResult["qid"]
+
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := moveQuizRequest(validToken, map[string]interface{}{
+ "qid": quizQID,
+ "accountID": "performance-test-account",
+ })
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(500))
+ })
+}
+
+// todo 25.3.4 25.4 25.5
+
+func createQuizTemplateRequest(token string) (*http.Response, error) {
+ req, err := http.NewRequest("POST", baseURL+"/quiz/template", nil)
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestCreateQuizTemplate_Success(t *testing.T) {
+ resp, err := createQuizTemplateRequest(validToken)
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.NotEmpty(t, result["id"])
+ assert.IsType(t, float64(0), result["id"]) // ID должен быть числом
+}
+
+func TestCreateQuizTemplate_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ req, err := http.NewRequest("POST", baseURL+"/quiz/template", nil)
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := createQuizTemplateRequest("invalid_token")
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestCreateQuizTemplate_Performance(t *testing.T) {
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := createQuizTemplateRequest(validToken)
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(500))
+ })
+
+ t.Run("MultipleTemplates", func(t *testing.T) {
+ var wg sync.WaitGroup
+ for i := 0; i < 5; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ resp, err := createQuizTemplateRequest(validToken)
+ if err == nil && resp != nil {
+ resp.Body.Close()
+ }
+ }()
+ }
+ wg.Wait()
+ })
+}
+
+// todo 26.3.3 26.4 26.5
+
+func getResultsRequest(token string, quizId string, body map[string]interface{}) (*http.Response, error) {
+ payload, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ req, err := http.NewRequest("POST", baseURL+"/results/getResults/"+quizId, bytes.NewReader(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestGetResults_Success(t *testing.T) {
+ // Сначала создаем квиз
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для результатов",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Получаем результаты квиза
+ resp, err := getResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "Page": 1,
+ "Limit": 10,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.NotNil(t, result["total_count"])
+ assert.NotNil(t, result["results"])
+ assert.IsType(t, []interface{}{}, result["results"])
+}
+
+func TestGetResults_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ payload, err := json.Marshal(map[string]interface{}{
+ "Page": 1,
+ "Limit": 10,
+ })
+ assert.NoError(t, err)
+
+ req, err := http.NewRequest("POST", baseURL+"/results/getResults/12345", bytes.NewReader(payload))
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := getResultsRequest("invalid_token", "12345", map[string]interface{}{
+ "Page": 1,
+ "Limit": 10,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestGetResults_InputValidation(t *testing.T) {
+ t.Run("InvalidQuizID", func(t *testing.T) {
+ resp, err := getResultsRequest(validToken, "invalid-quiz-id", map[string]interface{}{
+ "Page": 1,
+ "Limit": 10,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("NonExistentQuizID", func(t *testing.T) {
+ resp, err := getResultsRequest(validToken, "99999", map[string]interface{}{
+ "Page": 1,
+ "Limit": 10,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.Equal(t, float64(0), result["total_count"])
+ assert.Empty(t, result["results"])
+ })
+}
+
+func TestGetResults_Pagination(t *testing.T) {
+ // Создаем квиз
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для пагинации результатов",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ t.Run("FirstPage", func(t *testing.T) {
+ resp, err := getResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "Page": 1,
+ "Limit": 5,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ results := result["results"].([]interface{})
+ assert.LessOrEqual(t, len(results), 5)
+ })
+
+ t.Run("SecondPage", func(t *testing.T) {
+ resp, err := getResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "Page": 2,
+ "Limit": 5,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ })
+
+ t.Run("EmptyPage", func(t *testing.T) {
+ resp, err := getResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "Page": 100,
+ "Limit": 5,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ results := result["results"].([]interface{})
+ assert.Empty(t, results)
+ })
+}
+
+func TestGetResults_Filtering(t *testing.T) {
+ // Создаем квиз
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для фильтрации результатов",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ t.Run("WithDateRange", func(t *testing.T) {
+ resp, err := getResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "From": "2023-01-01",
+ "To": "2023-12-31",
+ "Page": 1,
+ "Limit": 10,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ })
+
+ t.Run("NewResultsOnly", func(t *testing.T) {
+ resp, err := getResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "New": true,
+ "Page": 1,
+ "Limit": 10,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ })
+}
+
+func TestGetResults_Performance(t *testing.T) {
+ // Создаем квиз
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для теста производительности результатов",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := getResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "Page": 1,
+ "Limit": 10,
+ })
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(500))
+ })
+
+ t.Run("ConcurrentRequests", func(t *testing.T) {
+ var wg sync.WaitGroup
+ for i := 0; i < 10; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ resp, err := getResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "Page": 1,
+ "Limit": 5,
+ })
+ if err == nil && resp != nil {
+ resp.Body.Close()
+ }
+ }()
+ }
+ wg.Wait()
+ })
+}
+
+// todo 27.3.4 27.4 27.5
+
+func deleteResultRequest(token string, resultId string) (*http.Response, error) {
+ req, err := http.NewRequest("DELETE", baseURL+"/results/"+resultId, nil)
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestDeleteResult_Success(t *testing.T) {
+ // Сначала создаем квиз и получаем результаты
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для удаления результатов",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Получаем результаты квиза
+ getResultsResp, err := getResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "Page": 1,
+ "Limit": 10,
+ })
+ assert.NoError(t, err)
+ defer getResultsResp.Body.Close()
+
+ var resultsData map[string]interface{}
+ err = json.NewDecoder(getResultsResp.Body).Decode(&resultsData)
+ assert.NoError(t, err)
+ results := resultsData["results"].([]interface{})
+
+ // Если есть результаты, удаляем первый
+ if len(results) > 0 {
+ firstResult := results[0].(map[string]interface{})
+ resultID := fmt.Sprintf("%v", firstResult["id"])
+
+ // Удаляем результат
+ resp, err := deleteResultRequest(validToken, resultID)
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ }
+}
+
+func TestDeleteResult_Idempotency(t *testing.T) {
+ // Создаем квиз и получаем результат для тестирования идемпотентности
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для идемпотентности удаления результатов",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Получаем результаты
+ getResultsResp, err := getResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "Page": 1,
+ "Limit": 5,
+ })
+ assert.NoError(t, err)
+ defer getResultsResp.Body.Close()
+
+ var resultsData map[string]interface{}
+ err = json.NewDecoder(getResultsResp.Body).Decode(&resultsData)
+ assert.NoError(t, err)
+ results := resultsData["results"].([]interface{})
+
+ if len(results) > 0 {
+ firstResult := results[0].(map[string]interface{})
+ resultID := fmt.Sprintf("%v", firstResult["id"])
+
+ // Первое удаление
+ resp1, err := deleteResultRequest(validToken, resultID)
+ assert.NoError(t, err)
+ defer resp1.Body.Close()
+ assert.Equal(t, http.StatusOK, resp1.StatusCode)
+
+ // Повторное удаление того же результата
+ resp2, err := deleteResultRequest(validToken, resultID)
+ assert.NoError(t, err)
+ defer resp2.Body.Close()
+ assert.Equal(t, http.StatusOK, resp2.StatusCode)
+ }
+}
+
+func TestDeleteResult_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ req, err := http.NewRequest("DELETE", baseURL+"/results/123456", nil)
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := deleteResultRequest("invalid_token", "123456")
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestDeleteResult_InputValidation(t *testing.T) {
+ t.Run("InvalidResultID", func(t *testing.T) {
+ resp, err := deleteResultRequest(validToken, "not_a_number")
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("NonExistentResultID", func(t *testing.T) {
+ resp, err := deleteResultRequest(validToken, "99999999")
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode) // Идемпотентность
+ })
+}
+
+func TestDeleteResult_Performance(t *testing.T) {
+ // Создаем квиз и получаем результаты для тестирования производительности
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для теста производительности удаления результатов",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Получаем результаты
+ getResultsResp, err := getResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "Page": 1,
+ "Limit": 10,
+ })
+ assert.NoError(t, err)
+ defer getResultsResp.Body.Close()
+
+ var resultsData map[string]interface{}
+ err = json.NewDecoder(getResultsResp.Body).Decode(&resultsData)
+ assert.NoError(t, err)
+ results := resultsData["results"].([]interface{})
+
+ if len(results) > 0 {
+ firstResult := results[0].(map[string]interface{})
+ resultID := fmt.Sprintf("%v", firstResult["id"])
+
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := deleteResultRequest(validToken, resultID)
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(500))
+ })
+ }
+
+ t.Run("BulkDelete", func(t *testing.T) {
+ // Получаем больше результатов для массового удаления
+ getMoreResultsResp, err := getResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "Page": 1,
+ "Limit": 20,
+ })
+ assert.NoError(t, err)
+ defer getMoreResultsResp.Body.Close()
+
+ var moreResultsData map[string]interface{}
+ err = json.NewDecoder(getMoreResultsResp.Body).Decode(&moreResultsData)
+ assert.NoError(t, err)
+ moreResults := moreResultsData["results"].([]interface{})
+
+ var wg sync.WaitGroup
+ for i := 0; i < len(moreResults) && i < 5; i++ {
+ wg.Add(1)
+ go func(result interface{}) {
+ defer wg.Done()
+ resultMap := result.(map[string]interface{})
+ resultID := fmt.Sprintf("%v", resultMap["id"])
+ resp, err := deleteResultRequest(validToken, resultID)
+ if err == nil && resp != nil {
+ resp.Body.Close()
+ }
+ }(moreResults[i])
+ }
+ wg.Wait()
+ })
+}
+
+// todo 28.3.4 28.3.5 28.3.6 28.4 28.5
+
+func updateResultsStatusRequest(token string, body map[string]interface{}) (*http.Response, error) {
+ payload, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ req, err := http.NewRequest("PATCH", baseURL+"/result/seen", bytes.NewReader(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestUpdateResultsStatus_Success(t *testing.T) {
+ // Сначала создаем квиз и получаем результаты
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для обновления статуса результатов",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Получаем результаты квиза
+ getResultsResp, err := getResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "Page": 1,
+ "Limit": 10,
+ })
+ assert.NoError(t, err)
+ defer getResultsResp.Body.Close()
+
+ var resultsData map[string]interface{}
+ err = json.NewDecoder(getResultsResp.Body).Decode(&resultsData)
+ assert.NoError(t, err)
+ results := resultsData["results"].([]interface{})
+
+ // Если есть результаты, обновляем статус первых трех
+ if len(results) >= 3 {
+ var answerIDs []int64
+ for i := 0; i < 3; i++ {
+ result := results[i].(map[string]interface{})
+ answerIDs = append(answerIDs, int64(result["id"].(float64)))
+ }
+
+ // Обновляем статус результатов
+ resp, err := updateResultsStatusRequest(validToken, map[string]interface{}{
+ "Answers": answerIDs,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ }
+}
+
+func TestUpdateResultsStatus_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ payload, err := json.Marshal(map[string]interface{}{
+ "Answers": []int64{101, 102, 103},
+ })
+ assert.NoError(t, err)
+
+ req, err := http.NewRequest("PATCH", baseURL+"/result/seen", bytes.NewReader(payload))
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := updateResultsStatusRequest("invalid_token", map[string]interface{}{
+ "Answers": []int64{101, 102, 103},
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestUpdateResultsStatus_InputValidation(t *testing.T) {
+ t.Run("MissingAnswers", func(t *testing.T) {
+ resp, err := updateResultsStatusRequest(validToken, map[string]interface{}{})
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("EmptyAnswers", func(t *testing.T) {
+ resp, err := updateResultsStatusRequest(validToken, map[string]interface{}{
+ "Answers": []int64{},
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidAnswersType", func(t *testing.T) {
+ resp, err := updateResultsStatusRequest(validToken, map[string]interface{}{
+ "Answers": "not_an_array",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("NonExistentAnswers", func(t *testing.T) {
+ resp, err := updateResultsStatusRequest(validToken, map[string]interface{}{
+ "Answers": []int64{999999, 999998, 999997},
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode) // Идемпотентность
+ })
+}
+
+func TestUpdateResultsStatus_Idempotency(t *testing.T) {
+ // Создаем квиз и получаем результаты
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для идемпотентности обновления статуса",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Получаем результаты
+ getResultsResp, err := getResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "Page": 1,
+ "Limit": 5,
+ })
+ assert.NoError(t, err)
+ defer getResultsResp.Body.Close()
+
+ var resultsData map[string]interface{}
+ err = json.NewDecoder(getResultsResp.Body).Decode(&resultsData)
+ assert.NoError(t, err)
+ results := resultsData["results"].([]interface{})
+
+ if len(results) >= 2 {
+ var answerIDs []int64
+ for i := 0; i < 2; i++ {
+ result := results[i].(map[string]interface{})
+ answerIDs = append(answerIDs, int64(result["id"].(float64)))
+ }
+
+ // Первое обновление
+ resp1, err := updateResultsStatusRequest(validToken, map[string]interface{}{
+ "Answers": answerIDs,
+ })
+ assert.NoError(t, err)
+ defer resp1.Body.Close()
+ assert.Equal(t, http.StatusOK, resp1.StatusCode)
+
+ // Повторное обновление тех же результатов
+ resp2, err := updateResultsStatusRequest(validToken, map[string]interface{}{
+ "Answers": answerIDs,
+ })
+ assert.NoError(t, err)
+ defer resp2.Body.Close()
+ assert.Equal(t, http.StatusOK, resp2.StatusCode)
+ }
+}
+
+func TestUpdateResultsStatus_Performance(t *testing.T) {
+ // Создаем квиз и получаем результаты для тестирования производительности
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для теста производительности обновления статуса",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Получаем результаты
+ getResultsResp, err := getResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "Page": 1,
+ "Limit": 20,
+ })
+ assert.NoError(t, err)
+ defer getResultsResp.Body.Close()
+
+ var resultsData map[string]interface{}
+ err = json.NewDecoder(getResultsResp.Body).Decode(&resultsData)
+ assert.NoError(t, err)
+ results := resultsData["results"].([]interface{})
+
+ if len(results) > 0 {
+ var answerIDs []int64
+ for i := 0; i < len(results) && i < 10; i++ {
+ result := results[i].(map[string]interface{})
+ answerIDs = append(answerIDs, int64(result["id"].(float64)))
+ }
+
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := updateResultsStatusRequest(validToken, map[string]interface{}{
+ "Answers": answerIDs,
+ })
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(500))
+ })
+
+ t.Run("LargeBatch", func(t *testing.T) {
+ // Тестируем обновление большого количества результатов
+ largeBatch := make([]int64, 100)
+ for i := 0; i < 100; i++ {
+ largeBatch[i] = int64(1000000 + i) // Используем несуществующие ID
+ }
+
+ resp, err := updateResultsStatusRequest(validToken, map[string]interface{}{
+ "Answers": largeBatch,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ })
+ }
+}
+
+// todo 29.3.2 29.3.3 29.4 29.5
+
+func exportResultsRequest(token string, quizID string, body map[string]interface{}) (*http.Response, error) {
+ payload, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ req, err := http.NewRequest("POST", baseURL+"/results/"+quizID+"/export", bytes.NewReader(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestExportResults_Success(t *testing.T) {
+ // Сначала создаем квиз
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для экспорта результатов",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Экспортируем результаты
+ resp, err := exportResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "From": "2023-01-01T00:00:00Z",
+ "To": "2023-12-31T23:59:59Z",
+ "New": false,
+ "Page": 1,
+ "Limit": 100,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", resp.Header.Get("Content-Type"))
+
+ // Проверяем, что ответ содержит данные
+ body, err := io.ReadAll(resp.Body)
+ assert.NoError(t, err)
+ assert.NotEmpty(t, body)
+}
+
+func TestExportResults_WithFilters(t *testing.T) {
+ // Создаем квиз
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для фильтрации экспорта",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ t.Run("NewResultsOnly", func(t *testing.T) {
+ resp, err := exportResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "New": true,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", resp.Header.Get("Content-Type"))
+ })
+
+ t.Run("WithDateRange", func(t *testing.T) {
+ resp, err := exportResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "From": "2023-02-01T00:00:00Z",
+ "To": "2023-02-28T23:59:59Z",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", resp.Header.Get("Content-Type"))
+ })
+
+ t.Run("WithPagination", func(t *testing.T) {
+ resp, err := exportResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "Page": 2,
+ "Limit": 50,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", resp.Header.Get("Content-Type"))
+ })
+}
+
+func TestExportResults_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ payload, err := json.Marshal(map[string]interface{}{
+ "Page": 1,
+ "Limit": 10,
+ })
+ assert.NoError(t, err)
+
+ req, err := http.NewRequest("POST", baseURL+"/results/123/export", bytes.NewReader(payload))
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := exportResultsRequest("invalid_token", "123", map[string]interface{}{
+ "Page": 1,
+ "Limit": 10,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestExportResults_InputValidation(t *testing.T) {
+ // Создаем квиз для тестирования
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для валидации экспорта",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ t.Run("InvalidDateFormat", func(t *testing.T) {
+ resp, err := exportResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "From": "not-a-date",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidQuizID", func(t *testing.T) {
+ resp, err := exportResultsRequest(validToken, "invalid-quiz-id", map[string]interface{}{
+ "Page": 1,
+ "Limit": 10,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("NonExistentQuizID", func(t *testing.T) {
+ resp, err := exportResultsRequest(validToken, "99999", map[string]interface{}{
+ "Page": 1,
+ "Limit": 10,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode) // Возвращает пустой файл
+ })
+}
+
+func TestExportResults_Performance(t *testing.T) {
+ // Создаем квиз для тестирования производительности
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для теста производительности экспорта",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := exportResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "Page": 1,
+ "Limit": 100,
+ })
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(2000)) // Экспорт может занимать больше времени
+ })
+
+ t.Run("LargeExport", func(t *testing.T) {
+ resp, err := exportResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "Page": 1,
+ "Limit": 1000,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ })
+}
+
+// todo 30.3.6 30.3.7 30.4 30.5
+
+func getResultRequest(token string, resultID string) (*http.Response, error) {
+ req, err := http.NewRequest("GET", baseURL+"/result/"+resultID, nil)
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestGetResult_Success(t *testing.T) {
+ // Сначала создаем квиз и получаем результаты
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для получения результата",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Получаем результаты квиза
+ getResultsResp, err := getResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "Page": 1,
+ "Limit": 5,
+ })
+ assert.NoError(t, err)
+ defer getResultsResp.Body.Close()
+
+ var resultsData map[string]interface{}
+ err = json.NewDecoder(getResultsResp.Body).Decode(&resultsData)
+ assert.NoError(t, err)
+ results := resultsData["results"].([]interface{})
+
+ // Если есть результаты, получаем детали первого
+ if len(results) > 0 {
+ firstResult := results[0].(map[string]interface{})
+ resultID := fmt.Sprintf("%v", firstResult["id"])
+
+ // Получаем детали результата
+ resp, err := getResultRequest(validToken, resultID)
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var answers []map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&answers)
+ assert.NoError(t, err)
+
+ // Проверяем структуру ответа
+ if len(answers) > 0 {
+ answer := answers[0]
+ assert.NotEmpty(t, answer["Id"])
+ assert.NotEmpty(t, answer["content"])
+ assert.NotEmpty(t, answer["question_id"])
+ assert.NotEmpty(t, answer["QuizId"])
+ assert.NotEmpty(t, answer["CreatedAt"])
+ assert.IsType(t, false, answer["Result"])
+ assert.IsType(t, false, answer["new"])
+ assert.IsType(t, false, answer["Deleted"])
+ assert.IsType(t, false, answer["Start"])
+ }
+ }
+}
+
+func TestGetResult_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ req, err := http.NewRequest("GET", baseURL+"/result/abc123xyz", nil)
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := getResultRequest("invalid_token", "abc123xyz")
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestGetResult_InputValidation(t *testing.T) {
+ t.Run("EmptyResultID", func(t *testing.T) {
+ resp, err := getResultRequest(validToken, "")
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("NonExistentResultID", func(t *testing.T) {
+ resp, err := getResultRequest(validToken, "nonexistent123")
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var answers []map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&answers)
+ assert.NoError(t, err)
+ assert.Empty(t, answers)
+ })
+
+ t.Run("InvalidResultID", func(t *testing.T) {
+ resp, err := getResultRequest(validToken, "invalid-result-id!")
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+}
+
+func TestGetResult_Performance(t *testing.T) {
+ // Создаем квиз и получаем результат для тестирования производительности
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для теста производительности результата",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Получаем результаты
+ getResultsResp, err := getResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "Page": 1,
+ "Limit": 1,
+ })
+ assert.NoError(t, err)
+ defer getResultsResp.Body.Close()
+
+ var resultsData map[string]interface{}
+ err = json.NewDecoder(getResultsResp.Body).Decode(&resultsData)
+ assert.NoError(t, err)
+ results := resultsData["results"].([]interface{})
+
+ if len(results) > 0 {
+ firstResult := results[0].(map[string]interface{})
+ resultID := fmt.Sprintf("%v", firstResult["id"])
+
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := getResultRequest(validToken, resultID)
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(500))
+ })
+ }
+}
+
+// todo 31.3.3 31.4 31.5
+
+func getDeviceStatsRequest(token string, quizID string, body map[string]interface{}) (*http.Response, error) {
+ payload, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ req, err := http.NewRequest("POST", baseURL+"/statistic/"+quizID+"/devices", bytes.NewReader(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestGetDeviceStats_Success(t *testing.T) {
+ // Сначала создаем квиз
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для статистики устройств",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Получаем статистику по устройствам
+ resp, err := getDeviceStatsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "From": 1700000000,
+ "To": 1709999999,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ // Проверяем структуру ответа
+ assert.NotNil(t, result["Device"])
+ assert.NotNil(t, result["OS"])
+ assert.NotNil(t, result["Browser"])
+
+ // Проверяем, что это словари с процентным распределением
+ deviceStats := result["Device"].(map[string]interface{})
+ osStats := result["OS"].(map[string]interface{})
+ browserStats := result["Browser"].(map[string]interface{})
+
+ // Проверяем, что значения являются числами (процентами)
+ for _, value := range deviceStats {
+ assert.IsType(t, float64(0), value)
+ }
+ for _, value := range osStats {
+ assert.IsType(t, float64(0), value)
+ }
+ for _, value := range browserStats {
+ assert.IsType(t, float64(0), value)
+ }
+}
+
+func TestGetDeviceStats_WithoutDateRange(t *testing.T) {
+ // Создаем квиз
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для статистики без диапазона дат",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Получаем статистику без фильтрации по датам
+ resp, err := getDeviceStatsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{})
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.NotNil(t, result["Device"])
+ assert.NotNil(t, result["OS"])
+ assert.NotNil(t, result["Browser"])
+}
+
+func TestGetDeviceStats_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ payload, err := json.Marshal(map[string]interface{}{
+ "From": 1700000000,
+ "To": 1709999999,
+ })
+ assert.NoError(t, err)
+
+ req, err := http.NewRequest("POST", baseURL+"/statistic/12345/devices", bytes.NewReader(payload))
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := getDeviceStatsRequest("invalid_token", "12345", map[string]interface{}{
+ "From": 1700000000,
+ "To": 1709999999,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestGetDeviceStats_InputValidation(t *testing.T) {
+ // Создаем квиз для тестирования
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для валидации статистики устройств",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ t.Run("InvalidDateFormat", func(t *testing.T) {
+ resp, err := getDeviceStatsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "From": "not_a_timestamp",
+ "To": 1709999999,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidQuizID", func(t *testing.T) {
+ resp, err := getDeviceStatsRequest(validToken, "invalid-quiz-id", map[string]interface{}{
+ "From": 1700000000,
+ "To": 1709999999,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("NonExistentQuizID", func(t *testing.T) {
+ resp, err := getDeviceStatsRequest(validToken, "99999", map[string]interface{}{
+ "From": 1700000000,
+ "To": 1709999999,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ // Проверяем, что возвращаются пустые статистики
+ deviceStats := result["Device"].(map[string]interface{})
+ osStats := result["OS"].(map[string]interface{})
+ browserStats := result["Browser"].(map[string]interface{})
+ assert.Empty(t, deviceStats)
+ assert.Empty(t, osStats)
+ assert.Empty(t, browserStats)
+ })
+}
+
+func TestGetDeviceStats_Performance(t *testing.T) {
+ // Создаем квиз для тестирования производительности
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для теста производительности статистики устройств",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := getDeviceStatsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "From": 1700000000,
+ "To": 1709999999,
+ })
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(500))
+ })
+
+ t.Run("ConcurrentRequests", func(t *testing.T) {
+ var wg sync.WaitGroup
+ for i := 0; i < 10; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ resp, err := getDeviceStatsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "From": 1700000000,
+ "To": 1709999999,
+ })
+ if err == nil && resp != nil {
+ resp.Body.Close()
+ }
+ }()
+ }
+ wg.Wait()
+ })
+}
+
+// todo 32.3.2 32.3.3 32.4 32.5
+
+func getGeneralStatsRequest(token string, quizID string, body map[string]interface{}) (*http.Response, error) {
+ payload, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ req, err := http.NewRequest("POST", baseURL+"/statistic/"+quizID+"/general", bytes.NewReader(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestGetGeneralStats_Success(t *testing.T) {
+ // Сначала создаем квиз
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для общей статистики",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Получаем общую статистику
+ resp, err := getGeneralStatsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "From": 1700000000,
+ "To": 1709999999,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ // Проверяем структуру ответа
+ assert.NotNil(t, result["Open"])
+ assert.NotNil(t, result["Result"])
+ assert.NotNil(t, result["AvTime"])
+ assert.NotNil(t, result["Conversion"])
+
+ // Проверяем, что это объекты с временными метками
+ openStats := result["Open"].(map[string]interface{})
+ resultStats := result["Result"].(map[string]interface{})
+ avTimeStats := result["AvTime"].(map[string]interface{})
+ conversionStats := result["Conversion"].(map[string]interface{})
+
+ // Проверяем типы значений
+ for _, value := range openStats {
+ assert.IsType(t, float64(0), value)
+ assert.GreaterOrEqual(t, value.(float64), float64(0))
+ }
+ for _, value := range resultStats {
+ assert.IsType(t, float64(0), value)
+ assert.GreaterOrEqual(t, value.(float64), float64(0))
+ }
+ for _, value := range avTimeStats {
+ assert.IsType(t, float64(0), value)
+ assert.GreaterOrEqual(t, value.(float64), float64(0))
+ }
+ for _, value := range conversionStats {
+ assert.IsType(t, float64(0), value)
+ assert.GreaterOrEqual(t, value.(float64), float64(0))
+ assert.LessOrEqual(t, value.(float64), float64(100))
+ }
+}
+
+func TestGetGeneralStats_WithoutDateRange(t *testing.T) {
+ // Создаем квиз
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для общей статистики без диапазона",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Получаем общую статистику без фильтрации по датам
+ resp, err := getGeneralStatsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{})
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.NotNil(t, result["Open"])
+ assert.NotNil(t, result["Result"])
+ assert.NotNil(t, result["AvTime"])
+ assert.NotNil(t, result["Conversion"])
+}
+
+func TestGetGeneralStats_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ payload, err := json.Marshal(map[string]interface{}{
+ "From": 1700000000,
+ "To": 1709999999,
+ })
+ assert.NoError(t, err)
+
+ req, err := http.NewRequest("POST", baseURL+"/statistic/12345/general", bytes.NewReader(payload))
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := getGeneralStatsRequest("invalid_token", "12345", map[string]interface{}{
+ "From": 1700000000,
+ "To": 1709999999,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestGetGeneralStats_InputValidation(t *testing.T) {
+ // Создаем квиз для тестирования
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для валидации общей статистики",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ t.Run("InvalidDateFormat", func(t *testing.T) {
+ resp, err := getGeneralStatsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "From": "not_a_timestamp",
+ "To": 1709999999,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidQuizID", func(t *testing.T) {
+ resp, err := getGeneralStatsRequest(validToken, "invalid-quiz-id", map[string]interface{}{
+ "From": 1700000000,
+ "To": 1709999999,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("NonExistentQuizID", func(t *testing.T) {
+ resp, err := getGeneralStatsRequest(validToken, "99999", map[string]interface{}{
+ "From": 1700000000,
+ "To": 1709999999,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ // Проверяем, что возвращаются пустые статистики
+ openStats := result["Open"].(map[string]interface{})
+ resultStats := result["Result"].(map[string]interface{})
+ avTimeStats := result["AvTime"].(map[string]interface{})
+ conversionStats := result["Conversion"].(map[string]interface{})
+ assert.Empty(t, openStats)
+ assert.Empty(t, resultStats)
+ assert.Empty(t, avTimeStats)
+ assert.Empty(t, conversionStats)
+ })
+}
+
+func TestGetGeneralStats_Performance(t *testing.T) {
+ // Создаем квиз для тестирования производительности
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для теста производительности общей статистики",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := getGeneralStatsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "From": 1700000000,
+ "To": 1709999999,
+ })
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(500))
+ })
+
+ t.Run("ConcurrentRequests", func(t *testing.T) {
+ var wg sync.WaitGroup
+ for i := 0; i < 10; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ resp, err := getGeneralStatsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "From": 1700000000,
+ "To": 1709999999,
+ })
+ if err == nil && resp != nil {
+ resp.Body.Close()
+ }
+ }()
+ }
+ wg.Wait()
+ })
+}
+
+// todo 33.3.2 33.3.3 33.4 33.5
+
+func getQuestionStatsRequest(token string, quizID string, body map[string]interface{}) (*http.Response, error) {
+ payload, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ req, err := http.NewRequest("POST", baseURL+"/statistic/"+quizID+"/questions", bytes.NewReader(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestGetQuestionStats_Success(t *testing.T) {
+ // Сначала создаем квиз
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для статистики вопросов",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Получаем статистику по вопросам
+ resp, err := getQuestionStatsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "From": 1700000000,
+ "To": 1709999999,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result []map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ // Проверяем, что это массив объектов QuestionStat
+ assert.NotEmpty(t, result)
+
+ if len(result) > 0 {
+ questionStat := result[0]
+
+ // Проверяем структуру ответа
+ assert.NotNil(t, questionStat["Funnel"])
+ assert.NotNil(t, questionStat["FunnelData"])
+ assert.NotNil(t, questionStat["Results"])
+ assert.NotNil(t, questionStat["Questions"])
+
+ // Проверяем типы данных
+ funnel := questionStat["Funnel"].([]interface{})
+ funnelData := questionStat["FunnelData"].([]interface{})
+ results := questionStat["Results"].(map[string]interface{})
+ questions := questionStat["Questions"].(map[string]interface{})
+
+ // Проверяем ограничения на размеры массивов
+ assert.LessOrEqual(t, len(funnel), 3)
+ assert.LessOrEqual(t, len(funnelData), 4)
+
+ // Проверяем типы значений в Funnel
+ for _, value := range funnel {
+ assert.IsType(t, float64(0), value)
+ assert.GreaterOrEqual(t, value.(float64), float64(0))
+ assert.LessOrEqual(t, value.(float64), float64(100))
+ }
+
+ // Проверяем типы значений в FunnelData
+ for _, value := range funnelData {
+ assert.IsType(t, float64(0), value)
+ assert.GreaterOrEqual(t, value.(float64), float64(0))
+ }
+
+ // Проверяем типы значений в Results
+ for _, value := range results {
+ assert.IsType(t, float64(0), value)
+ assert.GreaterOrEqual(t, value.(float64), float64(0))
+ assert.LessOrEqual(t, value.(float64), float64(100))
+ }
+
+ // Проверяем типы значений в Questions
+ for _, questionData := range questions {
+ questionMap := questionData.(map[string]interface{})
+ for _, percentage := range questionMap {
+ assert.IsType(t, float64(0), percentage)
+ assert.GreaterOrEqual(t, percentage.(float64), float64(0))
+ assert.LessOrEqual(t, percentage.(float64), float64(100))
+ }
+ }
+ }
+}
+
+func TestGetQuestionStats_WithoutDateRange(t *testing.T) {
+ // Создаем квиз
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для статистики вопросов без диапазона",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ // Получаем статистику по вопросам без фильтрации по датам
+ resp, err := getQuestionStatsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{})
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result []map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.NotEmpty(t, result)
+}
+
+func TestGetQuestionStats_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ payload, err := json.Marshal(map[string]interface{}{
+ "From": 1700000000,
+ "To": 1709999999,
+ })
+ assert.NoError(t, err)
+
+ req, err := http.NewRequest("POST", baseURL+"/statistic/12345/questions", bytes.NewReader(payload))
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := getQuestionStatsRequest("invalid_token", "12345", map[string]interface{}{
+ "From": 1700000000,
+ "To": 1709999999,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestGetQuestionStats_InputValidation(t *testing.T) {
+ // Создаем квиз для тестирования
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для валидации статистики вопросов",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ t.Run("InvalidDateFormat", func(t *testing.T) {
+ resp, err := getQuestionStatsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "From": "not_a_timestamp",
+ "To": 1709999999,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidQuizID", func(t *testing.T) {
+ resp, err := getQuestionStatsRequest(validToken, "invalid-quiz-id", map[string]interface{}{
+ "From": 1700000000,
+ "To": 1709999999,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("NonExistentQuizID", func(t *testing.T) {
+ resp, err := getQuestionStatsRequest(validToken, "99999", map[string]interface{}{
+ "From": 1700000000,
+ "To": 1709999999,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var result []map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+ assert.NotEmpty(t, result)
+
+ // Проверяем, что возвращаются пустые статистики
+ if len(result) > 0 {
+ questionStat := result[0]
+ funnel := questionStat["Funnel"].([]interface{})
+ funnelData := questionStat["FunnelData"].([]interface{})
+ results := questionStat["Results"].(map[string]interface{})
+ questions := questionStat["Questions"].(map[string]interface{})
+ assert.Empty(t, funnel)
+ assert.Empty(t, funnelData)
+ assert.Empty(t, results)
+ assert.Empty(t, questions)
+ }
+ })
+}
+
+func TestGetQuestionStats_Performance(t *testing.T) {
+ // Создаем квиз для тестирования производительности
+ createResp, err := createQuizRequest(validToken, map[string]interface{}{
+ "name": "Квиз для теста производительности статистики вопросов",
+ "status": "start",
+ })
+ assert.NoError(t, err)
+ defer createResp.Body.Close()
+ var createResult map[string]interface{}
+ err = json.NewDecoder(createResp.Body).Decode(&createResult)
+ assert.NoError(t, err)
+ quizID := createResult["id"]
+
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := getQuestionStatsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "From": 1700000000,
+ "To": 1709999999,
+ })
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(500))
+ })
+
+ t.Run("ConcurrentRequests", func(t *testing.T) {
+ var wg sync.WaitGroup
+ for i := 0; i < 10; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ resp, err := getQuestionStatsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
+ "From": 1700000000,
+ "To": 1709999999,
+ })
+ if err == nil && resp != nil {
+ resp.Body.Close()
+ }
+ }()
+ }
+ wg.Wait()
+ })
+}
+
+// todo 34.4.2 34.4.3 34.4.4 34.5
+
+// todo ПРОПУСК 35 36 тест кесов
+
+func getTelegramPoolRequest(token string) (*http.Response, error) {
+ req, err := http.NewRequest("GET", baseURL+"/telegram/pool", nil)
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestGetTelegramPool_Success(t *testing.T) {
+ resp, err := getTelegramPoolRequest(validToken)
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var accounts []map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&accounts)
+ assert.NoError(t, err)
+
+ // Проверяем структуру каждого аккаунта
+ for _, account := range accounts {
+ // Проверяем обязательные поля
+ assert.NotEmpty(t, account["ID"])
+ assert.NotEmpty(t, account["ApiID"])
+ assert.NotEmpty(t, account["ApiHash"])
+ assert.NotEmpty(t, account["PhoneNumber"])
+ assert.NotEmpty(t, account["Status"])
+ assert.NotEmpty(t, account["CreatedAt"])
+
+ // Проверяем типы данных
+ assert.IsType(t, float64(0), account["ID"])
+ assert.IsType(t, float64(0), account["ApiID"])
+ assert.IsType(t, "", account["ApiHash"])
+ assert.IsType(t, "", account["PhoneNumber"])
+ assert.IsType(t, "", account["Status"])
+ assert.IsType(t, "", account["CreatedAt"])
+ assert.IsType(t, false, account["Deleted"])
+
+ // Проверяем, что аккаунт не удален
+ assert.Equal(t, false, account["Deleted"])
+
+ // Проверяем, что статус соответствует допустимым значениям
+ status := account["Status"].(string)
+ assert.Contains(t, []string{"active", "inactive", "ban"}, status)
+
+ // Проверяем формат даты (ISO 8601)
+ createdAt := account["CreatedAt"].(string)
+ _, err := time.Parse(time.RFC3339, createdAt)
+ assert.NoError(t, err)
+ }
+}
+
+func TestGetTelegramPool_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ req, err := http.NewRequest("GET", baseURL+"/telegram/pool", nil)
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := getTelegramPoolRequest("invalid_token")
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("ExpiredToken", func(t *testing.T) {
+ resp, err := getTelegramPoolRequest(expiredToken)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestGetTelegramPool_Security(t *testing.T) {
+ t.Run("SQLInjectionAttempt", func(t *testing.T) {
+ resp, err := getTelegramPoolRequest("' OR 1=1 --")
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("XSSAttempt", func(t *testing.T) {
+ resp, err := getTelegramPoolRequest("")
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestGetTelegramPool_Performance(t *testing.T) {
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := getTelegramPoolRequest(validToken)
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(1000)) // Менее 1 секунды
+ })
+
+ t.Run("ConcurrentRequests", func(t *testing.T) {
+ var wg sync.WaitGroup
+ for i := 0; i < 10; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ resp, err := getTelegramPoolRequest(validToken)
+ if err == nil && resp != nil {
+ resp.Body.Close()
+ }
+ }()
+ }
+ wg.Wait()
+ })
+}
+
+// todo 37.3.3 37.3.4 37.3.5 37.4 37.5
+
+func createTelegramRequest(token string, body map[string]interface{}) (*http.Response, error) {
+ payload, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ req, err := http.NewRequest("POST", baseURL+"/telegram/create", bytes.NewReader(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestCreateTelegram_Success(t *testing.T) {
+ resp, err := createTelegramRequest(validToken, map[string]interface{}{
+ "api_id": 123456,
+ "api_hash": "abcdef1234567890abcdef1234567890",
+ "phone_number": "+12345678901",
+ "password": "secure_password",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ // Проверяем, что возвращается signature
+ assert.NotEmpty(t, result["signature"])
+ assert.IsType(t, "", result["signature"])
+}
+
+func TestCreateTelegram_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ payload, err := json.Marshal(map[string]interface{}{
+ "api_id": 123456,
+ "api_hash": "abcdef1234567890abcdef1234567890",
+ "phone_number": "+12345678901",
+ "password": "secure_password",
+ })
+ assert.NoError(t, err)
+
+ req, err := http.NewRequest("POST", baseURL+"/telegram/create", bytes.NewReader(payload))
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := createTelegramRequest("invalid_token", map[string]interface{}{
+ "api_id": 123456,
+ "api_hash": "abcdef1234567890abcdef1234567890",
+ "phone_number": "+12345678901",
+ "password": "secure_password",
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestCreateTelegram_InputValidation(t *testing.T) {
+ t.Run("MissingApiID", func(t *testing.T) {
+ resp, err := createTelegramRequest(validToken, map[string]interface{}{
+ "api_hash": "abcdef1234567890abcdef1234567890",
+ "phone_number": "+12345678901",
+ "password": "secure_password",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("MissingApiHash", func(t *testing.T) {
+ resp, err := createTelegramRequest(validToken, map[string]interface{}{
+ "api_id": 123456,
+ "phone_number": "+12345678901",
+ "password": "secure_password",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("MissingPhoneNumber", func(t *testing.T) {
+ resp, err := createTelegramRequest(validToken, map[string]interface{}{
+ "api_id": 123456,
+ "api_hash": "abcdef1234567890abcdef1234567890",
+ "password": "secure_password",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidApiID", func(t *testing.T) {
+ resp, err := createTelegramRequest(validToken, map[string]interface{}{
+ "api_id": "not_a_number",
+ "api_hash": "abcdef1234567890abcdef1234567890",
+ "phone_number": "+12345678901",
+ "password": "secure_password",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidPhoneNumber", func(t *testing.T) {
+ resp, err := createTelegramRequest(validToken, map[string]interface{}{
+ "api_id": 123456,
+ "api_hash": "abcdef1234567890abcdef1234567890",
+ "phone_number": "invalid_phone",
+ "password": "secure_password",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+}
+
+func TestCreateTelegram_Performance(t *testing.T) {
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := createTelegramRequest(validToken, map[string]interface{}{
+ "api_id": 123456,
+ "api_hash": "abcdef1234567890abcdef1234567890",
+ "phone_number": "+12345678901",
+ "password": "secure_password",
+ })
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(500))
+ })
+}
+
+// todo 38.3.2 38.3.3 38.4 38.5
+
+func deleteTelegramRequest(token string, id string) (*http.Response, error) {
+ req, err := http.NewRequest("DELETE", baseURL+"/telegram/"+id, nil)
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestDeleteTelegram_Success(t *testing.T) {
+ // Сначала получаем список аккаунтов, чтобы найти существующий ID
+ poolResp, err := getTelegramPoolRequest(validToken)
+ assert.NoError(t, err)
+ defer poolResp.Body.Close()
+
+ var accounts []map[string]interface{}
+ err = json.NewDecoder(poolResp.Body).Decode(&accounts)
+ assert.NoError(t, err)
+
+ // Если есть аккаунты, удаляем первый
+ if len(accounts) > 0 {
+ firstAccount := accounts[0]
+ accountID := fmt.Sprintf("%v", firstAccount["ID"])
+
+ // Удаляем аккаунт
+ resp, err := deleteTelegramRequest(validToken, accountID)
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ }
+}
+
+func TestDeleteTelegram_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ req, err := http.NewRequest("DELETE", baseURL+"/telegram/123", nil)
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := deleteTelegramRequest("invalid_token", "123")
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("ExpiredToken", func(t *testing.T) {
+ resp, err := deleteTelegramRequest(expiredToken, "123")
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestDeleteTelegram_InputValidation(t *testing.T) {
+ t.Run("NonExistentID", func(t *testing.T) {
+ resp, err := deleteTelegramRequest(validToken, "99999")
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidID", func(t *testing.T) {
+ resp, err := deleteTelegramRequest(validToken, "!@#")
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("EmptyID", func(t *testing.T) {
+ resp, err := deleteTelegramRequest(validToken, "")
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+}
+
+func TestDeleteTelegram_Security(t *testing.T) {
+ t.Run("SQLInjectionAttempt", func(t *testing.T) {
+ resp, err := deleteTelegramRequest(validToken, "' OR 1=1 --")
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("XSSAttempt", func(t *testing.T) {
+ resp, err := deleteTelegramRequest(validToken, "")
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+}
+
+func TestDeleteTelegram_Performance(t *testing.T) {
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := deleteTelegramRequest(validToken, "123")
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(500))
+ })
+}
+
+// todo 39.3.4 39.3.5 39.4 39.5
+
+func setTelegramCodeRequest(token string, body map[string]interface{}) (*http.Response, error) {
+ payload, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ req, err := http.NewRequest("POST", baseURL+"/telegram/setCode", bytes.NewReader(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ return http.DefaultClient.Do(req)
+}
+
+func TestSetTelegramCode_Success(t *testing.T) {
+ resp, err := setTelegramCodeRequest(validToken, map[string]interface{}{
+ "code": "123456",
+ "signature": "abc123signature",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
+
+ var result map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ assert.NoError(t, err)
+
+ // Проверяем, что возвращается ID
+ assert.NotEmpty(t, result["id"])
+ assert.IsType(t, float64(0), result["id"])
+}
+
+func TestSetTelegramCode_Auth(t *testing.T) {
+ t.Run("NoToken", func(t *testing.T) {
+ payload, err := json.Marshal(map[string]interface{}{
+ "code": "123456",
+ "signature": "abc123signature",
+ })
+ assert.NoError(t, err)
+
+ req, err := http.NewRequest("POST", baseURL+"/telegram/setCode", bytes.NewReader(payload))
+ assert.NoError(t, err)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ resp, err := setTelegramCodeRequest("invalid_token", map[string]interface{}{
+ "code": "123456",
+ "signature": "abc123signature",
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("ExpiredToken", func(t *testing.T) {
+ resp, err := setTelegramCodeRequest(expiredToken, map[string]interface{}{
+ "code": "123456",
+ "signature": "abc123signature",
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+func TestSetTelegramCode_InputValidation(t *testing.T) {
+ t.Run("MissingCode", func(t *testing.T) {
+ resp, err := setTelegramCodeRequest(validToken, map[string]interface{}{
+ "signature": "abc123signature",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("MissingSignature", func(t *testing.T) {
+ resp, err := setTelegramCodeRequest(validToken, map[string]interface{}{
+ "code": "123456",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidCodeType", func(t *testing.T) {
+ resp, err := setTelegramCodeRequest(validToken, map[string]interface{}{
+ "code": 123456,
+ "signature": "abc123signature",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InvalidSignatureType", func(t *testing.T) {
+ resp, err := setTelegramCodeRequest(validToken, map[string]interface{}{
+ "code": "123456",
+ "signature": 12345,
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+}
+
+func TestSetTelegramCode_Security(t *testing.T) {
+ t.Run("XSSInCode", func(t *testing.T) {
+ resp, err := setTelegramCodeRequest(validToken, map[string]interface{}{
+ "code": "",
+ "signature": "abc123signature",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("SQLInjectionInCode", func(t *testing.T) {
+ resp, err := setTelegramCodeRequest(validToken, map[string]interface{}{
+ "code": "' OR 1=1 --",
+ "signature": "abc123signature",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("XSSInSignature", func(t *testing.T) {
+ resp, err := setTelegramCodeRequest(validToken, map[string]interface{}{
+ "code": "123456",
+ "signature": "",
+ })
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+}
+
+func TestSetTelegramCode_Performance(t *testing.T) {
+ t.Run("ResponseTime", func(t *testing.T) {
+ start := time.Now()
+ resp, err := setTelegramCodeRequest(validToken, map[string]interface{}{
+ "code": "123456",
+ "signature": "abc123signature",
+ })
+ duration := time.Since(start)
+
+ assert.NoError(t, err)
+ defer resp.Body.Close()
+ assert.Less(t, duration.Milliseconds(), int64(500))
+ })
+
+ t.Run("ConcurrentRequests", func(t *testing.T) {
+ var wg sync.WaitGroup
+ for i := 0; i < 10; i++ {
+ wg.Add(1)
+ go func(index int) {
+ defer wg.Done()
+ resp, err := setTelegramCodeRequest(validToken, map[string]interface{}{
+ "code": fmt.Sprintf("12345%d", index),
+ "signature": fmt.Sprintf("signature%d", index),
+ })
+ if err == nil && resp != nil {
+ resp.Body.Close()
+ }
+ }(i)
+ }
+ wg.Wait()
+ })
+}
+
+// todo 40.4.4 40.4.5 40.4.6 40.5 40.6