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

UiPath / uipathcli / 14445806829

14 Apr 2025 12:37PM UTC coverage: 89.947% (-0.1%) from 90.047%
14445806829

Pull #171

github

thschmitt
Extend test run command to support JUnit and retrieve robot logs

Added two new parameters to the `uipath studio test run` command:

- `results-output`: defaults to `uipath` and also supports `junit`
  to generate a JUnit test result XML report

- `attach-robot-logs`: When enabled, retrieves the robot logs for each
  test case
Pull Request #171: Extend test run command to support JUnit and retrieve robot logs

397 of 445 new or added lines in 13 files covered. (89.21%)

1 existing line in 1 file now uncovered.

6505 of 7232 relevant lines covered (89.95%)

1.01 hits per line

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

82.22
/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
        "strconv"
14
        "time"
15

16
        "github.com/UiPath/uipathcli/auth"
17
        "github.com/UiPath/uipathcli/log"
18
        "github.com/UiPath/uipathcli/plugin"
19
        "github.com/UiPath/uipathcli/utils/network"
20
        "github.com/UiPath/uipathcli/utils/stream"
21
        "github.com/UiPath/uipathcli/utils/visualization"
22
)
23

24
var ErrPackageAlreadyExists = errors.New("Package already exists")
25

26
type OrchestratorClient struct {
27
        baseUri  string
28
        token    *auth.AuthToken
29
        debug    bool
30
        settings plugin.ExecutionSettings
31
        logger   log.Logger
32
}
33

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

50
        var result getFoldersResponseJson
1✔
51
        err = json.Unmarshal(body, &result)
1✔
52
        if err != nil {
2✔
53
                return -1, fmt.Errorf("Orchestrator returned invalid response body '%v'", string(body))
1✔
54
        }
1✔
55
        folderId := c.findFolderId(result)
1✔
56
        if folderId == nil {
2✔
57
                return -1, fmt.Errorf("Could not find 'Shared' orchestrator folder.")
1✔
58
        }
1✔
59
        return *folderId, nil
1✔
60
}
61

62
func (c OrchestratorClient) createGetFolderRequest() *network.HttpRequest {
1✔
63
        uri := c.baseUri + "/odata/Folders"
1✔
64
        header := http.Header{
1✔
65
                "Content-Type": {"application/json"},
1✔
66
        }
1✔
67
        return network.NewHttpGetRequest(uri, c.toAuthorization(c.token), header)
1✔
68
}
1✔
69

70
func (c OrchestratorClient) findFolderId(result getFoldersResponseJson) *int {
1✔
71
        for _, value := range result.Value {
2✔
72
                if value.Name == "Shared" {
2✔
73
                        return &value.Id
1✔
74
                }
1✔
75
        }
76
        if len(result.Value) > 0 {
1✔
77
                return &result.Value[0].Id
×
78
        }
×
79
        return nil
1✔
80
}
81

