|
1 | 1 | #!/usr/bin/env python3 |
2 | 2 | # /// script |
3 | 3 | # requires-python = ">=3.11" |
4 | | -# dependencies = ["pytest", "notion-client", "pydantic", "ruff", "mypy"] |
| 4 | +# dependencies = ["pytest"] |
5 | 5 | # /// |
6 | | -""" |
7 | | -Tests for pure functions in TIL workflow scripts. |
8 | | -
|
9 | | -Run with: uv run test_pure_functions.py |
10 | | -Or: uv run pytest test_pure_functions.py -v |
11 | | -""" |
| 6 | +"""Tests for git formatting utilities.""" |
12 | 7 |
|
13 | 8 | from __future__ import annotations |
14 | 9 |
|
15 | 10 | import sys |
16 | 11 | from pathlib import Path |
17 | | -from unittest.mock import patch |
18 | 12 |
|
19 | 13 | # Add parent directory to path for imports |
20 | | -sys.path.insert(0, str(Path(__file__).parent)) |
| 14 | +sys.path.insert(0, str(Path(__file__).parent.parent.parent)) |
21 | 15 |
|
22 | 16 | from git.formatting import format_markdown, format_relative_date, should_skip_commit |
23 | 17 | from git.types import Commit |
24 | | -from notion.blocks import extract_page_id, markdown_to_blocks |
25 | | -from notion.commits import get_assessed_commits_from_notion |
26 | 18 |
|
27 | 19 |
|
28 | 20 | class TestFormatRelativeDate: |
@@ -287,223 +279,6 @@ def test_shows_review_status_when_some_already_reviewed(self) -> None: |
287 | 279 | assert "(1 new, 4 already reviewed)" in result |
288 | 280 |
|
289 | 281 |
|
290 | | -class TestExtractPageId: |
291 | | - """Test Notion URL page ID extraction.""" |
292 | | - |
293 | | - def test_extracts_from_standard_url(self) -> None: |
294 | | - url = "https://www.notion.so/Page-Title-abc123def456" |
295 | | - result = extract_page_id(url) |
296 | | - assert result == "abc123def456" |
297 | | - |
298 | | - def test_extracts_from_url_with_query_params(self) -> None: |
299 | | - url = "https://www.notion.so/Page-Title-abc123def456?v=xyz" |
300 | | - result = extract_page_id(url) |
301 | | - assert result == "abc123def456" |
302 | | - |
303 | | - def test_extracts_from_short_url(self) -> None: |
304 | | - url = "https://notion.so/abc123def456" |
305 | | - result = extract_page_id(url) |
306 | | - assert result == "abc123def456" |
307 | | - |
308 | | - def test_handles_trailing_slash(self) -> None: |
309 | | - url = "https://www.notion.so/Page-Title-abc123def456/" |
310 | | - result = extract_page_id(url) |
311 | | - assert result == "abc123def456" |
312 | | - |
313 | | - def test_handles_empty_string(self) -> None: |
314 | | - result = extract_page_id("") |
315 | | - assert result == "" |
316 | | - |
317 | | - def test_extracts_uuid_with_dashes(self) -> None: |
318 | | - # Notion IDs can have dashes in UUID format |
319 | | - url = "https://www.notion.so/12345678-90ab-cdef-1234-567890abcdef" |
320 | | - result = extract_page_id(url) |
321 | | - # Should get the whole UUID including trailing segment |
322 | | - assert len(result) > 0 |
323 | | - |
324 | | - |
325 | | -def make_notion_page(commit_hash: str) -> dict: |
326 | | - """Helper: create a mock Notion page with a commit hash.""" |
327 | | - return { |
328 | | - "id": f"page-{commit_hash}", |
329 | | - "url": f"https://notion.so/page-{commit_hash}", |
330 | | - "properties": {"Commit Hash": {"title": [{"plain_text": commit_hash}]}}, |
331 | | - } |
332 | | - |
333 | | - |
334 | | -def make_notion_response( |
335 | | - hashes: list[str], has_more: bool = False, next_cursor: str | None = None |
336 | | -) -> dict: |
337 | | - """Helper: create a mock Notion SDK response.""" |
338 | | - return { |
339 | | - "results": [make_notion_page(h) for h in hashes], |
340 | | - "has_more": has_more, |
341 | | - "next_cursor": next_cursor, |
342 | | - } |
343 | | - |
344 | | - |
345 | | -def mock_collect_paginated_api(pages: list[dict]) -> list[dict]: |
346 | | - """Helper: mock collect_paginated_api to return all pages as a flat list.""" |
347 | | - all_results: list[dict] = [] |
348 | | - for page_response in pages: |
349 | | - all_results.extend(page_response["results"]) |
350 | | - return all_results |
351 | | - |
352 | | - |
353 | | -class TestGetAssessedCommitsFromNotion: |
354 | | - """Test fetching assessed commits from Notion.""" |
355 | | - |
356 | | - def test_returns_empty_set_when_no_token(self) -> None: |
357 | | - with patch("notion.commits.get_op_secret", side_effect=RuntimeError("Failed")): |
358 | | - result = get_assessed_commits_from_notion() |
359 | | - assert result == set() |
360 | | - |
361 | | - def test_returns_commit_hashes_from_single_page(self) -> None: |
362 | | - with ( |
363 | | - patch("notion.commits.get_op_secret", return_value="fake-token"), |
364 | | - patch("notion_client.Client"), |
365 | | - patch("notion_client.helpers.collect_paginated_api") as mock_paginate, |
366 | | - ): |
367 | | - pages = [make_notion_response(["abc123", "def456", "ghi789"])] |
368 | | - mock_paginate.return_value = mock_collect_paginated_api(pages) |
369 | | - |
370 | | - result = get_assessed_commits_from_notion() |
371 | | - assert result == {"abc123", "def456", "ghi789"} |
372 | | - |
373 | | - def test_handles_pagination(self) -> None: |
374 | | - with ( |
375 | | - patch("notion.commits.get_op_secret", return_value="fake-token"), |
376 | | - patch("notion_client.Client"), |
377 | | - patch("notion_client.helpers.collect_paginated_api") as mock_paginate, |
378 | | - ): |
379 | | - # First page with more results |
380 | | - first_response = make_notion_response( |
381 | | - ["abc123", "def456"], has_more=True, next_cursor="cursor-1" |
382 | | - ) |
383 | | - # Second page, final |
384 | | - second_response = make_notion_response(["ghi789", "jkl012"], has_more=False) |
385 | | - |
386 | | - # collect_paginated_api handles pagination internally, returns all results |
387 | | - pages = [first_response, second_response] |
388 | | - mock_paginate.return_value = mock_collect_paginated_api(pages) |
389 | | - |
390 | | - result = get_assessed_commits_from_notion() |
391 | | - assert result == {"abc123", "def456", "ghi789", "jkl012"} |
392 | | - |
393 | | - def test_handles_client_error_gracefully(self) -> None: |
394 | | - with ( |
395 | | - patch("notion.commits.get_op_secret", return_value="fake-token"), |
396 | | - patch("notion_client.Client") as MockClient, |
397 | | - ): |
398 | | - MockClient.side_effect = Exception("Connection error") |
399 | | - |
400 | | - result = get_assessed_commits_from_notion() |
401 | | - assert result == set() |
402 | | - |
403 | | - def test_handles_query_error_gracefully(self) -> None: |
404 | | - with ( |
405 | | - patch("notion.commits.get_op_secret", return_value="fake-token"), |
406 | | - patch("notion_client.Client"), |
407 | | - patch("notion_client.helpers.collect_paginated_api") as mock_paginate, |
408 | | - ): |
409 | | - mock_paginate.side_effect = Exception("Query error") |
410 | | - |
411 | | - result = get_assessed_commits_from_notion() |
412 | | - assert result == set() |
413 | | - |
414 | | - def test_skips_pages_without_commit_hash(self) -> None: |
415 | | - with ( |
416 | | - patch("notion.commits.get_op_secret", return_value="fake-token"), |
417 | | - patch("notion_client.Client"), |
418 | | - patch("notion_client.helpers.collect_paginated_api") as mock_paginate, |
419 | | - ): |
420 | | - response = { |
421 | | - "results": [ |
422 | | - make_notion_page("abc123"), |
423 | | - { # Empty title |
424 | | - "id": "page-empty", |
425 | | - "url": "https://notion.so/page-empty", |
426 | | - "properties": {"Commit Hash": {"title": []}}, |
427 | | - }, |
428 | | - make_notion_page("def456"), |
429 | | - ], |
430 | | - "has_more": False, |
431 | | - "next_cursor": None, |
432 | | - } |
433 | | - |
434 | | - mock_paginate.return_value = mock_collect_paginated_api([response]) |
435 | | - |
436 | | - result = get_assessed_commits_from_notion() |
437 | | - assert result == {"abc123", "def456"} |
438 | | - |
439 | | - |
440 | | -class TestMarkdownToBlocks: |
441 | | - """Test markdown to Notion blocks conversion.""" |
442 | | - |
443 | | - def test_converts_code_blocks(self) -> None: |
444 | | - markdown = "```python\nprint('hello')\n```" |
445 | | - blocks = markdown_to_blocks(markdown) |
446 | | - |
447 | | - assert len(blocks) == 1 |
448 | | - assert blocks[0]["type"] == "code" |
449 | | - assert blocks[0]["code"]["language"] == "python" |
450 | | - assert blocks[0]["code"]["rich_text"][0]["text"]["content"] == "print('hello')" |
451 | | - |
452 | | - def test_maps_language_aliases(self) -> None: |
453 | | - markdown = "```js\nconsole.log('test')\n```" |
454 | | - blocks = markdown_to_blocks(markdown) |
455 | | - |
456 | | - assert blocks[0]["code"]["language"] == "javascript" |
457 | | - |
458 | | - def test_converts_headings(self) -> None: |
459 | | - markdown = "# H1\n## H2\n### H3" |
460 | | - blocks = markdown_to_blocks(markdown) |
461 | | - |
462 | | - assert len(blocks) == 3 |
463 | | - assert blocks[0]["type"] == "heading_1" |
464 | | - assert blocks[1]["type"] == "heading_2" |
465 | | - assert blocks[2]["type"] == "heading_3" |
466 | | - |
467 | | - def test_converts_bullet_lists(self) -> None: |
468 | | - markdown = "- Item 1\n- Item 2" |
469 | | - blocks = markdown_to_blocks(markdown) |
470 | | - |
471 | | - assert len(blocks) == 2 |
472 | | - assert blocks[0]["type"] == "bulleted_list_item" |
473 | | - assert blocks[0]["bulleted_list_item"]["rich_text"][0]["text"]["content"] == "Item 1" |
474 | | - |
475 | | - def test_converts_numbered_lists(self) -> None: |
476 | | - markdown = "1. First\n2. Second" |
477 | | - blocks = markdown_to_blocks(markdown) |
478 | | - |
479 | | - assert len(blocks) == 2 |
480 | | - assert blocks[0]["type"] == "numbered_list_item" |
481 | | - assert blocks[1]["type"] == "numbered_list_item" |
482 | | - |
483 | | - def test_converts_paragraphs(self) -> None: |
484 | | - markdown = "This is a paragraph" |
485 | | - blocks = markdown_to_blocks(markdown) |
486 | | - |
487 | | - assert len(blocks) == 1 |
488 | | - assert blocks[0]["type"] == "paragraph" |
489 | | - assert blocks[0]["paragraph"]["rich_text"][0]["text"]["content"] == "This is a paragraph" |
490 | | - |
491 | | - def test_handles_empty_lines(self) -> None: |
492 | | - markdown = "Line 1\n\nLine 2" |
493 | | - blocks = markdown_to_blocks(markdown) |
494 | | - |
495 | | - assert len(blocks) == 3 |
496 | | - assert blocks[1]["type"] == "paragraph" |
497 | | - assert blocks[1]["paragraph"]["rich_text"] == [] |
498 | | - |
499 | | - def test_handles_multiline_code_blocks(self) -> None: |
500 | | - markdown = "```python\nline1\nline2\nline3\n```" |
501 | | - blocks = markdown_to_blocks(markdown) |
502 | | - |
503 | | - assert len(blocks) == 1 |
504 | | - assert "line1\nline2\nline3" in blocks[0]["code"]["rich_text"][0]["text"]["content"] |
505 | | - |
506 | | - |
507 | 282 | if __name__ == "__main__": |
508 | 283 | import pytest |
509 | 284 |
|
|
0 commit comments