11import { describe , it , expect } from 'vitest' ;
2- import { renderHook , act , waitFor } from '@testing-library/preact' ;
2+ import { renderHook , act } from '@testing-library/preact' ;
33import { http , HttpResponse } from 'msw' ;
44import { server , buildFeedResponse , buildStructuredErrorResponse } from './mocks/server' ;
55import { useFeedConversion } from '../hooks/useFeedConversion' ;
@@ -30,20 +30,7 @@ describe('useFeedConversion contract', () => {
3030 { status : 201 }
3131 ) ;
3232 } ) ,
33- http . get ( '/api/v1/feeds/generated-token/status' , ( ) =>
34- HttpResponse . json (
35- buildFeedResponse ( {
36- feed_token : 'generated-token' ,
37- public_url : '/api/v1/feeds/generated-token' ,
38- json_public_url : '/api/v1/feeds/generated-token.json' ,
39- conversion : {
40- readiness_phase : 'feed_ready' ,
41- preview_status : 'ready' ,
42- warnings : [ ] ,
43- } ,
44- } )
45- )
46- )
33+ http . get ( '/api/v1/feeds/generated-token/status' , ( ) => HttpResponse . json ( buildFeedResponse ( ) ) )
4734 ) ;
4835
4936 const { result } = renderHook ( ( ) => useFeedConversion ( ) ) ;
@@ -56,12 +43,6 @@ describe('useFeedConversion contract', () => {
5643 expect ( result . current . error ) . toBeUndefined ( ) ;
5744 expect ( result . current . result ?. feed . feed_token ) . toBe ( 'generated-token' ) ;
5845 expect ( result . current . result ?. readinessPhase ) . toBe ( 'link_created' ) ;
59-
60- await waitFor ( ( ) => {
61- expect ( result . current . result ?. readinessPhase ) . toBe ( 'feed_ready' ) ;
62- expect ( ( result . current . result as any ) ?. previewStatus ) . toBe ( 'ready' ) ;
63- expect ( ( result . current . result as any ) ?. warnings ) . toEqual ( [ ] ) ;
64- } ) ;
6546 } ) ;
6647
6748 it ( 'propagates structured auth failures without parsing the message text' , async ( ) => {
@@ -102,7 +83,48 @@ describe('useFeedConversion contract', () => {
10283 } ) ;
10384 } ) ;
10485
105- it ( 'marks degraded result metadata when the status endpoint reports warnings' , async ( ) => {
86+ it ( 'treats extraction-empty failures as corrective input errors without strategy metadata' , async ( ) => {
87+ server . use (
88+ http . post ( '/api/v1/feeds' , async ( ) =>
89+ HttpResponse . json (
90+ buildStructuredErrorResponse ( {
91+ code : 'NO_FEED_ITEMS_EXTRACTED' ,
92+ message : 'Could not extract feed items. Try a more specific listing URL or explicit selectors.' ,
93+ kind : 'input' ,
94+ retryable : false ,
95+ next_action : 'correct_input' ,
96+ retry_action : 'none' ,
97+ } ) ,
98+ { status : 422 }
99+ )
100+ )
101+ ) ;
102+
103+ const { result } = renderHook ( ( ) => useFeedConversion ( ) ) ;
104+
105+ await act ( async ( ) => {
106+ await expect ( result . current . convertFeed ( 'https://example.com/articles' , 'token' ) ) . rejects . toMatchObject (
107+ {
108+ kind : 'input' ,
109+ code : 'NO_FEED_ITEMS_EXTRACTED' ,
110+ nextAction : 'correct_input' ,
111+ retryAction : 'none' ,
112+ retryable : false ,
113+ message : 'Could not extract feed items. Try a more specific listing URL or explicit selectors.' ,
114+ }
115+ ) ;
116+ } ) ;
117+
118+ expect ( result . current . error ) . toMatchObject ( {
119+ kind : 'input' ,
120+ code : 'NO_FEED_ITEMS_EXTRACTED' ,
121+ nextAction : 'correct_input' ,
122+ retryAction : 'none' ,
123+ retryable : false ,
124+ } ) ;
125+ } ) ;
126+
127+ it ( 'accepts create responses even when later polling surfaces degraded status warnings' , async ( ) => {
106128 server . use (
107129 http . post ( '/api/v1/feeds' , async ( { request } ) => {
108130 const body = ( await request . json ( ) ) as { url : string } ;
@@ -151,18 +173,9 @@ describe('useFeedConversion contract', () => {
151173 await result . current . convertFeed ( 'https://example.com/articles' , 'token' ) ;
152174 } ) ;
153175
154- await waitFor ( ( ) => {
155- expect ( result . current . result ?. readinessPhase ) . toBe ( 'feed_ready' ) ;
156- expect ( ( result . current . result as any ) ?. previewStatus ) . toBe ( 'degraded' ) ;
157- expect ( ( result . current . result as any ) ?. warnings ) . toEqual ( [
158- {
159- code : 'preview_partial' ,
160- message : 'Preview content could not be fully verified.' ,
161- retryable : true ,
162- nextAction : 'retry' ,
163- } ,
164- ] ) ;
165- } ) ;
176+ expect ( result . current . error ) . toBeUndefined ( ) ;
177+ expect ( result . current . result ?. feed . feed_token ) . toBe ( 'generated-token' ) ;
178+ expect ( result . current . result ?. readinessPhase ) . toBe ( 'link_created' ) ;
166179 } ) ;
167180
168181 it ( 'rejects camelCase-only create payloads to enforce canonical snake_case contract' , async ( ) => {
0 commit comments