11import { fetchRemoteScorecardAndPlugins } from '../remote/fetch-scorecard.js' ;
2- import * as openapiCore from '@redocly/openapi-core ' ;
2+ import * as errorUtils from '../../../utils/error.js ' ;
33
44describe ( 'fetchRemoteScorecardAndPlugins' , ( ) => {
55 const mockFetch = vi . fn ( ) ;
@@ -9,7 +9,9 @@ describe('fetchRemoteScorecardAndPlugins', () => {
99 beforeEach ( ( ) => {
1010 global . fetch = mockFetch ;
1111 mockFetch . mockClear ( ) ;
12- vi . spyOn ( openapiCore . logger , 'warn' ) . mockImplementation ( ( ) => { } ) ;
12+ vi . spyOn ( errorUtils , 'exitWithError' ) . mockImplementation ( ( ) => {
13+ throw new Error ( 'exitWithError called' ) ;
14+ } ) ;
1315 } ) ;
1416
1517 afterEach ( ( ) => {
@@ -20,87 +22,65 @@ describe('fetchRemoteScorecardAndPlugins', () => {
2022 await expect ( fetchRemoteScorecardAndPlugins ( 'not-a-valid-url' , testToken ) ) . rejects . toThrow ( ) ;
2123 } ) ;
2224
23- it ( 'should return undefined when project URL pattern does not match' , async ( ) => {
24- const result = await fetchRemoteScorecardAndPlugins (
25- 'https://example.com/invalid/path' ,
26- testToken
27- ) ;
25+ it ( 'should throw error when project URL pattern does not match' , async ( ) => {
26+ await expect (
27+ fetchRemoteScorecardAndPlugins ( 'https://example.com/invalid/path' , testToken )
28+ ) . rejects . toThrow ( ) ;
2829
29- expect ( result ) . toBeUndefined ( ) ;
30- expect ( openapiCore . logger . warn ) . toHaveBeenCalledWith (
30+ expect ( errorUtils . exitWithError ) . toHaveBeenCalledWith (
3131 expect . stringContaining ( 'Invalid project URL format' )
3232 ) ;
3333 } ) ;
3434
35- it ( 'should return undefined when organization is not found' , async ( ) => {
35+ it ( 'should throw error when project is not found (404) ' , async ( ) => {
3636 mockFetch . mockResolvedValueOnce ( {
37- status : 200 ,
38- json : async ( ) => ( { items : [ ] } ) ,
37+ status : 404 ,
3938 } ) ;
4039
41- const result = await fetchRemoteScorecardAndPlugins ( validProjectUrl , testToken ) ;
40+ await expect ( fetchRemoteScorecardAndPlugins ( validProjectUrl , testToken ) ) . rejects . toThrow ( ) ;
4241
43- expect ( result ) . toBeUndefined ( ) ;
44- expect ( openapiCore . logger . warn ) . toHaveBeenCalledWith (
45- expect . stringContaining ( 'Organization not found' )
42+ expect ( errorUtils . exitWithError ) . toHaveBeenCalledWith (
43+ expect . stringContaining ( 'Failed to fetch project' )
4644 ) ;
4745 } ) ;
4846
49- it ( 'should return undefined when organization fetch fails ' , async ( ) => {
47+ it ( 'should throw error when unauthorized (401) ' , async ( ) => {
5048 mockFetch . mockResolvedValueOnce ( {
51- status : 404 ,
49+ status : 401 ,
5250 } ) ;
5351
54- const result = await fetchRemoteScorecardAndPlugins ( validProjectUrl , testToken ) ;
52+ await expect ( fetchRemoteScorecardAndPlugins ( validProjectUrl , testToken ) ) . rejects . toThrow ( ) ;
5553
56- expect ( result ) . toBeUndefined ( ) ;
57- expect ( openapiCore . logger . warn ) . toHaveBeenCalledWith (
58- expect . stringContaining ( 'Organization not found' )
54+ expect ( errorUtils . exitWithError ) . toHaveBeenCalledWith (
55+ expect . stringContaining ( 'Unauthorized access to project' )
5956 ) ;
6057 } ) ;
6158
62- it ( 'should return undefined when project is not found' , async ( ) => {
63- mockFetch
64- . mockResolvedValueOnce ( {
65- status : 200 ,
66- json : async ( ) => ( { items : [ { id : 'org-123' , slug : 'test-org' } ] } ) ,
67- } )
68- . mockResolvedValueOnce ( {
69- status : 200 ,
70- json : async ( ) => ( { items : [ ] } ) ,
71- } ) ;
59+ it ( 'should throw error when forbidden (403)' , async ( ) => {
60+ mockFetch . mockResolvedValueOnce ( {
61+ status : 403 ,
62+ } ) ;
7263
73- const result = await fetchRemoteScorecardAndPlugins ( validProjectUrl , testToken ) ;
64+ await expect ( fetchRemoteScorecardAndPlugins ( validProjectUrl , testToken ) ) . rejects . toThrow ( ) ;
7465
75- expect ( result ) . toBeUndefined ( ) ;
76- expect ( openapiCore . logger . warn ) . toHaveBeenCalledWith (
77- expect . stringContaining ( 'Project not found' )
66+ expect ( errorUtils . exitWithError ) . toHaveBeenCalledWith (
67+ expect . stringContaining ( 'Unauthorized access to project' )
7868 ) ;
7969 } ) ;
8070
81- it ( 'should return undefined when project has no scorecard config' , async ( ) => {
82- mockFetch
83- . mockResolvedValueOnce ( {
84- status : 200 ,
85- json : async ( ) => ( { items : [ { id : 'org-123' , slug : 'test-org' } ] } ) ,
86- } )
87- . mockResolvedValueOnce ( {
88- status : 200 ,
89- json : async ( ) => ( {
90- items : [
91- {
92- id : 'project-123' ,
93- slug : 'test-project' ,
94- config : { } ,
95- } ,
96- ] ,
97- } ) ,
98- } ) ;
71+ it ( 'should throw error when project has no scorecard config' , async ( ) => {
72+ mockFetch . mockResolvedValueOnce ( {
73+ status : 200 ,
74+ json : async ( ) => ( {
75+ id : 'project-123' ,
76+ slug : 'test-project' ,
77+ config : { } ,
78+ } ) ,
79+ } ) ;
9980
100- const result = await fetchRemoteScorecardAndPlugins ( validProjectUrl , testToken ) ;
81+ await expect ( fetchRemoteScorecardAndPlugins ( validProjectUrl , testToken ) ) . rejects . toThrow ( ) ;
10182
102- expect ( result ) . toBeUndefined ( ) ;
103- expect ( openapiCore . logger . warn ) . toHaveBeenCalledWith (
83+ expect ( errorUtils . exitWithError ) . toHaveBeenCalledWith (
10484 expect . stringContaining ( 'No scorecard configuration found' )
10585 ) ;
10686 } ) ;
@@ -110,33 +90,24 @@ describe('fetchRemoteScorecardAndPlugins', () => {
11090 levels : [ { name : 'Gold' , rules : { } } ] ,
11191 } ;
11292
113- mockFetch
114- . mockResolvedValueOnce ( {
115- status : 200 ,
116- json : async ( ) => ( { items : [ { id : 'org-123' , slug : 'test-org' } ] } ) ,
117- } )
118- . mockResolvedValueOnce ( {
119- status : 200 ,
120- json : async ( ) => ( {
121- items : [
122- {
123- id : 'project-123' ,
124- slug : 'test-project' ,
125- config : {
126- scorecard : mockScorecard ,
127- } ,
128- } ,
129- ] ,
130- } ) ,
131- } ) ;
93+ mockFetch . mockResolvedValueOnce ( {
94+ status : 200 ,
95+ json : async ( ) => ( {
96+ id : 'project-123' ,
97+ slug : 'test-project' ,
98+ config : {
99+ scorecard : mockScorecard ,
100+ } ,
101+ } ) ,
102+ } ) ;
132103
133104 const result = await fetchRemoteScorecardAndPlugins ( validProjectUrl , testToken ) ;
134105
135106 expect ( result ) . toEqual ( {
136107 scorecard : mockScorecard ,
137108 plugins : undefined ,
138109 } ) ;
139- expect ( openapiCore . logger . warn ) . not . toHaveBeenCalled ( ) ;
110+ expect ( errorUtils . exitWithError ) . not . toHaveBeenCalled ( ) ;
140111 } ) ;
141112
142113 it ( 'should return scorecard config with plugins when pluginsUrl is set' , async ( ) => {
@@ -146,23 +117,15 @@ describe('fetchRemoteScorecardAndPlugins', () => {
146117 const mockPluginsCode = 'export default [() => ({ id: "test-plugin" })]' ;
147118
148119 mockFetch
149- . mockResolvedValueOnce ( {
150- status : 200 ,
151- json : async ( ) => ( { items : [ { id : 'org-123' , slug : 'test-org' } ] } ) ,
152- } )
153120 . mockResolvedValueOnce ( {
154121 status : 200 ,
155122 json : async ( ) => ( {
156- items : [
157- {
158- id : 'project-123' ,
159- slug : 'test-project' ,
160- config : {
161- scorecard : mockScorecard ,
162- pluginsUrl : 'https://example.com/plugins.js' ,
163- } ,
164- } ,
165- ] ,
123+ id : 'project-123' ,
124+ slug : 'test-project' ,
125+ config : {
126+ scorecard : mockScorecard ,
127+ pluginsUrl : 'https://example.com/plugins.js' ,
128+ } ,
166129 } ) ,
167130 } )
168131 . mockResolvedValueOnce ( {
@@ -176,7 +139,7 @@ describe('fetchRemoteScorecardAndPlugins', () => {
176139 scorecard : mockScorecard ,
177140 plugins : mockPluginsCode ,
178141 } ) ;
179- expect ( mockFetch ) . toHaveBeenCalledTimes ( 3 ) ;
142+ expect ( mockFetch ) . toHaveBeenCalledTimes ( 2 ) ;
180143 } ) ;
181144
182145 it ( 'should return scorecard without plugins when plugin fetch fails' , async ( ) => {
@@ -185,23 +148,15 @@ describe('fetchRemoteScorecardAndPlugins', () => {
185148 } ;
186149
187150 mockFetch
188- . mockResolvedValueOnce ( {
189- status : 200 ,
190- json : async ( ) => ( { items : [ { id : 'org-123' , slug : 'test-org' } ] } ) ,
191- } )
192151 . mockResolvedValueOnce ( {
193152 status : 200 ,
194153 json : async ( ) => ( {
195- items : [
196- {
197- id : 'project-123' ,
198- slug : 'test-project' ,
199- config : {
200- scorecard : mockScorecard ,
201- pluginsUrl : 'https://example.com/plugins.js' ,
202- } ,
203- } ,
204- ] ,
154+ id : 'project-123' ,
155+ slug : 'test-project' ,
156+ config : {
157+ scorecard : mockScorecard ,
158+ pluginsUrl : 'https://example.com/plugins.js' ,
159+ } ,
205160 } ) ,
206161 } )
207162 . mockResolvedValueOnce ( {
@@ -216,10 +171,13 @@ describe('fetchRemoteScorecardAndPlugins', () => {
216171 } ) ;
217172 } ) ;
218173
219- it ( 'should use correct auth headers when fetching organization ' , async ( ) => {
174+ it ( 'should use correct auth headers with access token ' , async ( ) => {
220175 mockFetch . mockResolvedValueOnce ( {
221176 status : 200 ,
222- json : async ( ) => ( { items : [ ] } ) ,
177+ json : async ( ) => ( {
178+ id : 'project-123' ,
179+ config : { scorecard : { levels : [ ] } } ,
180+ } ) ,
223181 } ) ;
224182
225183 await fetchRemoteScorecardAndPlugins ( validProjectUrl , testToken ) ;
@@ -232,18 +190,49 @@ describe('fetchRemoteScorecardAndPlugins', () => {
232190 ) ;
233191 } ) ;
234192
193+ it ( 'should use correct auth headers with API key' , async ( ) => {
194+ const originalApiKey = process . env . REDOCLY_AUTHORIZATION ;
195+ process . env . REDOCLY_AUTHORIZATION = 'test-api-key' ;
196+
197+ mockFetch . mockResolvedValueOnce ( {
198+ status : 200 ,
199+ json : async ( ) => ( {
200+ id : 'project-123' ,
201+ config : { scorecard : { levels : [ ] } } ,
202+ } ) ,
203+ } ) ;
204+
205+ await fetchRemoteScorecardAndPlugins ( validProjectUrl , testToken ) ;
206+
207+ expect ( mockFetch ) . toHaveBeenCalledWith (
208+ expect . any ( URL ) ,
209+ expect . objectContaining ( {
210+ headers : { Authorization : 'Bearer test-api-key' } ,
211+ } )
212+ ) ;
213+
214+ // Restore original value
215+ if ( originalApiKey ) {
216+ process . env . REDOCLY_AUTHORIZATION = originalApiKey ;
217+ } else {
218+ delete process . env . REDOCLY_AUTHORIZATION ;
219+ }
220+ } ) ;
221+
235222 it ( 'should parse project URL with different residency' , async ( ) => {
236223 const customUrl = 'https://custom.redocly.com/org/my-org/project/my-project' ;
237224
238225 mockFetch . mockResolvedValueOnce ( {
239226 status : 200 ,
240- json : async ( ) => ( { items : [ ] } ) ,
227+ json : async ( ) => ( {
228+ id : 'project-123' ,
229+ config : { scorecard : { levels : [ ] } } ,
230+ } ) ,
241231 } ) ;
242232
243233 await fetchRemoteScorecardAndPlugins ( customUrl , testToken ) ;
244234
245235 const callUrl = mockFetch . mock . calls [ 0 ] [ 0 ] . toString ( ) ;
246- expect ( callUrl ) . toContain ( 'https://custom.redocly.com/api/orgs' ) ;
247- expect ( callUrl ) . toContain ( 'filter=slug%3Amy-org' ) ;
236+ expect ( callUrl ) . toBe ( 'https://custom.redocly.com/api/orgs/my-org/projects/my-project' ) ;
248237 } ) ;
249238} ) ;
0 commit comments