Files
Arvindh 16ba29cf4a
Property Based Tests / api-test (push) Has been cancelled
Continuous Delivery / lint-and-build (push) Has been cancelled
Deploy GitHub Pages / swagger-ui (push) Has been cancelled
CI Pipeline / Lint Proto (push) Has been cancelled
CI Pipeline / Detect Changes (push) Has been cancelled
Continuous Delivery / Build and Push Docker Images (push) Has been cancelled
CI Pipeline / lint-and-build (push) Has been cancelled
CI Pipeline / Test ${{ matrix.module }} (push) Has been cancelled
CI Pipeline / Upload Coverage (push) Has been cancelled
MG-3537 - Migrate Magistrala to ATOM for identity & authorization (#3532)
Signed-off-by: Arvindh <arvindh91@gmail.com>
Signed-off-by: dusan <borovcanindusan1@gmail.com>
Signed-off-by: Rodney Osodo <socials@rodneyosodo.com>
Co-authored-by: Dušan Borovčanin <dusan.borovcanin@absmach.eu>
Co-authored-by: Rodney Osodo <socials@rodneyosodo.com>
Co-authored-by: dusan <borovcanindusan1@gmail.com>
2026-06-26 22:06:57 +02:00

651 lines
16 KiB
Go

// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package reports_test
import (
"context"
"fmt"
"testing"
"time"
"github.com/0x6flab/namegenerator"
"github.com/absmach/magistrala/internal/testsutil"
"github.com/absmach/magistrala/pkg/authn"
emocks "github.com/absmach/magistrala/pkg/emailer/mocks"
"github.com/absmach/magistrala/pkg/errors"
repoerr "github.com/absmach/magistrala/pkg/errors/repository"
svcerr "github.com/absmach/magistrala/pkg/errors/service"
pkglog "github.com/absmach/magistrala/pkg/logger"
policymocks "github.com/absmach/magistrala/pkg/policies/mocks"
pkgSch "github.com/absmach/magistrala/pkg/schedule"
tmocks "github.com/absmach/magistrala/pkg/ticker/mocks"
"github.com/absmach/magistrala/pkg/uuid"
readmocks "github.com/absmach/magistrala/readers/mocks"
"github.com/absmach/magistrala/reports"
"github.com/absmach/magistrala/reports/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
var (
namegen = namegenerator.NewGenerator()
userID = testsutil.GenerateUUID(&testing.T{})
domainID = testsutil.GenerateUUID(&testing.T{})
now = time.Now().UTC()
template = reports.ReportTemplate("")
schedule = pkgSch.Schedule{
StartDateTime: now,
Recurring: pkgSch.Daily,
RecurringPeriod: 1,
Time: time.Now().Add(-time.Hour),
}
reportName = namegen.Generate()
rptConfig = reports.ReportConfig{
ID: testsutil.GenerateUUID(&testing.T{}),
Name: reportName,
DomainID: domainID,
Status: reports.EnabledStatus,
Schedule: schedule,
CreatedBy: userID,
UpdatedBy: userID,
UpdatedAt: time.Now(),
}
)
func newService(t *testing.T, runInfo chan pkglog.RunInfo) (reports.Service, *mocks.Repository, *tmocks.Ticker, *policymocks.Service) {
repo := new(mocks.Repository)
mockTicker := new(tmocks.Ticker)
idProvider := uuid.NewMock()
readersSvc := new(readmocks.ReadersServiceClient)
e := new(emocks.Emailer)
policy := new(policymocks.Service)
svc, err := reports.NewService(repo, runInfo, idProvider, mockTicker, e, readersSvc, template, "")
if err != nil {
t.Fatalf("Failed to create service: %v", err)
}
return svc, repo, mockTicker, policy
}
func TestAddReportConfig(t *testing.T) {
svc, repo, _, _ := newService(t, make(chan pkglog.RunInfo))
cases := []struct {
desc string
session authn.Session
cfg reports.ReportConfig
res reports.ReportConfig
err error
}{
{
desc: "Add report config successfully",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
cfg: reports.ReportConfig{
Name: reportName,
Schedule: schedule,
},
res: rptConfig,
err: nil,
},
{
desc: "Add report config with failed repo",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
cfg: reports.ReportConfig{
Name: reportName,
Schedule: schedule,
},
err: repoerr.ErrCreateEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := repo.On("AddReportConfig", mock.Anything, mock.Anything).Return(tc.res, tc.err)
res, err := svc.AddReportConfig(context.Background(), tc.session, tc.cfg)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
if err == nil {
assert.NotEmpty(t, res.ID, "expected non-empty result in ID")
assert.Equal(t, tc.cfg.Name, res.Name)
assert.Equal(t, tc.cfg.Schedule, res.Schedule)
}
repoCall.Unset()
})
}
}
func TestAddReportConfigWithoutRoleProvisioning(t *testing.T) {
repo := new(mocks.Repository)
mockTicker := new(tmocks.Ticker)
idProvider := uuid.NewMock()
readersSvc := new(readmocks.ReadersServiceClient)
e := new(emocks.Emailer)
svc, err := reports.NewService(repo, make(chan pkglog.RunInfo), idProvider, mockTicker, e, readersSvc, template, "")
if err != nil {
t.Fatalf("Failed to create service: %v", err)
}
session := authn.Session{
UserID: userID,
DomainID: domainID,
}
cfg := reports.ReportConfig{
Name: reportName,
Schedule: schedule,
}
saved := cfg
saved.ID = rptConfig.ID
saved.Status = reports.EnabledStatus
saved.CreatedBy = userID
saved.DomainID = domainID
repo.On("AddReportConfig", mock.Anything, mock.Anything).Return(saved, nil).Once()
res, err := svc.AddReportConfig(context.Background(), session, cfg)
assert.NoError(t, err)
assert.Equal(t, saved.ID, res.ID)
repo.AssertNotCalled(t, "AddRoles", mock.Anything, mock.Anything)
repo.AssertExpectations(t)
}
func TestViewReportConfig(t *testing.T) {
svc, repo, _, _ := newService(t, make(chan pkglog.RunInfo))
cases := []struct {
desc string
session authn.Session
id string
res reports.ReportConfig
err error
}{
{
desc: "view report config successfully",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
id: rptConfig.ID,
res: rptConfig,
err: nil,
},
{
desc: "view report config with failed repo",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
id: rptConfig.ID,
err: svcerr.ErrViewEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := repo.On("ViewReportConfig", mock.Anything, mock.Anything).Return(tc.res, tc.err)
res, err := svc.ViewReportConfig(context.Background(), tc.session, tc.id, false)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
if err == nil {
assert.Equal(t, tc.res, res)
}
defer repoCall.Unset()
})
}
}
func TestUpdateReportConfig(t *testing.T) {
svc, repo, _, _ := newService(t, make(chan pkglog.RunInfo))
newName := namegen.Generate()
now := time.Now().Add(time.Hour)
cases := []struct {
desc string
session authn.Session
cfg reports.ReportConfig
res reports.ReportConfig
err error
}{
{
desc: "update report config successfully",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
cfg: reports.ReportConfig{
Name: newName,
ID: rptConfig.ID,
Schedule: schedule,
},
res: reports.ReportConfig{
Name: newName,
ID: rptConfig.ID,
DomainID: rptConfig.DomainID,
Status: rptConfig.Status,
Schedule: rptConfig.Schedule,
UpdatedAt: now,
UpdatedBy: userID,
},
err: nil,
},
{
desc: "update report config with failed repo",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
cfg: reports.ReportConfig{
Name: rptConfig.Name,
ID: rptConfig.ID,
Schedule: schedule,
},
err: svcerr.ErrUpdateEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := repo.On("UpdateReportConfig", mock.Anything, mock.Anything).Return(tc.res, tc.err)
res, err := svc.UpdateReportConfig(context.Background(), tc.session, tc.cfg)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
if err == nil {
assert.Equal(t, tc.res, res)
}
defer repoCall.Unset()
})
}
}
func TestListReportsConfig(t *testing.T) {
svc, repo, _, _ := newService(t, make(chan pkglog.RunInfo))
numConfigs := 50
now := time.Now().Add(time.Hour)
var configs []reports.ReportConfig
for i := 0; i < numConfigs; i++ {
c := reports.ReportConfig{
ID: testsutil.GenerateUUID(t),
Name: namegen.Generate(),
DomainID: domainID,
Status: reports.EnabledStatus,
CreatedAt: now,
CreatedBy: userID,
Schedule: schedule,
}
configs = append(configs, c)
}
cases := []struct {
desc string
session authn.Session
pageMeta reports.PageMeta
res reports.ReportConfigPage
err error
superAdmin bool
}{
{
desc: "list report configs successfully",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
pageMeta: reports.PageMeta{},
res: reports.ReportConfigPage{
PageMeta: reports.PageMeta{
Total: uint64(numConfigs),
Offset: 0,
Limit: 10,
},
ReportConfigs: configs[0:10],
},
err: nil,
},
{
desc: "list report configs successfully with limit",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
pageMeta: reports.PageMeta{
Limit: 100,
},
res: reports.ReportConfigPage{
PageMeta: reports.PageMeta{
Total: uint64(numConfigs),
Offset: 0,
Limit: 100,
},
ReportConfigs: configs[0:numConfigs],
},
err: nil,
},
{
desc: "list report configs successfully with offset",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
pageMeta: reports.PageMeta{
Offset: 20,
Limit: 10,
},
res: reports.ReportConfigPage{
PageMeta: reports.PageMeta{
Total: uint64(numConfigs),
Offset: 20,
Limit: 10,
},
ReportConfigs: configs[20:30],
},
err: nil,
},
{
desc: "list report configs with failed repo",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
pageMeta: reports.PageMeta{},
err: svcerr.ErrViewEntity,
},
{
desc: "list report configs as super admin successfully",
session: authn.Session{
UserID: userID,
DomainID: domainID,
SuperAdmin: true,
},
pageMeta: reports.PageMeta{},
res: reports.ReportConfigPage{
PageMeta: reports.PageMeta{
Total: uint64(numConfigs),
Offset: 0,
Limit: 10,
},
ReportConfigs: configs[0:10],
},
superAdmin: true,
err: nil,
},
{
desc: "list report configs as super admin with failed repo",
session: authn.Session{
UserID: userID,
DomainID: domainID,
SuperAdmin: true,
},
pageMeta: reports.PageMeta{},
superAdmin: true,
err: svcerr.ErrViewEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
var repoCall *mock.Call
repoCall = repo.On("ListAllReportsConfig", mock.Anything, mock.Anything).Return(tc.res, tc.err)
res, err := svc.ListReportsConfig(context.Background(), tc.session, tc.pageMeta)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
if err == nil {
assert.Equal(t, tc.res, res)
}
defer repoCall.Unset()
})
}
}
func TestRemoveReportConfig(t *testing.T) {
svc, repo, _, policies := newService(t, make(chan pkglog.RunInfo))
cases := []struct {
desc string
session authn.Session
id string
err error
deletePoliciesErr error
}{
{
desc: "remove report config successfully",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
id: rptConfig.ID,
err: nil,
deletePoliciesErr: nil,
},
{
desc: "remove report config with failed repo",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
id: rptConfig.ID,
err: svcerr.ErrRemoveEntity,
deletePoliciesErr: nil,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := repo.On("RemoveReportConfig", mock.Anything, mock.Anything).Return(tc.err)
policyCall := policies.On("DeletePolicies", context.Background(), mock.Anything).Return(tc.deletePoliciesErr)
err := svc.RemoveReportConfig(context.Background(), tc.session, tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
policyCall.Unset()
repoCall.Unset()
})
}
}
func TestEnableReportConfig(t *testing.T) {
svc, repo, _, _ := newService(t, make(chan pkglog.RunInfo))
cases := []struct {
desc string
session authn.Session
id string
status reports.Status
res reports.ReportConfig
err error
}{
{
desc: "enable report config successfully",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
id: rptConfig.ID,
status: reports.EnabledStatus,
res: rptConfig,
err: nil,
},
{
desc: "enable report config with failed repo",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
id: rptConfig.ID,
status: reports.EnabledStatus,
err: svcerr.ErrUpdateEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := repo.On("UpdateReportConfigStatus", context.Background(), mock.Anything).Return(tc.res, tc.err)
res, err := svc.EnableReportConfig(context.Background(), tc.session, tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
if err == nil {
assert.Equal(t, tc.res, res)
}
defer repoCall.Unset()
})
}
}
func TestDisableReportConfig(t *testing.T) {
svc, repo, _, _ := newService(t, make(chan pkglog.RunInfo))
cases := []struct {
desc string
session authn.Session
id string
status reports.Status
res reports.ReportConfig
err error
}{
{
desc: "disable report config successfully",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
id: rptConfig.ID,
status: reports.DisabledStatus,
res: reports.ReportConfig{
ID: rptConfig.ID,
Name: rptConfig.Name,
DomainID: rptConfig.DomainID,
Status: reports.DisabledStatus,
Schedule: schedule,
UpdatedBy: userID,
UpdatedAt: time.Now(),
},
err: nil,
},
{
desc: "disable report config with failed repo",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
id: rptConfig.ID,
status: reports.DisabledStatus,
err: svcerr.ErrUpdateEntity,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
repoCall := repo.On("UpdateReportConfigStatus", mock.Anything, mock.Anything).Return(tc.res, tc.err)
res, err := svc.DisableReportConfig(context.Background(), tc.session, tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
if err == nil {
assert.Equal(t, tc.res, res)
}
defer repoCall.Unset()
})
}
}
func TestGenerateInstantEmailReport(t *testing.T) {
// nolint:dogsled
svc, _, _, _ := newService(t, make(chan pkglog.RunInfo))
validEmailConfig := reports.EmailSetting{
To: []string{"test@example.com"},
Subject: "Test Report",
Content: "Please find the attached report.",
}
validConfig := reports.ReportConfig{
ID: testsutil.GenerateUUID(&testing.T{}),
Name: "Test Report",
DomainID: domainID,
Status: reports.DisabledStatus,
Email: &validEmailConfig,
Config: &reports.MetricConfig{
Title: "Test Report",
FileFormat: reports.PDF,
From: "now-1h",
To: "now",
Aggregation: reports.AggConfig{
AggType: reports.AggregationAVG,
Interval: "1h",
},
},
Metrics: []reports.ReqMetric{
{
ChannelID: testsutil.GenerateUUID(&testing.T{}),
Name: "temperature",
ClientIDs: []string{testsutil.GenerateUUID(&testing.T{})},
},
},
ReportTemplate: template,
}
cases := []struct {
desc string
session authn.Session
config reports.ReportConfig
action reports.ReportAction
err error
}{
{
desc: "Generate instant email report with disabled config should succeed",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
config: validConfig,
action: reports.EmailReport,
err: nil,
},
{
desc: "Generate instant email report with enabled config should succeed",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
config: func() reports.ReportConfig {
cfg := validConfig
cfg.Status = reports.EnabledStatus
return cfg
}(),
action: reports.EmailReport,
err: nil,
},
{
desc: "Generate view report with disabled config should succeed",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
config: validConfig,
action: reports.ViewReport,
err: nil,
},
{
desc: "Generate download report with disabled config should succeed",
session: authn.Session{
UserID: userID,
DomainID: domainID,
},
config: validConfig,
action: reports.DownloadReport,
err: nil,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
_, err := svc.GenerateReport(context.Background(), tc.session, tc.config, tc.action)
if tc.err != nil {
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
} else {
assert.False(t, errors.Contains(err, svcerr.ErrInvalidStatus), fmt.Sprintf("%s: should not get ErrInvalidStatus for instant reports, got %s\n", tc.desc, err))
}
})
}
}