Refactor irc handler, add tests

This commit is contained in:
Ken-Håvard Lieng 2015-06-16 23:20:53 +02:00
parent d38daf976b
commit 047027ddec
4 changed files with 456 additions and 134 deletions

View File

@ -8,175 +8,231 @@ import (
"github.com/khlieng/name_pending/storage" "github.com/khlieng/name_pending/storage"
) )
func handleIRC(client *irc.Client, session *Session) { type ircHandler struct {
var whois WhoisReply client *irc.Client
userBuffers := make(map[string][]string) session *Session
var motd MOTD
whois WhoisReply
userBuffers map[string][]string
motdBuffer MOTD
handlers map[string]func(*irc.Message)
}
func newIRCHandler(client *irc.Client, session *Session) *ircHandler {
i := &ircHandler{
client: client,
session: session,
userBuffers: make(map[string][]string),
}
i.initHandlers()
return i
}
func (i *ircHandler) run() {
for { for {
msg, ok := <-client.Messages msg, ok := <-i.client.Messages
if !ok { if !ok {
session.deleteIRC(client.Host) i.session.deleteIRC(i.client.Host)
return return
} }
switch msg.Command { i.dispatchMessage(msg)
case irc.Nick: }
session.sendJSON("nick", Nick{ }
Server: client.Host,
Old: msg.Nick,
New: msg.Trailing,
})
channelStore.RenameUser(msg.Nick, msg.Trailing, client.Host) func (i *ircHandler) dispatchMessage(msg *irc.Message) {
if handler, ok := i.handlers[msg.Command]; ok {
handler(msg)
}
}
case irc.Join: func (i *ircHandler) nick(msg *irc.Message) {
session.sendJSON("join", Join{ i.session.sendJSON("nick", Nick{
Server: client.Host, Server: i.client.Host,
User: msg.Nick, Old: msg.Nick,
Channels: msg.Params, New: msg.Trailing,
}) })
channelStore.AddUser(msg.Nick, client.Host, msg.Params[0]) channelStore.RenameUser(msg.Nick, msg.Trailing, i.client.Host)
}
if msg.Nick == client.GetNick() { func (i *ircHandler) join(msg *irc.Message) {
session.user.AddChannel(storage.Channel{ i.session.sendJSON("join", Join{
Server: client.Host, Server: i.client.Host,
Name: msg.Params[0], User: msg.Nick,
}) Channels: msg.Params,
} })
case irc.Part: channelStore.AddUser(msg.Nick, i.client.Host, msg.Params[0])
session.sendJSON("part", Part{
Join: Join{
Server: client.Host,
User: msg.Nick,
Channels: msg.Params,
},
Reason: msg.Trailing,
})
channelStore.RemoveUser(msg.Nick, client.Host, msg.Params[0]) if msg.Nick == i.client.GetNick() {
i.session.user.AddChannel(storage.Channel{
Server: i.client.Host,
Name: msg.Params[0],
})
}
}
if msg.Nick == client.GetNick() { func (i *ircHandler) part(msg *irc.Message) {
session.user.RemoveChannel(client.Host, msg.Params[0]) i.session.sendJSON("part", Part{
} Join: Join{
Server: i.client.Host,
User: msg.Nick,
Channels: msg.Params,
},
Reason: msg.Trailing,
})
case irc.Mode: channelStore.RemoveUser(msg.Nick, i.client.Host, msg.Params[0])
target := msg.Params[0]
if len(msg.Params) > 2 && isChannel(target) {
mode := parseMode(msg.Params[1])
mode.Server = client.Host
mode.Channel = target
mode.User = msg.Params[2]
session.sendJSON("mode", mode) if msg.Nick == i.client.GetNick() {
i.session.user.RemoveChannel(i.client.Host, msg.Params[0])
}
}
channelStore.SetMode(client.Host, target, msg.Params[2], mode.Add, mode.Remove) func (i *ircHandler) mode(msg *irc.Message) {
} target := msg.Params[0]
if len(msg.Params) > 2 && isChannel(target) {
mode := parseMode(msg.Params[1])
mode.Server = i.client.Host
mode.Channel = target
mode.User = msg.Params[2]
case irc.Privmsg, irc.Notice: i.session.sendJSON("mode", mode)
if msg.Params[0] == client.GetNick() {
session.sendJSON("pm", Chat{
Server: client.Host,
From: msg.Nick,
Message: msg.Trailing,
})
} else {
session.sendJSON("message", Chat{
Server: client.Host,
From: msg.Nick,
To: msg.Params[0],
Message: msg.Trailing,
})
}
if msg.Params[0] != "*" { channelStore.SetMode(i.client.Host, target, msg.Params[2], mode.Add, mode.Remove)
go session.user.LogMessage(client.Host, msg.Nick, msg.Params[0], msg.Trailing) }
} }
case irc.Quit: func (i *ircHandler) message(msg *irc.Message) {
session.sendJSON("quit", Quit{ message := Chat{
Server: client.Host, Server: i.client.Host,
User: msg.Nick, From: msg.Nick,
Reason: msg.Trailing, Message: msg.Trailing,
}) }
channelStore.RemoveUserAll(msg.Nick, client.Host) if msg.Params[0] == i.client.GetNick() {
i.session.sendJSON("pm", message)
} else {
message.To = msg.Params[0]
i.session.sendJSON("message", message)
}
case irc.ReplyWelcome, if msg.Params[0] != "*" {
irc.ReplyYourHost, go i.session.user.LogMessage(i.client.Host, msg.Nick, msg.Params[0], msg.Trailing)
irc.ReplyCreated, }
irc.ReplyLUserClient, }
irc.ReplyLUserOp,
irc.ReplyLUserUnknown,
irc.ReplyLUserChannels,
irc.ReplyLUserMe:
session.sendJSON("pm", Chat{
Server: client.Host,
From: msg.Nick,
Message: strings.Join(msg.Params[1:], " "),
})
case irc.ReplyWhoisUser: func (i *ircHandler) quit(msg *irc.Message) {
whois.Nick = msg.Params[1] i.session.sendJSON("quit", Quit{
whois.Username = msg.Params[2] Server: i.client.Host,
whois.Host = msg.Params[3] User: msg.Nick,
whois.Realname = msg.Params[5] Reason: msg.Trailing,
})
case irc.ReplyWhoisServer: channelStore.RemoveUserAll(msg.Nick, i.client.Host)
whois.Server = msg.Params[2] }
case irc.ReplyWhoisChannels: func (i *ircHandler) info(msg *irc.Message) {
whois.Channels = append(whois.Channels, strings.Split(strings.TrimRight(msg.Trailing, " "), " ")...) i.session.sendJSON("pm", Chat{
Server: i.client.Host,
From: msg.Nick,
Message: strings.Join(msg.Params[1:], " "),
})
}
case irc.ReplyEndOfWhois: func (i *ircHandler) whoisUser(msg *irc.Message) {
session.sendJSON("whois", whois) i.whois.Nick = msg.Params[1]
i.whois.Username = msg.Params[2]
i.whois.Host = msg.Params[3]
i.whois.Realname = msg.Params[5]
}
whois = WhoisReply{} func (i *ircHandler) whoisServer(msg *irc.Message) {
i.whois.Server = msg.Params[2]
}
case irc.ReplyTopic: func (i *ircHandler) whoisChannels(msg *irc.Message) {
session.sendJSON("topic", Topic{ i.whois.Channels = append(i.whois.Channels, strings.Split(strings.TrimRight(msg.Trailing, " "), " ")...)
Server: client.Host, }
Channel: msg.Params[1],
Topic: msg.Trailing,
})
channelStore.SetTopic(msg.Trailing, client.Host, msg.Params[1]) func (i *ircHandler) whoisEnd(msg *irc.Message) {
i.session.sendJSON("whois", i.whois)
i.whois = WhoisReply{}
}
case irc.ReplyNamReply: func (i *ircHandler) topic(msg *irc.Message) {
users := strings.Split(msg.Trailing, " ") i.session.sendJSON("topic", Topic{
userBuffer := userBuffers[msg.Params[2]] Server: i.client.Host,
userBuffers[msg.Params[2]] = append(userBuffer, users...) Channel: msg.Params[1],
Topic: msg.Trailing,
})
case irc.ReplyEndOfNames: channelStore.SetTopic(msg.Trailing, i.client.Host, msg.Params[1])
channel := msg.Params[1] }
users := userBuffers[channel]
session.sendJSON("users", Userlist{ func (i *ircHandler) names(msg *irc.Message) {
Server: client.Host, users := strings.Split(msg.Trailing, " ")
Channel: channel, userBuffer := i.userBuffers[msg.Params[2]]
Users: users, i.userBuffers[msg.Params[2]] = append(userBuffer, users...)
}) }
channelStore.SetUsers(users, client.Host, channel) func (i *ircHandler) namesEnd(msg *irc.Message) {
delete(userBuffers, channel) channel := msg.Params[1]
users := i.userBuffers[channel]
case irc.ReplyMotdStart: i.session.sendJSON("users", Userlist{
motd.Server = client.Host Server: i.client.Host,
motd.Title = msg.Trailing Channel: channel,
Users: users,
})
case irc.ReplyMotd: channelStore.SetUsers(users, i.client.Host, channel)
motd.Content = append(motd.Content, msg.Trailing) delete(i.userBuffers, channel)
}
case irc.ReplyEndOfMotd: func (i *ircHandler) motdStart(msg *irc.Message) {
session.sendJSON("motd", motd) i.motdBuffer.Server = i.client.Host
i.motdBuffer.Title = msg.Trailing
}
motd = MOTD{} func (i *ircHandler) motd(msg *irc.Message) {
i.motdBuffer.Content = append(i.motdBuffer.Content, msg.Trailing)
}
default: func (i *ircHandler) motdEnd(msg *irc.Message) {
printMessage(msg, client) i.session.sendJSON("motd", i.motdBuffer)
} i.motdBuffer = MOTD{}
}
func (i *ircHandler) initHandlers() {
i.handlers = map[string]func(*irc.Message){
irc.Nick: i.nick,
irc.Join: i.join,
irc.Part: i.part,
irc.Mode: i.mode,
irc.Privmsg: i.message,
irc.Notice: i.message,
irc.Quit: i.quit,
irc.ReplyWelcome: i.info,
irc.ReplyYourHost: i.info,
irc.ReplyCreated: i.info,
irc.ReplyLUserClient: i.info,
irc.ReplyLUserOp: i.info,
irc.ReplyLUserUnknown: i.info,
irc.ReplyLUserChannels: i.info,
irc.ReplyLUserMe: i.info,
irc.ReplyWhoisUser: i.whoisUser,
irc.ReplyWhoisServer: i.whoisServer,
irc.ReplyWhoisChannels: i.whoisChannels,
irc.ReplyEndOfWhois: i.whoisEnd,
irc.ReplyTopic: i.topic,
irc.ReplyNamReply: i.names,
irc.ReplyEndOfNames: i.namesEnd,
irc.ReplyMotdStart: i.motdStart,
irc.ReplyMotd: i.motd,
irc.ReplyEndOfMotd: i.motdEnd,
} }
} }

