Repository: kedacore/keda

Issue:
[Azure Pipeline Scaler] Scaler logic is limited to 200 jobs, even with the $top query parameter
### Report

As stated in the documentation the Azure Pipeline scaler uses an undocumented API (/jobrequests). This API accepts a `$top` query parameter which, in theory, returns the response payload paginated limited to the top value. The current default value is 250, but the API always returns only 200 jobs. In those jobs returned, there are jobs already running and jobs not assigned, and sometimes even jobs that already ran successfully. As KEDA scale only based on the pending jobs (not assigned yet to an agent) it limits the execution to 200 pods maximum which increases a lot the queue time of the pool, as it will have more and more pending jobs.










### Expected Behavior

In my case, I use Karpenter to scale the cluster and KEDA to scale the agents pods, I do not want to have limits, I want the cluster to scale the pods as much as it needs, but what I observed is that KEDA limits the scale.

I was expecting that I could scale as much as I need it, without limits, or at least I could increase the limit to more than 200 running jobs.

### Actual Behavior

Now KEDA limits the scale to 200 pods.

### Steps to Reproduce the Problem

To simulate this you can create a scheduled pipeline triggering jobs by 1/1min with different sleep times, easily will get to the limit of 200 jobs. The API always returns 200 even with a top parameter greater the that.

There is another parameter this API accepts which is the `completedRequestCount` this parameter seems to return only jobs that didn't finish yet (running + pending). So, my suggestion is to get rid of the `top` parameter and use the `completedRequestCount`. Thus, it will return all jobs not finished and would scale as needed, not limiting to 200. Other option would be to follow the pagination until the limit set by us in scaler configuration (`jobsToFetch`), but this approach also returns jobs already finished which would be not necessary.

I  did those python script just to investigate this:

Option 1 (using `completedRequestCount`):

```python
import requests
from requests.auth import HTTPBasicAuth
import json
import os

# === CONFIGURATION ===
organization = "my-org"
pool_name = "poc-eks"  # Replace with your pool name
personal_access_token = os.getenv('ADO_PAT')

if not personal_access_token:
    raise Exception("Environment variable ADO_PAT is not set.")

# === Authentication ===
auth = HTTPBasicAuth('', personal_access_token)

# === Step 1: Get the Pool ID by Pool Name ===
pools_url = f"https://dev.azure.com/{organization}/_apis/distributedtask/pools?api-version=7.1-preview.1"

pools_response = requests.get(pools_url, auth=auth)

if pools_response.status_code != 200:
    print(f"Failed to retrieve pools. Status: {pools_response.status_code}")
    print(pools_response.text)
    exit(1)

pools_data = pools_response.json()
pools = pools_data.get('value', [])

matching_pools = [p for p in pools if p['name'].lower() == pool_name.lower()]

if not matching_pools:
    print(f"No pool found with name '{pool_name}'.")
    exit(1)

pool_id = matching_pools[0]['id']
print(f"Found pool '{pool_name}' with ID: {pool_id}")

# === Step 2: Get Job Requests for the Pool ===
jobs_url = (
    f"https://dev.azure.com/{organization}/_apis/distributedtask/pools/{pool_id}/jobrequests"
    f"?completedRequestCount=0"
)

jobs_response = requests.get(jobs_url, auth=auth)

if jobs_response.status_code != 200:
    print(f"Failed to retrieve job requests. Status: {jobs_response.status_code}")
    print(jobs_response.text)
    exit(1)

jobs_data = jobs_response.json()
job_requests = jobs_data.get('value', [])

# === Step 3: Count Job States ===
running_jobs = [job for job in job_requests if job.get('result') == None and job.get('assignTime') != None]
pending_jobs = [job for job in job_requests if job.get('assignTime') == None]
succeeded_jobs = [job for job in job_requests if job.get('result') == 'succeeded']

print(f"\n=== Job Status in Pool '{pool_name}' ===")
print(f"Total Jobs Retrieved: {len(job_requests)}")
print(f"Running Jobs   : {len(running_jobs)}")
print(f"Pending Jobs   : {len(pending_jobs)}")
print(f"Succeeded Jobs : {len(succeeded_jobs)}")

# === Optional: Print details of jobs ===
# for job in pending_jobs:
#     print(f"Pending Job: {job.get('requestId')} -> {job.get('owner')}")
```

