Files
certctl/internal/service/config_helpers_test.go
T
shankar0123 7382e5f03b test: comprehensive test gap closure across 24 packages
Close coverage gaps identified by dual-audit (qualitative + quantitative).
New test files for config (0%→98%), router (0%→100%), handler validation,
health, audit, response helpers, webhook notifier (0%→88%), email notifier,
middleware (recovery, rate limiter), domain profile, service nil-safety,
config helpers, issuer bootstrap, and server bootstrap wiring. Expanded
existing tests for ACME (34%→42%), step-ca (42%→52%), F5, SSH, agent
(43%→63%), scheduler (88%→99%), renewal service, and issuerfactory.

All tests pass: go test -short, go vet, go test -race clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 23:09:40 -04:00

275 lines
7.0 KiB
Go

package service
import (
"encoding/json"
"testing"
)
func TestIsSensitiveConfigKey_KnownSensitiveKeys(t *testing.T) {
tests := []struct {
name string
key string
expected bool
}{
{"api_key", "api_key", true},
{"password", "password", true},
{"secret", "secret", true},
{"token", "token", true},
{"hmac", "hmac", true},
{"private_key", "private_key", true},
{"credentials", "credentials", true},
{"winrm_password", "winrm_password", true},
{"keystore_password", "keystore_password", true},
// Variations with different casing
{"API_KEY", "API_KEY", true},
{"Password", "Password", true},
{"SECRET", "SECRET", true},
{"PrivateKey", "PrivateKey", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := isSensitiveConfigKey(tt.key)
if got != tt.expected {
t.Errorf("isSensitiveConfigKey(%q) = %v, want %v", tt.key, got, tt.expected)
}
})
}
}
func TestIsSensitiveConfigKey_NonSensitiveKeys(t *testing.T) {
tests := []struct {
name string
key string
}{
{"url", "url"},
{"host", "host"},
{"port", "port"},
{"region", "region"},
{"ca_pool", "ca_pool"},
{"namespace", "namespace"},
{"cert_path", "cert_path"},
{"base_url", "base_url"},
{"org_id", "org_id"},
{"product_type", "product_type"},
{"email", "email"},
{"enabled", "enabled"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := isSensitiveConfigKey(tt.key)
if got != false {
t.Errorf("isSensitiveConfigKey(%q) = %v, want false", tt.key, got)
}
})
}
}
func TestIsSensitiveConfigKey_CaseInsensitivity(t *testing.T) {
tests := []struct {
name string
key string
}{
{"api_key uppercase", "API_KEY"},
{"api_key mixed", "Api_Key"},
{"password uppercase", "PASSWORD"},
{"password mixed", "PassWord"},
{"secret uppercase", "SECRET"},
{"token mixed", "ToKeN"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := isSensitiveConfigKey(tt.key)
if got != true {
t.Errorf("isSensitiveConfigKey(%q) = %v, want true (case-insensitive)", tt.key, got)
}
})
}
}
func TestRedactConfigJSON_HidesSensitiveFields(t *testing.T) {
input := json.RawMessage(`{
"api_key": "secret-key-123",
"password": "my-password",
"token": "bearer-token",
"host": "example.com"
}`)
result := redactConfigJSON(input)
var m map[string]interface{}
if err := json.Unmarshal(result, &m); err != nil {
t.Fatalf("failed to unmarshal result: %v", err)
}
// Check sensitive fields are redacted
if m["api_key"] != "********" {
t.Errorf("api_key = %v, want ********", m["api_key"])
}
if m["password"] != "********" {
t.Errorf("password = %v, want ********", m["password"])
}
if m["token"] != "********" {
t.Errorf("token = %v, want ********", m["token"])
}
// Check non-sensitive field is preserved
if m["host"] != "example.com" {
t.Errorf("host = %v, want example.com", m["host"])
}
}
func TestRedactConfigJSON_PassesThroughNonSensitive(t *testing.T) {
input := json.RawMessage(`{
"url": "https://api.example.com",
"port": 443,
"region": "us-east-1"
}`)
result := redactConfigJSON(input)
var m map[string]interface{}
if err := json.Unmarshal(result, &m); err != nil {
t.Fatalf("failed to unmarshal result: %v", err)
}
// All fields should be preserved as-is
if m["url"] != "https://api.example.com" {
t.Errorf("url = %v, want https://api.example.com", m["url"])
}
if m["port"] != float64(443) {
t.Errorf("port = %v, want 443", m["port"])
}
if m["region"] != "us-east-1" {
t.Errorf("region = %v, want us-east-1", m["region"])
}
}
func TestRedactConfigJSON_EmptyConfig(t *testing.T) {
input := json.RawMessage(`{}`)
result := redactConfigJSON(input)
var m map[string]interface{}
if err := json.Unmarshal(result, &m); err != nil {
t.Fatalf("failed to unmarshal result: %v", err)
}
if len(m) != 0 {
t.Errorf("empty config should remain empty, got %v", m)
}
}
func TestRedactConfigJSON_EmptyStringPassword(t *testing.T) {
input := json.RawMessage(`{
"password": "",
"token": "my-token",
"host": "example.com"
}`)
result := redactConfigJSON(input)
var m map[string]interface{}
if err := json.Unmarshal(result, &m); err != nil {
t.Fatalf("failed to unmarshal result: %v", err)
}
// Empty password should be left as-is (empty string)
if m["password"] != "" {
t.Errorf("empty password = %v, want empty string", m["password"])
}
// Non-empty sensitive field should be redacted
if m["token"] != "********" {
t.Errorf("token = %v, want ********", m["token"])
}
// Non-sensitive field preserved
if m["host"] != "example.com" {
t.Errorf("host = %v, want example.com", m["host"])
}
}
func TestRedactConfigJSON_MalformedJSON(t *testing.T) {
// Malformed JSON should be returned as-is
input := json.RawMessage(`not valid json`)
result := redactConfigJSON(input)
// Should return the input unchanged when it can't be parsed as object
if string(result) != string(input) {
t.Errorf("malformed JSON not returned as-is: got %s, want %s", string(result), string(input))
}
}
func TestRedactConfigJSON_JSONArray(t *testing.T) {
// Array of objects should be returned as-is (not parsed as object)
input := json.RawMessage(`[{"key": "value"}]`)
result := redactConfigJSON(input)
// Should return the input unchanged since it's an array, not an object
if string(result) != string(input) {
t.Errorf("JSON array not returned as-is: got %s, want %s", string(result), string(input))
}
}
func TestRedactConfigJSON_NestedSensitiveFields(t *testing.T) {
input := json.RawMessage(`{
"outer_password": "should-be-redacted",
"config": {"inner_key": "value"}
}`)
result := redactConfigJSON(input)
var m map[string]interface{}
if err := json.Unmarshal(result, &m); err != nil {
t.Fatalf("failed to unmarshal result: %v", err)
}
// Outer level sensitive field is redacted
if m["outer_password"] != "********" {
t.Errorf("outer_password = %v, want ********", m["outer_password"])
}
// Note: nested fields are NOT redacted (function only processes top-level)
// This is the current behavior based on the implementation
if nested, ok := m["config"].(map[string]interface{}); ok {
if nested["inner_key"] != "value" {
t.Errorf("nested inner_key = %v, want value (nested not processed)", nested["inner_key"])
}
}
}
func TestRedactConfigJSON_NonStringValues(t *testing.T) {
input := json.RawMessage(`{
"password": 123,
"token": null,
"secret": true,
"api_key": ["list", "of", "values"]
}`)
result := redactConfigJSON(input)
var m map[string]interface{}
if err := json.Unmarshal(result, &m); err != nil {
t.Fatalf("failed to unmarshal result: %v", err)
}
// Non-string values should be left as-is (not redacted)
if m["password"] != float64(123) {
t.Errorf("password (number) = %v, want 123 (unchanged)", m["password"])
}
if m["token"] != nil {
t.Errorf("token (null) = %v, want nil (unchanged)", m["token"])
}
if m["secret"] != true {
t.Errorf("secret (bool) = %v, want true (unchanged)", m["secret"])
}
if _, ok := m["api_key"].([]interface{}); !ok {
t.Errorf("api_key (array) should remain as array, got %T", m["api_key"])
}
}