mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Add support for wecom apiapp (#55991)
This change adds new functionality to the wecom alerting contact point. In addition to a webhook address, you can now send alerts to the wecom apiapp endpoint. Based on https://github.com/grafana/grafana/discussions/55883 Signed-off-by: aimuz <mr.imuz@gmail.com>
This commit is contained in:
parent
b2408dd7c5
commit
c0cc85b5f1
@ -5,40 +5,92 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
"golang.org/x/sync/singleflight"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/notifications"
|
||||
)
|
||||
|
||||
var weComEndpoint = "https://qyapi.weixin.qq.com"
|
||||
|
||||
const defaultWeComChannelType = "groupRobot"
|
||||
const defaultWeComMsgType = WeComMsgTypeMarkdown
|
||||
const defaultWeComToUser = "@all"
|
||||
|
||||
type WeComMsgType string
|
||||
|
||||
const WeComMsgTypeMarkdown WeComMsgType = "markdown" // use these in available_channels.go too
|
||||
const WeComMsgTypeText WeComMsgType = "text"
|
||||
|
||||
// IsValid checks wecom message type
|
||||
func (mt WeComMsgType) IsValid() bool {
|
||||
return mt == WeComMsgTypeMarkdown || mt == WeComMsgTypeText
|
||||
}
|
||||
|
||||
type wecomSettings struct {
|
||||
URL string `json:"url" yaml:"url"`
|
||||
Message string `json:"message,omitempty" yaml:"message,omitempty"`
|
||||
Title string `json:"title,omitempty" yaml:"title,omitempty"`
|
||||
channel string
|
||||
EndpointURL string `json:"endpointUrl,omitempty" yaml:"endpointUrl,omitempty"`
|
||||
URL string `json:"url" yaml:"url"`
|
||||
AgentID string `json:"agent_id,omitempty" yaml:"agent_id,omitempty"`
|
||||
CorpID string `json:"corp_id,omitempty" yaml:"corp_id,omitempty"`
|
||||
Secret string `json:"secret,omitempty" yaml:"secret,omitempty"`
|
||||
MsgType WeComMsgType `json:"msgtype,omitempty" yaml:"msgtype,omitempty"`
|
||||
Message string `json:"message,omitempty" yaml:"message,omitempty"`
|
||||
Title string `json:"title,omitempty" yaml:"title,omitempty"`
|
||||
ToUser string `json:"touser,omitempty" yaml:"touser,omitempty"`
|
||||
}
|
||||
|
||||
func buildWecomSettings(factoryConfig FactoryConfig) (wecomSettings, error) {
|
||||
var settings = wecomSettings{}
|
||||
var settings = wecomSettings{
|
||||
channel: defaultWeComChannelType,
|
||||
}
|
||||
|
||||
err := factoryConfig.Config.unmarshalSettings(&settings)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("failed to unmarshal settings: %w", err)
|
||||
}
|
||||
|
||||
if settings.Message == "" {
|
||||
if len(settings.EndpointURL) == 0 {
|
||||
settings.EndpointURL = weComEndpoint
|
||||
}
|
||||
|
||||
if !settings.MsgType.IsValid() {
|
||||
settings.MsgType = defaultWeComMsgType
|
||||
}
|
||||
|
||||
if len(settings.Message) == 0 {
|
||||
settings.Message = DefaultMessageEmbed
|
||||
}
|
||||
if settings.Title == "" {
|
||||
if len(settings.Title) == 0 {
|
||||
settings.Title = DefaultMessageTitleEmbed
|
||||
}
|
||||
if len(settings.ToUser) == 0 {
|
||||
settings.ToUser = defaultWeComToUser
|
||||
}
|
||||
|
||||
settings.URL = factoryConfig.DecryptFunc(context.Background(), factoryConfig.Config.SecureSettings, "url", settings.URL)
|
||||
if settings.URL == "" {
|
||||
return settings, errors.New("could not find webhook URL in settings")
|
||||
settings.Secret = factoryConfig.DecryptFunc(context.Background(), factoryConfig.Config.SecureSettings, "secret", settings.Secret)
|
||||
|
||||
if len(settings.URL) == 0 && len(settings.Secret) == 0 {
|
||||
return settings, errors.New("either url or secret is required")
|
||||
}
|
||||
|
||||
if len(settings.URL) == 0 {
|
||||
settings.channel = "apiapp"
|
||||
if len(settings.AgentID) == 0 {
|
||||
return settings, errors.New("could not find AgentID in settings")
|
||||
}
|
||||
if len(settings.CorpID) == 0 {
|
||||
return settings, errors.New("could not find CorpID in settings")
|
||||
}
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
@ -76,10 +128,13 @@ func buildWecomNotifier(factoryConfig FactoryConfig) (*WeComNotifier, error) {
|
||||
// WeComNotifier is responsible for sending alert notifications to WeCom.
|
||||
type WeComNotifier struct {
|
||||
*Base
|
||||
tmpl *template.Template
|
||||
log log.Logger
|
||||
ns notifications.WebhookSender
|
||||
settings wecomSettings
|
||||
tmpl *template.Template
|
||||
log log.Logger
|
||||
ns notifications.WebhookSender
|
||||
settings wecomSettings
|
||||
tok *WeComAccessToken
|
||||
tokExpireAt time.Time
|
||||
group singleflight.Group
|
||||
}
|
||||
|
||||
// Notify send an alert notification to WeCom.
|
||||
@ -90,28 +145,50 @@ func (w *WeComNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, e
|
||||
tmpl, _ := TmplText(ctx, w.tmpl, as, w.log, &tmplErr)
|
||||
|
||||
bodyMsg := map[string]interface{}{
|
||||
"msgtype": "markdown",
|
||||
"msgtype": w.settings.MsgType,
|
||||
}
|
||||
content := fmt.Sprintf("# %s\n%s\n",
|
||||
tmpl(w.settings.Title),
|
||||
tmpl(w.settings.Message),
|
||||
)
|
||||
if w.settings.MsgType != defaultWeComMsgType {
|
||||
content = fmt.Sprintf("%s\n%s\n",
|
||||
tmpl(w.settings.Title),
|
||||
tmpl(w.settings.Message),
|
||||
)
|
||||
}
|
||||
|
||||
bodyMsg["markdown"] = map[string]interface{}{
|
||||
msgType := string(w.settings.MsgType)
|
||||
bodyMsg[msgType] = map[string]interface{}{
|
||||
"content": content,
|
||||
}
|
||||
|
||||
url := w.settings.URL
|
||||
if w.settings.channel != defaultWeComChannelType {
|
||||
bodyMsg["agentid"] = w.settings.AgentID
|
||||
bodyMsg["touser"] = w.settings.ToUser
|
||||
token, err := w.GetAccessToken(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
url = fmt.Sprintf(w.settings.EndpointURL+"/cgi-bin/message/send?access_token=%s", token)
|
||||
}
|
||||
|
||||
body, err := json.Marshal(bodyMsg)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if tmplErr != nil {
|
||||
w.log.Warn("failed to template WeCom message", "err", tmplErr.Error())
|
||||
}
|
||||
|
||||
cmd := &models.SendWebhookSync{
|
||||
Url: w.settings.URL,
|
||||
Url: url,
|
||||
Body: string(body),
|
||||
}
|
||||
|
||||
if err := w.ns.SendWebhookSync(ctx, cmd); err != nil {
|
||||
if err = w.ns.SendWebhookSync(ctx, cmd); err != nil {
|
||||
w.log.Error("failed to send WeCom webhook", "err", err, "notification", w.Name)
|
||||
return false, err
|
||||
}
|
||||
@ -119,6 +196,67 @@ func (w *WeComNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, e
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// GetAccessToken returns the access token for apiapp
|
||||
func (w *WeComNotifier) GetAccessToken(ctx context.Context) (string, error) {
|
||||
t := w.tok
|
||||
if w.tokExpireAt.Before(time.Now()) || w.tok == nil {
|
||||
// avoid multiple calls when there are multiple alarms
|
||||
tok, err, _ := w.group.Do("GetAccessToken", func() (interface{}, error) {
|
||||
return w.getAccessToken(ctx)
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
t = tok.(*WeComAccessToken)
|
||||
// expire five minutes in advance to avoid using it when it is about to expire
|
||||
w.tokExpireAt = time.Now().Add(time.Second * time.Duration(t.ExpireIn-300))
|
||||
w.tok = t
|
||||
}
|
||||
return t.AccessToken, nil
|
||||
}
|
||||
|
||||
type WeComAccessToken struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ErrMsg string `json:"errmsg"`
|
||||
ErrCode int `json:"errcode"`
|
||||
ExpireIn int `json:"expire_in"`
|
||||
}
|
||||
|
||||
func (w *WeComNotifier) getAccessToken(ctx context.Context) (*WeComAccessToken, error) {
|
||||
geTokenURL := fmt.Sprintf(w.settings.EndpointURL+"/cgi-bin/gettoken?corpid=%s&corpsecret=%s", w.settings.CorpID, w.settings.Secret)
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodPost, geTokenURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request.Header.Add("Content-Type", "application/json")
|
||||
request.Header.Add("User-Agent", "Grafana")
|
||||
|
||||
resp, err := http.DefaultClient.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode/100 != 2 {
|
||||
return nil, fmt.Errorf("WeCom returned statuscode invalid status code: %v", resp.Status)
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
var accessToken WeComAccessToken
|
||||
err = json.NewDecoder(resp.Body).Decode(&accessToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if accessToken.ErrCode != 0 {
|
||||
return nil, fmt.Errorf("WeCom returned errmsg: %s", accessToken.ErrMsg)
|
||||
}
|
||||
return &accessToken, nil
|
||||
}
|
||||
|
||||
func (w *WeComNotifier) SendResolved() bool {
|
||||
return !w.GetDisableResolveMessage()
|
||||
}
|
||||
|
@ -3,8 +3,13 @@ package channels
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/secrets/fakes"
|
||||
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
|
||||
@ -12,6 +17,7 @@ import (
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
@ -50,7 +56,8 @@ func TestWeComNotifier(t *testing.T) {
|
||||
"msgtype": "markdown",
|
||||
},
|
||||
expMsgError: nil,
|
||||
}, {
|
||||
},
|
||||
{
|
||||
name: "Custom config with multiple alerts",
|
||||
settings: `{
|
||||
"url": "http://localhost",
|
||||
@ -76,7 +83,8 @@ func TestWeComNotifier(t *testing.T) {
|
||||
"msgtype": "markdown",
|
||||
},
|
||||
expMsgError: nil,
|
||||
}, {
|
||||
},
|
||||
{
|
||||
name: "Custom title and message with multiple alerts",
|
||||
settings: `{
|
||||
"url": "http://localhost",
|
||||
@ -103,10 +111,11 @@ func TestWeComNotifier(t *testing.T) {
|
||||
"msgtype": "markdown",
|
||||
},
|
||||
expMsgError: nil,
|
||||
}, {
|
||||
},
|
||||
{
|
||||
name: "Error in initing",
|
||||
settings: `{}`,
|
||||
expInitError: `could not find webhook URL in settings`,
|
||||
expInitError: `either url or secret is required`,
|
||||
},
|
||||
{
|
||||
name: "Use default if optional fields are explicitly empty",
|
||||
@ -127,6 +136,25 @@ func TestWeComNotifier(t *testing.T) {
|
||||
},
|
||||
expMsgError: nil,
|
||||
},
|
||||
{
|
||||
name: "Use text are explicitly empty",
|
||||
settings: `{"url": "http://localhost", "message": "", "title": "", "msgtype": "text"}`,
|
||||
alerts: []*types.Alert{
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
|
||||
Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expMsg: map[string]interface{}{
|
||||
"text": map[string]interface{}{
|
||||
"content": "[FIRING:1] (val1)\n**Firing**\n\nValue: [no value]\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matcher=alertname%3Dalert1&matcher=lbl1%3Dval1\nDashboard: http://localhost/d/abcd\nPanel: http://localhost/d/abcd?viewPanel=efgh\n\n",
|
||||
},
|
||||
"msgtype": "text",
|
||||
},
|
||||
expMsgError: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
@ -178,3 +206,353 @@ func TestWeComNotifier(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestWeComNotifierAPIAPP Testing API Channels
|
||||
func TestWeComNotifierAPIAPP(t *testing.T) {
|
||||
tmpl := templateForTests(t)
|
||||
|
||||
externalURL, err := url.Parse("http://localhost")
|
||||
require.NoError(t, err)
|
||||
tmpl.ExternalURL = externalURL
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
settings string
|
||||
statusCode int
|
||||
accessToken string
|
||||
alerts []*types.Alert
|
||||
expMsg map[string]interface{}
|
||||
expInitError string
|
||||
expMsgError error
|
||||
}{
|
||||
{
|
||||
name: "not AgentID",
|
||||
settings: `{"secret": "secret"}`,
|
||||
accessToken: "access_token",
|
||||
expInitError: "could not find AgentID in settings",
|
||||
},
|
||||
{
|
||||
name: "not CorpID",
|
||||
settings: `{"secret": "secret", "agent_id": "agent_id"}`,
|
||||
accessToken: "access_token",
|
||||
expInitError: "could not find CorpID in settings",
|
||||
},
|
||||
{
|
||||
name: "Default APIAPP config with one alert",
|
||||
settings: `{"secret": "secret", "agent_id": "agent_id", "corp_id": "corp_id"}`,
|
||||
accessToken: "access_token",
|
||||
expInitError: "",
|
||||
alerts: []*types.Alert{
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
|
||||
Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expMsg: map[string]interface{}{
|
||||
"markdown": map[string]interface{}{
|
||||
"content": "# [FIRING:1] (val1)\n**Firing**\n\nValue: [no value]\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matcher=alertname%3Dalert1&matcher=lbl1%3Dval1\nDashboard: http://localhost/d/abcd\nPanel: http://localhost/d/abcd?viewPanel=efgh\n\n",
|
||||
},
|
||||
"msgtype": "markdown",
|
||||
"agentid": "agent_id",
|
||||
"touser": "@all",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Custom message(markdown) with multiple alert",
|
||||
settings: `{
|
||||
"secret": "secret", "agent_id": "agent_id", "corp_id": "corp_id",
|
||||
"message": "{{ len .Alerts.Firing }} alerts are firing, {{ len .Alerts.Resolved }} are resolved"}
|
||||
`,
|
||||
accessToken: "access_token",
|
||||
expInitError: "",
|
||||
alerts: []*types.Alert{
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
|
||||
Annotations: model.LabelSet{"ann1": "annv1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val2"},
|
||||
Annotations: model.LabelSet{"ann1": "annv2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expMsg: map[string]interface{}{
|
||||
"markdown": map[string]interface{}{
|
||||
"content": "# [FIRING:2] \n2 alerts are firing, 0 are resolved\n",
|
||||
},
|
||||
"msgtype": "markdown",
|
||||
"agentid": "agent_id",
|
||||
"touser": "@all",
|
||||
},
|
||||
expMsgError: nil,
|
||||
},
|
||||
{
|
||||
name: "Custom message(Text) with multiple alert",
|
||||
settings: `{
|
||||
"secret": "secret", "agent_id": "agent_id", "corp_id": "corp_id",
|
||||
"msgtype": "text",
|
||||
"message": "{{ len .Alerts.Firing }} alerts are firing, {{ len .Alerts.Resolved }} are resolved"}
|
||||
`,
|
||||
accessToken: "access_token",
|
||||
expInitError: "",
|
||||
alerts: []*types.Alert{
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
|
||||
Annotations: model.LabelSet{"ann1": "annv1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val2"},
|
||||
Annotations: model.LabelSet{"ann1": "annv2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expMsg: map[string]interface{}{
|
||||
"text": map[string]interface{}{
|
||||
"content": "[FIRING:2] \n2 alerts are firing, 0 are resolved\n",
|
||||
},
|
||||
"msgtype": "text",
|
||||
"agentid": "agent_id",
|
||||
"touser": "@all",
|
||||
},
|
||||
expMsgError: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
accessToken := r.URL.Query().Get("access_token")
|
||||
if accessToken != tt.accessToken {
|
||||
t.Errorf("Expected access_token=%s got %s", tt.accessToken, accessToken)
|
||||
return
|
||||
}
|
||||
|
||||
expBody, err := json.Marshal(tt.expMsg)
|
||||
require.NoError(t, err)
|
||||
|
||||
b, err := io.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, string(expBody), string(b))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
settingsJSON, err := simplejson.NewJson([]byte(tt.settings))
|
||||
require.NoError(t, err)
|
||||
|
||||
m := &NotificationChannelConfig{
|
||||
Name: "wecom_testing",
|
||||
Type: "wecom",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
webhookSender := mockNotificationService()
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
|
||||
fc := FactoryConfig{
|
||||
Config: m,
|
||||
NotificationService: webhookSender,
|
||||
DecryptFunc: secretsService.GetDecryptedValue,
|
||||
ImageStore: nil,
|
||||
Template: tmpl,
|
||||
}
|
||||
|
||||
pn, err := buildWecomNotifier(fc)
|
||||
if tt.expInitError != "" {
|
||||
require.Equal(t, tt.expInitError, err.Error())
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := notify.WithGroupKey(context.Background(), "alertname")
|
||||
ctx = notify.WithGroupLabels(ctx, model.LabelSet{"alertname": ""})
|
||||
|
||||
// Avoid calling GetAccessToken interfaces
|
||||
pn.tokExpireAt = time.Now().Add(10 * time.Second)
|
||||
pn.tok = &WeComAccessToken{AccessToken: tt.accessToken}
|
||||
|
||||
ok, err := pn.Notify(ctx, tt.alerts...)
|
||||
if tt.expMsgError != nil {
|
||||
require.False(t, ok)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, tt.expMsgError.Error(), err.Error())
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.True(t, ok)
|
||||
|
||||
expBody, err := json.Marshal(tt.expMsg)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.JSONEq(t, string(expBody), webhookSender.Webhook.Body)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWeComNotifier_GetAccessToken(t *testing.T) {
|
||||
type fields struct {
|
||||
tok *WeComAccessToken
|
||||
tokExpireAt time.Time
|
||||
corpid string
|
||||
secret string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want string
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "no corpid",
|
||||
fields: fields{
|
||||
tok: nil,
|
||||
tokExpireAt: time.Now().Add(-time.Minute),
|
||||
},
|
||||
want: "",
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.Error(t, err, i...)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no corpsecret",
|
||||
fields: fields{
|
||||
tok: nil,
|
||||
tokExpireAt: time.Now().Add(-time.Minute),
|
||||
},
|
||||
want: "",
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.Error(t, err, i...)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "get access token",
|
||||
fields: fields{
|
||||
corpid: "corpid",
|
||||
secret: "secret",
|
||||
},
|
||||
want: "access_token",
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
corpid := r.URL.Query().Get("corpid")
|
||||
corpsecret := r.URL.Query().Get("corpsecret")
|
||||
|
||||
assert.Equal(t, corpid, tt.fields.corpid, fmt.Sprintf("Expected corpid=%s got %s", tt.fields.corpid, corpid))
|
||||
if len(corpid) == 0 {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, corpsecret, tt.fields.secret, fmt.Sprintf("Expected corpsecret=%s got %s", tt.fields.secret, corpsecret))
|
||||
if len(corpsecret) == 0 {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
b, err := json.Marshal(map[string]interface{}{
|
||||
"errcode": 0,
|
||||
"errmsg": "ok",
|
||||
"access_token": tt.want,
|
||||
"expires_in": 7200,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err = w.Write(b)
|
||||
assert.NoError(t, err)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
w := &WeComNotifier{
|
||||
settings: wecomSettings{
|
||||
EndpointURL: server.URL,
|
||||
CorpID: tt.fields.corpid,
|
||||
Secret: tt.fields.secret,
|
||||
},
|
||||
tok: tt.fields.tok,
|
||||
tokExpireAt: tt.fields.tokExpireAt,
|
||||
}
|
||||
got, err := w.GetAccessToken(context.Background())
|
||||
if !tt.wantErr(t, err, "GetAccessToken()") {
|
||||
return
|
||||
}
|
||||
assert.Equalf(t, tt.want, got, "GetAccessToken()")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWeComFactory(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
settings string
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "null",
|
||||
settings: "{}",
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.Contains(t, err.Error(), "either url or secret is required", i...)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "webhook url",
|
||||
settings: `{"url": "https://example.com"}`,
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "apiapp missing AgentID",
|
||||
settings: `{"secret": "secret"}`,
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.Contains(t, err.Error(), "could not find AgentID in settings", i...)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "apiapp missing CorpID",
|
||||
settings: `{"secret": "secret", "agent_id": "agent_id"}`,
|
||||
wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.Contains(t, err.Error(), "could not find CorpID in settings", i...)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "apiapp",
|
||||
settings: `{"secret": "secret", "agent_id": "agent_id", "corp_id": "corp_id"}`,
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
settingsJSON, err := simplejson.NewJson([]byte(tt.settings))
|
||||
require.NoError(t, err)
|
||||
|
||||
m := &NotificationChannelConfig{
|
||||
Name: "wecom_testing",
|
||||
Type: "wecom",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
webhookSender := mockNotificationService()
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
|
||||
fc := FactoryConfig{
|
||||
Config: m,
|
||||
NotificationService: webhookSender,
|
||||
DecryptFunc: secretsService.GetDecryptedValue,
|
||||
ImageStore: nil,
|
||||
}
|
||||
|
||||
_, err = WeComFactory(fc)
|
||||
if !tt.wantErr(t, err, fmt.Sprintf("WeComFactory(%v)", fc)) {
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -696,13 +696,62 @@ func GetAvailableNotifiers() []*NotifierPlugin {
|
||||
Heading: "WeCom settings",
|
||||
Options: []NotifierOption{
|
||||
{
|
||||
Label: "URL",
|
||||
Label: "Webhook URL",
|
||||
Description: "Required if using GroupRobot",
|
||||
Element: ElementTypeInput,
|
||||
InputType: InputTypeText,
|
||||
Placeholder: "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxxxxxx",
|
||||
PropertyName: "url",
|
||||
Required: true,
|
||||
Secure: true,
|
||||
Required: true,
|
||||
DependsOn: "secret",
|
||||
},
|
||||
{
|
||||
Label: "Agent ID",
|
||||
Description: "Required if using APIAPP, see https://work.weixin.qq.com/wework_admin/frame#apps create ApiApp",
|
||||
Element: ElementTypeInput,
|
||||
InputType: InputTypeText,
|
||||
Placeholder: "1000002",
|
||||
PropertyName: "agent_id",
|
||||
Required: true,
|
||||
DependsOn: "url",
|
||||
},
|
||||
{
|
||||
Label: "Corp ID",
|
||||
Description: "Required if using APIAPP, see https://work.weixin.qq.com/wework_admin/frame#profile",
|
||||
Element: ElementTypeInput,
|
||||
InputType: InputTypeText,
|
||||
Placeholder: "wwxxxxxxxxx",
|
||||
PropertyName: "corp_id",
|
||||
Required: true,
|
||||
DependsOn: "url",
|
||||
},
|
||||
{
|
||||
Label: "Secret",
|
||||
Description: "Required if using APIAPP",
|
||||
Element: ElementTypeInput,
|
||||
InputType: InputTypePassword,
|
||||
Placeholder: "secret",
|
||||
PropertyName: "secret",
|
||||
Secure: true,
|
||||
Required: true,
|
||||
DependsOn: "url",
|
||||
},
|
||||
{
|
||||
Label: "Message Type",
|
||||
Element: ElementTypeSelect,
|
||||
PropertyName: "msgtype",
|
||||
SelectOptions: []SelectOption{
|
||||
{
|
||||
Value: "text",
|
||||
Label: "Text",
|
||||
},
|
||||
{
|
||||
Value: "markdown",
|
||||
Label: "Markdown",
|
||||
},
|
||||
},
|
||||
Placeholder: "Text",
|
||||
},
|
||||
{
|
||||
Label: "Message",
|
||||
@ -719,6 +768,13 @@ func GetAvailableNotifiers() []*NotifierPlugin {
|
||||
PropertyName: "title",
|
||||
Placeholder: `{{ template "default.title" . }}`,
|
||||
},
|
||||
{
|
||||
Label: "To User",
|
||||
Element: ElementTypeInput,
|
||||
InputType: InputTypeText,
|
||||
Placeholder: "@all",
|
||||
PropertyName: "touser",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user