82
func (c OrchestratorClient) Upload(file stream.Stream, uploadBar *visualization.ProgressBar) error {
1✔
83
        context, cancel := context.WithCancelCause(context.Background())
1✔
84
        request := c.createUploadRequest(file, 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 response.Body.Close()
1✔
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, 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
        uri := c.baseUri + "/odata/Processes/UiPath.Server.Configuration.OData.UploadPackage"
1✔
111
        header := http.Header{
1✔
112
                "Content-Type": {contentType},
1✔
113
        }
1✔
114
        return network.NewHttpPostRequest(uri, c.toAuthorization(c.token), header, uploadReader, -1)
1✔
115
}
1✔
116

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

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

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

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

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

183
func (c OrchestratorClient) createGetReleasesRequest(folderId int, processKey string) *network.HttpRequest {
1✔
184
        uri := c.baseUri + "/odata/Releases?$filter=ProcessKey%20eq%20'" + processKey + "'"
1✔
185
        header := http.Header{
1✔
186
                "Content-Type":                {"application/json"},
1✔
187
                "X-Uipath-Organizationunitid": {strconv.Itoa(folderId)},
1✔
188
        }
1✔
189
        return network.NewHttpGetRequest(uri, c.toAuthorization(c.token), header)
1✔
190
}
1✔
191

192
func (c OrchestratorClient) CreateOrUpdateRelease(folderId int, processKey string, processVersion string) (int, error) {
1✔
193
        releases, err := c.GetReleases(folderId, processKey)
1✔
194
        if err != nil {
2✔
195
                return -1, err
1✔
196
        }
1✔
197
        if len(releases) > 0 {
2✔
198
                return c.UpdateRelease(folderId, releases[0].Id, processKey, processVersion)
1✔
199
        }
1✔
200
        return c.CreateRelease(folderId, processKey, processVersion)
1✔
201
}
202

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

222
        var result createReleaseResponseJson
1✔
223
        err = json.Unmarshal(body, &result)
1✔
224
        if err != nil {
2✔
225
                return -1, fmt.Errorf("Orchestrator returned invalid response body '%v'", string(body))
1✔
226
        }
1✔
227
        return result.Id, nil
1✔
228
}
229

230
func (c OrchestratorClient) createNewReleaseRequest(folderId int, processKey string, processVersion string) (*network.HttpRequest, error) {
1✔
231
        json, err := json.Marshal(createReleaseRequestJson{
1✔
232
                Name:           processKey,
1✔
233
                ProcessKey:     processKey,
1✔
234
                ProcessVersion: processVersion,
1✔
235
        })
1✔
236
        if err != nil {
1✔
237
                return nil, err
×
238
        }
×
239
        uri := c.baseUri + "/odata/Releases"
1✔
240
        header := http.Header{
1✔
241
                "Content-Type":                {"application/json"},
1✔
242
                "X-Uipath-Organizationunitid": {strconv.Itoa(folderId)},
1✔
243
        }
1✔
244
        return network.NewHttpPostRequest(uri, c.toAuthorization(c.token), header, bytes.NewBuffer(json), -1), nil
1✔
245
}
246

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

268
func (c OrchestratorClient) createUpdateReleaseRequest(folderId int, releaseId int, processKey string, processVersion string) (*network.HttpRequest, error) {
1✔
269
        json, err := json.Marshal(createReleaseRequestJson{
1✔
270
                Name:           processKey,
1✔
271
                ProcessKey:     processKey,
1✔
272
                ProcessVersion: processVersion,
1✔
273
        })
1✔
274
        if err != nil {
1✔
275
                return nil, err
×
276
        }
×
277
        uri := c.baseUri + fmt.Sprintf("/odata/Releases(%d)", releaseId)
1✔
278
        header := http.Header{
1✔
279
                "Content-Type":                {"application/json"},
1✔
280
                "X-Uipath-Organizationunitid": {strconv.Itoa(folderId)},
1✔
281
        }
1✔
282
        return network.NewHttpPatchRequest(uri, c.toAuthorization(c.token), header, bytes.NewBuffer(json), -1), nil
1✔
283
}
284

285
func (c OrchestratorClient) CreateTestSet(folderId int, releaseId int, processVersion string) (int, error) {
1✔
286
        request, err := c.createTestSetRequest(folderId, releaseId, processVersion)
1✔
287
        if err != nil {
1✔
288
                return -1, err
×
289
        }
×
290
        client := network.NewHttpClient(c.logger, c.httpClientSettings())
1✔
291
        response, err := client.Send(request)
1✔
292
        if err != nil {
2✔
293
                return -1, err
1✔
294
        }
1✔
295
        defer response.Body.Close()
1✔
296
        body, err := io.ReadAll(response.Body)
1✔
297
        if err != nil {
1✔
298
                return -1, fmt.Errorf("Error reading response: %w", err)
×
299
        }
×
300
        if response.StatusCode != http.StatusCreated {
1✔
301
                return -1, fmt.Errorf("Orchestrator returned status code '%v' and body '%v'", response.StatusCode, string(body))
×
302
        }
×
303
        return strconv.Atoi(string(body))
1✔
304
}
305

