Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

134 lines
4.0 KiB
Go

package hooks
import (
"os"
"path/filepath"
"strings"
"testing"
)
// withSessionDir points the per-session state store at a fresh
// t.TempDir() for one test, so session files never touch the user's
// real cache directory.
func withSessionDir(t *testing.T) string {
t.Helper()
dir := t.TempDir()
t.Setenv(hookSessionDirEnvVar, dir)
return dir
}
func TestSessionState_RoundTrip(t *testing.T) {
dir := withSessionDir(t)
// A fresh session loads as the zero value.
if got := loadSessionState("sess-1"); got.GraphConsulted {
t.Fatalf("fresh session should be zero value, got %+v", got)
}
saveSessionState("sess-1", sessionState{GraphConsulted: true})
got := loadSessionState("sess-1")
if !got.GraphConsulted {
t.Errorf("expected GraphConsulted=true after save, got %+v", got)
}
// The file lands inside the redirected directory.
if _, err := os.Stat(filepath.Join(dir, "sess-1.json")); err != nil {
t.Errorf("expected session file on disk: %v", err)
}
}
func TestSessionState_DistinctSessions(t *testing.T) {
withSessionDir(t)
saveSessionState("a", sessionState{GraphConsulted: true})
saveSessionState("b", sessionState{GraphConsulted: false})
if !loadSessionState("a").GraphConsulted {
t.Error("session a should have its own state")
}
if loadSessionState("b").GraphConsulted {
t.Error("session b state must not bleed from session a")
}
}
func TestSessionState_EmptySessionID_NoOp(t *testing.T) {
withSessionDir(t)
// Empty ID: save is a no-op, load returns zero — never an error.
saveSessionState("", sessionState{GraphConsulted: true})
if loadSessionState("").GraphConsulted {
t.Error("empty session ID must not persist state")
}
}
func TestSessionState_CorruptFile_DegradesToZero(t *testing.T) {
dir := withSessionDir(t)
if err := os.WriteFile(filepath.Join(dir, "broken.json"), []byte("{not json"), 0o644); err != nil {
t.Fatal(err)
}
if got := loadSessionState("broken"); got.GraphConsulted {
t.Errorf("corrupt state file should degrade to zero value, got %+v", got)
}
}
func TestSessionState_MissingDir_NoPanic(t *testing.T) {
// Point at a path whose parent does not exist; save must create it
// and not panic, load of a still-absent file returns zero.
dir := filepath.Join(t.TempDir(), "nested", "deeper")
t.Setenv(hookSessionDirEnvVar, dir)
if got := loadSessionState("x"); got.GraphConsulted {
t.Errorf("missing dir should load as zero value, got %+v", got)
}
saveSessionState("x", sessionState{GraphConsulted: true})
if !loadSessionState("x").GraphConsulted {
t.Error("save should create the nested directory and persist")
}
}
func TestSanitizeSessionID(t *testing.T) {
cases := []struct {
in string
want string
}{
{"abc123", "abc123"},
{"a-b_c.d", "a-b_c.d"},
{" spaced ", "spaced"},
{"with/slash", "with_slash"},
{"../traversal", ".._traversal"},
{"a b c", "a_b_c"},
{"", ""},
{".", ""},
{"..", ""},
{"weird*chars$here", "weird_chars_here"},
}
for _, c := range cases {
if got := sanitizeSessionID(c.in); got != c.want {
t.Errorf("sanitizeSessionID(%q) = %q, want %q", c.in, got, c.want)
}
}
}
func TestSessionStatePath_RejectsTraversal(t *testing.T) {
dir := withSessionDir(t)
// A traversal-shaped ID must resolve to a path that stays inside
// the session directory — the separators are sanitized to '_', so
// the path can never climb out.
p := sessionStatePath("../../etc/passwd")
if p == "" {
t.Fatal("expected a non-empty sanitized path")
}
if !strings.HasPrefix(p, dir+string(os.PathSeparator)) {
t.Errorf("sanitized path %q escaped the session dir %q", p, dir)
}
// The file segment must be a single component — no separators, no
// ".." path element survives sanitization. (A literal ".." byte
// pair is harmless once it can't act as a directory step.)
segment := strings.TrimPrefix(p, dir+string(os.PathSeparator))
if strings.ContainsRune(segment, os.PathSeparator) {
t.Errorf("sanitized file segment %q must not contain a path separator", segment)
}
if filepath.Clean(p) != p {
t.Errorf("sanitized path %q is not already clean — a traversal element survived", p)
}
}