From dc78de3ddab1970b68e4b70a72a90785ab95548f Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 7 Jan 2026 00:36:59 +0200 Subject: [PATCH] event: add tests for parseQuoted --- event/botcommandparse.go | 2 +- event/botcommandparse_test.go | 67 +++++++++++++++++++++++ event/botcommandtestdata/data.go | 14 +++++ event/botcommandtestdata/parse_quote.json | 20 +++++++ 4 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 event/botcommandparse_test.go create mode 100644 event/botcommandtestdata/data.go create mode 100644 event/botcommandtestdata/parse_quote.json diff --git a/event/botcommandparse.go b/event/botcommandparse.go index 53681e5f..17f38664 100644 --- a/event/botcommandparse.go +++ b/event/botcommandparse.go @@ -45,7 +45,7 @@ func parseQuoted(val string) (parsed, remaining string, quoted bool) { buf.WriteByte(val[escapeIdx+1]) } val = val[min(escapeIdx+2, len(val)):] - } else if quoteIdx > 0 { + } else if quoteIdx >= 0 { buf.WriteString(val[:quoteIdx]) val = val[quoteIdx+1:] break diff --git a/event/botcommandparse_test.go b/event/botcommandparse_test.go new file mode 100644 index 00000000..611c055a --- /dev/null +++ b/event/botcommandparse_test.go @@ -0,0 +1,67 @@ +// Copyright (c) 2026 Tulir Asokan +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package event + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "go.mau.fi/util/exerrors" + + "maunium.net/go/mautrix/event/botcommandtestdata" +) + +type QuoteParseOutput struct { + Parsed string + Remaining string + Quoted bool +} + +type QuoteParseTestData struct { + Name string + Input string + Output QuoteParseOutput +} + +func (qptd *QuoteParseTestData) UnmarshalJSON(data []byte) error { + var arr []any + if err := json.Unmarshal(data, &arr); err != nil { + return err + } + qptd.Name = arr[0].(string) + qptd.Input = arr[1].(string) + output := arr[2].([]any) + qptd.Output.Parsed = output[0].(string) + qptd.Output.Remaining = output[1].(string) + qptd.Output.Quoted = output[2].(bool) + return nil +} + +func TestParseQuoted(t *testing.T) { + var qptd []QuoteParseTestData + dec := json.NewDecoder(exerrors.Must(botcommandtestdata.FS.Open("parse_quote.json"))) + exerrors.PanicIfNotNil(dec.Decode(&qptd)) + for _, test := range qptd { + t.Run(test.Name, func(t *testing.T) { + parsed, remaining, quoted := parseQuoted(test.Input) + assert.Equalf(t, test.Output, QuoteParseOutput{ + Parsed: parsed, + Remaining: remaining, + Quoted: quoted, + }, "Failed with input `%s`", test.Input) + // Note: can't just test that requoted == input, because some inputs + // have unnecessary escapes which won't survive roundtripping + t.Run("roundtrip", func(t *testing.T) { + requoted := quoteString(parsed) + " " + remaining + reparsed, newRemaining, _ := parseQuoted(requoted) + assert.Equal(t, parsed, reparsed) + assert.Equal(t, remaining, newRemaining) + }) + }) + } +} diff --git a/event/botcommandtestdata/data.go b/event/botcommandtestdata/data.go new file mode 100644 index 00000000..14119b97 --- /dev/null +++ b/event/botcommandtestdata/data.go @@ -0,0 +1,14 @@ +// Copyright (c) 2026 Tulir Asokan +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package botcommandtestdata + +import ( + "embed" +) + +//go:embed * +var FS embed.FS diff --git a/event/botcommandtestdata/parse_quote.json b/event/botcommandtestdata/parse_quote.json new file mode 100644 index 00000000..dd2e17c5 --- /dev/null +++ b/event/botcommandtestdata/parse_quote.json @@ -0,0 +1,20 @@ +[ + ["single word", "meow", ["meow", "", false]], + ["two words", "meow woof", ["meow", "woof", false]], + ["many words", "meow meow mrrp", ["meow", "meow mrrp", false]], + ["extra spaces", "meow meow mrrp", ["meow", "meow mrrp", false]], + ["trailing space", "meow ", ["meow", "", false]], + ["quoted word", "\"meow\" meow mrrp", ["meow", "meow mrrp", true]], + ["quoted words", "\"meow meow\" mrrp", ["meow meow", "mrrp", true]], + ["spaces in quotes", "\" meow meow \" mrrp", [" meow meow ", "mrrp", true]], + ["quotes after word", "meow \" meow mrrp \"", ["meow", "\" meow mrrp \"", false]], + ["escaped quote", "\"meow\\\" meow\" mrrp", ["meow\" meow", "mrrp", true]], + ["missing end quote", "\"meow meow mrrp", ["meow meow mrrp", "", true]], + ["missing end quote with escaped quote", "\"meow\\\" meow mrrp", ["meow\" meow mrrp", "", true]], + ["quote in the middle", "me\"ow meow mrrp", ["me\"ow", "meow mrrp", false]], + ["backslash in the middle", "me\\ow meow mrrp", ["me\\ow", "meow mrrp", false]], + ["other escaped character", "\"m\\eow\" meow mrrp", ["meow", "meow mrrp", true]], + ["escaped backslashes", "\"m\\\\e\\\"ow\\\\\" meow mrrp", ["m\\e\"ow\\", "meow mrrp", true]], + ["just quotes", "\"\\\"\\\"\\\\\\\"\" meow", ["\"\"\\\"", "meow", true]], + ["eof escape", "\"meow\\", ["meow\\", "", true]] +]