306
func (c OrchestratorClient) createTestSetRequest(folderId int, releaseId int, processVersion string) (*network.HttpRequest, error) {
1✔
307
        json, err := json.Marshal(createTestSetRequestJson{
1✔
308
                ReleaseId:     releaseId,
1✔
309
                VersionNumber: processVersion,
1✔
310
        })
1✔
311
        if err != nil {
1✔
312
                return nil, err
×
313
        }
×
314
        uri := c.baseUri + "/api/TestAutomation/CreateTestSetForReleaseVersion"
1✔
315
        header := http.Header{
1✔
316
                "Content-Type":                {"application/json"},
1✔
317
                "X-Uipath-Organizationunitid": {strconv.Itoa(folderId)},
1✔
318
        }
1✔
319
        return network.NewHttpPostRequest(uri, c.toAuthorization(c.token), header, bytes.NewBuffer(json), -1), nil
1✔
320
}
321

322
func (c OrchestratorClient) ExecuteTestSet(folderId int, testSetId int) (int, error) {
1✔
323
        request := c.createExecuteTestSetRequest(folderId, testSetId)
1✔
324
        client := network.NewHttpClient(c.logger, c.httpClientSettings())
1✔
325
        response, err := client.Send(request)
1✔
326
        if err != nil {
2✔
327
                return -1, err
1✔
328
        }
1✔
329
        defer response.Body.Close()
1✔
330
        body, err := io.ReadAll(response.Body)
1✔
331
        if err != nil {
1✔
332
                return -1, fmt.Errorf("Error reading response: %w", err)
×
333
        }
×
334
        if response.StatusCode != http.StatusOK {
1✔
335
                return -1, fmt.Errorf("Orchestrator returned status code '%v' and body '%v'", response.StatusCode, string(body))
×
336
        }
×
337
        return strconv.Atoi(string(body))
1✔
338
}
339

340
func (c OrchestratorClient) createExecuteTestSetRequest(folderId int, testSetId int) *network.HttpRequest {
1✔
341
        uri := c.baseUri + fmt.Sprintf("/api/TestAutomation/StartTestSetExecution?testSetId=%d&triggerType=ExternalTool", testSetId)
1✔
342
        header := http.Header{
1✔
343
                "Content-Type":                {"application/json"},
1✔
344
                "X-Uipath-Organizationunitid": {strconv.Itoa(folderId)},
1✔
345
        }
1✔
346
        return network.NewHttpPostRequest(uri, c.toAuthorization(c.token), header, bytes.NewReader([]byte{}), -1)
1✔
347
}
1✔
348

349
func (c OrchestratorClient) WaitForTestExecutionToFinish(folderId int, executionId int, timeout time.Duration, statusFunc func(TestExecution)) (*TestExecution, error) {
1✔
350
        startTime := time.Now()
1✔
351
        for {
2✔
352
                execution, err := c.GetTestExecution(folderId, executionId)
1✔
353
                if err != nil {
1✔
354
                        return nil, err
×
355
                }
×
356
                statusFunc(*execution)
1✔
357
                if execution.IsCompleted() {
2✔
358
                        return execution, nil
1✔
359
                }
1✔
360
                if time.Since(startTime) >= timeout {
2✔
361
                        return nil, fmt.Errorf("Timeout waiting for test execution '%d' to finish.", executionId)
1✔
362
                }
1✔
363
                time.Sleep(1 * time.Second)
1✔
364
        }
365
}
366

