diff --git a/parser/parser.go b/parser/parser.go index eeb5120..7468fb3 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -17,6 +17,7 @@ package parser import ( + "regexp" "strings" ) @@ -52,13 +53,16 @@ func parseTags(rawTags string) Tags { func ParseIRCMessage(line string) IRCMessage { ircMessage := IRCMessage{} + multipleSpacesRegex := regexp.MustCompile("[^\\S\\t]+") + trailingParamRegex := regexp.MustCompile("[^\\S\\t]+:") + whitespaceRegex := regexp.MustCompile("^[^\\S\\t]+$") if len(line) <= 0 { return IRCMessage{} } if line[0] == '@' { - substrs := strings.SplitN(line, " ", 2) + substrs := multipleSpacesRegex.Split(line, 2) tags := parseTags(substrs[0][1:]) ircMessage.Tags = tags @@ -66,24 +70,25 @@ func ParseIRCMessage(line string) IRCMessage { } if line[0] == ':' { - substrs := strings.SplitN(line, " ", 2) - ircMessage.Source = substrs[0] + substrs := multipleSpacesRegex.Split(line, 2) + ircMessage.Source = substrs[0][1:] line = substrs[1] } - substrs := strings.SplitN(line, " ", 2) + substrs := multipleSpacesRegex.Split(line, 2) + // TODO: return error(??) if the irc message is malformed (i.e doesn't contain command) ircMessage.Command = substrs[0] - // if parameters exist - if len(substrs) > 1 { - params := strings.SplitN(substrs[1], " :", 2) + // if parameters exist and it's not an empty string and it's not whitespace without ":" + if len(substrs) > 1 && len(substrs[1]) != 0 && !whitespaceRegex.MatchString(substrs[1]) { + params := trailingParamRegex.Split(substrs[1], 2) - parameters := strings.Split(params[0], " ") - // TODO: parameters can be empty. this is denoted by a ":" + parameters := multipleSpacesRegex.Split(params[0], -1) // if there's a trailing parameter + // this parses empty params as well if len(params) > 1 { parameters = append(parameters, params[1]) } diff --git a/parser/parser_test.go b/parser/parser_test.go new file mode 100644 index 0000000..41281a9 --- /dev/null +++ b/parser/parser_test.go @@ -0,0 +1,806 @@ +// gorc project +// Copyright (C) 2021 IllusionMan1212 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see https://www.gnu.org/licenses. + +package parser + +import "testing" + +// tests provided by https://github.com/ircdocs/parser-tests/blob/master/tests/msg-split.yaml + +func TestParser(t *testing.T) { + t.Run("Test simple command", func(t *testing.T) { + testMessage := "foo bar baz asdf" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Command != "foo" { + t.Fatal("Parsing message with only command and params") + } + + if ircMessage.Parameters[0] != "bar" { + t.Fatal("Parsing message with only command and params (1st param)") + } + + if ircMessage.Parameters[1] != "baz" { + t.Fatal("Parsing message with only command and params (2nd param)") + } + + if ircMessage.Parameters[2] != "asdf" { + t.Fatal("Parsing message with only command and params (3rd param)") + } + }) + + t.Run("Test source", func(t *testing.T) { + testMessage := ":coolguy foo bar baz asdf" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Source != "coolguy" { + t.Fatal("Parsing regular source") + } + + if ircMessage.Command != "foo" { + t.Fatal("Parsing command with source") + } + + if ircMessage.Parameters[0] != "bar" { + t.Fatal("Parsing 1st param of regular message") + } + + if ircMessage.Parameters[1] != "baz" { + t.Fatal("Parsing 2nd param of regular message") + } + + if ircMessage.Parameters[2] != "asdf" { + t.Fatal("Parsing 3rd param of regular message") + } + }) + + t.Run("Test trailing param", func(t *testing.T) { + testMessage := "foo bar baz :asdf quux" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Parameters[0] != "bar" { + t.Fatal("Parsing 1st param in message with trailing param") + } + + if ircMessage.Parameters[1] != "baz" { + t.Fatal("Parsing 2nd param in message with trailing param") + } + + if ircMessage.Parameters[2] != "asdf quux" { + t.Fatal("Parsing trailing param") + } + }) + + t.Run("Test empty trailing param", func(t *testing.T) { + testMessage := "foo bar baz :" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Parameters[0] != "bar" { + t.Fatal("Parsing 1st param in message with trailing param") + } + + if ircMessage.Parameters[1] != "baz" { + t.Fatal("Parsing 2nd param in message with trailing param") + } + + if ircMessage.Parameters[2] != "" { + t.Fatal("Parsing trailing param") + } + }) + + t.Run("Test multiple colons at trailing param", func(t *testing.T) { + testMessage := "foo bar baz ::asdf" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Parameters[0] != "bar" { + t.Fatal("Parsing 1st param in message with trailing param") + } + + if ircMessage.Parameters[1] != "baz" { + t.Fatal("Parsing 2nd param in message with trailing param") + } + + if ircMessage.Parameters[2] != ":asdf" { + t.Fatal("Parsing trailing param") + } + }) + + t.Run("Test source and trailing param", func(t *testing.T) { + testMessage := ":coolguy foo bar baz :asdf quux" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Source != "coolguy" { + t.Fatal("Parsing source") + } + + if ircMessage.Command != "foo" { + t.Fatal("Parsing command") + } + + if ircMessage.Parameters[0] != "bar" { + t.Fatal("Parsing 1st param") + } + + if ircMessage.Parameters[1] != "baz" { + t.Fatal("Parsing 2nd param") + } + + if ircMessage.Parameters[2] != "asdf quux" { + t.Fatal("Parsing trailing param") + } + }) + + t.Run("Test source and trailing param with spaces", func(t *testing.T) { + testMessage := ":coolguy foo bar baz : asdf quux " + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Source != "coolguy" { + t.Fatal("Parsing source") + } + + if ircMessage.Command != "foo" { + t.Fatal("Parsing command") + } + + if ircMessage.Parameters[0] != "bar" { + t.Fatal("Parsing 1st param") + } + + if ircMessage.Parameters[1] != "baz" { + t.Fatal("Parsing 2nd param") + } + + if ircMessage.Parameters[2] != " asdf quux " { + t.Fatal("Parsing trailing param") + } + }) + + t.Run("Test source and trailing param 2", func(t *testing.T) { + testMessage := ":coolguy PRIVMSG bar :lol :) " + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Source != "coolguy" { + t.Fatal("Parsing source") + } + + if ircMessage.Command != "PRIVMSG" { + t.Fatal("Parsing command") + } + + if ircMessage.Parameters[0] != "bar" { + t.Fatal("Parsing 1st param") + } + + if ircMessage.Parameters[1] != "lol :) " { + t.Fatal("Parsing trailing param") + } + }) + + t.Run("Test source and empty trailing param", func(t *testing.T) { + testMessage := ":coolguy foo bar baz :" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Source != "coolguy" { + t.Fatal("Parsing source") + } + + if ircMessage.Command != "foo" { + t.Fatal("Parsing command") + } + + if ircMessage.Parameters[0] != "bar" { + t.Fatal("Parsing 1st param") + } + + if ircMessage.Parameters[1] != "baz" { + t.Fatal("Parsing 2nd param") + } + + if ircMessage.Parameters[2] != "" { + t.Fatal("Parsing trailing param") + } + }) + + t.Run("Test source and trailing param with spaces 2", func(t *testing.T) { + testMessage := ":coolguy foo bar baz : " + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Source != "coolguy" { + t.Fatal("Parsing source") + } + + if ircMessage.Command != "foo" { + t.Fatal("Parsing command") + } + + if ircMessage.Parameters[0] != "bar" { + t.Fatal("Parsing 1st param") + } + + if ircMessage.Parameters[1] != "baz" { + t.Fatal("Parsing 2nd param") + } + + if ircMessage.Parameters[2] != " " { + t.Fatal("Parsing trailing param") + } + }) + + t.Run("Test tags", func(t *testing.T) { + testMessage := "@id=234AB;rose :dan!d@localhost PRIVMSG #chan :Hey what's up!" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Tags["id"] != "234AB" { + t.Fatal("Parsing regular tag") + } + + if ircMessage.Tags["rose"] != "" { + t.Fatal("Parsing value-less tag") + } + + if ircMessage.Source != "dan!d@localhost" { + t.Fatal("Parsing source") + } + + if ircMessage.Command != "PRIVMSG" { + t.Fatal("Parsing command") + } + + if ircMessage.Parameters[0] != "#chan" { + t.Fatal("Parsing 1st param") + } + + if ircMessage.Parameters[1] != "Hey what's up!" { + t.Fatal("Parsing 2nd param") + } + }) + + t.Run("Test tags 2", func(t *testing.T) { + testMessage := "@a=b;c=32;k;rt=q17 foo" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Tags["a"] != "b" { + t.Fatal("Parsing regular tag") + } + + if ircMessage.Tags["c"] != "32" { + t.Fatal("Parsing regular tag") + } + + if ircMessage.Tags["k"] != "" { + t.Fatal("Parsing value-less tag") + } + + if ircMessage.Tags["rt"] != "q17" { + t.Fatal("Parsing regular tag") + } + + if ircMessage.Command != "foo" { + t.Fatal("Parsing command") + } + }) + + // TODO: fails + t.Run("Test escaped tags", func(t *testing.T) { + testMessage := "@a=b\\\\and\\nk;c=72\\s45;d=gh\\:764 foo" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Tags["a"] != "b\\and\nk" { + t.Fatal("Parsing escaped tag") + } + + if ircMessage.Tags["c"] != "72 45" { + t.Fatal("Parsing regular tag") + } + + if ircMessage.Tags["d"] != "gh;764" { + t.Fatal("Parsing escaped tag") + } + + if ircMessage.Command != "foo" { + t.Fatal("Parsing command") + } + }) + + t.Run("Test tags with source", func(t *testing.T) { + testMessage := "@c;h=;a=b :quux ab cd" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Tags["c"] != "" { + t.Log(ircMessage.Tags) + t.Fatal("Parsing value-less tag") + } + + if ircMessage.Tags["h"] != "" { + t.Fatal("Parsing value-less tag") + } + + if ircMessage.Tags["a"] != "b" { + t.Fatal("Parsing regular tag") + } + + if ircMessage.Source != "quux" { + t.Fatal("Parsing source") + } + + if ircMessage.Command != "ab" { + t.Fatal("Parsing command") + } + + if ircMessage.Parameters[0] != "cd" { + t.Fatal("Parsing param") + } + }) + + // TODO: FAIL + t.Run("Test single param with preceeding :", func(t *testing.T) { + testMessage := ":src JOIN :#chan" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Parameters[0] != "#chan" { + t.Fatal("Parsing a single param with a preceeding \":\"") + } + }) + + t.Run("Test with no space as last param", func(t *testing.T) { + testMessage := ":src AWAY" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Source != "src" { + t.Fatal("Parsing source") + } + + if ircMessage.Command != "AWAY" { + t.Fatal("Parsing command") + } + + if len(ircMessage.Parameters) > 0 { + t.Fatal("Parsing params") + } + }) + + t.Run("Test with space as last param", func(t *testing.T) { + testMessage := ":src AWAY " + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Source != "src" { + t.Fatal("Parsing source") + } + + if ircMessage.Command != "AWAY" { + t.Fatal("Parsing command") + } + + if len(ircMessage.Parameters) > 0 { + t.Logf("'%v'", ircMessage.Parameters[0]) + t.Fatal("Parsing params") + } + }) + + t.Run("Test tab as space", func(t *testing.T) { + testMessage := ":cool\tguy foo bar baz" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Source != "cool\tguy" { + t.Fatal("Parsing source") + } + + if ircMessage.Command != "foo" { + t.Fatal("Parsing command") + } + + if ircMessage.Parameters[0] != "bar" { + t.Fatal("Parsing 1st param") + } + + if ircMessage.Parameters[1] != "baz" { + t.Fatal("Parsing 2nd param") + } + }) + + t.Run("Test weird control codes in source", func(t *testing.T) { + testMessage := ":coolguy!ag@net\x035w\x03ork.admin PRIVMSG foo :bar baz" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Source != "coolguy!ag@net\x035w\x03ork.admin" { + t.Fatal("Parsing source") + } + + if ircMessage.Command != "PRIVMSG" { + t.Fatal("Parsing command") + } + + if ircMessage.Parameters[0] != "foo" { + t.Fatal("Parsing 1st param") + } + + if ircMessage.Parameters[1] != "bar baz" { + t.Fatal("Parsing trailing param") + } + }) + + t.Run("Test more weird control codes", func(t *testing.T) { + testMessage := ":coolguy!~ag@n\x02et\x0305w\x0fork.admin PRIVMSG foo :bar baz" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Source != "coolguy!~ag@n\x02et\x0305w\x0fork.admin" { + t.Fatal("Parsing source") + } + + if ircMessage.Command != "PRIVMSG" { + t.Fatal("Parsing command") + } + + if ircMessage.Parameters[0] != "foo" { + t.Fatal("Parsing 1st param") + } + + if ircMessage.Parameters[1] != "bar baz" { + t.Fatal("Parsing trailing param") + } + }) + + t.Run("Test full message", func(t *testing.T) { + testMessage := "@tag1=value1;tag2;vendor1/tag3=value2;vendor2/tag4= :irc.example.com COMMAND param1 param2 :param3 param3" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Tags["tag1"] != "value1" { + t.Fatal("Parsing regular tag") + } + + if ircMessage.Tags["tag2"] != "" { + t.Fatal("Parsing value-less tag") + } + + if ircMessage.Tags["vendor1/tag3"] != "value2" { + t.Fatal("Parsing regular tag") + } + + if ircMessage.Tags["vendor2/tag4"] != "" { + t.Fatal("Parsing value-less tag") + } + + if ircMessage.Source != "irc.example.com" { + t.Fatal("Parsing source") + } + + if ircMessage.Command != "COMMAND" { + t.Fatal("Parsing command") + } + + if ircMessage.Parameters[0] != "param1" { + t.Fatal("Parsing 1st param") + } + + if ircMessage.Parameters[1] != "param2" { + t.Fatal("Parsing 2nd param") + } + + if ircMessage.Parameters[2] != "param3 param3" { + t.Fatal("Parsing trailing param") + } + }) + + t.Run("Test message without tags", func(t *testing.T) { + testMessage := ":irc.example.com COMMAND param1 param2 :param3 param3" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Source != "irc.example.com" { + t.Fatal("Parsing source") + } + + if ircMessage.Command != "COMMAND" { + t.Fatal("Parsing command") + } + + if ircMessage.Parameters[0] != "param1" { + t.Fatal("Parsing 1st param") + } + + if ircMessage.Parameters[1] != "param2" { + t.Fatal("Parsing 2nd param") + } + + if ircMessage.Parameters[2] != "param3 param3" { + t.Fatal("Parsing trailing param") + } + }) + + t.Run("Test message without source", func(t *testing.T) { + testMessage := "@tag1=value1;tag2;vendor1/tag3=value2;vendor2/tag4= COMMAND param1 param2 :param3 param3" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Tags["tag1"] != "value1" { + t.Fatal("Parsing regular tag") + } + + if ircMessage.Tags["tag2"] != "" { + t.Fatal("Parsing value-less tag") + } + + if ircMessage.Tags["vendor1/tag3"] != "value2" { + t.Fatal("Parsing regular tag") + } + + if ircMessage.Tags["vendor2/tag4"] != "" { + t.Fatal("Parsing value-less tag") + } + + if ircMessage.Source != "" { + t.Fatal("Parsing source") + } + + if ircMessage.Command != "COMMAND" { + t.Fatal("Parsing command") + } + + if ircMessage.Parameters[0] != "param1" { + t.Fatal("Parsing 1st param") + } + + if ircMessage.Parameters[1] != "param2" { + t.Fatal("Parsing 2nd param") + } + + if ircMessage.Parameters[2] != "param3 param3" { + t.Fatal("Parsing trailing param") + } + }) + + // TODO: FAIL + t.Run("Test yaml encoding with slashes", func(t *testing.T) { + testMessage := "@foo=\\\\\\\\\\:\\\\s\\s\\r\\n COMMAND" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Tags["foo"] != "\\\\;\\s \r\n" { + t.Fatal("Parsing escaped tag") + } + + if ircMessage.Command != "COMMAND" { + t.Fatal("Parsing command") + } + }) + + t.Run("Test broken messages 1", func(t *testing.T) { + testMessage := ":gravel.mozilla.org 432 #momo :Erroneous Nickname: Illegal characters" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Source != "gravel.mozilla.org" { + t.Fatal("Parsing source") + } + + if ircMessage.Command != "432" { + t.Fatal("Parsing command") + } + + if ircMessage.Parameters[0] != "#momo" { + t.Fatal("Parsing 1st param") + } + + if ircMessage.Parameters[1] != "Erroneous Nickname: Illegal characters" { + t.Fatal("Parsing trailing param") + } + }) + + t.Run("Test broken messages 2", func(t *testing.T) { + testMessage := ":gravel.mozilla.org MODE #tckk +n " + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Source != "gravel.mozilla.org" { + t.Fatal("Parsing source") + } + + if ircMessage.Command != "MODE" { + t.Fatal("Parsing command") + } + + if ircMessage.Parameters[0] != "#tckk" { + t.Fatal("Parsing 1st param") + } + + if ircMessage.Parameters[1] != "+n" { + t.Fatal("Parsing 2nd param") + } + }) + + t.Run("Test broken messages 3", func(t *testing.T) { + testMessage := ":services.esper.net MODE #foo-bar +o foobar " + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Source != "services.esper.net" { + t.Fatal("Parsing source") + } + + if ircMessage.Command != "MODE" { + t.Fatal("Parsing command") + } + + if ircMessage.Parameters[0] != "#foo-bar" { + t.Fatal("Parsing 1st param") + } + + if ircMessage.Parameters[1] != "+o" { + t.Fatal("Parsing 2nd param") + } + + if ircMessage.Parameters[2] != "foobar" { + t.Fatal("Parsing trailing param") + } + }) + + // TODO: FAIL + t.Run("Test tags. they should be parsed char-at-a-time", func(t *testing.T) { + testMessage := "@tag1=value\\\\ntest COMMAND" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Tags["tag1"] != "value\\ntest" { + t.Fatal("Parsing escaped tag") + } + + if ircMessage.Source != "" { + t.Fatal("Parsing source") + } + + if ircMessage.Command != "COMMAND" { + t.Fatal("Parsing command") + } + }) + + // TODO: FAIl + t.Run("Test tag escape for char that doesn't need it", func(t *testing.T) { + testMessage := "@tag1=value\\1 COMMAND" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Tags["tag1"] != "value1" { + t.Fatal("Parsing escaped tag") + } + + if ircMessage.Command != "COMMAND" { + t.Fatal("Parsing command") + } + }) + + // TODO: FAIL + t.Run("Test slash at end of tag", func(t *testing.T) { + testMessage := "@tag1=value1\\ COMMAND" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Tags["tag1"] != "value1" { + t.Fatal("Parsing escaped tag") + } + + if ircMessage.Command != "COMMAND" { + t.Fatal("Parsing command") + } + }) + + t.Run("Test duplicate tags", func(t *testing.T) { + testMessage := "@tag1=1;tag2=3;tag3=4;tag1=5 COMMAND" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Tags["tag1"] != "5" { + t.Fatal("Parsing duplicate tag") + } + + if ircMessage.Tags["tag2"] != "3" { + t.Fatal("Parsing regular tag") + } + + if ircMessage.Tags["tag3"] != "4" { + t.Fatal("Parsing regular tag") + } + + if ircMessage.Command != "COMMAND" { + t.Fatal("Parsing command") + } + }) + + t.Run("Test vendored tags can have the same name as unvendored tag", func(t *testing.T) { + testMessage := "@tag1=1;tag2=3;tag3=4;tag1=5;vendor/tag2=8 COMMAND" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Tags["tag1"] != "5" { + t.Fatal("Parsing duplicate tag") + } + + if ircMessage.Tags["tag2"] != "3" { + t.Fatal("Parsing unvendored tag") + } + + if ircMessage.Tags["tag3"] != "4" { + t.Fatal("Parsing regular tag") + } + + if ircMessage.Tags["vendor/tag2"] != "8" { + t.Fatal("Parsing vendored tag") + } + + if ircMessage.Command != "COMMAND" { + t.Fatal("Parsing command") + } + }) + + t.Run("Test: some parsers handle /MODE in a special way", func(t *testing.T) { + testMessage := ":SomeOp MODE #channel :+i" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Source != "SomeOp" { + t.Fatal("Parsing source") + } + + if ircMessage.Command != "MODE" { + t.Fatal("Parsing command") + } + + if ircMessage.Parameters[0] != "#channel" { + t.Fatal("Parsing 1st param") + } + + if ircMessage.Parameters[1] != "+i" { + t.Fatal("Parsing trailing param") + } + }) + + t.Run("Test: some parsers handle /MODE in a special way 2", func(t *testing.T) { + testMessage := ":SomeOp MODE #channel +oo SomeUser :AnotherUser" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Source != "SomeOp" { + t.Fatal("Parsing source") + } + + if ircMessage.Command != "MODE" { + t.Fatal("Parsing command") + } + + if ircMessage.Parameters[0] != "#channel" { + t.Fatal("Parsing 1st param") + } + + if ircMessage.Parameters[1] != "+oo" { + t.Fatal("Parsing 2nd param") + } + + if ircMessage.Parameters[2] != "SomeUser" { + t.Fatal("Parsing 3rd param") + } + + if ircMessage.Parameters[3] != "AnotherUser" { + t.Fatal("Parsing trailing param") + } + }) + + t.Run("Test multiple spaces", func(t *testing.T) { + testMessage := ":src JOIN #chan :thing lol haha" + ircMessage := ParseIRCMessage(testMessage) + + if ircMessage.Source != "src" { + t.Fatal("Parsing source with multiple spaces in between") + } + + if ircMessage.Command != "JOIN" { + t.Fatal("Parsing command with multiple spaces in between") + } + + if ircMessage.Parameters[0] != "#chan" { + t.Fatal("Parsing params with multiple spaces in between") + } + + if ircMessage.Parameters[1] != "thing lol haha" { + t.Fatal("Parsing trailing param with multiple spaces in between") + } + }) +}