History backfill, also tests pass.
This commit is contained in:
parent
544c9789c0
commit
0c21486992
3
auth.go
3
auth.go
|
@ -28,6 +28,9 @@ func NewAuthKey(key ssh.PublicKey) string {
|
|||
|
||||
// NewAuthAddr returns a string from a net.Addr
|
||||
func NewAuthAddr(addr net.Addr) string {
|
||||
if addr == nil {
|
||||
return ""
|
||||
}
|
||||
host, _, _ := net.SplitHostPort(addr.String())
|
||||
return host
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ func TestAuthWhitelist(t *testing.T) {
|
|||
}
|
||||
|
||||
auth := NewAuth()
|
||||
ok, err := auth.Check(key)
|
||||
ok, err := auth.Check(nil, key)
|
||||
if !ok || err != nil {
|
||||
t.Error("Failed to permit in default state:", err)
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ func TestAuthWhitelist(t *testing.T) {
|
|||
t.Error("Clone key does not match.")
|
||||
}
|
||||
|
||||
ok, err = auth.Check(keyClone)
|
||||
ok, err = auth.Check(nil, keyClone)
|
||||
if !ok || err != nil {
|
||||
t.Error("Failed to permit whitelisted:", err)
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ func TestAuthWhitelist(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ok, err = auth.Check(key2)
|
||||
ok, err = auth.Check(nil, key2)
|
||||
if ok || err == nil {
|
||||
t.Error("Failed to restrict not whitelisted:", err)
|
||||
}
|
||||
|
|
|
@ -145,7 +145,7 @@ func InitCommands(c *Commands) {
|
|||
}
|
||||
u := msg.From()
|
||||
oldId := u.Id()
|
||||
u.SetId(Id(args[0]))
|
||||
u.SetId(args[0])
|
||||
|
||||
err := room.Rename(oldId, u)
|
||||
if err != nil {
|
||||
|
@ -234,7 +234,7 @@ func InitCommands(c *Commands) {
|
|||
|
||||
// TODO: Add support for fingerprint-based op'ing. This will
|
||||
// probably need to live in host land.
|
||||
member, ok := room.MemberById(Id(args[0]))
|
||||
member, ok := room.MemberById(args[0])
|
||||
if !ok {
|
||||
return errors.New("user not found")
|
||||
}
|
||||
|
|
|
@ -4,23 +4,23 @@ import "sync"
|
|||
|
||||
// History contains the history entries
|
||||
type History struct {
|
||||
entries []interface{}
|
||||
entries []Message
|
||||
head int
|
||||
size int
|
||||
sync.RWMutex
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
// NewHistory constructs a new history of the given size
|
||||
func NewHistory(size int) *History {
|
||||
return &History{
|
||||
entries: make([]interface{}, size),
|
||||
entries: make([]Message, size),
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds the given entry to the entries in the history
|
||||
func (h *History) Add(entry interface{}) {
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
func (h *History) Add(entry Message) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
|
||||
max := cap(h.entries)
|
||||
h.head = (h.head + 1) % max
|
||||
|
@ -35,17 +35,17 @@ func (h *History) Len() int {
|
|||
return h.size
|
||||
}
|
||||
|
||||
// Get recent entries
|
||||
func (h *History) Get(num int) []interface{} {
|
||||
h.RLock()
|
||||
defer h.RUnlock()
|
||||
// Get the entry with the given number
|
||||
func (h *History) Get(num int) []Message {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
|
||||
max := cap(h.entries)
|
||||
if num > h.size {
|
||||
num = h.size
|
||||
}
|
||||
|
||||
r := make([]interface{}, num)
|
||||
r := make([]Message, num)
|
||||
for i := 0; i < num; i++ {
|
||||
idx := (h.head - i) % max
|
||||
if idx < 0 {
|
||||
|
|
|
@ -2,64 +2,61 @@ package chat
|
|||
|
||||
import "testing"
|
||||
|
||||
func equal(a []interface{}, b []string) bool {
|
||||
func msgEqual(a []Message, b []Message) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := 0; i < len(a); i++ {
|
||||
if a[0] != b[0] {
|
||||
for i := range a {
|
||||
if a[i].String() != b[i].String() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func TestHistory(t *testing.T) {
|
||||
var r []interface{}
|
||||
var expected []string
|
||||
var r, expected []Message
|
||||
var size int
|
||||
|
||||
h := NewHistory(5)
|
||||
|
||||
r = h.Get(10)
|
||||
expected = []string{}
|
||||
if !equal(r, expected) {
|
||||
expected = []Message{}
|
||||
if !msgEqual(r, expected) {
|
||||
t.Errorf("Got: %v, Expected: %v", r, expected)
|
||||
}
|
||||
|
||||
h.Add("1")
|
||||
h.Add(NewMsg("1"))
|
||||
|
||||
if size = h.Len(); size != 1 {
|
||||
t.Errorf("Wrong size: %v", size)
|
||||
}
|
||||
|
||||
r = h.Get(1)
|
||||
expected = []string{"1"}
|
||||
if !equal(r, expected) {
|
||||
expected = []Message{NewMsg("1")}
|
||||
if !msgEqual(r, expected) {
|
||||
t.Errorf("Got: %v, Expected: %v", r, expected)
|
||||
}
|
||||
|
||||
h.Add("2")
|
||||
h.Add("3")
|
||||
h.Add("4")
|
||||
h.Add("5")
|
||||
h.Add("6")
|
||||
h.Add(NewMsg("2"))
|
||||
h.Add(NewMsg("3"))
|
||||
h.Add(NewMsg("4"))
|
||||
h.Add(NewMsg("5"))
|
||||
h.Add(NewMsg("6"))
|
||||
|
||||
if size = h.Len(); size != 5 {
|
||||
t.Errorf("Wrong size: %v", size)
|
||||
}
|
||||
|
||||
r = h.Get(2)
|
||||
expected = []string{"5", "6"}
|
||||
if !equal(r, expected) {
|
||||
expected = []Message{NewMsg("5"), NewMsg("6")}
|
||||
if !msgEqual(r, expected) {
|
||||
t.Errorf("Got: %v, Expected: %v", r, expected)
|
||||
}
|
||||
|
||||
r = h.Get(10)
|
||||
expected = []string{"2", "3", "4", "5", "6"}
|
||||
if !equal(r, expected) {
|
||||
expected = []Message{NewMsg("2"), NewMsg("3"), NewMsg("4"), NewMsg("5"), NewMsg("6")}
|
||||
if !msgEqual(r, expected) {
|
||||
t.Errorf("Got: %v, Expected: %v", r, expected)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,12 +34,18 @@ func ParseInput(body string, from *User) Message {
|
|||
|
||||
// Msg is a base type for other message types.
|
||||
type Msg struct {
|
||||
Message
|
||||
body string
|
||||
timestamp time.Time
|
||||
// TODO: themeCache *map[*Theme]string
|
||||
}
|
||||
|
||||
func NewMsg(body string) *Msg {
|
||||
return &Msg{
|
||||
body: body,
|
||||
timestamp: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// Render message based on a theme.
|
||||
func (m *Msg) Render(t *Theme) string {
|
||||
// TODO: Render based on theme
|
||||
|
|
|
@ -2,6 +2,18 @@ package chat
|
|||
|
||||
import "testing"
|
||||
|
||||
type testId string
|
||||
|
||||
func (i testId) Id() string {
|
||||
return string(i)
|
||||
}
|
||||
func (i testId) SetId(s string) {
|
||||
// no-op
|
||||
}
|
||||
func (i testId) Name() string {
|
||||
return i.Id()
|
||||
}
|
||||
|
||||
func TestMessage(t *testing.T) {
|
||||
var expected, actual string
|
||||
|
||||
|
@ -11,7 +23,7 @@ func TestMessage(t *testing.T) {
|
|||
t.Errorf("Got: `%s`; Expected: `%s`", actual, expected)
|
||||
}
|
||||
|
||||
u := NewUser("foo")
|
||||
u := NewUser(testId("foo"))
|
||||
expected = "foo: hello"
|
||||
actual = NewPublicMsg("hello", u).String()
|
||||
if actual != expected {
|
||||
|
|
18
chat/room.go
18
chat/room.go
|
@ -79,6 +79,7 @@ func (r *Room) HandleMsg(m Message) {
|
|||
skipUser = fromMsg.From()
|
||||
}
|
||||
|
||||
r.history.Add(m)
|
||||
r.members.Each(func(u Identifier) {
|
||||
user := u.(*Member).User
|
||||
if skip && skipUser == user {
|
||||
|
@ -91,10 +92,7 @@ func (r *Room) HandleMsg(m Message) {
|
|||
return
|
||||
}
|
||||
}
|
||||
err := user.Send(m)
|
||||
if err != nil {
|
||||
user.Close()
|
||||
}
|
||||
user.Send(m)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -112,6 +110,13 @@ func (r *Room) Send(m Message) {
|
|||
r.broadcast <- m
|
||||
}
|
||||
|
||||
// History feeds the room's recent message history to the user's handler.
|
||||
func (r *Room) History(u *User) {
|
||||
for _, m := range r.history.Get(historyLen) {
|
||||
u.Send(m)
|
||||
}
|
||||
}
|
||||
|
||||
// Join the room as a user, will announce.
|
||||
func (r *Room) Join(u *User) (*Member, error) {
|
||||
if r.closed {
|
||||
|
@ -122,6 +127,7 @@ func (r *Room) Join(u *User) (*Member, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.History(u)
|
||||
s := fmt.Sprintf("%s joined. (Connected: %d)", u.Name(), r.members.Len())
|
||||
r.Send(NewAnnounceMsg(s))
|
||||
return &member, nil
|
||||
|
@ -139,7 +145,7 @@ func (r *Room) Leave(u *User) error {
|
|||
}
|
||||
|
||||
// Rename member with a new identity. This will not call rename on the member.
|
||||
func (r *Room) Rename(oldId Id, identity Identifier) error {
|
||||
func (r *Room) Rename(oldId string, identity Identifier) error {
|
||||
err := r.members.Replace(oldId, identity)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -164,7 +170,7 @@ func (r *Room) Member(u *User) (*Member, bool) {
|
|||
return m, true
|
||||
}
|
||||
|
||||
func (r *Room) MemberById(id Id) (*Member, bool) {
|
||||
func (r *Room) MemberById(id string) (*Member, bool) {
|
||||
m, err := r.members.Get(id)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
|
|
|
@ -22,7 +22,7 @@ func TestRoomJoin(t *testing.T) {
|
|||
var expected, actual []byte
|
||||
|
||||
s := &MockScreen{}
|
||||
u := NewUser("foo")
|
||||
u := NewUser(testId("foo"))
|
||||
|
||||
ch := NewRoom()
|
||||
go ch.Serve()
|
||||
|
@ -58,7 +58,7 @@ func TestRoomJoin(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRoomDoesntBroadcastAnnounceMessagesWhenQuiet(t *testing.T) {
|
||||
u := NewUser("foo")
|
||||
u := NewUser(testId("foo"))
|
||||
u.Config = UserConfig{
|
||||
Quiet: true,
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ func TestRoomDoesntBroadcastAnnounceMessagesWhenQuiet(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRoomQuietToggleBroadcasts(t *testing.T) {
|
||||
u := NewUser("foo")
|
||||
u := NewUser(testId("foo"))
|
||||
u.Config = UserConfig{
|
||||
Quiet: true,
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ func TestQuietToggleDisplayState(t *testing.T) {
|
|||
var expected, actual []byte
|
||||
|
||||
s := &MockScreen{}
|
||||
u := NewUser("foo")
|
||||
u := NewUser(testId("foo"))
|
||||
|
||||
ch := NewRoom()
|
||||
go ch.Serve()
|
||||
|
@ -168,7 +168,7 @@ func TestRoomNames(t *testing.T) {
|
|||
var expected, actual []byte
|
||||
|
||||
s := &MockScreen{}
|
||||
u := NewUser("foo")
|
||||
u := NewUser(testId("foo"))
|
||||
|
||||
ch := NewRoom()
|
||||
go ch.Serve()
|
||||
|
|
12
chat/set.go
12
chat/set.go
|
@ -15,14 +15,14 @@ var ErrItemMissing = errors.New("item does not exist")
|
|||
// Set with string lookup.
|
||||
// TODO: Add trie for efficient prefix lookup?
|
||||
type Set struct {
|
||||
lookup map[Id]Identifier
|
||||
lookup map[string]Identifier
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// NewSet creates a new set.
|
||||
func NewSet() *Set {
|
||||
return &Set{
|
||||
lookup: map[Id]Identifier{},
|
||||
lookup: map[string]Identifier{},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ func NewSet() *Set {
|
|||
func (s *Set) Clear() int {
|
||||
s.Lock()
|
||||
n := len(s.lookup)
|
||||
s.lookup = map[Id]Identifier{}
|
||||
s.lookup = map[string]Identifier{}
|
||||
s.Unlock()
|
||||
return n
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ func (s *Set) In(item Identifier) bool {
|
|||
}
|
||||
|
||||
// Get returns an item with the given Id.
|
||||
func (s *Set) Get(id Id) (Identifier, error) {
|
||||
func (s *Set) Get(id string) (Identifier, error) {
|
||||
s.RLock()
|
||||
item, ok := s.lookup[id]
|
||||
s.RUnlock()
|
||||
|
@ -88,9 +88,9 @@ func (s *Set) Remove(item Identifier) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Replace item from old Id with new Identifier.
|
||||
// Replace item from old id with new Identifier.
|
||||
// Used for moving the same identifier to a new Id, such as a rename.
|
||||
func (s *Set) Replace(oldId Id, item Identifier) error {
|
||||
func (s *Set) Replace(oldId string, item Identifier) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import "testing"
|
|||
func TestSet(t *testing.T) {
|
||||
var err error
|
||||
s := NewSet()
|
||||
u := NewUser("foo")
|
||||
u := NewUser(testId("foo"))
|
||||
|
||||
if s.In(u) {
|
||||
t.Errorf("Set should be empty.")
|
||||
|
@ -20,7 +20,7 @@ func TestSet(t *testing.T) {
|
|||
t.Errorf("Set should contain user.")
|
||||
}
|
||||
|
||||
u2 := NewUser("bar")
|
||||
u2 := NewUser(testId("bar"))
|
||||
err = s.Add(u2)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
|
|
|
@ -54,7 +54,7 @@ func TestTheme(t *testing.T) {
|
|||
t.Errorf("Got: `%s`; Expected: `%s`", actual, expected)
|
||||
}
|
||||
|
||||
u := NewUser("foo")
|
||||
u := NewUser(testId("foo"))
|
||||
u.colorIdx = 4
|
||||
actual = colorTheme.ColorName(u)
|
||||
expected = "\033[38;05;4mfoo\033[0m"
|
||||
|
|
|
@ -15,13 +15,10 @@ const reHighlight = `\b(%s)\b`
|
|||
|
||||
var ErrUserClosed = errors.New("user closed")
|
||||
|
||||
// Id is a unique immutable identifier for a user.
|
||||
type Id string
|
||||
|
||||
// Identifier is an interface that can uniquely identify itself.
|
||||
type Identifier interface {
|
||||
Id() Id
|
||||
SetId(Id)
|
||||
Id() string
|
||||
SetId(string)
|
||||
Name() string
|
||||
}
|
||||
|
||||
|
@ -59,7 +56,7 @@ func NewUserScreen(identity Identifier, screen io.Writer) *User {
|
|||
}
|
||||
|
||||
// Rename the user with a new Identifier.
|
||||
func (u *User) SetId(id Id) {
|
||||
func (u *User) SetId(id string) {
|
||||
u.Identifier.SetId(id)
|
||||
u.SetColorIdx(rand.Int())
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ func TestMakeUser(t *testing.T) {
|
|||
var actual, expected []byte
|
||||
|
||||
s := &MockScreen{}
|
||||
u := NewUser("foo")
|
||||
u := NewUser(testId("foo"))
|
||||
m := NewAnnounceMsg("hello")
|
||||
|
||||
defer u.Close()
|
||||
|
|
2
host.go
2
host.go
|
@ -208,7 +208,7 @@ func (h *Host) AutoCompleteFunction(u *chat.User) func(line string, pos int, key
|
|||
|
||||
// GetUser returns a chat.User based on a name.
|
||||
func (h *Host) GetUser(name string) (*chat.User, bool) {
|
||||
m, ok := h.MemberById(chat.Id(name))
|
||||
m, ok := h.MemberById(name)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ func stripPrompt(s string) string {
|
|||
func TestHostGetPrompt(t *testing.T) {
|
||||
var expected, actual string
|
||||
|
||||
u := chat.NewUser("foo")
|
||||
u := chat.NewUser(&Identity{nil, "foo"})
|
||||
u.SetColorIdx(2)
|
||||
|
||||
actual = GetPrompt(u)
|
||||
|
|
15
identity.go
15
identity.go
|
@ -11,32 +11,31 @@ import (
|
|||
// Identity is a container for everything that identifies a client.
|
||||
type Identity struct {
|
||||
sshd.Connection
|
||||
id chat.Id
|
||||
id string
|
||||
}
|
||||
|
||||
// NewIdentity returns a new identity object from an sshd.Connection.
|
||||
func NewIdentity(conn sshd.Connection) *Identity {
|
||||
id := chat.Id(conn.Name())
|
||||
return &Identity{
|
||||
Connection: conn,
|
||||
id: id,
|
||||
id: conn.Name(),
|
||||
}
|
||||
}
|
||||
|
||||
func (i Identity) Id() chat.Id {
|
||||
return chat.Id(i.id)
|
||||
func (i Identity) Id() string {
|
||||
return i.id
|
||||
}
|
||||
|
||||
func (i *Identity) SetId(id chat.Id) {
|
||||
func (i *Identity) SetId(id string) {
|
||||
i.id = id
|
||||
}
|
||||
|
||||
func (i *Identity) SetName(name string) {
|
||||
i.SetId(chat.Id(name))
|
||||
i.SetId(name)
|
||||
}
|
||||
|
||||
func (i Identity) Name() string {
|
||||
return string(i.id)
|
||||
return i.id
|
||||
}
|
||||
|
||||
func (i Identity) Whois() string {
|
||||
|
|
|
@ -2,6 +2,7 @@ package sshd
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
@ -14,7 +15,7 @@ type RejectAuth struct{}
|
|||
func (a RejectAuth) AllowAnonymous() bool {
|
||||
return false
|
||||
}
|
||||
func (a RejectAuth) Check(ssh.PublicKey) (bool, error) {
|
||||
func (a RejectAuth) Check(net.Addr, ssh.PublicKey) (bool, error) {
|
||||
return false, errRejectAuth
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue