Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/ee/agent-smith/pkg/detector/filesystem_test.go
2501 views
1
// Copyright (c) 2024 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package detector
6
7
import (
8
"os"
9
"path/filepath"
10
"testing"
11
"time"
12
13
"github.com/gitpod-io/gitpod/agent-smith/pkg/classifier"
14
"github.com/prometheus/client_golang/prometheus"
15
)
16
17
// mockFileClassifier is a mock implementation for testing
18
type mockFileClassifier struct{}
19
20
func (m *mockFileClassifier) MatchesFile(filePath string) (*classifier.Classification, error) {
21
return &classifier.Classification{Level: classifier.LevelNoMatch}, nil
22
}
23
24
func (m *mockFileClassifier) GetFileSignatures() []*classifier.Signature {
25
return nil
26
}
27
28
func (m *mockFileClassifier) Describe(d chan<- *prometheus.Desc) {}
29
func (m *mockFileClassifier) Collect(m2 chan<- prometheus.Metric) {}
30
31
func TestFileDetector_Config_Defaults(t *testing.T) {
32
tests := []struct {
33
name string
34
inputConfig FileScanningConfig
35
expectedConfig FileScanningConfig
36
}{
37
{
38
name: "all defaults",
39
inputConfig: FileScanningConfig{
40
Enabled: true,
41
WorkingArea: "/tmp/test-workspaces",
42
},
43
expectedConfig: FileScanningConfig{
44
Enabled: true,
45
ScanInterval: 5 * time.Minute,
46
MaxFileSize: 1024,
47
WorkingArea: "/tmp/test-workspaces",
48
},
49
},
50
{
51
name: "partial config",
52
inputConfig: FileScanningConfig{
53
Enabled: true,
54
ScanInterval: 10 * time.Minute,
55
MaxFileSize: 2048,
56
WorkingArea: "/tmp/test-workspaces",
57
},
58
expectedConfig: FileScanningConfig{
59
Enabled: true,
60
ScanInterval: 10 * time.Minute,
61
MaxFileSize: 2048,
62
WorkingArea: "/tmp/test-workspaces",
63
},
64
},
65
{
66
name: "all custom values",
67
inputConfig: FileScanningConfig{
68
Enabled: true,
69
ScanInterval: 2 * time.Minute,
70
MaxFileSize: 512,
71
WorkingArea: "/tmp/test-workspaces",
72
},
73
expectedConfig: FileScanningConfig{
74
Enabled: true,
75
ScanInterval: 2 * time.Minute,
76
MaxFileSize: 512,
77
WorkingArea: "/tmp/test-workspaces",
78
},
79
},
80
}
81
82
for _, tt := range tests {
83
t.Run(tt.name, func(t *testing.T) {
84
mockClassifier := &mockFileClassifier{}
85
detector, err := NewfileDetector(tt.inputConfig, mockClassifier)
86
if err != nil {
87
t.Fatalf("failed to create detector: %v", err)
88
}
89
90
if detector.config.ScanInterval != tt.expectedConfig.ScanInterval {
91
t.Errorf("ScanInterval = %v, expected %v", detector.config.ScanInterval, tt.expectedConfig.ScanInterval)
92
}
93
if detector.config.MaxFileSize != tt.expectedConfig.MaxFileSize {
94
t.Errorf("MaxFileSize = %v, expected %v", detector.config.MaxFileSize, tt.expectedConfig.MaxFileSize)
95
}
96
if detector.config.WorkingArea != tt.expectedConfig.WorkingArea {
97
t.Errorf("WorkingArea = %v, expected %v", detector.config.WorkingArea, tt.expectedConfig.WorkingArea)
98
}
99
})
100
}
101
}
102
103
func TestFileDetector_DisabledConfig(t *testing.T) {
104
config := FileScanningConfig{
105
Enabled: false,
106
}
107
108
mockClassifier := &mockFileClassifier{}
109
_, err := NewfileDetector(config, mockClassifier)
110
if err == nil {
111
t.Error("expected error when file scanning is disabled, got nil")
112
}
113
114
expectedError := "file scanning is disabled"
115
if err.Error() != expectedError {
116
t.Errorf("expected error %q, got %q", expectedError, err.Error())
117
}
118
}
119
120
func TestWorkspaceDirectory_Fields(t *testing.T) {
121
wsDir := WorkspaceDirectory{
122
InstanceID: "inst789",
123
Path: "/var/gitpod/workspaces/inst789",
124
}
125
126
if wsDir.InstanceID != "inst789" {
127
t.Errorf("InstanceID = %q, expected %q", wsDir.InstanceID, "inst789")
128
}
129
130
expectedPath := "/var/gitpod/workspaces/inst789"
131
if wsDir.Path != expectedPath {
132
t.Errorf("Path = %q, expected %q", wsDir.Path, expectedPath)
133
}
134
}
135
136
func TestDiscoverWorkspaceDirectories(t *testing.T) {
137
// Create a temporary working area
138
tempDir, err := os.MkdirTemp("", "agent-smith-test-*")
139
if err != nil {
140
t.Fatalf("failed to create temp dir: %v", err)
141
}
142
defer os.RemoveAll(tempDir)
143
144
// Create mock workspace directories
145
workspaceIDs := []string{"ws-abc123", "ws-def456", "ws-ghi789"}
146
for _, wsID := range workspaceIDs {
147
wsDir := filepath.Join(tempDir, wsID)
148
if err := os.Mkdir(wsDir, 0755); err != nil {
149
t.Fatalf("failed to create workspace dir %s: %v", wsDir, err)
150
}
151
}
152
153
// Create some files that should be ignored
154
if err := os.Mkdir(filepath.Join(tempDir, ".hidden"), 0755); err != nil {
155
t.Fatalf("failed to create hidden dir: %v", err)
156
}
157
if err := os.Mkdir(filepath.Join(tempDir, "ws-service-daemon"), 0755); err != nil {
158
t.Fatalf("failed to create daemon dir: %v", err)
159
}
160
161
// Create detector with temp working area
162
config := FileScanningConfig{
163
Enabled: true,
164
WorkingArea: tempDir,
165
}
166
mockClassifier := &mockFileClassifier{}
167
detector, err := NewfileDetector(config, mockClassifier)
168
if err != nil {
169
t.Fatalf("failed to create detector: %v", err)
170
}
171
172
// Test workspace directory discovery
173
workspaceDirs, err := detector.discoverWorkspaceDirectories()
174
if err != nil {
175
t.Fatalf("failed to discover workspace directories: %v", err)
176
}
177
178
// Should find exactly 3 workspace directories (ignoring hidden and daemon dirs)
179
if len(workspaceDirs) != 3 {
180
t.Errorf("found %d workspace directories, expected 3", len(workspaceDirs))
181
}
182
183
// Verify the discovered directories
184
foundIDs := make(map[string]bool)
185
for _, wsDir := range workspaceDirs {
186
foundIDs[wsDir.InstanceID] = true
187
188
// Verify path is correct
189
expectedPath := filepath.Join(tempDir, wsDir.InstanceID)
190
if wsDir.Path != expectedPath {
191
t.Errorf("workspace %s path = %q, expected %q", wsDir.InstanceID, wsDir.Path, expectedPath)
192
}
193
}
194
195
// Verify all expected workspace IDs were found
196
for _, expectedID := range workspaceIDs {
197
if !foundIDs[expectedID] {
198
t.Errorf("workspace ID %q not found in discovered directories", expectedID)
199
}
200
}
201
}
202
203
func TestFindMatchingFiles(t *testing.T) {
204
// Create a temporary workspace directory
205
tempDir, err := os.MkdirTemp("", "agent-smith-workspace-*")
206
if err != nil {
207
t.Fatalf("failed to create temp dir: %v", err)
208
}
209
defer os.RemoveAll(tempDir)
210
211
// Create test files
212
testFiles := map[string]string{
213
"config.json": `{"key": "value"}`,
214
"settings.conf": `setting=value`,
215
"script.sh": `#!/bin/bash\necho "hello"`,
216
"wallet.dat": `wallet data`,
217
"normal.txt": `just text`,
218
"subdir/nested.conf": `nested config`,
219
"dotfiles/data.txt": `some foobar thing`,
220
}
221
222
for filePath, content := range testFiles {
223
fullPath := filepath.Join(tempDir, filePath)
224
225
// Create directory if needed
226
if dir := filepath.Dir(fullPath); dir != tempDir {
227
if err := os.MkdirAll(dir, 0755); err != nil {
228
t.Fatalf("failed to create dir %s: %v", dir, err)
229
}
230
}
231
232
if err := os.WriteFile(fullPath, []byte(content), 0644); err != nil {
233
t.Fatalf("failed to create file %s: %v", fullPath, err)
234
}
235
}
236
237
// Create detector
238
config := FileScanningConfig{
239
Enabled: true,
240
WorkingArea: "/tmp", // Not used in this test
241
}
242
mockClassifier := &mockFileClassifier{}
243
detector, err := NewfileDetector(config, mockClassifier)
244
if err != nil {
245
t.Fatalf("failed to create detector: %v", err)
246
}
247
248
tests := []struct {
249
name string
250
filename string
251
expected []string
252
}{
253
{
254
name: "direct file match",
255
filename: "config.json",
256
expected: []string{filepath.Join(tempDir, "config.json")},
257
},
258
{
259
name: "wildcard pattern",
260
filename: "*.conf",
261
expected: []string{filepath.Join(tempDir, "settings.conf")},
262
},
263
{
264
name: "shell script pattern",
265
filename: "*.sh",
266
expected: []string{filepath.Join(tempDir, "script.sh")},
267
},
268
{
269
name: "no matches",
270
filename: "*.nonexistent",
271
expected: []string{},
272
},
273
{
274
name: "nonexistent direct file",
275
filename: "missing.txt",
276
expected: []string{},
277
},
278
{
279
name: "wildcard to dip into a sub-folder",
280
filename: "*/data.txt",
281
expected: []string{filepath.Join(tempDir, "dotfiles/data.txt")},
282
},
283
{
284
name: "exact match",
285
filename: "dotfiles/data.txt",
286
expected: []string{filepath.Join(tempDir, "dotfiles/data.txt")},
287
},
288
}
289
290
for _, tt := range tests {
291
t.Run(tt.name, func(t *testing.T) {
292
matches := detector.findMatchingFiles(tempDir, tt.filename)
293
294
if len(matches) != len(tt.expected) {
295
t.Errorf("found %d matches, expected %d", len(matches), len(tt.expected))
296
t.Errorf("matches: %v", matches)
297
t.Errorf("expected: %v", tt.expected)
298
return
299
}
300
301
// Convert to map for easier comparison
302
matchMap := make(map[string]bool)
303
for _, match := range matches {
304
matchMap[match] = true
305
}
306
307
for _, expected := range tt.expected {
308
if !matchMap[expected] {
309
t.Errorf("expected match %q not found", expected)
310
}
311
}
312
})
313
}
314
}
315
316
func TestFileDetector_GetFileSignatures(t *testing.T) {
317
// Create a mock classifier that returns some signatures
318
mockClassifier := &mockFileClassifierWithSignatures{
319
signatures: []*classifier.Signature{
320
{
321
Name: "test-sig",
322
Domain: "filesystem",
323
Filename: []string{"test.txt"},
324
},
325
},
326
}
327
328
config := FileScanningConfig{
329
Enabled: true,
330
WorkingArea: "/tmp",
331
}
332
333
detector, err := NewfileDetector(config, mockClassifier)
334
if err != nil {
335
t.Fatalf("failed to create detector: %v", err)
336
}
337
338
signatures := detector.GetFileSignatures()
339
if len(signatures) != 1 {
340
t.Errorf("expected 1 signature, got %d", len(signatures))
341
}
342
343
if signatures[0].Name != "test-sig" {
344
t.Errorf("expected signature name 'test-sig', got %s", signatures[0].Name)
345
}
346
}
347
348
// mockFileClassifierWithSignatures is a mock that returns signatures
349
type mockFileClassifierWithSignatures struct {
350
signatures []*classifier.Signature
351
}
352
353
func (m *mockFileClassifierWithSignatures) MatchesFile(filePath string) (*classifier.Classification, error) {
354
return &classifier.Classification{Level: classifier.LevelNoMatch}, nil
355
}
356
357
func (m *mockFileClassifierWithSignatures) GetFileSignatures() []*classifier.Signature {
358
return m.signatures
359
}
360
361
func (m *mockFileClassifierWithSignatures) Describe(d chan<- *prometheus.Desc) {}
362
func (m *mockFileClassifierWithSignatures) Collect(m2 chan<- prometheus.Metric) {}
363
364