• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

UiPath / uipathcli / 18678624158

21 Oct 2025 09:01AM UTC coverage: 90.797% (-0.08%) from 90.88%
18678624158

push

github

thschmitt
Improve configure command by providing tenant list

499 of 548 new or added lines in 18 files covered. (91.06%)

17 existing lines in 4 files now uncovered.

7044 of 7758 relevant lines covered (90.8%)

1.02 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.06
/utils/api/orchestrator_client.go
1
package api
2

3
import (
4
        "bytes"
5
        "context"
6
        "encoding/json"
7
        "errors"
8
        "fmt"
9
        "io"
10
        "mime/multipart"
11
        "net/http"
12
        "net/textproto"
13
        "net/url"
14
        "strconv"
15
        "strings"
16
        "time"
17

18
        "github.com/UiPath/uipathcli/auth"
19
        "github.com/UiPath/uipathcli/log"
20
        "github.com/UiPath/uipathcli/plugin"
21
        "github.com/UiPath/uipathcli/utils/converter"
22
        "github.com/UiPath/uipathcli/utils/network"
23
        "github.com/UiPath/uipathcli/utils/stream"
24
        "github.com/UiPath/uipathcli/utils/visualization"
25
)
26

27
var ErrPackageAlreadyExists = errors.New("Package already exists")
28

29
type OrchestratorClient struct {
30
        baseUri      url.URL
31
        organization string
32
        tenant       string
33
        token        *auth.AuthToken
34
        debug        bool
35
        settings     plugin.ExecutionSettings
36
        logger       log.Logger
37
}
38

39
func (c OrchestratorClient) GetFolderId(filter string) (int, error) {
1✔
40
        request := c.createGetFolderRequest(filter)
1✔
41
        client := network.NewHttpClient(c.logger, c.httpClientSettings())
1✔
42
        response, err := client.Send(request)
1✔
43
        if err != nil {
2✔
44
                return -1, err
1✔
45
        }
1✔
46
        defer func() { _ = response.Body.Close() }()
2✔
47
        body, err := io.ReadAll(response.Body)
1✔
48
        if err != nil {
1✔
49
                return -1, fmt.Errorf("Error reading response: %w", err)
×
50
        }
×
51
        if response.StatusCode != http.StatusOK {
2✔
52
                return -1, fmt.Errorf("Orchestrator returned status code '%v' and body '%v'", response.StatusCode, string(body))
1✔
53
        }
1✔
54

55
        var result getFoldersResponseJson
1✔
56
        err = json.Unmarshal(body, &result)
1✔
57
        if err != nil {
1✔
UNCOV
58
                return -1, fmt.Errorf("Orchestrator returned invalid response body '%v'", string(body))
×
UNCOV
59
        }
×
60
        if len(result.Value) == 0 {
2✔
61
                return -1, fmt.Errorf("Could not find orchestrator folder '%s'", filter)
1✔
62
        }
1✔
63
        return result.Value[0].Id, nil
1✔
64
}
65

66
func (c OrchestratorClient) createGetFolderRequest(filter string) *network.HttpRequest {
1✔
67
        filterQuery := "FullyQualifiedName eq '" + filter + "'"
1✔
68
        _, err := strconv.Atoi(filter)
1✔
69
        if err == nil {
2✔
70
                filterQuery += " or Id eq " + filter
1✔
71
        }
1✔
72

73
        uri := c.newUriBuilder("/odata/Folders").
1✔
74
                AddQueryString("$filter", filterQuery).
1✔
75
                Build()
1✔
76
        header := http.Header{
1✔
77
                "Content-Type": {"application/json"},
1✔
78
        }
1✔
79
        return network.NewHttpGetRequest(uri, c.toAuthorization(c.token), header)
1✔
80
}
81

82
func (c OrchestratorClient) Upload(file stream.Stream, feedId string, uploadBar *visualization.ProgressBar) error {
1✔
83
        context, cancel := context.WithCancelCause(context.Background())
1✔
84
        request := c.createUploadRequest(file, feedId, uploadBar, cancel)
1✔
85
        client := network.NewHttpClient(c.logger, c.httpClientSettings())
1✔
86
        response, err := client.SendWithContext(request, context)
1✔
87
        if err != nil {
2✔
88
                return err
1✔
89
        }
1✔
90
        defer func() { _ = response.Body.Close() }()
2✔
91
        body, err := io.ReadAll(response.Body)
1✔
92
        if err != nil {
1✔
93
                return fmt.Errorf("Error reading response: %w", err)
×
94
        }
×
95
        if response.StatusCode == http.StatusConflict {
2✔
96
                return ErrPackageAlreadyExists
1✔
97
        }
1✔
98
        if response.StatusCode != http.StatusOK {
2✔
99
                return fmt.Errorf("Orchestrator returned status code '%v' and body '%v'", response.StatusCode, string(body))
1✔
100
        }
1✔
101
        return nil
1✔
102
}
103

