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)