mirror of
https://github.com/fluffle/goirc
synced 2025-05-12 18:44:50 +00:00
Overhaul the state tracker to return copies of data that should be correct at the time of the response. Subsequent changes to tracked IRC state will not be reflected in the copies. For people fixing up their code because this merge broke everything, you probably want to be paying particular attention to the use of conn.Me() instead of conn.cfg.Me. Sorry if this causes you hassle, it's for the best. Lastly, if kballard is watching: sorry dude, you were mostly right ;-)
This commit is contained in:
commit
0cac69d2ee
12 changed files with 958 additions and 576 deletions
|
@ -90,7 +90,7 @@ type Config struct {
|
|||
|
||||
func NewConfig(nick string, args ...string) *Config {
|
||||
cfg := &Config{
|
||||
Me: state.NewNick(nick),
|
||||
Me: &state.Nick{Nick: nick},
|
||||
PingFreq: 3 * time.Minute,
|
||||
NewNick: func(s string) string { return s + "_" },
|
||||
Recover: (*Conn).LogPanic, // in dispatch.go
|
||||
|
@ -122,7 +122,7 @@ func Client(cfg *Config) *Conn {
|
|||
cfg = NewConfig("__idiot__")
|
||||
}
|
||||
if cfg.Me == nil || cfg.Me.Nick == "" || cfg.Me.Ident == "" {
|
||||
cfg.Me = state.NewNick("__idiot__")
|
||||
cfg.Me = &state.Nick{Nick: "__idiot__"}
|
||||
cfg.Me.Ident = "goirc"
|
||||
cfg.Me.Name = "Powered by GoIRC"
|
||||
}
|
||||
|
@ -169,6 +169,11 @@ func (conn *Conn) Config() *Config {
|
|||
}
|
||||
|
||||
func (conn *Conn) Me() *state.Nick {
|
||||
conn.mu.RLock()
|
||||
defer conn.mu.RUnlock()
|
||||
if conn.st != nil {
|
||||
conn.cfg.Me = conn.st.Me()
|
||||
}
|
||||
return conn.cfg.Me
|
||||
}
|
||||
|
||||
|
@ -177,6 +182,8 @@ func (conn *Conn) StateTracker() state.Tracker {
|
|||
}
|
||||
|
||||
func (conn *Conn) EnableStateTracking() {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
if conn.st == nil {
|
||||
n := conn.cfg.Me
|
||||
conn.st = state.NewTracker(n.Nick)
|
||||
|
@ -188,7 +195,10 @@ func (conn *Conn) EnableStateTracking() {
|
|||
}
|
||||
|
||||
func (conn *Conn) DisableStateTracking() {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
if conn.st != nil {
|
||||
conn.cfg.Me = conn.st.Me()
|
||||
conn.delSTHandlers()
|
||||
conn.st.Wipe()
|
||||
conn.st = nil
|
||||
|
|
|
@ -128,20 +128,23 @@ func TestClientAndStateTracking(t *testing.T) {
|
|||
}
|
||||
|
||||
// We're expecting the untracked me to be replaced by a tracked one
|
||||
if c.st == nil {
|
||||
t.Errorf("State tracker not enabled correctly.")
|
||||
}
|
||||
if me := c.cfg.Me; me.Nick != "test" || me.Ident != "test" ||
|
||||
me.Name != "Testing IRC" || me.Host != "" {
|
||||
t.Errorf("Enabling state tracking did not replace Me correctly.")
|
||||
}
|
||||
if c.st == nil || c.cfg.Me != c.st.Me() {
|
||||
t.Errorf("State tracker not enabled correctly.")
|
||||
}
|
||||
|
||||
// Now, shim in the mock state tracker and test disabling state tracking
|
||||
me := c.cfg.Me
|
||||
c.st = st
|
||||
st.EXPECT().Wipe()
|
||||
gomock.InOrder(
|
||||
st.EXPECT().Me().Return(me),
|
||||
st.EXPECT().Wipe(),
|
||||
)
|
||||
c.DisableStateTracking()
|
||||
if c.st != nil || c.cfg.Me != me {
|
||||
if c.st != nil || !c.cfg.Me.Equals(me) {
|
||||
t.Errorf("State tracker not disabled correctly.")
|
||||
}
|
||||
|
||||
|
|
|
@ -49,7 +49,12 @@ func (conn *Conn) h_001(line *Line) {
|
|||
if idx := strings.LastIndex(t, " "); idx != -1 {
|
||||
t = t[idx+1:]
|
||||
if idx = strings.Index(t, "@"); idx != -1 {
|
||||
conn.cfg.Me.Host = t[idx+1:]
|
||||
if conn.st != nil {
|
||||
me := conn.Me()
|
||||
conn.st.NickInfo(me.Nick, me.Ident, t[idx+1:], me.Name)
|
||||
} else {
|
||||
conn.cfg.Me.Host = t[idx+1:]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -65,14 +70,15 @@ func (conn *Conn) h_001(line *Line) {
|
|||
// Handler to deal with "433 :Nickname already in use"
|
||||
func (conn *Conn) h_433(line *Line) {
|
||||
// Args[1] is the new nick we were attempting to acquire
|
||||
me := conn.Me()
|
||||
neu := conn.cfg.NewNick(line.Args[1])
|
||||
conn.Nick(neu)
|
||||
// if this is happening before we're properly connected (i.e. the nick
|
||||
// we sent in the initial NICK command is in use) we will not receive
|
||||
// a NICK message to confirm our change of nick, so ReNick here...
|
||||
if line.Args[1] == conn.cfg.Me.Nick {
|
||||
if line.Args[1] == me.Nick {
|
||||
if conn.st != nil {
|
||||
conn.st.ReNick(conn.cfg.Me.Nick, neu)
|
||||
conn.cfg.Me = conn.st.ReNick(me.Nick, neu)
|
||||
} else {
|
||||
conn.cfg.Me.Nick = neu
|
||||
}
|
||||
|
|
|
@ -50,6 +50,11 @@ func Test001(t *testing.T) {
|
|||
hcon = true
|
||||
})
|
||||
|
||||
// Test state tracking first.
|
||||
gomock.InOrder(
|
||||
s.st.EXPECT().Me().Return(c.cfg.Me),
|
||||
s.st.EXPECT().NickInfo("test", "test", "somehost.com", "Testing IRC"),
|
||||
)
|
||||
// Call handler with a valid 001 line
|
||||
c.h_001(l)
|
||||
<-time.After(time.Millisecond)
|
||||
|
@ -57,10 +62,14 @@ func Test001(t *testing.T) {
|
|||
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
|
||||
|
@ -69,29 +78,21 @@ func Test433(t *testing.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 new_")
|
||||
|
||||
// In this case, we're expecting the server to send a NICK line
|
||||
if c.cfg.Me.Nick != "test" {
|
||||
t.Errorf("ReNick() called unexpectedly, Nick == '%s'.", c.cfg.Me.Nick)
|
||||
}
|
||||
|
||||
// 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.
|
||||
s.st.EXPECT().ReNick("test", "test_")
|
||||
gomock.InOrder(
|
||||
s.st.EXPECT().Me().Return(c.cfg.Me),
|
||||
s.st.EXPECT().ReNick("test", "test_").Return(c.cfg.Me),
|
||||
)
|
||||
c.h_433(ParseLine(":irc.server.org 433 test test :Nickname is already in use."))
|
||||
s.nc.Expect("NICK test_")
|
||||
|
||||
// Counter-intuitively, c.cfg.Me.Nick will not change in this case. This
|
||||
// is an artifact of the test set-up, with a mocked out state tracker that
|
||||
// doesn't actually change any state. Normally, this would be fine :-)
|
||||
if c.cfg.Me.Nick != "test" {
|
||||
t.Errorf("My nick changed from '%s'.", c.cfg.Me.Nick)
|
||||
}
|
||||
|
||||
// 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."))
|
||||
|
@ -164,13 +165,14 @@ func TestJOIN(t *testing.T) {
|
|||
defer s.tearDown()
|
||||
|
||||
// The state tracker should be creating a new channel in this first test
|
||||
chan1 := state.NewChannel("#test1")
|
||||
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(chan1, c.cfg.Me),
|
||||
s.st.EXPECT().Associate("#test1", "test"),
|
||||
)
|
||||
|
||||
// Use #test1 to test expected behaviour
|
||||
|
@ -182,13 +184,14 @@ func TestJOIN(t *testing.T) {
|
|||
s.nc.Expect("WHO #test1")
|
||||
|
||||
// In this second test, we should be creating a new nick
|
||||
nick1 := state.NewNick("user1")
|
||||
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().Associate(chan1, 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
|
||||
|
@ -198,11 +201,11 @@ func TestJOIN(t *testing.T) {
|
|||
s.nc.Expect("WHO user1")
|
||||
|
||||
// In this third test, we'll be pretending we know about the nick already.
|
||||
nick2 := state.NewNick("user2")
|
||||
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(chan1, nick2),
|
||||
s.st.EXPECT().Associate("#test1", "user2"),
|
||||
)
|
||||
c.h_JOIN(ParseLine(":user2!ident2@host2.com JOIN :#test1"))
|
||||
|
||||
|
@ -211,9 +214,11 @@ func TestJOIN(t *testing.T) {
|
|||
// 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"))
|
||||
|
@ -224,16 +229,8 @@ func TestPART(t *testing.T) {
|
|||
c, s := setUp(t)
|
||||
defer s.tearDown()
|
||||
|
||||
// We need some valid and associated nicks / channels to PART with.
|
||||
chan1 := state.NewChannel("#test1")
|
||||
nick1 := state.NewNick("user1")
|
||||
|
||||
// PART should dissociate a nick from a channel.
|
||||
gomock.InOrder(
|
||||
s.st.EXPECT().GetChannel("#test1").Return(chan1),
|
||||
s.st.EXPECT().GetNick("user1").Return(nick1),
|
||||
s.st.EXPECT().Dissociate(chan1, nick1),
|
||||
)
|
||||
s.st.EXPECT().Dissociate("#test1", "user1")
|
||||
c.h_PART(ParseLine(":user1!ident1@host1.com PART #test1 :Bye!"))
|
||||
}
|
||||
|
||||
|
@ -243,16 +240,8 @@ func TestKICK(t *testing.T) {
|
|||
c, s := setUp(t)
|
||||
defer s.tearDown()
|
||||
|
||||
// We need some valid and associated nicks / channels to KICK.
|
||||
chan1 := state.NewChannel("#test1")
|
||||
nick1 := state.NewNick("user1")
|
||||
|
||||
// KICK should dissociate a nick from a channel.
|
||||
gomock.InOrder(
|
||||
s.st.EXPECT().GetChannel("#test1").Return(chan1),
|
||||
s.st.EXPECT().GetNick("user1").Return(nick1),
|
||||
s.st.EXPECT().Dissociate(chan1, nick1),
|
||||
)
|
||||
s.st.EXPECT().Dissociate("#test1", "user1")
|
||||
c.h_KICK(ParseLine(":test!test@somehost.com KICK #test1 user1 :Bye!"))
|
||||
}
|
||||
|
||||
|
@ -271,34 +260,28 @@ func TestMODE(t *testing.T) {
|
|||
c, s := setUp(t)
|
||||
defer s.tearDown()
|
||||
|
||||
chan1 := state.NewChannel("#test1")
|
||||
nick1 := state.NewNick("user1")
|
||||
|
||||
// Send a channel mode line. Inconveniently, Channel and Nick objects
|
||||
// aren't mockable with gomock as they're not interface types (and I
|
||||
// don't want them to be, writing accessors for struct fields sucks).
|
||||
// This makes testing whether ParseModes is called correctly harder.
|
||||
s.st.EXPECT().GetChannel("#test1").Return(chan1)
|
||||
// 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"))
|
||||
if !chan1.Modes.Secret || chan1.Modes.Key != "somekey" {
|
||||
t.Errorf("Channel.ParseModes() not called correctly.")
|
||||
}
|
||||
|
||||
// Send a nick mode line, returning Me
|
||||
// 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"))
|
||||
if !c.cfg.Me.Modes.Invisible {
|
||||
t.Errorf("Nick.ParseModes() not called correctly.")
|
||||
}
|
||||
|
||||
// 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(nick1),
|
||||
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),
|
||||
|
@ -312,22 +295,13 @@ func TestTOPIC(t *testing.T) {
|
|||
c, s := setUp(t)
|
||||
defer s.tearDown()
|
||||
|
||||
chan1 := state.NewChannel("#test1")
|
||||
|
||||
// Assert that it has no topic originally
|
||||
if chan1.Topic != "" {
|
||||
t.Errorf("Test channel already has a topic.")
|
||||
}
|
||||
|
||||
// Send a TOPIC line
|
||||
s.st.EXPECT().GetChannel("#test1").Return(chan1)
|
||||
// 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"))
|
||||
|
||||
// Make sure the channel's topic has been changed
|
||||
if chan1.Topic != "something something" {
|
||||
t.Errorf("Topic of test channel not set correctly.")
|
||||
}
|
||||
|
||||
// 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"))
|
||||
|
@ -338,20 +312,14 @@ func Test311(t *testing.T) {
|
|||
c, s := setUp(t)
|
||||
defer s.tearDown()
|
||||
|
||||
// Create user1, who we know little about
|
||||
nick1 := state.NewNick("user1")
|
||||
|
||||
// Send a 311 reply
|
||||
s.st.EXPECT().GetNick("user1").Return(nick1)
|
||||
// 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"))
|
||||
|
||||
// Verify we now know more about user1
|
||||
if nick1.Ident != "ident1" ||
|
||||
nick1.Host != "host1.com" ||
|
||||
nick1.Name != "name" {
|
||||
t.Errorf("WHOIS info of user1 not set correctly.")
|
||||
}
|
||||
|
||||
// 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"))
|
||||
|
@ -362,15 +330,12 @@ func Test324(t *testing.T) {
|
|||
c, s := setUp(t)
|
||||
defer s.tearDown()
|
||||
|
||||
// Create #test1, whose modes we don't know
|
||||
chan1 := state.NewChannel("#test1")
|
||||
|
||||
// Send a 324 reply
|
||||
s.st.EXPECT().GetChannel("#test1").Return(chan1)
|
||||
// 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"))
|
||||
if !chan1.Modes.Secret || chan1.Modes.Key != "somekey" {
|
||||
t.Errorf("Channel.ParseModes() not called correctly.")
|
||||
}
|
||||
|
||||
// Check error paths -- send 324 for an unknown channel
|
||||
s.st.EXPECT().GetChannel("#test2").Return(nil)
|
||||
|
@ -382,23 +347,13 @@ func Test332(t *testing.T) {
|
|||
c, s := setUp(t)
|
||||
defer s.tearDown()
|
||||
|
||||
// Create #test1, whose topic we don't know
|
||||
chan1 := state.NewChannel("#test1")
|
||||
|
||||
// Assert that it has no topic originally
|
||||
if chan1.Topic != "" {
|
||||
t.Errorf("Test channel already has a topic.")
|
||||
}
|
||||
|
||||
// Send a 332 reply
|
||||
s.st.EXPECT().GetChannel("#test1").Return(chan1)
|
||||
// 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"))
|
||||
|
||||
// Make sure the channel's topic has been changed
|
||||
if chan1.Topic != "something something" {
|
||||
t.Errorf("Topic of test channel not set correctly.")
|
||||
}
|
||||
|
||||
// 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"))
|
||||
|
@ -409,30 +364,24 @@ func Test352(t *testing.T) {
|
|||
c, s := setUp(t)
|
||||
defer s.tearDown()
|
||||
|
||||
// Create user1, who we know little about
|
||||
nick1 := state.NewNick("user1")
|
||||
|
||||
// Send a 352 reply
|
||||
s.st.EXPECT().GetNick("user1").Return(nick1)
|
||||
// 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"))
|
||||
|
||||
// Verify we now know more about user1
|
||||
if nick1.Ident != "ident1" ||
|
||||
nick1.Host != "host1.com" ||
|
||||
nick1.Name != "name" ||
|
||||
nick1.Modes.Invisible ||
|
||||
nick1.Modes.Oper {
|
||||
t.Errorf("WHO info of user1 not set correctly.")
|
||||
}
|
||||
|
||||
// Check that modes are set correctly from WHOREPLY
|
||||
s.st.EXPECT().GetNick("user1").Return(nick1)
|
||||
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"))
|
||||
|
||||
if !nick1.Modes.Invisible || !nick1.Modes.Oper {
|
||||
t.Errorf("WHO modes of user1 not set correctly.")
|
||||
}
|
||||
|
||||
// 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"))
|
||||
|
@ -443,55 +392,42 @@ func Test353(t *testing.T) {
|
|||
c, s := setUp(t)
|
||||
defer s.tearDown()
|
||||
|
||||
// Create #test1, whose user list we're mostly unfamiliar with
|
||||
chan1 := state.NewChannel("#test1")
|
||||
|
||||
// Create maps for testing -- this is what the mock ST calls will return
|
||||
nicks := make(map[string]*state.Nick)
|
||||
privs := make(map[string]*state.ChanPrivs)
|
||||
|
||||
nicks["test"] = c.cfg.Me
|
||||
privs["test"] = new(state.ChanPrivs)
|
||||
|
||||
for _, n := range []string{"user1", "user2", "voice", "halfop",
|
||||
"op", "admin", "owner"} {
|
||||
nicks[n] = state.NewNick(n)
|
||||
privs[n] = new(state.ChanPrivs)
|
||||
}
|
||||
|
||||
// 353 handler is called twice, so GetChannel will be called twice
|
||||
s.st.EXPECT().GetChannel("#test1").Return(chan1).Times(2)
|
||||
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(privs["test"], true),
|
||||
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(nicks["user1"]),
|
||||
s.st.EXPECT().GetNick("user1").Return(&state.Nick{Nick: "user1"}),
|
||||
s.st.EXPECT().IsOn("#test1", "user1").Return(nil, false),
|
||||
s.st.EXPECT().Associate(chan1, nicks["user1"]).Return(privs["user1"]),
|
||||
s.st.EXPECT().Associate("#test1", "user1").Return(&state.ChanPrivs{}),
|
||||
s.st.EXPECT().ChannelModes("#test1", "+o", "user1"),
|
||||
)
|
||||
for _, n := range []string{"user2", "voice", "halfop", "op", "admin", "owner"} {
|
||||
gomock.InOrder(
|
||||
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(nicks[n]),
|
||||
s.st.EXPECT().NewNick(n).Return(&state.Nick{Nick: n}),
|
||||
s.st.EXPECT().IsOn("#test1", n).Return(nil, false),
|
||||
s.st.EXPECT().Associate(chan1, nicks[n]).Return(privs[n]),
|
||||
)
|
||||
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 "))
|
||||
|
||||
if p := privs["user2"]; p.Voice || p.HalfOp || p.Op || p.Admin || p.Owner {
|
||||
t.Errorf("353 handler incorrectly set modes on nick.")
|
||||
}
|
||||
|
||||
if !privs["user1"].Op || !privs["voice"].Voice || !privs["halfop"].HalfOp ||
|
||||
!privs["op"].Op || !privs["admin"].Admin || !privs["owner"].Owner {
|
||||
t.Errorf("353 handler failed to set correct modes for nicks.")
|
||||
}
|
||||
|
||||
// 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"))
|
||||
|
@ -502,21 +438,13 @@ func Test671(t *testing.T) {
|
|||
c, s := setUp(t)
|
||||
defer s.tearDown()
|
||||
|
||||
// Create user1, who should not be secure
|
||||
nick1 := state.NewNick("user1")
|
||||
if nick1.Modes.SSL {
|
||||
t.Errorf("Test nick user1 is already using SSL?")
|
||||
}
|
||||
|
||||
// Send a 671 reply
|
||||
s.st.EXPECT().GetNick("user1").Return(nick1)
|
||||
// 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"))
|
||||
|
||||
// Ensure user1 is now known to be on an SSL connection
|
||||
if !nick1.Modes.SSL {
|
||||
t.Errorf("Test nick user1 not using SSL?")
|
||||
}
|
||||
|
||||
// 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"))
|
||||
|
|
|
@ -51,44 +51,41 @@ func (conn *Conn) h_JOIN(line *Line) {
|
|||
if ch == nil {
|
||||
// first we've seen of this channel, so should be us joining it
|
||||
// NOTE this will also take care of nk == nil && ch == nil
|
||||
if nk != conn.cfg.Me {
|
||||
if !conn.Me().Equals(nk) {
|
||||
logging.Warn("irc.JOIN(): JOIN to unknown channel %s received "+
|
||||
"from (non-me) nick %s", line.Args[0], line.Nick)
|
||||
return
|
||||
}
|
||||
ch = conn.st.NewChannel(line.Args[0])
|
||||
conn.st.NewChannel(line.Args[0])
|
||||
// since we don't know much about this channel, ask server for info
|
||||
// we get the channel users automatically in 353 and the channel
|
||||
// topic in 332 on join, so we just need to get the modes
|
||||
conn.Mode(ch.Name)
|
||||
conn.Mode(line.Args[0])
|
||||
// sending a WHO for the channel is MUCH more efficient than
|
||||
// triggering a WHOIS on every nick from the 353 handler
|
||||
conn.Who(ch.Name)
|
||||
conn.Who(line.Args[0])
|
||||
}
|
||||
if nk == nil {
|
||||
// this is the first we've seen of this nick
|
||||
nk = conn.st.NewNick(line.Nick)
|
||||
nk.Ident = line.Ident
|
||||
nk.Host = line.Host
|
||||
conn.st.NewNick(line.Nick)
|
||||
conn.st.NickInfo(line.Nick, line.Ident, line.Host, "")
|
||||
// since we don't know much about this nick, ask server for info
|
||||
conn.Who(nk.Nick)
|
||||
conn.Who(line.Nick)
|
||||
}
|
||||
// this takes care of both nick and channel linking \o/
|
||||
conn.st.Associate(ch, nk)
|
||||
conn.st.Associate(line.Args[0], line.Nick)
|
||||
}
|
||||
|
||||
// Handle PARTs from channels to maintain state
|
||||
func (conn *Conn) h_PART(line *Line) {
|
||||
conn.st.Dissociate(conn.st.GetChannel(line.Args[0]),
|
||||
conn.st.GetNick(line.Nick))
|
||||
conn.st.Dissociate(line.Args[0], line.Nick)
|
||||
}
|
||||
|
||||
// Handle KICKs from channels to maintain state
|
||||
func (conn *Conn) h_KICK(line *Line) {
|
||||
// XXX: this won't handle autorejoining channels on KICK
|
||||
// it's trivial to do this in a seperate handler...
|
||||
conn.st.Dissociate(conn.st.GetChannel(line.Args[0]),
|
||||
conn.st.GetNick(line.Args[1]))
|
||||
conn.st.Dissociate(line.Args[0], line.Args[1])
|
||||
}
|
||||
|
||||
// Handle other people's QUITs
|
||||
|
@ -100,15 +97,15 @@ func (conn *Conn) h_QUIT(line *Line) {
|
|||
func (conn *Conn) h_MODE(line *Line) {
|
||||
if ch := conn.st.GetChannel(line.Args[0]); ch != nil {
|
||||
// channel modes first
|
||||
ch.ParseModes(line.Args[1], line.Args[2:]...)
|
||||
conn.st.ChannelModes(line.Args[0], line.Args[1], line.Args[2:]...)
|
||||
} else if nk := conn.st.GetNick(line.Args[0]); nk != nil {
|
||||
// nick mode change, should be us
|
||||
if nk != conn.cfg.Me {
|
||||
if !conn.Me().Equals(nk) {
|
||||
logging.Warn("irc.MODE(): recieved MODE %s for (non-me) nick %s",
|
||||
line.Args[1], line.Args[0])
|
||||
return
|
||||
}
|
||||
nk.ParseModes(line.Args[1])
|
||||
conn.st.NickModes(line.Args[0], line.Args[1])
|
||||
} else {
|
||||
logging.Warn("irc.MODE(): not sure what to do with MODE %s",
|
||||
strings.Join(line.Args, " "))
|
||||
|
@ -118,7 +115,7 @@ func (conn *Conn) h_MODE(line *Line) {
|
|||
// Handle TOPIC changes for channels
|
||||
func (conn *Conn) h_TOPIC(line *Line) {
|
||||
if ch := conn.st.GetChannel(line.Args[0]); ch != nil {
|
||||
ch.Topic = line.Args[1]
|
||||
conn.st.Topic(line.Args[0], line.Args[1])
|
||||
} else {
|
||||
logging.Warn("irc.TOPIC(): topic change on unknown channel %s",
|
||||
line.Args[0])
|
||||
|
@ -127,10 +124,8 @@ func (conn *Conn) h_TOPIC(line *Line) {
|
|||
|
||||
// Handle 311 whois reply
|
||||
func (conn *Conn) h_311(line *Line) {
|
||||
if nk := conn.st.GetNick(line.Args[1]); nk != nil && nk != conn.Me() {
|
||||
nk.Ident = line.Args[2]
|
||||
nk.Host = line.Args[3]
|
||||
nk.Name = line.Args[5]
|
||||
if nk := conn.st.GetNick(line.Args[1]); nk != nil && !conn.Me().Equals(nk) {
|
||||
conn.st.NickInfo(line.Args[1], line.Args[2], line.Args[3], line.Args[5])
|
||||
} else {
|
||||
logging.Warn("irc.311(): received WHOIS info for unknown nick %s",
|
||||
line.Args[1])
|
||||
|
@ -140,7 +135,7 @@ func (conn *Conn) h_311(line *Line) {
|
|||
// Handle 324 mode reply
|
||||
func (conn *Conn) h_324(line *Line) {
|
||||
if ch := conn.st.GetChannel(line.Args[1]); ch != nil {
|
||||
ch.ParseModes(line.Args[2], line.Args[3:]...)
|
||||
conn.st.ChannelModes(line.Args[1], line.Args[2], line.Args[3:]...)
|
||||
} else {
|
||||
logging.Warn("irc.324(): received MODE settings for unknown channel %s",
|
||||
line.Args[1])
|
||||
|
@ -150,7 +145,7 @@ func (conn *Conn) h_324(line *Line) {
|
|||
// Handle 332 topic reply on join to channel
|
||||
func (conn *Conn) h_332(line *Line) {
|
||||
if ch := conn.st.GetChannel(line.Args[1]); ch != nil {
|
||||
ch.Topic = line.Args[2]
|
||||
conn.st.Topic(line.Args[1], line.Args[2])
|
||||
} else {
|
||||
logging.Warn("irc.332(): received TOPIC value for unknown channel %s",
|
||||
line.Args[1])
|
||||
|
@ -165,24 +160,22 @@ func (conn *Conn) h_352(line *Line) {
|
|||
line.Args[5])
|
||||
return
|
||||
}
|
||||
if nk == conn.Me() {
|
||||
if conn.Me().Equals(nk) {
|
||||
return
|
||||
}
|
||||
nk.Ident = line.Args[2]
|
||||
nk.Host = line.Args[3]
|
||||
// XXX: do we care about the actual server the nick is on?
|
||||
// or the hop count to this server?
|
||||
// last arg contains "<hop count> <real name>"
|
||||
a := strings.SplitN(line.Args[len(line.Args)-1], " ", 2)
|
||||
nk.Name = a[1]
|
||||
conn.st.NickInfo(nk.Nick, line.Args[2], line.Args[3], a[1])
|
||||
if idx := strings.Index(line.Args[6], "*"); idx != -1 {
|
||||
nk.Modes.Oper = true
|
||||
conn.st.NickModes(nk.Nick, "+o")
|
||||
}
|
||||
if idx := strings.Index(line.Args[6], "B"); idx != -1 {
|
||||
nk.Modes.Bot = true
|
||||
conn.st.NickModes(nk.Nick, "+B")
|
||||
}
|
||||
if idx := strings.Index(line.Args[6], "H"); idx != -1 {
|
||||
nk.Modes.Invisible = true
|
||||
conn.st.NickModes(nk.Nick, "+i")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -200,27 +193,25 @@ func (conn *Conn) h_353(line *Line) {
|
|||
nick = nick[1:]
|
||||
fallthrough
|
||||
default:
|
||||
nk := conn.st.GetNick(nick)
|
||||
if nk == nil {
|
||||
if conn.st.GetNick(nick) == nil {
|
||||
// we don't know this nick yet!
|
||||
nk = conn.st.NewNick(nick)
|
||||
conn.st.NewNick(nick)
|
||||
}
|
||||
cp, ok := conn.st.IsOn(ch.Name, nick)
|
||||
if !ok {
|
||||
if _, ok := conn.st.IsOn(ch.Name, nick); !ok {
|
||||
// This nick isn't associated with this channel yet!
|
||||
cp = conn.st.Associate(ch, nk)
|
||||
conn.st.Associate(ch.Name, nick)
|
||||
}
|
||||
switch c {
|
||||
case '~':
|
||||
cp.Owner = true
|
||||
conn.st.ChannelModes(ch.Name, "+q", nick)
|
||||
case '&':
|
||||
cp.Admin = true
|
||||
conn.st.ChannelModes(ch.Name, "+a", nick)
|
||||
case '@':
|
||||
cp.Op = true
|
||||
conn.st.ChannelModes(ch.Name, "+o", nick)
|
||||
case '%':
|
||||
cp.HalfOp = true
|
||||
conn.st.ChannelModes(ch.Name, "+h", nick)
|
||||
case '+':
|
||||
cp.Voice = true
|
||||
conn.st.ChannelModes(ch.Name, "+v", nick)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -233,7 +224,7 @@ func (conn *Conn) h_353(line *Line) {
|
|||
// Handle 671 whois reply (nick connected via SSL)
|
||||
func (conn *Conn) h_671(line *Line) {
|
||||
if nk := conn.st.GetNick(line.Args[1]); nk != nil {
|
||||
nk.Modes.SSL = true
|
||||
conn.st.NickModes(nk.Nick, "+z")
|
||||
} else {
|
||||
logging.Warn("irc.671(): received WHOIS SSL info for unknown nick %s",
|
||||
line.Args[1])
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue