Files
15-puzzle/internal/web-service/handler/handler_test.go
T
Ilia Denisov 31957206b8 initial
2025-09-03 18:43:28 +03:00

209 lines
7.7 KiB
Go

package handler_test
import (
"15-puzzle/internal/model"
"15-puzzle/internal/repo"
"15-puzzle/internal/web-service/handler"
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
const (
botToken = "example:token"
initData = "auth_date=269666017&chat_instance=6039284203686499081&chat_type=sender&hash=40cde8dc7250ee616cd7d7a090749a9a42cc68f018c969fcb48fdf5e62657ad6&signature=FF5oTJSnmxdqgtNozxsLywXyVKdssh_DbvksGUaQuhkMiRfp10HJmf5o88uokPpqF4yhpHbX1c8uLbrKUuUdAA&user=%7B%22allows_write_to_pm%22%3Atrue%2C%22first_name%22%3A%22Ilia%22%2C%22id%22%3A303133707%2C%22is_premium%22%3Atrue%2C%22language_code%22%3A%22en%22%2C%22last_name%22%3A%22Denisov%22%2C%22photo_url%22%3A%22https%3A%2F%2Fyoutu.be%2FdQw4w9WgXcQ%22%7D"
userId = 303133707
)
func TestHandler(t *testing.T) {
// static
testCase(t, testStaticExistingFile)
testCase(t, testStaticWebAppIndex)
// api
testCase(t, testApiInfo)
testCase(t, testApiAuth)
testCase(t, testApiStart)
testCase(t, testApiSolve)
testCase(t, testApiStats)
testCase(t, testApiMonitoring)
}
func testStaticExistingFile(t *testing.T, ctxRoot string, h http.Handler) {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, ctxRoot+"/static/tgwebapp.html", nil)
h.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code, "static file request must succeed")
assert.Contains(t, w.Header().Get("Content-Type"), "text/html")
assert.Contains(t, w.Body.String(), "unit-test-header")
}
func testStaticWebAppIndex(t *testing.T, ctxRoot string, h http.Handler) {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, ctxRoot+"/puzzle.html", nil)
h.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code, "redirect to static file must succeed")
assert.Contains(t, w.Header().Get("Content-Type"), "text/html")
assert.Contains(t, w.Body.String(), "unit-test-header")
}
func testApiAuth(t *testing.T, ctxRoot string, h http.Handler) {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, ctxRoot+"/api/info", nil)
h.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
w = httptest.NewRecorder()
req = httptest.NewRequest(http.MethodPut, ctxRoot+"/api/start", nil)
h.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
w = httptest.NewRecorder()
req = httptest.NewRequest(http.MethodPut, ctxRoot+"/api/solve?moves=69", nil)
h.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
w = httptest.NewRecorder()
req = httptest.NewRequest(http.MethodGet, ctxRoot+"/api/stats", nil)
h.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
w = httptest.NewRecorder()
req = httptest.NewRequest(http.MethodGet, ctxRoot+"/api/monitoring", nil)
h.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
}
func testApiInfo(t *testing.T, ctxRoot string, h http.Handler) {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, ctxRoot+"/api/info", nil)
req.Header.Add(handler.WebAppInitDataHeader, initData)
h.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code, "api request with correct auth header should be ok")
assert.Contains(t, w.Header().Get("Content-Type"), "application/json")
var u model.ApiResponse
if err := json.Unmarshal(w.Body.Bytes(), &u); err != nil {
t.Fatalf("decode json %s: %s", w.Body.String(), err)
}
assert.NotNil(t, u.Info, "response: stats field should be set")
}
func testApiStart(t *testing.T, ctxRoot string, h http.Handler) {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPut, ctxRoot+"/api/start", nil)
req.Header.Add(handler.WebAppInitDataHeader, initData)
h.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code, "api request with correct auth header should be ok")
assert.Contains(t, w.Header().Get("Content-Type"), "application/json")
var u model.ApiResponse
if err := json.Unmarshal(w.Body.Bytes(), &u); err != nil {
t.Fatalf("decode json %s: %s", w.Body.String(), err)
}
assert.NotNil(t, u.Stats, "response: stats field should be set")
assert.Equal(t, 1, u.Stats.GamesStarted, "user started games should be exactly one")
}
func testApiSolve(t *testing.T, ctxRoot string, h http.Handler) {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPut, ctxRoot+"/api/solve", nil)
req.Header.Add(handler.WebAppInitDataHeader, initData)
h.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
w = httptest.NewRecorder()
req = httptest.NewRequest(http.MethodPut, ctxRoot+"/api/solve?moves=69", nil)
req.Header.Add(handler.WebAppInitDataHeader, initData)
h.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code, "api request with correct auth header should be ok")
assert.Contains(t, w.Header().Get("Content-Type"), "application/json")
var u model.ApiResponse
if err := json.Unmarshal(w.Body.Bytes(), &u); err != nil {
t.Fatalf("decode json %s: %s", w.Body.String(), err)
}
assert.NotNil(t, u.Stats, "response: stats field should be set")
assert.Equal(t, 1, u.Stats.GamesSolved, "user solved games should be exactly one")
}
func testApiStats(t *testing.T, ctxRoot string, h http.Handler) {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, ctxRoot+"/api/stats", nil)
req.Header.Add(handler.WebAppInitDataHeader, initData)
h.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code, "api request with correct auth header should be ok")
assert.Contains(t, w.Header().Get("Content-Type"), "application/json")
var u model.ApiResponse
if err := json.Unmarshal(w.Body.Bytes(), &u); err != nil {
fmt.Println(w.Body)
t.Fatalf("decode json %s: %s", w.Body.String(), err)
}
assert.NotNil(t, u.Stats, "response: stats field should be set")
}
func testApiMonitoring(t *testing.T, ctxRoot string, h http.Handler) {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, ctxRoot+"/api/monitoring", nil)
req.Header.Add(handler.WebAppInitDataHeader, initData)
h.ServeHTTP(w, req)
assert.Equal(t, http.StatusForbidden, w.Code)
assert.True(t, w.Body.Len() == 0)
w = httptest.NewRecorder()
req = httptest.NewRequest(http.MethodGet, ctxRoot+"/api/monitoring", nil)
req.Header.Add(handler.WebAppInitDataHeader, initData)
req.Header.Add(handler.WebAppExtraCodeHeader, "1234")
h.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code, w.Body.String())
assert.Contains(t, w.Header().Get("Content-Type"), "application/json")
var u model.ApiResponse
if err := json.Unmarshal(w.Body.Bytes(), &u); err != nil {
t.Fatalf("decode json %s: %s", w.Body.String(), err)
}
assert.NotNil(t, u.Monitoring, "monitoring field should be an object")
assert.NotNil(t, u.Monitoring.Users)
assert.NotNil(t, u.Monitoring.GamesStarted)
assert.NotNil(t, u.Monitoring.GamesSolved)
}
func testCase(t *testing.T, tc func(*testing.T, string, http.Handler)) {
testContextRoot(t, "", tc)
testContextRoot(t, "/", tc)
testContextRoot(t, "/15-puzzle/", tc)
testContextRoot(t, "/15-puzzle", tc)
}
func testContextRoot(t *testing.T, ctxRoot string, tc func(*testing.T, string, http.Handler)) {
f, err := os.CreateTemp("", "puzzle15-handler-test")
if err != nil {
t.Fatalf("temporary file create: %s", err)
}
if err := f.Close(); err != nil {
t.Fatalf("temporary file close: %s", err)
}
if err := os.Remove(f.Name()); err != nil {
t.Fatalf("temporary file remove before: %s", err)
}
defer func() {
if err := os.Remove(f.Name()); err != nil {
t.Errorf("temporary file remove after: %s", err)
}
}()
r, err := repo.NewFileRepo(context.Background(), f.Name())
if err != nil {
t.Fatalf("NewFileRepo: %s", err)
}
err = r.AddUser(userId)
if err != nil {
t.Fatalf("RegisterGameStart: %s", err)
}
tc(t, strings.TrimRight(ctxRoot, "/"), handler.NewHandler(r, botToken, "1234", ctxRoot, "testdata", "projectLink"))
}