Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions pkg/notnet/dashboard_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package notnet

import (
"encoding/json"
"net/http/httptest"
"testing"
)

func TestDashboard(t *testing.T) {
engine := New(nil)
engine.GET("/dashboard", Dashboard())

w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/dashboard", nil)
engine.ServeHTTP(w, r)

if w.Code != 200 {
t.Errorf("expected status 200, got %d", w.Code)
}

contentType := w.Header().Get("Content-Type")
if contentType != "text/html; charset=utf-8" {
t.Errorf("expected content-type text/html; charset=utf-8, got %s", contentType)
}

body := w.Body.String()
if body == "" {
t.Error("expected non-empty body")
}
}

func TestStatsAPI(t *testing.T) {
engine := New(nil)
engine.GET("/api/stats", StatsAPI())

w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/api/stats", nil)
engine.ServeHTTP(w, r)

if w.Code != 200 {
t.Errorf("expected status 200, got %d", w.Code)
}

contentType := w.Header().Get("Content-Type")
if contentType != "application/json; charset=utf-8" {
t.Errorf("expected content-type application/json; charset=utf-8, got %s", contentType)
}

var stats StatsData
err := json.Unmarshal(w.Body.Bytes(), &stats)
if err != nil {
t.Errorf("failed to unmarshal stats: %v", err)
}

if stats.UpTime <= 0 {
t.Errorf("expected positive uptime, got %v", stats.UpTime)
}
}
28 changes: 28 additions & 0 deletions pkg/notnet/middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -532,3 +532,31 @@ func TestMiddlewareErrorHandling(t *testing.T) {
t.Errorf("expected status 500, got %d", w.Code)
}
}

func TestStatsMiddleware(t *testing.T) {
stats := Stats()
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/test", nil)
req, res := AcquireRequestResponse(w, r)
defer ReleaseRequestResponse(req, res)

sc := GetStatsCollector()
initialCount := sc.GetStats().RequestCount

req.index = -1
req.handlers = []HandlerFunc{
func(req *Request, res *Response) error {
return res.String(200, "ok")
},
}

err := stats(req, res)
if err != nil {
t.Errorf("expected no error, got %v", err)
}

finalCount := sc.GetStats().RequestCount
if finalCount != initialCount+1 {
t.Errorf("expected request count to increase from %d to %d, got %d", initialCount, initialCount+1, finalCount)
}
}
146 changes: 146 additions & 0 deletions pkg/notnet/notnet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package notnet

import (
"fmt"
"net"
"net/http"
"net/http/httptest"
"strings"
"testing"
Expand Down Expand Up @@ -490,3 +492,147 @@ func TestApplyConfig_NilIsNoOp(t *testing.T) {
t.Errorf("expected readTimeout to remain %v after nil ApplyConfig, got %v", before, engine.readTimeout)
}
}

func TestRouteBuilderMethods(t *testing.T) {
engine := New(nil)
rb := engine.GET("/get", func(req *Request, res *Response) error { return nil })

// Test RouteBuilder chaining and HTTP methods
rb.POST("/post", func(req *Request, res *Response) error { return nil }).
PUT("/put", func(req *Request, res *Response) error { return nil }).
DELETE("/delete", func(req *Request, res *Response) error { return nil }).
PATCH("/patch", func(req *Request, res *Response) error { return nil }).
OPTIONS("/options", func(req *Request, res *Response) error { return nil }).
HEAD("/head", func(req *Request, res *Response) error { return nil }).
Use(func(req *Request, res *Response) error { return req.Next() }).
SetErrorHandler(func(req *Request, res *Response, err error) {}).
SetNotFoundHandler(func(req *Request, res *Response) {}).
SetPanicHandler(func(req *Request, res *Response, rec interface{}) {})

// Verify routes are registered
methods := []string{"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"}
paths := []string{"/get", "/post", "/put", "/delete", "/patch", "/options", "/head"}

for i, method := range methods {
_, _, found := engine.router.Match(method, paths[i])
if !found {
t.Errorf("expected route %s %s to be registered", method, paths[i])
}
}
}

func TestEngineGetters(t *testing.T) {
opts := &EngineOption{
ReadTimeout: 10 * time.Second,
WriteTimeout: 20 * time.Second,
IdleTimeout: 30 * time.Second,
MaxHeaderBytes: 4096,
}
engine := New(opts)

if engine.GetReadTimeout() != opts.ReadTimeout {
t.Errorf("expected %v, got %v", opts.ReadTimeout, engine.GetReadTimeout())
}
if engine.GetWriteTimeout() != opts.WriteTimeout {
t.Errorf("expected %v, got %v", opts.WriteTimeout, engine.GetWriteTimeout())
}
if engine.GetIdleTimeout() != opts.IdleTimeout {
t.Errorf("expected %v, got %v", opts.IdleTimeout, engine.GetIdleTimeout())
}
if engine.GetMaxHeaderBytes() != opts.MaxHeaderBytes {
t.Errorf("expected %d, got %d", opts.MaxHeaderBytes, engine.GetMaxHeaderBytes())
}
}

func TestRouteBuilderApplyConfig(t *testing.T) {
engine := New(nil)
rb := engine.GET("/test", func(req *Request, res *Response) error { return nil })

rb.ApplyConfig(&EngineOption{
ReadTimeout: 12 * time.Second,
})

if engine.readTimeout != 12*time.Second {
t.Errorf("expected engine readTimeout 12s, got %v", engine.readTimeout)
}
}

