Scert/backend/internal/cli/dns_test.go
xuans 0b74886c45 修复Linux启动就绪通知并隔离命令行测试目录
通过本机Unix套接字报告HTTPS监听状态,同步服务配置与安装模板,保持初始化业务就绪隔离。补充生命周期回归并修复旧CLI测试误用正式目录的问题。更新Linux原生测试证据,明确新候选尚未部署、生产验收未放行。
2026-10-02 12:20:47 +08:00

167 lines
5.8 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 cli
import (
"bytes"
"context"
"encoding/json"
"errors"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/spf13/cobra"
"git.pcux.cn/xuans/Scert/backend/internal/config"
"git.pcux.cn/xuans/Scert/backend/internal/dnsprovider"
"git.pcux.cn/xuans/Scert/backend/internal/secretstore"
)
func TestDNSCheckRequiresExplicitWriteConfirmation(t *testing.T) {
called := false
command := newDNSCheckCommand(func(context.Context, string, map[string]string) (dnsprovider.AuthorizationReport, error) {
called = true
return dnsprovider.AuthorizationReport{}, nil
})
command.SetArgs([]string{"--domain", "example.com"})
if err := command.Execute(); err == nil || !strings.Contains(err.Error(), "--confirm-dns-write") {
t.Fatalf("缺少写入确认时必须拒绝:%v", err)
}
if called {
t.Fatal("确认前不得调用云端 API")
}
}
func TestDNSCheckRejectsProtectedDomainBeforeConfigurationOrVerifier(t *testing.T) {
oldPath := configPath
configPath = filepath.Join(t.TempDir(), "missing-scert.json")
t.Cleanup(func() { configPath = oldPath })
command := newDNSCheckCommand(func(context.Context, string, map[string]string) (dnsprovider.AuthorizationReport, error) {
t.Fatal("保护域不得进入云端验证")
return dnsprovider.AuthorizationReport{}, nil
})
command.SilenceUsage, command.SilenceErrors = true, true
command.SetArgs([]string{"--domain", "pcux.cn", "--confirm-dns-write"})
if err := command.Execute(); err == nil || !strings.Contains(err.Error(), "pcux.cn") {
t.Fatalf("保护域必须在读取配置前被拒绝:%v", err)
}
}
func TestDNSCommandOnlyExposesConfirmedWriteCheck(t *testing.T) {
command := newDNSCommand()
var check *cobra.Command
for _, child := range command.Commands() {
if child.Name() == "inspect" {
t.Fatal("不得暴露会被误解为可签发授权的只读 inspect 命令")
}
if child.Name() == "check" {
check = child
}
}
if check == nil {
t.Fatal("必须提供 dns check 命令")
}
if check.Flag("domain") == nil || check.Flag("confirm-dns-write") == nil {
t.Fatal("dns check 必须同时要求明确域名和写入确认")
}
}
func TestDNSCheckReportsLiveResultWithoutPersistingConfiguration(t *testing.T) {
for _, outcome := range []string{"allowed", "denied", "unknown", "unverified"} {
t.Run(outcome, func(t *testing.T) {
cfg := prepareDNSCheckConfig(t)
before, err := os.ReadFile(configPath)
if err != nil {
t.Fatal(err)
}
calls := 0
command := newDNSCheckCommand(func(ctx context.Context, name string, credentials map[string]string) (dnsprovider.AuthorizationReport, error) {
calls++
if name != "example.com" || credentials["access_key_id"] != "test-id" {
t.Fatal("检查目标或凭据来源不正确")
}
deadline, ok := ctx.Deadline()
if !ok {
t.Fatal("权限请求必须设置超时")
}
remaining := time.Until(deadline)
if remaining < 5*time.Second || remaining > 7*time.Second {
t.Fatalf("阿里云 CLI 权限验证时限错误:%s", remaining)
}
report := dnsprovider.AuthorizationReport{Provider: "aliyun", RequestedDomain: name, Verified: outcome == "allowed"}
switch outcome {
case "denied":
return report, errors.New("Forbidden.NoPermission")
case "unknown":
return report, context.DeadlineExceeded
default:
return report, nil
}
})
var output bytes.Buffer
command.SilenceErrors, command.SilenceUsage = true, true
command.SetOut(&output)
command.SetArgs([]string{"--domain", "EXAMPLE.COM.", "--confirm-dns-write"})
err = command.Execute()
if (err == nil) != (outcome == "allowed") || calls != 1 {
t.Fatalf("检查结果错误:outcome=%s calls=%d err=%v", outcome, calls, err)
}
var report dnsprovider.AuthorizationReport
if err := json.Unmarshal(output.Bytes(), &report); err != nil || report.Verified != (outcome == "allowed") {
t.Fatalf("必须输出可机器读取的真实报告:%s", output.String())
}
if strings.Contains(output.String(), "test-secret") || strings.Contains(output.String(), "test-id") {
t.Fatal("输出不得包含凭据")
}
after, err := os.ReadFile(configPath)
if err != nil || !bytes.Equal(before, after) {
t.Fatal("权限诊断不得回写配置或初始化状态")
}
if _, err := os.Stat(cfg.Paths.Data); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("权限诊断不得初始化业务数据库:%v", err)
}
})
}
}
func TestDNSCheckDoesNotCreateMissingMasterKey(t *testing.T) {
oldPath := configPath
t.Cleanup(func() { configPath = oldPath })
cfg := isolatedCLIConfig(t.TempDir())
cfg.DNS.Provider, cfg.DNS.CredentialRef = "aliyun", "secret://dns/check"
configPath = filepath.Join(cfg.Paths.Config, "scert.json")
if err := cfg.Save(configPath); err != nil {
t.Fatal(err)
}
command := newDNSCheckCommand(nil)
command.SetArgs([]string{"--domain", "example.com", "--confirm-dns-write"})
if err := command.Execute(); err == nil {
t.Fatal("主密钥缺失必须拒绝")
}
if _, err := os.Stat(filepath.Join(cfg.Paths.Config, "master.key")); !errors.Is(err, os.ErrNotExist) {
t.Fatal("诊断不得创建新主密钥")
}
}
func prepareDNSCheckConfig(t *testing.T) config.Config {
t.Helper()
oldPath := configPath
t.Cleanup(func() { configPath = oldPath })
cfg := isolatedCLIConfig(t.TempDir())
cfg.DNS.Provider, cfg.DNS.CredentialRef = "aliyun", "secret://dns/check"
configPath = filepath.Join(cfg.Paths.Config, "scert.json")
if err := cfg.Save(configPath); err != nil {
t.Fatal(err)
}
store, err := secretstore.Open(cfg.Paths.Config)
if err != nil {
t.Fatal(err)
}
if err := store.Put(t.Context(), cfg.DNS.CredentialRef, map[string]string{"access_key_id": "test-id", "access_key_secret": "test-secret", "managed_zones": "example.com", "allowed_domains": "example.com"}); err != nil {
t.Fatal(err)
}
return cfg
}