Repository navigation
Expand file tree
/
Copy pathtestutil_test.go
More file actions
165 lines (145 loc) · 5.1 KB
/
Copy pathtestutil_test.go
File metadata and controls
165 lines (145 loc) · 5.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package main
import (
"crypto/ed25519"
"crypto/rand"
"encoding/pem"
"io/fs"
"net/http"
"net/http/httptest"
"net/url"
"path/filepath"
"strings"
"testing"
"time"
"github.com/d33mobile/dday/internal/matrixbot"
"github.com/d33mobile/dday/internal/store"
"filippo.io/age"
"filippo.io/age/agessh"
"golang.org/x/crypto/ssh"
)
// genKeypair creates an in-memory SSH ed25519 keypair and returns the matching
// age recipient (encrypt) and identity (decrypt).
func genKeypair(t *testing.T) (age.Recipient, age.Identity) {
t.Helper()
pub, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("generate key: %v", err)
}
block, err := ssh.MarshalPrivateKey(priv, "dday-test")
if err != nil {
t.Fatalf("marshal private key: %v", err)
}
id, err := agessh.ParseIdentity(pem.EncodeToMemory(block))
if err != nil {
t.Fatalf("parse identity: %v", err)
}
sshPub, err := ssh.NewPublicKey(pub)
if err != nil {
t.Fatalf("new public key: %v", err)
}
r, err := agessh.ParseRecipient(string(ssh.MarshalAuthorizedKey(sshPub)))
if err != nil {
t.Fatalf("parse recipient: %v", err)
}
return r, id
}
// testTokenSecret is the shared HMAC key both the test token helpers and the
// mux under test use, so tokens the tests mint verify in decode().
const testTokenSecret = "web-token-secret"
// testEnv wires an ephemeral store, keypair and mux together.
type testEnv struct {
recipient age.Recipient
store *store.Store
handler http.Handler
}
func newTestEnv(t *testing.T, open bool, limit int) *testEnv {
return newTestEnvToken(t, open, limit, "")
}
// newTestEnvToken is newTestEnv with an explicit internal token, so tests can
// exercise the guarded /api/registered endpoint (empty token disables it). It
// uses no waiting-list capacity, so total == seatLimit and the classic full
// behavior applies.
func newTestEnvToken(t *testing.T, open bool, limit int, internalToken string) *testEnv {
return newTestEnvSeats(t, open, limit, 0, internalToken)
}
// newTestEnvSeats builds the mux with an explicit confirmed-seat limit and
// waiting-list limit, so tests can exercise the two-tier capacity model.
func newTestEnvSeats(t *testing.T, open bool, seatLimit, waitlistLimit int, internalToken string) *testEnv {
t.Helper()
return newTestEnvFull(t, open, seatLimit, waitlistLimit, internalToken, "")
}
// newTestEnvAdmin builds the mux with an explicit admin token, so the /admin
// tests can exercise the guarded view (empty token disables it).
func newTestEnvAdmin(t *testing.T, seatLimit, waitlistLimit int, adminToken string) *testEnv {
t.Helper()
return newTestEnvFull(t, true, seatLimit, waitlistLimit, "", adminToken)
}
// newTestEnvFull is the single mux builder every helper above delegates to.
func newTestEnvFull(t *testing.T, open bool, seatLimit, waitlistLimit int, internalToken, adminToken string) *testEnv {
t.Helper()
rcpt, id := genKeypair(t)
st, err := store.Open(filepath.Join(t.TempDir(), "test.db"))
if err != nil {
t.Fatalf("open store: %v", err)
}
t.Cleanup(func() { _ = st.Close() })
sub, err := fs.Sub(embedded, ".")
if err != nil {
t.Fatalf("embed sub: %v", err)
}
h := newMux(deps{
store: st,
identity: id,
seatLimit: seatLimit,
waitlistLimit: waitlistLimit,
isOpen: func() bool { return open },
files: http.FS(sub),
internalToken: internalToken,
adminToken: adminToken,
tokenSecret: testTokenSecret,
})
return &testEnv{recipient: rcpt, store: st, handler: h}
}
func (e *testEnv) token(t *testing.T, handle string) string {
t.Helper()
return e.tokenAt(t, handle, time.Now().Unix())
}
// tokenAt mints a registration token with an explicit Issued time, so tests can
// exercise the TTL (expired / future-dated links).
func (e *testEnv) tokenAt(t *testing.T, handle string, issued int64) string {
t.Helper()
return e.tokenKindAt(t, handle, matrixbot.KindReg, issued)
}
// tokenKind mints a token of the given kind (matrixbot.KindReg / KindPanel) for
// "now", so tests can check that each endpoint only accepts its own kind.
func (e *testEnv) tokenKind(t *testing.T, handle, kind string) string {
t.Helper()
return e.tokenKindAt(t, handle, kind, time.Now().Unix())
}
func (e *testEnv) tokenKindAt(t *testing.T, handle, kind string, issued int64) string {
t.Helper()
tok, err := matrixbot.EncodeRegToken(e.recipient, testTokenSecret,
matrixbot.RegPayload{Handle: handle, Issued: issued, Kind: kind})
if err != nil {
t.Fatalf("encode token: %v", err)
}
return tok
}
func (e *testEnv) get(t *testing.T, path string) *httptest.ResponseRecorder {
t.Helper()
rec := httptest.NewRecorder()
e.handler.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, path, nil))
return rec
}
func (e *testEnv) post(t *testing.T, form url.Values) *httptest.ResponseRecorder {
t.Helper()
return e.postTo(t, "/register", form)
}
func (e *testEnv) postTo(t *testing.T, path string, form url.Values) *httptest.ResponseRecorder {
t.Helper()
req := httptest.NewRequest(http.MethodPost, path, strings.NewReader(form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
rec := httptest.NewRecorder()
e.handler.ServeHTTP(rec, req)
return rec
}