@@ -22,10 +22,13 @@ import (
2222 "testing"
2323
2424 pb "github.com/NVIDIA/ncx-infra-controller-rest/nvswitch-manager/internal/proto/v1"
25+ "github.com/NVIDIA/ncx-infra-controller-rest/nvswitch-manager/pkg/firmwaremanager"
2526 "github.com/NVIDIA/ncx-infra-controller-rest/nvswitch-manager/pkg/redfish"
2627
2728 "github.com/stretchr/testify/assert"
2829 "github.com/stretchr/testify/require"
30+ "google.golang.org/grpc/codes"
31+ "google.golang.org/grpc/status"
2932)
3033
3134func TestResetTarget_InvalidIP (t * testing.T ) {
@@ -206,18 +209,63 @@ func TestFirmwareTargetToRegisterRequest(t *testing.T) {
206209 assert .Equal (t , int32 (22 ), req .Nvos .Port )
207210}
208211
209- func TestQueueUpdates_InvalidTarget (t * testing.T ) {
212+ // TestQueueUpdates_NilFirmwareManager guards the early-return in QueueUpdates
213+ // that protects against being called before the firmware manager has been
214+ // initialized (e.g. when persistent mode is configured but the firmware
215+ // packages directory is unset). Per-target validation is exercised by the
216+ // TestValidateFirmwareTarget_* tests; driving end-to-end validation through
217+ // the handler would require a non-nil fwm and is left for handler-level
218+ // tests with a mocked FirmwareManager.
219+ func TestQueueUpdates_NilFirmwareManager (t * testing.T ) {
210220 srv := & NVSwitchManagerServerImpl {}
211- // fwm is nil, but the target validation should fail before reaching fwm
212- // We can't test the full flow without a fwm, but we can test validation errors
213- // by checking that the handler returns results (not a gRPC error) for invalid targets.
214221
215- // Without fwm, QueueUpdates returns "firmware manager not initialized"
216- // So we only test validateFirmwareTarget directly here.
217- target := validFirmwareTarget ()
218- target .BmcIp = "invalid"
219- err := validateFirmwareTarget (target )
220- assert .Error (t , err )
222+ resp , err := srv .QueueUpdates (context .Background (), & pb.QueueUpdatesRequest {})
223+
224+ require .Error (t , err )
225+ assert .Nil (t , resp )
226+ st , ok := status .FromError (err )
227+ require .True (t , ok , "expected a gRPC status error, got %T" , err )
228+ assert .Equal (t , codes .Unavailable , st .Code ())
229+ assert .Contains (t , st .Message (), "firmware manager not initialized" )
230+ }
231+
232+ // TestQueueUpdates_MutualExclusivity locks in the contract that
233+ // QueueUpdates rejects requests violating the switch_uuids/targets
234+ // mutual-exclusivity constraint. See QueueUpdatesRequest in
235+ // nvswitch-manager.proto for the full contract documentation.
236+ func TestQueueUpdates_MutualExclusivity (t * testing.T ) {
237+ cases := map [string ]struct {
238+ req * pb.QueueUpdatesRequest
239+ wantMessage string
240+ }{
241+ "empty request rejected (no work to do)" : {
242+ req : & pb.QueueUpdatesRequest {},
243+ wantMessage : "either switch_uuids or targets" ,
244+ },
245+ "both populated rejected (would risk duplicate updates)" : {
246+ req : & pb.QueueUpdatesRequest {
247+ SwitchUuids : []string {"00000000-0000-0000-0000-000000000001" },
248+ Targets : []* pb.FirmwareTarget {validFirmwareTarget ()},
249+ },
250+ wantMessage : "not both" ,
251+ },
252+ }
253+
254+ for name , tc := range cases {
255+ t .Run (name , func (t * testing.T ) {
256+ // Non-nil fwm so we get past the Unavailable guard. The
257+ // mutual-exclusivity validation rejects before any fwm method
258+ // is invoked, so a zero-value pointer is sufficient.
259+ srv := & NVSwitchManagerServerImpl {fwm : & firmwaremanager.FirmwareManager {}}
260+
261+ resp , err := srv .QueueUpdates (context .Background (), tc .req )
221262
222- _ = srv // suppress unused
263+ require .Error (t , err )
264+ assert .Nil (t , resp )
265+ st , ok := status .FromError (err )
266+ require .True (t , ok , "expected a gRPC status error, got %T" , err )
267+ assert .Equal (t , codes .InvalidArgument , st .Code ())
268+ assert .Contains (t , st .Message (), tc .wantMessage )
269+ })
270+ }
223271}
0 commit comments