WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s

This commit is contained in:
yawan
2026-09-22 16:19:06 +08:00
commit f7fc992f57
3929 changed files with 1006607 additions and 0 deletions
+183
View File
@@ -0,0 +1,183 @@
package config
import "testing"
// TestApplyAuthAndTenantDefaults_DisableRegistrationDrivesRegistrationMode
// pins down the env-vs-YAML contract for DISABLE_REGISTRATION. Without this,
// DISABLE_REGISTRATION=true would block /auth/register at the handler layer
// but leave /auth/config reporting self_serve, so the frontend would keep
// showing the (broken) Register entry. Coercing registration_mode here keeps
// both gates in sync, and matches the docs/RBAC说明.md "env always wins over
// YAML" rule.
func TestApplyAuthAndTenantDefaults_DisableRegistrationDrivesRegistrationMode(t *testing.T) {
cases := []struct {
name string
disable string // value for DISABLE_REGISTRATION (empty == unset)
cfgMode string // pre-set value on cfg.Auth.RegistrationMode
expected string
}{
{"true coerces empty YAML to invite_only", "true", "", AuthRegistrationModeInviteOnly},
{"case-insensitive TRUE also coerces", "TRUE", "", AuthRegistrationModeInviteOnly},
{"true overrides explicit self_serve YAML", "true", AuthRegistrationModeSelfServe, AuthRegistrationModeInviteOnly},
{"true is a no-op when YAML already invite_only", "true", AuthRegistrationModeInviteOnly, AuthRegistrationModeInviteOnly},
{"false leaves YAML untouched", "false", AuthRegistrationModeSelfServe, AuthRegistrationModeSelfServe},
{"unset falls back to default self_serve", "", "", AuthRegistrationModeSelfServe},
{"unset keeps explicit invite_only YAML", "", AuthRegistrationModeInviteOnly, AuthRegistrationModeInviteOnly},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Setenv("DISABLE_REGISTRATION", tc.disable)
// Other tenant env vars must not leak between cases.
t.Setenv("WEKNORA_TENANT_ENABLE_RBAC", "")
t.Setenv("WEKNORA_TENANT_MAX_OWNED_PER_USER", "")
t.Setenv("WEKNORA_TENANT_SELF_SERVICE_CREATION_ENABLED", "")
cfg := &Config{Auth: &AuthConfig{RegistrationMode: tc.cfgMode}}
applyAuthAndTenantDefaults(cfg)
if cfg.Auth.RegistrationMode != tc.expected {
t.Fatalf("registration_mode = %q, want %q", cfg.Auth.RegistrationMode, tc.expected)
}
})
}
}
func TestApplyAuthAndTenantDefaults_SelfServiceTenantCreation(t *testing.T) {
t.Run("defaults enabled", func(t *testing.T) {
t.Setenv("WEKNORA_TENANT_SELF_SERVICE_CREATION_ENABLED", "")
cfg := &Config{Tenant: &TenantConfig{}}
applyAuthAndTenantDefaults(cfg)
if !cfg.Tenant.IsSelfServiceCreationEnabled() {
t.Fatal("self-service tenant creation should default to enabled")
}
})
t.Run("environment disables yaml default", func(t *testing.T) {
t.Setenv("WEKNORA_TENANT_SELF_SERVICE_CREATION_ENABLED", "false")
on := true
cfg := &Config{Tenant: &TenantConfig{SelfServiceCreationEnabled: &on}}
applyAuthAndTenantDefaults(cfg)
if cfg.Tenant.IsSelfServiceCreationEnabled() {
t.Fatal("environment override should disable self-service tenant creation")
}
})
}
func TestApplyAuthAndTenantDefaults_DefaultTenantMode(t *testing.T) {
t.Run("historical default creates a personal tenant", func(t *testing.T) {
t.Setenv("WEKNORA_AUTH_DEFAULT_TENANT_MODE", "")
cfg := &Config{Auth: &AuthConfig{}}
applyAuthAndTenantDefaults(cfg)
if cfg.Auth.DefaultTenantMode != AuthDefaultTenantModeCreatePersonal {
t.Fatalf("default_tenant_mode = %q, want %q", cfg.Auth.DefaultTenantMode, AuthDefaultTenantModeCreatePersonal)
}
})
t.Run("environment overrides yaml", func(t *testing.T) {
t.Setenv("WEKNORA_AUTH_DEFAULT_TENANT_MODE", AuthDefaultTenantModeTenantless)
cfg := &Config{Auth: &AuthConfig{DefaultTenantMode: AuthDefaultTenantModeCreatePersonal}}
applyAuthAndTenantDefaults(cfg)
if cfg.Auth.DefaultTenantMode != AuthDefaultTenantModeTenantless {
t.Fatalf("default_tenant_mode = %q, want %q", cfg.Auth.DefaultTenantMode, AuthDefaultTenantModeTenantless)
}
})
t.Run("invalid environment value fails validation", func(t *testing.T) {
t.Setenv("WEKNORA_AUTH_DEFAULT_TENANT_MODE", "create_magic")
cfg := &Config{Auth: &AuthConfig{}}
applyAuthAndTenantDefaults(cfg)
if err := ValidateConfig(cfg); err == nil {
t.Fatal("ValidateConfig unexpectedly accepted an invalid default tenant mode")
}
})
}
// TestApplyAuthAndTenantDefaults_CrossTenantAccess is a regression test for the
// env-binding gap: viper.AutomaticEnv has no SetEnvPrefix, so
// WEKNORA_TENANT_ENABLE_CROSS_TENANT_ACCESS is never bound to the nested struct
// automatically. applyAuthAndTenantDefaults must read it explicitly (like RBAC);
// without that, only config.yaml's enable_cross_tenant_access would take effect
// and the documented env override would be silently ignored.
func TestApplyAuthAndTenantDefaults_CrossTenantAccess(t *testing.T) {
t.Run("environment true enables cross-tenant access", func(t *testing.T) {
t.Setenv("WEKNORA_TENANT_ENABLE_CROSS_TENANT_ACCESS", "true")
cfg := &Config{Tenant: &TenantConfig{EnableCrossTenantAccess: false}}
applyAuthAndTenantDefaults(cfg)
if !cfg.Tenant.EnableCrossTenantAccess {
t.Fatal("WEKNORA_TENANT_ENABLE_CROSS_TENANT_ACCESS=true should enable cross-tenant access")
}
})
t.Run("environment false overrides yaml true", func(t *testing.T) {
t.Setenv("WEKNORA_TENANT_ENABLE_CROSS_TENANT_ACCESS", "false")
cfg := &Config{Tenant: &TenantConfig{EnableCrossTenantAccess: true}}
applyAuthAndTenantDefaults(cfg)
if cfg.Tenant.EnableCrossTenantAccess {
t.Fatal("WEKNORA_TENANT_ENABLE_CROSS_TENANT_ACCESS=false should disable cross-tenant access")
}
})
t.Run("case-insensitive TRUE also enables", func(t *testing.T) {
t.Setenv("WEKNORA_TENANT_ENABLE_CROSS_TENANT_ACCESS", "TRUE")
cfg := &Config{Tenant: &TenantConfig{EnableCrossTenantAccess: false}}
applyAuthAndTenantDefaults(cfg)
if !cfg.Tenant.EnableCrossTenantAccess {
t.Fatal("WEKNORA_TENANT_ENABLE_CROSS_TENANT_ACCESS=TRUE should enable cross-tenant access (case-insensitive)")
}
})
t.Run("unset leaves yaml value untouched", func(t *testing.T) {
t.Setenv("WEKNORA_TENANT_ENABLE_CROSS_TENANT_ACCESS", "")
cfg := &Config{Tenant: &TenantConfig{EnableCrossTenantAccess: true}}
applyAuthAndTenantDefaults(cfg)
if !cfg.Tenant.EnableCrossTenantAccess {
t.Fatal("empty env should leave the YAML-provided cross-tenant access value untouched")
}
})
}
func TestApplyAuthAndTenantDefaults_ComplexPasswordEnabledEnv(t *testing.T) {
t.Run("1 enables via ParseBool", func(t *testing.T) {
t.Setenv("WEKNORA_AUTH_COMPLEX_PASSWORD_ENABLED", "1")
cfg := &Config{Auth: &AuthConfig{}}
applyAuthAndTenantDefaults(cfg)
if !cfg.Auth.ComplexPasswordEnabled {
t.Fatal("WEKNORA_AUTH_COMPLEX_PASSWORD_ENABLED=1 should enable complex passwords")
}
})
t.Run("false disables", func(t *testing.T) {
t.Setenv("WEKNORA_AUTH_COMPLEX_PASSWORD_ENABLED", "false")
cfg := &Config{Auth: &AuthConfig{ComplexPasswordEnabled: true}}
applyAuthAndTenantDefaults(cfg)
if cfg.Auth.ComplexPasswordEnabled {
t.Fatal("WEKNORA_AUTH_COMPLEX_PASSWORD_ENABLED=false should disable complex passwords")
}
})
t.Run("unset leaves yaml", func(t *testing.T) {
t.Setenv("WEKNORA_AUTH_COMPLEX_PASSWORD_ENABLED", "")
cfg := &Config{Auth: &AuthConfig{ComplexPasswordEnabled: true}}
applyAuthAndTenantDefaults(cfg)
if !cfg.Auth.ComplexPasswordEnabled {
t.Fatal("empty env should leave YAML complex-password flag untouched")
}
})
}
File diff suppressed because it is too large Load Diff