104
func (c OrchestratorClient) createUploadRequest(file stream.Stream, feedId string, uploadBar *visualization.ProgressBar, cancel context.CancelCauseFunc) *network.HttpRequest {
1✔
105
        bodyReader, bodyWriter := io.Pipe()
1✔
106
        streamSize, _ := file.Size()
1✔
107
        contentType := c.writeMultipartBody(bodyWriter, file, "application/octet-stream", cancel)
1✔
108
        uploadReader := c.progressReader("uploading...", "completing  ", bodyReader, streamSize, uploadBar)
1✔
109

1✔
110
        uriBuilder := c.newUriBuilder("/odata/Processes/UiPath.Server.Configuration.OData.UploadPackage")
1✔
111
        if feedId != "" {
2✔
112
                uriBuilder.AddQueryString("feedId", feedId)
1✔
113
        }
1✔
114
        uri := uriBuilder.Build()
1✔
115
        header := http.Header{
1✔
116
                "Content-Type": {contentType},
1✔
117
        }
1✔
118
        return network.NewHttpPostRequest(uri, c.toAuthorization(c.token), header, uploadReader, -1)
1✔
119
}
120

121
func (c OrchestratorClient) writeMultipartBody(bodyWriter *io.PipeWriter, stream stream.Stream, contentType string, cancel context.CancelCauseFunc) string {
1✔
122
        formWriter := multipart.NewWriter(bodyWriter)
1✔
123
        go func() {
2✔
124
                defer func() { _ = bodyWriter.Close() }()
2✔
125
                defer func() { _ = formWriter.Close() }()
2✔
126
                err := c.writeMultipartForm(formWriter, stream, contentType)
1✔
127
                if err != nil {
1✔
128
                        cancel(err)
×
129
                        return
×
130
                }
×
131
        }()
132
        return formWriter.FormDataContentType()
1✔
133
}
134

135
func (c OrchestratorClient) writeMultipartForm(writer *multipart.Writer, stream stream.Stream, contentType string) error {
1✔
136
        filePart := textproto.MIMEHeader{}
1✔
137
        filePart.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file"; filename="%s"`, stream.Name()))
1✔
138
        filePart.Set("Content-Type", contentType)
1✔
139
        w, err := writer.CreatePart(filePart)
1✔
140
        if err != nil {
1✔
141
                return fmt.Errorf("Error creating form field 'file': %w", err)
×
142
        }
×
143
        data, err := stream.Data()
1✔
144
        if err != nil {
1✔
145
                return err
×
146
        }
×
147
        defer func() { _ = data.Close() }()
2✔
148
        _, err = io.Copy(w, data)
1✔
149
        if err != nil {
1✔
150
                return fmt.Errorf("Error writing form field 'file': %w", err)
×
151
        }
×
152
        return nil
1✔
153
}
154

155
func (c OrchestratorClient) GetReleases(folderId int, processKey string) ([]Release, error) {
1✔
156
        request := c.createGetReleasesRequest(folderId, processKey)
1✔
157
        client := network.NewHttpClient(c.logger, c.httpClientSettings())
1✔
158
        response, err := client.Send(request)
1✔
159
        if err != nil {
1✔
160
                return []Release{}, err
×
161
        }
×
162
        defer func() { _ = response.Body.Close() }()
2✔
163
        body, err := io.ReadAll(response.Body)
1✔
164
        if err != nil {
1✔
165
                return []Release{}, fmt.Errorf("Error reading response: %w", err)
×
166
        }
×
167
        if response.StatusCode != http.StatusOK {
2✔
168
                return []Release{}, fmt.Errorf("Orchestrator returned status code '%v' and body '%v'", response.StatusCode, string(body))
1✔
169
        }
1✔
170

171
        var result getReleasesResponseJson
1✔
172
        err = json.Unmarshal(body, &result)
1✔
173
        if err != nil {
2✔
174
                return []Release{}, fmt.Errorf("Orchestrator returned invalid response body '%v'", string(body))
1✔
175
        }
1✔
176
        return c.convertToReleases(result), nil
1✔
177
}
178

179
func (c OrchestratorClient) convertToReleases(json getReleasesResponseJson) []Release {
1✔
180
        releases := []Release{}
1✔
181
        for _, v := range json.Value {
2✔
182
                releases = append(releases, *NewRelease(v.Id, v.Name))
1✔
183
        }
1✔
184
        return releases
1✔
185
}
186

187
func (c OrchestratorClient) createGetReleasesRequest(folderId int, processKey string) *network.HttpRequest {
1✔
188
        uri := c.newUriBuilder("/odata/Releases").
1✔
189
                AddQueryString("$filter", "ProcessKey eq '"+processKey+"'").
1✔
190
                Build()
1✔
191
        header := http.Header{
1✔
192
                "Content-Type":                {"application/json"},
1✔
193
                "X-Uipath-Organizationunitid": {strconv.Itoa(folderId)},
1✔
194
        }
1✔
195
        return network.NewHttpGetRequest(uri, c.toAuthorization(c.token), header)
1✔
196
}
1✔
197

198
func (c OrchestratorClient) CreateOrUpdateRelease(folderId int, processKey string, processVersion string) (int, error) {
1✔
199
        releases, err := c.GetReleases(folderId, processKey)
1✔
200
        if err != nil {
2✔
201
                return -1, err
1✔
202
        }
1✔
203
        if len(releases) > 0 {
2✔
204
                return c.UpdateRelease(folderId, releases[0].Id, processKey, processVersion)
1✔
205
        }
1✔
206
        return c.CreateRelease(folderId, processKey, processVersion)
1✔
207
}
208

