Add minimal Web UI server

This commit is contained in:
Andy Oknen 2025-07-29 20:14:41 +00:00
parent 707e90b1b3
commit 345d5b9cbd
13 changed files with 2058 additions and 0 deletions

219
src/webui/server_test.go Normal file
View file

@ -0,0 +1,219 @@
package webui
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"testing"
"time"
"github.com/gologme/log"
"github.com/yggdrasil-network/yggdrasil-go/src/core"
)
// Helper function to create a test logger
func createTestLogger() core.Logger {
return log.New(os.Stderr, "webui_test: ", log.Flags())
}
// Helper function to get available port for testing
func getTestAddress() string {
return "127.0.0.1:0" // Let OS assign available port
}
// Helper function to wait for server to be ready
func waitForServer(url string, timeout time.Duration) error {
client := &http.Client{Timeout: 1 * time.Second}
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
resp, err := client.Get(url)
if err == nil {
resp.Body.Close()
return nil
}
time.Sleep(50 * time.Millisecond)
}
return fmt.Errorf("server not ready within timeout")
}
func TestWebUIServer_Creation(t *testing.T) {
logger := createTestLogger()
listen := getTestAddress()
server := Server(listen, logger)
if server == nil {
t.Fatal("Server function returned nil")
}
if server.listen != listen {
t.Errorf("Expected listen address %s, got %s", listen, server.listen)
}
if server.log != logger {
t.Error("Logger not properly set")
}
if server.server != nil {
t.Error("HTTP server should be nil before Start()")
}
}
func TestWebUIServer_StartStop(t *testing.T) {
logger := createTestLogger()
listen := getTestAddress()
server := Server(listen, logger)
// Start server in goroutine
errChan := make(chan error, 1)
go func() {
errChan <- server.Start()
}()
// Give server time to start
time.Sleep(100 * time.Millisecond)
// Verify server is running
if server.server == nil {
t.Fatal("HTTP server not initialized after Start()")
}
// Stop server
err := server.Stop()
if err != nil {
t.Errorf("Error stopping server: %v", err)
}
// Check that Start() returns without error after Stop()
select {
case err := <-errChan:
if err != nil {
t.Errorf("Start() returned error: %v", err)
}
case <-time.After(2 * time.Second):
t.Error("Start() did not return after Stop()")
}
}
func TestWebUIServer_StopWithoutStart(t *testing.T) {
logger := createTestLogger()
listen := getTestAddress()
server := Server(listen, logger)
// Stop server that was never started should not error
err := server.Stop()
if err != nil {
t.Errorf("Stop() on unstarted server returned error: %v", err)
}
}
func TestWebUIServer_HealthEndpoint(t *testing.T) {
logger := createTestLogger()
// Create a test server using net/http/httptest for reliable testing
mux := http.NewServeMux()
setupStaticHandler(mux)
mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
serveFile(rw, r, logger)
})
mux.HandleFunc("/health", func(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(http.StatusOK)
rw.Write([]byte("OK"))
})
server := httptest.NewServer(mux)
defer server.Close()
// Test health endpoint
resp, err := http.Get(server.URL + "/health")
if err != nil {
t.Fatalf("Error requesting health endpoint: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected status 200, got %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("Error reading response body: %v", err)
}
if string(body) != "OK" {
t.Errorf("Expected body 'OK', got '%s'", string(body))
}
}
func TestWebUIServer_Timeouts(t *testing.T) {
logger := createTestLogger()
server := Server("127.0.0.1:0", logger)
// Start server
go func() {
server.Start()
}()
defer server.Stop()
// Wait for server to start
time.Sleep(200 * time.Millisecond)
if server.server == nil {
t.Fatal("Server not started")
}
// Check that timeouts are properly configured
expectedReadTimeout := 10 * time.Second
expectedWriteTimeout := 10 * time.Second
expectedMaxHeaderBytes := 1 << 20
if server.server.ReadTimeout != expectedReadTimeout {
t.Errorf("Expected ReadTimeout %v, got %v", expectedReadTimeout, server.server.ReadTimeout)
}
if server.server.WriteTimeout != expectedWriteTimeout {
t.Errorf("Expected WriteTimeout %v, got %v", expectedWriteTimeout, server.server.WriteTimeout)
}
if server.server.MaxHeaderBytes != expectedMaxHeaderBytes {
t.Errorf("Expected MaxHeaderBytes %d, got %d", expectedMaxHeaderBytes, server.server.MaxHeaderBytes)
}
}
func TestWebUIServer_ConcurrentStartStop(t *testing.T) {
logger := createTestLogger()
// Test concurrent start/stop operations with separate servers
for i := 0; i < 3; i++ {
server := Server("127.0.0.1:0", logger)
// Start server
startDone := make(chan error, 1)
go func() {
startDone <- server.Start()
}()
time.Sleep(100 * time.Millisecond)
// Stop server
err := server.Stop()
if err != nil {
t.Errorf("Iteration %d: Error stopping server: %v", i, err)
}
// Wait for Start() to return
select {
case <-startDone:
// Good, Start() returned
case <-time.After(2 * time.Second):
t.Errorf("Iteration %d: Start() did not return after Stop()", i)
}
time.Sleep(50 * time.Millisecond)
}
}