367
func (c OrchestratorClient) GetTestSet(folderId int, testSetId int) (*TestSet, error) {
1✔
368
        request := c.createGetTestSetRequest(folderId, testSetId)
1✔
369
        client := network.NewHttpClient(c.logger, c.httpClientSettings())
1✔
370
        response, err := client.Send(request)
1✔
371
        if err != nil {
1✔
NEW
372
                return nil, err
×
NEW
373
        }
×
374
        defer response.Body.Close()
1✔
375
        body, err := io.ReadAll(response.Body)
1✔
376
        if err != nil {
1✔
NEW
377
                return nil, fmt.Errorf("Error reading response: %w", err)
×
NEW
378
        }
×
379
        if response.StatusCode != http.StatusOK {
1✔
NEW
380
                return nil, fmt.Errorf("Orchestrator returned status code '%v' and body '%v'", response.StatusCode, string(body))
×
NEW
381
        }
×
382

383
        var result getTestSetResponseJson
1✔
384
        err = json.Unmarshal(body, &result)
1✔
385
        if err != nil {
1✔
NEW
386
                return nil, fmt.Errorf("Orchestrator returned invalid response body '%v'", string(body))
×
NEW
387
        }
×
388
        return c.convertToTestSet(result), nil
1✔
389
}
390

391
func (c OrchestratorClient) createGetTestSetRequest(folderId int, testSetId int) *network.HttpRequest {
1✔
392
        uri := c.baseUri + fmt.Sprintf("/odata/TestSets(%d)?$expand=TestCases($expand=Definition;$select=Id,Definition,DefinitionId,ReleaseId,VersionNumber),Packages&$select=TestCases,Name", testSetId)
1✔
393
        header := http.Header{
1✔
394
                "Content-Type":                {"application/json"},
1✔
395
                "X-Uipath-Organizationunitid": {strconv.Itoa(folderId)},
1✔
396
        }
1✔
397
        return network.NewHttpGetRequest(uri, c.toAuthorization(c.token), header)
1✔
398
}
1✔
399

400
func (c OrchestratorClient) GetTestExecution(folderId int, executionId int) (*TestExecution, error) {
1✔
401
        request := c.createGetTestExecutionRequest(folderId, executionId)
1✔
402
        client := network.NewHttpClient(c.logger, c.httpClientSettings())
1✔
403
        response, err := client.Send(request)
1✔
404
        if err != nil {
1✔
405
                return nil, err
×
406
        }
×
407
        defer response.Body.Close()
1✔
408
        body, err := io.ReadAll(response.Body)
1✔
409
        if err != nil {
1✔
410
                return nil, fmt.Errorf("Error reading response: %w", err)
×
411
        }
×
412
        if response.StatusCode != http.StatusOK {
1✔
413
                return nil, fmt.Errorf("Orchestrator returned status code '%v' and body '%v'", response.StatusCode, string(body))
×
414
        }
×
415

416
        var result getTestExecutionResponseJson
1✔
417
        err = json.Unmarshal(body, &result)
1✔
418
        if err != nil {
1✔
419
                return nil, fmt.Errorf("Orchestrator returned invalid response body '%v'", string(body))
×
420
        }
×
421
        return c.convertToTestExecution(result), nil
1✔
422
}
423

424
func (c OrchestratorClient) createGetTestExecutionRequest(folderId int, executionId int) *network.HttpRequest {
1✔
425
        uri := c.baseUri + fmt.Sprintf("/odata/TestSetExecutions(%d)?$expand=TestCaseExecutions($expand=TestCaseAssertions)", executionId)
1✔
426
        header := http.Header{
1✔
427
                "Content-Type":                {"application/json"},
1✔
428
                "X-Uipath-Organizationunitid": {strconv.Itoa(folderId)},
1✔
429
        }
1✔
430
        return network.NewHttpGetRequest(uri, c.toAuthorization(c.token), header)
1✔
431
}
1✔
432