209
func (c OrchestratorClient) CreateRelease(folderId int, processKey string, processVersion string) (int, error) {
1✔
210
        request, err := c.createNewReleaseRequest(folderId, processKey, processVersion)
1✔
211
        if err != nil {
1✔
212
                return -1, err
×
213
        }
×
214
        client := network.NewHttpClient(c.logger, c.httpClientSettings())
1✔
215
        response, err := client.Send(request)
1✔
216
        if err != nil {
1✔
217
                return -1, err
×
218
        }
×
219
        defer func() { _ = response.Body.Close() }()
2✔
220
        body, err := io.ReadAll(response.Body)
1✔
221
        if err != nil {
1✔
222
                return -1, fmt.Errorf("Error reading response: %w", err)
×
223
        }
×
224
        if response.StatusCode != http.StatusCreated {
2✔
225
                return -1, fmt.Errorf("Orchestrator returned status code '%v' and body '%v'", response.StatusCode, string(body))
1✔
226
        }
1✔
227

228
        var result createReleaseResponseJson
1✔
229
        err = json.Unmarshal(body, &result)
1✔
230
        if err != nil {
2✔
231
                return -1, fmt.Errorf("Orchestrator returned invalid response body '%v'", string(body))
1✔
232
        }
1✔
233
        return result.Id, nil
1✔
234
}
235

236
func (c OrchestratorClient) createNewReleaseRequest(folderId int, processKey string, processVersion string) (*network.HttpRequest, error) {
1✔
237
        json, err := json.Marshal(createReleaseRequestJson{
1✔
238
                Name:           processKey,
1✔
239
                ProcessKey:     processKey,
1✔
240
                ProcessVersion: processVersion,
1✔
241
        })
1✔
242
        if err != nil {
1✔
243
                return nil, err
×
244
        }
×
245

246
        uri := c.newUriBuilder("/odata/Releases").
1✔
247
                Build()
1✔
248
        header := http.Header{
1✔
249
                "Content-Type":                {"application/json"},
1✔
250
                "X-Uipath-Organizationunitid": {strconv.Itoa(folderId)},
1✔
251
        }
1✔
252
        return network.NewHttpPostRequest(uri, c.toAuthorization(c.token), header, bytes.NewBuffer(json), -1), nil
1✔
253
}
254

255
func (c OrchestratorClient) UpdateRelease(folderId int, releaseId int, processKey string, processVersion string) (int, error) {
1✔
256
        request, err := c.createUpdateReleaseRequest(folderId, releaseId, processKey, processVersion)
1✔
257
        if err != nil {
1✔
258
                return -1, err
×
259
        }
×
260
        client := network.NewHttpClient(c.logger, c.httpClientSettings())
1✔
261
        response, err := client.Send(request)
1✔
262
        if err != nil {
1✔
263
                return -1, err
×
264
        }
×
265
        defer func() { _ = response.Body.Close() }()
2✔
266
        body, err := io.ReadAll(response.Body)
1✔
267
        if err != nil {
1✔
268
                return -1, fmt.Errorf("Error reading response: %w", err)
×
269
        }
×
270
        if response.StatusCode != http.StatusOK {
1✔
271
                return -1, fmt.Errorf("Orchestrator returned status code '%v' and body '%v'", response.StatusCode, string(body))
×
272
        }
×
273
        return releaseId, nil
1✔
274
}
275

276
func (c OrchestratorClient) createUpdateReleaseRequest(folderId int, releaseId int, processKey string, processVersion string) (*network.HttpRequest, error) {
1✔
277
        json, err := json.Marshal(createReleaseRequestJson{
1✔
278
                Name:           processKey,
1✔
279
                ProcessKey:     processKey,
1✔
280
                ProcessVersion: processVersion,
1✔
281
        })
1✔
282
        if err != nil {
1✔
283
                return nil, err
×
284
        }
×
285
        uri := c.newUriBuilder("/odata/Releases({releaseId})").
1✔
286
                FormatPath("releaseId", releaseId).
1✔
287
                Build()
1✔
288
        header := http.Header{
1✔
289
                "Content-Type":                {"application/json"},
1✔
290
                "X-Uipath-Organizationunitid": {strconv.Itoa(folderId)},
1✔
291
        }
1✔
292
        return network.NewHttpPatchRequest(uri, c.toAuthorization(c.token), header, bytes.NewBuffer(json), -1), nil
1✔
293
}
294

295
func (c OrchestratorClient) CreateTestSet(folderId int, releaseId int, processVersion string) (int, error) {
1✔
296
        request, err := c.createTestSetRequest(folderId, releaseId, processVersion)
1✔
297
        if err != nil {
1✔
298
                return -1, err
×
299
        }
×
300
        client := network.NewHttpClient(c.logger, c.httpClientSettings())
1✔
301
        response, err := client.Send(request)
1✔
302
        if err != nil {
2✔
303
                return -1, err
1✔
304
        }
1✔
305
        defer func() { _ = response.Body.Close() }()
2✔
306
        body, err := io.ReadAll(response.Body)
1✔
307
        if err != nil {
1✔
308
                return -1, fmt.Errorf("Error reading response: %w", err)
×
309
        }
×
310
        if response.StatusCode != http.StatusCreated {
1✔
311
                return -1, fmt.Errorf("Orchestrator returned status code '%v' and body '%v'", response.StatusCode, string(body))
×
312
        }
×
313
        return strconv.Atoi(string(body))
1✔
314
}
315

