@@ -660,6 +660,125 @@ describe("containers registries delete", () => {
660660 } ) ;
661661} ) ;
662662
663+ describe ( "containers registries credentials" , ( ) => {
664+ const { setIsTTY } = useMockIsTTY ( ) ;
665+ const std = mockConsoleMethods ( ) ;
666+ mockAccountId ( ) ;
667+ mockApiToken ( ) ;
668+ beforeEach ( ( ) => {
669+ mockAccount ( ) ;
670+ } ) ;
671+
672+ afterEach ( ( ) => {
673+ msw . resetHandlers ( ) ;
674+ } ) ;
675+
676+ it ( "should reject non-Cloudflare registry domains" , async ( ) => {
677+ setIsTTY ( false ) ;
678+ await expect (
679+ runWrangler ( "containers registries credentials example.com --push" )
680+ ) . rejects . toThrowErrorMatchingInlineSnapshot (
681+ `[Error: The credentials command only accepts the Cloudflare managed registry (registry.cloudflare.com).]`
682+ ) ;
683+ } ) ;
684+
685+ it ( "should default to Cloudflare registry when DOMAIN is omitted" , async ( ) => {
686+ setIsTTY ( false ) ;
687+ mockGenerateCredentials ( "registry.cloudflare.com" , "test-password" , 15 , [
688+ "push" ,
689+ ] ) ;
690+
691+ await runWrangler ( "containers registries credentials --push" ) ;
692+
693+ expect ( std . out ) . toMatchInlineSnapshot ( `"test-password"` ) ;
694+ } ) ;
695+
696+ it ( "should require --push or --pull" , async ( ) => {
697+ setIsTTY ( false ) ;
698+ await expect (
699+ runWrangler ( "containers registries credentials registry.cloudflare.com" )
700+ ) . rejects . toThrowErrorMatchingInlineSnapshot (
701+ `[Error: You have to specify either --push or --pull in the command.]`
702+ ) ;
703+ } ) ;
704+
705+ it ( "should generate credentials with --push" , async ( ) => {
706+ setIsTTY ( false ) ;
707+ mockGenerateCredentials ( "registry.cloudflare.com" , "test-password" , 15 , [
708+ "push" ,
709+ ] ) ;
710+
711+ await runWrangler (
712+ "containers registries credentials registry.cloudflare.com --push"
713+ ) ;
714+
715+ expect ( std . out ) . toMatchInlineSnapshot ( `"test-password"` ) ;
716+ } ) ;
717+
718+ it ( "should generate credentials with --pull" , async ( ) => {
719+ setIsTTY ( false ) ;
720+ mockGenerateCredentials ( "registry.cloudflare.com" , "test-password" , 15 , [
721+ "pull" ,
722+ ] ) ;
723+
724+ await runWrangler (
725+ "containers registries credentials registry.cloudflare.com --pull"
726+ ) ;
727+
728+ expect ( std . out ) . toMatchInlineSnapshot ( `"test-password"` ) ;
729+ } ) ;
730+
731+ it ( "should generate credentials with both --push and --pull" , async ( ) => {
732+ setIsTTY ( false ) ;
733+ mockGenerateCredentials ( "registry.cloudflare.com" , "jwt-token" , 15 , [
734+ "push" ,
735+ "pull" ,
736+ ] ) ;
737+
738+ await runWrangler (
739+ "containers registries credentials registry.cloudflare.com --push --pull"
740+ ) ;
741+
742+ expect ( std . out ) . toMatchInlineSnapshot ( `"jwt-token"` ) ;
743+ } ) ;
744+
745+ it ( "should support custom expiration-minutes" , async ( ) => {
746+ setIsTTY ( false ) ;
747+ mockGenerateCredentials (
748+ "registry.cloudflare.com" ,
749+ "custom-expiry-token" ,
750+ 30 ,
751+ [ "push" ]
752+ ) ;
753+
754+ await runWrangler (
755+ "containers registries credentials registry.cloudflare.com --push --expiration-minutes=30"
756+ ) ;
757+
758+ expect ( std . out ) . toMatchInlineSnapshot ( `"custom-expiry-token"` ) ;
759+ } ) ;
760+
761+ it ( "should output valid JSON when --json flag is used" , async ( ) => {
762+ setIsTTY ( false ) ;
763+ mockGenerateCredentials ( "registry.cloudflare.com" , "test-password" , 15 , [
764+ "push" ,
765+ ] ) ;
766+
767+ await runWrangler (
768+ "containers registries credentials registry.cloudflare.com --push --json"
769+ ) ;
770+
771+ expect ( JSON . parse ( std . out ) ) . toMatchInlineSnapshot ( `
772+ {
773+ "account_id": "some-account-id",
774+ "password": "test-password",
775+ "registry_host": "registry.cloudflare.com",
776+ "username": "test-username",
777+ }
778+ ` ) ;
779+ } ) ;
780+ } ) ;
781+
663782const mockPutRegistry = ( expected ?: object ) => {
664783 msw . use (
665784 http . post (
@@ -704,3 +823,32 @@ const mockDeleteRegistry = (domain: string, secretsStoreRef?: string) => {
704823 )
705824 ) ;
706825} ;
826+
827+ const mockGenerateCredentials = (
828+ domain : string ,
829+ password : string ,
830+ expectedExpirationMinutes : number ,
831+ expectedPermissions : string [ ]
832+ ) => {
833+ msw . use (
834+ http . post (
835+ `*/accounts/:accountId/containers/registries/${ domain } /credentials` ,
836+ async ( { request, params } ) => {
837+ const body = ( await request . json ( ) ) as {
838+ expiration_minutes : number ;
839+ permissions : string [ ] ;
840+ } ;
841+ expect ( body . expiration_minutes ) . toBe ( expectedExpirationMinutes ) ;
842+ expect ( body . permissions ) . toEqual ( expectedPermissions ) ;
843+ return HttpResponse . json (
844+ createFetchResult ( {
845+ account_id : params . accountId ,
846+ registry_host : domain ,
847+ username : "test-username" ,
848+ password : password ,
849+ } )
850+ ) ;
851+ }
852+ )
853+ ) ;
854+ } ;
0 commit comments