1+ import unittest
2+ from application .utils .external_project_parsers .parsers .cheatsheet_extractor import (
3+ extract_cheatsheet_record ,
4+ )
5+ from application .defs .cheatsheet_defs import SUMMARY_MAX_LENGTH
6+
7+ SOURCE_PATH = "cheatsheets/Secrets_Management_Cheat_Sheet.md"
8+ EXPECTED_SOURCE_ID = "Secrets_Management_Cheat_Sheet"
9+ EXPECTED_HYPERLINK = "https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html"
10+
11+ NORMAL_MD = """\
12+ # Secrets Management Cheat Sheet
13+
14+ ## Introduction
15+ Storage guidance.
16+
17+ ## Architectural Patterns
18+ Use vaults and environment isolation.
19+ """
20+
21+ MISSING_H1_MD = """\
22+ ## Introduction
23+ No H1 present.
24+
25+ ## Details
26+ More content.
27+ """
28+
29+ EMPTY_MD = ""
30+
31+ # No ## headings — _extract_summary raises, _fallback_summary matches # via _ANY_HEADING_RE
32+ # and extracts body until len(markdown) since there is no next heading.
33+ BODY_UNDER_H1_MD = """\
34+ # Single Heading Cheat Sheet
35+
36+ Body text directly under H1, no subheadings at all.
37+ """
38+
39+ # Leading spaces before # and ##malformed (no space) — both handled by \s* and (?!#) regex
40+ MALFORMED_MD = """\
41+ # Malformed Title
42+
43+ ##malformed
44+
45+ ## Introduction
46+ Some intro text.
47+
48+ ## Valid Heading
49+ """
50+
51+
52+ class TestNormal (unittest .TestCase ):
53+ def setUp (self ):
54+ self .record = extract_cheatsheet_record (NORMAL_MD , SOURCE_PATH )
55+
56+ # source, source_id, hyperlink, raw_markdown_path are derived from SOURCE_PATH
57+ # and are independent of markdown content — verified once here for all cases
58+ def test_source (self ):
59+ self .assertEqual (self .record .source , "owasp_cheatsheets" )
60+
61+ def test_source_id (self ):
62+ self .assertEqual (self .record .source_id , EXPECTED_SOURCE_ID )
63+
64+ def test_hyperlink (self ):
65+ self .assertEqual (self .record .hyperlink , EXPECTED_HYPERLINK )
66+
67+ def test_raw_markdown_path (self ):
68+ self .assertEqual (self .record .raw_markdown_path , SOURCE_PATH )
69+
70+ def test_title (self ):
71+ self .assertEqual (self .record .title , "Secrets Management Cheat Sheet" )
72+
73+ def test_summary (self ):
74+ self .assertEqual (self .record .summary , "Storage guidance." )
75+
76+ def test_summary_bounded (self ):
77+ # SUMMARY_MAX_LENGTH truncation happens in CheatsheetRecord.__post_init__
78+ # for every record — testing once here covers all cases
79+ self .assertLessEqual (len (self .record .summary ), SUMMARY_MAX_LENGTH )
80+
81+ def test_headings (self ):
82+ self .assertIn ("Introduction" , self .record .headings )
83+ self .assertIn ("Architectural Patterns" , self .record .headings )
84+
85+ def test_fallback_not_used (self ):
86+ self .assertEqual (self .record .metadata ["fallback_used" ], "false" )
87+
88+
89+ class TestMissingH1 (unittest .TestCase ):
90+ def setUp (self ):
91+ self .record = extract_cheatsheet_record (MISSING_H1_MD , SOURCE_PATH )
92+
93+ def test_title_is_fallback (self ):
94+ self .assertEqual (self .record .title , "No title found." )
95+
96+ def test_summary_from_introduction (self ):
97+ self .assertIn ("no h1" , self .record .summary .lower ())
98+
99+ def test_headings_extracted (self ):
100+ self .assertIn ("Introduction" , self .record .headings )
101+ self .assertIn ("Details" , self .record .headings )
102+
103+ def test_fallback_used (self ):
104+ self .assertEqual (self .record .metadata ["fallback_used" ], "true" )
105+
106+
107+ class TestEmptyMarkdown (unittest .TestCase ):
108+ def setUp (self ):
109+ self .record = extract_cheatsheet_record (EMPTY_MD , SOURCE_PATH )
110+
111+ def test_title_is_fallback (self ):
112+ self .assertEqual (self .record .title , "No title found." )
113+
114+ def test_summary_no_summary_found (self ):
115+ # No headings at all — _fallback_summary returns this literal string
116+ self .assertEqual (self .record .summary , "No summary found." )
117+
118+ def test_headings_empty (self ):
119+ self .assertEqual (self .record .headings , [])
120+
121+ def test_fallback_used (self ):
122+ self .assertEqual (self .record .metadata ["fallback_used" ], "true" )
123+
124+
125+ class TestBodyUnderH1 (unittest .TestCase ):
126+ def setUp (self ):
127+ self .record = extract_cheatsheet_record (BODY_UNDER_H1_MD , SOURCE_PATH )
128+
129+ def test_title (self ):
130+ self .assertEqual (self .record .title , "Single Heading Cheat Sheet" )
131+
132+ def test_summary_from_fallback_via_h1 (self ):
133+ # _fallback_summary matches # heading, extracts body until len(markdown)
134+ self .assertIn ("body text" , self .record .summary .lower ())
135+
136+ def test_headings_empty (self ):
137+ # _HEADING_RE only matches ## — no ## present here
138+ self .assertEqual (self .record .headings , [])
139+
140+ def test_fallback_used (self ):
141+ self .assertEqual (self .record .metadata ["fallback_used" ], "true" )
142+
143+
144+ class TestMalformedHeadings (unittest .TestCase ):
145+ def setUp (self ):
146+ self .record = extract_cheatsheet_record (MALFORMED_MD , SOURCE_PATH )
147+
148+ def test_malformed_h1_extracted (self ):
149+ self .assertEqual (self .record .title , "Malformed Title" )
150+
151+ def test_malformed_h2_in_headings (self ):
152+ self .assertIn ("malformed" , self .record .headings )
153+
154+ def test_valid_headings_also_extracted (self ):
155+ self .assertIn ("Introduction" , self .record .headings )
156+ self .assertIn ("Valid Heading" , self .record .headings )
157+
158+ def test_summary_from_introduction (self ):
159+ self .assertIn ("intro" , self .record .summary .lower ())
160+
161+ def test_fallback_not_used (self ):
162+ self .assertEqual (self .record .metadata ["fallback_used" ], "false" )
163+
164+
165+ if __name__ == "__main__" :
166+ unittest .main ()
0 commit comments