266
server/irc_handler_test.go Normal file
View File

@ -0,0 +1,266 @@
package server
import (
"io/ioutil"
"log"
"os"
"path"
"testing"
"github.com/khlieng/name_pending/Godeps/_workspace/src/github.com/stretchr/testify/assert"
"github.com/khlieng/name_pending/irc"
"github.com/khlieng/name_pending/storage"
)
var user *storage.User
func TestMain(m *testing.M) {
tempdir, err := ioutil.TempDir("", "test_")
if err != nil {
log.Fatal(err)
}
os.Mkdir(path.Join(tempdir, "logs"), 0777)
storage.Initialize(tempdir)
user = storage.NewUser("uuid")
channelStore = storage.NewChannelStore()
code := m.Run()
os.RemoveAll(tempdir)
os.Exit(code)
}
func dispatchMessage(msg *irc.Message) WSResponse {
c := irc.NewClient("nick", "user")
c.Host = "host.com"
s := NewSession()
s.user = user
newIRCHandler(c, s).dispatchMessage(msg)
return <-s.out
}
func checkResponse(t *testing.T, expectedType string, expectedResponse interface{}, res WSResponse) {
assert.Equal(t, expectedType, res.Type)
assert.Equal(t, expectedResponse, res.Response)
}
func TestHandleIRCNick(t *testing.T) {
res := dispatchMessage(&irc.Message{
Command: irc.Nick,
Nick: "old",
Trailing: "new",
})
checkResponse(t, "nick", Nick{
Server: "host.com",
Old: "old",
New: "new",
}, res)
}
func TestHandleIRCJoin(t *testing.T) {
res := dispatchMessage(&irc.Message{
Command: irc.Join,
Nick: "joining",
Params: []string{"#chan"},
})
checkResponse(t, "join", Join{
Server: "host.com",
User: "joining",
Channels: []string{"#chan"},
}, res)
}
func TestHandleIRCPart(t *testing.T) {
res := dispatchMessage(&irc.Message{
Command: irc.Part,
Nick: "parting",
Params: []string{"#chan"},
Trailing: "the reason",
})
checkResponse(t, "part", Part{
Join: Join{
Server: "host.com",
User: "parting",
Channels: []string{"#chan"},
},
Reason: "the reason",
}, res)
}
func TestHandleIRCMode(t *testing.T) {
res := dispatchMessage(&irc.Message{
Command: irc.Mode,
Params: []string{"#chan", "+o-v", "nick"},
})
checkResponse(t, "mode", &Mode{
Server: "host.com",
Channel: "#chan",
User: "nick",
Add: "o",
Remove: "v",
}, res)
}
func TestHandleIRCMessage(t *testing.T) {
res := dispatchMessage(&irc.Message{
Command: irc.Privmsg,
Nick: "nick",
Params: []string{"#chan"},
Trailing: "the message",
})
checkResponse(t, "message", Chat{
Server: "host.com",
From: "nick",
To: "#chan",
Message: "the message",
}, res)
res = dispatchMessage(&irc.Message{
Command: irc.Privmsg,
Nick: "someone",
Params: []string{"nick"},
Trailing: "the message",
})
checkResponse(t, "pm", Chat{
Server: "host.com",
From: "someone",
Message: "the message",
}, res)
}
func TestHandleIRCQuit(t *testing.T) {
res := dispatchMessage(&irc.Message{
Command: irc.Quit,
Nick: "nick",
Trailing: "the reason",
})
checkResponse(t, "quit", Quit{
Server: "host.com",
User: "nick",
Reason: "the reason",
}, res)
}
func TestHandleIRCWelcome(t *testing.T) {
res := dispatchMessage(&irc.Message{
Command: irc.ReplyWelcome,
Nick: "nick",
Params: []string{"target", "some", "text"},
})
checkResponse(t, "pm", Chat{
Server: "host.com",
From: "nick",
Message: "some text",
}, res)
}
func TestHandleIRCWhois(t *testing.T) {
c := irc.NewClient("nick", "user")
c.Host = "host.com"
s := NewSession()
i := newIRCHandler(c, s)
i.dispatchMessage(&irc.Message{
Command: irc.ReplyWhoisUser,
Params: []string{"", "nick", "user", "host", "", "realname"},
})
i.dispatchMessage(&irc.Message{
Command: irc.ReplyWhoisServer,
Params: []string{"", "", "srv.com"},
})
i.dispatchMessage(&irc.Message{
Command: irc.ReplyWhoisChannels,
Trailing: "#chan #chan1",
})
i.dispatchMessage(&irc.Message{Command: irc.ReplyEndOfWhois})
checkResponse(t, "whois", WhoisReply{
Nick: "nick",
Username: "user",
Host: "host",
Realname: "realname",
Server: "srv.com",
Channels: []string{"#chan", "#chan1"},
}, <-s.out)
}
func TestHandleIRCTopic(t *testing.T) {
res := dispatchMessage(&irc.Message{
Command: irc.ReplyTopic,
Params: []string{"target", "#chan"},
Trailing: "the topic",
})
checkResponse(t, "topic", Topic{
Server: "host.com",
Channel: "#chan",
Topic: "the topic",
}, res)
}
func TestHandleIRCNames(t *testing.T) {
c := irc.NewClient("nick", "user")
c.Host = "host.com"
s := NewSession()
i := newIRCHandler(c, s)
i.dispatchMessage(&irc.Message{
Command: irc.ReplyNamReply,
Params: []string{"", "", "#chan"},
Trailing: "a b c",
})
i.dispatchMessage(&irc.Message{
Command: irc.ReplyNamReply,
Params: []string{"", "", "#chan"},
Trailing: "d",
})
i.dispatchMessage(&irc.Message{
Command: irc.ReplyEndOfNames,
Params: []string{"", "#chan"},
})
checkResponse(t, "users", Userlist{
Server: "host.com",
Channel: "#chan",
Users: []string{"a", "b", "c", "d"},
}, <-s.out)
}
func TestHandleIRCMotd(t *testing.T) {
c := irc.NewClient("nick", "user")
c.Host = "host.com"
s := NewSession()
i := newIRCHandler(c, s)
i.dispatchMessage(&irc.Message{
Command: irc.ReplyMotdStart,
Trailing: "motd title",
})
i.dispatchMessage(&irc.Message{
Command: irc.ReplyMotd,
Trailing: "line 1",
})
i.dispatchMessage(&irc.Message{
Command: irc.ReplyMotd,
Trailing: "line 2",
})
i.dispatchMessage(&irc.Message{Command: irc.ReplyEndOfMotd})
checkResponse(t, "motd", MOTD{
Server: "host.com",
Title: "motd title",
Content: []string{"line 1", "line 2"},
}, <-s.out)
}

View File

@ -79,7 +79,7 @@ func reconnect() {
i.Connect(server.Address) i.Connect(server.Address)
session.setIRC(i.Host, i) session.setIRC(i.Host, i)
go handleIRC(i, session) go newIRCHandler(i, session).run()
var joining []string var joining []string
for _, channel := range channels { for _, channel := range channels {

View File

@ -100,7 +100,7 @@ func handleWS(conn *websocket.Conn) {
} }
i.Connect(data.Server) i.Connect(data.Server)
go handleIRC(i, session) go newIRCHandler(i, session).run()
session.user.AddServer(storage.Server{ session.user.AddServer(storage.Server{
Name: data.Name, Name: data.Name,