From ae15cac727b7f82eba94d6ef3666e4ca6d9cfb0d Mon Sep 17 00:00:00 2001 From: Ralph Slooten Date: Wed, 27 Sep 2023 17:29:03 +1300 Subject: [PATCH] Testing: Add endpoints for integration tests See #166 --- server/apiv1/swagger.go | 7 ++ server/handlers/message-rendered.go | 163 ++++++++++++++++++++++++++++ server/server.go | 14 ++- server/ui/api/v1/swagger.json | 71 ++++++++++++ 4 files changed, 249 insertions(+), 6 deletions(-) create mode 100644 server/handlers/message-rendered.go diff --git a/server/apiv1/swagger.go b/server/apiv1/swagger.go index ee83f01..43e0646 100644 --- a/server/apiv1/swagger.go +++ b/server/apiv1/swagger.go @@ -81,6 +81,13 @@ type textResponse struct { Body string } +// HTML response +// swagger:response HTMLResponse +type htmlResponse struct { + // in: body + Body string +} + // Error response // swagger:response ErrorResponse type errorResponse struct { diff --git a/server/handlers/message-rendered.go b/server/handlers/message-rendered.go new file mode 100644 index 0000000..48894d3 --- /dev/null +++ b/server/handlers/message-rendered.go @@ -0,0 +1,163 @@ +package handlers + +import ( + "fmt" + "net/http" + "regexp" + "strings" + + "github.com/axllent/mailpit/config" + "github.com/axllent/mailpit/internal/storage" + "github.com/gorilla/mux" +) + +// GetMessageHTML (method: GET) returns a rendered version of a message's HTML part +func GetMessageHTML(w http.ResponseWriter, r *http.Request) { + // swagger:route GET /view/{ID}.html testing GetMessageHTML + // + // # Render message HTML part + // + // Renders just the message's HTML part which can be used for UI integration testing. + // Attached inline images are modified to link to the API provided they exist. + // Note that is the message does not contain a HTML part then an 404 error is returned. + // + // The ID can be set to `latest` to return the latest message. + // + // Produces: + // - text/html + // + // Schemes: http, https + // + // Parameters: + // + name: ID + // in: path + // description: Database ID or latest + // required: true + // type: string + // + // Responses: + // 200: HTMLResponse + // default: ErrorResponse + + vars := mux.Vars(r) + + id := vars["id"] + + if id == "latest" { + messages, err := storage.List(0, 1) + if err != nil { + httpError(w, err.Error()) + return + } + + if len(messages) == 0 { + w.WriteHeader(404) + fmt.Fprint(w, "Message not found") + return + } + + id = messages[0].ID + } + + msg, err := storage.GetMessage(id) + if err != nil { + w.WriteHeader(404) + fmt.Fprint(w, "Message not found") + return + } + if msg.HTML == "" { + w.WriteHeader(404) + fmt.Fprint(w, "This message does not contain a HTML part") + return + } + + html := linkInlinedImages(msg) + w.Header().Add("Content-Type", "text/html; charset=utf-8") + _, _ = w.Write([]byte(html)) +} + +// GetMessageText (method: GET) returns a message's text part +func GetMessageText(w http.ResponseWriter, r *http.Request) { + // swagger:route GET /view/{ID}.txt testing GetMessageText + // + // # Render message text part + // + // Renders just the message's text part which can be used for UI integration testing. + // + // The ID can be set to `latest` to return the latest message. + // + // Produces: + // - text/plain + // + // Schemes: http, https + // + // Parameters: + // + name: ID + // in: path + // description: Database ID or latest + // required: true + // type: string + // + // Responses: + // 200: TextResponse + // default: ErrorResponse + + vars := mux.Vars(r) + + id := vars["id"] + + if id == "latest" { + messages, err := storage.List(0, 1) + if err != nil { + httpError(w, err.Error()) + return + } + + if len(messages) == 0 { + w.WriteHeader(404) + fmt.Fprint(w, "Message not found") + return + } + + id = messages[0].ID + } + + msg, err := storage.GetMessage(id) + if err != nil { + w.WriteHeader(404) + fmt.Fprint(w, "Message not found") + return + } + + w.Header().Add("Content-Type", "text/plain; charset=utf-8") + _, _ = w.Write([]byte(msg.Text)) +} + +// This will remap all attachment images with relative paths +func linkInlinedImages(msg *storage.Message) string { + html := msg.HTML + + for _, a := range msg.Inline { + if a.ContentID != "" { + re := regexp.MustCompile(`(?i)(=["\']?)(cid:` + regexp.QuoteMeta(a.ContentID) + `)(["|\'|\\s|\\/|>|;])`) + u := config.Webroot + "api/v1/message/" + msg.ID + "/part/" + a.PartID + matches := re.FindAllStringSubmatch(html, -1) + for _, m := range matches { + html = strings.ReplaceAll(html, m[0], m[1]+u+m[3]) + } + } + } + + for _, a := range msg.Attachments { + if a.ContentID != "" { + re := regexp.MustCompile(`(?i)(=["\']?)(cid:` + regexp.QuoteMeta(a.ContentID) + `)(["|\'|\\s|\\/|>|;])`) + u := config.Webroot + "api/v1/message/" + msg.ID + "/part/" + a.PartID + matches := re.FindAllStringSubmatch(html, -1) + for _, m := range matches { + html = strings.ReplaceAll(html, m[0], m[1]+u+m[3]) + } + } + } + + return html +} diff --git a/server/server.go b/server/server.go index 7beb4c5..71d1f03 100644 --- a/server/server.go +++ b/server/server.go @@ -67,8 +67,14 @@ func Listen() { r.HandleFunc(redirect, middleWareFunc(addSlashToWebroot)).Methods("GET") } - // handle everything else with the virtual index.html - r.PathPrefix(config.Webroot).Handler(middleWareFunc(index)).Methods("GET") + // frontend testing + r.HandleFunc(config.Webroot+"view/{id}.html", handlers.GetMessageHTML).Methods("GET") + r.HandleFunc(config.Webroot+"view/{id}.txt", handlers.GetMessageText).Methods("GET") + + // web UI via virtual index.html + r.PathPrefix(config.Webroot + "view/").Handler(middleWareFunc(index)).Methods("GET") + r.Path(config.Webroot + "search").Handler(middleWareFunc(index)).Methods("GET") + r.Path(config.Webroot).Handler(middleWareFunc(index)).Methods("GET") // put it all together http.Handle("/", r) @@ -293,10 +299,6 @@ func index(w http.ResponseWriter, _ *http.Request) { buff.Bytes() - // f, err := embeddedFS.ReadFile("public/index.html") - // if err != nil { - // panic(err) - // } w.Header().Add("Content-Type", "text/html") _, _ = w.Write(buff.Bytes()) } diff --git a/server/ui/api/v1/swagger.json b/server/ui/api/v1/swagger.json index 825bde4..d0a85a5 100644 --- a/server/ui/api/v1/swagger.json +++ b/server/ui/api/v1/swagger.json @@ -654,6 +654,74 @@ } } } + }, + "/view/{ID}.html": { + "get": { + "description": "Renders just the message's HTML part which can be used for UI integration testing.\nAttached inline images are modified to link to the API provided they exist.\nNote that is the message does not contain a HTML part then an 404 error is returned.\n\nThe ID can be set to `latest` to return the latest message.", + "produces": [ + "text/html" + ], + "schemes": [ + "http", + "https" + ], + "tags": [ + "testing" + ], + "summary": "Render message HTML part", + "operationId": "GetMessageHTML", + "parameters": [ + { + "type": "string", + "description": "Database ID or latest", + "name": "ID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/HTMLResponse" + }, + "default": { + "$ref": "#/responses/ErrorResponse" + } + } + } + }, + "/view/{ID}.txt": { + "get": { + "description": "Renders just the message's text part which can be used for UI integration testing.\n\nThe ID can be set to `latest` to return the latest message.", + "produces": [ + "text/plain" + ], + "schemes": [ + "http", + "https" + ], + "tags": [ + "testing" + ], + "summary": "Render message text part", + "operationId": "GetMessageText", + "parameters": [ + { + "type": "string", + "description": "Database ID or latest", + "name": "ID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/TextResponse" + }, + "default": { + "$ref": "#/responses/ErrorResponse" + } + } + } } }, "definitions": { @@ -1300,6 +1368,9 @@ "ErrorResponse": { "description": "Error response" }, + "HTMLResponse": { + "description": "HTML response" + }, "InfoResponse": { "description": "Application information", "schema": {