"""Questions, articles and flashcard decks all file under one category tree. Run: DATABASE_URL=sqlite:///:memory: PYTHONPATH=backend python -m unittest discover -s backend/tests """ import os os.environ["DATABASE_URL"] = "sqlite:///:memory:" import unittest from fastapi import FastAPI from fastapi.testclient import TestClient from sqlalchemy import create_engine from sqlalchemy.orm import Session from sqlalchemy.pool import StaticPool from app.database import Base, get_db from app.models.article import Article from app.models.flashcard import FlashcardDeck from app.models.question import Question from app.models.question_category import QuestionCategory from app.models.user import User from app.routers import flashcards from app.utils.auth import get_current_user class SharedCategoryTests(unittest.TestCase): def setUp(self): self.engine = create_engine("sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool) Base.metadata.create_all(self.engine) self.db = Session(self.engine) self.user = User(id=1, name="Owner", email="owner@example.test", hashed_password="unused") self.db.add(self.user) self.db.add_all([ QuestionCategory(id=1, name="Cardiology", user_id=1), QuestionCategory(id=2, name="Kawasaki disease", parent_id=1, user_id=1), QuestionCategory(id=3, name="Neurology", user_id=1), ]) self.db.flush() # One of each content type filed under the same tree. self.db.add(Question(id=1, question_category_id=2, user_id=1, question_text="A question", question_type="mcq", options=["yes", "no"], correct_answer="yes")) self.db.add(Article(id=1, slug="kawasaki", title="Kawasaki disease", sections=[], category_id=2, user_id=1, status="published")) self.db.add_all([ FlashcardDeck(id=1, title="Kawasaki cards", user_id=1, card_count=0, category_id=2), FlashcardDeck(id=2, title="Neuro cards", user_id=1, card_count=0, category_id=3), FlashcardDeck(id=3, title="Unfiled cards", user_id=1, card_count=0), ]) self.db.commit() app = FastAPI() app.include_router(flashcards.router, prefix="/flashcards") app.dependency_overrides[get_db] = lambda: self.db app.dependency_overrides[get_current_user] = lambda: self.user self.client = TestClient(app) def tearDown(self): self.client.close() self.db.close() self.engine.dispose() def test_all_three_content_types_use_the_same_category_ids(self): question = self.db.get(Question, 1) article = self.db.get(Article, 1) deck = self.db.get(FlashcardDeck, 1) self.assertEqual({question.question_category_id, article.category_id, deck.category_id}, {2}) def test_deck_list_filters_by_category_including_descendants(self): titles = lambda params: {d["title"] for d in self.client.get("/flashcards/", params=params).json()} self.assertEqual(titles({"category_id": 2}), {"Kawasaki cards"}) # A parent picks up everything filed beneath it. self.assertEqual(titles({"category_id": 1}), {"Kawasaki cards"}) self.assertEqual(titles({"category_id": 3}), {"Neuro cards"}) self.assertEqual(titles({}), {"Kawasaki cards", "Neuro cards", "Unfiled cards"}) def test_a_deck_can_be_filed_and_unfiled(self): response = self.client.patch("/flashcards/3", json={"category_id": 3}) self.assertEqual(response.status_code, 200, response.text) self.assertEqual(response.json()["category_id"], 3) self.assertEqual(self.db.get(FlashcardDeck, 3).category_id, 3) self.assertEqual(self.client.patch("/flashcards/3", json={"category_id": None}).json()["category_id"], None) def test_filing_under_a_category_that_does_not_exist_is_refused(self): self.assertEqual(self.client.patch("/flashcards/1", json={"category_id": 999}).status_code, 404) self.assertEqual(self.db.get(FlashcardDeck, 1).category_id, 2) def test_renaming_a_deck_leaves_its_category_alone(self): self.client.patch("/flashcards/1", json={"title": "Renamed"}) deck = self.db.get(FlashcardDeck, 1) self.assertEqual((deck.title, deck.category_id), ("Renamed", 2)) if __name__ == "__main__": unittest.main()