Ctrl K
ComunidadGuías…

internal/server/raw_route_http_only_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// SPDX-License-Identifier: Apache-2.0

package server

import (
	"net/http"
	"net/http/httptest"
	"net/url"
	"path/filepath"
	"strconv"
	"testing"

	appdb "github.com/X4Applegate/caddyui/internal/db"
	"github.com/X4Applegate/caddyui/internal/models"
)

// Issue #121: an Advanced route whose Caddyfile uses explicit http:// site
// addresses is a deliberate plain-HTTP route. It must not keep a certificate,
// must not be force-redirected to HTTPS, and must not park the user on the
// "deploying" page that waits for an ACME certificate that will never exist.

func adaptedRouteFor(host string) string {
	return `{"result":{"apps":{"http":{"servers":{"srv0":{"listen":[":80"],"routes":[` +
		`{"match":[{"host":["` + host + `"]}],"handle":[{"handler":"static_response","body":"OK"}]}]}}}}}}`
}

func newHTTPOnlyFixture(t *testing.T, host string) (*Server, int64, string) {
	t.Helper()
	admin, fake := newLayer4FakeAdmin(t, `{"apps":{"http":{"servers":{"srv0":{"listen":[":443"],"routes":[]}}}}}`)
	fake.adaptBody = adaptedRouteFor(host)
	conn, err := appdb.Open(filepath.Join(t.TempDir(), "caddyui.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { _ = conn.Close() })
	id, err := models.CreateCaddyServer(conn, &models.CaddyServer{Name: "Primary", AdminURL: admin.URL, Type: models.CaddyServerTypeManaged})
	if err != nil {
		t.Fatal(err)
	}
	return newRenderingTestServer(t, conn), id, admin.URL
}

func TestHTTPOnlyAdvancedRouteDropsCertificateAndForceSSLAndSkipsDeployingPage(t *testing.T) {
	s, serverID, _ := newHTTPOnlyFixture(t, "127.0.0.1")

	form := url.Values{
		"label":          {"health"},
		"caddyfile_src":  {"# local health check\nhttp://127.0.0.1 {\n\trespond /health \"OK\" 200\n}"},
		"enabled":        {"on"},
		"ssl_forced":     {"on"}, // contradicts the explicit http:// address
		"certificate_id": {"5"},  // a certificate cannot bind to an http:// route
	}
	rec := httptest.NewRecorder()
	s.createRawRoute(rec, cookieReq(t, "/raw-routes", form, serverID))

	if rec.Code != http.StatusSeeOther {
		t.Fatalf("want 303, got %d: %s", rec.Code, rec.Body.String())
	}
	if loc := rec.Header().Get("Location"); loc != "/raw-routes" {
		t.Fatalf("redirected to %q, want the route list — an http:// route has no certificate for the deploying page to wait on", loc)
	}
	rows, err := models.ListRawRoutes(s.DB, serverID, 0, true, nil)
	if err != nil || len(rows) != 1 {
		t.Fatalf("saved routes = %d (%v), want 1", len(rows), err)
	}
	if rows[0].CertificateID != 0 || rows[0].ForceSSL {
		t.Fatalf("saved certificate_id=%d force_ssl=%v, want 0 and false for an explicit http:// route", rows[0].CertificateID, rows[0].ForceSSL)
	}
}

// An ordinary Advanced route is untouched: it keeps Force SSL and still goes
// to the deploying page.
func TestHTTPSAdvancedRouteKeepsForceSSLAndDeployingPage(t *testing.T) {
	s, serverID, _ := newHTTPOnlyFixture(t, "app.example.com")

	form := url.Values{
		"label":         {"app"},
		"caddyfile_src": {"app.example.com {\n\trespond \"hi\"\n}"},
		"enabled":       {"on"},
		"ssl_forced":    {"on"},
	}
	rec := httptest.NewRecorder()
	s.createRawRoute(rec, cookieReq(t, "/raw-routes", form, serverID))

	if rec.Code != http.StatusSeeOther {
		t.Fatalf("want 303, got %d: %s", rec.Code, rec.Body.String())
	}
	rows, _ := models.ListRawRoutes(s.DB, serverID, 0, true, nil)
	if len(rows) != 1 || !rows[0].ForceSSL {
		t.Fatalf("an https route must keep Force SSL, got %+v", rows)
	}
	if loc := rec.Header().Get("Location"); loc != "/raw-routes/"+strconv.FormatInt(rows[0].ID, 10)+"/deploying" {
		t.Fatalf("redirected to %q, want the deploying page for an https route", loc)
	}
}

func TestHTTPOnlyAdvancedRouteEditAndDeployingPageSkipTheCertificateStep(t *testing.T) {
	s, serverID, _ := newHTTPOnlyFixture(t, "127.0.0.1")
	form := url.Values{
		"label":         {"health"},
		"caddyfile_src": {"http://127.0.0.1 {\n\trespond /health \"OK\" 200\n}"},
		"enabled":       {"on"},
	}
	rec := httptest.NewRecorder()
	s.createRawRoute(rec, cookieReq(t, "/raw-routes", form, serverID))
	rows, _ := models.ListRawRoutes(s.DB, serverID, 0, true, nil)
	if len(rows) != 1 {
		t.Fatalf("setup: want 1 route, got %d", len(rows))
	}
	sid := strconv.FormatInt(rows[0].ID, 10)

	// Editing it goes straight back to the list too.
	form.Set("label", "health v2")
	rec = httptest.NewRecorder()
	s.updateRawRoute(rec, withChiURLParam(cookieReq(t, "/raw-routes/"+sid, form, serverID), "id", sid))
	if rec.Code != http.StatusSeeOther || rec.Header().Get("Location") != "/raw-routes" {
		t.Fatalf("edit: want 303 to /raw-routes, got %d %q", rec.Code, rec.Header().Get("Location"))
	}

	// And if someone opens the deploying page directly, it bounces to the list
	// instead of polling for an ACME certificate.
	rec = httptest.NewRecorder()
	req := withChiURLParam(cookieReq(t, "/raw-routes/"+sid+"/deploying", nil, serverID), "id", sid)
	s.rawRouteDeploying(rec, req)
	if rec.Code != http.StatusSeeOther || rec.Header().Get("Location") != "/raw-routes" {
		t.Fatalf("deploying page: want 303 to /raw-routes, got %d %q", rec.Code, rec.Header().Get("Location"))
	}
}