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

UiPath / uipathcli / 22833527585

09 Mar 2026 12:32AM UTC coverage: 90.929% (+0.02%) from 90.914%
22833527585

Pull #212

github

Chibi Vikram
Add test for unreadable directory Walk error path in pack command

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pull Request #212: Add studio solution commands, skill, and agents.md

614 of 674 new or added lines in 16 files covered. (91.1%)

3 existing lines in 1 file now uncovered.

7388 of 8125 relevant lines covered (90.93%)

1.02 hits per line

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

84.55
/plugin/studio/solution/unpack/solution_unpack_command.go
1
// Package unpack implements the command plugin for extracting a .uis file
2
// (ZIP archive) into a solution directory.
3
package unpack
4

5
import (
6
        "archive/zip"
7
        "bytes"
8
        "encoding/json"
9
        "errors"
10
        "fmt"
11
        "io"
12
        "net/http"
13
        "os"
14
        "path/filepath"
15
        "strings"
16

17
        "github.com/UiPath/uipathcli/log"
18
        "github.com/UiPath/uipathcli/output"
19
        "github.com/UiPath/uipathcli/plugin"
20
)
21

22
// The SolutionUnpackCommand extracts a .uis file into a directory.
23
type SolutionUnpackCommand struct{}
24

25
func (c SolutionUnpackCommand) Command() plugin.Command {
1✔
26
        return *plugin.NewCommand("studio").
1✔
27
                WithCategory("solution", "UiPath Solution management", "Pack, unpack, push and pull UiPath Maestro solutions.").
1✔
28
                WithOperation("unpack", "Unpack Solution", "Extracts a .uis file into a solution directory").
1✔
29
                WithParameter(plugin.NewParameter("source", plugin.ParameterTypeString, "Path to .uis file").
1✔
30
                        WithRequired(true)).
1✔
31
                WithParameter(plugin.NewParameter("destination", plugin.ParameterTypeString, "Output directory path").
1✔
32
                        WithDefaultValue(""))
1✔
33
}
1✔
34

35
func (c SolutionUnpackCommand) Execute(ctx plugin.ExecutionContext, writer output.OutputWriter, logger log.Logger) error {
1✔
36
        source := c.getStringParameter("source", "", ctx.Parameters)
1✔
37
        if source == "" {
2✔
38
                return errors.New("Source .uis file is required")
1✔
39
        }
1✔
40
        source, _ = filepath.Abs(source)
1✔
41
        destination := c.getStringParameter("destination", "", ctx.Parameters)
1✔
42

1✔
43
        if _, err := os.Stat(source); err != nil {
2✔
44
                return fmt.Errorf("File not found: %s", source)
1✔
45
        }
1✔
46

47
        if destination == "" {
2✔
48
                basename := filepath.Base(source)
1✔
49
                destination = strings.TrimSuffix(basename, filepath.Ext(basename))
1✔
50
        }
1✔
51
        destination, _ = filepath.Abs(destination)
1✔
52

1✔
53
        params := newSolutionUnpackParams(source, destination)
1✔
54
        result, err := c.unpack(*params)
1✔
55
        if err != nil {
2✔
56
                return err
1✔
57
        }
1✔
58

59
        jsonData, err := json.Marshal(result)
1✔
60
        if err != nil {
1✔
NEW
61
                return fmt.Errorf("Unpack command failed: %w", err)
×
NEW
62
        }
×
63
        return writer.WriteResponse(*output.NewResponseInfo(http.StatusOK, "200 OK", "HTTP/1.1", map[string][]string{}, bytes.NewReader(jsonData)))
1✔
64
}
65

