mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2026-06-19 07:36:59 +00:00
013634e8ca
* feat(cert): add Status, LastError, LastAttemptAt fields * feat(cert): sweep stale pending certs at startup * feat(cert): invoke SweepStalePending at cron startup * feat(cert): skip non-success status in auto-renew worker * feat(cert): persist draft on issuance entry, status transitions on completion * feat(cert): expose status, last_error, last_attempt_at on Cert type * feat(cert): show Pending/Failed status badges in cert list * feat(cert): add RetryCert component and wire into list actions * feat(cert): inline Retry button on issuance error in wildcard modal * chore(cert): minor cleanups after retry-on-failure review - Remove unused model.FirstOrInit helper (last caller was rewritten in the issuance handler change). - Normalize cleanup_test setupTestDB DSN to ":memory:" for per-test isolation, matching issue_test.go. - Reset errored state in DNSIssueCertificate.open() as a defensive guard against stale state on modal reopen. * refactor(cert): extract IssueCertModal wrapper shared by Renew and Retry Both RenewCert.vue and RetryCert.vue carried near-identical AModal + ObtainCertLive scaffolding (modalVisible/modalClosable refs, template ref, modal props). Lift the shared shell into IssueCertModal.vue and expose a single start() method returning Promise<CertificateResult>. The trigger components now own only the parts that actually differ: button styling, emit name, pre-issuance hook (certStore.save for Renew), and success toast. * chore(cert): fix small bugs with review - shortError now truncates by rune count instead of bytes, so non-ASCII error messages (e.g. localized ACME / DNS provider errors) cannot be split mid-rune. TestShortError gains a CJK case asserting valid UTF-8. - Cert.last_attempt_at is typed string | null on the frontend to reflect that the *time.Time pointer serializes as null for legacy / pre-attempt rows. - Drop redundant ?. on refModal / refObtainCertLive in the three click handlers. The refs are bound to components rendered alongside their trigger button, so they are guaranteed to be mounted by the time the handler fires. * fix(cert): guard certificate issuance ref before retry Co-authored-by: Jacky <me@jackyu.cn> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Jacky <me@jackyu.cn>
71 lines
2.3 KiB
Go
71 lines
2.3 KiB
Go
package cert
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/0xJacky/Nginx-UI/model"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// setupTestDB creates a per-test private in-memory SQLite DB with the Cert
|
|
// schema migrated, wires it into the model package, and returns the *gorm.DB
|
|
// for fixtures. Using ":memory:" (no shared cache) gives each gorm.Open call
|
|
// a fresh isolated database, preventing cross-test pollution.
|
|
func setupTestDB(t *testing.T) *gorm.DB {
|
|
t.Helper()
|
|
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&model.Cert{}))
|
|
model.Use(db)
|
|
t.Cleanup(func() { model.Use(nil) })
|
|
return db
|
|
}
|
|
|
|
func TestSweepStalePendingConvertsPendingToFailure(t *testing.T) {
|
|
db := setupTestDB(t)
|
|
|
|
pending := model.Cert{
|
|
Name: "example.com",
|
|
Filename: "example.com",
|
|
Status: model.CertStatusPending,
|
|
}
|
|
require.NoError(t, db.Create(&pending).Error)
|
|
|
|
require.NoError(t, SweepStalePending())
|
|
|
|
var got model.Cert
|
|
require.NoError(t, db.First(&got, pending.ID).Error)
|
|
assert.Equal(t, model.CertStatusFailure, got.Status)
|
|
assert.Equal(t, "Server restarted during issuance", got.LastError)
|
|
}
|
|
|
|
func TestSweepStalePendingLeavesSuccessAndFailureAlone(t *testing.T) {
|
|
db := setupTestDB(t)
|
|
|
|
success := model.Cert{Name: "ok.example.com", Filename: "ok.example.com", Status: model.CertStatusSuccess}
|
|
failure := model.Cert{Name: "bad.example.com", Filename: "bad.example.com", Status: model.CertStatusFailure, LastError: "DNS timeout"}
|
|
empty := model.Cert{Name: "legacy.example.com", Filename: "legacy.example.com"}
|
|
require.NoError(t, db.Create(&success).Error)
|
|
require.NoError(t, db.Create(&failure).Error)
|
|
require.NoError(t, db.Create(&empty).Error)
|
|
|
|
require.NoError(t, SweepStalePending())
|
|
|
|
var gotSuccess, gotFailure, gotEmpty model.Cert
|
|
require.NoError(t, db.First(&gotSuccess, success.ID).Error)
|
|
require.NoError(t, db.First(&gotFailure, failure.ID).Error)
|
|
require.NoError(t, db.First(&gotEmpty, empty.ID).Error)
|
|
assert.Equal(t, model.CertStatusSuccess, gotSuccess.Status)
|
|
assert.Equal(t, model.CertStatusFailure, gotFailure.Status)
|
|
assert.Equal(t, "DNS timeout", gotFailure.LastError)
|
|
assert.Equal(t, "", gotEmpty.Status)
|
|
}
|
|
|
|
func TestSweepStalePendingNoDBIsNoop(t *testing.T) {
|
|
model.Use(nil)
|
|
assert.NoError(t, SweepStalePending())
|
|
}
|