package chat import ( "errors" "fmt" "reflect" "testing" "github.com/shazow/ssh-chat/chat/message" "github.com/shazow/ssh-chat/set" ) // Used for testing type MockScreen struct { buffer []byte } func (s *MockScreen) Write(data []byte) (n int, err error) { s.buffer = append(s.buffer, data...) return len(data), nil } func (s *MockScreen) Read(p *[]byte) (n int, err error) { *p = s.buffer s.buffer = []byte{} return len(*p), nil } func (s *MockScreen) Close() error { return nil } func TestRoomServe(t *testing.T) { ch := NewRoom() ch.Send(message.NewAnnounceMsg("hello")) received := <-ch.broadcast actual := received.String() expected := " * hello" if actual != expected { t.Errorf("Got: %q; Expected: %q", actual, expected) } } type ScreenedUser struct { user *message.User screen *MockScreen } func TestIgnore(t *testing.T) { var buffer []byte ch := NewRoom() go ch.Serve() defer ch.Close() // Create 3 users, join the room and clear their screen buffers users := make([]ScreenedUser, 3) for i := 0; i < 3; i++ { screen := &MockScreen{} user := message.NewUserScreen(message.SimpleID(fmt.Sprintf("user%d", i)), screen) users[i] = ScreenedUser{ user: user, screen: screen, } _, err := ch.Join(user) if err != nil { t.Fatal(err) } } for _, u := range users { for i := 0; i < 3; i++ { u.user.HandleMsg(u.user.ConsumeOne()) u.screen.Read(&buffer) } } // Use some handy variable names for distinguish between roles ignorer := users[0] ignored := users[1] other := users[2] // test ignoring unexisting user if err := sendCommand("/ignore test", ignorer, ch, &buffer); err != nil { t.Fatal(err) } expectOutput(t, buffer, "-> Err: user not found: test"+message.Newline) // test ignoring existing user if err := sendCommand("/ignore "+ignored.user.Name(), ignorer, ch, &buffer); err != nil { t.Fatal(err) } expectOutput(t, buffer, "-> Ignoring: "+ignored.user.Name()+message.Newline) // ignoring the same user twice returns an error message and doesn't add the user twice if err := sendCommand("/ignore "+ignored.user.Name(), ignorer, ch, &buffer); err != nil { t.Fatal(err) } expectOutput(t, buffer, "-> Err: user already ignored: user1"+message.Newline) if ignoredList := ignorer.user.Ignored.ListPrefix(""); len(ignoredList) != 1 { t.Fatalf("should have %d ignored users, has %d", 1, len(ignoredList)) } // when an emote is sent by an ignored user, it should not be displayed for ignorer ch.HandleMsg(message.NewEmoteMsg("is crying", ignored.user)) if ignorer.user.HasMessages() { t.Fatal("should not have emote messages") } other.user.HandleMsg(other.user.ConsumeOne()) other.screen.Read(&buffer) expectOutput(t, buffer, "** "+ignored.user.Name()+" is crying"+message.Newline) // when a message is sent from the ignored user, it is delivered to non-ignoring users ch.HandleMsg(message.NewPublicMsg("hello", ignored.user)) other.user.HandleMsg(other.user.ConsumeOne()) other.screen.Read(&buffer) expectOutput(t, buffer, ignored.user.Name()+": hello"+message.Newline) // ensure ignorer doesn't have received any message if ignorer.user.HasMessages() { t.Fatal("should not have messages") } // `/ignore` returns a list of ignored users if err := sendCommand("/ignore", ignorer, ch, &buffer); err != nil { t.Fatal(err) } expectOutput(t, buffer, "-> 1 ignored: "+ignored.user.Name()+message.Newline) // `/unignore [USER]` removes the user from ignored ones if err := sendCommand("/unignore "+ignored.user.Name(), users[0], ch, &buffer); err != nil { t.Fatal(err) } expectOutput(t, buffer, "-> No longer ignoring: user1"+message.Newline) if err := sendCommand("/ignore", users[0], ch, &buffer); err != nil { t.Fatal(err) } expectOutput(t, buffer, "-> 0 users ignored."+message.Newline) if ignoredList := ignorer.user.Ignored.ListPrefix(""); len(ignoredList) != 0 { t.Fatalf("should have %d ignored users, has %d", 0, len(ignoredList)) } // after unignoring a user, its messages can be received again ch.HandleMsg(message.NewPublicMsg("hello again!", ignored.user)) // ensure ignorer has received the message if !ignorer.user.HasMessages() { t.Fatal("should have messages") } ignorer.user.HandleMsg(ignorer.user.ConsumeOne()) ignorer.screen.Read(&buffer) expectOutput(t, buffer, ignored.user.Name()+": hello again!"+message.Newline) } func TestMute(t *testing.T) { var buffer []byte ch := NewRoom() go ch.Serve() defer ch.Close() // Create 3 users, join the room and clear their screen buffers users := make([]ScreenedUser, 3) members := make([]*Member, 3) for i := 0; i < 3; i++ { screen := &MockScreen{} user := message.NewUserScreen(message.SimpleID(fmt.Sprintf("user%d", i)), screen) users[i] = ScreenedUser{ user: user, screen: screen, } member, err := ch.Join(user) if err != nil { t.Fatal(err) } members[i] = member } for _, u := range users { for i := 0; i < 3; i++ { u.user.HandleMsg(u.user.ConsumeOne()) u.screen.Read(&buffer) } } // Use some handy variable names for distinguish between roles muter := users[0] muted := users[1] other := users[2] members[0].IsOp = true // test muting unexisting user if err := sendCommand("/mute test", muter, ch, &buffer); err != nil { t.Fatal(err) } expectOutput(t, buffer, "-> Err: user not found"+message.Newline) // test muting by non-op if err := sendCommand("/mute "+muted.user.Name(), other, ch, &buffer); err != nil { t.Fatal(err) } expectOutput(t, buffer, "-> Err: must be op"+message.Newline) // test muting existing user if err := sendCommand("/mute "+muted.user.Name(), muter, ch, &buffer); err != nil { t.Fatal(err) } expectOutput(t, buffer, "-> Muted: "+muted.user.Name()+message.Newline) if got, want := members[1].IsMuted(), true; got != want { t.Error("muted user failed to set mute flag") } // when an emote is sent by a muted user, it should not be displayed for anyone ch.HandleMsg(message.NewPublicMsg("hello!", muted.user)) ch.HandleMsg(message.NewEmoteMsg("is crying", muted.user)) if muter.user.HasMessages() { muter.user.HandleMsg(muter.user.ConsumeOne()) muter.screen.Read(&buffer) t.Errorf("muter should not have messages: %s", buffer) } if other.user.HasMessages() { other.user.HandleMsg(other.user.ConsumeOne()) other.screen.Read(&buffer) t.Errorf("other should not have messages: %s", buffer) } // test unmuting if err := sendCommand("/mute "+muted.user.Name(), muter, ch, &buffer); err != nil { t.Fatal(err) } expectOutput(t, buffer, "-> Unmuted: "+muted.user.Name()+message.Newline) ch.HandleMsg(message.NewPublicMsg("hello again!", muted.user)) other.user.HandleMsg(other.user.ConsumeOne()) other.screen.Read(&buffer) expectOutput(t, buffer, muted.user.Name()+": hello again!"+message.Newline) } func expectOutput(t *testing.T, buffer []byte, expected string) { t.Helper() bytes := []byte(expected) if !reflect.DeepEqual(buffer, bytes) { t.Errorf("Got: %q; Expected: %q", buffer, expected) } } 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 s := &MockScreen{} u := message.NewUserScreen(message.SimpleID("foo"), s) ch := NewRoom() go ch.Serve() defer ch.Close() _, err := ch.Join(u) if err != nil { t.Fatal(err) } u.HandleMsg(u.ConsumeOne()) expected = []byte(" * foo joined. (Connected: 1)" + message.Newline) s.Read(&actual) if !reflect.DeepEqual(actual, expected) { t.Errorf("Got: %q; Expected: %q", actual, expected) } ch.Send(message.NewSystemMsg("hello", u)) u.HandleMsg(u.ConsumeOne()) expected = []byte("-> hello" + message.Newline) s.Read(&actual) if !reflect.DeepEqual(actual, expected) { t.Errorf("Got: %q; Expected: %q", actual, expected) } ch.Send(message.ParseInput("/me says hello.", u)) u.HandleMsg(u.ConsumeOne()) expected = []byte("** foo says hello." + message.Newline) s.Read(&actual) if !reflect.DeepEqual(actual, expected) { t.Errorf("Got: %q; Expected: %q", actual, expected) } } func TestRoomDoesntBroadcastAnnounceMessagesWhenQuiet(t *testing.T) { u := message.NewUser(message.SimpleID("foo")) u.SetConfig(message.UserConfig{ Quiet: true, }) ch := NewRoom() defer ch.Close() _, err := ch.Join(u) if err != nil { t.Fatal(err) } // Drain the initial Join message <-ch.broadcast go func() { /* for { msg := u.ConsumeChan() if _, ok := msg.(*message.AnnounceMsg); ok { t.Errorf("Got unexpected `%T`", msg) } } */ // XXX: Fix this }() // Call with an AnnounceMsg and all the other types // and assert we received only non-announce messages ch.HandleMsg(message.NewAnnounceMsg("Ignored")) // Assert we still get all other types of messages ch.HandleMsg(message.NewEmoteMsg("hello", u)) ch.HandleMsg(message.NewSystemMsg("hello", u)) ch.HandleMsg(message.NewPrivateMsg("hello", u, u)) ch.HandleMsg(message.NewPublicMsg("hello", u)) } func TestRoomQuietToggleBroadcasts(t *testing.T) { u := message.NewUser(message.SimpleID("foo")) u.SetConfig(message.UserConfig{ Quiet: true, }) ch := NewRoom() defer ch.Close() _, err := ch.Join(u) if err != nil { t.Fatal(err) } // Drain the initial Join message <-ch.broadcast u.SetConfig(message.UserConfig{ Quiet: false, }) expectedMsg := message.NewAnnounceMsg("Ignored") ch.HandleMsg(expectedMsg) msg := u.ConsumeOne() if _, ok := msg.(*message.AnnounceMsg); !ok { t.Errorf("Got: `%T`; Expected: `%T`", msg, expectedMsg) } u.SetConfig(message.UserConfig{ Quiet: true, }) ch.HandleMsg(message.NewAnnounceMsg("Ignored")) ch.HandleMsg(message.NewSystemMsg("hello", u)) msg = u.ConsumeOne() if _, ok := msg.(*message.AnnounceMsg); ok { t.Errorf("Got unexpected `%T`", msg) } } func TestQuietToggleDisplayState(t *testing.T) { var expected, actual []byte s := &MockScreen{} u := message.NewUserScreen(message.SimpleID("foo"), s) ch := NewRoom() go ch.Serve() defer ch.Close() _, err := ch.Join(u) if err != nil { t.Fatal(err) } u.HandleMsg(u.ConsumeOne()) expected = []byte(" * foo joined. (Connected: 1)" + message.Newline) s.Read(&actual) if !reflect.DeepEqual(actual, expected) { t.Errorf("Got: %q; Expected: %q", actual, expected) } ch.Send(message.ParseInput("/quiet", u)) u.HandleMsg(u.ConsumeOne()) expected = []byte("-> Quiet mode is toggled ON" + message.Newline) s.Read(&actual) if !reflect.DeepEqual(actual, expected) { t.Errorf("Got: %q; Expected: %q", actual, expected) } ch.Send(message.ParseInput("/quiet", u)) u.HandleMsg(u.ConsumeOne()) expected = []byte("-> Quiet mode is toggled OFF" + message.Newline) s.Read(&actual) if !reflect.DeepEqual(actual, expected) { t.Errorf("Got: %q; Expected: %q", actual, expected) } } func TestRoomNames(t *testing.T) { var expected, actual []byte s := &MockScreen{} u := message.NewUserScreen(message.SimpleID("foo"), s) ch := NewRoom() go ch.Serve() defer ch.Close() _, err := ch.Join(u) if err != nil { t.Fatal(err) } u.HandleMsg(u.ConsumeOne()) expected = []byte(" * foo joined. (Connected: 1)" + message.Newline) s.Read(&actual) if !reflect.DeepEqual(actual, expected) { t.Errorf("Got: %q; Expected: %q", actual, expected) } ch.Send(message.ParseInput("/names", u)) u.HandleMsg(u.ConsumeOne()) expected = []byte("-> 1 connected: foo" + message.Newline) s.Read(&actual) if !reflect.DeepEqual(actual, expected) { t.Errorf("Got: %q; Expected: %q", actual, expected) } } func TestRoomNamesPrefix(t *testing.T) { r := NewRoom() s := &MockScreen{} members := []*Member{ &Member{User: message.NewUserScreen(message.SimpleID("aaa"), s)}, &Member{User: message.NewUserScreen(message.SimpleID("aab"), s)}, &Member{User: message.NewUserScreen(message.SimpleID("aac"), s)}, &Member{User: message.NewUserScreen(message.SimpleID("foo"), s)}, } for _, m := range members { if err := r.Members.Add(set.Itemize(m.ID(), m)); err != nil { t.Fatal(err) } } sendMsg := func(from *Member, body string) { // lastMsg is set during render of self messags, so we can't use NewMsg from.HandleMsg(message.NewPublicMsg(body, from.User)) } // Inject some activity sendMsg(members[2], "hi") // aac sendMsg(members[0], "hi") // aaa sendMsg(members[3], "hi") // foo sendMsg(members[1], "hi") // aab if got, want := r.NamesPrefix("a"), []string{"aab", "aaa", "aac"}; !reflect.DeepEqual(got, want) { t.Errorf("got: %q; want: %q", got, want) } sendMsg(members[2], "hi") // aac if got, want := r.NamesPrefix("a"), []string{"aac", "aab", "aaa"}; !reflect.DeepEqual(got, want) { t.Errorf("got: %q; want: %q", got, want) } if got, want := r.NamesPrefix("f"), []string{"foo"}; !reflect.DeepEqual(got, want) { t.Errorf("got: %q; want: %q", got, want) } if got, want := r.NamesPrefix("bar"), []string{}; !reflect.DeepEqual(got, want) { t.Errorf("got: %q; want: %q", got, want) } }