modtest

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 22, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package modtest provides shared helpers for unit-testing scanner modules against an in-process httptest.Server.

Unlike the e2e harness (test/e2e/helper_test.go) these helpers carry no build tag, so they are available to fast `-short` unit tests. They wire up a real *http.Requester against the shared loopback dialer and build the *httpmsg.HttpRequestResponse / insertion points a module's scan method expects, letting a test exercise true detection logic without Docker.

Typical use:

srv := httptest.NewServer(handler)
defer srv.Close()
client := modtest.Requester(t)
rr := modtest.Request(t, srv.URL+"/?id=1")
ip := modtest.InsertionPoint(t, rr, "id")
res, err := mymod.New().ScanPerInsertionPoint(rr, ip, client, &modkit.ScanContext{})

Index

Constants

This section is empty.

Variables

View Source
var (
	// ApacheDefaultPageRHEL is the smaller of the pair.
	ApacheDefaultPageRHEL = "<html><head><title>Test Page for the HTTP Server on Red Hat Enterprise Linux</title></head>" +
		"<body><h1>Red Hat Enterprise Linux Test Page</h1>" +
		"<p>This page is used to test the proper operation of the HTTP server after it has been " +
		"installed. If you can read this page, it means that the HTTP server installed at this site " +
		"is working properly.</p>" +
		"<p>If you are a member of the general public: the fact that you are seeing this page " +
		"indicates that the website you just visited is either experiencing problems or is " +
		"undergoing routine maintenance.</p>" +
		redHatFiller + "</body></html>"

	// ApacheDefaultPageUbuntu is the larger of the pair. It carries the word
	// "installation" in ordinary prose, exactly as the real page does — the token
	// that once earned a Critical WordPress-installer finding on a parked host.
	ApacheDefaultPageUbuntu = "<html><head><title>Apache2 Ubuntu Default Page: It works</title></head>" +
		"<body><h1>Apache2 Ubuntu Default Page</h1>" +
		"<p>It works! This is the default welcome page used to test the correct operation of the " +
		"Apache2 server after installation on Ubuntu systems.</p>" +
		"<p>The configuration layout for an Apache2 web server installation on Ubuntu systems is " +
		"fully documented in the README file referenced below.</p>" +
		ubuntuFiller + "</body></html>"
)

A round-robin backend pool — a load balancer fronting two mismatched backends, so the SAME unmodified request returns one of two unrelated pages depending on which backend answers. This is the surface that manufactures phantom differentials for every body-diff oracle in the scanner (boolean-blind injection, path normalization, cross-ID comparison, header size shifts), and the shape modkit.EndpointVolatile exists to reject.

The two pages below are the real Apache defaults from the parked hosts that motivated the gate, sized ~4x apart like the originals (5.9 KB vs 11.3 KB). The proportions matter to the modules under test: the pages must share almost no vocabulary (so they are not "the same page" by textual similarity) and the length gap must clear the substantial-body-difference bar (>100 bytes AND >=20%), because a pair that failed either bar would let a test pass for the wrong reason — the module rejecting the differential on its own rather than on the determinism gate. Declared as vars rather than consts because the filler is built with strings.Repeat. Treat them as read-only.

Functions

func InsertionPoint

func InsertionPoint(t testing.TB, rr *httpmsg.HttpRequestResponse, name string) httpmsg.InsertionPoint

InsertionPoint returns the parameter insertion point named name from rr, or fails the test if no such point exists. Nested points are included so JSON / form sub-fields can be targeted by name.

func InsertionPointByType

InsertionPointByType returns the first insertion point of the given type from rr, or fails the test if none exists. Use it for the positional points that InsertionPoint cannot address readably: URL path folders and filenames are named by ordinal ("1", "2", …), so selecting them by name is both opaque and brittle whenever a test URL's path depth changes.

func Request

func Request(t testing.TB, rawURL string) *httpmsg.HttpRequestResponse

Request builds a GET *httpmsg.HttpRequestResponse targeting the absolute rawURL (e.g. an httptest.Server URL plus path/query). The HttpService is derived from the URL so a module's fuzzed requests route back to the test server. The response is left nil; modules that need a baseline fetch it themselves via the requester.

func RequestJSON

func RequestJSON(t testing.TB, rawURL, jsonBody string) *httpmsg.HttpRequestResponse

RequestJSON builds a POST request whose body is sent as application/json, so JSON fields become INS_PARAM_JSON insertion points. Use it for tests that target JSON parameters; RequestMethod sends form-urlencoded (INS_PARAM_BODY).

func RequestMethod

func RequestMethod(t testing.TB, method, rawURL, body string) *httpmsg.HttpRequestResponse

RequestMethod is like Request but lets a test pick the method and supply a request body (used for POST/PUT insertion-point coverage). The body is sent as application/x-www-form-urlencoded; an empty body omits the Content-Length/body section. For a JSON body (INS_PARAM_JSON insertion points) use RequestJSON.

func Requester

func Requester(t testing.TB) *httpRequester.Requester

Requester returns an *http.Requester wired to the shared loopback dialer, suitable for driving a module's scan method against an httptest.Server.

network.Init is reference-counted and idempotent: the dialer is created once per process and reused. We intentionally never Close it here — sequential and parallel tests share the dialer, and tearing it down mid-suite would break a concurrently running test.

func Response

func Response(rr *httpmsg.HttpRequestResponse, contentType, body string) *httpmsg.HttpRequestResponse

Response returns a copy of rr with a synthetic 200 OK response (carrying the given content-type and body) attached. It mirrors the captured baseline response the executor supplies before active scanning. Modules that use the original response as a baseline (e.g. ssrf_detection) need this; modules that fetch their own baseline can ignore it and pass the bare Request.

func ResponseChain added in v0.5.0

func ResponseChain(t testing.TB, status int, header http.Header, body string) *httputil.ResponseChain

ResponseChain builds a filled *httputil.ResponseChain from a status, header set and body, mirroring what the requester hands a module.

It exists for tests of the response-classification helpers modules layer on top of a response — block/edge detection, keep-alive gates, content checks — which need a chain but no server. The returned chain is closed on cleanup.

func RoundRobinHandler

func RoundRobinHandler(hits *int64, bodies ...string) http.HandlerFunc

RoundRobinHandler serves bodies in rotation — one per request, ignoring the request entirely — and counts every hit into hits, which the caller owns so a test can assert how many requests a gate actually spent. Pass at least one body; with a single body it degenerates into a stable endpoint, which is the useful negative control.

func VerifyNoLeaks

func VerifyNoLeaks(m *testing.M)

VerifyNoLeaks runs goleak.VerifyTestMain with the standard ignore list for the process-lifetime infra goroutines that Requester starts via network.Init (the active mem guardian and a leveldb store) — singletons with no stop hook. Use it as the whole body of a package's TestMain when that package's tests drive a real Requester:

func TestMain(m *testing.M) { modtest.VerifyNoLeaks(m) }

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL