This repository has been archived on 2026-03-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
oubliette/internal/web/web_test.go
Torjus Håkestad d4380c0aea chore: add golangci-lint config and fix all lint issues
Enable 15 additional linters (gosec, errorlint, gocritic, modernize,
misspell, bodyclose, sqlclosecheck, nilerr, unconvert, durationcheck,
sloglint, wastedassign, usestdlibvars) with sensible exclusion rules.

Fix all findings: errors.Is for error comparisons, run() pattern in
main to avoid exitAfterDefer, ReadHeaderTimeout for Slowloris
protection, bounds check in escape sequence reader, WaitGroup.Go,
slices.Contains, range-over-int loops, and http.MethodGet constants.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 21:43:49 +01:00

162 lines
3.9 KiB
Go

package web
import (
"context"
"log/slog"
"net/http"
"net/http/httptest"
"strings"
"testing"
"git.t-juice.club/torjus/oubliette/internal/storage"
)
func newTestServer(t *testing.T) *Server {
t.Helper()
store := storage.NewMemoryStore()
logger := slog.Default()
srv, err := NewServer(store, logger)
if err != nil {
t.Fatalf("creating server: %v", err)
}
return srv
}
func newSeededTestServer(t *testing.T) *Server {
t.Helper()
store := storage.NewMemoryStore()
ctx := context.Background()
for range 5 {
if err := store.RecordLoginAttempt(ctx, "root", "toor", "10.0.0.1"); err != nil {
t.Fatalf("seeding attempt: %v", err)
}
}
if err := store.RecordLoginAttempt(ctx, "admin", "admin", "10.0.0.2"); err != nil {
t.Fatalf("seeding attempt: %v", err)
}
if _, err := store.CreateSession(ctx, "10.0.0.1", "root", "bash"); err != nil {
t.Fatalf("creating session: %v", err)
}
if _, err := store.CreateSession(ctx, "10.0.0.2", "admin", "bash"); err != nil {
t.Fatalf("creating session: %v", err)
}
logger := slog.Default()
srv, err := NewServer(store, logger)
if err != nil {
t.Fatalf("creating server: %v", err)
}
return srv
}
func TestDashboardHandler(t *testing.T) {
t.Run("empty store", func(t *testing.T) {
srv := newTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/", nil)
w := httptest.NewRecorder()
srv.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
body := w.Body.String()
if !strings.Contains(body, "Oubliette") {
t.Error("response should contain 'Oubliette'")
}
if !strings.Contains(body, "No data") {
t.Error("response should contain 'No data' for empty tables")
}
})
t.Run("with data", func(t *testing.T) {
srv := newSeededTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/", nil)
w := httptest.NewRecorder()
srv.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
body := w.Body.String()
if !strings.Contains(body, "root") {
t.Error("response should contain username 'root'")
}
if !strings.Contains(body, "10.0.0.1") {
t.Error("response should contain IP '10.0.0.1'")
}
})
}
func TestFragmentStats(t *testing.T) {
srv := newSeededTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/fragments/stats", nil)
w := httptest.NewRecorder()
srv.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
body := w.Body.String()
// Should be a fragment, not a full HTML page.
if strings.Contains(body, "<!DOCTYPE html>") {
t.Error("stats fragment should not contain full HTML document")
}
if !strings.Contains(body, "Total Attempts") {
t.Error("stats fragment should contain 'Total Attempts'")
}
}
func TestFragmentActiveSessions(t *testing.T) {
srv := newSeededTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/fragments/active-sessions", nil)
w := httptest.NewRecorder()
srv.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
body := w.Body.String()
if strings.Contains(body, "<!DOCTYPE html>") {
t.Error("active sessions fragment should not contain full HTML document")
}
// Both sessions are active (not ended).
if !strings.Contains(body, "10.0.0.1") {
t.Error("active sessions should contain IP '10.0.0.1'")
}
}
func TestStaticAssets(t *testing.T) {
srv := newTestServer(t)
tests := []struct {
path string
contentType string
}{
{"/static/pico.min.css", "text/css"},
{"/static/htmx.min.js", "text/javascript"},
}
for _, tt := range tests {
t.Run(tt.path, func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, tt.path, nil)
w := httptest.NewRecorder()
srv.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want 200", w.Code)
}
ct := w.Header().Get("Content-Type")
if !strings.Contains(ct, tt.contentType) {
t.Errorf("Content-Type = %q, want to contain %q", ct, tt.contentType)
}
})
}
}