316
func (c OrchestratorClient) createTestSetRequest(folderId int, releaseId int, processVersion string) (*network.HttpRequest, error) {
1✔
317
        json, err := json.Marshal(createTestSetRequestJson{
1✔
318
                ReleaseId:     releaseId,
1✔
319
                VersionNumber: processVersion,
1✔
320
        })
1✔
321
        if err != nil {
1✔
322
                return nil, err
×
323
        }
×
324

325
        uri := c.newUriBuilder("/api/TestAutomation/CreateTestSetForReleaseVersion").
1✔
326
                Build()
1✔
327
        header := http.Header{
1✔
328
                "Content-Type":                {"application/json"},
1✔
329
                "X-Uipath-Organizationunitid": {strconv.Itoa(folderId)},
1✔
330
        }
1✔
331
        return network.NewHttpPostRequest(uri, c.toAuthorization(c.token), header, bytes.NewBuffer(json), -1), nil
1✔
332
}
333

334
func (c OrchestratorClient) ExecuteTestSet(folderId int, testSetId int) (int, error) {
1✔
335
        request := c.createExecuteTestSetRequest(folderId, testSetId)
1✔
336
        client := network.NewHttpClient(c.logger, c.httpClientSettings())
1✔
337
        response, err := client.Send(request)
1✔
338
        if err != nil {
2✔
339
                return -1, err
1✔
340
        }
1✔
341
        defer func() { _ = response.Body.Close() }()
2✔
342
        body, err := io.ReadAll(response.Body)
1✔
343
        if err != nil {
1✔
344
                return -1, fmt.Errorf("Error reading response: %w", err)
×
345
        }
×
346
        if response.StatusCode != http.StatusOK {
1✔
347
                return -1, fmt.Errorf("Orchestrator returned status code '%v' and body '%v'", response.StatusCode, string(body))
×
348
        }
×
349
        return strconv.Atoi(string(body))
1✔
350
}
351

352
func (c OrchestratorClient) createExecuteTestSetRequest(folderId int, testSetId int) *network.HttpRequest {
1✔
353
        uri := c.newUriBuilder("/api/TestAutomation/StartTestSetExecution").
1✔
354
                AddQueryString("testSetId", testSetId).
1✔
355
                AddQueryString("triggerType", "ExternalTool").
1✔
356
                Build()
1✔
357
        header := http.Header{
1✔
358
                "Content-Type":                {"application/json"},
1✔
359
                "X-Uipath-Organizationunitid": {strconv.Itoa(folderId)},
1✔
360
        }
1✔
361
        return network.NewHttpPostRequest(uri, c.toAuthorization(c.token), header, bytes.NewReader([]byte{}), -1)
1✔
362
}
1✔
363

364
func (c OrchestratorClient) WaitForTestExecutionToFinish(folderId int, executionId int, timeout time.Duration, statusFunc func(TestExecution)) (*TestExecution, error) {
1✔
365
        startTime := time.Now()
1✔
366
        for {
2✔
367
                execution, err := c.GetTestExecution(folderId, executionId)
1✔
368
                if err != nil {
1✔
369
                        return nil, err
×
370
                }
×
371
                statusFunc(*execution)
1✔
372
                if execution.IsCompleted() {
2✔
373
                        return execution, nil
1✔
374
                }
1✔
375
                if time.Since(startTime) >= timeout {
2✔
376
                        return nil, fmt.Errorf("Timeout waiting for test execution '%d' to finish.", executionId)
1✔
377
                }
1✔
378
                time.Sleep(1 * time.Second)
1✔
379
        }
380
}
381

382
func (c OrchestratorClient) GetTestSet(folderId int, testSetId int) (*TestSet, error) {
1✔
383
        request := c.createGetTestSetRequest(folderId, testSetId)
1✔
384
        client := network.NewHttpClient(c.logger, c.httpClientSettings())
1✔
385
        response, err := client.Send(request)
1✔
386
        if err != nil {
1✔
387
                return nil, err
×
388
        }
×
389
        defer func() { _ = response.Body.Close() }()
2✔
390
        body, err := io.ReadAll(response.Body)
1✔
391
        if err != nil {
1✔
392
                return nil, fmt.Errorf("Error reading response: %w", err)
×
393
        }
×
394
        if response.StatusCode != http.StatusOK {
1✔
395
                return nil, fmt.Errorf("Orchestrator returned status code '%v' and body '%v'", response.StatusCode, string(body))
×
396
        }
×
397

398
        var result getTestSetResponseJson
1✔
399
        err = json.Unmarshal(body, &result)
1✔
400
        if err != nil {
1✔
401
                return nil, fmt.Errorf("Orchestrator returned invalid response body '%v'", string(body))
×
402
        }
×
403
        return c.convertToTestSet(result), nil
1✔
404
}
405

406
func (c OrchestratorClient) createGetTestSetRequest(folderId int, testSetId int) *network.HttpRequest {
1✔
407
        uri := c.newUriBuilder("/odata/TestSets({testSetId})").
1✔
408
                FormatPath("testSetId", testSetId).
1✔
409
                AddQueryString("$expand", "TestCases($expand=Definition;$select=Id,Definition,DefinitionId,ReleaseId,VersionNumber),Packages").
1✔
410
                AddQueryString("$select", "TestCases,Name").
1✔
411
                Build()
1✔
412
        header := http.Header{
1✔
413
                "Content-Type":                {"application/json"},
1✔
414
                "X-Uipath-Organizationunitid": {strconv.Itoa(folderId)},
1✔
415
        }
1✔
416
        return network.NewHttpGetRequest(uri, c.toAuthorization(c.token), header)
1✔
417
}
1✔
418