func TestGroupApplyConfig(t *testing.T) {
engine := New(nil)
group := engine.Group("/api")

group.ApplyConfig(&EngineOption{
WriteTimeout: 14 * time.Second,
})

if engine.writeTimeout != 14*time.Second {
t.Errorf("expected engine writeTimeout 14s, got %v", engine.writeTimeout)
}
}

func TestRouteBuilderGroup(t *testing.T) {
engine := New(nil)
rb := engine.GET("/test", func(req *Request, res *Response) error { return nil })

group := rb.Group("/v1")
if group.prefix != "/v1" {
t.Errorf("expected group prefix /v1, got %s", group.prefix)
}
}

func TestListenListener(t *testing.T) {
engine := New(nil)
engine.GET("/ping", func(req *Request, res *Response) error {
return res.String(200, "pong")
})

ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("failed to create listener: %v", err)
}

go func() {
_ = engine.ListenListener(ln)
}()

// Wait a bit for server to start
time.Sleep(100 * time.Millisecond)

resp, err := http.Get("http://" + ln.Addr().String() + "/ping")
if err != nil {
t.Fatalf("failed to GET: %v", err)
}
defer resp.Body.Close()

if resp.StatusCode != 200 {
t.Errorf("expected status 200, got %d", resp.StatusCode)
}

_ = engine.Shutdown()
}

func TestRouteBuilderSetHandlers(t *testing.T) {
engine := New(nil)
rb := engine.GET("/test", func(req *Request, res *Response) error { return nil })

rb.SetNotFoundHandler(func(req *Request, res *Response) {})
rb.SetPanicHandler(func(req *Request, res *Response, rec interface{}) {})
}

func TestListenError(t *testing.T) {
engine := New(nil)
// Invalid address
err := engine.Listen("invalid")
if err == nil {
t.Error("expected error for invalid address")
}
}

func TestListenTLSError(t *testing.T) {
engine := New(nil)
// Invalid certs
err := engine.ListenTLS(":0", "nonexistent", "nonexistent")
if err == nil {
t.Error("expected error for nonexistent certs")
}
}
31 changes: 31 additions & 0 deletions pkg/notnet/reqandresp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"net/http/httptest"
"os"
"strings"
"testing"
)
Expand Down Expand Up @@ -581,3 +582,33 @@ func TestEmptyResponse(t *testing.T) {
t.Error("expected empty body")
}
}

func TestResponseFile(t *testing.T) {
// Create a temporary file
content := "hello from file"
tmpfile, err := os.CreateTemp("", "testfile")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmpfile.Name())

if _, err := tmpfile.Write([]byte(content)); err != nil {
t.Fatal(err)
}
if err := tmpfile.Close(); err != nil {
t.Fatal(err)
}

w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/test", nil)
_, res := AcquireRequestResponse(w, r)

err = res.File(tmpfile.Name())
if err != nil {
t.Errorf("expected no error, got %v", err)
}

if w.Body.String() != content {
t.Errorf("expected body %s, got %s", content, w.Body.String())
}
}
76 changes: 76 additions & 0 deletions pkg/notnet/stats_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package notnet

import (
"testing"
"time"
)

func TestStatsCollector(t *testing.T) {
sc := InitStatsCollector()
if sc == nil {
t.Fatal("expected stats collector to be initialized")
}

// Test RegisterRoute
sc.RegisterRoute("GET", "/test")
sc.RegisterRoute("GET", "/test") // Duplicate
sc.RegisterRoute("POST", "/data")

stats := sc.GetStats()
if len(stats.RegisteredRoutes) != 2 {
t.Errorf("expected 2 registered routes, got %d", len(stats.RegisteredRoutes))
}

// Test RecordRequest
sc.RecordRequest(100 * time.Millisecond)
sc.RecordRequest(200 * time.Millisecond)

stats = sc.GetStats()
if stats.RequestCount != 2 {
t.Errorf("expected request count 2, got %d", stats.RequestCount)
}

// Test CollectMetrics
sc.CollectMetrics()
stats = sc.GetStats()

if len(stats.MemoryHistory) == 0 {
t.Error("expected memory history to be populated")
}
if len(stats.CPUHistory) == 0 {
t.Error("expected CPU history to be populated")
}
}

func TestGetStatsCollector(t *testing.T) {
sc1 := GetStatsCollector()
sc2 := GetStatsCollector()
if sc1 != sc2 {
t.Error("expected GetStatsCollector to return the same instance")
}
}

func TestStatsCollector_MaxHistorySize(t *testing.T) {
sc := &StatsCollector{
maxHistorySize: 2,
requestHistory: make([]RequestMetric, 0),
memoryMetrics: make([]MemoryMetric, 0),
cpuMetrics: make([]CPUMetric, 0),
}

// Test memory history trimming
for i := 0; i < 5; i++ {
sc.memoryMetrics = append(sc.memoryMetrics, MemoryMetric{})
sc.cpuMetrics = append(sc.cpuMetrics, CPUMetric{})
}

sc.CollectMetrics()

stats := sc.GetStats()
if len(stats.MemoryHistory) > 2 {
t.Errorf("expected memory history size <= 2, got %d", len(stats.MemoryHistory))
}
if len(stats.CPUHistory) > 2 {
t.Errorf("expected cpu history size <= 2, got %d", len(stats.CPUHistory))
}
}
Loading