Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8c7ac665d |
No files matched your search
@@ -1236,7 +1236,7 @@ IRC listener sends the usual 3-digit code. Over the HTTP API, 005 arrives named
|
|||||||
| `001` | RPL_WELCOME | After session creation or login, on MOTD | `{"command":"RPL_WELCOME","code":1,"to":"alice","body":["Welcome to the network, alice"]}` |
|
| `001` | RPL_WELCOME | After session creation or login, on MOTD | `{"command":"RPL_WELCOME","code":1,"to":"alice","body":["Welcome to the network, alice"]}` |
|
||||||
| `002` | RPL_YOURHOST | After session creation or login, on MOTD | `{"command":"RPL_YOURHOST","code":2,"to":"alice","body":["Your host is neoirc, running version 0.1"]}` |
|
| `002` | RPL_YOURHOST | After session creation or login, on MOTD | `{"command":"RPL_YOURHOST","code":2,"to":"alice","body":["Your host is neoirc, running version 0.1"]}` |
|
||||||
| `003` | RPL_CREATED | After session creation or login, on MOTD | `{"command":"RPL_CREATED","code":3,"to":"alice","body":["This server was created 2026-02-10"]}` |
|
| `003` | RPL_CREATED | After session creation or login, on MOTD | `{"command":"RPL_CREATED","code":3,"to":"alice","body":["This server was created 2026-02-10"]}` |
|
||||||
| `004` | RPL_MYINFO | After session creation or login, on MOTD | `{"command":"RPL_MYINFO","code":4,"to":"alice","params":["neoirc","0.1","ow","ikmnostl"]}` |
|
| `004` | RPL_MYINFO | After session creation or login, on MOTD | `{"command":"RPL_MYINFO","code":4,"to":"alice","params":["neoirc","0.1","","ikmnostl"]}` |
|
||||||
| `005` | RPL_ISUPPORT | After session creation or login, on MOTD | `{"command":"RPL_BOUNCE","code":5,"to":"alice","params":["CHANTYPES=#","NICKLEN=32","PREFIX=(ov)@+","CHANMODES=b,k,Hl,imnst","NETWORK=neoirc","CASEMAPPING=ascii"],"body":["are supported by this server"]}` |
|
| `005` | RPL_ISUPPORT | After session creation or login, on MOTD | `{"command":"RPL_BOUNCE","code":5,"to":"alice","params":["CHANTYPES=#","NICKLEN=32","PREFIX=(ov)@+","CHANMODES=b,k,Hl,imnst","NETWORK=neoirc","CASEMAPPING=ascii"],"body":["are supported by this server"]}` |
|
||||||
| `221` | RPL_UMODEIS | In response to user MODE | `{"command":"RPL_UMODEIS","code":221,"to":"alice","body":["+"]}` |
|
| `221` | RPL_UMODEIS | In response to user MODE | `{"command":"RPL_UMODEIS","code":221,"to":"alice","body":["+"]}` |
|
||||||
| `251` | RPL_LUSERCLIENT | On connect, MOTD or LUSERS | `{"command":"RPL_LUSERCLIENT","code":251,"to":"alice","body":["There are 5 users and 0 invisible on 1 servers"]}` |
|
| `251` | RPL_LUSERCLIENT | On connect, MOTD or LUSERS | `{"command":"RPL_LUSERCLIENT","code":251,"to":"alice","body":["There are 5 users and 0 invisible on 1 servers"]}` |
|
||||||
|
|||||||
@@ -434,14 +434,45 @@ func TestSecondConnectStopsFirstPollLoop(t *testing.T) {
|
|||||||
// The first server answers every request at once, so the first
|
// The first server answers every request at once, so the first
|
||||||
// poll loop polls it until the second /connect.
|
// poll loop polls it until the second /connect.
|
||||||
first := newServer(t, session)
|
first := newServer(t, session)
|
||||||
second, polled := newHoldingServer(t, session)
|
|
||||||
|
// The second server sends on polled for each poll and holds the
|
||||||
|
// poll until its connection closes. It answers anything else
|
||||||
|
// with the session.
|
||||||
|
polled := make(chan struct{})
|
||||||
|
second := httptest.NewServer(http.HandlerFunc(
|
||||||
|
func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
if request.Method != http.MethodGet ||
|
||||||
|
request.URL.Path != "/api/v1/messages" {
|
||||||
|
_, _ = io.WriteString(writer, session)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case polled <- struct{}{}:
|
||||||
|
case <-request.Context().Done():
|
||||||
|
}
|
||||||
|
|
||||||
|
<-request.Context().Done()
|
||||||
|
},
|
||||||
|
))
|
||||||
|
// Close waits for the held polls, which end only once their
|
||||||
|
// connections close. Cleanups run last first, so the connections
|
||||||
|
// close before Close runs.
|
||||||
|
t.Cleanup(second.Close)
|
||||||
|
t.Cleanup(second.CloseClientConnections)
|
||||||
|
|
||||||
app := &App{ui: NewUI(), nick: testNick}
|
app := &App{ui: NewUI(), nick: testNick}
|
||||||
runUI(t, app.ui)
|
runUI(t, app.ui)
|
||||||
|
|
||||||
app.cmdConnect(first.URL)
|
app.cmdConnect(first.URL)
|
||||||
app.cmdConnect(second.URL)
|
app.cmdConnect(second.URL)
|
||||||
waitPoll(t, polled, "the second server")
|
|
||||||
|
select {
|
||||||
|
case <-polled:
|
||||||
|
case <-time.After(eventLoopTimeout):
|
||||||
|
t.Fatalf("the second server got no poll within %v", eventLoopTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
// The second connection's poll loop waits on its held poll, so
|
// The second connection's poll loop waits on its held poll, so
|
||||||
// another poll comes from the first loop.
|
// another poll comes from the first loop.
|
||||||
@@ -551,63 +582,6 @@ func TestSecondConnectIgnoresHeldPoll(t *testing.T) {
|
|||||||
app.mu.Unlock()
|
app.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSecondConnectAfterFailedPoll holds a poll of the earlier
|
|
||||||
// connection at its server while a second /connect runs, then
|
|
||||||
// fails it. A poll loop waits pollRetry after a failed poll, then
|
|
||||||
// polls again. The test fails if the earlier poll loop polls the
|
|
||||||
// second server.
|
|
||||||
func TestSecondConnectAfterFailedPoll(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
const session = `{"id": 1, "nick": "erin"}`
|
|
||||||
|
|
||||||
first, firstPolled := newHoldingServer(t, session)
|
|
||||||
|
|
||||||
// The second server holds the one poll of the second
|
|
||||||
// connection's poll loop, so any other poll comes from the
|
|
||||||
// earlier loop.
|
|
||||||
second, secondPolled := newHoldingServer(t, session)
|
|
||||||
|
|
||||||
// The test runs the earlier connection's poll loop itself, so
|
|
||||||
// that it can wait for the loop to end.
|
|
||||||
stop := make(chan struct{})
|
|
||||||
app := &App{
|
|
||||||
ui: NewUI(),
|
|
||||||
client: api.NewClient(first.URL),
|
|
||||||
nick: testNick,
|
|
||||||
connected: true,
|
|
||||||
stopPoll: stop,
|
|
||||||
}
|
|
||||||
runUI(t, app.ui)
|
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
app.pollLoop(stop)
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
|
|
||||||
waitPoll(t, firstPolled, "the first server")
|
|
||||||
app.cmdConnect(second.URL)
|
|
||||||
waitPoll(t, secondPolled, "the second server")
|
|
||||||
|
|
||||||
// Closing the held poll's connection makes that poll fail.
|
|
||||||
first.CloseClientConnections()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
case <-secondPolled:
|
|
||||||
t.Fatal("the earlier poll loop polled the second server")
|
|
||||||
case <-time.After(eventLoopTimeout):
|
|
||||||
t.Fatalf("the earlier poll loop did not end within %v", eventLoopTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop the second poll loop as /quit does, without stopping the UI.
|
|
||||||
app.mu.Lock()
|
|
||||||
close(app.stopPoll)
|
|
||||||
app.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestStartUp runs the client's start-up path on a simulation
|
// TestStartUp runs the client's start-up path on a simulation
|
||||||
// screen. It fails unless the event loop starts and the status
|
// screen. It fails unless the event loop starts and the status
|
||||||
// window gets two lines: first the one that holds "Welcome to
|
// window gets two lines: first the one that holds "Welcome to
|
||||||
@@ -892,53 +866,6 @@ func newServer(t *testing.T, body string) *httptest.Server {
|
|||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
// newHoldingServer starts a server that sends on the returned
|
|
||||||
// channel for each poll and holds the poll until its connection
|
|
||||||
// closes. It answers any other request with body.
|
|
||||||
func newHoldingServer(
|
|
||||||
t *testing.T, body string,
|
|
||||||
) (*httptest.Server, <-chan struct{}) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
polled := make(chan struct{})
|
|
||||||
server := httptest.NewServer(http.HandlerFunc(
|
|
||||||
func(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
if request.Method != http.MethodGet ||
|
|
||||||
request.URL.Path != "/api/v1/messages" {
|
|
||||||
_, _ = io.WriteString(writer, body)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case polled <- struct{}{}:
|
|
||||||
case <-request.Context().Done():
|
|
||||||
}
|
|
||||||
|
|
||||||
<-request.Context().Done()
|
|
||||||
},
|
|
||||||
))
|
|
||||||
// Close waits for the held polls, which end only once their
|
|
||||||
// connections close. Cleanups run last first, so the connections
|
|
||||||
// close before Close runs.
|
|
||||||
t.Cleanup(server.Close)
|
|
||||||
t.Cleanup(server.CloseClientConnections)
|
|
||||||
|
|
||||||
return server, polled
|
|
||||||
}
|
|
||||||
|
|
||||||
// waitPoll fails the test unless server gets a poll, sent on
|
|
||||||
// polled, within eventLoopTimeout.
|
|
||||||
func waitPoll(t *testing.T, polled <-chan struct{}, server string) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-polled:
|
|
||||||
case <-time.After(eventLoopTimeout):
|
|
||||||
t.Fatalf("%s got no poll within %v", server, eventLoopTimeout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// runUI runs ui on a simulated screen until the test ends.
|
// runUI runs ui on a simulated screen until the test ends.
|
||||||
func runUI(t *testing.T, ui *UI) {
|
func runUI(t *testing.T, ui *UI) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|||||||
@@ -412,7 +412,7 @@ func (hdlr *Handlers) deliverWelcome(
|
|||||||
// 004 RPL_MYINFO
|
// 004 RPL_MYINFO
|
||||||
hdlr.enqueueNumeric(
|
hdlr.enqueueNumeric(
|
||||||
ctx, clientID, irc.RplMyInfo, nick,
|
ctx, clientID, irc.RplMyInfo, nick,
|
||||||
[]string{srvName, version, service.UserModes, "ikmnostl"},
|
[]string{srvName, version, "", "ikmnostl"},
|
||||||
"",
|
"",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -6797,30 +6797,6 @@ func TestISUPPORT(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestMyInfoListsUserModes verifies the 004 numeric lists
|
|
||||||
// the user modes o and w as its third parameter.
|
|
||||||
func TestMyInfoListsUserModes(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
tserver := newTestServer(t)
|
|
||||||
token := tserver.createSession("myinfo")
|
|
||||||
|
|
||||||
msgs, _ := tserver.pollMessages(token, 0)
|
|
||||||
|
|
||||||
myInfo := findNumericWithParams(msgs, "004")
|
|
||||||
if myInfo == nil {
|
|
||||||
t.Fatal("expected 004 RPL_MYINFO")
|
|
||||||
}
|
|
||||||
|
|
||||||
params := getNumericParams(myInfo)
|
|
||||||
if len(params) != 4 || params[2] != "ow" {
|
|
||||||
t.Fatalf(
|
|
||||||
"004 params %v, want four with user modes ow third",
|
|
||||||
params,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestNonOpCannotSetModes verifies non-operators
|
// TestNonOpCannotSetModes verifies non-operators
|
||||||
// cannot set +i, +s, +k, +l, +b.
|
// cannot set +i, +s, +k, +l, +b.
|
||||||
func TestNonOpCannotSetModes(t *testing.T) {
|
func TestNonOpCannotSetModes(t *testing.T) {
|
||||||
|
|||||||
@@ -605,7 +605,7 @@ func (c *Conn) deliverWelcome() {
|
|||||||
)
|
)
|
||||||
c.sendNumeric(
|
c.sendNumeric(
|
||||||
irc.RplMyInfo,
|
irc.RplMyInfo,
|
||||||
c.serverSfx, "neoirc", service.UserModes, "mnst",
|
c.serverSfx, "neoirc", "", "mnst",
|
||||||
)
|
)
|
||||||
c.sendNumeric(
|
c.sendNumeric(
|
||||||
irc.RplIsupport,
|
irc.RplIsupport,
|
||||||
|
|||||||
@@ -486,22 +486,6 @@ func TestWelcomeContainsNick(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestMyInfoListsUserModes checks that RPL_MYINFO lists the
|
|
||||||
// user modes o and w, with no empty parameter before the
|
|
||||||
// channel modes.
|
|
||||||
func TestMyInfoListsUserModes(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
env := newTestEnv(t)
|
|
||||||
lines := env.dial(t).register("carol")
|
|
||||||
|
|
||||||
assertContains(
|
|
||||||
t, lines,
|
|
||||||
":test.irc 004 carol test.irc neoirc ow mnst",
|
|
||||||
"RPL_MYINFO with user modes",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPingPong(t *testing.T) {
|
func TestPingPong(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
@@ -1169,12 +1169,6 @@ func (s *Service) ApplyUserMode(
|
|||||||
return s.QueryUserMode(ctx, sessionID)
|
return s.QueryUserMode(ctx, sessionID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserModes lists the user modes the server supports, as
|
|
||||||
// RPL_MYINFO sends them: o (server operator) and w
|
|
||||||
// (receives WALLOPS). parseUserModeString must accept the
|
|
||||||
// same letters.
|
|
||||||
const UserModes = "ow"
|
|
||||||
|
|
||||||
// parseUserModeString parses a user mode string such as
|
// parseUserModeString parses a user mode string such as
|
||||||
// "+w", "-o" or "+w-o" into the new value of each flag it
|
// "+w", "-o" or "+w-o" into the new value of each flag it
|
||||||
// names, or nil for a flag it does not name; a later letter
|
// names, or nil for a flag it does not name; a later letter
|
||||||
|
|||||||
+2
-2
@@ -144,8 +144,8 @@ Where a schema file and the HTTP API disagree, the HTTP API is right:
|
|||||||
with `ERR_NEEDMOREPARAMS` (`461`). A `body` of `[""]` clears the topic.
|
with `ERR_NEEDMOREPARAMS` (`461`). A `body` of `[""]` clears the topic.
|
||||||
- `003.json` describes the server's creation date. The HTTP API sends the date
|
- `003.json` describes the server's creation date. The HTTP API sends the date
|
||||||
the server last started.
|
the server last started.
|
||||||
- `004.json` has example mode lists. The HTTP API sends `ow` as the user modes
|
- `004.json` has example mode lists. The HTTP API sends an empty string as the
|
||||||
and `ikmnostl` as the channel modes.
|
user modes and `ikmnostl` as the channel modes.
|
||||||
- `322.json` says `LIST` gets one reply per channel. The server leaves out a
|
- `322.json` says `LIST` gets one reply per channel. The server leaves out a
|
||||||
secret channel, one with mode `+s`, unless the user is on it.
|
secret channel, one with mode `+s`, unless the user is on it.
|
||||||
- `332.json` says `RPL_TOPIC` answers a `TOPIC` query. The HTTP API has no such
|
- `332.json` says `RPL_TOPIC` answers a `TOPIC` query. The HTTP API has no such
|
||||||
|
|||||||
Reference in new issue
Block a user