- PFR API ključ (pfr_api_key) u podešavanjima i fiskalizaciji - Formatiranje fiskalnog računa: veličina fonta, QR kod, layout - PFR mock: QR box_size/border, automatski PIN retry posle BE restarta - Servis: animirane kanban kartice, formatiranje računa - Dashboard i testovi: sitne ispravke
161 lines
5.1 KiB
Go
161 lines
5.1 KiB
Go
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: 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)
|
|
}
|
|
}
|