91 lines
2.8 KiB
Go
91 lines
2.8 KiB
Go
package api
|
|
|
|
import (
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/jasonross/idea-collect/backend/internal/ratelimit"
|
|
"github.com/jasonross/idea-collect/backend/internal/session"
|
|
)
|
|
|
|
func testServer(t *testing.T, perMinute float64, burst int) *Server {
|
|
t.Helper()
|
|
sm := session.NewManager([]byte("test-secret-at-least-16-bytes"), time.Hour, false)
|
|
rl := ratelimit.New(perMinute, burst)
|
|
t.Cleanup(rl.Close)
|
|
log := slog.New(slog.NewTextHandler(io.Discard, nil))
|
|
// store and publisher are nil: the paths under test return before touching them.
|
|
return NewServer(nil, sm, rl, nil, []string{"http://localhost:5173"}, log)
|
|
}
|
|
|
|
func TestComposeIssueBody(t *testing.T) {
|
|
got := composeIssueBody("Jane Doe", "Add dark mode")
|
|
if !strings.Contains(got, "**Submitted by:** Jane Doe") || !strings.Contains(got, "Add dark mode") {
|
|
t.Errorf("unexpected body: %q", got)
|
|
}
|
|
}
|
|
|
|
func TestClientIPPrefersForwardedFor(t *testing.T) {
|
|
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
r.RemoteAddr = "10.0.0.1:5000"
|
|
r.Header.Set("X-Forwarded-For", "203.0.113.7, 10.0.0.1")
|
|
if ip := clientIP(r); ip != "203.0.113.7" {
|
|
t.Errorf("got %q, want 203.0.113.7", ip)
|
|
}
|
|
}
|
|
|
|
func TestAuthEmptyCodeIsBadRequest(t *testing.T) {
|
|
s := testServer(t, 100, 100)
|
|
rec := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodPost, "/api/auth", strings.NewReader(`{"auth_code":" "}`))
|
|
s.Handler().ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusBadRequest {
|
|
t.Errorf("got %d, want 400", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestAuthRateLimited(t *testing.T) {
|
|
s := testServer(t, 1, 2) // burst of 2
|
|
do := func() int {
|
|
rec := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodPost, "/api/auth", strings.NewReader(`{"auth_code":""}`))
|
|
req.RemoteAddr = "198.51.100.5:1234"
|
|
s.Handler().ServeHTTP(rec, req)
|
|
return rec.Code
|
|
}
|
|
_ = do()
|
|
_ = do()
|
|
if code := do(); code != http.StatusTooManyRequests {
|
|
t.Errorf("third attempt: got %d, want 429", code)
|
|
}
|
|
}
|
|
|
|
func TestSubmitWithoutSessionUnauthorized(t *testing.T) {
|
|
s := testServer(t, 100, 100)
|
|
rec := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodPost, "/api/submissions", strings.NewReader(`{"title":"t","body":"b"}`))
|
|
s.Handler().ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusUnauthorized {
|
|
t.Errorf("got %d, want 401", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestCORSPreflight(t *testing.T) {
|
|
s := testServer(t, 100, 100)
|
|
rec := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodOptions, "/api/auth", nil)
|
|
req.Header.Set("Origin", "http://localhost:5173")
|
|
s.Handler().ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusNoContent {
|
|
t.Errorf("preflight got %d, want 204", rec.Code)
|
|
}
|
|
if rec.Header().Get("Access-Control-Allow-Origin") != "http://localhost:5173" {
|
|
t.Errorf("missing CORS allow-origin header")
|
|
}
|
|
}
|