defmodule BearCli.CLITest do use ExUnit.Case, async: false alias BearCli.CLI setup do old_creds = System.get_env("BEAR_CREDENTIALS") on_exit(fn -> restore_env("BEAR_CREDENTIALS", old_creds) end) :ok end describe "parse/1" do test "no args -> help" do assert {:help, %{}} = CLI.parse([]) end test "--version" do assert {:version, %{}} = CLI.parse(["--version"]) end test "-h alias -> help" do assert {:help, %{}} = CLI.parse(["-h"]) end test "login --token X" do assert {:ok, :login, %{token: "X"}} = CLI.parse(["login", "--token", "X"]) end test "global option before command" do assert {:ok, :whoami, %{json: true}} = CLI.parse(["--json", "whoami"]) end test "global option after command" do assert {:ok, :status, %{json: true}} = CLI.parse(["status", "--json"]) end test "token --refresh" do assert {:ok, :token, %{refresh: true}} = CLI.parse(["token", "--refresh"]) end test "issuer flag is captured" do assert {:ok, :whoami, %{issuer: "https://x"}} = CLI.parse(["whoami", "--issuer", "https://x"]) end test "unknown command -> usage error" do assert {:error, _, 2} = CLI.parse(["frobnicate"]) end test "unknown option -> usage error" do assert {:error, _, 2} = CLI.parse(["--bogus"]) end end describe "dispatch/2 (injected api)" do test "login --token writes credentials and prints email" do path = tmp_path() on_exit(fn -> File.rm(path) end) System.put_env("BEAR_CREDENTIALS", path) {code, out} = ExUnit.CaptureIO.with_io(fn -> CLI.dispatch(CLI.parse(["login", "--token", "good"]), api: fake_api()) end) assert code == 0 assert out =~ "已登入:alice@example.com" assert {:ok, %{"access_token" => "good"}} = BearCli.Credentials.load(path) end test "login with invalid token exits 1" do System.put_env("BEAR_CREDENTIALS", tmp_path()) {code, err} = ExUnit.CaptureIO.with_io( :stderr, "", fn -> CLI.dispatch(CLI.parse(["login", "--token", "bad"]), api: fake_api()) end ) assert code == 1 assert err =~ "token 無效" end test "login without --token points to Device Flow being unavailable" do System.put_env("BEAR_CREDENTIALS", tmp_path()) {code, err} = ExUnit.CaptureIO.with_io( :stderr, "", fn -> CLI.dispatch(CLI.parse(["login"]), api: fake_api()) end ) assert code == 2 assert err =~ "Device Flow" end test "whoami when not logged in exits 3" do System.put_env("BEAR_CREDENTIALS", "/nonexistent/credentials.json") {code, err} = ExUnit.CaptureIO.with_io( :stderr, "", fn -> CLI.dispatch(CLI.parse(["whoami"]), api: fake_api()) end ) assert code == 3 assert err =~ "未登入" end test "whoami prints claims" do path = tmp_path() on_exit(fn -> File.rm(path) end) System.put_env("BEAR_CREDENTIALS", path) BearCli.Credentials.save(%{"issuer" => "https://x", "access_token" => "good"}, path) {code, out} = ExUnit.CaptureIO.with_io(fn -> CLI.dispatch(CLI.parse(["whoami"]), api: fake_api()) end) assert code == 0 assert out =~ "email" assert out =~ "alice@example.com" end test "token prints only the token" do path = tmp_path() on_exit(fn -> File.rm(path) end) System.put_env("BEAR_CREDENTIALS", path) BearCli.Credentials.save(%{"issuer" => "https://x", "access_token" => "good-token"}, path) {code, out} = ExUnit.CaptureIO.with_io(fn -> CLI.dispatch(CLI.parse(["token"]), api: fake_api()) end) assert code == 0 assert String.trim(out) == "good-token" end test "status when logged in exits 0 and reports PAT mode" do path = tmp_path() on_exit(fn -> File.rm(path) end) System.put_env("BEAR_CREDENTIALS", path) BearCli.Credentials.save( %{"issuer" => "https://x", "email" => "a@b.c", "access_token" => "t"}, path ) {code, out} = ExUnit.CaptureIO.with_io(fn -> CLI.dispatch(CLI.parse(["status"]), api: fake_api()) end) assert code == 0 assert out =~ "已登入:a@b.c" assert out =~ "PAT" end test "logout clears credentials" do path = tmp_path() on_exit(fn -> File.rm(path) end) System.put_env("BEAR_CREDENTIALS", path) BearCli.Credentials.save(%{"issuer" => "https://x", "access_token" => "t"}, path) {code, _out} = ExUnit.CaptureIO.with_io(fn -> CLI.dispatch(CLI.parse(["logout"]), api: fake_api()) end) assert code == 0 assert :error == BearCli.Credentials.load(path) end end defp fake_api do fn _issuer, token -> case token do "good" -> {:ok, %{"sub" => "u1", "email" => "alice@example.com", "name" => "Alice"}} "bad" -> {:error, :unauthorized, "invalid_token"} _ -> {:error, :server_error, 500} end end end defp tmp_path do Path.join(System.tmp_dir!(), "bear_cli_cli_#{System.unique_integer([:positive])}.json") end defp restore_env(key, nil), do: System.delete_env(key) defp restore_env(key, value), do: System.put_env(key, value) end