package main import "testing" func TestParsing(t *testing.T) { testcases := []struct { input string output *Message }{ // these inputs are based on // https://github.com/ircdocs/parser-tests/blob/master/tests/msg-split.yaml // and https://modern.ircdocs.horse/#messages { input: `:irc.example.com CAP LS * :multi-prefix extended-join sasl`, output: &Message{ Source: "irc.example.com", Command: CAP, Parameters: []string{"LS", "*", "multi-prefix extended-join sasl"}, }}, { input: `@id=234AB :dan!d@localhost PRIVMSG #chan :Hey what's up!`, output: &Message{ Tags: []Tag{{"id", "234AB"}}, Source: "dan!d@localhost", Command: PRIVMSG, Parameters: []string{"#chan", "Hey what's up!"}, }}, { input: `@a=b;c=32;k;rt=ql7 :dan!d@localhost PRIVMSG #chan :Hey what's up!`, output: &Message{ Tags: []Tag{{"a", "b"}, {"c", "32"}, {"k", ""}, {"rt", "ql7"}}, Source: "dan!d@localhost", Command: PRIVMSG, Parameters: []string{"#chan", "Hey what's up!"}, }}, { input: `@a=b\\and\nk;c=72\s45;d=gh\:764 CAP`, output: &Message{ Tags: []Tag{{"a", "b\\and\nk"}, {"c", "72 45"}, {"d", `gh;764`}}, Command: CAP, }}, { input: `@tag1=value1;tag2;vendor1/tag3=value2;vendor2/tag4= :dan!d@localhost PRIVMSG #chan :Hey what's up!`, output: &Message{ Tags: []Tag{{"tag1", "value1"}, {"tag2", ""}, {"vendor1/tag3", "value2"}, {"vendor2/tag4", ""}}, Source: "dan!d@localhost", Command: PRIVMSG, Parameters: []string{"#chan", "Hey what's up!"}, }}, { input: ":src AWAY", output: &Message{ Source: "src", Command: AWAY, }}, { input: ":src AWAY ", output: &Message{ Source: "src", Command: AWAY, }}, { input: `CAP REQ :sasl`, output: &Message{ Command: CAP, Parameters: []string{"REQ", "sasl"}, }}, { input: `CAP REQ :`, output: &Message{ Command: CAP, Parameters: []string{"REQ", ""}, }}, { input: `CAP REQ ::asdf`, output: &Message{ Command: CAP, Parameters: []string{"REQ", ":asdf"}, }}, { input: `CAP REQ : asdf qwer`, output: &Message{ Command: CAP, Parameters: []string{"REQ", " asdf qwer"}, }}, { input: ":coolguy!ag@net\x035w\x03ork.admin PRIVMSG foo :bar baz", output: &Message{ Source: "coolguy!ag@net\x035w\x03ork.admin", Command: PRIVMSG, Parameters: []string{"foo", "bar baz"}, }}, { input: `:coolguy PRIVMSG bar :lol :) `, output: &Message{ Source: "coolguy", Command: PRIVMSG, Parameters: []string{"bar", "lol :) "}, }}, { input: `:gravel.mozilla.org 432 #momo :Erroneous Nickname: Illegal characters`, output: &Message{ Source: "gravel.mozilla.org", Command: ERR_ERRONEUSNICKNAME, Parameters: []string{"#momo", "Erroneous Nickname: Illegal characters"}, }}, { input: `:gravel.mozilla.org MODE #tckk +n `, output: &Message{ Source: "gravel.mozilla.org", Command: MODE, Parameters: []string{"#tckk", "+n"}, }}, { input: ":services.esper.net MODE #foo-bar +o foobar ", output: &Message{ Source: "services.esper.net", Command: MODE, Parameters: []string{"#foo-bar", "+o", "foobar"}, }}, { input: `@tag1=value\\ntest COMMAND`, output: &Message{ Tags: []Tag{{"tag1", `value\ntest`}}, Command: command("COMMAND"), }}, { input: `@tag1=value\1 COMMAND`, output: &Message{ Tags: []Tag{{"tag1", `value1`}}, Command: command("COMMAND"), }}, { input: `@tag1=value\ COMMAND`, output: &Message{ Tags: []Tag{{"tag1", `value`}}, Command: command("COMMAND"), }}, { input: `@tag1=1;tag2=3;tag3=4;tag1=5 COMMAND`, output: &Message{ Tags: []Tag{{"tag1", "5"}, {"tag2", "3"}, {"tag3", "4"}}, Command: command("COMMAND"), }}, { input: `@tag1=1;tag2=3;tag3=4;tag1=5;vendor/tag2=8 COMMAND`, output: &Message{ Tags: []Tag{{"tag1", "5"}, {"tag2", "3"}, {"tag3", "4"}, {"vendor/tag2", "8"}}, Command: command("COMMAND"), }}, { input: `:SomeOp MODE #channel :+i`, output: &Message{ Source: "SomeOp", Command: MODE, Parameters: []string{"#channel", "+i"}, }}, { input: `:SomeOp MODE #channel +oo SomeUser :AnotherUser`, output: &Message{ Source: "SomeOp", Command: MODE, Parameters: []string{"#channel", "+oo", "SomeUser", "AnotherUser"}, }}, } for _, tc := range testcases { m := &Message{Raw: tc.input} m.ParseMessage() if len(tc.output.Tags) != len(m.Tags) { t.Logf("tags: actual: %v, expected: %v", m.Tags, tc.output.Tags) t.Fail() } for i := range tc.output.Tags { if tc.output.Tags[i] != m.Tags[i] { t.Logf("tags: actual: %+v, expected: %+v", m.Tags[i], tc.output.Tags[i]) t.Fail() } } if tc.output.Source != m.Source { t.Logf("source: actual: %v, expected: %v", m.Source, tc.output.Source) t.Fail() } if tc.output.Command != m.Command { t.Logf("command: actual: %v, expected: %v", m.Command, tc.output.Command) t.Fail() } if len(tc.output.Parameters) != len(m.Parameters) { t.Logf("parameters: actual: %v (%d), expected: %v (%d)", m.Parameters, len(m.Parameters), tc.output.Parameters, len(tc.output.Parameters)) t.Fail() } for i := range tc.output.Parameters { if tc.output.Parameters[i] != m.Parameters[i] { t.Logf("parameters: actual: %v, expected: %v", m.Parameters[i], tc.output.Parameters[i]) t.Fail() } } } } func TestCanonicalization(t *testing.T) { testcases := []struct { input *Message output string }{ // these outputs are based on // https://github.com/ircdocs/parser-tests/blob/master/tests/msg-split.yaml // and https://modern.ircdocs.horse/#messages { input: &Message{ Source: "irc.example.com", Command: CAP, Parameters: []string{"LS", "*", "multi-prefix extended-join sasl"}, }, output: `:irc.example.com CAP LS * :multi-prefix extended-join sasl`, }, { input: &Message{ Tags: []Tag{{"id", "234AB"}}, Source: "dan!d@localhost", Command: PRIVMSG, Parameters: []string{"#chan", "Hey what's up!"}, }, output: `@id=234AB :dan!d@localhost PRIVMSG #chan :Hey what's up!`, }, { input: &Message{ Tags: []Tag{{"a", "b"}, {"c", "32"}, {"k", ""}, {"rt", "ql7"}}, Source: "dan!d@localhost", Command: PRIVMSG, Parameters: []string{"#chan", "Hey what's up!"}, }, output: `@a=b;c=32;k;rt=ql7 :dan!d@localhost PRIVMSG #chan :Hey what's up!`, }, { input: &Message{ Tags: []Tag{{"a", "b\\and\nk"}, {"c", "72 45"}, {"d", `gh;764`}}, Command: CAP, }, output: `@a=b\\and\nk;c=72\s45;d=gh\:764 CAP`, }, { input: &Message{ Tags: []Tag{{"tag1", "value1"}, {"tag2", ""}, {"vendor1/tag3", "value2"}, {"vendor2/tag4", ""}}, Source: "dan!d@localhost", Command: PRIVMSG, Parameters: []string{"#chan", "Hey what's up!"}, }, output: `@tag1=value1;tag2;vendor1/tag3=value2;vendor2/tag4 :dan!d@localhost PRIVMSG #chan :Hey what's up!`, }, { input: &Message{ Source: "src", Command: AWAY, }, output: ":src AWAY", }, { input: &Message{ Command: CAP, Parameters: []string{"REQ", "sasl"}, }, output: `CAP REQ sasl`, }, { input: &Message{ Command: CAP, Parameters: []string{"REQ", ""}, }, output: `CAP REQ :`, }, { input: &Message{ Command: CAP, Parameters: []string{"REQ", ":asdf"}, }, output: `CAP REQ ::asdf`, }, { input: &Message{ Command: CAP, Parameters: []string{"REQ", " asdf qwer"}, }, output: `CAP REQ : asdf qwer`, }, { input: &Message{ Source: "coolguy", Command: PRIVMSG, Parameters: []string{"bar", "lol :) "}, }, output: `:coolguy PRIVMSG bar :lol :) `, }, { input: &Message{ Source: "coolguy!ag@net\x035w\x03ork.admin", Command: PRIVMSG, Parameters: []string{"foo", "bar baz"}, }, output: ":coolguy!ag@net\x035w\x03ork.admin PRIVMSG foo :bar baz", }, { input: &Message{ Tags: []Tag{{"tag1", `value\ntest`}}, Command: command("COMMAND"), }, output: `@tag1=value\\ntest COMMAND`, }, } for _, tc := range testcases { output := string(tc.input.Canonicalize()) if output != tc.output { t.Logf("received '%s', expected '%s'", output, tc.output) t.Fail() } } }