From 93d10c8c57bff71d5d7ab82545ce21cc10ffe82d Mon Sep 17 00:00:00 2001 From: "C, Amarnath" Date: Wed, 4 Mar 2026 09:50:54 +0530 Subject: [PATCH 1/2] fix: resolve Memory Summary showing no data in Hardware Information (#816) Enhanced parseCIMResponse to handle typed slices using reflection. Added test for parseCIMResponse function achieving 100% coverage. Signed-off-by: C, Amarnath Signed-off-by: S, Devipriya --- internal/usecase/devices/info.go | 21 ++- internal/usecase/devices/info_private_test.go | 171 ++++++++++++++++++ 2 files changed, 189 insertions(+), 3 deletions(-) create mode 100644 internal/usecase/devices/info_private_test.go diff --git a/internal/usecase/devices/info.go b/internal/usecase/devices/info.go index c35155103..3b58cd43f 100644 --- a/internal/usecase/devices/info.go +++ b/internal/usecase/devices/info.go @@ -2,6 +2,7 @@ package devices import ( "context" + "reflect" "strconv" "github.com/device-management-toolkit/go-wsman-messages/v2/pkg/wsman/amt/setupandconfiguration" @@ -159,9 +160,23 @@ func (uc *UseCase) parseCIMResponse(hwInfo interface{}) dto.CIMResponse { result.Response = response } - responses, ok := info["responses"].([]interface{}) - if ok { - result.Responses = responses + // Handle both []interface{} and typed slices (e.g., []physical.PhysicalMemory) + if responsesRaw, exists := info["responses"]; exists && responsesRaw != nil { + // Try []interface{} first + if responses, ok := responsesRaw.([]interface{}); ok { + result.Responses = responses + } else { + // Handle typed slices by converting to []interface{} + responseValue := reflect.ValueOf(responsesRaw) + if responseValue.Kind() == reflect.Slice { + responses := make([]interface{}, responseValue.Len()) + for i := 0; i < responseValue.Len(); i++ { + responses[i] = responseValue.Index(i).Interface() + } + + result.Responses = responses + } + } } status, ok := info["status"].(int) diff --git a/internal/usecase/devices/info_private_test.go b/internal/usecase/devices/info_private_test.go new file mode 100644 index 000000000..50758c10c --- /dev/null +++ b/internal/usecase/devices/info_private_test.go @@ -0,0 +1,171 @@ +package devices + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/device-management-toolkit/console/internal/entity/dto/v1" +) + +type parseCIMTest struct { + name string + input interface{} + expected dto.CIMResponse +} + +func TestParseCIMResponse(t *testing.T) { + t.Parallel() + + // Create a minimal UseCase instance for testing + useCase := &UseCase{} + + tests := []parseCIMTest{ + { + name: "nil input", + input: nil, + expected: dto.CIMResponse{}, + }, + { + name: "non-map input", + input: "not a map", + expected: dto.CIMResponse{}, + }, + { + name: "empty map", + input: map[string]interface{}{}, + expected: dto.CIMResponse{}, + }, + { + name: "map with response field only", + input: map[string]interface{}{ + "response": "test response data", + }, + expected: dto.CIMResponse{ + Response: "test response data", + }, + }, + { + name: "map with status field as int", + input: map[string]interface{}{ + "status": 200, + }, + expected: dto.CIMResponse{ + Status: 200, + }, + }, + { + name: "map with status field as non-int", + input: map[string]interface{}{ + "status": "not an int", + }, + expected: dto.CIMResponse{}, // status should remain 0 + }, + { + name: "map with responses field as []interface{}", + input: map[string]interface{}{ + "responses": []interface{}{"response1", "response2", 42}, + }, + expected: dto.CIMResponse{ + Responses: []interface{}{"response1", "response2", 42}, + }, + }, + { + name: "map with responses field as typed slice - []string", + input: map[string]interface{}{ + "responses": []string{"response1", "response2"}, + }, + expected: dto.CIMResponse{ + Responses: []interface{}{"response1", "response2"}, + }, + }, + { + name: "map with responses field as typed slice - []int", + input: map[string]interface{}{ + "responses": []int{1, 2, 3}, + }, + expected: dto.CIMResponse{ + Responses: []interface{}{1, 2, 3}, + }, + }, + { + name: "map with responses field as empty typed slice", + input: map[string]interface{}{ + "responses": []string{}, + }, + expected: dto.CIMResponse{ + Responses: []interface{}{}, + }, + }, + { + name: "map with responses field as nil", + input: map[string]interface{}{ + "responses": nil, + }, + expected: dto.CIMResponse{}, // responses should remain nil + }, + { + name: "map with responses field as non-slice", + input: map[string]interface{}{ + "responses": "not a slice", + }, + expected: dto.CIMResponse{}, // responses should remain nil + }, + { + name: "map with responses field as non-slice number", + input: map[string]interface{}{ + "responses": 123, + }, + expected: dto.CIMResponse{}, // responses should remain nil + }, + { + name: "complete map with all valid fields", + input: map[string]interface{}{ + "response": "complete response", + "responses": []interface{}{"item1", "item2"}, + "status": 404, + }, + expected: dto.CIMResponse{ + Response: "complete response", + Responses: []interface{}{"item1", "item2"}, + Status: 404, + }, + }, + { + name: "complete map with typed slice responses", + input: map[string]interface{}{ + "response": 42, + "responses": []string{"typed1", "typed2"}, + "status": 201, + }, + expected: dto.CIMResponse{ + Response: 42, + Responses: []interface{}{"typed1", "typed2"}, + Status: 201, + }, + }, + { + name: "map with mixed valid and invalid fields", + input: map[string]interface{}{ + "response": "valid response", + "responses": "invalid responses", + "status": "invalid status", + }, + expected: dto.CIMResponse{ + Response: "valid response", + // Responses should remain nil due to invalid type + // Status should remain 0 due to invalid type + }, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + result := useCase.parseCIMResponse(tc.input) + require.Equal(t, tc.expected, result) + }) + } +} From 3c0a659263e8121e982fdb9b468f3c941636a8b2 Mon Sep 17 00:00:00 2001 From: "C, Amarnath" Date: Thu, 5 Mar 2026 15:28:32 +0530 Subject: [PATCH 2/2] fix: replace reflection with source conversion in memory summary Convert PhysicalMemory slice to interface slice at source. --- internal/usecase/devices/info.go | 21 +-- internal/usecase/devices/info_private_test.go | 171 ------------------ internal/usecase/devices/wsman/message.go | 17 +- .../usecase/devices/wsman/message_test.go | 84 +++++++++ 4 files changed, 103 insertions(+), 190 deletions(-) delete mode 100644 internal/usecase/devices/info_private_test.go create mode 100644 internal/usecase/devices/wsman/message_test.go diff --git a/internal/usecase/devices/info.go b/internal/usecase/devices/info.go index 3b58cd43f..c35155103 100644 --- a/internal/usecase/devices/info.go +++ b/internal/usecase/devices/info.go @@ -2,7 +2,6 @@ package devices import ( "context" - "reflect" "strconv" "github.com/device-management-toolkit/go-wsman-messages/v2/pkg/wsman/amt/setupandconfiguration" @@ -160,23 +159,9 @@ func (uc *UseCase) parseCIMResponse(hwInfo interface{}) dto.CIMResponse { result.Response = response } - // Handle both []interface{} and typed slices (e.g., []physical.PhysicalMemory) - if responsesRaw, exists := info["responses"]; exists && responsesRaw != nil { - // Try []interface{} first - if responses, ok := responsesRaw.([]interface{}); ok { - result.Responses = responses - } else { - // Handle typed slices by converting to []interface{} - responseValue := reflect.ValueOf(responsesRaw) - if responseValue.Kind() == reflect.Slice { - responses := make([]interface{}, responseValue.Len()) - for i := 0; i < responseValue.Len(); i++ { - responses[i] = responseValue.Index(i).Interface() - } - - result.Responses = responses - } - } + responses, ok := info["responses"].([]interface{}) + if ok { + result.Responses = responses } status, ok := info["status"].(int) diff --git a/internal/usecase/devices/info_private_test.go b/internal/usecase/devices/info_private_test.go deleted file mode 100644 index 50758c10c..000000000 --- a/internal/usecase/devices/info_private_test.go +++ /dev/null @@ -1,171 +0,0 @@ -package devices - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/device-management-toolkit/console/internal/entity/dto/v1" -) - -type parseCIMTest struct { - name string - input interface{} - expected dto.CIMResponse -} - -func TestParseCIMResponse(t *testing.T) { - t.Parallel() - - // Create a minimal UseCase instance for testing - useCase := &UseCase{} - - tests := []parseCIMTest{ - { - name: "nil input", - input: nil, - expected: dto.CIMResponse{}, - }, - { - name: "non-map input", - input: "not a map", - expected: dto.CIMResponse{}, - }, - { - name: "empty map", - input: map[string]interface{}{}, - expected: dto.CIMResponse{}, - }, - { - name: "map with response field only", - input: map[string]interface{}{ - "response": "test response data", - }, - expected: dto.CIMResponse{ - Response: "test response data", - }, - }, - { - name: "map with status field as int", - input: map[string]interface{}{ - "status": 200, - }, - expected: dto.CIMResponse{ - Status: 200, - }, - }, - { - name: "map with status field as non-int", - input: map[string]interface{}{ - "status": "not an int", - }, - expected: dto.CIMResponse{}, // status should remain 0 - }, - { - name: "map with responses field as []interface{}", - input: map[string]interface{}{ - "responses": []interface{}{"response1", "response2", 42}, - }, - expected: dto.CIMResponse{ - Responses: []interface{}{"response1", "response2", 42}, - }, - }, - { - name: "map with responses field as typed slice - []string", - input: map[string]interface{}{ - "responses": []string{"response1", "response2"}, - }, - expected: dto.CIMResponse{ - Responses: []interface{}{"response1", "response2"}, - }, - }, - { - name: "map with responses field as typed slice - []int", - input: map[string]interface{}{ - "responses": []int{1, 2, 3}, - }, - expected: dto.CIMResponse{ - Responses: []interface{}{1, 2, 3}, - }, - }, - { - name: "map with responses field as empty typed slice", - input: map[string]interface{}{ - "responses": []string{}, - }, - expected: dto.CIMResponse{ - Responses: []interface{}{}, - }, - }, - { - name: "map with responses field as nil", - input: map[string]interface{}{ - "responses": nil, - }, - expected: dto.CIMResponse{}, // responses should remain nil - }, - { - name: "map with responses field as non-slice", - input: map[string]interface{}{ - "responses": "not a slice", - }, - expected: dto.CIMResponse{}, // responses should remain nil - }, - { - name: "map with responses field as non-slice number", - input: map[string]interface{}{ - "responses": 123, - }, - expected: dto.CIMResponse{}, // responses should remain nil - }, - { - name: "complete map with all valid fields", - input: map[string]interface{}{ - "response": "complete response", - "responses": []interface{}{"item1", "item2"}, - "status": 404, - }, - expected: dto.CIMResponse{ - Response: "complete response", - Responses: []interface{}{"item1", "item2"}, - Status: 404, - }, - }, - { - name: "complete map with typed slice responses", - input: map[string]interface{}{ - "response": 42, - "responses": []string{"typed1", "typed2"}, - "status": 201, - }, - expected: dto.CIMResponse{ - Response: 42, - Responses: []interface{}{"typed1", "typed2"}, - Status: 201, - }, - }, - { - name: "map with mixed valid and invalid fields", - input: map[string]interface{}{ - "response": "valid response", - "responses": "invalid responses", - "status": "invalid status", - }, - expected: dto.CIMResponse{ - Response: "valid response", - // Responses should remain nil due to invalid type - // Status should remain 0 due to invalid type - }, - }, - } - - for _, tc := range tests { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - result := useCase.parseCIMResponse(tc.input) - require.Equal(t, tc.expected, result) - }) - } -} diff --git a/internal/usecase/devices/wsman/message.go b/internal/usecase/devices/wsman/message.go index 2a8fa761e..4dbb6ec19 100644 --- a/internal/usecase/devices/wsman/message.go +++ b/internal/usecase/devices/wsman/message.go @@ -531,11 +531,26 @@ func createMapInterfaceForHWInfo(hwResults HWResults) (interface{}, error) { }, "CIM_Processor": map[string]interface{}{ "responses": []interface{}{hwResults.ProcessorResult.Body.PackageResponse}, }, "CIM_PhysicalMemory": map[string]interface{}{ - "responses": hwResults.PhysicalMemoryResult.Body.PullResponse.MemoryItems, + "responses": interfaceSlice(hwResults.PhysicalMemoryResult.Body.PullResponse.MemoryItems), }, }, nil } +// interfaceSlice converts []physical.PhysicalMemory to []interface{} for consistent handling. +func interfaceSlice(items []physical.PhysicalMemory) []interface{} { + if items == nil { + return []interface{}{} + } + + result := make([]interface{}, len(items)) + + for i := range items { + result[i] = items[i] + } + + return result +} + func createMapInterfaceForDiskInfo(diskResults DiskResults) (interface{}, error) { return map[string]interface{}{ "CIM_MediaAccessDevice": map[string]interface{}{ diff --git a/internal/usecase/devices/wsman/message_test.go b/internal/usecase/devices/wsman/message_test.go new file mode 100644 index 000000000..97f7f8376 --- /dev/null +++ b/internal/usecase/devices/wsman/message_test.go @@ -0,0 +1,84 @@ +package wsman + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/device-management-toolkit/go-wsman-messages/v2/pkg/wsman/cim/physical" +) + +func TestInterfaceSlice(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input []physical.PhysicalMemory + expected []interface{} + }{ + { + name: "empty slice", + input: []physical.PhysicalMemory{}, + expected: []interface{}{}, + }, + { + name: "single item", + input: []physical.PhysicalMemory{ + { + Capacity: 17179869184, // 16 GB + Manufacturer: "Kingston", + PartNumber: "9905700-101.A00G", + }, + }, + expected: []interface{}{ + physical.PhysicalMemory{ + Capacity: 17179869184, + Manufacturer: "Kingston", + PartNumber: "9905700-101.A00G", + }, + }, + }, + { + name: "multiple items", + input: []physical.PhysicalMemory{ + { + Capacity: 8589934592, // 8 GB + Manufacturer: "Samsung", + PartNumber: "M471A1K43CB1-CRC", + }, + { + Capacity: 8589934592, // 8 GB + Manufacturer: "Samsung", + PartNumber: "M471A1K43CB1-CRC", + }, + }, + expected: []interface{}{ + physical.PhysicalMemory{ + Capacity: 8589934592, + Manufacturer: "Samsung", + PartNumber: "M471A1K43CB1-CRC", + }, + physical.PhysicalMemory{ + Capacity: 8589934592, + Manufacturer: "Samsung", + PartNumber: "M471A1K43CB1-CRC", + }, + }, + }, + { + name: "nil slice", + input: nil, + expected: []interface{}{}, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + result := interfaceSlice(tc.input) + require.Equal(t, tc.expected, result) + }) + } +}