Path: blob/main/components/ee/agent-smith/pkg/detector/filesystem_test.go
2501 views
// Copyright (c) 2024 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package detector56import (7"os"8"path/filepath"9"testing"10"time"1112"github.com/gitpod-io/gitpod/agent-smith/pkg/classifier"13"github.com/prometheus/client_golang/prometheus"14)1516// mockFileClassifier is a mock implementation for testing17type mockFileClassifier struct{}1819func (m *mockFileClassifier) MatchesFile(filePath string) (*classifier.Classification, error) {20return &classifier.Classification{Level: classifier.LevelNoMatch}, nil21}2223func (m *mockFileClassifier) GetFileSignatures() []*classifier.Signature {24return nil25}2627func (m *mockFileClassifier) Describe(d chan<- *prometheus.Desc) {}28func (m *mockFileClassifier) Collect(m2 chan<- prometheus.Metric) {}2930func TestFileDetector_Config_Defaults(t *testing.T) {31tests := []struct {32name string33inputConfig FileScanningConfig34expectedConfig FileScanningConfig35}{36{37name: "all defaults",38inputConfig: FileScanningConfig{39Enabled: true,40WorkingArea: "/tmp/test-workspaces",41},42expectedConfig: FileScanningConfig{43Enabled: true,44ScanInterval: 5 * time.Minute,45MaxFileSize: 1024,46WorkingArea: "/tmp/test-workspaces",47},48},49{50name: "partial config",51inputConfig: FileScanningConfig{52Enabled: true,53ScanInterval: 10 * time.Minute,54MaxFileSize: 2048,55WorkingArea: "/tmp/test-workspaces",56},57expectedConfig: FileScanningConfig{58Enabled: true,59ScanInterval: 10 * time.Minute,60MaxFileSize: 2048,61WorkingArea: "/tmp/test-workspaces",62},63},64{65name: "all custom values",66inputConfig: FileScanningConfig{67Enabled: true,68ScanInterval: 2 * time.Minute,69MaxFileSize: 512,70WorkingArea: "/tmp/test-workspaces",71},72expectedConfig: FileScanningConfig{73Enabled: true,74ScanInterval: 2 * time.Minute,75MaxFileSize: 512,76WorkingArea: "/tmp/test-workspaces",77},78},79}8081for _, tt := range tests {82t.Run(tt.name, func(t *testing.T) {83mockClassifier := &mockFileClassifier{}84detector, err := NewfileDetector(tt.inputConfig, mockClassifier)85if err != nil {86t.Fatalf("failed to create detector: %v", err)87}8889if detector.config.ScanInterval != tt.expectedConfig.ScanInterval {90t.Errorf("ScanInterval = %v, expected %v", detector.config.ScanInterval, tt.expectedConfig.ScanInterval)91}92if detector.config.MaxFileSize != tt.expectedConfig.MaxFileSize {93t.Errorf("MaxFileSize = %v, expected %v", detector.config.MaxFileSize, tt.expectedConfig.MaxFileSize)94}95if detector.config.WorkingArea != tt.expectedConfig.WorkingArea {96t.Errorf("WorkingArea = %v, expected %v", detector.config.WorkingArea, tt.expectedConfig.WorkingArea)97}98})99}100}101102func TestFileDetector_DisabledConfig(t *testing.T) {103config := FileScanningConfig{104Enabled: false,105}106107mockClassifier := &mockFileClassifier{}108_, err := NewfileDetector(config, mockClassifier)109if err == nil {110t.Error("expected error when file scanning is disabled, got nil")111}112113expectedError := "file scanning is disabled"114if err.Error() != expectedError {115t.Errorf("expected error %q, got %q", expectedError, err.Error())116}117}118119func TestWorkspaceDirectory_Fields(t *testing.T) {120wsDir := WorkspaceDirectory{121InstanceID: "inst789",122Path: "/var/gitpod/workspaces/inst789",123}124125if wsDir.InstanceID != "inst789" {126t.Errorf("InstanceID = %q, expected %q", wsDir.InstanceID, "inst789")127}128129expectedPath := "/var/gitpod/workspaces/inst789"130if wsDir.Path != expectedPath {131t.Errorf("Path = %q, expected %q", wsDir.Path, expectedPath)132}133}134135func TestDiscoverWorkspaceDirectories(t *testing.T) {136// Create a temporary working area137tempDir, err := os.MkdirTemp("", "agent-smith-test-*")138if err != nil {139t.Fatalf("failed to create temp dir: %v", err)140}141defer os.RemoveAll(tempDir)142143// Create mock workspace directories144workspaceIDs := []string{"ws-abc123", "ws-def456", "ws-ghi789"}145for _, wsID := range workspaceIDs {146wsDir := filepath.Join(tempDir, wsID)147if err := os.Mkdir(wsDir, 0755); err != nil {148t.Fatalf("failed to create workspace dir %s: %v", wsDir, err)149}150}151152// Create some files that should be ignored153if err := os.Mkdir(filepath.Join(tempDir, ".hidden"), 0755); err != nil {154t.Fatalf("failed to create hidden dir: %v", err)155}156if err := os.Mkdir(filepath.Join(tempDir, "ws-service-daemon"), 0755); err != nil {157t.Fatalf("failed to create daemon dir: %v", err)158}159160// Create detector with temp working area161config := FileScanningConfig{162Enabled: true,163WorkingArea: tempDir,164}165mockClassifier := &mockFileClassifier{}166detector, err := NewfileDetector(config, mockClassifier)167if err != nil {168t.Fatalf("failed to create detector: %v", err)169}170171// Test workspace directory discovery172workspaceDirs, err := detector.discoverWorkspaceDirectories()173if err != nil {174t.Fatalf("failed to discover workspace directories: %v", err)175}176177// Should find exactly 3 workspace directories (ignoring hidden and daemon dirs)178if len(workspaceDirs) != 3 {179t.Errorf("found %d workspace directories, expected 3", len(workspaceDirs))180}181182// Verify the discovered directories183foundIDs := make(map[string]bool)184for _, wsDir := range workspaceDirs {185foundIDs[wsDir.InstanceID] = true186187// Verify path is correct188expectedPath := filepath.Join(tempDir, wsDir.InstanceID)189if wsDir.Path != expectedPath {190t.Errorf("workspace %s path = %q, expected %q", wsDir.InstanceID, wsDir.Path, expectedPath)191}192}193194// Verify all expected workspace IDs were found195for _, expectedID := range workspaceIDs {196if !foundIDs[expectedID] {197t.Errorf("workspace ID %q not found in discovered directories", expectedID)198}199}200}201202func TestFindMatchingFiles(t *testing.T) {203// Create a temporary workspace directory204tempDir, err := os.MkdirTemp("", "agent-smith-workspace-*")205if err != nil {206t.Fatalf("failed to create temp dir: %v", err)207}208defer os.RemoveAll(tempDir)209210// Create test files211testFiles := map[string]string{212"config.json": `{"key": "value"}`,213"settings.conf": `setting=value`,214"script.sh": `#!/bin/bash\necho "hello"`,215"wallet.dat": `wallet data`,216"normal.txt": `just text`,217"subdir/nested.conf": `nested config`,218"dotfiles/data.txt": `some foobar thing`,219}220221for filePath, content := range testFiles {222fullPath := filepath.Join(tempDir, filePath)223224// Create directory if needed225if dir := filepath.Dir(fullPath); dir != tempDir {226if err := os.MkdirAll(dir, 0755); err != nil {227t.Fatalf("failed to create dir %s: %v", dir, err)228}229}230231if err := os.WriteFile(fullPath, []byte(content), 0644); err != nil {232t.Fatalf("failed to create file %s: %v", fullPath, err)233}234}235236// Create detector237config := FileScanningConfig{238Enabled: true,239WorkingArea: "/tmp", // Not used in this test240}241mockClassifier := &mockFileClassifier{}242detector, err := NewfileDetector(config, mockClassifier)243if err != nil {244t.Fatalf("failed to create detector: %v", err)245}246247tests := []struct {248name string249filename string250expected []string251}{252{253name: "direct file match",254filename: "config.json",255expected: []string{filepath.Join(tempDir, "config.json")},256},257{258name: "wildcard pattern",259filename: "*.conf",260expected: []string{filepath.Join(tempDir, "settings.conf")},261},262{263name: "shell script pattern",264filename: "*.sh",265expected: []string{filepath.Join(tempDir, "script.sh")},266},267{268name: "no matches",269filename: "*.nonexistent",270expected: []string{},271},272{273name: "nonexistent direct file",274filename: "missing.txt",275expected: []string{},276},277{278name: "wildcard to dip into a sub-folder",279filename: "*/data.txt",280expected: []string{filepath.Join(tempDir, "dotfiles/data.txt")},281},282{283name: "exact match",284filename: "dotfiles/data.txt",285expected: []string{filepath.Join(tempDir, "dotfiles/data.txt")},286},287}288289for _, tt := range tests {290t.Run(tt.name, func(t *testing.T) {291matches := detector.findMatchingFiles(tempDir, tt.filename)292293if len(matches) != len(tt.expected) {294t.Errorf("found %d matches, expected %d", len(matches), len(tt.expected))295t.Errorf("matches: %v", matches)296t.Errorf("expected: %v", tt.expected)297return298}299300// Convert to map for easier comparison301matchMap := make(map[string]bool)302for _, match := range matches {303matchMap[match] = true304}305306for _, expected := range tt.expected {307if !matchMap[expected] {308t.Errorf("expected match %q not found", expected)309}310}311})312}313}314315func TestFileDetector_GetFileSignatures(t *testing.T) {316// Create a mock classifier that returns some signatures317mockClassifier := &mockFileClassifierWithSignatures{318signatures: []*classifier.Signature{319{320Name: "test-sig",321Domain: "filesystem",322Filename: []string{"test.txt"},323},324},325}326327config := FileScanningConfig{328Enabled: true,329WorkingArea: "/tmp",330}331332detector, err := NewfileDetector(config, mockClassifier)333if err != nil {334t.Fatalf("failed to create detector: %v", err)335}336337signatures := detector.GetFileSignatures()338if len(signatures) != 1 {339t.Errorf("expected 1 signature, got %d", len(signatures))340}341342if signatures[0].Name != "test-sig" {343t.Errorf("expected signature name 'test-sig', got %s", signatures[0].Name)344}345}346347// mockFileClassifierWithSignatures is a mock that returns signatures348type mockFileClassifierWithSignatures struct {349signatures []*classifier.Signature350}351352func (m *mockFileClassifierWithSignatures) MatchesFile(filePath string) (*classifier.Classification, error) {353return &classifier.Classification{Level: classifier.LevelNoMatch}, nil354}355356func (m *mockFileClassifierWithSignatures) GetFileSignatures() []*classifier.Signature {357return m.signatures358}359360func (m *mockFileClassifierWithSignatures) Describe(d chan<- *prometheus.Desc) {}361func (m *mockFileClassifierWithSignatures) Collect(m2 chan<- prometheus.Metric) {}362363364