Files
cocos/pkg/progressbar/progress_test.go
T
Sammy Kerata Oina 4e8057f481
CI / ci (push) Has been cancelled
COCOS-460 - Restore test coverage to 65% (#465)
* Implement IMAMeasurements method in agentSDK and add corresponding unit tests

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Add unit tests for NewIMAMeasurements command in CLI

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Add error assertion for command execution in NewIMAMeasurements test

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Fix nil pointer dereference in Close method and update NewCreateVMCmd logic for manager client initialization

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Refactor file permission settings to use octal notation and improve cleanup handling in NewCreateVMCmd test

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Add comprehensive unit tests for state machine functionality

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Add mock implementation for Algorithm interface and corresponding test cases

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Refactor file permission settings to use octal notation in TestStopComputationIntegration

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Remove redundant reset test cases from TestStateMachine_Reset

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Fix race condition in action call verification in TestStateMachine_HandleEvent

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Enhance state machine with reset functionality and improve thread safety in event handling

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Improve error handling in state machine start function during tests

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Remove concurrent reset and send event test from state machine tests

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Remove error logging for Start function in transition tests

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Add mock implementations for AgentService_IMAMeasurementsClient and Service Shutdown method; enhance progress tests for IMA measurements handling

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Add comprehensive tests for FileStorage functionality including loading, saving, and concurrent access

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Enhance tests by adding dataset and algorithm hashes in handleRunReqChunks; improve error handling in TestFileStorage_ErrorHandling cleanup

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Enhance TestManagerClient_Process by adding new test cases for Agent state and Disconnect requests; update setupMocks to include grpcClient

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Fix graceful shutdown in gRPC server by adding nil checks for health and server instances

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Enhance TestAttestation by adding mock expectations for VTpmAttestation and Attestation methods; update service call to include platform parameter

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Enhance gRPC Server by adding synchronization for start/stop methods; prevent multiple starts and ensure graceful shutdown

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Add unit tests for gRPC server methods including VM creation, removal, and info retrieval

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Add tests for SEVSNP and TDX host capabilities; remove unused vsock code

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Add a newline for better readability in vm_test.go

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Add integration tests for gRPC client in cvm_test.go

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Remove unused vsock dependencies and add comprehensive unit tests for GCP attestation functions

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Skip GCP tests if credentials are not set

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Add tests for error handling in attestation configuration and GCP commands

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Improve error handling in Azure VM test response writing

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Skip tests in GCP functions if credentials are not set

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Add comprehensive unit tests for Azure attestation provider and verifier

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Add unit tests for TPM functionality and improve error handling

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Add comprehensive tests for attestation functionality and improve error handling

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Add validation for teeNonce in TeeAttestation and implement comprehensive tests for provider methods

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Refactor error messages in TDX attestation tests for clarity

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Fix error message in TeeAttestation test for valid nonce case

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Add MeasurementProvider mock and update mockery configuration

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Add logging for product in parseUints and rename test functions for clarity

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

* Refactor TestSevsnpverify to reset configuration and improve error logging

Signed-off-by: Sammy Oina <sammyoina@gmail.com>

---------

Signed-off-by: Sammy Oina <sammyoina@gmail.com>
2025-07-25 15:35:37 +02:00

637 lines
16 KiB
Go

// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package progressbar
import (
"bytes"
"fmt"
"io"
"os"
"strings"
"testing"
"github.com/absmach/magistrala/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/agent/mocks"
)
func TestNew(t *testing.T) {
pb := New(false)
assert.NotNil(t, pb)
assert.NotNil(t, pb.TerminalWidthFunc)
}
func TestSendAlgorithm(t *testing.T) {
testCases := []struct {
name string
sendError error
closeRecvError error
err error
}{
{
name: "successful send and close",
sendError: nil,
closeRecvError: nil,
err: nil,
},
{
name: "send failure",
sendError: fmt.Errorf("network error during send"),
closeRecvError: nil,
err: fmt.Errorf("network error during send"),
},
{
name: "close and receive failure",
sendError: nil,
closeRecvError: fmt.Errorf("connection terminated"),
err: fmt.Errorf("connection terminated"),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
pb := New(false)
algo, err := os.CreateTemp("", "test_algo")
assert.NoError(t, err)
req, err := os.CreateTemp("", "test_req")
assert.NoError(t, err)
_, err = algo.WriteString("test algorithm")
assert.NoError(t, err)
err = algo.Close()
assert.NoError(t, err)
algo, err = os.Open(algo.Name())
assert.NoError(t, err)
_, err = req.WriteString("test request")
assert.NoError(t, err)
err = req.Close()
assert.NoError(t, err)
req, err = os.Open(req.Name())
assert.NoError(t, err)
algoStream := new(mocks.AgentService_AlgoClient[agent.AlgoRequest, agent.AlgoResponse])
algoStream.On("Send", mock.Anything).Return(tc.sendError)
algoStream.On("CloseAndRecv").Return(&agent.AlgoResponse{}, tc.closeRecvError)
mockStream := &mockAlgoStream{stream: algoStream}
err = pb.SendAlgorithm("Test Algorithm", algo, req, mockStream.stream)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error: %v, got: %v", tc.err, err))
})
}
}
func TestSendData(t *testing.T) {
testCases := []struct {
name string
dataContent string
sendError error
closeRecvError error
err error
}{
{
name: "successful data send",
dataContent: "test data content",
sendError: nil,
closeRecvError: nil,
err: nil,
},
{
name: "send operation failure",
dataContent: "test data content",
sendError: fmt.Errorf("failed to send chunk"),
closeRecvError: nil,
err: fmt.Errorf("failed to send chunk"),
},
{
name: "close and receive failure",
dataContent: "test data content",
sendError: nil,
closeRecvError: fmt.Errorf("stream closed unexpectedly"),
err: fmt.Errorf("stream closed unexpectedly"),
},
{
name: "empty data content",
dataContent: "",
sendError: nil,
closeRecvError: nil,
err: nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
pb := New(false)
dataset, err := os.CreateTemp("", "test_dataset")
assert.NoError(t, err)
_, err = dataset.WriteString(tc.dataContent)
assert.NoError(t, err)
err = dataset.Close()
assert.NoError(t, err)
dataset, err = os.Open(dataset.Name())
assert.NoError(t, err)
dataStream := new(mocks.AgentService_DataClient[agent.DataRequest, agent.DataResponse])
dataStream.On("Send", mock.Anything).Return(tc.sendError)
dataStream.On("CloseAndRecv").Return(&agent.DataResponse{}, tc.closeRecvError)
mockStream := &mockDataStream{stream: dataStream}
err = pb.SendData("Test Data", "test.txt", dataset, mockStream.stream)
assert.True(t, errors.Contains(err, tc.err))
})
}
}
func TestRenderProgressBarWithDifferentDescriptions(t *testing.T) {
testCases := []struct {
description string
expectedEmoji string
}{
{"Uploading algorithm", "🚀"},
{"Uploading data", "📦"},
{"Processing", "🚀"},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
pb := &ProgressBar{
numberOfBytes: 100,
currentUploadedBytes: 50,
description: tc.description,
TerminalWidthFunc: func() (int, error) {
return 100, nil
},
}
var buf bytes.Buffer
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
err := pb.renderProgressBar()
assert.NoError(t, err)
w.Close()
os.Stdout = oldStdout
_, err = io.Copy(&buf, r)
assert.NoError(t, err)
renderedBar := buf.String()
assert.Contains(t, renderedBar, tc.expectedEmoji)
assert.Contains(t, renderedBar, tc.description)
assert.Contains(t, renderedBar, "[0%]")
})
}
}
func TestRenderProgressBarWithMockedWidth(t *testing.T) {
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
defer func() {
w.Close()
os.Stdout = oldStdout
}()
pb := &ProgressBar{
numberOfBytes: 100,
currentUploadedBytes: 0,
TerminalWidthFunc: func() (int, error) {
return 170, nil
},
}
err := pb.updateProgress(50)
assert.NoError(t, err)
err = pb.renderProgressBar()
assert.NoError(t, err)
err = w.Close()
assert.NoError(t, err)
var buf bytes.Buffer
_, err = io.Copy(&buf, r)
assert.NoError(t, err)
renderedBar := buf.String()
assert.Contains(t, renderedBar, "[50%]")
}
func TestClearProgressBar(t *testing.T) {
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
defer func() {
w.Close()
os.Stdout = oldStdout
}()
pb := &ProgressBar{
numberOfBytes: 100,
currentUploadedBytes: 0,
maxWidth: 100,
TerminalWidthFunc: func() (int, error) {
return 50, nil
},
}
err := pb.updateProgress(50)
assert.NoError(t, err)
err = pb.renderProgressBar()
assert.NoError(t, err)
err = pb.clearProgressBar()
assert.NoError(t, err)
w.Close()
var buf bytes.Buffer
_, err = io.Copy(&buf, r)
assert.NoError(t, err)
clearedBar := buf.String()
expectedClear := "\r" + strings.Repeat(" ", pb.maxWidth) + "\r"
assert.Contains(t, clearedBar, expectedClear)
}
func TestReset(t *testing.T) {
pb := &ProgressBar{
numberOfBytes: 100,
currentUploadedBytes: 7,
maxWidth: 100,
description: "Test Upload",
}
description := ""
totalBytes := 0
pb.reset(description, totalBytes)
assert.Equal(t, 0, pb.currentUploadedBytes)
assert.Equal(t, 0, pb.currentUploadPercentage)
assert.Equal(t, totalBytes, pb.numberOfBytes)
assert.Equal(t, description, pb.description)
}
func TestUpdateProgress(t *testing.T) {
pb := &ProgressBar{
numberOfBytes: 100,
currentUploadPercentage: 0,
currentUploadedBytes: 0,
}
bytesRead := 25
err := pb.updateProgress(bytesRead)
assert.NoError(t, err)
assert.Equal(t, 25, pb.currentUploadedBytes)
assert.Equal(t, 25, pb.currentUploadPercentage)
bytesRead = 50
err = pb.updateProgress(bytesRead)
assert.NoError(t, err)
assert.Equal(t, 75, pb.currentUploadedBytes)
assert.Equal(t, 75, pb.currentUploadPercentage)
bytesRead = 50
err = pb.updateProgress(bytesRead)
assert.Error(t, err)
assert.EqualError(t, err, "progress update exceeds total bytes: attempted to add 50 bytes, but only 25 bytes remain")
// Ensure the progress does not exceed 100% after the error
assert.Equal(t, 75, pb.currentUploadedBytes)
assert.Equal(t, 75, pb.currentUploadPercentage)
}
type MockResultStream struct {
mock.Mock
agent.AgentService_ResultClient
}
func (m *MockResultStream) Recv() (*agent.ResultResponse, error) {
args := m.Called()
if res := args.Get(0); res != nil {
return res.(*agent.ResultResponse), args.Error(1)
}
return nil, args.Error(1)
}
type MockAttestationStream struct {
mock.Mock
agent.AgentService_AttestationClient
}
func (m *MockAttestationStream) Recv() (*agent.AttestationResponse, error) {
args := m.Called()
if res := args.Get(0); res != nil {
return res.(*agent.AttestationResponse), args.Error(1)
}
return nil, args.Error(1)
}
func TestReceiveResult(t *testing.T) {
tests := []struct {
name string
description string
totalSize int
chunks [][]byte
setupMock func(*MockResultStream)
wantResult []byte
wantErr error
}{
{
name: "successful single chunk receive",
description: "Receiving result",
totalSize: 5,
chunks: [][]byte{[]byte("hello")},
setupMock: func(m *MockResultStream) {
m.On("Recv").Return(&agent.ResultResponse{File: []byte("hello")}, nil).Once()
m.On("Recv").Return(nil, io.EOF).Once()
},
wantResult: []byte("hello"),
wantErr: nil,
},
{
name: "successful multi-chunk receive",
description: "Receiving result",
totalSize: 10,
chunks: [][]byte{[]byte("hello"), []byte("world")},
setupMock: func(m *MockResultStream) {
m.On("Recv").Return(&agent.ResultResponse{File: []byte("hello")}, nil).Once()
m.On("Recv").Return(&agent.ResultResponse{File: []byte("world")}, nil).Once()
m.On("Recv").Return(nil, io.EOF).Once()
},
wantResult: []byte("helloworld"),
wantErr: nil,
},
{
name: "stream error",
description: "Receiving result",
totalSize: 5,
setupMock: func(m *MockResultStream) {
m.On("Recv").Return(nil, errors.New("stream error")).Once()
},
wantResult: nil,
wantErr: errors.New("stream error"),
},
{
name: "empty result",
description: "Receiving result",
totalSize: 0,
setupMock: func(m *MockResultStream) {
m.On("Recv").Return(nil, io.EOF).Once()
},
wantResult: []byte{},
wantErr: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockStream := &MockResultStream{}
tt.setupMock(mockStream)
p := New(true)
// Disable terminal width check for tests
p.TerminalWidthFunc = func() (int, error) { return 100, nil }
resultFile, err := os.CreateTemp("", "test_result")
assert.NoError(t, err)
t.Cleanup(func() {
os.Remove(resultFile.Name())
})
err = p.ReceiveResult(tt.description, tt.totalSize, mockStream, resultFile)
assert.NoError(t, resultFile.Close())
if tt.wantErr != nil {
assert.Error(t, err)
assert.Equal(t, tt.wantErr.Error(), err.Error())
} else {
assert.NoError(t, err)
result, err := os.ReadFile(resultFile.Name())
assert.NoError(t, err)
assert.Equal(t, tt.wantResult, result)
}
mockStream.AssertExpectations(t)
})
}
}
func TestReceiveAttestation(t *testing.T) {
tests := []struct {
name string
description string
totalSize int
chunks [][]byte
setupMock func(*MockAttestationStream)
wantResult []byte
wantErr error
}{
{
name: "successful single chunk receive",
description: "Receiving attestation",
totalSize: 5,
chunks: [][]byte{[]byte("proof")},
setupMock: func(m *MockAttestationStream) {
m.On("Recv").Return(&agent.AttestationResponse{File: []byte("proof")}, nil).Once()
m.On("Recv").Return(nil, io.EOF).Once()
},
wantResult: []byte("proof"),
wantErr: nil,
},
{
name: "successful multi-chunk receive",
description: "Receiving attestation",
totalSize: 15,
chunks: [][]byte{[]byte("proof"), []byte("signature")},
setupMock: func(m *MockAttestationStream) {
m.On("Recv").Return(&agent.AttestationResponse{File: []byte("proof")}, nil).Once()
m.On("Recv").Return(&agent.AttestationResponse{File: []byte("signature")}, nil).Once()
m.On("Recv").Return(nil, io.EOF).Once()
},
wantResult: []byte("proofsignature"),
wantErr: nil,
},
{
name: "stream error",
description: "Receiving attestation",
totalSize: 5,
setupMock: func(m *MockAttestationStream) {
m.On("Recv").Return(nil, errors.New("attestation error")).Once()
},
wantResult: nil,
wantErr: errors.New("attestation error"),
},
{
name: "size mismatch",
description: "Receiving attestation",
totalSize: 3,
chunks: [][]byte{[]byte("toolong")},
setupMock: func(m *MockAttestationStream) {
m.On("Recv").Return(&agent.AttestationResponse{File: []byte("toolong")}, nil).Once()
},
wantResult: nil,
wantErr: errors.New("progress update exceeds total bytes: attempted to add 7 bytes, but only 3 bytes remain"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockStream := &MockAttestationStream{}
tt.setupMock(mockStream)
p := New(true)
// Disable terminal width check for tests
p.TerminalWidthFunc = func() (int, error) { return 100, nil }
resultFile, err := os.CreateTemp("", "test_attestation")
assert.NoError(t, err)
t.Cleanup(func() {
os.Remove(resultFile.Name())
})
err = p.ReceiveAttestation(tt.description, tt.totalSize, mockStream, resultFile)
assert.NoError(t, resultFile.Close())
if tt.wantErr != nil {
assert.Error(t, err)
assert.Equal(t, tt.wantErr.Error(), err.Error())
} else {
assert.NoError(t, err)
result, err := os.ReadFile(resultFile.Name())
assert.NoError(t, err)
assert.Equal(t, tt.wantResult, result)
}
mockStream.AssertExpectations(t)
})
}
}
func TestReceiverIMAMeasurements(t *testing.T) {
tests := []struct {
name string
description string
totalSize int
chunks [][]byte
wantResult []byte
wantErr error
}{
{
name: "successful single chunk receive",
description: "Receiving IMA measurements",
totalSize: 20,
chunks: [][]byte{[]byte("12345678912345678999")},
wantResult: []byte("12345678912345678999"),
wantErr: nil,
},
{
name: "stream error",
description: "Receiving IMA measurements",
totalSize: 20,
chunks: [][]byte{[]byte("12345678912345678999")},
wantResult: nil,
wantErr: errors.New("stream error"),
},
{
name: "size mismatch",
description: "Receiving IMA measurements",
totalSize: 10,
chunks: [][]byte{[]byte("12345678912345678999")},
wantResult: nil,
wantErr: errors.New("progress update exceeds total bytes: attempted to add 20 bytes, but only 10 bytes remain"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockStream := new(mocks.AgentService_IMAMeasurementsClient[agent.IMAMeasurementsResponse])
p := New(true)
p.TerminalWidthFunc = func() (int, error) { return 100, nil }
resultFile, err := os.CreateTemp("", "test_ima_measurements")
assert.NoError(t, err)
t.Cleanup(func() {
os.Remove(resultFile.Name())
})
if tt.wantErr != nil {
mockStream.On("Recv").Return(nil, tt.wantErr).Once()
}
mockStream.On("Recv").Return(&agent.IMAMeasurementsResponse{Pcr10: []byte(tt.chunks[0]), File: []byte(tt.chunks[0])}, nil).Once()
mockStream.On("Recv").Return(nil, io.EOF).Once()
pcr10, err := p.ReceiveIMAMeasurements(tt.description, tt.totalSize, mockStream, resultFile)
assert.NoError(t, resultFile.Close())
if tt.wantErr != nil {
assert.Error(t, err)
assert.Equal(t, tt.wantErr.Error(), err.Error())
} else {
assert.NoError(t, err)
assert.Equal(t, tt.wantResult, pcr10)
}
})
}
}
type mockAlgoStream struct {
stream agent.AgentService_AlgoClient
sendCount int
closeAndRecvCalled bool
sendError error
closeRecvError error
}
func (m *mockAlgoStream) Send(*agent.AlgoRequest) error {
m.sendCount++
return m.sendError
}
func (m *mockAlgoStream) CloseAndRecv() (*agent.AlgoResponse, error) {
m.closeAndRecvCalled = true
return &agent.AlgoResponse{}, m.closeRecvError
}
type mockDataStream struct {
stream agent.AgentService_DataClient
sendCount int
closeAndRecvCalled bool
sendError error
closeRecvError error
}
func (m *mockDataStream) Send(*agent.DataRequest) error {
m.sendCount++
return m.sendError
}
func (m *mockDataStream) CloseAndRecv() (*agent.DataResponse, error) {
m.closeAndRecvCalled = true
return &agent.DataResponse{}, m.closeRecvError
}