Tests[RequestLogSimple]

This commit is contained in:
Mike Schwörer 2023-05-27 23:54:14 +02:00
parent b2df0a5a02
commit 03f60ff316
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
15 changed files with 378 additions and 36 deletions

View File

@ -77,6 +77,8 @@
- Pagination for ListChannels / ListSubscriptions / ListClients / ListChannelSubscriptions / ListUserSubscriptions - 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?) - cannot open sqlite in dbbrowsr (cannot parse schema?)
-> https://github.com/sqlitebrowser/sqlitebrowser/issues/292 -> https://github.com/sqlitebrowser/sqlitebrowser/issues/29266 -> https://github.com/sqlitebrowser/sqlitebrowser/issues/292 -> https://github.com/sqlitebrowser/sqlitebrowser/issues/29266

View File

@ -124,7 +124,7 @@ func createRequestLog(g *gin.Context, t0 time.Time, ctr int, resp HTTPResponse,
RequestBodySize: int64(len(reqbody)), RequestBodySize: int64(len(reqbody)),
RequestContentType: ct, RequestContentType: ct,
RemoteIP: g.RemoteIP(), 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), 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), 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), ResponseStatuscode: langext.ConditionalFn10(resp != nil, func() *int64 { return langext.Ptr(int64(resp.Statuscode())) }, nil),

View File

