diff --git a/internal/app/migrations/20260424000000_add_lms_available.down.sql b/internal/app/migrations/20260424000000_add_lms_available.down.sql new file mode 100644 index 000000000..c9f8139d3 --- /dev/null +++ b/internal/app/migrations/20260424000000_add_lms_available.down.sql @@ -0,0 +1,6 @@ +/********************************************************************* +* Copyright (c) Intel Corporation 2026 +* SPDX-License-Identifier: Apache-2.0 +**********************************************************************/ + +ALTER TABLE devices DROP COLUMN islmsavailable; diff --git a/internal/app/migrations/20260424000000_add_lms_available.up.sql b/internal/app/migrations/20260424000000_add_lms_available.up.sql new file mode 100644 index 000000000..d3120e725 --- /dev/null +++ b/internal/app/migrations/20260424000000_add_lms_available.up.sql @@ -0,0 +1,6 @@ +/********************************************************************* +* Copyright (c) Intel Corporation 2026 +* SPDX-License-Identifier: Apache-2.0 +**********************************************************************/ + +ALTER TABLE devices ADD COLUMN islmsavailable BOOLEAN NOT NULL DEFAULT FALSE; diff --git a/internal/entity/device.go b/internal/entity/device.go index 31a71b97d..39d866d3a 100644 --- a/internal/entity/device.go +++ b/internal/entity/device.go @@ -23,6 +23,7 @@ type Device struct { UseTLS bool AllowSelfSigned bool CertHash *string + IsLMSAvailable bool } type Explorer struct { diff --git a/internal/entity/dto/v1/device.go b/internal/entity/dto/v1/device.go index fccfd0676..73d66a807 100644 --- a/internal/entity/dto/v1/device.go +++ b/internal/entity/dto/v1/device.go @@ -34,6 +34,7 @@ type Device struct { UseTLS bool `json:"useTLS"` AllowSelfSigned bool `json:"allowSelfSigned"` CertHash string `json:"certHash"` + IsLMSAvailable bool `json:"isLMSAvailable"` } type DeviceInfo struct { diff --git a/internal/usecase/devices/transform_fuzz_test.go b/internal/usecase/devices/transform_fuzz_test.go index bca9ff933..7ccf4e224 100644 --- a/internal/usecase/devices/transform_fuzz_test.go +++ b/internal/usecase/devices/transform_fuzz_test.go @@ -71,6 +71,7 @@ func FuzzDeviceTransforms(f *testing.F) { UseTLS: useTLS, AllowSelfSigned: allowSelfSigned, CertHash: certHash, + IsLMSAvailable: connectionStatus, LastConnected: buildTimePtr(), LastSeen: buildTimePtr(), LastDisconnected: buildTimePtr(), @@ -90,6 +91,7 @@ func FuzzDeviceTransforms(f *testing.F) { UseTLS: useTLS, AllowSelfSigned: allowSelfSigned, CertHash: stringPtrOrNilDevice(certHash), + IsLMSAvailable: connectionStatus, LastConnected: buildTimePtr(), LastSeen: buildTimePtr(), LastDisconnected: buildTimePtr(), diff --git a/internal/usecase/devices/usecase.go b/internal/usecase/devices/usecase.go index 53fea6d11..40f441eb0 100644 --- a/internal/usecase/devices/usecase.go +++ b/internal/usecase/devices/usecase.go @@ -92,6 +92,7 @@ func (uc *UseCase) dtoToEntity(d *dto.Device) (*entity.Device, error) { Password: d.Password, UseTLS: d.UseTLS, AllowSelfSigned: d.AllowSelfSigned, + IsLMSAvailable: d.IsLMSAvailable, } var err error @@ -158,6 +159,7 @@ func (uc *UseCase) entityToDTO(d *entity.Device) *dto.Device { // Password: d.Password, UseTLS: d.UseTLS, AllowSelfSigned: d.AllowSelfSigned, + IsLMSAvailable: d.IsLMSAvailable, } if d.CertHash != nil { diff --git a/internal/usecase/sqldb/device.go b/internal/usecase/sqldb/device.go index a3bf3f18a..10d5245a5 100644 --- a/internal/usecase/sqldb/device.go +++ b/internal/usecase/sqldb/device.go @@ -87,7 +87,8 @@ func (r *DeviceRepo) Get(_ context.Context, top, skip int, tenantID string) ([]e "password", "usetls", "allowselfsigned", - "certhash"). + "certhash", + "islmsavailable"). From("devices"). Where("tenantid = ?", tenantID). OrderBy("guid"). @@ -114,7 +115,7 @@ func (r *DeviceRepo) Get(_ context.Context, top, skip int, tenantID string) ([]e for rows.Next() { d := entity.Device{} - err = rows.Scan(&d.GUID, &d.Hostname, &d.Tags, &d.MPSInstance, &d.ConnectionStatus, &d.MPSUsername, &d.TenantID, &d.FriendlyName, &d.DNSSuffix, &d.DeviceInfo, &d.Username, &d.Password, &d.UseTLS, &d.AllowSelfSigned, &d.CertHash) + err = rows.Scan(&d.GUID, &d.Hostname, &d.Tags, &d.MPSInstance, &d.ConnectionStatus, &d.MPSUsername, &d.TenantID, &d.FriendlyName, &d.DNSSuffix, &d.DeviceInfo, &d.Username, &d.Password, &d.UseTLS, &d.AllowSelfSigned, &d.CertHash, &d.IsLMSAvailable) if err != nil { return nil, ErrDeviceDatabase.Wrap("Get", "rows.Scan: ", err) } @@ -145,7 +146,8 @@ func (r *DeviceRepo) GetByID(_ context.Context, guid, tenantID string) (*entity. "mebxpassword", "usetls", "allowselfsigned", - "certhash"). + "certhash", + "islmsavailable"). From("devices"). Where("guid = ? and tenantid = ?"). ToSql() @@ -169,7 +171,7 @@ func (r *DeviceRepo) GetByID(_ context.Context, guid, tenantID string) (*entity. for rows.Next() { d := &entity.Device{} - err = rows.Scan(&d.GUID, &d.Hostname, &d.Tags, &d.MPSInstance, &d.ConnectionStatus, &d.MPSUsername, &d.TenantID, &d.FriendlyName, &d.DNSSuffix, &d.DeviceInfo, &d.Username, &d.Password, &d.MPSPassword, &d.MEBXPassword, &d.UseTLS, &d.AllowSelfSigned, &d.CertHash) + err = rows.Scan(&d.GUID, &d.Hostname, &d.Tags, &d.MPSInstance, &d.ConnectionStatus, &d.MPSUsername, &d.TenantID, &d.FriendlyName, &d.DNSSuffix, &d.DeviceInfo, &d.Username, &d.Password, &d.MPSPassword, &d.MEBXPassword, &d.UseTLS, &d.AllowSelfSigned, &d.CertHash, &d.IsLMSAvailable) if err != nil { return d, ErrDeviceDatabase.Wrap("Get", "rows.Scan: ", err) } @@ -346,6 +348,7 @@ func (r *DeviceRepo) Update(_ context.Context, d *entity.Device) (bool, error) { Set("useTLS", d.UseTLS). Set("allowSelfSigned", d.AllowSelfSigned). Set("certhash", d.CertHash). + Set("islmsavailable", d.IsLMSAvailable). Where("guid = ? AND tenantid = ?", d.GUID, d.TenantID). ToSql() if err != nil { @@ -418,8 +421,8 @@ func (r *DeviceRepo) UpdateLastSeen(_ context.Context, guid string) error { func (r *DeviceRepo) Insert(_ context.Context, d *entity.Device) (string, error) { insertBuilder := r.Builder. Insert("devices"). - Columns("guid", "hostname", "tags", "mpsinstance", "connectionstatus", "mpsusername", "tenantid", "friendlyname", "dnssuffix", "deviceinfo", "username", "password", "mpspassword", "mebxpassword", "usetls", "allowselfsigned", "certhash"). - Values(d.GUID, d.Hostname, d.Tags, d.MPSInstance, d.ConnectionStatus, d.MPSUsername, d.TenantID, d.FriendlyName, d.DNSSuffix, d.DeviceInfo, d.Username, d.Password, d.MPSPassword, d.MEBXPassword, d.UseTLS, d.AllowSelfSigned, d.CertHash) + Columns("guid", "hostname", "tags", "mpsinstance", "connectionstatus", "mpsusername", "tenantid", "friendlyname", "dnssuffix", "deviceinfo", "username", "password", "mpspassword", "mebxpassword", "usetls", "allowselfsigned", "certhash", "islmsavailable"). + Values(d.GUID, d.Hostname, d.Tags, d.MPSInstance, d.ConnectionStatus, d.MPSUsername, d.TenantID, d.FriendlyName, d.DNSSuffix, d.DeviceInfo, d.Username, d.Password, d.MPSPassword, d.MEBXPassword, d.UseTLS, d.AllowSelfSigned, d.CertHash, d.IsLMSAvailable) if !r.IsEmbedded { insertBuilder = insertBuilder.Suffix("RETURNING xmin::text") @@ -466,7 +469,8 @@ func (r *DeviceRepo) GetByColumn(_ context.Context, columnName, queryValue, tena "password", "usetls", "allowselfsigned", - "certhash"). + "certhash", + "islmsavailable"). From("devices"). Where(columnName+" = ? AND tenantid = ?", queryValue, tenantID). ToSql() @@ -490,7 +494,7 @@ func (r *DeviceRepo) GetByColumn(_ context.Context, columnName, queryValue, tena for rows.Next() { d := entity.Device{} - err = rows.Scan(&d.GUID, &d.Hostname, &d.Tags, &d.MPSInstance, &d.ConnectionStatus, &d.MPSUsername, &d.TenantID, &d.FriendlyName, &d.DNSSuffix, &d.DeviceInfo, &d.Username, &d.Password, &d.UseTLS, &d.AllowSelfSigned, &d.CertHash) + err = rows.Scan(&d.GUID, &d.Hostname, &d.Tags, &d.MPSInstance, &d.ConnectionStatus, &d.MPSUsername, &d.TenantID, &d.FriendlyName, &d.DNSSuffix, &d.DeviceInfo, &d.Username, &d.Password, &d.UseTLS, &d.AllowSelfSigned, &d.CertHash, &d.IsLMSAvailable) if err != nil { return nil, ErrDeviceDatabase.Wrap("Get", "rows.Scan: ", err) } diff --git a/internal/usecase/sqldb/device_test.go b/internal/usecase/sqldb/device_test.go index 1f31250ee..7645ed9af 100644 --- a/internal/usecase/sqldb/device_test.go +++ b/internal/usecase/sqldb/device_test.go @@ -50,7 +50,8 @@ func setupDeviceTable(t *testing.T) *sql.DB { certhash TEXT NOT NULL DEFAULT '', lastconnected TEXT, lastdisconnected TEXT, - lastseen TEXT + lastseen TEXT, + islmsavailable BOOLEAN NOT NULL DEFAULT FALSE ); `) require.NoError(t, err) @@ -662,7 +663,8 @@ func TestDeviceRepo_Delete(t *testing.T) { mpspassword TEXT, mebxpassword TEXT, usetls BOOLEAN NOT NULL DEFAULT FALSE, - allowselfsigned BOOLEAN NOT NULL DEFAULT FALSE + allowselfsigned BOOLEAN NOT NULL DEFAULT FALSE, + islmsavailable BOOLEAN NOT NULL DEFAULT FALSE ); `) require.NoError(t, err) @@ -813,7 +815,8 @@ func TestDeviceRepo_Update(t *testing.T) { mebxpassword TEXT, usetls BOOLEAN NOT NULL DEFAULT FALSE, allowselfsigned BOOLEAN NOT NULL DEFAULT FALSE, - certhash TEXT NOT NULL DEFAULT '' + certhash TEXT NOT NULL DEFAULT '', + islmsavailable BOOLEAN NOT NULL DEFAULT FALSE ); `) require.NoError(t, err) @@ -1068,7 +1071,8 @@ func TestDeviceRepo_GetByColumn(t *testing.T) { password TEXT NOT NULL DEFAULT '', usetls BOOLEAN NOT NULL DEFAULT FALSE, allowselfsigned BOOLEAN NOT NULL DEFAULT FALSE, - certhash TEXT NOT NULL DEFAULT '' + certhash TEXT NOT NULL DEFAULT '', + islmsavailable BOOLEAN NOT NULL DEFAULT FALSE ); `) require.NoError(t, err) @@ -1290,3 +1294,50 @@ func TestDeviceRepo_UpdateLastSeen(t *testing.T) { }) } } + +func TestDeviceRepo_IsLMSAvailable_RoundTrip(t *testing.T) { + t.Parallel() + + dbConn := setupDeviceTable(t) + defer dbConn.Close() + + sqlConfig := &db.SQL{ + Builder: squirrel.StatementBuilder.PlaceholderFormat(squirrel.Question), + Pool: dbConn, + IsEmbedded: true, + } + + mockLog := mocks.NewMockLogger(nil) + repo := sqldb.NewDeviceRepo(sqlConfig, mockLog) + + // Insert a device with IsLMSAvailable = true + dev := &entity.Device{ + GUID: "guid-lms-true", + Hostname: "host-lms", + TenantID: "tenant1", + Username: "admin", + Password: "pass", + CertHash: Certhash, + IsLMSAvailable: true, + } + + _, err := repo.Insert(context.Background(), dev) + require.NoError(t, err) + + // Retrieve and verify the field round-trips + got, err := repo.GetByID(context.Background(), "guid-lms-true", "tenant1") + require.NoError(t, err) + require.NotNil(t, got) + assert.True(t, got.IsLMSAvailable, "IsLMSAvailable should be true after insert") + + // Update the device with IsLMSAvailable = false + dev.IsLMSAvailable = false + + _, err = repo.Update(context.Background(), dev) + require.NoError(t, err) + + got, err = repo.GetByID(context.Background(), "guid-lms-true", "tenant1") + require.NoError(t, err) + require.NotNil(t, got) + assert.False(t, got.IsLMSAvailable, "IsLMSAvailable should be false after update") +}