Files

110 lines
4.1 KiB
Go

package web
import (
"context"
"io"
"log/slog"
"net/http"
"net/http/httptest"
"net/url"
"path/filepath"
"strconv"
"strings"
"sync/atomic"
"testing"
"github.com/root-kdw12/hetzner-api-dyndns-records-webinterface/internal/model"
"github.com/root-kdw12/hetzner-api-dyndns-records-webinterface/internal/store"
)
func TestPiHolePageAndRemovalNeverContactUnavailablePiHole(t *testing.T) {
t.Parallel()
var requests atomic.Int32
api := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { requests.Add(1); http.Error(w, "unavailable", 503) }))
defer api.Close()
ctx := context.Background()
database, err := store.Open(ctx, filepath.Join(t.TempDir(), "app.db"))
if err != nil {
t.Fatal(err)
}
defer database.Close()
integration, err := database.CreatePiHoleIntegration(ctx, model.PiHoleIntegration{Name: "Pi-hole", BaseURL: api.URL + "/api"}, model.PiHoleCredential{Ciphertext: []byte("secret"), Nonce: []byte("secret")})
if err != nil {
t.Fatal(err)
}
if _, err = database.SyncPiHoleRecordLinks(ctx, integration.ID, []string{"192.168.1.10 nas.lan"}); err != nil {
t.Fatal(err)
}
if err = database.MarkPiHoleSynced(ctx, integration.ID, "connection timed out"); err != nil {
t.Fatal(err)
}
templates, err := loadTemplates()
if err != nil {
t.Fatal(err)
}
server := &Server{store: database, templates: templates, logger: slog.New(slog.NewTextHandler(io.Discard, nil))}
session := &model.Session{CSRFToken: "csrf", Username: "admin", Role: "admin"}
response := httptest.NewRecorder()
server.piHolePage(response, httptest.NewRequest(http.MethodGet, "/pihole", nil), session)
if response.Code != 200 || !strings.Contains(response.Body.String(), "nas.lan") || !strings.Contains(response.Body.String(), "Lokale Verbindung entfernen") {
t.Fatalf("offline page unavailable: %d", response.Code)
}
id := strconv.FormatInt(integration.ID, 10)
request := httptest.NewRequest(http.MethodPost, "/pihole/"+id+"/remove", strings.NewReader(url.Values{"csrf": {"csrf"}}.Encode()))
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
request.SetPathValue("id", id)
response = httptest.NewRecorder()
server.piHoleRemove(response, request, session)
if response.Code != http.StatusSeeOther {
t.Fatalf("offline removal failed: %d", response.Code)
}
integrations, err := database.ListPiHoleIntegrations(ctx)
if err != nil || len(integrations) != 0 {
t.Fatal("connection not removed")
}
if requests.Load() != 0 {
t.Fatalf("offline page/removal contacted Pi-hole %d times", requests.Load())
}
}
func TestDiagnosticDownloadExcludesStoredSecretsAndFreeText(t *testing.T) {
t.Parallel()
ctx := context.Background()
database, err := store.Open(ctx, filepath.Join(t.TempDir(), "app.db"))
if err != nil {
t.Fatal(err)
}
defer database.Close()
const secret = "git-hetzner-pihole-cookie-password-secret"
if err = database.SaveSoftwareUpdateCredential(ctx, secret, []byte(secret), []byte(secret)); err != nil {
t.Fatal(err)
}
if err = database.AddAudit(ctx, "arbitrary", secret); err != nil {
t.Fatal(err)
}
id, err := database.CreateUpdateRun(ctx)
if err != nil {
t.Fatal(err)
}
if err = database.FinishUpdateRun(ctx, id, "error", secret, secret, "https://user:"+secret+"@host/?token="+secret); err != nil {
t.Fatal(err)
}
server := &Server{store: database, logger: slog.New(slog.NewTextHandler(io.Discard, nil))}
response := httptest.NewRecorder()
server.downloadDiagnostics(response, httptest.NewRequest(http.MethodGet, "/errors/diagnostics", nil), &model.Session{Role: "admin"})
if response.Code != 200 || !strings.Contains(response.Header().Get("Content-Disposition"), "attachment") || response.Header().Get("Cache-Control") != "no-store" {
t.Fatalf("invalid download: %d", response.Code)
}
if strings.Contains(response.Body.String(), secret) {
t.Fatal("secret included in download")
}
if !strings.Contains(response.Body.String(), `"status": "error"`) {
t.Fatal("run status missing")
}
anonymous := httptest.NewRecorder()
server.Handler().ServeHTTP(anonymous, httptest.NewRequest(http.MethodGet, "/errors/diagnostics", nil))
if anonymous.Code == 200 {
t.Fatal("anonymous diagnostic access accepted")
}
}