Skip to content

Trait-based I2C Device Refactoring for Testability #51

@mairas

Description

@mairas

Overview

Refactor the I2C device interface to use traits instead of concrete types, enabling comprehensive integration testing without hardware dependencies.

Current Problem

The daemon architecture tightly couples to the concrete HalpiDevice type:

  • AppState directly contains HalpiDevice
  • All HTTP handlers depend on AppState with real I2C hardware
  • No dependency injection mechanism for mocking
  • Integration tests cannot run without actual HALPI2 hardware
  • Cannot test error paths systematically

Proposed Solution

1. Define I2C Device Trait

pub trait I2CDeviceInterface: Send {
    fn get_measurements(&mut self) -> Result<Measurements, I2cError>;
    fn get_power_state(&mut self) -> Result<PowerState, I2cError>;
    fn feed_watchdog(&mut self) -> Result<(), I2cError>;
    fn get_usb_port_state(&mut self) -> Result<u8, I2cError>;
    fn set_usb_port_state(&mut self, port_bits: u8) -> Result<(), I2cError>;
    fn request_shutdown(&mut self) -> Result<(), I2cError>;
    fn request_standby(&mut self) -> Result<(), I2cError>;
    fn firmware_version(&mut self) -> Result<&str, I2cError>;
    fn get_hardware_version(&mut self) -> Result<Version, I2cError>;
    fn get_firmware_version(&mut self) -> Result<Version, I2cError>;
    fn get_device_id(&mut self) -> Result<String, I2cError>;
    // ... other methods
}

2. Make AppState Generic

pub struct AppState<D: I2CDeviceInterface> {
    pub device: Arc<Mutex<D>>,
    pub config: Arc<RwLock<Config>>,
    pub version: &'static str,
}

3. Implement Trait for HalpiDevice

impl I2CDeviceInterface for HalpiDevice {
    // Existing method implementations
}

4. Create Mock Implementation

pub struct MockI2CDevice {
    measurements: Measurements,
    power_state: PowerState,
    usb_ports: u8,
    // ... configurable test data
}

impl I2CDeviceInterface for MockI2CDevice {
    fn get_measurements(&mut self) -> Result<Measurements, I2cError> {
        Ok(self.measurements.clone())
    }
    // ... predetermined responses for testing
}

5. Update All Handlers

Update all HTTP handler functions to use generic type parameters or trait objects.

Benefits

  • ✅ Integration tests can run without hardware
  • ✅ Fast, deterministic test execution in CI
  • ✅ Can test error paths systematically
  • ✅ Can control I2C device state for testing
  • ✅ Better separation of concerns
  • ✅ More maintainable architecture

Implementation Plan

  1. Define I2CDeviceInterface trait in halpid/src/i2c/mod.rs
  2. Implement trait for HalpiDevice
  3. Create MockI2CDevice in test utilities
  4. Refactor AppState to be generic
  5. Update all handler functions
  6. Update main.rs to use concrete type
  7. Add comprehensive integration tests using mock
  8. Verify all existing tests still pass

Acceptance Criteria

  • I2CDeviceInterface trait defined with all necessary methods
  • HalpiDevice implements the trait
  • MockI2CDevice test implementation created
  • AppState is generic over device type
  • All handlers updated to use trait bounds
  • Integration tests added using mock device
  • All existing tests pass
  • Documentation updated

Dependencies

  • None (can be implemented independently)

Estimated Effort

Medium - Requires changes across multiple modules but is mostly mechanical refactoring.

Related Issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions