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
from django.contrib.auth import get_user_model
from django.core.management import call_command
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):
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 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
user = get_user_model().objects.create_user(username="no-purchases")
profile = UserProfile.objects.create(user=user)
def test_format_counts_with_last_name():
rows = [{"order__customer__first_name": "Alice", "order__customer__last_name": "Smith", "count": 3}]
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.test import Client
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")
def gql_client_fixture(client: Client):
return GraphQLTestClient(client)
CREATE_DEPOSIT = """
mutation CreateDeposit($userId: ID!, $amount: Int!) {
createDeposit(userId: $userId, amount: $amount) {
... on CreateDepositSuccess {
transaction { id amount }
}
... on AboveMaxSaldo {
maxSaldo
above
}
... on FormErrors {
message
}
}
}
"""
@pytest.fixture(name="product")
def product_fix(db): # noqa: ARG001
return Product.objects.create(name="borg", sale_price_ext=35, sale_price_int=30)
CREATE_USER = """
mutation CreateUser($input: UserInput!) {
createUser(input: $input) {
id
firstName
lastName
}
}
"""
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 products_result.data["allProducts"][0]["active"]
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