# How to Use openlogi-agent-mock for Scripted Inventory in Headless Development

> Learn how to use openlogi-agent-mock for scripted inventory in headless development. Enable CI pipelines and automated UI testing without physical hardware using this in-memory device inventory tool.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: how-to-guide
- Published: 2026-08-30

---

**The `openlogi-agent-mock` binary implements the same Tarpc `Agent` IPC contract as the production `openlogi-agent`, serving a fully scripted, in-memory device inventory that enables CI pipelines and automated UI testing without physical Logitech hardware.**

Developers working on the AprilNEA/OpenLogi ecosystem can leverage `openlogi-agent-mock` to simulate complete device inventories in headless environments. This specialized binary, located in [`crates/openlogi-agent/src/bin/mock_agent.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/bin/mock_agent.rs), mirrors the production agent's exact API surface while maintaining all device state in memory according to the patterns documented in the repository's **Development guide** (lines 9‑12).

## Architecture and Startup Sequence

The mock agent replicates the production startup sequence while substituting physical HID access with pure Rust data structures. This ensures the desktop GUI connects transparently without code changes.

### Single-Instance Locking

To prevent race conditions with a real agent, the mock acquires exclusive ownership of the agent socket by locking the `agent.lock` file. In lines 29‑33 of [`mock_agent.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/mock_agent.rs), the code calls `single_instance::acquire("agent.lock")` to guarantee that only one agent—mock or real—runs at a time. This forces the GUI to connect to the mock during development sessions.

### Scripted Inventory Initialization

The in-memory inventory is instantiated via `State::new()` (lines 21‑28), which populates a `DeviceInventory` containing:

- A Bolt receiver
- A directly-attached mouse
- A standalone Litra light
- Several paired peripheral devices

The `MockAgent` struct holds this state within an **`Arc<Mutex<State>>`**, allowing concurrent access by the Tokio runtime while preserving thread safety.

### IPC Transport Binding

Lines 56‑62 invoke `transport::listen()` to bind the same local-socket address used by the production agent. This exposes the full **Agent API**—including pairing flows, DPI changes, SmartShift torque adjustments, and ring observations—over the identical Tarpc service definition. The GUI handshakes the `PROTOCOL_VERSION` and receives the scripted state exactly as it would from physical hardware.

## Running the Mock Agent

By default, the binary launches with the **dev profile** (`OPENLOGI_PROFILE=dev`) as defined in lines 13‑17, ensuring it binds to the development socket without interfering with production installations.

Execute the mock from the repository root:

```bash
cargo run -p openlogi-agent --bin openlogi-agent-mock

```

To test production socket behavior instead, set the environment variable before launching:

```bash
OPENLOGI_PROFILE=prod cargo run -p openlogi-agent --bin openlogi-agent-mock

```

## Configuring the Scripted Inventory

The mock simulates dynamic device behaviors through constants defined at the top of [`mock_agent.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/mock_agent.rs). Developers can tune these values to test specific scenarios without hardware modifications:

- **`CAMERA_TOGGLE_PERIOD`** – Controls Litra light state change intervals
- **`FOREGROUND_SWITCH_PERIOD`** – Simulates application switching for foreground-aware features
- **`SCRIPTED_APPS`** – Defines the set of applications cycled through during the mock's simulated foreground-app rotation

These parameters drive the `State` machine, allowing you to simulate varying battery drain rates, pairing timings, or DPI switching behaviors purely in software.

## CI/CD Integration for Headless Testing

The mock enables fully automated UI validation in headless pipelines. The following GitHub Actions pattern starts the mock as a background service before executing GUI tests:

```yaml
- name: Start mock agent
  run: |
    cargo run -p openlogi-agent --bin openlogi-agent-mock &
    echo $! > mock.pid

- name: Run headless UI tests
  env:
    OPENLOGI_DEV_AGENT: 0   # Force GUI to use dev socket

  run: cargo test -p openlogi-desktop --test ui

- name: Stop mock agent
  run: kill $(cat mock.pid)

```

Setting `OPENLOGI_DEV_AGENT=0` directs the desktop GUI to connect to the development socket where the mock listens, ensuring all device interactions occur against the scripted inventory rather than physical USB devices.

## Embedded Testing with Custom Devices

For Rust unit tests requiring specific hardware configurations, instantiate `State` and `MockAgent` directly in your test code:

```rust
use openlogi_agent_mock::{MockAgent, State};
use tokio::runtime::Builder;

let mut state = State::new().expect("initial inventory");
state.inventory.devices.push(
    openlogi_core::device::DeviceKind::Mouse {
        model: "TestMouse".into(),
        dpi: vec![800, 1600, 3200],
        ..Default::default()
    }
);

let rt = Builder::new_multi_thread().enable_all().build().unwrap();
rt.block_on(async {
    MockAgent::new(state).serve().await.expect("serve mock");
});

```

This pattern allows validation of edge-case device capabilities—such as unusual DPI ranges or lighting modes—without maintaining physical hardware labs.

## Summary

- The `openlogi-agent-mock` binary in [`crates/openlogi-agent/src/bin/mock_agent.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-agent/src/bin/mock_agent.rs) implements the production **Tarpc `Agent`** service contract (lines 56‑62), providing complete API compatibility.
- **Single-instance locking** via `single_instance::acquire("agent.lock")` (lines 29‑33) prevents conflicts with real agents.
- The scripted **inventory** initializes through `State::new()` (lines 21‑28) and persists in **`Arc<Mutex<State>>`** for the process lifetime.
- **Headless CI pipelines** launch the mock with `cargo run -p openlogi-agent --bin openlogi-agent-mock` and connect the GUI using `OPENLOGI_DEV_AGENT=0`.
- Constants like `CAMERA_TOGGLE_PERIOD` and `SCRIPTED_APPS` enable fine-grained simulation of hardware behaviors without physical devices.

## Frequently Asked Questions

### How does the mock prevent conflicts with the production agent?

The mock calls `single_instance::acquire("agent.lock")` during startup (lines 29‑33), creating an exclusive filesystem lock that prevents the real `openlogi-agent` from starting simultaneously. This guarantees the desktop GUI connects to the mock instance rather than failing to attach to occupied hardware resources.

### Can I simulate custom device types not in the default inventory?

Yes. Import `State` from the mock crate and manually append devices to `state.inventory.devices` before calling `MockAgent::new(state)`. You can define any `DeviceKind` variant supported by `openlogi_core`, including custom DPI ranges or battery levels, to test specific UI code paths without physical hardware.

### Does the mock support testing production socket bindings?

Set `OPENLOGI_PROFILE=prod` before launching the mock to override the default dev profile (lines 13‑17). This configures `transport::listen()` to bind the production socket address instead of the development endpoint, allowing validation of production-specific IPC behavior in controlled environments.

### What Tokio runtime configuration does the mock use?

The mock initializes a multi-threaded Tokio runtime via `Builder::new_multi_thread()` that drives the Tarpc service asynchronously (as implemented in the `serve()` method). This matches the production agent's async architecture, ensuring that timing-sensitive operations like pairing handshakes and DPI changes behave identically to real hardware interactions.