mirror of
https://github.com/absmach/supermq.git
synced 2026-06-23 04:00:27 +00:00
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:
committed by
GitHub
parent
a031426715
commit
8e75edc9f5
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
}
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user