419
func (c OrchestratorClient) GetTestExecution(folderId int, executionId int) (*TestExecution, error) {
1✔
420
        request := c.createGetTestExecutionRequest(folderId, executionId)
1✔
421
        client := network.NewHttpClient(c.logger, c.httpClientSettings())
1✔
422
        response, err := client.Send(request)
1✔
423
        if err != nil {
1✔
424
                return nil, err
×
425
        }
×
426
        defer func() { _ = response.Body.Close() }()
2✔
427
        body, err := io.ReadAll(response.Body)
1✔
428
        if err != nil {
1✔
429
                return nil, fmt.Errorf("Error reading response: %w", err)
×
430
        }
×
431
        if response.StatusCode != http.StatusOK {
1✔
432
                return nil, fmt.Errorf("Orchestrator returned status code '%v' and body '%v'", response.StatusCode, string(body))
×
433
        }
×
434

435
        var result getTestExecutionResponseJson
1✔
436
        err = json.Unmarshal(body, &result)
1✔
437
        if err != nil {
1✔
438
                return nil, fmt.Errorf("Orchestrator returned invalid response body '%v'", string(body))
×
439
        }
×
440
        return c.convertToTestExecution(result), nil
1✔
441
}
442

443
func (c OrchestratorClient) createGetTestExecutionRequest(folderId int, executionId int) *network.HttpRequest {
1✔
444
        uri := c.newUriBuilder("/odata/TestSetExecutions({testSetExecutionId})").
1✔
445
                FormatPath("testSetExecutionId", executionId).
1✔
446
                AddQueryString("$expand", "TestCaseExecutions($expand=TestCaseAssertions)").
1✔
447
                Build()
1✔
448
        header := http.Header{
1✔
449
                "Content-Type":                {"application/json"},
1✔
450
                "X-Uipath-Organizationunitid": {strconv.Itoa(folderId)},
1✔
451
        }
1✔
452
        return network.NewHttpGetRequest(uri, c.toAuthorization(c.token), header)
1✔
453
}
1✔
454

455
func (c OrchestratorClient) GetRobotLogs(folderId int, jobKey string) ([]RobotLog, error) {
1✔
456
        request := c.createGetRobotLogsRequest(folderId, jobKey)
1✔
457
        client := network.NewHttpClient(c.logger, c.httpClientSettings())
1✔
458
        response, err := client.Send(request)
1✔
459
        if err != nil {
1✔
460
                return []RobotLog{}, err
×
461
        }
×
462
        defer func() { _ = response.Body.Close() }()
2✔
463
        body, err := io.ReadAll(response.Body)
1✔
464
        if err != nil {
1✔
465
                return []RobotLog{}, fmt.Errorf("Error reading response: %w", err)
×
466
        }
×
467
        if response.StatusCode != http.StatusOK {
1✔
468
                return []RobotLog{}, fmt.Errorf("Orchestrator returned status code '%v' and body '%v'", response.StatusCode, string(body))
×
469
        }
×
470

471
        var result getRobotLogsResponseJson
1✔
472
        err = json.Unmarshal(body, &result)
1✔
473
        if err != nil {
1✔
474
                return []RobotLog{}, fmt.Errorf("Orchestrator returned invalid response body '%v'", string(body))
×
475
        }
×
476
        return c.convertToRobotLogs(result), nil
1✔
477
}
478

479
func (c OrchestratorClient) createGetRobotLogsRequest(folderId int, jobKey string) *network.HttpRequest {
1✔
480
        uri := c.newUriBuilder("/odata/RobotLogs").
1✔
481
                AddQueryString("$filter", "JobKey eq "+jobKey).
1✔
482
                Build()
1✔
483
        header := http.Header{
1✔
484
                "X-Uipath-Organizationunitid": {strconv.Itoa(folderId)},
1✔
485
        }
1✔
486
        return network.NewHttpGetRequest(uri, c.toAuthorization(c.token), header)
1✔
487
}
1✔
488

489
func (c OrchestratorClient) GetFolderFeed(folderId int) (string, error) {
1✔
490
        request := c.createGetFolderFeedRequest(folderId)
1✔
491
        client := network.NewHttpClient(c.logger, c.httpClientSettings())
1✔
492
        response, err := client.Send(request)
1✔
493
        if err != nil {
1✔
494
                return "", err
×
495
        }
×
496
        defer func() { _ = response.Body.Close() }()
2✔
497
        body, err := io.ReadAll(response.Body)
1✔
498
        if err != nil {
1✔
499
                return "", fmt.Errorf("Error reading response: %w", err)
×
500
        }
×
501
        if response.StatusCode != http.StatusOK {
2✔
502
                return "", fmt.Errorf("Orchestrator returned status code '%v' and body '%v'", response.StatusCode, string(body))
1✔
503
        }
1✔
504
        feedId := string(body)
1✔
505
        if feedId == "null" {
2✔
506
                return "", nil
1✔
507
        }
1✔
508
        return strings.Trim(feedId, "\""), nil
1✔
509
}
510

511
func (c OrchestratorClient) createGetFolderFeedRequest(folderId int) *network.HttpRequest {
1✔
512
        uri := c.newUriBuilder("/api/PackageFeeds/GetFolderFeed").
1✔
513
                AddQueryString("folderId", folderId).
1✔
514
                Build()
1✔
515
        header := http.Header{
1✔
516
                "Content-Type": {"application/json"},
1✔
517
        }
1✔
518
        return network.NewHttpGetRequest(uri, c.toAuthorization(c.token), header)
1✔
519
}
1✔
520

