code.gitea.io/gitea@v1.21.7/tests/integration/api_packages_nuget_test.go (about)

     1  // Copyright 2021 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package integration
     5  
     6  import (
     7  	"archive/zip"
     8  	"bytes"
     9  	"encoding/base64"
    10  	"encoding/xml"
    11  	"fmt"
    12  	"io"
    13  	"net/http"
    14  	"net/http/httptest"
    15  	neturl "net/url"
    16  	"strconv"
    17  	"testing"
    18  	"time"
    19  
    20  	auth_model "code.gitea.io/gitea/models/auth"
    21  	"code.gitea.io/gitea/models/db"
    22  	"code.gitea.io/gitea/models/packages"
    23  	"code.gitea.io/gitea/models/unittest"
    24  	user_model "code.gitea.io/gitea/models/user"
    25  	nuget_module "code.gitea.io/gitea/modules/packages/nuget"
    26  	"code.gitea.io/gitea/modules/setting"
    27  	"code.gitea.io/gitea/modules/structs"
    28  	"code.gitea.io/gitea/routers/api/packages/nuget"
    29  	"code.gitea.io/gitea/tests"
    30  
    31  	"github.com/stretchr/testify/assert"
    32  )
    33  
    34  func addNuGetAPIKeyHeader(request *http.Request, token string) *http.Request {
    35  	request.Header.Set("X-NuGet-ApiKey", token)
    36  	return request
    37  }
    38  
    39  func decodeXML(t testing.TB, resp *httptest.ResponseRecorder, v any) {
    40  	t.Helper()
    41  
    42  	assert.NoError(t, xml.NewDecoder(resp.Body).Decode(v))
    43  }
    44  
    45  func TestPackageNuGet(t *testing.T) {
    46  	defer tests.PrepareTestEnv(t)()
    47  
    48  	type FeedEntryProperties struct {
    49  		Version                  string                      `xml:"Version"`
    50  		NormalizedVersion        string                      `xml:"NormalizedVersion"`
    51  		Authors                  string                      `xml:"Authors"`
    52  		Dependencies             string                      `xml:"Dependencies"`
    53  		Description              string                      `xml:"Description"`
    54  		VersionDownloadCount     nuget.TypedValue[int64]     `xml:"VersionDownloadCount"`
    55  		DownloadCount            nuget.TypedValue[int64]     `xml:"DownloadCount"`
    56  		PackageSize              nuget.TypedValue[int64]     `xml:"PackageSize"`
    57  		Created                  nuget.TypedValue[time.Time] `xml:"Created"`
    58  		LastUpdated              nuget.TypedValue[time.Time] `xml:"LastUpdated"`
    59  		Published                nuget.TypedValue[time.Time] `xml:"Published"`
    60  		ProjectURL               string                      `xml:"ProjectUrl,omitempty"`
    61  		ReleaseNotes             string                      `xml:"ReleaseNotes,omitempty"`
    62  		RequireLicenseAcceptance nuget.TypedValue[bool]      `xml:"RequireLicenseAcceptance"`
    63  		Title                    string                      `xml:"Title"`
    64  	}
    65  
    66  	type FeedEntry struct {
    67  		XMLName    xml.Name             `xml:"entry"`
    68  		Properties *FeedEntryProperties `xml:"properties"`
    69  		Content    string               `xml:",innerxml"`
    70  	}
    71  
    72  	type FeedEntryLink struct {
    73  		Rel  string `xml:"rel,attr"`
    74  		Href string `xml:"href,attr"`
    75  	}
    76  
    77  	type FeedResponse struct {
    78  		XMLName xml.Name        `xml:"feed"`
    79  		Links   []FeedEntryLink `xml:"link"`
    80  		Entries []*FeedEntry    `xml:"entry"`
    81  		Count   int64           `xml:"count"`
    82  	}
    83  
    84  	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
    85  	token := getUserToken(t, user.Name, auth_model.AccessTokenScopeWritePackage)
    86  
    87  	packageName := "test.package"
    88  	packageVersion := "1.0.3"
    89  	packageAuthors := "KN4CK3R"
    90  	packageDescription := "Gitea Test Package"
    91  	symbolFilename := "test.pdb"
    92  	symbolID := "d910bb6948bd4c6cb40155bcf52c3c94"
    93  
    94  	createPackage := func(id, version string) io.Reader {
    95  		var buf bytes.Buffer
    96  		archive := zip.NewWriter(&buf)
    97  		w, _ := archive.Create("package.nuspec")
    98  		w.Write([]byte(`<?xml version="1.0" encoding="utf-8"?>
    99  		<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
   100  			<metadata>
   101  				<id>` + id + `</id>
   102  				<version>` + version + `</version>
   103  				<authors>` + packageAuthors + `</authors>
   104  				<description>` + packageDescription + `</description>
   105  				<dependencies>
   106  					<group targetFramework=".NETStandard2.0">
   107  						<dependency id="Microsoft.CSharp" version="4.5.0" />
   108  					</group>
   109  				</dependencies>
   110  			</metadata>
   111  		</package>`))
   112  		archive.Close()
   113  		return &buf
   114  	}
   115  
   116  	content, _ := io.ReadAll(createPackage(packageName, packageVersion))
   117  
   118  	url := fmt.Sprintf("/api/packages/%s/nuget", user.Name)
   119  
   120  	t.Run("ServiceIndex", func(t *testing.T) {
   121  		t.Run("v2", func(t *testing.T) {
   122  			defer tests.PrintCurrentTest(t)()
   123  
   124  			privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Visibility: structs.VisibleTypePrivate})
   125  
   126  			cases := []struct {
   127  				Owner        string
   128  				UseBasicAuth bool
   129  				UseTokenAuth bool
   130  			}{
   131  				{privateUser.Name, false, false},
   132  				{privateUser.Name, true, false},
   133  				{privateUser.Name, false, true},
   134  				{user.Name, false, false},
   135  				{user.Name, true, false},
   136  				{user.Name, false, true},
   137  			}
   138  
   139  			for _, c := range cases {
   140  				url := fmt.Sprintf("/api/packages/%s/nuget", c.Owner)
   141  
   142  				req := NewRequest(t, "GET", url)
   143  				if c.UseBasicAuth {
   144  					req = AddBasicAuthHeader(req, user.Name)
   145  				} else if c.UseTokenAuth {
   146  					req = addNuGetAPIKeyHeader(req, token)
   147  				}
   148  				resp := MakeRequest(t, req, http.StatusOK)
   149  
   150  				var result nuget.ServiceIndexResponseV2
   151  				decodeXML(t, resp, &result)
   152  
   153  				assert.Equal(t, setting.AppURL+url[1:], result.Base)
   154  				assert.Equal(t, "Packages", result.Workspace.Collection.Href)
   155  			}
   156  		})
   157  
   158  		t.Run("v3", func(t *testing.T) {
   159  			defer tests.PrintCurrentTest(t)()
   160  
   161  			privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Visibility: structs.VisibleTypePrivate})
   162  
   163  			cases := []struct {
   164  				Owner        string
   165  				UseBasicAuth bool
   166  				UseTokenAuth bool
   167  			}{
   168  				{privateUser.Name, false, false},
   169  				{privateUser.Name, true, false},
   170  				{privateUser.Name, false, true},
   171  				{user.Name, false, false},
   172  				{user.Name, true, false},
   173  				{user.Name, false, true},
   174  			}
   175  
   176  			for _, c := range cases {
   177  				url := fmt.Sprintf("/api/packages/%s/nuget", c.Owner)
   178  
   179  				req := NewRequest(t, "GET", fmt.Sprintf("%s/index.json", url))
   180  				if c.UseBasicAuth {
   181  					req = AddBasicAuthHeader(req, user.Name)
   182  				} else if c.UseTokenAuth {
   183  					req = addNuGetAPIKeyHeader(req, token)
   184  				}
   185  				resp := MakeRequest(t, req, http.StatusOK)
   186  
   187  				var result nuget.ServiceIndexResponseV3
   188  				DecodeJSON(t, resp, &result)
   189  
   190  				assert.Equal(t, "3.0.0", result.Version)
   191  				assert.NotEmpty(t, result.Resources)
   192  
   193  				root := setting.AppURL + url[1:]
   194  				for _, r := range result.Resources {
   195  					switch r.Type {
   196  					case "SearchQueryService":
   197  						fallthrough
   198  					case "SearchQueryService/3.0.0-beta":
   199  						fallthrough
   200  					case "SearchQueryService/3.0.0-rc":
   201  						assert.Equal(t, root+"/query", r.ID)
   202  					case "RegistrationsBaseUrl":
   203  						fallthrough
   204  					case "RegistrationsBaseUrl/3.0.0-beta":
   205  						fallthrough
   206  					case "RegistrationsBaseUrl/3.0.0-rc":
   207  						assert.Equal(t, root+"/registration", r.ID)
   208  					case "PackageBaseAddress/3.0.0":
   209  						assert.Equal(t, root+"/package", r.ID)
   210  					case "PackagePublish/2.0.0":
   211  						assert.Equal(t, root, r.ID)
   212  					}
   213  				}
   214  			}
   215  		})
   216  	})
   217  
   218  	t.Run("Upload", func(t *testing.T) {
   219  		t.Run("DependencyPackage", func(t *testing.T) {
   220  			defer tests.PrintCurrentTest(t)()
   221  
   222  			req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content))
   223  			req = AddBasicAuthHeader(req, user.Name)
   224  			MakeRequest(t, req, http.StatusCreated)
   225  
   226  			pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNuGet)
   227  			assert.NoError(t, err)
   228  			assert.Len(t, pvs, 1)
   229  
   230  			pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
   231  			assert.NoError(t, err)
   232  			assert.NotNil(t, pd.SemVer)
   233  			assert.IsType(t, &nuget_module.Metadata{}, pd.Metadata)
   234  			assert.Equal(t, packageName, pd.Package.Name)
   235  			assert.Equal(t, packageVersion, pd.Version.Version)
   236  
   237  			pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
   238  			assert.NoError(t, err)
   239  			assert.Len(t, pfs, 1)
   240  			assert.Equal(t, fmt.Sprintf("%s.%s.nupkg", packageName, packageVersion), pfs[0].Name)
   241  			assert.True(t, pfs[0].IsLead)
   242  
   243  			pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID)
   244  			assert.NoError(t, err)
   245  			assert.Equal(t, int64(len(content)), pb.Size)
   246  
   247  			req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content))
   248  			req = AddBasicAuthHeader(req, user.Name)
   249  			MakeRequest(t, req, http.StatusConflict)
   250  		})
   251  
   252  		t.Run("SymbolPackage", func(t *testing.T) {
   253  			defer tests.PrintCurrentTest(t)()
   254  
   255  			createSymbolPackage := func(id, packageType string) io.Reader {
   256  				var buf bytes.Buffer
   257  				archive := zip.NewWriter(&buf)
   258  
   259  				w, _ := archive.Create("package.nuspec")
   260  				w.Write([]byte(`<?xml version="1.0" encoding="utf-8"?>
   261  				<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
   262  				<metadata>
   263  					<id>` + id + `</id>
   264  					<version>` + packageVersion + `</version>
   265  					<authors>` + packageAuthors + `</authors>
   266  					<description>` + packageDescription + `</description>
   267  					<packageTypes><packageType name="` + packageType + `" /></packageTypes>
   268  				</metadata>
   269  				</package>`))
   270  
   271  				w, _ = archive.Create(symbolFilename)
   272  				b, _ := base64.StdEncoding.DecodeString(`QlNKQgEAAQAAAAAADAAAAFBEQiB2MS4wAAAAAAAABgB8AAAAWAAAACNQZGIAAAAA1AAAAAgBAAAj
   273  fgAA3AEAAAQAAAAjU3RyaW5ncwAAAADgAQAABAAAACNVUwDkAQAAMAAAACNHVUlEAAAAFAIAACgB
   274  AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
   275  				w.Write(b)
   276  
   277  				archive.Close()
   278  				return &buf
   279  			}
   280  
   281  			req := NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createSymbolPackage("unknown-package", "SymbolsPackage"))
   282  			req = AddBasicAuthHeader(req, user.Name)
   283  			MakeRequest(t, req, http.StatusNotFound)
   284  
   285  			req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createSymbolPackage(packageName, "DummyPackage"))
   286  			req = AddBasicAuthHeader(req, user.Name)
   287  			MakeRequest(t, req, http.StatusBadRequest)
   288  
   289  			req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createSymbolPackage(packageName, "SymbolsPackage"))
   290  			req = AddBasicAuthHeader(req, user.Name)
   291  			MakeRequest(t, req, http.StatusCreated)
   292  
   293  			pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNuGet)
   294  			assert.NoError(t, err)
   295  			assert.Len(t, pvs, 1)
   296  
   297  			pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
   298  			assert.NoError(t, err)
   299  			assert.NotNil(t, pd.SemVer)
   300  			assert.IsType(t, &nuget_module.Metadata{}, pd.Metadata)
   301  			assert.Equal(t, packageName, pd.Package.Name)
   302  			assert.Equal(t, packageVersion, pd.Version.Version)
   303  
   304  			pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
   305  			assert.NoError(t, err)
   306  			assert.Len(t, pfs, 3)
   307  			for _, pf := range pfs {
   308  				switch pf.Name {
   309  				case fmt.Sprintf("%s.%s.nupkg", packageName, packageVersion):
   310  				case fmt.Sprintf("%s.%s.snupkg", packageName, packageVersion):
   311  					assert.False(t, pf.IsLead)
   312  
   313  					pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID)
   314  					assert.NoError(t, err)
   315  					assert.Equal(t, int64(616), pb.Size)
   316  				case symbolFilename:
   317  					assert.False(t, pf.IsLead)
   318  
   319  					pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID)
   320  					assert.NoError(t, err)
   321  					assert.Equal(t, int64(160), pb.Size)
   322  
   323  					pps, err := packages.GetProperties(db.DefaultContext, packages.PropertyTypeFile, pf.ID)
   324  					assert.NoError(t, err)
   325  					assert.Len(t, pps, 1)
   326  					assert.Equal(t, nuget_module.PropertySymbolID, pps[0].Name)
   327  					assert.Equal(t, symbolID, pps[0].Value)
   328  				default:
   329  					assert.Fail(t, "unexpected file: %v", pf.Name)
   330  				}
   331  			}
   332  
   333  			req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createSymbolPackage(packageName, "SymbolsPackage"))
   334  			req = AddBasicAuthHeader(req, user.Name)
   335  			MakeRequest(t, req, http.StatusConflict)
   336  		})
   337  	})
   338  
   339  	t.Run("Download", func(t *testing.T) {
   340  		defer tests.PrintCurrentTest(t)()
   341  
   342  		checkDownloadCount := func(count int64) {
   343  			pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNuGet)
   344  			assert.NoError(t, err)
   345  			assert.Len(t, pvs, 1)
   346  			assert.Equal(t, count, pvs[0].DownloadCount)
   347  		}
   348  
   349  		checkDownloadCount(0)
   350  
   351  		req := NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s.%s.nupkg", url, packageName, packageVersion, packageName, packageVersion))
   352  		req = AddBasicAuthHeader(req, user.Name)
   353  		resp := MakeRequest(t, req, http.StatusOK)
   354  
   355  		assert.Equal(t, content, resp.Body.Bytes())
   356  
   357  		checkDownloadCount(1)
   358  
   359  		req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s.%s.snupkg", url, packageName, packageVersion, packageName, packageVersion))
   360  		req = AddBasicAuthHeader(req, user.Name)
   361  		MakeRequest(t, req, http.StatusOK)
   362  
   363  		checkDownloadCount(1)
   364  
   365  		t.Run("Symbol", func(t *testing.T) {
   366  			defer tests.PrintCurrentTest(t)()
   367  
   368  			req := NewRequest(t, "GET", fmt.Sprintf("%s/symbols/%s/%sFFFFFFFF/gitea.pdb", url, symbolFilename, symbolID))
   369  			MakeRequest(t, req, http.StatusBadRequest)
   370  
   371  			req = NewRequest(t, "GET", fmt.Sprintf("%s/symbols/%s/%sFFFFFFFF/%s", url, symbolFilename, "00000000000000000000000000000000", symbolFilename))
   372  			req = AddBasicAuthHeader(req, user.Name)
   373  			MakeRequest(t, req, http.StatusNotFound)
   374  
   375  			req = NewRequest(t, "GET", fmt.Sprintf("%s/symbols/%s/%sFFFFffff/%s", url, symbolFilename, symbolID, symbolFilename))
   376  			req = AddBasicAuthHeader(req, user.Name)
   377  			MakeRequest(t, req, http.StatusOK)
   378  
   379  			checkDownloadCount(1)
   380  		})
   381  	})
   382  
   383  	containsOneNextLink := func(t *testing.T, links []FeedEntryLink) func() bool {
   384  		return func() bool {
   385  			found := 0
   386  			for _, l := range links {
   387  				if l.Rel == "next" {
   388  					found++
   389  					u, err := neturl.Parse(l.Href)
   390  					assert.NoError(t, err)
   391  					q := u.Query()
   392  					assert.Contains(t, q, "$skip")
   393  					assert.Contains(t, q, "$top")
   394  					assert.Equal(t, "1", q.Get("$skip"))
   395  					assert.Equal(t, "1", q.Get("$top"))
   396  				}
   397  			}
   398  			return found == 1
   399  		}
   400  	}
   401  
   402  	t.Run("SearchService", func(t *testing.T) {
   403  		cases := []struct {
   404  			Query           string
   405  			Skip            int
   406  			Take            int
   407  			ExpectedTotal   int64
   408  			ExpectedResults int
   409  		}{
   410  			{"", 0, 0, 1, 1},
   411  			{"", 0, 10, 1, 1},
   412  			{"gitea", 0, 10, 0, 0},
   413  			{"test", 0, 10, 1, 1},
   414  			{"test", 1, 10, 1, 0},
   415  		}
   416  
   417  		req := NewRequestWithBody(t, "PUT", url, createPackage(packageName, "1.0.99"))
   418  		req = AddBasicAuthHeader(req, user.Name)
   419  		MakeRequest(t, req, http.StatusCreated)
   420  
   421  		t.Run("v2", func(t *testing.T) {
   422  			t.Run("Search()", func(t *testing.T) {
   423  				defer tests.PrintCurrentTest(t)()
   424  
   425  				for i, c := range cases {
   426  					req := NewRequest(t, "GET", fmt.Sprintf("%s/Search()?searchTerm='%s'&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take))
   427  					req = AddBasicAuthHeader(req, user.Name)
   428  					resp := MakeRequest(t, req, http.StatusOK)
   429  
   430  					var result FeedResponse
   431  					decodeXML(t, resp, &result)
   432  
   433  					assert.Equal(t, c.ExpectedTotal, result.Count, "case %d: unexpected total hits", i)
   434  					assert.Len(t, result.Entries, c.ExpectedResults, "case %d: unexpected result count", i)
   435  
   436  					req = NewRequest(t, "GET", fmt.Sprintf("%s/Search()/$count?searchTerm='%s'&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take))
   437  					req = AddBasicAuthHeader(req, user.Name)
   438  					resp = MakeRequest(t, req, http.StatusOK)
   439  
   440  					assert.Equal(t, strconv.FormatInt(c.ExpectedTotal, 10), resp.Body.String(), "case %d: unexpected total hits", i)
   441  				}
   442  			})
   443  
   444  			t.Run("Packages()", func(t *testing.T) {
   445  				defer tests.PrintCurrentTest(t)()
   446  
   447  				for i, c := range cases {
   448  					req := NewRequest(t, "GET", fmt.Sprintf("%s/Packages()?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take))
   449  					req = AddBasicAuthHeader(req, user.Name)
   450  					resp := MakeRequest(t, req, http.StatusOK)
   451  
   452  					var result FeedResponse
   453  					decodeXML(t, resp, &result)
   454  
   455  					assert.Equal(t, c.ExpectedTotal, result.Count, "case %d: unexpected total hits", i)
   456  					assert.Len(t, result.Entries, c.ExpectedResults, "case %d: unexpected result count", i)
   457  
   458  					req = NewRequest(t, "GET", fmt.Sprintf("%s/Packages()/$count?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take))
   459  					req = AddBasicAuthHeader(req, user.Name)
   460  					resp = MakeRequest(t, req, http.StatusOK)
   461  
   462  					assert.Equal(t, strconv.FormatInt(c.ExpectedTotal, 10), resp.Body.String(), "case %d: unexpected total hits", i)
   463  				}
   464  			})
   465  
   466  			t.Run("Next", func(t *testing.T) {
   467  				req := NewRequest(t, "GET", fmt.Sprintf("%s/Search()?searchTerm='test'&$skip=0&$top=1", url))
   468  				req = AddBasicAuthHeader(req, user.Name)
   469  				resp := MakeRequest(t, req, http.StatusOK)
   470  
   471  				var result FeedResponse
   472  				decodeXML(t, resp, &result)
   473  
   474  				assert.Condition(t, containsOneNextLink(t, result.Links))
   475  			})
   476  		})
   477  
   478  		t.Run("v3", func(t *testing.T) {
   479  			defer tests.PrintCurrentTest(t)()
   480  
   481  			for i, c := range cases {
   482  				req := NewRequest(t, "GET", fmt.Sprintf("%s/query?q=%s&skip=%d&take=%d", url, c.Query, c.Skip, c.Take))
   483  				req = AddBasicAuthHeader(req, user.Name)
   484  				resp := MakeRequest(t, req, http.StatusOK)
   485  
   486  				var result nuget.SearchResultResponse
   487  				DecodeJSON(t, resp, &result)
   488  
   489  				assert.Equal(t, c.ExpectedTotal, result.TotalHits, "case %d: unexpected total hits", i)
   490  				assert.Len(t, result.Data, c.ExpectedResults, "case %d: unexpected result count", i)
   491  			}
   492  
   493  			t.Run("EnforceGrouped", func(t *testing.T) {
   494  				defer tests.PrintCurrentTest(t)()
   495  
   496  				req := NewRequestWithBody(t, "PUT", url, createPackage(packageName+".dummy", "1.0.0"))
   497  				req = AddBasicAuthHeader(req, user.Name)
   498  				MakeRequest(t, req, http.StatusCreated)
   499  
   500  				req = NewRequest(t, "GET", fmt.Sprintf("%s/query?q=%s", url, packageName))
   501  				req = AddBasicAuthHeader(req, user.Name)
   502  				resp := MakeRequest(t, req, http.StatusOK)
   503  
   504  				var result nuget.SearchResultResponse
   505  				DecodeJSON(t, resp, &result)
   506  
   507  				assert.EqualValues(t, 2, result.TotalHits)
   508  				assert.Len(t, result.Data, 2)
   509  				for _, sr := range result.Data {
   510  					if sr.ID == packageName {
   511  						assert.Len(t, sr.Versions, 2)
   512  					} else {
   513  						assert.Len(t, sr.Versions, 1)
   514  					}
   515  				}
   516  
   517  				req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, packageName+".dummy", "1.0.0"))
   518  				req = AddBasicAuthHeader(req, user.Name)
   519  				MakeRequest(t, req, http.StatusNoContent)
   520  			})
   521  		})
   522  
   523  		req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, packageName, "1.0.99"))
   524  		req = AddBasicAuthHeader(req, user.Name)
   525  		MakeRequest(t, req, http.StatusNoContent)
   526  	})
   527  
   528  	t.Run("RegistrationService", func(t *testing.T) {
   529  		indexURL := fmt.Sprintf("%s%s/registration/%s/index.json", setting.AppURL, url[1:], packageName)
   530  		leafURL := fmt.Sprintf("%s%s/registration/%s/%s.json", setting.AppURL, url[1:], packageName, packageVersion)
   531  		contentURL := fmt.Sprintf("%s%s/package/%s/%s/%s.%s.nupkg", setting.AppURL, url[1:], packageName, packageVersion, packageName, packageVersion)
   532  
   533  		t.Run("RegistrationIndex", func(t *testing.T) {
   534  			defer tests.PrintCurrentTest(t)()
   535  
   536  			req := NewRequest(t, "GET", fmt.Sprintf("%s/registration/%s/index.json", url, packageName))
   537  			req = AddBasicAuthHeader(req, user.Name)
   538  			resp := MakeRequest(t, req, http.StatusOK)
   539  
   540  			var result nuget.RegistrationIndexResponse
   541  			DecodeJSON(t, resp, &result)
   542  
   543  			assert.Equal(t, indexURL, result.RegistrationIndexURL)
   544  			assert.Equal(t, 1, result.Count)
   545  			assert.Len(t, result.Pages, 1)
   546  			assert.Equal(t, indexURL, result.Pages[0].RegistrationPageURL)
   547  			assert.Equal(t, packageVersion, result.Pages[0].Lower)
   548  			assert.Equal(t, packageVersion, result.Pages[0].Upper)
   549  			assert.Equal(t, 1, result.Pages[0].Count)
   550  			assert.Len(t, result.Pages[0].Items, 1)
   551  			assert.Equal(t, packageName, result.Pages[0].Items[0].CatalogEntry.ID)
   552  			assert.Equal(t, packageVersion, result.Pages[0].Items[0].CatalogEntry.Version)
   553  			assert.Equal(t, packageAuthors, result.Pages[0].Items[0].CatalogEntry.Authors)
   554  			assert.Equal(t, packageDescription, result.Pages[0].Items[0].CatalogEntry.Description)
   555  			assert.Equal(t, leafURL, result.Pages[0].Items[0].CatalogEntry.CatalogLeafURL)
   556  			assert.Equal(t, contentURL, result.Pages[0].Items[0].CatalogEntry.PackageContentURL)
   557  		})
   558  
   559  		t.Run("RegistrationLeaf", func(t *testing.T) {
   560  			t.Run("v2", func(t *testing.T) {
   561  				defer tests.PrintCurrentTest(t)()
   562  
   563  				req := NewRequest(t, "GET", fmt.Sprintf("%s/Packages(Id='%s',Version='%s')", url, packageName, packageVersion))
   564  				req = AddBasicAuthHeader(req, user.Name)
   565  				resp := MakeRequest(t, req, http.StatusOK)
   566  
   567  				var result FeedEntry
   568  				decodeXML(t, resp, &result)
   569  
   570  				assert.Equal(t, packageName, result.Properties.Title)
   571  				assert.Equal(t, packageVersion, result.Properties.Version)
   572  				assert.Equal(t, packageAuthors, result.Properties.Authors)
   573  				assert.Equal(t, packageDescription, result.Properties.Description)
   574  				assert.Equal(t, "Microsoft.CSharp:4.5.0:.NETStandard2.0", result.Properties.Dependencies)
   575  			})
   576  
   577  			t.Run("v3", func(t *testing.T) {
   578  				defer tests.PrintCurrentTest(t)()
   579  
   580  				req := NewRequest(t, "GET", fmt.Sprintf("%s/registration/%s/%s.json", url, packageName, packageVersion))
   581  				req = AddBasicAuthHeader(req, user.Name)
   582  				resp := MakeRequest(t, req, http.StatusOK)
   583  
   584  				var result nuget.RegistrationLeafResponse
   585  				DecodeJSON(t, resp, &result)
   586  
   587  				assert.Equal(t, leafURL, result.RegistrationLeafURL)
   588  				assert.Equal(t, contentURL, result.PackageContentURL)
   589  				assert.Equal(t, indexURL, result.RegistrationIndexURL)
   590  			})
   591  		})
   592  	})
   593  
   594  	t.Run("PackageService", func(t *testing.T) {
   595  		t.Run("v2", func(t *testing.T) {
   596  			defer tests.PrintCurrentTest(t)()
   597  
   598  			req := NewRequest(t, "GET", fmt.Sprintf("%s/FindPackagesById()?id='%s'&$top=1", url, packageName))
   599  			req = AddBasicAuthHeader(req, user.Name)
   600  			resp := MakeRequest(t, req, http.StatusOK)
   601  
   602  			var result FeedResponse
   603  			decodeXML(t, resp, &result)
   604  
   605  			assert.Len(t, result.Entries, 1)
   606  			assert.Equal(t, packageVersion, result.Entries[0].Properties.Version)
   607  			assert.Condition(t, containsOneNextLink(t, result.Links))
   608  
   609  			req = NewRequest(t, "GET", fmt.Sprintf("%s/FindPackagesById()/$count?id='%s'", url, packageName))
   610  			req = AddBasicAuthHeader(req, user.Name)
   611  			resp = MakeRequest(t, req, http.StatusOK)
   612  
   613  			assert.Equal(t, "1", resp.Body.String())
   614  		})
   615  
   616  		t.Run("v3", func(t *testing.T) {
   617  			defer tests.PrintCurrentTest(t)()
   618  
   619  			req := NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/index.json", url, packageName))
   620  			req = AddBasicAuthHeader(req, user.Name)
   621  			resp := MakeRequest(t, req, http.StatusOK)
   622  
   623  			var result nuget.PackageVersionsResponse
   624  			DecodeJSON(t, resp, &result)
   625  
   626  			assert.Len(t, result.Versions, 1)
   627  			assert.Equal(t, packageVersion, result.Versions[0])
   628  		})
   629  	})
   630  
   631  	t.Run("Delete", func(t *testing.T) {
   632  		defer tests.PrintCurrentTest(t)()
   633  
   634  		req := NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, packageName, packageVersion))
   635  		req = AddBasicAuthHeader(req, user.Name)
   636  		MakeRequest(t, req, http.StatusNoContent)
   637  
   638  		pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNuGet)
   639  		assert.NoError(t, err)
   640  		assert.Empty(t, pvs)
   641  	})
   642  
   643  	t.Run("DownloadNotExists", func(t *testing.T) {
   644  		defer tests.PrintCurrentTest(t)()
   645  
   646  		req := NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s.%s.nupkg", url, packageName, packageVersion, packageName, packageVersion))
   647  		req = AddBasicAuthHeader(req, user.Name)
   648  		MakeRequest(t, req, http.StatusNotFound)
   649  
   650  		req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s.%s.snupkg", url, packageName, packageVersion, packageName, packageVersion))
   651  		req = AddBasicAuthHeader(req, user.Name)
   652  		MakeRequest(t, req, http.StatusNotFound)
   653  	})
   654  
   655  	t.Run("DeleteNotExists", func(t *testing.T) {
   656  		defer tests.PrintCurrentTest(t)()
   657  
   658  		req := NewRequest(t, "DELETE", fmt.Sprintf("%s/package/%s/%s", url, packageName, packageVersion))
   659  		req = AddBasicAuthHeader(req, user.Name)
   660  		MakeRequest(t, req, http.StatusNotFound)
   661  	})
   662  }