Coverage for functions \ flipdare \ service \ chat_service.py: 88%

59 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-05-08 12:22 +1000

1#!/usr/bin/env python 

2# Copyright (c) 2026 Flipdare Pty Ltd. All rights reserved. 

3# 

4# This file is part of Flipdare's proprietary software and contains 

5# confidential and copyrighted material. Unauthorised copying, 

6# modification, distribution, or use of this file is strictly 

7# prohibited without prior written permission from Flipdare Pty Ltd. 

8# 

9# This software includes third-party components licensed under MIT, 

10# BSD, and Apache 2.0 licences. See THIRD_PARTY_NOTICES for details. 

11# 

12 

13from __future__ import annotations 

14 

15from typing import TYPE_CHECKING 

16from flipdare.app_log import LOG 

17from flipdare.constants import IS_DEBUG 

18 

19from flipdare.result.app_result import AppResult 

20from flipdare.core.job_type_decorator import job_type_decorator 

21from flipdare.result.job_result import JobResult 

22from flipdare.core.trigger_decorator import trigger_decorator 

23from flipdare.generated import AppErrorCode, AppJobType 

24from flipdare.message.user_message import UserMessage 

25from flipdare.service.safety.core.restriction_calculator import ChatRestrictionCalculator 

26from flipdare.generated.shared.firestore_collections import FirestoreCollections 

27from flipdare.service._service_provider import ServiceProvider 

28from flipdare.wrapper import AppJobWrapper, ChatCommentWrapper 

29 

30if TYPE_CHECKING: 

31 from flipdare.manager.service_manager import ServiceManager 

32 from flipdare.manager.db_manager import DbManager 

33 from flipdare.manager.backend_manager import BackendManager 

34 

35__all__ = ["ChatService"] 

36 

37_JT = AppJobType 

38_COL = FirestoreCollections.CHAT 

39 

40 

41class ChatService(ServiceProvider): 

42 

43 def __init__( 

44 self, 

45 db_manager: DbManager | None = None, 

46 service_manager: ServiceManager | None = None, 

47 backend_manager: BackendManager | None = None, 

48 ) -> None: 

49 super().__init__( 

50 backend_manager=backend_manager, 

51 service_manager=service_manager, 

52 db_manager=db_manager, 

53 ) 

54 

55 @job_type_decorator(_JT.TR_CHAT) 

56 @trigger_decorator(job_type=_JT.TR_CHAT, collection=_COL, wrapper_class=ChatCommentWrapper) 

57 def trigger_chat( 

58 self, 

59 job: AppJobWrapper, 

60 *, 

61 wrapper: ChatCommentWrapper, 

62 ) -> JobResult[ChatCommentWrapper]: 

63 """ 

64 Check if the user has a bad reputation, if so, perform 

65 moderation on the chat comment to check if it needs to be flagged. 

66 """ 

67 job_id = job.doc_id 

68 comment_id = wrapper.doc_id 

69 

70 main_result = AppResult[ChatCommentWrapper]( 

71 task_name=f"chat comment {comment_id}", doc_id=job_id 

72 ) 

73 parent_id = job.parent_obj_id 

74 if parent_id is None: 

75 msg = f"Missing parent_obj_id in job {job.doc_id}: {job.obj_id}" 

76 LOG().error(msg) 

77 main_result.add_error(AppErrorCode.INVALID_INPUT, msg) 

78 return JobResult.from_result(main_result, data=job.to_json_dict()) 

79 

80 try: 

81 outcome = self.moderation_service.review_comment(wrapper) 

82 if outcome.is_approved: 

83 # Auto-approved 

84 if IS_DEBUG: 

85 LOG().debug(f"Comment auto-approved: {job_id}") 

86 

87 return JobResult.ok(doc_id=job_id, message="Comment auto-approved.") 

88 """ 

89 Process: 

90 1. decrease reputation of user (should already be done in review_comment) 

91 2. Block the message is the reputation is low enough 

92 """ 

93 calculator = ChatRestrictionCalculator( 

94 decision=outcome.decision, 

95 reputation=outcome.new_reputation, 

96 ) 

97 should_block = calculator.should_block() 

98 if not should_block: 

99 if IS_DEBUG: 

100 LOG().debug(f"Comment requires review but not blocked: {job_id}") 

101 return JobResult.ok( 

102 doc_id=job_id, 

103 message="Comment requires review but not blocked.", 

104 ) 

105 

106 # Block the comment 

107 category = outcome.assessment.moderation_category if outcome.assessment else None 

108 if category is None: 

109 # assume safe if no category 

110 if IS_DEBUG: 

111 LOG().debug(f"No moderation category for comment {job_id}, cannot block.") 

112 return JobResult.ok( 

113 doc_id=job_id, 

114 message="No moderation category, cannot block comment.", 

115 ) 

116 

117 block_message = UserMessage.chat_block(category_label=category.label) 

118 wrapper.admin_block_reason = block_message 

119 

120 self.db_manager.chat_db.update_comment(parent_id=parent_id, comment=wrapper) 

121 

122 LOG().info(f"Comment blocked: {job_id} Reason: {block_message}") 

123 return JobResult.ok(doc_id=job_id, message="Comment blocked due to moderation.") 

124 except Exception as e: 

125 cause = f"Error processing comment: {e}" 

126 main_result.add_error(AppErrorCode.MODERATION, cause) 

127 return JobResult.from_result(main_result, data=wrapper.to_json_dict())