521
func (c OrchestratorClient) GetReadUrl(folderId int, bucketId int, path string) (string, error) {
1✔
522
        request := c.createReadUrlRequest(folderId, bucketId, path)
1✔
523
        client := network.NewHttpClient(c.logger, c.httpClientSettings())
1✔
524
        response, err := client.Send(request)
1✔
525
        if err != nil {
1✔
526
                return "", fmt.Errorf("Error sending request: %w", err)
×
527
        }
×
528
        defer func() { _ = response.Body.Close() }()
2✔
529
        body, err := io.ReadAll(response.Body)
1✔
530
        if err != nil {
1✔
531
                return "", fmt.Errorf("Error reading response: %w", err)
×
532
        }
×
533
        if response.StatusCode != http.StatusOK {
2✔
534
                return "", fmt.Errorf("Orchestrator returned status code '%v' and body '%v'", response.StatusCode, string(body))
1✔
535
        }
1✔
536
        var result urlResponse
1✔
537
        err = json.Unmarshal(body, &result)
1✔
538
        if err != nil {
1✔
539
                return "", fmt.Errorf("Error parsing json response: %w", err)
×
540
        }
×
541
        return result.Uri, nil
1✔
542
}
543

544
func (c OrchestratorClient) createReadUrlRequest(folderId int, bucketId int, path string) *network.HttpRequest {
1✔
545
        uri := c.newUriBuilder("/odata/Buckets({bucketId})/UiPath.Server.Configuration.OData.GetReadUri").
1✔
546
                FormatPath("bucketId", bucketId).
1✔
547
                AddQueryString("path", path).
1✔
548
                Build()
1✔
549
        header := http.Header{
1✔
550
                "X-UiPath-OrganizationUnitId": {strconv.Itoa(folderId)},
1✔
551
        }
1✔
552
        return network.NewHttpGetRequest(uri, c.toAuthorization(c.token), header)
1✔
553
}
1✔
554

555
func (c OrchestratorClient) GetWriteUrl(folderId int, bucketId int, path string) (string, error) {
1✔
556
        request := c.createWriteUrlRequest(folderId, bucketId, path)
1✔
557
        client := network.NewHttpClient(c.logger, c.httpClientSettings())
1✔
558
        response, err := client.Send(request)
1✔
559
        if err != nil {
1✔
560
                return "", err
×
561
        }
×
562
        defer func() { _ = response.Body.Close() }()
2✔
563
        body, err := io.ReadAll(response.Body)
1✔
564
        if err != nil {
1✔
565
                return "", fmt.Errorf("Error reading response: %w", err)
×
566
        }
×
567
        if response.StatusCode != http.StatusOK {
2✔
568
                return "", fmt.Errorf("Orchestrator returned status code '%v' and body '%v'", response.StatusCode, string(body))
1✔
569
        }
1✔
570
        var result urlResponse
1✔
571
        err = json.Unmarshal(body, &result)
1✔
572
        if err != nil {
1✔
573
                return "", fmt.Errorf("Error parsing json response: %w", err)
×
574
        }
×
575
        return result.Uri, nil
1✔
576
}
577

578
func (c OrchestratorClient) createWriteUrlRequest(folderId int, bucketId int, path string) *network.HttpRequest {
1✔
579
        uri := c.newUriBuilder("/odata/Buckets({bucketId})/UiPath.Server.Configuration.OData.GetWriteUri").
1✔
580
                FormatPath("bucketId", bucketId).
1✔
581
                AddQueryString("path", path).
1✔
582
                Build()
1✔
583
        header := http.Header{
1✔
584
                "X-UiPath-OrganizationUnitId": {strconv.Itoa(folderId)},
1✔
585
        }
1✔
586
        return network.NewHttpGetRequest(uri, c.toAuthorization(c.token), header)
1✔
587
}
1✔
588

589
func (c OrchestratorClient) convertToRobotLogs(json getRobotLogsResponseJson) []RobotLog {
1✔
590
        logs := []RobotLog{}
1✔
591
        for _, l := range json.Value {
2✔
592
                logs = append(logs, c.convertToRobotLog(l))
1✔
593
        }
1✔
594
        return logs
1✔
595
}
596

597
func (c OrchestratorClient) convertToRobotLog(json getRobotLogJson) RobotLog {
1✔
598
        return *NewRobotLog(
1✔
599
                json.Id,
1✔
600
                json.Level,
1✔
601
                json.WindowsIdentity,
1✔
602
                json.ProcessName,
1✔
603
                json.TimeStamp,
1✔
604
                json.Message,
1✔
605
                json.RobotName,
1✔
606
                json.HostMachineName,
1✔
607
                json.MachineId,
1✔
608
                json.MachineKey,
1✔
609
                json.RuntimeType,
1✔
610
        )
1✔
611
}
1✔
612

