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>
179 lines
4.7 KiB
Go
179 lines
4.7 KiB
Go
package handler
|
|
|
|
import (
|
|
"dal-license-server/internal/model"
|
|
"dal-license-server/internal/service"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
func TestWriteJSON(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
data := map[string]string{"key": "value"}
|
|
writeJSON(w, http.StatusOK, data)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("status: ocekivano 200, dobijeno %d", w.Code)
|
|
}
|
|
if ct := w.Header().Get("Content-Type"); ct != "application/json" {
|
|
t.Errorf("Content-Type: ocekivano 'application/json', dobijeno %q", ct)
|
|
}
|
|
|
|
var result map[string]string
|
|
json.Unmarshal(w.Body.Bytes(), &result)
|
|
if result["key"] != "value" {
|
|
t.Errorf("body: ocekivano 'value', dobijeno %q", result["key"])
|
|
}
|
|
}
|
|
|
|
func TestWriteJSON_StatusCreated(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
writeJSON(w, http.StatusCreated, map[string]int{"id": 1})
|
|
|
|
if w.Code != http.StatusCreated {
|
|
t.Errorf("status: ocekivano 201, dobijeno %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestWriteJSON_NilData(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
writeJSON(w, http.StatusOK, nil)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("status: ocekivano 200, dobijeno %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestWriteJSON_EmptySlice(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
writeJSON(w, http.StatusOK, []string{})
|
|
|
|
body := w.Body.String()
|
|
if body != "[]\n" {
|
|
t.Errorf("body: ocekivano '[]\\n', dobijeno %q", body)
|
|
}
|
|
}
|
|
|
|
func TestWriteError(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
writeError(w, http.StatusBadRequest, "INVALID_KEY", "Kljuc nije pronadjen")
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("status: ocekivano 400, dobijeno %d", w.Code)
|
|
}
|
|
|
|
var resp model.ErrorResponse
|
|
json.Unmarshal(w.Body.Bytes(), &resp)
|
|
|
|
if resp.Error.Code != "INVALID_KEY" {
|
|
t.Errorf("error code: ocekivano 'INVALID_KEY', dobijeno %q", resp.Error.Code)
|
|
}
|
|
if resp.Error.Message != "Kljuc nije pronadjen" {
|
|
t.Errorf("error message: ocekivano 'Kljuc nije pronadjen', dobijeno %q", resp.Error.Message)
|
|
}
|
|
}
|
|
|
|
func TestWriteError_Unauthorized(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
writeError(w, http.StatusUnauthorized, "UNAUTHORIZED", "Neautorizovan pristup")
|
|
|
|
if w.Code != http.StatusUnauthorized {
|
|
t.Errorf("status: ocekivano 401, dobijeno %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestWriteError_InternalServerError(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
writeError(w, http.StatusInternalServerError, "INTERNAL_ERROR", "Nesto je poslo naopako")
|
|
|
|
if w.Code != http.StatusInternalServerError {
|
|
t.Errorf("status: ocekivano 500, dobijeno %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestWriteLicenseError(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
err := &service.LicenseError{
|
|
Code: "ALREADY_ACTIVATED",
|
|
Message: "Licenca je vec aktivirana",
|
|
Details: map[string]interface{}{"activated_on": "PC-1"},
|
|
}
|
|
writeLicenseError(w, err)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("status: ocekivano 400, dobijeno %d", w.Code)
|
|
}
|
|
|
|
var resp model.ErrorResponse
|
|
json.Unmarshal(w.Body.Bytes(), &resp)
|
|
|
|
if resp.Error.Code != "ALREADY_ACTIVATED" {
|
|
t.Errorf("error code: ocekivano 'ALREADY_ACTIVATED', dobijeno %q", resp.Error.Code)
|
|
}
|
|
}
|
|
|
|
func TestWriteLicenseError_GenericError(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
writeLicenseError(w, &genericError{"some error"})
|
|
|
|
if w.Code != http.StatusInternalServerError {
|
|
t.Errorf("status: ocekivano 500, dobijeno %d", w.Code)
|
|
}
|
|
|
|
var resp model.ErrorResponse
|
|
json.Unmarshal(w.Body.Bytes(), &resp)
|
|
if resp.Error.Code != "INTERNAL_ERROR" {
|
|
t.Errorf("generic error mora vratiti INTERNAL_ERROR, dobijeno %q", resp.Error.Code)
|
|
}
|
|
}
|
|
|
|
type genericError struct{ msg string }
|
|
|
|
func (e *genericError) Error() string { return e.msg }
|
|
|
|
func TestClientIP_RemoteAddr(t *testing.T) {
|
|
req := httptest.NewRequest("GET", "/", nil)
|
|
req.RemoteAddr = "1.2.3.4:5678"
|
|
|
|
ip := clientIP(req)
|
|
if ip != "1.2.3.4:5678" {
|
|
t.Errorf("ocekivano '1.2.3.4:5678', dobijeno %q", ip)
|
|
}
|
|
}
|
|
|
|
func TestClientIP_XForwardedFor(t *testing.T) {
|
|
req := httptest.NewRequest("GET", "/", nil)
|
|
req.RemoteAddr = "10.0.0.1:1234"
|
|
req.Header.Set("X-Forwarded-For", "5.5.5.5")
|
|
|
|
ip := clientIP(req)
|
|
if ip != "5.5.5.5" {
|
|
t.Errorf("ocekivano '5.5.5.5', dobijeno %q", ip)
|
|
}
|
|
}
|
|
|
|
func TestClientIP_XRealIP(t *testing.T) {
|
|
req := httptest.NewRequest("GET", "/", nil)
|
|
req.RemoteAddr = "10.0.0.1:1234"
|
|
req.Header.Set("X-Real-IP", "8.8.8.8")
|
|
|
|
ip := clientIP(req)
|
|
if ip != "8.8.8.8" {
|
|
t.Errorf("ocekivano '8.8.8.8', dobijeno %q", ip)
|
|
}
|
|
}
|
|
|
|
func TestClientIP_XForwardedFor_Priority(t *testing.T) {
|
|
req := httptest.NewRequest("GET", "/", nil)
|
|
req.RemoteAddr = "10.0.0.1:1234"
|
|
req.Header.Set("X-Forwarded-For", "1.1.1.1")
|
|
req.Header.Set("X-Real-IP", "2.2.2.2")
|
|
|
|
ip := clientIP(req)
|
|
if ip != "1.1.1.1" {
|
|
t.Errorf("X-Forwarded-For mora imati prioritet, ocekivano '1.1.1.1', dobijeno %q", ip)
|
|
}
|
|
}
|