code.gitea.io/gitea@v1.22.3/tests/integration/api_packages_conan_test.go (about)

     1  // Copyright 2022 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package integration
     5  
     6  import (
     7  	"fmt"
     8  	"net/http"
     9  	stdurl "net/url"
    10  	"strings"
    11  	"testing"
    12  	"time"
    13  
    14  	auth_model "code.gitea.io/gitea/models/auth"
    15  	"code.gitea.io/gitea/models/db"
    16  	"code.gitea.io/gitea/models/packages"
    17  	conan_model "code.gitea.io/gitea/models/packages/conan"
    18  	"code.gitea.io/gitea/models/unittest"
    19  	user_model "code.gitea.io/gitea/models/user"
    20  	conan_module "code.gitea.io/gitea/modules/packages/conan"
    21  	"code.gitea.io/gitea/modules/setting"
    22  	conan_router "code.gitea.io/gitea/routers/api/packages/conan"
    23  	package_service "code.gitea.io/gitea/services/packages"
    24  	"code.gitea.io/gitea/tests"
    25  
    26  	"github.com/stretchr/testify/assert"
    27  )
    28  
    29  const (
    30  	conanfileName = "conanfile.py"
    31  	conaninfoName = "conaninfo.txt"
    32  
    33  	conanLicense     = "MIT"
    34  	conanAuthor      = "Gitea <info@gitea.io>"
    35  	conanHomepage    = "https://gitea.io/"
    36  	conanURL         = "https://gitea.com/"
    37  	conanDescription = "Description of ConanPackage"
    38  	conanTopic       = "gitea"
    39  
    40  	conanPackageReference = "dummyreference"
    41  
    42  	contentConaninfo = `[settings]
    43      arch=x84_64
    44  
    45  [requires]
    46      fmt/7.1.3
    47  
    48  [options]
    49      shared=False
    50  
    51  [full_settings]
    52      arch=x84_64
    53  
    54  [full_requires]
    55      fmt/7.1.3
    56  
    57  [full_options]
    58      shared=False
    59  
    60  [recipe_hash]
    61      74714915a51073acb548ca1ce29afbac
    62  
    63  [env]
    64  CC=gcc-10`
    65  )
    66  
    67  func buildConanfileContent(name, version string) string {
    68  	return `from conans import ConanFile, CMake, tools
    69  
    70  class ConanPackageConan(ConanFile):
    71  	name = "` + name + `"
    72  	version = "` + version + `"
    73  	license = "` + conanLicense + `"
    74  	author = "` + conanAuthor + `"
    75  	homepage = "` + conanHomepage + `"
    76  	url = "` + conanURL + `"
    77  	description = "` + conanDescription + `"
    78  	topics = ("` + conanTopic + `")
    79  	settings = "os", "compiler", "build_type", "arch"
    80  	options = {"shared": [True, False], "fPIC": [True, False]}
    81  	default_options = {"shared": False, "fPIC": True}
    82  	generators = "cmake"`
    83  }
    84  
    85  func uploadConanPackageV1(t *testing.T, baseURL, token, name, version, user, channel string) {
    86  	contentConanfile := buildConanfileContent(name, version)
    87  
    88  	recipeURL := fmt.Sprintf("%s/v1/conans/%s/%s/%s/%s", baseURL, name, version, user, channel)
    89  
    90  	req := NewRequest(t, "GET", recipeURL).
    91  		AddTokenAuth(token)
    92  	MakeRequest(t, req, http.StatusNotFound)
    93  
    94  	req = NewRequest(t, "GET", fmt.Sprintf("%s/digest", recipeURL)).
    95  		AddTokenAuth(token)
    96  	MakeRequest(t, req, http.StatusNotFound)
    97  
    98  	req = NewRequest(t, "GET", fmt.Sprintf("%s/download_urls", recipeURL)).
    99  		AddTokenAuth(token)
   100  	MakeRequest(t, req, http.StatusNotFound)
   101  
   102  	req = NewRequest(t, "POST", fmt.Sprintf("%s/upload_urls", recipeURL))
   103  	MakeRequest(t, req, http.StatusUnauthorized)
   104  
   105  	req = NewRequestWithJSON(t, "POST", fmt.Sprintf("%s/upload_urls", recipeURL), map[string]int64{
   106  		conanfileName: int64(len(contentConanfile)),
   107  		"removed.txt": 0,
   108  	}).AddTokenAuth(token)
   109  	resp := MakeRequest(t, req, http.StatusOK)
   110  
   111  	uploadURLs := make(map[string]string)
   112  	DecodeJSON(t, resp, &uploadURLs)
   113  
   114  	assert.Contains(t, uploadURLs, conanfileName)
   115  	assert.NotContains(t, uploadURLs, "removed.txt")
   116  
   117  	uploadURL := uploadURLs[conanfileName]
   118  	assert.NotEmpty(t, uploadURL)
   119  
   120  	req = NewRequestWithBody(t, "PUT", uploadURL, strings.NewReader(contentConanfile)).
   121  		AddTokenAuth(token)
   122  	MakeRequest(t, req, http.StatusCreated)
   123  
   124  	packageURL := fmt.Sprintf("%s/packages/%s", recipeURL, conanPackageReference)
   125  
   126  	req = NewRequest(t, "GET", packageURL).
   127  		AddTokenAuth(token)
   128  	MakeRequest(t, req, http.StatusNotFound)
   129  
   130  	req = NewRequest(t, "GET", fmt.Sprintf("%s/digest", packageURL)).
   131  		AddTokenAuth(token)
   132  	MakeRequest(t, req, http.StatusNotFound)
   133  
   134  	req = NewRequest(t, "GET", fmt.Sprintf("%s/download_urls", packageURL)).
   135  		AddTokenAuth(token)
   136  	MakeRequest(t, req, http.StatusNotFound)
   137  
   138  	req = NewRequest(t, "POST", fmt.Sprintf("%s/upload_urls", packageURL))
   139  	MakeRequest(t, req, http.StatusUnauthorized)
   140  
   141  	req = NewRequestWithJSON(t, "POST", fmt.Sprintf("%s/upload_urls", packageURL), map[string]int64{
   142  		conaninfoName: int64(len(contentConaninfo)),
   143  		"removed.txt": 0,
   144  	}).AddTokenAuth(token)
   145  	resp = MakeRequest(t, req, http.StatusOK)
   146  
   147  	uploadURLs = make(map[string]string)
   148  	DecodeJSON(t, resp, &uploadURLs)
   149  
   150  	assert.Contains(t, uploadURLs, conaninfoName)
   151  	assert.NotContains(t, uploadURLs, "removed.txt")
   152  
   153  	uploadURL = uploadURLs[conaninfoName]
   154  	assert.NotEmpty(t, uploadURL)
   155  
   156  	req = NewRequestWithBody(t, "PUT", uploadURL, strings.NewReader(contentConaninfo)).
   157  		AddTokenAuth(token)
   158  	MakeRequest(t, req, http.StatusCreated)
   159  }
   160  
   161  func uploadConanPackageV2(t *testing.T, baseURL, token, name, version, user, channel, recipeRevision, packageRevision string) {
   162  	contentConanfile := buildConanfileContent(name, version)
   163  
   164  	recipeURL := fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s", baseURL, name, version, user, channel, recipeRevision)
   165  
   166  	req := NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/files/%s", recipeURL, conanfileName), strings.NewReader(contentConanfile)).
   167  		AddTokenAuth(token)
   168  	MakeRequest(t, req, http.StatusCreated)
   169  
   170  	req = NewRequest(t, "GET", fmt.Sprintf("%s/files", recipeURL)).
   171  		AddTokenAuth(token)
   172  	resp := MakeRequest(t, req, http.StatusOK)
   173  
   174  	var list *struct {
   175  		Files map[string]any `json:"files"`
   176  	}
   177  	DecodeJSON(t, resp, &list)
   178  	assert.Len(t, list.Files, 1)
   179  	assert.Contains(t, list.Files, conanfileName)
   180  
   181  	packageURL := fmt.Sprintf("%s/packages/%s/revisions/%s", recipeURL, conanPackageReference, packageRevision)
   182  
   183  	req = NewRequest(t, "GET", fmt.Sprintf("%s/files", packageURL)).
   184  		AddTokenAuth(token)
   185  	MakeRequest(t, req, http.StatusNotFound)
   186  
   187  	req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/files/%s", packageURL, conaninfoName), strings.NewReader(contentConaninfo)).
   188  		AddTokenAuth(token)
   189  	MakeRequest(t, req, http.StatusCreated)
   190  
   191  	req = NewRequest(t, "GET", fmt.Sprintf("%s/files", packageURL)).
   192  		AddTokenAuth(token)
   193  	resp = MakeRequest(t, req, http.StatusOK)
   194  
   195  	list = nil
   196  	DecodeJSON(t, resp, &list)
   197  	assert.Len(t, list.Files, 1)
   198  	assert.Contains(t, list.Files, conaninfoName)
   199  }
   200  
   201  func TestPackageConan(t *testing.T) {
   202  	defer tests.PrepareTestEnv(t)()
   203  
   204  	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
   205  
   206  	name := "ConanPackage"
   207  	version1 := "1.2"
   208  	version2 := "1.3"
   209  	user1 := "dummy"
   210  	user2 := "gitea"
   211  	channel1 := "test"
   212  	channel2 := "final"
   213  	revision1 := "rev1"
   214  	revision2 := "rev2"
   215  
   216  	url := fmt.Sprintf("%sapi/packages/%s/conan", setting.AppURL, user.Name)
   217  
   218  	t.Run("v1", func(t *testing.T) {
   219  		t.Run("Ping", func(t *testing.T) {
   220  			defer tests.PrintCurrentTest(t)()
   221  
   222  			req := NewRequest(t, "GET", fmt.Sprintf("%s/v1/ping", url))
   223  			resp := MakeRequest(t, req, http.StatusOK)
   224  
   225  			assert.Equal(t, "revisions", resp.Header().Get("X-Conan-Server-Capabilities"))
   226  		})
   227  
   228  		token := ""
   229  
   230  		t.Run("UserName/Password Authenticate", func(t *testing.T) {
   231  			defer tests.PrintCurrentTest(t)()
   232  
   233  			req := NewRequest(t, "GET", fmt.Sprintf("%s/v1/users/authenticate", url)).
   234  				AddBasicAuth(user.Name)
   235  			resp := MakeRequest(t, req, http.StatusOK)
   236  
   237  			token = resp.Body.String()
   238  			assert.NotEmpty(t, token)
   239  
   240  			pkgMeta, err := package_service.ParseAuthorizationToken(token)
   241  			assert.NoError(t, err)
   242  			assert.Equal(t, user.ID, pkgMeta.UserID)
   243  			assert.Equal(t, auth_model.AccessTokenScopeAll, pkgMeta.Scope)
   244  		})
   245  
   246  		badToken := ""
   247  		t.Run("Token Scope Authentication", func(t *testing.T) {
   248  			defer tests.PrintCurrentTest(t)()
   249  
   250  			session := loginUser(t, user.Name)
   251  
   252  			badToken = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadNotification)
   253  
   254  			testCase := func(t *testing.T, scope auth_model.AccessTokenScope, expectedAuthStatusCode, expectedStatusCode int) {
   255  				t.Helper()
   256  
   257  				token := getTokenForLoggedInUser(t, session, scope)
   258  
   259  				req := NewRequest(t, "GET", fmt.Sprintf("%s/v1/users/authenticate", url)).
   260  					AddTokenAuth(token)
   261  				resp := MakeRequest(t, req, expectedAuthStatusCode)
   262  				if expectedAuthStatusCode != http.StatusOK {
   263  					return
   264  				}
   265  
   266  				body := resp.Body.String()
   267  				assert.NotEmpty(t, body)
   268  
   269  				pkgMeta, err := package_service.ParseAuthorizationToken(body)
   270  				assert.NoError(t, err)
   271  				assert.Equal(t, user.ID, pkgMeta.UserID)
   272  				assert.Equal(t, scope, pkgMeta.Scope)
   273  
   274  				recipeURL := fmt.Sprintf("%s/v1/conans/%s/%s/%s/%s", url, "TestScope", version1, "testing", channel1)
   275  
   276  				req = NewRequestWithJSON(t, "POST", fmt.Sprintf("%s/upload_urls", recipeURL), map[string]int64{
   277  					conanfileName: 64,
   278  					"removed.txt": 0,
   279  				}).AddTokenAuth(token)
   280  				MakeRequest(t, req, expectedStatusCode)
   281  			}
   282  
   283  			t.Run("No Package permission", func(t *testing.T) {
   284  				defer tests.PrintCurrentTest(t)()
   285  
   286  				testCase(t, auth_model.AccessTokenScopeReadNotification, http.StatusUnauthorized, http.StatusForbidden)
   287  			})
   288  
   289  			t.Run("Package Read permission", func(t *testing.T) {
   290  				defer tests.PrintCurrentTest(t)()
   291  
   292  				testCase(t, auth_model.AccessTokenScopeReadPackage, http.StatusOK, http.StatusUnauthorized)
   293  			})
   294  
   295  			t.Run("Package Write permission", func(t *testing.T) {
   296  				defer tests.PrintCurrentTest(t)()
   297  
   298  				testCase(t, auth_model.AccessTokenScopeWritePackage, http.StatusOK, http.StatusOK)
   299  			})
   300  
   301  			t.Run("All permission", func(t *testing.T) {
   302  				defer tests.PrintCurrentTest(t)()
   303  
   304  				testCase(t, auth_model.AccessTokenScopeAll, http.StatusOK, http.StatusOK)
   305  			})
   306  		})
   307  
   308  		t.Run("CheckCredentials", func(t *testing.T) {
   309  			defer tests.PrintCurrentTest(t)()
   310  
   311  			req := NewRequest(t, "GET", fmt.Sprintf("%s/v1/users/check_credentials", url)).
   312  				AddTokenAuth(token)
   313  			MakeRequest(t, req, http.StatusOK)
   314  		})
   315  
   316  		t.Run("Upload", func(t *testing.T) {
   317  			defer tests.PrintCurrentTest(t)()
   318  
   319  			uploadConanPackageV1(t, url, token, name, version1, user1, channel1)
   320  
   321  			t.Run("Validate", func(t *testing.T) {
   322  				defer tests.PrintCurrentTest(t)()
   323  
   324  				pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeConan)
   325  				assert.NoError(t, err)
   326  				assert.Len(t, pvs, 1)
   327  
   328  				pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
   329  				assert.NoError(t, err)
   330  				assert.Nil(t, pd.SemVer)
   331  				assert.Equal(t, name, pd.Package.Name)
   332  				assert.Equal(t, version1, pd.Version.Version)
   333  				assert.IsType(t, &conan_module.Metadata{}, pd.Metadata)
   334  				metadata := pd.Metadata.(*conan_module.Metadata)
   335  				assert.Equal(t, conanLicense, metadata.License)
   336  				assert.Equal(t, conanAuthor, metadata.Author)
   337  				assert.Equal(t, conanHomepage, metadata.ProjectURL)
   338  				assert.Equal(t, conanURL, metadata.RepositoryURL)
   339  				assert.Equal(t, conanDescription, metadata.Description)
   340  				assert.Equal(t, []string{conanTopic}, metadata.Keywords)
   341  
   342  				pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
   343  				assert.NoError(t, err)
   344  				assert.Len(t, pfs, 2)
   345  
   346  				for _, pf := range pfs {
   347  					pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID)
   348  					assert.NoError(t, err)
   349  
   350  					if pf.Name == conanfileName {
   351  						assert.True(t, pf.IsLead)
   352  
   353  						assert.Equal(t, int64(len(buildConanfileContent(name, version1))), pb.Size)
   354  					} else if pf.Name == conaninfoName {
   355  						assert.False(t, pf.IsLead)
   356  
   357  						assert.Equal(t, int64(len(contentConaninfo)), pb.Size)
   358  					} else {
   359  						assert.FailNow(t, "unknown file: %s", pf.Name)
   360  					}
   361  				}
   362  			})
   363  		})
   364  
   365  		t.Run("Download", func(t *testing.T) {
   366  			defer tests.PrintCurrentTest(t)()
   367  
   368  			recipeURL := fmt.Sprintf("%s/v1/conans/%s/%s/%s/%s", url, name, version1, user1, channel1)
   369  
   370  			req := NewRequest(t, "GET", recipeURL)
   371  			resp := MakeRequest(t, req, http.StatusOK)
   372  
   373  			fileHashes := make(map[string]string)
   374  			DecodeJSON(t, resp, &fileHashes)
   375  			assert.Len(t, fileHashes, 1)
   376  			assert.Contains(t, fileHashes, conanfileName)
   377  			assert.Equal(t, "7abc52241c22090782c54731371847a8", fileHashes[conanfileName])
   378  
   379  			req = NewRequest(t, "GET", fmt.Sprintf("%s/digest", recipeURL))
   380  			resp = MakeRequest(t, req, http.StatusOK)
   381  
   382  			downloadURLs := make(map[string]string)
   383  			DecodeJSON(t, resp, &downloadURLs)
   384  			assert.Contains(t, downloadURLs, conanfileName)
   385  
   386  			req = NewRequest(t, "GET", fmt.Sprintf("%s/download_urls", recipeURL))
   387  			resp = MakeRequest(t, req, http.StatusOK)
   388  
   389  			DecodeJSON(t, resp, &downloadURLs)
   390  			assert.Contains(t, downloadURLs, conanfileName)
   391  
   392  			req = NewRequest(t, "GET", downloadURLs[conanfileName])
   393  			resp = MakeRequest(t, req, http.StatusOK)
   394  			assert.Equal(t, buildConanfileContent(name, version1), resp.Body.String())
   395  
   396  			packageURL := fmt.Sprintf("%s/packages/%s", recipeURL, conanPackageReference)
   397  
   398  			req = NewRequest(t, "GET", packageURL)
   399  			resp = MakeRequest(t, req, http.StatusOK)
   400  
   401  			fileHashes = make(map[string]string)
   402  			DecodeJSON(t, resp, &fileHashes)
   403  			assert.Len(t, fileHashes, 1)
   404  			assert.Contains(t, fileHashes, conaninfoName)
   405  			assert.Equal(t, "7628bfcc5b17f1470c468621a78df394", fileHashes[conaninfoName])
   406  
   407  			req = NewRequest(t, "GET", fmt.Sprintf("%s/digest", packageURL))
   408  			resp = MakeRequest(t, req, http.StatusOK)
   409  
   410  			downloadURLs = make(map[string]string)
   411  			DecodeJSON(t, resp, &downloadURLs)
   412  			assert.Contains(t, downloadURLs, conaninfoName)
   413  
   414  			req = NewRequest(t, "GET", fmt.Sprintf("%s/download_urls", packageURL))
   415  			resp = MakeRequest(t, req, http.StatusOK)
   416  
   417  			DecodeJSON(t, resp, &downloadURLs)
   418  			assert.Contains(t, downloadURLs, conaninfoName)
   419  
   420  			req = NewRequest(t, "GET", downloadURLs[conaninfoName])
   421  			resp = MakeRequest(t, req, http.StatusOK)
   422  			assert.Equal(t, contentConaninfo, resp.Body.String())
   423  		})
   424  
   425  		t.Run("Search", func(t *testing.T) {
   426  			uploadConanPackageV1(t, url, token, name, version2, user1, channel1)
   427  			uploadConanPackageV1(t, url, token, name, version1, user1, channel2)
   428  			uploadConanPackageV1(t, url, token, name, version1, user2, channel1)
   429  			uploadConanPackageV1(t, url, token, name, version1, user2, channel2)
   430  
   431  			t.Run("Recipe", func(t *testing.T) {
   432  				defer tests.PrintCurrentTest(t)()
   433  
   434  				cases := []struct {
   435  					Query    string
   436  					Expected []string
   437  				}{
   438  					{"ConanPackage", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.3@dummy/test", "ConanPackage/1.2@dummy/final", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}},
   439  					{"ConanPackage/1.2", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.2@dummy/final", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}},
   440  					{"ConanPackage/1.1", []string{}},
   441  					{"Conan*", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.3@dummy/test", "ConanPackage/1.2@dummy/final", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}},
   442  					{"ConanPackage/", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.3@dummy/test", "ConanPackage/1.2@dummy/final", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}},
   443  					{"ConanPackage/*", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.3@dummy/test", "ConanPackage/1.2@dummy/final", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}},
   444  					{"ConanPackage/1*", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.3@dummy/test", "ConanPackage/1.2@dummy/final", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}},
   445  					{"ConanPackage/*2", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.2@dummy/final", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}},
   446  					{"ConanPackage/1*2", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.2@dummy/final", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}},
   447  					{"ConanPackage/1.2@", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.2@dummy/final", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}},
   448  					{"ConanPackage/1.2@du*", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.2@dummy/final"}},
   449  					{"ConanPackage/1.2@du*/", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.2@dummy/final"}},
   450  					{"ConanPackage/1.2@du*/*test", []string{"ConanPackage/1.2@dummy/test"}},
   451  					{"ConanPackage/1.2@du*/*st", []string{"ConanPackage/1.2@dummy/test"}},
   452  					{"ConanPackage/1.2@gitea/*", []string{"ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}},
   453  					{"*/*@dummy", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.3@dummy/test", "ConanPackage/1.2@dummy/final"}},
   454  					{"*/*@*/final", []string{"ConanPackage/1.2@dummy/final", "ConanPackage/1.2@gitea/final"}},
   455  				}
   456  
   457  				for i, c := range cases {
   458  					req := NewRequest(t, "GET", fmt.Sprintf("%s/v1/conans/search?q=%s", url, stdurl.QueryEscape(c.Query)))
   459  					resp := MakeRequest(t, req, http.StatusOK)
   460  
   461  					var result *conan_router.SearchResult
   462  					DecodeJSON(t, resp, &result)
   463  
   464  					assert.ElementsMatch(t, c.Expected, result.Results, "case %d: unexpected result", i)
   465  				}
   466  			})
   467  
   468  			t.Run("Package", func(t *testing.T) {
   469  				defer tests.PrintCurrentTest(t)()
   470  
   471  				req := NewRequest(t, "GET", fmt.Sprintf("%s/v1/conans/%s/%s/%s/%s/search", url, name, version1, user1, channel2))
   472  				resp := MakeRequest(t, req, http.StatusOK)
   473  
   474  				var result map[string]*conan_module.Conaninfo
   475  				DecodeJSON(t, resp, &result)
   476  
   477  				assert.Contains(t, result, conanPackageReference)
   478  				info := result[conanPackageReference]
   479  				assert.NotEmpty(t, info.Settings)
   480  			})
   481  		})
   482  
   483  		t.Run("Delete", func(t *testing.T) {
   484  			t.Run("Package", func(t *testing.T) {
   485  				defer tests.PrintCurrentTest(t)()
   486  
   487  				cases := []struct {
   488  					Channel    string
   489  					References []string
   490  				}{
   491  					{channel1, []string{conanPackageReference}},
   492  					{channel2, []string{}},
   493  				}
   494  
   495  				for i, c := range cases {
   496  					rref, _ := conan_module.NewRecipeReference(name, version1, user1, c.Channel, conan_module.DefaultRevision)
   497  					references, err := conan_model.GetPackageReferences(db.DefaultContext, user.ID, rref)
   498  					assert.NoError(t, err)
   499  					assert.NotEmpty(t, references)
   500  
   501  					req := NewRequestWithJSON(t, "POST", fmt.Sprintf("%s/v1/conans/%s/%s/%s/%s/packages/delete", url, name, version1, user1, c.Channel), map[string][]string{
   502  						"package_ids": c.References,
   503  					}).AddTokenAuth(badToken)
   504  					MakeRequest(t, req, http.StatusUnauthorized)
   505  
   506  					req = NewRequestWithJSON(t, "POST", fmt.Sprintf("%s/v1/conans/%s/%s/%s/%s/packages/delete", url, name, version1, user1, c.Channel), map[string][]string{
   507  						"package_ids": c.References,
   508  					}).AddTokenAuth(token)
   509  					MakeRequest(t, req, http.StatusOK)
   510  
   511  					references, err = conan_model.GetPackageReferences(db.DefaultContext, user.ID, rref)
   512  					assert.NoError(t, err)
   513  					assert.Empty(t, references, "case %d: should be empty", i)
   514  				}
   515  			})
   516  
   517  			t.Run("Recipe", func(t *testing.T) {
   518  				defer tests.PrintCurrentTest(t)()
   519  
   520  				cases := []struct {
   521  					Channel string
   522  				}{
   523  					{channel1},
   524  					{channel2},
   525  				}
   526  
   527  				for i, c := range cases {
   528  					rref, _ := conan_module.NewRecipeReference(name, version1, user1, c.Channel, conan_module.DefaultRevision)
   529  					revisions, err := conan_model.GetRecipeRevisions(db.DefaultContext, user.ID, rref)
   530  					assert.NoError(t, err)
   531  					assert.NotEmpty(t, revisions)
   532  
   533  					req := NewRequest(t, "DELETE", fmt.Sprintf("%s/v1/conans/%s/%s/%s/%s", url, name, version1, user1, c.Channel)).
   534  						AddTokenAuth(badToken)
   535  					MakeRequest(t, req, http.StatusUnauthorized)
   536  
   537  					req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v1/conans/%s/%s/%s/%s", url, name, version1, user1, c.Channel)).
   538  						AddTokenAuth(token)
   539  					MakeRequest(t, req, http.StatusOK)
   540  
   541  					revisions, err = conan_model.GetRecipeRevisions(db.DefaultContext, user.ID, rref)
   542  					assert.NoError(t, err)
   543  					assert.Empty(t, revisions, "case %d: should be empty", i)
   544  				}
   545  			})
   546  		})
   547  	})
   548  
   549  	t.Run("v2", func(t *testing.T) {
   550  		t.Run("Ping", func(t *testing.T) {
   551  			defer tests.PrintCurrentTest(t)()
   552  
   553  			req := NewRequest(t, "GET", fmt.Sprintf("%s/v2/ping", url))
   554  			resp := MakeRequest(t, req, http.StatusOK)
   555  
   556  			assert.Equal(t, "revisions", resp.Header().Get("X-Conan-Server-Capabilities"))
   557  		})
   558  
   559  		token := ""
   560  
   561  		t.Run("UserName/Password Authenticate", func(t *testing.T) {
   562  			defer tests.PrintCurrentTest(t)()
   563  
   564  			req := NewRequest(t, "GET", fmt.Sprintf("%s/v2/users/authenticate", url)).
   565  				AddBasicAuth(user.Name)
   566  			resp := MakeRequest(t, req, http.StatusOK)
   567  
   568  			body := resp.Body.String()
   569  			assert.NotEmpty(t, body)
   570  
   571  			pkgMeta, err := package_service.ParseAuthorizationToken(body)
   572  			assert.NoError(t, err)
   573  			assert.Equal(t, user.ID, pkgMeta.UserID)
   574  			assert.Equal(t, auth_model.AccessTokenScopeAll, pkgMeta.Scope)
   575  
   576  			token = fmt.Sprintf("Bearer %s", body)
   577  		})
   578  
   579  		badToken := ""
   580  
   581  		t.Run("Token Scope Authentication", func(t *testing.T) {
   582  			defer tests.PrintCurrentTest(t)()
   583  
   584  			session := loginUser(t, user.Name)
   585  
   586  			badToken = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadNotification)
   587  
   588  			testCase := func(t *testing.T, scope auth_model.AccessTokenScope, expectedAuthStatusCode, expectedStatusCode int) {
   589  				t.Helper()
   590  
   591  				token := getTokenForLoggedInUser(t, session, scope)
   592  
   593  				req := NewRequest(t, "GET", fmt.Sprintf("%s/v2/users/authenticate", url)).
   594  					AddTokenAuth(token)
   595  				resp := MakeRequest(t, req, expectedAuthStatusCode)
   596  				if expectedAuthStatusCode != http.StatusOK {
   597  					return
   598  				}
   599  
   600  				body := resp.Body.String()
   601  				assert.NotEmpty(t, body)
   602  
   603  				pkgMeta, err := package_service.ParseAuthorizationToken(body)
   604  				assert.NoError(t, err)
   605  				assert.Equal(t, user.ID, pkgMeta.UserID)
   606  				assert.Equal(t, scope, pkgMeta.Scope)
   607  
   608  				recipeURL := fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s", url, "TestScope", version1, "testing", channel1, revision1)
   609  
   610  				req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/files/%s", recipeURL, conanfileName), strings.NewReader("Demo Conan file")).
   611  					AddTokenAuth(token)
   612  				MakeRequest(t, req, expectedStatusCode)
   613  			}
   614  
   615  			t.Run("No Package permission", func(t *testing.T) {
   616  				defer tests.PrintCurrentTest(t)()
   617  
   618  				testCase(t, auth_model.AccessTokenScopeReadNotification, http.StatusUnauthorized, http.StatusUnauthorized)
   619  			})
   620  
   621  			t.Run("Package Read permission", func(t *testing.T) {
   622  				defer tests.PrintCurrentTest(t)()
   623  
   624  				testCase(t, auth_model.AccessTokenScopeReadPackage, http.StatusOK, http.StatusUnauthorized)
   625  			})
   626  
   627  			t.Run("Package Write permission", func(t *testing.T) {
   628  				defer tests.PrintCurrentTest(t)()
   629  
   630  				testCase(t, auth_model.AccessTokenScopeWritePackage, http.StatusOK, http.StatusCreated)
   631  			})
   632  
   633  			t.Run("All permission", func(t *testing.T) {
   634  				defer tests.PrintCurrentTest(t)()
   635  
   636  				testCase(t, auth_model.AccessTokenScopeAll, http.StatusOK, http.StatusCreated)
   637  			})
   638  		})
   639  
   640  		t.Run("CheckCredentials", func(t *testing.T) {
   641  			defer tests.PrintCurrentTest(t)()
   642  
   643  			req := NewRequest(t, "GET", fmt.Sprintf("%s/v2/users/check_credentials", url)).
   644  				AddTokenAuth(token)
   645  			MakeRequest(t, req, http.StatusOK)
   646  		})
   647  
   648  		t.Run("Upload", func(t *testing.T) {
   649  			defer tests.PrintCurrentTest(t)()
   650  
   651  			uploadConanPackageV2(t, url, token, name, version1, user1, channel1, revision1, revision1)
   652  
   653  			t.Run("Validate", func(t *testing.T) {
   654  				defer tests.PrintCurrentTest(t)()
   655  
   656  				pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeConan)
   657  				assert.NoError(t, err)
   658  				assert.Len(t, pvs, 3)
   659  			})
   660  		})
   661  
   662  		t.Run("Latest", func(t *testing.T) {
   663  			defer tests.PrintCurrentTest(t)()
   664  
   665  			recipeURL := fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s", url, name, version1, user1, channel1)
   666  
   667  			req := NewRequest(t, "GET", fmt.Sprintf("%s/latest", recipeURL))
   668  			resp := MakeRequest(t, req, http.StatusOK)
   669  
   670  			obj := make(map[string]string)
   671  			DecodeJSON(t, resp, &obj)
   672  			assert.Contains(t, obj, "revision")
   673  			assert.Equal(t, revision1, obj["revision"])
   674  
   675  			req = NewRequest(t, "GET", fmt.Sprintf("%s/revisions/%s/packages/%s/latest", recipeURL, revision1, conanPackageReference))
   676  			resp = MakeRequest(t, req, http.StatusOK)
   677  
   678  			obj = make(map[string]string)
   679  			DecodeJSON(t, resp, &obj)
   680  			assert.Contains(t, obj, "revision")
   681  			assert.Equal(t, revision1, obj["revision"])
   682  		})
   683  
   684  		t.Run("ListRevisions", func(t *testing.T) {
   685  			defer tests.PrintCurrentTest(t)()
   686  
   687  			uploadConanPackageV2(t, url, token, name, version1, user1, channel1, revision1, revision2)
   688  			uploadConanPackageV2(t, url, token, name, version1, user1, channel1, revision2, revision1)
   689  			uploadConanPackageV2(t, url, token, name, version1, user1, channel1, revision2, revision2)
   690  
   691  			recipeURL := fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions", url, name, version1, user1, channel1)
   692  
   693  			req := NewRequest(t, "GET", recipeURL)
   694  			resp := MakeRequest(t, req, http.StatusOK)
   695  
   696  			type RevisionInfo struct {
   697  				Revision string    `json:"revision"`
   698  				Time     time.Time `json:"time"`
   699  			}
   700  
   701  			type RevisionList struct {
   702  				Revisions []*RevisionInfo `json:"revisions"`
   703  			}
   704  
   705  			var list *RevisionList
   706  			DecodeJSON(t, resp, &list)
   707  			assert.Len(t, list.Revisions, 2)
   708  			revs := make([]string, 0, len(list.Revisions))
   709  			for _, rev := range list.Revisions {
   710  				revs = append(revs, rev.Revision)
   711  			}
   712  			assert.ElementsMatch(t, []string{revision1, revision2}, revs)
   713  
   714  			req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/packages/%s/revisions", recipeURL, revision1, conanPackageReference))
   715  			resp = MakeRequest(t, req, http.StatusOK)
   716  
   717  			DecodeJSON(t, resp, &list)
   718  			assert.Len(t, list.Revisions, 2)
   719  			revs = make([]string, 0, len(list.Revisions))
   720  			for _, rev := range list.Revisions {
   721  				revs = append(revs, rev.Revision)
   722  			}
   723  			assert.ElementsMatch(t, []string{revision1, revision2}, revs)
   724  		})
   725  
   726  		t.Run("Search", func(t *testing.T) {
   727  			t.Run("Recipe", func(t *testing.T) {
   728  				defer tests.PrintCurrentTest(t)()
   729  
   730  				cases := []struct {
   731  					Query    string
   732  					Expected []string
   733  				}{
   734  					{"ConanPackage", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.3@dummy/test", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}},
   735  					{"ConanPackage/1.2", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}},
   736  					{"ConanPackage/1.1", []string{}},
   737  					{"Conan*", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.3@dummy/test", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}},
   738  					{"ConanPackage/", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.3@dummy/test", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}},
   739  					{"ConanPackage/*", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.3@dummy/test", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}},
   740  					{"ConanPackage/1*", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.3@dummy/test", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}},
   741  					{"ConanPackage/*2", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}},
   742  					{"ConanPackage/1*2", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}},
   743  					{"ConanPackage/1.2@", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}},
   744  					{"ConanPackage/1.2@du*", []string{"ConanPackage/1.2@dummy/test"}},
   745  					{"ConanPackage/1.2@du*/", []string{"ConanPackage/1.2@dummy/test"}},
   746  					{"ConanPackage/1.2@du*/*test", []string{"ConanPackage/1.2@dummy/test"}},
   747  					{"ConanPackage/1.2@du*/*st", []string{"ConanPackage/1.2@dummy/test"}},
   748  					{"ConanPackage/1.2@gitea/*", []string{"ConanPackage/1.2@gitea/test", "ConanPackage/1.2@gitea/final"}},
   749  					{"*/*@dummy", []string{"ConanPackage/1.2@dummy/test", "ConanPackage/1.3@dummy/test"}},
   750  					{"*/*@*/final", []string{"ConanPackage/1.2@gitea/final"}},
   751  				}
   752  
   753  				for i, c := range cases {
   754  					req := NewRequest(t, "GET", fmt.Sprintf("%s/v2/conans/search?q=%s", url, stdurl.QueryEscape(c.Query)))
   755  					resp := MakeRequest(t, req, http.StatusOK)
   756  
   757  					var result *conan_router.SearchResult
   758  					DecodeJSON(t, resp, &result)
   759  
   760  					assert.ElementsMatch(t, c.Expected, result.Results, "case %d: unexpected result", i)
   761  				}
   762  			})
   763  
   764  			t.Run("Package", func(t *testing.T) {
   765  				defer tests.PrintCurrentTest(t)()
   766  
   767  				req := NewRequest(t, "GET", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/search", url, name, version1, user1, channel1))
   768  				resp := MakeRequest(t, req, http.StatusOK)
   769  
   770  				var result map[string]*conan_module.Conaninfo
   771  				DecodeJSON(t, resp, &result)
   772  
   773  				assert.Contains(t, result, conanPackageReference)
   774  				info := result[conanPackageReference]
   775  				assert.NotEmpty(t, info.Settings)
   776  
   777  				req = NewRequest(t, "GET", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/search", url, name, version1, user1, channel1, revision1))
   778  				resp = MakeRequest(t, req, http.StatusOK)
   779  
   780  				result = make(map[string]*conan_module.Conaninfo)
   781  				DecodeJSON(t, resp, &result)
   782  
   783  				assert.Contains(t, result, conanPackageReference)
   784  				info = result[conanPackageReference]
   785  				assert.NotEmpty(t, info.Settings)
   786  			})
   787  		})
   788  
   789  		t.Run("Delete", func(t *testing.T) {
   790  			t.Run("Package", func(t *testing.T) {
   791  				defer tests.PrintCurrentTest(t)()
   792  
   793  				rref, _ := conan_module.NewRecipeReference(name, version1, user1, channel1, revision1)
   794  				pref, _ := conan_module.NewPackageReference(rref, conanPackageReference, conan_module.DefaultRevision)
   795  
   796  				checkPackageRevisionCount := func(count int) {
   797  					revisions, err := conan_model.GetPackageRevisions(db.DefaultContext, user.ID, pref)
   798  					assert.NoError(t, err)
   799  					assert.Len(t, revisions, count)
   800  				}
   801  				checkPackageReferenceCount := func(count int) {
   802  					references, err := conan_model.GetPackageReferences(db.DefaultContext, user.ID, rref)
   803  					assert.NoError(t, err)
   804  					assert.Len(t, references, count)
   805  				}
   806  
   807  				checkPackageRevisionCount(2)
   808  
   809  				req := NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages/%s/revisions/%s", url, name, version1, user1, channel1, revision1, conanPackageReference, revision1)).
   810  					AddTokenAuth(badToken)
   811  				MakeRequest(t, req, http.StatusUnauthorized)
   812  
   813  				req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages/%s/revisions/%s", url, name, version1, user1, channel1, revision1, conanPackageReference, revision1)).
   814  					AddTokenAuth(token)
   815  				MakeRequest(t, req, http.StatusOK)
   816  
   817  				checkPackageRevisionCount(1)
   818  
   819  				req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages/%s", url, name, version1, user1, channel1, revision1, conanPackageReference)).
   820  					AddTokenAuth(badToken)
   821  				MakeRequest(t, req, http.StatusUnauthorized)
   822  
   823  				req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages/%s", url, name, version1, user1, channel1, revision1, conanPackageReference)).
   824  					AddTokenAuth(token)
   825  				MakeRequest(t, req, http.StatusOK)
   826  
   827  				checkPackageRevisionCount(0)
   828  
   829  				rref = rref.WithRevision(revision2)
   830  
   831  				checkPackageReferenceCount(1)
   832  
   833  				req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages", url, name, version1, user1, channel1, revision2)).
   834  					AddTokenAuth(badToken)
   835  				MakeRequest(t, req, http.StatusUnauthorized)
   836  
   837  				req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages", url, name, version1, user1, channel1, revision2)).
   838  					AddTokenAuth(token)
   839  				MakeRequest(t, req, http.StatusOK)
   840  
   841  				checkPackageReferenceCount(0)
   842  			})
   843  
   844  			t.Run("Recipe", func(t *testing.T) {
   845  				defer tests.PrintCurrentTest(t)()
   846  
   847  				rref, _ := conan_module.NewRecipeReference(name, version1, user1, channel1, conan_module.DefaultRevision)
   848  
   849  				checkRecipeRevisionCount := func(count int) {
   850  					revisions, err := conan_model.GetRecipeRevisions(db.DefaultContext, user.ID, rref)
   851  					assert.NoError(t, err)
   852  					assert.Len(t, revisions, count)
   853  				}
   854  
   855  				checkRecipeRevisionCount(2)
   856  
   857  				req := NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s", url, name, version1, user1, channel1, revision1)).
   858  					AddTokenAuth(badToken)
   859  				MakeRequest(t, req, http.StatusUnauthorized)
   860  
   861  				req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s", url, name, version1, user1, channel1, revision1)).
   862  					AddTokenAuth(token)
   863  				MakeRequest(t, req, http.StatusOK)
   864  
   865  				checkRecipeRevisionCount(1)
   866  
   867  				req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s", url, name, version1, user1, channel1)).
   868  					AddTokenAuth(badToken)
   869  				MakeRequest(t, req, http.StatusUnauthorized)
   870  
   871  				req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s", url, name, version1, user1, channel1)).
   872  					AddTokenAuth(token)
   873  				MakeRequest(t, req, http.StatusOK)
   874  
   875  				checkRecipeRevisionCount(0)
   876  			})
   877  		})
   878  	})
   879  }