Ograničenje konekcija BE emulatora, sopstveni kontekst fiskalizacije nezavisan od prekida zahteva, validacija porta na setup stranici, serverska optimizacija kratke pretrage bez eval-a, testovi

This commit is contained in:
2026-07-13 15:10:02 +02:00
parent bc60288a0d
commit 9cf4e66f00
24 changed files with 619 additions and 40 deletions
+160
View File
@@ -0,0 +1,160 @@
package fiskal
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"time"
)
func testniZahtev() InvoiceRequest {
return InvoiceRequest{
InvoiceRequest: InvoiceRequestBody{
InvoiceType: "Normal",
TransactionType: "Sale",
Payment: []PaymentItem{{Amount: 100, PaymentType: "Cash"}},
Items: []InvoiceItem{{Name: "Test", TotalAmount: 100, UnitPrice: 100, Quantity: 1}},
Cashier: "Test Kasir",
},
}
}
func TestIzdajRacunUspeh(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost || r.URL.Path != "/api/invoices" {
t.Fatalf("neočekivan zahtev: %s %s", r.Method, r.URL.Path)
}
if got := r.Header.Get("Authorization"); got != "Bearer test-key" {
t.Fatalf("Authorization header = %q, očekivano Bearer test-key", got)
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"invoiceNumber":"ПП123","totalAmount":100,"totalTax":16.67}`))
}))
defer srv.Close()
k := NoviKlijent(srv.URL, "test-key")
odgovor, err := k.IzdajRacun(context.Background(), testniZahtev())
if err != nil {
t.Fatalf("IzdajRacun greška: %v", err)
}
if odgovor.InvoiceNumber != "ПП123" {
t.Errorf("InvoiceNumber = %q, očekivano ПП123", odgovor.InvoiceNumber)
}
if odgovor.TotalAmount != 100 {
t.Errorf("TotalAmount = %v, očekivano 100", odgovor.TotalAmount)
}
}
func TestIzdajRacunHttpGreska(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(`{"message":"nevažeći zahtev"}`))
}))
defer srv.Close()
k := NoviKlijent(srv.URL, "test-key")
if _, err := k.IzdajRacun(context.Background(), testniZahtev()); err == nil {
t.Fatal("očekivana greška za HTTP 400, dobijeno nil")
}
}
func TestIzdajRacunServerNedostupan(t *testing.T) {
// port na kome sigurno niko ne sluša (zatvoren odmah posle otvaranja)
ln := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
url := ln.URL
ln.Close()
k := NoviKlijent(url, "test-key")
if _, err := k.IzdajRacun(context.Background(), testniZahtev()); err == nil {
t.Fatal("očekivana greška kad server nije dostupan, dobijeno nil")
}
}
func TestIzdajRacunKontekstIstekao(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(50 * time.Millisecond)
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{}`))
}))
defer srv.Close()
k := NoviKlijent(srv.URL, "test-key")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Millisecond)
defer cancel()
if _, err := k.IzdajRacun(ctx, testniZahtev()); err == nil {
t.Fatal("očekivana greška kad kontekst istekne pre odgovora, dobijeno nil")
}
}
func TestIzdajRacunPrezivljavaOtkazivanjeOriginalnogKonteksta(t *testing.T) {
// simulira #35 iz BUG.md: HTTP handler-ov r.Context() se otkaže (npr. klijent
// zatvori konekciju), ali fiskalni poziv nastavlja preko odvojenog konteksta.
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(30 * time.Millisecond)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"invoiceNumber":"ПП999"}`))
}))
defer srv.Close()
originalni, otkazi := context.WithCancel(context.Background())
odvojeni := context.WithoutCancel(originalni)
odvojeni, cancel := context.WithTimeout(odvojeni, time.Second)
defer cancel()
// otkaži originalni kontekst odmah — kao da je browser konekcija prekinuta
otkazi()
k := NoviKlijent(srv.URL, "test-key")
odgovor, err := k.IzdajRacun(odvojeni, testniZahtev())
if err != nil {
t.Fatalf("fiskalni poziv nije smeo da pukne posle otkazivanja originalnog konteksta: %v", err)
}
if odgovor.InvoiceNumber != "ПП999" {
t.Errorf("InvoiceNumber = %q, očekivano ПП999", odgovor.InvoiceNumber)
}
}
func TestStatusUspeh(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/api/status" {
t.Fatalf("neočekivana putanja: %s", r.URL.Path)
}
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"status":"ok"}`))
}))
defer srv.Close()
k := NoviKlijent(srv.URL, "")
rezultat, err := k.Status(context.Background())
if err != nil {
t.Fatalf("Status greška: %v", err)
}
if rezultat["status"] != "ok" {
t.Errorf("status = %v, očekivano ok", rezultat["status"])
}
}
func TestZakljuciDanNoContent(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodDelete {
t.Fatalf("očekivan DELETE, dobijeno %s", r.Method)
}
w.WriteHeader(http.StatusNoContent)
}))
defer srv.Close()
k := NoviKlijent(srv.URL, "")
if err := k.ZakljuciDan(context.Background()); err != nil {
t.Fatalf("ZakljuciDan greška: %v", err)
}
}
func TestNoviKlijentSkidaKrajnjuKosuCrtu(t *testing.T) {
k := NoviKlijent("http://127.0.0.1:4566/", "kljuc")
if k.BaseURL != "http://127.0.0.1:4566" {
t.Errorf("BaseURL = %q, očekivano bez krajnje kose crte", k.BaseURL)
}
}