source: code/trunk/server_test.go@ 622

Last change on this file since 622 was 622, checked in by contact, 4 years ago

Run server test with PostgreSQL too

File size: 5.0 KB
Line 
1package soju
2
3import (
4 "database/sql"
5 "net"
6 "os"
7 "testing"
8
9 "golang.org/x/crypto/bcrypt"
10 "gopkg.in/irc.v3"
11)
12
13var testServerPrefix = &irc.Prefix{Name: "soju-test-server"}
14
15const (
16 testUsername = "soju-test-user"
17 testPassword = testUsername
18)
19
20func createTempSqliteDB(t *testing.T) Database {
21 db, err := OpenDB("sqlite3", ":memory:")
22 if err != nil {
23 t.Fatalf("failed to create temporary SQLite database: %v", err)
24 }
25 // :memory: will open a separate database for each new connection. Make
26 // sure the sql package only uses a single connection. An alternative
27 // solution is to use "file::memory:?cache=shared".
28 db.(*SqliteDB).db.SetMaxOpenConns(1)
29 return db
30}
31
32func createTempPostgresDB(t *testing.T) Database {
33 db := &PostgresDB{db: openTempPostgresDB(t)}
34 if err := db.upgrade(); err != nil {
35 t.Fatalf("failed to upgrade PostgreSQL database: %v", err)
36 }
37
38 return db
39}
40
41func createTestUser(t *testing.T, db Database) *User {
42 hashed, err := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost)
43 if err != nil {
44 t.Fatalf("failed to generate bcrypt hash: %v", err)
45 }
46
47 record := &User{Username: testUsername, Password: string(hashed)}
48 if err := db.StoreUser(record); err != nil {
49 t.Fatalf("failed to store test user: %v", err)
50 }
51
52 return record
53}
54
55func createTestDownstream(t *testing.T, srv *Server) ircConn {
56 c1, c2 := net.Pipe()
57 go srv.handle(newNetIRCConn(c1))
58 return newNetIRCConn(c2)
59}
60
61func createTestUpstream(t *testing.T, db Database, user *User) (*Network, net.Listener) {
62 ln, err := net.Listen("tcp", "localhost:0")
63 if err != nil {
64 t.Fatalf("failed to create TCP listener: %v", err)
65 }
66
67 network := &Network{
68 Name: "testnet",
69 Addr: "irc+insecure://" + ln.Addr().String(),
70 Nick: user.Username,
71 Enabled: true,
72 }
73 if err := db.StoreNetwork(user.ID, network); err != nil {
74 t.Fatalf("failed to store test network: %v", err)
75 }
76
77 return network, ln
78}
79
80func mustAccept(t *testing.T, ln net.Listener) ircConn {
81 c, err := ln.Accept()
82 if err != nil {
83 t.Fatalf("failed accepting connection: %v", err)
84 }
85 return newNetIRCConn(c)
86}
87
88func expectMessage(t *testing.T, c ircConn, cmd string) *irc.Message {
89 msg, err := c.ReadMessage()
90 if err != nil {
91 t.Fatalf("failed to read IRC message (want %q): %v", cmd, err)
92 }
93 if msg.Command != cmd {
94 t.Fatalf("invalid message received: want %q, got: %v", cmd, msg)
95 }
96 return msg
97}
98
99func registerDownstreamConn(t *testing.T, c ircConn, network *Network) {
100 c.WriteMessage(&irc.Message{
101 Command: "PASS",
102 Params: []string{testPassword},
103 })
104 c.WriteMessage(&irc.Message{
105 Command: "NICK",
106 Params: []string{testUsername},
107 })
108 c.WriteMessage(&irc.Message{
109 Command: "USER",
110 Params: []string{testUsername + "/" + network.Name, "0", "*", testUsername},
111 })
112
113 expectMessage(t, c, irc.RPL_WELCOME)
114}
115
116func registerUpstreamConn(t *testing.T, c ircConn) {
117 msg := expectMessage(t, c, "CAP")
118 if msg.Params[0] != "LS" {
119 t.Fatalf("invalid CAP LS: got: %v", msg)
120 }
121 msg = expectMessage(t, c, "NICK")
122 nick := msg.Params[0]
123 if nick != testUsername {
124 t.Fatalf("invalid NICK: want %q, got: %v", testUsername, msg)
125 }
126 expectMessage(t, c, "USER")
127
128 c.WriteMessage(&irc.Message{
129 Prefix: testServerPrefix,
130 Command: irc.RPL_WELCOME,
131 Params: []string{nick, "Welcome!"},
132 })
133 c.WriteMessage(&irc.Message{
134 Prefix: testServerPrefix,
135 Command: irc.RPL_YOURHOST,
136 Params: []string{nick, "Your host is soju-test-server"},
137 })
138 c.WriteMessage(&irc.Message{
139 Prefix: testServerPrefix,
140 Command: irc.RPL_CREATED,
141 Params: []string{nick, "Who cares when the server was created?"},
142 })
143 c.WriteMessage(&irc.Message{
144 Prefix: testServerPrefix,
145 Command: irc.RPL_MYINFO,
146 Params: []string{nick, testServerPrefix.Name, "soju", "aiwroO", "OovaimnqpsrtklbeI"},
147 })
148 c.WriteMessage(&irc.Message{
149 Prefix: testServerPrefix,
150 Command: irc.ERR_NOMOTD,
151 Params: []string{nick, "No MOTD"},
152 })
153}
154
155func testServer(t *testing.T, db Database) {
156 user := createTestUser(t, db)
157 network, upstream := createTestUpstream(t, db, user)
158 defer upstream.Close()
159
160 srv := NewServer(db)
161 if err := srv.Start(); err != nil {
162 t.Fatalf("failed to start server: %v", err)
163 }
164 defer srv.Shutdown()
165
166 uc := mustAccept(t, upstream)
167 defer uc.Close()
168 registerUpstreamConn(t, uc)
169
170 dc := createTestDownstream(t, srv)
171 defer dc.Close()
172 registerDownstreamConn(t, dc, network)
173
174 noticeText := "This is a very important server notice."
175 uc.WriteMessage(&irc.Message{
176 Prefix: testServerPrefix,
177 Command: "NOTICE",
178 Params: []string{testUsername, noticeText},
179 })
180
181 var msg *irc.Message
182 for {
183 var err error
184 msg, err = dc.ReadMessage()
185 if err != nil {
186 t.Fatalf("failed to read IRC message: %v", err)
187 }
188 if msg.Command == "NOTICE" {
189 break
190 }
191 }
192
193 if msg.Params[1] != noticeText {
194 t.Fatalf("invalid NOTICE text: want %q, got: %v", noticeText, msg)
195 }
196}
197
198func TestServer(t *testing.T) {
199 t.Run("sqlite", func(t *testing.T) {
200 db := createTempSqliteDB(t)
201 testServer(t, db)
202 })
203
204 t.Run("postgres", func(t *testing.T) {
205 db := createTempPostgresDB(t)
206 testServer(t, db)
207 })
208}
Note: See TracBrowser for help on using the repository browser.