613
func (c OrchestratorClient) convertToTestSet(json getTestSetResponseJson) *TestSet {
1✔
614
        testCases := []TestCase{}
1✔
615
        for _, c := range json.TestCases {
2✔
616
                testCase := NewTestCase(
1✔
617
                        c.Id,
1✔
618
                        c.Definition.Name,
1✔
619
                        c.Definition.PackageIdentifier,
1✔
620
                )
1✔
621
                testCases = append(testCases, *testCase)
1✔
622
        }
1✔
623
        testPackages := []TestPackage{}
1✔
624
        for _, p := range json.Packages {
2✔
625
                testPackage := NewTestPackage(
1✔
626
                        p.PackageIdentifier,
1✔
627
                        p.VersionMask,
1✔
628
                )
1✔
629
                testPackages = append(testPackages, *testPackage)
1✔
630
        }
1✔
631
        return NewTestSet(json.Id, testCases, testPackages)
1✔
632
}
633

634
func (c OrchestratorClient) convertToTestExecution(json getTestExecutionResponseJson) *TestExecution {
1✔
635
        return NewTestExecution(
1✔
636
                json.Id,
1✔
637
                json.Status,
1✔
638
                json.TestSetId,
1✔
639
                json.Name,
1✔
640
                json.StartTime,
1✔
641
                json.EndTime,
1✔
642
                c.convertToTestCaseExecutions(json.TestCaseExecutions),
1✔
643
        )
1✔
644
}
1✔
645

646
func (c OrchestratorClient) convertToTestCaseExecutions(json []testCaseExecutionJson) []TestCaseExecution {
1✔
647
        executions := []TestCaseExecution{}
1✔
648
        for _, v := range json {
2✔
649
                execution := NewTestCaseExecution(
1✔
650
                        v.Id,
1✔
651
                        v.Status,
1✔
652
                        v.TestCaseId,
1✔
653
                        v.VersionNumber,
1✔
654
                        v.EntryPointPath,
1✔
655
                        v.Info,
1✔
656
                        v.StartTime,
1✔
657
                        v.EndTime,
1✔
658
                        v.JobId,
1✔
659
                        v.JobKey,
1✔
660
                        v.InputArguments,
1✔
661
                        v.OutputArguments,
1✔
662
                        v.DataVariationIdentifier,
1✔
663
                        c.convertToTestCaseAssertions(v.TestCaseAssertions),
1✔
664
                        nil,
1✔
665
                )
1✔
666
                executions = append(executions, *execution)
1✔
667
        }
1✔
668
        return executions
1✔
669
}
670

671
func (c OrchestratorClient) convertToTestCaseAssertions(json []testCaseExecutionAssertionJson) []TestCaseAssertion {
1✔
672
        assertions := []TestCaseAssertion{}
1✔
673
        for _, v := range json {
1✔
674
                assertion := NewTestCaseAssertion(v.Message, v.Succeeded)
×
675
                assertions = append(assertions, *assertion)
×
676
        }
×
677
        return assertions
1✔
678
}
679

680
func (c OrchestratorClient) progressReader(text string, completedText string, reader io.Reader, length int64, progressBar *visualization.ProgressBar) io.Reader {
1✔
681
        if progressBar == nil || length < 10*1024*1024 {
2✔
682
                return reader
1✔
683
        }
1✔
684
        return visualization.NewProgressReader(reader, func(progress visualization.Progress) {
2✔
685
                displayText := text
1✔
686
                if progress.Completed {
2✔
687
                        displayText = completedText
1✔
688
                }
1✔
689
                progressBar.UpdateProgress(displayText, progress.BytesRead, length, progress.BytesPerSecond)
1✔
690
        })
691
}
692

693
func (c OrchestratorClient) httpClientSettings() network.HttpClientSettings {
1✔
694
        return *network.NewHttpClientSettings(
1✔
695
                c.debug,
1✔
696
                c.settings.OperationId,
1✔
697
                c.settings.Header,
1✔
698
                c.settings.Timeout,
1✔
699
                c.settings.MaxAttempts,
1✔
700
                c.settings.Insecure)
1✔
701
}
1✔
702

703
func (c OrchestratorClient) GetTestSetExecutionUrl(folderId int, testSetExecutionId int) string {
1✔
704
        return c.newUriBuilder("/test/executions/{testSetExecutionId}").
1✔
705
                FormatPath("testSetExecutionId", testSetExecutionId).
1✔
706
                AddQueryString("fid", folderId).
1✔
707
                Build()
1✔
708
}
1✔
709

710
func (c OrchestratorClient) GetTestCaseExecutionUrl(folderId int, testSetExecutionId int, testCaseName string) string {
1✔
711
        return c.newUriBuilder("/test/executions/{testSetExecutionId}").
1✔
712
                FormatPath("testSetExecutionId", testSetExecutionId).
1✔
713
                AddQueryString("fid", folderId).
1✔
714
                AddQueryString("search", testCaseName).
1✔
715
                Build()
1✔
716
}
1✔
717

718
func (c OrchestratorClient) GetTestCaseExecutionLogsUrl(folderId int, testSetExecutionId int, jobId int) string {
1✔
719
        return c.newUriBuilder("/test/executions/{testSetExecutionId}/logs/{jobId}").
1✔
720
                FormatPath("testSetExecutionId", testSetExecutionId).
1✔
721
                FormatPath("jobId", jobId).
1✔
722
                AddQueryString("fid", folderId).
1✔
723
                Build()
1✔
724
}
1✔
725

