@@ -30,7 +30,7 @@ test("nested GitLab repositories use their own instance without the gitlab.com t
3030 license : { spdx_identifier : "MPL-2.0" } ,
3131 } ) ;
3232 if ( url . endsWith ( "/languages" ) ) return Response . json ( { C : 100 } ) ;
33- if ( url . endsWith ( "/releases/permalink/latest " ) ) return Response . json ( { } , { status : 404 } ) ;
33+ if ( url . includes ( "/releases? " ) ) return Response . json ( { } , { status : 404 } ) ;
3434 return Response . json ( [ { committed_date : "2026-10-31T00:00:00Z" } ] ) ;
3535 } ) ;
3636 const facts = await gitlabFacts ( { source_code_url : "https://gitlab.torproject.org/tpo/applications/mullvad-browser.git/-/tree/main" } ) ;
@@ -48,6 +48,57 @@ test("nested GitLab repositories use their own instance without the gitlab.com t
4848 assert . ok ( calls . every ( ( { options } ) => options . headers . Authorization === "Bearer gitlab-com-test-token" ) ) ;
4949} ) ;
5050
51+ test ( "GitLab release list refreshes stars and the latest release without following redirects" , async ( t ) => {
52+ const calls = [ ] ;
53+ t . mock . method ( globalThis , "fetch" , async ( url , options ) => {
54+ calls . push ( { url, options } ) ;
55+ if ( url . endsWith ( "?license=true" ) ) return Response . json ( { id : 1 , star_count : 42 } ) ;
56+ if ( url . endsWith ( "/languages" ) ) return Response . json ( { Python : 100 } ) ;
57+ if ( url . endsWith ( "/releases?per_page=1&order_by=released_at&sort=desc" ) ) {
58+ return Response . json ( [ { tag_name : "v6.0.0" , released_at : "2026-11-01T00:00:00Z" } ] ) ;
59+ }
60+ if ( url . endsWith ( "/repository/commits?per_page=1" ) ) return Response . json ( [ ] ) ;
61+ throw new Error ( `Unexpected request: ${ url } ` ) ;
62+ } ) ;
63+ const facts = await gitlabFacts ( { source_code_url : "https://gitlab.com/glitchtip/glitchtip" } ) ;
64+ assert . equal ( facts . source_stars , 42 ) ;
65+ assert . equal ( facts . source_latest_release , "v6.0.0" ) ;
66+ assert . equal ( facts . source_latest_release_at , "2026-11-01T00:00:00Z" ) ;
67+ assert . equal ( facts . raw . release . tag_name , "v6.0.0" ) ;
68+ assert . ok ( calls . every ( ( { options } ) => options . redirect === "error" ) ) ;
69+ } ) ;
70+
71+ for ( const status of [ 200 , 403 , 404 ] ) {
72+ test ( `GitLab stars are written when releases are unavailable or empty (${ status } )` , async ( t ) => {
73+ t . mock . method ( globalThis , "fetch" , async ( url ) => {
74+ if ( url . endsWith ( "?license=true" ) ) return Response . json ( { id : 1 , star_count : 123 } ) ;
75+ if ( url . endsWith ( "/languages" ) ) return Response . json ( { Ruby : 100 } ) ;
76+ if ( url . includes ( "/releases?" ) ) return Response . json ( status === 200 ? [ ] : { } , { status } ) ;
77+ return Response . json ( [ ] ) ;
78+ } ) ;
79+ const writes = [ ] ;
80+ const client = { async query ( sql , values ) {
81+ if ( sql . includes ( "select id, slug" ) ) return { rows : [ { id : "app-id" , slug : "gitlab" , source_code_url : "https://gitlab.com/gitlab-org/gitlab" } ] } ;
82+ writes . push ( values ) ;
83+ return { rows : [ ] } ;
84+ } } ;
85+ const report = await refreshSourceHost ( client , { host : "gitlab" } ) ;
86+ assert . equal ( report . refreshed , 1 ) ;
87+ assert . equal ( report . failed , 0 ) ;
88+ assert . equal ( writes . length , 1 ) ;
89+ assert . equal ( writes [ 0 ] [ 3 ] , 123 ) ;
90+ assert . equal ( writes [ 0 ] [ 19 ] , null ) ;
91+ } ) ;
92+ }
93+
94+ test ( "GitLab project permission errors still fail the refresh" , async ( t ) => {
95+ t . mock . method ( globalThis , "fetch" , async ( url ) => {
96+ if ( url . endsWith ( "?license=true" ) ) return Response . json ( { } , { status : 403 } ) ;
97+ return Response . json ( [ ] ) ;
98+ } ) ;
99+ await assert . rejects ( gitlabFacts ( { source_code_url : "https://gitlab.com/private/app" } ) , { status : 403 } ) ;
100+ } ) ;
101+
51102test ( "catalog requests send a GitHub credential only to the GitHub API" , async ( t ) => {
52103 env ( t , "GITHUB_TOKEN" , "github-test-token" ) ;
53104 const calls = [ ] ;
0 commit comments