@@ -69,6 +69,15 @@ function buildEndpointUrl(
6969 return `${ baseUrl } ${ targetId } /${ resourceType } ` ;
7070}
7171
72+ function buildVersionedEndpointUrl (
73+ baseUrl : string ,
74+ targetId : string ,
75+ versionId : string ,
76+ resourceType : 'sdl' | 'supergraph' | 'services' | 'metadata' ,
77+ ) {
78+ return `${ baseUrl } ${ targetId } /version/${ versionId } /${ resourceType } ` ;
79+ }
80+
7281function generateLegacyToken ( targetId : string ) {
7382 const encoder = new TextEncoder ( ) ;
7483 return (
@@ -425,6 +434,353 @@ function runArtifactsCDNTests(
425434 await server . stop ( ) ;
426435 }
427436 } ) ;
437+
438+ test . concurrent ( 'access versioned SDL artifact with valid credentials' , async ( { expect } ) => {
439+ const { createOrg } = await initSeed ( ) . createOwner ( ) ;
440+ const { createProject } = await createOrg ( ) ;
441+ const { createTargetAccessToken, createCdnAccess, target } = await createProject (
442+ ProjectType . Single ,
443+ ) ;
444+ const writeToken = await createTargetAccessToken ( { } ) ;
445+
446+ // Publish Schema
447+ const publishSchemaResult = await writeToken
448+ . publishSchema ( {
449+ author : 'Kamil' ,
450+ commit : 'abc123' ,
451+ sdl : `type Query { ping: String }` ,
452+ } )
453+ . then ( r => r . expectNoGraphQLErrors ( ) ) ;
454+
455+ expect ( publishSchemaResult . schemaPublish . __typename ) . toBe ( 'SchemaPublishSuccess' ) ;
456+
457+ // Fetch the latest valid version to get the version ID
458+ const latestVersion = await writeToken . fetchLatestValidSchema ( ) ;
459+ const versionId = latestVersion . latestValidVersion ?. id ;
460+ expect ( versionId ) . toBeDefined ( ) ;
461+
462+ const cdnAccessResult = await createCdnAccess ( ) ;
463+ const endpointBaseUrl = await getBaseEndpoint ( ) ;
464+
465+ // Test latest endpoint
466+ const latestUrl = buildEndpointUrl ( endpointBaseUrl , target . id , 'sdl' ) ;
467+ const latestResponse = await fetch ( latestUrl , {
468+ method : 'GET' ,
469+ headers : {
470+ 'x-hive-cdn-key' : cdnAccessResult . secretAccessToken ,
471+ } ,
472+ } ) ;
473+ expect ( latestResponse . status ) . toBe ( 200 ) ;
474+ const latestBody = await latestResponse . text ( ) ;
475+
476+ // Test versioned endpoint
477+ const versionedUrl = buildVersionedEndpointUrl ( endpointBaseUrl , target . id , versionId ! , 'sdl' ) ;
478+ const versionedResponse = await fetch ( versionedUrl , {
479+ method : 'GET' ,
480+ headers : {
481+ 'x-hive-cdn-key' : cdnAccessResult . secretAccessToken ,
482+ } ,
483+ } ) ;
484+
485+ expect ( versionedResponse . status ) . toBe ( 200 ) ;
486+ const versionedBody = await versionedResponse . text ( ) ;
487+
488+ // Both should return the same content
489+ expect ( versionedBody ) . toBe ( latestBody ) ;
490+ expect ( versionedBody ) . toMatchInlineSnapshot ( `
491+ type Query {
492+ ping: String
493+ }
494+ ` ) ;
495+
496+ // Verify the versioned S3 key exists
497+ const versionedArtifact = await fetchS3ObjectArtifact (
498+ 'artifacts' ,
499+ `artifact/${ target . id } /version/${ versionId } /sdl` ,
500+ ) ;
501+ expect ( versionedArtifact . body ) . toBe ( latestBody ) ;
502+
503+ expect ( versionedResponse . headers . get ( 'cache-control' ) ) . toBe (
504+ 'public, max-age=31536000, immutable' ,
505+ ) ;
506+ } ) ;
507+
508+ test . concurrent (
509+ 'versioned artifact returns 404 for non-existent version' ,
510+ async ( { expect } ) => {
511+ const { createOrg } = await initSeed ( ) . createOwner ( ) ;
512+ const { createProject } = await createOrg ( ) ;
513+ const { createTargetAccessToken, createCdnAccess, target } = await createProject (
514+ ProjectType . Single ,
515+ ) ;
516+ const writeToken = await createTargetAccessToken ( { } ) ;
517+
518+ // Publish Schema
519+ await writeToken
520+ . publishSchema ( {
521+ author : 'Kamil' ,
522+ commit : 'abc123' ,
523+ sdl : `type Query { ping: String }` ,
524+ } )
525+ . then ( r => r . expectNoGraphQLErrors ( ) ) ;
526+
527+ const cdnAccessResult = await createCdnAccess ( ) ;
528+ const endpointBaseUrl = await getBaseEndpoint ( ) ;
529+
530+ // Use a non-existent but valid UUID
531+ const nonExistentVersionId = '00000000-0000-0000-0000-000000000000' ;
532+ const versionedUrl = buildVersionedEndpointUrl (
533+ endpointBaseUrl ,
534+ target . id ,
535+ nonExistentVersionId ,
536+ 'sdl' ,
537+ ) ;
538+
539+ const response = await fetch ( versionedUrl , {
540+ method : 'GET' ,
541+ headers : {
542+ 'x-hive-cdn-key' : cdnAccessResult . secretAccessToken ,
543+ } ,
544+ } ) ;
545+
546+ expect ( response . status ) . toBe ( 404 ) ;
547+ } ,
548+ ) ;
549+
550+ test . concurrent (
551+ 'versioned artifact returns 404 for invalid UUID format' ,
552+ async ( { expect } ) => {
553+ const { createOrg } = await initSeed ( ) . createOwner ( ) ;
554+ const { createProject } = await createOrg ( ) ;
555+ const { createTargetAccessToken, createCdnAccess, target } = await createProject (
556+ ProjectType . Single ,
557+ ) ;
558+ const writeToken = await createTargetAccessToken ( { } ) ;
559+
560+ // Publish Schema
561+ await writeToken
562+ . publishSchema ( {
563+ author : 'Kamil' ,
564+ commit : 'abc123' ,
565+ sdl : `type Query { ping: String }` ,
566+ } )
567+ . then ( r => r . expectNoGraphQLErrors ( ) ) ;
568+
569+ const cdnAccessResult = await createCdnAccess ( ) ;
570+ const endpointBaseUrl = await getBaseEndpoint ( ) ;
571+
572+ // Use an invalid UUID format
573+ const invalidVersionId = 'not-a-valid-uuid' ;
574+ const versionedUrl = buildVersionedEndpointUrl (
575+ endpointBaseUrl ,
576+ target . id ,
577+ invalidVersionId ,
578+ 'sdl' ,
579+ ) ;
580+
581+ const response = await fetch ( versionedUrl , {
582+ method : 'GET' ,
583+ headers : {
584+ 'x-hive-cdn-key' : cdnAccessResult . secretAccessToken ,
585+ } ,
586+ } ) ;
587+
588+ expect ( response . status ) . toBe ( 404 ) ;
589+ } ,
590+ ) ;
591+
592+ test . concurrent ( 'access versioned federation supergraph artifact' , async ( { expect } ) => {
593+ const { createOrg } = await initSeed ( ) . createOwner ( ) ;
594+ const { createProject } = await createOrg ( ) ;
595+ const { createTargetAccessToken, createCdnAccess, target } = await createProject (
596+ ProjectType . Federation ,
597+ ) ;
598+ const writeToken = await createTargetAccessToken ( { } ) ;
599+
600+ // Publish Schema
601+ const publishSchemaResult = await writeToken
602+ . publishSchema ( {
603+ author : 'Kamil' ,
604+ commit : 'abc123' ,
605+ sdl : `type Query { ping: String }` ,
606+ service : 'ping' ,
607+ url : 'http://ping.com' ,
608+ } )
609+ . then ( r => r . expectNoGraphQLErrors ( ) ) ;
610+
611+ expect ( publishSchemaResult . schemaPublish . __typename ) . toBe ( 'SchemaPublishSuccess' ) ;
612+
613+ // Fetch the latest valid version to get the version ID
614+ const latestVersion = await writeToken . fetchLatestValidSchema ( ) ;
615+ const versionId = latestVersion . latestValidVersion ?. id ;
616+ expect ( versionId ) . toBeDefined ( ) ;
617+
618+ const cdnAccessResult = await createCdnAccess ( ) ;
619+ const endpointBaseUrl = await getBaseEndpoint ( ) ;
620+
621+ // Test versioned supergraph endpoint
622+ const versionedUrl = buildVersionedEndpointUrl (
623+ endpointBaseUrl ,
624+ target . id ,
625+ versionId ! ,
626+ 'supergraph' ,
627+ ) ;
628+ const versionedResponse = await fetch ( versionedUrl , {
629+ method : 'GET' ,
630+ headers : {
631+ 'x-hive-cdn-key' : cdnAccessResult . secretAccessToken ,
632+ } ,
633+ } ) ;
634+
635+ expect ( versionedResponse . status ) . toBe ( 200 ) ;
636+ const supergraphBody = await versionedResponse . text ( ) ;
637+ expect ( supergraphBody ) . toContain ( 'schema' ) ;
638+
639+ // Verify the versioned S3 key exists
640+ const versionedArtifact = await fetchS3ObjectArtifact (
641+ 'artifacts' ,
642+ `artifact/${ target . id } /version/${ versionId } /supergraph` ,
643+ ) ;
644+ expect ( versionedArtifact . body ) . toBe ( supergraphBody ) ;
645+
646+ expect ( versionedResponse . headers . get ( 'cache-control' ) ) . toBe (
647+ 'public, max-age=31536000, immutable' ,
648+ ) ;
649+ } ) ;
650+
651+ test . concurrent ( 'access versioned federation services artifact' , async ( { expect } ) => {
652+ const { createOrg } = await initSeed ( ) . createOwner ( ) ;
653+ const { createProject } = await createOrg ( ) ;
654+ const { createTargetAccessToken, createCdnAccess, target } = await createProject (
655+ ProjectType . Federation ,
656+ ) ;
657+ const writeToken = await createTargetAccessToken ( { } ) ;
658+
659+ // Publish Schema
660+ const publishSchemaResult = await writeToken
661+ . publishSchema ( {
662+ author : 'Kamil' ,
663+ commit : 'abc123' ,
664+ sdl : `type Query { ping: String }` ,
665+ service : 'ping' ,
666+ url : 'http://ping.com' ,
667+ } )
668+ . then ( r => r . expectNoGraphQLErrors ( ) ) ;
669+
670+ expect ( publishSchemaResult . schemaPublish . __typename ) . toBe ( 'SchemaPublishSuccess' ) ;
671+
672+ // Fetch the latest valid version to get the version ID
673+ const latestVersion = await writeToken . fetchLatestValidSchema ( ) ;
674+ const versionId = latestVersion . latestValidVersion ?. id ;
675+ expect ( versionId ) . toBeDefined ( ) ;
676+
677+ const cdnAccessResult = await createCdnAccess ( ) ;
678+ const endpointBaseUrl = await getBaseEndpoint ( ) ;
679+
680+ // Test versioned services endpoint
681+ const versionedUrl = buildVersionedEndpointUrl (
682+ endpointBaseUrl ,
683+ target . id ,
684+ versionId ! ,
685+ 'services' ,
686+ ) ;
687+ const versionedResponse = await fetch ( versionedUrl , {
688+ method : 'GET' ,
689+ headers : {
690+ 'x-hive-cdn-key' : cdnAccessResult . secretAccessToken ,
691+ } ,
692+ } ) ;
693+
694+ expect ( versionedResponse . status ) . toBe ( 200 ) ;
695+ expect ( versionedResponse . headers . get ( 'content-type' ) ) . toContain ( 'application/json' ) ;
696+ const servicesBody = await versionedResponse . text ( ) ;
697+ expect ( servicesBody ) . toMatchInlineSnapshot (
698+ '[{"name":"ping","sdl":"type Query { ping: String }","url":"http://ping.com"}]' ,
699+ ) ;
700+
701+ // Verify the versioned S3 key exists
702+ const versionedArtifact = await fetchS3ObjectArtifact (
703+ 'artifacts' ,
704+ `artifact/${ target . id } /version/${ versionId } /services` ,
705+ ) ;
706+ expect ( versionedArtifact . body ) . toBe ( servicesBody ) ;
707+
708+ expect ( versionedResponse . headers . get ( 'cache-control' ) ) . toBe (
709+ 'public, max-age=31536000, immutable' ,
710+ ) ;
711+ } ) ;
712+
713+ test . concurrent ( 'versioned artifact access without credentials' , async ( { expect } ) => {
714+ const { createOrg } = await initSeed ( ) . createOwner ( ) ;
715+ const { createProject } = await createOrg ( ) ;
716+ const { createTargetAccessToken, target } = await createProject ( ProjectType . Single ) ;
717+ const writeToken = await createTargetAccessToken ( { } ) ;
718+
719+ await writeToken
720+ . publishSchema ( {
721+ author : 'Kamil' ,
722+ commit : 'abc123' ,
723+ sdl : `type Query { ping: String }` ,
724+ } )
725+ . then ( r => r . expectNoGraphQLErrors ( ) ) ;
726+
727+ const latestVersion = await writeToken . fetchLatestValidSchema ( ) ;
728+ const versionId = latestVersion . latestValidVersion ?. id ;
729+ expect ( versionId ) . toBeDefined ( ) ;
730+
731+ const endpointBaseUrl = await getBaseEndpoint ( ) ;
732+ const versionedUrl = buildVersionedEndpointUrl ( endpointBaseUrl , target . id , versionId ! , 'sdl' ) ;
733+
734+ // Request without credentials
735+ const response = await fetch ( versionedUrl , { method : 'GET' } ) ;
736+ expect ( response . status ) . toBe ( 400 ) ;
737+ expect ( response . headers . get ( 'content-type' ) ) . toContain ( 'application/json' ) ;
738+ expect ( await response . json ( ) ) . toEqual ( {
739+ code : 'MISSING_AUTH_KEY' ,
740+ error : 'Hive CDN authentication key is missing' ,
741+ description :
742+ 'Please refer to the documentation for more details: https://docs.graphql-hive.com/features/registry-usage ' ,
743+ } ) ;
744+ } ) ;
745+
746+ test . concurrent ( 'versioned artifact access with invalid credentials' , async ( { expect } ) => {
747+ const { createOrg } = await initSeed ( ) . createOwner ( ) ;
748+ const { createProject } = await createOrg ( ) ;
749+ const { createTargetAccessToken, target } = await createProject ( ProjectType . Single ) ;
750+ const writeToken = await createTargetAccessToken ( { } ) ;
751+
752+ await writeToken
753+ . publishSchema ( {
754+ author : 'Kamil' ,
755+ commit : 'abc123' ,
756+ sdl : `type Query { ping: String }` ,
757+ } )
758+ . then ( r => r . expectNoGraphQLErrors ( ) ) ;
759+
760+ const latestVersion = await writeToken . fetchLatestValidSchema ( ) ;
761+ const versionId = latestVersion . latestValidVersion ?. id ;
762+ expect ( versionId ) . toBeDefined ( ) ;
763+
764+ const endpointBaseUrl = await getBaseEndpoint ( ) ;
765+ const versionedUrl = buildVersionedEndpointUrl ( endpointBaseUrl , target . id , versionId ! , 'sdl' ) ;
766+
767+ // Request with invalid credentials
768+ const response = await fetch ( versionedUrl , {
769+ method : 'GET' ,
770+ headers : {
771+ 'x-hive-cdn-key' : 'invalid-key' ,
772+ } ,
773+ } ) ;
774+ expect ( response . status ) . toBe ( 403 ) ;
775+ expect ( response . headers . get ( 'content-type' ) ) . toContain ( 'application/json' ) ;
776+ expect ( await response . json ( ) ) . toEqual ( {
777+ code : 'INVALID_AUTH_KEY' ,
778+ error :
779+ 'Hive CDN authentication key is invalid, or it does not match the requested target ID.' ,
780+ description :
781+ 'Please refer to the documentation for more details: https://docs.graphql-hive.com/features/registry-usage ' ,
782+ } ) ;
783+ } ) ;
428784 } ) ;
429785}
430786
0 commit comments