Skip to content

Commit 9191c1f

Browse files
committed
cleanup: translate Turkish comments to English, remove debug code, add unit tests, fix On-Prem remote scopes URL
1 parent 9fec255 commit 9191c1f

8 files changed

Lines changed: 274 additions & 15 deletions

File tree

backend/plugins/azuredevops_go/api/azuredevops/client.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ func NewClient(con *models.AzuredevopsConnection, apiClient plugin.ApiClient, ur
5656
}
5757

5858
func (c *Client) GetUserProfile() (Profile, errors.Error) {
59-
// On-Premises Azure DevOps Server kurulumlarında global VSSPS profil API'si bulunmaz.
60-
// Bu durumda isteği bypass edip sahte/geçerli bir profil döndürerek kilitlenmeyi önlüyoruz.
59+
// On-Premises Azure DevOps Server does not have the global VSSPS profile API.
60+
// Return a placeholder profile to avoid blocking the connection test flow.
6161
if c.connection != nil && c.connection.Endpoint != "" {
6262
return Profile{
6363
DisplayName: "On-Premises User",
@@ -93,7 +93,7 @@ func (c *Client) GetUserProfile() (Profile, errors.Error) {
9393
}
9494

9595
func (c *Client) GetUserAccounts(memberId string) (AccountResponse, errors.Error) {
96-
// On-Premises kurulumlarda global accounts API'si olmadığı için boş liste dönüyoruz.
96+
// On-Premises installations do not have the global accounts API, return empty list.
9797
if c.connection != nil && c.connection.Endpoint != "" {
9898
return AccountResponse{}, nil
9999
}
@@ -163,8 +163,6 @@ func (c *Client) GetProjects(args GetProjectsArgs) ([]Project, errors.Error) {
163163
query.Set("$top", strconv.Itoa(top))
164164
query.Set("$skip", strconv.Itoa(skip))
165165

166-
//path := fmt.Sprintf("%s/_apis/projects", args.OrgId)
167-
// GÜNCELLENMİŞ KOD:
168166
path := "_apis/projects"
169167
if args.OrgId != "" {
170168
path = fmt.Sprintf("%s/_apis/projects", args.OrgId)
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
Licensed to the Apache Software Foundation (ASF) under one or more
3+
contributor license agreements. See the NOTICE file distributed with
4+
this work for additional information regarding copyright ownership.
5+
The ASF licenses this file to You under the Apache License, Version 2.0
6+
(the "License"); you may not use this file except in compliance with
7+
the License. You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
package azuredevops
19+
20+
import (
21+
"github.com/apache/incubator-devlake/helpers/pluginhelper/api"
22+
"github.com/apache/incubator-devlake/plugins/azuredevops_go/models"
23+
"testing"
24+
)
25+
26+
func TestGetUserProfile_OnPremises(t *testing.T) {
27+
conn := &models.AzuredevopsConnection{
28+
BaseConnection: api.BaseConnection{},
29+
AzuredevopsConn: models.AzuredevopsConn{
30+
AzuredevopsAccessToken: models.AzuredevopsAccessToken{
31+
Token: "test-token",
32+
},
33+
Endpoint: "https://tfs.company.local/DefaultCollection/",
34+
},
35+
}
36+
37+
client := NewClient(conn, nil, "https://tfs.company.local/DefaultCollection/")
38+
profile, err := client.GetUserProfile()
39+
if err != nil {
40+
t.Fatalf("GetUserProfile() returned unexpected error for On-Premises: %v", err)
41+
}
42+
if profile.DisplayName != "On-Premises User" {
43+
t.Errorf("GetUserProfile() DisplayName = %q; want %q", profile.DisplayName, "On-Premises User")
44+
}
45+
}
46+
47+
func TestGetUserAccounts_OnPremises(t *testing.T) {
48+
conn := &models.AzuredevopsConnection{
49+
BaseConnection: api.BaseConnection{},
50+
AzuredevopsConn: models.AzuredevopsConn{
51+
AzuredevopsAccessToken: models.AzuredevopsAccessToken{
52+
Token: "test-token",
53+
},
54+
Endpoint: "https://tfs.company.local/DefaultCollection/",
55+
},
56+
}
57+
58+
client := NewClient(conn, nil, "https://tfs.company.local/DefaultCollection/")
59+
accounts, err := client.GetUserAccounts("test-member-id")
60+
if err != nil {
61+
t.Fatalf("GetUserAccounts() returned unexpected error for On-Premises: %v", err)
62+
}
63+
if len(accounts) != 0 {
64+
t.Errorf("GetUserAccounts() returned %d accounts; want 0 for On-Premises", len(accounts))
65+
}
66+
}
67+
68+
func TestGetUserProfile_Cloud(t *testing.T) {
69+
// For Cloud connections (no Endpoint set), GetUserProfile should NOT return
70+
// the placeholder profile. It should attempt to call the VSSPS API.
71+
conn := &models.AzuredevopsConnection{
72+
BaseConnection: api.BaseConnection{},
73+
AzuredevopsConn: models.AzuredevopsConn{
74+
AzuredevopsAccessToken: models.AzuredevopsAccessToken{
75+
Token: "test-token",
76+
},
77+
// Endpoint is empty = Cloud mode
78+
},
79+
}
80+
81+
// Without a mock server, this will fail with a connection error,
82+
// which is expected - the important thing is it does NOT bypass to placeholder
83+
client := NewClient(conn, nil, "http://localhost:0")
84+
_, err := client.GetUserProfile()
85+
if err == nil {
86+
t.Error("GetUserProfile() for Cloud should attempt real API call and fail without a server")
87+
}
88+
}
89+
90+
func TestGetUserAccounts_Cloud(t *testing.T) {
91+
conn := &models.AzuredevopsConnection{
92+
BaseConnection: api.BaseConnection{},
93+
AzuredevopsConn: models.AzuredevopsConn{
94+
AzuredevopsAccessToken: models.AzuredevopsAccessToken{
95+
Token: "test-token",
96+
},
97+
// Endpoint is empty = Cloud mode
98+
},
99+
}
100+
101+
// Without a mock server, this will fail with a connection error
102+
client := NewClient(conn, nil, "http://localhost:0")
103+
_, err := client.GetUserAccounts("test-member-id")
104+
if err == nil {
105+
t.Error("GetUserAccounts() for Cloud should attempt real API call and fail without a server")
106+
}
107+
}

backend/plugins/azuredevops_go/api/remote_helper.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,12 @@ func listAzuredevopsRemoteScopes(
5959
) {
6060

6161
org := connection.Organization
62-
vsc := azuredevops.NewClient(connection, apiClient, "https://app.vssps.visualstudio.com")
62+
// Use the connection endpoint for On-Premises, or VSSPS for Cloud
63+
clientUrl := "https://app.vssps.visualstudio.com"
64+
if connection.Endpoint != "" {
65+
clientUrl = connection.GetEndpoint()
66+
}
67+
vsc := azuredevops.NewClient(connection, apiClient, clientUrl)
6368

6469
if groupId == "" {
6570
return listAzuredevopsProjects(connection, vsc, page, org)

backend/plugins/azuredevops_go/models/connection.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,12 @@ func (at *AzuredevopsAccessToken) SetupAuthentication(req *http.Request) errors.
5151

5252
// AzuredevopsConn holds the essential information to connect to the Azure DevOps API
5353
type AzuredevopsConn struct {
54-
//api.RestConnection `mapstructure:",squash"`
5554
AzuredevopsAccessToken `mapstructure:",squash"`
5655
Organization string `json:"organization"`
5756
Username string `mapstructure:"username" json:"username"`
5857
Proxy string `mapstructure:"proxy" json:"proxy"`
5958

60-
//endpoint yeni ekledik
59+
// Endpoint is the base URL for On-Premises Azure DevOps Server (optional, empty for Cloud)
6160
Endpoint string `mapstructure:"endpoint" json:"endpoint" validate:"omitempty,url"`
6261
}
6362

@@ -76,7 +75,7 @@ func (conn *AzuredevopsConn) SetupAuthentication(req *http.Request) errors.Error
7675
}
7776

7877
func (conn *AzuredevopsConn) GetEndpoint() string {
79-
// Eğer kullanıcı Endpoint alanını doldurduysa onu dön, boş bıraktıysa varsayılan Cloud URL'ini dön
78+
// Returns the On-Premises endpoint if configured, otherwise defaults to Azure DevOps Cloud URL
8079
if conn.Endpoint != "" {
8180
ep := conn.Endpoint
8281
if !strings.HasSuffix(ep, "/") {
@@ -104,7 +103,7 @@ type AzuredevopsConnection struct {
104103
}
105104

106105
func (c AzuredevopsConnection) GetEndpoint() string {
107-
// AzuredevopsConn içindeki GetEndpoint mantığıyla aynı dinamik yapıyı çağır
106+
// Delegates to the embedded AzuredevopsConn.GetEndpoint()
108107
return c.AzuredevopsConn.GetEndpoint()
109108
}
110109
func (c AzuredevopsConnection) GetProxy() string {
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/*
2+
Licensed to the Apache Software Foundation (ASF) under one or more
3+
contributor license agreements. See the NOTICE file distributed with
4+
this work for additional information regarding copyright ownership.
5+
The ASF licenses this file to You under the Apache License, Version 2.0
6+
(the "License"); you may not use this file except in compliance with
7+
the License. You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
package models
19+
20+
import (
21+
"net/http"
22+
"testing"
23+
)
24+
25+
func TestGetEndpoint_CloudDefault(t *testing.T) {
26+
conn := &AzuredevopsConn{}
27+
endpoint := conn.GetEndpoint()
28+
if endpoint != "https://dev.azure.com/" {
29+
t.Errorf("GetEndpoint() = %q; want %q", endpoint, "https://dev.azure.com/")
30+
}
31+
}
32+
33+
func TestGetEndpoint_OnPremises(t *testing.T) {
34+
conn := &AzuredevopsConn{
35+
Endpoint: "https://tfs.company.local/DefaultCollection/",
36+
}
37+
endpoint := conn.GetEndpoint()
38+
if endpoint != "https://tfs.company.local/DefaultCollection/" {
39+
t.Errorf("GetEndpoint() = %q; want %q", endpoint, "https://tfs.company.local/DefaultCollection/")
40+
}
41+
}
42+
43+
func TestGetEndpoint_TrailingSlash(t *testing.T) {
44+
conn := &AzuredevopsConn{
45+
Endpoint: "https://tfs.company.local/DefaultCollection",
46+
}
47+
endpoint := conn.GetEndpoint()
48+
expected := "https://tfs.company.local/DefaultCollection/"
49+
if endpoint != expected {
50+
t.Errorf("GetEndpoint() = %q; want %q (trailing slash should be appended)", endpoint, expected)
51+
}
52+
}
53+
54+
func TestGetEndpoint_ConnectionWrapper(t *testing.T) {
55+
conn := AzuredevopsConnection{
56+
AzuredevopsConn: AzuredevopsConn{
57+
Endpoint: "https://tfs.company.local/DefaultCollection/",
58+
},
59+
}
60+
endpoint := conn.GetEndpoint()
61+
if endpoint != "https://tfs.company.local/DefaultCollection/" {
62+
t.Errorf("AzuredevopsConnection.GetEndpoint() = %q; want %q", endpoint, "https://tfs.company.local/DefaultCollection/")
63+
}
64+
}
65+
66+
func TestGetEndpoint_ConnectionWrapperCloud(t *testing.T) {
67+
conn := AzuredevopsConnection{}
68+
endpoint := conn.GetEndpoint()
69+
if endpoint != "https://dev.azure.com/" {
70+
t.Errorf("AzuredevopsConnection.GetEndpoint() = %q; want %q", endpoint, "https://dev.azure.com/")
71+
}
72+
}
73+
74+
func TestSetupAuthentication_Cloud(t *testing.T) {
75+
conn := &AzuredevopsConn{
76+
AzuredevopsAccessToken: AzuredevopsAccessToken{
77+
Token: "test-pat-token",
78+
},
79+
Username: "testuser",
80+
}
81+
82+
req, _ := http.NewRequest("GET", "https://dev.azure.com/org/_apis/projects", nil)
83+
err := conn.SetupAuthentication(req)
84+
if err != nil {
85+
t.Fatalf("SetupAuthentication() returned error: %v", err)
86+
}
87+
88+
user, pass, ok := req.BasicAuth()
89+
if !ok {
90+
t.Fatal("SetupAuthentication() did not set Basic Auth header")
91+
}
92+
if user != "testuser" {
93+
t.Errorf("Basic Auth username = %q; want %q", user, "testuser")
94+
}
95+
if pass != "test-pat-token" {
96+
t.Errorf("Basic Auth password = %q; want %q", pass, "test-pat-token")
97+
}
98+
}
99+
100+
func TestSetupAuthentication_OnPremises(t *testing.T) {
101+
conn := &AzuredevopsConn{
102+
AzuredevopsAccessToken: AzuredevopsAccessToken{
103+
Token: "on-prem-pat-token",
104+
},
105+
Username: "domain\\admin",
106+
Endpoint: "https://tfs.company.local/DefaultCollection/",
107+
}
108+
109+
req, _ := http.NewRequest("GET", "https://tfs.company.local/DefaultCollection/_apis/projects", nil)
110+
err := conn.SetupAuthentication(req)
111+
if err != nil {
112+
t.Fatalf("SetupAuthentication() returned error: %v", err)
113+
}
114+
115+
user, pass, ok := req.BasicAuth()
116+
if !ok {
117+
t.Fatal("SetupAuthentication() did not set Basic Auth header")
118+
}
119+
// On-Premises: username should be empty (PAT auth with empty username)
120+
if user != "" {
121+
t.Errorf("On-Premises Basic Auth username = %q; want empty string", user)
122+
}
123+
if pass != "on-prem-pat-token" {
124+
t.Errorf("On-Premises Basic Auth password = %q; want %q", pass, "on-prem-pat-token")
125+
}
126+
}
127+
128+
func TestSanitize(t *testing.T) {
129+
conn := &AzuredevopsConn{
130+
AzuredevopsAccessToken: AzuredevopsAccessToken{
131+
Token: "secret-token",
132+
},
133+
Username: "testuser",
134+
Endpoint: "https://tfs.company.local/",
135+
}
136+
137+
sanitized := conn.Sanitize()
138+
if sanitized.Endpoint != "https://tfs.company.local/" {
139+
t.Errorf("Sanitize() should preserve Endpoint, got %q", sanitized.Endpoint)
140+
}
141+
if sanitized.Username != "testuser" {
142+
t.Errorf("Sanitize() should preserve Username, got %q", sanitized.Username)
143+
}
144+
}

backend/plugins/azuredevops_go/models/migrationscripts/register.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func All() []plugin.MigrationScript {
2626
return []plugin.MigrationScript{
2727
new(addInitTables),
2828
new(extendRepoTable),
29-
new(addEndpointToAzuredevops), //new raw
29+
new(addEndpointToAzuredevops),
3030
new(addUsernameToAzuredevops),
3131
}
3232
}

config-ui/src/plugins/register/azure/config.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,13 @@ export const AzureGoConfig: IPluginConfig = {
9999
},
100100
{
101101
key: 'token',
102-
label: 'Personal Access Token / Password',
102+
label: 'Personal Access Token',
103+
subLabel: (
104+
<span>
105+
For Azure DevOps Cloud, use a Personal Access Token (PAT). For On-Premises Server, use a PAT or your domain
106+
password.
107+
</span>
108+
),
103109
},
104110
({ initialValues, values, setValues }: any) => (
105111
<ConnectionOrganization

config-ui/src/plugins/register/azure/connection-fields/base-url.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,22 @@
1818

1919
import React, { useState } from 'react';
2020
import { Input, Radio } from 'antd';
21-
import { Block } from '@/components'; // Projedeki mevcut Block bileşeni yoluna göre gerekirse import'u ayarla
21+
import { Block } from '@/components';
2222

2323
interface Props {
2424
value?: string;
2525
onChange?: (value: string) => void;
2626
}
2727

2828
export const BaseURL = ({ value, onChange }: Props) => {
29-
// Eğer dışarıdan gelen bir değer varsa 'server', yoksa varsayılan 'cloud' seç
29+
// Default to 'server' mode if an endpoint value already exists, 'cloud' otherwise
3030
const [version, setVersion] = useState<'cloud' | 'server'>(value ? 'server' : 'cloud');
3131

3232
const handleVersionChange = (e: any) => {
3333
const selectedVersion = e.target.value;
3434
setVersion(selectedVersion);
3535
if (selectedVersion === 'cloud') {
36-
onChange?.(''); // Cloud seçilirse endpoint sıfırlanır
36+
onChange?.(''); // Reset endpoint when switching to Cloud mode
3737
}
3838
};
3939

0 commit comments

Comments
 (0)