返回 DeepSeek-Reasonix
auth_fragment_test.go
根目录 / internal / serve / auth_fragment_test.go
1 package serve
2
3 import (
4 "net/http"
5 "net/http/httptest"
6 "strings"
7 "testing"
8
9 "reasonix/internal/config"
10 )
11
12 func TestTokenModeAllowsOnlyBootstrapShellWithoutAuth(t *testing.T) {
13 ag := newAuthGate(config.ServeConfig{AuthMode: "token", Token: "secret"})
14 ts := httptest.NewServer(ag.middleware(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
15 w.WriteHeader(http.StatusOK)
16 })))
17 defer ts.Close()
18
19 for _, path := range []string{"/", "/assets/logo-wordmark.svg", "/sessions/session-123"} {
20 resp, err := http.Get(ts.URL + path)
21 if err != nil {
22 t.Fatal(err)
23 }
24 resp.Body.Close()
25 if resp.StatusCode != http.StatusOK {
26 t.Errorf("GET %s status = %d, want 200", path, resp.StatusCode)
27 }
28 }
29 resp, err := http.Get(ts.URL + "/status")
30 if err != nil {
31 t.Fatal(err)
32 }
33 resp.Body.Close()
34 if resp.StatusCode != http.StatusUnauthorized {
35 t.Errorf("GET /status = %d, want 401", resp.StatusCode)
36 }
37 }
38
39 func TestTokenModeDoesNotPublishNestedSessionLikePaths(t *testing.T) {
40 ag := newAuthGate(config.ServeConfig{AuthMode: "token", Token: "secret"})
41 ts := httptest.NewServer(ag.middleware(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
42 w.WriteHeader(http.StatusOK)
43 })))
44 defer ts.Close()
45
46 for _, path := range []string{"/sessions/", "/sessions/a/status", "/sessions"} {
47 resp, err := http.Get(ts.URL + path)
48 if err != nil {
49 t.Fatal(err)
50 }
51 resp.Body.Close()
52 if resp.StatusCode != http.StatusUnauthorized {
53 t.Errorf("GET %s status = %d, want 401", path, resp.StatusCode)
54 }
55 }
56 }
57
58 func TestTokenModeFragmentBootstrapSetsHTTPOnlyCookie(t *testing.T) {
59 ag := newAuthGate(config.ServeConfig{AuthMode: "token", Token: "secret"})
60 passed := false
61 ts := httptest.NewServer(ag.middleware(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
62 passed = true
63 w.WriteHeader(http.StatusOK)
64 })))
65 defer ts.Close()
66
67 resp, err := http.Post(ts.URL+"/auth/token", "application/json", strings.NewReader(`{"token":"secret"}`))
68 if err != nil {
69 t.Fatal(err)
70 }
71 resp.Body.Close()
72 if resp.StatusCode != http.StatusNoContent {
73 t.Fatalf("bootstrap status = %d, want 204", resp.StatusCode)
74 }
75 if passed {
76 t.Fatal("bootstrap request must not reach the application handler")
77 }
78 cookie := findCookie(resp.Cookies(), cookieToken)
79 if cookie == nil || !cookie.HttpOnly {
80 t.Fatal("bootstrap response must set an HttpOnly token cookie")
81 }
82
83 req, _ := http.NewRequest(http.MethodGet, ts.URL+"/status", nil)
84 req.AddCookie(cookie)
85 resp, err = http.DefaultClient.Do(req)
86 if err != nil {
87 t.Fatal(err)
88 }
89 resp.Body.Close()
90 if resp.StatusCode != http.StatusOK || !passed {
91 t.Fatalf("cookie-authenticated request status = %d, passed = %v", resp.StatusCode, passed)
92 }
93 }
94
95 func TestTokenModeFragmentBootstrapRejectsInvalidRequests(t *testing.T) {
96 ag := newAuthGate(config.ServeConfig{AuthMode: "token", Token: "secret"})
97 ts := httptest.NewServer(ag.middleware(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
98 t.Fatal("invalid bootstrap request reached application handler")
99 })))
100 defer ts.Close()
101
102 tests := []struct {
103 name string
104 contentType string
105 body string
106 want int
107 }{
108 {name: "wrong token", contentType: "application/json", body: `{"token":"wrong"}`, want: http.StatusUnauthorized},
109 {name: "non JSON", contentType: "text/plain", body: `{"token":"secret"}`, want: http.StatusUnsupportedMediaType},
110 {name: "malformed JSON", contentType: "application/json", body: `{`, want: http.StatusBadRequest},
111 }
112 for _, tt := range tests {
113 t.Run(tt.name, func(t *testing.T) {
114 resp, err := http.Post(ts.URL+"/auth/token", tt.contentType, strings.NewReader(tt.body))
115 if err != nil {
116 t.Fatal(err)
117 }
118 resp.Body.Close()
119 if resp.StatusCode != tt.want {
120 t.Fatalf("status = %d, want %d", resp.StatusCode, tt.want)
121 }
122 })
123 }
124 }
125
125 lines GO