All checks were successful
Tests / unit-tests (push) Successful in 25s
- CreateProject() sa validacijom imena (regex, duplikati, prazno) - POST /projects/create ruta sa AuthMiddleware - Modal forma na projects stranici za unos imena - 10 unit testova za CreateProject - change-password.html template i testovi - Ažuriran TESTING.md sa novom sekcijom Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
259 lines
6.3 KiB
Go
259 lines
6.3 KiB
Go
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")
|
|
}
|
|
}
|