mirror of
https://github.com/MengMengCode/VoCat.git
synced 2026-08-20 06:43:42 +08:00
feat: add WeCom payload renderer
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var wecomTemplateVariableNames = []string{
|
||||
"event",
|
||||
"title",
|
||||
"message",
|
||||
"timestamp",
|
||||
"content",
|
||||
"number",
|
||||
"device_id",
|
||||
"device_name",
|
||||
"device_label",
|
||||
"time",
|
||||
}
|
||||
|
||||
type wecomTemplateValues map[string]string
|
||||
|
||||
func renderWecomPayload(template string, values wecomTemplateValues) ([]byte, error) {
|
||||
for _, name := range wecomTemplateVariableNames {
|
||||
encoded, err := json.Marshal(values[name])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encode WeCom template value %q: %w", name, err)
|
||||
}
|
||||
template = strings.ReplaceAll(template, "{{"+name+"}}", string(encoded))
|
||||
}
|
||||
if strings.Contains(template, "{{") {
|
||||
return nil, errors.New("wecom.payload_template contains an unsupported variable")
|
||||
}
|
||||
|
||||
var payload map[string]json.RawMessage
|
||||
if err := json.Unmarshal([]byte(template), &payload); err != nil || len(payload) == 0 {
|
||||
return nil, errors.New("wecom.payload_template must render to a non-empty JSON object")
|
||||
}
|
||||
return []byte(template), nil
|
||||
}
|
||||
|
||||
func validateWecomResponse(status int, body []byte) error {
|
||||
var result struct {
|
||||
ErrCode *int `json:"errcode"`
|
||||
}
|
||||
if status < http.StatusOK || status >= http.StatusMultipleChoices ||
|
||||
json.Unmarshal(body, &result) != nil || result.ErrCode == nil || *result.ErrCode != 0 {
|
||||
return fmt.Errorf("%w: WeCom response was not successful", errProviderRejected)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRenderWecomPayloadEscapesTemplateValues(t *testing.T) {
|
||||
payload, err := renderWecomPayload(
|
||||
`{"msgtype":"text","text":{"content":{{message}},"number":{{number}}}}`,
|
||||
wecomTemplateValues{
|
||||
"message": "quote: \"\nline",
|
||||
"number": "+447386",
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, want := string(payload), `{"msgtype":"text","text":{"content":"quote: \"\nline","number":"+447386"}}`; got != want {
|
||||
t.Fatalf("payload = %s, want %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderWecomPayloadRejectsInvalidTemplate(t *testing.T) {
|
||||
for _, template := range []string{
|
||||
`{"text":{{unknown}}}`,
|
||||
`[]`,
|
||||
`{"msgtype":"text"`,
|
||||
} {
|
||||
t.Run(template, func(t *testing.T) {
|
||||
if _, err := renderWecomPayload(template, wecomTemplateValues{}); err == nil {
|
||||
t.Fatalf("template %q was accepted", template)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateWecomResponse(t *testing.T) {
|
||||
if err := validateWecomResponse(http.StatusOK, []byte(`{"errcode":0,"errmsg":"ok"}`)); err != nil {
|
||||
t.Fatalf("successful response = %v", err)
|
||||
}
|
||||
for _, response := range []struct {
|
||||
status int
|
||||
body string
|
||||
}{
|
||||
{http.StatusBadGateway, `{"errcode":0}`},
|
||||
{http.StatusOK, `{"errcode":40058,"errmsg":"invalid"}`},
|
||||
{http.StatusOK, `{}`},
|
||||
{http.StatusOK, `not-json`},
|
||||
} {
|
||||
if err := validateWecomResponse(response.status, []byte(response.body)); !errors.Is(err, errProviderRejected) {
|
||||
t.Fatalf("validateWecomResponse(%d, %s) = %v", response.status, response.body, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user