Files
ip-echo/internal/httpapi/server_test.go
2026-05-13 03:05:24 +03:30

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)
}
}