Scert/backend/internal/adminconsole/flow_test.go
xuans 9cd8ce3530 优化本机重置不可用提示
当系统通知或本机重置通道不可用时,向运维人员显示明确的中文原因,并补充回归测试。
2026-10-05 03:15:09 +08:00

161 lines
4.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package adminconsole
import (
"context"
"errors"
"reflect"
"strings"
"testing"
"git.pcux.cn/xuans/Scert/backend/internal/localreset"
)
type testTerminalInput struct {
values []string
hidden []bool
prompts []string
shown []string
failAt int
readHook func(int)
}
func (s *testTerminalInput) Read(ctx context.Context, prompt string, hidden bool) (string, error) {
if err := ctx.Err(); err != nil {
return "", err
}
s.hidden = append(s.hidden, hidden)
s.prompts = append(s.prompts, prompt)
index := len(s.hidden)
if s.readHook != nil {
s.readHook(index)
}
if s.failAt == index {
return "", errors.New("test-input-failure")
}
if index > len(s.values) {
return "", errors.New("test-input-exhausted")
}
return s.values[index-1], nil
}
func (s *testTerminalInput) Show(message string) error {
s.shown = append(s.shown, message)
return nil
}
type testResetClient struct {
methods []localreset.Method
calls []string
fail string
password string
}
func (c *testResetClient) Methods() []localreset.Method { return c.methods }
func (c *testResetClient) invoke(action string) error {
c.calls = append(c.calls, action)
if c.fail == action {
return localreset.ErrProtocol
}
return nil
}
func (c *testResetClient) Select(value string) error {
if value != "email" {
return localreset.ErrProtocol
}
return c.invoke("select")
}
func (c *testResetClient) Verify(value string) error {
if value != "01234567" {
return localreset.ErrProtocol
}
return c.invoke("verify")
}
func (c *testResetClient) Reset(value string) error {
c.password = value
return c.invoke("reset")
}
func (*testResetClient) Close() error { return nil }
func TestAdminConsoleFlowRequiresVerificationAndHiddenInput(t *testing.T) {
client := &testResetClient{methods: []localreset.Method{{ID: "email"}}}
screen := &testTerminalInput{values: []string{"1", "01234567", "NewPass123!", "NewPass123!"}}
if err := resetFlow(t.Context(), client, screen); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(client.calls, []string{"select", "verify", "reset"}) || client.password != "NewPass123!" {
t.Fatal("重置未按验证后提交的顺序执行")
}
if !reflect.DeepEqual(screen.hidden, []bool{false, true, true, true}) {
t.Fatal("验证码或密码出现回显")
}
for _, prompt := range screen.prompts {
if strings.Contains(prompt, "01234567") || strings.Contains(prompt, "NewPass123!") {
t.Fatal("提示文字泄露秘密")
}
}
}
func TestAdminConsoleFailureNeverSubmitsPassword(t *testing.T) {
for _, scenario := range []string{"unknown-method", "cancel-choice", "select", "verify", "input-code", "input-password", "input-confirm", "weak-password", "mismatch", "cancel-after-verify"} {
t.Run(scenario, func(t *testing.T) {
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
client := &testResetClient{methods: []localreset.Method{{ID: "email"}}}
screen := &testTerminalInput{values: []string{"1", "01234567", "NewPass123!", "NewPass123!"}}
switch scenario {
case "unknown-method":
client.methods[0].ID = "unknown"
case "cancel-choice":
screen.values[0] = "0"
case "select", "verify":
client.fail = scenario
case "input-code":
screen.failAt = 2
case "input-password":
screen.failAt = 3
case "input-confirm":
screen.failAt = 4
case "weak-password":
screen.values[2] = "short"
case "mismatch":
screen.values[3] = "WrongPass12!"
case "cancel-after-verify":
screen.readHook = func(index int) {
if index == 4 {
cancel()
}
}
}
if err := resetFlow(ctx, client, screen); err == nil {
t.Fatal("失败条件未被拒绝")
}
for _, call := range client.calls {
if call == "reset" {
t.Fatal("验证或交互失败仍然提交了密码")
}
}
})
}
}
func TestAdminConsoleShowsUnavailableResetCause(t *testing.T) {
screen := &testTerminalInput{}
showResetFailure(screen, localreset.ErrUnavailable)
if len(screen.shown) != 1 || !strings.Contains(screen.shown[0], `验证码发送服务或本机重置通道不可用`) {
t.Fatalf(`未显示SMTP或本机通道不可用提示:%v`, screen.shown)
}
}
func TestAdminConsoleRejectsAllArgumentBasedInput(t *testing.T) {
for _, args := range [][]string{
{"--password", "TestPass123!"}, {"--config", "scert.json"}, {"--code-output", "reset-code"}, {"TestPass123!"}, {"--help"},
} {
err := Run(t.Context(), args)
if err == nil {
t.Fatal("接受了命令行参数")
}
if strings.Contains(err.Error(), "TestPass123!") {
t.Fatal("错误消息泄露了参数中的密码")
}
}
}