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>
141 lines
3.1 KiB
Go
141 lines
3.1 KiB
Go
package bash
|
|
|
|
import (
|
|
"sort"
|
|
"testing"
|
|
)
|
|
|
|
func TestNewFilesystem(t *testing.T) {
|
|
fs := newFilesystem("testhost")
|
|
|
|
// Standard directories should exist.
|
|
for _, dir := range []string{"/etc", "/root", "/home", "/var/log", "/tmp", "/usr/bin"} {
|
|
if !fs.isDirectory(dir) {
|
|
t.Errorf("%s should be a directory", dir)
|
|
}
|
|
}
|
|
|
|
// Standard files should exist.
|
|
for _, file := range []string{"/etc/passwd", "/etc/hostname", "/root/.bashrc", "/tmp/notes.txt"} {
|
|
if !fs.exists(file) {
|
|
t.Errorf("%s should exist", file)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFilesystemHostname(t *testing.T) {
|
|
fs := newFilesystem("myhost")
|
|
content, err := fs.read("/etc/hostname")
|
|
if err != nil {
|
|
t.Fatalf("read /etc/hostname: %v", err)
|
|
}
|
|
if content != "myhost\n" {
|
|
t.Errorf("hostname content = %q, want %q", content, "myhost\n")
|
|
}
|
|
}
|
|
|
|
func TestResolvePath(t *testing.T) {
|
|
tests := []struct {
|
|
cwd string
|
|
arg string
|
|
want string
|
|
}{
|
|
{"/root", "file.txt", "/root/file.txt"},
|
|
{"/root", "/etc/passwd", "/etc/passwd"},
|
|
{"/root", "..", "/"},
|
|
{"/var/log", "../..", "/"},
|
|
{"/root", ".", "/root"},
|
|
{"/root", "./sub/file", "/root/sub/file"},
|
|
{"/", "etc", "/etc"},
|
|
}
|
|
for _, tt := range tests {
|
|
got := resolvePath(tt.cwd, tt.arg)
|
|
if got != tt.want {
|
|
t.Errorf("resolvePath(%q, %q) = %q, want %q", tt.cwd, tt.arg, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFilesystemList(t *testing.T) {
|
|
fs := newFilesystem("testhost")
|
|
|
|
names, err := fs.list("/etc")
|
|
if err != nil {
|
|
t.Fatalf("list /etc: %v", err)
|
|
}
|
|
sort.Strings(names)
|
|
|
|
// Should contain at least passwd, hostname, hosts.
|
|
found := map[string]bool{}
|
|
for _, n := range names {
|
|
found[n] = true
|
|
}
|
|
for _, want := range []string{"passwd", "hostname", "hosts"} {
|
|
if !found[want] {
|
|
t.Errorf("list /etc missing %q, got %v", want, names)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFilesystemListNonexistent(t *testing.T) {
|
|
fs := newFilesystem("testhost")
|
|
_, err := fs.list("/nonexistent")
|
|
if err == nil {
|
|
t.Fatal("expected error listing nonexistent directory")
|
|
}
|
|
}
|
|
|
|
func TestFilesystemListFile(t *testing.T) {
|
|
fs := newFilesystem("testhost")
|
|
_, err := fs.list("/etc/passwd")
|
|
if err == nil {
|
|
t.Fatal("expected error listing a file")
|
|
}
|
|
}
|
|
|
|
func TestFilesystemRead(t *testing.T) {
|
|
fs := newFilesystem("testhost")
|
|
content, err := fs.read("/etc/passwd")
|
|
if err != nil {
|
|
t.Fatalf("read: %v", err)
|
|
}
|
|
if content == "" {
|
|
t.Error("expected non-empty content")
|
|
}
|
|
}
|
|
|
|
func TestFilesystemReadNonexistent(t *testing.T) {
|
|
fs := newFilesystem("testhost")
|
|
_, err := fs.read("/no/such/file")
|
|
if err == nil {
|
|
t.Fatal("expected error for nonexistent file")
|
|
}
|
|
}
|
|
|
|
func TestFilesystemReadDirectory(t *testing.T) {
|
|
fs := newFilesystem("testhost")
|
|
_, err := fs.read("/etc")
|
|
if err == nil {
|
|
t.Fatal("expected error for reading a directory")
|
|
}
|
|
}
|
|
|
|
func TestFilesystemDirectoryListing(t *testing.T) {
|
|
fs := newFilesystem("testhost")
|
|
names, err := fs.list("/")
|
|
if err != nil {
|
|
t.Fatalf("list /: %v", err)
|
|
}
|
|
|
|
// Root directories should end with /
|
|
found := map[string]bool{}
|
|
for _, n := range names {
|
|
found[n] = true
|
|
}
|
|
for _, want := range []string{"etc/", "root/", "home/", "var/", "tmp/", "usr/"} {
|
|
if !found[want] {
|
|
t.Errorf("list / missing %q, got %v", want, names)
|
|
}
|
|
}
|
|
}
|