@@ -406,6 +406,168 @@ describe('netlify and railway deploys', () => {
406406 expect ( requestedUrls ) . toContain ( 'GET https://api.netlify.com/api/v1/deploys/deploy-1' ) ;
407407 } ) ;
408408
409+ it ( 'throws DeployError when existing site repository settings update fails' , async ( ) => {
410+ const fetchMock = vi . fn ( async ( input : string | URL | Request , init ?: RequestInit ) => {
411+ const url =
412+ typeof input === 'string'
413+ ? input
414+ : input instanceof Request
415+ ? input . url
416+ : String ( input ) ;
417+ const method = init ?. method || ( input instanceof Request ? input . method : 'GET' ) ;
418+
419+ if ( url === 'https://api.github.com/user' && method === 'GET' ) {
420+ return new Response ( JSON . stringify ( { login : 'testuser' } ) , {
421+ status : 200 ,
422+ headers : { 'content-type' : 'application/json' } ,
423+ } ) ;
424+ }
425+ if ( url === 'https://api.github.com/repos/testuser/od-p1' && method === 'GET' ) {
426+ return new Response ( JSON . stringify ( { id : 123 , name : 'od-p1' } ) , {
427+ status : 200 ,
428+ headers : { 'content-type' : 'application/json' } ,
429+ } ) ;
430+ }
431+ if ( url === 'https://api.github.com/repos/testuser/od-p1/keys' && method === 'POST' ) {
432+ return new Response ( JSON . stringify ( { id : 789 } ) , {
433+ status : 201 ,
434+ headers : { 'content-type' : 'application/json' } ,
435+ } ) ;
436+ }
437+ if ( url . startsWith ( 'https://api.github.com/repos/testuser/od-p1/contents/' ) && method === 'GET' ) {
438+ return new Response ( '' , { status : 404 } ) ;
439+ }
440+ if ( url . startsWith ( 'https://api.github.com/repos/testuser/od-p1/contents/' ) && method === 'PUT' ) {
441+ return new Response ( JSON . stringify ( { content : { sha : 'abc123' } } ) , {
442+ status : 201 ,
443+ headers : { 'content-type' : 'application/json' } ,
444+ } ) ;
445+ }
446+ if ( url . endsWith ( '/deploy_keys' ) && method === 'POST' ) {
447+ return new Response ( JSON . stringify ( { id : 'deploy-key-1' , public_key : 'ssh-rsa AAAAB3NzaC1...' } ) , {
448+ status : 200 ,
449+ headers : { 'content-type' : 'application/json' } ,
450+ } ) ;
451+ }
452+ if ( url . endsWith ( '/sites/site-1' ) && method === 'PUT' ) {
453+ return new Response ( JSON . stringify ( { message : 'Repository settings update rejected' } ) , {
454+ status : 422 ,
455+ headers : { 'content-type' : 'application/json' } ,
456+ } ) ;
457+ }
458+
459+ throw new Error ( `Unexpected fetch: ${ method } ${ url } ` ) ;
460+ } ) ;
461+ vi . stubGlobal ( 'fetch' , fetchMock ) ;
462+
463+ await expect (
464+ deployToNetlify ( {
465+ config : { token : 'netlify-token-secret' , githubToken : 'ghp-test-token' } ,
466+ projectId : 'p1' ,
467+ projectsRoot : '/tmp/test-projects' ,
468+ files : [
469+ {
470+ file : 'index.html' ,
471+ data : Buffer . from ( '<!doctype html><h1>Hello</h1>' ) ,
472+ contentType : 'text/html' ,
473+ } ,
474+ ] ,
475+ priorMetadata : { siteId : 'site-1' } ,
476+ } )
477+ ) . rejects . toThrowError ( / R e p o s i t o r y s e t t i n g s u p d a t e r e j e c t e d / ) ;
478+ } ) ;
479+
480+ it ( 'throws DeployError when fallback site repository settings update fails' , async ( ) => {
481+ const fetchMock = vi . fn ( async ( input : string | URL | Request , init ?: RequestInit ) => {
482+ const url =
483+ typeof input === 'string'
484+ ? input
485+ : input instanceof Request
486+ ? input . url
487+ : String ( input ) ;
488+ const method = init ?. method || ( input instanceof Request ? input . method : 'GET' ) ;
489+
490+ if ( url === 'https://api.github.com/user' && method === 'GET' ) {
491+ return new Response ( JSON . stringify ( { login : 'testuser' } ) , {
492+ status : 200 ,
493+ headers : { 'content-type' : 'application/json' } ,
494+ } ) ;
495+ }
496+ if ( url === 'https://api.github.com/repos/testuser/od-p1' && method === 'GET' ) {
497+ return new Response ( JSON . stringify ( { id : 123 , name : 'od-p1' } ) , {
498+ status : 200 ,
499+ headers : { 'content-type' : 'application/json' } ,
500+ } ) ;
501+ }
502+ if ( url === 'https://api.github.com/repos/testuser/od-p1/keys' && method === 'POST' ) {
503+ return new Response ( JSON . stringify ( { id : 789 } ) , {
504+ status : 201 ,
505+ headers : { 'content-type' : 'application/json' } ,
506+ } ) ;
507+ }
508+ if ( url . startsWith ( 'https://api.github.com/repos/testuser/od-p1/contents/' ) && method === 'GET' ) {
509+ return new Response ( '' , { status : 404 } ) ;
510+ }
511+ if ( url . startsWith ( 'https://api.github.com/repos/testuser/od-p1/contents/' ) && method === 'PUT' ) {
512+ return new Response ( JSON . stringify ( { content : { sha : 'abc123' } } ) , {
513+ status : 201 ,
514+ headers : { 'content-type' : 'application/json' } ,
515+ } ) ;
516+ }
517+ if ( url . includes ( '/sites?name=od-p1' ) && method === 'GET' ) {
518+ return new Response ( JSON . stringify ( [ ] ) , {
519+ status : 200 ,
520+ headers : { 'content-type' : 'application/json' } ,
521+ } ) ;
522+ }
523+ if ( url . endsWith ( '/deploy_keys' ) && method === 'POST' ) {
524+ return new Response ( JSON . stringify ( { id : 'deploy-key-1' , public_key : 'ssh-rsa AAAAB3NzaC1...' } ) , {
525+ status : 200 ,
526+ headers : { 'content-type' : 'application/json' } ,
527+ } ) ;
528+ }
529+ if ( url . endsWith ( '/sites' ) && method === 'POST' ) {
530+ const parsed = JSON . parse ( init ?. body ? String ( init . body ) : '{}' ) ;
531+ if ( parsed . repo ) {
532+ return new Response ( JSON . stringify ( { message : 'Direct create failed' } ) , {
533+ status : 400 ,
534+ headers : { 'content-type' : 'application/json' } ,
535+ } ) ;
536+ } else {
537+ return new Response ( JSON . stringify ( { id : 'site-fallback' , site_id : 'site-fallback' } ) , {
538+ status : 200 ,
539+ headers : { 'content-type' : 'application/json' } ,
540+ } ) ;
541+ }
542+ }
543+ if ( url . endsWith ( '/sites/site-fallback' ) && method === 'PUT' ) {
544+ return new Response ( JSON . stringify ( { message : 'Fallback update rejected' } ) , {
545+ status : 422 ,
546+ headers : { 'content-type' : 'application/json' } ,
547+ } ) ;
548+ }
549+
550+ throw new Error ( `Unexpected fetch: ${ method } ${ url } ` ) ;
551+ } ) ;
552+ vi . stubGlobal ( 'fetch' , fetchMock ) ;
553+
554+ await expect (
555+ deployToNetlify ( {
556+ config : { token : 'netlify-token-secret' , githubToken : 'ghp-test-token' } ,
557+ projectId : 'p1' ,
558+ projectsRoot : '/tmp/test-projects' ,
559+ files : [
560+ {
561+ file : 'index.html' ,
562+ data : Buffer . from ( '<!doctype html><h1>Hello</h1>' ) ,
563+ contentType : 'text/html' ,
564+ } ,
565+ ] ,
566+ } )
567+ ) . rejects . toThrowError ( / F a l l b a c k u p d a t e r e j e c t e d / ) ;
568+ } ) ;
569+
570+
409571 it ( 'creates a Railway project, service, deployment, and service domain from the UI-backed file set' , async ( ) => {
410572 const graphQlCalls : Array < { query : string ; variables : Record < string , unknown > } > = [ ] ;
411573 const uploadedPaths : string [ ] = [ ] ;
0 commit comments