package ircevent import ( "bytes" "crypto/rand" "encoding/hex" "fmt" "testing" "github.com/ergochat/irc-go/ircmsg" ) const ( multilineName = "draft/multiline" chathistoryName = "draft/chathistory" concatTag = "draft/multiline-concat" playbackCap = "draft/event-playback" ) func TestLabeledResponse(t *testing.T) { irccon := connForTesting("go-eventirc", "go-eventirc", false) irccon.Debug = true irccon.RequestCaps = []string{"message-tags", "batch", "labeled-response"} irccon.RealName = "ecf61da38b58" results := make(map[string]string) irccon.AddConnectCallback(func(e ircmsg.Message) { irccon.SendWithLabel(func(batch *Batch) { if batch == nil { return } for _, line := range batch.Items { results[line.Command] = line.Params[len(line.Params)-1] } irccon.Quit() }, nil, "WHOIS", irccon.CurrentNick()) }) err := irccon.Connect() if err != nil { t.Fatalf("labeled response connection failed: %s", err) } irccon.Loop() // RPL_WHOISUSER, last param is the realname assertEqual(results["311"], "ecf61da38b58") if _, ok := results["379"]; !ok { t.Errorf("Expected 379 RPL_WHOISMODES in response, but not received") } assertEqual(len(irccon.batches), 0) } func TestLabeledResponseNoCaps(t *testing.T) { irccon := connForTesting("go-eventirc", "go-eventirc", false) irccon.Debug = true irccon.RequestCaps = []string{"message-tags"} irccon.RealName = "ecf61da38b58" err := irccon.Connect() if err != nil { t.Fatalf("labeled response connection failed: %s", err) } go irccon.Loop() results := make(map[string]string) err = irccon.SendWithLabel(func(batch *Batch) { if batch == nil { return } for _, line := range batch.Items { results[line.Command] = line.Params[len(line.Params)-1] } irccon.Quit() }, nil, "WHOIS", irccon.CurrentNick()) if err != CapabilityNotNegotiated { t.Errorf("expected capability negotiation error, got %v", err) } assertEqual(len(irccon.batches), 0) irccon.Quit() } // test labeled single-line response, and labeled ACK func TestLabeledResponseSingleResponse(t *testing.T) { irc := connForTesting("go-eventirc", "go-eventirc", false) irc.Debug = true irc.RequestCaps = []string{"message-tags", "batch", "labeled-response"} err := irc.Connect() if err != nil { t.Fatalf("labeled response connection failed: %s", err) } go irc.Loop() channel := fmt.Sprintf("#%s", randomString()) irc.Join(channel) event := make(chan empty) err = irc.SendWithLabel(func(batch *Batch) { if !(batch != nil && batch.Command == "PONG" && batch.Params[len(batch.Params)-1] == "asdf") { t.Errorf("expected labeled PONG, got %#v", batch) } close(event) }, nil, "PING", "asdf") <-event // no-op JOIN will send labeled ACK event = make(chan empty) err = irc.SendWithLabel(func(batch *Batch) { if !(batch != nil && batch.Command == "ACK") { t.Errorf("expected labeled ACK, got %#v", batch) } close(event) }, nil, "JOIN", channel) <-event assertEqual(len(irc.batches), 0) irc.Quit() } func randomString() string { buf := make([]byte, 8) rand.Read(buf) return hex.EncodeToString(buf) } func TestNestedBatch(t *testing.T) { irc := connForTesting("go-eventirc", "go-eventirc", false) irc.Debug = true irc.RequestCaps = []string{"message-tags", "batch", "labeled-response", "server-time", multilineName, chathistoryName, playbackCap} channel := fmt.Sprintf("#%s", randomString()) err := irc.Connect() if err != nil { t.Fatalf("labeled response connection failed: %s", err) } go irc.Loop() irc.Join(channel) irc.Privmsg(channel, "hi") irc.Send("BATCH", "+123", "draft/multiline", channel) irc.SendWithTags(map[string]string{"batch": "123"}, "PRIVMSG", channel, "hello") irc.SendWithTags(map[string]string{"batch": "123"}, "PRIVMSG", channel, "") irc.SendWithTags(map[string]string{"batch": "123", concatTag: ""}, "PRIVMSG", channel, "how is ") irc.SendWithTags(map[string]string{"batch": "123"}, "PRIVMSG", channel, "everyone?") irc.Send("BATCH", "-123") var historyBatch *Batch event := make(chan empty) irc.SendWithLabel(func(batch *Batch) { historyBatch = batch close(event) }, nil, "CHATHISTORY", "LATEST", channel, "*", "10") <-event assertEqual(len(irc.labelCallbacks), 0) if historyBatch == nil { t.Errorf("received nil history batch") } // history should contain the JOIN, the PRIVMSG, and the multiline batch as a single item if !(historyBatch.Command == "BATCH" && len(historyBatch.Items) == 3) { t.Errorf("chathistory must send a real batch, got %#v", historyBatch) } var privmsg, multiline *Batch for _, item := range historyBatch.Items { switch item.Command { case "PRIVMSG": privmsg = item case "BATCH": multiline = item } } if !(privmsg.Command == "PRIVMSG" && privmsg.Params[0] == channel && privmsg.Params[1] == "hi") { t.Errorf("expected echo of individual privmsg, got %#v", privmsg) } if !(multiline.Command == "BATCH" && len(multiline.Items) == 4 && multiline.Items[3].Command == "PRIVMSG" && multiline.Items[3].Params[1] == "everyone?") { t.Errorf("expected multiline in history, got %#v\n", multiline) } assertEqual(len(irc.batches), 0) irc.Quit() } func TestBatchHandlers(t *testing.T) { alice := connForTesting("alice", "go-eventirc", false) alice.Debug = true alice.RequestCaps = []string{"message-tags", "batch", "labeled-response", "server-time", "echo-message", multilineName, chathistoryName, playbackCap} channel := fmt.Sprintf("#%s", randomString()) aliceUnderstandsBatches := true var aliceBatchCount, alicePrivmsgCount int alice.AddBatchCallback(func(batch *Batch) bool { if aliceUnderstandsBatches { aliceBatchCount++ return true } return false }) alice.AddCallback("PRIVMSG", func(e ircmsg.Message) { alicePrivmsgCount++ }) err := alice.Connect() if err != nil { t.Fatalf("labeled response connection failed: %s", err) } go alice.Loop() alice.Join(channel) synchronize(alice) bob := connForTesting("bob", "go-eventirc", false) bob.Debug = true bob.RequestCaps = []string{"message-tags", "batch", "labeled-response", "server-time", "echo-message", multilineName, chathistoryName, playbackCap} var buf bytes.Buffer bob.AddBatchCallback(func(b *Batch) bool { if !(len(b.Params) >= 3 && b.Params[1] == multilineName) { return false } for i, item := range b.Items { if item.Command == "PRIVMSG" { buf.WriteString(item.Params[1]) if !(item.HasTag(concatTag) || i == len(b.Items)-1) { buf.WriteByte('\n') } } } return true }) err = bob.Connect() if err != nil { t.Fatalf("labeled response connection failed: %s", err) } go bob.Loop() bob.Join(channel) synchronize(bob) sendMultiline := func() { alice.Send("BATCH", "+123", "draft/multiline", channel) alice.SendWithTags(map[string]string{"batch": "123"}, "PRIVMSG", channel, "hello") alice.SendWithTags(map[string]string{"batch": "123"}, "PRIVMSG", channel, "") alice.SendWithTags(map[string]string{"batch": "123", concatTag: ""}, "PRIVMSG", channel, "how is ") alice.SendWithTags(map[string]string{"batch": "123"}, "PRIVMSG", channel, "everyone?") alice.Send("BATCH", "-123") synchronize(alice) synchronize(bob) } multilineMessageValue := "hello\n\nhow is everyone?" sendMultiline() assertEqual(alicePrivmsgCount, 0) alicePrivmsgCount = 0 assertEqual(aliceBatchCount, 1) aliceBatchCount = 0 assertEqual(buf.String(), multilineMessageValue) buf.Reset() aliceUnderstandsBatches = false sendMultiline() // disabled alice's batch handler, she should see a flattened batch assertEqual(alicePrivmsgCount, 4) assertEqual(aliceBatchCount, 0) assertEqual(buf.String(), multilineMessageValue) assertEqual(len(alice.batches), 0) assertEqual(len(bob.batches), 0) alice.Quit() bob.Quit() } func synchronize(irc *Connection) { event := make(chan empty) irc.SendWithLabel(func(b *Batch) { close(event) }, nil, "PING", "!") <-event } func TestSynchronousLabeledResponse(t *testing.T) { irccon := connForTesting("go-eventirc", "go-eventirc", false) irccon.Debug = true irccon.RequestCaps = []string{"message-tags", "batch", "labeled-response"} irccon.RealName = "Al_b6AHLrxh8TZb5kNO1gw" err := irccon.Connect() if err != nil { t.Fatalf("labeled response connection failed: %s", err) } go irccon.Loop() batch, err := irccon.GetLabeledResponse(nil, "WHOIS", irccon.CurrentNick()) if err != nil { t.Fatalf("labeled response failed: %v", err) } assertEqual(batch.Command, "BATCH") results := make(map[string]string) for _, line := range batch.Items { results[line.Command] = line.Params[len(line.Params)-1] } // RPL_WHOISUSER, last param is the realname assertEqual(results["311"], "Al_b6AHLrxh8TZb5kNO1gw") if _, ok := results["379"]; !ok { t.Errorf("Expected 379 RPL_WHOISMODES in response, but not received") } assertEqual(len(irccon.batches), 0) }