8110 lines
242 KiB
Go
8110 lines
242 KiB
Go
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"
|
||
)
|
||
|
||
var baseURL = os.Getenv("API_BASE_URL")
|
||
var validToken = os.Getenv("VALID_JWT_TOKEN")
|
||
var expiredToken = os.Getenv("EXPIRED_JWT_TOKEN")
|
||
var validTokenForDelete = os.Getenv("VALID_JWT_TOKEN_FOR_DELETE")
|
||
var validAdminToken = os.Getenv("VALID_ADMIN_JWT_TOKEN")
|
||
var existingUserIDToken = os.Getenv("EXISTING_USER_ID_JWT_TOKEN")
|
||
|
||
var userIDForDelete = os.Getenv("USER_ID_FOR_DELETE")
|
||
var existingUserID = os.Getenv("EXISTING_USER_ID")
|
||
var testUserID = os.Getenv("TEST_USER_ID")
|
||
var sqlInjectionInput = "'; DROP TABLE accounts; --"
|
||
var xssInput = "<script>alert('xss')</script>"
|
||
|
||
func TestGetAccount_Success(t *testing.T) {
|
||
req, err := http.NewRequest("GET", baseURL+"/account/get", nil)
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+validToken)
|
||
|
||
client := &http.Client{Timeout: 5 * time.Second}
|
||
resp, err := client.Do(req)
|
||
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 acc model.Account
|
||
err = json.NewDecoder(resp.Body).Decode(&acc)
|
||
assert.NoError(t, err)
|
||
assert.NotEmpty(t, acc.ID)
|
||
assert.NotEmpty(t, acc.UserID)
|
||
assert.IsType(t, map[string]interface{}{}, acc.Privileges)
|
||
}
|
||
|
||
func TestGetAccount_Auth(t *testing.T) {
|
||
t.Run("AccountNoToken", func(t *testing.T) {
|
||
resp, err := http.Get(baseURL + "/account/get")
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
||
})
|
||
|
||
t.Run("AccountInvalidToken", func(t *testing.T) {
|
||
req, err := http.NewRequest("GET", baseURL+"/account/get", nil)
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer invalid_token")
|
||
resp, err := http.DefaultClient.Do(req)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
||
})
|
||
|
||
t.Run("AccountExpiredToken", func(t *testing.T) {
|
||
req, err := http.NewRequest("GET", baseURL+"/account/get", nil)
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+expiredToken)
|
||
resp, err := http.DefaultClient.Do(req)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
||
})
|
||
}
|
||
|
||
func TestGetAccount_NotFound(t *testing.T) {
|
||
t.Run("DeletedAccount", func(t *testing.T) {
|
||
req, err := http.NewRequest("GET", baseURL+"/account/get", nil)
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+os.Getenv("DELETED_ACCOUNT_TOKEN"))
|
||
resp, err := http.DefaultClient.Do(req)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
|
||
})
|
||
}
|
||
|
||
func TestGetAccount_Privileges(t *testing.T) {
|
||
t.Run("NoPrivileges", func(t *testing.T) {
|
||
req, err := http.NewRequest("GET", baseURL+"/account/get", nil)
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+os.Getenv("NO_PRIVILEGES_TOKEN"))
|
||
resp, err := http.DefaultClient.Do(req)
|
||
assert.NoError(t, err)
|
||
defer resp.Body.Close()
|
||
|
||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||
|
||
var acc model.Account
|
||
err = json.NewDecoder(resp.Body).Decode(&acc)
|
||
assert.NoError(t, err)
|
||
assert.Empty(t, acc.Privileges)
|
||
})
|
||
|
||
t.Run("MultiplePrivileges", func(t *testing.T) {
|
||
req, err := http.NewRequest("GET", baseURL+"/account/get", nil)
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+os.Getenv("MULTI_PRIVILEGES_TOKEN"))
|
||
resp, err := http.DefaultClient.Do(req)
|
||
assert.NoError(t, err)
|
||
defer resp.Body.Close()
|
||
|
||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||
|
||
var acc model.Account
|
||
err = json.NewDecoder(resp.Body).Decode(&acc)
|
||
assert.NoError(t, err)
|
||
assert.Greater(t, len(acc.Privileges), 1)
|
||
})
|
||
}
|
||
|
||
func TestAccount_Performance(t *testing.T) {
|
||
t.Run("AccountResponseTime", func(t *testing.T) {
|
||
start := time.Now()
|
||
req, err := http.NewRequest("GET", baseURL+"/account/get", nil)
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+validToken)
|
||
resp, err := http.DefaultClient.Do(req)
|
||
assert.NoError(t, err)
|
||
defer resp.Body.Close()
|
||
|
||
duration := time.Since(start)
|
||
assert.Less(t, duration.Milliseconds(), int64(500))
|
||
})
|
||
|
||
t.Run("AccountLoadTest", func(t *testing.T) {
|
||
var wg sync.WaitGroup
|
||
for i := 0; i < 100; i++ {
|
||
wg.Add(1)
|
||
go func() {
|
||
defer wg.Done()
|
||
req, err := http.NewRequest("GET", baseURL+"/account/get", nil)
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+validToken)
|
||
resp, err := http.DefaultClient.Do(req)
|
||
if err == nil {
|
||
defer resp.Body.Close()
|
||
}
|
||
}()
|
||
}
|
||
wg.Wait()
|
||
})
|
||
}
|
||
|
||
// todo 1.3.6 1.3.7 1.3.8. 1.4 пока не знаю как делать надо подумать
|
||
|
||
func TestCreateAccount(t *testing.T) {
|
||
t.Run("Success", func(t *testing.T) {
|
||
resp := createAccountRequest(t, validToken, map[string]interface{}{
|
||
"user_id": testUserID,
|
||
})
|
||
defer resp.Body.Close()
|
||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||
assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
|
||
})
|
||
|
||
t.Run("MissingToken", func(t *testing.T) {
|
||
req, err := http.NewRequest("POST", baseURL+"/account/create", 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 := createAccountRequest(t, expiredToken, map[string]interface{}{
|
||
"user_id": "some-id",
|
||
})
|
||
defer resp.Body.Close()
|
||
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
||
})
|
||
|
||
t.Run("ExpiredToken", func(t *testing.T) {
|
||
resp := createAccountRequest(t, expiredToken, map[string]interface{}{
|
||
"user_id": "some-id",
|
||
})
|
||
defer resp.Body.Close()
|
||
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
||
})
|
||
|
||
t.Run("Conflict_ExistingUserID", func(t *testing.T) {
|
||
resp := createAccountRequest(t, validToken, map[string]interface{}{
|
||
"user_id": existingUserID,
|
||
})
|
||
defer resp.Body.Close()
|
||
assert.Equal(t, http.StatusConflict, resp.StatusCode)
|
||
})
|
||
|
||
t.Run("EmptyJSON", func(t *testing.T) {
|
||
resp := createAccountRequest(t, validToken, map[string]interface{}{})
|
||
defer resp.Body.Close()
|
||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||
})
|
||
|
||
t.Run("InvalidFormat", func(t *testing.T) {
|
||
resp := createAccountRequest(t, validToken, map[string]interface{}{
|
||
"user_id": 123,
|
||
})
|
||
defer resp.Body.Close()
|
||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||
})
|
||
|
||
t.Run("SQLInjection", func(t *testing.T) {
|
||
resp := createAccountRequest(t, validToken, map[string]interface{}{
|
||
"user_id": sqlInjectionInput,
|
||
})
|
||
defer resp.Body.Close()
|
||
assert.NotEqual(t, http.StatusInternalServerError, resp.StatusCode)
|
||
})
|
||
|
||
t.Run("XSSInjection", func(t *testing.T) {
|
||
resp := createAccountRequest(t, validToken, map[string]interface{}{
|
||
"user_id": xssInput,
|
||
})
|
||
defer resp.Body.Close()
|
||
assert.NotEqual(t, http.StatusInternalServerError, resp.StatusCode)
|
||
})
|
||
|
||
// todo 2.3.6 2.3.7 2.3.8 2.4
|
||
}
|
||
|
||
func createAccountRequest(t *testing.T, token string, payload map[string]interface{}) *http.Response {
|
||
body, err := json.Marshal(payload)
|
||
assert.NoError(t, err)
|
||
req, err := http.NewRequest("POST", baseURL+"/account/create", bytes.NewBuffer(body))
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+token)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
|
||
resp, err := http.DefaultClient.Do(req)
|
||
assert.NoError(t, err)
|
||
return resp
|
||
}
|
||
|
||
func TestDeleteAccount_Success(t *testing.T) {
|
||
req, err := http.NewRequest("DELETE", baseURL+"/account/delete", nil)
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+validTokenForDelete)
|
||
|
||
client := &http.Client{Timeout: 5 * time.Second}
|
||
resp, err := client.Do(req)
|
||
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]string
|
||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||
assert.NoError(t, err)
|
||
assert.NotEmpty(t, result["accountId"])
|
||
}
|
||
|
||
func TestDeleteAccount_Auth(t *testing.T) {
|
||
t.Run("NoToken", func(t *testing.T) {
|
||
req, err := http.NewRequest("DELETE", baseURL+"/account/delete", 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) {
|
||
req, err := http.NewRequest("DELETE", baseURL+"/account/delete", nil)
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer invalid_token")
|
||
resp, err := http.DefaultClient.Do(req)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
||
})
|
||
|
||
t.Run("ExpiredToken", func(t *testing.T) {
|
||
req, err := http.NewRequest("DELETE", baseURL+"/account/delete", nil)
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+expiredToken)
|
||
resp, err := http.DefaultClient.Do(req)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
||
})
|
||
}
|
||
|
||
func TestDeleteAccount_AlreadyDeleted(t *testing.T) {
|
||
req, err := http.NewRequest("DELETE", baseURL+"/account/delete", nil)
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+validTokenForDelete)
|
||
resp, err := http.DefaultClient.Do(req)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||
}
|
||
|
||
func TestDeleteAccount_NonExistent(t *testing.T) {
|
||
req, err := http.NewRequest("DELETE", baseURL+"/account/delete", nil)
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+validTokenForDelete)
|
||
resp, err := http.DefaultClient.Do(req)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||
}
|
||
|
||
// todo 3.3.4 3.3.5
|
||
|
||
func TestDeleteAccount_Performance(t *testing.T) {
|
||
t.Run("ResponseTime", func(t *testing.T) {
|
||
req, _ := http.NewRequest("DELETE", baseURL+"/account/delete", nil)
|
||
req.Header.Set("Authorization", "Bearer "+validToken)
|
||
|
||
start := time.Now()
|
||
resp, err := http.DefaultClient.Do(req)
|
||
assert.NoError(t, err)
|
||
defer resp.Body.Close()
|
||
|
||
duration := time.Since(start)
|
||
assert.Less(t, duration.Milliseconds(), int64(500))
|
||
})
|
||
}
|
||
|
||
func TestDeleteAccount_Load(t *testing.T) {
|
||
var wg sync.WaitGroup
|
||
for i := 0; i < 50; i++ {
|
||
wg.Add(1)
|
||
go func() {
|
||
defer wg.Done()
|
||
req, err := http.NewRequest("DELETE", baseURL+"/account/delete", nil)
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+validToken)
|
||
resp, err := http.DefaultClient.Do(req)
|
||
assert.NoError(t, err)
|
||
if resp != nil {
|
||
defer resp.Body.Close()
|
||
}
|
||
}()
|
||
}
|
||
wg.Wait()
|
||
}
|
||
|
||
// todo 3.3.7 3.3.8 3.4
|
||
|
||
func TestGetAccounts_Success(t *testing.T) {
|
||
body := map[string]interface{}{
|
||
"limit": 10,
|
||
"page": 1,
|
||
}
|
||
b, err := json.Marshal(body)
|
||
assert.NoError(t, err)
|
||
|
||
req, err := http.NewRequest("GET", baseURL+"/accounts", bytes.NewReader(b))
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+validAdminToken)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
|
||
client := &http.Client{}
|
||
resp, err := client.Do(req)
|
||
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 struct {
|
||
Count uint64 `json:"count"`
|
||
Items []model.Account `json:"items"`
|
||
}
|
||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||
assert.NoError(t, err)
|
||
|
||
assert.LessOrEqual(t, len(result.Items), 2)
|
||
for _, acc := range result.Items {
|
||
assert.NotEmpty(t, acc.ID)
|
||
assert.NotEmpty(t, acc.UserID)
|
||
assert.NotEmpty(t, acc.CreatedAt)
|
||
}
|
||
}
|
||
|
||
func TestGetAccounts_Auth(t *testing.T) {
|
||
t.Run("NoToken", func(t *testing.T) {
|
||
body := map[string]interface{}{
|
||
"limit": 10,
|
||
"page": 1,
|
||
}
|
||
b, err := json.Marshal(body)
|
||
assert.NoError(t, err)
|
||
req, err := http.NewRequest("GET", baseURL+"/accounts", bytes.NewReader(b))
|
||
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) {
|
||
body := map[string]interface{}{
|
||
"limit": 10,
|
||
"page": 1,
|
||
}
|
||
b, err := json.Marshal(body)
|
||
assert.NoError(t, err)
|
||
req, err := http.NewRequest("GET", baseURL+"/accounts", bytes.NewReader(b))
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer invalid_token")
|
||
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("ExpiredToken", func(t *testing.T) {
|
||
body := map[string]interface{}{
|
||
"limit": 10,
|
||
"page": 1,
|
||
}
|
||
b, err := json.Marshal(body)
|
||
assert.NoError(t, err)
|
||
req, err := http.NewRequest("GET", baseURL+"/accounts", bytes.NewReader(b))
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+expiredToken)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
|
||
resp, err := http.DefaultClient.Do(req)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
||
})
|
||
}
|
||
|
||
func TestGetAccounts_Pagination(t *testing.T) {
|
||
t.Run("ValidPagination", func(t *testing.T) {
|
||
body := map[string]interface{}{"limit": 5, "page": 1}
|
||
b, err := json.Marshal(body)
|
||
assert.NoError(t, err)
|
||
req, err := http.NewRequest("GET", baseURL+"/accounts", bytes.NewReader(b))
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+validAdminToken)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
|
||
resp, err := http.DefaultClient.Do(req)
|
||
assert.NoError(t, err)
|
||
defer resp.Body.Close()
|
||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||
})
|
||
// todo
|
||
//t.Run("ZeroPagination", func(t *testing.T) {
|
||
// body := map[string]interface{}{"limit": 0, "page": 0}
|
||
// b, err := json.Marshal(body)
|
||
// assert.NoError(t, err)
|
||
// req, err := http.NewRequest("GET", baseURL+"/accounts", bytes.NewReader(b))
|
||
// assert.NoError(t, err)
|
||
// req.Header.Set("Authorization", "Bearer "+validAdminToken)
|
||
// req.Header.Set("Content-Type", "application/json")
|
||
//
|
||
// resp, err := http.DefaultClient.Do(req)
|
||
// assert.NoError(t, err)
|
||
// defer resp.Body.Close()
|
||
// assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||
//})
|
||
|
||
// todo
|
||
//t.Run("TooHighLimit", func(t *testing.T) {
|
||
// body := map[string]interface{}{"limit": 1000}
|
||
// b, err := json.Marshal(body)
|
||
// assert.NoError(t, err)
|
||
// req, err := http.NewRequest("GET", baseURL+"/accounts", bytes.NewReader(b))
|
||
// assert.NoError(t, err)
|
||
// req.Header.Set("Authorization", "Bearer "+validAdminToken)
|
||
// req.Header.Set("Content-Type", "application/json")
|
||
//
|
||
// resp, err := http.DefaultClient.Do(req)
|
||
// assert.NoError(t, err)
|
||
// defer resp.Body.Close()
|
||
// assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||
//})
|
||
}
|
||
|
||
// todo 4.3.4 4.3.5
|
||
|
||
func TestGetAccounts_Performance(t *testing.T) {
|
||
t.Run("ResponseTimeUnder500ms", func(t *testing.T) {
|
||
body := map[string]interface{}{"limit": 10, "page": 1}
|
||
b, err := json.Marshal(body)
|
||
assert.NoError(t, err)
|
||
req, err := http.NewRequest("GET", baseURL+"/accounts", bytes.NewReader(b))
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+validAdminToken)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
|
||
start := time.Now()
|
||
resp, err := http.DefaultClient.Do(req)
|
||
duration := time.Since(start)
|
||
|
||
assert.NoError(t, err)
|
||
defer resp.Body.Close()
|
||
assert.Less(t, duration.Milliseconds(), int64(500))
|
||
})
|
||
|
||
t.Run("LoadTest100Requests", func(t *testing.T) {
|
||
var wg sync.WaitGroup
|
||
for i := 0; i < 100; i++ {
|
||
wg.Add(1)
|
||
go func() {
|
||
defer wg.Done()
|
||
body := map[string]interface{}{"limit": 10, "page": 1}
|
||
b, err := json.Marshal(body)
|
||
assert.NoError(t, err)
|
||
req, err := http.NewRequest("GET", baseURL+"/accounts", bytes.NewReader(b))
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+validAdminToken)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
resp, err := http.DefaultClient.Do(req)
|
||
if err == nil {
|
||
resp.Body.Close()
|
||
}
|
||
}()
|
||
}
|
||
wg.Wait()
|
||
})
|
||
}
|
||
|
||
// todo 4.3.7 4.3.8 4.4
|
||
|
||
func TestGetPrivilege_Success(t *testing.T) {
|
||
body := map[string]string{"userId": existingUserID}
|
||
data, err := json.Marshal(body)
|
||
assert.NoError(t, err)
|
||
|
||
req, err := http.NewRequest("GET", baseURL+"/privilege/"+existingUserID, bytes.NewBuffer(data))
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+existingUserIDToken)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
|
||
resp, err := http.DefaultClient.Do(req)
|
||
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 privileges []model.ShortPrivilege
|
||
err = json.NewDecoder(resp.Body).Decode(&privileges)
|
||
assert.NoError(t, err)
|
||
|
||
for _, p := range privileges {
|
||
assert.NotEmpty(t, p.ID)
|
||
assert.NotEmpty(t, p.PrivilegeID)
|
||
assert.NotEmpty(t, p.PrivilegeName)
|
||
}
|
||
}
|
||
|
||
func TestGetPrivilege_Auth(t *testing.T) {
|
||
t.Run("NoToken", func(t *testing.T) {
|
||
req, err := http.NewRequest("GET", baseURL+"/privilege/"+existingUserID, 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) {
|
||
req, err := http.NewRequest("GET", baseURL+"/privilege/"+existingUserID, nil)
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer invalid_token")
|
||
resp, err := http.DefaultClient.Do(req)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
||
})
|
||
|
||
t.Run("ExpiredToken", func(t *testing.T) {
|
||
req, err := http.NewRequest("GET", baseURL+"/privilege/"+existingUserID, nil)
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+expiredToken)
|
||
resp, err := http.DefaultClient.Do(req)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
||
})
|
||
}
|
||
|
||
func TestGetPrivilege_InputValidation(t *testing.T) {
|
||
t.Run("MissingUserID", func(t *testing.T) {
|
||
req, err := http.NewRequest("GET", baseURL+"/privilege/", 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.StatusNotFound, resp.StatusCode)
|
||
})
|
||
|
||
t.Run("InvalidUserID", func(t *testing.T) {
|
||
body := map[string]int{"userId": 111}
|
||
data, err := json.Marshal(body)
|
||
assert.NoError(t, err)
|
||
req, err := http.NewRequest("GET", baseURL+"/privilege/!!!", bytes.NewBuffer(data))
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+validToken)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
resp, err := http.DefaultClient.Do(req)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||
})
|
||
|
||
t.Run("NonExistentUserID", func(t *testing.T) {
|
||
nonExistentID := "non_existent_user_123"
|
||
body := map[string]string{"userId": nonExistentID}
|
||
data, err := json.Marshal(body)
|
||
assert.NoError(t, err)
|
||
|
||
req, err := http.NewRequest("GET", baseURL+"/privilege/"+nonExistentID, bytes.NewBuffer(data))
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+validToken)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
|
||
resp, err := http.DefaultClient.Do(req)
|
||
assert.NoError(t, err)
|
||
defer resp.Body.Close()
|
||
|
||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||
|
||
var privileges []model.ShortPrivilege
|
||
err = json.NewDecoder(resp.Body).Decode(&privileges)
|
||
assert.NoError(t, err)
|
||
assert.Empty(t, privileges)
|
||
})
|
||
}
|
||
|
||
// todo 5.3.4
|
||
|
||
func TestGetPrivilege_Security(t *testing.T) {
|
||
t.Run("SQLInjection", func(t *testing.T) {
|
||
injection := "1' OR '1'='1"
|
||
body := map[string]string{"userId": injection}
|
||
data, err := json.Marshal(body)
|
||
assert.NoError(t, err)
|
||
req, err := http.NewRequest("GET", baseURL+"/privilege/"+injection, bytes.NewBuffer(data))
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+validToken)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
resp, err := http.DefaultClient.Do(req)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||
})
|
||
|
||
t.Run("XSS", func(t *testing.T) {
|
||
body := map[string]string{"userId": xssInput}
|
||
data, err := json.Marshal(body)
|
||
assert.NoError(t, err)
|
||
req, err := http.NewRequest("GET", baseURL+"/privilege/"+xssInput, bytes.NewBuffer(data))
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+validToken)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
resp, err := http.DefaultClient.Do(req)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||
})
|
||
}
|
||
|
||
func TestGetPrivilege_Performance(t *testing.T) {
|
||
t.Run("ResponseTime", func(t *testing.T) {
|
||
body := map[string]string{"userId": existingUserID}
|
||
data, err := json.Marshal(body)
|
||
assert.NoError(t, err)
|
||
start := time.Now()
|
||
req, err := http.NewRequest("GET", baseURL+"/privilege/"+existingUserID, bytes.NewBuffer(data))
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+existingUserIDToken)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
resp, err := http.DefaultClient.Do(req)
|
||
assert.NoError(t, err)
|
||
defer resp.Body.Close()
|
||
assert.Less(t, time.Since(start).Milliseconds(), int64(300))
|
||
})
|
||
|
||
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()
|
||
body := map[string]string{"userId": existingUserID}
|
||
data, err := json.Marshal(body)
|
||
assert.NoError(t, err)
|
||
req, err := http.NewRequest("GET", baseURL+"/privilege/"+existingUserID, bytes.NewBuffer(data))
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+existingUserIDToken)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
resp, err := http.DefaultClient.Do(req)
|
||
assert.NoError(t, err)
|
||
if resp != nil {
|
||
defer resp.Body.Close()
|
||
}
|
||
}()
|
||
}
|
||
wg.Wait()
|
||
})
|
||
}
|
||
|
||
// todo 5.3.7 5.3.8 5.4
|
||
|
||
func deleteAccountByUserIDRequest(token string, body interface{}) (*http.Response, error) {
|
||
payload, err := json.Marshal(body)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
req, err := http.NewRequest("DELETE", baseURL+"/account/"+body.(map[string]string)["userId"], 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 TestDeleteAccountByUserID_Success(t *testing.T) {
|
||
resp, err := deleteAccountByUserIDRequest(validAdminToken, map[string]string{"userId": userIDForDelete})
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||
assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
|
||
|
||
var result map[string]string
|
||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, testUserID, result["userId"])
|
||
}
|
||
|
||
func TestDeleteAccountByUserID_Auth(t *testing.T) {
|
||
t.Run("NoToken", func(t *testing.T) {
|
||
req, err := http.NewRequest("DELETE", baseURL+"/account/"+userIDForDelete, 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 := deleteAccountByUserIDRequest("invalid_token", map[string]string{"userId": userIDForDelete})
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
||
})
|
||
|
||
t.Run("ExpiredToken", func(t *testing.T) {
|
||
resp, err := deleteAccountByUserIDRequest(expiredToken, map[string]string{"userId": userIDForDelete})
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
||
})
|
||
}
|
||
|
||
func TestDeleteAccountByUserID_Validation(t *testing.T) {
|
||
t.Run("EmptyBody", func(t *testing.T) {
|
||
req, err := http.NewRequest("DELETE", baseURL+"/account/", bytes.NewReader([]byte(`{}`)))
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+validAdminToken)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
resp, err := http.DefaultClient.Do(req)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||
})
|
||
|
||
t.Run("InvalidUserID", func(t *testing.T) {
|
||
resp, err := deleteAccountByUserIDRequest(validAdminToken, map[string]string{"userId": "invalid_id"})
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||
})
|
||
}
|
||
|
||
// todo 6.3.4 6.3.5
|
||
|
||
func TestDeleteAccountByUserID_SQLInjection_XSS(t *testing.T) {
|
||
t.Run("SQLInjection", func(t *testing.T) {
|
||
resp, err := deleteAccountByUserIDRequest(validAdminToken, map[string]string{"userId": sqlInjectionInput})
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||
})
|
||
|
||
t.Run("XSS", func(t *testing.T) {
|
||
resp, err := deleteAccountByUserIDRequest(validAdminToken, map[string]string{"userId": xssInput})
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||
})
|
||
}
|
||
|
||
// todo 6.3.7 6.3.8 6.3.9 6.4
|
||
|
||
func manualDoneRequest(token string, body map[string]string) (*http.Response, error) {
|
||
payload, err := json.Marshal(body)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
req, err := http.NewRequest("POST", baseURL+"/account/manualdone", 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 TestManualDone_Success(t *testing.T) {
|
||
resp, err := manualDoneRequest(validAdminToken, map[string]string{"id": testUserID})
|
||
assert.NoError(t, err)
|
||
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, testUserID, result["id"])
|
||
}
|
||
|
||
func TestManualDone_Auth(t *testing.T) {
|
||
t.Run("NoToken", func(t *testing.T) {
|
||
payload, err := json.Marshal(map[string]string{"id": testUserID})
|
||
assert.NoError(t, err)
|
||
|
||
req, err := http.NewRequest("POST", baseURL+"/account/manualdone", 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 := manualDoneRequest("invalid_token", map[string]string{"id": testUserID})
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
||
})
|
||
|
||
t.Run("ExpiredToken", func(t *testing.T) {
|
||
resp, err := manualDoneRequest(expiredToken, map[string]string{"id": testUserID})
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
||
})
|
||
}
|
||
|
||
func TestManualDone_Validation(t *testing.T) {
|
||
t.Run("EmptyBody", func(t *testing.T) {
|
||
payload := []byte(`{}`)
|
||
|
||
req, err := http.NewRequest("POST", baseURL+"/account/manualdone", bytes.NewReader(payload))
|
||
assert.NoError(t, err)
|
||
req.Header.Set("Authorization", "Bearer "+validAdminToken)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
|
||
resp, err := http.DefaultClient.Do(req)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||
})
|
||
|
||
t.Run("InvalidID", func(t *testing.T) {
|
||
resp, err := manualDoneRequest(validAdminToken, map[string]string{"id": "invalid_id"})
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||
})
|
||
|
||
t.Run("NonExistentID", func(t *testing.T) {
|
||
resp, err := manualDoneRequest(validAdminToken, map[string]string{"id": "nonexistent_id"})
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
|
||
})
|
||
}
|
||
|
||
// todo 7.3.4 7.3.5
|
||
|
||
func TestManualDone_Security(t *testing.T) {
|
||
t.Run("SQLInjection", func(t *testing.T) {
|
||
resp, err := manualDoneRequest(validAdminToken, map[string]string{"id": "1' OR '1'='1"})
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||
})
|
||
|
||
t.Run("XSSAttack", func(t *testing.T) {
|
||
resp, err := manualDoneRequest(validAdminToken, map[string]string{"id": "<script>alert('xss')</script>"})
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||
})
|
||
}
|
||
|
||
// 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": "<script>alert('xss')</script>",
|
||
})
|
||
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": "<script>alert('xss')</script>",
|
||
})
|
||
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, "<script>alert('xss')</script>")
|
||
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, "<script>alert('xss')</script>")
|
||
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": "<script>alert('xss')</script>",
|
||
"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, "<script>alert('xss')</script>", 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": "<script>alert('xss')</script>",
|
||
})
|
||
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": "<script>alert('xss')</script>",
|
||
"description": "<img src=x onerror=alert('xss')>",
|
||
})
|
||
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, "<script>alert('xss')</script>", 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": "<script>alert('xss')</script>",
|
||
"desc": "<img src=x onerror=alert('xss')>",
|
||
"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("<script>alert('xss')</script>")
|
||
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, "<script>alert(1)</script>")
|
||
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": "<script>alert(1)</script>",
|
||
"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": "<script>alert(1)</script>",
|
||
})
|
||
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
|