Option 2 (following the pagination):

```python
import requests
from requests.auth import HTTPBasicAuth
import json
import os

# === CONFIGURATION ===
organization = "my-org"
pool_name = "poc-eks"  # Replace with your pool name
personal_access_token = os.getenv('ADO_PAT')

if not personal_access_token:
    raise Exception("Environment variable ADO_PAT is not set.")

# === Authentication ===
auth = HTTPBasicAuth('', personal_access_token)

# === Step 1: Get the Pool ID by Pool Name ===
pools_url = f"https://dev.azure.com/{organization}/_apis/distributedtask/pools?api-version=7.1-preview.1"

pools_response = requests.get(pools_url, auth=auth)

if pools_response.status_code != 200:
    print(f"Failed to retrieve pools. Status: {pools_response.status_code}")
    print(pools_response.text)
    exit(1)

pools_data = pools_response.json()
pools = pools_data.get('value', [])

matching_pools = [p for p in pools if p['name'].lower() == pool_name.lower()]

if not matching_pools:
    print(f"No pool found with name '{pool_name}'.")
    exit(1)

pool_id = matching_pools[0]['id']
print(f"Found pool '{pool_name}' with ID: {pool_id}")

# === Step 2: Get Job Requests for the Pool ===
base_url = f"https://dev.azure.com/{organization}/_apis/distributedtask/pools/{pool_id}/jobrequests"
continuation_token = None
count = 0
jobs_to_fetch = 2000
all_jobs = []

while True:
    params = {
        '$top': jobs_to_fetch,
        # 'completedRequestCount': 0
    }
    if continuation_token:
        params['continuationToken'] = continuation_token


    print(f"\nFetching jobs with params: {params}")
    response = requests.get(base_url, auth=auth, params=params)

    if response.status_code != 200:
        print(f"Request failed: {response.status_code}")
        print(response.text)
        break

    data = response.json()
    jobs = data.get('value', [])
    count += len(jobs)
    all_jobs.extend(jobs)

    print(f"Retrieved {len(jobs)} jobs (Total so far: {count})")

    # Check for continuation token
    continuation_token = response.headers.get('x-ms-continuationtoken')

    # print response headers for debugging
    print("\n=== Response Headers ===")
    print(f"{response.headers.get('x-ms-continuationtoken')}")

    if not continuation_token:
        print("\nNo continuation token. Pagination complete.")
        break
    elif count >= jobs_to_fetch:
        print(f"Reached the limit of {jobs_to_fetch} jobs. Stopping further requests.")
        break
    else:
        print(f"Found continuation token: {continuation_token} — fetching next page...")

print(f"\n=== Total jobs retrieved: {count} ===")

running_jobs = [job for job in all_jobs if job.get('result') == None and job.get('assignTime') != None]
pending_jobs = [job for job in all_jobs if job.get('assignTime') == None]
succeeded_jobs = [job for job in all_jobs if job.get('result') == 'succeeded']

print(f"\n=== Job Status in Pool '{pool_name}' ===")
print(f"Total Jobs Retrieved: {len(all_jobs)}")
print(f"Running Jobs   : {len(running_jobs)}")
print(f"Pending Jobs   : {len(pending_jobs)}")
print(f"Succeeded Jobs : {len(succeeded_jobs)}")
```





### Logs from KEDA operator

_No response_

### KEDA Version

2.17.0

### Kubernetes Version

1.31

### Platform

Amazon Web Services

### Scaler Details

Azure Pipelines

### Anything else?

_No response_

Discussion:
@andrecastro: This may be related to the unsolved issue #6148 @wbpascal: +1 for this. We are running into the same issue on our end. Some of our agent pools seem to return thousands of jobs through the private jobrequest API. Because KEDA specifies the `$top` query parameter, only the "first" 200 entries are returned by the API, as you have already described. However, it appears that these entries are not really sorted by any means, and I could not figure out a way to get them to be sorted. The typical `$orderBy` query parameter for OData APIs seems not to work as well. Because the results are returned in a random order from the private API, KEDA takes a very long time until it discovers that there is a new job to scale on. Sometimes it does not happen at all, even.

