Files
vm/api/admin/client_test.go
zeekayandHanzo Dev f993831e33 The test suite compiles, and 246 tests run where 159 did
Six packages would not build, each from a migration that stopped one step
short, and a package that does not compile reports nothing at all.

The generated mocks had fallen behind their interfaces: Manager kept four of
its eight methods, Server was missing SetRootInfoProvider, and there was no
Factory mock under the name the tests used. All three are regenerated, and
each package now carries the go:generate line that produces them, so a
method added to an interface is a regeneration away rather than a puzzle in
another package's tests.

The interfaces had also taken a context that the expectations had not, so
forty-six of them name it now. Aliases and the level test followed their
types: the standalone atomic package rather than the compatibility
re-export, and log's own Level constants beside the field that holds one.
One table field was renamed in every place but the line that read it.

Two services lost a behaviour along the way. Registration aliases every VM
to its own id, and both GetVMs and LoadVMs handed that back as an alias of
itself, which the tests had said all along they should not; and both read
their context from a request the tests passed as nil, which segfaulted
before a single expectation could run.

chains/atomic still fails, deliberately: its fixture asserts a big-endian
wire layout "so any on-disk record from the legacy build still parses", and
luxfi/util's packer moved to little-endian on purpose. Which side gives is a
question about records already on disk, not about this test.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-17 22:03:36 -07:00

