@@ -17,6 +17,7 @@ import (
1717
1818 "github.com/rest-sh/restish/v2/config"
1919 "github.com/rest-sh/restish/v2/internal/cli"
20+ internalspec "github.com/rest-sh/restish/v2/internal/spec"
2021)
2122
2223type roundTripperFunc func (* http.Request ) (* http.Response , error )
@@ -307,6 +308,58 @@ func TestCommandSurfaceSyncedFallbackPreservesOriginalArgs(t *testing.T) {
307308 }
308309}
309310
311+ func TestCommandSurfaceBaseURLFallbackRefreshesUnknownOperation (t * testing.T ) {
312+ specIncludesNew := false
313+ var specRequests int
314+ var lastPath string
315+ srv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
316+ switch r .URL .Path {
317+ case "/openapi.json" :
318+ specRequests ++
319+ w .Header ().Set ("Content-Type" , "application/json" )
320+ fmt .Fprint (w , commandSurfaceRefreshSpec (specIncludesNew ))
321+ case "/new" :
322+ lastPath = r .URL .Path
323+ w .Header ().Set ("Content-Type" , "application/json" )
324+ fmt .Fprint (w , `{"ok":true}` )
325+ default :
326+ http .NotFound (w , r )
327+ }
328+ }))
329+ defer srv .Close ()
330+
331+ cfgFile := writeTestConfig (t , & config.Config {APIs : map [string ]* config.APIConfig {
332+ "api" : {BaseURL : srv .URL },
333+ }})
334+ cacheDir := t .TempDir ()
335+
336+ c , out , _ := newTestCLI (t )
337+ c .Hooks ().ConfigPath = cfgFile
338+ c .Hooks ().SpecCachePath = cacheDir
339+ c .SetCommandSurface (cli.CommandSurface {PromotedAPI : "api" })
340+ if err := c .Run ([]string {"example" , "--help" }); err != nil {
341+ t .Fatalf ("prime help: %v" , err )
342+ }
343+ if got := out .String (); ! strings .Contains (got , "get-old" ) || strings .Contains (got , "get-new" ) {
344+ t .Fatalf ("expected initial help to use old cached operations, got:\n %s" , got )
345+ }
346+
347+ specIncludesNew = true
348+ c , _ , _ = newTestCLI (t )
349+ c .Hooks ().ConfigPath = cfgFile
350+ c .Hooks ().SpecCachePath = cacheDir
351+ c .SetCommandSurface (cli.CommandSurface {PromotedAPI : "api" })
352+ if err := c .Run ([]string {"example" , "get-new" }); err != nil {
353+ t .Fatalf ("refreshed generated command: %v" , err )
354+ }
355+ if lastPath != "/new" {
356+ t .Fatalf ("request path = %q, want /new" , lastPath )
357+ }
358+ if specRequests < 2 {
359+ t .Fatalf ("spec requests = %d, want initial discovery and refresh" , specRequests )
360+ }
361+ }
362+
310363func commandSurfaceRefreshSpec (includeNew bool ) string {
311364 paths := `"/old":{"get":{"operationId":"getOld","responses":{"200":{"description":"OK"}}}}`
312365 if includeNew {
@@ -404,6 +457,233 @@ func TestCommandSurfaceRootAuthHeader(t *testing.T) {
404457 }
405458}
406459
460+ func TestCommandSurfaceRootAuthOperationFetchesMetadataOnFirstRun (t * testing.T ) {
461+ var specRequests int
462+ var baseURL string
463+ srv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
464+ switch r .URL .Path {
465+ case "/openapi.json" :
466+ specRequests ++
467+ w .Header ().Set ("Content-Type" , "application/json" )
468+ fmt .Fprint (w , commandSurfaceAuthSpec (baseURL , true ))
469+ default :
470+ http .NotFound (w , r )
471+ }
472+ }))
473+ defer srv .Close ()
474+ baseURL = srv .URL
475+
476+ cfgFile := writeTestConfig (t , & config.Config {APIs : map [string ]* config.APIConfig {
477+ "api" : {
478+ BaseURL : srv .URL ,
479+ Profiles : map [string ]* config.ProfileConfig {
480+ "default" : profileCredentials (map [string ]* config.CredentialConfig {
481+ "PartnerKey" : testCredential (apiKeyAuth ("header" , "X-Partner-Key" , "secret" )),
482+ }),
483+ },
484+ },
485+ }})
486+
487+ c , out , _ := newTestCLI (t )
488+ c .Hooks ().ConfigPath = cfgFile
489+ c .SetCommandSurface (cli.CommandSurface {PromotedAPI : "api" })
490+ if err := c .Run ([]string {"example" , "auth" , "header" , "--operation" , "partner-report" }); err != nil {
491+ t .Fatalf ("auth header operation: %v" , err )
492+ }
493+ if got := strings .TrimSpace (out .String ()); got != "X-Partner-Key: secret" {
494+ t .Fatalf ("auth header operation = %q, want X-Partner-Key: secret" , got )
495+ }
496+ if specRequests != 1 {
497+ t .Fatalf ("spec requests = %d, want 1" , specRequests )
498+ }
499+ }
500+
501+ func TestCommandSurfaceRootAuthOperationRefreshesMissingFreshOperation (t * testing.T ) {
502+ specIncludesPartner := false
503+ var specRequests int
504+ var baseURL string
505+ srv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
506+ switch r .URL .Path {
507+ case "/openapi.json" :
508+ specRequests ++
509+ w .Header ().Set ("Content-Type" , "application/json" )
510+ fmt .Fprint (w , commandSurfaceAuthSpec (baseURL , specIncludesPartner ))
511+ default :
512+ http .NotFound (w , r )
513+ }
514+ }))
515+ defer srv .Close ()
516+ baseURL = srv .URL
517+
518+ cfgFile := writeTestConfig (t , & config.Config {APIs : map [string ]* config.APIConfig {
519+ "api" : {
520+ BaseURL : srv .URL ,
521+ Profiles : map [string ]* config.ProfileConfig {
522+ "default" : profileCredentials (map [string ]* config.CredentialConfig {
523+ "PartnerKey" : testCredential (apiKeyAuth ("header" , "X-Partner-Key" , "secret" )),
524+ }),
525+ },
526+ },
527+ }})
528+ cacheDir := t .TempDir ()
529+
530+ c , out , _ := newTestCLI (t )
531+ c .Hooks ().ConfigPath = cfgFile
532+ c .Hooks ().SpecCachePath = cacheDir
533+ c .SetCommandSurface (cli.CommandSurface {PromotedAPI : "api" })
534+ if err := c .Run ([]string {"example" , "--help" }); err != nil {
535+ t .Fatalf ("prime help: %v" , err )
536+ }
537+ if got := out .String (); strings .Contains (got , "partner-report" ) {
538+ t .Fatalf ("expected initial help to omit partner-report, got:\n %s" , got )
539+ }
540+
541+ specIncludesPartner = true
542+ c , out , _ = newTestCLI (t )
543+ c .Hooks ().ConfigPath = cfgFile
544+ c .Hooks ().SpecCachePath = cacheDir
545+ c .SetCommandSurface (cli.CommandSurface {PromotedAPI : "api" })
546+ if err := c .Run ([]string {"example" , "auth" , "header" , "--operation" , "partner-report" }); err != nil {
547+ t .Fatalf ("auth header operation: %v" , err )
548+ }
549+ if got := strings .TrimSpace (out .String ()); got != "X-Partner-Key: secret" {
550+ t .Fatalf ("auth header operation = %q, want X-Partner-Key: secret" , got )
551+ }
552+ if specRequests < 2 {
553+ t .Fatalf ("spec requests = %d, want initial discovery and refresh" , specRequests )
554+ }
555+ }
556+
557+ func TestCommandSurfaceRootAuthOperationUsesRawSpecCacheBeforeRefresh (t * testing.T ) {
558+ var originRequests int
559+ origin := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
560+ originRequests ++
561+ http .NotFound (w , r )
562+ }))
563+ defer origin .Close ()
564+ var profileRequests int
565+ profile := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
566+ profileRequests ++
567+ http .NotFound (w , r )
568+ }))
569+ defer profile .Close ()
570+
571+ rawSpec := openAPISpec (origin .URL , "Auth API" ,
572+ openAPISecuritySchemes (`"PartnerKey":{"type":"apiKey","in":"header","name":"X-Partner-Key"}` ),
573+ openAPIPaths (openAPIGet ("/v2/private" , "" , `"security":[{"PartnerKey":[]}]` )))
574+ apiSpec , err := internalspec.OpenAPILoader {}.Load ([]byte (rawSpec ))
575+ if err != nil {
576+ t .Fatalf ("load spec: %v" , err )
577+ }
578+ cacheDir := t .TempDir ()
579+ if err := internalspec .StoreSpecInCache (cacheDir , "api" , cli .Version , apiSpec , nil , internalspec.OperationOptions {
580+ BaseURL : origin .URL ,
581+ OperationBase : "/v1" ,
582+ }, time .Hour ); err != nil {
583+ t .Fatalf ("store spec cache: %v" , err )
584+ }
585+
586+ cfgFile := writeTestConfig (t , & config.Config {APIs : map [string ]* config.APIConfig {
587+ "api" : {
588+ BaseURL : origin .URL ,
589+ OperationBase : "/v1" ,
590+ Profiles : map [string ]* config.ProfileConfig {
591+ "staging" : {
592+ BaseURL : profile .URL ,
593+ OperationBase : "/v2" ,
594+ Credentials : map [string ]* config.CredentialConfig {
595+ "PartnerKey" : testCredential (apiKeyAuth ("header" , "X-Partner-Key" , "secret" )),
596+ },
597+ },
598+ },
599+ },
600+ }})
601+
602+ c , out , _ := newTestCLI (t )
603+ c .Hooks ().ConfigPath = cfgFile
604+ c .Hooks ().SpecCachePath = cacheDir
605+ c .SetCommandSurface (cli.CommandSurface {PromotedAPI : "api" })
606+ if err := c .Run ([]string {"example" , "--rsh-profile" , "staging" , "auth" , "header" , "--operation" , "get-private" }); err != nil {
607+ t .Fatalf ("auth header operation: %v" , err )
608+ }
609+ if got := strings .TrimSpace (out .String ()); got != "X-Partner-Key: secret" {
610+ t .Fatalf ("auth header operation = %q, want X-Partner-Key: secret" , got )
611+ }
612+ if originRequests != 0 || profileRequests != 0 {
613+ t .Fatalf ("discovery requests: origin=%d profile=%d, want 0" , originRequests , profileRequests )
614+ }
615+ }
616+
617+ func TestCommandSurfaceRootAuthOperationConflictsDoNotRefreshMetadata (t * testing.T ) {
618+ var specRequests int
619+ srv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
620+ if r .URL .Path == "/openapi.json" {
621+ specRequests ++
622+ }
623+ http .NotFound (w , r )
624+ }))
625+ defer srv .Close ()
626+
627+ cfgFile := writeTestConfig (t , & config.Config {APIs : map [string ]* config.APIConfig {
628+ "api" : {
629+ BaseURL : srv .URL ,
630+ Profiles : map [string ]* config.ProfileConfig {
631+ "default" : profileCredentials (map [string ]* config.CredentialConfig {
632+ "PartnerKey" : testCredential (apiKeyAuth ("header" , "X-Partner-Key" , "secret" )),
633+ }),
634+ },
635+ },
636+ }})
637+
638+ tests := []struct {
639+ name string
640+ args []string
641+ want string
642+ }{
643+ {
644+ name : "auth get credential and operation" ,
645+ args : []string {"example" , "auth" , "get" , "PartnerKey" , "--operation" , "partner-report" },
646+ want : "--operation and credential ID are mutually exclusive" ,
647+ },
648+ {
649+ name : "auth inspect credential and operation" ,
650+ args : []string {"example" , "auth" , "inspect" , "--credential" , "PartnerKey" , "--operation" , "partner-report" },
651+ want : "--operation and --credential are mutually exclusive" ,
652+ },
653+ {
654+ name : "auth inspect operation and output format" ,
655+ args : []string {"example" , "auth" , "inspect" , "--operation" , "partner-report" , "-o" , "json" },
656+ want : "does not support -o/--rsh-output-format" ,
657+ },
658+ }
659+ for _ , tt := range tests {
660+ t .Run (tt .name , func (t * testing.T ) {
661+ specRequests = 0
662+ c , _ , _ := newTestCLI (t )
663+ c .Hooks ().ConfigPath = cfgFile
664+ c .SetCommandSurface (cli.CommandSurface {PromotedAPI : "api" })
665+
666+ err := c .Run (tt .args )
667+ if err == nil || ! strings .Contains (err .Error (), tt .want ) {
668+ t .Fatalf ("error = %v, want %q" , err , tt .want )
669+ }
670+ if specRequests != 0 {
671+ t .Fatalf ("spec requests = %d, want 0" , specRequests )
672+ }
673+ })
674+ }
675+ }
676+
677+ func commandSurfaceAuthSpec (baseURL string , includePartner bool ) string {
678+ paths := []string {openAPIGet ("/old" , "oldReport" , `"security":[{"PartnerKey":[]}]` )}
679+ if includePartner {
680+ paths = append (paths , openAPIGet ("/partner" , "partnerReport" , `"security":[{"PartnerKey":[]}]` ))
681+ }
682+ return openAPISpec (baseURL , "Auth API" ,
683+ openAPISecuritySchemes (`"PartnerKey":{"type":"apiKey","in":"header","name":"X-Partner-Key"}` ),
684+ openAPIPaths (paths ... ))
685+ }
686+
407687func writeCommandSurfaceSpecConfig (t * testing.T , c * cli.CLI , apiName , operationID string ) {
408688 t .Helper ()
409689 specPath := filepath .Join (t .TempDir (), "openapi.yaml" )
0 commit comments