@@ -237,3 +237,81 @@ test("stable releases fail when no nightly is published", async () => {
237237 const { options } = nightlyCommitFixture ( { releases : [ nightly ( 1 , { tagName : "v1.0.0" } ) ] } ) ;
238238 await assert . rejects ( resolveLatestNightlyCommit ( options ) , / N o p u b l i s h e d n i g h t l y f o u n d / ) ;
239239} ) ;
240+
241+ const { readNightlyUpdateFeed } = require ( "./check-nightly-release.cjs" ) ;
242+
243+ // GitHub percent-encodes tag names in the feed, and any tag in the repository takes a slot.
244+ const atomEntry = ( tag ) =>
245+ `<entry><link rel="alternate" type="text/html" href="https://github.com/example/app/releases/tag/${ encodeURIComponent ( tag ) } "/></entry>` ;
246+ const atomFeed = ( tags ) => new Response ( `<feed>${ tags . map ( atomEntry ) . join ( "" ) } </feed>` ) ;
247+ const NEWEST = "v1.0.1-nightly.20260905.123" ;
248+ const OLDER = "v1.0.1-nightly.20260905.100" ;
249+
250+ function updateFeedFixture ( { feed, pointer } ) {
251+ const { options } = fixture ( {
252+ releases : [ nightly ( 1 ) , nightly ( 0 , { tagName : "v1.0.2-preview.1" } ) ] ,
253+ } ) ;
254+ options . fetch = async ( url ) =>
255+ url === "https://github.com/example/app/releases.atom"
256+ ? feed
257+ : url === "https://github.com/example/app/releases/download/update-feed/nightly.json"
258+ ? pointer
259+ : assert . fail ( `unexpected fetch ${ url } ` ) ;
260+ return options ;
261+ }
262+
263+ const missing = ( ) => new Response ( "Not Found" , { status : 404 } ) ;
264+ // HTTP 200, then the connection drops before the body arrives.
265+ const brokenBody = ( ) =>
266+ new Response ( new ReadableStream ( { start : ( controller ) => controller . error ( new Error ( "reset" ) ) } ) ) ;
267+ for ( const [ name , { feed, pointer } , expected ] of [
268+ [
269+ "a nightly crowded out of the feed and a missing pointer" ,
270+ { feed : atomFeed ( [ "v1.0.2-preview.1" , "mobile-android-build/2.3.0/abc" ] ) , pointer : missing ( ) } ,
271+ { pointerTag : undefined , feedNightly : undefined } ,
272+ ] ,
273+ [
274+ "the first nightly in the feed, which is the one older installs take" ,
275+ { feed : atomFeed ( [ "update-feed" , OLDER , NEWEST ] ) , pointer : Response . json ( { tag_name : OLDER } ) } ,
276+ { pointerTag : OLDER , feedNightly : OLDER } ,
277+ ] ,
278+ [
279+ "a malformed pointer as stale and an unreadable feed as naming nothing" ,
280+ { feed : new Response ( "" , { status : 503 } ) , pointer : Response . json ( null ) } ,
281+ { pointerTag : undefined , feedNightly : undefined , unreadable : true } ,
282+ ] ,
283+ [
284+ "an HTML page with release links as an unreadable feed" ,
285+ {
286+ feed : new Response (
287+ `<html><feedback-form></feedback-form><a href="/example/app/releases/tag/${ OLDER } ">x</a></html>` ,
288+ ) ,
289+ pointer : Response . json ( { tag_name : NEWEST } ) ,
290+ } ,
291+ { pointerTag : NEWEST , feedNightly : undefined , unreadable : true } ,
292+ ] ,
293+ [
294+ "the pointer when the feed's body fails to arrive" ,
295+ { feed : brokenBody ( ) , pointer : Response . json ( { tag_name : NEWEST } ) } ,
296+ { pointerTag : NEWEST , feedNightly : undefined , unreadable : true } ,
297+ ] ,
298+ ] ) {
299+ test ( `update feed check reports ${ name } ` , async ( ) => {
300+ const { tag, pointerTag, feedNightly, feedTags } = await readNightlyUpdateFeed (
301+ updateFeedFixture ( { feed, pointer } ) ,
302+ ) ;
303+ const { unreadable = false , ...named } = expected ;
304+ assert . deepEqual ( { tag, pointerTag, feedNightly } , { tag : NEWEST , ...named } ) ;
305+ assert . equal ( feedTags === undefined , unreadable ) ;
306+ } ) ;
307+ }
308+
309+ for ( const [ name , pointer , error ] of [
310+ [ "answers with an error" , ( ) => new Response ( "" , { status : 503 } ) , / p o i n t e r a n s w e r e d H T T P 5 0 3 / ] ,
311+ [ "drops the connection" , brokenBody , / r e s e t / ] ,
312+ ] ) {
313+ test ( `update feed check does not call a pointer stale when GitHub ${ name } ` , async ( ) => {
314+ const options = updateFeedFixture ( { feed : atomFeed ( [ NEWEST ] ) , pointer : pointer ( ) } ) ;
315+ await assert . rejects ( readNightlyUpdateFeed ( options ) , error ) ;
316+ } ) ;
317+ }
0 commit comments