Bots often send commands via `ssh user@host <command>` (exec request) rather than requesting an interactive shell. These were previously rejected silently. Now exec commands are captured, stored on the session record, and displayed in the web UI session detail page. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
84 lines
2.2 KiB
Go
84 lines
2.2 KiB
Go
package storage
|
|
|
|
import (
|
|
"database/sql"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
_ "modernc.org/sqlite"
|
|
)
|
|
|
|
func TestMigrateCreatesTablesAndVersion(t *testing.T) {
|
|
dbPath := filepath.Join(t.TempDir(), "test.db")
|
|
db, err := sql.Open("sqlite", dbPath)
|
|
if err != nil {
|
|
t.Fatalf("open: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
if err := Migrate(db); err != nil {
|
|
t.Fatalf("migrate: %v", err)
|
|
}
|
|
|
|
// Verify schema version.
|
|
var version int
|
|
if err := db.QueryRow(`SELECT version FROM schema_version`).Scan(&version); err != nil {
|
|
t.Fatalf("query version: %v", err)
|
|
}
|
|
if version != 4 {
|
|
t.Errorf("version = %d, want 4", version)
|
|
}
|
|
|
|
// Verify tables exist by inserting into them.
|
|
_, err = db.Exec(`INSERT INTO login_attempts (username, password, ip, count, first_seen, last_seen) VALUES ('a', 'b', 'c', 1, '2024-01-01', '2024-01-01')`)
|
|
if err != nil {
|
|
t.Fatalf("insert into login_attempts: %v", err)
|
|
}
|
|
_, err = db.Exec(`INSERT INTO sessions (id, ip, username, shell_name, connected_at) VALUES ('test-id', 'c', 'a', '', '2024-01-01')`)
|
|
if err != nil {
|
|
t.Fatalf("insert into sessions: %v", err)
|
|
}
|
|
_, err = db.Exec(`INSERT INTO session_logs (session_id, timestamp, input, output) VALUES ('test-id', '2024-01-01', '', '')`)
|
|
if err != nil {
|
|
t.Fatalf("insert into session_logs: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestMigrateIdempotent(t *testing.T) {
|
|
dbPath := filepath.Join(t.TempDir(), "test.db")
|
|
db, err := sql.Open("sqlite", dbPath)
|
|
if err != nil {
|
|
t.Fatalf("open: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
// Run twice; second should be a no-op.
|
|
if err := Migrate(db); err != nil {
|
|
t.Fatalf("first migrate: %v", err)
|
|
}
|
|
if err := Migrate(db); err != nil {
|
|
t.Fatalf("second migrate: %v", err)
|
|
}
|
|
|
|
var version int
|
|
if err := db.QueryRow(`SELECT version FROM schema_version`).Scan(&version); err != nil {
|
|
t.Fatalf("query version: %v", err)
|
|
}
|
|
if version != 4 {
|
|
t.Errorf("version = %d after double migrate, want 4", version)
|
|
}
|
|
}
|
|
|
|
func TestLoadMigrations(t *testing.T) {
|
|
migrations, err := loadMigrations()
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
if len(migrations) == 0 {
|
|
t.Fatal("no migrations found")
|
|
}
|
|
if migrations[0].Version != 1 {
|
|
t.Errorf("first migration version = %d, want 1", migrations[0].Version)
|
|
}
|
|
}
|