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