|
| 1 | +package agent_test |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "io" |
| 6 | + "net/http" |
| 7 | + "os" |
| 8 | + "path/filepath" |
| 9 | + "testing" |
| 10 | + |
| 11 | + "github.com/spf13/afero" |
| 12 | + "github.com/stretchr/testify/require" |
| 13 | + |
| 14 | + "github.com/coder/coder/v2/agent" |
| 15 | + "github.com/coder/coder/v2/agent/agenttest" |
| 16 | + "github.com/coder/coder/v2/coderd/coderdtest" |
| 17 | + "github.com/coder/coder/v2/codersdk/agentsdk" |
| 18 | + "github.com/coder/coder/v2/testutil" |
| 19 | +) |
| 20 | + |
| 21 | +type testFs struct { |
| 22 | + afero.Fs |
| 23 | + // intercept can return an error for testing when a call fails. |
| 24 | + intercept func(call, file string) error |
| 25 | +} |
| 26 | + |
| 27 | +func newTestFs(base afero.Fs, intercept func(call, file string) error) *testFs { |
| 28 | + return &testFs{ |
| 29 | + Fs: base, |
| 30 | + intercept: intercept, |
| 31 | + } |
| 32 | +} |
| 33 | + |
| 34 | +func (fs *testFs) Open(name string) (afero.File, error) { |
| 35 | + if err := fs.intercept("open", name); err != nil { |
| 36 | + return nil, err |
| 37 | + } |
| 38 | + return fs.Fs.Open(name) |
| 39 | +} |
| 40 | + |
| 41 | +func TestReadFile(t *testing.T) { |
| 42 | + t.Parallel() |
| 43 | + |
| 44 | + tmpdir := os.TempDir() |
| 45 | + noPermsFilePath := filepath.Join(tmpdir, "no-perms") |
| 46 | + //nolint:dogsled |
| 47 | + conn, _, _, fs, _ := setupAgent(t, agentsdk.Manifest{}, 0, func(_ *agenttest.Client, opts *agent.Options) { |
| 48 | + opts.Filesystem = newTestFs(opts.Filesystem, func(call, file string) error { |
| 49 | + if file == noPermsFilePath { |
| 50 | + return os.ErrPermission |
| 51 | + } |
| 52 | + return nil |
| 53 | + }) |
| 54 | + }) |
| 55 | + |
| 56 | + dirPath := filepath.Join(tmpdir, "a-directory") |
| 57 | + err := fs.MkdirAll(dirPath, 0o755) |
| 58 | + require.NoError(t, err) |
| 59 | + |
| 60 | + filePath := filepath.Join(tmpdir, "file") |
| 61 | + err = afero.WriteFile(fs, filePath, []byte("content"), 0o644) |
| 62 | + require.NoError(t, err) |
| 63 | + |
| 64 | + imagePath := filepath.Join(tmpdir, "file.png") |
| 65 | + err = afero.WriteFile(fs, imagePath, []byte("not really an image"), 0o644) |
| 66 | + require.NoError(t, err) |
| 67 | + |
| 68 | + tests := []struct { |
| 69 | + name string |
| 70 | + path string |
| 71 | + limit int64 |
| 72 | + offset int64 |
| 73 | + bytes []byte |
| 74 | + mimeType string |
| 75 | + errCode int |
| 76 | + error string |
| 77 | + }{ |
| 78 | + { |
| 79 | + name: "NoPath", |
| 80 | + path: "", |
| 81 | + errCode: http.StatusBadRequest, |
| 82 | + error: "\"path\" is required", |
| 83 | + }, |
| 84 | + { |
| 85 | + name: "RelativePath", |
| 86 | + path: "./relative", |
| 87 | + errCode: http.StatusBadRequest, |
| 88 | + error: "file path must be absolute", |
| 89 | + }, |
| 90 | + { |
| 91 | + name: "RelativePath", |
| 92 | + path: "also-relative", |
| 93 | + errCode: http.StatusBadRequest, |
| 94 | + error: "file path must be absolute", |
| 95 | + }, |
| 96 | + { |
| 97 | + name: "NegativeLimit", |
| 98 | + path: filePath, |
| 99 | + limit: -10, |
| 100 | + errCode: http.StatusBadRequest, |
| 101 | + error: "value is negative", |
| 102 | + }, |
| 103 | + { |
| 104 | + name: "NegativeOffset", |
| 105 | + path: filePath, |
| 106 | + offset: -10, |
| 107 | + errCode: http.StatusBadRequest, |
| 108 | + error: "value is negative", |
| 109 | + }, |
| 110 | + { |
| 111 | + name: "NonExistent", |
| 112 | + path: filepath.Join(tmpdir, "does-not-exist"), |
| 113 | + errCode: http.StatusNotFound, |
| 114 | + error: "file does not exist", |
| 115 | + }, |
| 116 | + { |
| 117 | + name: "IsDir", |
| 118 | + path: dirPath, |
| 119 | + errCode: http.StatusBadRequest, |
| 120 | + error: "not a file", |
| 121 | + }, |
| 122 | + { |
| 123 | + name: "NoPermissions", |
| 124 | + path: noPermsFilePath, |
| 125 | + errCode: http.StatusForbidden, |
| 126 | + error: "permission denied", |
| 127 | + }, |
| 128 | + { |
| 129 | + name: "Defaults", |
| 130 | + path: filePath, |
| 131 | + bytes: []byte("content"), |
| 132 | + mimeType: "application/octet-stream", |
| 133 | + }, |
| 134 | + { |
| 135 | + name: "Limit1", |
| 136 | + path: filePath, |
| 137 | + limit: 1, |
| 138 | + bytes: []byte("c"), |
| 139 | + mimeType: "application/octet-stream", |
| 140 | + }, |
| 141 | + { |
| 142 | + name: "Offset1", |
| 143 | + path: filePath, |
| 144 | + offset: 1, |
| 145 | + bytes: []byte("ontent"), |
| 146 | + mimeType: "application/octet-stream", |
| 147 | + }, |
| 148 | + { |
| 149 | + name: "Limit1Offset2", |
| 150 | + path: filePath, |
| 151 | + limit: 1, |
| 152 | + offset: 2, |
| 153 | + bytes: []byte("n"), |
| 154 | + mimeType: "application/octet-stream", |
| 155 | + }, |
| 156 | + { |
| 157 | + name: "Limit7Offset0", |
| 158 | + path: filePath, |
| 159 | + limit: 7, |
| 160 | + offset: 0, |
| 161 | + bytes: []byte("content"), |
| 162 | + mimeType: "application/octet-stream", |
| 163 | + }, |
| 164 | + { |
| 165 | + name: "Limit100", |
| 166 | + path: filePath, |
| 167 | + limit: 100, |
| 168 | + bytes: []byte("content"), |
| 169 | + mimeType: "application/octet-stream", |
| 170 | + }, |
| 171 | + { |
| 172 | + name: "Offset7", |
| 173 | + path: filePath, |
| 174 | + offset: 7, |
| 175 | + bytes: []byte{}, |
| 176 | + mimeType: "application/octet-stream", |
| 177 | + }, |
| 178 | + { |
| 179 | + name: "Offset100", |
| 180 | + path: filePath, |
| 181 | + offset: 100, |
| 182 | + bytes: []byte{}, |
| 183 | + mimeType: "application/octet-stream", |
| 184 | + }, |
| 185 | + { |
| 186 | + name: "MimeTypePng", |
| 187 | + path: imagePath, |
| 188 | + bytes: []byte("not really an image"), |
| 189 | + mimeType: "image/png", |
| 190 | + }, |
| 191 | + } |
| 192 | + |
| 193 | + for _, tt := range tests { |
| 194 | + t.Run(tt.name, func(t *testing.T) { |
| 195 | + t.Parallel() |
| 196 | + |
| 197 | + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) |
| 198 | + defer cancel() |
| 199 | + |
| 200 | + reader, mimeType, err := conn.ReadFile(ctx, tt.path, tt.offset, tt.limit) |
| 201 | + if tt.errCode != 0 { |
| 202 | + require.Error(t, err) |
| 203 | + cerr := coderdtest.SDKError(t, err) |
| 204 | + require.Contains(t, cerr.Error(), tt.error) |
| 205 | + require.Equal(t, tt.errCode, cerr.StatusCode()) |
| 206 | + } else { |
| 207 | + require.NoError(t, err) |
| 208 | + defer reader.Close() |
| 209 | + bytes, err := io.ReadAll(reader) |
| 210 | + require.NoError(t, err) |
| 211 | + require.Equal(t, tt.bytes, bytes) |
| 212 | + require.Equal(t, tt.mimeType, mimeType) |
| 213 | + } |
| 214 | + }) |
| 215 | + } |
| 216 | +} |
0 commit comments