Files
compute-blade-agent/pkg/ledengine/ledengine_test.go
Cedric Kienzler 70541d86ba feat(agent)!: add support for mTLS authentication in gRPC server (#54)
* refactor(fancontroller): improve fan controller validation logic and error handling for temperature steps

* refactor(agent): restructure gRPC server implementation by moving it to a new api package for better organization and maintainability

* feat(agent): implement gRPC server for managing compute blade agents and add graceful shutdown support
refactor(agent): restructure agent code by moving API logic to a dedicated file and improving error handling
fix(agent): update logging messages for clarity and consistency across the agent's operations
chore(agent): remove unused API code and consolidate event handling logic for better maintainability
style(agent): improve code formatting and organization for better readability and adherence to conventions

* feat(agent): add support for TLS configuration in gRPC server

* feat(api): add gRPC server authentication

* fix

* feat(config): add listen mode configuration to support tcp or unix sockets
feat(agent): implement listen mode in gRPC service to allow flexible socket types
feat(bladectl): enhance configuration loading and add support for TLS credentials
fix(bladectl): improve error handling for gRPC connection and event emission
style(logging): change log level from Warn to Info for better clarity in logs

* add logging middleware + fixes

* fix remote-connection to gRPC API Server

debugging the SAN issues took the soul out of me... And then the stupid
mistake in cmd_root where I didn't construct the TLS credentials
correctly... Oh dear...

* cleanup

* cleanup

* cleanup commands

* cleanup

* make README.md nicer

* Update cmd/agent/main.go

Co-authored-by: Matthias Riegler <github@m4tbit.de>

* Update cmd/bladectl/cmd_root.go

Co-authored-by: Matthias Riegler <github@m4tbit.de>

* move bladectl config into correct directory

* fix bugs

* // FIXME: No dead code

* nit: code style

* nit(YAGNI): you aint gonna need it. Don't make life harder than it needs to be

* nit(YAGNI): you aint gonna need it. Don't make life harder than it needs to be

* nit(YAGNI): you aint gonna need it. Don't make life harder than it needs to be

* nit(cmd_identify)

---------

Co-authored-by: Matthias Riegler <github@m4tbit.de>
2025-05-12 00:00:55 +02:00

257 lines
6.1 KiB
Go

package ledengine_test
import (
"context"
"errors"
"reflect"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/uptime-industries/compute-blade-agent/pkg/hal"
"github.com/uptime-industries/compute-blade-agent/pkg/hal/led"
"github.com/uptime-industries/compute-blade-agent/pkg/ledengine"
"github.com/uptime-industries/compute-blade-agent/pkg/util"
)
func TestNewStaticPattern(t *testing.T) {
t.Parallel()
type args struct {
color led.Color
}
tests := []struct {
name string
args args
want ledengine.BlinkPattern
}{
{
"Green",
args{led.Color{Green: 255}},
ledengine.BlinkPattern{
BaseColor: led.Color{Green: 255},
ActiveColor: led.Color{Green: 255},
Delays: []time.Duration{time.Hour},
},
},
{
"Red",
args{led.Color{Red: 255}},
ledengine.BlinkPattern{
BaseColor: led.Color{Red: 255},
ActiveColor: led.Color{Red: 255},
Delays: []time.Duration{time.Hour},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ledengine.NewStaticPattern(tt.args.color); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewStaticPattern() = %v, want %v", got, tt.want)
}
})
}
}
func TestNewBurstPattern(t *testing.T) {
t.Parallel()
type args struct {
baseColor led.Color
burstColor led.Color
}
tests := []struct {
name string
args args
want ledengine.BlinkPattern
}{
{
"Green <-> Red",
args{
baseColor: led.Color{Green: 255},
burstColor: led.Color{Red: 255},
},
ledengine.BlinkPattern{
BaseColor: led.Color{Green: 255},
ActiveColor: led.Color{Red: 255},
Delays: []time.Duration{
500 * time.Millisecond, // 750ms off
100 * time.Millisecond, // 100ms on
100 * time.Millisecond, // 100ms off
100 * time.Millisecond, // 100ms on
100 * time.Millisecond, // 100ms off
100 * time.Millisecond, // 100ms on
},
},
},
{
"Green <-> Green (valid, but no visual effect)",
args{
baseColor: led.Color{Green: 255},
burstColor: led.Color{Green: 255},
},
ledengine.BlinkPattern{
BaseColor: led.Color{Green: 255},
ActiveColor: led.Color{Green: 255},
Delays: []time.Duration{
500 * time.Millisecond, // 750ms off
100 * time.Millisecond, // 100ms on
100 * time.Millisecond, // 100ms off
100 * time.Millisecond, // 100ms on
100 * time.Millisecond, // 100ms off
100 * time.Millisecond, // 100ms on
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ledengine.NewBurstPattern(tt.args.baseColor, tt.args.burstColor); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewBurstPattern() = %v, want %v", got, tt.want)
}
})
}
}
func TestNewLedEngine(t *testing.T) {
t.Parallel()
engine := ledengine.Options{
Clock: util.RealClock{},
LedIdx: 0,
Hal: &hal.ComputeBladeHalMock{},
}
assert.NotNil(t, engine)
}
func Test_LedEngine_SetPattern_WhileRunning(t *testing.T) {
t.Parallel()
clk := util.MockClock{}
clkAfterChan := make(chan time.Time)
clk.On("After", time.Hour).Times(2).Return(clkAfterChan)
cbMock := hal.ComputeBladeHalMock{}
cbMock.On("SetLed", uint(0), led.Color{Green: 0, Blue: 0, Red: 0}).Once().Return(nil)
cbMock.On("SetLed", uint(0), led.Color{Green: 0, Blue: 0, Red: 255}).Once().Return(nil)
opts := ledengine.Options{
Hal: &cbMock,
Clock: &clk,
LedIdx: 0,
}
engine := ledengine.NewLedEngine(opts)
ctx, cancel := context.WithCancel(context.Background())
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
t.Log("LedEngine.Run() started")
err := engine.Run(ctx)
assert.ErrorIs(t, err, context.Canceled)
t.Log("LedEngine.Run() exited")
}()
// We want to change the pattern while the engine is running
time.Sleep(5 * time.Millisecond)
// Set pattern
t.Log("Setting pattern")
err := engine.SetPattern(ledengine.NewStaticPattern(led.Color{Red: 255}))
assert.NoError(t, err)
t.Log("Canceling context")
cancel()
wg.Wait()
clk.AssertExpectations(t)
cbMock.AssertExpectations(t)
}
func Test_LedEngine_SetPattern_BeforeRun(t *testing.T) {
t.Parallel()
clk := util.MockClock{}
clkAfterChan := make(chan time.Time)
clk.On("After", time.Hour).Once().Return(clkAfterChan)
cbMock := hal.ComputeBladeHalMock{}
cbMock.On("SetLed", uint(0), led.Color{Green: 0, Blue: 0, Red: 255}).Once().Return(nil)
opts := ledengine.Options{
Hal: &cbMock,
Clock: &clk,
LedIdx: 0,
}
engine := ledengine.NewLedEngine(opts)
// We want to change the pattern BEFORE the engine is started
t.Log("Setting pattern")
err := engine.SetPattern(ledengine.NewStaticPattern(led.Color{Red: 255}))
assert.NoError(t, err)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
t.Log("LedEngine.Run() started")
err := engine.Run(ctx)
assert.ErrorIs(t, err, context.DeadlineExceeded)
t.Log("LedEngine.Run() exited")
}()
t.Log("Waiting for context to timeout")
wg.Wait()
clk.AssertExpectations(t)
cbMock.AssertExpectations(t)
}
func Test_LedEngine_SetPattern_SetLedFailureInPattern(t *testing.T) {
t.Parallel()
clk := util.MockClock{}
clkAfterChan := make(chan time.Time)
clk.On("After", time.Hour).Once().Return(clkAfterChan)
cbMock := hal.ComputeBladeHalMock{}
call0 := cbMock.On("SetLed", uint(0), led.Color{Green: 0, Blue: 0, Red: 0}).Once().Return(nil)
cbMock.On("SetLed", uint(0), led.Color{Green: 0, Blue: 0, Red: 0}).Once().Return(errors.New("failure")).NotBefore(call0)
opts := ledengine.Options{
Hal: &cbMock,
Clock: &clk,
LedIdx: 0,
}
engine := ledengine.NewLedEngine(opts)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
t.Log("LedEngine.Run() started")
err := engine.Run(ctx)
assert.Error(t, err)
t.Log("LedEngine.Run() exited")
}()
time.Sleep(5 * time.Millisecond)
// Time tick -> SetLed() fails
clkAfterChan <- time.Now()
t.Log("Waiting for context to timeout")
wg.Wait()
clk.AssertExpectations(t)
cbMock.AssertExpectations(t)
}