mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-01-11 20:00:26 +01:00
588 lines
22 KiB
Python
588 lines
22 KiB
Python
from datetime import date
|
|
from decimal import Decimal
|
|
|
|
from django.contrib.auth import get_user_model
|
|
from django.test import TestCase, override_settings
|
|
from rest_framework import status
|
|
from rest_framework.test import APIClient
|
|
|
|
from apps.accounts.models import Account, AccountGroup
|
|
from apps.currencies.models import Currency
|
|
from apps.dca.models import DCAStrategy, DCAEntry
|
|
from apps.transactions.models import (
|
|
Transaction,
|
|
TransactionCategory,
|
|
TransactionTag,
|
|
TransactionEntity,
|
|
)
|
|
|
|
|
|
ACCESS_DENIED_CODES = [status.HTTP_403_FORBIDDEN, status.HTTP_404_NOT_FOUND]
|
|
|
|
|
|
@override_settings(
|
|
STORAGES={
|
|
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
|
|
"staticfiles": {
|
|
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage"
|
|
},
|
|
},
|
|
WHITENOISE_AUTOREFRESH=True,
|
|
)
|
|
class SharedAccountAccessTests(TestCase):
|
|
"""Tests for shared account access via shared_with field."""
|
|
|
|
def setUp(self):
|
|
"""Set up test data with shared accounts."""
|
|
User = get_user_model()
|
|
|
|
# User 1 - owner
|
|
self.user1 = User.objects.create_user(
|
|
email="user1@test.com", password="testpass123"
|
|
)
|
|
self.client1 = APIClient()
|
|
self.client1.force_authenticate(user=self.user1)
|
|
|
|
# User 2 - will have shared access
|
|
self.user2 = User.objects.create_user(
|
|
email="user2@test.com", password="testpass123"
|
|
)
|
|
self.client2 = APIClient()
|
|
self.client2.force_authenticate(user=self.user2)
|
|
|
|
# User 3 - no shared access
|
|
self.user3 = User.objects.create_user(
|
|
email="user3@test.com", password="testpass123"
|
|
)
|
|
self.client3 = APIClient()
|
|
self.client3.force_authenticate(user=self.user3)
|
|
|
|
self.currency = Currency.objects.create(
|
|
code="USD", name="US Dollar", decimal_places=2, prefix="$ "
|
|
)
|
|
|
|
# User 1's account shared with user 2
|
|
self.shared_account = Account.all_objects.create(
|
|
name="Shared Account",
|
|
currency=self.currency,
|
|
owner=self.user1,
|
|
visibility="private",
|
|
)
|
|
self.shared_account.shared_with.add(self.user2)
|
|
|
|
# User 1's private account (not shared)
|
|
self.private_account = Account.all_objects.create(
|
|
name="Private Account",
|
|
currency=self.currency,
|
|
owner=self.user1,
|
|
visibility="private",
|
|
)
|
|
|
|
# Transaction in shared account
|
|
self.shared_transaction = Transaction.userless_all_objects.create(
|
|
account=self.shared_account,
|
|
type=Transaction.Type.INCOME,
|
|
amount=Decimal("100.00"),
|
|
is_paid=True,
|
|
date=date(2025, 1, 1),
|
|
description="Shared Transaction",
|
|
owner=self.user1,
|
|
)
|
|
|
|
# Transaction in private account
|
|
self.private_transaction = Transaction.userless_all_objects.create(
|
|
account=self.private_account,
|
|
type=Transaction.Type.EXPENSE,
|
|
amount=Decimal("50.00"),
|
|
is_paid=True,
|
|
date=date(2025, 1, 1),
|
|
description="Private Transaction",
|
|
owner=self.user1,
|
|
)
|
|
|
|
def test_user_can_see_accounts_shared_with_them(self):
|
|
"""User2 should see the account shared with them."""
|
|
response = self.client2.get("/api/accounts/")
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
account_ids = [acc["id"] for acc in response.data["results"]]
|
|
self.assertIn(self.shared_account.id, account_ids)
|
|
|
|
def test_user_cannot_see_accounts_not_shared_with_them(self):
|
|
"""User2 should NOT see user1's private (non-shared) account."""
|
|
response = self.client2.get("/api/accounts/")
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
account_ids = [acc["id"] for acc in response.data["results"]]
|
|
self.assertNotIn(self.private_account.id, account_ids)
|
|
|
|
def test_user_can_access_shared_account_detail(self):
|
|
"""User2 should be able to access shared account details."""
|
|
response = self.client2.get(f"/api/accounts/{self.shared_account.id}/")
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
self.assertEqual(response.data["name"], "Shared Account")
|
|
|
|
def test_user_without_share_cannot_access_shared_account(self):
|
|
"""User3 should NOT be able to access the shared account."""
|
|
response = self.client3.get(f"/api/accounts/{self.shared_account.id}/")
|
|
|
|
self.assertIn(response.status_code, ACCESS_DENIED_CODES)
|
|
|
|
def test_user_can_see_transactions_in_shared_account(self):
|
|
"""User2 should see transactions in the shared account."""
|
|
response = self.client2.get("/api/transactions/")
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
transaction_ids = [t["id"] for t in response.data["results"]]
|
|
self.assertIn(self.shared_transaction.id, transaction_ids)
|
|
self.assertNotIn(self.private_transaction.id, transaction_ids)
|
|
|
|
def test_user_can_access_transaction_in_shared_account(self):
|
|
"""User2 should be able to access transaction details in shared account."""
|
|
response = self.client2.get(f"/api/transactions/{self.shared_transaction.id}/")
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
self.assertEqual(response.data["description"], "Shared Transaction")
|
|
|
|
def test_user_cannot_access_transaction_in_non_shared_account(self):
|
|
"""User2 should NOT access transactions in user1's private account."""
|
|
response = self.client2.get(f"/api/transactions/{self.private_transaction.id}/")
|
|
|
|
self.assertIn(response.status_code, ACCESS_DENIED_CODES)
|
|
|
|
def test_user_can_get_balance_of_shared_account(self):
|
|
"""User2 should be able to get balance of shared account."""
|
|
response = self.client2.get(f"/api/accounts/{self.shared_account.id}/balance/")
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
self.assertIn("current_balance", response.data)
|
|
|
|
def test_sharing_works_with_multiple_users(self):
|
|
"""Account shared with multiple users should be accessible by all."""
|
|
# Add user3 to shared_with
|
|
self.shared_account.shared_with.add(self.user3)
|
|
|
|
# User2 still has access
|
|
response2 = self.client2.get(f"/api/accounts/{self.shared_account.id}/")
|
|
self.assertEqual(response2.status_code, status.HTTP_200_OK)
|
|
|
|
# User3 now has access
|
|
response3 = self.client3.get(f"/api/accounts/{self.shared_account.id}/")
|
|
self.assertEqual(response3.status_code, status.HTTP_200_OK)
|
|
|
|
|
|
@override_settings(
|
|
STORAGES={
|
|
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
|
|
"staticfiles": {
|
|
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage"
|
|
},
|
|
},
|
|
WHITENOISE_AUTOREFRESH=True,
|
|
)
|
|
class PublicVisibilityTests(TestCase):
|
|
"""Tests for public visibility access."""
|
|
|
|
def setUp(self):
|
|
"""Set up test data with public accounts."""
|
|
User = get_user_model()
|
|
|
|
self.user1 = User.objects.create_user(
|
|
email="user1@test.com", password="testpass123"
|
|
)
|
|
self.client1 = APIClient()
|
|
self.client1.force_authenticate(user=self.user1)
|
|
|
|
self.user2 = User.objects.create_user(
|
|
email="user2@test.com", password="testpass123"
|
|
)
|
|
self.client2 = APIClient()
|
|
self.client2.force_authenticate(user=self.user2)
|
|
|
|
self.currency = Currency.objects.create(
|
|
code="USD", name="US Dollar", decimal_places=2, prefix="$ "
|
|
)
|
|
|
|
# User 1's public account
|
|
self.public_account = Account.all_objects.create(
|
|
name="Public Account",
|
|
currency=self.currency,
|
|
owner=self.user1,
|
|
visibility="public",
|
|
)
|
|
|
|
# User 1's private account
|
|
self.private_account = Account.all_objects.create(
|
|
name="Private Account",
|
|
currency=self.currency,
|
|
owner=self.user1,
|
|
visibility="private",
|
|
)
|
|
|
|
# Transaction in public account
|
|
self.public_transaction = Transaction.userless_all_objects.create(
|
|
account=self.public_account,
|
|
type=Transaction.Type.INCOME,
|
|
amount=Decimal("100.00"),
|
|
is_paid=True,
|
|
date=date(2025, 1, 1),
|
|
description="Public Transaction",
|
|
owner=self.user1,
|
|
)
|
|
|
|
def test_user_can_see_public_accounts(self):
|
|
"""User2 should see user1's public account."""
|
|
response = self.client2.get("/api/accounts/")
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
account_ids = [acc["id"] for acc in response.data["results"]]
|
|
self.assertIn(self.public_account.id, account_ids)
|
|
self.assertNotIn(self.private_account.id, account_ids)
|
|
|
|
def test_user_can_access_public_account_detail(self):
|
|
"""User2 should be able to access public account details."""
|
|
response = self.client2.get(f"/api/accounts/{self.public_account.id}/")
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
self.assertEqual(response.data["name"], "Public Account")
|
|
|
|
def test_user_can_see_transactions_in_public_accounts(self):
|
|
"""User2 should see transactions in public accounts."""
|
|
response = self.client2.get("/api/transactions/")
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
transaction_ids = [t["id"] for t in response.data["results"]]
|
|
self.assertIn(self.public_transaction.id, transaction_ids)
|
|
|
|
|
|
@override_settings(
|
|
STORAGES={
|
|
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
|
|
"staticfiles": {
|
|
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage"
|
|
},
|
|
},
|
|
WHITENOISE_AUTOREFRESH=True,
|
|
)
|
|
class SharedCategoryTagEntityTests(TestCase):
|
|
"""Tests for shared categories, tags, and entities."""
|
|
|
|
def setUp(self):
|
|
"""Set up test data with shared categories/tags/entities."""
|
|
User = get_user_model()
|
|
|
|
self.user1 = User.objects.create_user(
|
|
email="user1@test.com", password="testpass123"
|
|
)
|
|
self.client1 = APIClient()
|
|
self.client1.force_authenticate(user=self.user1)
|
|
|
|
self.user2 = User.objects.create_user(
|
|
email="user2@test.com", password="testpass123"
|
|
)
|
|
self.client2 = APIClient()
|
|
self.client2.force_authenticate(user=self.user2)
|
|
|
|
self.user3 = User.objects.create_user(
|
|
email="user3@test.com", password="testpass123"
|
|
)
|
|
self.client3 = APIClient()
|
|
self.client3.force_authenticate(user=self.user3)
|
|
|
|
# User 1's category shared with user 2
|
|
self.shared_category = TransactionCategory.all_objects.create(
|
|
name="Shared Category", owner=self.user1
|
|
)
|
|
self.shared_category.shared_with.add(self.user2)
|
|
|
|
# User 1's private category
|
|
self.private_category = TransactionCategory.all_objects.create(
|
|
name="Private Category", owner=self.user1
|
|
)
|
|
|
|
# User 1's public category
|
|
self.public_category = TransactionCategory.all_objects.create(
|
|
name="Public Category", owner=self.user1, visibility="public"
|
|
)
|
|
|
|
# User 1's tag shared with user 2
|
|
self.shared_tag = TransactionTag.all_objects.create(
|
|
name="Shared Tag", owner=self.user1
|
|
)
|
|
self.shared_tag.shared_with.add(self.user2)
|
|
|
|
# User 1's entity shared with user 2
|
|
self.shared_entity = TransactionEntity.all_objects.create(
|
|
name="Shared Entity", owner=self.user1
|
|
)
|
|
self.shared_entity.shared_with.add(self.user2)
|
|
|
|
def test_user_can_see_shared_categories(self):
|
|
"""User2 should see categories shared with them."""
|
|
response = self.client2.get("/api/categories/")
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
category_ids = [c["id"] for c in response.data["results"]]
|
|
self.assertIn(self.shared_category.id, category_ids)
|
|
self.assertNotIn(self.private_category.id, category_ids)
|
|
|
|
def test_user_can_access_shared_category_detail(self):
|
|
"""User2 should be able to access shared category details."""
|
|
response = self.client2.get(f"/api/categories/{self.shared_category.id}/")
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
self.assertEqual(response.data["name"], "Shared Category")
|
|
|
|
def test_user_can_see_public_categories(self):
|
|
"""User3 should see public categories."""
|
|
response = self.client3.get("/api/categories/")
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
category_ids = [c["id"] for c in response.data["results"]]
|
|
self.assertIn(self.public_category.id, category_ids)
|
|
|
|
def test_user_without_share_cannot_see_shared_category(self):
|
|
"""User3 should NOT see category shared only with user2."""
|
|
response = self.client3.get("/api/categories/")
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
category_ids = [c["id"] for c in response.data["results"]]
|
|
self.assertNotIn(self.shared_category.id, category_ids)
|
|
|
|
def test_user_can_see_shared_tags(self):
|
|
"""User2 should see tags shared with them."""
|
|
response = self.client2.get("/api/tags/")
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
tag_ids = [t["id"] for t in response.data["results"]]
|
|
self.assertIn(self.shared_tag.id, tag_ids)
|
|
|
|
def test_user_can_access_shared_tag_detail(self):
|
|
"""User2 should be able to access shared tag details."""
|
|
response = self.client2.get(f"/api/tags/{self.shared_tag.id}/")
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
self.assertEqual(response.data["name"], "Shared Tag")
|
|
|
|
def test_user_can_see_shared_entities(self):
|
|
"""User2 should see entities shared with them."""
|
|
response = self.client2.get("/api/entities/")
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
entity_ids = [e["id"] for e in response.data["results"]]
|
|
self.assertIn(self.shared_entity.id, entity_ids)
|
|
|
|
def test_user_can_access_shared_entity_detail(self):
|
|
"""User2 should be able to access shared entity details."""
|
|
response = self.client2.get(f"/api/entities/{self.shared_entity.id}/")
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
self.assertEqual(response.data["name"], "Shared Entity")
|
|
|
|
|
|
@override_settings(
|
|
STORAGES={
|
|
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
|
|
"staticfiles": {
|
|
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage"
|
|
},
|
|
},
|
|
WHITENOISE_AUTOREFRESH=True,
|
|
)
|
|
class SharedDCAAccessTests(TestCase):
|
|
"""Tests for shared DCA strategy access."""
|
|
|
|
def setUp(self):
|
|
"""Set up test data with shared DCA strategies."""
|
|
User = get_user_model()
|
|
|
|
self.user1 = User.objects.create_user(
|
|
email="user1@test.com", password="testpass123"
|
|
)
|
|
self.client1 = APIClient()
|
|
self.client1.force_authenticate(user=self.user1)
|
|
|
|
self.user2 = User.objects.create_user(
|
|
email="user2@test.com", password="testpass123"
|
|
)
|
|
self.client2 = APIClient()
|
|
self.client2.force_authenticate(user=self.user2)
|
|
|
|
self.user3 = User.objects.create_user(
|
|
email="user3@test.com", password="testpass123"
|
|
)
|
|
self.client3 = APIClient()
|
|
self.client3.force_authenticate(user=self.user3)
|
|
|
|
self.currency1 = Currency.objects.create(
|
|
code="BTC", name="Bitcoin", decimal_places=8, prefix=""
|
|
)
|
|
self.currency2 = Currency.objects.create(
|
|
code="USD", name="US Dollar", decimal_places=2, prefix="$ "
|
|
)
|
|
|
|
# User 1's DCA strategy shared with user 2
|
|
self.shared_strategy = DCAStrategy.all_objects.create(
|
|
name="Shared BTC Strategy",
|
|
target_currency=self.currency1,
|
|
payment_currency=self.currency2,
|
|
owner=self.user1,
|
|
)
|
|
self.shared_strategy.shared_with.add(self.user2)
|
|
|
|
# Entry in shared strategy
|
|
self.shared_entry = DCAEntry.objects.create(
|
|
strategy=self.shared_strategy,
|
|
date=date(2025, 1, 1),
|
|
amount_paid=Decimal("100.00"),
|
|
amount_received=Decimal("0.001"),
|
|
)
|
|
|
|
# User 1's private strategy
|
|
self.private_strategy = DCAStrategy.all_objects.create(
|
|
name="Private BTC Strategy",
|
|
target_currency=self.currency1,
|
|
payment_currency=self.currency2,
|
|
owner=self.user1,
|
|
)
|
|
|
|
def test_user_can_see_shared_dca_strategies(self):
|
|
"""User2 should see DCA strategies shared with them."""
|
|
response = self.client2.get("/api/dca/strategies/")
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
strategy_ids = [s["id"] for s in response.data["results"]]
|
|
self.assertIn(self.shared_strategy.id, strategy_ids)
|
|
self.assertNotIn(self.private_strategy.id, strategy_ids)
|
|
|
|
def test_user_can_access_shared_dca_strategy_detail(self):
|
|
"""User2 should be able to access shared strategy details."""
|
|
response = self.client2.get(f"/api/dca/strategies/{self.shared_strategy.id}/")
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
self.assertEqual(response.data["name"], "Shared BTC Strategy")
|
|
|
|
def test_user_without_share_cannot_see_shared_strategy(self):
|
|
"""User3 should NOT see strategy shared only with user2."""
|
|
response = self.client3.get("/api/dca/strategies/")
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
strategy_ids = [s["id"] for s in response.data["results"]]
|
|
self.assertNotIn(self.shared_strategy.id, strategy_ids)
|
|
|
|
def test_user_can_access_shared_strategy_actions(self):
|
|
"""User2 should be able to access actions on shared strategy."""
|
|
# investment_frequency
|
|
response1 = self.client2.get(
|
|
f"/api/dca/strategies/{self.shared_strategy.id}/investment_frequency/"
|
|
)
|
|
self.assertEqual(response1.status_code, status.HTTP_200_OK)
|
|
|
|
# price_comparison
|
|
response2 = self.client2.get(
|
|
f"/api/dca/strategies/{self.shared_strategy.id}/price_comparison/"
|
|
)
|
|
self.assertEqual(response2.status_code, status.HTTP_200_OK)
|
|
|
|
# current_price
|
|
response3 = self.client2.get(
|
|
f"/api/dca/strategies/{self.shared_strategy.id}/current_price/"
|
|
)
|
|
self.assertEqual(response3.status_code, status.HTTP_200_OK)
|
|
|
|
|
|
@override_settings(
|
|
STORAGES={
|
|
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
|
|
"staticfiles": {
|
|
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage"
|
|
},
|
|
},
|
|
WHITENOISE_AUTOREFRESH=True,
|
|
)
|
|
class SharedAccountGroupTests(TestCase):
|
|
"""Tests for shared account group access."""
|
|
|
|
def setUp(self):
|
|
"""Set up test data with shared account groups."""
|
|
User = get_user_model()
|
|
|
|
self.user1 = User.objects.create_user(
|
|
email="user1@test.com", password="testpass123"
|
|
)
|
|
self.client1 = APIClient()
|
|
self.client1.force_authenticate(user=self.user1)
|
|
|
|
self.user2 = User.objects.create_user(
|
|
email="user2@test.com", password="testpass123"
|
|
)
|
|
self.client2 = APIClient()
|
|
self.client2.force_authenticate(user=self.user2)
|
|
|
|
self.user3 = User.objects.create_user(
|
|
email="user3@test.com", password="testpass123"
|
|
)
|
|
self.client3 = APIClient()
|
|
self.client3.force_authenticate(user=self.user3)
|
|
|
|
# User 1's account group shared with user 2
|
|
self.shared_group = AccountGroup.all_objects.create(
|
|
name="Shared Group", owner=self.user1
|
|
)
|
|
self.shared_group.shared_with.add(self.user2)
|
|
|
|
# User 1's private account group
|
|
self.private_group = AccountGroup.all_objects.create(
|
|
name="Private Group", owner=self.user1
|
|
)
|
|
|
|
# User 1's public account group
|
|
self.public_group = AccountGroup.all_objects.create(
|
|
name="Public Group", owner=self.user1, visibility="public"
|
|
)
|
|
|
|
def test_user_can_see_shared_account_groups(self):
|
|
"""User2 should see account groups shared with them."""
|
|
response = self.client2.get("/api/account-groups/")
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
group_ids = [g["id"] for g in response.data["results"]]
|
|
self.assertIn(self.shared_group.id, group_ids)
|
|
self.assertNotIn(self.private_group.id, group_ids)
|
|
|
|
def test_user_can_access_shared_account_group_detail(self):
|
|
"""User2 should be able to access shared account group details."""
|
|
response = self.client2.get(f"/api/account-groups/{self.shared_group.id}/")
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
self.assertEqual(response.data["name"], "Shared Group")
|
|
|
|
def test_user_can_see_public_account_groups(self):
|
|
"""User3 should see public account groups."""
|
|
response = self.client3.get("/api/account-groups/")
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
group_ids = [g["id"] for g in response.data["results"]]
|
|
self.assertIn(self.public_group.id, group_ids)
|
|
|
|
def test_user_without_share_cannot_access_shared_group(self):
|
|
"""User3 should NOT be able to access shared account group."""
|
|
response = self.client3.get(f"/api/account-groups/{self.shared_group.id}/")
|
|
|
|
self.assertIn(response.status_code, ACCESS_DENIED_CODES)
|