Therefore, I would also like if we could define the `completedRequestCount` parameter in the request, as you have already described.


Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8d184b6fa..117e91e25 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -78,7 +78,9 @@ To learn more about active deprecations, we recommend checking [GitHub Discussio
 - **General**: Allow excluding labels from being propagated from ScaledObject and ScaledJob to generated HPA and Job objects ([#6849](https://github.com/kedacore/keda/issues/6849))
 - **General**: chore: only add webhook DNS names when webhook patching is enabled ([#7002](https://github.com/kedacore/keda/issues/7002))
 - **General**: Improve Events emitted from ScaledObject controller ([#6802](https://github.com/kedacore/keda/issues/6802))
+- **Azure Pipelines Scaler**: Add new `fetchUnfinishedJobsOnly` property to fetch only unfinished pipeline jobs for a pool ([#6819](https://github.com/kedacore/keda/issues/6819))
 - **Datadog Scaler**: Add a specific timeout configuration parameter for the Datadog trigger ([#6999](https://github.com/kedacore/keda/pull/6999))
+- **Datadog Scaler**: Fix bug with datadogNamespace config ([#6828](https://github.com/kedacore/keda/pull/6828))
 - **Datadog Scaler**: Improve Datadog scaler error messages ([#6999](https://github.com/kedacore/keda/pull/6999))
 - **Metrics API**: Support multiple auth methods simultaneously in Metrics API scaler ([#6642](https://github.com/kedacore/keda/issues/6642))
 - **Temporal Scaler**: Support custom tlsServerName ([#6820](https://github.com/kedacore/keda/pull/6820))
diff --git a/pkg/scalers/azure_pipelines_scaler.go b/pkg/scalers/azure_pipelines_scaler.go
index a620c5c55..eaf6697fa 100644
--- a/pkg/scalers/azure_pipelines_scaler.go
+++ b/pkg/scalers/azure_pipelines_scaler.go
@@ -144,6 +144,7 @@ type azurePipelinesMetadata struct {
 	TargetPipelinesQueueLength           int64  `keda:"name=targetPipelinesQueueLength,          order=triggerMetadata, default=1, optional"`
 	ActivationTargetPipelinesQueueLength int64  `keda:"name=activationTargetPipelinesQueueLength,          order=triggerMetadata, default=0, optional"`
 	JobsToFetch                          int64  `keda:"name=jobsToFetch,          order=triggerMetadata, default=250, optional"`
+	FetchUnfinishedJobsOnly              bool   `keda:"name=fetchUnfinishedJobsOnly,          order=triggerMetadata, default=false, optional"`
 	triggerIndex                         int
 	RequireAllDemands                    bool `keda:"name=requireAllDemands,          order=triggerMetadata, default=false, optional"`
 	RequireAllDemandsAndIgnoreOthers     bool `keda:"name=requireAllDemandsAndIgnoreOthers,          order=triggerMetadata, default=false, optional"`
@@ -213,6 +214,13 @@ func getAuthMethod(logger logr.Logger, config *scalersconfig.ScalerConfig) (stri
 }
 
 func parseAzurePipelinesMetadata(ctx context.Context, logger logr.Logger, config *scalersconfig.ScalerConfig, httpClient *http.Client) (*azurePipelinesMetadata, kedav1alpha1.AuthPodIdentity, error) {
+	if config.TriggerMetadata["jobsToFetch"] != "" && config.TriggerMetadata["fetchUnfinishedJobsOnly"] != "" {
+		return nil, kedav1alpha1.AuthPodIdentity{}, fmt.Errorf("cannot specify both jobsToFetch and fetchUnfinishedJobsOnly at the same time")
+	}
+	if config.TriggerMetadata["jobsToFetch"] != "" && config.TriggerMetadata["parent"] != "" {
+		return nil, kedav1alpha1.AuthPodIdentity{}, fmt.Errorf("cannot specify both jobsToFetch and parent at the same time")
+	}
+
 	meta := &azurePipelinesMetadata{}
 	if err := config.TypedConfig(meta); err != nil {
 		return nil, kedav1alpha1.AuthPodIdentity{}, fmt.Errorf("error parsing azure pipeline metadata: %w", err)
@@ -368,14 +376,29 @@ func getAzurePipelineRequest(ctx context.Context, logger logr.Logger, urlString
 	return b, nil
 }
 
-func (s *azurePipelinesScaler) GetAzurePipelinesQueueLength(ctx context.Context) (int64, error) {
-	// HotFix Issue (#4387), $top changes the format of the returned JSON
+func (s *azurePipelinesScaler) GetAzurePipelinesQueueURL() (string, error) {
 	var urlString string
-	if s.metadata.Parent != "" {
+	if s.metadata.FetchUnfinishedJobsOnly {
+		// Because completedRequestCount=0 does not change the format of the returned JSON like $top does, we can also use this URL when a `Parent` agent is given
+		// However, in order to not force existing users of the `Parent` property to always send the `completedRequestCount=0` query parameter (which may change over time
+		//  due it it being an undocumented API), we only send the query parameter when `FetchUnfinishedJobsOnly` is explicitly set to true.
+		urlString = fmt.Sprintf("%s/_apis/distributedtask/pools/%d/jobrequests?completedRequestCount=0", s.metadata.OrganizationURL, s.metadata.PoolID)
+	} else if s.metadata.Parent != "" {
+		// HotFix Issue (#4387), $top changes the format of the returned JSON
 		urlString = fmt.Sprintf("%s/_apis/distributedtask/pools/%d/jobrequests", s.metadata.OrganizationURL, s.metadata.PoolID)
 	} else {
 		urlString = fmt.Sprintf("%s/_apis/distributedtask/pools/%d/jobrequests?$top=%d", s.metadata.OrganizationURL, s.metadata.PoolID, s.metadata.JobsToFetch)
 	}
+
+	return urlString, nil
+}
+
+func (s *azurePipelinesScaler) GetAzurePipelinesQueueLength(ctx context.Context) (int64, error) {
+	urlString, err := s.GetAzurePipelinesQueueURL()
+	if err != nil {
+		return -1, err
+	}
+
 	body, err := getAzurePipelineRequest(ctx, s.logger, urlString, s.metadata, s.podIdentity, s.httpClient)
 	if err != nil {
 		return -1, err



Test patch:
diff --git a/pkg/scalers/azure_pipelines_scaler_test.go b/pkg/scalers/azure_pipelines_scaler_test.go
index 609759adf..4171db041 100644
--- a/pkg/scalers/azure_pipelines_scaler_test.go
+++ b/pkg/scalers/azure_pipelines_scaler_test.go
@@ -2,6 +2,7 @@ package scalers
 
 import (
 	"context"
+	"fmt"
 	"net/http"
 	"net/http/httptest"
 	"strings"
@@ -46,6 +47,14 @@ var testAzurePipelinesMetadata = []parseAzurePipelinesMetadataTestData{
 	{"all properly formed", map[string]string{"organizationURLFromEnv": "AZP_URL", "personalAccessTokenFromEnv": "AZP_TOKEN", "poolID": "1", "targetPipelinesQueueLength": "1", "activationTargetPipelinesQueueLength": "A"}, true, testAzurePipelinesResolvedEnv, map[string]string{}},
 	// jobsToFetch malformed
 	{"jobsToFetch malformed", map[string]string{"organizationURLFromEnv": "AZP_URL", "personalAccessTokenFromEnv": "AZP_TOKEN", "poolID": "1", "targetPipelinesQueueLength": "1", "jobsToFetch": "test"}, true, testAzurePipelinesResolvedEnv, map[string]string{}},
+	// fetchUnfinishedJobsOnly malformed
+	{"fetchUnfinishedJobsOnly malformed", map[string]string{"organizationURLFromEnv": "AZP_URL", "personalAccessTokenFromEnv": "AZP_TOKEN", "poolID": "1", "targetPipelinesQueueLength": "1", "fetchUnfinishedJobsOnly": "test"}, true, testAzurePipelinesResolvedEnv, map[string]string{}},
+	// jobsToFetch and fetchUnfinishedJobsOnly given
+	{"jobsToFetch and fetchUnfinishedJobsOnly given", map[string]string{"organizationURLFromEnv": "AZP_URL", "personalAccessTokenFromEnv": "AZP_TOKEN", "poolID": "1", "targetPipelinesQueueLength": "1", "fetchUnfinishedJobsOnly": "true", "jobsToFetch": "42"}, true, testAzurePipelinesResolvedEnv, map[string]string{}},
+	// jobsToFetch and parent given
+	{"jobsToFetch and parent given", map[string]string{"organizationURLFromEnv": "AZP_URL", "personalAccessTokenFromEnv": "AZP_TOKEN", "poolID": "1", "targetPipelinesQueueLength": "1", "parent": "test-agent", "jobsToFetch": "42"}, true, testAzurePipelinesResolvedEnv, map[string]string{}},
+	// parent and fetchUnfinishedJobsOnly given
+	{"parent and fetchUnfinishedJobsOnly given", map[string]string{"organizationURLFromEnv": "AZP_URL", "personalAccessTokenFromEnv": "AZP_TOKEN", "poolID": "1", "targetPipelinesQueueLength": "1", "fetchUnfinishedJobsOnly": "true", "parent": "test-agent"}, false, testAzurePipelinesResolvedEnv, map[string]string{}},
 }
 
 var testJobRequestResponse = `{"count":2,"value":[{"requestId":890659,"queueTime":"2022-09-28T11:19:49.89Z","assignTime":"2022-09-28T11:20:29.5033333Z","receiveTime":"2022-09-28T11:20:32.0530499Z","lockedUntil":"2022-09-28T11:30:32.07Z","serviceOwner":"xxx","hostId":"xxx","scopeId":"xxx","planType":"Build","planId":"xxx","jobId":"xxx","demands":["kubectl","Agent.Version -gtVersion 2.182.1"],"reservedAgent":{"_links":{"self":{"href":"https://dev.azure.com/FOO/_apis/distributedtask/pools/44/agents/11735"},"web":{"href":"https://dev.azure.com/FOO/_settings/agentpools?view=jobs&poolId=44&agentId=11735"}},"id":11735,"name":"kube-scaledjob-5nlph-kzpgf","version":"2.210.1","osDescription":"Linux 5.4.0-1089-azure #94~18.04.1-Ubuntu SMP Fri Aug 5 12:34:50 UTC 2022","enabled":true,"status":"online","provisioningState":"Provisioned","accessPoint":"CodexAccessMapping"},"definition":{"_links":{"web":{"href":"https://dev.azure.com/FOO/1858395a-257e-4efd-bbc5-eb618128452b/_build/definition?definitionId=4869"},"self":{"href":"https://dev.azure.com/FOO/1858395a-257e-4efd-bbc5-eb618128452b/_apis/build/Definitions/4869"}},"id":4869,"name":"base - main"},"owner":{"_links":{"web":{"href":"https://dev.azure.com/FOO/1858395a-257e-4efd-bbc5-eb618128452b/_build/results?buildId=673584"},"self":{"href":"https://dev.azure.com/FOO/1858395a-257e-4efd-bbc5-eb618128452b/_apis/build/Builds/673584"}},"id":673584,"name":"20220928.2"},"data":{"ParallelismTag":"Private","IsScheduledKey":"False"},"poolId":44,"orchestrationId":"5c5c8ec9-786f-4e97-99d4-a29279befba3.build.__default","priority":0},{"requestId":890663,"queueTime":"2022-09-28T11:20:22.4633333Z","serviceOwner":"00025394-6065-48ca-87d9-7f5672854ef7","hostId":"41a18c7d-df5e-4032-a4df-d533b56bd2de","scopeId":"02696e26-a35b-424c-86b8-1f54e1b0b4b7","planType":"Build","planId":"b718cfed-493c-46be-a650-88fe762f75aa","jobId":"15b95994-59ec-5502-695d-0b93722883bd","demands":["dotnet60","java","cmake","Agent.Version -gtVersion 2.182.1"],"matchedAgents":[{"_links":{"self":{"href":"https://dev.azure.com/FOO/_apis/distributedtask/pools/44/agents/1755"},"web":{"href":"https://dev.azure.com/FOO/_settings/agentpools?view=jobs&poolId=44&agentId=1755"}},"id":1755,"name":"dotnet60-keda-template","version":"2.210.1","enabled":true,"status":"offline","provisioningState":"Provisioned"},{"_links":{"self":{"href":"https://dev.azure.com/FOO/_apis/distributedtask/pools/44/agents/11732"},"web":{"href":"https://dev.azure.com/FOO/_settings/agentpools?view=jobs&poolId=44&agentId=11732"}},"id":11732,"name":"dotnet60-scaledjob-5dsgc-pkqvm","version":"2.210.1","enabled":true,"status":"online","provisioningState":"Provisioned"},{"_links":{"self":{"href":"https://dev.azure.com/FOO/_apis/distributedtask/pools/44/agents/11733"},"web":{"href":"https://dev.azure.com/FOO/_settings/agentpools?view=jobs&poolId=44&agentId=11733"}},"id":11733,"name":"dotnet60-scaledjob-zgqnp-8h4z4","version":"2.210.1","enabled":true,"status":"online","provisioningState":"Provisioned"},{"_links":{"self":{"href":"https://dev.azure.com/FOO/_apis/distributedtask/pools/44/agents/11734"},"web":{"href":"https://dev.azure.com/FOO/_settings/agentpools?view=jobs&poolId=44&agentId=11734"}},"id":11734,"name":"dotnet60-scaledjob-wr65c-ff2cv","version":"2.210.1","enabled":true,"status":"online","provisioningState":"Provisioned"}],"definition":{"_links":{"web":{"href":"https://FOO.visualstudio.com/02696e26-a35b-424c-86b8-1f54e1b0b4b7/_build/definition?definitionId=3129"},"self":{"href":"https://FOO.visualstudio.com/02696e26-a35b-424c-86b8-1f54e1b0b4b7/_apis/build/Definitions/3129"}},"id":3129,"name":"Other Build CI"},"owner":{"_links":{"web":{"href":"https://FOO.visualstudio.com/02696e26-a35b-424c-86b8-1f54e1b0b4b7/_build/results?buildId=673585"},"self":{"href":"https://FOO.visualstudio.com/02696e26-a35b-424c-86b8-1f54e1b0b4b7/_apis/build/Builds/673585"}},"id":673585,"name":"20220928.11"},"data":{"ParallelismTag":"Private","IsScheduledKey":"False"},"poolId":44,"orchestrationId":"b718cfed-493c-46be-a650-88fe762f75aa.buildtest.build_and_test.__default","priority":0}]}`
@@ -413,3 +422,54 @@ func buildLoadJSON() []byte {
 
 	return []byte(output)
 }
+
+type validateAzurePipelinesQueueURLTestData struct {
+	testName    string
+	metadata    azurePipelinesMetadata
+	expectedURL string
+}
+
+var testValidateAzurePipelinesQueueURLData = []validateAzurePipelinesQueueURLTestData{
+	// parent agent defined
+	{"parent agent given", azurePipelinesMetadata{Parent: "test-agent"}, "%s/_apis/distributedtask/pools/%d/jobrequests"},
+	// jobsToFetch given
+	{"jobsToFetch given", azurePipelinesMetadata{JobsToFetch: 1250}, "%s/_apis/distributedtask/pools/%d/jobrequests?$top=1250"},
+	// fetchUnfinishedJobsOnly set to true
+	{"fetchUnfinishedJobsOnly set to true", azurePipelinesMetadata{FetchUnfinishedJobsOnly: true}, "%s/_apis/distributedtask/pools/%d/jobrequests?completedRequestCount=0"},
+	// fetchUnfinishedJobsOnly set to true and parent agent defined
+	{"fetchUnfinishedJobsOnly set to true and parent agent defined", azurePipelinesMetadata{FetchUnfinishedJobsOnly: true, Parent: "test-agent"}, "%s/_apis/distributedtask/pools/%d/jobrequests?completedRequestCount=0"},
+}
+
+func TestAzurePipelinesQueueURLTest(t *testing.T) {
+	for _, testData := range testValidateAzurePipelinesQueueURLData {
+		t.Run(testData.testName, func(t *testing.T) {
+			var apiStub = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+				w.WriteHeader(http.StatusOK)
+				_, _ = w.Write(buildLoadJSON())
+			}))
+
+			meta := testData.metadata
+			meta.OrganizationName = "testOrg"
+			meta.OrganizationURL = apiStub.URL
+			meta.authContext.pat = "testPAT"
+			meta.PoolID = 1
+			meta.TargetPipelinesQueueLength = 1
+
+			mockAzurePipelinesScaler := azurePipelinesScaler{
+				metadata:   &meta,
+				httpClient: http.DefaultClient,
+			}
+
+			queueURL, err := mockAzurePipelinesScaler.GetAzurePipelinesQueueURL()
+
+			if err != nil {
+				t.Fail()
+			}
+
+			expectedURL := fmt.Sprintf(testData.expectedURL, meta.OrganizationURL, meta.PoolID)
+			if queueURL != expectedURL {
+				t.Fail()
+			}
+		})
+	}
+}