Files
fscan/common/session_test.go
T
ZacharyZcR 0612255893 test: 补充单元测试覆盖率 29.9% → 36.6%
新建 18 个测试文件,追加 30 个已有测试文件,覆盖协议解析、
错误分类、CEL 表达式求值、YAML 反序列化、字节编码等纯函数。
2026-06-17 12:51:41 +08:00

250 lines
6.7 KiB
Go

package common
import (
"io"
"net/http"
"strings"
"testing"
"time"
"github.com/shadow1ng/fscan/common/output"
)
func TestScanSessionLogMethodsHonorSilentConfig(t *testing.T) {
loggerMu.Lock()
silentLoggerRefs = 0
resetLoggerLocked()
loggerMu.Unlock()
t.Cleanup(func() {
loggerMu.Lock()
silentLoggerRefs = 0
resetLoggerLocked()
loggerMu.Unlock()
})
cfg := NewConfig()
cfg.Output.Silent = true
session := NewScanSession(cfg, NewState(), &FlagVars{})
session.LogDebug("debug")
session.LogInfo("info")
session.LogSuccess("success")
session.LogVuln("vuln")
session.LogError("error")
loggerMu.Lock()
defer loggerMu.Unlock()
if globalLogger != nil {
t.Fatal("silent session log methods initialized global logger")
}
}
func TestScanSessionDialerCacheIsTimeoutAware(t *testing.T) {
cfg := NewConfig()
cfg.Timeout = 5 * time.Second
session := NewScanSession(cfg, NewState(), &FlagVars{})
shortTimeout := 100 * time.Millisecond
longTimeout := 2 * time.Second
shortDialer, err := session.getDialer(shortTimeout)
if err != nil {
t.Fatal(err)
}
shortDialerAgain, err := session.getDialer(shortTimeout)
if err != nil {
t.Fatal(err)
}
longDialer, err := session.getDialer(longTimeout)
if err != nil {
t.Fatal(err)
}
if shortDialer != shortDialerAgain {
t.Fatal("same timeout should reuse the session dialer")
}
if shortDialer == longDialer {
t.Fatal("different timeouts should not share one session dialer")
}
if got := session.createProxyConfig(shortTimeout).Timeout; got != shortTimeout {
t.Fatalf("proxy timeout = %v, want %v", got, shortTimeout)
}
}
func TestScanSessionHTTPDoUsesSessionState(t *testing.T) {
previousState := GetGlobalState()
globalState := NewState()
SetGlobalState(globalState)
t.Cleanup(func() { SetGlobalState(previousState) })
sessionState := NewState()
session := NewScanSession(NewConfig(), sessionState, &FlagVars{})
client := &http.Client{
Transport: roundTripFunc(func(*http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusNoContent,
Body: io.NopCloser(strings.NewReader("")),
Header: make(http.Header),
}, nil
}),
}
req, err := http.NewRequest(http.MethodHead, "http://example.com", nil)
if err != nil {
t.Fatal(err)
}
resp, err := session.HTTPDo(client, req)
if err != nil {
t.Fatal(err)
}
_ = resp.Body.Close()
if got := sessionState.GetTCPSuccessPacketCount(); got != 1 {
t.Fatalf("session TCP success count = %d, want 1", got)
}
if got := globalState.GetTCPSuccessPacketCount(); got != 0 {
t.Fatalf("global TCP success count = %d, want 0", got)
}
}
func TestScanSessionProxyStateComesFromConfig(t *testing.T) {
direct := NewScanSession(NewConfig(), NewState(), &FlagVars{})
if direct.ProxyEnabled() {
t.Fatal("direct session should not report proxy enabled")
}
if direct.IsSOCKS5Proxy() {
t.Fatal("direct session should not report SOCKS5")
}
if !direct.ProxyReliable() {
t.Fatal("direct session should be reliable")
}
httpCfg := NewConfig()
httpCfg.Network.HTTPProxy = "http://127.0.0.1:8080"
httpSession := NewScanSession(httpCfg, NewState(), &FlagVars{})
if !httpSession.ProxyEnabled() {
t.Fatal("HTTP proxy session should report proxy enabled")
}
if httpSession.IsSOCKS5Proxy() {
t.Fatal("HTTP proxy session should not report SOCKS5")
}
if !httpSession.ProxyReliable() {
t.Fatal("HTTP proxy session should be reliable")
}
socksCfg := NewConfig()
socksCfg.Network.Socks5Proxy = "127.0.0.1:1080"
socksSession := NewScanSession(socksCfg, NewState(), &FlagVars{})
if !socksSession.ProxyEnabled() {
t.Fatal("SOCKS5 proxy session should report proxy enabled")
}
if !socksSession.IsSOCKS5Proxy() {
t.Fatal("SOCKS5 proxy session should report SOCKS5")
}
}
func TestParseProxyURLFallsBackWhenHostIsEmpty(t *testing.T) {
host, username, password := parseProxyURL("127.0.0.1:8080", "127.0.0.1:8080")
if host != "127.0.0.1:8080" {
t.Fatalf("host = %q, want fallback address", host)
}
if username != "" || password != "" {
t.Fatalf("unexpected credentials: %q/%q", username, password)
}
}
func TestParseProxyURLExtractsAuthWithoutScheme(t *testing.T) {
host, username, password := parseProxyURL("user:[email protected]:8080", "user:[email protected]:8080")
if host != "127.0.0.1:8080" {
t.Fatalf("host = %q, want proxy address", host)
}
if username != "user" || password != "pass" {
t.Fatalf("credentials = %q/%q, want user/pass", username, password)
}
}
// TestScanSessionSaveResultUsesSink 测试 SaveResult 通过 ResultSink 分发
func TestScanSessionSaveResultUsesSink(t *testing.T) {
preserveOutputAPIGlobals(t)
cfg := NewConfig()
cfg.Output.DisableSave = true
SetGlobalConfig(cfg)
flagVars = &FlagVars{DisableSave: true}
_ = InitOutput()
var sinkGot *output.ScanResult
session := NewScanSession(cfg, NewState(), &FlagVars{})
session.ResultSink = func(r *output.ScanResult) error {
sinkGot = r
return nil
}
result := &output.ScanResult{
Type: output.TypeHost,
Target: "10.0.0.1",
Status: "ALIVE",
}
if err := session.SaveResult(result); err != nil {
t.Fatalf("session.SaveResult error = %v", err)
}
if sinkGot != result {
t.Fatalf("ResultSink 未被调用或参数不符: got %v", sinkGot)
}
}
// TestScanSessionSaveResultFallsBackToGlobal 测试无 sink 时回退到全局 SaveResult
func TestScanSessionSaveResultFallsBackToGlobal(t *testing.T) {
preserveOutputAPIGlobals(t)
cfg := NewConfig()
cfg.Output.DisableSave = true
SetGlobalConfig(cfg)
flagVars = &FlagVars{DisableSave: true}
_ = InitOutput()
called := false
SetResultCallback(func(payload interface{}) {
called = true
})
session := NewScanSession(cfg, NewState(), &FlagVars{})
// 不设置 ResultSink,应回退到全局
result := &output.ScanResult{
Type: output.TypeHost,
Target: "10.0.0.2",
Status: "ALIVE",
}
if err := session.SaveResult(result); err != nil {
t.Fatalf("session.SaveResult (fallback) error = %v", err)
}
if !called {
t.Fatal("回退到全局 SaveResult 时应触发 ResultCallback")
}
}
// TestScanSessionLogMethodsEnabledByDefault 测试非 Silent 配置下 Log 方法不被屏蔽
func TestScanSessionLogMethodsEnabledByDefault(t *testing.T) {
cfg := NewConfig()
cfg.Output.Silent = false
session := NewScanSession(cfg, NewState(), &FlagVars{})
if !session.loggingEnabled() {
t.Fatal("非 Silent 配置下 loggingEnabled 应返回 true")
}
}
// TestNilScanSessionLoggingEnabled 测试 nil session 的 loggingEnabled
func TestNilScanSessionLoggingEnabled(t *testing.T) {
var session *ScanSession
if !session.loggingEnabled() {
t.Fatal("nil session 的 loggingEnabled 应返回 true(安全降级)")
}
}
type roundTripFunc func(*http.Request) (*http.Response, error)
func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return f(req)
}