Share test fixtures and expand model, query and mutation tests

This commit is contained in:
ponas committed 2026-10-23 02:18:35 +02:00
1 parent 58a415494a
commit 6ae99528dd
5 files changed
+574 -25

No files matched your search

+40
View File
@@ -0,0 +1,40 @@
import pytest
from django.contrib.auth.models import User as DjangoUser
from django.test import Client
from strawberry.django.test import GraphQLTestClient
from bbs.models import Order, OrderLine, Product, Transaction, UserProfile
@pytest.fixture
def product(db): # noqa: ARG001
return Product.objects.create(name="borg", sale_price_ext=35, sale_price_int=30)
@pytest.fixture
def user(db): # noqa: ARG001
u = DjangoUser.objects.create_user(
username="test@example.com",
email="test@example.com",
first_name="Test",
last_name="User",
)
UserProfile.objects.create(user=u, balance=10)
return u
@pytest.fixture
def order(product):
o = Order.objects.create(order_sum=35)
OrderLine.objects.create(order=o, product=product, amount=1, unit_price=35)
return o
@pytest.fixture
def transaction(user):
return Transaction.objects.create(user=user, amount=100)
@pytest.fixture
def gql_client(client: Client):
return GraphQLTestClient(client)
-8
View File
@@ -1,17 +1,9 @@
import pytest import pytest
from django.contrib.auth import get_user_model
from django.core.management import call_command from django.core.management import call_command
from bbs.models import UserProfile from bbs.models import UserProfile
@pytest.fixture
def user(db): # noqa: ARG001
user = get_user_model().objects.create_user(username="profile-image-user", email="image@example.com")
UserProfile.objects.create(user=user)
return user
def test_profile_images_updates_missing_image(user): def test_profile_images_updates_missing_image(user):
UserProfile.objects.filter(pk=user.profile.pk).update(image="") UserProfile.objects.filter(pk=user.profile.pk).update(image="")
+307 -6
View File
@@ -1,12 +1,313 @@
import hashlib
from datetime import UTC, datetime from datetime import UTC, datetime
from decimal import Decimal
from django.contrib.auth import get_user_model import pytest
from django.contrib.auth.models import User as DjangoUser
from bbs.models import UserProfile from bbs.models import (
EXTERNAL_USER,
InventoryTransaction,
Order,
OrderLine,
Product,
UserProfile,
_format_counts,
)
# _format_counts
# ==============
def test_last_purchase_date_without_orders_is_timezone_aware(db): # noqa: ARG001 def test_format_counts_with_last_name():
user = get_user_model().objects.create_user(username="no-purchases") rows = [{"order__customer__first_name": "Alice", "order__customer__last_name": "Smith", "count": 3}]
profile = UserProfile.objects.create(user=user) result = _format_counts(rows)
assert result[0]["name"] == "Alice S"
assert profile.last_purchase_date() == datetime.min.replace(tzinfo=UTC)
def test_format_counts_no_last_name():
rows = [{"order__customer__first_name": "Bob", "order__customer__last_name": "", "count": 1}]
result = _format_counts(rows)
assert result[0]["name"] == "Bob"
def test_format_counts_external():
rows = [{"order__customer__first_name": None, "order__customer__last_name": None, "count": 2}]
result = _format_counts(rows)
assert result[0]["name"] == EXTERNAL_USER
# Product
# =======
def test_product_str(product):
assert str(product) == "borg"
def test_image_url_without_image(product):
assert product.image_url == ""
def test_image_url_with_image(product):
product.image = "products/test.jpg"
assert product.image_url == product.image.url
def test_wholesale_unit_price_no_inventory(product):
product.inventory_amount = 0
assert product.wholesale_unit_price == 0
def test_wholesale_unit_price_no_transactions(product):
product.inventory_amount = 5
assert product.wholesale_unit_price == 0
def test_wholesale_unit_price_within_batch(user, product):
product.inventory_amount = 5
product.save()
InventoryTransaction.objects.create(user=user, product=product, amount=10, unit_price=Decimal("8.0"))
assert product.wholesale_unit_price == Decimal("8.0")
def test_wholesale_unit_price_exceeds_all_batches(user, product):
product.inventory_amount = 20
product.save()
InventoryTransaction.objects.create(user=user, product=product, amount=5, unit_price=Decimal("8.0"))
InventoryTransaction.objects.create(user=user, product=product, amount=5, unit_price=Decimal("6.0"))
# inventory(20) > total transactions(10), falls back to first transaction's unit_price
assert product.wholesale_unit_price == Decimal("8.0")
def test_wholesale_value(user, product):
product.inventory_amount = 5
product.save()
InventoryTransaction.objects.create(user=user, product=product, amount=10, unit_price=Decimal("8.0"))
assert product.wholesale_value == product.wholesale_unit_price * 5
def test_user_counts(user, product):
o = Order.objects.create(customer=user, order_sum=60)
OrderLine.objects.create(order=o, product=product, amount=2, unit_price=30)
counts = product.user_counts()
assert len(counts) == 1
assert counts[0]["name"] == "Test U"
assert counts[0]["count"] == 2
# OrderManager
# ============
def test_count_by_created_period_yearly(order): # noqa: ARG001
qs = list(Order.objects.count_by_created_period("yearly"))
assert len(qs) == 1
assert "period" in qs[0]
def test_count_by_created_period_monthly(order): # noqa: ARG001
qs = list(Order.objects.count_by_created_period("monthly"))
assert len(qs) == 1
def test_count_by_created_period_daily(order): # noqa: ARG001
qs = list(Order.objects.count_by_created_period("daily"))
assert len(qs) == 1
def test_count_by_created_period_hourly(order): # noqa: ARG001
qs = list(Order.objects.count_by_created_period("hourly"))
assert len(qs) == 1
def test_count_by_created_period_invalid():
with pytest.raises(AssertionError):
Order.objects.count_by_created_period("weekly")
# Order
# =====
def test_order_is_external_true(db): # noqa: ARG001
o = Order.objects.create(order_sum=10)
assert o.is_external() is True
def test_order_is_external_false(user):
o = Order.objects.create(customer=user, order_sum=10)
assert o.is_external() is False
def test_order_receipt_single_line(order):
ol = order.orderlines.first()
assert order.reciept() == str(ol)
def test_order_receipt_multiple_lines(product):
product2 = Product.objects.create(name="cola", sale_price_ext=20, sale_price_int=15)
o = Order.objects.create(order_sum=55)
OrderLine.objects.create(order=o, product=product, amount=1, unit_price=35)
OrderLine.objects.create(order=o, product=product2, amount=1, unit_price=20)
receipt = o.reciept()
assert "borg" in receipt
assert "cola" in receipt
assert ", " in receipt
def test_order_str_external(db): # noqa: ARG001
o = Order.objects.create(order_sum=Decimal("15.0"))
assert EXTERNAL_USER in str(o)
assert "15.0" in str(o)
def test_order_str_with_customer(user):
o = Order.objects.create(customer=user, order_sum=Decimal("20.0"))
assert str(user) in str(o)
# OrderLine / OrderLineManager
# ============================
def test_orderline_price(order):
ol = order.orderlines.first()
assert ol.price == ol.amount * ol.unit_price
def test_orderline_str(order):
ol = order.orderlines.first()
assert str(ol) == f"{ol.amount} {ol.product} ({ol.unit_price} kr per)"
def test_product_order_stats(order): # noqa: ARG001
qs = list(OrderLine.objects.all().product_order_stats())
assert len(qs) == 1
assert qs[0]["product_name"] == "borg"
assert qs[0]["count"] == 1
def test_as_series_single_product(order): # noqa: ARG001
qs = OrderLine.objects.all().product_order_stats()
series = qs.as_series()
assert len(series) == 1
assert series[0]["product_name"] == "borg"
assert len(series[0]["data"]) == 1
assert "x" in series[0]["data"][0]
assert "y" in series[0]["data"][0]
def test_as_series_multiple_products(user, product):
product2 = Product.objects.create(name="cola", sale_price_ext=20, sale_price_int=15)
o = Order.objects.create(customer=user, order_sum=55)
OrderLine.objects.create(order=o, product=product, amount=2, unit_price=30)
OrderLine.objects.create(order=o, product=product2, amount=1, unit_price=20)
qs = OrderLine.objects.all().product_order_stats()
series = qs.as_series()
assert {s["product_name"] for s in series} == {"borg", "cola"}
# Transaction
# ===========
def test_transaction_str(transaction, user):
result = str(transaction)
assert str(transaction.id) in result
assert str(user) in result
assert str(transaction.amount) in result
# InventoryTransaction
# ====================
def test_inventory_transaction_price(user, product):
it = InventoryTransaction.objects.create(user=user, product=product, amount=3, unit_price=Decimal("10.0"))
assert it.price == Decimal("30.0")
def test_inventory_transaction_str(user, product):
it = InventoryTransaction.objects.create(user=user, product=product, amount=3, unit_price=Decimal("10.0"))
result = str(it)
assert str(it.id) in result
assert str(user) in result
assert "borg" in result
# UserProfile
# ===========
def test_userprofile_str(user):
assert str(user.profile) == str(user)
def test_last_purchase_none(user):
assert user.profile.last_purchase() is None
def test_last_purchase_returns_latest(user):
Order.objects.create(customer=user, order_sum=10)
o2 = Order.objects.create(customer=user, order_sum=20)
assert user.profile.last_purchase() == o2
def test_last_purchase_date_no_orders(user):
assert user.profile.last_purchase_date() == datetime.min.replace(tzinfo=UTC)
def test_last_purchase_date_with_orders(user):
o = Order.objects.create(customer=user, order_sum=10)
assert user.profile.last_purchase_date() == o.created
def test_top_months(user):
Order.objects.create(customer=user, order_sum=50)
Order.objects.create(customer=user, order_sum=30)
result = list(user.profile.top_months())
assert len(result) == 1
assert result[0]["sum"] == Decimal("80.0")
def test_order_sum_total_no_orders(user):
assert user.profile.order_sum_total() == 0
def test_order_sum_total_with_orders(user):
Order.objects.create(customer=user, order_sum=Decimal("25.0"))
Order.objects.create(customer=user, order_sum=Decimal("15.0"))
assert user.profile.order_sum_total() == Decimal("40.0")
def test_userprofile_product_order_stats(user, product):
o = Order.objects.create(customer=user, order_sum=30)
OrderLine.objects.create(order=o, product=product, amount=1, unit_price=30)
result = list(user.profile.product_order_stats())
assert len(result) == 1
assert result[0]["product_name"] == "borg"
def test_image_url_with_email(user):
expected_hash = hashlib.sha256(user.email.lower().strip().encode()).hexdigest()
assert expected_hash in user.profile.image_url()
def test_image_url_no_email(db): # noqa: ARG001
u = DjangoUser.objects.create_user(username="noemail", email="")
profile = UserProfile.objects.create(user=u, balance=0)
assert profile.image_url() == ""
def test_save_auto_populates_image(db): # noqa: ARG001
u = DjangoUser.objects.create_user(username="auto@test.com", email="auto@test.com")
profile = UserProfile.objects.create(user=u, balance=0)
assert profile.image != ""
assert "gravatar" in profile.image
def test_save_preserves_existing_image(db): # noqa: ARG001
u = DjangoUser.objects.create_user(username="img@test.com", email="img@test.com")
profile = UserProfile.objects.create(user=u, balance=0, image="https://example.com/custom.jpg")
assert profile.image == "https://example.com/custom.jpg"
+164 -11
View File
@@ -1,20 +1,51 @@
import pytest
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.test import Client
from django.utils import timezone from django.utils import timezone
from strawberry.django.test import GraphQLTestClient
from bbs.models import InventoryTransaction, Order, OrderLine, Product, Transaction from bbs.models import InventoryTransaction, Order, OrderLine, Transaction, UserProfile
CREATE_ORDER = """
mutation CreateOrder($customerId: ID!, $isExternal: Boolean!, $orderLines: [OrderLineInput!]!) {
createOrder(customerId: $customerId, isExternal: $isExternal, orderLines: $orderLines) {
... on CreateOrderSuccess {
order { id orderSum orderlines { id product { id } } }
}
... on InsufficientFunds {
amountLacking
error
}
... on FormErrors {
message
}
}
}
"""
@pytest.fixture(name="gql_client") CREATE_DEPOSIT = """
def gql_client_fixture(client: Client): mutation CreateDeposit($userId: ID!, $amount: Int!) {
return GraphQLTestClient(client) createDeposit(userId: $userId, amount: $amount) {
... on CreateDepositSuccess {
transaction { id amount }
}
... on AboveMaxSaldo {
maxSaldo
above
}
... on FormErrors {
message
}
}
}
"""
CREATE_USER = """
@pytest.fixture(name="product") mutation CreateUser($input: UserInput!) {
def product_fix(db): # noqa: ARG001 createUser(input: $input) {
return Product.objects.create(name="borg", sale_price_ext=35, sale_price_int=30) id
firstName
lastName
}
}
"""
def test_user_not_found(db, gql_client): # noqa: ARG001 def test_user_not_found(db, gql_client): # noqa: ARG001
@@ -206,3 +237,125 @@ def test_inventory_report_aggregates_period_data(product, gql_client):
assert not products_result.errors assert not products_result.errors
assert products_result.data["allProducts"][0]["active"] assert products_result.data["allProducts"][0]["active"]
assert products_result.data["inventoryTransactionList"][0]["amount"] == 5 assert products_result.data["inventoryTransactionList"][0]["amount"] == 5
def test_create_order_no_customer(product, gql_client):
res = gql_client.query(
CREATE_ORDER,
{
"customerId": "999",
"isExternal": False,
"orderLines": [{"productId": product.id, "amount": 1}],
},
)
assert not res.errors
assert res.data["createOrder"]["message"]
def test_create_order_empty_lines(product, gql_client): # noqa: ARG001
res = gql_client.query(
CREATE_ORDER,
{
"customerId": "external",
"isExternal": True,
"orderLines": [],
},
)
assert not res.errors
assert res.data["createOrder"]["message"]
def test_create_order_insufficient_funds(user, product, gql_client):
res = gql_client.query(
CREATE_ORDER,
{
"customerId": str(user.id),
"isExternal": False,
"orderLines": [{"productId": product.id, "amount": 1}],
},
)
assert not res.errors
assert res.data["createOrder"]["amountLacking"] == 20
assert res.data["createOrder"]["error"] is True
assert Order.objects.count() == 0
product.refresh_from_db()
assert product.inventory_amount == 0
def test_create_order_internal_success(user, product, gql_client):
user.profile.balance = 100
user.profile.save()
res = gql_client.query(
CREATE_ORDER,
{
"customerId": str(user.id),
"isExternal": False,
"orderLines": [{"productId": product.id, "amount": 1}],
},
)
assert not res.errors
assert res.data["createOrder"]["order"]
user.profile.refresh_from_db()
assert user.profile.balance == 70
product.refresh_from_db()
assert product.inventory_amount == -1
def test_create_order_exact_balance(user, product, gql_client):
user.profile.balance = 30
user.profile.save()
res = gql_client.query(
CREATE_ORDER,
{
"customerId": str(user.id),
"isExternal": False,
"orderLines": [{"productId": product.id, "amount": 1}],
},
)
assert not res.errors
assert res.data["createOrder"]["order"]
user.profile.refresh_from_db()
assert user.profile.balance == 0
def test_create_deposit(user, gql_client):
res = gql_client.query(CREATE_DEPOSIT, {"userId": str(user.id), "amount": 100})
assert not res.errors
assert res.data["createDeposit"]["transaction"]
assert Transaction.objects.count() == 1
user.profile.refresh_from_db()
assert user.profile.balance == 110
def test_create_deposit_invalid_user(db, gql_client): # noqa: ARG001
res = gql_client.query(CREATE_DEPOSIT, {"userId": "999", "amount": 100})
assert not res.errors
assert res.data["createDeposit"]["message"]
def test_create_deposit_invalid_amount(user, gql_client):
res = gql_client.query(CREATE_DEPOSIT, {"userId": str(user.id), "amount": 0})
assert not res.errors
assert res.data["createDeposit"]["message"]
def test_create_deposit_above_max_saldo(user, gql_client):
res = gql_client.query(CREATE_DEPOSIT, {"userId": str(user.id), "amount": 1000})
assert not res.errors
assert res.data["createDeposit"]["maxSaldo"] is not None
assert res.data["createDeposit"]["above"] == 10
assert Transaction.objects.count() == 0
def test_create_deposit_1337(user, gql_client):
# balance=10, so amount=1327 makes balance+amount==1337, bypassing max saldo
res = gql_client.query(CREATE_DEPOSIT, {"userId": str(user.id), "amount": 1327})
assert not res.errors
assert res.data["createDeposit"]["transaction"]
def test_create_user(db, gql_client): # noqa: ARG001
res = gql_client.query(CREATE_USER, {"input": {"email": "new@example.com", "firstName": "New", "lastName": "User"}})
assert not res.errors
assert get_user_model().objects.filter(username="new@example.com").exists()
assert UserProfile.objects.count() == 1
+63
View File
@@ -0,0 +1,63 @@
ALL_USERS = "query { allUsers { id firstName lastName } }"
USER = "query User($userId: ID!) { user(userId: $userId) { id firstName lastName } }"
USER_PROFILE = "query User($userId: ID!) { user(userId: $userId) { profile { balance topMonths { period count sum } productOrderStats { productName data { x y } } } } }"
ALL_PRODUCTS = "query { allProducts { id name } }"
ALL_ORDERS = "query { allOrders { id orderSum } }"
ORDER_LIST = "query { orderList { id orderSum } }"
ORDER_STATS = "query { orderStats { yearly { period count } monthly { period count } daily { period count } hourly { period count } } }"
PRODUCT_STATS = "query { productStats { productName data { x y } } }"
TRANSACTION_LIST = "query { transactionList { id amount } }"
def test_user(user, gql_client):
res = gql_client.query(USER, {"userId": str(user.id)})
assert not res.errors
assert res.data["user"]["id"] == str(user.id)
def test_all_users(user, gql_client): # noqa: ARG001
res = gql_client.query(ALL_USERS)
assert not res.errors
assert len(res.data["allUsers"]) == 1
def test_all_products(product, gql_client): # noqa: ARG001
res = gql_client.query(ALL_PRODUCTS)
assert not res.errors
assert len(res.data["allProducts"]) == 1
def test_all_orders(order, gql_client): # noqa: ARG001
res = gql_client.query(ALL_ORDERS)
assert not res.errors
assert len(res.data["allOrders"]) == 1
def test_order_list(order, gql_client): # noqa: ARG001
res = gql_client.query(ORDER_LIST)
assert not res.errors
assert len(res.data["orderList"]) == 1
def test_order_stats(db, gql_client): # noqa: ARG001
res = gql_client.query(ORDER_STATS)
assert not res.errors
assert "daily" in res.data["orderStats"]
def test_product_stats(order, gql_client): # noqa: ARG001
res = gql_client.query(PRODUCT_STATS)
assert not res.errors
assert res.data["productStats"]
def test_transaction_list(transaction, gql_client): # noqa: ARG001
res = gql_client.query(TRANSACTION_LIST)
assert not res.errors
assert len(res.data["transactionList"]) == 1
def test_user_profile(user, gql_client):
res = gql_client.query(USER_PROFILE, {"userId": str(user.id)})
assert not res.errors
assert res.data["user"]["profile"]["balance"] is not None