check / check (push) Waiting to run
When the server refuses a JOIN, POST /api/v1/messages answers 200 with status "error" and sends the error numeric to the queue. Both clients opened the channel anyway. JoinChannel in internal/cli/api now returns an error unless the status is "joined". cmdJoin then keeps its buffer and target and prints its usual "Join failed" line. The web client opens no tab for a refused join, whether it came from /join or from the saved channels a fresh session joins again. Model: opus-5-5
338 lines
7.2 KiB
Go
338 lines
7.2 KiB
Go
package cli
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/gdamore/tcell/v2"
|
|
api "sneak.berlin/go/neoirc/internal/cli/api"
|
|
"sneak.berlin/go/neoirc/pkg/irc"
|
|
)
|
|
|
|
// The user's nick, a channel it is in, another user's nick and
|
|
// a message text, for the tests below.
|
|
const (
|
|
testNick = "erin"
|
|
testChannel = "#chan"
|
|
testOtherNick = "bob"
|
|
testText = "hello"
|
|
)
|
|
|
|
func TestMemberPrefix(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
member api.Member
|
|
want string
|
|
}{
|
|
{
|
|
name: "operator",
|
|
member: api.Member{Nick: "alice", IsOperator: true},
|
|
want: "@",
|
|
},
|
|
{
|
|
name: "voiced member",
|
|
member: api.Member{Nick: "bob", IsVoiced: true},
|
|
want: "+",
|
|
},
|
|
{
|
|
name: "voiced operator",
|
|
member: api.Member{
|
|
Nick: "Carol", IsOperator: true, IsVoiced: true,
|
|
},
|
|
want: "@",
|
|
},
|
|
{
|
|
name: "plain member",
|
|
member: api.Member{Nick: "dave"},
|
|
want: "",
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got := memberPrefix(test.member)
|
|
if got != test.want {
|
|
t.Fatalf("got %q, want %q", got, test.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestPartEvent checks that a PART event adds a line to the
|
|
// channel's buffer and marks it unread, except that the
|
|
// user's own part does not mark it unread.
|
|
func TestPartEvent(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
from string
|
|
wantUnread int
|
|
}{
|
|
{name: "own part", from: testNick, wantUnread: 0},
|
|
{name: "other member", from: testOtherNick, wantUnread: 1},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
app := &App{ui: NewUI(), nick: testNick}
|
|
runUI(t, app.ui)
|
|
|
|
app.handleServerMessage(&api.Message{
|
|
Command: irc.CmdPart, From: test.from, To: testChannel,
|
|
})
|
|
|
|
lines, unread := bufferState(app.ui, testChannel)
|
|
if lines != 1 || unread != test.wantUnread {
|
|
t.Fatalf("got %d lines and %d unread, want 1 and %d",
|
|
lines, unread, test.wantUnread)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestOwnCommandShownOnce checks that a message, join or
|
|
// part made from neoirc-cli shows as one line in the
|
|
// channel's buffer once the server's event for it arrives,
|
|
// and does not mark the channel unread.
|
|
func TestOwnCommandShownOnce(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
send func(app *App)
|
|
event api.Message
|
|
}{
|
|
{
|
|
name: "message",
|
|
send: func(app *App) { app.handleInput(testText) },
|
|
event: api.Message{
|
|
Command: irc.CmdPrivmsg, From: testNick, To: testChannel,
|
|
Body: []string{testText},
|
|
},
|
|
},
|
|
{
|
|
name: "join",
|
|
send: func(app *App) { app.cmdJoin(testChannel) },
|
|
event: api.Message{
|
|
Command: irc.CmdJoin, From: testNick, To: testChannel,
|
|
},
|
|
},
|
|
{
|
|
name: "part",
|
|
send: func(app *App) { app.cmdPart(testChannel) },
|
|
event: api.Message{
|
|
Command: irc.CmdPart, From: testNick, To: testChannel,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// The server accepts every command. Only the response
|
|
// to a JOIN is read, and it says the session joined.
|
|
server := newServer(t, `{"status": "joined"}`)
|
|
|
|
app := &App{
|
|
ui: NewUI(),
|
|
client: api.NewClient(server.URL),
|
|
nick: testNick,
|
|
target: testChannel,
|
|
connected: true,
|
|
}
|
|
runUI(t, app.ui)
|
|
app.ui.SwitchToBuffer(testChannel)
|
|
|
|
test.send(app)
|
|
app.handleServerMessage(&test.event)
|
|
|
|
lines, unread := bufferState(app.ui, testChannel)
|
|
if lines != 1 || unread != 0 {
|
|
t.Fatalf("got %d lines and %d unread, want 1 and 0",
|
|
lines, unread)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestJoinResponse checks that /join switches to the channel's
|
|
// buffer and makes it the target only when the server answers
|
|
// that the session joined.
|
|
func TestJoinResponse(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
response string
|
|
// The buffer shown and the target after the join.
|
|
want string
|
|
}{
|
|
{
|
|
name: "joined",
|
|
response: `{"status": "joined", "channel": "#chan"}`,
|
|
want: testChannel,
|
|
},
|
|
{
|
|
name: "refused",
|
|
response: `{"status": "error"}`,
|
|
want: testOtherNick,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
app := &App{
|
|
ui: NewUI(),
|
|
client: api.NewClient(newServer(t, test.response).URL),
|
|
nick: testNick,
|
|
target: testOtherNick,
|
|
connected: true,
|
|
}
|
|
runUI(t, app.ui)
|
|
app.ui.SwitchToBuffer(testOtherNick)
|
|
|
|
app.cmdJoin(testChannel)
|
|
|
|
var shown string
|
|
|
|
app.ui.app.QueueUpdate(func() {
|
|
shown = app.ui.buffers[app.ui.currentBuffer].Name
|
|
})
|
|
|
|
if shown != test.want || app.target != test.want {
|
|
t.Fatalf("got buffer %s and target %s, want %s",
|
|
shown, app.target, test.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestOwnEventFromOtherClient checks that a message, notice,
|
|
// join or part that the user's session makes from another
|
|
// client shows as one line in the buffer it belongs to.
|
|
func TestOwnEventFromOtherClient(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
event api.Message
|
|
buffer string
|
|
}{
|
|
{
|
|
name: "channel message",
|
|
event: api.Message{
|
|
Command: irc.CmdPrivmsg, From: testNick, To: testChannel,
|
|
Body: []string{testText},
|
|
},
|
|
buffer: testChannel,
|
|
},
|
|
{
|
|
name: "direct message",
|
|
event: api.Message{
|
|
Command: irc.CmdPrivmsg, From: testNick, To: testOtherNick,
|
|
Body: []string{testText},
|
|
},
|
|
buffer: testOtherNick,
|
|
},
|
|
{
|
|
name: "notice",
|
|
event: api.Message{
|
|
Command: irc.CmdNotice, From: testNick, To: testChannel,
|
|
Body: []string{testText},
|
|
},
|
|
buffer: "(status)",
|
|
},
|
|
{
|
|
name: "join",
|
|
event: api.Message{
|
|
Command: irc.CmdJoin, From: testNick, To: testChannel,
|
|
},
|
|
buffer: testChannel,
|
|
},
|
|
{
|
|
name: "part",
|
|
event: api.Message{
|
|
Command: irc.CmdPart, From: testNick, To: testChannel,
|
|
},
|
|
buffer: testChannel,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
app := &App{ui: NewUI(), nick: testNick}
|
|
runUI(t, app.ui)
|
|
|
|
app.handleServerMessage(&test.event)
|
|
|
|
lines, _ := bufferState(app.ui, test.buffer)
|
|
if lines != 1 {
|
|
t.Fatalf("got %d lines in %s, want 1", lines, test.buffer)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// bufferState returns the number of lines in the named
|
|
// buffer and how many of them are unread.
|
|
func bufferState(ui *UI, name string) (int, int) {
|
|
var lines, unread int
|
|
|
|
ui.app.QueueUpdate(func() {
|
|
buf := ui.getOrCreateBuffer(name)
|
|
lines = len(buf.Lines)
|
|
unread = buf.Unread
|
|
})
|
|
|
|
return lines, unread
|
|
}
|
|
|
|
// newServer starts a server that answers every request with
|
|
// body until the test ends.
|
|
func newServer(t *testing.T, body string) *httptest.Server {
|
|
t.Helper()
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(
|
|
func(writer http.ResponseWriter, _ *http.Request) {
|
|
_, _ = io.WriteString(writer, body)
|
|
},
|
|
))
|
|
t.Cleanup(server.Close)
|
|
|
|
return server
|
|
}
|
|
|
|
// runUI runs ui on a simulated screen until the test ends.
|
|
func runUI(t *testing.T, ui *UI) {
|
|
t.Helper()
|
|
|
|
ui.app.SetScreen(tcell.NewSimulationScreen(""))
|
|
|
|
done := make(chan error)
|
|
|
|
go func() { done <- ui.Run() }()
|
|
|
|
t.Cleanup(func() {
|
|
ui.Stop()
|
|
|
|
err := <-done
|
|
if err != nil {
|
|
t.Errorf("run ui: %v", err)
|
|
}
|
|
})
|
|
}
|