feat(go): add ping CLI subcommand (§4.3)
Add PingRaw to Session (session.go), runPing to the CLI dispatch (main.go), and three tests covering plain-text echo, JSON output, and missing-session-id validation (main_test.go). Default message is "ping"; gateway echo is read from DiagnosticMessage, falling back to the kind string if absent.
This commit is contained in:
@@ -121,6 +121,8 @@ func runWithIO(ctx context.Context, args []string, stdout, stderr io.Writer) err
|
||||
return runGalaxyDiscover(ctx, args[1:], stdout, stderr)
|
||||
case "galaxy-watch":
|
||||
return runGalaxyWatch(ctx, args[1:], stdout, stderr)
|
||||
case "ping":
|
||||
return runPing(ctx, args[1:], stdout, stderr)
|
||||
case "batch":
|
||||
return runBatch(ctx, os.Stdin, stdout, stderr)
|
||||
default:
|
||||
@@ -228,6 +230,47 @@ func runCloseSession(ctx context.Context, args []string, stdout, stderr io.Write
|
||||
return nil
|
||||
}
|
||||
|
||||
func runPing(ctx context.Context, args []string, stdout, stderr io.Writer) error {
|
||||
flags := flag.NewFlagSet("ping", flag.ContinueOnError)
|
||||
flags.SetOutput(stderr)
|
||||
common := bindCommonFlags(flags)
|
||||
jsonOutput := flags.Bool("json", false, "write JSON output")
|
||||
sessionID := flags.String("session-id", "", "gateway session id")
|
||||
message := flags.String("message", "ping", "ping payload message")
|
||||
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
if *sessionID == "" {
|
||||
return errors.New("session-id is required")
|
||||
}
|
||||
|
||||
client, options, err := dialForCommand(ctx, common)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
session := mxgateway.NewSessionForID(client, *sessionID)
|
||||
reply, err := session.PingRaw(ctx, *message)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *jsonOutput {
|
||||
return writeJSON(stdout, commandReplyOutput{
|
||||
Command: "ping",
|
||||
Options: options,
|
||||
Reply: mustMarshalProto(reply),
|
||||
})
|
||||
}
|
||||
echo := reply.GetDiagnosticMessage()
|
||||
if echo == "" {
|
||||
echo = reply.GetKind().String()
|
||||
}
|
||||
fmt.Fprintln(stdout, echo)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runRegister(ctx context.Context, args []string, stdout, stderr io.Writer) error {
|
||||
flags := flag.NewFlagSet("register", flag.ContinueOnError)
|
||||
flags.SetOutput(stderr)
|
||||
@@ -1196,7 +1239,7 @@ type protojsonMessage interface {
|
||||
}
|
||||
|
||||
func writeUsage(writer io.Writer) {
|
||||
fmt.Fprintln(writer, "usage: mxgw-go <version|open-session|close-session|register|add-item|advise|subscribe-bulk|unsubscribe-bulk|read-bulk|write-bulk|write2-bulk|write-secured-bulk|write-secured2-bulk|bench-read-bulk|write|stream-events|stream-alarms|acknowledge-alarm|smoke|galaxy-test-connection|galaxy-last-deploy|galaxy-discover|galaxy-watch|batch>")
|
||||
fmt.Fprintln(writer, "usage: mxgw-go <version|open-session|close-session|ping|register|add-item|advise|subscribe-bulk|unsubscribe-bulk|read-bulk|write-bulk|write2-bulk|write-secured-bulk|write-secured2-bulk|bench-read-bulk|write|stream-events|stream-alarms|acknowledge-alarm|smoke|galaxy-test-connection|galaxy-last-deploy|galaxy-discover|galaxy-watch|batch>")
|
||||
}
|
||||
|
||||
// batchEOR is the end-of-result sentinel emitted to stdout after every command
|
||||
|
||||
@@ -190,6 +190,102 @@ func TestRunBenchReadBulkRespectsContextCancellation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestRunPingPlainText verifies the ping subcommand round-trips through the
|
||||
// fake gateway and prints the echo (diagnostic_message) in plain-text mode.
|
||||
func TestRunPingPlainText(t *testing.T) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("listen: %v", err)
|
||||
}
|
||||
server := grpc.NewServer()
|
||||
fake := &pingFakeGateway{}
|
||||
pb.RegisterMxAccessGatewayServer(server, fake)
|
||||
go func() { _ = server.Serve(listener) }()
|
||||
defer server.Stop()
|
||||
defer listener.Close()
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
args := []string{
|
||||
"ping",
|
||||
"-endpoint", listener.Addr().String(),
|
||||
"-plaintext",
|
||||
"-api-key", "test",
|
||||
"-session-id", "test-session",
|
||||
"-message", "hello",
|
||||
}
|
||||
if err := runWithIO(t.Context(), args, &stdout, &stderr); err != nil {
|
||||
t.Fatalf("runWithIO() error = %v; stderr = %s", err, stderr.String())
|
||||
}
|
||||
got := strings.TrimSpace(stdout.String())
|
||||
if got != "pong:hello" {
|
||||
t.Fatalf("ping plain-text output = %q, want %q", got, "pong:hello")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRunPingJSON verifies the ping subcommand emits valid JSON in --json mode.
|
||||
func TestRunPingJSON(t *testing.T) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("listen: %v", err)
|
||||
}
|
||||
server := grpc.NewServer()
|
||||
fake := &pingFakeGateway{}
|
||||
pb.RegisterMxAccessGatewayServer(server, fake)
|
||||
go func() { _ = server.Serve(listener) }()
|
||||
defer server.Stop()
|
||||
defer listener.Close()
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
args := []string{
|
||||
"ping",
|
||||
"-endpoint", listener.Addr().String(),
|
||||
"-plaintext",
|
||||
"-api-key", "test",
|
||||
"-session-id", "test-session",
|
||||
"-message", "hello",
|
||||
"-json",
|
||||
}
|
||||
if err := runWithIO(t.Context(), args, &stdout, &stderr); err != nil {
|
||||
t.Fatalf("runWithIO() error = %v; stderr = %s", err, stderr.String())
|
||||
}
|
||||
var out commandReplyOutput
|
||||
if err := json.Unmarshal(stdout.Bytes(), &out); err != nil {
|
||||
t.Fatalf("parse JSON: %v\noutput: %s", err, stdout.String())
|
||||
}
|
||||
if out.Command != "ping" {
|
||||
t.Fatalf("command = %q, want %q", out.Command, "ping")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRunPingRequiresSessionID verifies the ping subcommand rejects missing session-id.
|
||||
func TestRunPingRequiresSessionID(t *testing.T) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
err := runWithIO(t.Context(), []string{"ping", "-plaintext", "-api-key", "test"}, &stdout, &stderr)
|
||||
if err == nil {
|
||||
t.Fatalf("runWithIO(ping without --session-id) returned no error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "session-id is required") {
|
||||
t.Fatalf("error = %v; want 'session-id is required'", err)
|
||||
}
|
||||
}
|
||||
|
||||
// pingFakeGateway handles Invoke for MX_COMMAND_KIND_PING by echoing the
|
||||
// message back in the diagnostic_message field so the CLI plain-text path
|
||||
// has a deterministic, non-empty string to assert on.
|
||||
type pingFakeGateway struct {
|
||||
pb.UnimplementedMxAccessGatewayServer
|
||||
}
|
||||
|
||||
func (g *pingFakeGateway) Invoke(_ context.Context, req *pb.MxCommandRequest) (*pb.MxCommandReply, error) {
|
||||
echo := "pong:" + req.GetCommand().GetPing().GetMessage()
|
||||
return &pb.MxCommandReply{
|
||||
SessionId: req.GetSessionId(),
|
||||
Kind: pb.MxCommandKind_MX_COMMAND_KIND_PING,
|
||||
DiagnosticMessage: echo,
|
||||
ProtocolStatus: &pb.ProtocolStatus{Code: pb.ProtocolStatusCode_PROTOCOL_STATUS_CODE_OK},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// benchFakeGateway is a minimal MxAccessGatewayServer that satisfies the
|
||||
// bench-read-bulk session-setup sequence (OpenSession + Invoke for Register
|
||||
// / SubscribeBulk / ReadBulk / UnsubscribeBulk / CloseSession).
|
||||
|
||||
@@ -580,6 +580,17 @@ func (s *Session) WriteRaw(ctx context.Context, serverHandle, itemHandle int32,
|
||||
})
|
||||
}
|
||||
|
||||
// PingRaw sends a diagnostic PING command and returns the raw reply.
|
||||
// The message is echoed back by the gateway in the reply's DiagnosticMessage field.
|
||||
func (s *Session) PingRaw(ctx context.Context, message string) (*MxCommandReply, error) {
|
||||
return s.invokeCommand(ctx, &pb.MxCommand{
|
||||
Kind: pb.MxCommandKind_MX_COMMAND_KIND_PING,
|
||||
Payload: &pb.MxCommand_Ping{
|
||||
Ping: &pb.PingCommand{Message: message},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Write2 invokes MXAccess Write2 (timestamped single-item write).
|
||||
func (s *Session) Write2(ctx context.Context, serverHandle, itemHandle int32, value, timestampValue *MxValue, userID int32) error {
|
||||
_, err := s.Write2Raw(ctx, serverHandle, itemHandle, value, timestampValue, userID)
|
||||
|
||||
Reference in New Issue
Block a user