Skip to main content
Glama
instance_test.go6.6 kB
package discovery import ( "encoding/json" "os" "path/filepath" "sync" "testing" "time" ) func TestInstanceDiscovery(t *testing.T) { // Create temp directory for testing tempDir := t.TempDir() instancesDir := filepath.Join(tempDir, "instances") // Create discovery system discovery, err := New(instancesDir) if err != nil { t.Fatalf("Failed to create discovery: %v", err) } defer discovery.Stop() // Start discovery discovery.Start() // Test 1: Initial state should be empty instances := discovery.GetInstances() if len(instances) != 0 { t.Errorf("Expected 0 instances, got %d", len(instances)) } // Test 2: Add an instance instance1 := &Instance{ ID: "test-instance-1", Name: "Test Instance 1", Directory: "/test/dir1", Port: 7777, StartedAt: time.Now(), LastPing: time.Now(), ProcessInfo: struct { PID int `json:"pid"` Executable string `json:"executable"` }{ PID: 12345, Executable: "brum", }, } if err := RegisterInstance(instancesDir, instance1); err != nil { t.Fatalf("Failed to register instance: %v", err) } // Wait for file watcher to pick up the change time.Sleep(100 * time.Millisecond) instances = discovery.GetInstances() if len(instances) != 1 { t.Errorf("Expected 1 instance, got %d", len(instances)) } if inst, ok := instances["test-instance-1"]; !ok { t.Error("Instance test-instance-1 not found") } else { if inst.Name != "Test Instance 1" { t.Errorf("Expected name 'Test Instance 1', got '%s'", inst.Name) } if inst.Port != 7777 { t.Errorf("Expected port 7777, got %d", inst.Port) } } // Test 3: Add another instance instance2 := &Instance{ ID: "test-instance-2", Name: "Test Instance 2", Directory: "/test/dir2", Port: 7778, StartedAt: time.Now(), LastPing: time.Now(), ProcessInfo: struct { PID int `json:"pid"` Executable string `json:"executable"` }{ PID: 12346, Executable: "brum", }, } if err := RegisterInstance(instancesDir, instance2); err != nil { t.Fatalf("Failed to register instance2: %v", err) } time.Sleep(100 * time.Millisecond) instances = discovery.GetInstances() if len(instances) != 2 { t.Errorf("Expected 2 instances, got %d", len(instances)) } // Test 4: Remove an instance if err := UnregisterInstance(instancesDir, "test-instance-1"); err != nil { t.Fatalf("Failed to unregister instance: %v", err) } time.Sleep(100 * time.Millisecond) instances = discovery.GetInstances() if len(instances) != 1 { t.Errorf("Expected 1 instance after removal, got %d", len(instances)) } if _, ok := instances["test-instance-1"]; ok { t.Error("Instance test-instance-1 should have been removed") } if _, ok := instances["test-instance-2"]; !ok { t.Error("Instance test-instance-2 should still exist") } } func TestInstanceUpdateCallback(t *testing.T) { tempDir := t.TempDir() instancesDir := filepath.Join(tempDir, "instances") discovery, err := New(instancesDir) if err != nil { t.Fatalf("Failed to create discovery: %v", err) } defer discovery.Stop() discovery.Start() // Set up callback to track updates var mu sync.Mutex var updateCount int var lastInstances map[string]*Instance discovery.OnUpdate(func(instances map[string]*Instance) { mu.Lock() defer mu.Unlock() updateCount++ lastInstances = instances }) // Register an instance instance := &Instance{ ID: "callback-test", Name: "Callback Test", Directory: "/test", Port: 9999, StartedAt: time.Now(), LastPing: time.Now(), ProcessInfo: struct { PID int `json:"pid"` Executable string `json:"executable"` }{ PID: 12347, Executable: "brum", }, } if err := RegisterInstance(instancesDir, instance); err != nil { t.Fatalf("Failed to register instance: %v", err) } // Wait for callback time.Sleep(150 * time.Millisecond) mu.Lock() if updateCount != 1 { t.Errorf("Expected 1 update callback, got %d", updateCount) } if len(lastInstances) != 1 { t.Errorf("Expected 1 instance in callback, got %d", len(lastInstances)) } mu.Unlock() } func TestAtomicFileOperations(t *testing.T) { tempDir := t.TempDir() afo := NewAtomicFileOperations(tempDir) // Test basic atomic file operations through AtomicFileOperations testData := []byte("Hello, World!") testFile := filepath.Join(tempDir, "test.txt") if err := afo.atomicWriteFile(testFile, testData, 0644); err != nil { t.Fatalf("atomicWriteFile failed: %v", err) } // Verify file contents data, err := os.ReadFile(testFile) if err != nil { t.Fatalf("Failed to read file: %v", err) } if string(data) != string(testData) { t.Errorf("Expected '%s', got '%s'", testData, data) } } func TestInstancePingUpdate(t *testing.T) { tempDir := t.TempDir() instancesDir := filepath.Join(tempDir, "instances") // Register an instance instance := &Instance{ ID: "ping-test", Name: "Ping Test", Directory: "/test", Port: 8888, StartedAt: time.Now(), LastPing: time.Now().Add(-1 * time.Hour), // Old ping time ProcessInfo: struct { PID int `json:"pid"` Executable string `json:"executable"` }{ PID: 12348, Executable: "brum", }, } if err := RegisterInstance(instancesDir, instance); err != nil { t.Fatalf("Failed to register instance: %v", err) } // Update ping beforeUpdate := time.Now() if err := UpdateInstancePing(instancesDir, "ping-test"); err != nil { t.Fatalf("Failed to update ping: %v", err) } // Read back and verify data, err := os.ReadFile(filepath.Join(instancesDir, "ping-test.json")) if err != nil { t.Fatalf("Failed to read instance file: %v", err) } var updated Instance if err := json.Unmarshal(data, &updated); err != nil { t.Fatalf("Failed to unmarshal instance: %v", err) } if updated.LastPing.Before(beforeUpdate) { t.Error("LastPing was not updated") } } func TestGetDefaultInstancesDir(t *testing.T) { // Test with XDG_RUNTIME_DIR set oldXDG := os.Getenv("XDG_RUNTIME_DIR") defer os.Setenv("XDG_RUNTIME_DIR", oldXDG) os.Setenv("XDG_RUNTIME_DIR", "/run/user/1000") dir := GetDefaultInstancesDir() expected := "/run/user/1000/brummer/instances" if dir != expected { t.Errorf("Expected %s, got %s", expected, dir) } // Test without XDG_RUNTIME_DIR os.Unsetenv("XDG_RUNTIME_DIR") dir = GetDefaultInstancesDir() if !filepath.IsAbs(dir) { t.Error("Expected absolute path") } if !filepath.HasPrefix(dir, os.TempDir()) { t.Errorf("Expected path under temp dir, got %s", dir) } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/standardbeagle/brummer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server