mirror of
https://github.com/airlabspl/uptimemonitor.git
synced 2025-08-14 12:19:19 +02:00
350 lines
8.2 KiB
Go
350 lines
8.2 KiB
Go
package test
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
"uptimemonitor"
|
|
"uptimemonitor/handler"
|
|
"uptimemonitor/pkg/testutil"
|
|
"uptimemonitor/router"
|
|
"uptimemonitor/service"
|
|
"uptimemonitor/store"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
var TestWebhookCalledCount int64
|
|
var TestWebhookBody string
|
|
var ExpectedWebhookBody string
|
|
var ExpectedWebhookHeaderKey string
|
|
var ExpectedWebhookHeaderValue string
|
|
|
|
type TestCase struct {
|
|
T *testing.T
|
|
Server *httptest.Server
|
|
Client *http.Client
|
|
Store *store.Store
|
|
User *uptimemonitor.User
|
|
Headers map[string]string
|
|
Cookies []*http.Cookie
|
|
}
|
|
|
|
func NewTestCase(t *testing.T) *TestCase {
|
|
store := store.New(":memory:")
|
|
service := service.New(store)
|
|
handler := handler.New(store, service, false)
|
|
router := router.New(handler, registerRoutes)
|
|
server := httptest.NewServer(router)
|
|
|
|
TestWebhookBody = ""
|
|
TestWebhookCalledCount = 0
|
|
ExpectedWebhookBody = ""
|
|
ExpectedWebhookHeaderKey = ""
|
|
ExpectedWebhookHeaderValue = ""
|
|
|
|
return &TestCase{
|
|
T: t,
|
|
Server: server,
|
|
Client: server.Client(),
|
|
Store: store,
|
|
Headers: map[string]string{},
|
|
Cookies: []*http.Cookie{},
|
|
}
|
|
}
|
|
|
|
func registerRoutes(router *http.ServeMux) {
|
|
router.HandleFunc("GET /test/200", func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
})
|
|
|
|
router.HandleFunc("GET /test/404", func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
})
|
|
|
|
router.HandleFunc("GET /test/500", func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
})
|
|
|
|
router.HandleFunc("GET /test/timeout", func(w http.ResponseWriter, r *http.Request) {
|
|
time.Sleep(30 * time.Second)
|
|
w.WriteHeader(http.StatusOK)
|
|
})
|
|
|
|
router.HandleFunc("GET /test/panic", func(w http.ResponseWriter, r *http.Request) {
|
|
panic("test")
|
|
})
|
|
|
|
i := 0
|
|
router.HandleFunc("GET /test/even", func(w http.ResponseWriter, r *http.Request) {
|
|
defer func() {
|
|
i++
|
|
}()
|
|
|
|
if i%2 == 0 {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
})
|
|
|
|
router.HandleFunc("POST /test/post", func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
})
|
|
router.HandleFunc("PATCH /test/patch", func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
})
|
|
router.HandleFunc("PUT /test/put", func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
})
|
|
router.HandleFunc("DELETE /test/delete", func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
})
|
|
|
|
router.HandleFunc("POST /test/body", func(w http.ResponseWriter, r *http.Request) {
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
defer r.Body.Close()
|
|
|
|
if string(body) != `{"test":123}` {
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
})
|
|
|
|
router.HandleFunc("POST /test/headers", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Header.Get("test") != "abc" {
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
})
|
|
|
|
router.HandleFunc("POST /test/webhook", func(w http.ResponseWriter, r *http.Request) {
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
defer r.Body.Close()
|
|
|
|
if ExpectedWebhookBody != "" && string(body) != ExpectedWebhookBody {
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if ExpectedWebhookHeaderKey != "" && r.Header.Get(ExpectedWebhookHeaderKey) != ExpectedWebhookHeaderValue {
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
TestWebhookCalledCount++
|
|
TestWebhookBody = string(body)
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
})
|
|
}
|
|
|
|
func (tc *TestCase) Close() {
|
|
tc.Server.Close()
|
|
}
|
|
|
|
func (tc *TestCase) WithHeader(key, value string) *TestCase {
|
|
tc.Headers[key] = value
|
|
|
|
return tc
|
|
}
|
|
|
|
func (tc *TestCase) WithCookie(c *http.Cookie) *TestCase {
|
|
tc.Cookies = append(tc.Cookies, c)
|
|
|
|
return tc
|
|
}
|
|
|
|
func (tc *TestCase) Get(url string) *testutil.AssertableResponse {
|
|
req, err := http.NewRequest(http.MethodGet, tc.Server.URL+url, nil)
|
|
if err != nil {
|
|
tc.T.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
if len(tc.Cookies) > 0 {
|
|
for _, c := range tc.Cookies {
|
|
req.AddCookie(c)
|
|
}
|
|
}
|
|
|
|
if len(tc.Headers) > 0 {
|
|
for k, v := range tc.Headers {
|
|
req.Header.Set(k, v)
|
|
}
|
|
}
|
|
|
|
res, err := tc.Client.Do(req)
|
|
if err != nil {
|
|
tc.T.Fatalf("failed to get %s: %v", url, err)
|
|
}
|
|
|
|
return testutil.NewAssertableResponse(tc.T, res)
|
|
}
|
|
|
|
func (tc *TestCase) Post(url string, data url.Values) *testutil.AssertableResponse {
|
|
req, err := http.NewRequest(http.MethodPost, tc.Server.URL+url, strings.NewReader(data.Encode()))
|
|
if err != nil {
|
|
tc.T.Fatalf("unexpected error: %v", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
|
|
if len(tc.Cookies) > 0 {
|
|
for _, c := range tc.Cookies {
|
|
req.AddCookie(c)
|
|
}
|
|
}
|
|
|
|
res, err := tc.Client.Do(req)
|
|
if err != nil {
|
|
tc.T.Fatalf("failed to post %s: %v", url, err)
|
|
}
|
|
|
|
for _, c := range res.Cookies() {
|
|
if c.Name == "session" {
|
|
tc.Cookies = append(tc.Cookies, c)
|
|
}
|
|
}
|
|
|
|
return testutil.NewAssertableResponse(tc.T, res)
|
|
}
|
|
|
|
func (tc *TestCase) Patch(url string, data url.Values) *testutil.AssertableResponse {
|
|
req, err := http.NewRequest(http.MethodPatch, tc.Server.URL+url, strings.NewReader(data.Encode()))
|
|
if err != nil {
|
|
tc.T.Fatalf("unexpected error: %v", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
|
|
if len(tc.Cookies) > 0 {
|
|
for _, c := range tc.Cookies {
|
|
req.AddCookie(c)
|
|
}
|
|
}
|
|
|
|
res, err := tc.Client.Do(req)
|
|
if err != nil {
|
|
tc.T.Fatalf("failed to post %s: %v", url, err)
|
|
}
|
|
|
|
return testutil.NewAssertableResponse(tc.T, res)
|
|
}
|
|
|
|
func (tc *TestCase) Delete(url string) *testutil.AssertableResponse {
|
|
req, err := http.NewRequest(http.MethodDelete, tc.Server.URL+url, nil)
|
|
if err != nil {
|
|
tc.T.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
if len(tc.Cookies) > 0 {
|
|
for _, c := range tc.Cookies {
|
|
req.AddCookie(c)
|
|
}
|
|
}
|
|
|
|
res, err := tc.Client.Do(req)
|
|
if err != nil {
|
|
tc.T.Fatalf("failed to delete %s: %v", url, err)
|
|
}
|
|
|
|
return testutil.NewAssertableResponse(tc.T, res)
|
|
}
|
|
|
|
func (tc *TestCase) AssertDatabaseCount(table string, expected int) *TestCase {
|
|
tc.T.Helper()
|
|
|
|
stmt := fmt.Sprintf(`SELECT COUNT(*) FROM %s`, table)
|
|
var count int
|
|
|
|
err := tc.Store.DB().QueryRow(stmt).Scan(&count)
|
|
if err != nil {
|
|
tc.T.Fatalf("failed to count rows from table '%s', error: %v", table, err)
|
|
}
|
|
|
|
if count != expected {
|
|
tc.T.Fatalf("expected to find %d number of rows in a table '%s, but found %d", expected, table, count)
|
|
}
|
|
|
|
return tc
|
|
}
|
|
|
|
func (tc *TestCase) CreateTestUser(email, password string) *TestCase {
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
tc.T.Fatalf("unexpected bcrypt error: %v", err)
|
|
}
|
|
|
|
user, err := tc.Store.CreateUser(tc.T.Context(), uptimemonitor.User{
|
|
Name: "Test User",
|
|
Email: email,
|
|
PasswordHash: string(hash),
|
|
})
|
|
if err != nil {
|
|
tc.T.Fatalf("unable to create test user: %v", err)
|
|
}
|
|
|
|
tc.User = &user
|
|
|
|
return tc
|
|
}
|
|
|
|
func (tc *TestCase) LogIn() *TestCase {
|
|
tc.CreateTestUser("test@example.com", "password")
|
|
|
|
session, err := tc.Store.CreateSession(tc.T.Context(), uptimemonitor.Session{
|
|
User: *tc.User,
|
|
UserID: tc.User.ID,
|
|
ExpiresAt: time.Now().Add(time.Hour),
|
|
})
|
|
if err != nil {
|
|
tc.T.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
c := &http.Cookie{
|
|
Name: "session",
|
|
Value: session.Uuid,
|
|
HttpOnly: true,
|
|
SameSite: http.SameSiteLaxMode,
|
|
Secure: false,
|
|
Expires: session.ExpiresAt,
|
|
}
|
|
|
|
return tc.WithCookie(c)
|
|
}
|
|
|
|
func (tc *TestCase) AssertEqual(a, b any) *TestCase {
|
|
tc.T.Helper()
|
|
|
|
if !reflect.DeepEqual(a, b) {
|
|
tc.T.Fatalf(`expected "%v" to be equal to "%v"`, a, b)
|
|
}
|
|
return tc
|
|
}
|
|
|
|
func (tc *TestCase) AssertNoError(err error) *TestCase {
|
|
tc.T.Helper()
|
|
|
|
if err != nil {
|
|
tc.T.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
return tc
|
|
}
|