@@ -60,4 +60,122 @@ describe("federation", () => {
6060 const failed = out . providerReports . find ( ( r ) => r . provider === "openverse" ) ;
6161 expect ( failed ?. ok ) . toBe ( false ) ;
6262 } ) ;
63+
64+ test ( "errorKind=ok on successful provider" , async ( ) => {
65+ const fetcher = stubFetcher ( [
66+ {
67+ match : ( u ) => u . includes ( "commons.wikimedia.org" ) ,
68+ handler : async ( ) => jsonResponse ( fixture ( "wikimedia.json" ) ) ,
69+ } ,
70+ ] ) ;
71+ const out = await searchImages ( "test" , { providers : [ "wikimedia" ] , fetcher } ) ;
72+ const report = out . providerReports . find ( ( r ) => r . provider === "wikimedia" ) ;
73+ expect ( report ?. ok ) . toBe ( true ) ;
74+ expect ( report ?. errorKind ) . toBe ( "ok" ) ;
75+ } ) ;
76+
77+ test ( "errorKind=timeout when provider aborts due to timeout" , async ( ) => {
78+ const fetcher = stubFetcher ( [
79+ {
80+ match : ( u ) => u . includes ( "commons.wikimedia.org" ) ,
81+ handler : async ( _url , init ) =>
82+ new Promise ( ( _resolve , reject ) => {
83+ // Simulate an async operation that respects the abort signal
84+ const signal = init ?. signal as AbortSignal | undefined ;
85+ if ( signal ?. aborted ) {
86+ reject ( Object . assign ( new Error ( "The operation was aborted" ) , { name : "AbortError" } ) ) ;
87+ return ;
88+ }
89+ signal ?. addEventListener ( "abort" , ( ) => {
90+ reject ( Object . assign ( new Error ( "The operation was aborted" ) , { name : "AbortError" } ) ) ;
91+ } ) ;
92+ } ) ,
93+ } ,
94+ ] ) ;
95+ const out = await searchImages ( "test" , {
96+ providers : [ "wikimedia" ] ,
97+ fetcher,
98+ timeoutMs : 10 , // very short — will fire before the never-resolving promise
99+ } ) ;
100+ const report = out . providerReports . find ( ( r ) => r . provider === "wikimedia" ) ;
101+ expect ( report ?. ok ) . toBe ( false ) ;
102+ expect ( report ?. errorKind ) . toBe ( "timeout" ) ;
103+ } ) ;
104+
105+ test ( "errorKind=http-4xx for 404 response" , async ( ) => {
106+ const fetcher = stubFetcher ( [
107+ {
108+ match : ( u ) => u . includes ( "commons.wikimedia.org" ) ,
109+ handler : async ( ) => {
110+ throw Object . assign ( new Error ( "HTTP 404 Not Found" ) , { status : 404 } ) ;
111+ } ,
112+ } ,
113+ ] ) ;
114+ const out = await searchImages ( "test" , { providers : [ "wikimedia" ] , fetcher } ) ;
115+ const report = out . providerReports . find ( ( r ) => r . provider === "wikimedia" ) ;
116+ expect ( report ?. ok ) . toBe ( false ) ;
117+ expect ( report ?. errorKind ) . toBe ( "http-4xx" ) ;
118+ expect ( report ?. errorContext ?. httpStatus ) . toBe ( 404 ) ;
119+ } ) ;
120+
121+ test ( "errorKind=http-5xx for 500 response thrown as error" , async ( ) => {
122+ const fetcher = stubFetcher ( [
123+ {
124+ match : ( u ) => u . includes ( "commons.wikimedia.org" ) ,
125+ handler : async ( ) => {
126+ throw Object . assign ( new Error ( "HTTP 500 Internal Server Error" ) , { status : 500 } ) ;
127+ } ,
128+ } ,
129+ ] ) ;
130+ const out = await searchImages ( "test" , { providers : [ "wikimedia" ] , fetcher } ) ;
131+ const report = out . providerReports . find ( ( r ) => r . provider === "wikimedia" ) ;
132+ expect ( report ?. ok ) . toBe ( false ) ;
133+ expect ( report ?. errorKind ) . toBe ( "http-5xx" ) ;
134+ expect ( report ?. errorContext ?. httpStatus ) . toBe ( 500 ) ;
135+ } ) ;
136+
137+ test ( "errorKind=rate-limited for 429 response" , async ( ) => {
138+ const fetcher = stubFetcher ( [
139+ {
140+ match : ( u ) => u . includes ( "commons.wikimedia.org" ) ,
141+ handler : async ( ) => {
142+ throw Object . assign ( new Error ( "HTTP 429 Too Many Requests" ) , { status : 429 } ) ;
143+ } ,
144+ } ,
145+ ] ) ;
146+ const out = await searchImages ( "test" , { providers : [ "wikimedia" ] , fetcher } ) ;
147+ const report = out . providerReports . find ( ( r ) => r . provider === "wikimedia" ) ;
148+ expect ( report ?. ok ) . toBe ( false ) ;
149+ expect ( report ?. errorKind ) . toBe ( "rate-limited" ) ;
150+ } ) ;
151+
152+ test ( "errorKind=network for generic fetch failure" , async ( ) => {
153+ const fetcher = stubFetcher ( [
154+ {
155+ match : ( u ) => u . includes ( "commons.wikimedia.org" ) ,
156+ handler : async ( ) => {
157+ throw new Error ( "fetch failed: ECONNREFUSED" ) ;
158+ } ,
159+ } ,
160+ ] ) ;
161+ const out = await searchImages ( "test" , { providers : [ "wikimedia" ] , fetcher } ) ;
162+ const report = out . providerReports . find ( ( r ) => r . provider === "wikimedia" ) ;
163+ expect ( report ?. ok ) . toBe ( false ) ;
164+ expect ( report ?. errorKind ) . toBe ( "network" ) ;
165+ } ) ;
166+
167+ test ( "errorKind=decode for JSON parse failure" , async ( ) => {
168+ const fetcher = stubFetcher ( [
169+ {
170+ match : ( u ) => u . includes ( "commons.wikimedia.org" ) ,
171+ handler : async ( ) => {
172+ throw new Error ( "Unexpected token < in JSON at position 0" ) ;
173+ } ,
174+ } ,
175+ ] ) ;
176+ const out = await searchImages ( "test" , { providers : [ "wikimedia" ] , fetcher } ) ;
177+ const report = out . providerReports . find ( ( r ) => r . provider === "wikimedia" ) ;
178+ expect ( report ?. ok ) . toBe ( false ) ;
179+ expect ( report ?. errorKind ) . toBe ( "decode" ) ;
180+ } ) ;
63181} ) ;
0 commit comments