diff --git a/server/api/handler/message.go b/server/api/handler/message.go index 52f5b8b..3dc3a7f 100644 --- a/server/api/handler/message.go +++ b/server/api/handler/message.go @@ -115,6 +115,7 @@ func (h MessageHandler) SendMessage(g *gin.Context) ginresp.HTTPResponse { } defer ctx.Cancel() + // query has highest prio, then form, then json data := dataext.ObjectMerge(dataext.ObjectMerge(b, f), q) return h.sendMessageInternal(g, ctx, data.UserID, data.UserKey, data.Channel, data.ChanKey, data.Title, data.Content, data.Priority, data.UserMessageID, data.SendTimestamp, data.SenderName) diff --git a/server/logic/application.go b/server/logic/application.go index bb35f1f..9010ad2 100644 --- a/server/logic/application.go +++ b/server/logic/application.go @@ -203,13 +203,13 @@ func (app *Application) StartRequest(g *gin.Context, uri any, query any, body an } } - if body != nil && g.Request.Header.Get("Content-Type") == "application/json" { + if body != nil && g.ContentType() == "application/json" { if err := g.ShouldBindJSON(body); err != nil { return nil, langext.Ptr(ginresp.APIError(g, 400, apierr.BINDFAIL_BODY_PARAM, "Failed to read body", err)) } } - if form != nil && g.Request.Header.Get("Content-Type") == "multipart/form-data" { + if form != nil && g.ContentType() == "multipart/form-data" { if err := g.ShouldBindWith(form, binding.Form); err != nil { return nil, langext.Ptr(ginresp.APIError(g, 400, apierr.BINDFAIL_BODY_PARAM, "Failed to read multipart-form", err)) } diff --git a/server/test/message_test.go b/server/test/message_test.go index c8e386f..2dd7e85 100644 --- a/server/test/message_test.go +++ b/server/test/message_test.go @@ -5,6 +5,7 @@ import ( tt "blackforestbytes.com/simplecloudnotifier/test/util" "fmt" "github.com/gin-gonic/gin" + "net/url" "testing" ) @@ -54,12 +55,143 @@ func TestSendSimpleMessageJSON(t *testing.T) { msg1Get := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, "/api/messages/"+fmt.Sprintf("%v", msg1["scn_msg_id"])) tt.AssertEqual(t, "msg.title", "HelloWorld_001", msg1Get["title"]) tt.AssertEqual(t, "msg.channel_name", "main", msg1Get["channel_name"]) - } -//TODO message -> query -//TODO message -> form -//TODO overwrite order when all 3 are specified +func TestSendSimpleMessageQuery(t *testing.T) { + ws, stop := tt.StartSimpleWebserver(t) + defer stop() + + pusher := ws.Pusher.(*push.TestSink) + + baseUrl := "http://127.0.0.1:" + ws.Port + + r0 := tt.RequestPost[gin.H](t, baseUrl, "/api/users", gin.H{ + "agent_model": "DUMMY_PHONE", + "agent_version": "4X", + "client_type": "ANDROID", + "fcm_token": "DUMMY_FCM", + }) + + uid := int(r0["user_id"].(float64)) + admintok := r0["admin_key"].(string) + sendtok := r0["send_key"].(string) + + msg1 := tt.RequestPost[gin.H](t, baseUrl, fmt.Sprintf("/?user_id=%d&user_key=%s&title=%s", uid, sendtok, url.QueryEscape("Hello World 2134")), nil) + + tt.AssertEqual(t, "messageCount", 1, len(pusher.Data)) + tt.AssertEqual(t, "msg.title", "Hello World 2134", pusher.Last().Message.Title) + tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.SCNMessageID) + + type mglist struct { + Messages []gin.H `json:"messages"` + } + + msgList1 := tt.RequestAuthGet[mglist](t, admintok, baseUrl, "/api/messages") + tt.AssertEqual(t, "len(messages)", 1, len(msgList1.Messages)) + + msg1Get := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, "/api/messages/"+fmt.Sprintf("%v", msg1["scn_msg_id"])) + tt.AssertEqual(t, "msg.title", "Hello World 2134", msg1Get["title"]) + tt.AssertEqual(t, "msg.channel_name", "main", msg1Get["channel_name"]) +} + +func TestSendSimpleMessageForm(t *testing.T) { + ws, stop := tt.StartSimpleWebserver(t) + defer stop() + + pusher := ws.Pusher.(*push.TestSink) + + baseUrl := "http://127.0.0.1:" + ws.Port + + r0 := tt.RequestPost[gin.H](t, baseUrl, "/api/users", gin.H{ + "agent_model": "DUMMY_PHONE", + "agent_version": "4X", + "client_type": "ANDROID", + "fcm_token": "DUMMY_FCM", + }) + + uid := int(r0["user_id"].(float64)) + admintok := r0["admin_key"].(string) + sendtok := r0["send_key"].(string) + + msg1 := tt.RequestPost[gin.H](t, baseUrl, "/", tt.FormData{ + "user_key": sendtok, + "user_id": fmt.Sprintf("%d", uid), + "title": "Hello World 9999 [$$$]", + }) + + tt.AssertEqual(t, "messageCount", 1, len(pusher.Data)) + tt.AssertEqual(t, "msg.title", "Hello World 9999 [$$$]", pusher.Last().Message.Title) + tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.SCNMessageID) + + type mglist struct { + Messages []gin.H `json:"messages"` + } + + msgList1 := tt.RequestAuthGet[mglist](t, admintok, baseUrl, "/api/messages") + tt.AssertEqual(t, "len(messages)", 1, len(msgList1.Messages)) + + msg1Get := tt.RequestAuthGet[gin.H](t, admintok, baseUrl, "/api/messages/"+fmt.Sprintf("%v", msg1["scn_msg_id"])) + tt.AssertEqual(t, "msg.title", "Hello World 9999 [$$$]", msg1Get["title"]) + tt.AssertEqual(t, "msg.channel_name", "main", msg1Get["channel_name"]) +} + +func TestSendSimpleMessageFormAndQuery(t *testing.T) { + ws, stop := tt.StartSimpleWebserver(t) + defer stop() + + pusher := ws.Pusher.(*push.TestSink) + + baseUrl := "http://127.0.0.1:" + ws.Port + + r0 := tt.RequestPost[gin.H](t, baseUrl, "/api/users", gin.H{ + "agent_model": "DUMMY_PHONE", + "agent_version": "4X", + "client_type": "ANDROID", + "fcm_token": "DUMMY_FCM", + }) + + uid := int(r0["user_id"].(float64)) + sendtok := r0["send_key"].(string) + + msg1 := tt.RequestPost[gin.H](t, baseUrl, fmt.Sprintf("/?user_id=%d&user_key=%s&title=%s", uid, sendtok, url.QueryEscape("1111111")), tt.FormData{ + "user_key": "ERR", + "user_id": "999999", + "title": "2222222", + }) + + tt.AssertEqual(t, "messageCount", 1, len(pusher.Data)) + tt.AssertEqual(t, "msg.title", "1111111", pusher.Last().Message.Title) + tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.SCNMessageID) +} + +func TestSendSimpleMessageJSONAndQuery(t *testing.T) { + ws, stop := tt.StartSimpleWebserver(t) + defer stop() + + pusher := ws.Pusher.(*push.TestSink) + + baseUrl := "http://127.0.0.1:" + ws.Port + + r0 := tt.RequestPost[gin.H](t, baseUrl, "/api/users", gin.H{ + "agent_model": "DUMMY_PHONE", + "agent_version": "4X", + "client_type": "ANDROID", + "fcm_token": "DUMMY_FCM", + }) + + uid := int(r0["user_id"].(float64)) + sendtok := r0["send_key"].(string) + + msg1 := tt.RequestPost[gin.H](t, baseUrl, fmt.Sprintf("/?user_id=%d&user_key=%s&title=%s", uid, sendtok, url.QueryEscape("1111111")), gin.H{ + "user_key": "ERR", + "user_id": 999999, + "title": "2222222", + }) + + tt.AssertEqual(t, "messageCount", 1, len(pusher.Data)) + tt.AssertEqual(t, "msg.title", "1111111", pusher.Last().Message.Title) + tt.AssertStrRepEqual(t, "msg.scn_msg_id", msg1["scn_msg_id"], pusher.Last().Message.SCNMessageID) +} //TODO send content //TODO sendername diff --git a/server/test/util/formData.go b/server/test/util/formData.go new file mode 100644 index 0000000..b4b31c8 --- /dev/null +++ b/server/test/util/formData.go @@ -0,0 +1,3 @@ +package util + +type FormData map[string]string diff --git a/server/test/util/requests.go b/server/test/util/requests.go index f4d728e..07e551c 100644 --- a/server/test/util/requests.go +++ b/server/test/util/requests.go @@ -8,7 +8,9 @@ import ( "github.com/gin-gonic/gin" "gogs.mikescher.com/BlackForestBytes/goext/langext" "io" + "mime/multipart" "net/http" + "strings" "testing" ) @@ -90,12 +92,32 @@ func RequestAny[TResult any](t *testing.T, akey string, method string, baseURL s fmt.Printf("[-> REQUEST] (%s) %s%s [%s] [%s]\n", method, baseURL, urlSuffix, langext.Conditional(akey == "", "NO AUTH", "AUTH"), langext.Conditional(body == nil, "NO BODY", "BODY")) bytesbody := make([]byte, 0) + contentType := "" if body != nil { - bjson, err := json.Marshal(body) - if err != nil { - TestFailErr(t, err) + switch bd := body.(type) { + case FormData: + bodybuffer := &bytes.Buffer{} + writer := multipart.NewWriter(bodybuffer) + for bdk, bdv := range bd { + err := writer.WriteField(bdk, bdv) + if err != nil { + TestFailErr(t, err) + } + } + err := writer.Close() + if err != nil { + TestFailErr(t, err) + } + bytesbody = bodybuffer.Bytes() + contentType = writer.FormDataContentType() + default: + bjson, err := json.Marshal(body) + if err != nil { + TestFailErr(t, err) + } + bytesbody = bjson + contentType = "application/json" } - bytesbody = bjson } req, err := http.NewRequest(method, baseURL+urlSuffix, bytes.NewReader(bytesbody)) @@ -103,8 +125,8 @@ func RequestAny[TResult any](t *testing.T, akey string, method string, baseURL s TestFailErr(t, err) } - if body != nil { - req.Header.Set("Content-Type", "application/json") + if contentType != "" { + req.Header.Set("Content-Type", contentType) } if akey != "" { @@ -125,6 +147,7 @@ func RequestAny[TResult any](t *testing.T, akey string, method string, baseURL s fmt.Println("") fmt.Printf("---------------- RESPONSE (%d) ----------------\n", resp.StatusCode) fmt.Println(langext.TryPrettyPrintJson(string(respBodyBin))) + TryPrintTraceObj("---------------- -------- ----------------", respBodyBin, "") fmt.Println("---------------- -------- ----------------") fmt.Println("") @@ -181,6 +204,7 @@ func RequestAuthAnyShouldFail(t *testing.T, akey string, method string, baseURL fmt.Println("") fmt.Printf("---------------- RESPONSE (%d) ----------------\n", resp.StatusCode) fmt.Println(langext.TryPrettyPrintJson(string(respBodyBin))) + TryPrintTraceObj("---------------- -------- ----------------", respBodyBin, "") fmt.Println("---------------- -------- ----------------") fmt.Println("") @@ -211,3 +235,22 @@ func RequestAuthAnyShouldFail(t *testing.T, akey string, method string, baseURL TestFail(t, "missing response['error']") } } + +func TryPrintTraceObj(prefix string, body []byte, suffix string) { + v1 := gin.H{} + if err := json.Unmarshal(body, &v1); err == nil { + if v2, ok := v1["traceObj"]; ok { + if v3, ok := v2.(string); ok { + if prefix != "" { + fmt.Println(prefix) + } + + fmt.Println(strings.TrimSpace(v3)) + + if suffix != "" { + fmt.Println(suffix) + } + } + } + } +}