726
func (c OrchestratorClient) newUriBuilder(path string) *converter.UriBuilder {
1✔
727
        baseUri := c.baseUri
1✔
728
        if baseUri.Path == "" {
2✔
729
                baseUri.Path = "/{organization}/{tenant}/orchestrator_"
1✔
730
        }
1✔
731
        return converter.NewUriBuilder(baseUri, path).
1✔
732
                FormatPath("organization", c.organization).
1✔
733
                FormatPath("tenant", c.tenant)
1✔
734
}
735

736
func (c OrchestratorClient) toAuthorization(token *auth.AuthToken) *network.Authorization {
1✔
737
        if token == nil {
2✔
738
                return nil
1✔
739
        }
1✔
740
        return network.NewAuthorization(token.Type, token.Value)
1✔
741
}
742

743
type createReleaseRequestJson struct {
744
        Name           string `json:"name"`
745
        ProcessKey     string `json:"processKey"`
746
        ProcessVersion string `json:"processVersion"`
747
}
748

749
type createTestSetRequestJson struct {
750
        ReleaseId     int    `json:"releaseId"`
751
        VersionNumber string `json:"versionNumber"`
752
}
753

754
type getFoldersResponseJson struct {
755
        Value []getFoldersResponseValueJson `json:"value"`
756
}
757

758
type getFoldersResponseValueJson struct {
759
        Id int `json:"Id"`
760
}
761

762
type getReleasesResponseJson struct {
763
        Value []getReleasesResponseValueJson `json:"value"`
764
}
765

766
type getReleasesResponseValueJson struct {
767
        Id   int    `json:"Id"`
768
        Name string `json:"Name"`
769
}
770

771
type createReleaseResponseJson struct {
772
        Id int `json:"id"`
773
}
774

775
type getTestSetResponseJson struct {
776
        Id        int                      `json:"Id"`
777
        TestCases []getTestSetTestCaseJson `json:"TestCases"`
778
        Packages  []getTestSetPackagesJson `json:"Packages"`
779
}
780

781
type getTestSetTestCaseJson struct {
782
        Id         int                              `json:"Id"`
783
        Definition getTestSetTestCaseDefinitionJson `json:"Definition"`
784
}
785

786
type getTestSetPackagesJson struct {
787
        PackageIdentifier string `json:"PackageIdentifier"`
788
        VersionMask       string `json:"VersionMask"`
789
}
790

791
type getTestSetTestCaseDefinitionJson struct {
792
        Name              string `json:"Name"`
793
        PackageIdentifier string `json:"PackageIdentifier"`
794
}
795

796
type getTestExecutionResponseJson struct {
797
        Id                 int                     `json:"Id"`
798
        Status             string                  `json:"Status"`
799
        TestSetId          int                     `json:"TestSetId"`
800
        Name               string                  `json:"Name"`
801
        StartTime          time.Time               `json:"StartTime"`
802
        EndTime            time.Time               `json:"EndTime"`
803
        TestCaseExecutions []testCaseExecutionJson `json:"TestCaseExecutions"`
804
}
805

806
type testCaseExecutionJson struct {
807
        Id                      int                              `json:"Id"`
808
        Status                  string                           `json:"Status"`
809
        TestCaseId              int                              `json:"TestCaseId"`
810
        VersionNumber           string                           `json:"VersionNumber"`
811
        EntryPointPath          string                           `json:"EntryPointPath"`
812
        Info                    string                           `json:"Info"`
813
        StartTime               time.Time                        `json:"StartTime"`
814
        EndTime                 time.Time                        `json:"EndTime"`
815
        JobId                   int                              `json:"JobId"`
816
        JobKey                  string                           `json:"JobKey"`
817
        InputArguments          string                           `json:"InputArguments"`
818
        OutputArguments         string                           `json:"OutputArguments"`
819
        DataVariationIdentifier string                           `json:"DataVariationIdentifier"`
820
        TestCaseAssertions      []testCaseExecutionAssertionJson `json:"TestCaseAssertions"`
821
}
822

823
type testCaseExecutionAssertionJson struct {
824
        Message   string `json:"Message"`
825
        Succeeded bool   `json:"Succeeded"`
826
}
827

828
type getRobotLogsResponseJson struct {
829
        Value []getRobotLogJson `json:"value"`
830
}
831

832
type getRobotLogJson struct {
833
        Id              int       `json:"Id"`
834
        Level           string    `json:"Level"`
835
        WindowsIdentity string    `json:"WindowsIdentity"`
836
        ProcessName     string    `json:"ProcessName"`
837
        TimeStamp       time.Time `json:"TimeStamp"`
838
        Message         string    `json:"Message"`
839
        RobotName       string    `json:"RobotName"`
840
        HostMachineName string    `json:"HostMachineName"`
841
        MachineId       int       `json:"MachineId"`
842
        MachineKey      string    `json:"MachineKey"`
843
        RuntimeType     string    `json:"RuntimeType"`
844
}
845

846
type urlResponse struct {
847
        Uri string `json:"Uri"`
848
}
849

850
func NewOrchestratorClient(
851
        baseUri url.URL,
852
        organization string,
853
        tenant string,
854
        token *auth.AuthToken,
855
        debug bool,
856
        settings plugin.ExecutionSettings,
857
        logger log.Logger,
858
) *OrchestratorClient {
1✔
859
        return &OrchestratorClient{
1✔
860
                baseUri,
1✔
861
                organization,
1✔
862
                tenant,
1✔
863
                token,
1✔
864
                debug,
1✔
865
                settings,
1✔
866
                logger,
1✔
867
        }
1✔
868
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc