soju/server_test.go

208 lines
5.0 KiB
Go
Raw Normal View History

2021-10-05 09:59:30 +00:00
package soju
import (
"context"
2021-10-05 09:59:30 +00:00
"net"
"testing"
"golang.org/x/crypto/bcrypt"
"gopkg.in/irc.v3"
)
2021-10-05 12:03:17 +00:00
var testServerPrefix = &irc.Prefix{Name: "soju-test-server"}
2021-10-05 09:59:30 +00:00
const (
testUsername = "soju-test-user"
testPassword = testUsername
)
2021-10-11 13:57:37 +00:00
func createTempSqliteDB(t *testing.T) Database {
2021-10-08 17:15:56 +00:00
db, err := OpenDB("sqlite3", ":memory:")
2021-10-05 09:59:30 +00:00
if err != nil {
t.Fatalf("failed to create temporary SQLite database: %v", err)
}
// :memory: will open a separate database for each new connection. Make
// sure the sql package only uses a single connection. An alternative
// solution is to use "file::memory:?cache=shared".
db.(*SqliteDB).db.SetMaxOpenConns(1)
return db
}
2021-10-11 13:57:37 +00:00
func createTempPostgresDB(t *testing.T) Database {
db := &PostgresDB{db: openTempPostgresDB(t)}
if err := db.upgrade(); err != nil {
t.Fatalf("failed to upgrade PostgreSQL database: %v", err)
}
return db
}
2021-10-05 12:03:17 +00:00
func createTestUser(t *testing.T, db Database) *User {
2021-10-05 09:59:30 +00:00
hashed, err := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost)
if err != nil {
t.Fatalf("failed to generate bcrypt hash: %v", err)
}
record := &User{Username: testUsername, Password: string(hashed)}
if err := db.StoreUser(context.TODO(), record); err != nil {
2021-10-05 09:59:30 +00:00
t.Fatalf("failed to store test user: %v", err)
}
2021-10-05 12:03:17 +00:00
return record
}
2021-10-05 12:14:31 +00:00
func createTestDownstream(t *testing.T, srv *Server) ircConn {
c1, c2 := net.Pipe()
go srv.handle(newNetIRCConn(c1))
return newNetIRCConn(c2)
}
func createTestUpstream(t *testing.T, db Database, user *User) (*Network, net.Listener) {
2021-10-05 12:03:17 +00:00
ln, err := net.Listen("tcp", "localhost:0")
if err != nil {
t.Fatalf("failed to create TCP listener: %v", err)
}
network := &Network{
Name: "testnet",
Addr: "irc+insecure://" + ln.Addr().String(),
Nick: user.Username,
Enabled: true,
}
if err := db.StoreNetwork(context.TODO(), user.ID, network); err != nil {
2021-10-05 12:03:17 +00:00
t.Fatalf("failed to store test network: %v", err)
}
return network, ln
}
func mustAccept(t *testing.T, ln net.Listener) ircConn {
c, err := ln.Accept()
if err != nil {
t.Fatalf("failed accepting connection: %v", err)
}
return newNetIRCConn(c)
2021-10-05 09:59:30 +00:00
}
func expectMessage(t *testing.T, c ircConn, cmd string) *irc.Message {
msg, err := c.ReadMessage()
if err != nil {
t.Fatalf("failed to read IRC message (want %q): %v", cmd, err)
}
if msg.Command != cmd {
t.Fatalf("invalid message received: want %q, got: %v", cmd, msg)
}
return msg
}
2021-10-05 12:03:17 +00:00
func registerDownstreamConn(t *testing.T, c ircConn, network *Network) {
2021-10-05 09:59:30 +00:00
c.WriteMessage(&irc.Message{
Command: "PASS",
Params: []string{testPassword},
})
c.WriteMessage(&irc.Message{
Command: "NICK",
Params: []string{testUsername},
})
c.WriteMessage(&irc.Message{
Command: "USER",
2021-10-05 12:03:17 +00:00
Params: []string{testUsername + "/" + network.Name, "0", "*", testUsername},
2021-10-05 09:59:30 +00:00
})
expectMessage(t, c, irc.RPL_WELCOME)
}
2021-10-05 12:03:17 +00:00
func registerUpstreamConn(t *testing.T, c ircConn) {
msg := expectMessage(t, c, "CAP")
if msg.Params[0] != "LS" {
t.Fatalf("invalid CAP LS: got: %v", msg)
}
msg = expectMessage(t, c, "NICK")
nick := msg.Params[0]
if nick != testUsername {
t.Fatalf("invalid NICK: want %q, got: %v", testUsername, msg)
}
expectMessage(t, c, "USER")
c.WriteMessage(&irc.Message{
Prefix: testServerPrefix,
Command: irc.RPL_WELCOME,
Params: []string{nick, "Welcome!"},
})
c.WriteMessage(&irc.Message{
Prefix: testServerPrefix,
Command: irc.RPL_YOURHOST,
Params: []string{nick, "Your host is soju-test-server"},
})
c.WriteMessage(&irc.Message{
Prefix: testServerPrefix,
Command: irc.RPL_CREATED,
Params: []string{nick, "Who cares when the server was created?"},
})
c.WriteMessage(&irc.Message{
Prefix: testServerPrefix,
Command: irc.RPL_MYINFO,
Params: []string{nick, testServerPrefix.Name, "soju", "aiwroO", "OovaimnqpsrtklbeI"},
})
c.WriteMessage(&irc.Message{
Prefix: testServerPrefix,
Command: irc.ERR_NOMOTD,
Params: []string{nick, "No MOTD"},
})
}
2021-10-11 13:57:37 +00:00
func testServer(t *testing.T, db Database) {
2021-10-05 12:03:17 +00:00
user := createTestUser(t, db)
network, upstream := createTestUpstream(t, db, user)
defer upstream.Close()
srv := NewServer(db)
2021-10-05 09:59:30 +00:00
if err := srv.Start(); err != nil {
t.Fatalf("failed to start server: %v", err)
}
defer srv.Shutdown()
uc := mustAccept(t, upstream)
2021-10-05 12:03:17 +00:00
defer uc.Close()
registerUpstreamConn(t, uc)
dc := createTestDownstream(t, srv)
defer dc.Close()
registerDownstreamConn(t, dc, network)
2021-10-05 12:14:31 +00:00
noticeText := "This is a very important server notice."
uc.WriteMessage(&irc.Message{
Prefix: testServerPrefix,
Command: "NOTICE",
Params: []string{testUsername, noticeText},
})
var msg *irc.Message
for {
var err error
msg, err = dc.ReadMessage()
if err != nil {
t.Fatalf("failed to read IRC message: %v", err)
}
if msg.Command == "NOTICE" {
break
}
}
if msg.Params[1] != noticeText {
t.Fatalf("invalid NOTICE text: want %q, got: %v", noticeText, msg)
}
2021-10-05 09:59:30 +00:00
}
2021-10-11 13:57:37 +00:00
func TestServer(t *testing.T) {
t.Run("sqlite", func(t *testing.T) {
db := createTempSqliteDB(t)
testServer(t, db)
})
t.Run("postgres", func(t *testing.T) {
db := createTempPostgresDB(t)
testServer(t, db)
})
}