176 lines
7.4 KiB
Go
176 lines
7.4 KiB
Go
package httpapi
|
||
|
||
import (
|
||
"encoding/json"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"path/filepath"
|
||
"strings"
|
||
"testing"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
|
||
"git.pcux.cn/xuans/Scert/backend/internal/config"
|
||
"git.pcux.cn/xuans/Scert/backend/internal/repository"
|
||
"git.pcux.cn/xuans/Scert/backend/internal/setup"
|
||
)
|
||
|
||
func TestAdminDNSProvidersRequiresAuthentication(t *testing.T) {
|
||
cfg := config.Default(t.TempDir())
|
||
cfg.SetupReady = true
|
||
server := New(Dependencies{Config: cfg, SetupDNS: &fakeSetupDNSVerifier{}})
|
||
response := httptest.NewRecorder()
|
||
request := httptest.NewRequest(http.MethodGet, "/api/v1/dns/providers", nil)
|
||
server.Handler().ServeHTTP(response, request)
|
||
if response.Code != http.StatusUnauthorized {
|
||
t.Fatalf("未登录访问 DNS 服务商接口应返回 401,实际为 %d:%s", response.Code, response.Body.String())
|
||
}
|
||
}
|
||
|
||
func TestAdminDNSProvidersReturnsCatalog(t *testing.T) {
|
||
cfg := config.Default(t.TempDir())
|
||
cfg.SetupReady = true
|
||
server := New(Dependencies{Config: cfg, SetupDNS: &fakeSetupDNSVerifier{}})
|
||
response := httptest.NewRecorder()
|
||
request := httptest.NewRequest(http.MethodGet, "/api/v1/dns/providers", nil)
|
||
serveAdminHTTP(server, response, request)
|
||
if response.Code != http.StatusOK {
|
||
t.Fatalf("读取 DNS 服务商目录失败:%d %s", response.Code, response.Body.String())
|
||
}
|
||
if !strings.Contains(response.Body.String(), "Cloudflare") || !strings.Contains(response.Body.String(), "credential_fields") {
|
||
t.Fatalf("DNS 服务商目录内容不完整:%s", response.Body.String())
|
||
}
|
||
}
|
||
|
||
func TestAdminDNSAccountsExposeProjectScope(t *testing.T) {
|
||
cfg := config.Default(t.TempDir())
|
||
cfg.SetupReady = true
|
||
cfg.DNS.ProjectID = "project-a"
|
||
cfg.DNS.CredentialRef = "secret://dns/cloudflare/project-a"
|
||
secrets := &fakeSecretReaderStore{data: map[string]map[string]string{
|
||
cfg.DNS.CredentialRef: {"api_token": "test-token"},
|
||
}}
|
||
server := New(Dependencies{Config: cfg, Secrets: secrets})
|
||
response := httptest.NewRecorder()
|
||
request := httptest.NewRequest(http.MethodGet, "/api/v1/dns/accounts", nil)
|
||
serveAdminHTTP(server, response, request)
|
||
if response.Code != http.StatusOK {
|
||
t.Fatalf("读取 DNS 账户失败:%d %s", response.Code, response.Body.String())
|
||
}
|
||
if !strings.Contains(response.Body.String(), `"project_id":"project-a"`) {
|
||
t.Fatalf("DNS 账户响应未包含项目归属:%s", response.Body.String())
|
||
}
|
||
}
|
||
|
||
func TestDNSCredentialUseRejectsUnauthorizedProject(t *testing.T) {
|
||
server := New(Dependencies{
|
||
ProjectDomains: projectDomainRepositoryStub{authorizeErr: repository.ErrProjectAccessDenied},
|
||
})
|
||
gin.SetMode(gin.TestMode)
|
||
recorder := httptest.NewRecorder()
|
||
context, _ := gin.CreateTestContext(recorder)
|
||
context.Request = httptest.NewRequest(http.MethodGet, "/", nil)
|
||
context.Set(authenticatedActorKey, "user-a")
|
||
if server.authorizeDNSCredentialUse(context, "project-a") {
|
||
t.Fatal("无 DNS 凭据使用权限的项目不应通过授权")
|
||
}
|
||
if recorder.Code != http.StatusForbidden {
|
||
t.Fatalf("无 DNS 凭据使用权限应返回 403,实际为 %d", recorder.Code)
|
||
}
|
||
}
|
||
|
||
func TestSettingsDNSProjectAssignmentRequiresDNSManagePermission(t *testing.T) {
|
||
dir := t.TempDir()
|
||
cfg := config.Default(dir)
|
||
cfg.SetupReady = true
|
||
cfg.DNS.VerificationState = "verified"
|
||
cfg.Web.Hostname = "scert.example.test"
|
||
cfg.Admin.Username = "admin"
|
||
cfg.Admin.Email = "admin@example.test"
|
||
cfg.Admin.PasswordHash = mustTestPasswordHash(t, "Password123")
|
||
configPath := filepath.Join(dir, "config", "scert.json")
|
||
if err := cfg.Save(configPath); err != nil {
|
||
t.Fatalf("保存测试配置失败:%v", err)
|
||
}
|
||
server := New(Dependencies{
|
||
Config: cfg, ConfigPath: configPath,
|
||
ProjectDomains: projectDomainRepositoryStub{authorizeErr: repository.ErrProjectAccessDenied},
|
||
})
|
||
cookie := loginForSettingsTest(t, server)
|
||
request := httptest.NewRequest(http.MethodPut, "/api/v1/settings", strings.NewReader(`{"dns":{"project_id":"project-a"}}`))
|
||
request.Header.Set("Content-Type", "application/json")
|
||
request.AddCookie(cookie)
|
||
response := httptest.NewRecorder()
|
||
server.Handler().ServeHTTP(response, request)
|
||
if response.Code != http.StatusForbidden {
|
||
t.Fatalf("无 DNS 管理权限修改项目归属应返回 403,实际为 %d:%s", response.Code, response.Body.String())
|
||
}
|
||
if server.config.DNS.ProjectID != "default" {
|
||
t.Fatalf("拒绝修改后 DNS 项目归属不应变化:%q", server.config.DNS.ProjectID)
|
||
}
|
||
}
|
||
|
||
func TestAdminDNSTestReturnsVerificationResult(t *testing.T) {
|
||
cfg := config.Default(t.TempDir())
|
||
cfg.SetupReady = true
|
||
verifier := &fakeSetupDNSVerifier{}
|
||
server := New(Dependencies{Config: cfg, SetupDNS: verifier})
|
||
response := httptest.NewRecorder()
|
||
request := httptest.NewRequest(http.MethodPost, "/api/v1/dns/test", strings.NewReader(`{
|
||
"provider":"cloudflare",
|
||
"test_domain":"example.com",
|
||
"credentials":{"api_token":"test-token"}
|
||
}`))
|
||
request.Header.Set("Content-Type", "application/json")
|
||
serveAdminHTTP(server, response, request)
|
||
if response.Code != http.StatusOK {
|
||
t.Fatalf("DNS 在线测试失败:%d %s", response.Code, response.Body.String())
|
||
}
|
||
var payload struct {
|
||
Verified bool `json:"verified"`
|
||
CredentialRef string `json:"credential_ref"`
|
||
VerificationToken string `json:"verification_token"`
|
||
TestReport setup.DNSVerificationReport `json:"test_report"`
|
||
VerificationTimeoutSecs int `json:"verification_timeout_secs"`
|
||
}
|
||
if err := json.Unmarshal(response.Body.Bytes(), &payload); err != nil {
|
||
t.Fatalf("解析 DNS 在线测试响应失败:%v", err)
|
||
}
|
||
if !payload.Verified || payload.CredentialRef == "" || payload.VerificationToken == "" || payload.TestReport.RecordName == "" || payload.VerificationTimeoutSecs <= 0 {
|
||
t.Fatalf("DNS 在线测试响应内容不正确:%+v", payload)
|
||
}
|
||
}
|
||
|
||
func TestAdminAliyunDNSTestReportsFastVerificationTimeout(t *testing.T) {
|
||
cfg := config.Default(t.TempDir())
|
||
cfg.SetupReady = true
|
||
server := New(Dependencies{Config: cfg, SetupDNS: &fakeSetupDNSVerifier{}})
|
||
response := httptest.NewRecorder()
|
||
request := httptest.NewRequest(http.MethodPost, "/api/v1/dns/test", strings.NewReader(`{
|
||
"provider":"aliyun",
|
||
"test_domain":"example.com",
|
||
"credentials":{"access_key_id":"test-id","access_key_secret":"test-secret","managed_zones":"example.com","allowed_domains":"example.com"}
|
||
}`))
|
||
request.Header.Set("Content-Type", "application/json")
|
||
serveAdminHTTP(server, response, request)
|
||
if response.Code != http.StatusOK || !strings.Contains(response.Body.String(), `"verification_timeout_secs":6`) {
|
||
t.Fatalf("管理端阿里云 DNS 在线测试未返回 6 秒等待上限:%d %s", response.Code, response.Body.String())
|
||
}
|
||
}
|
||
|
||
func TestAdminDNSTestRejectsUnavailableService(t *testing.T) {
|
||
cfg := config.Default(t.TempDir())
|
||
cfg.SetupReady = true
|
||
server := New(Dependencies{Config: cfg})
|
||
response := httptest.NewRecorder()
|
||
request := httptest.NewRequest(http.MethodPost, "/api/v1/dns/test", strings.NewReader(`{"provider":"cloudflare","test_domain":"example.com","credentials":{"api_token":"test-token"}}`))
|
||
request.Header.Set("Content-Type", "application/json")
|
||
serveAdminHTTP(server, response, request)
|
||
if response.Code != http.StatusServiceUnavailable {
|
||
t.Fatalf("DNS 服务未初始化时应返回 503,实际为 %d:%s", response.Code, response.Body.String())
|
||
}
|
||
if !strings.Contains(response.Body.String(), `"verification_timeout_secs":60`) {
|
||
t.Fatalf("DNS 服务未初始化响应应包含当前最大等待时间:%s", response.Body.String())
|
||
}
|
||
}
|