Scert/backend/internal/httpapi/http_challenge_test.go
xuans 79c5fe112c 支持主域名同时申请多级泛域名
完善多级子域名的泛域名生成、申请类型展示、编辑回显与挑战类型校验,并补充前后端测试。
2026-10-16 02:19:20 +08:00

145 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 httpapi
import (
"context"
"net/http"
"net/http/httptest"
"strings"
"testing"
"git.pcux.cn/xuans/Scert/backend/internal/certificate"
"git.pcux.cn/xuans/Scert/backend/internal/deployment"
"git.pcux.cn/xuans/Scert/backend/internal/domain"
"git.pcux.cn/xuans/Scert/backend/internal/httpchallenge"
"git.pcux.cn/xuans/Scert/backend/internal/repository"
)
func TestHTTPChallengeProductionHandler(t *testing.T) {
store := httpchallenge.NewStore()
lease, err := store.NewLease(t.Context(), []string{"issued.example.com"})
if err != nil {
t.Fatal(err)
}
defer lease.Close()
const token = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG"
if err := lease.Present(t.Context(), "issued.example.com", token, token+"."+token); err != nil {
t.Fatal(err)
}
server := New(Dependencies{Config: readyTestConfig(t.TempDir()), HTTPChallenges: store})
for _, tc := range []struct {
host, path string
status int
}{
{"issued.example.com", token, 200},
{"other.example.com", token, 404},
{"issued.example.com", "unknown", 404},
{"issued.example.com", token + "/", 404},
} {
w := httptest.NewRecorder()
server.Handler().ServeHTTP(w, httptest.NewRequest("GET", "http://"+tc.host+httpchallenge.PathPrefix+tc.path, nil))
if w.Code != tc.status || w.Header().Get("Location") != "" || w.Header().Get("Cache-Control") != "no-store" {
t.Fatalf("正式挑战路由错误:%d %s", w.Code, w.Body.String())
}
}
server.httpChallenges = nil
w := httptest.NewRecorder()
server.Handler().ServeHTTP(w, httptest.NewRequest("GET", "http://issued.example.com"+httpchallenge.PathPrefix+token, nil))
if w.Code != 404 {
t.Fatal("缺失挑战存储时未拒绝")
}
}
func TestHTTPDomainCreateAndUpdateBypassDNSOnly(t *testing.T) {
cfg := readyTestConfig(t.TempDir())
cfg.DNS.Provider = "aliyun"
repo := &stubDomainRepository{loaded: domain.Domain{ID: "domain-1", Name: "example.com", ChallengeType: domain.ChallengeHTTP01}}
server := New(Dependencies{
Config: cfg, Repository: repo,
ProjectDomains: projectDomainRepositoryStub{DomainRepository: repo},
ProjectResources: projectResourceReaderStub{domains: repo},
})
for _, tc := range []struct {
method, path, body string
status int
}{
{"POST", "/api/v1/domains", `{"name":"example.com","certificate_type":"single","challenge_type":"http-01"}`, 201},
{"POST", "/api/v1/domains", `{"name":"example.com","certificate_type":"san","sans":["*.example.com"],"challenge_type":"http-01"}`, 400},
{"POST", "/api/v1/domains", `{"name":"example.com","certificate_type":"single","challenge_type":"unknown"}`, 400},
{"PUT", "/api/v1/domains/domain-1", `{"name":"example.com","certificate_type":"single"}`, 200},
} {
w := httptest.NewRecorder()
serveAdminHTTP(server, w, httptest.NewRequest(tc.method, tc.path, strings.NewReader(tc.body)))
if w.Code != tc.status {
t.Fatalf("域名接口状态 %d,预期 %d:%s", w.Code, tc.status, w.Body.String())
}
}
if repo.created.ChallengeType != domain.ChallengeHTTP01 || repo.updated.ChallengeType != domain.ChallengeHTTP01 {
t.Fatal("HTTP 验证方式未保存或旧客户端覆盖了现有方式")
}
}
func TestDomainCreateCanIncludeWildcardForMultiLevelName(t *testing.T) {
item, err := newDomainFromRequest(domainRequest{
Name: "x.x.x.com",
CertificateType: string(domain.CertificateSingle),
IncludeWildcard: true,
ChallengeType: stringPointer(domain.ChallengeDNS01),
})
if err != nil {
t.Fatalf("主域名同时申请泛域名失败:%v", err)
}
if item.CertificateType != domain.CertificateSAN {
t.Fatalf("联合申请应使用 SAN 证书链路:%s", item.CertificateType)
}
if len(item.SANs) != 2 || item.SANs[0] != "x.x.x.com" || item.SANs[1] != "*.x.x.x.com" {
t.Fatalf("联合申请的多级域名标识不正确:%v", item.SANs)
}
_, err = newDomainFromRequest(domainRequest{
Name: "x.x.x.com",
CertificateType: string(domain.CertificateSingle),
IncludeWildcard: true,
ChallengeType: stringPointer(domain.ChallengeHTTP01),
})
if err == nil {
t.Fatal("主域名同时申请泛域名时不应允许 HTTP-01")
}
}
func stringPointer(value string) *string {
return &value
}
func TestHTTPTaskAuthorizationRetainsProjectPermissions(t *testing.T) {
for _, denied := range []domain.ProjectPermission{domain.PermissionDNSCredentialUse, domain.PermissionCertificateManage, domain.PermissionDeploymentExecute} {
t.Run(string(denied), func(t *testing.T) {
repo := &stubDomainRepository{loaded: domain.Domain{ID: "domain-1", Name: "example.com", ChallengeType: domain.ChallengeHTTP01, ResourceScope: domain.ResourceScope{ProjectID: "project-a"}}}
calls := 0
server := New(Dependencies{
Config: readyTestConfig(t.TempDir()), Repository: repo,
ProjectResources: projectResourceReaderStub{domains: repo},
ProjectMembers: permissionProjectMemberRepository{allow: func(permission domain.ProjectPermission) error {
if permission == denied {
return repository.ErrProjectAccessDenied
}
return nil
}},
IssueCertificate: func(context.Context, string) (certificate.IssueResult, deployment.Report, error) {
calls++
return certificate.IssueResult{}, deployment.Report{}, nil
},
})
w := httptest.NewRecorder()
serveAdminHTTP(server, w, httptest.NewRequest(http.MethodPost, "/api/v1/domains/domain-1/issue", nil))
want := denied == domain.PermissionDNSCredentialUse
if (calls == 1) != want {
t.Fatalf("HTTP 签发项目授权错误:%d %s", w.Code, w.Body.String())
}
err := server.authorizeCertificateTaskExecution(t.Context(), repository.Task{DomainID: "domain-1", HistoryScope: repository.HistoryScope{ProjectID: "project-a", ScopeType: repository.ScopeProject}, OperatorID: "actor", TaskType: "certificate_issue"}, repository.TaskAttempt{})
if (err == nil) != want {
t.Fatal("执行前授权与验证方式不匹配:", err)
}
})
}
}