Files
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

450 lines
12 KiB
Go

// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package provision_test
import (
"context"
"fmt"
"testing"
"github.com/absmach/magistrala/internal/testsutil"
mglog "github.com/absmach/magistrala/logger"
"github.com/absmach/magistrala/pkg/errors"
repoerr "github.com/absmach/magistrala/pkg/errors/repository"
svcerr "github.com/absmach/magistrala/pkg/errors/service"
smqSDK "github.com/absmach/magistrala/pkg/sdk"
sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks"
"github.com/absmach/magistrala/provision"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
var validToken = "valid"
func TestMapping(t *testing.T) {
mgsdk := new(sdkmocks.SDK)
svc := provision.New(validConfig, mgsdk, mglog.NewMock())
cases := []struct {
desc string
content map[string]any
sdkerr error
err error
}{
{
desc: "valid request",
content: validConfig.Bootstrap.Content,
sdkerr: nil,
err: nil,
},
}
for _, c := range cases {
t.Run(c.desc, func(t *testing.T) {
content := svc.Mapping()
assert.Equal(t, c.content, content)
})
}
}
func TestCert(t *testing.T) {
cases := []struct {
desc string
config provision.Config
domainID string
token string
returnedToken string
clientID string
ttl string
serial string
cert string
key string
sdkClientErr error
sdkCertErr error
sdkTokenErr error
err error
}{
{
desc: "valid",
config: validConfig,
domainID: testsutil.GenerateUUID(t),
token: validToken,
clientID: testsutil.GenerateUUID(t),
ttl: "1h",
cert: "cert",
key: "key",
sdkClientErr: nil,
sdkCertErr: nil,
sdkTokenErr: nil,
err: nil,
},
{
desc: "empty token with config API key",
config: provision.Config{
Server: provision.ServiceConf{MgAPIKey: "key"},
Cert: provision.Cert{TTL: "1h"},
},
domainID: testsutil.GenerateUUID(t),
token: "",
returnedToken: "key",
clientID: testsutil.GenerateUUID(t),
ttl: "1h",
cert: "cert",
key: "key",
sdkClientErr: nil,
sdkCertErr: nil,
sdkTokenErr: nil,
err: nil,
},
{
desc: "empty token with username and password",
config: provision.Config{
Server: provision.ServiceConf{
MgUsername: "testUsername",
MgPass: "12345678",
MgDomainID: testsutil.GenerateUUID(t),
},
Cert: provision.Cert{TTL: "1h"},
},
domainID: testsutil.GenerateUUID(t),
token: "",
returnedToken: validToken,
clientID: testsutil.GenerateUUID(t),
ttl: "1h",
cert: "cert",
key: "key",
sdkClientErr: nil,
sdkCertErr: nil,
sdkTokenErr: nil,
err: nil,
},
{
desc: "empty token with username and invalid password",
config: provision.Config{
Server: provision.ServiceConf{
MgUsername: "testUsername",
MgPass: "12345678",
MgDomainID: testsutil.GenerateUUID(t),
},
Cert: provision.Cert{TTL: "1h"},
},
domainID: testsutil.GenerateUUID(t),
token: "",
clientID: testsutil.GenerateUUID(t),
ttl: "1h",
cert: "",
key: "",
sdkClientErr: nil,
sdkCertErr: nil,
sdkTokenErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, 401),
err: provision.ErrFailedToCreateToken,
},
{
desc: "empty token with empty username and password",
config: provision.Config{
Server: provision.ServiceConf{},
Cert: provision.Cert{TTL: "1h"},
},
domainID: testsutil.GenerateUUID(t),
token: "",
clientID: testsutil.GenerateUUID(t),
ttl: "1h",
cert: "",
key: "",
sdkClientErr: nil,
sdkCertErr: nil,
sdkTokenErr: nil,
err: provision.ErrMissingCredentials,
},
{
desc: "invalid clientID",
config: validConfig,
domainID: testsutil.GenerateUUID(t),
token: "invalid",
clientID: testsutil.GenerateUUID(t),
ttl: "1h",
cert: "",
key: "",
sdkClientErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthentication, 401),
sdkCertErr: nil,
sdkTokenErr: nil,
err: provision.ErrUnauthorized,
},
{
desc: "invalid clientID",
config: validConfig,
domainID: testsutil.GenerateUUID(t),
token: validToken,
clientID: "invalid",
ttl: "1h",
cert: "",
key: "",
sdkClientErr: errors.NewSDKErrorWithStatus(repoerr.ErrNotFound, 404),
sdkCertErr: nil,
sdkTokenErr: nil,
err: provision.ErrUnauthorized,
},
{
desc: "failed to issue cert",
config: validConfig,
domainID: testsutil.GenerateUUID(t),
token: validToken,
clientID: testsutil.GenerateUUID(t),
ttl: "1h",
cert: "",
key: "",
sdkClientErr: nil,
sdkTokenErr: nil,
sdkCertErr: errors.NewSDKError(repoerr.ErrCreateEntity),
err: repoerr.ErrCreateEntity,
},
}
mgsdk := new(sdkmocks.SDK)
for _, c := range cases {
t.Run(c.desc, func(t *testing.T) {
svc := provision.New(c.config, mgsdk, mglog.NewMock())
call1 := mgsdk.On("Client", mock.Anything, c.clientID, c.domainID, mock.Anything).Return(smqSDK.Client{ID: c.clientID}, c.sdkClientErr)
var call2 *mock.Call
switch c.token {
case "":
call2 = mgsdk.On("IssueCert", context.Background(), c.clientID, c.config.Cert.TTL, []string{}, smqSDK.Options{}, c.domainID, c.returnedToken).Return(smqSDK.Certificate{SerialNumber: c.serial}, c.sdkCertErr)
default:
call2 = mgsdk.On("IssueCert", context.Background(), c.clientID, c.config.Cert.TTL, []string{}, smqSDK.Options{}, c.domainID, c.token).Return(smqSDK.Certificate{SerialNumber: c.serial}, c.sdkCertErr)
}
call3 := mgsdk.On("ViewCert", mock.Anything, c.serial, mock.Anything, mock.Anything).Return(smqSDK.Certificate{Certificate: c.cert, Key: c.key}, c.sdkCertErr)
login := smqSDK.Login{
Username: c.config.Server.MgUsername,
Password: c.config.Server.MgPass,
}
call4 := mgsdk.On("CreateToken", mock.Anything, login).Return(smqSDK.Token{AccessToken: validToken}, c.sdkTokenErr)
cert, key, err := svc.Cert(context.Background(), c.domainID, c.token, c.clientID, c.ttl)
assert.Equal(t, c.cert, cert)
assert.Equal(t, c.key, key)
assert.True(t, errors.Contains(err, c.err), fmt.Sprintf("expected error %v, got %v", c.err, err))
call1.Unset()
call2.Unset()
call3.Unset()
call4.Unset()
})
}
}
func TestProvisionUsesBootstrapEnrollmentID(t *testing.T) {
cfg := validConfig
cfg.Bootstrap = provision.Bootstrap{
X509Provision: true,
Provision: true,
AutoWhiteList: true,
ProfileID: "gateway-profile",
RenderContext: map[string]any{
"site": "warehouse-1",
},
Bindings: []provision.BootstrapBinding{
{
Slot: "mqtt_client",
Type: "client",
},
{
Slot: "control",
Type: "channel",
MetadataKey: "type",
MetadataValue: "control",
},
},
Content: map[string]any{
"broker": "mqtt://localhost:1883",
},
}
cfg.Clients[0].Metadata = map[string]any{
"external_id": "placeholder",
}
cfg.Channels[0].Name = "control-channel"
cfg.Channels[0].Metadata = map[string]any{
"type": "control",
}
cfg.Cert.TTL = "1h"
const (
name = "gateway-1"
externalID = "AA:BB:CC:DD"
externalKey = "secret"
certPEM = "cert-pem"
keyPEM = "key-pem"
serial = "serial-1"
)
clientID := testsutil.GenerateUUID(t)
channelID := testsutil.GenerateUUID(t)
bootstrapID := testsutil.GenerateUUID(t)
domainID := testsutil.GenerateUUID(t)
clientMetadata := map[string]any{
"external_id": externalID,
}
var updatedClient smqSDK.Client
mgsdk := new(sdkmocks.SDK)
svc := provision.New(cfg, mgsdk, mglog.NewMock())
createClientCall := mgsdk.On(
"CreateClient",
mock.Anything,
mock.Anything,
domainID,
validToken,
).Return(smqSDK.Client{ID: clientID}, nil)
clientCall := mgsdk.On(
"Client",
mock.Anything,
clientID,
domainID,
validToken,
).Return(smqSDK.Client{ID: clientID, Name: name, Metadata: clientMetadata}, nil).Twice()
createChannelCall := mgsdk.On(
"CreateChannel",
mock.Anything,
mock.Anything,
domainID,
validToken,
).Return(smqSDK.Channel{ID: channelID}, nil)
channelCall := mgsdk.On(
"Channel",
mock.Anything,
channelID,
domainID,
validToken,
).Return(smqSDK.Channel{ID: channelID, Metadata: smqSDK.Metadata{"type": "control"}}, nil)
addBootstrapCall := mgsdk.On(
"AddBootstrap",
mock.Anything,
mock.MatchedBy(func(cfg smqSDK.BootstrapConfig) bool {
return cfg.ProfileID == "gateway-profile" &&
cfg.RenderContext["site"] == "warehouse-1" &&
cfg.RenderContext["external_id"] == externalID &&
cfg.RenderContext["name"] == name
}),
domainID,
validToken,
).Return(bootstrapID, nil)
viewBootstrapCall := mgsdk.On(
"ViewBootstrap",
mock.Anything,
bootstrapID,
domainID,
validToken,
).Return(smqSDK.BootstrapConfig{
ID: bootstrapID,
ExternalID: externalID,
ExternalKey: externalKey,
}, nil)
bindBootstrapResourcesCall := mgsdk.On(
"BindBootstrapResources",
mock.Anything,
bootstrapID,
[]smqSDK.BootstrapBindingRequest{
{
Slot: "mqtt_client",
Type: "client",
ResourceID: clientID,
},
{
Slot: "control",
Type: "channel",
ResourceID: channelID,
},
},
domainID,
validToken,
).Return(nil)
issueCertCall := mgsdk.On(
"IssueCert",
mock.Anything,
clientID,
cfg.Cert.TTL,
mock.Anything,
mock.Anything,
domainID,
validToken,
).Return(smqSDK.Certificate{SerialNumber: serial}, nil)
viewCertCall := mgsdk.On(
"ViewCert",
mock.Anything,
serial,
domainID,
validToken,
).Return(smqSDK.Certificate{Certificate: certPEM, Key: keyPEM}, nil)
updateBootstrapCertsCall := mgsdk.On(
"UpdateBootstrapCerts",
mock.Anything,
bootstrapID,
certPEM,
keyPEM,
"",
domainID,
validToken,
).Return(smqSDK.BootstrapConfig{
ID: bootstrapID,
ExternalID: externalID,
ExternalKey: externalKey,
ClientCert: certPEM,
ClientKey: keyPEM,
}, nil)
whitelistCall := mgsdk.On(
"Whitelist",
mock.Anything,
bootstrapID,
smqSDK.BootstrapEnabledStatus,
domainID,
validToken,
).Return(nil)
updateClientCall := mgsdk.On(
"UpdateClient",
mock.Anything,
mock.Anything,
domainID,
validToken,
).Run(func(args mock.Arguments) {
updatedClient = args.Get(1).(smqSDK.Client)
}).Return(smqSDK.Client{ID: clientID}, nil)
res, err := svc.Provision(context.Background(), domainID, validToken, name, externalID, externalKey)
assert.NoError(t, err)
assert.Len(t, res.Clients, 1)
assert.Len(t, res.Channels, 1)
assert.True(t, res.Whitelisted[bootstrapID])
assert.Equal(t, certPEM, res.ClientCert[clientID])
assert.Equal(t, keyPEM, res.ClientKey[clientID])
assert.Equal(t, clientID, updatedClient.ID)
assert.Equal(t, bootstrapID, updatedClient.Metadata["cfg_id"])
assert.Equal(t, externalID, updatedClient.Metadata["external_id"])
assert.Equal(t, channelID, updatedClient.Metadata["ctrl_channel_id"])
assert.Equal(t, "gateway", updatedClient.Metadata["type"])
createClientCall.Unset()
clientCall.Unset()
createChannelCall.Unset()
channelCall.Unset()
_ = addBootstrapCall
viewBootstrapCall.Unset()
bindBootstrapResourcesCall.Unset()
issueCertCall.Unset()
viewCertCall.Unset()
updateBootstrapCertsCall.Unset()
whitelistCall.Unset()
updateClientCall.Unset()
}