diff --git a/tests/main_test.go b/tests/main_test.go new file mode 100644 index 0000000..76aefdd --- /dev/null +++ b/tests/main_test.go @@ -0,0 +1,240 @@ +package tests + +import ( + "bytes" + "encoding/json" + "gitea.pena/SQuiz/common/model" + "github.com/stretchr/testify/assert" + "io" + "net/http" + "os" + "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 existingUserID = os.Getenv("EXISTING_USER_ID") +var testUserID = os.Getenv("TEST_USER_ID") +var sqlInjectionInput = "'; DROP TABLE accounts; --" +var xssInput = "" + +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")) + + body, err := io.ReadAll(resp.Body) + assert.NoError(t, err) + var acc model.Account + err = json.Unmarshal(body, &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 +}