This commit is contained in:
112
backend/modules/invoicing/tests/conftest.py
Normal file
112
backend/modules/invoicing/tests/conftest.py
Normal file
@@ -0,0 +1,112 @@
|
||||
# backend/modules/invoicing/tests/conftest.py
|
||||
|
||||
import pytest
|
||||
from django.contrib.auth import get_user_model
|
||||
from modules.invoicing.models import Invoice
|
||||
from unittest.mock import MagicMock
|
||||
from django.conf import settings
|
||||
from core.celery import app as celery_app
|
||||
import modules.invoicing.tasks as tasks
|
||||
import uuid
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
@pytest.fixture
|
||||
def user():
|
||||
# Dodan username
|
||||
return User.objects.create_user(email="test@test.hr", password="password")
|
||||
|
||||
@pytest.fixture
|
||||
def api_client(user):
|
||||
from rest_framework.test import APIClient
|
||||
client = APIClient()
|
||||
client.force_authenticate(user=user)
|
||||
return client
|
||||
|
||||
@pytest.fixture
|
||||
def staff_user():
|
||||
return User.objects.create_superuser(email='admin@erp.hr', password='password', first_name='Admin', last_name='User')
|
||||
|
||||
@pytest.fixture
|
||||
def regular_user():
|
||||
return User.objects.create_user(email='user@erp.hr', password='password', first_name='User', last_name='User')
|
||||
|
||||
@pytest.fixture
|
||||
def serviser_a():
|
||||
return User.objects.create_user(email="a@test.hr", password="password", first_name='A', last_name='User')
|
||||
|
||||
@pytest.fixture
|
||||
def serviser_b():
|
||||
return User.objects.create_user(email="b@test.hr", password="password", first_name='B', last_name='User')
|
||||
|
||||
@pytest.fixture
|
||||
def test_client():
|
||||
from modules.crm.models import Client
|
||||
return Client.objects.create(name="Testni Klijent", tax_id="12345678901")
|
||||
|
||||
@pytest.fixture
|
||||
def invoice_a(serviser_a, test_client): # Dodali smo test_client
|
||||
from modules.invoicing.utils import generate_invoice_number
|
||||
return Invoice.objects.create(
|
||||
invoice_number=generate_invoice_number(),
|
||||
creator=serviser_a,
|
||||
client=test_client, # OVDJE JE BIO PROBLEM (bio je null)
|
||||
amount=100.00,
|
||||
due_date="2026-12-31"
|
||||
)
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def paperless_settings(settings):
|
||||
# Postavi privremene vrijednosti za vrijeme trajanja testa
|
||||
settings.PAPERLESS_API_URL = "http://mock-paperless.local"
|
||||
settings.PAPERLESS_API_TOKEN = "mock-token"
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def force_celery_eager(settings):
|
||||
settings.CELERY_TASK_ALWAYS_EAGER = True
|
||||
settings.CELERY_TASK_EAGER_PROPAGATES = True
|
||||
|
||||
# Ovo je ključ: prisilno ažuriraj konfiguraciju Celery aplikacije
|
||||
celery_app.conf.update(
|
||||
task_always_eager=True,
|
||||
task_eager_propagates=True,
|
||||
)
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_paperless_gateway(monkeypatch):
|
||||
"""
|
||||
Koristi monkeypatch za zamjenu PaperlessGateway klase.
|
||||
"""
|
||||
mock_client = MagicMock()
|
||||
mock_client.get_document_metadata.return_value = {
|
||||
"correspondent": "INV-2026-TEST",
|
||||
"total_amount": "100.00",
|
||||
"created": "2026-07-01"
|
||||
}
|
||||
|
||||
# Kreiramo tvornicu koja vraća naš mock
|
||||
MockGateway = MagicMock(return_value=mock_client)
|
||||
|
||||
# Zamijeni klasu unutar tasks modula
|
||||
monkeypatch.setattr(tasks, "PaperlessGateway", MockGateway)
|
||||
|
||||
yield MockGateway
|
||||
|
||||
@pytest.fixture
|
||||
def mock_client(db):
|
||||
"""Kreira klijenta kojeg koristi PaperlessGateway u testovima."""
|
||||
from modules.crm.models import Client
|
||||
return Client.objects.create(name="INV-2026-TEST") # Ime mora odgovarati mocku iz conftest.py
|
||||
|
||||
@pytest.fixture
|
||||
def create_unique_client(db):
|
||||
"""Factory funkcija za kreiranje unikatnog klijenta."""
|
||||
from modules.crm.models import Client
|
||||
def _create_client(name):
|
||||
return Client.objects.create(
|
||||
name=name,
|
||||
tax_id=str(uuid.uuid4()), # Garantira unikatnost
|
||||
email=f"{uuid.uuid4()}@test.hr" # Garantira unikatnost
|
||||
)
|
||||
return _create_client
|
||||
|
||||
46
backend/modules/invoicing/tests/test_api.py
Normal file
46
backend/modules/invoicing/tests/test_api.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import uuid
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APIClient
|
||||
from django.contrib.auth import get_user_model
|
||||
from decimal import Decimal
|
||||
from datetime import date
|
||||
|
||||
from modules.crm.models import Client
|
||||
from modules.invoicing.models import Invoice
|
||||
|
||||
|
||||
class InvoiceAPITests(TestCase):
|
||||
def setUp(self):
|
||||
self.api_client = APIClient()
|
||||
User = get_user_model()
|
||||
# Kreiraj test korisnika (ako treba autentikacija)
|
||||
self.user = User.objects.create_user(
|
||||
username=f"user_{uuid.uuid4().hex[:8]}",
|
||||
email=f"user+{uuid.uuid4().hex[:8]}@example.test",
|
||||
password="pass"
|
||||
)
|
||||
# Kreiraj jedinstvenog klijenta (email s uuid kako bismo izbjegli duplicate key)
|
||||
self.client_obj = Client.objects.create(
|
||||
name="API Test Client",
|
||||
email=f"client+{uuid.uuid4()}@example.test"
|
||||
)
|
||||
# autentikacija ako je endpoint zaštićen
|
||||
self.api_client.force_authenticate(user=self.user)
|
||||
|
||||
def test_api_creates_invoice_with_id(self):
|
||||
url = reverse('invoice-list') # prilagodi ako je druga ruta
|
||||
payload = {
|
||||
"client": self.client_obj.id,
|
||||
"due_date": date.today().isoformat(),
|
||||
"items": [
|
||||
{"description": "Test item", "quantity": "1.00", "unit_price": "10.00"}
|
||||
]
|
||||
}
|
||||
|
||||
response = self.api_client.post(url, payload, format='json')
|
||||
assert response.status_code in (200, 201)
|
||||
# provjeri da se faktura stvorila i da vraća id/kod
|
||||
created = Invoice.objects.filter(client=self.client_obj).first()
|
||||
assert created is not None
|
||||
assert created.amount == Decimal("10.00")
|
||||
18
backend/modules/invoicing/tests/test_models.py
Normal file
18
backend/modules/invoicing/tests/test_models.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import pytest
|
||||
from modules.invoicing.models import Invoice, InvoiceItem
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_invoice_str_representation(invoice_a):
|
||||
assert str(invoice_a) == f"{invoice_a.invoice_number} - {invoice_a.client}"
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_invoice_total_calculation(invoice_a):
|
||||
InvoiceItem.objects.create(invoice=invoice_a, description="Test", quantity=2, unit_price=50.00)
|
||||
InvoiceItem.objects.create(invoice=invoice_a, description="Test2", quantity=1, unit_price=25.00)
|
||||
assert invoice_a.get_total_amount() == 125.00
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_invoice_number_autogenerate():
|
||||
# Testiramo da li se automatski generira ako nije zadan
|
||||
from modules.invoicing.models import Invoice
|
||||
# (Za ovo ti treba 'creator' i 'client' fixture-i)
|
||||
10
backend/modules/invoicing/tests/test_permissions.py
Normal file
10
backend/modules/invoicing/tests/test_permissions.py
Normal file
@@ -0,0 +1,10 @@
|
||||
import pytest
|
||||
from rest_framework import status
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_invoice_access_restriction(api_client, regular_user, invoice_a):
|
||||
# invoice_a pripada serviseru_a, a api_client je regular_user
|
||||
url = f'/api/invoicing/invoices/{invoice_a.id}/'
|
||||
response = api_client.get(url)
|
||||
# Trebao bi dobiti 404 ili 403 ovisno o tvojoj logici
|
||||
assert response.status_code in [status.HTTP_404_NOT_FOUND, status.HTTP_403_FORBIDDEN]
|
||||
22
backend/modules/invoicing/tests/test_serializers.py
Normal file
22
backend/modules/invoicing/tests/test_serializers.py
Normal file
@@ -0,0 +1,22 @@
|
||||
import pytest
|
||||
from modules.invoicing.serializers import InvoiceSerializer
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_invoice_serializer_valid(regular_user, test_client):
|
||||
data = {
|
||||
"client": test_client.id,
|
||||
"due_date": "2026-12-31",
|
||||
"status": "draft",
|
||||
"items": [
|
||||
{"description": "Servis", "quantity": 1, "unit_price": 100.00}
|
||||
]
|
||||
}
|
||||
# Context je ključan za pristup useru
|
||||
request = MagicMock()
|
||||
request.user = regular_user
|
||||
serializer = InvoiceSerializer(data=data, context={'request': request})
|
||||
|
||||
assert serializer.is_valid(), serializer.errors
|
||||
invoice = serializer.save()
|
||||
assert invoice.amount == 100.00
|
||||
40
backend/modules/invoicing/tests/test_services.py
Normal file
40
backend/modules/invoicing/tests/test_services.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth import get_user_model
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
|
||||
from modules.crm.models import Client
|
||||
from modules.invoicing.models import Invoice, InvoiceItem
|
||||
from modules.invoicing.services import InvoiceService
|
||||
|
||||
|
||||
class InvoiceServiceTests(TestCase):
|
||||
def setUp(self):
|
||||
User = get_user_model()
|
||||
self.creator = User.objects.create_user(username="creator", email="c@example.com", password="pass")
|
||||
self.client = Client.objects.create(name="Test Client")
|
||||
|
||||
def test_create_full_invoice_creates_invoice_and_items(self):
|
||||
items = [
|
||||
{"description": "Item A", "quantity": Decimal("2.00"), "unit_price": Decimal("10.00")},
|
||||
{"description": "Item B", "quantity": Decimal("1.00"), "unit_price": Decimal("5.50")},
|
||||
]
|
||||
validated_data = {
|
||||
"client": self.client,
|
||||
"due_date": date.today(),
|
||||
"items": items,
|
||||
}
|
||||
|
||||
invoice = InvoiceService.create_full_invoice(user=self.creator, **validated_data)
|
||||
|
||||
# Basic assertions
|
||||
self.assertIsInstance(invoice, Invoice)
|
||||
self.assertEqual(invoice.creator, self.creator)
|
||||
self.assertEqual(invoice.client, self.client)
|
||||
self.assertEqual(invoice.items.count(), 2)
|
||||
|
||||
# Total should be 2*10 + 1*5.5 = 25.5
|
||||
self.assertEqual(invoice.amount, Decimal("25.50"))
|
||||
item_descriptions = [i.description for i in invoice.items.all()]
|
||||
self.assertIn("Item A", item_descriptions)
|
||||
self.assertIn("Item B", item_descriptions)
|
||||
40
backend/modules/invoicing/tests/test_tasks.py
Normal file
40
backend/modules/invoicing/tests/test_tasks.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import uuid
|
||||
from django.test import TestCase
|
||||
from unittest.mock import patch
|
||||
from datetime import datetime
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from modules.crm.models import Client
|
||||
from modules.invoicing import tasks
|
||||
|
||||
|
||||
class TasksTests(TestCase):
|
||||
def setUp(self):
|
||||
# Kreiramo klijenta koji vraća PaperlessGateway; email jedinstven radi izbjegavanja duplikata
|
||||
self.client = Client.objects.create(
|
||||
name="Paperless Corp",
|
||||
email=f"paperless+{uuid.uuid4()}@example.test"
|
||||
)
|
||||
User = get_user_model()
|
||||
# Kreiramo sistemskog korisnika koji već postoji (trebao bi se koristiti)
|
||||
self.system_user = User.objects.create_user(
|
||||
username="bot@erp.hr",
|
||||
email="bot@erp.hr",
|
||||
password="pass"
|
||||
)
|
||||
|
||||
@patch("modules.invoicing.tasks.PaperlessGateway.get_document_metadata")
|
||||
@patch("modules.invoicing.tasks.InvoiceService.create_full_invoice")
|
||||
def test_process_paperless_document_calls_service(self, mock_create_full_invoice, mock_get_metadata):
|
||||
# simuliramo odgovor infrastrukture
|
||||
mock_get_metadata.return_value = {
|
||||
"created": "2026-01-01T00:00:00Z",
|
||||
"correspondent": "Paperless Corp",
|
||||
"total_amount": "100.00"
|
||||
}
|
||||
|
||||
# Pozovemo task sinkrono pomoću .apply (ispravno mapira argumente)
|
||||
tasks.process_paperless_document_task.apply(args=(123,))
|
||||
|
||||
# Očekujemo da je pozvan servis za izradu fakture
|
||||
mock_create_full_invoice.assert_called()
|
||||
28
backend/modules/invoicing/tests/test_utils.py
Normal file
28
backend/modules/invoicing/tests/test_utils.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from django.test import TestCase
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from modules.invoicing.models import Invoice
|
||||
from modules.invoicing.utils import generate_invoice_number
|
||||
from modules.crm.models import Client
|
||||
|
||||
|
||||
class UtilsTests(TestCase):
|
||||
def setUp(self):
|
||||
User = get_user_model()
|
||||
self.user = User.objects.create_user(username="creator", email="c@example.com", password="pass")
|
||||
self.client = Client.objects.create(name="Client Utils")
|
||||
|
||||
def test_generate_invoice_number_increments(self):
|
||||
year = date.today().year
|
||||
Invoice.objects.create(
|
||||
invoice_number=f"INV-{year}-0001",
|
||||
creator=self.user,
|
||||
client=self.client,
|
||||
amount=Decimal("1.00"),
|
||||
due_date=date.today()
|
||||
)
|
||||
next_num = generate_invoice_number()
|
||||
# očekujemo INV-<year>-0002
|
||||
self.assertTrue(next_num.endswith("-0002"))
|
||||
46
backend/modules/invoicing/tests/test_views.py
Normal file
46
backend/modules/invoicing/tests/test_views.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from django.test import TestCase, RequestFactory
|
||||
from django.contrib.auth import get_user_model
|
||||
from unittest.mock import patch
|
||||
|
||||
from modules.invoicing.views import InvoiceTransactionEntryViewSet
|
||||
from modules.invoicing.serializers import InvoiceTransactionSerializer
|
||||
from modules.invoicing.models import InvoiceTransaction
|
||||
from modules.invoicing.models import Invoice
|
||||
from modules.crm.models import Client
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
class ViewsTests(TestCase):
|
||||
def setUp(self):
|
||||
User = get_user_model()
|
||||
self.user = User.objects.create_user(username="u1", email="u1@example.com", password="pass")
|
||||
self.client = Client.objects.create(name="Client Views")
|
||||
self.invoice = Invoice.objects.create(
|
||||
invoice_number="INV-TEST-0001",
|
||||
creator=self.user,
|
||||
client=self.client,
|
||||
amount=Decimal("10.00"),
|
||||
due_date=date.today()
|
||||
)
|
||||
self.factory = RequestFactory()
|
||||
self.viewset = InvoiceTransactionEntryViewSet()
|
||||
|
||||
@patch("modules.invoicing.services.InvoiceService.create_invoice_entry")
|
||||
def test_perform_create_calls_service(self, mock_create_invoice_entry):
|
||||
# simuliramo serializer.validated_data i poziv perform_create
|
||||
serializer = InvoiceTransactionSerializer(data={
|
||||
"invoice": self.invoice.id,
|
||||
"invoice_number": self.invoice.invoice_number,
|
||||
"amount": "5.00",
|
||||
"is_active": True
|
||||
}, context={"request": self.factory.get("/")})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
# Poziv perform_create koji bi trebao delegirati na servis
|
||||
self.viewset.request = self.factory.post("/")
|
||||
self.viewset.request.user = self.user
|
||||
|
||||
# Ako kod poziva InvoiceService.create_invoice_entry, mock se aktivira.
|
||||
self.viewset.perform_create(serializer)
|
||||
mock_create_invoice_entry.assert_called()
|
||||
Reference in New Issue
Block a user