433
func (c OrchestratorClient) GetRobotLogs(folderId int, jobKey string) ([]RobotLog, error) {
1✔
434
        request := c.createGetRobotLogsRequest(folderId, jobKey)
1✔
435
        client := network.NewHttpClient(c.logger, c.httpClientSettings())
1✔
436
        response, err := client.Send(request)
1✔
437
        if err != nil {
1✔
NEW
438
                return []RobotLog{}, err
×
NEW
439
        }
×
440
        defer response.Body.Close()
1✔
441
        body, err := io.ReadAll(response.Body)
1✔
442
        if err != nil {
1✔
NEW
443
                return []RobotLog{}, fmt.Errorf("Error reading response: %w", err)
×
NEW
444
        }
×
445
        if response.StatusCode != http.StatusOK {
1✔
NEW
446
                return []RobotLog{}, fmt.Errorf("Orchestrator returned status code '%v' and body '%v'", response.StatusCode, string(body))
×
NEW
447
        }
×
448

449
        var result getRobotLogsResponseJson
1✔
450
        err = json.Unmarshal(body, &result)
1✔
451
        if err != nil {
1✔
NEW
452
                return []RobotLog{}, fmt.Errorf("Orchestrator returned invalid response body '%v'", string(body))
×
NEW
453
        }
×
454
        return c.convertToRobotLogs(result), nil
1✔
455
}
456

457
func (c OrchestratorClient) createGetRobotLogsRequest(folderId int, jobKey string) *network.HttpRequest {
1✔
458
        uri := c.baseUri + "/odata/RobotLogs?$filter=JobKey%20eq%20" + jobKey
1✔
459
        header := http.Header{
1✔
460
                "X-Uipath-Organizationunitid": {strconv.Itoa(folderId)},
1✔
461
        }
1✔
462
        return network.NewHttpGetRequest(uri, c.toAuthorization(c.token), header)
1✔
463
}
1✔
464

465
func (c OrchestratorClient) GetReadUrl(folderId int, bucketId int, path string) (string, error) {
1✔
466
        request := c.createReadUrlRequest(folderId, bucketId, path)
1✔
467
        client := network.NewHttpClient(c.logger, c.httpClientSettings())
1✔
468
        response, err := client.Send(request)
1✔
469
        if err != nil {
1✔
470
                return "", fmt.Errorf("Error sending request: %w", err)
×
471
        }
×
472
        defer response.Body.Close()
1✔
473
        body, err := io.ReadAll(response.Body)
1✔
474
        if err != nil {
1✔
475
                return "", fmt.Errorf("Error reading response: %w", err)
×
476
        }
×
477
        if response.StatusCode != http.StatusOK {
2✔
478
                return "", fmt.Errorf("Orchestrator returned status code '%v' and body '%v'", response.StatusCode, string(body))
1✔
479
        }
1✔
480
        var result urlResponse
1✔
481
        err = json.Unmarshal(body, &result)
1✔
482
        if err != nil {
1✔
483
                return "", fmt.Errorf("Error parsing json response: %w", err)
×
484
        }
×
485
        return result.Uri, nil
1✔
486
}
487

488
func (c OrchestratorClient) createReadUrlRequest(folderId int, bucketId int, path string) *network.HttpRequest {
1✔
489
        uri := c.baseUri + fmt.Sprintf("/odata/Buckets(%d)/UiPath.Server.Configuration.OData.GetReadUri?path=%s", bucketId, path)
1✔
490
        header := http.Header{
1✔
491
                "X-UiPath-OrganizationUnitId": {fmt.Sprintf("%d", folderId)},
1✔
492
        }
1✔
493
        return network.NewHttpGetRequest(uri, c.toAuthorization(c.token), header)
1✔
494
}
1✔
495

