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/bash_test.go
Torjus Håkestad 8e90f21d91 feat: add Smart Fridge shell and per-credential shell routing
Implement Samsung FridgeOS-themed shell (PLAN.md §3.3) with inventory
management, temperature controls, diagnostics, alerts, and other
appliance commands. Add per-credential shell routing so static
credentials can specify which shell to use via the `shell` config field,
passed through ssh.Permissions.Extensions.

Also extract shared ReadLine helper from bash to the shell package so
both shells can reuse terminal input handling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 22:34:29 +01:00

200 lines
4.3 KiB
Go

package bash
import (
"bytes"
"context"
"errors"
"io"
"strings"
"testing"
"time"
"git.t-juice.club/torjus/oubliette/internal/shell"
"git.t-juice.club/torjus/oubliette/internal/storage"
)
type rwCloser struct {
io.Reader
io.Writer
closed bool
}
func (r *rwCloser) Close() error {
r.closed = true
return nil
}
func TestFormatPrompt(t *testing.T) {
tests := []struct {
cwd string
want string
}{
{"/root", "root@host:~# "},
{"/root/sub", "root@host:~/sub# "},
{"/tmp", "root@host:/tmp# "},
{"/", "root@host:/# "},
}
for _, tt := range tests {
state := &shellState{cwd: tt.cwd, username: "root", hostname: "host"}
got := formatPrompt(state)
if got != tt.want {
t.Errorf("formatPrompt(cwd=%q) = %q, want %q", tt.cwd, got, tt.want)
}
}
}
func TestReadLineEnter(t *testing.T) {
input := bytes.NewBufferString("hello\r")
var output bytes.Buffer
rw := struct {
io.Reader
io.Writer
}{input, &output}
ctx := context.Background()
line, err := shell.ReadLine(ctx, rw)
if err != nil {
t.Fatalf("readLine: %v", err)
}
if line != "hello" {
t.Errorf("line = %q, want %q", line, "hello")
}
}
func TestReadLineBackspace(t *testing.T) {
// Type "helo", backspace, then "lo\r"
input := bytes.NewBuffer([]byte{'h', 'e', 'l', 'o', 127, 'l', 'o', '\r'})
var output bytes.Buffer
rw := struct {
io.Reader
io.Writer
}{input, &output}
ctx := context.Background()
line, err := shell.ReadLine(ctx, rw)
if err != nil {
t.Fatalf("readLine: %v", err)
}
if line != "hello" {
t.Errorf("line = %q, want %q", line, "hello")
}
}
func TestReadLineCtrlC(t *testing.T) {
input := bytes.NewBuffer([]byte("partial\x03"))
var output bytes.Buffer
rw := struct {
io.Reader
io.Writer
}{input, &output}
ctx := context.Background()
line, err := shell.ReadLine(ctx, rw)
if err != nil {
t.Fatalf("readLine: %v", err)
}
if line != "" {
t.Errorf("line after Ctrl+C = %q, want empty", line)
}
}
func TestReadLineCtrlD(t *testing.T) {
input := bytes.NewBuffer([]byte{4}) // Ctrl+D on empty line
var output bytes.Buffer
rw := struct {
io.Reader
io.Writer
}{input, &output}
ctx := context.Background()
_, err := shell.ReadLine(ctx, rw)
if !errors.Is(err, io.EOF) {
t.Fatalf("expected io.EOF, got %v", err)
}
}
func TestBashShellHandle(t *testing.T) {
store := storage.NewMemoryStore()
sessID, _ := store.CreateSession(context.Background(), "127.0.0.1", "root", "bash")
sess := &shell.SessionContext{
SessionID: sessID,
Username: "root",
Store: store,
CommonConfig: shell.ShellCommonConfig{
Hostname: "testhost",
Banner: "Welcome to Ubuntu 22.04.3 LTS\r\n\r\n",
},
}
// Simulate typing commands followed by "exit\r"
commands := "pwd\rwhoami\rexit\r"
clientInput := bytes.NewBufferString(commands)
var clientOutput bytes.Buffer
rw := &rwCloser{
Reader: clientInput,
Writer: &clientOutput,
}
sh := NewBashShell()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err := sh.Handle(ctx, sess, rw)
if err != nil {
t.Fatalf("Handle: %v", err)
}
output := clientOutput.String()
// Should contain banner.
if !strings.Contains(output, "Welcome to Ubuntu") {
t.Error("output should contain banner")
}
// Should contain prompt with hostname.
if !strings.Contains(output, "root@testhost") {
t.Errorf("output should contain prompt, got: %s", output)
}
// Check session logs were recorded.
if len(store.SessionLogs) < 2 {
t.Errorf("expected at least 2 session logs, got %d", len(store.SessionLogs))
}
}
func TestBashShellFakeUser(t *testing.T) {
store := storage.NewMemoryStore()
sessID, _ := store.CreateSession(context.Background(), "127.0.0.1", "attacker", "bash")
sess := &shell.SessionContext{
SessionID: sessID,
Username: "attacker",
Store: store,
CommonConfig: shell.ShellCommonConfig{
Hostname: "testhost",
FakeUser: "admin",
},
}
commands := "whoami\rexit\r"
clientInput := bytes.NewBufferString(commands)
var clientOutput bytes.Buffer
rw := &rwCloser{
Reader: clientInput,
Writer: &clientOutput,
}
sh := NewBashShell()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
sh.Handle(ctx, sess, rw)
output := clientOutput.String()
if !strings.Contains(output, "admin") {
t.Errorf("output should contain fake user 'admin', got: %s", output)
}
}