package mxgateway import ( "context" "errors" "os" "path/filepath" "strings" "testing" pb "gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go/internal/generated" "google.golang.org/protobuf/encoding/protojson" ) // loadCommandReplyFixture parses a shared command-reply fixture into an // MxCommandReply so the Go client can be driven through the same wire shapes the // other language clients exercise. func loadCommandReplyFixture(t *testing.T, name string) *pb.MxCommandReply { t.Helper() path := filepath.Join("..", "..", "proto", "fixtures", "behavior", "command-replies", name) data, err := os.ReadFile(path) if err != nil { t.Fatalf("read fixture %s: %v", name, err) } var reply pb.MxCommandReply if err := protojson.Unmarshal(data, &reply); err != nil { t.Fatalf("parse fixture %s: %v", name, err) } return &reply } func TestAuthenticateUserMissingPayloadReturnsMalformedReplyError(t *testing.T) { fake := &fakeGatewayServer{ invokeReply: loadCommandReplyFixture(t, "authenticate-user.missing-payload.reply.json"), } client, cleanup := newBufconnClient(t, fake) defer cleanup() session := NewSessionForID(client, "session-1") _, err := session.AuthenticateUser(context.Background(), 12, "operator", "secret") var malformed *MalformedReplyError if !errors.As(err, &malformed) { t.Fatalf("AuthenticateUser() error = %v (%T), want *MalformedReplyError", err, err) } if malformed.Op != "authenticate user" { t.Fatalf("MalformedReplyError.Op = %q, want %q", malformed.Op, "authenticate user") } } func TestAuthenticateUserReturnValueOnlyUsesInt32ReturnValue(t *testing.T) { fake := &fakeGatewayServer{ invokeReply: loadCommandReplyFixture(t, "authenticate-user.return-value-only.reply.json"), } client, cleanup := newBufconnClient(t, fake) defer cleanup() session := NewSessionForID(client, "session-1") userID, err := session.AuthenticateUser(context.Background(), 12, "operator", "secret") if err != nil { t.Fatalf("AuthenticateUser() error = %v", err) } if userID != 7 { t.Fatalf("AuthenticateUser() = %d, want 7", userID) } } // AddBufferedItem shares the prefer-payload / int32-return-value / malformed // fallback code path; cover both branches for one of the siblings. func TestAddBufferedItemFallbackHonoursReturnValueAndReportsMalformed(t *testing.T) { t.Run("return-value-only", func(t *testing.T) { fake := &fakeGatewayServer{ invokeReply: loadCommandReplyFixture(t, "authenticate-user.return-value-only.reply.json"), } client, cleanup := newBufconnClient(t, fake) defer cleanup() session := NewSessionForID(client, "session-1") itemHandle, err := session.AddBufferedItem(context.Background(), 12, "Area001.Pump001.Speed", "runtime") if err != nil { t.Fatalf("AddBufferedItem() error = %v", err) } if itemHandle != 7 { t.Fatalf("AddBufferedItem() = %d, want 7", itemHandle) } }) t.Run("missing-payload", func(t *testing.T) { fake := &fakeGatewayServer{ invokeReply: loadCommandReplyFixture(t, "authenticate-user.missing-payload.reply.json"), } client, cleanup := newBufconnClient(t, fake) defer cleanup() session := NewSessionForID(client, "session-1") _, err := session.AddBufferedItem(context.Background(), 12, "Area001.Pump001.Speed", "runtime") var malformed *MalformedReplyError if !errors.As(err, &malformed) { t.Fatalf("AddBufferedItem() error = %v (%T), want *MalformedReplyError", err, err) } if malformed.Op != "add buffered item" { t.Fatalf("MalformedReplyError.Op = %q, want %q", malformed.Op, "add buffered item") } }) } // TestAuthenticateUserScrubsEchoedCredentialFromError is the CLI-40 regression: // a gateway diagnostic that echoes the raw credential back must never reach the // caller's surfaced error text. func TestAuthenticateUserScrubsEchoedCredentialFromError(t *testing.T) { const credential = "sup3rSecretVerify9f3a2b" fake := &fakeGatewayServer{ invokeReply: loadCommandReplyFixture(t, "authenticate-user.echoed-credential.reply.json"), } client, cleanup := newBufconnClient(t, fake) defer cleanup() session := NewSessionForID(client, "session-1") _, err := session.AuthenticateUser(context.Background(), 12, "operator", credential) if err == nil { t.Fatal("AuthenticateUser() error = nil, want an MXAccess failure") } message := err.Error() if strings.Contains(message, credential) { t.Fatalf("surfaced error leaked the credential: %q", message) } if !strings.Contains(message, "") { t.Fatalf("surfaced error missing redaction marker: %q", message) } }