496
func (c OrchestratorClient) GetWriteUrl(folderId int, bucketId int, path string) (string, error) {
1✔
497
        request := c.createWriteUrlRequest(folderId, bucketId, path)
1✔
498
        client := network.NewHttpClient(c.logger, c.httpClientSettings())
1✔
499
        response, err := client.Send(request)
1✔
500
        if err != nil {
1✔
501
                return "", err
×
502
        }
×
503
        defer response.Body.Close()
1✔
504
        body, err := io.ReadAll(response.Body)
1✔
505
        if err != nil {
1✔
506
                return "", fmt.Errorf("Error reading response: %w", err)
×
507
        }
×
508
        if response.StatusCode != http.StatusOK {
2✔
509
                return "", fmt.Errorf("Orchestrator returned status code '%v' and body '%v'", response.StatusCode, string(body))
1✔
510
        }
1✔
511
        var result urlResponse
1✔
512
        err = json.Unmarshal(body, &result)
1✔
513
        if err != nil {
1✔
514
                return "", fmt.Errorf("Error parsing json response: %w", err)
×
515
        }
×
516
        return result.Uri, nil
1✔
517
}
518

519
func (c OrchestratorClient) createWriteUrlRequest(folderId int, bucketId int, path string) *network.HttpRequest {
1✔
520
        uri := c.baseUri + fmt.Sprintf("/odata/Buckets(%d)/UiPath.Server.Configuration.OData.GetWriteUri?path=%s", bucketId, path)
1✔
521
        header := http.Header{
1✔
522
                "X-UiPath-OrganizationUnitId": {fmt.Sprintf("%d", folderId)},
1✔
523
        }
1✔
524
        return network.NewHttpGetRequest(uri, c.toAuthorization(c.token), header)
1✔
525
}
1✔
526

527
func (c OrchestratorClient) convertToRobotLogs(json getRobotLogsResponseJson) []RobotLog {
1✔
528
        logs := []RobotLog{}
1✔
529
        for _, l := range json.Value {
2✔
530
                logs = append(logs, c.convertToRobotLog(l))
1✔
531
        }
1✔
532
        return logs
1✔
533
}
534

535
func (c OrchestratorClient) convertToRobotLog(json getRobotLogJson) RobotLog {
1✔
536
        return *NewRobotLog(
1✔
537
                json.Id,
1✔
538
                json.Level,
1✔
539
                json.WindowsIdentity,
1✔
540
                json.ProcessName,
1✔
541
                json.TimeStamp,
1✔
542
                json.Message,
1✔
543
                json.RobotName,
1✔
544
                json.HostMachineName,
1✔
545
                json.MachineId,
1✔
546
                json.MachineKey,
1✔
547
                json.RuntimeType,
1✔
548
        )
1✔
549
}
1✔
550

551
func (c OrchestratorClient) convertToTestSet(json getTestSetResponseJson) *TestSet {
1✔
552
        testCases := []TestCase{}
1✔
553
        for _, c := range json.TestCases {
2✔
554
                testCase := NewTestCase(
1✔
555
                        c.Id,
1✔
556
                        c.Definition.Name,
1✔
557
                        c.Definition.PackageIdentifier,
1✔
558
                )
1✔
559
                testCases = append(testCases, *testCase)
1✔
560
        }
1✔
561
        testPackages := []TestPackage{}
1✔
562
        for _, p := range json.Packages {
2✔
563
                testPackage := NewTestPackage(
1✔
564
                        p.PackageIdentifier,
1✔
565
                        p.VersionMask,
1✔
566
                )
1✔
567
                testPackages = append(testPackages, *testPackage)
1✔
568
        }
1✔
569
        return NewTestSet(json.Id, testCases, testPackages)
1✔
570
}
571

572
func (c OrchestratorClient) convertToTestExecution(json getTestExecutionResponseJson) *TestExecution {
1✔
573
        return NewTestExecution(
1✔
574
                json.Id,
1✔
575
                json.Status,
1✔
576
                json.TestSetId,
1✔
577
                json.Name,
1✔
578
                json.StartTime,
1✔
579
                json.EndTime,
1✔
580
                c.convertToTestCaseExecutions(json.TestCaseExecutions),
1✔
581
        )
1✔
582
}
1✔
583

