Skip to content

Commit 3750bd7

Browse files
committed
V2.1.2: Expanded unit tests and fixed core logic bugs
- Improved URL normalization to handle trailing slashes with query strings. - Fixed DatabaseManager to support persistent in-memory databases for testing. - Added test cases for link scraper (mocked), report generation, and database operations. - Total tests: 9.
1 parent 1ea9986 commit 3750bd7

3 files changed

Lines changed: 119 additions & 8 deletions

File tree

src/deadlink/database.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,17 @@ def __init__(self, db_path=None):
1111
db_path = os.path.join(base_dir, "deadlink_history.db")
1212

1313
self.db_path = db_path
14+
self._shared_conn = None
15+
16+
# In-memory databases require a persistent connection to keep data
17+
if self.db_path == ":memory:":
18+
self._shared_conn = sqlite3.connect(self.db_path)
19+
1420
self._init_db()
1521

1622
def _get_connection(self):
23+
if self._shared_conn:
24+
return self._shared_conn
1725
return sqlite3.connect(self.db_path)
1826

1927
def _init_db(self):

src/deadlink/utils.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,16 @@ def get_status_text(status_code: int) -> str:
4949

5050
def normalize_url(url: str) -> str:
5151
"""Normalize URL by removing fragments and trailing slashes."""
52+
from urllib.parse import urlparse
5253
parsed = urlparse(url)
53-
# Remove fragments
54-
url = f"{parsed.scheme}://{parsed.netloc}{parsed.path}"
54+
path = parsed.path
55+
if path.endswith('/') and len(path) > 1:
56+
path = path[:-1]
57+
58+
normalized = f"{parsed.scheme}://{parsed.netloc}{path}"
5559
if parsed.query:
56-
url += f"?{parsed.query}"
57-
# Remove trailing slash for consistency (except for root domain)
58-
if url.endswith('/') and len(parsed.path) > 1:
59-
url = url[:-1]
60-
return url
60+
normalized += f"?{parsed.query}"
61+
return normalized
6162

6263
def open_file(path: str):
6364
"""Open a file or directory using the default system application."""

tests/test_core.py

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,132 @@
11
import sys
22
import os
33
import unittest
4+
import tempfile
5+
import shutil
46

57
# Add src to path
68
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'src')))
79

8-
from deadlink.utils import is_external_url, normalize_url
10+
from deadlink.utils import is_external_url, normalize_url, get_status_text
11+
from deadlink.models import LinkResult
12+
from deadlink.reporter import generate_report, get_report_filename
13+
from deadlink.database import DatabaseManager
14+
from deadlink.scanner import get_all_links
915
from deadlink.version import VERSION
16+
from unittest.mock import patch, MagicMock
1017

1118
class TestCore(unittest.TestCase):
19+
def setUp(self):
20+
self.test_dir = tempfile.mkdtemp()
21+
22+
def tearDown(self):
23+
shutil.rmtree(self.test_dir)
24+
1225
def test_version_exists(self):
1326
self.assertIsNotNone(VERSION)
1427
print(f"Testing version: {VERSION}")
1528

1629
def test_url_normalization(self):
1730
url = "https://example.com/path/"
1831
normalized = normalize_url(url)
32+
# normalize_url should strip trailing slash for paths
1933
self.assertEqual(normalized, "https://example.com/path")
34+
35+
url_with_query = "https://example.com/path/?q=1#frag"
36+
normalized_q = normalize_url(url_with_query)
37+
self.assertEqual(normalized_q, "https://example.com/path?q=1")
38+
39+
url_home = "https://example.com/"
40+
# home page should keep trailing slash
41+
self.assertEqual(normalize_url(url_home), "https://example.com/")
2042

2143
def test_external_link_check(self):
2244
base = "https://example.com"
2345
external = "https://google.com"
2446
internal = "https://example.com/about"
47+
internal_www = "https://www.example.com/about"
2548

2649
self.assertTrue(is_external_url(external, base))
2750
self.assertFalse(is_external_url(internal, base))
51+
self.assertFalse(is_external_url(internal_www, base))
52+
53+
def test_status_text_lookup(self):
54+
self.assertEqual(get_status_text(200), "200 OK")
55+
self.assertEqual(get_status_text(404), "404 Not Found")
56+
self.assertEqual(get_status_text(500), "500 Internal Server Error")
57+
self.assertIn("999", get_status_text(999))
58+
59+
def test_link_result_model(self):
60+
res = LinkResult(
61+
url="https://test.com",
62+
status_code=200,
63+
status_text="OK",
64+
response_time=0.5,
65+
found_on="https://base.com",
66+
is_dead=False,
67+
is_external=True,
68+
link_type="Image"
69+
)
70+
self.assertEqual(res.url, "https://test.com")
71+
self.assertTrue(res.is_external)
72+
self.assertFalse(res.is_dead)
73+
self.assertEqual(res.link_type, "Image")
74+
75+
def test_report_generation(self):
76+
results = [
77+
LinkResult("https://a.com", 200, "OK", 0.1, "base", False, False),
78+
LinkResult("https://b.com", 404, "Not Found", 0.1, "base", True, False),
79+
LinkResult("https://external.com", 200, "OK", 0.1, "base", False, True)
80+
]
81+
report = generate_report(results)
82+
self.assertIn("DEAD LINK CHECKER REPORT", report)
83+
self.assertIn("Total items checked: 3", report)
84+
self.assertIn("Working items: 2", report)
85+
self.assertIn("Dead items: 1", report)
86+
self.assertIn("Internal: 2", report)
87+
self.assertIn("External: 1", report)
88+
89+
def test_report_filename_generation(self):
90+
url = "https://www.Example-Site.com/page"
91+
filename = get_report_filename(url, "csv", reports_dir=self.test_dir)
92+
self.assertIn("example-site_com", filename)
93+
self.assertTrue(filename.endswith(".csv"))
94+
95+
filename_session = get_report_filename(url, "txt", reports_dir=self.test_dir, session_folder="session_1")
96+
self.assertIn("session_1", filename_session)
97+
self.assertTrue(os.path.exists(os.path.join(self.test_dir, "session_1")))
98+
99+
def test_database_manager(self):
100+
# Use in-memory database for testing
101+
db = DatabaseManager(":memory:")
102+
results = [
103+
LinkResult("https://a.com", 200, "OK", 0.1, "base", False, False)
104+
]
105+
session_id = db.save_session("https://test.com", "website", results, "folder_1")
106+
107+
sessions = db.get_sessions()
108+
self.assertEqual(len(sessions), 1)
109+
self.assertEqual(sessions[0]['url'], "https://test.com")
110+
111+
session_results = db.get_session_results(session_id)
112+
self.assertEqual(len(session_results), 1)
113+
self.assertEqual(session_results[0]['url'], "https://a.com")
114+
115+
db.delete_session(session_id)
116+
self.assertEqual(len(db.get_sessions()), 0)
117+
118+
@patch('requests.get')
119+
def test_link_scraper(self, mock_get):
120+
mock_response = MagicMock()
121+
mock_response.text = '<html><body><a href="/page1">Link</a><img src="img.png"></body></html>'
122+
mock_response.raise_for_status = MagicMock()
123+
mock_get.return_value = mock_response
124+
125+
links, base = get_all_links("https://test.com")
126+
self.assertEqual(len(links), 2)
127+
urls = [l[0] for l in links]
128+
self.assertIn("https://test.com/page1", urls)
129+
self.assertIn("https://test.com/img.png", urls)
28130

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

0 commit comments

Comments
 (0)