[FEAT] API support for repository flags
Expose the repository flags feature over the API, so the flags can be managed by a site administrator without using the web API. Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu> (cherry picked from commit bac9f0225d47e159afa90e5bbea9562cbc860dae) (cherry picked from commit e7f5c1ba141ac7f8c7834b5048d0ffd3ce50900b) (cherry picked from commit 95d9fe19cf3ed5787855ac2a442d29104498aa36) (cherry picked from commit 7fc51991e405ea8d44fd6b4b4de13ad65da63ae7)
This commit is contained in:
		
					parent
					
						
							
								36f7c162e2
							
						
					
				
			
			
				commit
				
					
						639b428cf4
					
				
			
		
					 6 changed files with 686 additions and 0 deletions
				
			
		
							
								
								
									
										9
									
								
								modules/structs/repo_flags.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								modules/structs/repo_flags.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | |||
| // Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved. | ||||
| // SPDX-License-Identifier: MIT | ||||
| 
 | ||||
| package structs | ||||
| 
 | ||||
| // ReplaceFlagsOption options when replacing the flags of a repository | ||||
| type ReplaceFlagsOption struct { | ||||
| 	Flags []string `json:"flags"` | ||||
| } | ||||
|  | @ -1096,6 +1096,18 @@ func Routes() *web.Route { | |||
| 						m.Get("/permission", repo.GetRepoPermissions) | ||||
| 					}) | ||||
| 				}, reqToken()) | ||||
| 				if setting.Repository.EnableFlags { | ||||
| 					m.Group("/flags", func() { | ||||
| 						m.Combo("").Get(repo.ListFlags). | ||||
| 							Put(bind(api.ReplaceFlagsOption{}), repo.ReplaceAllFlags). | ||||
| 							Delete(repo.DeleteAllFlags) | ||||
| 						m.Group("/{flag}", func() { | ||||
| 							m.Combo("").Get(repo.HasFlag). | ||||
| 								Put(repo.AddFlag). | ||||
| 								Delete(repo.DeleteFlag) | ||||
| 						}) | ||||
| 					}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryAdmin), reqToken(), reqSiteAdmin()) | ||||
| 				} | ||||
| 				m.Get("/assignees", reqToken(), reqAnyRepoReader(), repo.GetAssignees) | ||||
| 				m.Get("/reviewers", reqToken(), reqAnyRepoReader(), repo.GetReviewers) | ||||
| 				m.Group("/teams", func() { | ||||
|  |  | |||
							
								
								
									
										245
									
								
								routers/api/v1/repo/flags.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								routers/api/v1/repo/flags.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,245 @@ | |||
| // Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved. | ||||
| // SPDX-License-Identifier: MIT | ||||
| 
 | ||||
| package repo | ||||
| 
 | ||||
| import ( | ||||
| 	"net/http" | ||||
| 
 | ||||
| 	"code.gitea.io/gitea/modules/context" | ||||
| 	api "code.gitea.io/gitea/modules/structs" | ||||
| 	"code.gitea.io/gitea/modules/web" | ||||
| ) | ||||
| 
 | ||||
| func ListFlags(ctx *context.APIContext) { | ||||
| 	// swagger:operation GET /repos/{owner}/{repo}/flags repository repoListFlags | ||||
| 	// --- | ||||
| 	// summary: List a repository's flags | ||||
| 	// produces: | ||||
| 	// - application/json | ||||
| 	// parameters: | ||||
| 	// - name: owner | ||||
| 	//   in: path | ||||
| 	//   description: owner of the repo | ||||
| 	//   type: string | ||||
| 	//   required: true | ||||
| 	// - name: repo | ||||
| 	//   in: path | ||||
| 	//   description: name of the repo | ||||
| 	//   type: string | ||||
| 	//   required: true | ||||
| 	// responses: | ||||
| 	//   "200": | ||||
| 	//     "$ref": "#/responses/StringSlice" | ||||
| 	//   "403": | ||||
| 	//     "$ref": "#/responses/forbidden" | ||||
| 	//   "404": | ||||
| 	//     "$ref": "#/responses/notFound" | ||||
| 
 | ||||
| 	repoFlags, err := ctx.Repo.Repository.ListFlags(ctx) | ||||
| 	if err != nil { | ||||
| 		ctx.InternalServerError(err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	flags := make([]string, len(repoFlags)) | ||||
| 	for i := range repoFlags { | ||||
| 		flags[i] = repoFlags[i].Name | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.SetTotalCountHeader(int64(len(repoFlags))) | ||||
| 	ctx.JSON(http.StatusOK, flags) | ||||
| } | ||||
| 
 | ||||
| func ReplaceAllFlags(ctx *context.APIContext) { | ||||
| 	// swagger:operation PUT /repos/{owner}/{repo}/flags repository repoReplaceAllFlags | ||||
| 	// --- | ||||
| 	// summary: Replace all flags of a repository | ||||
| 	// produces: | ||||
| 	// - application/json | ||||
| 	// parameters: | ||||
| 	// - name: owner | ||||
| 	//   in: path | ||||
| 	//   description: owner of the repo | ||||
| 	//   type: string | ||||
| 	//   required: true | ||||
| 	// - name: repo | ||||
| 	//   in: path | ||||
| 	//   description: name of the repo | ||||
| 	//   type: string | ||||
| 	//   required: true | ||||
| 	// - name: body | ||||
| 	//   in: body | ||||
| 	//   schema: | ||||
| 	//     "$ref": "#/definitions/ReplaceFlagsOption" | ||||
| 	// responses: | ||||
| 	//   "204": | ||||
| 	//     "$ref": "#/responses/empty" | ||||
| 	//   "403": | ||||
| 	//     "$ref": "#/responses/forbidden" | ||||
| 	//   "404": | ||||
| 	//     "$ref": "#/responses/notFound" | ||||
| 
 | ||||
| 	flagsForm := web.GetForm(ctx).(*api.ReplaceFlagsOption) | ||||
| 
 | ||||
| 	if err := ctx.Repo.Repository.ReplaceAllFlags(ctx, flagsForm.Flags); err != nil { | ||||
| 		ctx.InternalServerError(err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.Status(http.StatusNoContent) | ||||
| } | ||||
| 
 | ||||
| func DeleteAllFlags(ctx *context.APIContext) { | ||||
| 	// swagger:operation DELETE /repos/{owner}/{repo}/flags repository repoDeleteAllFlags | ||||
| 	// --- | ||||
| 	// summary: Remove all flags from a repository | ||||
| 	// produces: | ||||
| 	// - application/json | ||||
| 	// parameters: | ||||
| 	// - name: owner | ||||
| 	//   in: path | ||||
| 	//   description: owner of the repo | ||||
| 	//   type: string | ||||
| 	//   required: true | ||||
| 	// - name: repo | ||||
| 	//   in: path | ||||
| 	//   description: name of the repo | ||||
| 	//   type: string | ||||
| 	//   required: true | ||||
| 	// responses: | ||||
| 	//   "204": | ||||
| 	//     "$ref": "#/responses/empty" | ||||
| 	//   "403": | ||||
| 	//     "$ref": "#/responses/forbidden" | ||||
| 	//   "404": | ||||
| 	//     "$ref": "#/responses/notFound" | ||||
| 
 | ||||
| 	if err := ctx.Repo.Repository.ReplaceAllFlags(ctx, nil); err != nil { | ||||
| 		ctx.InternalServerError(err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.Status(http.StatusNoContent) | ||||
| } | ||||
| 
 | ||||
| func HasFlag(ctx *context.APIContext) { | ||||
| 	// swagger:operation GET /repos/{owner}/{repo}/flags/{flag} repository repoCheckFlag | ||||
| 	// --- | ||||
| 	// summary: Check if a repository has a given flag | ||||
| 	// produces: | ||||
| 	// - application/json | ||||
| 	// parameters: | ||||
| 	// - name: owner | ||||
| 	//   in: path | ||||
| 	//   description: owner of the repo | ||||
| 	//   type: string | ||||
| 	//   required: true | ||||
| 	// - name: repo | ||||
| 	//   in: path | ||||
| 	//   description: name of the repo | ||||
| 	//   type: string | ||||
| 	//   required: true | ||||
| 	// - name: flag | ||||
| 	//   in: path | ||||
| 	//   description: name of the flag | ||||
| 	//   type: string | ||||
| 	//   required: true | ||||
| 	// responses: | ||||
| 	//   "204": | ||||
| 	//     "$ref": "#/responses/empty" | ||||
| 	//   "403": | ||||
| 	//     "$ref": "#/responses/forbidden" | ||||
| 	//   "404": | ||||
| 	//     "$ref": "#/responses/notFound" | ||||
| 
 | ||||
| 	hasFlag := ctx.Repo.Repository.HasFlag(ctx, ctx.Params(":flag")) | ||||
| 	if hasFlag { | ||||
| 		ctx.Status(http.StatusNoContent) | ||||
| 	} else { | ||||
| 		ctx.NotFound() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func AddFlag(ctx *context.APIContext) { | ||||
| 	// swagger:operation PUT /repos/{owner}/{repo}/flags/{flag} repository repoAddFlag | ||||
| 	// --- | ||||
| 	// summary: Add a flag to a repository | ||||
| 	// produces: | ||||
| 	// - application/json | ||||
| 	// parameters: | ||||
| 	// - name: owner | ||||
| 	//   in: path | ||||
| 	//   description: owner of the repo | ||||
| 	//   type: string | ||||
| 	//   required: true | ||||
| 	// - name: repo | ||||
| 	//   in: path | ||||
| 	//   description: name of the repo | ||||
| 	//   type: string | ||||
| 	//   required: true | ||||
| 	// - name: flag | ||||
| 	//   in: path | ||||
| 	//   description: name of the flag | ||||
| 	//   type: string | ||||
| 	//   required: true | ||||
| 	// responses: | ||||
| 	//   "204": | ||||
| 	//     "$ref": "#/responses/empty" | ||||
| 	//   "403": | ||||
| 	//     "$ref": "#/responses/forbidden" | ||||
| 	//   "404": | ||||
| 	//     "$ref": "#/responses/notFound" | ||||
| 
 | ||||
| 	flag := ctx.Params(":flag") | ||||
| 
 | ||||
| 	if ctx.Repo.Repository.HasFlag(ctx, flag) { | ||||
| 		ctx.Status(http.StatusNoContent) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if err := ctx.Repo.Repository.AddFlag(ctx, flag); err != nil { | ||||
| 		ctx.InternalServerError(err) | ||||
| 		return | ||||
| 	} | ||||
| 	ctx.Status(http.StatusNoContent) | ||||
| } | ||||
| 
 | ||||
| func DeleteFlag(ctx *context.APIContext) { | ||||
| 	// swagger:operation DELETE /repos/{owner}/{repo}/flags/{flag} repository repoDeleteFlag | ||||
| 	// --- | ||||
| 	// summary: Remove a flag from a repository | ||||
| 	// produces: | ||||
| 	// - application/json | ||||
| 	// parameters: | ||||
| 	// - name: owner | ||||
| 	//   in: path | ||||
| 	//   description: owner of the repo | ||||
| 	//   type: string | ||||
| 	//   required: true | ||||
| 	// - name: repo | ||||
| 	//   in: path | ||||
| 	//   description: name of the repo | ||||
| 	//   type: string | ||||
| 	//   required: true | ||||
| 	// - name: flag | ||||
| 	//   in: path | ||||
| 	//   description: name of the flag | ||||
| 	//   type: string | ||||
| 	//   required: true | ||||
| 	// responses: | ||||
| 	//   "204": | ||||
| 	//     "$ref": "#/responses/empty" | ||||
| 	//   "403": | ||||
| 	//     "$ref": "#/responses/forbidden" | ||||
| 	//   "404": | ||||
| 	//     "$ref": "#/responses/notFound" | ||||
| 
 | ||||
| 	flag := ctx.Params(":flag") | ||||
| 
 | ||||
| 	if _, err := ctx.Repo.Repository.DeleteFlag(ctx, flag); err != nil { | ||||
| 		ctx.InternalServerError(err) | ||||
| 		return | ||||
| 	} | ||||
| 	ctx.Status(http.StatusNoContent) | ||||
| } | ||||
|  | @ -17,6 +17,9 @@ type swaggerParameterBodies struct { | |||
| 	// in:body | ||||
| 	AddCollaboratorOption api.AddCollaboratorOption | ||||
| 
 | ||||
| 	// in:body | ||||
| 	ReplaceFlagsOption api.ReplaceFlagsOption | ||||
| 
 | ||||
| 	// in:body | ||||
| 	CreateEmailOption api.CreateEmailOption | ||||
| 	// in:body | ||||
|  |  | |||
							
								
								
									
										268
									
								
								templates/swagger/v1_json.tmpl
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										268
									
								
								templates/swagger/v1_json.tmpl
									
										
									
										generated
									
									
									
								
							|  | @ -4992,6 +4992,260 @@ | |||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "/repos/{owner}/{repo}/flags": { | ||||
|       "get": { | ||||
|         "produces": [ | ||||
|           "application/json" | ||||
|         ], | ||||
|         "tags": [ | ||||
|           "repository" | ||||
|         ], | ||||
|         "summary": "List a repository's flags", | ||||
|         "operationId": "repoListFlags", | ||||
|         "parameters": [ | ||||
|           { | ||||
|             "type": "string", | ||||
|             "description": "owner of the repo", | ||||
|             "name": "owner", | ||||
|             "in": "path", | ||||
|             "required": true | ||||
|           }, | ||||
|           { | ||||
|             "type": "string", | ||||
|             "description": "name of the repo", | ||||
|             "name": "repo", | ||||
|             "in": "path", | ||||
|             "required": true | ||||
|           } | ||||
|         ], | ||||
|         "responses": { | ||||
|           "200": { | ||||
|             "$ref": "#/responses/StringSlice" | ||||
|           }, | ||||
|           "403": { | ||||
|             "$ref": "#/responses/forbidden" | ||||
|           }, | ||||
|           "404": { | ||||
|             "$ref": "#/responses/notFound" | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|       "put": { | ||||
|         "produces": [ | ||||
|           "application/json" | ||||
|         ], | ||||
|         "tags": [ | ||||
|           "repository" | ||||
|         ], | ||||
|         "summary": "Replace all flags of a repository", | ||||
|         "operationId": "repoReplaceAllFlags", | ||||
|         "parameters": [ | ||||
|           { | ||||
|             "type": "string", | ||||
|             "description": "owner of the repo", | ||||
|             "name": "owner", | ||||
|             "in": "path", | ||||
|             "required": true | ||||
|           }, | ||||
|           { | ||||
|             "type": "string", | ||||
|             "description": "name of the repo", | ||||
|             "name": "repo", | ||||
|             "in": "path", | ||||
|             "required": true | ||||
|           }, | ||||
|           { | ||||
|             "name": "body", | ||||
|             "in": "body", | ||||
|             "schema": { | ||||
|               "$ref": "#/definitions/ReplaceFlagsOption" | ||||
|             } | ||||
|           } | ||||
|         ], | ||||
|         "responses": { | ||||
|           "204": { | ||||
|             "$ref": "#/responses/empty" | ||||
|           }, | ||||
|           "403": { | ||||
|             "$ref": "#/responses/forbidden" | ||||
|           }, | ||||
|           "404": { | ||||
|             "$ref": "#/responses/notFound" | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|       "delete": { | ||||
|         "produces": [ | ||||
|           "application/json" | ||||
|         ], | ||||
|         "tags": [ | ||||
|           "repository" | ||||
|         ], | ||||
|         "summary": "Remove all flags from a repository", | ||||
|         "operationId": "repoDeleteAllFlags", | ||||
|         "parameters": [ | ||||
|           { | ||||
|             "type": "string", | ||||
|             "description": "owner of the repo", | ||||
|             "name": "owner", | ||||
|             "in": "path", | ||||
|             "required": true | ||||
|           }, | ||||
|           { | ||||
|             "type": "string", | ||||
|             "description": "name of the repo", | ||||
|             "name": "repo", | ||||
|             "in": "path", | ||||
|             "required": true | ||||
|           } | ||||
|         ], | ||||
|         "responses": { | ||||
|           "204": { | ||||
|             "$ref": "#/responses/empty" | ||||
|           }, | ||||
|           "403": { | ||||
|             "$ref": "#/responses/forbidden" | ||||
|           }, | ||||
|           "404": { | ||||
|             "$ref": "#/responses/notFound" | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "/repos/{owner}/{repo}/flags/{flag}": { | ||||
|       "get": { | ||||
|         "produces": [ | ||||
|           "application/json" | ||||
|         ], | ||||
|         "tags": [ | ||||
|           "repository" | ||||
|         ], | ||||
|         "summary": "Check if a repository has a given flag", | ||||
|         "operationId": "repoCheckFlag", | ||||
|         "parameters": [ | ||||
|           { | ||||
|             "type": "string", | ||||
|             "description": "owner of the repo", | ||||
|             "name": "owner", | ||||
|             "in": "path", | ||||
|             "required": true | ||||
|           }, | ||||
|           { | ||||
|             "type": "string", | ||||
|             "description": "name of the repo", | ||||
|             "name": "repo", | ||||
|             "in": "path", | ||||
|             "required": true | ||||
|           }, | ||||
|           { | ||||
|             "type": "string", | ||||
|             "description": "name of the flag", | ||||
|             "name": "flag", | ||||
|             "in": "path", | ||||
|             "required": true | ||||
|           } | ||||
|         ], | ||||
|         "responses": { | ||||
|           "204": { | ||||
|             "$ref": "#/responses/empty" | ||||
|           }, | ||||
|           "403": { | ||||
|             "$ref": "#/responses/forbidden" | ||||
|           }, | ||||
|           "404": { | ||||
|             "$ref": "#/responses/notFound" | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|       "put": { | ||||
|         "produces": [ | ||||
|           "application/json" | ||||
|         ], | ||||
|         "tags": [ | ||||
|           "repository" | ||||
|         ], | ||||
|         "summary": "Add a flag to a repository", | ||||
|         "operationId": "repoAddFlag", | ||||
|         "parameters": [ | ||||
|           { | ||||
|             "type": "string", | ||||
|             "description": "owner of the repo", | ||||
|             "name": "owner", | ||||
|             "in": "path", | ||||
|             "required": true | ||||
|           }, | ||||
|           { | ||||
|             "type": "string", | ||||
|             "description": "name of the repo", | ||||
|             "name": "repo", | ||||
|             "in": "path", | ||||
|             "required": true | ||||
|           }, | ||||
|           { | ||||
|             "type": "string", | ||||
|             "description": "name of the flag", | ||||
|             "name": "flag", | ||||
|             "in": "path", | ||||
|             "required": true | ||||
|           } | ||||
|         ], | ||||
|         "responses": { | ||||
|           "204": { | ||||
|             "$ref": "#/responses/empty" | ||||
|           }, | ||||
|           "403": { | ||||
|             "$ref": "#/responses/forbidden" | ||||
|           }, | ||||
|           "404": { | ||||
|             "$ref": "#/responses/notFound" | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|       "delete": { | ||||
|         "produces": [ | ||||
|           "application/json" | ||||
|         ], | ||||
|         "tags": [ | ||||
|           "repository" | ||||
|         ], | ||||
|         "summary": "Remove a flag from a repository", | ||||
|         "operationId": "repoDeleteFlag", | ||||
|         "parameters": [ | ||||
|           { | ||||
|             "type": "string", | ||||
|             "description": "owner of the repo", | ||||
|             "name": "owner", | ||||
|             "in": "path", | ||||
|             "required": true | ||||
|           }, | ||||
|           { | ||||
|             "type": "string", | ||||
|             "description": "name of the repo", | ||||
|             "name": "repo", | ||||
|             "in": "path", | ||||
|             "required": true | ||||
|           }, | ||||
|           { | ||||
|             "type": "string", | ||||
|             "description": "name of the flag", | ||||
|             "name": "flag", | ||||
|             "in": "path", | ||||
|             "required": true | ||||
|           } | ||||
|         ], | ||||
|         "responses": { | ||||
|           "204": { | ||||
|             "$ref": "#/responses/empty" | ||||
|           }, | ||||
|           "403": { | ||||
|             "$ref": "#/responses/forbidden" | ||||
|           }, | ||||
|           "404": { | ||||
|             "$ref": "#/responses/notFound" | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "/repos/{owner}/{repo}/forks": { | ||||
|       "get": { | ||||
|         "produces": [ | ||||
|  | @ -22012,6 +22266,20 @@ | |||
|       }, | ||||
|       "x-go-package": "code.gitea.io/gitea/modules/structs" | ||||
|     }, | ||||
|     "ReplaceFlagsOption": { | ||||
|       "description": "ReplaceFlagsOption options when replacing the flags of a repository", | ||||
|       "type": "object", | ||||
|       "properties": { | ||||
|         "flags": { | ||||
|           "type": "array", | ||||
|           "items": { | ||||
|             "type": "string" | ||||
|           }, | ||||
|           "x-go-name": "Flags" | ||||
|         } | ||||
|       }, | ||||
|       "x-go-package": "code.gitea.io/gitea/modules/structs" | ||||
|     }, | ||||
|     "RepoCollaboratorPermission": { | ||||
|       "description": "RepoCollaboratorPermission to get repository permission for a collaborator", | ||||
|       "type": "object", | ||||
|  |  | |||
|  | @ -7,13 +7,16 @@ import ( | |||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"net/http/httptest" | ||||
| 	"slices" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	auth_model "code.gitea.io/gitea/models/auth" | ||||
| 	"code.gitea.io/gitea/models/db" | ||||
| 	repo_model "code.gitea.io/gitea/models/repo" | ||||
| 	"code.gitea.io/gitea/models/unittest" | ||||
| 	user_model "code.gitea.io/gitea/models/user" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	api "code.gitea.io/gitea/modules/structs" | ||||
| 	"code.gitea.io/gitea/modules/test" | ||||
| 	"code.gitea.io/gitea/routers" | ||||
| 	"code.gitea.io/gitea/tests" | ||||
|  | @ -42,6 +45,152 @@ func TestRepositoryFlagsUIDisabled(t *testing.T) { | |||
| 	assert.Equal(t, 0, flagsLinkCount) | ||||
| } | ||||
| 
 | ||||
| func TestRepositoryFlagsAPI(t *testing.T) { | ||||
| 	defer tests.PrepareTestEnv(t)() | ||||
| 	defer test.MockVariableValue(&setting.Repository.EnableFlags, true)() | ||||
| 	defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())() | ||||
| 
 | ||||
| 	// ************* | ||||
| 	// ** Helpers ** | ||||
| 	// ************* | ||||
| 
 | ||||
| 	adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{IsAdmin: true}).Name | ||||
| 	normalUserBean := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) | ||||
| 	assert.False(t, normalUserBean.IsAdmin) | ||||
| 	normalUser := normalUserBean.Name | ||||
| 
 | ||||
| 	assertAccess := func(t *testing.T, user, method, uri string, expectedStatus int) { | ||||
| 		session := loginUser(t, user) | ||||
| 		token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeReadAdmin) | ||||
| 
 | ||||
| 		req := NewRequestf(t, method, "/api/v1/repos/user2/repo1/flags%s", uri).AddTokenAuth(token) | ||||
| 		MakeRequest(t, req, expectedStatus) | ||||
| 	} | ||||
| 
 | ||||
| 	// *********** | ||||
| 	// ** Tests ** | ||||
| 	// *********** | ||||
| 
 | ||||
| 	t.Run("API access", func(t *testing.T) { | ||||
| 		t.Run("as admin", func(t *testing.T) { | ||||
| 			defer tests.PrintCurrentTest(t)() | ||||
| 
 | ||||
| 			assertAccess(t, adminUser, "GET", "", http.StatusOK) | ||||
| 		}) | ||||
| 
 | ||||
| 		t.Run("as normal user", func(t *testing.T) { | ||||
| 			defer tests.PrintCurrentTest(t)() | ||||
| 
 | ||||
| 			assertAccess(t, normalUser, "GET", "", http.StatusForbidden) | ||||
| 		}) | ||||
| 	}) | ||||
| 
 | ||||
| 	t.Run("token scopes", func(t *testing.T) { | ||||
| 		defer tests.PrintCurrentTest(t)() | ||||
| 
 | ||||
| 		// Trying to access the API with a token that lacks permissions, will | ||||
| 		// fail, even if the token owner is an instance admin. | ||||
| 		session := loginUser(t, adminUser) | ||||
| 		token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) | ||||
| 
 | ||||
| 		req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/flags").AddTokenAuth(token) | ||||
| 		MakeRequest(t, req, http.StatusForbidden) | ||||
| 	}) | ||||
| 
 | ||||
| 	t.Run("setting.Repository.EnableFlags is respected", func(t *testing.T) { | ||||
| 		defer tests.PrintCurrentTest(t)() | ||||
| 		defer test.MockVariableValue(&setting.Repository.EnableFlags, false)() | ||||
| 		defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())() | ||||
| 
 | ||||
| 		t.Run("as admin", func(t *testing.T) { | ||||
| 			defer tests.PrintCurrentTest(t)() | ||||
| 
 | ||||
| 			assertAccess(t, adminUser, "GET", "", http.StatusNotFound) | ||||
| 		}) | ||||
| 
 | ||||
| 		t.Run("as normal user", func(t *testing.T) { | ||||
| 			defer tests.PrintCurrentTest(t)() | ||||
| 
 | ||||
| 			assertAccess(t, normalUser, "GET", "", http.StatusNotFound) | ||||
| 		}) | ||||
| 	}) | ||||
| 
 | ||||
| 	t.Run("API functionality", func(t *testing.T) { | ||||
| 		defer tests.PrintCurrentTest(t)() | ||||
| 		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) | ||||
| 		defer func() { | ||||
| 			repo.ReplaceAllFlags(db.DefaultContext, []string{}) | ||||
| 		}() | ||||
| 
 | ||||
| 		baseURLFmtStr := "/api/v1/repos/user5/repo4/flags%s" | ||||
| 
 | ||||
| 		session := loginUser(t, adminUser) | ||||
| 		token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteAdmin) | ||||
| 
 | ||||
| 		// Listing flags | ||||
| 		req := NewRequestf(t, "GET", baseURLFmtStr, "").AddTokenAuth(token) | ||||
| 		resp := MakeRequest(t, req, http.StatusOK) | ||||
| 		var flags []string | ||||
| 		DecodeJSON(t, resp, &flags) | ||||
| 		assert.Empty(t, flags) | ||||
| 
 | ||||
| 		// Replacing all tags works, twice in a row | ||||
| 		for i := 0; i < 2; i++ { | ||||
| 			req = NewRequestWithJSON(t, "PUT", fmt.Sprintf(baseURLFmtStr, ""), &api.ReplaceFlagsOption{ | ||||
| 				Flags: []string{"flag-1", "flag-2", "flag-3"}, | ||||
| 			}).AddTokenAuth(token) | ||||
| 			MakeRequest(t, req, http.StatusNoContent) | ||||
| 		} | ||||
| 
 | ||||
| 		// The list now includes all three flags | ||||
| 		req = NewRequestf(t, "GET", baseURLFmtStr, "").AddTokenAuth(token) | ||||
| 		resp = MakeRequest(t, req, http.StatusOK) | ||||
| 		DecodeJSON(t, resp, &flags) | ||||
| 		assert.Len(t, flags, 3) | ||||
| 		for _, flag := range []string{"flag-1", "flag-2", "flag-3"} { | ||||
| 			assert.True(t, slices.Contains(flags, flag)) | ||||
| 		} | ||||
| 
 | ||||
| 		// Check a flag that is on the repo | ||||
| 		req = NewRequestf(t, "GET", baseURLFmtStr, "/flag-1").AddTokenAuth(token) | ||||
| 		MakeRequest(t, req, http.StatusNoContent) | ||||
| 
 | ||||
| 		// Check a flag that isn't on the repo | ||||
| 		req = NewRequestf(t, "GET", baseURLFmtStr, "/no-such-flag").AddTokenAuth(token) | ||||
| 		MakeRequest(t, req, http.StatusNotFound) | ||||
| 
 | ||||
| 		// We can add the same flag twice | ||||
| 		for i := 0; i < 2; i++ { | ||||
| 			req = NewRequestf(t, "PUT", baseURLFmtStr, "/brand-new-flag").AddTokenAuth(token) | ||||
| 			MakeRequest(t, req, http.StatusNoContent) | ||||
| 		} | ||||
| 
 | ||||
| 		// The new flag is there | ||||
| 		req = NewRequestf(t, "GET", baseURLFmtStr, "/brand-new-flag").AddTokenAuth(token) | ||||
| 		MakeRequest(t, req, http.StatusNoContent) | ||||
| 
 | ||||
| 		// We can delete a flag, twice | ||||
| 		for i := 0; i < 2; i++ { | ||||
| 			req = NewRequestf(t, "DELETE", baseURLFmtStr, "/flag-3").AddTokenAuth(token) | ||||
| 			MakeRequest(t, req, http.StatusNoContent) | ||||
| 		} | ||||
| 
 | ||||
| 		// We can delete a flag that wasn't there | ||||
| 		req = NewRequestf(t, "DELETE", baseURLFmtStr, "/no-such-flag").AddTokenAuth(token) | ||||
| 		MakeRequest(t, req, http.StatusNoContent) | ||||
| 
 | ||||
| 		// We can delete all of the flags in one go, too | ||||
| 		req = NewRequestf(t, "DELETE", baseURLFmtStr, "").AddTokenAuth(token) | ||||
| 		MakeRequest(t, req, http.StatusNoContent) | ||||
| 
 | ||||
| 		// ..once all flags are deleted, none are listed, either | ||||
| 		req = NewRequestf(t, "GET", baseURLFmtStr, "").AddTokenAuth(token) | ||||
| 		resp = MakeRequest(t, req, http.StatusOK) | ||||
| 		DecodeJSON(t, resp, &flags) | ||||
| 		assert.Empty(t, flags) | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| func TestRepositoryFlagsUI(t *testing.T) { | ||||
| 	defer tests.PrepareTestEnv(t)() | ||||
| 	defer test.MockVariableValue(&setting.Repository.EnableFlags, true)() | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Gergely Nagy
				Gergely Nagy