github.com/pelicanplatform/pelican@v1.0.5/client/director_test.go (about) 1 /*************************************************************** 2 * 3 * Copyright (C) 2023, Pelican Project, Morgridge Institute for Research 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); you 6 * may not use this file except in compliance with the License. You may 7 * obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 ***************************************************************/ 18 19 package client 20 21 import ( 22 "bytes" 23 "github.com/stretchr/testify/assert" 24 "io" 25 "net/http" 26 "net/http/httptest" 27 "os" 28 "testing" 29 30 namespaces "github.com/pelicanplatform/pelican/namespaces" 31 ) 32 33 func TestHeaderParser(t *testing.T) { 34 header1 := "namespace=/foo/bar, issuer = https://get-your-tokens.org, readhttps=False" 35 newMap1 := HeaderParser(header1) 36 37 assert.Equal(t, "/foo/bar", newMap1["namespace"]) 38 assert.Equal(t, "https://get-your-tokens.org", newMap1["issuer"]) 39 assert.Equal(t, "False", newMap1["readhttps"]) 40 41 header2 := "" 42 newMap2 := HeaderParser(header2) 43 assert.Equal(t, map[string]string{}, newMap2) 44 } 45 46 func TestGetCachesFromDirectorResponse(t *testing.T) { 47 // Construct the Director's Response, comprising headers and a body 48 directorHeaders := make(map[string][]string) 49 directorHeaders["Link"] = []string{"<my-cache.edu:8443>; rel=\"duplicate\"; pri=1, <another-cache.edu:8443>; rel=\"duplicate\"; pri=2"} 50 directorBody := []byte(`{"key": "value"}`) 51 52 directorResponse := &http.Response{ 53 StatusCode: 307, 54 Header: directorHeaders, 55 Body: io.NopCloser(bytes.NewReader(directorBody)), 56 } 57 58 // Call the function in question 59 caches, err := GetCachesFromDirectorResponse(directorResponse, true) 60 61 // Test for expected outputs 62 assert.NoError(t, err, "Error getting caches from the Director's response") 63 64 assert.Equal(t, "my-cache.edu:8443", caches[0].EndpointUrl) 65 assert.Equal(t, 1, caches[0].Priority) 66 assert.Equal(t, true, caches[0].AuthedReq) 67 68 assert.Equal(t, "another-cache.edu:8443", caches[1].EndpointUrl) 69 assert.Equal(t, 2, caches[1].Priority) 70 assert.Equal(t, true, caches[1].AuthedReq) 71 } 72 73 func TestCreateNsFromDirectorResp(t *testing.T) { 74 //Craft the Director's response 75 directorHeaders := make(map[string][]string) 76 directorHeaders["Link"] = []string{"<my-cache.edu:8443>; rel=\"duplicate\"; pri=1, <another-cache.edu:8443>; rel=\"duplicate\"; pri=2"} 77 directorHeaders["X-Pelican-Namespace"] = []string{"namespace=/foo/bar, readhttps=True, require-token=True"} 78 directorHeaders["X-Pelican-Authorization"] = []string{"issuer=https://get-your-tokens.org, base-path=/foo/bar"} 79 directorBody := []byte(`{"key": "value"}`) 80 81 directorResponse := &http.Response{ 82 StatusCode: 307, 83 Header: directorHeaders, 84 Body: io.NopCloser(bytes.NewReader(directorBody)), 85 } 86 87 // Create a namespace instance to test against 88 cache1 := namespaces.DirectorCache{ 89 EndpointUrl: "my-cache.edu:8443", 90 Priority: 1, 91 AuthedReq: true, 92 } 93 cache2 := namespaces.DirectorCache{ 94 EndpointUrl: "another-cache.edu:8443", 95 Priority: 2, 96 AuthedReq: true, 97 } 98 99 caches := []namespaces.DirectorCache{} 100 caches = append(caches, cache1) 101 caches = append(caches, cache2) 102 103 constructedNamespace := &namespaces.Namespace{ 104 SortedDirectorCaches: caches, 105 Path: "/foo/bar", 106 Issuer: "https://get-your-tokens.org", 107 ReadHTTPS: true, 108 UseTokenOnRead: true, 109 } 110 111 // Call the function in question 112 ns, err := CreateNsFromDirectorResp(directorResponse) 113 114 // Test for expected outputs 115 assert.NoError(t, err, "Error creating Namespace from Director response") 116 117 assert.Equal(t, constructedNamespace.SortedDirectorCaches, ns.SortedDirectorCaches) 118 assert.Equal(t, constructedNamespace.Path, ns.Path) 119 assert.Equal(t, constructedNamespace.Issuer, ns.Issuer) 120 assert.Equal(t, constructedNamespace.ReadHTTPS, ns.ReadHTTPS) 121 assert.Equal(t, constructedNamespace.UseTokenOnRead, ns.UseTokenOnRead) 122 } 123 124 func TestNewTransferDetailsUsingDirector(t *testing.T) { 125 os.Setenv("http_proxy", "http://proxy.edu:3128") 126 127 // Construct the input caches 128 // Cache with http 129 nonAuthCache := namespaces.DirectorCache{ 130 ResourceName: "mycache", 131 EndpointUrl: "my-cache-url:8000", 132 Priority: 99, 133 AuthedReq: false, 134 } 135 136 // Cache with https 137 authCache := namespaces.DirectorCache{ 138 ResourceName: "mycache", 139 EndpointUrl: "my-cache-url:8443", 140 Priority: 99, 141 AuthedReq: true, 142 } 143 144 // Case 1: cache with http 145 146 transfers := NewTransferDetailsUsingDirector(nonAuthCache, TransferDetailsOptions{nonAuthCache.AuthedReq, ""}) 147 assert.Equal(t, 2, len(transfers)) 148 assert.Equal(t, "my-cache-url:8000", transfers[0].Url.Host) 149 assert.Equal(t, "http", transfers[0].Url.Scheme) 150 assert.Equal(t, true, transfers[0].Proxy) 151 assert.Equal(t, 2, len(transfers)) 152 assert.Equal(t, "my-cache-url:8000", transfers[1].Url.Host) 153 assert.Equal(t, "http", transfers[1].Url.Scheme) 154 assert.Equal(t, false, transfers[1].Proxy) 155 156 // Case 2: cache with https 157 transfers = NewTransferDetailsUsingDirector(authCache, TransferDetailsOptions{authCache.AuthedReq, ""}) 158 assert.Equal(t, 1, len(transfers)) 159 assert.Equal(t, "my-cache-url:8443", transfers[0].Url.Host) 160 assert.Equal(t, "https", transfers[0].Url.Scheme) 161 assert.Equal(t, false, transfers[0].Proxy) 162 163 // Case 3: cache without port with http 164 nonAuthCache.EndpointUrl = "my-cache-url" 165 transfers = NewTransferDetailsUsingDirector(nonAuthCache, TransferDetailsOptions{nonAuthCache.AuthedReq, ""}) 166 assert.Equal(t, 2, len(transfers)) 167 assert.Equal(t, "my-cache-url:8000", transfers[0].Url.Host) 168 assert.Equal(t, "http", transfers[0].Url.Scheme) 169 assert.Equal(t, true, transfers[0].Proxy) 170 assert.Equal(t, "my-cache-url:8000", transfers[1].Url.Host) 171 assert.Equal(t, "http", transfers[1].Url.Scheme) 172 assert.Equal(t, false, transfers[1].Proxy) 173 174 // Case 4. cache without port with https 175 authCache.EndpointUrl = "my-cache-url" 176 transfers = NewTransferDetailsUsingDirector(authCache, TransferDetailsOptions{authCache.AuthedReq, ""}) 177 assert.Equal(t, 2, len(transfers)) 178 assert.Equal(t, "my-cache-url:8444", transfers[0].Url.Host) 179 assert.Equal(t, "https", transfers[0].Url.Scheme) 180 assert.Equal(t, false, transfers[0].Proxy) 181 assert.Equal(t, "my-cache-url:8443", transfers[1].Url.Host) 182 assert.Equal(t, "https", transfers[1].Url.Scheme) 183 assert.Equal(t, false, transfers[1].Proxy) 184 } 185 186 func TestQueryDirector(t *testing.T) { 187 // Construct a local server that we can poke with QueryDirector 188 expectedLocation := "http://redirect.com" 189 handler := func(w http.ResponseWriter, r *http.Request) { 190 w.Header().Set("Location", expectedLocation) 191 w.WriteHeader(http.StatusTemporaryRedirect) 192 } 193 server := httptest.NewServer(http.HandlerFunc(handler)) 194 defer server.Close() 195 196 // Call QueryDirector with the test server URL and a source path 197 actualResp, err := QueryDirector("/foo/bar", server.URL) 198 if err != nil { 199 t.Fatal(err) 200 } 201 202 // Check the Location header 203 actualLocation := actualResp.Header.Get("Location") 204 if actualLocation != expectedLocation { 205 t.Errorf("Expected Location header %q, but got %q", expectedLocation, actualLocation) 206 } 207 208 // Check the HTTP status code 209 if actualResp.StatusCode != http.StatusTemporaryRedirect { 210 t.Errorf("Expected HTTP status code %d, but got %d", http.StatusFound, actualResp.StatusCode) 211 } 212 }