176 lines
4.7 KiB
Go
176 lines
4.7 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"ipecho/internal/clientip"
|
|
)
|
|
|
|
func TestIPEndpointReturnsPlainTextClientIP(t *testing.T) {
|
|
handler := New(clientip.Resolver{})
|
|
req := httptest.NewRequest(http.MethodGet, "/ip", nil)
|
|
req.RemoteAddr = "198.51.100.20:54123"
|
|
rec := httptest.NewRecorder()
|
|
|
|
handler.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("got status %d, want %d", rec.Code, http.StatusOK)
|
|
}
|
|
|
|
if got, want := rec.Body.String(), "198.51.100.20\n"; got != want {
|
|
t.Fatalf("got body %q, want %q", got, want)
|
|
}
|
|
|
|
if got := rec.Header().Get("Content-Type"); !strings.HasPrefix(got, "text/plain") {
|
|
t.Fatalf("got content-type %q, want text/plain", got)
|
|
}
|
|
}
|
|
|
|
func TestRootReturnsPlainTextClientIPForCLIRequests(t *testing.T) {
|
|
handler := New(clientip.Resolver{})
|
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
req.RemoteAddr = "198.51.100.20:54123"
|
|
req.Header.Set("Accept", "*/*")
|
|
rec := httptest.NewRecorder()
|
|
|
|
handler.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("got status %d, want %d", rec.Code, http.StatusOK)
|
|
}
|
|
|
|
if got, want := rec.Body.String(), "198.51.100.20\n"; got != want {
|
|
t.Fatalf("got body %q, want %q", got, want)
|
|
}
|
|
|
|
if got := rec.Header().Get("Content-Type"); !strings.HasPrefix(got, "text/plain") {
|
|
t.Fatalf("got content-type %q, want text/plain", got)
|
|
}
|
|
}
|
|
|
|
func TestRootReturnsHTMLForBrowserRequests(t *testing.T) {
|
|
handler := New(clientip.Resolver{})
|
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
req.RemoteAddr = "198.51.100.20:54123"
|
|
req.Header.Set("Accept", "text/html,application/xhtml+xml")
|
|
rec := httptest.NewRecorder()
|
|
|
|
handler.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("got status %d, want %d", rec.Code, http.StatusOK)
|
|
}
|
|
|
|
if got := rec.Header().Get("Content-Type"); !strings.HasPrefix(got, "text/html") {
|
|
t.Fatalf("got content-type %q, want text/html", got)
|
|
}
|
|
|
|
body := rec.Body.String()
|
|
for _, want := range []string{"IP Echo", "198.51.100.20", "Copy IP", "/json"} {
|
|
if !strings.Contains(body, want) {
|
|
t.Fatalf("body does not contain %q", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestJSONEndpointReturnsRequestMetadata(t *testing.T) {
|
|
handler := New(clientip.Resolver{TrustProxyHeaders: true})
|
|
req := httptest.NewRequest(http.MethodGet, "/json", nil)
|
|
req.RemoteAddr = "198.51.100.20:54123"
|
|
req.Header.Set("User-Agent", "ipecho-test")
|
|
req.Header.Set("X-Forwarded-For", "203.0.113.10")
|
|
rec := httptest.NewRecorder()
|
|
|
|
handler.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("got status %d, want %d", rec.Code, http.StatusOK)
|
|
}
|
|
|
|
var got ClientInfo
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &got); err != nil {
|
|
t.Fatalf("decode response: %v", err)
|
|
}
|
|
|
|
if got.IP != "203.0.113.10" {
|
|
t.Fatalf("got ip %q, want %q", got.IP, "203.0.113.10")
|
|
}
|
|
|
|
if got.UserAgent != "ipecho-test" {
|
|
t.Fatalf("got user agent %q, want %q", got.UserAgent, "ipecho-test")
|
|
}
|
|
}
|
|
|
|
func TestHeadersEndpointIncludesHostAndHeaders(t *testing.T) {
|
|
handler := New(clientip.Resolver{})
|
|
req := httptest.NewRequest(http.MethodGet, "/headers", nil)
|
|
req.Host = "example.test"
|
|
req.Header.Set("User-Agent", "ipecho-test")
|
|
rec := httptest.NewRecorder()
|
|
|
|
handler.ServeHTTP(rec, req)
|
|
|
|
body := rec.Body.String()
|
|
for _, want := range []string{"Host: example.test", "User-Agent: ipecho-test"} {
|
|
if !strings.Contains(body, want) {
|
|
t.Fatalf("body %q does not contain %q", body, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFaviconEndpointReturnsIconWithoutHTMLLink(t *testing.T) {
|
|
handler := New(clientip.Resolver{})
|
|
req := httptest.NewRequest(http.MethodGet, "/favicon.ico", nil)
|
|
rec := httptest.NewRecorder()
|
|
|
|
handler.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("got status %d, want %d", rec.Code, http.StatusOK)
|
|
}
|
|
|
|
if got := rec.Header().Get("Content-Type"); got != "image/x-icon" {
|
|
t.Fatalf("got content-type %q, want image/x-icon", got)
|
|
}
|
|
|
|
if rec.Body.Len() == 0 {
|
|
t.Fatal("favicon response is empty")
|
|
}
|
|
}
|
|
|
|
func TestSecurityHeadersAreApplied(t *testing.T) {
|
|
handler := New(clientip.Resolver{})
|
|
req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
|
|
rec := httptest.NewRecorder()
|
|
|
|
handler.ServeHTTP(rec, req)
|
|
|
|
for _, header := range []string{
|
|
"X-Content-Type-Options",
|
|
"X-Frame-Options",
|
|
"Referrer-Policy",
|
|
"Content-Security-Policy",
|
|
} {
|
|
if rec.Header().Get(header) == "" {
|
|
t.Fatalf("missing %s header", header)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUnknownEndpointReturnsNotFound(t *testing.T) {
|
|
handler := New(clientip.Resolver{})
|
|
req := httptest.NewRequest(http.MethodGet, "/missing", nil)
|
|
rec := httptest.NewRecorder()
|
|
|
|
handler.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusNotFound {
|
|
t.Fatalf("got status %d, want %d", rec.Code, http.StatusNotFound)
|
|
}
|
|
}
|