zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/extensions/sync/httpclient/client_internal_test.go (about)

     1  package client
     2  
     3  import (
     4  	"net/http"
     5  	"net/http/httptest"
     6  	"testing"
     7  	"time"
     8  
     9  	. "github.com/smartystreets/goconvey/convey"
    10  
    11  	"zotregistry.dev/zot/pkg/log"
    12  )
    13  
    14  func TestTokenCache(t *testing.T) {
    15  	Convey("Get/Set tokens", t, func() {
    16  		tokenCache := NewTokenCache()
    17  		token := &bearerToken{
    18  			Token:     "tokenA",
    19  			ExpiresIn: 3,
    20  			IssuedAt:  time.Now(),
    21  		}
    22  
    23  		token.expirationTime = token.IssuedAt.Add(time.Duration(token.ExpiresIn) * time.Second).Add(tokenBuffer)
    24  
    25  		tokenCache.Set("repo", token)
    26  		cachedToken := tokenCache.Get("repo")
    27  		So(cachedToken.Token, ShouldEqual, token.Token)
    28  
    29  		// add token which expires soon
    30  		token2 := &bearerToken{
    31  			Token:     "tokenB",
    32  			ExpiresIn: 1,
    33  			IssuedAt:  time.Now(),
    34  		}
    35  
    36  		token2.expirationTime = token2.IssuedAt.Add(time.Duration(token2.ExpiresIn) * time.Second).Add(tokenBuffer)
    37  
    38  		tokenCache.Set("repo2", token2)
    39  		cachedToken = tokenCache.Get("repo2")
    40  		So(cachedToken.Token, ShouldEqual, token2.Token)
    41  
    42  		time.Sleep(1 * time.Second)
    43  
    44  		// token3 should be expired when adding a new one
    45  		token3 := &bearerToken{
    46  			Token:     "tokenC",
    47  			ExpiresIn: 3,
    48  			IssuedAt:  time.Now(),
    49  		}
    50  
    51  		token3.expirationTime = token3.IssuedAt.Add(time.Duration(token3.ExpiresIn) * time.Second).Add(tokenBuffer)
    52  
    53  		tokenCache.Set("repo3", token3)
    54  		cachedToken = tokenCache.Get("repo3")
    55  		So(cachedToken.Token, ShouldEqual, token3.Token)
    56  
    57  		// token2 should be expired
    58  		token = tokenCache.Get("repo2")
    59  		So(token, ShouldBeNil)
    60  
    61  		time.Sleep(2 * time.Second)
    62  
    63  		// the rest of them should also be expired
    64  		tokenCache.Set("repo4", &bearerToken{
    65  			Token: "tokenD",
    66  		})
    67  
    68  		// token1 should be expired
    69  		token = tokenCache.Get("repo1")
    70  		So(token, ShouldBeNil)
    71  	})
    72  
    73  	Convey("Error paths", t, func() {
    74  		tokenCache := NewTokenCache()
    75  		token := tokenCache.Get("repo")
    76  		So(token, ShouldBeNil)
    77  
    78  		tokenCache = nil
    79  		token = tokenCache.Get("repo")
    80  		So(token, ShouldBeNil)
    81  
    82  		tokenCache = NewTokenCache()
    83  		tokenCache.Set("repo", nil)
    84  		token = tokenCache.Get("repo")
    85  		So(token, ShouldBeNil)
    86  	})
    87  }
    88  
    89  func TestNeedsRetryOnInsuficientScope(t *testing.T) {
    90  	resp := http.Response{
    91  		Status:     "401 Unauthorized",
    92  		StatusCode: http.StatusUnauthorized,
    93  		Proto:      "HTTP/1.1",
    94  		ProtoMajor: 1,
    95  		ProtoMinor: 1,
    96  		Header: map[string][]string{
    97  			"Content-Length":         {"145"},
    98  			"Content-Type":           {"application/json"},
    99  			"Date":                   {"Fri, 26 Aug 2022 08:03:13 GMT"},
   100  			"X-Content-Type-Options": {"nosniff"},
   101  		},
   102  		Request: nil,
   103  	}
   104  
   105  	Convey("Test client retries on insufficient scope", t, func() {
   106  		resp.Header["Www-Authenticate"] = []string{
   107  			`Bearer realm="https://registry.suse.com/auth",service="SUSE Linux Docker Registry"` +
   108  				`,scope="registry:catalog:*",error="insufficient_scope"`,
   109  		}
   110  
   111  		expectedScope := "registry:catalog:*"
   112  		expectedRealm := "https://registry.suse.com/auth"
   113  		expectedService := "SUSE Linux Docker Registry"
   114  
   115  		needsRetry, params := needsRetryWithUpdatedScope(nil, &resp)
   116  
   117  		So(needsRetry, ShouldBeTrue)
   118  		So(params.scope, ShouldEqual, expectedScope)
   119  		So(params.realm, ShouldEqual, expectedRealm)
   120  		So(params.service, ShouldEqual, expectedService)
   121  	})
   122  
   123  	Convey("Test client fails on insufficient scope", t, func() {
   124  		resp.Header["Www-Authenticate"] = []string{
   125  			`Bearer realm="https://registry.suse.com/auth=error"`,
   126  		}
   127  
   128  		needsRetry, _ := needsRetryWithUpdatedScope(nil, &resp)
   129  		So(needsRetry, ShouldBeFalse)
   130  	})
   131  }
   132  
   133  func TestClient(t *testing.T) {
   134  	Convey("Test client", t, func() {
   135  		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   136  			w.WriteHeader(http.StatusInternalServerError)
   137  		}))
   138  		defer server.Close()
   139  
   140  		client, err := New(Config{
   141  			URL:       server.URL,
   142  			TLSVerify: false,
   143  		}, log.NewLogger("", ""))
   144  		So(err, ShouldBeNil)
   145  
   146  		Convey("Test Ping() fails", func() {
   147  			ok := client.Ping()
   148  			So(ok, ShouldBeFalse)
   149  		})
   150  
   151  		Convey("Test makeAndDoRequest() fails", func() {
   152  			client.authType = tokenAuth
   153  			//nolint: bodyclose
   154  			_, _, err := client.makeAndDoRequest(http.MethodGet, "application/json", "catalog", server.URL)
   155  			So(err, ShouldNotBeNil)
   156  		})
   157  
   158  		Convey("Test setupAuth() fails", func() {
   159  			request, err := http.NewRequest(http.MethodGet, server.URL, nil) //nolint: noctx
   160  			So(err, ShouldBeNil)
   161  
   162  			client.authType = tokenAuth
   163  			err = client.setupAuth(request, "catalog")
   164  			So(err, ShouldNotBeNil)
   165  		})
   166  	})
   167  }