From 03f60ff3164622f86a5b6ad9411c5a6002f65f8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Schw=C3=B6rer?= Date: Sat, 27 May 2023 23:54:14 +0200 Subject: [PATCH] Tests[RequestLogSimple] --- scnserver/TODO.md | 2 + scnserver/api/ginresp/wrapper.go | 2 +- scnserver/api/handler/api.go | 8 +- scnserver/api/handler/compat.go | 2 +- scnserver/db/impl/primary/messages.go | 17 ++- scnserver/db/impl/requests/requestlogs.go | 48 ++++++- .../db/impl/requests/schema/schema_1.ddl | 1 + scnserver/models/requestlog.go | 15 ++- scnserver/models/requestlogfilter.go | 45 +++++++ scnserver/test/keytoken_test.go | 56 ++++++++ scnserver/test/requestlog_test.go | 125 +++++++++++++++++- scnserver/test/util/common.go | 6 +- scnserver/test/util/factory.go | 51 +++++++ scnserver/test/util/requests.go | 32 +++-- scnserver/website/css/style.css | 4 +- 15 files changed, 378 insertions(+), 36 deletions(-) create mode 100644 scnserver/models/requestlogfilter.go create mode 100644 scnserver/test/keytoken_test.go diff --git a/scnserver/TODO.md b/scnserver/TODO.md index 056b8ee..4f2dfbf 100644 --- a/scnserver/TODO.md +++ b/scnserver/TODO.md @@ -77,6 +77,8 @@ - Pagination for ListChannels / ListSubscriptions / ListClients / ListChannelSubscriptions / ListUserSubscriptions + - Add .Insert() function to sq.DB interface (auto generate insert for an object based on struct keys) + - cannot open sqlite in dbbrowsr (cannot parse schema?) -> https://github.com/sqlitebrowser/sqlitebrowser/issues/292 -> https://github.com/sqlitebrowser/sqlitebrowser/issues/29266 diff --git a/scnserver/api/ginresp/wrapper.go b/scnserver/api/ginresp/wrapper.go index 4572c7d..8df212c 100644 --- a/scnserver/api/ginresp/wrapper.go +++ b/scnserver/api/ginresp/wrapper.go @@ -124,7 +124,7 @@ func createRequestLog(g *gin.Context, t0 time.Time, ctr int, resp HTTPResponse, RequestBodySize: int64(len(reqbody)), RequestContentType: ct, RemoteIP: g.RemoteIP(), - TokenID: langext.ConditionalFn10(hasTok, func() *models.KeyTokenID { return langext.Ptr(permObj.(models.PermissionSet).Token.KeyTokenID) }, nil), + KeyID: langext.ConditionalFn10(hasTok, func() *models.KeyTokenID { return langext.Ptr(permObj.(models.PermissionSet).Token.KeyTokenID) }, nil), UserID: langext.ConditionalFn10(hasTok, func() *models.UserID { return langext.Ptr(permObj.(models.PermissionSet).Token.OwnerUserID) }, nil), Permissions: langext.ConditionalFn10(hasTok, func() *string { return langext.Ptr(permObj.(models.PermissionSet).Token.Permissions.String()) }, nil), ResponseStatuscode: langext.ConditionalFn10(resp != nil, func() *int64 { return langext.Ptr(int64(resp.Statuscode())) }, nil), diff --git a/scnserver/api/handler/api.go b/scnserver/api/handler/api.go index 858e1b6..5986a6a 100644 --- a/scnserver/api/handler/api.go +++ b/scnserver/api/handler/api.go @@ -900,7 +900,7 @@ func (h APIHandler) ListChannelMessages(g *gin.Context) ginresp.HTTPResponse { ChannelID: langext.Ptr([]models.ChannelID{channel.ChannelID}), } - messages, npt, err := h.database.ListMessages(ctx, filter, pageSize, tok) + messages, npt, err := h.database.ListMessages(ctx, filter, &pageSize, tok) if err != nil { return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query messages", err) } @@ -1398,7 +1398,7 @@ func (h APIHandler) ListMessages(g *gin.Context) ginresp.HTTPResponse { filter.SearchString = langext.Ptr([]string{strings.TrimSpace(*q.Filter)}) } - messages, npt, err := h.database.ListMessages(ctx, filter, pageSize, tok) + messages, npt, err := h.database.ListMessages(ctx, filter, &pageSize, tok) if err != nil { return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query messages", err) } @@ -1561,7 +1561,7 @@ func (h APIHandler) ListUserKeys(g *gin.Context) ginresp.HTTPResponse { UserID models.UserID `uri:"uid" binding:"entityid"` } type response struct { - Tokens []models.KeyTokenJSON `json:"tokens"` + Keys []models.KeyTokenJSON `json:"keys"` } var u uri @@ -1582,7 +1582,7 @@ func (h APIHandler) ListUserKeys(g *gin.Context) ginresp.HTTPResponse { res := langext.ArrMap(clients, func(v models.KeyToken) models.KeyTokenJSON { return v.JSON() }) - return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{Tokens: res})) + return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{Keys: res})) } // GetUserKey swaggerdoc diff --git a/scnserver/api/handler/compat.go b/scnserver/api/handler/compat.go index 80e281d..6d33567 100644 --- a/scnserver/api/handler/compat.go +++ b/scnserver/api/handler/compat.go @@ -527,7 +527,7 @@ func (h CompatHandler) Requery(g *gin.Context) ginresp.HTTPResponse { CompatAcknowledged: langext.Ptr(false), } - msgs, _, err := h.database.ListMessages(ctx, filter, 16, ct.Start()) + msgs, _, err := h.database.ListMessages(ctx, filter, langext.Ptr(16), ct.Start()) if err != nil { return ginresp.CompatAPIError(0, "Failed to query user") } diff --git a/scnserver/db/impl/primary/messages.go b/scnserver/db/impl/primary/messages.go index b60c9e3..dec4437 100644 --- a/scnserver/db/impl/primary/messages.go +++ b/scnserver/db/impl/primary/messages.go @@ -118,7 +118,7 @@ func (db *Database) DeleteMessage(ctx TxContext, messageID models.MessageID) err return nil } -func (db *Database) ListMessages(ctx TxContext, filter models.MessageFilter, pageSize int, inTok ct.CursorToken) ([]models.Message, ct.CursorToken, error) { +func (db *Database) ListMessages(ctx TxContext, filter models.MessageFilter, pageSize *int, inTok ct.CursorToken) ([]models.Message, ct.CursorToken, error) { tx, err := ctx.GetOrCreateTransaction(db) if err != nil { return nil, ct.CursorToken{}, err @@ -135,11 +135,16 @@ func (db *Database) ListMessages(ctx TxContext, filter models.MessageFilter, pag filterCond, filterJoin, prepParams, err := filter.SQL() - orderClause := "ORDER BY COALESCE(timestamp_client, timestamp_real) DESC, message_id DESC LIMIT :lim" + orderClause := "" + if pageSize != nil { + orderClause = "ORDER BY COALESCE(timestamp_client, timestamp_real) DESC, message_id DESC LIMIT :lim" + prepParams["lim"] = *pageSize + 1 + } else { + orderClause = "ORDER BY COALESCE(timestamp_client, timestamp_real) DESC, message_id DESC" + } sqlQuery := "SELECT " + "messages.*" + " FROM messages " + filterJoin + " WHERE ( " + pageCond + " ) AND ( " + filterCond + " ) " + orderClause - prepParams["lim"] = pageSize + 1 prepParams["tokts"] = inTok.Timestamp prepParams["tokid"] = inTok.Id @@ -153,10 +158,10 @@ func (db *Database) ListMessages(ctx TxContext, filter models.MessageFilter, pag return nil, ct.CursorToken{}, err } - if len(data) <= pageSize { + if pageSize == nil || len(data) <= *pageSize { return data, ct.End(), nil } else { - outToken := ct.Normal(data[pageSize-1].Timestamp(), data[pageSize-1].MessageID.String(), "DESC", filter.Hash()) - return data[0:pageSize], outToken, nil + outToken := ct.Normal(data[*pageSize-1].Timestamp(), data[*pageSize-1].MessageID.String(), "DESC", filter.Hash()) + return data[0:*pageSize], outToken, nil } } diff --git a/scnserver/db/impl/requests/requestlogs.go b/scnserver/db/impl/requests/requestlogs.go index e7daf9d..9ea2628 100644 --- a/scnserver/db/impl/requests/requestlogs.go +++ b/scnserver/db/impl/requests/requestlogs.go @@ -1,6 +1,7 @@ package requests import ( + ct "blackforestbytes.com/simplecloudnotifier/db/cursortoken" "blackforestbytes.com/simplecloudnotifier/models" "context" "gogs.mikescher.com/BlackForestBytes/goext/sq" @@ -11,7 +12,7 @@ func (db *Database) InsertRequestLog(ctx context.Context, requestid models.Reque now := time.Now() - _, err := db.db.Exec(ctx, "INSERT INTO requests (request_id, method, uri, user_agent, authentication, request_body, request_body_size, request_content_type, remote_ip, userid, permissions, response_statuscode, response_body_size, response_body, response_content_type, retry_count, panicked, panic_str, processing_time, timestamp_created, timestamp_start, timestamp_finish) VALUES (:request_id, :method, :uri, :user_agent, :authentication, :request_body, :request_body_size, :request_content_type, :remote_ip, :userid, :permissions, :response_statuscode, :response_body_size, :response_body, :response_content_type, :retry_count, :panicked, :panic_str, :processing_time, :timestamp_created, :timestamp_start, :timestamp_finish)", sq.PP{ + _, err := db.db.Exec(ctx, "INSERT INTO requests (request_id, method, uri, user_agent, authentication, request_body, request_body_size, request_content_type, remote_ip, userid, permissions, response_statuscode, response_body_size, response_body, response_content_type, retry_count, panicked, panic_str, processing_time, timestamp_created, timestamp_start, timestamp_finish, key_id) VALUES (:request_id, :method, :uri, :user_agent, :authentication, :request_body, :request_body_size, :request_content_type, :remote_ip, :userid, :permissions, :response_statuscode, :response_body_size, :response_body, :response_content_type, :retry_count, :panicked, :panic_str, :processing_time, :timestamp_created, :timestamp_start, :timestamp_finish, :kid)", sq.PP{ "request_id": requestid, "method": data.Method, "uri": data.URI, @@ -34,6 +35,7 @@ func (db *Database) InsertRequestLog(ctx context.Context, requestid models.Reque "timestamp_created": now.UnixMilli(), "timestamp_start": data.TimestampStart, "timestamp_finish": data.TimestampFinish, + "kid": data.KeyID, }) if err != nil { return models.RequestLogDB{}, err @@ -62,6 +64,7 @@ func (db *Database) InsertRequestLog(ctx context.Context, requestid models.Reque TimestampCreated: now.UnixMilli(), TimestampStart: data.TimestampStart, TimestampFinish: data.TimestampFinish, + KeyID: data.KeyID, }, nil } @@ -90,3 +93,46 @@ func (db *Database) Cleanup(ctx context.Context, count int, duration time.Durati return affected1 + affected2, nil } + +func (db *Database) ListRequestLogs(ctx context.Context, filter models.RequestLogFilter, pageSize *int, inTok ct.CursorToken) ([]models.RequestLog, ct.CursorToken, error) { + if inTok.Mode == ct.CTMEnd { + return make([]models.RequestLog, 0), ct.End(), nil + } + + pageCond := "1=1" + if inTok.Mode == ct.CTMNormal { + pageCond = "timestamp_created < :tokts OR (timestamp_created = :tokts AND request_id < :tokid )" + } + + filterCond, filterJoin, prepParams, err := filter.SQL() + + orderClause := "" + if pageSize != nil { + orderClause = "ORDER BY timestamp_created DESC, request_id DESC LIMIT :lim" + prepParams["lim"] = *pageSize + 1 + } else { + orderClause = "ORDER BY timestamp_created DESC, request_id DESC" + } + + sqlQuery := "SELECT " + "requests.*" + " FROM requests " + filterJoin + " WHERE ( " + pageCond + " ) AND ( " + filterCond + " ) " + orderClause + + prepParams["tokts"] = inTok.Timestamp + prepParams["tokid"] = inTok.Id + + rows, err := db.db.Query(ctx, sqlQuery, prepParams) + if err != nil { + return nil, ct.CursorToken{}, err + } + + data, err := models.DecodeRequestLogs(rows) + if err != nil { + return nil, ct.CursorToken{}, err + } + + if pageSize == nil || len(data) <= *pageSize { + return data, ct.End(), nil + } else { + outToken := ct.Normal(data[*pageSize-1].TimestampCreated, data[*pageSize-1].RequestID.String(), "DESC", filter.Hash()) + return data[0:*pageSize], outToken, nil + } +} diff --git a/scnserver/db/impl/requests/schema/schema_1.ddl b/scnserver/db/impl/requests/schema/schema_1.ddl index 64859d2..83c0d60 100644 --- a/scnserver/db/impl/requests/schema/schema_1.ddl +++ b/scnserver/db/impl/requests/schema/schema_1.ddl @@ -11,6 +11,7 @@ CREATE TABLE `requests` request_body_size INTEGER NOT NULL, request_content_type TEXT NOT NULL, remote_ip TEXT NOT NULL, + key_id TEXT NULL, userid TEXT NULL, permissions TEXT NULL, diff --git a/scnserver/models/requestlog.go b/scnserver/models/requestlog.go index bd4dc7f..c61d8b7 100644 --- a/scnserver/models/requestlog.go +++ b/scnserver/models/requestlog.go @@ -18,7 +18,7 @@ type RequestLog struct { RequestBodySize int64 RequestContentType string RemoteIP string - TokenID *KeyTokenID + KeyID *KeyTokenID UserID *UserID Permissions *string ResponseStatuscode *int64 @@ -45,7 +45,7 @@ func (c RequestLog) JSON() RequestLogJSON { RequestBodySize: c.RequestBodySize, RequestContentType: c.RequestContentType, RemoteIP: c.RemoteIP, - TokenID: c.TokenID, + KeyID: c.KeyID, UserID: c.UserID, Permissions: c.Permissions, ResponseStatuscode: c.ResponseStatuscode, @@ -73,7 +73,7 @@ func (c RequestLog) DB() RequestLogDB { RequestBodySize: c.RequestBodySize, RequestContentType: c.RequestContentType, RemoteIP: c.RemoteIP, - TokenID: c.TokenID, + KeyID: c.KeyID, UserID: c.UserID, Permissions: c.Permissions, ResponseStatuscode: c.ResponseStatuscode, @@ -100,7 +100,7 @@ type RequestLogJSON struct { RequestBodySize int64 `json:"request_body_size"` RequestContentType string `json:"request_content_type"` RemoteIP string `json:"remote_ip"` - TokenID *KeyTokenID `json:"token_id"` + KeyID *KeyTokenID `json:"key_id"` UserID *UserID `json:"userid"` Permissions *string `json:"permissions"` ResponseStatuscode *int64 `json:"response_statuscode"` @@ -117,7 +117,7 @@ type RequestLogJSON struct { } type RequestLogDB struct { - RequestID RequestID `db:"requestLog_id"` + RequestID RequestID `db:"request_id"` Method string `db:"method"` URI string `db:"uri"` UserAgent *string `db:"user_agent"` @@ -126,13 +126,13 @@ type RequestLogDB struct { RequestBodySize int64 `db:"request_body_size"` RequestContentType string `db:"request_content_type"` RemoteIP string `db:"remote_ip"` - TokenID *KeyTokenID `db:"token_id"` + KeyID *KeyTokenID `db:"key_id"` UserID *UserID `db:"userid"` Permissions *string `db:"permissions"` ResponseStatuscode *int64 `db:"response_statuscode"` ResponseBodySize *int64 `db:"response_body_size"` ResponseBody *string `db:"response_body"` - ResponseContentType string `db:"request_content_type"` + ResponseContentType string `db:"response_content_type"` RetryCount int64 `db:"retry_count"` Panicked int64 `db:"panicked"` PanicStr *string `db:"panic_str"` @@ -153,6 +153,7 @@ func (c RequestLogDB) Model() RequestLog { RequestBodySize: c.RequestBodySize, RequestContentType: c.RequestContentType, RemoteIP: c.RemoteIP, + KeyID: c.KeyID, UserID: c.UserID, Permissions: c.Permissions, ResponseStatuscode: c.ResponseStatuscode, diff --git a/scnserver/models/requestlogfilter.go b/scnserver/models/requestlogfilter.go new file mode 100644 index 0000000..e97ad37 --- /dev/null +++ b/scnserver/models/requestlogfilter.go @@ -0,0 +1,45 @@ +package models + +import ( + "crypto/sha512" + "encoding/hex" + "gogs.mikescher.com/BlackForestBytes/goext/dataext" + "gogs.mikescher.com/BlackForestBytes/goext/mathext" + "gogs.mikescher.com/BlackForestBytes/goext/sq" + "strings" +) + +type RequestLogFilter struct { +} + +func (f RequestLogFilter) SQL() (string, string, sq.PP, error) { + + joinClause := "" + + // ... + + sqlClauses := make([]string, 0) + + params := sq.PP{} + + // ... + + sqlClause := "" + if len(sqlClauses) > 0 { + sqlClause = strings.Join(sqlClauses, " AND ") + } else { + sqlClause = "1=1" + } + + return sqlClause, joinClause, params, nil +} + +func (f RequestLogFilter) Hash() string { + bh, err := dataext.StructHash(f, dataext.StructHashOptions{HashAlgo: sha512.New()}) + if err != nil { + return "00000000" + } + + str := hex.EncodeToString(bh) + return str[0:mathext.Min(8, len(bh))] +} diff --git a/scnserver/test/keytoken_test.go b/scnserver/test/keytoken_test.go new file mode 100644 index 0000000..92d138c --- /dev/null +++ b/scnserver/test/keytoken_test.go @@ -0,0 +1,56 @@ +package test + +import ( + tt "blackforestbytes.com/simplecloudnotifier/test/util" + "fmt" + "testing" +) + +func TestListUserKeys(t *testing.T) { + ws, baseUrl, stop := tt.StartSimpleWebserver(t) + defer stop() + + data := tt.InitSingleData(t, ws) + + type keylist struct { + Tokens []struct { + AllChannels bool `json:"all_channels"` + Channels []string `json:"channels"` + KeytokenId string `json:"keytoken_id"` + MessagesSent int `json:"messages_sent"` + Name string `json:"name"` + OwnerUserId string `json:"owner_user_id"` + Permissions string `json:"permissions"` + } `json:"tokens"` + } + + klist := tt.RequestAuthGet[keylist](t, data.AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s/keys", data.UserID)) + + tt.AssertEqual(t, "len(keys)", 1, len(klist.Tokens)) + + t.SkipNow() //TODO +} + +func TestCreateUserKey(t *testing.T) { + t.SkipNow() //TODO +} + +func TestDeleteUserKey(t *testing.T) { + t.SkipNow() //TODO +} + +func TestGetUserKey(t *testing.T) { + t.SkipNow() //TODO +} + +func TestUpdateUserKey(t *testing.T) { + t.SkipNow() //TODO +} + +func TestUserKeyPermissions(t *testing.T) { + t.SkipNow() //TODO +} + +func TestUsedKeyInMessage(t *testing.T) { + t.SkipNow() //TODO +} diff --git a/scnserver/test/requestlog_test.go b/scnserver/test/requestlog_test.go index 743553d..f9bb0e3 100644 --- a/scnserver/test/requestlog_test.go +++ b/scnserver/test/requestlog_test.go @@ -1,3 +1,126 @@ package test -//TODO test requestlog +import ( + ct "blackforestbytes.com/simplecloudnotifier/db/cursortoken" + "blackforestbytes.com/simplecloudnotifier/models" + tt "blackforestbytes.com/simplecloudnotifier/test/util" + "fmt" + "github.com/gin-gonic/gin" + "net/url" + "testing" + "time" +) + +func TestRequestLogSimple(t *testing.T) { + ws, baseUrl, stop := tt.StartSimpleWebserver(t) + defer stop() + + ctx := ws.NewSimpleTransactionContext(5 * time.Second) + defer ctx.Cancel() + + // Ping + { + tt.RequestGet[tt.Void](t, baseUrl, fmt.Sprintf("/api/ping")) + time.Sleep(100 * time.Millisecond) + + rl, _, err := ws.Database.Requests.ListRequestLogs(ctx, models.RequestLogFilter{}, nil, ct.Start()) + tt.TestFailIfErr(t, err) + + tt.AssertEqual(t, "requestlog.count", 1, len(rl)) + + tt.AssertEqual(t, "requestlog[0].Method", "GET", rl[0].Method) + tt.AssertEqual(t, "requestlog[0].KeyID", nil, rl[0].KeyID) + tt.AssertEqual(t, "requestlog[0].UserID", nil, rl[0].UserID) + tt.AssertEqual(t, "requestlog[0].Panicked", false, rl[0].Panicked) + tt.AssertEqual(t, "requestlog[0].URI", "/api/ping", rl[0].URI) + tt.AssertEqual(t, "requestlog[0].ResponseContentType", "application/json", rl[0].ResponseContentType) + } + + // HTMl request + { + tt.RequestRaw(t, baseUrl, fmt.Sprintf("/")) + time.Sleep(100 * time.Millisecond) + + rl, _, err := ws.Database.Requests.ListRequestLogs(ctx, models.RequestLogFilter{}, nil, ct.Start()) + tt.TestFailIfErr(t, err) + + tt.AssertEqual(t, "requestlog.count", 2, len(rl)) + + tt.AssertEqual(t, "requestlog[0].Method", "GET", rl[0].Method) + tt.AssertEqual(t, "requestlog[0].KeyID", nil, rl[0].KeyID) + tt.AssertEqual(t, "requestlog[0].UserID", nil, rl[0].UserID) + tt.AssertEqual(t, "requestlog[0].Panicked", false, rl[0].Panicked) + tt.AssertEqual(t, "requestlog[0].URI", "/", rl[0].URI) + tt.AssertEqual(t, "requestlog[0].ResponseContentType", "text/html", rl[0].ResponseContentType) + } + + type R struct { + Clients []struct { + ClientId string `json:"client_id"` + UserId string `json:"user_id"` + } `json:"clients"` + ReadKey string `json:"read_key"` + SendKey string `json:"send_key"` + AdminKey string `json:"admin_key"` + UserId string `json:"user_id"` + } + usr := tt.RequestPost[R](t, baseUrl, "/api/v2/users", gin.H{ + "agent_model": "DUMMY_PHONE", + "agent_version": "4X", + "client_type": "ANDROID", + "fcm_token": "DUMMY_FCM", + }) + time.Sleep(100 * time.Millisecond) + + // API request + { + + tt.RequestAuthGet[R](t, usr.AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s", usr.UserId)) + time.Sleep(100 * time.Millisecond) + + rl, _, err := ws.Database.Requests.ListRequestLogs(ctx, models.RequestLogFilter{}, nil, ct.Start()) + tt.TestFailIfErr(t, err) + + tt.AssertEqual(t, "requestlog.count", 4, len(rl)) + + tt.AssertEqual(t, "requestlog[0].Method", "GET", rl[0].Method) + tt.AssertNotEqual(t, "requestlog[0].KeyID", nil, rl[0].KeyID) + tt.AssertStrRepEqual(t, "requestlog[0].UserID", usr.UserId, rl[0].UserID) + tt.AssertEqual(t, "requestlog[0].Panicked", false, rl[0].Panicked) + tt.AssertStrRepEqual(t, "requestlog[0].Permissions", "A", rl[0].Permissions) + tt.AssertEqual(t, "requestlog[0].URI", fmt.Sprintf("/api/v2/users/%s", usr.UserId), rl[0].URI) + tt.AssertEqual(t, "requestlog[0].ResponseContentType", "application/json", rl[0].ResponseContentType) + } + + // Send request + { + tt.RequestPost[gin.H](t, baseUrl, fmt.Sprintf("/?user_id=%s&key=%s&title=%s", usr.UserId, usr.SendKey, url.QueryEscape("Hello World 2134")), nil) + time.Sleep(100 * time.Millisecond) + + rl, _, err := ws.Database.Requests.ListRequestLogs(ctx, models.RequestLogFilter{}, nil, ct.Start()) + tt.TestFailIfErr(t, err) + + tt.AssertEqual(t, "requestlog.count", 5, len(rl)) + + tt.AssertEqual(t, "requestlog[0].Method", "POST", rl[0].Method) + tt.AssertEqual(t, "requestlog[0].UserID", nil, rl[0].UserID) + tt.AssertEqual(t, "requestlog[0].ResponseContentType", "application/json", rl[0].ResponseContentType) + } + + // Failed request + { + tt.RequestAuthGetShouldFail(t, usr.AdminKey, baseUrl, fmt.Sprintf("/api/v2/users/%s", models.NewUserID()), 0, 0) + time.Sleep(100 * time.Millisecond) + + rl, _, err := ws.Database.Requests.ListRequestLogs(ctx, models.RequestLogFilter{}, nil, ct.Start()) + tt.TestFailIfErr(t, err) + + tt.AssertEqual(t, "requestlog.count", 6, len(rl)) + + tt.AssertEqual(t, "requestlog[0].Method", "GET", rl[0].Method) + tt.AssertStrRepEqual(t, "requestlog[0].UserID", usr.UserId, rl[0].UserID) + tt.AssertEqual(t, "requestlog[0].ResponseContentType", "application/json", rl[0].ResponseContentType) + tt.AssertStrRepEqual(t, "requestlog[0].ResponseStatuscode", 401, rl[0].ResponseStatuscode) + } + +} diff --git a/scnserver/test/util/common.go b/scnserver/test/util/common.go index 5623968..0c12a36 100644 --- a/scnserver/test/util/common.go +++ b/scnserver/test/util/common.go @@ -140,6 +140,10 @@ func AssertEqual(t *testing.T, key string, expected any, actual any) { } + if langext.IsNil(expected) && langext.IsNil(actual) { + return + } + if expected != actual { t.Errorf("Value [%s] differs (%T <-> %T):\n", key, expected, actual) @@ -173,7 +177,7 @@ func AssertTrue(t *testing.T, key string, v bool) { } func AssertNotEqual(t *testing.T, key string, expected any, actual any) { - if expected == actual { + if expected == actual || (langext.IsNil(expected) && langext.IsNil(actual)) { t.Errorf("Value [%s] does not differ (%T <-> %T):\n", key, expected, actual) str1 := fmt.Sprintf("%v", expected) diff --git a/scnserver/test/util/factory.go b/scnserver/test/util/factory.go index c2d3f8e..9a33d76 100644 --- a/scnserver/test/util/factory.go +++ b/scnserver/test/util/factory.go @@ -395,6 +395,57 @@ func InitDefaultData(t *testing.T, ws *logic.Application) DefData { return DefData{User: users} } +type SingleData struct { + UserID string + AdminKey string + SendKey string + ReadKey string + ClientID string +} + +func InitSingleData(t *testing.T, ws *logic.Application) SingleData { + + // set logger to buffer, only output if error occured + success := false + SetBufLogger() + defer func() { + ClearBufLogger(!success) + if success { + log.Info().Msgf("Succesfully initialized default data (%d messages, %d users)", len(messageExamples), len(userExamples)) + } + }() + + baseUrl := "http://127.0.0.1:" + ws.Port + + type R struct { + Clients []struct { + ClientId string `json:"client_id"` + UserId string `json:"user_id"` + } `json:"clients"` + ReadKey string `json:"read_key"` + SendKey string `json:"send_key"` + AdminKey string `json:"admin_key"` + UserId string `json:"user_id"` + } + + r0 := RequestPost[R](t, baseUrl, "/api/v2/users", gin.H{ + "agent_model": "DUMMY_PHONE", + "agent_version": "4X", + "client_type": "ANDROID", + "fcm_token": "DUMMY_FCM", + }) + + success = true + + return SingleData{ + UserID: r0.UserId, + AdminKey: r0.AdminKey, + SendKey: r0.SendKey, + ReadKey: r0.ReadKey, + ClientID: r0.Clients[0].ClientId, + } +} + func doSubscribe(t *testing.T, baseUrl string, user Userdat, chanOwner Userdat, chanInternalName string) { if user == chanOwner { diff --git a/scnserver/test/util/requests.go b/scnserver/test/util/requests.go index e7199ad..e35af8c 100644 --- a/scnserver/test/util/requests.go +++ b/scnserver/test/util/requests.go @@ -14,44 +14,48 @@ import ( "testing" ) +func RequestRaw(t *testing.T, baseURL string, urlSuffix string) { + RequestAny[Void](t, "", "GET", baseURL, urlSuffix, nil, false) +} + func RequestGet[TResult any](t *testing.T, baseURL string, urlSuffix string) TResult { - return RequestAny[TResult](t, "", "GET", baseURL, urlSuffix, nil) + return RequestAny[TResult](t, "", "GET", baseURL, urlSuffix, nil, true) } func RequestAuthGet[TResult any](t *testing.T, akey string, baseURL string, urlSuffix string) TResult { - return RequestAny[TResult](t, akey, "GET", baseURL, urlSuffix, nil) + return RequestAny[TResult](t, akey, "GET", baseURL, urlSuffix, nil, true) } func RequestPost[TResult any](t *testing.T, baseURL string, urlSuffix string, body any) TResult { - return RequestAny[TResult](t, "", "POST", baseURL, urlSuffix, body) + return RequestAny[TResult](t, "", "POST", baseURL, urlSuffix, body, true) } func RequestAuthPost[TResult any](t *testing.T, akey string, baseURL string, urlSuffix string, body any) TResult { - return RequestAny[TResult](t, akey, "POST", baseURL, urlSuffix, body) + return RequestAny[TResult](t, akey, "POST", baseURL, urlSuffix, body, true) } func RequestPut[TResult any](t *testing.T, baseURL string, urlSuffix string, body any) TResult { - return RequestAny[TResult](t, "", "PUT", baseURL, urlSuffix, body) + return RequestAny[TResult](t, "", "PUT", baseURL, urlSuffix, body, true) } func RequestAuthPUT[TResult any](t *testing.T, akey string, baseURL string, urlSuffix string, body any) TResult { - return RequestAny[TResult](t, akey, "PUT", baseURL, urlSuffix, body) + return RequestAny[TResult](t, akey, "PUT", baseURL, urlSuffix, body, true) } func RequestPatch[TResult any](t *testing.T, baseURL string, urlSuffix string, body any) TResult { - return RequestAny[TResult](t, "", "PATCH", baseURL, urlSuffix, body) + return RequestAny[TResult](t, "", "PATCH", baseURL, urlSuffix, body, true) } func RequestAuthPatch[TResult any](t *testing.T, akey string, baseURL string, urlSuffix string, body any) TResult { - return RequestAny[TResult](t, akey, "PATCH", baseURL, urlSuffix, body) + return RequestAny[TResult](t, akey, "PATCH", baseURL, urlSuffix, body, true) } func RequestDelete[TResult any](t *testing.T, baseURL string, urlSuffix string, body any) TResult { - return RequestAny[TResult](t, "", "DELETE", baseURL, urlSuffix, body) + return RequestAny[TResult](t, "", "DELETE", baseURL, urlSuffix, body, true) } func RequestAuthDelete[TResult any](t *testing.T, akey string, baseURL string, urlSuffix string, body any) TResult { - return RequestAny[TResult](t, akey, "DELETE", baseURL, urlSuffix, body) + return RequestAny[TResult](t, akey, "DELETE", baseURL, urlSuffix, body, true) } func RequestGetShouldFail(t *testing.T, baseURL string, urlSuffix string, statusCode int, errcode apierr.APIError) { @@ -86,7 +90,7 @@ func RequestAuthDeleteShouldFail(t *testing.T, akey string, baseURL string, urlS RequestAuthAnyShouldFail(t, akey, "DELETE", baseURL, urlSuffix, body, statusCode, errcode) } -func RequestAny[TResult any](t *testing.T, akey string, method string, baseURL string, urlSuffix string, body any) TResult { +func RequestAny[TResult any](t *testing.T, akey string, method string, baseURL string, urlSuffix string, body any, deserialize bool) TResult { client := http.Client{} TPrintf("[-> REQUEST] (%s) %s%s [%s] [%s]\n", method, baseURL, urlSuffix, langext.Conditional(akey == "", "NO AUTH", "AUTH"), langext.Conditional(body == nil, "NO BODY", "BODY")) @@ -156,8 +160,10 @@ func RequestAny[TResult any](t *testing.T, akey string, method string, baseURL s } var data TResult - if err := json.Unmarshal(respBodyBin, &data); err != nil { - TestFailErr(t, err) + if deserialize { + if err := json.Unmarshal(respBodyBin, &data); err != nil { + TestFailErr(t, err) + } } return data diff --git a/scnserver/website/css/style.css b/scnserver/website/css/style.css index 1ce010e..ef33bb3 100644 --- a/scnserver/website/css/style.css +++ b/scnserver/website/css/style.css @@ -319,4 +319,6 @@ pre, pre span code { background: #F9F9F9; border: .0625rem solid var(--secondary-border-color); -} \ No newline at end of file +} + +code { white-space: pre; } \ No newline at end of file