mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-01 14:46:36 +08:00
272 lines
8.4 KiB
Go
272 lines
8.4 KiB
Go
package service
|
|
|
|
import (
|
|
"errors"
|
|
"openflare/model"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestAcmeAndDnsIntegration(t *testing.T) {
|
|
setupServiceTestDB(t)
|
|
|
|
// 1. Create a DNS Account
|
|
dnsAccount := &model.DnsAccount{
|
|
Name: "Test Cloudflare",
|
|
Type: "cloudflare",
|
|
Authorization: `{"api_token": "dummy_token"}`,
|
|
}
|
|
if err := dnsAccount.Insert(); err != nil {
|
|
t.Fatalf("Failed to insert DNS Account: %v", err)
|
|
}
|
|
|
|
// 2. Apply for TLS Certificate (using the new ApplyTLSCertificate function)
|
|
certInput := TLSApplyInput{
|
|
Name: "Test ACME Cert",
|
|
PrimaryDomain: "example.com",
|
|
OtherDomains: "*.example.com",
|
|
DnsAccountID: dnsAccount.ID,
|
|
KeyAlgorithm: "RSA2048",
|
|
AutoRenew: true,
|
|
}
|
|
|
|
cert, err := ApplyTLSCertificate(certInput)
|
|
if err != nil {
|
|
t.Fatalf("ApplyTLSCertificate failed: %v", err)
|
|
}
|
|
|
|
if cert.ApplyStatus != "applying" {
|
|
t.Fatalf("Expected cert ApplyStatus to be applying, got %s", cert.ApplyStatus)
|
|
}
|
|
|
|
if cert.Provider != "acme" {
|
|
t.Fatalf("Expected cert Provider to be acme, got %s", cert.Provider)
|
|
}
|
|
|
|
// 3. Try to delete the DNS account (should fail since it's used by the cert)
|
|
// Actually, the delete logic is in the controller for the foreign key check.
|
|
// But let's check if the controller logic can be tested here, or we just trust the DB setup.
|
|
var count int64
|
|
model.DB.Model(&model.TLSCertificate{}).Where("dns_account_id = ?", dnsAccount.ID).Count(&count)
|
|
if count != 1 {
|
|
t.Fatalf("Expected 1 certificate associated with DNS account, got %d", count)
|
|
}
|
|
|
|
// 4. Test RenewTLSCertificate
|
|
renewedCert, err := RenewTLSCertificate(cert.ID)
|
|
if err != nil {
|
|
t.Fatalf("RenewTLSCertificate failed: %v", err)
|
|
}
|
|
if renewedCert.ApplyStatus != "applying" {
|
|
t.Fatalf("Expected renewed cert ApplyStatus to be applying, got %s", renewedCert.ApplyStatus)
|
|
}
|
|
|
|
// Wait for the async goroutine to fail (it now registers an LE account, which takes longer)
|
|
time.Sleep(5 * time.Second)
|
|
|
|
// Reload cert and verify error status
|
|
finalCert, err := model.GetTLSCertificateByID(renewedCert.ID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to reload cert: %v", err)
|
|
}
|
|
if finalCert.ApplyStatus != "error" {
|
|
t.Fatalf("Expected final cert ApplyStatus to be error, got %s", finalCert.ApplyStatus)
|
|
}
|
|
if finalCert.ApplyMessage == "" {
|
|
t.Fatalf("Expected final cert ApplyMessage to be populated, got empty")
|
|
}
|
|
|
|
// Clean up
|
|
if err := DeleteTLSCertificate(cert.ID); err != nil {
|
|
t.Fatalf("DeleteTLSCertificate failed: %v", err)
|
|
}
|
|
|
|
if err := dnsAccount.Delete(); err != nil {
|
|
t.Fatalf("Failed to delete DNS Account after cert cleanup: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestConvertTLSCertificateToAcmePreservesUploadUntilSuccess(t *testing.T) {
|
|
setupServiceTestDB(t)
|
|
|
|
originalCertPEM, originalKeyPEM := generateCertificatePair(t, []string{"manual.example.com"})
|
|
cert, err := CreateTLSCertificate(TLSCertificateInput{
|
|
Name: "manual-cert",
|
|
CertPEM: originalCertPEM,
|
|
KeyPEM: originalKeyPEM,
|
|
Remark: "manual upload",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("CreateTLSCertificate failed: %v", err)
|
|
}
|
|
originalCertPEM = cert.CertPEM
|
|
originalKeyPEM = cert.KeyPEM
|
|
|
|
newCertPEM, newKeyPEM := generateCertificatePair(t, []string{"managed.example.com"})
|
|
started := make(chan struct{}, 1)
|
|
release := make(chan struct{})
|
|
restore := SetTLSCertificateObtainFuncForTest(func(c *model.TLSCertificate) error {
|
|
started <- struct{}{}
|
|
<-release
|
|
c.CertPEM = newCertPEM
|
|
c.KeyPEM = newKeyPEM
|
|
c.NotBefore = time.Now().Add(-time.Hour)
|
|
c.NotAfter = time.Now().Add(90 * 24 * time.Hour)
|
|
c.ApplyStatus = "ready"
|
|
c.ApplyMessage = ""
|
|
return model.DB.Save(c).Error
|
|
})
|
|
t.Cleanup(restore)
|
|
|
|
converted, err := ConvertTLSCertificateToAcme(cert.ID, TLSApplyInput{
|
|
Name: "managed-cert",
|
|
Remark: "converted",
|
|
AcmeAccountID: 1,
|
|
DnsAccountID: 2,
|
|
KeyAlgorithm: "EC256",
|
|
AutoRenew: true,
|
|
PrimaryDomain: "managed.example.com",
|
|
OtherDomains: "www.managed.example.com",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("ConvertTLSCertificateToAcme failed: %v", err)
|
|
}
|
|
if converted.ID != cert.ID {
|
|
t.Fatalf("expected converted certificate to keep id %d, got %d", cert.ID, converted.ID)
|
|
}
|
|
|
|
select {
|
|
case <-started:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("expected conversion obtain task to start")
|
|
}
|
|
|
|
applying, err := model.GetTLSCertificateByID(cert.ID)
|
|
if err != nil {
|
|
t.Fatalf("reload applying certificate failed: %v", err)
|
|
}
|
|
if applying.Provider != "upload" {
|
|
t.Fatalf("expected provider to remain upload while applying, got %s", applying.Provider)
|
|
}
|
|
if applying.ApplyStatus != "applying" {
|
|
t.Fatalf("expected applying status, got %s", applying.ApplyStatus)
|
|
}
|
|
if applying.CertPEM != originalCertPEM || applying.KeyPEM != originalKeyPEM {
|
|
t.Fatal("expected original PEM payloads to be preserved while applying")
|
|
}
|
|
|
|
close(release)
|
|
|
|
finalCert := waitForCertificateState(t, cert.ID, func(c *model.TLSCertificate) bool {
|
|
return c.Provider == "acme" && c.ApplyStatus == "ready"
|
|
})
|
|
if finalCert.CertPEM != newCertPEM || finalCert.KeyPEM != newKeyPEM {
|
|
t.Fatal("expected successful conversion to replace PEM payloads")
|
|
}
|
|
if !finalCert.AutoRenew {
|
|
t.Fatal("expected converted certificate to keep auto renew enabled")
|
|
}
|
|
if finalCert.PrimaryDomain != "managed.example.com" || finalCert.OtherDomains != "www.managed.example.com" {
|
|
t.Fatalf("expected converted certificate to persist ACME domains, got %+v", finalCert)
|
|
}
|
|
}
|
|
|
|
func TestConvertTLSCertificateToAcmePreservesUploadOnFailure(t *testing.T) {
|
|
setupServiceTestDB(t)
|
|
|
|
originalCertPEM, originalKeyPEM := generateCertificatePair(t, []string{"manual.example.com"})
|
|
cert, err := CreateTLSCertificate(TLSCertificateInput{
|
|
Name: "manual-cert",
|
|
CertPEM: originalCertPEM,
|
|
KeyPEM: originalKeyPEM,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("CreateTLSCertificate failed: %v", err)
|
|
}
|
|
originalCertPEM = cert.CertPEM
|
|
originalKeyPEM = cert.KeyPEM
|
|
|
|
restore := SetTLSCertificateObtainFuncForTest(func(c *model.TLSCertificate) error {
|
|
err := errors.New("dns challenge failed")
|
|
updateCertError(c, err.Error())
|
|
return err
|
|
})
|
|
t.Cleanup(restore)
|
|
|
|
if _, err := ConvertTLSCertificateToAcme(cert.ID, TLSApplyInput{
|
|
Name: "manual-cert",
|
|
DnsAccountID: 1,
|
|
PrimaryDomain: "manual.example.com",
|
|
}); err != nil {
|
|
t.Fatalf("ConvertTLSCertificateToAcme failed: %v", err)
|
|
}
|
|
|
|
finalCert := waitForCertificateState(t, cert.ID, func(c *model.TLSCertificate) bool {
|
|
return c.ApplyStatus == "error"
|
|
})
|
|
if finalCert.Provider != "upload" {
|
|
t.Fatalf("expected failed conversion to keep upload provider, got %s", finalCert.Provider)
|
|
}
|
|
if finalCert.CertPEM != originalCertPEM || finalCert.KeyPEM != originalKeyPEM {
|
|
t.Fatal("expected failed conversion to preserve original PEM payloads")
|
|
}
|
|
if !strings.Contains(finalCert.ApplyMessage, "dns challenge failed") {
|
|
t.Fatalf("expected conversion error message, got %q", finalCert.ApplyMessage)
|
|
}
|
|
}
|
|
|
|
func TestConvertTLSCertificateToAcmeRejectsInvalidStates(t *testing.T) {
|
|
setupServiceTestDB(t)
|
|
|
|
certPEM, keyPEM := generateCertificatePair(t, []string{"manual.example.com"})
|
|
cert, err := CreateTLSCertificate(TLSCertificateInput{
|
|
Name: "manual-cert",
|
|
CertPEM: certPEM,
|
|
KeyPEM: keyPEM,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("CreateTLSCertificate failed: %v", err)
|
|
}
|
|
|
|
cert.Provider = "acme"
|
|
if err := cert.Update(); err != nil {
|
|
t.Fatalf("failed to mark certificate acme: %v", err)
|
|
}
|
|
if _, err := ConvertTLSCertificateToAcme(cert.ID, TLSApplyInput{Name: "manual-cert"}); err == nil || !strings.Contains(err.Error(), "only uploaded") {
|
|
t.Fatalf("expected non-upload conversion to fail, got %v", err)
|
|
}
|
|
|
|
cert.Provider = "upload"
|
|
cert.ApplyStatus = "applying"
|
|
if err := cert.Update(); err != nil {
|
|
t.Fatalf("failed to mark certificate applying: %v", err)
|
|
}
|
|
if _, err := ConvertTLSCertificateToAcme(cert.ID, TLSApplyInput{Name: "manual-cert"}); err == nil || !strings.Contains(err.Error(), "already applying") {
|
|
t.Fatalf("expected applying conversion to fail, got %v", err)
|
|
}
|
|
}
|
|
|
|
func waitForCertificateState(t *testing.T, id uint, matches func(*model.TLSCertificate) bool) *model.TLSCertificate {
|
|
t.Helper()
|
|
|
|
deadline := time.Now().Add(2 * time.Second)
|
|
for time.Now().Before(deadline) {
|
|
cert, err := model.GetTLSCertificateByID(id)
|
|
if err != nil {
|
|
t.Fatalf("reload certificate %d failed: %v", id, err)
|
|
}
|
|
if matches(cert) {
|
|
return cert
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
|
|
cert, err := model.GetTLSCertificateByID(id)
|
|
if err != nil {
|
|
t.Fatalf("reload certificate %d failed: %v", id, err)
|
|
}
|
|
t.Fatalf("certificate %d did not reach expected state: %+v", id, cert)
|
|
return nil
|
|
}
|