Novi Go testovi: - config_test.go: 9 testova (defaults, override, DSN, .env loading) - helpers_test.go: 13 testova (writeJSON, writeError, clientIP) Prosireni E2E testovi za svaku stranicu: - login: 15 testova (forma, auth, redirect, sesije) - dashboard: 18 testova (statistike, navbar, navigacija, odjava) - licenses: 20 testova (tabela, filteri, pretraga, kombinacije) - license-crud: 22 testa (forma, validacija, svi proizvodi/tipovi) - license-detail: 26 testova (info, aktivacije, audit, revoke, release) - audit: 14 testova (tabela, API zapisi, formati) - api-client: 18 testova (activate flow, auth, revoke flow) Azuriran TESTING.md sa kompletnom checklistom Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
216 lines
5.5 KiB
Go
216 lines
5.5 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestLoad_Defaults(t *testing.T) {
|
|
// Clear env to test defaults
|
|
envVars := []string{"APP_PORT", "APP_ENV", "DB_HOST", "DB_PORT", "DB_NAME", "DB_USER", "DB_PASS",
|
|
"ADMIN_API_KEY", "ADMIN_PASSWORD", "SESSION_SECRET", "RSA_PRIVATE_KEY_PATH",
|
|
"RATE_LIMIT_ACTIVATE", "RATE_LIMIT_VALIDATE", "LOG_LEVEL"}
|
|
saved := make(map[string]string)
|
|
for _, v := range envVars {
|
|
saved[v] = os.Getenv(v)
|
|
os.Unsetenv(v)
|
|
}
|
|
defer func() {
|
|
for k, v := range saved {
|
|
if v != "" {
|
|
os.Setenv(k, v)
|
|
}
|
|
}
|
|
}()
|
|
|
|
cfg := Load()
|
|
|
|
if cfg.Port != "8090" {
|
|
t.Errorf("Port default: ocekivano '8090', dobijeno %q", cfg.Port)
|
|
}
|
|
if cfg.Env != "development" {
|
|
t.Errorf("Env default: ocekivano 'development', dobijeno %q", cfg.Env)
|
|
}
|
|
if cfg.DBHost != "localhost" {
|
|
t.Errorf("DBHost default: ocekivano 'localhost', dobijeno %q", cfg.DBHost)
|
|
}
|
|
if cfg.DBPort != "3306" {
|
|
t.Errorf("DBPort default: ocekivano '3306', dobijeno %q", cfg.DBPort)
|
|
}
|
|
if cfg.DBName != "dal_license_db" {
|
|
t.Errorf("DBName default: ocekivano 'dal_license_db', dobijeno %q", cfg.DBName)
|
|
}
|
|
if cfg.DBUser != "license" {
|
|
t.Errorf("DBUser default: ocekivano 'license', dobijeno %q", cfg.DBUser)
|
|
}
|
|
if cfg.RSAPrivateKey != "./crypto/private.pem" {
|
|
t.Errorf("RSAPrivateKey default: ocekivano './crypto/private.pem', dobijeno %q", cfg.RSAPrivateKey)
|
|
}
|
|
if cfg.RateLimitActivate != "10" {
|
|
t.Errorf("RateLimitActivate default: ocekivano '10', dobijeno %q", cfg.RateLimitActivate)
|
|
}
|
|
if cfg.RateLimitValidate != "60" {
|
|
t.Errorf("RateLimitValidate default: ocekivano '60', dobijeno %q", cfg.RateLimitValidate)
|
|
}
|
|
if cfg.LogLevel != "info" {
|
|
t.Errorf("LogLevel default: ocekivano 'info', dobijeno %q", cfg.LogLevel)
|
|
}
|
|
}
|
|
|
|
func TestLoad_EnvOverrides(t *testing.T) {
|
|
os.Setenv("APP_PORT", "9999")
|
|
os.Setenv("APP_ENV", "production")
|
|
os.Setenv("DB_HOST", "db.example.com")
|
|
defer func() {
|
|
os.Unsetenv("APP_PORT")
|
|
os.Unsetenv("APP_ENV")
|
|
os.Unsetenv("DB_HOST")
|
|
}()
|
|
|
|
cfg := Load()
|
|
|
|
if cfg.Port != "9999" {
|
|
t.Errorf("Port override: ocekivano '9999', dobijeno %q", cfg.Port)
|
|
}
|
|
if cfg.Env != "production" {
|
|
t.Errorf("Env override: ocekivano 'production', dobijeno %q", cfg.Env)
|
|
}
|
|
if cfg.DBHost != "db.example.com" {
|
|
t.Errorf("DBHost override: ocekivano 'db.example.com', dobijeno %q", cfg.DBHost)
|
|
}
|
|
}
|
|
|
|
func TestDSN(t *testing.T) {
|
|
cfg := &Config{
|
|
DBUser: "testuser",
|
|
DBPass: "testpass",
|
|
DBHost: "localhost",
|
|
DBPort: "3306",
|
|
DBName: "testdb",
|
|
}
|
|
|
|
dsn := cfg.DSN()
|
|
expected := "testuser:testpass@tcp(localhost:3306)/testdb?parseTime=true&charset=utf8mb4&multiStatements=true"
|
|
if dsn != expected {
|
|
t.Errorf("DSN:\nocekivano: %q\ndobijeno: %q", expected, dsn)
|
|
}
|
|
}
|
|
|
|
func TestDSN_SpecialChars(t *testing.T) {
|
|
cfg := &Config{
|
|
DBUser: "user",
|
|
DBPass: "p@ss:w0rd",
|
|
DBHost: "192.168.1.1",
|
|
DBPort: "3307",
|
|
DBName: "my_db",
|
|
}
|
|
|
|
dsn := cfg.DSN()
|
|
if dsn == "" {
|
|
t.Error("DSN ne sme biti prazan")
|
|
}
|
|
// Mora sadrzati sve parametre
|
|
if !contains(dsn, "parseTime=true") {
|
|
t.Error("DSN mora sadrzati parseTime=true")
|
|
}
|
|
if !contains(dsn, "multiStatements=true") {
|
|
t.Error("DSN mora sadrzati multiStatements=true")
|
|
}
|
|
if !contains(dsn, "charset=utf8mb4") {
|
|
t.Error("DSN mora sadrzati charset=utf8mb4")
|
|
}
|
|
}
|
|
|
|
func TestLoadEnvFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
envPath := filepath.Join(dir, ".env")
|
|
content := `# Comment
|
|
TEST_CONFIG_VAR=hello_world
|
|
TEST_CONFIG_NUM=42
|
|
`
|
|
os.WriteFile(envPath, []byte(content), 0644)
|
|
|
|
os.Unsetenv("TEST_CONFIG_VAR")
|
|
os.Unsetenv("TEST_CONFIG_NUM")
|
|
|
|
loadEnvFile(envPath)
|
|
|
|
if v := os.Getenv("TEST_CONFIG_VAR"); v != "hello_world" {
|
|
t.Errorf("TEST_CONFIG_VAR: ocekivano 'hello_world', dobijeno %q", v)
|
|
}
|
|
if v := os.Getenv("TEST_CONFIG_NUM"); v != "42" {
|
|
t.Errorf("TEST_CONFIG_NUM: ocekivano '42', dobijeno %q", v)
|
|
}
|
|
|
|
os.Unsetenv("TEST_CONFIG_VAR")
|
|
os.Unsetenv("TEST_CONFIG_NUM")
|
|
}
|
|
|
|
func TestLoadEnvFile_ExistingEnvNotOverwritten(t *testing.T) {
|
|
dir := t.TempDir()
|
|
envPath := filepath.Join(dir, ".env")
|
|
os.WriteFile(envPath, []byte("TEST_EXISTING=from_file\n"), 0644)
|
|
|
|
os.Setenv("TEST_EXISTING", "from_env")
|
|
defer os.Unsetenv("TEST_EXISTING")
|
|
|
|
loadEnvFile(envPath)
|
|
|
|
if v := os.Getenv("TEST_EXISTING"); v != "from_env" {
|
|
t.Errorf("env varijabla ne sme biti prepisana iz fajla: ocekivano 'from_env', dobijeno %q", v)
|
|
}
|
|
}
|
|
|
|
func TestLoadEnvFile_SkipsComments(t *testing.T) {
|
|
dir := t.TempDir()
|
|
envPath := filepath.Join(dir, ".env")
|
|
os.WriteFile(envPath, []byte("# COMMENTED_VAR=should_not_set\nACTUAL_VAR=set_this\n"), 0644)
|
|
|
|
os.Unsetenv("COMMENTED_VAR")
|
|
os.Unsetenv("ACTUAL_VAR")
|
|
|
|
loadEnvFile(envPath)
|
|
|
|
if v := os.Getenv("COMMENTED_VAR"); v != "" {
|
|
t.Errorf("komentarisana varijabla ne sme biti setovana: dobijeno %q", v)
|
|
}
|
|
if v := os.Getenv("ACTUAL_VAR"); v != "set_this" {
|
|
t.Errorf("ACTUAL_VAR: ocekivano 'set_this', dobijeno %q", v)
|
|
}
|
|
|
|
os.Unsetenv("ACTUAL_VAR")
|
|
}
|
|
|
|
func TestLoadEnvFile_EmptyLines(t *testing.T) {
|
|
dir := t.TempDir()
|
|
envPath := filepath.Join(dir, ".env")
|
|
os.WriteFile(envPath, []byte("\n\nVAR_AFTER_EMPTY=works\n\n"), 0644)
|
|
|
|
os.Unsetenv("VAR_AFTER_EMPTY")
|
|
loadEnvFile(envPath)
|
|
|
|
if v := os.Getenv("VAR_AFTER_EMPTY"); v != "works" {
|
|
t.Errorf("ocekivano 'works', dobijeno %q", v)
|
|
}
|
|
os.Unsetenv("VAR_AFTER_EMPTY")
|
|
}
|
|
|
|
func TestLoadEnvFile_NonexistentFile(t *testing.T) {
|
|
// Ne sme pasti
|
|
loadEnvFile("/nonexistent/path/.env")
|
|
}
|
|
|
|
func contains(s, substr string) bool {
|
|
return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsStr(s, substr))
|
|
}
|
|
|
|
func containsStr(s, sub string) bool {
|
|
for i := 0; i <= len(s)-len(sub); i++ {
|
|
if s[i:i+len(sub)] == sub {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|