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>
44 lines
802 B
Go
44 lines
802 B
Go
package shell
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"testing"
|
|
)
|
|
|
|
// nopCloser wraps a ReadWriter with a no-op Close.
|
|
type nopCloser struct {
|
|
io.ReadWriter
|
|
}
|
|
|
|
func (nopCloser) Close() error { return nil }
|
|
|
|
func TestRecordingChannelPassthrough(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
rc := NewRecordingChannel(nopCloser{&buf})
|
|
|
|
// Write through the recorder.
|
|
msg := []byte("hello")
|
|
n, err := rc.Write(msg)
|
|
if err != nil {
|
|
t.Fatalf("Write: %v", err)
|
|
}
|
|
if n != len(msg) {
|
|
t.Errorf("Write n = %d, want %d", n, len(msg))
|
|
}
|
|
|
|
// Read through the recorder.
|
|
out := make([]byte, 16)
|
|
n, err = rc.Read(out)
|
|
if err != nil {
|
|
t.Fatalf("Read: %v", err)
|
|
}
|
|
if string(out[:n]) != "hello" {
|
|
t.Errorf("Read = %q, want %q", out[:n], "hello")
|
|
}
|
|
|
|
if err := rc.Close(); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
}
|