File size: 15,545 Bytes
40a3caf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3c29fcc
40a3caf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3c29fcc
40a3caf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
#!/usr/bin/env python3
"""
Integration test for scheduling system with image handling.
Tests the end-to-end workflow from scheduling through content generation to publishing,
specifically for posts with images in both bytes and URL formats.
"""

import sys
import os
import unittest
import uuid
from unittest.mock import patch, MagicMock
from datetime import datetime, timedelta

# Add the backend directory to the path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))

from backend.scheduler.apscheduler_service import APSchedulerService
from backend.services.content_service import ContentService
from backend.utils.image_utils import ensure_bytes_format


class TestSchedulerImageIntegration(unittest.TestCase):
    """Test cases for scheduler integration with image handling."""

    def setUp(self):
        """Set up test fixtures."""
        self.user_id = "test_user_123"
        self.schedule_id = str(uuid.uuid4())
        self.social_account_id = "test_social_456"
        
        # Mock Flask app
        self.mock_app = MagicMock()
        self.mock_app.config = {
            'SUPABASE_URL': 'test_url',
            'SUPABASE_KEY': 'test_key'
        }
        
        # Initialize scheduler service with mock app
        self.scheduler_service = APSchedulerService(self.mock_app)
        
        # Mock Supabase client
        self.mock_supabase = MagicMock()
        self.scheduler_service.supabase_client = self.mock_supabase
        
        # Mock scheduler
        self.mock_scheduler = MagicMock()
        self.scheduler_service.scheduler = self.mock_scheduler
        
    def test_image_bytes_processing_in_content_generation(self):
        """Test that image bytes are properly processed and stored during content generation."""
        # Mock content service to return content with image bytes
        test_content = "This is a test post with an image"
        test_image_bytes = b"fake image bytes data"
        
        with patch('backend.scheduler.apscheduler_service.ContentService') as mock_content_service_class:
            mock_content_service = MagicMock()
            mock_content_service.generate_post_content.return_value = (test_content, test_image_bytes)
            mock_content_service_class.return_value = mock_content_service
            
            # Mock database response for schedule lookup
            self.mock_supabase.table.return_value.select.return_value.eq.return_value.execute.return_value.data = [
                {'id_social': self.social_account_id}
            ]
            
            # Mock database response for content storage
            mock_insert_response = MagicMock()
            mock_insert_response.data = [{'id': 'test_post_789'}]
            self.mock_supabase.table.return_value.insert.return_value.execute.return_value = mock_insert_response
            
            # Execute the content generation task
            self.scheduler_service.generate_content_task(self.user_id, self.schedule_id)
            
            # Verify content service was called
            mock_content_service.generate_post_content.assert_called_once_with(self.user_id)
            
            # Verify database insert was called with correct parameters
            self.mock_supabase.table.assert_called_with("Post_content")
            self.mock_supabase.table.return_value.insert.assert_called_once()
            
            # Get the actual call arguments to verify image handling
            call_args = self.mock_supabase.table.return_value.insert.call_args
            inserted_data = call_args[0][0]  # First positional argument
            
            # Verify text content is stored correctly
            self.assertEqual(inserted_data['Text_content'], test_content)
            
            # Verify image data is stored correctly (should be bytes)
            self.assertEqual(inserted_data['image_content_url'], test_image_bytes)
            self.assertEqual(inserted_data['is_published'], False)
            self.assertEqual(inserted_data['sched'], self.schedule_id)
    
    def test_image_url_processing_in_content_generation(self):
        """Test that image URLs are properly stored during content generation."""
        # Mock content service to return content with image URL
        test_content = "This is a test post with an image URL"
        test_image_url = "https://example.com/test-image.jpg"
        
        with patch('backend.scheduler.apscheduler_service.ContentService') as mock_content_service_class:
            mock_content_service = MagicMock()
            mock_content_service.generate_post_content.return_value = (test_content, test_image_url)
            mock_content_service_class.return_value = mock_content_service
            
            # Mock database response for schedule lookup
            self.mock_supabase.table.return_value.select.return_value.eq.return_value.execute.return_value.data = [
                {'id_social': self.social_account_id}
            ]
            
            # Mock database response for content storage
            mock_insert_response = MagicMock()
            mock_insert_response.data = [{'id': 'test_post_789'}]
            self.mock_supabase.table.return_value.insert.return_value.execute.return_value = mock_insert_response
            
            # Execute the content generation task
            self.scheduler_service.generate_content_task(self.user_id, self.schedule_id)
            
            # Verify content service was called
            mock_content_service.generate_post_content.assert_called_once_with(self.user_id)
            
            # Verify database insert was called with correct parameters
            self.mock_supabase.table.assert_called_with("Post_content")
            self.mock_supabase.table.return_value.insert.assert_called_once()
            
            # Get the actual call arguments to verify image handling
            call_args = self.mock_supabase.table.return_value.insert.call_args
            inserted_data = call_args[0][0]  # First positional argument
            
            # Verify text content is stored correctly
            self.assertEqual(inserted_data['Text_content'], test_content)
            
            # Verify image URL is stored correctly
            self.assertEqual(inserted_data['image_content_url'], test_image_url)
            self.assertEqual(inserted_data['is_published'], False)
            self.assertEqual(inserted_data['sched'], self.schedule_id)
    
    def test_image_base64_processing_in_content_generation(self):
        """Test that base64 encoded images are properly converted to bytes during content generation."""
        # Mock content service to return content with base64 image
        test_content = "This is a test post with a base64 image"
        test_base64_image = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=="
        expected_bytes = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\rIDATx\xda\x63\xfc\xff\x9f\xa1\x1e\x00\x07\x82\x02\x7f=\xc8H\xef\x00\x00\x00\x00IEND\xaeB`\x82'
        
        with patch('backend.scheduler.apscheduler_service.ContentService') as mock_content_service_class:
            mock_content_service = MagicMock()
            mock_content_service.generate_post_content.return_value = (test_content, test_base64_image)
            mock_content_service_class.return_value = mock_content_service
            
            # Mock database response for schedule lookup
            self.mock_supabase.table.return_value.select.return_value.eq.return_value.execute.return_value.data = [
                {'id_social': self.social_account_id}
            ]
            
            # Mock database response for content storage
            mock_insert_response = MagicMock()
            mock_insert_response.data = [{'id': 'test_post_789'}]
            self.mock_supabase.table.return_value.insert.return_value.execute.return_value = mock_insert_response
            
            # Execute the content generation task
            self.scheduler_service.generate_content_task(self.user_id, self.schedule_id)
            
            # Verify content service was called
            mock_content_service.generate_post_content.assert_called_once_with(self.user_id)
            
            # Verify database insert was called with correct parameters
            self.mock_supabase.table.assert_called_with("Post_content")
            self.mock_supabase.table.return_value.insert.assert_called_once()
            
            # Get the actual call arguments to verify image handling
            call_args = self.mock_supabase.table.return_value.insert.call_args
            inserted_data = call_args[0][0]  # First positional argument
            
            # Verify text content is stored correctly
            self.assertEqual(inserted_data['Text_content'], test_content)
            
            # Verify base64 image was converted to bytes
            self.assertEqual(inserted_data['image_content_url'], expected_bytes)
            self.assertEqual(inserted_data['is_published'], False)
            self.assertEqual(inserted_data['sched'], self.schedule_id)
    
    def test_publishing_with_image_bytes(self):
        """Test that posts with image bytes are properly published."""
        # Test data
        test_post_id = "test_post_123"
        test_content = "This is a test post for publishing"
        test_image_bytes = b"fake image bytes data for publishing"
        
        # Mock database response for unpublished post lookup
        self.mock_supabase.table.return_value.select.return_value.eq.return_value.eq.return_value.order.return_value.limit.return_value.execute.return_value.data = [
            {
                'id': test_post_id,
                'Text_content': test_content,
                'image_content_url': test_image_bytes,
                'is_published': False
            }
        ]
        
        # Mock database response for schedule lookup
        self.mock_supabase.table.return_value.select.return_value.eq.return_value.execute.return_value.data = [
            {
                'Social_network': {
                    'token': 'test_access_token',
                    'sub': 'test_user_sub'
                }
            }
        ]
        
        # Mock LinkedIn service
        with patch('backend.scheduler.apscheduler_service.LinkedInService') as mock_linkedin_service_class:
            mock_linkedin_service = MagicMock()
            mock_linkedin_service.publish_post.return_value = {'success': True}
            mock_linkedin_service_class.return_value = mock_linkedin_service
            
            # Mock database response for post update
            mock_update_response = MagicMock()
            mock_update_response.data = [{'id': test_post_id}]
            self.mock_supabase.table.return_value.update.return_value.eq.return_value.execute.return_value = mock_update_response
            
            # Execute the publishing task
            self.scheduler_service.publish_post_task(self.schedule_id)
            
            # Verify LinkedIn service was called with correct parameters
            mock_linkedin_service.publish_post.assert_called_once_with(
                'test_access_token',
                'test_user_sub',
                test_content,
                test_image_bytes  # Bytes should be passed directly
            )
            
            # Verify post status was updated in database
            self.mock_supabase.table.return_value.update.assert_called_once_with({"is_published": True})
            self.mock_supabase.table.return_value.update.return_value.eq.assert_called_once_with("id", test_post_id)
    
    def test_publishing_with_image_url(self):
        """Test that posts with image URLs are properly published."""
        # Test data
        test_post_id = "test_post_456"
        test_content = "This is a test post with URL for publishing"
        test_image_url = "https://example.com/publish-test-image.jpg"
        
        # Mock database response for unpublished post lookup
        self.mock_supabase.table.return_value.select.return_value.eq.return_value.eq.return_value.order.return_value.limit.return_value.execute.return_value.data = [
            {
                'id': test_post_id,
                'Text_content': test_content,
                'image_content_url': test_image_url,
                'is_published': False
            }
        ]
        
        # Mock database response for schedule lookup
        self.mock_supabase.table.return_value.select.return_value.eq.return_value.execute.return_value.data = [
            {
                'Social_network': {
                    'token': 'test_access_token',
                    'sub': 'test_user_sub'
                }
            }
        ]
        
        # Mock LinkedIn service
        with patch('backend.scheduler.apscheduler_service.LinkedInService') as mock_linkedin_service_class:
            mock_linkedin_service = MagicMock()
            mock_linkedin_service.publish_post.return_value = {'success': True}
            mock_linkedin_service_class.return_value = mock_linkedin_service
            
            # Mock database response for post update
            mock_update_response = MagicMock()
            mock_update_response.data = [{'id': test_post_id}]
            self.mock_supabase.table.return_value.update.return_value.eq.return_value.execute.return_value = mock_update_response
            
            # Execute the publishing task
            self.scheduler_service.publish_post_task(self.schedule_id)
            
            # Verify LinkedIn service was called with correct parameters
            mock_linkedin_service.publish_post.assert_called_once_with(
                'test_access_token',
                'test_user_sub',
                test_content,
                test_image_url  # URL should be passed directly
            )
            
            # Verify post status was updated in database
            self.mock_supabase.table.return_value.update.assert_called_once_with({"is_published": True})
            self.mock_supabase.table.return_value.update.return_value.eq.assert_called_once_with("id", test_post_id)
    
    def test_ensure_bytes_format_utility(self):
        """Test the ensure_bytes_format utility function with different input types."""
        # Test with bytes input
        test_bytes = b"test bytes data"
        result = ensure_bytes_format(test_bytes)
        self.assertEqual(result, test_bytes)
        
        # Test with base64 string input
        test_base64 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=="
        expected_bytes = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\rIDATx\xda\x63\xfc\xff\x9f\xa1\x1e\x00\x07\x82\x02\x7f=\xc8H\xef\x00\x00\x00\x00IEND\xaeB`\x82'
        result = ensure_bytes_format(test_base64)
        self.assertEqual(result, expected_bytes)
        
        # Test with URL string input
        test_url = "https://example.com/image.jpg"
        result = ensure_bytes_format(test_url)
        self.assertEqual(result, test_url)
        
        # Test with None input
        result = ensure_bytes_format(None)
        self.assertIsNone(result)


if __name__ == '__main__':
    unittest.main()