diff --git a/bootstrap/events/producer/streams_test.go b/bootstrap/events/producer/streams_test.go index a5b278ff2..d929bc61a 100644 --- a/bootstrap/events/producer/streams_test.go +++ b/bootstrap/events/producer/streams_test.go @@ -15,7 +15,6 @@ import ( "github.com/absmach/magistrala/bootstrap/events/producer" "github.com/absmach/magistrala/bootstrap/mocks" "github.com/absmach/magistrala/internal/testsutil" - "github.com/absmach/supermq/pkg/authn" smqauthn "github.com/absmach/supermq/pkg/authn" "github.com/absmach/supermq/pkg/errors" svcerr "github.com/absmach/supermq/pkg/errors/service" @@ -990,7 +989,7 @@ func TestChangeState(t *testing.T) { token string session smqauthn.Session state bootstrap.State - authResponse authn.Session + authResponse smqauthn.Session authorizeErr error connectErr error retrieveErr error @@ -1006,7 +1005,7 @@ func TestChangeState(t *testing.T) { userID: validID, domainID: domainID, state: bootstrap.Active, - authResponse: authn.Session{}, + authResponse: smqauthn.Session{}, err: nil, event: map[string]any{ "client_id": config.ClientID, diff --git a/bootstrap/middleware/authorization.go b/bootstrap/middleware/authorization.go index dc5f3a560..2c5227069 100644 --- a/bootstrap/middleware/authorization.go +++ b/bootstrap/middleware/authorization.go @@ -9,7 +9,6 @@ import ( "github.com/absmach/magistrala/bootstrap" smqauthn "github.com/absmach/supermq/pkg/authn" "github.com/absmach/supermq/pkg/authz" - smqauthz "github.com/absmach/supermq/pkg/authz" "github.com/absmach/supermq/pkg/policies" ) @@ -23,11 +22,11 @@ var _ bootstrap.Service = (*authorizationMiddleware)(nil) type authorizationMiddleware struct { svc bootstrap.Service - authz smqauthz.Authorization + authz authz.Authorization } // AuthorizationMiddleware adds authorization to the clients service. -func AuthorizationMiddleware(svc bootstrap.Service, authz smqauthz.Authorization) bootstrap.Service { +func AuthorizationMiddleware(svc bootstrap.Service, authz authz.Authorization) bootstrap.Service { return &authorizationMiddleware{ svc: svc, authz: authz, diff --git a/cmd/cli/main.go b/cmd/cli/main.go index 122de0a8f..e5dd85c2f 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -8,7 +8,6 @@ import ( "log" certscli "github.com/absmach/certs/cli" - "github.com/absmach/magistrala/cli" mgcli "github.com/absmach/magistrala/cli" mgsdk "github.com/absmach/magistrala/pkg/sdk" smqcli "github.com/absmach/supermq/cli" @@ -174,18 +173,18 @@ func main() { ) rootCmd.PersistentFlags().StringVarP( - &cli.ConfigPath, + &mgcli.ConfigPath, "config", "c", - cli.ConfigPath, + mgcli.ConfigPath, "Config path", ) rootCmd.PersistentFlags().BoolVarP( - &cli.RawOutput, + &mgcli.RawOutput, "raw", "r", - cli.RawOutput, + mgcli.RawOutput, "Enables raw output mode for easier parsing of output", ) rootCmd.PersistentFlags().BoolVarP( @@ -198,7 +197,7 @@ func main() { // Client and Channels Flags rootCmd.PersistentFlags().Uint64VarP( - &cli.Limit, + &mgcli.Limit, "limit", "l", 10, @@ -206,7 +205,7 @@ func main() { ) rootCmd.PersistentFlags().Uint64VarP( - &cli.Offset, + &mgcli.Offset, "offset", "o", 0, @@ -214,7 +213,7 @@ func main() { ) rootCmd.PersistentFlags().StringVarP( - &cli.Name, + &mgcli.Name, "name", "n", "", @@ -222,7 +221,7 @@ func main() { ) rootCmd.PersistentFlags().StringVarP( - &cli.Identity, + &mgcli.Identity, "identity", "I", "", @@ -230,7 +229,7 @@ func main() { ) rootCmd.PersistentFlags().StringVarP( - &cli.Metadata, + &mgcli.Metadata, "metadata", "m", "", @@ -238,7 +237,7 @@ func main() { ) rootCmd.PersistentFlags().StringVarP( - &cli.Status, + &mgcli.Status, "status", "S", "", @@ -246,7 +245,7 @@ func main() { ) rootCmd.PersistentFlags().StringVarP( - &cli.State, + &mgcli.State, "state", "z", "", @@ -254,7 +253,7 @@ func main() { ) rootCmd.PersistentFlags().StringVarP( - &cli.Topic, + &mgcli.Topic, "topic", "T", "", @@ -262,7 +261,7 @@ func main() { ) rootCmd.PersistentFlags().StringVarP( - &cli.Contact, + &mgcli.Contact, "contact", "C", "", diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 305aaa0cb..7d2b04628 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -509,7 +509,7 @@ services: create_host_path: true pdf-generator: - image: gotenberg/gotenberg:${MG_RELEASE_TAG} + image: gotenberg/gotenberg:8.25.1 container_name: magistrala-pdf ports: - "4000:3000" diff --git a/go.mod b/go.mod index 76c806e63..f1644c943 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/0x6flab/namegenerator v1.4.0 github.com/absmach/callhome v0.18.2 github.com/absmach/certs v0.18.2 - github.com/absmach/supermq v0.18.2 + github.com/absmach/supermq v0.18.3 github.com/authzed/authzed-go v1.7.0 github.com/authzed/grpcutil v0.0.0-20250221190651-1985b19b35b8 github.com/caarlos0/env/v11 v11.3.1 @@ -27,7 +27,7 @@ require ( github.com/ory/dockertest/v3 v3.12.0 github.com/pelletier/go-toml v1.9.5 github.com/prometheus/client_golang v1.23.2 - github.com/redis/go-redis/v9 v9.17.0 + github.com/redis/go-redis/v9 v9.17.1 github.com/rubenv/sql-migrate v1.8.1 github.com/slack-go/slack v0.17.3 github.com/spf13/cobra v1.10.1 @@ -49,14 +49,15 @@ require ( require ( go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/time v0.14.0 // indirect ) require ( buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.10-20250912141014-52f32327d4b0.1 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect - dario.cat/mergo v1.0.0 // indirect + dario.cat/mergo v1.0.2 // indirect filippo.io/edwards25519 v1.1.0 // indirect - github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/absmach/senml v1.0.8 @@ -111,7 +112,7 @@ require ( github.com/oklog/ulid/v2 v2.1.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/opencontainers/runc v1.2.3 // indirect + github.com/opencontainers/runc v1.2.8 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587 // indirect diff --git a/go.sum b/go.sum index df6e15273..b22e2ce00 100644 --- a/go.sum +++ b/go.sum @@ -5,14 +5,14 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/0x6flab/namegenerator v1.4.0 h1:QnkI813SZsI/hYnKD9pg3mkIlcYzCx0N4hnzb0YYME4= github.com/0x6flab/namegenerator v1.4.0/go.mod h1:2sQzXuS6dX/KEwWtB6GJU729O3m4gBdD5oAU8hd0SyY= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= @@ -30,8 +30,8 @@ github.com/absmach/mgate v0.5.0 h1:RV2Aalra3xIm+XTs13TM7iE7v4WTL2SKhKcPbKr22Ac= github.com/absmach/mgate v0.5.0/go.mod h1:0KVq7mxM0wayosmyXPPxp1EL0c2d9kRp5V8NZCKdetA= github.com/absmach/senml v1.0.8 h1:+opem/r4g6c6eA/JLyCIuksyEhj7eBdysY3pEmy1mqo= github.com/absmach/senml v1.0.8/go.mod h1:DRhzHLgvQoIUHroBgpFrSWso+bJZO9E96RlHAHy+VRI= -github.com/absmach/supermq v0.18.2 h1:oGiuMyjiFJhAmysmApOsYKtN4Kf/WXpLWElEUqmcZec= -github.com/absmach/supermq v0.18.2/go.mod h1:ehhfnpFhHZLF9EAdxqDSUCWLJxYhu0mqFd7a4CKMY0g= +github.com/absmach/supermq v0.18.3 h1:IUIZ/uCZ+ZH1O4/fOZwCZOrrll6kImlLuA6Gbye7HtU= +github.com/absmach/supermq v0.18.3/go.mod h1:QAKnqBIYfTMkFfMgWfqGTP0pTIZQxTXCJkTf7Vf7VOo= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -372,8 +372,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/opencontainers/runc v1.2.3 h1:fxE7amCzfZflJO2lHXf4y/y8M1BoAqp+FVmG19oYB80= -github.com/opencontainers/runc v1.2.3/go.mod h1:nSxcWUydXrsBZVYNSkTjoQ/N6rcyTtn+1SD5D4+kRIM= +github.com/opencontainers/runc v1.2.8 h1:RnEICeDReapbZ5lZEgHvj7E9Q3Eex9toYmaGBsbvU5Q= +github.com/opencontainers/runc v1.2.8/go.mod h1:cC0YkmZcuvr+rtBZ6T7NBoVbMGNAdLa/21vIElJDOzI= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCyRCw= github.com/ory/dockertest/v3 v3.12.0/go.mod h1:aKNDTva3cp8dwOWwb9cWuX84aH5akkxXRvO7KCwWVjE= @@ -424,8 +424,8 @@ github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7D github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw= github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o= -github.com/redis/go-redis/v9 v9.17.0 h1:K6E+ZlYN95KSMmZeEQPbU/c++wfmEvfFB17yEAq/VhM= -github.com/redis/go-redis/v9 v9.17.0/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= +github.com/redis/go-redis/v9 v9.17.1 h1:7tl732FjYPRT9H9aNfyTwKg9iTETjWjGKEJ2t/5iWTs= +github.com/redis/go-redis/v9 v9.17.1/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= @@ -613,8 +613,8 @@ golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= -golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= +golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -679,8 +679,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= diff --git a/re/middleware/logging.go b/re/middleware/logging.go index 948e528ed..c7afb8236 100644 --- a/re/middleware/logging.go +++ b/re/middleware/logging.go @@ -238,5 +238,5 @@ func (lm *loggingMiddleware) Handle(msg *messaging.Message) (err error) { } func (lm *loggingMiddleware) Cancel() error { - return lm.Cancel() + return lm.svc.Cancel() } diff --git a/reports/postgres/repository.go b/reports/postgres/repository.go index b268556cc..4fc181006 100644 --- a/reports/postgres/repository.go +++ b/reports/postgres/repository.go @@ -140,7 +140,7 @@ func (repo *PostgresRepository) UpdateReportConfig(ctx context.Context, cfg repo var q string if len(query) > 0 { - q = fmt.Sprintf("%s", strings.Join(query, ", ")) + q = strings.Join(query, ", ") } q = fmt.Sprintf(` diff --git a/reports/postgres/repository_test.go b/reports/postgres/repository_test.go new file mode 100644 index 000000000..7d6edf630 --- /dev/null +++ b/reports/postgres/repository_test.go @@ -0,0 +1,822 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package postgres_test + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/0x6flab/namegenerator" + "github.com/absmach/magistrala/pkg/schedule" + "github.com/absmach/magistrala/reports" + "github.com/absmach/magistrala/reports/postgres" + "github.com/absmach/supermq/pkg/errors" + repoerr "github.com/absmach/supermq/pkg/errors/repository" + "github.com/absmach/supermq/pkg/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + namegen = namegenerator.NewGenerator() + idProvider = uuid.New() +) + +func generateUUID(t *testing.T) string { + id, err := idProvider.ID() + require.Nil(t, err, fmt.Sprintf("generate uuid unexpected error: %s", err)) + return id +} + +func TestAddReportConfig(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM report_config") + require.Nil(t, err, fmt.Sprintf("clean report_config unexpected error: %s", err)) + }) + + repo := postgres.NewRepository(database) + + reportConfig := reports.ReportConfig{ + ID: generateUUID(t), + Name: namegen.Generate(), + Description: namegen.Generate(), + DomainID: generateUUID(t), + Config: &reports.MetricConfig{ + From: "now-1h", + To: "now", + Title: "Test Report", + }, + Metrics: []reports.ReqMetric{ + { + ChannelID: generateUUID(t), + Name: "temperature", + }, + }, + Email: &reports.EmailSetting{ + To: []string{"test@example.com"}, + Subject: "Test Report", + Content: "Report content", + }, + Schedule: schedule.Schedule{ + StartDateTime: time.Now().UTC(), + Time: time.Now().UTC().Add(time.Hour), + Recurring: schedule.Daily, + RecurringPeriod: 1, + }, + Status: reports.EnabledStatus, + CreatedAt: time.Now().UTC(), + CreatedBy: generateUUID(t), + UpdatedAt: time.Now().UTC(), + UpdatedBy: generateUUID(t), + } + + cases := []struct { + desc string + report reports.ReportConfig + err error + }{ + { + desc: "add valid report config", + report: reportConfig, + err: nil, + }, + { + desc: "add duplicate report config", + report: reportConfig, + err: repoerr.ErrConflict, + }, + { + desc: "add report config with empty ID", + report: reports.ReportConfig{ + Name: namegen.Generate(), + DomainID: generateUUID(t), + CreatedAt: time.Now().UTC(), + UpdatedAt: time.Now().UTC(), + }, + err: repoerr.ErrCreateEntity, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + rpt, err := repo.AddReportConfig(context.Background(), tc.report) + 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)) + return + } + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + require.NotEmpty(t, rpt.ID) + require.Equal(t, tc.report.Name, rpt.Name) + require.Equal(t, tc.report.DomainID, rpt.DomainID) + require.Equal(t, tc.report.Status, rpt.Status) + }) + } +} + +func TestViewReportConfig(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM report_config") + require.Nil(t, err, fmt.Sprintf("clean report_config unexpected error: %s", err)) + }) + + repo := postgres.NewRepository(database) + + reportConfig := reports.ReportConfig{ + ID: generateUUID(t), + Name: namegen.Generate(), + Description: namegen.Generate(), + DomainID: generateUUID(t), + Config: &reports.MetricConfig{ + From: "now-1h", + To: "now", + Title: "Test Report", + }, + Metrics: []reports.ReqMetric{ + { + ChannelID: generateUUID(t), + Name: "temperature", + }, + }, + Email: &reports.EmailSetting{ + To: []string{"test@example.com"}, + Subject: "Test Report", + }, + Status: reports.EnabledStatus, + CreatedAt: time.Now().UTC(), + CreatedBy: generateUUID(t), + UpdatedAt: time.Now().UTC(), + UpdatedBy: generateUUID(t), + } + + saved, err := repo.AddReportConfig(context.Background(), reportConfig) + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + + cases := []struct { + desc string + id string + err error + }{ + { + desc: "view existing report config", + id: saved.ID, + err: nil, + }, + { + desc: "view non-existing report config", + id: generateUUID(t), + err: repoerr.ErrNotFound, + }, + { + desc: "view with empty id", + id: "", + err: repoerr.ErrNotFound, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + rpt, err := repo.ViewReportConfig(context.Background(), tc.id) + 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)) + return + } + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + require.Equal(t, saved.ID, rpt.ID) + require.Equal(t, saved.Name, rpt.Name) + require.Equal(t, saved.DomainID, rpt.DomainID) + }) + } +} + +func TestUpdateReportConfig(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM report_config") + require.Nil(t, err, fmt.Sprintf("clean report_config unexpected error: %s", err)) + }) + + repo := postgres.NewRepository(database) + + reportConfig := reports.ReportConfig{ + ID: generateUUID(t), + Name: namegen.Generate(), + Description: namegen.Generate(), + DomainID: generateUUID(t), + Status: reports.EnabledStatus, + CreatedAt: time.Now().UTC(), + CreatedBy: generateUUID(t), + UpdatedAt: time.Now().UTC(), + UpdatedBy: generateUUID(t), + Metrics: []reports.ReqMetric{ + { + ChannelID: generateUUID(t), + Name: "temperature", + }, + }, + } + + saved, err := repo.AddReportConfig(context.Background(), reportConfig) + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + + cases := []struct { + desc string + report reports.ReportConfig + err error + }{ + { + desc: "update report name", + report: reports.ReportConfig{ + ID: saved.ID, + Name: "Updated Name", + UpdatedAt: time.Now().UTC(), + UpdatedBy: generateUUID(t), + }, + err: nil, + }, + { + desc: "update report description", + report: reports.ReportConfig{ + ID: saved.ID, + Description: "Updated Description", + UpdatedAt: time.Now().UTC(), + UpdatedBy: generateUUID(t), + }, + err: nil, + }, + { + desc: "update non-existing report", + report: reports.ReportConfig{ + ID: generateUUID(t), + Name: "New Name", + UpdatedAt: time.Now().UTC(), + }, + err: repoerr.ErrNotFound, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + rpt, err := repo.UpdateReportConfig(context.Background(), tc.report) + 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)) + return + } + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + require.Equal(t, tc.report.ID, rpt.ID) + if tc.report.Name != "" { + require.Equal(t, tc.report.Name, rpt.Name) + } + if tc.report.Description != "" { + require.Equal(t, tc.report.Description, rpt.Description) + } + }) + } +} + +func TestUpdateReportConfigStatus(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM report_config") + require.Nil(t, err, fmt.Sprintf("clean report_config unexpected error: %s", err)) + }) + + repo := postgres.NewRepository(database) + + reportConfig := reports.ReportConfig{ + ID: generateUUID(t), + Name: namegen.Generate(), + DomainID: generateUUID(t), + Status: reports.EnabledStatus, + CreatedAt: time.Now().UTC(), + CreatedBy: generateUUID(t), + UpdatedAt: time.Now().UTC(), + UpdatedBy: generateUUID(t), + } + + saved, err := repo.AddReportConfig(context.Background(), reportConfig) + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + + cases := []struct { + desc string + report reports.ReportConfig + err error + }{ + { + desc: "disable report", + report: reports.ReportConfig{ + ID: saved.ID, + Status: reports.DisabledStatus, + UpdatedAt: time.Now().UTC(), + UpdatedBy: generateUUID(t), + }, + err: nil, + }, + { + desc: "enable report", + report: reports.ReportConfig{ + ID: saved.ID, + Status: reports.EnabledStatus, + UpdatedAt: time.Now().UTC(), + UpdatedBy: generateUUID(t), + }, + err: nil, + }, + { + desc: "update status of non-existing report", + report: reports.ReportConfig{ + ID: generateUUID(t), + Status: reports.DisabledStatus, + UpdatedAt: time.Now().UTC(), + }, + err: repoerr.ErrNotFound, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + rpt, err := repo.UpdateReportConfigStatus(context.Background(), tc.report) + 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)) + return + } + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + require.Equal(t, tc.report.Status, rpt.Status) + }) + } +} + +func TestRemoveReportConfig(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM report_config") + require.Nil(t, err, fmt.Sprintf("clean report_config unexpected error: %s", err)) + }) + + repo := postgres.NewRepository(database) + + reportConfig := reports.ReportConfig{ + ID: generateUUID(t), + Name: namegen.Generate(), + DomainID: generateUUID(t), + Status: reports.EnabledStatus, + CreatedAt: time.Now().UTC(), + UpdatedAt: time.Now().UTC(), + } + + saved, err := repo.AddReportConfig(context.Background(), reportConfig) + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + + cases := []struct { + desc string + id string + err error + }{ + { + desc: "remove existing report", + id: saved.ID, + err: nil, + }, + { + desc: "remove non-existing report", + id: generateUUID(t), + err: nil, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + err := repo.RemoveReportConfig(context.Background(), tc.id) + 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)) + return + } + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + }) + } +} + +func TestListReportsConfig(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM report_config") + require.Nil(t, err, fmt.Sprintf("clean report_config unexpected error: %s", err)) + }) + + repo := postgres.NewRepository(database) + + domainID := generateUUID(t) + + num := uint64(10) + for i := uint64(0); i < num; i++ { + reportConfig := reports.ReportConfig{ + ID: generateUUID(t), + Name: fmt.Sprintf("Report-%d", i), + DomainID: domainID, + Status: reports.EnabledStatus, + CreatedAt: time.Now().UTC(), + UpdatedAt: time.Now().UTC(), + } + _, err := repo.AddReportConfig(context.Background(), reportConfig) + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + } + + cases := []struct { + desc string + pageMeta reports.PageMeta + size uint64 + err error + }{ + { + desc: "list all reports", + pageMeta: reports.PageMeta{ + Domain: domainID, + Limit: num, + Offset: 0, + }, + size: num, + err: nil, + }, + { + desc: "list with limit", + pageMeta: reports.PageMeta{ + Domain: domainID, + Limit: 5, + Offset: 0, + }, + size: 5, + err: nil, + }, + { + desc: "list with offset", + pageMeta: reports.PageMeta{ + Domain: domainID, + Limit: num, + Offset: 5, + }, + size: 5, + err: nil, + }, + { + desc: "list enabled reports", + pageMeta: reports.PageMeta{ + Domain: domainID, + Limit: num, + Status: reports.EnabledStatus, + }, + size: num, + err: nil, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + page, err := repo.ListReportsConfig(context.Background(), tc.pageMeta) + 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)) + return + } + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + require.Equal(t, tc.size, uint64(len(page.ReportConfigs))) + }) + } +} + +func TestUpdateReportSchedule(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM report_config") + require.Nil(t, err, fmt.Sprintf("clean report_config unexpected error: %s", err)) + }) + + repo := postgres.NewRepository(database) + + reportConfig := reports.ReportConfig{ + ID: generateUUID(t), + Name: namegen.Generate(), + DomainID: generateUUID(t), + Status: reports.EnabledStatus, + CreatedAt: time.Now().UTC(), + CreatedBy: generateUUID(t), + UpdatedAt: time.Now().UTC(), + UpdatedBy: generateUUID(t), + Metrics: []reports.ReqMetric{ + { + ChannelID: generateUUID(t), + Name: "temperature", + }, + }, + Schedule: schedule.Schedule{ + StartDateTime: time.Now().UTC(), + Time: time.Now().UTC().Add(time.Hour), + Recurring: schedule.Daily, + RecurringPeriod: 1, + }, + } + + saved, err := repo.AddReportConfig(context.Background(), reportConfig) + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + + newSchedule := schedule.Schedule{ + StartDateTime: time.Now().UTC().Add(24 * time.Hour), + Time: time.Now().UTC().Add(25 * time.Hour), + Recurring: schedule.Weekly, + RecurringPeriod: 2, + } + + cases := []struct { + desc string + report reports.ReportConfig + expected schedule.Schedule + err error + }{ + { + desc: "update schedule", + report: reports.ReportConfig{ + ID: saved.ID, + Schedule: newSchedule, + UpdatedAt: time.Now().UTC(), + UpdatedBy: generateUUID(t), + }, + expected: newSchedule, + err: nil, + }, + { + desc: "update schedule of non-existing report", + report: reports.ReportConfig{ + ID: generateUUID(t), + Schedule: newSchedule, + UpdatedAt: time.Now().UTC(), + }, + err: repoerr.ErrNotFound, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + rpt, err := repo.UpdateReportSchedule(context.Background(), tc.report) + 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)) + return + } + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + require.Equal(t, tc.expected.Recurring, rpt.Schedule.Recurring) + require.Equal(t, tc.expected.RecurringPeriod, rpt.Schedule.RecurringPeriod) + }) + } +} + +func TestUpdateReportDue(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM report_config") + require.Nil(t, err, fmt.Sprintf("clean report_config unexpected error: %s", err)) + }) + + repo := postgres.NewRepository(database) + + reportConfig := reports.ReportConfig{ + ID: generateUUID(t), + Name: namegen.Generate(), + DomainID: generateUUID(t), + Status: reports.EnabledStatus, + CreatedAt: time.Now().UTC(), + UpdatedAt: time.Now().UTC(), + Metrics: []reports.ReqMetric{ + { + ChannelID: generateUUID(t), + Name: "temperature", + }, + }, + } + + saved, err := repo.AddReportConfig(context.Background(), reportConfig) + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + + newDue := time.Now().UTC().Add(24 * time.Hour) + + cases := []struct { + desc string + id string + due time.Time + err error + }{ + { + desc: "update due time", + id: saved.ID, + due: newDue, + err: nil, + }, + { + desc: "update due time of non-existing report", + id: generateUUID(t), + due: newDue, + err: repoerr.ErrNotFound, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + rpt, err := repo.UpdateReportDue(context.Background(), tc.id, tc.due) + 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)) + return + } + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + require.True(t, tc.due.Equal(rpt.Schedule.Time)) + }) + } +} + +func TestUpdateReportTemplate(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM report_config") + require.Nil(t, err, fmt.Sprintf("clean report_config unexpected error: %s", err)) + }) + + repo := postgres.NewRepository(database) + + domainID := generateUUID(t) + reportConfig := reports.ReportConfig{ + ID: generateUUID(t), + Name: namegen.Generate(), + DomainID: domainID, + Status: reports.EnabledStatus, + CreatedAt: time.Now().UTC(), + UpdatedAt: time.Now().UTC(), + Metrics: []reports.ReqMetric{ + { + ChannelID: generateUUID(t), + Name: "temperature", + }, + }, + } + + saved, err := repo.AddReportConfig(context.Background(), reportConfig) + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + + template := reports.ReportTemplate("Test Template") + + cases := []struct { + desc string + domainID string + reportID string + template reports.ReportTemplate + err error + }{ + { + desc: "update template", + domainID: domainID, + reportID: saved.ID, + template: template, + err: nil, + }, + { + desc: "update template for non-existing report", + domainID: domainID, + reportID: generateUUID(t), + template: template, + err: nil, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + err := repo.UpdateReportTemplate(context.Background(), tc.domainID, tc.reportID, tc.template) + 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)) + return + } + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + }) + } +} + +func TestViewReportTemplate(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM report_config") + require.Nil(t, err, fmt.Sprintf("clean report_config unexpected error: %s", err)) + }) + + repo := postgres.NewRepository(database) + + domainID := generateUUID(t) + template := reports.ReportTemplate("Test Template") + + reportConfig := reports.ReportConfig{ + ID: generateUUID(t), + Name: namegen.Generate(), + DomainID: domainID, + Status: reports.EnabledStatus, + CreatedAt: time.Now().UTC(), + UpdatedAt: time.Now().UTC(), + ReportTemplate: template, + Metrics: []reports.ReqMetric{ + { + ChannelID: generateUUID(t), + Name: "temperature", + }, + }, + } + + saved, err := repo.AddReportConfig(context.Background(), reportConfig) + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + + cases := []struct { + desc string + domainID string + reportID string + expected reports.ReportTemplate + err error + }{ + { + desc: "view existing template", + domainID: domainID, + reportID: saved.ID, + expected: template, + err: nil, + }, + { + desc: "view template for non-existing report", + domainID: domainID, + reportID: generateUUID(t), + err: repoerr.ErrNotFound, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + tmpl, err := repo.ViewReportTemplate(context.Background(), tc.domainID, tc.reportID) + 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)) + return + } + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + require.Equal(t, tc.expected, tmpl) + }) + } +} + +func TestDeleteReportTemplate(t *testing.T) { + t.Cleanup(func() { + _, err := db.Exec("DELETE FROM report_config") + require.Nil(t, err, fmt.Sprintf("clean report_config unexpected error: %s", err)) + }) + + repo := postgres.NewRepository(database) + + domainID := generateUUID(t) + template := reports.ReportTemplate("Test Template") + + reportConfig := reports.ReportConfig{ + ID: generateUUID(t), + Name: namegen.Generate(), + DomainID: domainID, + Status: reports.EnabledStatus, + CreatedAt: time.Now().UTC(), + UpdatedAt: time.Now().UTC(), + ReportTemplate: template, + Metrics: []reports.ReqMetric{ + { + ChannelID: generateUUID(t), + Name: "temperature", + }, + }, + } + + saved, err := repo.AddReportConfig(context.Background(), reportConfig) + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + + cases := []struct { + desc string + domainID string + reportID string + err error + }{ + { + desc: "delete existing template", + domainID: domainID, + reportID: saved.ID, + err: nil, + }, + { + desc: "delete template for non-existing report", + domainID: domainID, + reportID: generateUUID(t), + err: nil, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + err := repo.DeleteReportTemplate(context.Background(), tc.domainID, tc.reportID) + 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)) + return + } + require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err)) + + if tc.reportID == saved.ID { + tmpl, err := repo.ViewReportTemplate(context.Background(), tc.domainID, tc.reportID) + require.Nil(t, err) + require.Empty(t, tmpl) + } + }) + } +} diff --git a/reports/postgres/setup_test.go b/reports/postgres/setup_test.go new file mode 100644 index 000000000..c8a1b7c47 --- /dev/null +++ b/reports/postgres/setup_test.go @@ -0,0 +1,91 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package postgres_test + +import ( + "database/sql" + "fmt" + "log" + "os" + "testing" + "time" + + rpostgres "github.com/absmach/magistrala/reports/postgres" + "github.com/absmach/supermq/pkg/postgres" + "github.com/jmoiron/sqlx" + dockertest "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" + "go.opentelemetry.io/otel" +) + +var ( + db *sqlx.DB + database postgres.Database + tracer = otel.Tracer("repo_tests") +) + +func TestMain(m *testing.M) { + pool, err := dockertest.NewPool("") + if err != nil { + log.Fatalf("Could not connect to docker: %s", err) + } + + container, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "postgres", + Tag: "16.2-alpine", + Env: []string{ + "POSTGRES_USER=test", + "POSTGRES_PASSWORD=test", + "POSTGRES_DB=test", + "listen_addresses = '*'", + }, + }, func(config *docker.HostConfig) { + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{Name: "no"} + }) + if err != nil { + log.Fatalf("Could not start container: %s", err) + } + + port := container.GetPort("5432/tcp") + + pool.MaxWait = 120 * time.Second + if err := pool.Retry(func() error { + url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port) + db, err := sql.Open("pgx", url) + if err != nil { + return err + } + return db.Ping() + }); err != nil { + log.Fatalf("Could not connect to docker: %s", err) + } + + dbConfig := postgres.Config{ + Host: "localhost", + Port: port, + User: "test", + Pass: "test", + Name: "test", + SSLMode: "disable", + SSLCert: "", + SSLKey: "", + SSLRootCert: "", + } + + if db, err = postgres.Setup(dbConfig, *rpostgres.Migration()); err != nil { + log.Fatalf("Could not setup test DB connection: %s", err) + } + + database = postgres.NewDatabase(db, dbConfig, tracer) + + code := m.Run() + + db.Close() + if err := pool.Purge(container); err != nil { + log.Fatalf("Could not purge container: %s", err) + } + + os.Exit(code) +}