11import { assert } from 'https://deno.land/std@0.208.0/assert/mod.ts' ;
2+ import { stub } from 'https://deno.land/std@0.208.0/testing/mock.ts' ;
23import { fetchArticle } from '../tasks/fetch-article.ts' ;
34import { load } from 'https://deno.land/std@0.208.0/dotenv/mod.ts' ;
45
@@ -7,47 +8,113 @@ await load({ envPath: '../.env', export: true }).catch(() => {
78 console . log ( 'No .env file found, using environment variables' ) ;
89} ) ;
910
10- Deno . test ( 'fetchArticle - fetches real article from Hacker News' , async ( ) => {
11- // Use a stable HN article URL that should always exist
12- const url = 'https://news.ycombinator.com/item?id=35629516' ;
11+ const mockJinaResponse = `# Mock Article Title
1312
14- const result = await fetchArticle ( url ) ;
13+ This is mock content with enough text to pass validation tests.
14+ It has multiple lines and is over 100 characters long.
15+ ` ;
1516
16- // Verify we got a result with both content and title
17- assert ( result . content , 'Should have content' ) ;
18- assert ( result . title , 'Should have title' ) ;
19- assert ( result . content . length > 100 , 'Content should be substantial' ) ;
20- assert ( result . title !== 'Untitled Article' , 'Should extract a real title' ) ;
17+ function stubFetch ( response : { status : number ; statusText : string ; body ?: string } ) {
18+ return stub ( globalThis , 'fetch' , ( ) =>
19+ Promise . resolve (
20+ new Response ( response . body || '' , {
21+ status : response . status ,
22+ statusText : response . statusText
23+ } )
24+ )
25+ ) ;
26+ }
2127
22- console . log ( `✓ Fetched article: "${ result . title } " (${ result . content . length } chars)` ) ;
23- } ) ;
28+ function stubFetchError ( error : Error ) {
29+ return stub ( globalThis , 'fetch' , ( ) => Promise . reject ( error ) ) ;
30+ }
2431
25- Deno . test ( 'fetchArticle - fetches real article from TechCrunch' , async ( ) => {
26- // Use TechCrunch homepage which should always work
27- const url = 'https://techcrunch.com' ;
32+ // Mocked tests (always run)
33+ Deno . test ( 'fetchArticle - fetches article with mocked fetch' , async ( ) => {
34+ const fetchStub = stubFetch ( { status : 200 , statusText : 'OK' , body : mockJinaResponse } ) ;
2835
29- const result = await fetchArticle ( url ) ;
36+ try {
37+ const url = 'https://example.com/article' ;
38+ const result = await fetchArticle ( url ) ;
3039
31- assert ( result . content , 'Should have content' ) ;
32- assert ( result . title , 'Should have title' ) ;
33- assert ( result . content . length > 100 , 'Content should be substantial' ) ;
40+ assert ( result . content , 'Should have content' ) ;
41+ assert ( result . title , 'Should have title' ) ;
42+ assert ( result . content . length > 100 , 'Content should be substantial' ) ;
43+ assert ( result . title === 'Mock Article Title' , 'Should extract title from mock response' ) ;
3444
35- console . log ( `✓ Fetched article: "${ result . title } " (${ result . content . length } chars)` ) ;
45+ console . log ( '✓ Mock fetch works' ) ;
46+ } finally {
47+ fetchStub . restore ( ) ;
48+ }
49+ } ) ;
50+
51+ Deno . test ( 'fetchArticle - handles non-OK response with mocked fetch' , async ( ) => {
52+ const fetchStub = stubFetch ( {
53+ status : 451 ,
54+ statusText : 'Unavailable For Legal Reasons' ,
55+ body : 'Unavailable'
56+ } ) ;
57+
58+ try {
59+ const url = 'https://example.com/blocked-article' ;
60+ await fetchArticle ( url ) ;
61+ assert ( false , 'Should have thrown an error' ) ;
62+ } catch ( error ) {
63+ assert ( error instanceof Error ) ;
64+ assert ( error . message . includes ( 'Failed to fetch' ) ) ;
65+ assert ( error . message . includes ( '451' ) ) ;
66+ console . log ( '✓ Properly handles non-OK responses' ) ;
67+ } finally {
68+ fetchStub . restore ( ) ;
69+ }
3670} ) ;
3771
3872Deno . test ( {
39- name : 'fetchArticle - handles non-existent URL gracefully ' ,
40- sanitizeResources : false , // Disable resource leak check for this test
73+ name : 'fetchArticle - handles network errors with mocked fetch ' ,
74+ sanitizeResources : false ,
4175 fn : async ( ) => {
42- const url = 'https://this-domain-definitely-does-not-exist-12345.com/article' ;
76+ const fetchStub = stubFetchError ( new TypeError ( 'Network error' ) ) ;
4377
4478 try {
79+ const url = 'https://example.com/article' ;
4580 await fetchArticle ( url ) ;
4681 assert ( false , 'Should have thrown an error' ) ;
4782 } catch ( error ) {
4883 assert ( error instanceof Error ) ;
4984 assert ( error . message . includes ( 'Failed to fetch' ) ) ;
50- console . log ( '✓ Properly handles fetch errors' ) ;
85+ console . log ( '✓ Properly handles network errors' ) ;
86+ } finally {
87+ fetchStub . restore ( ) ;
5188 }
5289 }
5390} ) ;
91+
92+ // Real HTTP tests (only run when USE_REAL_HTTP=true)
93+ if ( Deno . env . get ( 'USE_REAL_HTTP' ) === 'true' ) {
94+ Deno . test ( 'fetchArticle - fetches real article from Hacker News' , async ( ) => {
95+ const url = 'https://news.ycombinator.com/item?id=35629516' ;
96+
97+ const result = await fetchArticle ( url ) ;
98+
99+ assert ( result . content , 'Should have content' ) ;
100+ assert ( result . title , 'Should have title' ) ;
101+ assert ( result . content . length > 100 , 'Content should be substantial' ) ;
102+ assert ( result . title !== 'Untitled Article' , 'Should extract a real title' ) ;
103+
104+ console . log ( `✓ Fetched real article: "${ result . title } " (${ result . content . length } chars)` ) ;
105+ } ) ;
106+
107+ Deno . test ( 'fetchArticle - fetches real article from TechCrunch' , async ( ) => {
108+ const url = 'https://techcrunch.com' ;
109+
110+ const result = await fetchArticle ( url ) ;
111+
112+ assert ( result . content , 'Should have content' ) ;
113+ assert ( result . title , 'Should have title' ) ;
114+ assert ( result . content . length > 100 , 'Content should be substantial' ) ;
115+
116+ console . log ( `✓ Fetched real article: "${ result . title } " (${ result . content . length } chars)` ) ;
117+ } ) ;
118+ } else {
119+ console . log ( 'ℹ Skipping real HTTP tests (set USE_REAL_HTTP=true to run them)' ) ;
120+ }
0 commit comments