mirror of
https://github.com/gissleh/irc.git
synced 2026-10-02 08:35:35 +00:00
Added more test utilities for client_test.go, added NAMES and MODE handling for channels
This commit is contained in:
+51
-2
@@ -1,11 +1,15 @@
|
||||
package irc
|
||||
|
||||
import "git.aiterp.net/gisle/irc/list"
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"git.aiterp.net/gisle/irc/list"
|
||||
)
|
||||
|
||||
// A Channel is a target that manages the userlist
|
||||
type Channel struct {
|
||||
name string
|
||||
userlist list.List
|
||||
userlist *list.List
|
||||
}
|
||||
|
||||
// Kind returns "channel"
|
||||
@@ -62,5 +66,50 @@ func (channel *Channel) Handle(event *Event, client *Client) {
|
||||
|
||||
channel.userlist.Patch(event.Nick, list.UserPatch{User: newUser, Host: newHost})
|
||||
}
|
||||
case "packet.353": // NAMES
|
||||
{
|
||||
channel.userlist.SetAutoSort(false)
|
||||
tokens := strings.Split(event.Text, " ")
|
||||
for _, token := range tokens {
|
||||
channel.userlist.InsertFromNamesToken(token)
|
||||
}
|
||||
}
|
||||
case "packet.366": // End of NAMES
|
||||
{
|
||||
channel.userlist.SetAutoSort(true)
|
||||
}
|
||||
case "packet.mode":
|
||||
{
|
||||
isupport := client.ISupport()
|
||||
plus := false
|
||||
argIndex := 2
|
||||
|
||||
for _, ch := range event.Arg(1) {
|
||||
if ch == '+' {
|
||||
plus = true
|
||||
continue
|
||||
}
|
||||
if ch == '-' {
|
||||
plus = false
|
||||
continue
|
||||
}
|
||||
|
||||
arg := ""
|
||||
if isupport.ModeTakesArgument(ch, plus) {
|
||||
arg = event.Arg(argIndex)
|
||||
argIndex++
|
||||
}
|
||||
|
||||
if isupport.IsPermissionMode(ch) {
|
||||
if plus {
|
||||
channel.userlist.AddMode(arg, ch)
|
||||
} else {
|
||||
channel.userlist.RemoveMode(arg, ch)
|
||||
}
|
||||
} else {
|
||||
// TODO: track non-permission modes
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
|
||||
"git.aiterp.net/gisle/irc/ircutil"
|
||||
"git.aiterp.net/gisle/irc/isupport"
|
||||
"git.aiterp.net/gisle/irc/list"
|
||||
)
|
||||
|
||||
var supportedCaps = []string{
|
||||
@@ -789,13 +790,13 @@ func (client *Client) handleEvent(event *Event) {
|
||||
client.handleInTargets(event.Nick, event)
|
||||
}
|
||||
|
||||
// Join/part handling
|
||||
// Channel join/leave/mode handling
|
||||
case "packet.join":
|
||||
{
|
||||
var channel *Channel
|
||||
|
||||
if event.Nick == client.nick {
|
||||
channel = &Channel{name: event.Arg(0)}
|
||||
channel = &Channel{name: event.Arg(0), userlist: list.New(&client.isupport)}
|
||||
client.AddTarget(channel)
|
||||
} else {
|
||||
channel = client.Channel(event.Arg(0))
|
||||
@@ -829,12 +830,44 @@ func (client *Client) handleEvent(event *Event) {
|
||||
client.handleInTargets(event.Nick, event)
|
||||
}
|
||||
|
||||
case "packet.353": // NAMES
|
||||
{
|
||||
channel := client.Channel(event.Arg(2))
|
||||
if channel != nil {
|
||||
channel.Handle(event, client)
|
||||
}
|
||||
}
|
||||
|
||||
case "packet.366": // End of NAMES
|
||||
{
|
||||
channel := client.Channel(event.Arg(1))
|
||||
if channel != nil {
|
||||
channel.Handle(event, client)
|
||||
}
|
||||
}
|
||||
|
||||
case "packet.mode":
|
||||
{
|
||||
targetName := event.Arg(0)
|
||||
|
||||
if client.isupport.IsChannel(targetName) {
|
||||
channel := client.Channel(targetName)
|
||||
if channel != nil {
|
||||
channel.Handle(event, client)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Account handling
|
||||
case "packet.account":
|
||||
{
|
||||
client.handleInTargets(event.Nick, event)
|
||||
}
|
||||
}
|
||||
|
||||
if len(event.targets) == 0 {
|
||||
event.targets = append(event.targets, client.status)
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) handleInTargets(nick string, event *Event) {
|
||||
@@ -857,16 +890,19 @@ func (client *Client) handleInTargets(nick string, event *Event) {
|
||||
{
|
||||
if target.user.Nick == nick {
|
||||
target.Handle(event, client)
|
||||
event.targets = append(event.targets, target)
|
||||
}
|
||||
}
|
||||
case *Status:
|
||||
{
|
||||
if client.nick == event.Nick {
|
||||
target.Handle(event, client)
|
||||
event.targets = append(event.targets, target)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.mutex.RUnlock()
|
||||
}
|
||||
|
||||
|
||||
+35
-3
@@ -2,6 +2,7 @@ package irc_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"git.aiterp.net/gisle/irc"
|
||||
@@ -13,7 +14,7 @@ func TestClient(t *testing.T) {
|
||||
Nick: "Test",
|
||||
User: "Tester",
|
||||
RealName: "...",
|
||||
Alternatives: []string{"Test2", "Test3", "Test4"},
|
||||
Alternatives: []string{"Test2", "Test3", "Test4", "Test768"},
|
||||
})
|
||||
|
||||
t.Logf("Client.ID = %#+v", client.ID())
|
||||
@@ -38,7 +39,7 @@ func TestClient(t *testing.T) {
|
||||
{Kind: 'S', Data: ":testserver.example.com 443 * Test3 :Nick is not available"},
|
||||
{Kind: 'C', Data: "NICK Test4"},
|
||||
{Kind: 'S', Data: ":testserver.example.com 443 * Test4 :Nick is not available"},
|
||||
{Kind: 'C', Data: "NICK Test*"},
|
||||
{Kind: 'C', Data: "NICK Test768"},
|
||||
{Kind: 'S', Data: ":testserver.example.com 001 Test768 :Welcome to the TestServer Internet Relay Chat Network test"},
|
||||
{Kind: 'C', Data: "WHO Test768*"},
|
||||
{Kind: 'S', Data: ":testserver.example.com 002 Test768 :Your host is testserver.example.com[testserver.example.com/6667], running version charybdis-4-rc3"},
|
||||
@@ -61,8 +62,38 @@ func TestClient(t *testing.T) {
|
||||
{Kind: 'S', Data: ":testserver.example.com 372 Test768 :- - #Test :: Test Channel"},
|
||||
{Kind: 'S', Data: ":testserver.example.com 372 Test768 :- - #Test2 :: Other Test Channel"},
|
||||
{Kind: 'S', Data: ":testserver.example.com 376 Test768 :End of /MOTD command."},
|
||||
{Kind: 'S', Data: ":test MODE Test768 :+i"},
|
||||
{Kind: 'S', Data: ":Test768 MODE Test768 :+i"},
|
||||
{Kind: 'C', Data: "JOIN #Test"},
|
||||
{Kind: 'S', Data: ":Test768!~test@127.0.0.1 JOIN #Test *"},
|
||||
{Kind: 'S', Data: ":testserver.example.com 353 Test768 = #Test :Test768!~test@127.0.0.1 @+Gisle!gisle@gisle.me"},
|
||||
{Kind: 'S', Data: ":testserver.example.com 366 Test768 #Test :End of /NAMES list."},
|
||||
{Kind: 'S', Data: ":Gisle!~irce@10.32.0.1 MODE #Test +osv Test768 Test768"},
|
||||
{Kind: 'S', Data: ":Gisle!~irce@10.32.0.1 MODE #Test +N-s "},
|
||||
{Kind: 'S', Data: ":Test1234!~test2@172.17.37.1 JOIN #Test Test1234"},
|
||||
{Kind: 'S', Data: ":Gisle!~irce@10.32.0.1 MODE #Test +v Test1234"},
|
||||
{Kind: 'S', Data: "PING :archgisle.lan"},
|
||||
{Kind: 'C', Data: "PONG :archgisle.lan"},
|
||||
{Callback: func() error {
|
||||
channel := client.Channel("#Test")
|
||||
if channel == nil {
|
||||
return errors.New("Channel #Test not found")
|
||||
}
|
||||
|
||||
err := irctest.AssertUserlist(t, channel, "@Gisle", "@Test768", "+Test1234")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userTest1234, ok := channel.UserList().User("Test1234")
|
||||
if !ok {
|
||||
return errors.New("Test1234 not found")
|
||||
}
|
||||
if userTest1234.Account != "Test1234" {
|
||||
return errors.New("Test1234 did not get account from extended-join")
|
||||
}
|
||||
|
||||
return nil
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -89,6 +120,7 @@ func TestClient(t *testing.T) {
|
||||
if fail != nil {
|
||||
t.Error("Index:", fail.Index)
|
||||
t.Error("NetErr:", fail.NetErr)
|
||||
t.Error("CBErr:", fail.CBErr)
|
||||
t.Error("Result:", fail.Result)
|
||||
if fail.Index >= 0 {
|
||||
t.Error("Line.Kind:", interaction.Lines[fail.Index].Kind)
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package irctest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.aiterp.net/gisle/irc"
|
||||
)
|
||||
|
||||
// AssertUserlist compares the userlist to a list of prefixed nicks
|
||||
func AssertUserlist(t *testing.T, channel *irc.Channel, assertedOrder ...string) error {
|
||||
users := channel.UserList().Users()
|
||||
order := make([]string, 0, len(users))
|
||||
for _, user := range users {
|
||||
order = append(order, user.PrefixedNick)
|
||||
}
|
||||
|
||||
orderA := strings.Join(order, ", ")
|
||||
orderB := strings.Join(assertedOrder, ", ")
|
||||
|
||||
if orderA != orderB {
|
||||
t.Logf("Userlist: %s", orderA)
|
||||
t.Logf("Asserted: %s", orderB)
|
||||
|
||||
t.Fail()
|
||||
|
||||
return errors.New("Userlists does not match")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -49,52 +49,64 @@ func (interaction *Interaction) Listen() (addr string, err error) {
|
||||
for i := 0; i < len(lines); i++ {
|
||||
line := lines[i]
|
||||
|
||||
switch line.Kind {
|
||||
case 'S':
|
||||
{
|
||||
_, err := conn.Write(append([]byte(line.Data), '\r', '\n'))
|
||||
if err != nil {
|
||||
interaction.Failure = &InteractionFailure{
|
||||
Index: i, NetErr: err,
|
||||
if line.Data != "" {
|
||||
switch line.Kind {
|
||||
case 'S':
|
||||
{
|
||||
_, err := conn.Write(append([]byte(line.Data), '\r', '\n'))
|
||||
if err != nil {
|
||||
interaction.Failure = &InteractionFailure{
|
||||
Index: i, NetErr: err,
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
case 'C':
|
||||
{
|
||||
conn.SetReadDeadline(time.Now().Add(time.Second))
|
||||
input, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
interaction.Failure = &InteractionFailure{
|
||||
Index: i, NetErr: err,
|
||||
}
|
||||
return
|
||||
}
|
||||
input = strings.Replace(input, "\r", "", -1)
|
||||
input = strings.Replace(input, "\n", "", 1)
|
||||
|
||||
match := line.Data
|
||||
success := false
|
||||
|
||||
if strings.HasSuffix(match, "*") {
|
||||
success = strings.HasPrefix(input, match[:len(match)-1])
|
||||
} else {
|
||||
success = match == input
|
||||
}
|
||||
|
||||
interaction.Log = append(interaction.Log, input)
|
||||
|
||||
if !success {
|
||||
if !interaction.Strict {
|
||||
i--
|
||||
continue
|
||||
}
|
||||
|
||||
interaction.Failure = &InteractionFailure{
|
||||
Index: i, Result: input,
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
case 'C':
|
||||
{
|
||||
conn.SetReadDeadline(time.Now().Add(time.Second))
|
||||
input, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
interaction.Failure = &InteractionFailure{
|
||||
Index: i, NetErr: err,
|
||||
}
|
||||
return
|
||||
}
|
||||
input = strings.Replace(input, "\r", "", -1)
|
||||
input = strings.Replace(input, "\n", "", 1)
|
||||
}
|
||||
|
||||
match := line.Data
|
||||
success := false
|
||||
|
||||
if strings.HasSuffix(match, "*") {
|
||||
success = strings.HasPrefix(input, match[:len(match)-1])
|
||||
} else {
|
||||
success = match == input
|
||||
}
|
||||
|
||||
interaction.Log = append(interaction.Log, input)
|
||||
|
||||
if !success {
|
||||
if !interaction.Strict {
|
||||
i--
|
||||
continue
|
||||
}
|
||||
|
||||
interaction.Failure = &InteractionFailure{
|
||||
Index: i, Result: input,
|
||||
}
|
||||
return
|
||||
if line.Callback != nil {
|
||||
err := line.Callback()
|
||||
if err != nil {
|
||||
interaction.Failure = &InteractionFailure{
|
||||
Index: i, CBErr: err,
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,11 +126,13 @@ type InteractionFailure struct {
|
||||
Index int
|
||||
Result string
|
||||
NetErr error
|
||||
CBErr error
|
||||
}
|
||||
|
||||
// InteractionLine is part of an interaction, whether it is a line
|
||||
// that is sent to a client or a line expected from a client.
|
||||
type InteractionLine struct {
|
||||
Kind byte
|
||||
Data string
|
||||
Kind byte
|
||||
Data string
|
||||
Callback func() error
|
||||
}
|
||||
|
||||
@@ -218,6 +218,30 @@ func (isupport *ISupport) IsPermissionMode(flag rune) bool {
|
||||
return strings.ContainsRune(isupport.modeOrder, flag)
|
||||
}
|
||||
|
||||
// ModeTakesArgument returns true if the mode takes an argument
|
||||
func (isupport *ISupport) ModeTakesArgument(flag rune, plus bool) bool {
|
||||
isupport.lock.RLock()
|
||||
defer isupport.lock.RUnlock()
|
||||
|
||||
// Permission modes always take an argument.
|
||||
if strings.ContainsRune(isupport.modeOrder, flag) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Modes in category A and B always takes an argument
|
||||
if strings.ContainsRune(isupport.chanModes[0], flag) || strings.ContainsRune(isupport.chanModes[1], flag) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Modes in category C only takes one when added
|
||||
if plus && strings.ContainsRune(isupport.chanModes[1], flag) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Modes in category D and outside never does
|
||||
return false
|
||||
}
|
||||
|
||||
// ChannelModeType returns a number from 0 to 3 based on what block of mode
|
||||
// in the CHANMODES variable it fits into. If it's not found at all, it will
|
||||
// return -1
|
||||
|
||||
Reference in New Issue
Block a user