1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458
|
package client
import (
"testing"
"time"
"github.com/fluffle/goirc/state"
"github.com/golang/mock/gomock"
)
// This test performs a simple end-to-end verification of correct line parsing
// and event dispatch as well as testing the PING handler. All the other tests
// in this file will call their respective handlers synchronously, otherwise
// testing becomes more difficult.
func TestPING(t *testing.T) {
_, s := setUp(t)
defer s.tearDown()
s.nc.Send("PING :1234567890")
s.nc.Expect("PONG :1234567890")
}
// Test the REGISTER handler matches section 3.1 of rfc2812
func TestREGISTER(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
c.h_REGISTER(&Line{Cmd: REGISTER})
s.nc.Expect("NICK test")
s.nc.Expect("USER test 12 * :Testing IRC")
s.nc.ExpectNothing()
c.cfg.Pass = "12345"
c.cfg.Me.Ident = "idiot"
c.cfg.Me.Name = "I've got the same combination on my luggage!"
c.h_REGISTER(&Line{Cmd: REGISTER})
s.nc.Expect("PASS 12345")
s.nc.Expect("NICK test")
s.nc.Expect("USER idiot 12 * :I've got the same combination on my luggage!")
s.nc.ExpectNothing()
}
// Test the handler for 001 / RPL_WELCOME
func Test001(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
l := ParseLine(":irc.server.org 001 newnick :Welcome to IRC newnick!ident@somehost.com")
// Set up a handler to detect whether connected handler is called from 001
hcon := false
c.HandleFunc("connected", func(conn *Conn, line *Line) {
hcon = true
})
// Test state tracking first.
gomock.InOrder(
s.st.EXPECT().Me().Return(c.cfg.Me),
s.st.EXPECT().NickInfo("test", "ident", "somehost.com", "Testing IRC"),
s.st.EXPECT().ReNick("test", "newnick").Return(&state.Nick{
Nick: "newnick",
Ident: c.cfg.Me.Ident,
Name: c.cfg.Me.Name,
}),
)
// Call handler with a valid 001 line
c.h_001(l)
<-time.After(time.Millisecond)
if !hcon {
t.Errorf("001 handler did not dispatch connected event.")
}
// Now without state tracking.
c.st = nil
c.h_001(l)
// Check host parsed correctly
if c.cfg.Me.Host != "somehost.com" {
t.Errorf("Host parsing failed, host is '%s'.", c.cfg.Me.Host)
}
c.st = s.st
}
// Test the handler for 433 / ERR_NICKNAMEINUSE
func Test433(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
// Call handler with a 433 line, not triggering c.cfg.Me.Renick()
s.st.EXPECT().Me().Return(c.cfg.Me)
c.h_433(ParseLine(":irc.server.org 433 test new :Nickname is already in use."))
s.nc.Expect("NICK " + DefaultNewNick("new"))
// Send a line that will trigger a renick. This happens when our wanted
// nick is unavailable during initial negotiation, so we must choose a
// different one before the connection can proceed. No NICK line will be
// sent by the server to confirm nick change in this case.
want := DefaultNewNick(c.cfg.Me.Nick)
gomock.InOrder(
s.st.EXPECT().Me().Return(c.cfg.Me),
s.st.EXPECT().ReNick("test", want).Return(c.cfg.Me),
)
c.h_433(ParseLine(":irc.server.org 433 test test :Nickname is already in use."))
s.nc.Expect("NICK " + want)
// Test the code path that *doesn't* involve state tracking.
c.st = nil
c.h_433(ParseLine(":irc.server.org 433 test test :Nickname is already in use."))
s.nc.Expect("NICK " + want)
if c.cfg.Me.Nick != want {
t.Errorf("My nick not updated from '%s'.", c.cfg.Me.Nick)
}
c.st = s.st
}
// Test the handler for NICK messages when state tracking is disabled
func TestNICK(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
// State tracking is enabled by default in setUp
c.st = nil
// Call handler with a NICK line changing "our" nick to test1.
c.h_NICK(ParseLine(":test!test@somehost.com NICK :test1"))
// Verify that our Nick has changed
if c.cfg.Me.Nick != "test1" {
t.Errorf("NICK did not result in changing our nick.")
}
// Send a NICK line for something that isn't us.
c.h_NICK(ParseLine(":blah!moo@cows.com NICK :milk"))
// Verify that our Nick hasn't changed
if c.cfg.Me.Nick != "test1" {
t.Errorf("NICK did not result in changing our nick.")
}
// Re-enable state tracking and send a line that *should* change nick.
c.st = s.st
c.h_NICK(ParseLine(":test1!test@somehost.com NICK :test2"))
// Verify that our Nick hasn't changed (should be handled by h_STNICK).
if c.cfg.Me.Nick != "test1" {
t.Errorf("NICK changed our nick when state tracking enabled.")
}
}
// Test the handler for CTCP messages
func TestCTCP(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
// Call handler with CTCP VERSION
c.h_CTCP(ParseLine(":blah!moo@cows.com PRIVMSG test :\001VERSION\001"))
// Expect a version reply
s.nc.Expect("NOTICE blah :\001VERSION Powered by GoIRC\001")
// Call handler with CTCP PING
c.h_CTCP(ParseLine(":blah!moo@cows.com PRIVMSG test :\001PING 1234567890\001"))
// Expect a ping reply
s.nc.Expect("NOTICE blah :\001PING 1234567890\001")
// Call handler with CTCP UNKNOWN
c.h_CTCP(ParseLine(":blah!moo@cows.com PRIVMSG test :\001UNKNOWN ctcp\001"))
}
// Test the handler for JOIN messages
func TestJOIN(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
// The state tracker should be creating a new channel in this first test
chan1 := &state.Channel{Name: "#test1"}
gomock.InOrder(
s.st.EXPECT().GetChannel("#test1").Return(nil),
s.st.EXPECT().GetNick("test").Return(c.cfg.Me),
s.st.EXPECT().Me().Return(c.cfg.Me),
s.st.EXPECT().NewChannel("#test1").Return(chan1),
s.st.EXPECT().Associate("#test1", "test"),
)
// Use #test1 to test expected behaviour
// Call handler with JOIN by test to #test1
c.h_JOIN(ParseLine(":test!test@somehost.com JOIN :#test1"))
// Verify that the MODE and WHO commands are sent correctly
s.nc.Expect("MODE #test1")
s.nc.Expect("WHO #test1")
// In this second test, we should be creating a new nick
nick1 := &state.Nick{Nick: "user1"}
gomock.InOrder(
s.st.EXPECT().GetChannel("#test1").Return(chan1),
s.st.EXPECT().GetNick("user1").Return(nil),
s.st.EXPECT().NewNick("user1").Return(nick1),
s.st.EXPECT().NickInfo("user1", "ident1", "host1.com", "").Return(nick1),
s.st.EXPECT().Associate("#test1", "user1"),
)
// OK, now #test1 exists, JOIN another user we don't know about
c.h_JOIN(ParseLine(":user1!ident1@host1.com JOIN :#test1"))
// Verify that the WHO command is sent correctly
s.nc.Expect("WHO user1")
// In this third test, we'll be pretending we know about the nick already.
nick2 := &state.Nick{Nick: "user2"}
gomock.InOrder(
s.st.EXPECT().GetChannel("#test1").Return(chan1),
s.st.EXPECT().GetNick("user2").Return(nick2),
s.st.EXPECT().Associate("#test1", "user2"),
)
c.h_JOIN(ParseLine(":user2!ident2@host2.com JOIN :#test1"))
// Test error paths
gomock.InOrder(
// unknown channel, unknown nick
s.st.EXPECT().GetChannel("#test2").Return(nil),
s.st.EXPECT().GetNick("blah").Return(nil),
s.st.EXPECT().Me().Return(c.cfg.Me),
// unknown channel, known nick that isn't Me.
s.st.EXPECT().GetChannel("#test2").Return(nil),
s.st.EXPECT().GetNick("user2").Return(nick2),
s.st.EXPECT().Me().Return(c.cfg.Me),
)
c.h_JOIN(ParseLine(":blah!moo@cows.com JOIN :#test2"))
c.h_JOIN(ParseLine(":user2!ident2@host2.com JOIN :#test2"))
}
// Test the handler for PART messages
func TestPART(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
// PART should dissociate a nick from a channel.
s.st.EXPECT().Dissociate("#test1", "user1")
c.h_PART(ParseLine(":user1!ident1@host1.com PART #test1 :Bye!"))
}
// Test the handler for KICK messages
// (this is very similar to the PART message test)
func TestKICK(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
// KICK should dissociate a nick from a channel.
s.st.EXPECT().Dissociate("#test1", "user1")
c.h_KICK(ParseLine(":test!test@somehost.com KICK #test1 user1 :Bye!"))
}
// Test the handler for QUIT messages
func TestQUIT(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
// Have user1 QUIT. All possible errors handled by state tracker \o/
s.st.EXPECT().DelNick("user1")
c.h_QUIT(ParseLine(":user1!ident1@host1.com QUIT :Bye!"))
}
// Test the handler for MODE messages
func TestMODE(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
// Channel modes
gomock.InOrder(
s.st.EXPECT().GetChannel("#test1").Return(&state.Channel{Name: "#test1"}),
s.st.EXPECT().ChannelModes("#test1", "+sk", "somekey"),
)
c.h_MODE(ParseLine(":user1!ident1@host1.com MODE #test1 +sk somekey"))
// Nick modes for Me.
gomock.InOrder(
s.st.EXPECT().GetChannel("test").Return(nil),
s.st.EXPECT().GetNick("test").Return(c.cfg.Me),
s.st.EXPECT().Me().Return(c.cfg.Me),
s.st.EXPECT().NickModes("test", "+i"),
)
c.h_MODE(ParseLine(":test!test@somehost.com MODE test +i"))
// Check error paths
gomock.InOrder(
// send a valid user mode that's not us
s.st.EXPECT().GetChannel("user1").Return(nil),
s.st.EXPECT().GetNick("user1").Return(&state.Nick{Nick: "user1"}),
s.st.EXPECT().Me().Return(c.cfg.Me),
// Send a random mode for an unknown channel
s.st.EXPECT().GetChannel("#test2").Return(nil),
s.st.EXPECT().GetNick("#test2").Return(nil),
)
c.h_MODE(ParseLine(":user1!ident1@host1.com MODE user1 +w"))
c.h_MODE(ParseLine(":user1!ident1@host1.com MODE #test2 +is"))
}
// Test the handler for TOPIC messages
func TestTOPIC(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
// Ensure TOPIC reply calls Topic
gomock.InOrder(
s.st.EXPECT().GetChannel("#test1").Return(&state.Channel{Name: "#test1"}),
s.st.EXPECT().Topic("#test1", "something something"),
)
c.h_TOPIC(ParseLine(":user1!ident1@host1.com TOPIC #test1 :something something"))
// Check error paths -- send a topic for an unknown channel
s.st.EXPECT().GetChannel("#test2").Return(nil)
c.h_TOPIC(ParseLine(":user1!ident1@host1.com TOPIC #test2 :dark side"))
}
// Test the handler for 311 / RPL_WHOISUSER
func Test311(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
// Ensure 311 reply calls NickInfo
gomock.InOrder(
s.st.EXPECT().GetNick("user1").Return(&state.Nick{Nick: "user1"}),
s.st.EXPECT().Me().Return(c.cfg.Me),
s.st.EXPECT().NickInfo("user1", "ident1", "host1.com", "name"),
)
c.h_311(ParseLine(":irc.server.org 311 test user1 ident1 host1.com * :name"))
// Check error paths -- send a 311 for an unknown nick
s.st.EXPECT().GetNick("user2").Return(nil)
c.h_311(ParseLine(":irc.server.org 311 test user2 ident2 host2.com * :dongs"))
}
// Test the handler for 324 / RPL_CHANNELMODEIS
func Test324(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
// Ensure 324 reply calls ChannelModes
gomock.InOrder(
s.st.EXPECT().GetChannel("#test1").Return(&state.Channel{Name: "#test1"}),
s.st.EXPECT().ChannelModes("#test1", "+sk", "somekey"),
)
c.h_324(ParseLine(":irc.server.org 324 test #test1 +sk somekey"))
// Check error paths -- send 324 for an unknown channel
s.st.EXPECT().GetChannel("#test2").Return(nil)
c.h_324(ParseLine(":irc.server.org 324 test #test2 +pmt"))
}
// Test the handler for 332 / RPL_TOPIC
func Test332(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
// Ensure 332 reply calls Topic
gomock.InOrder(
s.st.EXPECT().GetChannel("#test1").Return(&state.Channel{Name: "#test1"}),
s.st.EXPECT().Topic("#test1", "something something"),
)
c.h_332(ParseLine(":irc.server.org 332 test #test1 :something something"))
// Check error paths -- send 332 for an unknown channel
s.st.EXPECT().GetChannel("#test2").Return(nil)
c.h_332(ParseLine(":irc.server.org 332 test #test2 :dark side"))
}
// Test the handler for 352 / RPL_WHOREPLY
func Test352(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
// Ensure 352 reply calls NickInfo and NickModes
gomock.InOrder(
s.st.EXPECT().GetNick("user1").Return(&state.Nick{Nick: "user1"}),
s.st.EXPECT().Me().Return(c.cfg.Me),
s.st.EXPECT().NickInfo("user1", "ident1", "host1.com", "name"),
)
c.h_352(ParseLine(":irc.server.org 352 test #test1 ident1 host1.com irc.server.org user1 G :0 name"))
// Check that modes are set correctly from WHOREPLY
gomock.InOrder(
s.st.EXPECT().GetNick("user1").Return(&state.Nick{Nick: "user1"}),
s.st.EXPECT().Me().Return(c.cfg.Me),
s.st.EXPECT().NickInfo("user1", "ident1", "host1.com", "name"),
s.st.EXPECT().NickModes("user1", "+o"),
s.st.EXPECT().NickModes("user1", "+i"),
)
c.h_352(ParseLine(":irc.server.org 352 test #test1 ident1 host1.com irc.server.org user1 H* :0 name"))
// Check error paths -- send a 352 for an unknown nick
s.st.EXPECT().GetNick("user2").Return(nil)
c.h_352(ParseLine(":irc.server.org 352 test #test2 ident2 host2.com irc.server.org user2 G :0 fooo"))
}
// Test the handler for 353 / RPL_NAMREPLY
func Test353(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
// 353 handler is called twice, so GetChannel will be called twice
s.st.EXPECT().GetChannel("#test1").Return(&state.Channel{Name: "#test1"}).Times(2)
gomock.InOrder(
// "test" is Me, i am known, and already on the channel
s.st.EXPECT().GetNick("test").Return(c.cfg.Me),
s.st.EXPECT().IsOn("#test1", "test").Return(&state.ChanPrivs{}, true),
// user1 is known, but not on the channel, so should be associated
s.st.EXPECT().GetNick("user1").Return(&state.Nick{Nick: "user1"}),
s.st.EXPECT().IsOn("#test1", "user1").Return(nil, false),
s.st.EXPECT().Associate("#test1", "user1").Return(&state.ChanPrivs{}),
s.st.EXPECT().ChannelModes("#test1", "+o", "user1"),
)
for n, m := range map[string]string{
"user2": "",
"voice": "+v",
"halfop": "+h",
"op": "+o",
"admin": "+a",
"owner": "+q",
} {
calls := []*gomock.Call{
s.st.EXPECT().GetNick(n).Return(nil),
s.st.EXPECT().NewNick(n).Return(&state.Nick{Nick: n}),
s.st.EXPECT().IsOn("#test1", n).Return(nil, false),
s.st.EXPECT().Associate("#test1", n).Return(&state.ChanPrivs{}),
}
if m != "" {
calls = append(calls, s.st.EXPECT().ChannelModes("#test1", m, n))
}
gomock.InOrder(calls...)
}
// Send a couple of names replies (complete with trailing space)
c.h_353(ParseLine(":irc.server.org 353 test = #test1 :test @user1 user2 +voice "))
c.h_353(ParseLine(":irc.server.org 353 test = #test1 :%halfop @op &admin ~owner "))
// Check error paths -- send 353 for an unknown channel
s.st.EXPECT().GetChannel("#test2").Return(nil)
c.h_353(ParseLine(":irc.server.org 353 test = #test2 :test ~user3"))
}
// Test the handler for 671 (unreal specific)
func Test671(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
// Ensure 671 reply calls NickModes
gomock.InOrder(
s.st.EXPECT().GetNick("user1").Return(&state.Nick{Nick: "user1"}),
s.st.EXPECT().NickModes("user1", "+z"),
)
c.h_671(ParseLine(":irc.server.org 671 test user1 :some ignored text"))
// Check error paths -- send a 671 for an unknown nick
s.st.EXPECT().GetNick("user2").Return(nil)
c.h_671(ParseLine(":irc.server.org 671 test user2 :some ignored text"))
}
|