Vlasnik izlazi iz DAL price za ovaj projekat - postaje samostalan proizvod. Preimenovanje je mehanicko (import putanje, imena fajlova/baze/UI naslova), NIJEDNA linija poslovne logike u internal/handler ili internal/service nije dirana - go build, go vet i go test ./... prolaze cisto pod novim imenom. - go.mod: module dal-license-server -> licence-server; 28 import linija u 13 .go fajlova usaglaseno - internal/config/config.go (+test): default DB_NAME licence_db - HTML sabloni (7 stranica) + 7 Playwright e2e spec fajlova: "DAL License Server" -> "Licence Server" u title/nav-brand, testovi usaglaseni u istom prolazu (nista ne moze da se razdvoji) - package.json/package-lock.json/.gitignore/.claude/project.json: ime projekta/binarnog fajla/work_dir - Usput: package.json je u repository.url nosio OTVORENU Gitea lozinku u cistom tekstu - uklonjena (URL sad bez kredencijala). Lozinka je i dalje u staroj git istoriji - preporuka: rotirati je posebno. - CLAUDE.md/README/API/TESTING/docs/SPEC/ARCHITECTURE/SETUP: naslovi, ASCII dijagrami, git clone URL, mysqldump primer, systemd predlozak u SETUP.md zamenjen stvarnim (obrazac terminia.service - journal log + graceful shutdown, ne stari minimalni predlozak) - Debrendiranje: "Univerzalni licencni server za sve DAL proizvode" -> "za vise proizvoda i klijenata"; potpis "Nenad Djukic / DAL d.o.o." -> "Nenad Djukic". NE dirano: nazivi ESIR/ARV/LIGHT_TICKET (tudji proizvodi, ne DAL brend), "DAL" kao stvaran naziv org-a u RBAC modelu (docs/loggerservice/README.md) - to je podatak, ne branding ovog servera. - docs/asp-terminia/, docs/loggerservice/: prozni pomeni imena servera Baza (dal_license_db -> licence_db) i deploy na 151 idu u posebnom koraku, posle preimenovanja Gitea repoa - vidi CLAUDE.md istorijsku belesku na dnu. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
179 lines
4.6 KiB
Go
179 lines
4.6 KiB
Go
package handler
|
|
|
|
import (
|
|
"licence-server/internal/model"
|
|
"licence-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)
|
|
}
|
|
}
|