Files
magistrala/bootstrap/events/producer/streams_test.go
T
Steve Munene 7f03134d8e
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
NOISSUE - Update bootstrap and provision service (#3476)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
Signed-off-by: JeffMboya <jangina.mboya@gmail.com>
Co-authored-by: JeffMboya <jangina.mboya@gmail.com>
2026-05-08 10:35:00 +02:00

915 lines
25 KiB
Go

// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package producer_test
import (
"context"
"fmt"
"strconv"
"strings"
"testing"
"time"
"github.com/absmach/magistrala/bootstrap"
"github.com/absmach/magistrala/bootstrap/events/producer"
bootstraphasher "github.com/absmach/magistrala/bootstrap/hasher"
"github.com/absmach/magistrala/bootstrap/mocks"
"github.com/absmach/magistrala/internal/testsutil"
smqauthn "github.com/absmach/magistrala/pkg/authn"
"github.com/absmach/magistrala/pkg/errors"
svcerr "github.com/absmach/magistrala/pkg/errors/service"
"github.com/absmach/magistrala/pkg/events/store"
sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks"
"github.com/absmach/magistrala/pkg/uuid"
"github.com/redis/go-redis/v9"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
const (
streamID = "magistrala.bootstrap"
validToken = "validToken"
unknownID = "unknown"
configPrefix = "config."
configCreate = configPrefix + "create"
configView = configPrefix + "view"
configUpdate = configPrefix + "update"
configRemove = configPrefix + "remove"
configList = configPrefix + "list"
clientPrefix = "client."
clientBootstrap = clientPrefix + "bootstrap"
configEnable = configPrefix + "enable"
configDisable = configPrefix + "disable"
certUpdate = "cert.update"
)
var (
encKey = []byte("1234567891011121")
domainID = testsutil.GenerateUUID(&testing.T{})
validID = testsutil.GenerateUUID(&testing.T{})
config = bootstrap.Config{
ID: testsutil.GenerateUUID(&testing.T{}),
ExternalID: testsutil.GenerateUUID(&testing.T{}),
ExternalKey: testsutil.GenerateUUID(&testing.T{}),
Content: "config",
Status: bootstrap.EnabledStatus,
}
)
type testVariable struct {
svc bootstrap.Service
boot *mocks.ConfigRepository
sdk *sdkmocks.SDK
}
func newTestVariable(t *testing.T, redisURL string) testVariable {
boot := new(mocks.ConfigRepository)
sdk := new(sdkmocks.SDK)
idp := uuid.NewMock()
svc := bootstrap.New(boot, nil, nil, nil, nil, sdk, bootstraphasher.New(), encKey, idp)
publisher, err := store.NewPublisher(context.Background(), redisURL, "bootstrap-es-pub-test")
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
svc = producer.NewEventStoreMiddleware(svc, publisher)
return testVariable{
svc: svc,
boot: boot,
sdk: sdk,
}
}
func TestAdd(t *testing.T) {
err := redisClient.FlushAll(context.Background()).Err()
assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
tv := newTestVariable(t, redisURL)
cases := []struct {
desc string
config bootstrap.Config
token string
session smqauthn.Session
id string
domainID string
saveErr error
err error
event map[string]any
}{
{
desc: "create config successfully",
config: config,
token: validToken,
id: validID,
domainID: domainID,
event: map[string]any{
"config_id": "1",
"domain_id": domainID,
"name": config.Name,
"external_id": config.ExternalID,
"content": config.Content,
"timestamp": time.Now().Unix(),
"operation": configCreate,
},
err: nil,
},
{
desc: "create config with failed to save",
config: config,
token: validToken,
id: validID,
domainID: domainID,
event: nil,
saveErr: svcerr.ErrCreateEntity,
err: svcerr.ErrCreateEntity,
},
}
lastID := "0"
for _, tc := range cases {
tc.session = smqauthn.Session{UserID: validID, DomainID: tc.domainID, DomainUserID: validID}
repoCall := tv.boot.On("Save", context.Background(), mock.Anything).Return(mock.Anything, tc.saveErr)
_, err := tv.svc.Add(context.Background(), tc.session, tc.token, tc.config)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
streams := redisClient.XRead(context.Background(), &redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]any
if len(streams) > 0 && len(streams[0].Messages) > 0 {
event := streams[0].Messages
lastID = event[0].ID
}
test(t, tc.event, event, tc.desc)
repoCall.Unset()
}
}
func TestView(t *testing.T) {
err := redisClient.FlushAll(context.Background()).Err()
assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
tv := newTestVariable(t, redisURL)
nonExisting := config
nonExisting.ID = unknownID
cases := []struct {
desc string
config bootstrap.Config
token string
session smqauthn.Session
id string
domainID string
retrieveErr error
err error
event map[string]any
}{
{
desc: "view successfully",
config: config,
token: validToken,
id: validID,
domainID: domainID,
err: nil,
event: map[string]any{
"config_id": config.ID,
"domain_id": config.DomainID,
"name": config.Name,
"external_id": config.ExternalID,
"content": config.Content,
"timestamp": time.Now().Unix(),
"operation": configView,
},
},
{
desc: "view with failed retrieve",
config: nonExisting,
token: validToken,
id: validID,
domainID: domainID,
retrieveErr: svcerr.ErrViewEntity,
err: svcerr.ErrViewEntity,
event: nil,
},
}
lastID := "0"
for _, tc := range cases {
tc.session = smqauthn.Session{UserID: validID, DomainID: tc.domainID, DomainUserID: validID}
repoCall := tv.boot.On("RetrieveByID", context.Background(), tc.domainID, tc.config.ID).Return(config, tc.retrieveErr)
_, err := tv.svc.View(context.Background(), tc.session, tc.config.ID)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
streams := redisClient.XRead(context.Background(), &redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]any
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
event["timestamp"] = msg.ID
lastID = msg.ID
}
test(t, tc.event, event, tc.desc)
repoCall.Unset()
}
}
func TestUpdate(t *testing.T) {
err := redisClient.FlushAll(context.Background()).Err()
assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
tv := newTestVariable(t, redisURL)
modified := config
modified.Content = "new-config"
modified.Name = "new name"
nonExisting := config
nonExisting.ID = unknownID
cases := []struct {
desc string
config bootstrap.Config
token string
session smqauthn.Session
id string
domainID string
updateErr error
err error
event map[string]any
}{
{
desc: "update config successfully",
config: modified,
token: validToken,
id: validID,
domainID: domainID,
err: nil,
event: map[string]any{
"name": modified.Name,
"content": modified.Content,
"timestamp": time.Now().UnixNano(),
"operation": configUpdate,
"external_id": modified.ExternalID,
"config_id": modified.ID,
"domain_id": domainID,
"status": bootstrap.Disabled,
"occurred_at": time.Now().UnixNano(),
},
},
{
desc: "update with failed update",
config: nonExisting,
token: validToken,
id: validID,
domainID: domainID,
updateErr: svcerr.ErrNotFound,
err: svcerr.ErrNotFound,
event: nil,
},
}
lastID := "0"
for _, tc := range cases {
tc.session = smqauthn.Session{UserID: validID, DomainID: tc.domainID, DomainUserID: validID}
repoCall := tv.boot.On("Update", context.Background(), mock.Anything).Return(tc.updateErr)
err := tv.svc.Update(context.Background(), tc.session, tc.config)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
streams := redisClient.XRead(context.Background(), &redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]any
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
event["timestamp"] = msg.ID
lastID = msg.ID
}
test(t, tc.event, event, tc.desc)
repoCall.Unset()
}
}
func TestUpdateCert(t *testing.T) {
err := redisClient.FlushAll(context.Background()).Err()
assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
tv := newTestVariable(t, redisURL)
cases := []struct {
desc string
configID string
userID string
domainID string
token string
session smqauthn.Session
clientCert string
clientKey string
caCert string
updateErr error
err error
event map[string]any
}{
{
desc: "update cert successfully",
configID: config.ID,
userID: validID,
domainID: domainID,
token: validToken,
clientCert: "clientCert",
clientKey: "clientKey",
caCert: "caCert",
err: nil,
event: map[string]any{
"client_cert": "clientCert",
"client_key": "clientKey",
"ca_cert": "caCert",
"operation": certUpdate,
},
},
{
desc: "update cert with failed update",
configID: "clientID",
token: validToken,
userID: validID,
domainID: domainID,
clientCert: "clientCert",
clientKey: "clientKey",
caCert: "caCert",
updateErr: svcerr.ErrNotFound,
err: svcerr.ErrNotFound,
event: nil,
},
{
desc: "update cert with empty client certificate",
configID: config.ID,
token: validToken,
userID: validID,
domainID: domainID,
clientCert: "",
clientKey: "clientKey",
caCert: "caCert",
err: nil,
event: nil,
},
{
desc: "update cert with empty client key",
configID: config.ID,
token: validToken,
userID: validID,
domainID: domainID,
clientCert: "clientCert",
clientKey: "",
caCert: "caCert",
err: nil,
event: nil,
},
{
desc: "update cert with empty CA certificate",
configID: config.ID,
token: validToken,
userID: validID,
domainID: domainID,
clientCert: "clientCert",
clientKey: "clientKey",
caCert: "",
err: nil,
event: nil,
},
}
lastID := "0"
for _, tc := range cases {
tc.session = smqauthn.Session{UserID: tc.userID, DomainID: tc.domainID, DomainUserID: validID}
repoCall := tv.boot.On("UpdateCert", context.Background(), tc.domainID, tc.configID, tc.clientCert, tc.clientKey, tc.caCert).Return(config, tc.updateErr)
_, err := tv.svc.UpdateCert(context.Background(), tc.session, tc.configID, tc.clientCert, tc.clientKey, tc.caCert)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
streams := redisClient.XRead(context.Background(), &redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]any
if len(streams) > 0 && len(streams[0].Messages) > 0 {
event := streams[0].Messages
lastID = event[0].ID
}
test(t, tc.event, event, tc.desc)
repoCall.Unset()
}
}
func TestList(t *testing.T) {
tv := newTestVariable(t, redisURL)
numClients := 101
var c bootstrap.Config
saved := make([]bootstrap.Config, 0)
for i := 0; i < numClients; i++ {
c = config
c.ExternalID = testsutil.GenerateUUID(t)
c.ExternalKey = testsutil.GenerateUUID(t)
c.Name = fmt.Sprintf("%s-%d", config.Name, i)
if i == 41 {
c.Status = bootstrap.Active
}
saved = append(saved, c)
}
cases := []struct {
desc string
token string
session smqauthn.Session
userID string
domainID string
config bootstrap.ConfigsPage
filter bootstrap.Filter
offset uint64
limit uint64
retrieveErr error
err error
event map[string]any
}{
{
desc: "list successfully as super admin",
token: validToken,
userID: validID,
domainID: domainID,
session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true},
config: bootstrap.ConfigsPage{
Total: uint64(len(saved)),
Offset: 0,
Limit: 10,
Configs: saved[0:10],
},
filter: bootstrap.Filter{},
offset: 0,
limit: 10,
err: nil,
event: map[string]any{
"config_id": c.ID,
"domain_id": c.DomainID,
"name": c.Name,
"external_id": c.ExternalID,
"content": c.Content,
"timestamp": time.Now().Unix(),
"operation": configList,
},
},
{
desc: "list successfully as domain admin",
token: validToken,
userID: validID,
domainID: domainID,
session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true},
config: bootstrap.ConfigsPage{
Total: uint64(len(saved)),
Offset: 0,
Limit: 10,
Configs: saved[0:10],
},
filter: bootstrap.Filter{},
offset: 0,
limit: 10,
err: nil,
event: map[string]any{
"config_id": c.ID,
"domain_id": c.DomainID,
"name": c.Name,
"external_id": c.ExternalID,
"content": c.Content,
"timestamp": time.Now().Unix(),
"operation": configList,
},
},
{
desc: "list successfully as non admin",
token: validToken,
userID: validID,
domainID: domainID,
session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID},
config: bootstrap.ConfigsPage{
Total: uint64(len(saved)),
Offset: 0,
Limit: 10,
Configs: saved[0:10],
},
filter: bootstrap.Filter{},
offset: 0,
limit: 10,
err: nil,
event: map[string]any{
"config_id": c.ID,
"domain_id": c.DomainID,
"name": c.Name,
"external_id": c.ExternalID,
"content": c.Content,
"timestamp": time.Now().Unix(),
"operation": configList,
},
},
{
desc: "list as super admin with failed retrieve all",
token: validToken,
userID: validID,
domainID: domainID,
session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true},
filter: bootstrap.Filter{},
offset: 0,
limit: 10,
retrieveErr: nil,
err: nil,
event: nil,
},
{
desc: "list as domain admin with failed retrieve all",
token: validToken,
userID: validID,
domainID: domainID,
session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID, SuperAdmin: true},
filter: bootstrap.Filter{},
offset: 0,
limit: 10,
retrieveErr: nil,
err: nil,
event: nil,
},
{
desc: "list as non admin with failed retrieve all",
token: validToken,
userID: validID,
domainID: domainID,
session: smqauthn.Session{UserID: validID, DomainID: domainID, DomainUserID: validID},
filter: bootstrap.Filter{},
offset: 0,
limit: 10,
retrieveErr: nil,
err: nil,
event: nil,
},
}
lastID := "0"
for _, tc := range cases {
repoCall := tv.boot.On("RetrieveAll", context.Background(), mock.Anything, tc.filter, tc.offset, tc.limit).Return(tc.config, tc.retrieveErr)
_, err := tv.svc.List(context.Background(), tc.session, tc.filter, tc.offset, tc.limit)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
streams := redisClient.XRead(context.Background(), &redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]any
if len(streams) > 0 && len(streams[0].Messages) > 0 {
event := streams[0].Messages
lastID = event[0].ID
}
test(t, tc.event, event, tc.desc)
repoCall.Unset()
}
}
func TestRemove(t *testing.T) {
err := redisClient.FlushAll(context.Background()).Err()
assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
tv := newTestVariable(t, redisURL)
nonExisting := config
nonExisting.ID = unknownID
cases := []struct {
desc string
configID string
userID string
domainID string
token string
session smqauthn.Session
removeErr error
err error
event map[string]any
}{
{
desc: "remove config successfully",
configID: config.ID,
token: validToken,
userID: validID,
domainID: domainID,
err: nil,
event: map[string]any{
"config_id": config.ID,
"timestamp": time.Now().Unix(),
"operation": configRemove,
},
},
{
desc: "remove config with failed removal",
configID: nonExisting.ID,
token: validToken,
userID: validID,
domainID: domainID,
removeErr: svcerr.ErrNotFound,
err: svcerr.ErrNotFound,
event: nil,
},
}
lastID := "0"
for _, tc := range cases {
tc.session = smqauthn.Session{UserID: validID, DomainID: tc.domainID, DomainUserID: validID}
repoCall := tv.boot.On("Remove", context.Background(), mock.Anything, mock.Anything).Return(tc.removeErr)
err := tv.svc.Remove(context.Background(), tc.session, tc.configID)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
streams := redisClient.XRead(context.Background(), &redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]any
if len(streams) > 0 && len(streams[0].Messages) > 0 {
event := streams[0].Messages
lastID = event[0].ID
}
test(t, tc.event, event, tc.desc)
repoCall.Unset()
}
}
func TestBootstrap(t *testing.T) {
err := redisClient.FlushAll(context.Background()).Err()
assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
tv := newTestVariable(t, redisURL)
cases := []struct {
desc string
externalID string
externalKey string
err error
retrieveErr error
event map[string]any
}{
{
desc: "bootstrap successfully",
externalID: config.ExternalID,
externalKey: config.ExternalKey,
err: nil,
event: map[string]any{
"external_id": config.ExternalID,
"success": "1",
"timestamp": time.Now().Unix(),
"operation": clientBootstrap,
},
},
{
desc: "bootstrap with an error",
externalID: "external_id1",
externalKey: "external_id",
retrieveErr: bootstrap.ErrBootstrap,
err: bootstrap.ErrBootstrap,
event: map[string]any{
"external_id": "external_id",
"success": "0",
"timestamp": time.Now().Unix(),
"operation": clientBootstrap,
},
},
}
lastID := "0"
for _, tc := range cases {
repoCall := tv.boot.On("RetrieveByExternalID", context.Background(), mock.Anything).Return(config, tc.retrieveErr)
_, err = tv.svc.Bootstrap(context.Background(), tc.externalKey, tc.externalID, false)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
streams := redisClient.XRead(context.Background(), &redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]any
if len(streams) > 0 && len(streams[0].Messages) > 0 {
event := streams[0].Messages
lastID = event[0].ID
}
test(t, tc.event, event, tc.desc)
repoCall.Unset()
}
}
func TestEnableConfig(t *testing.T) {
err := redisClient.FlushAll(context.Background()).Err()
assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
tv := newTestVariable(t, redisURL)
cases := []struct {
desc string
id string
userID string
domainID string
session smqauthn.Session
retrieveErr error
statusErr error
err error
event map[string]any
}{
{
desc: "enable config",
id: config.ID,
userID: validID,
domainID: domainID,
err: nil,
event: map[string]any{
"config_id": config.ID,
"timestamp": time.Now().Unix(),
"operation": configEnable,
},
},
{
desc: "enable with failed retrieve by ID",
id: "",
userID: validID,
domainID: domainID,
retrieveErr: svcerr.ErrNotFound,
err: svcerr.ErrNotFound,
event: nil,
},
{
desc: "enable with repo status error",
id: config.ID,
userID: validID,
domainID: domainID,
statusErr: svcerr.ErrUpdateEntity,
err: svcerr.ErrUpdateEntity,
event: nil,
},
}
disabledConfig := config
disabledConfig.Status = bootstrap.DisabledStatus
lastID := "0"
for _, tc := range cases {
tc.session = smqauthn.Session{UserID: validID, DomainID: tc.domainID, DomainUserID: validID}
repoCall := tv.boot.On("RetrieveByID", context.Background(), tc.domainID, tc.id).Return(disabledConfig, tc.retrieveErr)
repoCall1 := tv.boot.On("ChangeStatus", context.Background(), mock.Anything, mock.Anything, mock.Anything).Return(tc.statusErr)
_, err := tv.svc.EnableConfig(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))
streams := redisClient.XRead(context.Background(), &redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]any
if len(streams) > 0 && len(streams[0].Messages) > 0 {
event := streams[0].Messages
lastID = event[0].ID
}
test(t, tc.event, event, tc.desc)
repoCall.Unset()
repoCall1.Unset()
}
}
func TestDisableConfig(t *testing.T) {
err := redisClient.FlushAll(context.Background()).Err()
assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
tv := newTestVariable(t, redisURL)
cases := []struct {
desc string
id string
userID string
domainID string
session smqauthn.Session
retrieveErr error
statusErr error
err error
event map[string]any
}{
{
desc: "disable config",
id: config.ID,
userID: validID,
domainID: domainID,
err: nil,
event: map[string]any{
"config_id": config.ID,
"timestamp": time.Now().Unix(),
"operation": configDisable,
},
},
{
desc: "disable with failed retrieve by ID",
id: "",
userID: validID,
domainID: domainID,
retrieveErr: svcerr.ErrNotFound,
err: svcerr.ErrNotFound,
event: nil,
},
{
desc: "disable with repo status error",
id: config.ID,
userID: validID,
domainID: domainID,
statusErr: svcerr.ErrUpdateEntity,
err: svcerr.ErrUpdateEntity,
event: nil,
},
}
lastID := "0"
for _, tc := range cases {
tc.session = smqauthn.Session{UserID: validID, DomainID: tc.domainID, DomainUserID: validID}
repoCall := tv.boot.On("RetrieveByID", context.Background(), tc.domainID, tc.id).Return(config, tc.retrieveErr)
repoCall1 := tv.boot.On("ChangeStatus", context.Background(), mock.Anything, mock.Anything, mock.Anything).Return(tc.statusErr)
_, err := tv.svc.DisableConfig(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))
streams := redisClient.XRead(context.Background(), &redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]any
if len(streams) > 0 && len(streams[0].Messages) > 0 {
event := streams[0].Messages
lastID = event[0].ID
}
test(t, tc.event, event, tc.desc)
repoCall.Unset()
repoCall1.Unset()
}
}
func test(t *testing.T, expected, actual map[string]any, description string) {
if expected != nil && actual != nil {
ts1 := expected["timestamp"].(int64)
ats := actual["timestamp"].(string)
ts2, err := strconv.ParseInt(strings.Split(ats, "-")[0], 10, 64)
require.Nil(t, err, fmt.Sprintf("%s: expected to get a valid timestamp, got %s", description, err))
ts1 = ts1 / 1e9
ts2 = ts2 / 1e3
if assert.WithinDuration(t, time.Unix(ts1, 0), time.Unix(ts2, 0), time.Second, fmt.Sprintf("%s: timestamp is not in valid range of 1 second", description)) {
delete(expected, "timestamp")
delete(actual, "timestamp")
}
oa1 := expected["occurred_at"].(int64)
aoa := actual["occurred_at"].(string)
oa2, err := strconv.ParseInt(aoa, 10, 64)
require.Nil(t, err, fmt.Sprintf("%s: expected to get a valid occurred_at, got %s", description, err))
oa1 = oa1 / 1e9
oa2 = oa2 / 1e9
if assert.WithinDuration(t, time.Unix(oa1, 0), time.Unix(oa2, 0), time.Second, fmt.Sprintf("%s: occurred_at is not in valid range of 1 second", description)) {
delete(expected, "occurred_at")
delete(actual, "occurred_at")
}
assert.Equal(t, expected, actual, fmt.Sprintf("%s: got incorrect event\n", description))
}
}