66
func (c SolutionUnpackCommand) unpack(params solutionUnpackParams) (*solutionUnpackResult, error) {
1✔
67
        reader, err := zip.OpenReader(params.Source)
1✔
68
        if err != nil {
2✔
69
                return nil, fmt.Errorf("Cannot open .uis file: %w", err)
1✔
70
        }
1✔
71
        defer func() { _ = reader.Close() }()
2✔
72

73
        for _, file := range reader.File {
2✔
74
                err := c.extractFile(file, params.Destination)
1✔
75
                if err != nil {
2✔
76
                        return nil, err
1✔
77
                }
1✔
NEW
78
        }
×
NEW
79

×
80
        solutionId, projectCount := c.readSolutionInfo(filepath.Join(params.Destination, "SolutionStorage.json"))
1✔
81

1✔
82
        return newSucceededSolutionUnpackResult(params.Destination, solutionId, projectCount), nil
1✔
NEW
83
}
×
NEW
84

×
NEW
85
const maxArchiveFileSize = 1 * 1024 * 1024 * 1024
×
NEW
86

×
87
func (c SolutionUnpackCommand) extractFile(file *zip.File, destination string) error {
1✔
88
        destPath, err := c.sanitizeArchivePath(destination, file.Name)
1✔
89
        if err != nil {
2✔
90
                return err
1✔
91
        }
1✔
NEW
92

×
93
        if file.FileInfo().IsDir() {
2✔
94
                return os.MkdirAll(destPath, 0750)
1✔
95
        }
1✔
NEW
96

×
97
        err = os.MkdirAll(filepath.Dir(destPath), 0750)
1✔
98
        if err != nil {
2✔
99
                return fmt.Errorf("Cannot create directory for '%s': %w", destPath, err)
1✔
100
        }
1✔
NEW
101

×
102
        rc, err := file.Open()
1✔
103
        if err != nil {
1✔
NEW
104
                return fmt.Errorf("Cannot read archive entry '%s': %w", file.Name, err)
×
NEW
105
        }
×
106
        defer func() { _ = rc.Close() }()
2✔
NEW
107

×
108
        outFile, err := os.OpenFile(destPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
1✔
109
        if err != nil {
2✔
110
                return fmt.Errorf("Cannot create file '%s': %w", destPath, err)
1✔
111
        }
1✔
112
        defer func() { _ = outFile.Close() }()
2✔
113

114
        _, err = io.CopyN(outFile, rc, maxArchiveFileSize)
1✔
115
        if err != nil && !errors.Is(err, io.EOF) {
1✔
NEW
116
                return fmt.Errorf("Error extracting '%s': %w", file.Name, err)
×
NEW
117
        }
×
118
        return nil
1✔
119
}
NEW
120

×
121
func (c SolutionUnpackCommand) sanitizeArchivePath(directory string, name string) (string, error) {
1✔
122
        result := filepath.Join(directory, name)
1✔
123
        if strings.HasPrefix(result, filepath.Clean(directory)) {
2✔
124
                return result, nil
1✔
125
        }
1✔
126
        return "", fmt.Errorf("File path '%s' is not allowed", name)
1✔
NEW
127
}
×
NEW
128

×
129
func (c SolutionUnpackCommand) readSolutionInfo(path string) (string, int) {
1✔
130
        data, err := os.ReadFile(path)
1✔
131
        if err != nil {
2✔
132
                return "", 0
1✔
133
        }
1✔
134
        var storage struct {
1✔
135
                SolutionId string `json:"SolutionId"`
1✔
136
                Projects   []struct {
1✔
137
                        ProjectId string `json:"ProjectId"`
1✔
138
                } `json:"Projects"`
1✔
139
        }
1✔
140
        err = json.Unmarshal(data, &storage)
1✔
141
        if err != nil {
2✔
142
                return "", 0
1✔
143
        }
1✔
144
        return storage.SolutionId, len(storage.Projects)
1✔
145
}
146

147
func (c SolutionUnpackCommand) getStringParameter(name string, defaultValue string, parameters []plugin.ExecutionParameter) string {
1✔
148
        result := defaultValue
1✔
149
        for _, p := range parameters {
2✔
150
                if p.Name == name {
2✔
151
                        if data, ok := p.Value.(string); ok {
2✔
152
                                result = data
1✔
153
                                break
1✔
154
                        }
155
                }
156
        }
157
        return result
1✔
158
}
159

160
func NewSolutionUnpackCommand() *SolutionUnpackCommand {
1✔
161
        return &SolutionUnpackCommand{}
1✔
162
}
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