package main import ( "net/http" "net/http/httptest" "net/url" "os" "path/filepath" "strings" "testing" ) func setupTestApp(t *testing.T) (cleanupFn func()) { t.Helper() dir := t.TempDir() cfgPath := filepath.Join(dir, "config.json") data := `{ "username": "admin", "password": "secret123", "session_secret": "test-secret", "projects_path": "/tmp" }` os.WriteFile(cfgPath, []byte(data), 0644) var err error oldCfg := cfg oldTemplates := templates oldSessionMgr := sessionMgr cfg, err = LoadConfig(cfgPath) if err != nil { t.Fatalf("LoadConfig: %v", err) } templates, err = NewTemplateRenderer("templates") if err != nil { t.Fatalf("Templates: %v", err) } sessionMgr = NewSessionManager(cfg.SessionSecret) return func() { cfg = oldCfg templates = oldTemplates sessionMgr = oldSessionMgr } } func TestChangePasswordPage(t *testing.T) { cleanup := setupTestApp(t) defer cleanup() sess := sessionMgr.Create("admin") req := httptest.NewRequest("GET", "/change-password", nil) req.AddCookie(&http.Cookie{Name: sessionCookieName, Value: sess.Token}) w := httptest.NewRecorder() handleChangePasswordPage(w, req) if w.Code != http.StatusOK { t.Errorf("status = %d, want %d", w.Code, http.StatusOK) } body := w.Body.String() if !strings.Contains(body, "Promena lozinke") { t.Error("expected page title in response") } if !strings.Contains(body, "current_password") { t.Error("expected current_password field") } } func TestChangePassword(t *testing.T) { t.Run("uspešna promena", func(t *testing.T) { cleanup := setupTestApp(t) defer cleanup() form := url.Values{ "current_password": {"secret123"}, "new_password": {"newpass123"}, "confirm_password": {"newpass123"}, } req := httptest.NewRequest("POST", "/change-password", strings.NewReader(form.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") w := httptest.NewRecorder() handleChangePassword(w, req) if w.Code != http.StatusOK { t.Errorf("status = %d, want %d", w.Code, http.StatusOK) } body := w.Body.String() if !strings.Contains(body, "uspešno promenjena") { t.Error("expected success message") } // Nova šifra radi if !cfg.CheckPassword("newpass123") { t.Error("new password should work") } // Stara ne radi if cfg.CheckPassword("secret123") { t.Error("old password should not work") } }) t.Run("pogrešna trenutna lozinka", func(t *testing.T) { cleanup := setupTestApp(t) defer cleanup() form := url.Values{ "current_password": {"wrongpass"}, "new_password": {"newpass123"}, "confirm_password": {"newpass123"}, } req := httptest.NewRequest("POST", "/change-password", strings.NewReader(form.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") w := httptest.NewRecorder() handleChangePassword(w, req) if w.Code != http.StatusBadRequest { t.Errorf("status = %d, want %d", w.Code, http.StatusBadRequest) } body := w.Body.String() if !strings.Contains(body, "Pogrešna trenutna lozinka") { t.Error("expected wrong password error message") } }) t.Run("prekratka nova lozinka", func(t *testing.T) { cleanup := setupTestApp(t) defer cleanup() form := url.Values{ "current_password": {"secret123"}, "new_password": {"abc"}, "confirm_password": {"abc"}, } req := httptest.NewRequest("POST", "/change-password", strings.NewReader(form.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") w := httptest.NewRecorder() handleChangePassword(w, req) if w.Code != http.StatusBadRequest { t.Errorf("status = %d, want %d", w.Code, http.StatusBadRequest) } body := w.Body.String() if !strings.Contains(body, "najmanje 6 karaktera") { t.Error("expected min length error message") } }) t.Run("lozinke se ne poklapaju", func(t *testing.T) { cleanup := setupTestApp(t) defer cleanup() form := url.Values{ "current_password": {"secret123"}, "new_password": {"newpass123"}, "confirm_password": {"different"}, } req := httptest.NewRequest("POST", "/change-password", strings.NewReader(form.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") w := httptest.NewRecorder() handleChangePassword(w, req) if w.Code != http.StatusBadRequest { t.Errorf("status = %d, want %d", w.Code, http.StatusBadRequest) } body := w.Body.String() if !strings.Contains(body, "ne poklapaju") { t.Error("expected mismatch error message") } }) } func TestConfigCheckPassword(t *testing.T) { dir := t.TempDir() cfgPath := filepath.Join(dir, "config.json") data := `{ "username": "admin", "password": "testpass", "session_secret": "abc123" }` os.WriteFile(cfgPath, []byte(data), 0644) c, err := LoadConfig(cfgPath) if err != nil { t.Fatalf("LoadConfig: %v", err) } // Šifra je hešovana u fajlu if !isHashed(c.Password) { t.Error("password should be hashed after LoadConfig") } // CheckPassword radi if !c.CheckPassword("testpass") { t.Error("CheckPassword should return true for correct password") } if c.CheckPassword("wrong") { t.Error("CheckPassword should return false for wrong password") } } func TestConfigSetPassword(t *testing.T) { dir := t.TempDir() cfgPath := filepath.Join(dir, "config.json") data := `{ "username": "admin", "password": "original", "session_secret": "abc123" }` os.WriteFile(cfgPath, []byte(data), 0644) c, err := LoadConfig(cfgPath) if err != nil { t.Fatalf("LoadConfig: %v", err) } if err := c.SetPassword("newpassword"); err != nil { t.Fatalf("SetPassword: %v", err) } if !c.CheckPassword("newpassword") { t.Error("new password should work") } if c.CheckPassword("original") { t.Error("old password should not work") } // Proveri da je sačuvano u fajl c2, err := LoadConfig(cfgPath) if err != nil { t.Fatalf("LoadConfig after SetPassword: %v", err) } if !c2.CheckPassword("newpassword") { t.Error("password should persist after reload") } } func TestIsHashed(t *testing.T) { if isHashed("plaintext") { t.Error("plaintext should not be detected as hashed") } if !isHashed("$2a$10$abcdefghijklmnopqrstuuABC") { t.Error("$2a$ prefix should be detected as hashed") } if !isHashed("$2b$10$abcdefghijklmnopqrstuuABC") { t.Error("$2b$ prefix should be detected as hashed") } }