fix: stdin TTY 偵測改以 fd 0 實際裝置判定,escript 真終端機不再誤判(issue #19)

- 新增 BearCli.TTY:readlink /proc/self/fd/0 判定 pts/tty/console/ptmx;
  無 /proc 平台退回 :io.rows/0
- vault/self_service 的 tty_stdin?、accounts 的 tty? 改用 TTY.stdin_tty?
- 根因::io.rows/0/:io.columns/0 走 io 協定問 group leader,escript
  (noshell standard_io)不支援行列查詢,一律回 enotsup——真終端機被
  誤判成非 TTY,互動指令(vault unlock 等)無法讀 stdin 密碼
  (bear #52 驗收建議 2)
- 非 TTY(pipe/redirect)行為不變:明確提示+退出碼 2
- 新增 tty_test(4 測試);mix precommit 全綠(200 passed)
This commit is contained in:
2026-09-14 19:26:23 +08:00
parent a2edb03a0e
commit a29b976f13
5 changed files with 128 additions and 6 deletions
+57
View File
@@ -0,0 +1,57 @@
defmodule BearCli.TTYTest do
@moduledoc """
`BearCli.TTY` 單元測試(issue #19):
- `tty_path?/1`:裝置路徑分類(pts/tty/console=TTY;pipe/檔案/
socket//dev/null=非 TTY)。
- `stdin_tty?/0`:在本測試環境(ExUnit 捕獲 IO,stdin 非 TTY)應為
`false`;`rows_tty?/0` 在 escript/noshell 環境回 `enotsup` → `false`。
- fd 0 偵測走 `/proc/self/fd/0`,Linux 上必可用。
"""
use ExUnit.Case, async: true
alias BearCli.TTY
describe "tty_path?/1" do
test "終端機裝置路徑 → true" do
assert TTY.tty_path?("/dev/pts/0")
assert TTY.tty_path?("/dev/pts/17")
assert TTY.tty_path?("/dev/tty1")
assert TTY.tty_path?("/dev/tty")
assert TTY.tty_path?("/dev/ttyS0")
assert TTY.tty_path?("/dev/console")
assert TTY.tty_path?("/dev/ptmx")
end
test "pipe/檔案/socket/null → false" do
refute TTY.tty_path?("pipe:[12345]")
refute TTY.tty_path?("socket:[12345]")
refute TTY.tty_path?("/dev/null")
refute TTY.tty_path?("/home/user/secret.txt")
refute TTY.tty_path?("/proc/self/fd/0")
refute TTY.tty_path?("")
end
end
describe "stdin_tty?/0" do
test "測試環境(stdin 非 TTY)→ false" do
# ExUnit 的 group leader 為擷取裝置、測試程序的 stdin 非終端機。
refute TTY.stdin_tty?()
end
end
describe "fd0 實體判定(Linux /proc)" do
test "/proc/self/fd/0 可解析且與路徑分類一致" do
# 本測試在 Linux 跑:readlink 一定有結果;stdin 非 TTY → 分類為非 TTY。
assert {:ok, path} = call_fd0_path()
assert TTY.stdin_tty?() == TTY.tty_path?(path)
end
end
defp call_fd0_path do
case :file.read_link('/proc/self/fd/0') do
{:ok, p} -> {:ok, to_string(p)}
{:error, e} -> {:error, e}
end
end
end