feat(api): integrate TLS certificate renewal into async task framework

Replace native goroutines in RenewCertificate logic with Asynq task dispatching to support queue execution, retry capability, and detailed task execution logs.
This commit is contained in:
ryan
2026-06-22 10:54:15 +08:00
parent df3bcd3d19
commit 3a2878d070
10 changed files with 303 additions and 4 deletions
+155
View File
@@ -0,0 +1,155 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package tls
import (
"context"
"encoding/json"
"errors"
"testing"
"github.com/Rain-kl/Wavelet/internal/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSSLSingleRenewHandler_ValidatePayload(t *testing.T) {
tests := []struct {
name string
payload []byte
wantErr bool
errMsg string
}{
{
name: "empty payload",
payload: nil,
wantErr: true,
errMsg: "任务参数不能为空",
},
{
name: "invalid JSON",
payload: []byte(`{`),
wantErr: true,
errMsg: "无效的 JSON 格式",
},
{
name: "zero ID",
payload: []byte(`{"id":0}`),
wantErr: true,
errMsg: "证书 ID 不能为空或零",
},
{
name: "valid ID",
payload: []byte(`{"id":123}`),
wantErr: false,
},
}
handler := &SSLSingleRenewHandler{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := handler.ValidatePayload(tt.payload)
if tt.wantErr {
require.Error(t, err)
assert.Contains(t, err.Error(), tt.errMsg)
assert.Nil(t, got)
} else {
require.NoError(t, err)
var payload SSLSingleRenewPayload
err = json.Unmarshal(got, &payload)
require.NoError(t, err)
assert.Equal(t, uint(123), payload.ID)
}
})
}
}
func TestSSLSingleRenewHandler_Execute(t *testing.T) {
cleanup := setupTLSTestDB(t)
defer cleanup()
ctx := context.Background()
handler := &SSLSingleRenewHandler{}
t.Run("invalid payload", func(t *testing.T) {
_, err := handler.Execute(ctx, []byte(`{`))
require.Error(t, err)
})
t.Run("certificate not found", func(t *testing.T) {
payload, err := json.Marshal(SSLSingleRenewPayload{ID: 999})
require.NoError(t, err)
_, err = handler.Execute(ctx, payload)
require.Error(t, err)
assert.Contains(t, err.Error(), "获取证书记录失败")
})
t.Run("certificate provider is not ACME", func(t *testing.T) {
cert := &model.TLSCertificate{
Name: "custom-cert",
Provider: "custom",
PrimaryDomain: "example.com",
}
err := model.CreateTLSCertificateRecord(ctx, cert)
require.NoError(t, err)
payload, err := json.Marshal(SSLSingleRenewPayload{ID: cert.ID})
require.NoError(t, err)
_, err = handler.Execute(ctx, payload)
require.Error(t, err)
assert.Contains(t, err.Error(), "不是 ACME 托管证书")
})
t.Run("successful renewal", func(t *testing.T) {
cert := &model.TLSCertificate{
Name: "acme-cert-success",
Provider: tlsProviderACME,
PrimaryDomain: "success.example.com",
}
err := model.CreateTLSCertificateRecord(ctx, cert)
require.NoError(t, err)
// Mock obtainCertificate to succeed
restore := SetObtainCertificateFuncForTest(func(ctx context.Context, c *model.TLSCertificate) error {
c.ApplyStatus = tlsApplyStatusReady
return model.SaveTLSCertificate(ctx, c)
})
defer restore()
payload, err := json.Marshal(SSLSingleRenewPayload{ID: cert.ID})
require.NoError(t, err)
res, err := handler.Execute(ctx, payload)
require.NoError(t, err)
assert.Contains(t, res.Message, "续签成功")
updated, err := model.GetTLSCertificateByID(ctx, cert.ID)
require.NoError(t, err)
assert.Equal(t, tlsApplyStatusReady, updated.ApplyStatus)
})
t.Run("failed renewal in obtain", func(t *testing.T) {
cert := &model.TLSCertificate{
Name: "acme-cert-fail",
Provider: tlsProviderACME,
PrimaryDomain: "fail.example.com",
}
err := model.CreateTLSCertificateRecord(ctx, cert)
require.NoError(t, err)
// Mock obtainCertificate to fail
restore := SetObtainCertificateFuncForTest(func(ctx context.Context, c *model.TLSCertificate) error {
return errors.New("ACME server timeout")
})
defer restore()
payload, err := json.Marshal(SSLSingleRenewPayload{ID: cert.ID})
require.NoError(t, err)
_, err = handler.Execute(ctx, payload)
require.Error(t, err)
assert.Equal(t, "ACME server timeout", err.Error())
})
}