source: code/trunk/server_test.go@ 686

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

Add context args to Database interface

This is a mecanical change, which just lifts up the context.TODO()
calls from inside the DB implementations to the callers.

Future work involves properly wiring up the contexts when it makes
sense.

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