mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-18 12:49:07 +02:00
86 lines
3.5 KiB
Go
86 lines
3.5 KiB
Go
//go:build !android && !ios && !freebsd && !js
|
|
|
|
package services
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/genproto/googleapis/rpc/errdetails"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/codes"
|
|
gstatus "google.golang.org/grpc/status"
|
|
|
|
"github.com/netbirdio/netbird/client/internal/ipcauth"
|
|
"github.com/netbirdio/netbird/client/proto"
|
|
)
|
|
|
|
// stubProfileDaemon refuses SwitchProfile the way the daemon refuses a caller
|
|
// who does not hold the session. The embedded interface is nil, so any other
|
|
// call panics rather than passing quietly.
|
|
type stubProfileDaemon struct {
|
|
proto.DaemonServiceClient
|
|
err error
|
|
}
|
|
|
|
func (d *stubProfileDaemon) SwitchProfile(_ context.Context, _ *proto.SwitchProfileRequest, _ ...grpc.CallOption) (*proto.SwitchProfileResponse, error) {
|
|
return nil, d.err
|
|
}
|
|
|
|
// sessionHeldRefusal is the error the daemon raises when another user holds the
|
|
// connection, detail and all: see ipcauth.SessionHeldError.
|
|
func sessionHeldRefusal(t *testing.T) error {
|
|
t.Helper()
|
|
|
|
st, err := gstatus.New(codes.PermissionDenied, sessionHeldSummaryText+"\n\nsudo netbird down").
|
|
WithDetails(&errdetails.ErrorInfo{
|
|
Reason: ipcauth.ErrorReasonSessionHeld,
|
|
Domain: ipcauth.ErrorDomain,
|
|
Metadata: map[string]string{
|
|
ipcauth.ErrorMetaSummary: sessionHeldSummaryText,
|
|
ipcauth.ErrorMetaCommand: "sudo netbird down",
|
|
},
|
|
})
|
|
require.NoError(t, err, "build the refusal detail")
|
|
return st.Err()
|
|
}
|
|
|
|
const sessionHeldSummaryText = "Switching profiles is refused while another user has this machine connected."
|
|
|
|
func profilesRefusingSwitch(t *testing.T) *Profiles {
|
|
t.Helper()
|
|
// nil translator → Short is the bare "error.<code>" key, which is enough to
|
|
// tell a resolved headline from the daemon's own sentence.
|
|
return NewProfiles(stubConn{client: &stubProfileDaemon{err: sessionHeldRefusal(t)}}, nil, nil)
|
|
}
|
|
|
|
// A refused switch has to reach the caller as the classified value, since that is
|
|
// the only thing carrying the headline and the command the frontend renders.
|
|
func TestProfilesSwitchClassifiesRefusal(t *testing.T) {
|
|
_, err := profilesRefusingSwitch(t).Switch(context.Background(), ProfileRef{ProfileName: "work"})
|
|
|
|
clientErr, ok := err.(*ClientError)
|
|
require.True(t, ok, "Switch must return the classified error, got %T", err)
|
|
assert.Equal(t, "session_held", clientErr.Code, "the refusal reason decides the code")
|
|
assert.Equal(t, sessionHeldSummaryText, clientErr.Long, "the daemon's sentence is the detail")
|
|
assert.Equal(t, "sudo netbird down", clientErr.Command, "the suggested command survives")
|
|
}
|
|
|
|
// The switcher used to wrap this in fmt.Errorf, which left the Wails binding
|
|
// nothing to marshal and put the raw "switch profile %q: rpc error: ..." string
|
|
// in front of the user instead of the headline and the copyable command.
|
|
func TestProfileSwitcherReturnsClassifiedRefusal(t *testing.T) {
|
|
switcher := NewProfileSwitcher(profilesRefusingSwitch(t), nil, nil)
|
|
|
|
err := switcher.SwitchActive(context.Background(), ProfileRef{ProfileName: "01HZY0000000000000000000"})
|
|
|
|
clientErr, ok := err.(*ClientError)
|
|
require.True(t, ok, "the switcher must pass the classified error through, got %T", err)
|
|
assert.Equal(t, "session_held", clientErr.Code, "the refusal reason decides the code")
|
|
assert.Equal(t, "sudo netbird down", clientErr.Command, "the suggested command survives")
|
|
assert.Equal(t, "error.session_held", err.Error(),
|
|
"no wrapping prefix and no gRPC dump in front of the headline")
|
|
}
|