123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427 |
- package registry
- import (
- "context"
- "encoding/json"
- "net/http"
- "net/http/httptest"
- "net/http/httputil"
- "testing"
- "github.com/docker/distribution/registry/client/transport"
- "github.com/docker/docker/api/types/filters"
- "github.com/docker/docker/api/types/registry"
- "github.com/docker/docker/errdefs"
- "gotest.tools/v3/assert"
- )
- func spawnTestRegistrySession(t *testing.T) *session {
- authConfig := ®istry.AuthConfig{}
- endpoint, err := newV1Endpoint(makeIndex("/v1/"), nil)
- if err != nil {
- t.Fatal(err)
- }
- userAgent := "docker test client"
- var tr http.RoundTripper = debugTransport{newTransport(nil), t.Log}
- tr = transport.NewTransport(newAuthTransport(tr, authConfig, false), Headers(userAgent, nil)...)
- client := httpClient(tr)
- if err := authorizeClient(client, authConfig, endpoint); err != nil {
- t.Fatal(err)
- }
- r := newSession(client, endpoint)
- // In a normal scenario for the v1 registry, the client should send a `X-Docker-Token: true`
- // header while authenticating, in order to retrieve a token that can be later used to
- // perform authenticated actions.
- //
- // The mock v1 registry does not support that, (TODO(tiborvass): support it), instead,
- // it will consider authenticated any request with the header `X-Docker-Token: fake-token`.
- //
- // Because we know that the client's transport is an `*authTransport` we simply cast it,
- // in order to set the internal cached token to the fake token, and thus send that fake token
- // upon every subsequent requests.
- r.client.Transport.(*authTransport).token = []string{"fake-token"}
- return r
- }
- type debugTransport struct {
- http.RoundTripper
- log func(...interface{})
- }
- func (tr debugTransport) RoundTrip(req *http.Request) (*http.Response, error) {
- dump, err := httputil.DumpRequestOut(req, false)
- if err != nil {
- tr.log("could not dump request")
- }
- tr.log(string(dump))
- resp, err := tr.RoundTripper.RoundTrip(req)
- if err != nil {
- return nil, err
- }
- dump, err = httputil.DumpResponse(resp, false)
- if err != nil {
- tr.log("could not dump response")
- }
- tr.log(string(dump))
- return resp, err
- }
- func TestSearchRepositories(t *testing.T) {
- r := spawnTestRegistrySession(t)
- results, err := r.searchRepositories("fakequery", 25)
- if err != nil {
- t.Fatal(err)
- }
- if results == nil {
- t.Fatal("Expected non-nil SearchResults object")
- }
- assert.Equal(t, results.NumResults, 1, "Expected 1 search results")
- assert.Equal(t, results.Query, "fakequery", "Expected 'fakequery' as query")
- assert.Equal(t, results.Results[0].StarCount, 42, "Expected 'fakeimage' to have 42 stars")
- }
- func TestSearchErrors(t *testing.T) {
- errorCases := []struct {
- filtersArgs filters.Args
- shouldReturnError bool
- expectedError string
- }{
- {
- expectedError: "Unexpected status code 500",
- shouldReturnError: true,
- },
- {
- filtersArgs: filters.NewArgs(filters.Arg("type", "custom")),
- expectedError: "invalid filter 'type'",
- },
- {
- filtersArgs: filters.NewArgs(filters.Arg("is-automated", "invalid")),
- expectedError: "invalid filter 'is-automated=[invalid]'",
- },
- {
- filtersArgs: filters.NewArgs(
- filters.Arg("is-automated", "true"),
- filters.Arg("is-automated", "false"),
- ),
- expectedError: "invalid filter 'is-automated",
- },
- {
- filtersArgs: filters.NewArgs(filters.Arg("is-official", "invalid")),
- expectedError: "invalid filter 'is-official=[invalid]'",
- },
- {
- filtersArgs: filters.NewArgs(
- filters.Arg("is-official", "true"),
- filters.Arg("is-official", "false"),
- ),
- expectedError: "invalid filter 'is-official",
- },
- {
- filtersArgs: filters.NewArgs(filters.Arg("stars", "invalid")),
- expectedError: "invalid filter 'stars=invalid'",
- },
- {
- filtersArgs: filters.NewArgs(
- filters.Arg("stars", "1"),
- filters.Arg("stars", "invalid"),
- ),
- expectedError: "invalid filter 'stars=invalid'",
- },
- }
- for _, tc := range errorCases {
- tc := tc
- t.Run(tc.expectedError, func(t *testing.T) {
- srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if !tc.shouldReturnError {
- t.Errorf("unexpected HTTP request")
- }
- http.Error(w, "no search for you", http.StatusInternalServerError)
- }))
- defer srv.Close()
- // Construct the search term by cutting the 'http://' prefix off srv.URL.
- term := srv.URL[7:] + "/term"
- reg, err := NewService(ServiceOptions{})
- assert.NilError(t, err)
- _, err = reg.Search(context.Background(), tc.filtersArgs, term, 0, nil, map[string][]string{})
- assert.ErrorContains(t, err, tc.expectedError)
- if tc.shouldReturnError {
- assert.Check(t, errdefs.IsUnknown(err), "got: %T: %v", err, err)
- return
- }
- assert.Check(t, errdefs.IsInvalidParameter(err), "got: %T: %v", err, err)
- })
- }
- }
- func TestSearch(t *testing.T) {
- const term = "term"
- successCases := []struct {
- name string
- filtersArgs filters.Args
- registryResults []registry.SearchResult
- expectedResults []registry.SearchResult
- }{
- {
- name: "empty results",
- registryResults: []registry.SearchResult{},
- expectedResults: []registry.SearchResult{},
- },
- {
- name: "no filter",
- registryResults: []registry.SearchResult{
- {
- Name: "name",
- Description: "description",
- },
- },
- expectedResults: []registry.SearchResult{
- {
- Name: "name",
- Description: "description",
- },
- },
- },
- {
- name: "is-automated=true, no results",
- filtersArgs: filters.NewArgs(filters.Arg("is-automated", "true")),
- registryResults: []registry.SearchResult{
- {
- Name: "name",
- Description: "description",
- },
- },
- expectedResults: []registry.SearchResult{},
- },
- {
- name: "is-automated=true",
- filtersArgs: filters.NewArgs(filters.Arg("is-automated", "true")),
- registryResults: []registry.SearchResult{
- {
- Name: "name",
- Description: "description",
- IsAutomated: true, //nolint:staticcheck // ignore SA1019 (field is deprecated).
- },
- },
- expectedResults: []registry.SearchResult{
- {
- Name: "name",
- Description: "description",
- IsAutomated: true, //nolint:staticcheck // ignore SA1019 (field is deprecated).
- },
- },
- },
- {
- name: "is-automated=false, no results",
- filtersArgs: filters.NewArgs(filters.Arg("is-automated", "false")),
- registryResults: []registry.SearchResult{
- {
- Name: "name",
- Description: "description",
- IsAutomated: true, //nolint:staticcheck // ignore SA1019 (field is deprecated).
- },
- },
- expectedResults: []registry.SearchResult{},
- },
- {
- name: "is-automated=false",
- filtersArgs: filters.NewArgs(filters.Arg("is-automated", "false")),
- registryResults: []registry.SearchResult{
- {
- Name: "name",
- Description: "description",
- },
- },
- expectedResults: []registry.SearchResult{
- {
- Name: "name",
- Description: "description",
- },
- },
- },
- {
- name: "is-official=true, no results",
- filtersArgs: filters.NewArgs(filters.Arg("is-official", "true")),
- registryResults: []registry.SearchResult{
- {
- Name: "name",
- Description: "description",
- },
- },
- expectedResults: []registry.SearchResult{},
- },
- {
- name: "is-official=true",
- filtersArgs: filters.NewArgs(filters.Arg("is-official", "true")),
- registryResults: []registry.SearchResult{
- {
- Name: "name",
- Description: "description",
- IsOfficial: true,
- },
- },
- expectedResults: []registry.SearchResult{
- {
- Name: "name",
- Description: "description",
- IsOfficial: true,
- },
- },
- },
- {
- name: "is-official=false, no results",
- filtersArgs: filters.NewArgs(filters.Arg("is-official", "false")),
- registryResults: []registry.SearchResult{
- {
- Name: "name",
- Description: "description",
- IsOfficial: true,
- },
- },
- expectedResults: []registry.SearchResult{},
- },
- {
- name: "is-official=false",
- filtersArgs: filters.NewArgs(filters.Arg("is-official", "false")),
- registryResults: []registry.SearchResult{
- {
- Name: "name",
- Description: "description",
- IsOfficial: false,
- },
- },
- expectedResults: []registry.SearchResult{
- {
- Name: "name",
- Description: "description",
- IsOfficial: false,
- },
- },
- },
- {
- name: "stars=0",
- filtersArgs: filters.NewArgs(filters.Arg("stars", "0")),
- registryResults: []registry.SearchResult{
- {
- Name: "name",
- Description: "description",
- StarCount: 0,
- },
- },
- expectedResults: []registry.SearchResult{
- {
- Name: "name",
- Description: "description",
- StarCount: 0,
- },
- },
- },
- {
- name: "stars=0, no results",
- filtersArgs: filters.NewArgs(filters.Arg("stars", "1")),
- registryResults: []registry.SearchResult{
- {
- Name: "name",
- Description: "description",
- StarCount: 0,
- },
- },
- expectedResults: []registry.SearchResult{},
- },
- {
- name: "stars=1",
- filtersArgs: filters.NewArgs(filters.Arg("stars", "1")),
- registryResults: []registry.SearchResult{
- {
- Name: "name0",
- Description: "description0",
- StarCount: 0,
- },
- {
- Name: "name1",
- Description: "description1",
- StarCount: 1,
- },
- },
- expectedResults: []registry.SearchResult{
- {
- Name: "name1",
- Description: "description1",
- StarCount: 1,
- },
- },
- },
- {
- name: "stars=1, is-official=true, is-automated=true",
- filtersArgs: filters.NewArgs(
- filters.Arg("stars", "1"),
- filters.Arg("is-official", "true"),
- filters.Arg("is-automated", "true"),
- ),
- registryResults: []registry.SearchResult{
- {
- Name: "name0",
- Description: "description0",
- StarCount: 0,
- IsOfficial: true,
- IsAutomated: true, //nolint:staticcheck // ignore SA1019 (field is deprecated).
- },
- {
- Name: "name1",
- Description: "description1",
- StarCount: 1,
- IsOfficial: false,
- IsAutomated: true, //nolint:staticcheck // ignore SA1019 (field is deprecated).
- },
- {
- Name: "name2",
- Description: "description2",
- StarCount: 1,
- IsOfficial: true,
- },
- {
- Name: "name3",
- Description: "description3",
- StarCount: 2,
- IsOfficial: true,
- IsAutomated: true, //nolint:staticcheck // ignore SA1019 (field is deprecated).
- },
- },
- expectedResults: []registry.SearchResult{
- {
- Name: "name3",
- Description: "description3",
- StarCount: 2,
- IsOfficial: true,
- IsAutomated: true, //nolint:staticcheck // ignore SA1019 (field is deprecated).
- },
- },
- },
- }
- for _, tc := range successCases {
- tc := tc
- t.Run(tc.name, func(t *testing.T) {
- srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Content-type", "application/json")
- json.NewEncoder(w).Encode(registry.SearchResults{
- Query: term,
- NumResults: len(tc.registryResults),
- Results: tc.registryResults,
- })
- }))
- defer srv.Close()
- // Construct the search term by cutting the 'http://' prefix off srv.URL.
- searchTerm := srv.URL[7:] + "/" + term
- reg, err := NewService(ServiceOptions{})
- assert.NilError(t, err)
- results, err := reg.Search(context.Background(), tc.filtersArgs, searchTerm, 0, nil, map[string][]string{})
- assert.NilError(t, err)
- assert.DeepEqual(t, results, tc.expectedResults)
- })
- }
- }
|