broken: Use Member rather than message.User when possible; chat tests are broken.

This commit is contained in:
Andrey Petrov 2016-09-10 20:36:23 -04:00
parent 9ecd2a6fa2
commit 33a76bb7f4
7 changed files with 94 additions and 101 deletions

View File

@ -5,6 +5,7 @@ package chat
import (
"errors"
"fmt"
"io"
"strings"
"github.com/shazow/ssh-chat/chat/message"
@ -110,7 +111,7 @@ func InitCommands(c *Commands) {
c.Add(Command{
Prefix: "/help",
Handler: func(room *Room, msg message.CommandMsg) error {
op := room.IsOp(msg.From())
op := room.IsOp(msg.From().(Member))
room.Send(message.NewSystemMsg(room.commands.Help(op), msg.From()))
return nil
},
@ -135,8 +136,7 @@ func InitCommands(c *Commands) {
Prefix: "/exit",
Help: "Exit the chat.",
Handler: func(room *Room, msg message.CommandMsg) error {
msg.From().Close()
return nil
return msg.From().(io.Closer).Close()
},
})
c.Alias("/exit", "/quit")
@ -150,7 +150,7 @@ func InitCommands(c *Commands) {
if len(args) != 1 {
return ErrMissingArg
}
member, ok := room.MemberByID(msg.From().ID())
member, ok := room.MemberByID(msg.From().(Member).ID())
if !ok {
return ErrMissingMember
}
@ -184,7 +184,7 @@ func InitCommands(c *Commands) {
PrefixHelp: "[colors|...]",
Help: "Set your color theme. (More themes: solarized, mono, hacker)",
Handler: func(room *Room, msg message.CommandMsg) error {
user := msg.From()
user := msg.From().(Member)
args := msg.Args()
cfg := user.Config()
if len(args) == 0 {
@ -215,7 +215,7 @@ func InitCommands(c *Commands) {
Prefix: "/quiet",
Help: "Silence room announcements.",
Handler: func(room *Room, msg message.CommandMsg) error {
u := msg.From()
u := msg.From().(Member)
cfg := u.Config()
cfg.Quiet = !cfg.Quiet
u.SetConfig(cfg)
@ -253,7 +253,7 @@ func InitCommands(c *Commands) {
PrefixHelp: "[USER]",
Help: "Hide messages from USER, /unignore USER to stop hiding.",
Handler: func(room *Room, msg message.CommandMsg) error {
from, ok := room.Member(msg.From())
from, ok := room.Member(msg.From().(Member))
if !ok {
return ErrMissingMember
}
@ -278,7 +278,7 @@ func InitCommands(c *Commands) {
return nil
}
if id == msg.From().ID() {
if id == msg.From().(Member).ID() {
return errors.New("cannot ignore self")
}
target, ok := room.MemberByID(id)
@ -302,7 +302,7 @@ func InitCommands(c *Commands) {
Prefix: "/unignore",
PrefixHelp: "USER",
Handler: func(room *Room, msg message.CommandMsg) error {
from, ok := room.Member(msg.From())
from, ok := room.Member(msg.From().(Member))
if !ok {
return ErrMissingMember
}

View File

@ -12,9 +12,9 @@ type roomMember struct {
}
type Member interface {
ID() string
message.Author
Name() string
ID() string
SetName(string)
Config() message.UserConfig

View File

@ -6,6 +6,11 @@ import (
"time"
)
type Author interface {
Name() string
Color() int
}
// Message is an interface for messages.
type Message interface {
Render(*Theme) string
@ -16,15 +21,15 @@ type Message interface {
type MessageTo interface {
Message
To() *User
To() Author
}
type MessageFrom interface {
Message
From() *User
From() Author
}
func ParseInput(body string, from *User) Message {
func ParseInput(body string, from Author) Message {
m := NewPublicMsg(body, from)
cmd, isCmd := m.ParseCommand()
if isCmd {
@ -69,10 +74,10 @@ func (m Msg) Timestamp() time.Time {
// PublicMsg is any message from a user sent to the room.
type PublicMsg struct {
Msg
from *User
from Author
}
func NewPublicMsg(body string, from *User) PublicMsg {
func NewPublicMsg(body string, from Author) PublicMsg {
return PublicMsg{
Msg: Msg{
body: body,
@ -82,7 +87,7 @@ func NewPublicMsg(body string, from *User) PublicMsg {
}
}
func (m PublicMsg) From() *User {
func (m PublicMsg) From() Author {
return m.from
}
@ -137,10 +142,10 @@ func (m PublicMsg) String() string {
// sender to see the emote.
type EmoteMsg struct {
Msg
from *User
from Author
}
func NewEmoteMsg(body string, from *User) *EmoteMsg {
func NewEmoteMsg(body string, from Author) *EmoteMsg {
return &EmoteMsg{
Msg: Msg{
body: body,
@ -161,17 +166,17 @@ func (m EmoteMsg) String() string {
// PrivateMsg is a message sent to another user, not shown to anyone else.
type PrivateMsg struct {
PublicMsg
to *User
to Author
}
func NewPrivateMsg(body string, from *User, to *User) PrivateMsg {
func NewPrivateMsg(body string, from Author, to Author) PrivateMsg {
return PrivateMsg{
PublicMsg: NewPublicMsg(body, from),
to: to,
}
}
func (m PrivateMsg) To() *User {
func (m PrivateMsg) To() Author {
return m.to
}
@ -191,10 +196,10 @@ func (m PrivateMsg) String() string {
// to anyone else. Usually in response to something, like /help.
type SystemMsg struct {
Msg
to *User
to Author
}
func NewSystemMsg(body string, to *User) *SystemMsg {
func NewSystemMsg(body string, to Author) *SystemMsg {
return &SystemMsg{
Msg: Msg{
body: body,
@ -215,7 +220,7 @@ func (m *SystemMsg) String() string {
return fmt.Sprintf("-> %s", m.body)
}
func (m *SystemMsg) To() *User {
func (m *SystemMsg) To() Author {
return m.to
}

View File

@ -127,12 +127,12 @@ func (t Theme) ID() string {
}
// Colorize name string given some index
func (t Theme) ColorName(u *User) string {
func (t Theme) ColorName(u Author) string {
if t.names == nil {
return u.Name()
}
return t.names.Get(u.colorIdx).Format(u.Name())
return t.names.Get(u.Color()).Format(u.Name())
}
// Colorize the PM string

View File

@ -16,10 +16,53 @@ const reHighlight = `\b(%s)\b`
var ErrUserClosed = errors.New("user closed")
// User container that knows about writing to an IO screen.
type UserScreen struct {
*User
io.WriteCloser
}
func (u *UserScreen) Close() error {
u.User.Close()
return u.WriteCloser.Close()
}
// HandleMsg will render the message to the screen, blocking.
func (u *UserScreen) HandleMsg(m Message) error {
r := u.render(m)
_, err := u.Write([]byte(r))
if err != nil {
logger.Printf("Write failed to %s, closing: %s", u.Name(), err)
u.User.Close()
u.WriteCloser.Close()
}
return err
}
// Consume message buffer into the handler. Will block, should be called in a
// goroutine.
func (u *UserScreen) Consume() {
for {
select {
case <-u.done:
return
case m, ok := <-u.msg:
if !ok {
return
}
u.HandleMsg(m)
}
}
}
// Consume one message and stop, mostly for testing
// TODO: Stop using it and remove it.
func (u *UserScreen) ConsumeOne() Message {
return <-u.msg
}
// User definition, implemented set Item interface and io.Writer
type User struct {
io.WriteCloser
colorIdx int
joined time.Time
closeOnce sync.Once
@ -29,7 +72,7 @@ type User struct {
mu sync.Mutex
name string
config UserConfig
replyTo *User // Set when user gets a /msg, for replying.
replyTo Author // Set when user gets a /msg, for replying.
}
func NewUser(name string) *User {
@ -45,11 +88,11 @@ func NewUser(name string) *User {
return &u
}
func NewUserScreen(name string, screen io.WriteCloser) *User {
u := NewUser(name)
u.WriteCloser = screen
return u
func NewUserScreen(name string, screen io.WriteCloser) *UserScreen {
return &UserScreen{
User: NewUser(name),
WriteCloser: screen,
}
}
func (u *User) Config() UserConfig {
@ -70,6 +113,10 @@ func (u *User) ID() string {
return SanitizeName(u.name)
}
func (u *User) Color() int {
return u.colorIdx
}
func (u *User) Name() string {
u.mu.Lock()
defer u.mu.Unlock()
@ -89,14 +136,14 @@ func (u *User) SetName(name string) {
}
// ReplyTo returns the last user that messaged this user.
func (u *User) ReplyTo() *User {
func (u *User) ReplyTo() Author {
u.mu.Lock()
defer u.mu.Unlock()
return u.replyTo
}
// SetReplyTo sets the last user to message this user.
func (u *User) SetReplyTo(user *User) {
func (u *User) SetReplyTo(user Author) {
// TODO: Use UserConfig.ReplyTo string
u.mu.Lock()
defer u.mu.Unlock()
@ -112,46 +159,11 @@ func (u *User) setColorIdx(idx int) {
// Disconnect user, stop accepting messages
func (u *User) Close() {
u.closeOnce.Do(func() {
u.WriteCloser.Close()
// close(u.msg) TODO: Close?
close(u.done)
})
}
// Consume message buffer into the handler. Will block, should be called in a
// goroutine.
func (u *User) Consume() {
for {
select {
case <-u.done:
return
case m, ok := <-u.msg:
if !ok {
return
}
u.HandleMsg(m)
}
}
}
// Consume one message and stop, mostly for testing
// TODO: Stop using it and remove it.
func (u *User) ConsumeOne() Message {
return <-u.msg
}
// Check if there are pending messages, used for testing
// TODO: Stop using it and remove it.
func (u *User) HasMessages() bool {
select {
case msg := <-u.msg:
u.msg <- msg
return true
default:
return false
}
}
// SetHighlight sets the highlighting regular expression to match string.
func (u *User) SetHighlight(s string) error {
re, err := regexp.Compile(fmt.Sprintf(reHighlight, s))
@ -177,17 +189,6 @@ func (u *User) render(m Message) string {
}
}
// HandleMsg will render the message to the screen, blocking.
func (u *User) HandleMsg(m Message) error {
r := u.render(m)
_, err := u.Write([]byte(r))
if err != nil {
logger.Printf("Write failed to %s, closing: %s", u.Name(), err)
u.Close()
}
return err
}
// Add message to consume by user
func (u *User) Send(m Message) error {
select {

View File

@ -78,21 +78,22 @@ func (r *Room) HandleMsg(m message.Message) {
go r.HandleMsg(m)
}
case message.MessageTo:
user := m.To()
user := m.To().(Member)
user.Send(m)
default:
fromMsg, skip := m.(message.MessageFrom)
var skipUser Member
if skip {
skipUser = fromMsg.From()
skipUser = fromMsg.From().(Member)
}
r.history.Add(m)
r.Members.Each(func(_ string, item set.Item) (err error) {
roomMember := item.Value().(*roomMember)
user := roomMember.Member
from := fromMsg.From().(Member)
if fromMsg != nil && roomMember.Ignored.In(fromMsg.From().ID()) {
if fromMsg != nil && roomMember.Ignored.In(from.ID()) {
// Skip because ignored
return
}

View File

@ -1,7 +1,6 @@
package chat
import (
"errors"
"reflect"
"testing"
@ -42,7 +41,7 @@ func TestRoomServe(t *testing.T) {
}
type ScreenedUser struct {
user *message.User
*message.User
screen *MockScreen
}
@ -159,19 +158,6 @@ func expectOutput(t *testing.T, buffer []byte, expected string) {
}
}
func sendCommand(cmd string, mock ScreenedUser, room *Room, buffer *[]byte) error {
msg, ok := message.NewPublicMsg(cmd, mock.user).ParseCommand()
if !ok {
return errors.New("cannot parse command message")
}
room.Send(msg)
mock.user.HandleMsg(mock.user.ConsumeOne())
mock.screen.Read(buffer)
return nil
}
func TestRoomJoin(t *testing.T) {
var expected, actual []byte