NOISSUE - Add alarms, reports and rules sdk (#423)

* add alarms, reports and rules sdk

Signed-off-by: ianmuchyri <ianmuchiri8@gmail.com>

* fix tests

Signed-off-by: ianmuchyri <ianmuchiri8@gmail.com>

* fix linter

Signed-off-by: ianmuchyri <ianmuchiri8@gmail.com>

---------

Signed-off-by: ianmuchyri <ianmuchiri8@gmail.com>
This commit is contained in:
Ian Ngethe Muchiri
2026-03-05 12:54:14 +03:00
committed by GitHub
parent a031426715
commit 8e75edc9f5
9 changed files with 4602 additions and 11 deletions
+115
View File
@@ -0,0 +1,115 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package sdk
import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/absmach/supermq/pkg/errors"
)
const alarmsEndpoint = "alarms"
// Alarm represents an alarm instance.
type Alarm struct {
ID string `json:"id,omitempty"`
RuleID string `json:"rule_id,omitempty"`
DomainID string `json:"domain_id,omitempty"`
ChannelID string `json:"channel_id,omitempty"`
ClientID string `json:"client_id,omitempty"`
Subtopic string `json:"subtopic,omitempty"`
Status string `json:"status,omitempty"`
Measurement string `json:"measurement,omitempty"`
Value string `json:"value,omitempty"`
Unit string `json:"unit,omitempty"`
Threshold string `json:"threshold,omitempty"`
Cause string `json:"cause,omitempty"`
Severity uint8 `json:"severity,omitempty"`
AssigneeID string `json:"assignee_id,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
UpdatedBy string `json:"updated_by,omitempty"`
AssignedAt time.Time `json:"assigned_at,omitempty"`
AssignedBy string `json:"assigned_by,omitempty"`
AcknowledgedAt time.Time `json:"acknowledged_at,omitempty"`
AcknowledgedBy string `json:"acknowledged_by,omitempty"`
ResolvedAt time.Time `json:"resolved_at,omitempty"`
ResolvedBy string `json:"resolved_by,omitempty"`
Metadata Metadata `json:"metadata,omitempty"`
}
type AlarmsPage struct {
Offset uint64 `json:"offset"`
Limit uint64 `json:"limit"`
Total uint64 `json:"total"`
Alarms []Alarm `json:"alarms"`
}
func (sdk mgSDK) UpdateAlarm(ctx context.Context, alarm Alarm, domainID, token string) (Alarm, errors.SDKError) {
data, err := json.Marshal(alarm)
if err != nil {
return Alarm{}, errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s/%s", sdk.alarmsURL, domainID, alarmsEndpoint, alarm.ID)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPut, url, token, data, nil, http.StatusOK)
if sdkerr != nil {
return Alarm{}, sdkerr
}
var a Alarm
if err := json.Unmarshal(body, &a); err != nil {
return Alarm{}, errors.NewSDKError(err)
}
return a, nil
}
func (sdk mgSDK) ViewAlarm(ctx context.Context, id, domainID, token string) (Alarm, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s/%s", sdk.alarmsURL, domainID, alarmsEndpoint, id)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, url, token, nil, nil, http.StatusOK)
if sdkerr != nil {
return Alarm{}, sdkerr
}
var a Alarm
if err := json.Unmarshal(body, &a); err != nil {
return Alarm{}, errors.NewSDKError(err)
}
return a, nil
}
func (sdk mgSDK) ListAlarms(ctx context.Context, pm PageMetadata, domainID, token string) (AlarmsPage, errors.SDKError) {
endpoint := fmt.Sprintf("%s/%s", domainID, alarmsEndpoint)
url, err := sdk.withQueryParams(sdk.alarmsURL, endpoint, pm)
if err != nil {
return AlarmsPage{}, errors.NewSDKError(err)
}
_, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, url, token, nil, nil, http.StatusOK)
if sdkerr != nil {
return AlarmsPage{}, sdkerr
}
var ap AlarmsPage
if err := json.Unmarshal(body, &ap); err != nil {
return AlarmsPage{}, errors.NewSDKError(err)
}
return ap, nil
}
func (sdk mgSDK) DeleteAlarm(ctx context.Context, id, domainID, token string) errors.SDKError {
url := fmt.Sprintf("%s/%s/%s/%s", sdk.alarmsURL, domainID, alarmsEndpoint, id)
_, _, sdkerr := sdk.processRequest(ctx, http.MethodDelete, url, token, nil, nil, http.StatusNoContent, http.StatusOK)
return sdkerr
}
+390
View File
@@ -0,0 +1,390 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package sdk_test
import (
"context"
"net/http/httptest"
"testing"
"time"
"github.com/absmach/magistrala/alarms"
"github.com/absmach/magistrala/alarms/api"
amocks "github.com/absmach/magistrala/alarms/mocks"
"github.com/absmach/magistrala/pkg/sdk"
smqlog "github.com/absmach/supermq/logger"
smqauthn "github.com/absmach/supermq/pkg/authn"
authnmocks "github.com/absmach/supermq/pkg/authn/mocks"
"github.com/absmach/supermq/pkg/errors"
"github.com/absmach/supermq/pkg/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
const alarmID = "alarm-1"
var testAlarm = sdk.Alarm{
ID: alarmID,
RuleID: "rule-1",
DomainID: domainID,
ChannelID: "chan-1",
ClientID: "client-1",
Subtopic: "subtopic",
Status: "active",
Measurement: "temperature",
Value: "30.5",
Unit: "C",
Threshold: "25",
Cause: "threshold_exceeded",
Severity: 80,
AssigneeID: "user-1",
Metadata: sdk.Metadata{"key": "value"},
}
func setupAlarms() (*httptest.Server, *amocks.Service, *authnmocks.Authentication) {
asvc := new(amocks.Service)
logger := smqlog.NewMock()
authn := new(authnmocks.Authentication)
am := smqauthn.NewAuthNMiddleware(authn, smqauthn.WithAllowUnverifiedUser(true))
idp := uuid.NewMock()
mux := api.MakeHandler(asvc, logger, idp, "", am)
return httptest.NewServer(mux), asvc, authn
}
func TestUpdateAlarm(t *testing.T) {
as, asvc, auth := setupAlarms()
defer as.Close()
conf := sdk.Config{
AlarmsURL: as.URL,
}
mgsdk := sdk.NewSDK(conf)
updated := testAlarm
updated.Status = "cleared"
svcAlarm := alarms.Alarm{
ID: alarmID,
RuleID: "rule-1",
DomainID: domainID,
ChannelID: "chan-1",
ClientID: "client-1",
Subtopic: "subtopic",
Status: alarms.ClearedStatus,
Measurement: "temperature",
Value: "30.5",
Unit: "C",
Threshold: "25",
Cause: "threshold_exceeded",
Severity: 80,
AssigneeID: "user-1",
Metadata: alarms.Metadata{"key": "value"},
}
cases := []struct {
desc string
alarm sdk.Alarm
token string
session smqauthn.Session
svcRes alarms.Alarm
svcErr error
authenticateErr error
wantErr bool
resp sdk.Alarm
}{
{
desc: "update alarm successfully",
alarm: updated,
token: validToken,
svcRes: svcAlarm,
resp: testAlarm,
},
{
desc: "update alarm with empty token",
alarm: updated,
token: "",
wantErr: true,
},
{
desc: "update non-existent alarm",
alarm: sdk.Alarm{ID: "non-existent"},
token: validToken,
svcErr: errors.New("not found"),
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := asvc.On("UpdateAlarm", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.UpdateAlarm(context.Background(), tc.alarm, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestViewAlarm(t *testing.T) {
as, asvc, auth := setupAlarms()
defer as.Close()
conf := sdk.Config{
AlarmsURL: as.URL,
}
mgsdk := sdk.NewSDK(conf)
svcAlarm := alarms.Alarm{
ID: alarmID,
RuleID: "rule-1",
DomainID: domainID,
ChannelID: "chan-1",
ClientID: "client-1",
Subtopic: "subtopic",
Status: alarms.ActiveStatus,
Measurement: "temperature",
Value: "30.5",
Unit: "C",
Threshold: "25",
Cause: "threshold_exceeded",
Severity: 80,
AssigneeID: "user-1",
Metadata: alarms.Metadata{"key": "value"},
}
cases := []struct {
desc string
id string
token string
session smqauthn.Session
svcRes alarms.Alarm
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "view alarm successfully",
id: alarmID,
token: validToken,
svcRes: svcAlarm,
},
{
desc: "view alarm with empty token",
id: alarmID,
token: "",
wantErr: true,
},
{
desc: "view non-existent alarm",
id: "non-existent",
token: validToken,
svcErr: errors.New("not found"),
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := asvc.On("ViewAlarm", mock.Anything, tc.session, tc.id).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.ViewAlarm(context.Background(), tc.id, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestListAlarms(t *testing.T) {
as, asvc, auth := setupAlarms()
defer as.Close()
conf := sdk.Config{
AlarmsURL: as.URL,
}
mgsdk := sdk.NewSDK(conf)
svcAlarm := alarms.Alarm{
ID: alarmID,
RuleID: "rule-1",
DomainID: domainID,
ChannelID: "chan-1",
ClientID: "client-1",
Subtopic: "subtopic",
Status: alarms.ActiveStatus,
Measurement: "temperature",
Value: "30.5",
Unit: "C",
Threshold: "25",
Cause: "threshold_exceeded",
Severity: 80,
AssigneeID: "user-1",
Metadata: alarms.Metadata{"key": "value"},
}
svcAlarmsPage := alarms.AlarmsPage{
Total: 2,
Offset: 0,
Limit: 10,
Alarms: []alarms.Alarm{svcAlarm},
}
cases := []struct {
desc string
pm sdk.PageMetadata
token string
session smqauthn.Session
svcRes alarms.AlarmsPage
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "list alarms successfully",
pm: sdk.PageMetadata{Offset: 0, Limit: 10},
token: validToken,
svcRes: svcAlarmsPage,
},
{
desc: "list alarms with status and entity filters",
pm: sdk.PageMetadata{
Limit: 5,
Status: "active",
ChannelID: "chan-1",
ClientID: "client-1",
RuleID: "rule-1",
AssigneeID: "user-1",
Severity: 80,
},
token: validToken,
svcRes: svcAlarmsPage,
},
{
desc: "list alarms with time range and sorting",
pm: sdk.PageMetadata{
Limit: 10,
CreatedFrom: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
CreatedTo: time.Date(2024, 12, 31, 0, 0, 0, 0, time.UTC),
Order: "created_at",
Dir: "asc",
},
token: validToken,
svcRes: svcAlarmsPage,
},
{
desc: "list alarms with actor filters",
pm: sdk.PageMetadata{
Limit: 10,
UpdatedBy: "user-2",
AssignedBy: "user-3",
AcknowledgedBy: "user-4",
ResolvedBy: "user-5",
Subtopic: "subtopic-1",
},
token: validToken,
svcRes: svcAlarmsPage,
},
{
desc: "list alarms with empty metadata excludes severity",
pm: sdk.PageMetadata{},
token: validToken,
svcRes: alarms.AlarmsPage{},
},
{
desc: "list alarms with zero severity excluded",
pm: sdk.PageMetadata{Status: "active", Severity: 0},
token: validToken,
svcRes: alarms.AlarmsPage{},
},
{
desc: "list alarms with empty token",
pm: sdk.PageMetadata{Limit: 10},
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := asvc.On("ListAlarms", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.ListAlarms(context.Background(), tc.pm, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.Equal(t, tc.svcRes.Total, result.Total)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestDeleteAlarm(t *testing.T) {
as, asvc, auth := setupAlarms()
defer as.Close()
conf := sdk.Config{
AlarmsURL: as.URL,
}
mgsdk := sdk.NewSDK(conf)
cases := []struct {
desc string
id string
token string
session smqauthn.Session
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "delete alarm successfully",
id: alarmID,
token: validToken,
},
{
desc: "delete alarm with empty token",
id: alarmID,
token: "",
wantErr: true,
},
{
desc: "delete non-existent alarm",
id: "non-existent",
token: validToken,
svcErr: errors.New("not found"),
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := asvc.On("DeleteAlarm", mock.Anything, tc.session, tc.id).Return(tc.svcErr)
err := mgsdk.DeleteAlarm(context.Background(), tc.id, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
svcCall.Unset()
authCall.Unset()
})
}
}
+1971
View File
File diff suppressed because it is too large Load Diff
+302
View File
@@ -0,0 +1,302 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package sdk
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"github.com/absmach/supermq/pkg/errors"
)
const (
reportsEndpoint = "reports"
configsEndpointReports = "configs"
)
// ReportConfig represents a report configuration.
type ReportConfig struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
DomainID string `json:"domain_id,omitempty"`
Schedule any `json:"schedule,omitempty"`
Config any `json:"config,omitempty"`
Email any `json:"email,omitempty"`
Metrics any `json:"metrics,omitempty"`
ReportTemplate ReportTemplate `json:"report_template,omitempty"`
Status string `json:"status,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
CreatedBy string `json:"created_by,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
UpdatedBy string `json:"updated_by,omitempty"`
}
type ReportTemplate any
type ReportFile struct {
Name string
Format string
Data []byte
}
type ReportPage struct {
Total uint64 `json:"total"`
From time.Time `json:"from,omitempty"`
To time.Time `json:"to,omitempty"`
Aggregation any `json:"aggregation,omitempty"`
Reports any `json:"reports,omitempty"`
File any `json:"file,omitempty"`
}
type ReportConfigPage struct {
Total uint64 `json:"total"`
Offset uint64 `json:"offset"`
Limit uint64 `json:"limit"`
ReportConfigs []ReportConfig `json:"report_configs"`
}
type ReportAction string
const (
ViewReportAction ReportAction = "view"
DownloadReportAction ReportAction = "download"
EmailReportAction ReportAction = "email"
)
func (sdk mgSDK) AddReportConfig(ctx context.Context, cfg ReportConfig, domainID, token string) (ReportConfig, errors.SDKError) {
data, err := json.Marshal(cfg)
if err != nil {
return ReportConfig{}, errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s/%s", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPost, url, token, data, nil, http.StatusCreated, http.StatusOK)
if sdkerr != nil {
return ReportConfig{}, sdkerr
}
var rc ReportConfig
if err := json.Unmarshal(body, &rc); err != nil {
return ReportConfig{}, errors.NewSDKError(err)
}
return rc, nil
}
func (sdk mgSDK) ViewReportConfig(ctx context.Context, id, domainID, token string) (ReportConfig, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, id)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, url, token, nil, nil, http.StatusOK)
if sdkerr != nil {
return ReportConfig{}, sdkerr
}
var rc ReportConfig
if err := json.Unmarshal(body, &rc); err != nil {
return ReportConfig{}, errors.NewSDKError(err)
}
return rc, nil
}
func (sdk mgSDK) UpdateReportConfig(ctx context.Context, cfg ReportConfig, domainID, token string) (ReportConfig, errors.SDKError) {
data, err := json.Marshal(cfg)
if err != nil {
return ReportConfig{}, errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, cfg.ID)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPatch, url, token, data, nil, http.StatusOK)
if sdkerr != nil {
return ReportConfig{}, sdkerr
}
var rc ReportConfig
if err := json.Unmarshal(body, &rc); err != nil {
return ReportConfig{}, errors.NewSDKError(err)
}
return rc, nil
}
func (sdk mgSDK) UpdateReportSchedule(ctx context.Context, cfg ReportConfig, domainID, token string) (ReportConfig, errors.SDKError) {
data, err := json.Marshal(map[string]any{"schedule": cfg.Schedule})
if err != nil {
return ReportConfig{}, errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s/%s/%s/schedule", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, cfg.ID)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPatch, url, token, data, nil, http.StatusOK)
if sdkerr != nil {
return ReportConfig{}, sdkerr
}
var rc ReportConfig
if err := json.Unmarshal(body, &rc); err != nil {
return ReportConfig{}, errors.NewSDKError(err)
}
return rc, nil
}
func (sdk mgSDK) RemoveReportConfig(ctx context.Context, id, domainID, token string) errors.SDKError {
url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, id)
_, _, sdkerr := sdk.processRequest(ctx, http.MethodDelete, url, token, nil, nil, http.StatusNoContent, http.StatusOK)
return sdkerr
}
func (sdk mgSDK) ListReportsConfig(ctx context.Context, pm PageMetadata, domainID, token string) (ReportConfigPage, errors.SDKError) {
endpoint := fmt.Sprintf("%s/%s/%s", domainID, reportsEndpoint, configsEndpointReports)
url, err := sdk.withQueryParams(sdk.reportsURL, endpoint, pm)
if err != nil {
return ReportConfigPage{}, errors.NewSDKError(err)
}
_, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, url, token, nil, nil, http.StatusOK)
if sdkerr != nil {
return ReportConfigPage{}, sdkerr
}
var rcp ReportConfigPage
if err := json.Unmarshal(body, &rcp); err != nil {
return ReportConfigPage{}, errors.NewSDKError(err)
}
return rcp, nil
}
func (sdk mgSDK) EnableReportConfig(ctx context.Context, id, domainID, token string) (ReportConfig, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s/%s/%s/enable", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, id)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPost, url, token, nil, nil, http.StatusOK)
if sdkerr != nil {
return ReportConfig{}, sdkerr
}
var rc ReportConfig
if err := json.Unmarshal(body, &rc); err != nil {
return ReportConfig{}, errors.NewSDKError(err)
}
return rc, nil
}
func (sdk mgSDK) DisableReportConfig(ctx context.Context, id, domainID, token string) (ReportConfig, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s/%s/%s/disable", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, id)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPost, url, token, nil, nil, http.StatusOK)
if sdkerr != nil {
return ReportConfig{}, sdkerr
}
var rc ReportConfig
if err := json.Unmarshal(body, &rc); err != nil {
return ReportConfig{}, errors.NewSDKError(err)
}
return rc, nil
}
func (sdk mgSDK) UpdateReportTemplate(ctx context.Context, cfg ReportConfig, domainID, token string) errors.SDKError {
data, err := json.Marshal(cfg)
if err != nil {
return errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s/%s/%s/template", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, cfg.ID)
_, _, sdkerr := sdk.processRequest(ctx, http.MethodPut, url, token, data, nil, http.StatusNoContent)
return sdkerr
}
func (sdk mgSDK) ViewReportTemplate(ctx context.Context, id, domainID, token string) (ReportTemplate, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s/%s/%s/template", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, id)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, url, token, nil, nil, http.StatusOK)
if sdkerr != nil {
return "", sdkerr
}
var rt ReportTemplate
if err := json.Unmarshal(body, &rt); err != nil {
return "", errors.NewSDKError(err)
}
return rt, nil
}
func (sdk mgSDK) DeleteReportTemplate(ctx context.Context, id, domainID, token string) errors.SDKError {
url := fmt.Sprintf("%s/%s/%s/%s/%s/template", sdk.reportsURL, domainID, reportsEndpoint, configsEndpointReports, id)
_, _, sdkerr := sdk.processRequest(ctx, http.MethodDelete, url, token, nil, nil, http.StatusNoContent, http.StatusOK)
return sdkerr
}
func (sdk mgSDK) GenerateReport(
ctx context.Context,
config ReportConfig,
action ReportAction,
domainID,
token string,
) (ReportPage, *ReportFile, errors.SDKError) {
data, err := json.Marshal(config)
if err != nil {
return ReportPage{}, nil, errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s?action=%s",
sdk.reportsURL,
domainID,
reportsEndpoint,
action,
)
headers, body, sdkerr := sdk.processRequest(
ctx,
http.MethodPost,
url,
token,
data,
nil,
http.StatusOK,
)
if sdkerr != nil {
return ReportPage{}, nil, sdkerr
}
// ✅ Handle Download Action
if action == DownloadReportAction {
file := &ReportFile{
Name: extractFilename(headers.Get("Content-Disposition")),
Format: "pdf",
Data: body,
}
return ReportPage{}, file, nil
}
// ✅ Handle JSON response (view/email)
var rp ReportPage
if err := json.Unmarshal(body, &rp); err != nil {
return ReportPage{}, nil, errors.NewSDKError(err)
}
return rp, nil, nil
}
func extractFilename(contentDisposition string) string {
const prefix = "filename="
if idx := strings.Index(contentDisposition, prefix); idx != -1 {
return strings.Trim(contentDisposition[idx+len(prefix):], `"`)
}
return "report"
}
+867
View File
@@ -0,0 +1,867 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package sdk_test
import (
"context"
"errors"
"net/http/httptest"
"testing"
"time"
pkgSch "github.com/absmach/magistrala/pkg/schedule"
"github.com/absmach/magistrala/pkg/sdk"
"github.com/absmach/magistrala/reports"
"github.com/absmach/magistrala/reports/api"
rmocks "github.com/absmach/magistrala/reports/mocks"
smqlog "github.com/absmach/supermq/logger"
smqauthn "github.com/absmach/supermq/pkg/authn"
authnmocks "github.com/absmach/supermq/pkg/authn/mocks"
"github.com/go-chi/chi/v5"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
const (
reportConfigID = "report-config-1"
name = "daily-report"
updatedName = "updated daily-report"
description = "Daily temperature report"
updatedDescription = "updated Daily temperature report"
validTemplate = `<!DOCTYPE html>
<html>
<head>
<title>{{$.Title}}</title>
<style>
body { font-family: Arial, sans-serif; }
.header { background-color: #f0f0f0; padding: 10px; }
.content { padding: 20px; }
</style>
</head>
<body>
<div class="header">
<h1>{{$.Title}}</h1>
<p>Generated on: {{$.GeneratedDate}}</p>
</div>
<div class="content">
<h2>Messages</h2>
{{range .Messages}}
<div class="message">
<p>Time: {{formatTime .Time}}</p>
<p>Value: {{formatValue .}}</p>
</div>
{{end}}
</div>
</body>
</html>`
)
var (
now = time.Now().UTC().Truncate(time.Minute)
future = now.Add(1 * time.Hour)
schedule = pkgSch.Schedule{
StartDateTime: future,
Recurring: pkgSch.Daily,
RecurringPeriod: 1,
Time: future,
}
metrics = []reports.ReqMetric{
{
ChannelID: "channel1",
ClientIDs: []string{"client1"},
Name: "metric_name",
},
}
config = reports.MetricConfig{
From: "now()-1h",
To: "now()",
Title: "test_title",
Aggregation: reports.AggConfig{AggType: reports.AggregationAVG, Interval: "1h"},
}
email = reports.EmailSetting{
To: []string{"test@example.com"},
Subject: "Test Report",
}
testReportConfig = sdk.ReportConfig{
ID: reportConfigID,
Name: name,
Description: description,
DomainID: domainID,
Status: "enabled",
Schedule: schedule,
Metrics: metrics,
Config: &config,
Email: &email,
}
)
func setupReports() (*httptest.Server, *rmocks.Service, *authnmocks.Authentication) {
rsvc := new(rmocks.Service)
log := smqlog.NewMock()
authn := new(authnmocks.Authentication)
am := smqauthn.NewAuthNMiddleware(authn, smqauthn.WithAllowUnverifiedUser(true))
mux := chi.NewRouter()
_ = api.MakeHandler(rsvc, am, mux, log, "")
return httptest.NewServer(mux), rsvc, authn
}
func TestAddReportConfig(t *testing.T) {
rs, rsvc, auth := setupReports()
defer rs.Close()
conf := sdk.Config{
ReportsURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcCfg := reports.ReportConfig{
ID: reportConfigID,
Name: "daily-report",
Description: "Daily temperature report",
DomainID: domainID,
Status: reports.EnabledStatus,
Schedule: schedule,
Metrics: []reports.ReqMetric{
{
ChannelID: "channel1",
ClientIDs: []string{"client1"},
Name: "metric_name",
},
},
Config: &reports.MetricConfig{
From: "now()-1h",
To: "now()",
Title: "test_title",
Aggregation: reports.AggConfig{AggType: reports.AggregationAVG, Interval: "1h"},
},
Email: &reports.EmailSetting{
To: []string{"test@example.com"},
Subject: "Test Report",
},
}
cases := []struct {
desc string
cfg sdk.ReportConfig
token string
session smqauthn.Session
svcRes reports.ReportConfig
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "add report config successfully",
cfg: testReportConfig,
token: validToken,
svcRes: svcCfg,
},
{
desc: "add report config with empty token",
cfg: sdk.ReportConfig{Name: "daily-report"},
token: "",
wantErr: true,
svcErr: errors.New("missing or invalid bearer user token"),
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("AddReportConfig", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.AddReportConfig(context.Background(), tc.cfg, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestViewReportConfig(t *testing.T) {
rs, rsvc, auth := setupReports()
defer rs.Close()
conf := sdk.Config{
ReportsURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcCfg := reports.ReportConfig{
ID: reportConfigID,
Name: name,
Description: description,
DomainID: domainID,
Status: reports.EnabledStatus,
Metrics: metrics,
Config: &config,
Email: &email,
}
cases := []struct {
desc string
id string
token string
session smqauthn.Session
svcRes reports.ReportConfig
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "view report config successfully",
id: reportConfigID,
token: validToken,
svcRes: svcCfg,
},
{
desc: "view report config with empty token",
id: reportConfigID,
token: "",
wantErr: true,
},
{
desc: "view non-existent report config",
id: "non-existent",
token: validToken,
svcErr: errors.New("not found"),
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("ViewReportConfig", mock.Anything, tc.session, tc.id).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.ViewReportConfig(context.Background(), tc.id, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestUpdateReportConfig(t *testing.T) {
rs, rsvc, auth := setupReports()
defer rs.Close()
conf := sdk.Config{
ReportsURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
updatedConfig := testReportConfig
updatedConfig.Name = updatedName
updatedConfig.Description = updatedDescription
svcCfg := reports.ReportConfig{
ID: reportConfigID,
Name: updatedName,
Description: updatedDescription,
DomainID: domainID,
Status: reports.EnabledStatus,
Metrics: metrics,
Config: &config,
Email: &email,
}
cases := []struct {
desc string
cfg sdk.ReportConfig
token string
session smqauthn.Session
svcRes reports.ReportConfig
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "update report config successfully",
cfg: updatedConfig,
token: validToken,
svcRes: svcCfg,
},
{
desc: "update report config with empty token",
cfg: sdk.ReportConfig{ID: reportConfigID, Name: "updated-report"},
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("UpdateReportConfig", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.UpdateReportConfig(context.Background(), tc.cfg, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestUpdateReportSchedule(t *testing.T) {
rs, rsvc, auth := setupReports()
defer rs.Close()
conf := sdk.Config{
ReportsURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcCfg := reports.ReportConfig{
ID: reportConfigID,
Name: name,
Status: reports.EnabledStatus,
}
cases := []struct {
desc string
cfg sdk.ReportConfig
token string
session smqauthn.Session
svcRes reports.ReportConfig
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "update report schedule successfully",
cfg: sdk.ReportConfig{ID: reportConfigID, Schedule: map[string]any{"cron": "0 9 * * *"}},
token: validToken,
svcRes: svcCfg,
},
{
desc: "update report schedule with empty token",
cfg: sdk.ReportConfig{ID: reportConfigID},
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("UpdateReportSchedule", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.UpdateReportSchedule(context.Background(), tc.cfg, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestRemoveReportConfig(t *testing.T) {
rs, rsvc, auth := setupReports()
defer rs.Close()
conf := sdk.Config{
ReportsURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
cases := []struct {
desc string
id string
token string
session smqauthn.Session
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "remove report config successfully",
id: reportConfigID,
token: validToken,
},
{
desc: "remove report config with empty token",
id: reportConfigID,
token: "",
wantErr: true,
},
{
desc: "remove non-existent report config",
id: "non-existent",
token: validToken,
svcErr: errors.New("not found"),
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("RemoveReportConfig", mock.Anything, tc.session, tc.id).Return(tc.svcErr)
err := mgsdk.RemoveReportConfig(context.Background(), tc.id, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
svcCall.Unset()
authCall.Unset()
})
}
}
func TestListReportsConfig(t *testing.T) {
rs, rsvc, auth := setupReports()
defer rs.Close()
conf := sdk.Config{
ReportsURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcPage := reports.ReportConfigPage{}
cases := []struct {
desc string
pm sdk.PageMetadata
token string
session smqauthn.Session
svcRes reports.ReportConfigPage
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "list reports config successfully",
pm: sdk.PageMetadata{Offset: 0, Limit: 10},
token: validToken,
svcRes: svcPage,
},
{
desc: "list reports config with filters",
pm: sdk.PageMetadata{
Limit: 10,
Name: "daily",
Status: "enabled",
Dir: "desc",
Order: "created_at",
},
token: validToken,
svcRes: svcPage,
},
{
desc: "list reports config with empty metadata excludes filter params",
pm: sdk.PageMetadata{},
token: validToken,
svcRes: reports.ReportConfigPage{},
},
{
desc: "list reports config with empty token",
pm: sdk.PageMetadata{Limit: 10},
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("ListReportsConfig", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.ListReportsConfig(context.Background(), tc.pm, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotNil(t, result)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestEnableReportConfig(t *testing.T) {
rs, rsvc, auth := setupReports()
defer rs.Close()
conf := sdk.Config{
ReportsURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcCfg := reports.ReportConfig{
ID: reportConfigID,
Status: reports.EnabledStatus,
}
cases := []struct {
desc string
id string
token string
session smqauthn.Session
svcRes reports.ReportConfig
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "enable report config successfully",
id: reportConfigID,
token: validToken,
svcRes: svcCfg,
},
{
desc: "enable report config with empty token",
id: reportConfigID,
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("EnableReportConfig", mock.Anything, tc.session, tc.id).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.EnableReportConfig(context.Background(), tc.id, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestDisableReportConfig(t *testing.T) {
rs, rsvc, auth := setupReports()
defer rs.Close()
conf := sdk.Config{
ReportsURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcCfg := reports.ReportConfig{
ID: reportConfigID,
Status: reports.DisabledStatus,
}
cases := []struct {
desc string
id string
token string
session smqauthn.Session
svcRes reports.ReportConfig
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "disable report config successfully",
id: reportConfigID,
token: validToken,
svcRes: svcCfg,
},
{
desc: "disable report config with empty token",
id: reportConfigID,
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("DisableReportConfig", mock.Anything, tc.session, tc.id).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.DisableReportConfig(context.Background(), tc.id, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestUpdateReportTemplate(t *testing.T) {
rs, rsvc, auth := setupReports()
defer rs.Close()
conf := sdk.Config{
ReportsURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
cases := []struct {
desc string
cfg sdk.ReportConfig
token string
session smqauthn.Session
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "update report template successfully",
cfg: sdk.ReportConfig{
ID: reportConfigID,
ReportTemplate: validTemplate,
},
token: validToken,
},
{
desc: "update report template with empty token",
cfg: sdk.ReportConfig{ID: reportConfigID},
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("UpdateReportTemplate", mock.Anything, tc.session, mock.Anything).Return(tc.svcErr)
err := mgsdk.UpdateReportTemplate(context.Background(), tc.cfg, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
svcCall.Unset()
authCall.Unset()
})
}
}
func TestViewReportTemplate(t *testing.T) {
rs, rsvc, auth := setupReports()
defer rs.Close()
conf := sdk.Config{
ReportsURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcTmpl := reports.ReportTemplate(validTemplate)
cases := []struct {
desc string
id string
token string
session smqauthn.Session
svcRes reports.ReportTemplate
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "view report template successfully",
id: reportConfigID,
token: validToken,
svcRes: svcTmpl,
},
{
desc: "view report template with empty token",
id: reportConfigID,
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("ViewReportTemplate", mock.Anything, tc.session, tc.id).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.ViewReportTemplate(context.Background(), tc.id, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestDeleteReportTemplate(t *testing.T) {
rs, rsvc, auth := setupReports()
defer rs.Close()
conf := sdk.Config{
ReportsURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
cases := []struct {
desc string
id string
token string
session smqauthn.Session
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "delete report template successfully",
id: reportConfigID,
token: validToken,
},
{
desc: "delete report template with empty token",
id: reportConfigID,
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("DeleteReportTemplate", mock.Anything, tc.session, tc.id).Return(tc.svcErr)
err := mgsdk.DeleteReportTemplate(context.Background(), tc.id, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
svcCall.Unset()
authCall.Unset()
})
}
}
func TestGenerateReport(t *testing.T) {
rs, rsvc, auth := setupReports()
defer rs.Close()
conf := sdk.Config{
ReportsURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcPage := reports.ReportPage{}
config := sdk.ReportConfig{
ID: reportConfigID,
Name: name,
Description: description,
DomainID: domainID,
Metrics: metrics,
Config: &config,
ReportTemplate: reports.ReportTemplate(validTemplate),
}
cases := []struct {
desc string
cfg sdk.ReportConfig
action sdk.ReportAction
token string
session smqauthn.Session
svcRes reports.ReportPage
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "generate report successfully",
cfg: config,
action: sdk.ViewReportAction,
token: validToken,
svcRes: svcPage,
},
{
desc: "generate report with download action",
cfg: config,
action: sdk.DownloadReportAction,
token: validToken,
svcRes: svcPage,
},
{
desc: "generate report with empty token",
cfg: config,
action: sdk.ViewReportAction,
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{
DomainUserID: domainID + "_" + validID,
UserID: validID,
DomainID: domainID,
}
}
authCall := auth.On(
"Authenticate",
mock.Anything,
tc.token,
).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On(
"GenerateReport",
mock.Anything,
tc.session,
mock.Anything,
mock.Anything,
).Return(tc.svcRes, tc.svcErr)
page, file, err := mgsdk.GenerateReport(
context.Background(),
tc.cfg,
tc.action,
domainID,
tc.token,
)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
if tc.action == sdk.DownloadReportAction {
// download should return file
assert.NotNil(t, file)
} else {
// view/email should return page
assert.Equal(t, tc.svcRes.Total, page.Total)
}
}
svcCall.Unset()
authCall.Unset()
})
}
}
+200
View File
@@ -0,0 +1,200 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package sdk
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/absmach/supermq/pkg/errors"
)
const rulesEndpoint = "rules"
// Rule represents a rule configuration.
type Rule struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
DomainID string `json:"domain,omitempty"`
Metadata Metadata `json:"metadata,omitempty"`
Tags []string `json:"tags,omitempty"`
InputChannel string `json:"input_channel,omitempty"`
InputTopic string `json:"input_topic,omitempty"`
Logic any `json:"logic,omitempty"`
Outputs any `json:"outputs,omitempty"`
Schedule any `json:"schedule,omitempty"`
Status string `json:"status,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
CreatedBy string `json:"created_by,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
UpdatedBy string `json:"updated_by,omitempty"`
}
type Page struct {
Offset uint64 `json:"offset"`
Limit uint64 `json:"limit"`
Total uint64 `json:"total"`
Rules []Rule `json:"rules"`
}
func (sdk mgSDK) AddRule(ctx context.Context, r Rule, domainID, token string) (Rule, errors.SDKError) {
data, err := json.Marshal(r)
if err != nil {
return Rule{}, errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s", sdk.rulesEngineURL, domainID, rulesEndpoint)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPost, url, token, data, nil, http.StatusCreated, http.StatusOK)
if sdkerr != nil {
return Rule{}, sdkerr
}
var a Rule
if err := json.Unmarshal(body, &a); err != nil {
return Rule{}, errors.NewSDKError(err)
}
return a, nil
}
func (sdk mgSDK) ViewRule(ctx context.Context, id, domainID, token string) (Rule, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s/%s", sdk.rulesEngineURL, domainID, rulesEndpoint, id)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, url, token, nil, nil, http.StatusOK)
if sdkerr != nil {
return Rule{}, sdkerr
}
var a Rule
if err := json.Unmarshal(body, &a); err != nil {
return Rule{}, errors.NewSDKError(err)
}
return a, nil
}
func (sdk mgSDK) UpdateRule(ctx context.Context, r Rule, domainID, token string) (Rule, errors.SDKError) {
data, err := json.Marshal(r)
if err != nil {
return Rule{}, errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s/%s", sdk.rulesEngineURL, domainID, rulesEndpoint, r.ID)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPatch, url, token, data, nil, http.StatusOK)
if sdkerr != nil {
return Rule{}, sdkerr
}
var a Rule
if err := json.Unmarshal(body, &a); err != nil {
return Rule{}, errors.NewSDKError(err)
}
return a, nil
}
func (sdk mgSDK) UpdateRuleTags(ctx context.Context, r Rule, domainID, token string) (Rule, errors.SDKError) {
data, err := json.Marshal(map[string]any{"tags": r.Tags})
if err != nil {
return Rule{}, errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s/%s/tags", sdk.rulesEngineURL, domainID, rulesEndpoint, r.ID)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPatch, url, token, data, nil, http.StatusOK)
if sdkerr != nil {
return Rule{}, sdkerr
}
var a Rule
if err := json.Unmarshal(body, &a); err != nil {
return Rule{}, errors.NewSDKError(err)
}
return a, nil
}
func (sdk mgSDK) UpdateRuleSchedule(ctx context.Context, r Rule, domainID, token string) (Rule, errors.SDKError) {
data, err := json.Marshal(map[string]any{"schedule": r.Schedule})
if err != nil {
return Rule{}, errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s/%s/schedule", sdk.rulesEngineURL, domainID, rulesEndpoint, r.ID)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPatch, url, token, data, nil, http.StatusOK)
if sdkerr != nil {
return Rule{}, sdkerr
}
var a Rule
if err := json.Unmarshal(body, &a); err != nil {
return Rule{}, errors.NewSDKError(err)
}
return a, nil
}
func (sdk mgSDK) ListRules(ctx context.Context, pm PageMetadata, domainID, token string) (Page, errors.SDKError) {
endpoint := fmt.Sprintf("%s/%s", domainID, rulesEndpoint)
url, err := sdk.withQueryParams(sdk.rulesEngineURL, endpoint, pm)
if err != nil {
return Page{}, errors.NewSDKError(err)
}
_, body, sdkerr := sdk.processRequest(ctx, http.MethodGet, url, token, nil, nil, http.StatusOK)
if sdkerr != nil {
return Page{}, sdkerr
}
var ap Page
if err := json.Unmarshal(body, &ap); err != nil {
return Page{}, errors.NewSDKError(err)
}
return ap, nil
}
func (sdk mgSDK) RemoveRule(ctx context.Context, id, domainID, token string) errors.SDKError {
url := fmt.Sprintf("%s/%s/%s/%s", sdk.rulesEngineURL, domainID, rulesEndpoint, id)
_, _, sdkerr := sdk.processRequest(ctx, http.MethodDelete, url, token, nil, nil, http.StatusNoContent, http.StatusOK)
return sdkerr
}
func (sdk mgSDK) EnableRule(ctx context.Context, id, domainID, token string) (Rule, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s/%s/enable", sdk.rulesEngineURL, domainID, rulesEndpoint, id)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPost, url, token, nil, nil, http.StatusOK)
if sdkerr != nil {
return Rule{}, sdkerr
}
var a Rule
if err := json.Unmarshal(body, &a); err != nil {
return Rule{}, errors.NewSDKError(err)
}
return a, nil
}
func (sdk mgSDK) DisableRule(ctx context.Context, id, domainID, token string) (Rule, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s/%s/disable", sdk.rulesEngineURL, domainID, rulesEndpoint, id)
_, body, sdkerr := sdk.processRequest(ctx, http.MethodPost, url, token, nil, nil, http.StatusOK)
if sdkerr != nil {
return Rule{}, sdkerr
}
var a Rule
if err := json.Unmarshal(body, &a); err != nil {
return Rule{}, errors.NewSDKError(err)
}
return a, nil
}
+585
View File
@@ -0,0 +1,585 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package sdk_test
import (
"context"
"errors"
"net/http/httptest"
"testing"
"github.com/absmach/magistrala/pkg/sdk"
"github.com/absmach/magistrala/re"
"github.com/absmach/magistrala/re/api"
remocks "github.com/absmach/magistrala/re/mocks"
smqlog "github.com/absmach/supermq/logger"
smqauthn "github.com/absmach/supermq/pkg/authn"
authnmocks "github.com/absmach/supermq/pkg/authn/mocks"
"github.com/go-chi/chi/v5"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
const ruleID = "rule-1"
var testRule = sdk.Rule{
ID: ruleID,
Name: "temperature-rule",
InputChannel: "chan-1",
InputTopic: "sensors/temperature",
Status: "enabled",
Tags: []string{"temperature", "alerts"},
}
func setupRules() (*httptest.Server, *remocks.Service, *authnmocks.Authentication) {
rsvc := new(remocks.Service)
log := smqlog.NewMock()
authn := new(authnmocks.Authentication)
am := smqauthn.NewAuthNMiddleware(authn, smqauthn.WithAllowUnverifiedUser(true))
mux := chi.NewRouter()
_ = api.MakeHandler(rsvc, am, mux, log, "")
return httptest.NewServer(mux), rsvc, authn
}
func TestAddRule(t *testing.T) {
rs, rsvc, auth := setupRules()
defer rs.Close()
conf := sdk.Config{
RulesEngineURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcRule := re.Rule{
ID: ruleID,
Name: "temperature-rule",
InputChannel: "chan-1",
Status: re.EnabledStatus,
}
cases := []struct {
desc string
rule sdk.Rule
token string
session smqauthn.Session
svcRes re.Rule
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "add rule successfully",
rule: sdk.Rule{Name: "temp-rule", InputChannel: "chan-1"},
token: validToken,
svcRes: svcRule,
},
{
desc: "add rule with empty token",
rule: sdk.Rule{Name: "temp-rule"},
token: "",
wantErr: true,
},
{
desc: "add rule with bad request",
rule: sdk.Rule{},
token: validToken,
svcErr: errors.New("bad request"),
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("AddRule", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.AddRule(context.Background(), tc.rule, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestViewRule(t *testing.T) {
rs, rsvc, auth := setupRules()
defer rs.Close()
conf := sdk.Config{
RulesEngineURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcRule := re.Rule{
ID: ruleID,
Name: "temperature-rule",
InputChannel: "chan-1",
Status: re.EnabledStatus,
}
cases := []struct {
desc string
id string
token string
session smqauthn.Session
svcRes re.Rule
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "view rule successfully",
id: ruleID,
token: validToken,
svcRes: svcRule,
},
{
desc: "view rule with empty token",
id: ruleID,
token: "",
wantErr: true,
},
{
desc: "view non-existent rule",
id: "non-existent",
token: validToken,
svcErr: errors.New("not found"),
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("ViewRule", mock.Anything, tc.session, tc.id).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.ViewRule(context.Background(), tc.id, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestUpdateRule(t *testing.T) {
rs, rsvc, auth := setupRules()
defer rs.Close()
conf := sdk.Config{
RulesEngineURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
updatedRule := testRule
updatedRule.Name = "updated-rule"
svcRule := re.Rule{
ID: ruleID,
Name: "updated-rule",
InputChannel: "chan-1",
InputTopic: "sensors/temperature",
Status: re.EnabledStatus,
Tags: []string{"temperature", "alerts"},
}
cases := []struct {
desc string
rule sdk.Rule
token string
session smqauthn.Session
svcRes re.Rule
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "update rule successfully",
rule: updatedRule,
token: validToken,
svcRes: svcRule,
},
{
desc: "update rule with empty token",
rule: updatedRule,
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("UpdateRule", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.UpdateRule(context.Background(), tc.rule, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestUpdateRuleTags(t *testing.T) {
rs, rsvc, auth := setupRules()
defer rs.Close()
conf := sdk.Config{
RulesEngineURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcRule := re.Rule{
ID: ruleID,
Tags: []string{"new-tag"},
}
cases := []struct {
desc string
rule sdk.Rule
token string
session smqauthn.Session
svcRes re.Rule
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "update rule tags successfully",
rule: sdk.Rule{ID: ruleID, Tags: []string{"new-tag"}},
token: validToken,
svcRes: svcRule,
},
{
desc: "update rule tags with empty token",
rule: sdk.Rule{ID: ruleID},
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("UpdateRuleTags", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.UpdateRuleTags(context.Background(), tc.rule, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestUpdateRuleSchedule(t *testing.T) {
rs, rsvc, auth := setupRules()
defer rs.Close()
conf := sdk.Config{
RulesEngineURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcRule := re.Rule{
ID: ruleID,
}
cases := []struct {
desc string
rule sdk.Rule
token string
session smqauthn.Session
svcRes re.Rule
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "update rule schedule successfully",
rule: sdk.Rule{ID: ruleID, Schedule: map[string]any{"cron": "0 * * * *"}},
token: validToken,
svcRes: svcRule,
},
{
desc: "update rule schedule with empty token",
rule: sdk.Rule{ID: ruleID},
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("UpdateRuleSchedule", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.UpdateRuleSchedule(context.Background(), tc.rule, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestListRules(t *testing.T) {
rs, rsvc, auth := setupRules()
defer rs.Close()
conf := sdk.Config{
RulesEngineURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcPage := re.Page{}
cases := []struct {
desc string
pm sdk.PageMetadata
token string
session smqauthn.Session
svcRes re.Page
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "list rules successfully",
pm: sdk.PageMetadata{Offset: 0, Limit: 10},
token: validToken,
svcRes: svcPage,
},
{
desc: "list rules with filters",
pm: sdk.PageMetadata{
Limit: 5,
Name: "temp",
Status: "enabled",
InputChannel: "chan-1",
Tag: "temperature",
Dir: "desc",
Order: "created_at",
},
token: validToken,
svcRes: svcPage,
},
{
desc: "list rules with empty metadata excludes filter params",
pm: sdk.PageMetadata{},
token: validToken,
svcRes: re.Page{},
},
{
desc: "list rules with empty token",
pm: sdk.PageMetadata{Limit: 10},
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("ListRules", mock.Anything, tc.session, mock.Anything).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.ListRules(context.Background(), tc.pm, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotNil(t, result)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestEnableRule(t *testing.T) {
rs, rsvc, auth := setupRules()
defer rs.Close()
conf := sdk.Config{
RulesEngineURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcRule := re.Rule{
ID: ruleID,
Status: re.EnabledStatus,
}
cases := []struct {
desc string
id string
token string
session smqauthn.Session
svcRes re.Rule
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "enable rule successfully",
id: ruleID,
token: validToken,
svcRes: svcRule,
},
{
desc: "enable rule with empty token",
id: ruleID,
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("EnableRule", mock.Anything, tc.session, tc.id).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.EnableRule(context.Background(), tc.id, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestDisableRule(t *testing.T) {
rs, rsvc, auth := setupRules()
defer rs.Close()
conf := sdk.Config{
RulesEngineURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
svcRule := re.Rule{
ID: ruleID,
Status: re.DisabledStatus,
}
cases := []struct {
desc string
id string
token string
session smqauthn.Session
svcRes re.Rule
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "disable rule successfully",
id: ruleID,
token: validToken,
svcRes: svcRule,
},
{
desc: "disable rule with empty token",
id: ruleID,
token: "",
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("DisableRule", mock.Anything, tc.session, tc.id).Return(tc.svcRes, tc.svcErr)
result, err := mgsdk.DisableRule(context.Background(), tc.id, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
if !tc.wantErr {
assert.NotEmpty(t, result.ID)
}
svcCall.Unset()
authCall.Unset()
})
}
}
func TestRemoveRule(t *testing.T) {
rs, rsvc, auth := setupRules()
defer rs.Close()
conf := sdk.Config{
RulesEngineURL: rs.URL,
}
mgsdk := sdk.NewSDK(conf)
cases := []struct {
desc string
id string
token string
session smqauthn.Session
svcErr error
authenticateErr error
wantErr bool
}{
{
desc: "remove rule successfully",
id: ruleID,
token: validToken,
},
{
desc: "remove rule with empty token",
id: ruleID,
token: "",
wantErr: true,
},
{
desc: "remove non-existent rule",
id: "non-existent",
token: validToken,
svcErr: errors.New("not found"),
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
if tc.token == validToken {
tc.session = smqauthn.Session{DomainUserID: domainID + "_" + validID, UserID: validID, DomainID: domainID}
}
authCall := auth.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authenticateErr)
svcCall := rsvc.On("RemoveRule", mock.Anything, tc.session, tc.id).Return(tc.svcErr)
err := mgsdk.RemoveRule(context.Background(), tc.id, domainID, tc.token)
assert.Equal(t, tc.wantErr, err != nil)
svcCall.Unset()
authCall.Unset()
})
}
}
+171 -10
View File
@@ -15,6 +15,7 @@ import (
"net/url"
"strconv"
"strings"
"time"
"github.com/absmach/supermq/pkg/errors"
smqSDK "github.com/absmach/supermq/pkg/sdk"
@@ -26,16 +27,33 @@ var _ SDK = (*mgSDK)(nil)
type Metadata map[string]any
type PageMetadata struct {
Total uint64 `json:"total"`
Offset uint64 `json:"offset"`
Limit uint64 `json:"limit"`
Metadata Metadata `json:"metadata,omitempty"`
Topic string `json:"topic,omitempty"`
Contact string `json:"contact,omitempty"`
DomainID string `json:"domain_id,omitempty"`
Level uint64 `json:"level,omitempty"`
State string `json:"state,omitempty"`
Name string `json:"name,omitempty"`
Total uint64 `json:"total"`
Offset uint64 `json:"offset"`
Limit uint64 `json:"limit"`
Metadata Metadata `json:"metadata,omitempty"`
Topic string `json:"topic,omitempty"`
Contact string `json:"contact,omitempty"`
DomainID string `json:"domain_id,omitempty"`
Level uint64 `json:"level,omitempty"`
State string `json:"state,omitempty"`
Name string `json:"name,omitempty"`
Status string `json:"status,omitempty"`
Dir string `json:"dir,omitempty"`
Order string `json:"order,omitempty"`
Tag string `json:"tag,omitempty"`
InputChannel string `json:"input_channel,omitempty"`
RuleID string `json:"rule_id,omitempty"`
ChannelID string `json:"channel_id,omitempty"`
ClientID string `json:"client_id,omitempty"`
Subtopic string `json:"subtopic,omitempty"`
AssigneeID string `json:"assignee_id,omitempty"`
Severity uint8 `json:"severity,omitempty"`
UpdatedBy string `json:"updated_by,omitempty"`
AssignedBy string `json:"assigned_by,omitempty"`
AcknowledgedBy string `json:"acknowledged_by,omitempty"`
ResolvedBy string `json:"resolved_by,omitempty"`
CreatedFrom time.Time `json:"created_from,omitempty"`
CreatedTo time.Time `json:"created_to,omitempty"`
}
type MessagePageMetadata struct {
@@ -190,12 +208,95 @@ type SDK interface {
// err := sdk.DeleteSubscription(ctx, "id", "token")
// fmt.Println(err)
DeleteSubscription(ctx context.Context, id, token string) errors.SDKError
// Alarms API
// UpdateAlarm updates an existing alarm.
UpdateAlarm(ctx context.Context, alarm Alarm, domainID, token string) (Alarm, errors.SDKError)
// ViewAlarm retrieves an alarm by its ID.
ViewAlarm(ctx context.Context, id, domainID, token string) (Alarm, errors.SDKError)
// ListAlarms retrieves a page of alarms.
ListAlarms(ctx context.Context, pm PageMetadata, domainID, token string) (AlarmsPage, errors.SDKError)
// DeleteAlarm deletes an alarm.
DeleteAlarm(ctx context.Context, id, domainID, token string) errors.SDKError
// Reports API
// AddReportConfig creates a new report configuration.
AddReportConfig(ctx context.Context, cfg ReportConfig, domainID, token string) (ReportConfig, errors.SDKError)
// ViewReportConfig retrieves a report config by its ID.
ViewReportConfig(ctx context.Context, id, domainID, token string) (ReportConfig, errors.SDKError)
// UpdateReportConfig updates an existing report configuration.
UpdateReportConfig(ctx context.Context, cfg ReportConfig, domainID, token string) (ReportConfig, errors.SDKError)
// UpdateReportSchedule updates an existing report configuration's schedule.
UpdateReportSchedule(ctx context.Context, cfg ReportConfig, domainID, token string) (ReportConfig, errors.SDKError)
// RemoveReportConfig deletes a report config.
RemoveReportConfig(ctx context.Context, id, domainID, token string) errors.SDKError
// ListReportsConfig retrieves a page of report configs.
ListReportsConfig(ctx context.Context, pm PageMetadata, domainID, token string) (ReportConfigPage, errors.SDKError)
// EnableReportConfig enables a report config.
EnableReportConfig(ctx context.Context, id, domainID, token string) (ReportConfig, errors.SDKError)
// DisableReportConfig disables a report config.
DisableReportConfig(ctx context.Context, id, domainID, token string) (ReportConfig, errors.SDKError)
// UpdateReportTemplate updates a report template.
UpdateReportTemplate(ctx context.Context, cfg ReportConfig, domainID, token string) errors.SDKError
// ViewReportTemplate retrieves a report template.
ViewReportTemplate(ctx context.Context, id, domainID, token string) (ReportTemplate, errors.SDKError)
// DeleteReportTemplate deletes a report template.
DeleteReportTemplate(ctx context.Context, id, domainID, token string) errors.SDKError
// GenerateReport generates a report from a configuration.
GenerateReport(ctx context.Context, config ReportConfig, action ReportAction, domainID, token string) (ReportPage, *ReportFile, errors.SDKError)
// Rules Engine API
// AddRule creates a new rule.
AddRule(ctx context.Context, r Rule, domainID, token string) (Rule, errors.SDKError)
// ViewRule retrieves a rule by its ID.
ViewRule(ctx context.Context, id, domainID, token string) (Rule, errors.SDKError)
// UpdateRule updates an existing rule.
UpdateRule(ctx context.Context, r Rule, domainID, token string) (Rule, errors.SDKError)
// UpdateRuleTags updates an existing rule's tags.
UpdateRuleTags(ctx context.Context, r Rule, domainID, token string) (Rule, errors.SDKError)
// UpdateRuleSchedule updates an existing rule's schedule.
UpdateRuleSchedule(ctx context.Context, r Rule, domainID, token string) (Rule, errors.SDKError)
// ListRules retrieves a page of rules.
ListRules(ctx context.Context, pm PageMetadata, domainID, token string) (Page, errors.SDKError)
// RemoveRule deletes a rule.
RemoveRule(ctx context.Context, id, domainID, token string) errors.SDKError
// EnableRule enables a rule.
EnableRule(ctx context.Context, id, domainID, token string) (Rule, errors.SDKError)
// DisableRule disables a rule.
DisableRule(ctx context.Context, id, domainID, token string) (Rule, errors.SDKError)
}
type mgSDK struct {
bootstrapURL string
readersURL string
usersURL string
alarmsURL string
reportsURL string
rulesEngineURL string
client *http.Client
curlFlag bool
msgContentType smqSDK.ContentType
@@ -216,6 +317,9 @@ type Config struct {
DomainsURL string
JournalURL string
HostURL string
AlarmsURL string
ReportsURL string
RulesEngineURL string
MsgContentType smqSDK.ContentType
TLSVerification bool
@@ -244,6 +348,9 @@ func NewSDK(conf Config) SDK {
bootstrapURL: conf.BootstrapURL,
readersURL: conf.ReaderURL,
usersURL: conf.UsersURL,
alarmsURL: conf.AlarmsURL,
reportsURL: conf.ReportsURL,
rulesEngineURL: conf.RulesEngineURL,
msgContentType: conf.MsgContentType,
client: &http.Client{
@@ -347,6 +454,60 @@ func (pm PageMetadata) query() (string, error) {
if pm.Level != 0 {
q.Add("level", strconv.FormatUint(pm.Level, 10))
}
if pm.Name != "" {
q.Add("name", pm.Name)
}
if pm.Status != "" {
q.Add("status", pm.Status)
}
if pm.Dir != "" {
q.Add("dir", pm.Dir)
}
if pm.Order != "" {
q.Add("order", pm.Order)
}
if pm.Tag != "" {
q.Add("tag", pm.Tag)
}
if pm.InputChannel != "" {
q.Add("input_channel", pm.InputChannel)
}
if pm.RuleID != "" {
q.Add("rule_id", pm.RuleID)
}
if pm.ChannelID != "" {
q.Add("channel_id", pm.ChannelID)
}
if pm.ClientID != "" {
q.Add("client_id", pm.ClientID)
}
if pm.Subtopic != "" {
q.Add("subtopic", pm.Subtopic)
}
if pm.AssigneeID != "" {
q.Add("assignee_id", pm.AssigneeID)
}
if pm.Severity != 0 {
q.Add("severity", strconv.FormatUint(uint64(pm.Severity), 10))
}
if pm.UpdatedBy != "" {
q.Add("updated_by", pm.UpdatedBy)
}
if pm.AssignedBy != "" {
q.Add("assigned_by", pm.AssignedBy)
}
if pm.AcknowledgedBy != "" {
q.Add("acknowledged_by", pm.AcknowledgedBy)
}
if pm.ResolvedBy != "" {
q.Add("resolved_by", pm.ResolvedBy)
}
if !pm.CreatedFrom.IsZero() {
q.Add("created_from", pm.CreatedFrom.UTC().Format(time.RFC3339))
}
if !pm.CreatedTo.IsZero() {
q.Add("created_to", pm.CreatedTo.UTC().Format(time.RFC3339))
}
return q.Encode(), nil
}
+1 -1
View File
@@ -34,7 +34,7 @@ type generateReportResp struct {
}
func (res generateReportResp) Code() int {
return http.StatusCreated
return http.StatusOK
}
func (res generateReportResp) Headers() map[string]string {