Scert/backend/internal/httpapi/certificate_api_test.go

138 lines
5.2 KiB
Go

package httpapi
import (
"bytes"
"context"
"encoding/json"
"mime/multipart"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"time"
"git.pcux.cn/xuans/Scert/backend/internal/certificate"
"git.pcux.cn/xuans/Scert/backend/internal/deployment"
"git.pcux.cn/xuans/Scert/backend/internal/domain"
)
type certificateReaderStub struct {
result certificate.IssueResult
versions []certificate.SavedResource
err error
}
func (s certificateReaderStub) Current(context.Context, string) (certificate.IssueResult, error) {
return s.result, s.err
}
func (s certificateReaderStub) ListVersions(context.Context, string) ([]certificate.SavedResource, error) {
return s.versions, s.err
}
func TestCertificateReadAPIs(t *testing.T) {
expiresAt := time.Now().UTC().Add(90 * 24 * time.Hour)
reader := certificateReaderStub{
result: certificate.IssueResult{
DomainID: "domain-1",
Domains: []string{"example.com", "www.example.com"},
Saved: certificate.SavedResource{
Domain: "example.com",
CertificateType: "san",
Version: "20260827-120000.000000000",
NotAfter: expiresAt,
},
},
versions: []certificate.SavedResource{{Version: "20260827-120000.000000000"}},
}
repo := &stubDomainRepository{items: []domain.Domain{{ID: "domain-1", Name: "example.com", CertificateType: domain.CertificateSAN}}}
server := New(Dependencies{Config: readyTestConfig(t.TempDir()), Repository: repo, CurrentCertificate: reader})
list := httptest.NewRecorder()
serveAdminHTTP(server, list, httptest.NewRequest(http.MethodGet, "/api/v1/certificates", nil))
if list.Code != http.StatusOK || !strings.Contains(list.Body.String(), "example.com") {
t.Fatalf("证书列表响应错误:%d %s", list.Code, list.Body.String())
}
detail := httptest.NewRecorder()
serveAdminHTTP(server, detail, httptest.NewRequest(http.MethodGet, "/api/v1/certificates/domain-1", nil))
if detail.Code != http.StatusOK || !strings.Contains(detail.Body.String(), "20260827-120000.000000000") {
t.Fatalf("证书详情响应错误:%d %s", detail.Code, detail.Body.String())
}
versions := httptest.NewRecorder()
serveAdminHTTP(server, versions, httptest.NewRequest(http.MethodGet, "/api/v1/domains/domain-1/certificate/versions", nil))
if versions.Code != http.StatusOK || !strings.Contains(versions.Body.String(), "20260827-120000.000000000") {
t.Fatalf("证书历史版本响应错误:%d %s", versions.Code, versions.Body.String())
}
}
func TestCertificateImportAPIAcceptsMultipartFiles(t *testing.T) {
var received certificate.ImportRequest
importer := func(_ context.Context, request certificate.ImportRequest) (certificate.IssueResult, deployment.Report, error) {
received = request
certificateData, err := os.ReadFile(request.Certificate)
if err != nil {
return certificate.IssueResult{}, deployment.Report{}, err
}
privateKeyData, err := os.ReadFile(request.PrivateKey)
if err != nil {
return certificate.IssueResult{}, deployment.Report{}, err
}
if string(certificateData) != "certificate" || string(privateKeyData) != "private-key" {
return certificate.IssueResult{}, deployment.Report{}, os.ErrInvalid
}
return certificate.IssueResult{DomainID: request.DomainID}, deployment.Report{Status: "success"}, nil
}
server := New(Dependencies{
Config: readyTestConfig(t.TempDir()),
Repository: &stubDomainRepository{},
ImportCertificate: importer,
})
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
certificatePart, err := writer.CreateFormFile("certificate", "cert.pem")
if err != nil {
t.Fatal(err)
}
_, _ = certificatePart.Write([]byte("certificate"))
privateKeyPart, err := writer.CreateFormFile("private_key", "key.pem")
if err != nil {
t.Fatal(err)
}
_, _ = privateKeyPart.Write([]byte("private-key"))
if err := writer.Close(); err != nil {
t.Fatal(err)
}
request := httptest.NewRequest(http.MethodPost, "/api/v1/domains/domain-1/certificate/import", body)
request.Header.Set("Content-Type", writer.FormDataContentType())
response := httptest.NewRecorder()
serveAdminHTTP(server, response, request)
if response.Code != http.StatusOK || received.DomainID != "domain-1" {
t.Fatalf("证书导入响应错误:%d %s", response.Code, response.Body.String())
}
if _, err := os.Stat(received.Certificate); !os.IsNotExist(err) {
t.Fatalf("证书上传临时文件未在请求结束后清理:%s", received.Certificate)
}
}
func TestCertificateImportAPIRejectsMissingFiles(t *testing.T) {
server := New(Dependencies{
Config: readyTestConfig(t.TempDir()),
Repository: &stubDomainRepository{},
ImportCertificate: func(context.Context, certificate.ImportRequest) (certificate.IssueResult, deployment.Report, error) {
return certificate.IssueResult{}, deployment.Report{}, nil
},
})
body, _ := json.Marshal(certificatePathRequest{CertificatePath: "", PrivateKeyPath: ""})
request := httptest.NewRequest(http.MethodPost, "/api/v1/domains/domain-1/certificate/import", bytes.NewReader(body))
request.Header.Set("Content-Type", "application/json")
response := httptest.NewRecorder()
serveAdminHTTP(server, response, request)
if response.Code != http.StatusBadRequest {
t.Fatalf("缺少证书路径应返回参数错误:%d %s", response.Code, response.Body.String())
}
}