Files
at-container-registry/pkg/appview/config_test.go
T

971 lines
21 KiB
Go

package appview
import (
"os"
"testing"
"github.com/distribution/distribution/v3/configuration"
)
func TestGetEnvOrDefault(t *testing.T) {
tests := []struct {
name string
key string
defaultValue string
envValue string
setEnv bool
want string
}{
{
name: "env var not set",
key: "TEST_VAR_NOT_SET",
defaultValue: "default",
setEnv: false,
want: "default",
},
{
name: "env var set to value",
key: "TEST_VAR_SET",
defaultValue: "default",
envValue: "custom",
setEnv: true,
want: "custom",
},
{
name: "env var set to empty string",
key: "TEST_VAR_EMPTY",
defaultValue: "default",
envValue: "",
setEnv: true,
want: "default",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.setEnv {
t.Setenv(tt.key, tt.envValue)
}
got := GetEnvOrDefault(tt.key, tt.defaultValue)
if got != tt.want {
t.Errorf("GetEnvOrDefault() = %v, want %v", got, tt.want)
}
})
}
}
func TestGetBaseURL(t *testing.T) {
tests := []struct {
name string
httpAddr string
envBaseURL string
setEnv bool
want string
}{
{
name: "env var set",
httpAddr: ":5000",
envBaseURL: "https://registry.example.com",
setEnv: true,
want: "https://registry.example.com",
},
{
name: "port only - auto detect localhost",
httpAddr: ":5000",
setEnv: false,
want: "http://127.0.0.1:5000",
},
{
name: "full address",
httpAddr: "0.0.0.0:5000",
setEnv: false,
want: "http://0.0.0.0:5000",
},
{
name: "custom port",
httpAddr: ":8080",
setEnv: false,
want: "http://127.0.0.1:8080",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.setEnv {
t.Setenv("ATCR_BASE_URL", tt.envBaseURL)
} else {
os.Unsetenv("ATCR_BASE_URL")
}
got := GetBaseURL(tt.httpAddr)
if got != tt.want {
t.Errorf("GetBaseURL() = %v, want %v", got, tt.want)
}
})
}
}
func Test_getServiceName(t *testing.T) {
tests := []struct {
name string
baseURL string
envService string
setEnv bool
want string
}{
{
name: "env var set",
baseURL: "http://127.0.0.1:5000",
envService: "custom.registry.io",
setEnv: true,
want: "custom.registry.io",
},
{
name: "localhost - use default",
baseURL: "http://localhost:5000",
setEnv: false,
want: "atcr.io",
},
{
name: "127.0.0.1 - use default",
baseURL: "http://127.0.0.1:5000",
setEnv: false,
want: "atcr.io",
},
{
name: "custom domain",
baseURL: "https://registry.example.com",
setEnv: false,
want: "registry.example.com",
},
{
name: "domain with port",
baseURL: "https://registry.example.com:443",
setEnv: false,
want: "registry.example.com",
},
{
name: "invalid URL - use default",
baseURL: "://invalid",
setEnv: false,
want: "atcr.io",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.setEnv {
t.Setenv("ATCR_SERVICE_NAME", tt.envService)
} else {
os.Unsetenv("ATCR_SERVICE_NAME")
}
got := getServiceName(tt.baseURL)
if got != tt.want {
t.Errorf("getServiceName() = %v, want %v", got, tt.want)
}
})
}
}
func TestBuildLogConfig(t *testing.T) {
tests := []struct {
name string
envLevel string
envFormatter string
setLevel bool
setFormatter bool
wantLevel configuration.Loglevel
wantFormatter string
}{
{
name: "defaults",
setLevel: false,
setFormatter: false,
wantLevel: "info",
wantFormatter: "text",
},
{
name: "custom level",
envLevel: "debug",
setLevel: true,
setFormatter: false,
wantLevel: "debug",
wantFormatter: "text",
},
{
name: "custom formatter",
envLevel: "info",
envFormatter: "json",
setLevel: true,
setFormatter: true,
wantLevel: "info",
wantFormatter: "json",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.setLevel {
t.Setenv("ATCR_LOG_LEVEL", tt.envLevel)
} else {
os.Unsetenv("ATCR_LOG_LEVEL")
}
if tt.setFormatter {
t.Setenv("ATCR_LOG_FORMATTER", tt.envFormatter)
} else {
os.Unsetenv("ATCR_LOG_FORMATTER")
}
got := buildLogConfig()
if got.Level != tt.wantLevel {
t.Errorf("buildLogConfig().Level = %v, want %v", got.Level, tt.wantLevel)
}
if got.Formatter != tt.wantFormatter {
t.Errorf("buildLogConfig().Formatter = %v, want %v", got.Formatter, tt.wantFormatter)
}
if got.Fields["service"] != "atcr-appview" {
t.Errorf("buildLogConfig().Fields[service] = %v, want atcr-appview", got.Fields["service"])
}
})
}
}
func TestBuildHTTPConfig(t *testing.T) {
tests := []struct {
name string
envAddr string
envDebugAddr string
envSecret string
setAddr bool
setDebugAddr bool
setSecret bool
wantAddr string
wantDebug string
wantSecret string // empty means "should be generated"
}{
{
name: "defaults",
setAddr: false,
wantAddr: ":5000",
wantDebug: ":5001",
wantSecret: "", // generated
},
{
name: "custom addr",
envAddr: ":8080",
setAddr: true,
setDebugAddr: false,
wantAddr: ":8080",
wantDebug: ":5001",
wantSecret: "",
},
{
name: "custom debug addr",
envDebugAddr: ":9001",
setAddr: false,
setDebugAddr: true,
wantAddr: ":5000",
wantDebug: ":9001",
wantSecret: "",
},
{
name: "custom secret",
envSecret: "my-custom-secret",
setAddr: false,
setSecret: true,
wantAddr: ":5000",
wantDebug: ":5001",
wantSecret: "my-custom-secret",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.setAddr {
t.Setenv("ATCR_HTTP_ADDR", tt.envAddr)
} else {
os.Unsetenv("ATCR_HTTP_ADDR")
}
if tt.setDebugAddr {
t.Setenv("ATCR_DEBUG_ADDR", tt.envDebugAddr)
} else {
os.Unsetenv("ATCR_DEBUG_ADDR")
}
if tt.setSecret {
t.Setenv("REGISTRY_HTTP_SECRET", tt.envSecret)
} else {
os.Unsetenv("REGISTRY_HTTP_SECRET")
}
got, err := buildHTTPConfig()
if err != nil {
t.Fatalf("buildHTTPConfig() error = %v", err)
}
if got.Addr != tt.wantAddr {
t.Errorf("buildHTTPConfig().Addr = %v, want %v", got.Addr, tt.wantAddr)
}
if got.Debug.Addr != tt.wantDebug {
t.Errorf("buildHTTPConfig().Debug.Addr = %v, want %v", got.Debug.Addr, tt.wantDebug)
}
if tt.wantSecret == "" {
// Should be generated (64 hex chars = 32 bytes)
if len(got.Secret) != 64 {
t.Errorf("buildHTTPConfig().Secret length = %v, want 64", len(got.Secret))
}
} else {
if got.Secret != tt.wantSecret {
t.Errorf("buildHTTPConfig().Secret = %v, want %v", got.Secret, tt.wantSecret)
}
}
// Verify headers
if got.Headers["X-Content-Type-Options"][0] != "nosniff" {
t.Error("buildHTTPConfig() missing X-Content-Type-Options header")
}
})
}
}
func TestBuildStorageConfig(t *testing.T) {
got := buildStorageConfig()
// Verify inmemory driver exists
if _, ok := got["inmemory"]; !ok {
t.Error("buildStorageConfig() missing inmemory driver")
}
// Verify maintenance config
maintenance, ok := got["maintenance"]
if !ok {
t.Fatal("buildStorageConfig() missing maintenance config")
}
uploadPurging, ok := maintenance["uploadpurging"]
if !ok {
t.Fatal("buildStorageConfig() missing uploadpurging config")
}
// Verify uploadpurging is map[any]any (for distribution validation)
purging, ok := uploadPurging.(map[any]any)
if !ok {
t.Fatalf("uploadpurging is %T, want map[any]any", uploadPurging)
}
if purging["enabled"] != false {
t.Error("uploadpurging enabled should be false")
}
}
func TestBuildMiddlewareConfig(t *testing.T) {
tests := []struct {
name string
defaultHoldDID string
baseURL string
testMode bool
setTestMode bool
wantTestMode bool
}{
{
name: "normal mode",
defaultHoldDID: "did:web:hold01.atcr.io",
baseURL: "https://atcr.io",
setTestMode: false,
wantTestMode: false,
},
{
name: "test mode enabled",
defaultHoldDID: "did:web:hold01.atcr.io",
baseURL: "https://atcr.io",
testMode: true,
setTestMode: true,
wantTestMode: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.setTestMode {
t.Setenv("TEST_MODE", "true")
} else {
os.Unsetenv("TEST_MODE")
}
got := buildMiddlewareConfig(tt.defaultHoldDID, tt.baseURL)
registryMW, ok := got["registry"]
if !ok {
t.Fatal("buildMiddlewareConfig() missing registry middleware")
}
if len(registryMW) != 1 {
t.Fatalf("buildMiddlewareConfig() registry middleware count = %v, want 1", len(registryMW))
}
mw := registryMW[0]
if mw.Name != "atproto-resolver" {
t.Errorf("middleware name = %v, want atproto-resolver", mw.Name)
}
if mw.Options["default_hold_did"] != tt.defaultHoldDID {
t.Errorf("default_hold_did = %v, want %v", mw.Options["default_hold_did"], tt.defaultHoldDID)
}
if mw.Options["base_url"] != tt.baseURL {
t.Errorf("base_url = %v, want %v", mw.Options["base_url"], tt.baseURL)
}
if mw.Options["test_mode"] != tt.wantTestMode {
t.Errorf("test_mode = %v, want %v", mw.Options["test_mode"], tt.wantTestMode)
}
})
}
}
func TestBuildAuthConfig(t *testing.T) {
tests := []struct {
name string
baseURL string
envKeyPath string
envCertPath string
envExpiration string
setKeyPath bool
setCertPath bool
setExpiration bool
wantKeyPath string
wantCertPath string
wantExpiration int
wantRealm string
wantService string
wantError bool
}{
{
name: "defaults",
baseURL: "http://127.0.0.1:5000",
setKeyPath: false,
setCertPath: false,
setExpiration: false,
wantKeyPath: "/var/lib/atcr/auth/private-key.pem",
wantCertPath: "/var/lib/atcr/auth/private-key.crt",
wantExpiration: 300,
wantRealm: "http://127.0.0.1:5000/auth/token",
wantService: "atcr.io",
wantError: false,
},
{
name: "custom values",
baseURL: "https://registry.example.com",
envKeyPath: "/custom/key.pem",
envCertPath: "/custom/cert.crt",
envExpiration: "600",
setKeyPath: true,
setCertPath: true,
setExpiration: true,
wantKeyPath: "/custom/key.pem",
wantCertPath: "/custom/cert.crt",
wantExpiration: 600,
wantRealm: "https://registry.example.com/auth/token",
wantService: "registry.example.com",
wantError: false,
},
{
name: "invalid expiration",
baseURL: "http://127.0.0.1:5000",
envExpiration: "not-a-number",
setExpiration: true,
wantError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.setKeyPath {
t.Setenv("ATCR_AUTH_KEY_PATH", tt.envKeyPath)
} else {
os.Unsetenv("ATCR_AUTH_KEY_PATH")
}
if tt.setCertPath {
t.Setenv("ATCR_AUTH_CERT_PATH", tt.envCertPath)
} else {
os.Unsetenv("ATCR_AUTH_CERT_PATH")
}
if tt.setExpiration {
t.Setenv("ATCR_TOKEN_EXPIRATION", tt.envExpiration)
} else {
os.Unsetenv("ATCR_TOKEN_EXPIRATION")
}
// Clear service name env var
os.Unsetenv("ATCR_SERVICE_NAME")
got, err := buildAuthConfig(tt.baseURL)
if (err != nil) != tt.wantError {
t.Errorf("buildAuthConfig() error = %v, wantError %v", err, tt.wantError)
return
}
if tt.wantError {
return
}
tokenParams, ok := got["token"]
if !ok {
t.Fatal("buildAuthConfig() missing token params")
}
if tokenParams["privatekey"] != tt.wantKeyPath {
t.Errorf("privatekey = %v, want %v", tokenParams["privatekey"], tt.wantKeyPath)
}
if tokenParams["rootcertbundle"] != tt.wantCertPath {
t.Errorf("rootcertbundle = %v, want %v", tokenParams["rootcertbundle"], tt.wantCertPath)
}
if tokenParams["expiration"] != tt.wantExpiration {
t.Errorf("expiration = %v, want %v", tokenParams["expiration"], tt.wantExpiration)
}
if tokenParams["realm"] != tt.wantRealm {
t.Errorf("realm = %v, want %v", tokenParams["realm"], tt.wantRealm)
}
if tokenParams["service"] != tt.wantService {
t.Errorf("service = %v, want %v", tokenParams["service"], tt.wantService)
}
if tokenParams["issuer"] != tt.wantService {
t.Errorf("issuer = %v, want %v", tokenParams["issuer"], tt.wantService)
}
})
}
}
func TestBuildHealthConfig(t *testing.T) {
got := buildHealthConfig()
if !got.StorageDriver.Enabled {
t.Error("buildHealthConfig().StorageDriver.Enabled = false, want true")
}
if got.StorageDriver.Interval.Seconds() != 10 {
t.Errorf("buildHealthConfig().StorageDriver.Interval = %v, want 10s", got.StorageDriver.Interval)
}
if got.StorageDriver.Threshold != 3 {
t.Errorf("buildHealthConfig().StorageDriver.Threshold = %v, want 3", got.StorageDriver.Threshold)
}
}
func TestGetStringParam(t *testing.T) {
tests := []struct {
name string
params configuration.Parameters
key string
defaultValue string
want string
}{
{
name: "string value exists",
params: configuration.Parameters{
"foo": "bar",
},
key: "foo",
defaultValue: "default",
want: "bar",
},
{
name: "key does not exist",
params: configuration.Parameters{},
key: "foo",
defaultValue: "default",
want: "default",
},
{
name: "value is not a string",
params: configuration.Parameters{
"foo": 123,
},
key: "foo",
defaultValue: "default",
want: "default",
},
{
name: "empty string value",
params: configuration.Parameters{
"foo": "",
},
key: "foo",
defaultValue: "default",
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := GetStringParam(tt.params, tt.key, tt.defaultValue)
if got != tt.want {
t.Errorf("GetStringParam() = %v, want %v", got, tt.want)
}
})
}
}
func TestGetIntParam(t *testing.T) {
tests := []struct {
name string
params configuration.Parameters
key string
defaultValue int
want int
}{
{
name: "int value exists",
params: configuration.Parameters{
"foo": 42,
},
key: "foo",
defaultValue: 100,
want: 42,
},
{
name: "key does not exist",
params: configuration.Parameters{},
key: "foo",
defaultValue: 100,
want: 100,
},
{
name: "value is not an int",
params: configuration.Parameters{
"foo": "not-an-int",
},
key: "foo",
defaultValue: 100,
want: 100,
},
{
name: "zero value",
params: configuration.Parameters{
"foo": 0,
},
key: "foo",
defaultValue: 100,
want: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := GetIntParam(tt.params, tt.key, tt.defaultValue)
if got != tt.want {
t.Errorf("GetIntParam() = %v, want %v", got, tt.want)
}
})
}
}
func TestExtractDefaultHoldDID(t *testing.T) {
tests := []struct {
name string
config *configuration.Configuration
want string
}{
{
name: "valid config with hold DID",
config: &configuration.Configuration{
Middleware: map[string][]configuration.Middleware{
"registry": {
{
Name: "atproto-resolver",
Options: configuration.Parameters{
"default_hold_did": "did:web:hold01.atcr.io",
},
},
},
},
},
want: "did:web:hold01.atcr.io",
},
{
name: "no registry middleware",
config: &configuration.Configuration{
Middleware: map[string][]configuration.Middleware{},
},
want: "",
},
{
name: "no atproto-resolver middleware",
config: &configuration.Configuration{
Middleware: map[string][]configuration.Middleware{
"registry": {
{
Name: "other-middleware",
Options: configuration.Parameters{
"foo": "bar",
},
},
},
},
},
want: "",
},
{
name: "atproto-resolver without default_hold_did",
config: &configuration.Configuration{
Middleware: map[string][]configuration.Middleware{
"registry": {
{
Name: "atproto-resolver",
Options: configuration.Parameters{
"other_option": "value",
},
},
},
},
},
want: "",
},
{
name: "default_hold_did is not a string",
config: &configuration.Configuration{
Middleware: map[string][]configuration.Middleware{
"registry": {
{
Name: "atproto-resolver",
Options: configuration.Parameters{
"default_hold_did": 123,
},
},
},
},
},
want: "",
},
{
name: "nil options",
config: &configuration.Configuration{
Middleware: map[string][]configuration.Middleware{
"registry": {
{
Name: "atproto-resolver",
Options: nil,
},
},
},
},
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ExtractDefaultHoldDID(tt.config)
if got != tt.want {
t.Errorf("ExtractDefaultHoldDID() = %v, want %v", got, tt.want)
}
})
}
}
func TestExtractTestMode(t *testing.T) {
tests := []struct {
name string
config *configuration.Configuration
want bool
}{
{
name: "test mode enabled",
config: &configuration.Configuration{
Middleware: map[string][]configuration.Middleware{
"registry": {
{
Name: "atproto-resolver",
Options: configuration.Parameters{
"test_mode": true,
},
},
},
},
},
want: true,
},
{
name: "test mode disabled",
config: &configuration.Configuration{
Middleware: map[string][]configuration.Middleware{
"registry": {
{
Name: "atproto-resolver",
Options: configuration.Parameters{
"test_mode": false,
},
},
},
},
},
want: false,
},
{
name: "no registry middleware",
config: &configuration.Configuration{
Middleware: map[string][]configuration.Middleware{},
},
want: false,
},
{
name: "no atproto-resolver middleware",
config: &configuration.Configuration{
Middleware: map[string][]configuration.Middleware{
"registry": {
{
Name: "other-middleware",
Options: configuration.Parameters{
"foo": "bar",
},
},
},
},
},
want: false,
},
{
name: "atproto-resolver without test_mode",
config: &configuration.Configuration{
Middleware: map[string][]configuration.Middleware{
"registry": {
{
Name: "atproto-resolver",
Options: configuration.Parameters{
"other_option": "value",
},
},
},
},
},
want: false,
},
{
name: "test_mode is not a bool",
config: &configuration.Configuration{
Middleware: map[string][]configuration.Middleware{
"registry": {
{
Name: "atproto-resolver",
Options: configuration.Parameters{
"test_mode": "true",
},
},
},
},
},
want: false,
},
{
name: "nil options",
config: &configuration.Configuration{
Middleware: map[string][]configuration.Middleware{
"registry": {
{
Name: "atproto-resolver",
Options: nil,
},
},
},
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ExtractTestMode(tt.config)
if got != tt.want {
t.Errorf("ExtractTestMode() = %v, want %v", got, tt.want)
}
})
}
}
func TestLoadConfigFromEnv(t *testing.T) {
tests := []struct {
name string
envHoldDID string
setHoldDID bool
wantError bool
}{
{
name: "valid config",
envHoldDID: "did:web:hold01.atcr.io",
setHoldDID: true,
wantError: false,
},
{
name: "missing default hold DID",
setHoldDID: false,
wantError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.setHoldDID {
t.Setenv("ATCR_DEFAULT_HOLD_DID", tt.envHoldDID)
} else {
os.Unsetenv("ATCR_DEFAULT_HOLD_DID")
}
// Clear other env vars to use defaults
os.Unsetenv("ATCR_BASE_URL")
os.Unsetenv("ATCR_SERVICE_NAME")
got, err := LoadConfigFromEnv()
if (err != nil) != tt.wantError {
t.Errorf("LoadConfigFromEnv() error = %v, wantError %v", err, tt.wantError)
return
}
if tt.wantError {
return
}
// Verify config structure
if got.Version.Major() != 0 || got.Version.Minor() != 1 {
t.Errorf("version = %v, want 0.1", got.Version)
}
if got.Log.Level != "info" {
t.Errorf("log level = %v, want info", got.Log.Level)
}
if got.HTTP.Addr != ":5000" {
t.Errorf("HTTP addr = %v, want :5000", got.HTTP.Addr)
}
if _, ok := got.Storage["inmemory"]; !ok {
t.Error("storage missing inmemory driver")
}
if _, ok := got.Middleware["registry"]; !ok {
t.Error("middleware missing registry")
}
if _, ok := got.Auth["token"]; !ok {
t.Error("auth missing token config")
}
if !got.Health.StorageDriver.Enabled {
t.Error("health storage driver not enabled")
}
})
}
}