Files
TrollSSH/src/app_test.go
T

266 lines
6.8 KiB
Go
Raw Normal View History

2026-07-13 17:47:00 +07:00
package main
import (
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func TestTSFRoundTrip(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "f.tsf")
original := &FramesContainer{
ColorFrames: [][]byte{{100, 101, 102}, {110, 120, 130}},
FPS: 29.97,
2026-07-13 17:47:00 +07:00
}
if err := writeTSF(path, original); err != nil {
t.Fatalf("writeTSF: %v", err)
}
fc, err := loadTSF(path)
if err != nil {
t.Fatalf("loadTSF: %v", err)
}
if fc.FPS != 29.97 {
t.Errorf("fps = %v", fc.FPS)
}
if len(fc.ColorFrames) != 2 {
t.Fatalf("frames = %d color", len(fc.ColorFrames))
2026-07-13 17:47:00 +07:00
}
if string(fc.ColorFrames[0]) != string([]byte{100, 101, 102}) {
t.Errorf("color frame0 = %v", fc.ColorFrames[0])
2026-07-13 17:47:00 +07:00
}
if string(fc.ColorFrames[1]) != string([]byte{110, 120, 130}) {
t.Errorf("color frame1 = %v", fc.ColorFrames[1])
2026-07-13 17:47:00 +07:00
}
}
func TestTSFInvalid(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "bad.tsf")
if err := os.WriteFile(path, []byte("not a tsf file"), 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
2026-07-13 17:47:00 +07:00
if _, err := loadTSF(path); err == nil {
t.Error("expected error for garbage input")
}
// Valid container but no frames.
if err := writeTSF(path, &FramesContainer{FPS: 30}); err != nil {
t.Fatalf("writeTSF: %v", err)
}
2026-07-13 17:47:00 +07:00
if _, err := loadTSF(path); err == nil {
t.Error("expected error for empty frames")
}
// Valid container but fps <= 0.
if err := writeTSF(path, &FramesContainer{ColorFrames: [][]byte{{1}}, FPS: 0}); err != nil {
t.Fatalf("writeTSF: %v", err)
}
2026-07-13 17:47:00 +07:00
if _, err := loadTSF(path); err == nil {
t.Error("expected error for fps<=0")
}
// Truncated payload.
if err := writeTSF(path, &FramesContainer{ColorFrames: [][]byte{{1, 2, 3, 4}}, FPS: 30}); err != nil {
t.Fatalf("writeTSF: %v", err)
}
raw, err := os.ReadFile(path)
if err != nil {
t.Fatalf("ReadFile: %v", err)
}
if err := os.WriteFile(path, raw[:len(raw)-2], 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
2026-07-13 17:47:00 +07:00
if _, err := loadTSF(path); err == nil {
t.Error("expected error for truncated file")
}
}
func TestLoadConfigDefaults(t *testing.T) {
t.Setenv("HOST", "")
t.Setenv("PORT", "")
t.Setenv("PLAYBACK_MODE", "")
t.Setenv("LOGIN_DELAY", "")
cfg := loadConfig()
if cfg.Host != "0.0.0.0" {
t.Errorf("host = %q", cfg.Host)
}
if cfg.Port != 22 {
t.Errorf("port = %d", cfg.Port)
}
if cfg.PlaybackMode != PlaybackLoop {
t.Errorf("playbackMode = %q", cfg.PlaybackMode)
}
if cfg.Charset != "detailed" {
t.Errorf("charset = %q", cfg.Charset)
}
if cfg.LoginDelay != 1500*time.Millisecond {
t.Errorf("loginDelay = %v", cfg.LoginDelay)
}
}
func TestLoadConfigClamping(t *testing.T) {
t.Setenv("PORT", "999999")
t.Setenv("BRIGHTNESS_THRESHOLD", "-5")
cfg := loadConfig()
if cfg.Port != 65535 {
t.Errorf("port clamp = %d", cfg.Port)
}
if cfg.BrightnessThreshold != 0 {
t.Errorf("brightness clamp = %d", cfg.BrightnessThreshold)
}
}
func TestLoadConfigInvalidFallsBack(t *testing.T) {
t.Setenv("PORT", "not-a-number")
t.Setenv("INVERT", "yes-please")
t.Setenv("PLAYBACK_MODE", "shuffle")
cfg := loadConfig()
if cfg.Port != 22 {
t.Errorf("port = %d, want default 22", cfg.Port)
}
if cfg.Invert {
t.Error("invert should fall back to false")
}
if cfg.PlaybackMode != PlaybackLoop {
t.Errorf("playbackMode = %q, want default loop", cfg.PlaybackMode)
}
}
func TestEnvDurationMs(t *testing.T) {
t.Setenv("D", "250")
if got := envDurationMs("D", time.Second); got != 250*time.Millisecond {
t.Errorf("250 = %v, want 250ms", got)
}
t.Setenv("D", "-10")
if got := envDurationMs("D", time.Second); got != 0 {
t.Errorf("negative = %v, want 0", got)
}
t.Setenv("D", "banana")
if got := envDurationMs("D", time.Second); got != time.Second {
t.Errorf("invalid = %v, want fallback 1s", got)
}
}
func TestPlaybackModeRandom(t *testing.T) {
t.Setenv("PLAYBACK_MODE", "RaNdOm")
if loadConfig().PlaybackMode != PlaybackRandom {
t.Error("expected random")
}
}
func TestResolveCharset(t *testing.T) {
if got := resolveCharset("blocks"); got != " ░▒▓█" {
t.Errorf("blocks preset = %q", got)
}
if got := resolveCharset("XYZ"); got != "XYZ" {
t.Errorf("custom ramp = %q", got)
}
if got := resolveCharset(""); !strings.HasPrefix(got, " .") {
t.Errorf("default = %q", got)
}
}
func TestFrameToAscii(t *testing.T) {
// Below threshold -> first ramp char; full brightness -> last.
opts := asciiOptions{brightnessThreshold: 40, charset: "standard"}
ramp := []rune(resolveCharset("standard"))
out := []rune(frameToAscii([]byte{0, 255}, opts))
if out[0] != ramp[0] {
t.Errorf("dark px = %q, want %q", out[0], ramp[0])
}
if out[1] != ramp[len(ramp)-1] {
t.Errorf("bright px = %q, want %q", out[1], ramp[len(ramp)-1])
}
}
func TestFrameToAsciiInvert(t *testing.T) {
opts := asciiOptions{brightnessThreshold: 40, charset: "standard", invert: true}
ramp := []rune(resolveCharset("standard"))
out := []rune(frameToAscii([]byte{255}, opts))
if out[0] != ramp[0] {
t.Errorf("inverted bright = %q, want %q", out[0], ramp[0])
}
}
func TestConnectionTracker(t *testing.T) {
tr := newConnectionTracker()
tr.increment("1.2.3.4")
tr.increment("1.2.3.4")
if !tr.hasReachedLimits("1.2.3.4", 2, 100) {
t.Error("expected per-ip limit reached")
}
tr.decrement("1.2.3.4")
tr.decrement("1.2.3.4")
if tr.totalCount() != 0 {
t.Errorf("total = %d", tr.totalCount())
}
if tr.hasReachedLimits("1.2.3.4", 2, 100) {
t.Error("should be cleared")
}
}
func TestClampDimension(t *testing.T) {
if clampDimension(0, 100) != 1 {
t.Error("floor")
}
if clampDimension(500, 100) != 100 {
t.Error("ceil")
}
if clampDimension(50, 100) != 50 {
t.Error("passthrough")
}
}
func TestParseDimsPtyReq(t *testing.T) {
// "xterm" + cols=100 rows=40 + widthpx + heightpx
payload := []byte{
0, 0, 0, 5, 'x', 't', 'e', 'r', 'm',
0, 0, 0, 100,
0, 0, 0, 40,
0, 0, 0, 0,
0, 0, 0, 0,
}
cols, rows, ok := parseDims(payload)
if !ok || cols != 100 || rows != 40 {
t.Errorf("parseDims = %d,%d,%v", cols, rows, ok)
}
term, ok := parsePtyTerm(payload)
if !ok || term != "xterm" {
t.Errorf("parsePtyTerm = %q,%v", term, ok)
}
}
func TestDetectColorTier(t *testing.T) {
cases := map[string]colorTier{
"": colorTierNone,
"dumb": colorTierNone,
"vt100": colorTierNone,
"linux": colorTierNone,
"xterm": colorTierTrueColor,
"xterm-256color": colorTier256,
"screen-256color": colorTier256,
"tmux-256color": colorTier256,
"xterm-direct": colorTierTrueColor,
"xterm-kitty": colorTierTrueColor,
}
for term, want := range cases {
if got := detectColorTier(term); got != want {
t.Errorf("detectColorTier(%q) = %d, want %d", term, got, want)
}
}
}
func TestQuantize256(t *testing.T) {
if got := quantize256(0, 0, 0); got != 16 {
t.Errorf("black = %d, want 16", got)
}
if got := quantize256(255, 255, 255); got != 231 {
t.Errorf("white = %d, want 231", got)
}
2026-07-13 17:47:00 +07:00
}