584
func (c OrchestratorClient) convertToTestCaseExecutions(json []testCaseExecutionJson) []TestCaseExecution {
1✔
585
        executions := []TestCaseExecution{}
1✔
586
        for _, v := range json {
2✔
587
                execution := NewTestCaseExecution(
1✔
588
                        v.Id,
1✔
589
                        v.Status,
1✔
590
                        v.TestCaseId,
1✔
591
                        v.VersionNumber,
1✔
592
                        v.EntryPointPath,
1✔
593
                        v.Info,
1✔
594
                        v.StartTime,
1✔
595
                        v.EndTime,
1✔
596
                        v.JobId,
1✔
597
                        v.JobKey,
1✔
598
                        v.InputArguments,
1✔
599
                        v.OutputArguments,
1✔
600
                        v.DataVariationIdentifier,
1✔
601
                        c.convertToTestCaseAssertions(v.TestCaseAssertions),
1✔
602
                        nil,
1✔
603
                )
1✔
604
                executions = append(executions, *execution)
1✔
605
        }
1✔
606
        return executions
1✔
607
}
608

609
func (c OrchestratorClient) convertToTestCaseAssertions(json []testCaseExecutionAssertionJson) []TestCaseAssertion {
1✔
610
        assertions := []TestCaseAssertion{}
1✔
611
        for _, v := range json {
1✔
NEW
612
                assertion := NewTestCaseAssertion(v.Message, v.Succeeded)
×
NEW
613
                assertions = append(assertions, *assertion)
×
NEW
614
        }
×
615
        return assertions
1✔
616
}
617

618
func (c OrchestratorClient) progressReader(text string, completedText string, reader io.Reader, length int64, progressBar *visualization.ProgressBar) io.Reader {
1✔
619
        if progressBar == nil || length < 10*1024*1024 {
2✔
620
                return reader
1✔
621
        }
1✔
622
        return visualization.NewProgressReader(reader, func(progress visualization.Progress) {
2✔
623
                displayText := text
1✔
624
                if progress.Completed {
2✔
625
                        displayText = completedText
1✔
626
                }
1✔
627
                progressBar.UpdateProgress(displayText, progress.BytesRead, length, progress.BytesPerSecond)
1✔
628
        })
629
}
630

631
func (c OrchestratorClient) httpClientSettings() network.HttpClientSettings {
1✔
632
        return *network.NewHttpClientSettings(
1✔
633
                c.debug,
1✔
634
                c.settings.OperationId,
1✔
635
                c.settings.Timeout,
1✔
636
                c.settings.MaxAttempts,
1✔
637
                c.settings.Insecure)
1✔
638
}
1✔
639

640
func (c OrchestratorClient) toAuthorization(token *auth.AuthToken) *network.Authorization {
1✔
641
        if token == nil {
2✔
642
                return nil
1✔
643
        }
1✔
644
        return network.NewAuthorization(token.Type, token.Value)
1✔
645
}
646

647
type createReleaseRequestJson struct {
648
        Name           string `json:"name"`
649
        ProcessKey     string `json:"processKey"`
650
        ProcessVersion string `json:"processVersion"`
651
}
652

653
type createTestSetRequestJson struct {
654
        ReleaseId     int    `json:"releaseId"`
655
        VersionNumber string `json:"versionNumber"`
656
}
657

658
type getFoldersResponseJson struct {
659
        Value []getFoldersResponseValueJson `json:"value"`
660
}
661

662
type getFoldersResponseValueJson struct {
663
        Id   int    `json:"Id"`
664
        Name string `json:"FullyQualifiedName"`
665
}
666

667
type getReleasesResponseJson struct {
668
        Value []getReleasesResponseValueJson `json:"value"`
669
}
670

