- Remove user ForeignKey from all 7 models (single-user app) - Update all views, handlers, forms, admin, API, seed, and tests - Add MonthlyGoal model with goals and promises sections - Goals/promises: add, toggle (strikethrough), delete via LiveView
163 lines
4.5 KiB
Python
163 lines
4.5 KiB
Python
import os
|
|
|
|
from django.test import TestCase, override_settings
|
|
from rest_framework.test import APIClient
|
|
|
|
from app.expenses.models import Category, Subcategory
|
|
|
|
|
|
TEST_TOKEN = "test-token-123"
|
|
|
|
|
|
@override_settings()
|
|
class APITestCase(TestCase):
|
|
def setUp(self):
|
|
os.environ["API_TOKEN"] = TEST_TOKEN
|
|
self.client = APIClient()
|
|
self.category = Category.objects.create(name="Survival", order=1)
|
|
self.subcategory = Subcategory.objects.create(
|
|
name="Food", order=1, category=self.category
|
|
)
|
|
self.other_sub = Subcategory.objects.create(
|
|
name="Other", order=99, category=self.category
|
|
)
|
|
|
|
def tearDown(self):
|
|
if "API_TOKEN" in os.environ:
|
|
del os.environ["API_TOKEN"]
|
|
|
|
def auth_headers(self):
|
|
return {"HTTP_AUTHORIZATION": f"Bearer {TEST_TOKEN}"}
|
|
|
|
# --- Authentication ---
|
|
|
|
def test_no_token_returns_403(self):
|
|
response = self.client.get("/api/categories/")
|
|
self.assertEqual(response.status_code, 403)
|
|
|
|
def test_invalid_token_returns_403(self):
|
|
response = self.client.get(
|
|
"/api/categories/", HTTP_AUTHORIZATION="Bearer wrong-token"
|
|
)
|
|
self.assertEqual(response.status_code, 403)
|
|
|
|
def test_valid_token_returns_200(self):
|
|
response = self.client.get("/api/categories/", **self.auth_headers())
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
# --- Root ---
|
|
|
|
def test_root_returns_links(self):
|
|
response = self.client.get("/api/", **self.auth_headers())
|
|
self.assertEqual(response.status_code, 200)
|
|
data = response.json()
|
|
self.assertEqual(data["type"], "Success")
|
|
self.assertIn("categories", data["_links"])
|
|
self.assertIn("expenses", data["_links"])
|
|
|
|
# --- Categories ---
|
|
|
|
def test_get_categories(self):
|
|
response = self.client.get("/api/categories/", **self.auth_headers())
|
|
self.assertEqual(response.status_code, 200)
|
|
data = response.json()
|
|
self.assertEqual(data["type"], "Success")
|
|
self.assertEqual(len(data["data"]), 1)
|
|
self.assertEqual(data["data"][0]["name"], "Survival")
|
|
self.assertIn("subcategories", data["data"][0])
|
|
self.assertEqual(len(data["data"][0]["subcategories"]), 2)
|
|
self.assertIn("_links", data)
|
|
|
|
# --- Subcategories ---
|
|
|
|
def test_get_subcategories(self):
|
|
response = self.client.get(
|
|
f"/api/categories/{self.category.id}/subcategories/",
|
|
**self.auth_headers(),
|
|
)
|
|
self.assertEqual(response.status_code, 200)
|
|
data = response.json()
|
|
self.assertEqual(data["type"], "Success")
|
|
self.assertEqual(len(data["data"]), 2)
|
|
self.assertIn("_links", data)
|
|
|
|
def test_get_subcategories_invalid_category(self):
|
|
response = self.client.get(
|
|
"/api/categories/9999/subcategories/",
|
|
**self.auth_headers(),
|
|
)
|
|
self.assertEqual(response.status_code, 404)
|
|
data = response.json()
|
|
self.assertEqual(data["type"], "ResourceError")
|
|
|
|
# --- Expenses ---
|
|
|
|
def test_create_expense(self):
|
|
response = self.client.post(
|
|
"/api/expenses/",
|
|
{
|
|
"concept": "Groceries",
|
|
"amount": "42.50",
|
|
"category": self.category.id,
|
|
"subcategory": self.subcategory.id,
|
|
},
|
|
format="json",
|
|
**self.auth_headers(),
|
|
)
|
|
self.assertEqual(response.status_code, 201)
|
|
data = response.json()
|
|
self.assertEqual(data["type"], "Success")
|
|
self.assertEqual(data["data"]["concept"], "Groceries")
|
|
self.assertEqual(data["data"]["amount"], "42.50")
|
|
self.assertIn("_links", data)
|
|
|
|
def test_create_expense_missing_fields(self):
|
|
response = self.client.post(
|
|
"/api/expenses/",
|
|
{"concept": "Test"},
|
|
format="json",
|
|
**self.auth_headers(),
|
|
)
|
|
self.assertEqual(response.status_code, 400)
|
|
data = response.json()
|
|
self.assertEqual(data["type"], "ParametersError")
|
|
self.assertTrue(len(data["errors"]) > 0)
|
|
|
|
def test_create_expense_mismatched_subcategory(self):
|
|
other_cat = Category.objects.create(name="Culture", order=2)
|
|
other_sub = Subcategory.objects.create(
|
|
name="Books", order=1, category=other_cat
|
|
)
|
|
response = self.client.post(
|
|
"/api/expenses/",
|
|
{
|
|
"concept": "Wrong combo",
|
|
"amount": "10.00",
|
|
"category": self.category.id,
|
|
"subcategory": other_sub.id,
|
|
},
|
|
format="json",
|
|
**self.auth_headers(),
|
|
)
|
|
self.assertEqual(response.status_code, 400)
|
|
data = response.json()
|
|
self.assertEqual(data["type"], "ParametersError")
|
|
|
|
def test_create_expense_invalid_amount(self):
|
|
response = self.client.post(
|
|
"/api/expenses/",
|
|
{
|
|
"concept": "Test",
|
|
"amount": "not-a-number",
|
|
"category": self.category.id,
|
|
"subcategory": self.subcategory.id,
|
|
},
|
|
format="json",
|
|
**self.auth_headers(),
|
|
)
|
|
self.assertEqual(response.status_code, 400)
|
|
|
|
def test_expenses_get_not_allowed(self):
|
|
response = self.client.get("/api/expenses/", **self.auth_headers())
|
|
self.assertEqual(response.status_code, 405)
|