Skip to main content
Glama
appLifecycleMonitor.test.ts10.7 kB
import { expect } from "chai"; import sinon from "sinon"; import { AppLifecycleMonitor, AppLifecycleEvent } from "../../src/utils/appLifecycleMonitor"; import { AdbUtils } from "../../src/utils/android-cmdline-tools/adb"; import { ExecResult } from "../../src/models"; describe("AppLifecycleMonitor", () => { let monitor: AppLifecycleMonitor; let adbUtilsStub: sinon.SinonStubbedInstance<AdbUtils>; beforeEach(() => { // Stub the AdbUtils constructor and its methods adbUtilsStub = sinon.createStubInstance(AdbUtils); sinon.stub(AdbUtils.prototype, "executeCommand").callsFake(adbUtilsStub.executeCommand); monitor = AppLifecycleMonitor.getInstance(); }); afterEach(async () => { // Clean up singleton state const trackedPackages = monitor.getTrackedPackages(); for (const pkg of trackedPackages) { await monitor.untrackPackage("test-device", pkg); } // Clear all event listeners monitor.removeAllListeners(); // Clean up sinon stubs sinon.restore(); }); describe("singleton pattern", () => { it("should return the same instance", () => { const instance1 = AppLifecycleMonitor.getInstance(); const instance2 = AppLifecycleMonitor.getInstance(); expect(instance1).to.equal(instance2); }); }); describe("package tracking", () => { it("should track packages", async () => { const mockOutput: ExecResult = { stdout: "", stderr: "", toString: () => "", trim: () => "", includes: () => false }; adbUtilsStub.executeCommand.resolves(mockOutput); await monitor.trackPackage("test-device", "com.example.app"); expect(monitor.getTrackedPackages()).to.include("com.example.app"); }); it("should untrack packages", async () => { const mockOutput: ExecResult = { stdout: "", stderr: "", toString: () => "", trim: () => "", includes: () => false }; adbUtilsStub.executeCommand.resolves(mockOutput); await monitor.trackPackage("test-device", "com.example.app"); await monitor.untrackPackage("test-device", "com.example.app"); expect(monitor.getTrackedPackages()).to.not.include("com.example.app"); }); it("should track multiple packages", async () => { const mockOutput: ExecResult = { stdout: "", stderr: "", toString: () => "", trim: () => "", includes: () => false }; adbUtilsStub.executeCommand.resolves(mockOutput); await monitor.trackPackage("test-device", "com.example.app1"); await monitor.trackPackage("test-device", "com.example.app2"); expect(monitor.getTrackedPackages()).to.have.length(2); expect(monitor.getTrackedPackages()).to.include("com.example.app1"); expect(monitor.getTrackedPackages()).to.include("com.example.app2"); }); }); describe("isPackageRunning", () => { it("should return true when package is running", async () => { const mockOutput: ExecResult = { stdout: "12345", stderr: "", toString: () => "12345", trim: () => "12345", includes: (str: string) => mockOutput.stdout.includes(str) }; adbUtilsStub.executeCommand.resolves(mockOutput); const isRunning = await monitor.isPackageRunning("test-device", "com.example.app"); expect(isRunning).to.be.true; expect(adbUtilsStub.executeCommand.calledWith("shell pidof com.example.app")).to.be.true; }); it("should return false when package is not running", async () => { const mockOutput: ExecResult = { stdout: "", stderr: "", toString: () => "", trim: () => "", includes: () => false }; adbUtilsStub.executeCommand.resolves(mockOutput); const isRunning = await monitor.isPackageRunning("test-device", "com.example.app"); expect(isRunning).to.be.false; }); it("should return false when pidof command fails", async () => { adbUtilsStub.executeCommand.rejects(new Error("pidof failed")); const isRunning = await monitor.isPackageRunning("test-device", "com.example.app"); expect(isRunning).to.be.false; }); }); describe("getRunningPackages", () => { it("should return empty array initially", () => { expect(monitor.getRunningPackages()).to.have.length(0); }); it("should return running packages after checkForChanges", async () => { const mockOutput: ExecResult = { stdout: "12345", stderr: "", toString: () => "12345", trim: () => "12345", includes: (str: string) => mockOutput.stdout.includes(str) }; adbUtilsStub.executeCommand.resolves(mockOutput); await monitor.trackPackage("test-device", "com.example.app"); await monitor.checkForChanges("test-device"); expect(monitor.getRunningPackages()).to.include("com.example.app"); }); }); describe("event emission", () => { let launchEvents: AppLifecycleEvent[] = []; let terminateEvents: AppLifecycleEvent[] = []; beforeEach(() => { launchEvents = []; terminateEvents = []; monitor.addEventListener("launch", async event => { launchEvents.push(event); }); monitor.addEventListener("terminate", async event => { terminateEvents.push(event); }); }); it("should emit launch event for new package", async () => { await monitor.trackPackage("test-device", "com.example.app"); // Simulate package launch const mockOutput: ExecResult = { stdout: "12345", stderr: "", toString: () => "12345", trim: () => "12345", includes: (str: string) => mockOutput.stdout.includes(str) }; adbUtilsStub.executeCommand.resolves(mockOutput); // Explicitly check for changes await monitor.checkForChanges("test-device"); expect(launchEvents).to.have.length(1); expect(launchEvents[0].type).to.equal("launch"); expect(launchEvents[0].appId).to.equal("com.example.app"); expect(launchEvents[0].metadata?.detectionMethod).to.equal("pidof"); }); it("should emit terminate event when package stops", async () => { await monitor.trackPackage("test-device", "com.example.app"); // First, simulate package running let mockOutput: ExecResult = { stdout: "12345", stderr: "", toString: () => "12345", trim: () => "12345", includes: (str: string) => mockOutput.stdout.includes(str) }; adbUtilsStub.executeCommand.resolves(mockOutput); // Check for changes to establish running state await monitor.checkForChanges("test-device"); // Clear events from the launch launchEvents.length = 0; // Simulate package termination mockOutput = { stdout: "", stderr: "", toString: () => "", trim: () => "", includes: () => false }; adbUtilsStub.executeCommand.resolves(mockOutput); // Check for changes to detect termination await monitor.checkForChanges("test-device"); expect(terminateEvents).to.have.length(1); expect(terminateEvents[0].type).to.equal("terminate"); expect(terminateEvents[0].appId).to.equal("com.example.app"); expect(terminateEvents[0].metadata?.detectionMethod).to.equal("pidof"); }); it("should handle multiple packages", async () => { await monitor.trackPackage("test-device", "com.example.app1"); await monitor.trackPackage("test-device", "com.example.app2"); // Simulate both packages launching adbUtilsStub.executeCommand.callsFake(async (command: string) => { if (command.includes("com.example.app1") || command.includes("com.example.app2")) { return { stdout: "12345", stderr: "", toString: () => "12345", trim: () => "12345", includes: (str: string) => "12345".includes(str) }; } return { stdout: "", stderr: "", toString: () => "", trim: () => "", includes: () => false }; }); // Check for changes to detect launches await monitor.checkForChanges("test-device"); expect(launchEvents).to.have.length(2); expect(launchEvents.map(e => e.appId)).to.include("com.example.app1"); expect(launchEvents.map(e => e.appId)).to.include("com.example.app2"); }); }); describe("event listener management", () => { it("should add and remove event listeners", () => { const listener = sinon.stub(); monitor.addEventListener("launch", listener); monitor.removeEventListener("launch", listener); // Listeners are managed by EventEmitter, so we just verify no errors expect(monitor.listenerCount("launch")).to.equal(0); }); }); describe("checkForChanges", () => { it("should detect package state changes", async () => { await monitor.trackPackage("test-device", "com.example.app"); // Initially package is not running let mockOutput: ExecResult = { stdout: "", stderr: "", toString: () => "", trim: () => "", includes: () => false }; adbUtilsStub.executeCommand.resolves(mockOutput); await monitor.checkForChanges("test-device"); expect(monitor.getRunningPackages()).to.not.include("com.example.app"); // Package starts running mockOutput = { stdout: "12345", stderr: "", toString: () => "12345", trim: () => "12345", includes: (str: string) => mockOutput.stdout.includes(str) }; adbUtilsStub.executeCommand.resolves(mockOutput); await monitor.checkForChanges("test-device"); expect(monitor.getRunningPackages()).to.include("com.example.app"); }); }); describe("error handling", () => { it("should handle event emission errors gracefully", async () => { await monitor.trackPackage("test-device", "com.example.app"); // Add listener that throws monitor.addEventListener("launch", async () => { throw new Error("Event handler error"); }); // Simulate package launch const launchOutput: ExecResult = { stdout: "12345", stderr: "", toString: () => "12345", trim: () => "12345", includes: (str: string) => launchOutput.stdout.includes(str) }; adbUtilsStub.executeCommand.resolves(launchOutput); // Should not throw await monitor.checkForChanges("test-device"); }); }); });

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/zillow/auto-mobile'

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