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/registry_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

108 lines
2.2 KiB
Go

package shell
import (
"context"
"io"
"testing"
)
// stubShell implements Shell for testing.
type stubShell struct {
name string
}
func (s *stubShell) Name() string { return s.name }
func (s *stubShell) Description() string { return "stub" }
func (s *stubShell) Handle(_ context.Context, _ *SessionContext, _ io.ReadWriteCloser) error {
return nil
}
func TestRegistryRegisterAndGet(t *testing.T) {
r := NewRegistry()
sh := &stubShell{name: "test"}
if err := r.Register(sh, 1); err != nil {
t.Fatalf("Register: %v", err)
}
got, ok := r.Get("test")
if !ok {
t.Fatal("Get returned false")
}
if got.Name() != "test" {
t.Errorf("Name = %q, want %q", got.Name(), "test")
}
}
func TestRegistryGetMissing(t *testing.T) {
r := NewRegistry()
_, ok := r.Get("nope")
if ok {
t.Fatal("Get returned true for missing shell")
}
}
func TestRegistryDuplicateName(t *testing.T) {
r := NewRegistry()
r.Register(&stubShell{name: "dup"}, 1)
err := r.Register(&stubShell{name: "dup"}, 1)
if err == nil {
t.Fatal("expected error for duplicate name")
}
}
func TestRegistryInvalidWeight(t *testing.T) {
r := NewRegistry()
err := r.Register(&stubShell{name: "a"}, 0)
if err == nil {
t.Fatal("expected error for weight 0")
}
err = r.Register(&stubShell{name: "b"}, -1)
if err == nil {
t.Fatal("expected error for negative weight")
}
}
func TestRegistrySelectEmpty(t *testing.T) {
r := NewRegistry()
_, err := r.Select()
if err == nil {
t.Fatal("expected error from empty registry")
}
}
func TestRegistrySelectSingle(t *testing.T) {
r := NewRegistry()
r.Register(&stubShell{name: "only"}, 1)
for range 10 {
sh, err := r.Select()
if err != nil {
t.Fatalf("Select: %v", err)
}
if sh.Name() != "only" {
t.Errorf("Name = %q, want %q", sh.Name(), "only")
}
}
}
func TestRegistrySelectWeighted(t *testing.T) {
r := NewRegistry()
r.Register(&stubShell{name: "heavy"}, 100)
r.Register(&stubShell{name: "light"}, 1)
counts := map[string]int{}
for range 1000 {
sh, err := r.Select()
if err != nil {
t.Fatalf("Select: %v", err)
}
counts[sh.Name()]++
}
// "heavy" has weight 100 vs "light" weight 1, so heavy should get ~99%.
if counts["heavy"] < 900 {
t.Errorf("heavy selected %d/1000 times, expected >900", counts["heavy"])
}
}