gitea源码

api_packages_nuget_test.go 34KB

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