gperdrizet commited on
Commit
90e1657
·
1 Parent(s): cb97851

Added tests for Gradio and GitHub functions.

Browse files
Files changed (3) hide show
  1. functions/gradio.py +1 -1
  2. tests/test_github.py +385 -0
  3. tests/test_gradio.py +304 -0
functions/gradio.py CHANGED
@@ -96,7 +96,7 @@ def get_processed_data(linkedin_pdf, github_url, job_post_text):
96
  processed_data = {
97
  "linkedin": None,
98
  "github": None,
99
- "job_post": job_post_text.strip() if job_post_text else None,
100
  "errors": []
101
  }
102
 
 
96
  processed_data = {
97
  "linkedin": None,
98
  "github": None,
99
+ "job_post": job_post_text.strip() if job_post_text and job_post_text.strip() else None,
100
  "errors": []
101
  }
102
 
tests/test_github.py ADDED
@@ -0,0 +1,385 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Unit tests for the github module.
3
+ """
4
+
5
+ import unittest
6
+ from unittest.mock import patch, MagicMock
7
+ import requests
8
+ from functions import github
9
+
10
+
11
+ class TestExtractGitHubUsername(unittest.TestCase):
12
+ """Test cases for the _extract_github_username function."""
13
+
14
+ def test_valid_github_urls(self):
15
+ """Test extraction from valid GitHub URLs."""
16
+ test_cases = [
17
+ ("https://github.com/octocat", "octocat"),
18
+ ("https://github.com/octocat/", "octocat"),
19
+ ("http://github.com/test-user", "test-user"),
20
+ ("github.com/user_name", "user_name"),
21
+ ("https://github.com/user123", "user123"),
22
+ ("https://github.com/octocat/Hello-World", "octocat"),
23
+ ]
24
+
25
+ for url, expected in test_cases:
26
+ with self.subTest(url=url):
27
+ result = github._extract_github_username(url)
28
+ self.assertEqual(result, expected)
29
+
30
+ def test_invalid_github_urls(self):
31
+ """Test handling of invalid GitHub URLs."""
32
+ invalid_urls = [
33
+ "",
34
+ "https://gitlab.com/user",
35
+ "https://github.com/",
36
37
+ "https://github.com/user-with-very-long-name-that-exceeds-github-limit",
38
+ None
39
+ ]
40
+
41
+ for url in invalid_urls:
42
+ with self.subTest(url=url):
43
+ result = github._extract_github_username(url)
44
+ self.assertIsNone(result)
45
+
46
+ def test_username_validation(self):
47
+ """Test username format validation."""
48
+ # Valid usernames
49
+ valid_usernames = ["user", "user-name", "user_name", "user123", "a" * 39]
50
+ for username in valid_usernames:
51
+ with self.subTest(username=username):
52
+ result = github._extract_github_username(f"github.com/{username}")
53
+ self.assertEqual(result, username)
54
+
55
+ # Invalid usernames
56
+ invalid_usernames = ["", "a" * 40, "user@name", "user.name"]
57
+ for username in invalid_usernames:
58
+ with self.subTest(username=username):
59
+ result = github._extract_github_username(f"github.com/{username}")
60
+ self.assertIsNone(result)
61
+
62
+
63
+ class TestGetGitHubUserInfo(unittest.TestCase):
64
+ """Test cases for the _get_github_user_info function."""
65
+
66
+ @patch('requests.get')
67
+ def test_successful_user_info(self, mock_get):
68
+ """Test successful user info retrieval."""
69
+ mock_response = MagicMock()
70
+ mock_response.status_code = 200
71
+ mock_response.json.return_value = {
72
+ "login": "octocat",
73
+ "public_repos": 10,
74
+ "name": "The Octocat"
75
+ }
76
+ mock_get.return_value = mock_response
77
+
78
+ result = github._get_github_user_info("octocat")
79
+
80
+ self.assertEqual(result["status"], "success")
81
+ self.assertIn("data", result)
82
+ self.assertEqual(result["data"]["login"], "octocat")
83
+
84
+ @patch('requests.get')
85
+ def test_user_not_found(self, mock_get):
86
+ """Test handling of non-existent user."""
87
+ mock_response = MagicMock()
88
+ mock_response.status_code = 404
89
+ mock_get.return_value = mock_response
90
+
91
+ result = github._get_github_user_info("nonexistentuser")
92
+
93
+ self.assertEqual(result["status"], "error")
94
+ self.assertIn("not found", result["message"])
95
+
96
+ @patch('requests.get')
97
+ def test_rate_limit_exceeded(self, mock_get):
98
+ """Test handling of rate limit exceeded."""
99
+ mock_response = MagicMock()
100
+ mock_response.status_code = 403
101
+ mock_get.return_value = mock_response
102
+
103
+ result = github._get_github_user_info("octocat")
104
+
105
+ self.assertEqual(result["status"], "error")
106
+ self.assertIn("rate limit", result["message"])
107
+
108
+ @patch('requests.get')
109
+ def test_network_error(self, mock_get):
110
+ """Test handling of network errors."""
111
+ mock_get.side_effect = requests.RequestException("Connection error")
112
+
113
+ result = github._get_github_user_info("octocat")
114
+
115
+ self.assertEqual(result["status"], "error")
116
+ self.assertIn("Network error", result["message"])
117
+
118
+
119
+ class TestGetUserRepositories(unittest.TestCase):
120
+ """Test cases for the _get_user_repositories function."""
121
+
122
+ @patch('requests.get')
123
+ def test_successful_repository_retrieval(self, mock_get):
124
+ """Test successful repository retrieval."""
125
+ mock_response = MagicMock()
126
+ mock_response.status_code = 200
127
+ mock_response.json.return_value = [
128
+ {
129
+ "name": "Hello-World",
130
+ "description": "My first repository",
131
+ "language": "Python",
132
+ "stargazers_count": 5,
133
+ "forks_count": 2
134
+ }
135
+ ]
136
+ mock_get.return_value = mock_response
137
+
138
+ result = github._get_user_repositories("octocat")
139
+
140
+ self.assertEqual(result["status"], "success")
141
+ self.assertIn("data", result)
142
+ self.assertEqual(len(result["data"]), 1)
143
+ self.assertEqual(result["data"][0]["name"], "Hello-World")
144
+
145
+ @patch('requests.get')
146
+ def test_empty_repository_list(self, mock_get):
147
+ """Test handling of empty repository list."""
148
+ mock_response = MagicMock()
149
+ mock_response.status_code = 200
150
+ mock_response.json.return_value = []
151
+ mock_get.return_value = mock_response
152
+
153
+ result = github._get_user_repositories("octocat")
154
+
155
+ self.assertEqual(result["status"], "success")
156
+ self.assertEqual(len(result["data"]), 0)
157
+
158
+ @patch('requests.get')
159
+ def test_api_error(self, mock_get):
160
+ """Test handling of API errors."""
161
+ mock_response = MagicMock()
162
+ mock_response.status_code = 500
163
+ mock_get.return_value = mock_response
164
+
165
+ result = github._get_user_repositories("octocat")
166
+
167
+ self.assertEqual(result["status"], "error")
168
+ self.assertIn("GitHub API error", result["message"])
169
+
170
+
171
+ class TestProcessRepositoryData(unittest.TestCase):
172
+ """Test cases for the _process_repository_data function."""
173
+
174
+ def test_basic_processing(self):
175
+ """Test basic repository data processing."""
176
+ raw_repos = [
177
+ {
178
+ "name": "test-repo",
179
+ "description": "Test repository",
180
+ "language": "Python",
181
+ "stargazers_count": 10,
182
+ "forks_count": 5,
183
+ "updated_at": "2024-01-01T00:00:00Z",
184
+ "html_url": "https://github.com/user/test-repo",
185
+ "topics": ["python", "test"],
186
+ "fork": False
187
+ }
188
+ ]
189
+
190
+ result = github._process_repository_data(raw_repos)
191
+
192
+ self.assertEqual(len(result), 1)
193
+ processed_repo = result[0]
194
+ self.assertEqual(processed_repo["name"], "test-repo")
195
+ self.assertEqual(processed_repo["description"], "Test repository")
196
+ self.assertEqual(processed_repo["language"], "Python")
197
+ self.assertEqual(processed_repo["stars"], 10)
198
+ self.assertEqual(processed_repo["forks"], 5)
199
+ self.assertFalse(processed_repo["is_fork"])
200
+
201
+ def test_fork_filtering(self):
202
+ """Test filtering of unmodified forks."""
203
+ raw_repos = [
204
+ {
205
+ "name": "original-repo",
206
+ "fork": False,
207
+ "stargazers_count": 5
208
+ },
209
+ {
210
+ "name": "unmodified-fork",
211
+ "fork": True,
212
+ "stargazers_count": 0
213
+ },
214
+ {
215
+ "name": "modified-fork",
216
+ "fork": True,
217
+ "stargazers_count": 3
218
+ }
219
+ ]
220
+
221
+ result = github._process_repository_data(raw_repos)
222
+
223
+ # Should include original repo and modified fork, exclude unmodified fork
224
+ self.assertEqual(len(result), 2)
225
+ repo_names = [repo["name"] for repo in result]
226
+ self.assertIn("original-repo", repo_names)
227
+ self.assertIn("modified-fork", repo_names)
228
+ self.assertNotIn("unmodified-fork", repo_names)
229
+
230
+ def test_missing_fields(self):
231
+ """Test handling of missing fields in repository data."""
232
+ raw_repos = [
233
+ {
234
+ "name": "minimal-repo"
235
+ # Missing most optional fields
236
+ }
237
+ ]
238
+
239
+ result = github._process_repository_data(raw_repos)
240
+
241
+ self.assertEqual(len(result), 1)
242
+ processed_repo = result[0]
243
+ self.assertEqual(processed_repo["name"], "minimal-repo")
244
+ self.assertEqual(processed_repo["description"], "")
245
+ self.assertEqual(processed_repo["language"], "")
246
+ self.assertEqual(processed_repo["stars"], 0)
247
+ self.assertEqual(processed_repo["forks"], 0)
248
+
249
+
250
+ class TestGetGitHubRepositories(unittest.TestCase):
251
+ """Test cases for the get_github_repositories function."""
252
+
253
+ def test_empty_url(self):
254
+ """Test handling of empty or None URL."""
255
+ test_cases = [None, "", " "]
256
+
257
+ for url in test_cases:
258
+ with self.subTest(url=url):
259
+ result = github.get_github_repositories(url)
260
+ self.assertEqual(result["status"], "error")
261
+ self.assertIn("No GitHub URL provided", result["message"])
262
+
263
+ @patch('functions.github._get_user_repositories')
264
+ @patch('functions.github._get_github_user_info')
265
+ @patch('functions.github._extract_github_username')
266
+ def test_successful_retrieval(self, mock_extract, mock_user_info, mock_repos):
267
+ """Test successful repository retrieval."""
268
+ mock_extract.return_value = "octocat"
269
+ mock_user_info.return_value = {
270
+ "status": "success",
271
+ "data": {"public_repos": 10}
272
+ }
273
+ mock_repos.return_value = {
274
+ "status": "success",
275
+ "data": [{"name": "Hello-World", "fork": False, "stargazers_count": 5}]
276
+ }
277
+
278
+ result = github.get_github_repositories("https://github.com/octocat")
279
+
280
+ self.assertEqual(result["status"], "success")
281
+ self.assertIn("repositories", result)
282
+ self.assertIn("metadata", result)
283
+ self.assertEqual(result["metadata"]["username"], "octocat")
284
+
285
+ def test_invalid_url_format(self):
286
+ """Test handling of invalid URL format."""
287
+ result = github.get_github_repositories("https://gitlab.com/user")
288
+
289
+ self.assertEqual(result["status"], "error")
290
+ self.assertIn("Invalid GitHub URL format", result["message"])
291
+
292
+
293
+ class TestFormatRepositoriesForLLM(unittest.TestCase):
294
+ """Test cases for the format_repositories_for_llm function."""
295
+
296
+ def test_successful_formatting(self):
297
+ """Test successful formatting of repository data."""
298
+ github_result = {
299
+ "status": "success",
300
+ "repositories": [
301
+ {
302
+ "name": "test-repo",
303
+ "description": "A test repository",
304
+ "language": "Python",
305
+ "stars": 10,
306
+ "forks": 5,
307
+ "updated_at": "2024-01-01T00:00:00Z",
308
+ "html_url": "https://github.com/user/test-repo",
309
+ "topics": ["python", "test"]
310
+ }
311
+ ],
312
+ "metadata": {
313
+ "username": "testuser",
314
+ "profile_url": "https://github.com/testuser"
315
+ }
316
+ }
317
+
318
+ result = github.format_repositories_for_llm(github_result)
319
+
320
+ self.assertIn("=== GITHUB REPOSITORIES ===", result)
321
+ self.assertIn("testuser", result)
322
+ self.assertIn("test-repo", result)
323
+ self.assertIn("A test repository", result)
324
+ self.assertIn("Python", result)
325
+ self.assertIn("=== END GITHUB REPOSITORIES ===", result)
326
+
327
+ def test_error_status(self):
328
+ """Test formatting when there's an error status."""
329
+ github_result = {
330
+ "status": "error",
331
+ "message": "User not found"
332
+ }
333
+
334
+ result = github.format_repositories_for_llm(github_result)
335
+
336
+ self.assertIn("could not be retrieved", result)
337
+ self.assertIn("User not found", result)
338
+
339
+ def test_no_repositories(self):
340
+ """Test formatting when no repositories are found."""
341
+ github_result = {
342
+ "status": "success",
343
+ "repositories": [],
344
+ "metadata": {"username": "emptyuser"}
345
+ }
346
+
347
+ result = github.format_repositories_for_llm(github_result)
348
+
349
+ self.assertIn("No public repositories found", result)
350
+ self.assertIn("emptyuser", result)
351
+
352
+ def test_many_repositories_limit(self):
353
+ """Test that formatting limits to 20 repositories."""
354
+ repositories = []
355
+ for i in range(25):
356
+ repositories.append({
357
+ "name": f"repo-{i}",
358
+ "description": f"Repository {i}",
359
+ "language": "Python",
360
+ "stars": i,
361
+ "forks": 0,
362
+ "updated_at": "2024-01-01T00:00:00Z",
363
+ "html_url": f"https://github.com/user/repo-{i}",
364
+ "topics": []
365
+ })
366
+
367
+ github_result = {
368
+ "status": "success",
369
+ "repositories": repositories,
370
+ "metadata": {"username": "manyrepos"}
371
+ }
372
+
373
+ result = github.format_repositories_for_llm(github_result)
374
+
375
+ # Should mention "and 5 more repositories"
376
+ self.assertIn("and 5 more repositories", result)
377
+ # Should contain the first 20 repos
378
+ self.assertIn("repo-0", result)
379
+ self.assertIn("repo-19", result)
380
+ # Should not contain repos beyond 20
381
+ self.assertNotIn("repo-20", result)
382
+
383
+
384
+ if __name__ == '__main__':
385
+ unittest.main()
tests/test_gradio.py ADDED
@@ -0,0 +1,304 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Unit tests for the gradio module.
3
+ """
4
+
5
+ import unittest
6
+ from unittest.mock import patch, MagicMock, mock_open
7
+ import tempfile
8
+ import os
9
+ from functions import gradio
10
+
11
+
12
+ class TestProcessInputs(unittest.TestCase):
13
+ """Test cases for the process_inputs function."""
14
+
15
+ def test_no_inputs_provided(self):
16
+ """Test when no inputs are provided."""
17
+ result = gradio.process_inputs(None, "", "")
18
+
19
+ self.assertIn("❌ No LinkedIn resume PDF file uploaded", result)
20
+ self.assertIn("❌ No GitHub profile URL provided", result)
21
+ self.assertIn("❌ Job post not provided", result)
22
+
23
+ def test_all_inputs_provided_success(self):
24
+ """Test when all inputs are provided and successful."""
25
+ # Mock LinkedIn PDF file
26
+ mock_pdf = MagicMock()
27
+ mock_pdf.name = "test_resume.pdf"
28
+
29
+ # Mock successful extraction results
30
+ mock_linkedin_result = {
31
+ "status": "success",
32
+ "structured_text": {"sections": {}, "llm_formatted": "test content"},
33
+ "metadata": {"filename": "test_resume.pdf"}
34
+ }
35
+
36
+ mock_github_result = {
37
+ "status": "success",
38
+ "repositories": [{"name": "test-repo"}],
39
+ "metadata": {"username": "testuser"}
40
+ }
41
+
42
+ with patch('functions.gradio.extract_text_from_linkedin_pdf') as mock_linkedin, \
43
+ patch('functions.gradio.get_github_repositories') as mock_github:
44
+
45
+ mock_linkedin.return_value = mock_linkedin_result
46
+ mock_github.return_value = mock_github_result
47
+
48
+ result = gradio.process_inputs(
49
+ mock_pdf,
50
+ "https://github.com/testuser",
51
+ "Job posting text here"
52
+ )
53
+
54
+ self.assertIn("✅ LinkedIn Resume PDF uploaded", result)
55
+ self.assertIn("✅ Text extraction successful", result)
56
+ self.assertIn("✅ GitHub Profile URL provided", result)
57
+ self.assertIn("✅ GitHub list download successful", result)
58
+ self.assertIn("✅ Job post text provided", result)
59
+
60
+ @patch('functions.gradio.extract_text_from_linkedin_pdf')
61
+ def test_linkedin_extraction_failure(self, mock_extract):
62
+ """Test LinkedIn PDF extraction failure."""
63
+ mock_pdf = MagicMock()
64
+ mock_pdf.name = "test_resume.pdf"
65
+
66
+ mock_extract.return_value = {
67
+ "status": "error",
68
+ "message": "Failed to read PDF"
69
+ }
70
+
71
+ result = gradio.process_inputs(mock_pdf, "", "")
72
+
73
+ self.assertIn("✅ LinkedIn Resume PDF uploaded", result)
74
+ self.assertIn("❌ Text extraction failed: Failed to read PDF", result)
75
+
76
+ @patch('functions.gradio.extract_text_from_linkedin_pdf')
77
+ def test_linkedin_extraction_warning(self, mock_extract):
78
+ """Test LinkedIn PDF extraction warning."""
79
+ mock_pdf = MagicMock()
80
+ mock_pdf.name = "test_resume.pdf"
81
+
82
+ mock_extract.return_value = {
83
+ "status": "warning",
84
+ "message": "No text found in PDF"
85
+ }
86
+
87
+ result = gradio.process_inputs(mock_pdf, "", "")
88
+
89
+ self.assertIn("✅ LinkedIn Resume PDF uploaded", result)
90
+ self.assertIn("⚠️ Text extraction: No text found in PDF", result)
91
+
92
+ @patch('functions.gradio.get_github_repositories')
93
+ def test_github_retrieval_failure(self, mock_github):
94
+ """Test GitHub repository retrieval failure."""
95
+ mock_github.return_value = {
96
+ "status": "error",
97
+ "message": "User not found"
98
+ }
99
+
100
+ result = gradio.process_inputs(None, "https://github.com/nonexistent", "")
101
+
102
+ self.assertIn("✅ GitHub Profile URL provided", result)
103
+ self.assertIn("❌ GitHub extraction failed: User not found", result)
104
+
105
+ def test_whitespace_only_inputs(self):
106
+ """Test inputs with only whitespace."""
107
+ result = gradio.process_inputs(None, " ", " ")
108
+
109
+ self.assertIn("❌ No LinkedIn resume PDF file uploaded", result)
110
+ self.assertIn("❌ No GitHub profile URL provided", result)
111
+ self.assertIn("❌ Job post not provided", result)
112
+
113
+ def test_job_post_with_content(self):
114
+ """Test job post with actual content."""
115
+ job_text = "Software Engineer position at Tech Company"
116
+
117
+ result = gradio.process_inputs(None, "", job_text)
118
+
119
+ self.assertIn("✅ Job post text provided", result)
120
+
121
+ @patch('functions.gradio.logger')
122
+ def test_logging_calls(self, mock_logger):
123
+ """Test that appropriate logging calls are made."""
124
+ mock_pdf = MagicMock()
125
+ mock_pdf.name = "test.pdf"
126
+
127
+ with patch('functions.gradio.extract_text_from_linkedin_pdf') as mock_extract, \
128
+ patch('functions.gradio.get_github_repositories') as mock_github:
129
+
130
+ mock_extract.return_value = {"status": "success"}
131
+ mock_github.return_value = {"status": "success", "metadata": {"username": "test"}}
132
+
133
+ gradio.process_inputs(mock_pdf, "https://github.com/test", "job text")
134
+
135
+ # Verify logging calls were made
136
+ mock_logger.info.assert_called()
137
+
138
+
139
+ class TestGetProcessedData(unittest.TestCase):
140
+ """Test cases for the get_processed_data function."""
141
+
142
+ def test_no_inputs(self):
143
+ """Test with no inputs provided."""
144
+ result = gradio.get_processed_data(None, "", "")
145
+
146
+ self.assertIsNone(result["linkedin"])
147
+ self.assertIsNone(result["github"])
148
+ self.assertIsNone(result["job_post"])
149
+ self.assertEqual(len(result["errors"]), 0)
150
+
151
+ def test_all_successful_inputs(self):
152
+ """Test with all successful inputs."""
153
+ mock_pdf = MagicMock()
154
+ mock_pdf.name = "test.pdf"
155
+
156
+ mock_linkedin_result = {
157
+ "status": "success",
158
+ "structured_text": {"sections": {}, "llm_formatted": "content"}
159
+ }
160
+
161
+ mock_github_result = {
162
+ "status": "success",
163
+ "repositories": [{"name": "repo"}],
164
+ "metadata": {"username": "user"}
165
+ }
166
+
167
+ with patch('functions.gradio.extract_text_from_linkedin_pdf') as mock_linkedin, \
168
+ patch('functions.gradio.get_github_repositories') as mock_github:
169
+
170
+ mock_linkedin.return_value = mock_linkedin_result
171
+ mock_github.return_value = mock_github_result
172
+
173
+ result = gradio.get_processed_data(
174
+ mock_pdf,
175
+ "https://github.com/user",
176
+ "Job posting content"
177
+ )
178
+
179
+ self.assertEqual(result["linkedin"], mock_linkedin_result)
180
+ self.assertEqual(result["github"], mock_github_result)
181
+ self.assertEqual(result["job_post"], "Job posting content")
182
+ self.assertEqual(len(result["errors"]), 0)
183
+
184
+ def test_linkedin_error(self):
185
+ """Test with LinkedIn processing error."""
186
+ mock_pdf = MagicMock()
187
+ mock_pdf.name = "test.pdf"
188
+
189
+ with patch('functions.gradio.extract_text_from_linkedin_pdf') as mock_extract:
190
+ mock_extract.return_value = {
191
+ "status": "error",
192
+ "message": "PDF read failed"
193
+ }
194
+
195
+ result = gradio.get_processed_data(mock_pdf, "", "")
196
+
197
+ self.assertIsNone(result["linkedin"])
198
+ self.assertEqual(len(result["errors"]), 1)
199
+ self.assertIn("LinkedIn: PDF read failed", result["errors"])
200
+
201
+ def test_github_error(self):
202
+ """Test with GitHub processing error."""
203
+ with patch('functions.gradio.get_github_repositories') as mock_github:
204
+ mock_github.return_value = {
205
+ "status": "error",
206
+ "message": "User not found"
207
+ }
208
+
209
+ result = gradio.get_processed_data(None, "https://github.com/invalid", "")
210
+
211
+ self.assertIsNone(result["github"])
212
+ self.assertEqual(len(result["errors"]), 1)
213
+ self.assertIn("GitHub: User not found", result["errors"])
214
+
215
+ def test_multiple_errors(self):
216
+ """Test with multiple processing errors."""
217
+ mock_pdf = MagicMock()
218
+ mock_pdf.name = "test.pdf"
219
+
220
+ with patch('functions.gradio.extract_text_from_linkedin_pdf') as mock_linkedin, \
221
+ patch('functions.gradio.get_github_repositories') as mock_github:
222
+
223
+ mock_linkedin.return_value = {
224
+ "status": "error",
225
+ "message": "LinkedIn error"
226
+ }
227
+ mock_github.return_value = {
228
+ "status": "error",
229
+ "message": "GitHub error"
230
+ }
231
+
232
+ result = gradio.get_processed_data(
233
+ mock_pdf,
234
+ "https://github.com/user",
235
+ ""
236
+ )
237
+
238
+ self.assertIsNone(result["linkedin"])
239
+ self.assertIsNone(result["github"])
240
+ self.assertEqual(len(result["errors"]), 2)
241
+ self.assertIn("LinkedIn: LinkedIn error", result["errors"])
242
+ self.assertIn("GitHub: GitHub error", result["errors"])
243
+
244
+ def test_job_post_whitespace_handling(self):
245
+ """Test job post whitespace handling."""
246
+ # Test with leading/trailing whitespace
247
+ result = gradio.get_processed_data(None, "", " Job content ")
248
+ self.assertEqual(result["job_post"], "Job content")
249
+
250
+ # Test with only whitespace
251
+ result = gradio.get_processed_data(None, "", " ")
252
+ self.assertIsNone(result["job_post"])
253
+
254
+ # Test with empty string
255
+ result = gradio.get_processed_data(None, "", "")
256
+ self.assertIsNone(result["job_post"])
257
+
258
+ def test_github_url_whitespace_handling(self):
259
+ """Test GitHub URL whitespace handling."""
260
+ with patch('functions.gradio.get_github_repositories') as mock_github:
261
+ mock_github.return_value = {"status": "success", "repositories": []}
262
+
263
+ # Test with leading/trailing whitespace
264
+ result = gradio.get_processed_data(None, " https://github.com/user ", "")
265
+ mock_github.assert_called_with(" https://github.com/user ")
266
+
267
+ # Test with only whitespace - should not call function
268
+ mock_github.reset_mock()
269
+ result = gradio.get_processed_data(None, " ", "")
270
+ mock_github.assert_not_called()
271
+
272
+ def test_data_structure_consistency(self):
273
+ """Test that returned data structure is consistent."""
274
+ result = gradio.get_processed_data(None, "", "")
275
+
276
+ # Check all required keys exist
277
+ required_keys = ["linkedin", "github", "job_post", "errors"]
278
+ for key in required_keys:
279
+ self.assertIn(key, result)
280
+
281
+ # Check data types
282
+ self.assertIsInstance(result["errors"], list)
283
+
284
+ @patch('functions.gradio.extract_text_from_linkedin_pdf')
285
+ def test_linkedin_warning_status(self, mock_extract):
286
+ """Test handling of LinkedIn warning status."""
287
+ mock_pdf = MagicMock()
288
+ mock_pdf.name = "test.pdf"
289
+
290
+ mock_extract.return_value = {
291
+ "status": "warning",
292
+ "message": "Some warning"
293
+ }
294
+
295
+ result = gradio.get_processed_data(mock_pdf, "", "")
296
+
297
+ # Warning status should not be treated as success
298
+ self.assertIsNone(result["linkedin"])
299
+ self.assertEqual(len(result["errors"]), 1)
300
+ self.assertIn("LinkedIn: Some warning", result["errors"])
301
+
302
+
303
+ if __name__ == '__main__':
304
+ unittest.main()