@ -900,7 +900,7 @@ func (h APIHandler) ListChannelMessages(g *gin.Context) ginresp.HTTPResponse {
ChannelID: langext.Ptr([]models.ChannelID{channel.ChannelID}), 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 { if err != nil {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query messages", err) 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)}) 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 { if err != nil {
return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query messages", err) 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"` UserID models.UserID `uri:"uid" binding:"entityid"`
} }
type response struct { type response struct {
Tokens []models.KeyTokenJSON `json:"tokens"` Keys []models.KeyTokenJSON `json:"keys"`
} }
var u uri 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() }) 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 // GetUserKey swaggerdoc

View File

@ -527,7 +527,7 @@ func (h CompatHandler) Requery(g *gin.Context) ginresp.HTTPResponse {
CompatAcknowledged: langext.Ptr(false), 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 { if err != nil {
return ginresp.CompatAPIError(0, "Failed to query user") return ginresp.CompatAPIError(0, "Failed to query user")
} }

View File

@ -118,7 +118,7 @@ func (db *Database) DeleteMessage(ctx TxContext, messageID models.MessageID) err
return nil 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) tx, err := ctx.GetOrCreateTransaction(db)
if err != nil { if err != nil {
return nil, ct.CursorToken{}, err 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() 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 sqlQuery := "SELECT " + "messages.*" + " FROM messages " + filterJoin + " WHERE ( " + pageCond + " ) AND ( " + filterCond + " ) " + orderClause
prepParams["lim"] = pageSize + 1
prepParams["tokts"] = inTok.Timestamp prepParams["tokts"] = inTok.Timestamp
prepParams["tokid"] = inTok.Id prepParams["tokid"] = inTok.Id
@ -153,10 +158,10 @@ func (db *Database) ListMessages(ctx TxContext, filter models.MessageFilter, pag
return nil, ct.CursorToken{}, err return nil, ct.CursorToken{}, err
} }
if len(data) <= pageSize { if pageSize == nil || len(data) <= *pageSize {
return data, ct.End(), nil return data, ct.End(), nil
} else { } else {
outToken := ct.Normal(data[pageSize-1].Timestamp(), data[pageSize-1].MessageID.String(), "DESC", filter.Hash()) outToken := ct.Normal(data[*pageSize-1].Timestamp(), data[*pageSize-1].MessageID.String(), "DESC", filter.Hash())
return data[0:pageSize], outToken, nil return data[0:*pageSize], outToken, nil
} }
} }

View File

@ -1,6 +1,7 @@
package requests package requests
import ( import (
ct "blackforestbytes.com/simplecloudnotifier/db/cursortoken"
"blackforestbytes.com/simplecloudnotifier/models" "blackforestbytes.com/simplecloudnotifier/models"
"context" "context"
"gogs.mikescher.com/BlackForestBytes/goext/sq" "gogs.mikescher.com/BlackForestBytes/goext/sq"
@ -11,7 +12,7 @@ func (db *Database) InsertRequestLog(ctx context.Context, requestid models.Reque
now := time.Now() 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, "request_id": requestid,
"method": data.Method, "method": data.Method,
"uri": data.URI, "uri": data.URI,
@ -34,6 +35,7 @@ func (db *Database) InsertRequestLog(ctx context.Context, requestid models.Reque
"timestamp_created": now.UnixMilli(), "timestamp_created": now.UnixMilli(),
"timestamp_start": data.TimestampStart, "timestamp_start": data.TimestampStart,
"timestamp_finish": data.TimestampFinish, "timestamp_finish": data.TimestampFinish,
"kid": data.KeyID,
}) })
if err != nil { if err != nil {
return models.RequestLogDB{}, err return models.RequestLogDB{}, err
@ -62,6 +64,7 @@ func (db *Database) InsertRequestLog(ctx context.Context, requestid models.Reque
TimestampCreated: now.UnixMilli(), TimestampCreated: now.UnixMilli(),
TimestampStart: data.TimestampStart, TimestampStart: data.TimestampStart,
TimestampFinish: data.TimestampFinish, TimestampFinish: data.TimestampFinish,
KeyID: data.KeyID,
}, nil }, nil
} }
@ -90,3 +93,46 @@ func (db *Database) Cleanup(ctx context.Context, count int, duration time.Durati
return affected1 + affected2, nil 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
}
}

View File

@ -11,6 +11,7 @@ CREATE TABLE `requests`
request_body_size INTEGER NOT NULL, request_body_size INTEGER NOT NULL,
request_content_type TEXT NOT NULL, request_content_type TEXT NOT NULL,
remote_ip TEXT NOT NULL, remote_ip TEXT NOT NULL,
key_id TEXT NULL,
userid TEXT NULL, userid TEXT NULL,
permissions TEXT NULL, permissions TEXT NULL,

View File

@ -18,7 +18,7 @@ type RequestLog struct {
RequestBodySize int64 RequestBodySize int64
RequestContentType string RequestContentType string
RemoteIP string RemoteIP string
TokenID *KeyTokenID KeyID *KeyTokenID
UserID *UserID UserID *UserID
Permissions *string Permissions *string
ResponseStatuscode *int64 ResponseStatuscode *int64
@ -45,7 +45,7 @@ func (c RequestLog) JSON() RequestLogJSON {
RequestBodySize: c.RequestBodySize, RequestBodySize: c.RequestBodySize,
RequestContentType: c.RequestContentType, RequestContentType: c.RequestContentType,
RemoteIP: c.RemoteIP, RemoteIP: c.RemoteIP,
TokenID: c.TokenID, KeyID: c.KeyID,
UserID: c.UserID, UserID: c.UserID,
Permissions: c.Permissions, Permissions: c.Permissions,
ResponseStatuscode: c.ResponseStatuscode, ResponseStatuscode: c.ResponseStatuscode,
@ -73,7 +73,7 @@ func (c RequestLog) DB() RequestLogDB {
RequestBodySize: c.RequestBodySize, RequestBodySize: c.RequestBodySize,
RequestContentType: c.RequestContentType, RequestContentType: c.RequestContentType,
RemoteIP: c.RemoteIP, RemoteIP: c.RemoteIP,
TokenID: c.TokenID, KeyID: c.KeyID,
UserID: c.UserID, UserID: c.UserID,
Permissions: c.Permissions, Permissions: c.Permissions,
ResponseStatuscode: c.ResponseStatuscode, ResponseStatuscode: c.ResponseStatuscode,
@ -100,7 +100,7 @@ type RequestLogJSON struct {
RequestBodySize int64 `json:"request_body_size"` RequestBodySize int64 `json:"request_body_size"`
RequestContentType string `json:"request_content_type"` RequestContentType string `json:"request_content_type"`
RemoteIP string `json:"remote_ip"` RemoteIP string `json:"remote_ip"`
TokenID *KeyTokenID `json:"token_id"` KeyID *KeyTokenID `json:"key_id"`
UserID *UserID `json:"userid"` UserID *UserID `json:"userid"`
Permissions *string `json:"permissions"` Permissions *string `json:"permissions"`
ResponseStatuscode *int64 `json:"response_statuscode"` ResponseStatuscode *int64 `json:"response_statuscode"`
@ -117,7 +117,7 @@ type RequestLogJSON struct {
} }
type RequestLogDB struct { type RequestLogDB struct {
RequestID RequestID `db:"requestLog_id"` RequestID RequestID `db:"request_id"`
Method string `db:"method"` Method string `db:"method"`
URI string `db:"uri"` URI string `db:"uri"`
UserAgent *string `db:"user_agent"` UserAgent *string `db:"user_agent"`
@ -126,13 +126,13 @@ type RequestLogDB struct {
RequestBodySize int64 `db:"request_body_size"` RequestBodySize int64 `db:"request_body_size"`
RequestContentType string `db:"request_content_type"` RequestContentType string `db:"request_content_type"`
RemoteIP string `db:"remote_ip"` RemoteIP string `db:"remote_ip"`
TokenID *KeyTokenID `db:"token_id"` KeyID *KeyTokenID `db:"key_id"`
UserID *UserID `db:"userid"` UserID *UserID `db:"userid"`
Permissions *string `db:"permissions"` Permissions *string `db:"permissions"`
ResponseStatuscode *int64 `db:"response_statuscode"` ResponseStatuscode *int64 `db:"response_statuscode"`
ResponseBodySize *int64 `db:"response_body_size"` ResponseBodySize *int64 `db:"response_body_size"`
ResponseBody *string `db:"response_body"` ResponseBody *string `db:"response_body"`
ResponseContentType string `db:"request_content_type"` ResponseContentType string `db:"response_content_type"`
RetryCount int64 `db:"retry_count"` RetryCount int64 `db:"retry_count"`
Panicked int64 `db:"panicked"` Panicked int64 `db:"panicked"`
PanicStr *string `db:"panic_str"` PanicStr *string `db:"panic_str"`
@ -153,6 +153,7 @@ func (c RequestLogDB) Model() RequestLog {
RequestBodySize: c.RequestBodySize, RequestBodySize: c.RequestBodySize,
RequestContentType: c.RequestContentType, RequestContentType: c.RequestContentType,
RemoteIP: c.RemoteIP, RemoteIP: c.RemoteIP,
KeyID: c.KeyID,
UserID: c.UserID, UserID: c.UserID,
Permissions: c.Permissions, Permissions: c.Permissions,
ResponseStatuscode: c.ResponseStatuscode, ResponseStatuscode: c.ResponseStatuscode,

View File

@ -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))]
}

View File

@ -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
}

View File

@ -1,3 +1,126 @@
package test 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)
}
}

View File

@ -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 { if expected != actual {
t.Errorf("Value [%s] differs (%T <-> %T):\n", key, 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) { 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) t.Errorf("Value [%s] does not differ (%T <-> %T):\n", key, expected, actual)
str1 := fmt.Sprintf("%v", expected) str1 := fmt.Sprintf("%v", expected)

View File

@ -395,6 +395,57 @@ func InitDefaultData(t *testing.T, ws *logic.Application) DefData {
return DefData{User: users} 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) { func doSubscribe(t *testing.T, baseUrl string, user Userdat, chanOwner Userdat, chanInternalName string) {
if user == chanOwner { if user == chanOwner {

View File

@ -14,44 +14,48 @@ import (
"testing" "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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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) { 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) 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{} 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")) 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,9 +160,11 @@ func RequestAny[TResult any](t *testing.T, akey string, method string, baseURL s
} }
var data TResult var data TResult
if deserialize {
if err := json.Unmarshal(respBodyBin, &data); err != nil { if err := json.Unmarshal(respBodyBin, &data); err != nil {
TestFailErr(t, err) TestFailErr(t, err)
} }
}
return data return data
} }

View File

@ -320,3 +320,5 @@ code {
background: #F9F9F9; background: #F9F9F9;
border: .0625rem solid var(--secondary-border-color); border: .0625rem solid var(--secondary-border-color);
} }
code { white-space: pre; }