Coverage for src/app/modules/decks/services.py: 100%

62 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-06-24 08:34 +0000

1import logging 

2 

3from app.modules.decks.repository import DeckRepository 

4 

5logger = logging.getLogger(__name__) 

6 

7 

8class DeckService: 

9 def __init__(self, repo: DeckRepository): 

10 self.repo = repo 

11 

12 def create_deck(self, name: str): 

13 logger.debug("create_deck called: name=%s", name) 

14 

15 if not name.strip(): 

16 logger.warning("create_deck validation failed: empty name") 

17 raise ValueError("Deck name cannot be empty") 

18 

19 try: 

20 deck = self.repo.create(name=name.strip()) 

21 logger.info("Deck created: id=%s name=%s", getattr(deck, "id", None), name.strip()) 

22 return deck 

23 except Exception: 

24 logger.exception("Error creating deck with name=%s", name) 

25 raise 

26 

27 def get_deck(self, deck_id: int): 

28 logger.debug("get_deck called: deck_id=%s", deck_id) 

29 try: 

30 deck = self.repo.get(deck_id) 

31 except Exception: 

32 logger.exception("Failed to fetch deck_id=%s", deck_id) 

33 raise 

34 

35 if not deck: 

36 logger.info("Deck not found: deck_id=%s", deck_id) 

37 raise ValueError("Deck not found") 

38 

39 logger.debug("Deck fetched: deck_id=%s", deck_id) 

40 return deck 

41 

42 def get_deck_with_cards(self, deck_id: int): 

43 logger.debug("get_deck_with_cards called: deck_id=%s", deck_id) 

44 try: 

45 deck = self.repo.get_with_cards(deck_id) 

46 except Exception: 

47 logger.exception("Failed to fetch deck with cards for deck_id=%s", deck_id) 

48 raise 

49 

50 if not deck: 

51 logger.info("Deck not found (with cards): deck_id=%s", deck_id) 

52 raise ValueError("Deck not found") 

53 

54 card_count = len(getattr(deck, "cards", [])) 

55 logger.debug( 

56 "Transforming deck to API shape: deck_id=%s card_count=%s", deck_id, card_count 

57 ) 

58 

59 # transform ORM → API shape 

60 result = { 

61 "id": deck.id, 

62 "name": deck.name, 

63 "cards": [{"id": c.id, "front": c.front, "back": c.back} for c in deck.cards], 

64 } 

65 

66 logger.info("Deck with cards prepared: deck_id=%s card_count=%s", deck_id, card_count) 

67 return result 

68 

69 def delete_deck(self, deck_id: int): 

70 logger.debug("delete_deck called: deck_id=%s", deck_id) 

71 try: 

72 deck = self.repo.get(deck_id) 

73 except Exception: 

74 logger.exception("Failed to fetch deck before delete: deck_id=%s", deck_id) 

75 raise 

76 

77 if not deck: 

78 logger.info("Attempted to delete non-existent deck: deck_id=%s", deck_id) 

79 raise ValueError("Deck not found") 

80 

81 try: 

82 # cascade behavior happens via ORM config OR manual cleanup fallback 

83 self.repo.delete(deck) 

84 logger.info("Deleted deck: deck_id=%s", deck_id) 

85 return {"status": "deleted"} 

86 except Exception: 

87 logger.exception("Failed to delete deck_id=%s", deck_id) 

88 raise