Initial vibeproxy implementation
Self-contained HTTP/HTTPS/TCP/UDP reverse proxy manager written in Go, with SQLite storage, a Bootstrap 5 admin UI (Go templates, no JS framework), and a REST API under /api/v1. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QTXFrNP1imCW7jtfL5SZrw
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/owen/vibeproxy/internal/database"
|
||||
"github.com/owen/vibeproxy/internal/models"
|
||||
"github.com/owen/vibeproxy/internal/stats"
|
||||
)
|
||||
|
||||
func mustRender(t *testing.T, name string, data PageData) {
|
||||
t.Helper()
|
||||
var buf bytes.Buffer
|
||||
if err := templates.ExecuteTemplate(&buf, name, data); err != nil {
|
||||
t.Fatalf("render %s: %v", name, err)
|
||||
}
|
||||
if buf.Len() == 0 {
|
||||
t.Fatalf("render %s: empty output", name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderAllPages(t *testing.T) {
|
||||
now := time.Now()
|
||||
aid := int64(1)
|
||||
|
||||
host := models.ProxyHost{
|
||||
ID: 1, Name: "example", Type: models.ProxyHostTypeProxy, Domains: []string{"example.com"},
|
||||
ForwardScheme: "http", LoadBalancingMethod: "round_robin", CertificateID: &aid, AccessListID: &aid,
|
||||
Backends: []models.Backend{{ID: 1, Host: "10.0.0.1", Port: 8080, Weight: 1, Enabled: true, HealthCheckType: models.HealthCheckTCP}},
|
||||
CustomHeaders: []models.CustomHeader{{Direction: models.HeaderDirectionRequest, Action: models.HeaderActionSet, Name: "X-Test", Value: "1"}},
|
||||
Enabled: true, CreatedAt: now, UpdatedAt: now,
|
||||
}
|
||||
redirectHost := host
|
||||
redirectHost.Type = models.ProxyHostTypeRedirect
|
||||
redirectHost.RedirectTargetURL = "https://new.example.com"
|
||||
redirectHost.RedirectCode = 301
|
||||
|
||||
acl := models.AccessList{ID: 1, Name: "Internal", DefaultAllow: false,
|
||||
Rules: []models.AccessRule{{Action: models.AccessAllow, CIDR: "10.0.0.0/8"}},
|
||||
BasicAuthUsers: []models.AccessListUser{{Username: "bob", PasswordHash: "hash"}},
|
||||
}
|
||||
|
||||
cert := models.Certificate{ID: 1, Name: "example", Type: models.CertTypeLetsEncrypt, Domains: []string{"example.com"}, NotAfter: &now}
|
||||
|
||||
stream := models.Stream{ID: 1, Protocol: models.StreamTCP, ListenIP: "0.0.0.0", ListenPort: 25565, DestinationHost: "10.0.0.1", DestinationPort: 25565, Enabled: true}
|
||||
|
||||
reqLog := models.RequestLog{ID: 1, Timestamp: now, ProxyHostName: "example", ClientIP: "1.2.3.4", Method: "GET", Host: "example.com", Path: "/", Status: 200, ResponseSize: 100, Backend: "10.0.0.1:8080", ResponseTimeMS: 12}
|
||||
auditLog := models.AuditLog{ID: 1, Timestamp: now, Actor: "admin", Action: "proxy_created", Target: "example", Details: "domains=[example.com]"}
|
||||
|
||||
token := models.APIToken{ID: 1, Name: "ci", Prefix: "vp_abcd12", CreatedAt: now}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
pd PageData
|
||||
}{
|
||||
{"dashboard", PageData{Data: &stats.Dashboard{
|
||||
BackendHealth: []stats.BackendHealthEntry{{ProxyHost: "example", Backend: "10.0.0.1", Healthy: true}},
|
||||
StatusBreakdown: []database.StatusCount{{Status: 200, Count: 5}},
|
||||
TopHosts: []database.HostCount{{Host: "example.com", Count: 5}},
|
||||
TopClients: []database.HostCount{{Host: "1.2.3.4", Count: 5}},
|
||||
CertsExpiringSoon: []models.Certificate{cert},
|
||||
RecentErrors: []models.RequestLog{reqLog},
|
||||
}}},
|
||||
{"proxy_hosts", PageData{CSRFToken: "tok", Data: map[string]any{
|
||||
"Hosts": []models.ProxyHost{host, redirectHost}, "AccessLists": []models.AccessList{acl}, "Certificates": []models.Certificate{cert},
|
||||
}}},
|
||||
{"proxy_host_form", PageData{CSRFToken: "tok", Data: proxyHostFormData{Host: host, AccessLists: []models.AccessList{acl}, Certificates: []models.Certificate{cert}, IsNew: false}}},
|
||||
{"proxy_host_form", PageData{CSRFToken: "tok", Data: proxyHostFormData{Host: redirectHost, AccessLists: []models.AccessList{acl}, Certificates: []models.Certificate{cert}, IsNew: true}}},
|
||||
{"streams", PageData{CSRFToken: "tok", Data: []models.Stream{stream}}},
|
||||
{"stream_form", PageData{CSRFToken: "tok", Data: map[string]any{"Stream": stream, "IsNew": false}}},
|
||||
{"backends", PageData{Data: []backendRow{{ProxyHostID: 1, ProxyHostName: "example", Host: "10.0.0.1", Port: 8080, Weight: 1, Enabled: true, Healthy: true, HealthCheck: "tcp"}}}},
|
||||
{"certificates", PageData{CSRFToken: "tok", Data: []models.Certificate{cert}}},
|
||||
{"access_lists", PageData{CSRFToken: "tok", Data: []models.AccessList{acl}}},
|
||||
{"access_list_form", PageData{CSRFToken: "tok", Data: map[string]any{"List": acl, "IsNew": false}}},
|
||||
{"logs", PageData{Data: map[string]any{"Logs": []models.RequestLog{reqLog}, "Total": 1, "Page": 1, "TotalPages": 1, "Query": "", "Status": 0}}},
|
||||
{"logs_audit", PageData{Data: map[string]any{"Logs": []models.AuditLog{auditLog}, "Total": 1, "Page": 1, "TotalPages": 1}}},
|
||||
{"settings", PageData{CSRFToken: "tok", Data: models.Settings{AdminUsername: "admin", TrustedProxyCIDRs: []string{"10.0.0.0/8"}}}},
|
||||
{"api_tokens", PageData{CSRFToken: "tok", Data: []models.APIToken{token}}},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
mustRender(t, tc.name, tc.pd)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user