code.gitea.io/gitea@v1.22.3/tests/integration/api_packages_alpine_test.go (about) 1 // Copyright 2023 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package integration 5 6 import ( 7 "archive/tar" 8 "bufio" 9 "bytes" 10 "compress/gzip" 11 "encoding/base64" 12 "fmt" 13 "io" 14 "net/http" 15 "testing" 16 17 "code.gitea.io/gitea/models/db" 18 "code.gitea.io/gitea/models/packages" 19 "code.gitea.io/gitea/models/unittest" 20 user_model "code.gitea.io/gitea/models/user" 21 alpine_module "code.gitea.io/gitea/modules/packages/alpine" 22 alpine_service "code.gitea.io/gitea/services/packages/alpine" 23 "code.gitea.io/gitea/tests" 24 25 "github.com/stretchr/testify/assert" 26 ) 27 28 func TestPackageAlpine(t *testing.T) { 29 defer tests.PrepareTestEnv(t)() 30 31 user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) 32 33 packageName := "gitea-test" 34 packageVersion := "1.4.1-r3" 35 36 base64AlpinePackageContent := `H4sIAAAAAAACA9ML9nT30wsKdtTLzjNJzjYuckjPLElN1DUzMUxMNTa11CsqTtQrKE1ioAAYAIGZ 37 iQmYBgJ02hDENjQxMTAzMzQ1MTVjMDA0MTQ1ZlAwYKADKC0uSSxSUGAYoWDm4sZZtypv75+q2fVT 38 POD1bKkFB22ms+g1z+H4dk7AhC3HwUSj9EbT0Rk3Dn55dHxy/K7Q+Nl/i+L7Z036ypcRvvpZuMiN 39 s7wbZL/klqRGGshv9Gi0qHTgTZfw3HytnJdx9c3NTRp/PHn+Z50uq2pjkilzjtpfd+uzQMw1M7cY 40 i9RXJasnT2M+vDXCesLK7MilJt8sGplj4xUlLMUun9SzY+phFpxWxRXa06AseV9WvzH3jtGGoL5A 41 vQkea+VKPj5R+Cb461tIk97qpa9nJYsJujTNl2B/J1P52H/D2rPr/j19uU8p7cMSq5tmXk51ReXl 42 F/Yddr9XsMpEwFKlXSPo3QSGwnCOG8y2uadjm6ui998WYXNYubjg78N3a7bnXjhrl5fB8voI++LI 43 1FP5W44e2xf4Ou2wrtyic1Onz7MzMV5ksuno2V/LVG4eN/15X/n2/2vJ2VV+T68aT327dOrhd6e6 44 q5Y0V82Y83tdqkFa8TW2BvGCZ0ds/iibHVpzKuPcuSULO63/bNmfrnhjWqXzhMSXTb5Cv4vPaxSL 45 8LFMdqmxbN7+Y+Yi0ZyZhz4UxexLuHHFd1VFvk+kwvniq3P+f9rh52InWnL8Lpvedcecoh1GFSc5 46 xZ9VBGex2V269HZfwxSVCvP35wQfi2xKX+lYMXtF48n1R65O2PLWpm69RdESMa79dlrTGazsZacu 47 MbMLeSSScPORZde76/MBV6SFJAAEAAAfiwgAAAAAAAID7VRLaxsxEN6zfoUgZ++OVq+1aUIhUDeY 48 pKa49FhmJdkW3ofRysXpr69220t9SCk0gZJ+IGaY56eBmbxY4/m9Q+vCUOTr1fLu4d2H7O8CEpQQ 49 k0y4lAClypgQoBSTQqoMGBMgMnrOXgCnIWJIVLLXCcaoib5110CSij/V7D9eCZ5p5f9o/5VkF/tf 50 MqUzCi+5/6Hv41Nxv/Nffu4fwRVdus4FjM7S+pFiffKNpTxnkMMsALmin5PnHgMtS8rkgvGFBPpp 51 c0tLKDk5HnYdto5e052PDmfRDXE0fnUh2VgucjYLU5h1g0mm5RhGNymMrtEccOfIKTTJsY/xOCyK 52 YqqT+74gExWbmI2VlJ6LeQUcyPFH2lh/9SBuV/wjfXPohDnw8HZKviGD/zYmCZgrgsHsk36u1Bcl 53 SB/8zne/0jV92/qYbKRF38X0niiemN2QxhvXDWOL+7tNGhGeYt+m22mwaR6pddGZNM8FSeRxj8PY 54 X7PaqdqAVlqWXHKnmQGmK43VlqNlILRilbBSMI2jV5Vbu5XGSVsDyGc7yd8B/gK2qgAIAAAfiwgA 55 AAAAAAID7dNNSgMxGAbg7MSCOxcu5wJOv0x+OlkU7K5QoYXqVsxMMihlKMwP1Fu48QQewCN4DfEQ 56 egUz4sYuFKEtFN9n870hWSSQN+7P7GrsrfNV3Y9dW5Z3bNMo0FJ+zmB9EhcJ41KS1lxJpRnxbsWi 57 FduBtm5sFa7C/ifOo7y5Lf2QeiHar6jTaDSbnF5Mp+fzOL/x+aJuy3g+HvGhs8JY4b3yOpMZOZEo 58 lRW+MEoTTw3ZwqU0INNjsAe2VPk/9b/L3/s/kIKzqOtk+IbJGTtmr+bx7WoxOUoun98frk/un14O 59 Djfa/2q5bH4699v++uMAAAAAAAAAAAAAAAAAAAAAAHbgA/eXQh8AKAAA` 60 content, err := base64.StdEncoding.DecodeString(base64AlpinePackageContent) 61 assert.NoError(t, err) 62 63 base64AlpinePackageNoArchContent := `H4sIAAAAAAACA9ML9nT30wsKdtQrLU4t0jUzTUo1NDVP0ysqTtQrKE1ioAYwAAIzExMwDQTotCGI 64 bWhiampuYmRiaGrMYGBoZGZkxKBgwEAHUFpcklikoMAwQkHLB7eoE40P9n5jvx32t7Dy9rq7x19k 65 66cJPV38t/h+vWe2jdXy+/PzPT0YTF5z39i4cPFptcLa1C1lD0z/XvrNp6In/7nP4PPCF2pZu8uV 66 z74QXLxpY1XWJuVFysqVf+PdizccFbD6ZL/QPGXd1Ri1fec2XBNuYfK/rFa6wF/h3dK/W12f8mxP 67 04iP3aCy+vPx7h9S+5M1LLkWr5M/4ezGt3bDW/FjBp/S9hiKP72s/XrJ0vWtO0zr5wa+D/X8XluW 68 d7BLP7XS3YUhd8WbPPF/NW3691ONJbXsRb69O7BIMZC96uTri+utC/fbie5J+n7zhCxD4Aep/qet 69 QnlCZyN8MhNdVNlNl7965R1nExrrGvfI/YQZFx8Dg+d9122hZsYd/24WL/L69OWrDAN/y//nS7im 70 XEive3v7QeTe433TPj/X71+9yHiV6+E9k++3TL8V0Xoq9panhNt23fLgau/pTOvmKx6bV/pS26+Y 71 5UP4viyuklYeu4/BZl6rLINe1L/uWuUXcH5z7pa2b9+/rp/v/8dFgc1PL3bO3/iVcrI//J/LMU2X 72 Nzu1IaMmWXnGp7CmyQIR39d0Nai9/+tdPbfjvmsNH88Tu7uVrvNuJE0wjxfePXGv/KHNXD+mnG0t 73 yTPu+Na0b5WR9O4t0yMd9T5k6ui7hOyU/jL/4dOn6neLwhdrZIZfcl1ectnGvUTurWDo1vY5Gw9k 74 PTQLVgcA61F+7gAEAAAfiwgAAAAAAAID7VVNa9wwEPXZv2Ig53hHlizbCzkVkobQJtDkB4wl2SvW 75 lhdbTpP++oyXQGEPLYU2paTvIs3X05PQSNnmjp4+OrJumjfZ3c3V9efL2+T3AhlaqePIOB0Rc50I 76 VRSlypUoZIJCKJQJPCVvgGWONLGU5H1CCDDRD+4CU57S6zT5j3eCP9Tyv9T/GsuT/scyLxPAt+z/ 77 aRzjj/J+Fv9HcQZXLriJorPQPAM1i+8tyEzkGZ5PmJ7BMvvQQUt7tx4BPPJH4ccAIpN5Jjj+hSJc 78 ugZAghDbArco4eH+A+SYq/Sw7wINDi6g89HReRhpMrvVzTzsFZlaV2Hbutmw4zVhmXo2djEe5u1m 79 c6zNzDikR3mW1a61JepaC0SZHsjsqTsyPoR9GL+GdPbf1iSFtU5Xyu/c4+Q7H04lMfvgI3vT3hsX 80 5rX40/U9b5CWOA78Mhrq+2ewLjrDp7VNWQbtaF6ZXVWZIhdV09RWOIvU6BqNboSxLSEpkrpQq80x 81 W1Nla6NavuqtrJQ0sv17D+4L2oD1lwAIAAAfiwgAAAAAAAID7dM/SgNBFAbw6cSAnYXlXsDNm50/ 82 u1METBeIkEBMK87uzKKEJbB/IN7CxhN4AI/gNcRD6BWciI0WSiBGxO/XvA9mile8L+5P7WrkrfN1 83 049dV1XXbNso0FK+zeDzJC4SxqVSqUwkV4IR51KkLFqxHeia1tZhFfY/cR4V7VXlB9QL0b5HnUXD 84 6fj4bDI5ncXFpS8WTVfFs9GQD5wVxgrvlde5zMmJRKm89KVRmnhmyJYuo5RMj8Ef8EOV36j/6/yx 85 /5qnxKJ1J8MZJifskD2Zu+fzxfggmT+83F4c3dw/7u1vtf/1ctl+9e+7dwAAAAAAAAAAAAAAAAAA 86 AACAX/AKARNTyAAoAAA=` 87 noarchContent, err := base64.StdEncoding.DecodeString(base64AlpinePackageNoArchContent) 88 assert.NoError(t, err) 89 90 branches := []string{"v3.16", "v3.17"} 91 repositories := []string{"main", "testing"} 92 93 rootURL := fmt.Sprintf("/api/packages/%s/alpine", user.Name) 94 95 t.Run("RepositoryKey", func(t *testing.T) { 96 defer tests.PrintCurrentTest(t)() 97 98 req := NewRequest(t, "GET", rootURL+"/key") 99 resp := MakeRequest(t, req, http.StatusOK) 100 101 assert.Equal(t, "application/x-pem-file", resp.Header().Get("Content-Type")) 102 assert.Contains(t, resp.Body.String(), "-----BEGIN PUBLIC KEY-----") 103 }) 104 105 for _, branch := range branches { 106 for _, repository := range repositories { 107 t.Run(fmt.Sprintf("[Branch:%s,Repository:%s]", branch, repository), func(t *testing.T) { 108 t.Run("Upload", func(t *testing.T) { 109 defer tests.PrintCurrentTest(t)() 110 111 uploadURL := fmt.Sprintf("%s/%s/%s", rootURL, branch, repository) 112 113 req := NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{})) 114 MakeRequest(t, req, http.StatusUnauthorized) 115 116 req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{})). 117 AddBasicAuth(user.Name) 118 MakeRequest(t, req, http.StatusBadRequest) 119 120 req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader(content)). 121 AddBasicAuth(user.Name) 122 MakeRequest(t, req, http.StatusCreated) 123 124 pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeAlpine) 125 assert.NoError(t, err) 126 assert.Len(t, pvs, 1) 127 128 pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0]) 129 assert.NoError(t, err) 130 assert.Nil(t, pd.SemVer) 131 assert.IsType(t, &alpine_module.VersionMetadata{}, pd.Metadata) 132 assert.Equal(t, packageName, pd.Package.Name) 133 assert.Equal(t, packageVersion, pd.Version.Version) 134 135 pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) 136 assert.NoError(t, err) 137 assert.NotEmpty(t, pfs) 138 assert.Condition(t, func() bool { 139 seen := false 140 expectedFilename := fmt.Sprintf("%s-%s.apk", packageName, packageVersion) 141 expectedCompositeKey := fmt.Sprintf("%s|%s|x86_64", branch, repository) 142 for _, pf := range pfs { 143 if pf.Name == expectedFilename && pf.CompositeKey == expectedCompositeKey { 144 if seen { 145 return false 146 } 147 seen = true 148 149 assert.True(t, pf.IsLead) 150 151 pfps, err := packages.GetProperties(db.DefaultContext, packages.PropertyTypeFile, pf.ID) 152 assert.NoError(t, err) 153 154 for _, pfp := range pfps { 155 switch pfp.Name { 156 case alpine_module.PropertyBranch: 157 assert.Equal(t, branch, pfp.Value) 158 case alpine_module.PropertyRepository: 159 assert.Equal(t, repository, pfp.Value) 160 case alpine_module.PropertyArchitecture: 161 assert.Equal(t, "x86_64", pfp.Value) 162 } 163 } 164 } 165 } 166 return seen 167 }) 168 }) 169 170 readIndexContent := func(r io.Reader) (string, error) { 171 br := bufio.NewReader(r) 172 173 gzr, err := gzip.NewReader(br) 174 if err != nil { 175 return "", err 176 } 177 178 for { 179 gzr.Multistream(false) 180 181 tr := tar.NewReader(gzr) 182 for { 183 hd, err := tr.Next() 184 if err == io.EOF { 185 break 186 } 187 if err != nil { 188 return "", err 189 } 190 191 if hd.Name == alpine_service.IndexFilename { 192 buf, err := io.ReadAll(tr) 193 if err != nil { 194 return "", err 195 } 196 197 return string(buf), nil 198 } 199 } 200 201 err = gzr.Reset(br) 202 if err == io.EOF { 203 break 204 } 205 if err != nil { 206 return "", err 207 } 208 } 209 210 return "", io.EOF 211 } 212 213 t.Run("Index", func(t *testing.T) { 214 defer tests.PrintCurrentTest(t)() 215 216 req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/x86_64/APKINDEX.tar.gz", rootURL, branch, repository)) 217 resp := MakeRequest(t, req, http.StatusOK) 218 219 content, err := readIndexContent(resp.Body) 220 assert.NoError(t, err) 221 222 assert.Contains(t, content, "C:Q1/se1PjO94hYXbfpNR1/61hVORIc=\n") 223 assert.Contains(t, content, "P:"+packageName+"\n") 224 assert.Contains(t, content, "V:"+packageVersion+"\n") 225 assert.Contains(t, content, "A:x86_64\n") 226 assert.NotContains(t, content, "A:noarch\n") 227 assert.Contains(t, content, "T:Gitea Test Package\n") 228 assert.Contains(t, content, "U:https://gitea.io/\n") 229 assert.Contains(t, content, "L:MIT\n") 230 assert.Contains(t, content, "S:1353\n") 231 assert.Contains(t, content, "I:4096\n") 232 assert.Contains(t, content, "o:gitea-test\n") 233 assert.Contains(t, content, "m:KN4CK3R <kn4ck3r@gitea.io>\n") 234 assert.Contains(t, content, "t:1679498030\n") 235 }) 236 237 t.Run("Download", func(t *testing.T) { 238 defer tests.PrintCurrentTest(t)() 239 240 req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/x86_64/%s-%s.apk", rootURL, branch, repository, packageName, packageVersion)) 241 MakeRequest(t, req, http.StatusOK) 242 }) 243 244 t.Run("NoArch", func(t *testing.T) { 245 defer tests.PrintCurrentTest(t)() 246 247 req := NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/%s/%s", rootURL, branch, repository), bytes.NewReader(noarchContent)). 248 AddBasicAuth(user.Name) 249 MakeRequest(t, req, http.StatusCreated) 250 251 req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/x86_64/APKINDEX.tar.gz", rootURL, branch, repository)) 252 resp := MakeRequest(t, req, http.StatusOK) 253 254 content, err := readIndexContent(resp.Body) 255 assert.NoError(t, err) 256 257 assert.Contains(t, content, "C:Q1/se1PjO94hYXbfpNR1/61hVORIc=\n") 258 assert.Contains(t, content, "A:x86_64\n") 259 assert.Contains(t, content, "C:Q1kbH5WoIPFccQYyATanaKXd2cJcc=\n") 260 assert.NotContains(t, content, "A:noarch\n") 261 262 // noarch package should be available with every architecture requested 263 for _, arch := range []string{alpine_module.NoArch, "x86_64", "my_arch"} { 264 req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/%s/gitea-noarch-1.4-r0.apk", rootURL, branch, repository, arch)) 265 MakeRequest(t, req, http.StatusOK) 266 } 267 268 req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s/noarch/gitea-noarch-1.4-r0.apk", rootURL, branch, repository)). 269 AddBasicAuth(user.Name) 270 MakeRequest(t, req, http.StatusNoContent) 271 }) 272 }) 273 } 274 } 275 276 t.Run("Delete", func(t *testing.T) { 277 defer tests.PrintCurrentTest(t)() 278 279 for _, branch := range branches { 280 for _, repository := range repositories { 281 req := NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s/x86_64/%s-%s.apk", rootURL, branch, repository, packageName, packageVersion)) 282 MakeRequest(t, req, http.StatusUnauthorized) 283 284 req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s/x86_64/%s-%s.apk", rootURL, branch, repository, packageName, packageVersion)). 285 AddBasicAuth(user.Name) 286 MakeRequest(t, req, http.StatusNoContent) 287 288 // Deleting the last file of an architecture should remove that index 289 req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/x86_64/APKINDEX.tar.gz", rootURL, branch, repository)) 290 MakeRequest(t, req, http.StatusNotFound) 291 } 292 } 293 }) 294 }