| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448 |
- """
- Unit tests for uploader module.
- """
- import pytest
- from datetime import datetime
- from src.uploader import (
- CUCalculator,
- ContentChecker,
- MockUploadClient,
- UploadManager,
- create_mock_upload_manager,
- ChapterUploadData,
- UploadResult,
- UploadStatus,
- CheckStatus,
- QuotaInfo
- )
- class TestCUCalculator:
- """Test suite for CUCalculator."""
- def test_calculate_cu_basic(self):
- """Test basic CU calculation."""
- calc = CUCalculator()
- # Minimum CU (1-100 chars = 1 CU)
- assert calc.calculate_cu("A" * 50) == 1
- assert calc.calculate_cu("A" * 100) == 1
- # Second CU (101-200 chars = 2 CU)
- assert calc.calculate_cu("A" * 101) == 2
- assert calc.calculate_cu("A" * 200) == 2
- # Third CU (201-300 chars = 3 CU)
- assert calc.calculate_cu("A" * 201) == 3
- assert calc.calculate_cu("A" * 300) == 3
- def test_calculate_cu_examples(self):
- """Test examples from the spec."""
- calc = CUCalculator()
- # "123字" → 2 CU (123/100=1.23→向上取整2)
- assert calc.calculate_cu("A" * 123) == 2
- # "300字" → 3 CU
- assert calc.calculate_cu("A" * 300) == 3
- # "99字" → 1 CU
- assert calc.calculate_cu("A" * 99) == 1
- def test_calculate_cu_empty(self):
- """Test CU calculation for empty content."""
- calc = CUCalculator()
- assert calc.calculate_cu("") == 0
- def test_calculate_cu_from_count(self):
- """Test CU calculation from character count."""
- calc = CUCalculator()
- assert calc.calculate_cu_from_count(0) == 0
- assert calc.calculate_cu_from_count(150) == 2
- assert calc.calculate_cu_from_count(350) == 4
- def test_calculate_batch(self):
- """Test batch CU calculation."""
- calc = CUCalculator()
- # (50/100→1) + (150/100→2) = 3
- result = calc.calculate_batch(["A" * 50, "A" * 150])
- assert result == 3
- def test_estimate_cu(self):
- """Test CU estimation."""
- calc = CUCalculator()
- # 10 chapters × 150 chars each = 10 × 2 CU = 20 CU
- result = calc.estimate_cu(10, 150)
- assert result == 20
- def test_get_breakdown(self):
- """Test CU calculation breakdown."""
- calc = CUCalculator()
- breakdown = calc.get_breakdown("A" * 123)
- assert breakdown["character_count"] == 123
- assert breakdown["cu_cost"] == 2
- assert breakdown["efficiency_percentage"] == pytest.approx(61.5, 0.1)
- assert breakdown["remaining_characters_in_cu"] == 77
- def test_reverse_calculate_max_chars(self):
- """Test reverse calculation of max chars."""
- calc = CUCalculator()
- assert calc.reverse_calculate_max_chars(1) == 100
- assert calc.reverse_calculate_max_chars(5) == 500
- def test_calculate_cost_summary(self):
- """Test cost summary calculation."""
- calc = CUCalculator()
- contents = ["A" * 50, "A" * 150, "A" * 250]
- summary = calc.calculate_cost_summary(contents)
- assert summary["total_chapters"] == 3
- assert summary["total_cu"] == 6 # 1 + 2 + 3 = 6
- assert summary["min_cu"] == 1
- assert summary["max_cu"] == 3
- def test_custom_divisor(self):
- """Test calculator with custom divisor."""
- calc = CUCalculator(divisor=50)
- # With divisor=50: 1-50=1 CU, 51-100=2 CU
- assert calc.calculate_cu("A" * 25) == 1
- assert calc.calculate_cu("A" * 50) == 1
- assert calc.calculate_cu("A" * 51) == 2
- assert calc.calculate_cu("A" * 100) == 2
- class TestContentChecker:
- """Test suite for ContentChecker."""
- def test_check_normal_content(self):
- """Test checking normal, compliant content."""
- checker = ContentChecker()
- result = checker.check_chapter("Test Title", "This is normal content.")
- assert result.passed is True
- assert result.status == CheckStatus.PASSED
- assert len(result.issues) == 0
- def test_check_empty_title(self):
- """Test checking chapter with empty title."""
- checker = ContentChecker()
- result = checker.check_chapter("", "Content here")
- assert result.passed is False
- assert len(result.issues) > 0
- assert result.issues[0].type == "empty_title"
- def test_check_sensitive_words(self):
- """Test checking for sensitive words."""
- checker = ContentChecker()
- result = checker.check_chapter("Normal Title", "This contains 暴力 content.")
- assert result.passed is False
- assert any(i.type == "sensitive_word" for i in result.issues)
- def test_check_content_too_short(self):
- """Test checking content that's too short."""
- checker = ContentChecker(min_chapter_length=50)
- result = checker.check_chapter("Title", "Short")
- assert result.passed is False
- assert any(i.type == "content_too_short" for i in result.issues)
- def test_check_content_too_long(self):
- """Test checking content that's too long."""
- checker = ContentChecker(max_chapter_length=100)
- result = checker.check_chapter("Title", "A" * 101)
- assert result.passed is False
- assert any(i.type == "content_too_long" for i in result.issues)
- def test_check_placeholders(self):
- """Test checking for unreplaced placeholders."""
- checker = ContentChecker()
- result = checker.check_chapter("Title", "This has [TERM_test] placeholder.")
- # Bracket-style placeholders are checked
- assert len(result.warnings) > 0 or len(result.issues) > 0
- def test_check_strict_mode(self):
- """Test strict mode where warnings become errors."""
- checker = ContentChecker()
- result = checker.check_chapter("Title", "This has [TERM_test] placeholder.", strict_mode=True)
- # In strict mode, warnings become errors
- assert result.passed is False or len(result.issues) > 0
- def test_check_all_chapters(self):
- """Test checking multiple chapters."""
- checker = ContentChecker()
- chapters = [
- {"title": "Chapter 1", "content": "Normal content."},
- {"title": "Chapter 2", "content": "Content with 暴力."}
- ]
- results = checker.check_all_chapters(chapters)
- assert len(results) == 2
- assert results["ch_0"].passed is True
- assert results["ch_1"].passed is False
- def test_add_sensitive_word(self):
- """Test adding custom sensitive word."""
- checker = ContentChecker()
- checker.add_sensitive_word("testword")
- assert "testword" in checker.sensitive_words
- def test_remove_sensitive_word(self):
- """Test removing sensitive word."""
- checker = ContentChecker()
- checker.remove_sensitive_word("暴力")
- assert "暴力" not in checker.sensitive_words
- def test_set_sensitive_words(self):
- """Test replacing sensitive words list."""
- checker = ContentChecker()
- checker.set_sensitive_words({"custom1", "custom2"})
- assert "custom1" in checker.sensitive_words
- assert "暴力" not in checker.sensitive_words
- class TestMockUploadClient:
- """Test suite for MockUploadClient."""
- def test_upload_single_chapter(self):
- """Test uploading a single chapter."""
- client = MockUploadClient()
- result = client.upload_chapter(
- "work123",
- {
- "title": "Chapter 1",
- "content": "A" * 150,
- "index": 0
- }
- )
- assert result.success is True
- assert result.cu_consumed == 2 # 150/100 → 2 CU
- def test_batch_upload(self):
- """Test batch upload."""
- client = MockUploadClient()
- chapters = [
- {"title": "Ch1", "content": "A" * 50, "index": 0},
- {"title": "Ch2", "content": "A" * 150, "index": 1}
- ]
- result = client.batch_upload("work123", chapters)
- assert result.total_chapters == 2
- assert result.successful == 2
- assert result.failed == 0
- assert result.status == UploadStatus.COMPLETED
- def test_check_quota(self):
- """Test quota checking."""
- client = MockUploadClient()
- quota = client.check_quota()
- assert quota.total_cu > 0
- assert quota.remaining_cu > 0
- def test_health_check(self):
- """Test health check."""
- client = MockUploadClient()
- assert client.health_check() is True
- class TestUploadManager:
- """Test suite for UploadManager."""
- def test_upload_workflow_dry_run(self):
- """Test upload workflow in dry run mode."""
- mock_client = MockUploadClient()
- manager = UploadManager(mock_client, dry_run=True)
- chapters = [
- {"title": "Chapter 1", "content": "This is chapter one.", "index": 0},
- {"title": "Chapter 2", "content": "This is chapter two.", "index": 1}
- ]
- result = manager.upload_workflow("work123", chapters)
- assert result.status == UploadStatus.COMPLETED
- assert len(result.upload_results) == 2
- def test_upload_workflow_with_content_check_fail(self):
- """Test workflow with content check failure."""
- mock_client = MockUploadClient()
- manager = UploadManager(mock_client, strict_mode=True)
- # Content with sensitive word will fail
- chapters = [
- {"title": "", "content": "Content with empty title causes failure", "index": 0}
- ]
- result = manager.upload_workflow("work123", chapters)
- # Empty title causes content check failure in strict mode
- assert result.status == UploadStatus.FAILED or len(result.upload_results) == 0
- def test_estimate_cu(self):
- """Test CU estimation."""
- mock_client = MockUploadClient()
- manager = UploadManager(mock_client)
- chapters = [
- {"content": "A" * 50},
- {"content": "A" * 150}
- ]
- estimate = manager.estimate_cu(chapters)
- assert estimate["total_cu"] == 3 # 1 + 2
- def test_preview_upload(self):
- """Test upload preview."""
- mock_client = MockUploadClient()
- manager = UploadManager(mock_client)
- chapters = [
- {"title": "Ch1", "content": "Content 1"},
- {"title": "Ch2", "content": "Content 2"}
- ]
- preview = manager.preview_upload("work123", chapters)
- assert preview["total_chapters"] == 2
- assert "content_check" in preview
- assert "cu_estimate" in preview
- def test_validate_before_upload(self):
- """Test pre-upload validation."""
- mock_client = MockUploadClient()
- manager = UploadManager(mock_client)
- # Invalid chapters (missing title, empty content)
- chapters = [
- {"content": "Content without title"},
- {"title": "Title without content", "content": ""}
- ]
- validation = manager.validate_before_upload(chapters)
- # Should have errors or warnings
- assert validation["has_errors"] is True or validation["has_warnings"] is True
- assert len(validation["errors"]) + len(validation["warnings"]) >= 2
- def test_check_quota(self):
- """Test checking quota through manager."""
- mock_client = MockUploadClient()
- manager = UploadManager(mock_client)
- quota_info = manager.check_quota()
- assert "total_cu" in quota_info
- assert "remaining_cu" in quota_info
- class TestChapterUploadData:
- """Test suite for ChapterUploadData model."""
- def test_content_length(self):
- """Test content_length property."""
- data = ChapterUploadData(
- "ch1",
- "work1",
- "Title",
- "Content here",
- 0
- )
- assert data.content_length == 12
- def test_word_count(self):
- """Test word_count property."""
- data = ChapterUploadData(
- "ch1",
- "work1",
- "Title",
- "Content here",
- 0
- )
- # word_count removes newlines and spaces
- assert data.word_count > 0 # Should count non-whitespace characters
- class TestUploadResult:
- """Test suite for UploadResult model."""
- def test_success_result(self):
- """Test successful upload result."""
- result = UploadResult(
- success=True,
- chapter_id="ch1",
- server_chapter_id="srv_ch1",
- cu_consumed=2
- )
- assert result.success is True
- assert result.cu_consumed == 2
- def test_failed_result(self):
- """Test failed upload result."""
- result = UploadResult(
- success=False,
- chapter_id="ch1",
- error_message="Upload failed"
- )
- assert result.success is False
- assert result.error_message == "Upload failed"
- class TestQuotaInfo:
- """Test suite for QuotaInfo model."""
- def test_quota_calculations(self):
- """Test quota calculation properties."""
- quota = QuotaInfo(
- total_cu=1000,
- used_cu=300,
- remaining_cu=700
- )
- assert quota.usage_percentage == 0.3
- assert quota.is_exceeded is False
- def test_quota_exceeded(self):
- """Test exceeded quota."""
- quota = QuotaInfo(
- total_cu=1000,
- used_cu=1000,
- remaining_cu=0
- )
- assert quota.is_exceeded is True
- def test_create_mock_upload_manager():
- """Test factory function for mock upload manager."""
- manager = create_mock_upload_manager()
- assert isinstance(manager, UploadManager)
- # Check that it uses a mock client (has upload_count attribute)
- assert hasattr(manager.client, "upload_count")
|