Skip to content

Commit 5765ae5

Browse files
committed
more unit tests
1 parent b4d15fd commit 5765ae5

2 files changed

Lines changed: 165 additions & 0 deletions

File tree

internal/helper/google_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,23 @@ func TestFetchGoogleCrawlerIPs(t *testing.T) {
124124
t.Fatalf("unexpected CIDRs: got %v want %v", got, want)
125125
}
126126
}
127+
128+
func TestFetchGoogleCrawlerIPsError(t *testing.T) {
129+
log := slog.New(slog.NewTextHandler(os.Stdout, nil))
130+
131+
okServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
132+
w.Header().Set("Content-Type", "application/json")
133+
_, _ = w.Write([]byte(`{"prefixes":[{"ipv4Prefix":"8.8.8.0/24"}]}`))
134+
}))
135+
defer okServer.Close()
136+
137+
errServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
138+
http.Error(w, "boom", http.StatusInternalServerError)
139+
}))
140+
defer errServer.Close()
141+
142+
_, err := FetchGoogleCrawlerIPs(log, okServer.Client(), []string{okServer.URL, errServer.URL})
143+
if err == nil {
144+
t.Fatal("expected error when one endpoint returns non-200")
145+
}
146+
}

main_test.go

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1629,3 +1629,148 @@ func TestPojChallengeGeneration(t *testing.T) {
16291629
t.Errorf("Expected PoJ JS URL in challenge page")
16301630
}
16311631
}
1632+
1633+
func TestPerformHealthCheckSuccessResetsFailures(t *testing.T) {
1634+
config := CreateConfig()
1635+
config.SiteKey = "test"
1636+
config.SecretKey = "test"
1637+
config.ProtectRoutes = []string{"/"}
1638+
config.CaptchaProvider = "turnstile"
1639+
config.PeriodSeconds = 3600
1640+
config.FailureThreshold = 2
1641+
1642+
ctx, cancel := context.WithCancel(context.Background())
1643+
defer cancel()
1644+
1645+
bc, err := NewCaptchaProtect(ctx, nil, config, "test")
1646+
if err != nil {
1647+
t.Fatalf("Failed to create CaptchaProtect: %v", err)
1648+
}
1649+
1650+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
1651+
w.WriteHeader(http.StatusOK)
1652+
}))
1653+
defer server.Close()
1654+
1655+
bc.captchaConfig.js = server.URL
1656+
bc.recordHealthCheckFailure()
1657+
1658+
bc.performHealthCheck()
1659+
1660+
bc.mu.RLock()
1661+
defer bc.mu.RUnlock()
1662+
if bc.healthCheckFailureCount != 0 {
1663+
t.Fatalf("expected failure count reset to 0, got %d", bc.healthCheckFailureCount)
1664+
}
1665+
if bc.circuitState != circuitClosed {
1666+
t.Fatalf("expected circuit to be closed, got %v", bc.circuitState)
1667+
}
1668+
}
1669+
1670+
func TestPerformHealthCheckFailurePaths(t *testing.T) {
1671+
tests := []struct {
1672+
name string
1673+
jsURL string
1674+
status int
1675+
expectErr bool
1676+
}{
1677+
{
1678+
name: "404 considered failure",
1679+
status: http.StatusNotFound,
1680+
},
1681+
{
1682+
name: "503 considered failure",
1683+
status: http.StatusServiceUnavailable,
1684+
},
1685+
{
1686+
name: "invalid URL request creation failure",
1687+
jsURL: "://invalid-url",
1688+
expectErr: true,
1689+
},
1690+
}
1691+
1692+
for _, tt := range tests {
1693+
t.Run(tt.name, func(t *testing.T) {
1694+
config := CreateConfig()
1695+
config.SiteKey = "test"
1696+
config.SecretKey = "test"
1697+
config.ProtectRoutes = []string{"/"}
1698+
config.CaptchaProvider = "turnstile"
1699+
config.PeriodSeconds = 3600
1700+
config.FailureThreshold = 1
1701+
1702+
ctx, cancel := context.WithCancel(context.Background())
1703+
defer cancel()
1704+
1705+
bc, err := NewCaptchaProtect(ctx, nil, config, "test")
1706+
if err != nil {
1707+
t.Fatalf("Failed to create CaptchaProtect: %v", err)
1708+
}
1709+
1710+
if tt.expectErr {
1711+
bc.captchaConfig.js = tt.jsURL
1712+
} else {
1713+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
1714+
w.WriteHeader(tt.status)
1715+
}))
1716+
defer server.Close()
1717+
bc.captchaConfig.js = server.URL
1718+
}
1719+
1720+
bc.performHealthCheck()
1721+
1722+
bc.mu.RLock()
1723+
defer bc.mu.RUnlock()
1724+
if bc.healthCheckFailureCount != 1 {
1725+
t.Fatalf("expected failure count 1, got %d", bc.healthCheckFailureCount)
1726+
}
1727+
if bc.circuitState != circuitOpen {
1728+
t.Fatalf("expected circuit to open, got %v", bc.circuitState)
1729+
}
1730+
})
1731+
}
1732+
}
1733+
1734+
func TestVerifyChallengePagePojFallbackUsesOneHourTTL(t *testing.T) {
1735+
config := CreateConfig()
1736+
config.SiteKey = "test-key"
1737+
config.SecretKey = "test-secret"
1738+
config.ProtectRoutes = []string{"/"}
1739+
config.CaptchaProvider = "turnstile"
1740+
config.PeriodSeconds = 3600
1741+
config.FailureThreshold = 1
1742+
1743+
ctx, cancel := context.WithCancel(context.Background())
1744+
defer cancel()
1745+
1746+
bc, err := NewCaptchaProtect(ctx, nil, config, "test")
1747+
if err != nil {
1748+
t.Fatalf("Failed to create CaptchaProtect: %v", err)
1749+
}
1750+
1751+
// Open the circuit so PoJ becomes active fallback provider.
1752+
bc.recordHealthCheckFailure()
1753+
1754+
form := url.Values{}
1755+
form.Add("poj-captcha-response", "ok")
1756+
form.Add("destination", "%2F")
1757+
req := httptest.NewRequest(http.MethodPost, "/challenge", strings.NewReader(form.Encode()))
1758+
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
1759+
rr := httptest.NewRecorder()
1760+
clientIP := "203.0.113.10"
1761+
1762+
status := bc.verifyChallengePage(rr, req, clientIP)
1763+
if status != http.StatusFound {
1764+
t.Fatalf("expected status %d, got %d", http.StatusFound, status)
1765+
}
1766+
1767+
item, found := bc.verifiedCache.Items()[clientIP]
1768+
if !found {
1769+
t.Fatalf("expected %s to be in verified cache", clientIP)
1770+
}
1771+
1772+
remaining := time.Until(time.Unix(0, item.Expiration))
1773+
if remaining < 50*time.Minute || remaining > 70*time.Minute {
1774+
t.Fatalf("expected PoJ fallback TTL around 1h, got %s", remaining)
1775+
}
1776+
}

0 commit comments

Comments
 (0)