@@ -5,10 +5,12 @@ import (
5
5
"net/http"
6
6
"os"
7
7
"path/filepath"
8
+ "slices"
8
9
"testing"
9
10
10
11
"github.com/spf13/afero"
11
12
"github.com/stretchr/testify/require"
13
+ "golang.org/x/xerrors"
12
14
13
15
"github.com/coder/coder/v2/agent"
14
16
"github.com/coder/coder/v2/agent/agenttest"
@@ -19,23 +21,57 @@ import (
19
21
20
22
type testFs struct {
21
23
afero.Fs
22
- deny string
24
+ deny [] string
23
25
}
24
26
25
- func newTestFs (base afero.Fs , deny string ) * testFs {
27
+ func newTestFs (base afero.Fs , deny ... string ) * testFs {
26
28
return & testFs {
27
29
Fs : base ,
28
30
deny : deny ,
29
31
}
30
32
}
31
33
32
34
func (fs * testFs ) Open (name string ) (afero.File , error ) {
33
- if name == fs .deny {
35
+ if slices . Contains ( fs .deny , name ) {
34
36
return nil , os .ErrPermission
35
37
}
36
38
return fs .Fs .Open (name )
37
39
}
38
40
41
+ func (fs * testFs ) Create (name string ) (afero.File , error ) {
42
+ if slices .Contains (fs .deny , name ) {
43
+ return nil , os .ErrPermission
44
+ }
45
+ // Unlike os, afero lets you create files where directories already exist and
46
+ // lets you nest them underneath files, somehow.
47
+ stat , err := fs .Fs .Stat (name )
48
+ if err == nil && stat .IsDir () {
49
+ return nil , xerrors .New ("is a directory" )
50
+ }
51
+ stat , err = fs .Fs .Stat (filepath .Dir (name ))
52
+ if err == nil && ! stat .IsDir () {
53
+ return nil , xerrors .New ("not a directory" )
54
+ }
55
+ return fs .Fs .Create (name )
56
+ }
57
+
58
+ func (fs * testFs ) MkdirAll (name string , mode os.FileMode ) error {
59
+ if slices .Contains (fs .deny , name ) {
60
+ return os .ErrPermission
61
+ }
62
+ // Unlike os, afero lets you create directories where files already exist and
63
+ // lets you nest them underneath files somehow.
64
+ stat , err := fs .Fs .Stat (filepath .Dir (name ))
65
+ if err == nil && ! stat .IsDir () {
66
+ return xerrors .New ("not a directory" )
67
+ }
68
+ stat , err = fs .Fs .Stat (name )
69
+ if err == nil && ! stat .IsDir () {
70
+ return xerrors .New ("not a directory" )
71
+ }
72
+ return fs .Fs .MkdirAll (name , mode )
73
+ }
74
+
39
75
func TestReadFile (t * testing.T ) {
40
76
t .Parallel ()
41
77
@@ -200,3 +236,101 @@ func TestReadFile(t *testing.T) {
200
236
})
201
237
}
202
238
}
239
+
240
+ func TestWriteFile (t * testing.T ) {
241
+ t .Parallel ()
242
+
243
+ tmpdir := os .TempDir ()
244
+ noPermsFilePath := filepath .Join (tmpdir , "no-perms-file" )
245
+ noPermsDirPath := filepath .Join (tmpdir , "no-perms-dir" )
246
+ //nolint:dogsled
247
+ conn , _ , _ , fs , _ := setupAgent (t , agentsdk.Manifest {}, 0 , func (_ * agenttest.Client , opts * agent.Options ) {
248
+ opts .Filesystem = newTestFs (opts .Filesystem , noPermsFilePath , noPermsDirPath )
249
+ })
250
+
251
+ dirPath := filepath .Join (tmpdir , "directory" )
252
+ err := fs .MkdirAll (dirPath , 0o755 )
253
+ require .NoError (t , err )
254
+
255
+ filePath := filepath .Join (tmpdir , "file" )
256
+ err = afero .WriteFile (fs , filePath , []byte ("content" ), 0o644 )
257
+ require .NoError (t , err )
258
+
259
+ tests := []struct {
260
+ name string
261
+ path string
262
+ bytes []byte
263
+ errCode int
264
+ error string
265
+ }{
266
+ {
267
+ name : "NoPath" ,
268
+ path : "" ,
269
+ errCode : http .StatusBadRequest ,
270
+ error : "\" path\" is required" ,
271
+ },
272
+ {
273
+ name : "RelativePath" ,
274
+ path : "./relative" ,
275
+ errCode : http .StatusBadRequest ,
276
+ error : "file path must be absolute" ,
277
+ },
278
+ {
279
+ name : "RelativePath" ,
280
+ path : "also-relative" ,
281
+ errCode : http .StatusBadRequest ,
282
+ error : "file path must be absolute" ,
283
+ },
284
+ {
285
+ name : "NonExistent" ,
286
+ path : filepath .Join (tmpdir , "/nested/does-not-exist" ),
287
+ bytes : []byte ("now it does exist" ),
288
+ },
289
+ {
290
+ name : "IsDir" ,
291
+ path : dirPath ,
292
+ errCode : http .StatusBadRequest ,
293
+ error : "is a directory" ,
294
+ },
295
+ {
296
+ name : "IsNotDir" ,
297
+ path : filepath .Join (filePath , "file2" ),
298
+ errCode : http .StatusBadRequest ,
299
+ error : "not a directory" ,
300
+ },
301
+ {
302
+ name : "NoPermissionsFile" ,
303
+ path : noPermsFilePath ,
304
+ errCode : http .StatusForbidden ,
305
+ error : "permission denied" ,
306
+ },
307
+ {
308
+ name : "NoPermissionsDir" ,
309
+ path : filepath .Join (noPermsDirPath , "within-no-perm-dir" ),
310
+ errCode : http .StatusForbidden ,
311
+ error : "permission denied" ,
312
+ },
313
+ }
314
+
315
+ for _ , tt := range tests {
316
+ t .Run (tt .name , func (t * testing.T ) {
317
+ t .Parallel ()
318
+
319
+ ctx , cancel := context .WithTimeout (context .Background (), testutil .WaitLong )
320
+ defer cancel ()
321
+
322
+ err := conn .WriteFile (ctx , tt .path , tt .bytes )
323
+ if tt .errCode != 0 {
324
+ require .Error (t , err )
325
+ cerr := coderdtest .SDKError (t , err )
326
+ require .Equal (t , tt .errCode , cerr .StatusCode ())
327
+ require .Contains (t , cerr .Error (), tt .error )
328
+ } else {
329
+ require .NoError (t , err )
330
+ b , err := afero .ReadFile (fs , tt .path )
331
+ require .NoError (t , err )
332
+ require .Equal (t , tt .bytes , b )
333
+ }
334
+ })
335
+ }
336
+ }
0 commit comments