359 lines
8.6 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package admin
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/require"
"github.com/luxfi/ids"
"github.com/luxfi/log"
"github.com/luxfi/rpc"
"github.com/luxfi/vm/api"
)
var (
errTest = errors.New("non-nil error")
SuccessResponseTests = []struct {
name string
expectedErr error
}{
{
name: "no error",
expectedErr: nil,
},
{
name: "error",
expectedErr: errTest,
},
}
)
type mockClient struct {
response interface{}
err error
}
// NewMockClient returns a mock client for testing
func NewMockClient(response interface{}, err error) rpc.EndpointRequester {
return &mockClient{
response: response,
err: err,
}
}
func (mc *mockClient) SendRequest(_ context.Context, _ string, _ interface{}, reply interface{}, _ ...rpc.Option) error {
if mc.err != nil {
return mc.err
}
switch p := reply.(type) {
case *api.EmptyReply:
response := mc.response.(*api.EmptyReply)
*p = *response
case *GetChainAliasesReply:
response := mc.response.(*GetChainAliasesReply)
*p = *response
case *LoadVMsReply:
response := mc.response.(*LoadVMsReply)
*p = *response
case *LoggerLevelReply:
response := mc.response.(*LoggerLevelReply)
*p = *response
case *interface{}:
response := mc.response.(*interface{})
*p = *response
default:
panic("illegal type")
}
return nil
}
func TestStartCPUProfiler(t *testing.T) {
for _, test := range SuccessResponseTests {
t.Run(test.name, func(t *testing.T) {
mockClient := Client{Requester: NewMockClient(&api.EmptyReply{}, test.expectedErr)}
err := mockClient.StartCPUProfiler(context.Background())
require.ErrorIs(t, err, test.expectedErr)
})
}
}
func TestStopCPUProfiler(t *testing.T) {
for _, test := range SuccessResponseTests {
t.Run(test.name, func(t *testing.T) {
mockClient := Client{Requester: NewMockClient(&api.EmptyReply{}, test.expectedErr)}
err := mockClient.StopCPUProfiler(context.Background())
require.ErrorIs(t, err, test.expectedErr)
})
}
}
func TestMemoryProfile(t *testing.T) {
for _, test := range SuccessResponseTests {
t.Run(test.name, func(t *testing.T) {
mockClient := Client{Requester: NewMockClient(&api.EmptyReply{}, test.expectedErr)}
err := mockClient.MemoryProfile(context.Background())
require.ErrorIs(t, err, test.expectedErr)
})
}
}
func TestLockProfile(t *testing.T) {
for _, test := range SuccessResponseTests {
t.Run(test.name, func(t *testing.T) {
mockClient := Client{Requester: NewMockClient(&api.EmptyReply{}, test.expectedErr)}
err := mockClient.LockProfile(context.Background())
require.ErrorIs(t, err, test.expectedErr)
})
}
}
func TestAlias(t *testing.T) {
for _, test := range SuccessResponseTests {
t.Run(test.name, func(t *testing.T) {
mockClient := Client{Requester: NewMockClient(&api.EmptyReply{}, test.expectedErr)}
err := mockClient.Alias(context.Background(), "alias", "alias2")
require.ErrorIs(t, err, test.expectedErr)
})
}
}
func TestAliasChain(t *testing.T) {
for _, test := range SuccessResponseTests {
t.Run(test.name, func(t *testing.T) {
mockClient := Client{Requester: NewMockClient(&api.EmptyReply{}, test.expectedErr)}
err := mockClient.AliasChain(context.Background(), "chain", "chain-alias")
require.ErrorIs(t, err, test.expectedErr)
})
}
}
func TestGetChainAliases(t *testing.T) {
t.Run("successful", func(t *testing.T) {
require := require.New(t)
expectedReply := []string{"alias1", "alias2"}
mockClient := Client{Requester: NewMockClient(&GetChainAliasesReply{
Aliases: expectedReply,
}, nil)}
reply, err := mockClient.GetChainAliases(context.Background(), "chain")
require.NoError(err)
require.Equal(expectedReply, reply)
})
t.Run("failure", func(t *testing.T) {
mockClient := Client{Requester: NewMockClient(&GetChainAliasesReply{}, errTest)}
_, err := mockClient.GetChainAliases(context.Background(), "chain")
require.ErrorIs(t, err, errTest)
})
}
func TestStacktrace(t *testing.T) {
for _, test := range SuccessResponseTests {
t.Run(test.name, func(t *testing.T) {
mockClient := Client{Requester: NewMockClient(&api.EmptyReply{}, test.expectedErr)}
err := mockClient.Stacktrace(context.Background())
require.ErrorIs(t, err, test.expectedErr)
})
}
}
func TestReloadInstalledVMs(t *testing.T) {
t.Run("successful", func(t *testing.T) {
require := require.New(t)
expectedNewVMs := map[ids.ID][]string{
ids.GenerateTestID(): {"foo"},
ids.GenerateTestID(): {"bar"},
}
expectedFailedVMs := map[ids.ID]string{
ids.GenerateTestID(): "oops",
ids.GenerateTestID(): "uh-oh",
}
mockClient := Client{Requester: NewMockClient(&LoadVMsReply{
NewVMs: expectedNewVMs,
FailedVMs: expectedFailedVMs,
}, nil)}
loadedVMs, failedVMs, err := mockClient.LoadVMs(context.Background())
require.NoError(err)
require.Equal(expectedNewVMs, loadedVMs)
require.Equal(expectedFailedVMs, failedVMs)
})
t.Run("failure", func(t *testing.T) {
mockClient := Client{Requester: NewMockClient(&LoadVMsReply{}, errTest)}
_, _, err := mockClient.LoadVMs(context.Background())
require.ErrorIs(t, err, errTest)
})
}
func TestSetLoggerLevel(t *testing.T) {
type test struct {
name string
logLevel string
displayLevel string
serviceResponse map[string]LogAndDisplayLevels
serviceErr error
clientErr error
}
tests := []test{
{
name: "Happy path",
logLevel: "INFO",
displayLevel: "INFO",
serviceResponse: map[string]LogAndDisplayLevels{
"Happy path": {LogLevel: log.InfoLevel, DisplayLevel: log.InfoLevel},
},
serviceErr: nil,
clientErr: nil,
},
{
name: "Service errors",
logLevel: "INFO",
displayLevel: "INFO",
serviceResponse: nil,
serviceErr: errTest,
clientErr: errTest,
},
{
name: "Invalid log level",
logLevel: "invalid",
displayLevel: "INFO",
serviceResponse: nil,
serviceErr: nil,
clientErr: log.ErrUnknownLevel,
},
{
name: "Invalid display level",
logLevel: "INFO",
displayLevel: "invalid",
serviceResponse: nil,
serviceErr: nil,
clientErr: log.ErrUnknownLevel,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require := require.New(t)
c := Client{
Requester: NewMockClient(
&LoggerLevelReply{
LoggerLevels: tt.serviceResponse,
},
tt.serviceErr,
),
}
res, err := c.SetLoggerLevel(
context.Background(),
"",
tt.logLevel,
tt.displayLevel,
)
require.ErrorIs(err, tt.clientErr)
if tt.clientErr != nil {
return
}
require.Equal(tt.serviceResponse, res)
})
}
}
func TestGetLoggerLevel(t *testing.T) {
type test struct {
name string
loggerName string
serviceResponse map[string]LogAndDisplayLevels
serviceErr error
clientErr error
}
tests := []test{
{
name: "Happy Path",
loggerName: "foo",
serviceResponse: map[string]LogAndDisplayLevels{
"foo": {LogLevel: log.InfoLevel, DisplayLevel: log.InfoLevel},
},
serviceErr: nil,
clientErr: nil,
},
{
name: "service errors",
loggerName: "foo",
serviceResponse: nil,
serviceErr: errTest,
clientErr: errTest,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require := require.New(t)
c := Client{
Requester: NewMockClient(
&LoggerLevelReply{
LoggerLevels: tt.serviceResponse,
},
tt.serviceErr,
),
}
res, err := c.GetLoggerLevel(
context.Background(),
tt.loggerName,
)
require.ErrorIs(err, tt.clientErr)
if tt.clientErr != nil {
return
}
require.Equal(tt.serviceResponse, res)
})
}
}
func TestGetConfig(t *testing.T) {
type test struct {
name string
serviceErr error
clientErr error
expectedResponse interface{}
}
var resp interface{} = "response"
tests := []test{
{
name: "Happy path",
serviceErr: nil,
clientErr: nil,
expectedResponse: &resp,
},
{
name: "service errors",
serviceErr: errTest,
clientErr: errTest,
expectedResponse: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require := require.New(t)
c := Client{
Requester: NewMockClient(tt.expectedResponse, tt.serviceErr),
}
res, err := c.GetConfig(context.Background())
require.ErrorIs(err, tt.clientErr)
if tt.clientErr != nil {
return
}
require.Equal(resp, res)
})
}
}