Files
gitea/tests/integration/api_repo_edit_test.go
pomidorry 02b1b8a549 Add mirror auth updates to repo edit API and settings (#37468)
## Summary

This PR adds support for updating pull mirror authentication via the
repository edit API and UI.

It introduces new mirror authentication fields in _EditRepoOption_,
updates the API logic to safely handle partial credential updates, and
fixes the web settings flow so that the existing remote username is
preserved when only the password is changed.

### What changed
- added _auth_username_, _auth_password_, and _auth_token_ to
EditRepoOption
- updated the repository edit API to apply mirror auth changes via
_updateMirror_
- preserved existing username/password when only part of the auth
payload is provided
- used oauth2 as the default username when _auth_token_ is provided
- kept stored mirror URLs sanitized in DB and API responses
- updated Swagger schema for the new API fields
- added API integration tests for password-only and token-only updates
- added a web settings test to ensure username preservation on partial
updates

## Why

Some use cases require automated synchronization of pull mirrors, for
example in CI/CD pipelines or integrations with external systems.

At the same time, many organizations enforce security policies that
require periodic token rotation (e.g., monthly).

Currently, mirror credentials can only be updated via the UI, which
makes automation difficult.

## This change enables:

- automated token rotation
- avoiding manual updates via the UI
- easier integration with secret management systems
## Testing
- added integration coverage for mirror auth updates via _PATCH
/api/v1/repos/{owner}/{repo}_
- added web settings tests for password-only updates preserving the
existing username

## Result
Ability to automate auth update
<img width="2400" height="1245" alt="1"
src="https://github.com/user-attachments/assets/67fd5cca-9cb3-4536-b0e2-4d09b8ebff0f"
/>
<img width="962" height="932" alt="image"
src="https://github.com/user-attachments/assets/5d548f5d-aadf-4807-ba52-9c29df93a4cc"
/>

Generative AI was used to help with making this PR.
##
2026-05-01 11:00:03 +00:00

491 lines
22 KiB
Go

// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"context"
"fmt"
"net/http"
"net/url"
"testing"
auth_model "code.gitea.io/gitea/models/auth"
repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/gitrepo"
api "code.gitea.io/gitea/modules/structs"
mirror_service "code.gitea.io/gitea/services/mirror"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// getRepoEditOptionFromRepo gets the options for an existing repo exactly as is
func getRepoEditOptionFromRepo(repo *repo_model.Repository) *api.EditRepoOption {
ctx := context.TODO()
name := repo.Name
description := repo.Description
website := repo.Website
private := repo.IsPrivate
hasIssues := false
var internalTracker *api.InternalTracker
var externalTracker *api.ExternalTracker
if unit, err := repo.GetUnit(ctx, unit_model.TypeIssues); err == nil {
config := unit.IssuesConfig()
hasIssues = true
internalTracker = &api.InternalTracker{
EnableTimeTracker: config.EnableTimetracker,
AllowOnlyContributorsToTrackTime: config.AllowOnlyContributorsToTrackTime,
EnableIssueDependencies: config.EnableDependencies,
}
} else if unit, err := repo.GetUnit(ctx, unit_model.TypeExternalTracker); err == nil {
config := unit.ExternalTrackerConfig()
hasIssues = true
externalTracker = &api.ExternalTracker{
ExternalTrackerURL: config.ExternalTrackerURL,
ExternalTrackerFormat: config.ExternalTrackerFormat,
ExternalTrackerStyle: config.ExternalTrackerStyle,
ExternalTrackerRegexpPattern: config.ExternalTrackerRegexpPattern,
}
}
hasWiki := false
var externalWiki *api.ExternalWiki
if _, err := repo.GetUnit(ctx, unit_model.TypeWiki); err == nil {
hasWiki = true
} else if unit, err := repo.GetUnit(ctx, unit_model.TypeExternalWiki); err == nil {
hasWiki = true
externalWiki = &api.ExternalWiki{
ExternalWikiURL: unit.ExternalWikiConfig().ExternalWikiURL,
}
}
defaultBranch := repo.DefaultBranch
hasPullRequests := false
ignoreWhitespaceConflicts := false
allowMerge := false
allowRebase := false
allowRebaseMerge := false
allowSquash := false
allowFastForwardOnly := false
if unit, err := repo.GetUnit(ctx, unit_model.TypePullRequests); err == nil {
config := unit.PullRequestsConfig()
hasPullRequests = true
ignoreWhitespaceConflicts = config.IgnoreWhitespaceConflicts
allowMerge = config.AllowMerge
allowRebase = config.AllowRebase
allowRebaseMerge = config.AllowRebaseMerge
allowSquash = config.AllowSquash
allowFastForwardOnly = config.AllowFastForwardOnly
}
archived := repo.IsArchived
hasProjects := false
var projectsMode *string
if unit, err := repo.GetUnit(ctx, unit_model.TypeProjects); err == nil && unit != nil {
hasProjects = true
pm := string(unit.ProjectsConfig().ProjectsMode)
projectsMode = &pm
}
hasCode := repo.UnitEnabled(ctx, unit_model.TypeCode)
hasPackages := repo.UnitEnabled(ctx, unit_model.TypePackages)
hasReleases := repo.UnitEnabled(ctx, unit_model.TypeReleases)
hasActions := false
if unit, err := repo.GetUnit(ctx, unit_model.TypeActions); err == nil && unit != nil {
hasActions = true
// TODO: expose action config of repo to api
// actionsConfig = &api.RepoActionsConfig{
// DisabledWorkflows: unit.ActionsConfig().DisabledWorkflows,
// }
}
return &api.EditRepoOption{
Name: &name,
Description: &description,
Website: &website,
Private: &private,
HasIssues: &hasIssues,
HasProjects: &hasProjects,
ProjectsMode: projectsMode,
HasCode: &hasCode,
HasPackages: &hasPackages,
HasReleases: &hasReleases,
HasActions: &hasActions,
ExternalTracker: externalTracker,
InternalTracker: internalTracker,
HasWiki: &hasWiki,
ExternalWiki: externalWiki,
DefaultBranch: &defaultBranch,
HasPullRequests: &hasPullRequests,
IgnoreWhitespaceConflicts: &ignoreWhitespaceConflicts,
AllowMerge: &allowMerge,
AllowRebase: &allowRebase,
AllowRebaseMerge: &allowRebaseMerge,
AllowSquash: &allowSquash,
AllowFastForwardOnly: &allowFastForwardOnly,
Archived: &archived,
}
}
// getNewRepoEditOption Gets the options to change everything about an existing repo by adding to strings or changing
// the boolean
func getNewRepoEditOption(opts *api.EditRepoOption) *api.EditRepoOption {
// Gives a new property to everything
name := *opts.Name + "renamed"
description := "new description"
website := "http://wwww.newwebsite.com"
private := !*opts.Private
hasIssues := !*opts.HasIssues
hasWiki := !*opts.HasWiki
hasProjects := !*opts.HasProjects
hasCode := !*opts.HasCode
hasPackages := !*opts.HasPackages
hasReleases := !*opts.HasReleases
hasActions := !*opts.HasActions
defaultBranch := "master"
hasPullRequests := !*opts.HasPullRequests
ignoreWhitespaceConflicts := !*opts.IgnoreWhitespaceConflicts
allowMerge := !*opts.AllowMerge
allowRebase := !*opts.AllowRebase
allowRebaseMerge := !*opts.AllowRebaseMerge
allowSquash := !*opts.AllowSquash
archived := !*opts.Archived
return &api.EditRepoOption{
Name: &name,
Description: &description,
Website: &website,
Private: &private,
DefaultBranch: &defaultBranch,
HasIssues: &hasIssues,
HasWiki: &hasWiki,
HasProjects: &hasProjects,
HasCode: &hasCode,
HasPackages: &hasPackages,
HasReleases: &hasReleases,
HasActions: &hasActions,
HasPullRequests: &hasPullRequests,
IgnoreWhitespaceConflicts: &ignoreWhitespaceConflicts,
AllowMerge: &allowMerge,
AllowRebase: &allowRebase,
AllowRebaseMerge: &allowRebaseMerge,
AllowSquash: &allowSquash,
Archived: &archived,
}
}
func TestAPIRepoEdit(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
bFalse, bTrue := false, true
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the repo3, is an org
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // owner of neither repos
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // public repo
repo15 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 15}) // empty repo
repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo
// Get user2's token
session := loginUser(t, user2.Name)
token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
// Get user4's token
session = loginUser(t, user4.Name)
token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
// Test editing a repo1 which user2 owns, changing name and many properties
origRepoEditOption := getRepoEditOptionFromRepo(repo1)
assert.True(t, *origRepoEditOption.HasCode)
assert.True(t, *origRepoEditOption.HasPackages)
assert.True(t, *origRepoEditOption.HasProjects)
assert.True(t, *origRepoEditOption.HasReleases)
assert.True(t, *origRepoEditOption.HasActions)
repoEditOption := getNewRepoEditOption(origRepoEditOption)
req := NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo1.Name), &repoEditOption).
AddTokenAuth(token2)
resp := MakeRequest(t, req, http.StatusOK)
repo := DecodeJSON(t, resp, &api.Repository{})
assert.NotNil(t, repo)
// check response
assert.Equal(t, *repoEditOption.Name, repo.Name)
assert.Equal(t, *repoEditOption.Description, repo.Description)
assert.Equal(t, *repoEditOption.Website, repo.Website)
assert.Equal(t, *repoEditOption.Archived, repo.Archived)
// check repo1 from database
repo1edited := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
repo1editedOption := getRepoEditOptionFromRepo(repo1edited)
assert.Equal(t, *repoEditOption.Name, *repo1editedOption.Name)
assert.Equal(t, *repoEditOption.Description, *repo1editedOption.Description)
assert.Equal(t, *repoEditOption.Website, *repo1editedOption.Website)
assert.Equal(t, *repoEditOption.Archived, *repo1editedOption.Archived)
assert.Equal(t, *repoEditOption.Private, *repo1editedOption.Private)
assert.Equal(t, *repoEditOption.HasWiki, *repo1editedOption.HasWiki)
assert.Equal(t, *repoEditOption.HasCode, *repo1editedOption.HasCode)
assert.Equal(t, *repoEditOption.HasPackages, *repo1editedOption.HasPackages)
assert.Equal(t, *repoEditOption.HasProjects, *repo1editedOption.HasProjects)
assert.Equal(t, *repoEditOption.HasReleases, *repo1editedOption.HasReleases)
assert.Equal(t, *repoEditOption.HasActions, *repo1editedOption.HasActions)
// Test editing repo1 to use internal issue and wiki (default)
*repoEditOption.HasIssues = true
repoEditOption.ExternalTracker = nil
repoEditOption.InternalTracker = &api.InternalTracker{
EnableTimeTracker: false,
AllowOnlyContributorsToTrackTime: false,
EnableIssueDependencies: false,
}
*repoEditOption.HasWiki = true
repoEditOption.ExternalWiki = nil
url := fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, *repoEditOption.Name)
req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption).
AddTokenAuth(token2)
resp = MakeRequest(t, req, http.StatusOK)
repo = DecodeJSON(t, resp, &api.Repository{})
assert.NotNil(t, repo)
// check repo1 was written to database
repo1edited = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
repo1editedOption = getRepoEditOptionFromRepo(repo1edited)
assert.True(t, *repo1editedOption.HasIssues)
assert.Nil(t, repo1editedOption.ExternalTracker)
assert.Equal(t, *repo1editedOption.InternalTracker, *repoEditOption.InternalTracker)
assert.True(t, *repo1editedOption.HasWiki)
assert.Nil(t, repo1editedOption.ExternalWiki)
// Test editing repo1 to use external issue and wiki
repoEditOption.ExternalTracker = &api.ExternalTracker{
ExternalTrackerURL: "http://www.somewebsite.com",
ExternalTrackerFormat: "http://www.somewebsite.com/{user}/{repo}?issue={index}",
ExternalTrackerStyle: "alphanumeric",
}
repoEditOption.ExternalWiki = &api.ExternalWiki{
ExternalWikiURL: "http://www.somewebsite.com",
}
req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption).
AddTokenAuth(token2)
resp = MakeRequest(t, req, http.StatusOK)
repo = DecodeJSON(t, resp, &api.Repository{})
assert.NotNil(t, repo)
// check repo1 was written to database
repo1edited = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
repo1editedOption = getRepoEditOptionFromRepo(repo1edited)
assert.True(t, *repo1editedOption.HasIssues)
assert.Equal(t, *repo1editedOption.ExternalTracker, *repoEditOption.ExternalTracker)
assert.True(t, *repo1editedOption.HasWiki)
assert.Equal(t, *repo1editedOption.ExternalWiki, *repoEditOption.ExternalWiki)
assert.False(t, *repo1editedOption.HasCode)
assert.False(t, *repo1editedOption.HasPackages)
assert.False(t, *repo1editedOption.HasProjects)
assert.False(t, *repo1editedOption.HasReleases)
assert.False(t, *repo1editedOption.HasActions)
repoEditOption.ExternalTracker.ExternalTrackerStyle = "regexp"
repoEditOption.ExternalTracker.ExternalTrackerRegexpPattern = `(\d+)`
req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption).
AddTokenAuth(token2)
resp = MakeRequest(t, req, http.StatusOK)
repo = DecodeJSON(t, resp, &api.Repository{})
assert.NotNil(t, repo)
repo1edited = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
repo1editedOption = getRepoEditOptionFromRepo(repo1edited)
assert.True(t, *repo1editedOption.HasIssues)
assert.Equal(t, *repo1editedOption.ExternalTracker, *repoEditOption.ExternalTracker)
// Do some tests with invalid URL for external tracker and wiki
repoEditOption.ExternalTracker.ExternalTrackerURL = "htp://www.somewebsite.com"
req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption).
AddTokenAuth(token2)
MakeRequest(t, req, http.StatusUnprocessableEntity)
repoEditOption.ExternalTracker.ExternalTrackerURL = "http://www.somewebsite.com"
repoEditOption.ExternalTracker.ExternalTrackerFormat = "http://www.somewebsite.com/{user/{repo}?issue={index}"
req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption).
AddTokenAuth(token2)
MakeRequest(t, req, http.StatusUnprocessableEntity)
repoEditOption.ExternalTracker.ExternalTrackerFormat = "http://www.somewebsite.com/{user}/{repo}?issue={index}"
repoEditOption.ExternalWiki.ExternalWikiURL = "htp://www.somewebsite.com"
req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption).
AddTokenAuth(token2)
MakeRequest(t, req, http.StatusUnprocessableEntity)
// Test small repo change through API with issue and wiki option not set; They shall not be touched.
*repoEditOption.Description = "small change"
repoEditOption.HasIssues = nil
repoEditOption.ExternalTracker = nil
repoEditOption.HasWiki = nil
repoEditOption.ExternalWiki = nil
req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption).
AddTokenAuth(token2)
resp = MakeRequest(t, req, http.StatusOK)
repo = DecodeJSON(t, resp, &api.Repository{})
assert.NotNil(t, repo)
// check repo1 was written to database
repo1edited = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
repo1editedOption = getRepoEditOptionFromRepo(repo1edited)
assert.Equal(t, *repo1editedOption.Description, *repoEditOption.Description)
assert.True(t, *repo1editedOption.HasIssues)
assert.NotNil(t, *repo1editedOption.ExternalTracker)
assert.True(t, *repo1editedOption.HasWiki)
assert.NotNil(t, *repo1editedOption.ExternalWiki)
assert.False(t, *repo1editedOption.HasCode)
assert.False(t, *repo1editedOption.HasPackages)
assert.False(t, *repo1editedOption.HasProjects)
assert.False(t, *repo1editedOption.HasReleases)
assert.False(t, *repo1editedOption.HasActions)
// reset repo in db
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, *repoEditOption.Name), &origRepoEditOption).
AddTokenAuth(token2)
_ = MakeRequest(t, req, http.StatusOK)
// Test editing a non-existing repo
name := "repodoesnotexist"
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, name), &api.EditRepoOption{Name: &name}).
AddTokenAuth(token2)
_ = MakeRequest(t, req, http.StatusNotFound)
// Test editing repo16 by user4 who does not have write access
origRepoEditOption = getRepoEditOptionFromRepo(repo16)
repoEditOption = getNewRepoEditOption(origRepoEditOption)
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo16.Name), &repoEditOption).
AddTokenAuth(token4)
MakeRequest(t, req, http.StatusNotFound)
// Tests a repo with no token given so will fail
origRepoEditOption = getRepoEditOptionFromRepo(repo16)
repoEditOption = getNewRepoEditOption(origRepoEditOption)
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo16.Name), &repoEditOption)
_ = MakeRequest(t, req, http.StatusNotFound)
// Test using access token for a private repo that the user of the token owns
origRepoEditOption = getRepoEditOptionFromRepo(repo16)
repoEditOption = getNewRepoEditOption(origRepoEditOption)
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo16.Name), &repoEditOption).
AddTokenAuth(token2)
_ = MakeRequest(t, req, http.StatusOK)
// reset repo in db
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, *repoEditOption.Name), &origRepoEditOption).
AddTokenAuth(token2)
_ = MakeRequest(t, req, http.StatusOK)
// Test making a repo public that is private
repo16 = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16})
assert.True(t, repo16.IsPrivate)
repoEditOption = &api.EditRepoOption{
Private: &bFalse,
}
url = fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo16.Name)
req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption).
AddTokenAuth(token2)
_ = MakeRequest(t, req, http.StatusOK)
repo16 = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16})
assert.False(t, repo16.IsPrivate)
// Make it private again
repoEditOption.Private = &bTrue
req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption).
AddTokenAuth(token2)
_ = MakeRequest(t, req, http.StatusOK)
// Test to change empty repo
assert.False(t, repo15.IsArchived)
url = fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo15.Name)
req = NewRequestWithJSON(t, "PATCH", url, &api.EditRepoOption{
Archived: &bTrue,
}).AddTokenAuth(token2)
_ = MakeRequest(t, req, http.StatusOK)
repo15 = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 15})
assert.True(t, repo15.IsArchived)
req = NewRequestWithJSON(t, "PATCH", url, &api.EditRepoOption{
Archived: &bFalse,
}).AddTokenAuth(token2)
_ = MakeRequest(t, req, http.StatusOK)
// Test using org repo "org3/repo3" where user2 is a collaborator
origRepoEditOption = getRepoEditOptionFromRepo(repo3)
repoEditOption = getNewRepoEditOption(origRepoEditOption)
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s", org3.Name, repo3.Name), &repoEditOption).
AddTokenAuth(token2)
MakeRequest(t, req, http.StatusOK)
// reset repo in db
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s", org3.Name, *repoEditOption.Name), &origRepoEditOption).
AddTokenAuth(token2)
_ = MakeRequest(t, req, http.StatusOK)
// Test using org repo "org3/repo3" with no user token
origRepoEditOption = getRepoEditOptionFromRepo(repo3)
repoEditOption = getNewRepoEditOption(origRepoEditOption)
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s", org3.Name, repo3.Name), &repoEditOption)
MakeRequest(t, req, http.StatusNotFound)
// Test using repo "user2/repo1" where user4 is a NOT collaborator
origRepoEditOption = getRepoEditOptionFromRepo(repo1)
repoEditOption = getNewRepoEditOption(origRepoEditOption)
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo1.Name), &repoEditOption).
AddTokenAuth(token4)
MakeRequest(t, req, http.StatusForbidden)
// Test updating pull request settings without setting has_pull_requests
repo1 = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
url = fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo1.Name)
req = NewRequestWithJSON(t, "PATCH", url, &api.EditRepoOption{
DefaultDeleteBranchAfterMerge: &bTrue,
}).AddTokenAuth(token2)
resp = MakeRequest(t, req, http.StatusOK)
repo = DecodeJSON(t, resp, &api.Repository{})
assert.True(t, repo.DefaultDeleteBranchAfterMerge)
// reset
req = NewRequestWithJSON(t, "PATCH", url, &api.EditRepoOption{
DefaultDeleteBranchAfterMerge: &bFalse,
}).AddTokenAuth(token2)
_ = MakeRequest(t, req, http.StatusOK)
// Test updating mirror password without changing the existing username
ctx := t.Context()
mirrorRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 5})
mirror := unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: 5})
newPassword := "updated-password"
require.NoError(t, mirror_service.UpdateAddress(ctx, mirror, "https://existing-user:existing-password@example.com/user2/repo1.git"))
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s", mirrorRepo.OwnerName, mirrorRepo.Name), &api.EditRepoOption{
MirrorPassword: &newPassword,
}).AddTokenAuth(token2)
MakeRequest(t, req, http.StatusOK)
updatedMirror := unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: mirrorRepo.ID})
assert.Equal(t, "https://example.com/user2/repo1.git", updatedMirror.RemoteAddress)
updatedRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: mirrorRepo.ID})
assert.Equal(t, "https://example.com/user2/repo1.git", updatedRepo.OriginalURL)
remoteURL, err := gitrepo.GitRemoteGetURL(ctx, updatedRepo, updatedMirror.GetRemoteName())
require.NoError(t, err)
require.NotNil(t, remoteURL.User)
assert.Equal(t, "existing-user", remoteURL.User.Username())
password, ok := remoteURL.User.Password()
require.True(t, ok)
assert.Equal(t, newPassword, password)
// Test updating mirror token without guessing a username
token := "mirror-token-value"
require.NoError(t, mirror_service.UpdateAddress(ctx, mirror, "https://example.com/user2/repo1.git"))
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s", mirrorRepo.OwnerName, mirrorRepo.Name), &api.EditRepoOption{
MirrorToken: &token,
}).AddTokenAuth(token2)
MakeRequest(t, req, http.StatusOK)
updatedMirror = unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: mirrorRepo.ID})
assert.Equal(t, "https://example.com/user2/repo1.git", updatedMirror.RemoteAddress)
updatedRepo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: mirrorRepo.ID})
assert.Equal(t, "https://example.com/user2/repo1.git", updatedRepo.OriginalURL)
remoteURL, err = gitrepo.GitRemoteGetURL(ctx, updatedRepo, updatedMirror.GetRemoteName())
require.NoError(t, err)
require.NotNil(t, remoteURL.User)
assert.Empty(t, remoteURL.User.Username())
password, ok = remoteURL.User.Password()
require.True(t, ok)
assert.Equal(t, token, password)
})
}