github.com/lusis/distribution@v2.0.1+incompatible/registry/handlers/app_test.go (about) 1 package handlers 2 3 import ( 4 "encoding/json" 5 "net/http" 6 "net/http/httptest" 7 "net/url" 8 "reflect" 9 "testing" 10 11 "github.com/docker/distribution/configuration" 12 "github.com/docker/distribution/registry/api/v2" 13 "github.com/docker/distribution/registry/auth" 14 _ "github.com/docker/distribution/registry/auth/silly" 15 "github.com/docker/distribution/registry/storage" 16 "github.com/docker/distribution/registry/storage/cache" 17 "github.com/docker/distribution/registry/storage/driver/inmemory" 18 "golang.org/x/net/context" 19 ) 20 21 // TestAppDispatcher builds an application with a test dispatcher and ensures 22 // that requests are properly dispatched and the handlers are constructed. 23 // This only tests the dispatch mechanism. The underlying dispatchers must be 24 // tested individually. 25 func TestAppDispatcher(t *testing.T) { 26 driver := inmemory.New() 27 app := &App{ 28 Config: configuration.Configuration{}, 29 Context: context.Background(), 30 router: v2.Router(), 31 driver: driver, 32 registry: storage.NewRegistryWithDriver(driver, cache.NewInMemoryLayerInfoCache()), 33 } 34 server := httptest.NewServer(app) 35 router := v2.Router() 36 37 serverURL, err := url.Parse(server.URL) 38 if err != nil { 39 t.Fatalf("error parsing server url: %v", err) 40 } 41 42 varCheckingDispatcher := func(expectedVars map[string]string) dispatchFunc { 43 return func(ctx *Context, r *http.Request) http.Handler { 44 // Always checks the same name context 45 if ctx.Repository.Name() != getName(ctx) { 46 t.Fatalf("unexpected name: %q != %q", ctx.Repository.Name(), "foo/bar") 47 } 48 49 // Check that we have all that is expected 50 for expectedK, expectedV := range expectedVars { 51 if ctx.Value(expectedK) != expectedV { 52 t.Fatalf("unexpected %s in context vars: %q != %q", expectedK, ctx.Value(expectedK), expectedV) 53 } 54 } 55 56 // Check that we only have variables that are expected 57 for k, v := range ctx.Value("vars").(map[string]string) { 58 _, ok := expectedVars[k] 59 60 if !ok { // name is checked on context 61 // We have an unexpected key, fail 62 t.Fatalf("unexpected key %q in vars with value %q", k, v) 63 } 64 } 65 66 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 67 w.WriteHeader(http.StatusOK) 68 }) 69 } 70 } 71 72 // unflatten a list of variables, suitable for gorilla/mux, to a map[string]string 73 unflatten := func(vars []string) map[string]string { 74 m := make(map[string]string) 75 for i := 0; i < len(vars)-1; i = i + 2 { 76 m[vars[i]] = vars[i+1] 77 } 78 79 return m 80 } 81 82 for _, testcase := range []struct { 83 endpoint string 84 vars []string 85 }{ 86 { 87 endpoint: v2.RouteNameManifest, 88 vars: []string{ 89 "name", "foo/bar", 90 "reference", "sometag", 91 }, 92 }, 93 { 94 endpoint: v2.RouteNameTags, 95 vars: []string{ 96 "name", "foo/bar", 97 }, 98 }, 99 { 100 endpoint: v2.RouteNameBlob, 101 vars: []string{ 102 "name", "foo/bar", 103 "digest", "tarsum.v1+bogus:abcdef0123456789", 104 }, 105 }, 106 { 107 endpoint: v2.RouteNameBlobUpload, 108 vars: []string{ 109 "name", "foo/bar", 110 }, 111 }, 112 { 113 endpoint: v2.RouteNameBlobUploadChunk, 114 vars: []string{ 115 "name", "foo/bar", 116 "uuid", "theuuid", 117 }, 118 }, 119 } { 120 app.register(testcase.endpoint, varCheckingDispatcher(unflatten(testcase.vars))) 121 route := router.GetRoute(testcase.endpoint).Host(serverURL.Host) 122 u, err := route.URL(testcase.vars...) 123 124 if err != nil { 125 t.Fatal(err) 126 } 127 128 resp, err := http.Get(u.String()) 129 130 if err != nil { 131 t.Fatal(err) 132 } 133 134 if resp.StatusCode != http.StatusOK { 135 t.Fatalf("unexpected status code: %v != %v", resp.StatusCode, http.StatusOK) 136 } 137 } 138 } 139 140 // TestNewApp covers the creation of an application via NewApp with a 141 // configuration. 142 func TestNewApp(t *testing.T) { 143 ctx := context.Background() 144 config := configuration.Configuration{ 145 Storage: configuration.Storage{ 146 "inmemory": nil, 147 }, 148 Auth: configuration.Auth{ 149 // For now, we simply test that new auth results in a viable 150 // application. 151 "silly": { 152 "realm": "realm-test", 153 "service": "service-test", 154 }, 155 }, 156 } 157 158 // Mostly, with this test, given a sane configuration, we are simply 159 // ensuring that NewApp doesn't panic. We might want to tweak this 160 // behavior. 161 app := NewApp(ctx, config) 162 163 server := httptest.NewServer(app) 164 builder, err := v2.NewURLBuilderFromString(server.URL) 165 if err != nil { 166 t.Fatalf("error creating urlbuilder: %v", err) 167 } 168 169 baseURL, err := builder.BuildBaseURL() 170 if err != nil { 171 t.Fatalf("error creating baseURL: %v", err) 172 } 173 174 // TODO(stevvooe): The rest of this test might belong in the API tests. 175 176 // Just hit the app and make sure we get a 401 Unauthorized error. 177 req, err := http.Get(baseURL) 178 if err != nil { 179 t.Fatalf("unexpected error during GET: %v", err) 180 } 181 defer req.Body.Close() 182 183 if req.StatusCode != http.StatusUnauthorized { 184 t.Fatalf("unexpected status code during request: %v", err) 185 } 186 187 if req.Header.Get("Content-Type") != "application/json; charset=utf-8" { 188 t.Fatalf("unexpected content-type: %v != %v", req.Header.Get("Content-Type"), "application/json; charset=utf-8") 189 } 190 191 expectedAuthHeader := "Bearer realm=\"realm-test\",service=\"service-test\"" 192 if e, a := expectedAuthHeader, req.Header.Get("WWW-Authenticate"); e != a { 193 t.Fatalf("unexpected WWW-Authenticate header: %q != %q", e, a) 194 } 195 196 var errs v2.Errors 197 dec := json.NewDecoder(req.Body) 198 if err := dec.Decode(&errs); err != nil { 199 t.Fatalf("error decoding error response: %v", err) 200 } 201 202 if errs.Errors[0].Code != v2.ErrorCodeUnauthorized { 203 t.Fatalf("unexpected error code: %v != %v", errs.Errors[0].Code, v2.ErrorCodeUnauthorized) 204 } 205 } 206 207 // Test the access record accumulator 208 func TestAppendAccessRecords(t *testing.T) { 209 repo := "testRepo" 210 211 expectedResource := auth.Resource{ 212 Type: "repository", 213 Name: repo, 214 } 215 216 expectedPullRecord := auth.Access{ 217 Resource: expectedResource, 218 Action: "pull", 219 } 220 expectedPushRecord := auth.Access{ 221 Resource: expectedResource, 222 Action: "push", 223 } 224 expectedAllRecord := auth.Access{ 225 Resource: expectedResource, 226 Action: "*", 227 } 228 229 records := []auth.Access{} 230 result := appendAccessRecords(records, "GET", repo) 231 expectedResult := []auth.Access{expectedPullRecord} 232 if ok := reflect.DeepEqual(result, expectedResult); !ok { 233 t.Fatalf("Actual access record differs from expected") 234 } 235 236 records = []auth.Access{} 237 result = appendAccessRecords(records, "HEAD", repo) 238 expectedResult = []auth.Access{expectedPullRecord} 239 if ok := reflect.DeepEqual(result, expectedResult); !ok { 240 t.Fatalf("Actual access record differs from expected") 241 } 242 243 records = []auth.Access{} 244 result = appendAccessRecords(records, "POST", repo) 245 expectedResult = []auth.Access{expectedPullRecord, expectedPushRecord} 246 if ok := reflect.DeepEqual(result, expectedResult); !ok { 247 t.Fatalf("Actual access record differs from expected") 248 } 249 250 records = []auth.Access{} 251 result = appendAccessRecords(records, "PUT", repo) 252 expectedResult = []auth.Access{expectedPullRecord, expectedPushRecord} 253 if ok := reflect.DeepEqual(result, expectedResult); !ok { 254 t.Fatalf("Actual access record differs from expected") 255 } 256 257 records = []auth.Access{} 258 result = appendAccessRecords(records, "PATCH", repo) 259 expectedResult = []auth.Access{expectedPullRecord, expectedPushRecord} 260 if ok := reflect.DeepEqual(result, expectedResult); !ok { 261 t.Fatalf("Actual access record differs from expected") 262 } 263 264 records = []auth.Access{} 265 result = appendAccessRecords(records, "DELETE", repo) 266 expectedResult = []auth.Access{expectedAllRecord} 267 if ok := reflect.DeepEqual(result, expectedResult); !ok { 268 t.Fatalf("Actual access record differs from expected") 269 } 270 271 }