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  }