671
type getReleasesResponseValueJson struct {
672
        Id   int    `json:"Id"`
673
        Name string `json:"Name"`
674
}
675

676
type createReleaseResponseJson struct {
677
        Id int `json:"id"`
678
}
679

680
type getTestSetResponseJson struct {
681
        Id        int                      `json:"Id"`
682
        TestCases []getTestSetTestCaseJson `json:"TestCases"`
683
        Packages  []getTestSetPackagesJson `json:"Packages"`
684
}
685

686
type getTestSetTestCaseJson struct {
687
        Id         int                              `json:"Id"`
688
        Definition getTestSetTestCaseDefinitionJson `json:"Definition"`
689
}
690

691
type getTestSetPackagesJson struct {
692
        PackageIdentifier string `json:"PackageIdentifier"`
693
        VersionMask       string `json:"VersionMask"`
694
}
695

696
type getTestSetTestCaseDefinitionJson struct {
697
        Name              string `json:"Name"`
698
        PackageIdentifier string `json:"PackageIdentifier"`
699
}
700

701
type getTestExecutionResponseJson struct {
702
        Id                 int                     `json:"Id"`
703
        Status             string                  `json:"Status"`
704
        TestSetId          int                     `json:"TestSetId"`
705
        Name               string                  `json:"Name"`
706
        StartTime          time.Time               `json:"StartTime"`
707
        EndTime            time.Time               `json:"EndTime"`
708
        TestCaseExecutions []testCaseExecutionJson `json:"TestCaseExecutions"`
709
}
710

711
type testCaseExecutionJson struct {
712
        Id                      int                              `json:"Id"`
713
        Status                  string                           `json:"Status"`
714
        TestCaseId              int                              `json:"TestCaseId"`
715
        VersionNumber           string                           `json:"VersionNumber"`
716
        EntryPointPath          string                           `json:"EntryPointPath"`
717
        Info                    string                           `json:"Info"`
718
        StartTime               time.Time                        `json:"StartTime"`
719
        EndTime                 time.Time                        `json:"EndTime"`
720
        JobId                   int                              `json:"JobId"`
721
        JobKey                  string                           `json:"JobKey"`
722
        InputArguments          string                           `json:"InputArguments"`
723
        OutputArguments         string                           `json:"OutputArguments"`
724
        DataVariationIdentifier string                           `json:"DataVariationIdentifier"`
725
        TestCaseAssertions      []testCaseExecutionAssertionJson `json:"TestCaseAssertions"`
726
}
727

728
type testCaseExecutionAssertionJson struct {
729
        Message   string `json:"Message"`
730
        Succeeded bool   `json:"Succeeded"`
731
}
732

733
type getRobotLogsResponseJson struct {
734
        Value []getRobotLogJson `json:"value"`
735
}
736

737
type getRobotLogJson struct {
738
        Id              int       `json:"Id"`
739
        Level           string    `json:"Level"`
740
        WindowsIdentity string    `json:"WindowsIdentity"`
741
        ProcessName     string    `json:"ProcessName"`
742
        TimeStamp       time.Time `json:"TimeStamp"`
743
        Message         string    `json:"Message"`
744
        RobotName       string    `json:"RobotName"`
745
        HostMachineName string    `json:"HostMachineName"`
746
        MachineId       int       `json:"MachineId"`
747
        MachineKey      string    `json:"MachineKey"`
748
        RuntimeType     string    `json:"RuntimeType"`
749
}
750

751
type urlResponse struct {
752
        Uri string `json:"Uri"`
753
}
754

755
func NewOrchestratorClient(baseUri string, token *auth.AuthToken, debug bool, settings plugin.ExecutionSettings, logger log.Logger) *OrchestratorClient {
1✔
756
        return &OrchestratorClient{baseUri, token, debug, settings, logger}
1✔
757
}
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