mirror of
https://github.com/grafana/grafana.git
synced 2025-07-31 07:42:31 +08:00

When modifying rule groups the `uid` can be specified but only if the rule already existed in the DB. If the rule is new the update would be rejected. This updates the RuleGroup provisioning apis to allow specifying the `uid` when creating/updating rule groups. Additionally, the RuleGroupIdx was not being updated when rules were reordered in the group. Context: https://github.com/grafana/terraform-provider-grafana/pull/1971#issuecomment-2599223897 Relates to: https://github.com/grafana/terraform-provider-grafana/issues/1928 Fixes: #98283
1143 lines
33 KiB
Go
1143 lines
33 KiB
Go
package alerting
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
|
"github.com/prometheus/common/model"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/grafana/grafana/pkg/api"
|
|
"github.com/grafana/grafana/pkg/expr"
|
|
"github.com/grafana/grafana/pkg/infra/db"
|
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
"github.com/grafana/grafana/pkg/services/folder"
|
|
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
|
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
"github.com/grafana/grafana/pkg/services/org/orgimpl"
|
|
"github.com/grafana/grafana/pkg/services/quota"
|
|
"github.com/grafana/grafana/pkg/services/quota/quotaimpl"
|
|
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
|
|
"github.com/grafana/grafana/pkg/services/user"
|
|
"github.com/grafana/grafana/pkg/services/user/userimpl"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
"github.com/grafana/grafana/pkg/util"
|
|
)
|
|
|
|
const defaultAlertmanagerConfigJSON = `
|
|
{
|
|
"template_files": null,
|
|
"alertmanager_config": {
|
|
"route": {
|
|
"receiver": "grafana-default-email",
|
|
"group_by": ["grafana_folder", "alertname"]
|
|
},
|
|
"receivers": [{
|
|
"name": "grafana-default-email",
|
|
"grafana_managed_receiver_configs": [{
|
|
"uid": "",
|
|
"name": "email receiver",
|
|
"type": "email",
|
|
"disableResolveMessage": false,
|
|
"settings": {
|
|
"addresses": "\u003cexample@email.com\u003e"
|
|
},
|
|
"secureFields": {}
|
|
}]
|
|
}]
|
|
}
|
|
}
|
|
`
|
|
|
|
type Response struct {
|
|
Message string `json:"message"`
|
|
TraceID string `json:"traceID"`
|
|
}
|
|
|
|
func getRequest(t *testing.T, url string, expStatusCode int) *http.Response {
|
|
t.Helper()
|
|
// nolint:gosec
|
|
resp, err := http.Get(url)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() {
|
|
require.NoError(t, resp.Body.Close())
|
|
})
|
|
if expStatusCode != resp.StatusCode {
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
t.Fatal(string(b))
|
|
}
|
|
return resp
|
|
}
|
|
|
|
func postRequest(t *testing.T, url string, body string, expStatusCode int) *http.Response {
|
|
t.Helper()
|
|
buf := bytes.NewReader([]byte(body))
|
|
// nolint:gosec
|
|
resp, err := http.Post(url, "application/json", buf)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() {
|
|
require.NoError(t, resp.Body.Close())
|
|
})
|
|
if expStatusCode != resp.StatusCode {
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
t.Log(string(b))
|
|
require.Equal(t, expStatusCode, resp.StatusCode)
|
|
}
|
|
return resp
|
|
}
|
|
|
|
func getBody(t *testing.T, body io.ReadCloser) string {
|
|
t.Helper()
|
|
b, err := io.ReadAll(body)
|
|
require.NoError(t, err)
|
|
return string(b)
|
|
}
|
|
|
|
type ruleMutator func(r *apimodels.PostableExtendedRuleNode)
|
|
|
|
func alertRuleGen(mutators ...ruleMutator) func() apimodels.PostableExtendedRuleNode {
|
|
return func() apimodels.PostableExtendedRuleNode {
|
|
forDuration := model.Duration(10 * time.Second)
|
|
rule := apimodels.PostableExtendedRuleNode{
|
|
ApiRuleNode: &apimodels.ApiRuleNode{
|
|
For: &forDuration,
|
|
Labels: map[string]string{"label1": "val1"},
|
|
Annotations: map[string]string{"annotation1": "val1"},
|
|
},
|
|
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
|
Title: fmt.Sprintf("rule-%s", util.GenerateShortUID()),
|
|
Condition: "A",
|
|
Data: []apimodels.AlertQuery{
|
|
{
|
|
RefID: "A",
|
|
RelativeTimeRange: apimodels.RelativeTimeRange{
|
|
From: apimodels.Duration(time.Duration(5) * time.Hour),
|
|
To: apimodels.Duration(time.Duration(3) * time.Hour),
|
|
},
|
|
DatasourceUID: expr.DatasourceUID,
|
|
Model: json.RawMessage(`{
|
|
"type": "math",
|
|
"expression": "2 + 3 > 1"
|
|
}`),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, mutator := range mutators {
|
|
mutator(&rule)
|
|
}
|
|
return rule
|
|
}
|
|
}
|
|
|
|
func withDatasourceQuery(uid string) func(r *apimodels.PostableExtendedRuleNode) {
|
|
data := []apimodels.AlertQuery{
|
|
{
|
|
RefID: "A",
|
|
RelativeTimeRange: apimodels.RelativeTimeRange{
|
|
From: apimodels.Duration(600 * time.Second),
|
|
To: 0,
|
|
},
|
|
DatasourceUID: uid,
|
|
Model: json.RawMessage(fmt.Sprintf(`{
|
|
"refId": "A",
|
|
"hide": false,
|
|
"datasource": {
|
|
"type": "testdata",
|
|
"uid": "%s"
|
|
},
|
|
"scenarioId": "random_walk",
|
|
"seriesCount": 5,
|
|
"labels": "series=series-$seriesIndex"
|
|
}`, uid)),
|
|
},
|
|
{
|
|
RefID: "B",
|
|
DatasourceUID: expr.DatasourceType,
|
|
Model: json.RawMessage(`{
|
|
"type": "reduce",
|
|
"reducer": "last",
|
|
"expression": "A"
|
|
}`),
|
|
},
|
|
{
|
|
RefID: "C",
|
|
DatasourceUID: expr.DatasourceType,
|
|
Model: json.RawMessage(`{
|
|
"refId": "C",
|
|
"type": "threshold",
|
|
"conditions": [
|
|
{
|
|
"type": "query",
|
|
"evaluator": {
|
|
"params": [
|
|
0
|
|
],
|
|
"type": "gt"
|
|
}
|
|
}
|
|
],
|
|
"expression": "B"
|
|
}`),
|
|
},
|
|
}
|
|
|
|
return func(r *apimodels.PostableExtendedRuleNode) {
|
|
r.GrafanaManagedAlert.Data = data
|
|
r.GrafanaManagedAlert.Condition = "C"
|
|
}
|
|
}
|
|
|
|
func generateAlertRuleGroup(rulesCount int, gen func() apimodels.PostableExtendedRuleNode) apimodels.PostableRuleGroupConfig {
|
|
rules := make([]apimodels.PostableExtendedRuleNode, 0, rulesCount)
|
|
for i := 0; i < rulesCount; i++ {
|
|
rules = append(rules, gen())
|
|
}
|
|
return apimodels.PostableRuleGroupConfig{
|
|
Name: "arulegroup-" + uuid.NewString(),
|
|
Interval: model.Duration(10 * time.Second),
|
|
Rules: rules,
|
|
}
|
|
}
|
|
|
|
func convertGettableRuleGroupToPostable(gettable apimodels.GettableRuleGroupConfig) apimodels.PostableRuleGroupConfig {
|
|
rules := make([]apimodels.PostableExtendedRuleNode, 0, len(gettable.Rules))
|
|
for _, rule := range gettable.Rules {
|
|
rules = append(rules, convertGettableRuleToPostable(rule))
|
|
}
|
|
return apimodels.PostableRuleGroupConfig{
|
|
Name: gettable.Name,
|
|
Interval: gettable.Interval,
|
|
Rules: rules,
|
|
}
|
|
}
|
|
|
|
func convertGettableRuleToPostable(gettable apimodels.GettableExtendedRuleNode) apimodels.PostableExtendedRuleNode {
|
|
return apimodels.PostableExtendedRuleNode{
|
|
ApiRuleNode: gettable.ApiRuleNode,
|
|
GrafanaManagedAlert: convertGettableGrafanaRuleToPostable(gettable.GrafanaManagedAlert),
|
|
}
|
|
}
|
|
|
|
func convertGettableGrafanaRuleToPostable(gettable *apimodels.GettableGrafanaRule) *apimodels.PostableGrafanaRule {
|
|
if gettable == nil {
|
|
return nil
|
|
}
|
|
return &apimodels.PostableGrafanaRule{
|
|
Title: gettable.Title,
|
|
Condition: gettable.Condition,
|
|
Data: gettable.Data,
|
|
UID: gettable.UID,
|
|
NoDataState: gettable.NoDataState,
|
|
ExecErrState: gettable.ExecErrState,
|
|
IsPaused: &gettable.IsPaused,
|
|
NotificationSettings: gettable.NotificationSettings,
|
|
Metadata: gettable.Metadata,
|
|
}
|
|
}
|
|
|
|
type apiClient struct {
|
|
url string
|
|
}
|
|
|
|
type LegacyApiClient struct {
|
|
apiClient
|
|
}
|
|
|
|
func NewAlertingLegacyAPIClient(host, user, pass string) LegacyApiClient {
|
|
cli := newAlertingApiClient(host, user, pass)
|
|
return LegacyApiClient{
|
|
apiClient: cli,
|
|
}
|
|
}
|
|
|
|
func newAlertingApiClient(host, user, pass string) apiClient {
|
|
if len(user) == 0 && len(pass) == 0 {
|
|
return apiClient{url: fmt.Sprintf("http://%s", host)}
|
|
}
|
|
return apiClient{url: fmt.Sprintf("http://%s:%s@%s", user, pass, host)}
|
|
}
|
|
|
|
// ReloadCachedPermissions sends a request to access control API to refresh cached user permissions
|
|
func (a apiClient) ReloadCachedPermissions(t *testing.T) {
|
|
t.Helper()
|
|
|
|
u := fmt.Sprintf("%s/api/access-control/user/permissions?reloadcache=true", a.url)
|
|
// nolint:gosec
|
|
resp, err := http.Get(u)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
require.NoErrorf(t, err, "failed to reload permissions cache")
|
|
require.Equalf(t, http.StatusOK, resp.StatusCode, "failed to reload permissions cache")
|
|
}
|
|
|
|
// AssignReceiverPermission sends a request to access control API to assign permissions to a user, role, or team on a receiver.
|
|
func (a apiClient) AssignReceiverPermission(t *testing.T, receiverUID string, cmd accesscontrol.SetResourcePermissionCommand) (int, string) {
|
|
t.Helper()
|
|
|
|
var assignment string
|
|
var assignTo string
|
|
if cmd.UserID != 0 {
|
|
assignment = "users"
|
|
assignTo = fmt.Sprintf("%d", cmd.UserID)
|
|
} else if cmd.TeamID != 0 {
|
|
assignment = "teams"
|
|
assignTo = fmt.Sprintf("%d", cmd.TeamID)
|
|
} else {
|
|
assignment = "builtInRoles"
|
|
assignTo = cmd.BuiltinRole
|
|
}
|
|
|
|
body := strings.NewReader(fmt.Sprintf(`{"permission": "%s"}`, cmd.Permission))
|
|
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/access-control/receivers/%s/%s/%s", a.url, receiverUID, assignment, assignTo), body)
|
|
require.NoError(t, err)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
return resp.StatusCode, string(b)
|
|
}
|
|
|
|
// CreateFolder creates a folder for storing our alerts, and then refreshes the permission cache to make sure that following requests will be accepted
|
|
func (a apiClient) CreateFolder(t *testing.T, uID string, title string, parentUID ...string) {
|
|
t.Helper()
|
|
cmd := folder.CreateFolderCommand{
|
|
UID: uID,
|
|
Title: title,
|
|
}
|
|
if len(parentUID) > 0 {
|
|
cmd.ParentUID = parentUID[0]
|
|
}
|
|
|
|
blob, err := json.Marshal(cmd)
|
|
require.NoError(t, err)
|
|
|
|
payload := string(blob)
|
|
u := fmt.Sprintf("%s/api/folders", a.url)
|
|
r := strings.NewReader(payload)
|
|
// nolint:gosec
|
|
resp, err := http.Post(u, "application/json", r)
|
|
defer func() {
|
|
require.NoError(t, resp.Body.Close())
|
|
}()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
a.ReloadCachedPermissions(t)
|
|
}
|
|
|
|
func (a apiClient) ReloadAlertingFileProvisioning(t *testing.T) {
|
|
t.Helper()
|
|
|
|
u := fmt.Sprintf("%s/api/admin/provisioning/alerting/reload", a.url)
|
|
// nolint:gosec
|
|
resp, err := http.Post(u, "application/json", nil)
|
|
defer func() {
|
|
require.NoError(t, resp.Body.Close())
|
|
}()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
}
|
|
|
|
func (a apiClient) GetOrgQuotaLimits(t *testing.T, orgID int64) (int64, int64) {
|
|
t.Helper()
|
|
|
|
u := fmt.Sprintf("%s/api/orgs/%d/quotas", a.url, orgID)
|
|
// nolint:gosec
|
|
resp, err := http.Get(u)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
|
|
|
results := []quota.QuotaDTO{}
|
|
require.NoError(t, json.Unmarshal(b, &results))
|
|
|
|
var limit int64 = 0
|
|
var used int64 = 0
|
|
for _, q := range results {
|
|
if q.Target != string(ngmodels.QuotaTargetSrv) {
|
|
continue
|
|
}
|
|
limit = q.Limit
|
|
used = q.Used
|
|
}
|
|
return limit, used
|
|
}
|
|
|
|
func (a apiClient) UpdateAlertRuleOrgQuota(t *testing.T, orgID int64, limit int64) {
|
|
t.Helper()
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode("a.UpdateQuotaCmd{
|
|
Target: "alert_rule",
|
|
Limit: limit,
|
|
OrgID: orgID,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
u := fmt.Sprintf("%s/api/orgs/%d/quotas/alert_rule", a.url, orgID)
|
|
// nolint:gosec
|
|
client := &http.Client{}
|
|
req, err := http.NewRequest(http.MethodPut, u, &buf)
|
|
require.NoError(t, err)
|
|
req.Header.Add("Content-Type", "application/json")
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
}
|
|
|
|
func (a apiClient) PostConfiguration(t *testing.T, c apimodels.PostableUserConfig) (bool, error) {
|
|
t.Helper()
|
|
|
|
b, err := json.Marshal(c)
|
|
require.NoError(t, err)
|
|
|
|
u := fmt.Sprintf("%s/api/alertmanager/grafana/config/api/v1/alerts", a.url)
|
|
req, err := http.NewRequest(http.MethodPost, u, bytes.NewReader(b))
|
|
require.NoError(t, err)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err = io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
data := struct {
|
|
Message string `json:"message"`
|
|
}{}
|
|
require.NoError(t, json.Unmarshal(b, &data))
|
|
|
|
if resp.StatusCode == http.StatusAccepted {
|
|
return true, nil
|
|
}
|
|
|
|
return false, errors.New(data.Message)
|
|
}
|
|
|
|
func (a apiClient) PostRulesGroupWithStatus(t *testing.T, folder string, group *apimodels.PostableRuleGroupConfig) (apimodels.UpdateRuleGroupResponse, int, string) {
|
|
t.Helper()
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(group)
|
|
require.NoError(t, err)
|
|
|
|
u := fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules/%s", a.url, folder)
|
|
// nolint:gosec
|
|
resp, err := http.Post(u, "application/json", &buf)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
var m apimodels.UpdateRuleGroupResponse
|
|
if resp.StatusCode == http.StatusAccepted {
|
|
require.NoError(t, json.Unmarshal(b, &m))
|
|
}
|
|
return m, resp.StatusCode, string(b)
|
|
}
|
|
|
|
func (a apiClient) PostRulesGroup(t *testing.T, folder string, group *apimodels.PostableRuleGroupConfig) apimodels.UpdateRuleGroupResponse {
|
|
t.Helper()
|
|
m, status, raw := a.PostRulesGroupWithStatus(t, folder, group)
|
|
requireStatusCode(t, http.StatusAccepted, status, raw)
|
|
return m
|
|
}
|
|
|
|
func (a apiClient) PostRulesExportWithStatus(t *testing.T, folder string, group *apimodels.PostableRuleGroupConfig, params *apimodels.ExportQueryParams) (int, string) {
|
|
t.Helper()
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(group)
|
|
require.NoError(t, err)
|
|
|
|
u, err := url.Parse(fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules/%s/export", a.url, folder))
|
|
require.NoError(t, err)
|
|
|
|
if params != nil {
|
|
q := url.Values{}
|
|
if params.Format != "" {
|
|
q.Set("format", params.Format)
|
|
}
|
|
if params.Download {
|
|
q.Set("download", "true")
|
|
}
|
|
u.RawQuery = q.Encode()
|
|
}
|
|
|
|
req, err := http.NewRequest(http.MethodPost, u.String(), &buf)
|
|
req.Header.Add("Content-Type", "application/json")
|
|
require.NoError(t, err)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
return resp.StatusCode, string(b)
|
|
}
|
|
|
|
func (a apiClient) DeleteRulesGroup(t *testing.T, folder string, group string) (int, string) {
|
|
t.Helper()
|
|
|
|
u := fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules/%s/%s", a.url, folder, group)
|
|
req, err := http.NewRequest(http.MethodDelete, u, nil)
|
|
require.NoError(t, err)
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
return resp.StatusCode, string(b)
|
|
}
|
|
|
|
func (a apiClient) PostSilence(t *testing.T, s apimodels.PostableSilence) (apimodels.PostSilencesOKBody, int, string) {
|
|
t.Helper()
|
|
|
|
b, err := json.Marshal(s)
|
|
require.NoError(t, err)
|
|
|
|
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/alertmanager/grafana/api/v2/silences", a.url), bytes.NewReader(b))
|
|
require.NoError(t, err)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
return sendRequest[apimodels.PostSilencesOKBody](t, req, http.StatusAccepted)
|
|
}
|
|
|
|
func (a apiClient) GetSilence(t *testing.T, id string) (apimodels.GettableSilence, int, string) {
|
|
t.Helper()
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/alertmanager/grafana/api/v2/silence/%s", a.url, id), nil)
|
|
require.NoError(t, err)
|
|
return sendRequest[apimodels.GettableSilence](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) GetSilences(t *testing.T, filters ...string) (apimodels.GettableSilences, int, string) {
|
|
t.Helper()
|
|
|
|
u, err := url.Parse(fmt.Sprintf("%s/api/alertmanager/grafana/api/v2/silences", a.url))
|
|
require.NoError(t, err)
|
|
if len(filters) > 0 {
|
|
u.RawQuery = url.Values{"filter": filters}.Encode()
|
|
}
|
|
|
|
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequest[apimodels.GettableSilences](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) DeleteSilence(t *testing.T, id string) (any, int, string) {
|
|
t.Helper()
|
|
req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/api/alertmanager/grafana/api/v2/silence/%s", a.url, id), nil)
|
|
require.NoError(t, err)
|
|
|
|
type dynamic struct {
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
return sendRequest[dynamic](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) GetRulesGroup(t *testing.T, folder string, group string) (apimodels.RuleGroupConfigResponse, int) {
|
|
result, status, _ := a.GetRulesGroupWithStatus(t, folder, group)
|
|
return result, status
|
|
}
|
|
|
|
func (a apiClient) GetRulesGroupWithStatus(t *testing.T, folder string, group string) (apimodels.RuleGroupConfigResponse, int, []byte) {
|
|
t.Helper()
|
|
u := fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules/%s/%s", a.url, folder, group)
|
|
// nolint:gosec
|
|
resp, err := http.Get(u)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
result := apimodels.RuleGroupConfigResponse{}
|
|
|
|
if http.StatusAccepted == resp.StatusCode {
|
|
require.NoError(t, json.Unmarshal(b, &result))
|
|
}
|
|
return result, resp.StatusCode, b
|
|
}
|
|
|
|
func (a apiClient) GetAllRulesGroupInFolderWithStatus(t *testing.T, folder string) (apimodels.NamespaceConfigResponse, int, []byte) {
|
|
t.Helper()
|
|
u := fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules/%s", a.url, folder)
|
|
// nolint:gosec
|
|
resp, err := http.Get(u)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
result := apimodels.NamespaceConfigResponse{}
|
|
if http.StatusAccepted == resp.StatusCode {
|
|
require.NoError(t, json.Unmarshal(b, &result))
|
|
}
|
|
return result, resp.StatusCode, b
|
|
}
|
|
|
|
func (a apiClient) GetAllRulesWithStatus(t *testing.T) (apimodels.NamespaceConfigResponse, int, []byte) {
|
|
t.Helper()
|
|
u := fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules", a.url)
|
|
// nolint:gosec
|
|
resp, err := http.Get(u)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
result := apimodels.NamespaceConfigResponse{}
|
|
if http.StatusOK == resp.StatusCode {
|
|
require.NoError(t, json.Unmarshal(b, &result))
|
|
}
|
|
return result, resp.StatusCode, b
|
|
}
|
|
|
|
func (a apiClient) ExportRulesWithStatus(t *testing.T, params *apimodels.AlertRulesExportParameters) (int, string) {
|
|
t.Helper()
|
|
u, err := url.Parse(fmt.Sprintf("%s/api/ruler/grafana/api/v1/export/rules", a.url))
|
|
require.NoError(t, err)
|
|
if params != nil {
|
|
q := url.Values{}
|
|
if params.Format != "" {
|
|
q.Set("format", params.Format)
|
|
}
|
|
if params.Download {
|
|
q.Set("download", "true")
|
|
}
|
|
if len(params.FolderUID) > 0 {
|
|
for _, s := range params.FolderUID {
|
|
q.Add("folderUid", s)
|
|
}
|
|
}
|
|
if params.GroupName != "" {
|
|
q.Set("group", params.GroupName)
|
|
}
|
|
if params.RuleUID != "" {
|
|
q.Set("ruleUid", params.RuleUID)
|
|
}
|
|
u.RawQuery = q.Encode()
|
|
}
|
|
|
|
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
|
require.NoError(t, err)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
return resp.StatusCode, string(b)
|
|
}
|
|
|
|
func (a apiClient) GetRuleGroupProvisioning(t *testing.T, folderUID string, groupName string) (apimodels.AlertRuleGroup, int, string) {
|
|
t.Helper()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/provisioning/folder/%s/rule-groups/%s", a.url, folderUID, groupName), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequest[apimodels.AlertRuleGroup](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) CreateOrUpdateRuleGroupProvisioning(t *testing.T, group apimodels.AlertRuleGroup) (apimodels.AlertRuleGroup, int, string) {
|
|
t.Helper()
|
|
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(group)
|
|
require.NoError(t, err)
|
|
|
|
req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("%s/api/v1/provisioning/folder/%s/rule-groups/%s", a.url, group.FolderUID, group.Title), &buf)
|
|
require.NoError(t, err)
|
|
req.Header.Add("Content-Type", "application/json")
|
|
|
|
return sendRequest[apimodels.AlertRuleGroup](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) SubmitRuleForBacktesting(t *testing.T, config apimodels.BacktestConfig) (int, string) {
|
|
t.Helper()
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(config)
|
|
require.NoError(t, err)
|
|
|
|
u := fmt.Sprintf("%s/api/v1/rule/backtest", a.url)
|
|
// nolint:gosec
|
|
resp, err := http.Post(u, "application/json", &buf)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
return resp.StatusCode, string(b)
|
|
}
|
|
|
|
func (a apiClient) SubmitRuleForTesting(t *testing.T, config apimodels.PostableExtendedRuleNodeExtended) (int, string) {
|
|
t.Helper()
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(config)
|
|
require.NoError(t, err)
|
|
|
|
u := fmt.Sprintf("%s/api/v1/rule/test/grafana", a.url)
|
|
// nolint:gosec
|
|
resp, err := http.Post(u, "application/json", &buf)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
return resp.StatusCode, string(b)
|
|
}
|
|
|
|
func (a apiClient) CreateTestDatasource(t *testing.T) (result api.CreateOrUpdateDatasourceResponse) {
|
|
t.Helper()
|
|
|
|
payload := fmt.Sprintf(`{"name":"TestData-%s","type":"testdata","access":"proxy","isDefault":false}`, uuid.NewString())
|
|
buf := bytes.Buffer{}
|
|
buf.Write([]byte(payload))
|
|
|
|
u := fmt.Sprintf("%s/api/datasources", a.url)
|
|
|
|
// nolint:gosec
|
|
resp, err := http.Post(u, "application/json", &buf)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
if resp.StatusCode != 200 {
|
|
require.Failf(t, "failed to create data source", "API request to create a datasource failed. Status code: %d, response: %s", resp.StatusCode, string(b))
|
|
}
|
|
require.NoError(t, json.Unmarshal([]byte(fmt.Sprintf(`{ "body": %s }`, string(b))), &result))
|
|
return result
|
|
}
|
|
|
|
func (a apiClient) DeleteDatasource(t *testing.T, uid string) {
|
|
t.Helper()
|
|
|
|
u := fmt.Sprintf("%s/api/datasources/uid/%s", a.url, uid)
|
|
|
|
req, err := http.NewRequest(http.MethodDelete, u, nil)
|
|
require.NoError(t, err)
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
if resp.StatusCode != 200 {
|
|
require.Failf(t, "failed to create data source", "API request to create a datasource failed. Status code: %d, response: %s", resp.StatusCode, string(b))
|
|
}
|
|
}
|
|
|
|
func (a apiClient) GetAllMuteTimingsWithStatus(t *testing.T) (apimodels.MuteTimings, int, string) {
|
|
t.Helper()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/provisioning/mute-timings", a.url), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequest[apimodels.MuteTimings](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) GetMuteTimingByNameWithStatus(t *testing.T, name string) (apimodels.MuteTimeInterval, int, string) {
|
|
t.Helper()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/provisioning/mute-timings/%s", a.url, name), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequest[apimodels.MuteTimeInterval](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) CreateMuteTimingWithStatus(t *testing.T, interval apimodels.MuteTimeInterval) (apimodels.MuteTimeInterval, int, string) {
|
|
t.Helper()
|
|
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(interval)
|
|
require.NoError(t, err)
|
|
|
|
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/v1/provisioning/mute-timings", a.url), &buf)
|
|
req.Header.Add("Content-Type", "application/json")
|
|
require.NoError(t, err)
|
|
|
|
return sendRequest[apimodels.MuteTimeInterval](t, req, http.StatusCreated)
|
|
}
|
|
|
|
func (a apiClient) EnsureMuteTiming(t *testing.T, interval apimodels.MuteTimeInterval) {
|
|
t.Helper()
|
|
|
|
_, status, body := a.CreateMuteTimingWithStatus(t, interval)
|
|
require.Equalf(t, http.StatusCreated, status, body)
|
|
}
|
|
|
|
func (a apiClient) UpdateMuteTimingWithStatus(t *testing.T, interval apimodels.MuteTimeInterval) (apimodels.MuteTimeInterval, int, string) {
|
|
t.Helper()
|
|
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(interval)
|
|
require.NoError(t, err)
|
|
|
|
req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("%s/api/v1/provisioning/mute-timings/%s", a.url, interval.Name), &buf)
|
|
req.Header.Add("Content-Type", "application/json")
|
|
require.NoError(t, err)
|
|
|
|
return sendRequest[apimodels.MuteTimeInterval](t, req, http.StatusAccepted)
|
|
}
|
|
|
|
func (a apiClient) DeleteMuteTimingWithStatus(t *testing.T, name string) (int, string) {
|
|
t.Helper()
|
|
|
|
req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/api/v1/provisioning/mute-timings/%s", a.url, name), nil)
|
|
req.Header.Add("Content-Type", "application/json")
|
|
require.NoError(t, err)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
body, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
return resp.StatusCode, string(body)
|
|
}
|
|
|
|
func (a apiClient) ExportMuteTiming(t *testing.T, name string, format string) string {
|
|
t.Helper()
|
|
|
|
u, err := url.Parse(fmt.Sprintf("%s/api/v1/provisioning/mute-timings/%s/export", a.url, name))
|
|
require.NoError(t, err)
|
|
q := url.Values{}
|
|
q.Set("format", format)
|
|
u.RawQuery = q.Encode()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
|
require.NoError(t, err)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
body, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
requireStatusCode(t, http.StatusOK, resp.StatusCode, string(body))
|
|
return string(body)
|
|
}
|
|
|
|
func (a apiClient) GetRouteWithStatus(t *testing.T) (apimodels.Route, int, string) {
|
|
t.Helper()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/provisioning/policies", a.url), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequest[apimodels.Route](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) GetRoute(t *testing.T) apimodels.Route {
|
|
t.Helper()
|
|
|
|
route, status, data := a.GetRouteWithStatus(t)
|
|
requireStatusCode(t, http.StatusOK, status, data)
|
|
return route
|
|
}
|
|
|
|
func (a apiClient) UpdateRouteWithStatus(t *testing.T, route apimodels.Route, noProvenance bool) (int, string) {
|
|
t.Helper()
|
|
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(route)
|
|
require.NoError(t, err)
|
|
|
|
req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("%s/api/v1/provisioning/policies", a.url), &buf)
|
|
req.Header.Add("Content-Type", "application/json")
|
|
if noProvenance {
|
|
req.Header.Add("X-Disable-Provenance", "true")
|
|
}
|
|
require.NoError(t, err)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
body, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
return resp.StatusCode, string(body)
|
|
}
|
|
|
|
func (a apiClient) ExportNotificationPolicy(t *testing.T, format string) string {
|
|
t.Helper()
|
|
|
|
u, err := url.Parse(fmt.Sprintf("%s/api/v1/provisioning/policies/export", a.url))
|
|
require.NoError(t, err)
|
|
q := url.Values{}
|
|
q.Set("format", format)
|
|
u.RawQuery = q.Encode()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
|
require.NoError(t, err)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
body, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
requireStatusCode(t, http.StatusOK, resp.StatusCode, string(body))
|
|
return string(body)
|
|
}
|
|
|
|
func (a apiClient) UpdateRoute(t *testing.T, route apimodels.Route, noProvenance bool) {
|
|
t.Helper()
|
|
status, data := a.UpdateRouteWithStatus(t, route, noProvenance)
|
|
requireStatusCode(t, http.StatusAccepted, status, data)
|
|
}
|
|
|
|
func (a apiClient) GetRuleHistoryWithStatus(t *testing.T, ruleUID string) (data.Frame, int, string) {
|
|
t.Helper()
|
|
u, err := url.Parse(fmt.Sprintf("%s/api/v1/rules/history", a.url))
|
|
require.NoError(t, err)
|
|
q := url.Values{}
|
|
q.Set("ruleUID", ruleUID)
|
|
u.RawQuery = q.Encode()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequest[data.Frame](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) GetAllTimeIntervalsWithStatus(t *testing.T) ([]apimodels.GettableTimeIntervals, int, string) {
|
|
t.Helper()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/notifications/time-intervals", a.url), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequest[[]apimodels.GettableTimeIntervals](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) GetTimeIntervalByNameWithStatus(t *testing.T, name string) (apimodels.GettableTimeIntervals, int, string) {
|
|
t.Helper()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/notifications/time-intervals/%s", a.url, name), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequest[apimodels.GettableTimeIntervals](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) CreateReceiverWithStatus(t *testing.T, receiver apimodels.EmbeddedContactPoint) (apimodels.EmbeddedContactPoint, int, string) {
|
|
t.Helper()
|
|
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(receiver)
|
|
require.NoError(t, err)
|
|
|
|
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/v1/provisioning/contact-points", a.url), &buf)
|
|
req.Header.Add("Content-Type", "application/json")
|
|
require.NoError(t, err)
|
|
|
|
return sendRequest[apimodels.EmbeddedContactPoint](t, req, http.StatusAccepted)
|
|
}
|
|
|
|
func (a apiClient) EnsureReceiver(t *testing.T, receiver apimodels.EmbeddedContactPoint) {
|
|
t.Helper()
|
|
|
|
_, status, body := a.CreateReceiverWithStatus(t, receiver)
|
|
require.Equalf(t, http.StatusAccepted, status, body)
|
|
}
|
|
|
|
func (a apiClient) ExportReceiver(t *testing.T, name string, format string, decrypt bool) string {
|
|
t.Helper()
|
|
u, err := url.Parse(fmt.Sprintf("%s/api/v1/provisioning/contact-points/export", a.url))
|
|
require.NoError(t, err)
|
|
q := url.Values{}
|
|
q.Set("name", name)
|
|
q.Set("format", format)
|
|
q.Set("decrypt", fmt.Sprintf("%v", decrypt))
|
|
u.RawQuery = q.Encode()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
|
require.NoError(t, err)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
body, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
requireStatusCode(t, http.StatusOK, resp.StatusCode, string(body))
|
|
return string(body)
|
|
}
|
|
|
|
func (a apiClient) ExportReceiverTyped(t *testing.T, name string, decrypt bool) apimodels.ContactPointExport {
|
|
t.Helper()
|
|
|
|
response := a.ExportReceiver(t, name, "json", decrypt)
|
|
|
|
var export apimodels.AlertingFileExport
|
|
require.NoError(t, json.Unmarshal([]byte(response), &export))
|
|
require.Len(t, export.ContactPoints, 1)
|
|
return export.ContactPoints[0]
|
|
}
|
|
|
|
func (a apiClient) GetAlertmanagerConfigWithStatus(t *testing.T) (apimodels.GettableUserConfig, int, string) {
|
|
t.Helper()
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/alertmanager/grafana/config/api/v1/alerts", a.url), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequest[apimodels.GettableUserConfig](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) GetActiveAlertsWithStatus(t *testing.T) (apimodels.AlertGroups, int, string) {
|
|
t.Helper()
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/alertmanager/grafana/api/v2/alerts/groups", a.url), nil)
|
|
require.NoError(t, err)
|
|
return sendRequest[apimodels.AlertGroups](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) GetRuleVersionsWithStatus(t *testing.T, ruleUID string) (apimodels.GettableRuleVersions, int, string) {
|
|
t.Helper()
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/ruler/grafana/api/v1/rule/%s/versions", a.url, ruleUID), nil)
|
|
require.NoError(t, err)
|
|
return sendRequest[apimodels.GettableRuleVersions](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) GetRuleByUID(t *testing.T, ruleUID string) apimodels.GettableExtendedRuleNode {
|
|
t.Helper()
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/ruler/grafana/api/v1/rule/%s", a.url, ruleUID), nil)
|
|
require.NoError(t, err)
|
|
rule, status, raw := sendRequest[apimodels.GettableExtendedRuleNode](t, req, http.StatusOK)
|
|
requireStatusCode(t, http.StatusOK, status, raw)
|
|
return rule
|
|
}
|
|
|
|
func sendRequest[T any](t *testing.T, req *http.Request, successStatusCode int) (T, int, string) {
|
|
t.Helper()
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
body, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
var result T
|
|
|
|
if resp.StatusCode != successStatusCode {
|
|
return result, resp.StatusCode, string(body)
|
|
}
|
|
|
|
err = json.Unmarshal(body, &result)
|
|
require.NoError(t, err)
|
|
return result, resp.StatusCode, string(body)
|
|
}
|
|
|
|
func requireStatusCode(t *testing.T, expected, actual int, response string) {
|
|
t.Helper()
|
|
require.Equalf(t, expected, actual, "Unexpected status. Response: %s", response)
|
|
}
|
|
|
|
func createUser(t *testing.T, db db.DB, cfg *setting.Cfg, cmd user.CreateUserCommand) int64 {
|
|
t.Helper()
|
|
|
|
cfg.AutoAssignOrg = true
|
|
cfg.AutoAssignOrgId = 1
|
|
|
|
quotaService := quotaimpl.ProvideService(db, cfg)
|
|
orgService, err := orgimpl.ProvideService(db, cfg, quotaService)
|
|
require.NoError(t, err)
|
|
usrSvc, err := userimpl.ProvideService(
|
|
db, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(),
|
|
quotaService, supportbundlestest.NewFakeBundleService(),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
u, err := usrSvc.Create(context.Background(), &cmd)
|
|
require.NoError(t, err)
|
|
return u.ID
|
|
}
|