initial commit
Format / gofmt (push) Failing after 27s
CI / Build (push) Successful in 51s
CI / Go Tests (push) Failing after 21s

This commit is contained in:
2026-06-06 07:54:44 -05:00
commit 1cc94f2c99
68 changed files with 14615 additions and 0 deletions
+230
View File
@@ -0,0 +1,230 @@
package certificate
import (
"crypto/x509"
"encoding/pem"
"fmt"
"os"
"strings"
"testing"
"time"
"helix-proxy/internal/config"
"helix-proxy/internal/store"
)
func TestGenerateSelfSignedTestCert(t *testing.T) {
domains := []string{"foo.example.com", "bar.example.com"}
certPEM, keyPEM, err := generateSelfSignedTestCert(domains)
if err != nil {
t.Fatalf("generate: %v", err)
}
if !strings.Contains(string(certPEM), "BEGIN CERTIFICATE") {
t.Error("no cert pem marker")
}
if !strings.Contains(string(keyPEM), "BEGIN RSA PRIVATE KEY") {
t.Error("no key pem marker")
}
// parse and check
block, _ := pem.Decode(certPEM)
if block == nil {
t.Fatal("decode failed")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
t.Fatalf("parse: %v", err)
}
if cert.NotAfter.Sub(cert.NotBefore) < 89*24*time.Hour {
t.Error("validity too short")
}
found := false
for _, d := range cert.DNSNames {
if d == "foo.example.com" {
found = true
}
}
if !found {
t.Error("dns names not in cert")
}
}
func TestIssueTestCertPath(t *testing.T) {
// use temp data dir, reset config
config.ResetForTest()
tmp := t.TempDir()
t.Setenv("DATA_DIR", tmp)
// ensure dirs so writes don't fail
_ = config.EnsureDataDirs()
// minimal store: use New which is bbolt in the tmp (via reset+env)
st, err := store.New()
if err != nil {
t.Fatalf("new store: %v", err)
}
if c, ok := st.(interface{ Close() error }); ok {
defer c.Close()
}
cm := NewManager(st)
// create a cert record (simulating what API would do)
cert := store.Certificate{
Provider: "letsencrypt",
NiceName: "test local",
DomainNames: []string{"mytest.example.com"}, // any domain; in dev mode all LE become test certs
Meta: map[string]any{},
}
created, err := st.CreateCertificate(cert)
if err != nil {
t.Fatalf("create cert: %v", err)
}
// dev mode => all letsencrypt certs are test/self-signed
t.Setenv("PROXY_MODE", "development")
email := cm.GetEmailForCert(created)
if err := cm.Issue(created, email); err != nil {
t.Fatalf("Issue test cert: %v", err)
}
// reload from store
got, ok := st.GetCertificate(created.ID)
if !ok {
t.Fatal("cert not found after issue")
}
if got.ExpiresOn == "" {
t.Error("no expires set")
}
if v, _ := got.Meta["test_cert"].(bool); !v {
t.Error("test_cert marker not set")
}
if _, ok := got.Meta["certificate"]; !ok {
t.Error("certificate pem not in meta")
}
if _, ok := got.Meta["key"]; !ok {
t.Error("key not in meta")
}
// file written?
if _, err := os.Stat(config.Resolve("certs")); err != nil {
t.Error("certs dir missing")
}
// the per-id dir
perID := fmt.Sprintf("%s/%d", config.Resolve("certs"), got.ID)
if _, err := os.Stat(perID); err != nil {
t.Logf("per-id cert dir %s not present (may be ok): %v", perID, err)
}
// exercise non-local LE path meta parsing (staging/key_type from meta) in *production* mode
// (real LE path; will fail on obtain due to no DNS/reach, but pre-LE branches + meta covered)
t.Setenv("PROXY_MODE", "")
c2 := store.Certificate{
Provider: "letsencrypt",
DomainNames: []string{"nonlocal.example.com"},
Meta: map[string]any{"staging": "true", "key_type": "rsa4096"},
}
created2, _ := st.CreateCertificate(c2)
email2 := cm.GetEmailForCert(created2)
if err2 := cm.Issue(created2, email2); err2 == nil || strings.Contains(err2.Error(), "not a letsencrypt") {
t.Logf("non-local LE Issue err (expected without public reach/DNS; meta parse covered): %v", err2)
}
}
func TestIssueRejectsNonLE(t *testing.T) {
config.ResetForTest()
tmp := t.TempDir()
t.Setenv("DATA_DIR", tmp)
_ = config.EnsureDataDirs()
st, err := store.New()
if err != nil {
t.Fatalf("store: %v", err)
}
if c, ok := st.(interface{ Close() error }); ok {
defer c.Close()
}
cm := NewManager(st)
c := store.Certificate{Provider: "other", DomainNames: []string{"ex.com"}}
created, _ := st.CreateCertificate(c)
err = cm.Issue(created, "e@e.com")
if err == nil || !strings.Contains(err.Error(), "not a letsencrypt") {
t.Errorf("expected reject non-letsencrypt: got %v", err)
}
}
func TestShouldRenew(t *testing.T) {
config.ResetForTest()
tmp := t.TempDir()
t.Setenv("DATA_DIR", tmp)
_ = config.EnsureDataDirs()
st, err := store.New()
if err != nil {
t.Fatalf("store: %v", err)
}
if c, ok := st.(interface{ Close() error }); ok {
defer c.Close()
}
cm := NewManager(st)
now := time.Now().UTC()
tests := []struct {
name string
exp string
want bool
}{
{"empty", "", true},
{"past", now.Add(-40 * 24 * time.Hour).Format("2006-01-02 15:04:05"), true},
{"near 29d", now.Add(29 * 24 * time.Hour).Format("2006-01-02 15:04:05"), true},
{"far 31d", now.Add(31 * 24 * time.Hour).Format("2006-01-02 15:04:05"), false},
{"rfc3339 near", now.Add(10 * 24 * time.Hour).Format(time.RFC3339), true},
{"bad format", "not-a-date", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := store.Certificate{Provider: "letsencrypt", ExpiresOn: tt.exp}
if got := cm.ShouldRenew(c); got != tt.want {
t.Errorf("ShouldRenew(%s) = %v want %v", tt.exp, got, tt.want)
}
})
}
}
func TestProcessRenewals(t *testing.T) {
config.ResetForTest()
tmp := t.TempDir()
t.Setenv("DATA_DIR", tmp)
_ = config.EnsureDataDirs()
st, err := store.New()
if err != nil {
t.Fatalf("store: %v", err)
}
if c, ok := st.(interface{ Close() error }); ok {
defer c.Close()
}
cm := NewManager(st)
// dev mode: *all* letsencrypt certs (any domain) use test/self-signed path in Issue/renew
t.Setenv("PROXY_MODE", "development")
// LE due soon
dueExp := time.Now().Add(5 * 24 * time.Hour).Format("2006-01-02 15:04:05")
leDue := store.Certificate{Provider: "letsencrypt", DomainNames: []string{"due.example.com"}, ExpiresOn: dueExp}
createdDue, _ := st.CreateCertificate(leDue)
// LE far future (no renew)
farExp := time.Now().Add(40 * 24 * time.Hour).Format("2006-01-02 15:04:05")
leFar := store.Certificate{Provider: "letsencrypt", DomainNames: []string{"far.example.com"}, ExpiresOn: farExp}
createdFar, _ := st.CreateCertificate(leFar)
// non-LE (ignored even if "due")
nonLE := store.Certificate{Provider: "other", DomainNames: []string{"x.com"}, ExpiresOn: time.Now().Add(-1 * time.Hour).Format("2006-01-02 15:04:05")}
st.CreateCertificate(nonLE)
cm.ProcessRenewals()
gDue, _ := st.GetCertificate(createdDue.ID)
if tDue, _ := time.Parse("2006-01-02 15:04:05", gDue.ExpiresOn); time.Until(tDue) < 80*24*time.Hour {
t.Error("due LE cert was not renewed (expires not extended)")
}
gFar, _ := st.GetCertificate(createdFar.ID)
if gFar.ExpiresOn != farExp {
t.Error("far LE should not have been touched")
}
}