Files
3x-ui/internal/web/runtime/tls_client_property_test.go
Sanaei 7605902324 Test-quality audit: fix 2 prod bugs, strengthen weak tests, add mutation/fuzz/CI tooling (#5345)
* test(audit): add gremlins/rapid/coverage tooling + AUDIT.md scaffold

* test(audit): hygiene sweep (race-clean except logger global; Finding #2) + smell inventory

* test(audit): cover untested error/edge branches (TLS proxy+pin, migration tag cleanup=Finding #1)

* test(audit): strengthen internal/sub link tests (dedup key, TLS/Reality mapping, clash well-formedness)

* test(audit): property (rapid) + fuzz tests for joinHostPort/userinfo/pin/ParseLink

* test(audit): tighten frontend subSortIndex rejection assertions + wire coverage

* ci(audit): add shuffle gate + non-blocking race job (Finding #2) + fuzz-smoke; document mutation policy

* chore(audit): gitignore frontend coverage output

* test(audit): exhaustive whole-repo pass — strengthen 5 weak/fake tests (netproxy, CSP, modal per-protocol loops, schema coercions)

* docs(contributing): add Testing section (conventions, race/shuffle, fuzz, mutation policy); drop AUDIT.md ledger

* fix(logger,migration): guard logBuffer with mutex; execute legacy tag cleanup (tx.Exec); make CI race gate blocking

* ci(mutation): add nightly scoped gremlins workflow (informational artifacts)

* test(audit): strengthen runtime tests — baseURL scheme/port bounds, isNonEmptySlice, trafficReset

* test(audit): strengthen clash tests — reality field mapping + tcp-header validation

* test(audit): runtime — egress-proxy + content-type tests; drop redundant bp=='' branch

* test(audit): strengthen link parser/helper tests (defaultPort, splitComma, base64, canonicalQuery, tls/reality/transport mapping)

* test(audit): strengthen sub/xray/common/netsafe/mtproto/config/middleware tests (kill surviving mutants)

* test(audit): raise timeout on protocol-iteration modal tests (heavy re-renders, slow on CI)

* fix(logger): GetLogs returns at most c entries (off-by-one fix; addresses PR review)

* perf(logger): snapshot logBuffer under lock so GetLogs doesn't block logging; clarify fuzz-seed docs (addresses PR review)
2026-06-15 15:17:03 +02:00

74 lines
2.1 KiB
Go

package runtime
import (
"bytes"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"strings"
"testing"
"pgregory.net/rapid"
)
func insertColons(h string) string {
var b strings.Builder
for i := 0; i < len(h); i += 2 {
if i > 0 {
b.WriteByte(':')
}
b.WriteString(h[i : i+2])
}
return b.String()
}
// TestProp_DecodeCertPin_FormatAgnostic asserts that for ANY 32-byte pin, every
// accepted encoding (hex lower/upper, openssl colon-hex, base64 std/raw/url) decodes
// back to the same bytes. Generalizes the fixed-input TestDecodeCertPin so a mutant
// that breaks one decoding path is caught across the whole input space.
func TestProp_DecodeCertPin_FormatAgnostic(t *testing.T) {
rapid.Check(t, func(t *rapid.T) {
raw := rapid.SliceOfN(rapid.Byte(), sha256.Size, sha256.Size).Draw(t, "raw")
hx := hex.EncodeToString(raw)
forms := []string{
hx,
strings.ToUpper(hx),
insertColons(hx),
base64.StdEncoding.EncodeToString(raw),
base64.RawStdEncoding.EncodeToString(raw),
base64.URLEncoding.EncodeToString(raw),
base64.RawURLEncoding.EncodeToString(raw),
}
for _, f := range forms {
got, err := DecodeCertPin(f)
if err != nil {
t.Fatalf("DecodeCertPin(%q) errored: %v", f, err)
}
if !bytes.Equal(got, raw) {
t.Fatalf("DecodeCertPin(%q) = %x, want %x", f, got, raw)
}
}
})
}
// FuzzDecodeCertPin asserts the security-load-bearing decoder never panics, never
// returns a non-32-byte slice with a nil error, and never returns bytes alongside an
// error. Seeded from the known-good/known-bad cases.
func FuzzDecodeCertPin(f *testing.F) {
seed := sha256.Sum256([]byte("seed"))
f.Add(hex.EncodeToString(seed[:]))
f.Add(base64.StdEncoding.EncodeToString(seed[:]))
f.Add(insertColons(hex.EncodeToString(seed[:])))
f.Add("")
f.Add("not-a-pin")
f.Fuzz(func(t *testing.T, s string) {
got, err := DecodeCertPin(s)
if err == nil && len(got) != sha256.Size {
t.Fatalf("DecodeCertPin(%q): nil error but %d bytes, want %d", s, len(got), sha256.Size)
}
if err != nil && got != nil {
t.Fatalf("DecodeCertPin(%q): error %v but returned bytes %x", s, err, got)
}
})
}