~netlandish/links-dev

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
1

[PATCH links] Removing test as it's not needed. The functionality of `ValidDomain` is covered in another test, which is what we care about.

Details
Message ID
<20251112134204.18527-1-peter@netlandish.com>
Sender timestamp
1762933322
DKIM signature
missing
Download raw message
Patch: +0 -132
---
 domain/integration_test.go | 132 -------------------------------------
 1 file changed, 132 deletions(-)

diff --git a/domain/integration_test.go b/domain/integration_test.go
index a05bb1e..6028118 100644
--- a/domain/integration_test.go
+++ b/domain/integration_test.go
@@ -4,16 +4,10 @@ import (
	"context"
	"database/sql"
	"links/models"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"
	"time"

	"github.com/labstack/echo/v4"
	"netlandish.com/x/gobwebs/config"
	"netlandish.com/x/gobwebs/database"
	"netlandish.com/x/gobwebs/server"
	"netlandish.com/x/gobwebs/timezone"

	_ "github.com/lib/pq"
@@ -48,132 +42,6 @@ func testDB(t *testing.T) *sql.DB {
	return db
}

// TestDomainValidationContextCancellation tests the exact scenario that was causing transaction leaks:
// Domain validation middleware calling ValidDomain() with context cancellation during database lookup
func TestDomainValidationContextCancellation(t *testing.T) {
	db := testDB(t)

	// Set up basic test data - create a test domain
	// First try to delete any existing test domain
	db.Exec("DELETE FROM domains WHERE lookup_name = 'test.example.com'")

	_, err := db.Exec(`
		INSERT INTO domains (lookup_name, name, service, status, level, is_active)
		VALUES ($1, $2, $3, $4, $5, $6)
	`, "test.example.com", "test.example.com", models.DomainServiceLinks, models.DomainStatusApproved, models.DomainLevelSystem, true)
	if err != nil {
		t.Fatalf("Failed to create test domain: %v", err)
	}

	// Clean up after test
	defer func() {
		db.Exec("DELETE FROM domains WHERE lookup_name = 'test.example.com'")
	}()

	// Track connection stats before test
	statsBefore := db.Stats()

	// Create Echo server with middleware stack similar to production
	e := echo.New()
	cfg := &config.Config{Debug: true}
	server.New(e, db, cfg).Initialize()

	// Add middleware in same order as production
	e.Use(
		// Custom context middleware (matches server.DefaultMiddlewareWithConfig)
		func(next echo.HandlerFunc) echo.HandlerFunc {
			return func(c echo.Context) error {
				ctx := &server.Context{Context: c, Server: server.New(e, db, cfg)}
				return next(ctx)
			}
		},
		database.DBIMiddleware(db), // DBI must be first
		func(next echo.HandlerFunc) echo.HandlerFunc { // Add timezone context
			return func(c echo.Context) error {
				ctx := timezone.Context(c.Request().Context(), "UTC")
				c.SetRequest(c.Request().WithContext(ctx))
				return next(c)
			}
		},
		DomainContext(models.DomainServiceLinks), // Domain validation
	)

	// Simple handler that should never be reached due to domain validation failure
	e.GET("/test", func(c echo.Context) error {
		return c.String(http.StatusOK, "success")
	})

	// Test 1: Context cancellation during domain validation
	req := httptest.NewRequest(http.MethodGet, "/test", nil)
	req.Host = "test.example.com" // Valid domain that exists in DB

	// Create cancellable context
	ctx, cancel := context.WithCancel(req.Context())
	req = req.WithContext(ctx)
	rec := httptest.NewRecorder()

	// Start request processing in goroutine
	done := make(chan bool)
	go func() {
		defer func() { done <- true }()
		e.ServeHTTP(rec, req)
	}()

	// Cancel context immediately to simulate client disconnect
	// This should trigger the "context canceled" error in ValidDomain()
	cancel()

	// Wait for request to complete
	<-done

	// The request should fail (exact status depends on error handling)
	if rec.Code == http.StatusOK {
		t.Error("Expected request to fail due to context cancellation")
	}

	// Wait for cleanup
	time.Sleep(300 * time.Millisecond)

	// Verify no connection leak occurred - this is the critical test
	statsAfter := db.Stats()
	if statsAfter.OpenConnections > statsBefore.OpenConnections {
		t.Errorf("Connection leak detected after context cancellation: before=%d, after=%d",
			statsBefore.OpenConnections, statsAfter.OpenConnections)
	}

	t.Logf("Context cancellation test - Connection stats before: %d, after: %d",
		statsBefore.OpenConnections, statsAfter.OpenConnections)

	// Test 2: Multiple rapid cancellations (stress test)
	for range 5 {
		req := httptest.NewRequest(http.MethodGet, "/test", nil)
		req.Host = "test.example.com"

		ctx, cancel := context.WithCancel(req.Context())
		req = req.WithContext(ctx)
		rec := httptest.NewRecorder()

		go func() {
			e.ServeHTTP(rec, req)
		}()

		// Cancel quickly
		cancel()
		time.Sleep(50 * time.Millisecond)
	}

	// Final connection check after stress test
	time.Sleep(500 * time.Millisecond)
	statsFinal := db.Stats()

	if statsFinal.OpenConnections > statsBefore.OpenConnections+2 { // Allow some tolerance
		t.Errorf("Connection leak detected after stress test: before=%d, final=%d",
			statsBefore.OpenConnections, statsFinal.OpenConnections)
	}

	t.Logf("Stress test complete - Connection stats before: %d, final: %d",
		statsBefore.OpenConnections, statsFinal.OpenConnections)
}

// TestValidDomainWithContextCancellation tests the ValidDomain function directly
func TestValidDomainWithContextCancellation(t *testing.T) {
-- 
2.49.1
Details
Message ID
<DE6RGDEBUGMW.3IEE6AVR68UJU@netlandish.com>
In-Reply-To
<20251112134204.18527-1-peter@netlandish.com> (view parent)
Sender timestamp
1762933580
DKIM signature
missing
Download raw message
Applied.

To git@git.code.netlandish.com:~netlandish/links
   04b88f7..d4cc8e5  master -> master
Reply to thread Export thread (mbox)