Skip to content

Refactor for Testability - Core Architecture #209

Description

@loispostula

Problem

The codebase evolved from bash scripts to Rust CLI, resulting in:

  • Monolithic command functions that are hard to test in isolation
  • Heavy coupling to external services (GitHub API, Docker, S3, etc.)
  • Side effects scattered throughout (file I/O, network calls, command execution)
  • Limited ability to test error conditions and edge cases
  • No clear separation between business logic and I/O

Current State Analysis

Hard-to-test patterns:

  1. Commands directly instantiate external clients (Octocrab, ObjectStore, etc.)
  2. Business logic intertwined with I/O operations
  3. No dependency injection or trait-based abstractions
  4. Tests require real git repositories and cargo workspaces
  5. Parallel execution logic hard to test without real semaphores

Proposed Refactoring Strategy

Phase 1: Extract Core Business Logic

1.1 Create domain models

// src/domain/mod.rs
pub mod package;      // Pure Package representations
pub mod dependency;   // Dependency graph logic
pub mod change_set;   // Changed packages computation
pub mod publish_plan; // What to publish, where, how

1.2 Define service traits

// src/services/traits.rs
pub trait GitService {
    fn diff_files(&self, base: &str, head: &str) -> Result<Vec<PathBuf>>;
    fn resolve_ref(&self, ref_name: &str) -> Result<String>;
}

pub trait GithubService {
    async fn find_release(&self, tag: &str) -> Result<Option<Release>>;
    async fn upload_asset(&self, release_id: u64, name: &str, data: Vec<u8>) -> Result<()>;
}

pub trait CargoRegistry {
    async fn package_exists(&self, name: &str, version: &str) -> Result<bool>;
    async fn publish(&self, package_path: &Path, registry: &str) -> Result<()>;
}

1.3 Implement production and test implementations

Phase 2: Refactor Commands to Use Services

Extract testable functions from monolithic commands. Split into:

  • Pure planning functions (no I/O)
  • Execution functions using service traits (testable with mocks)

Phase 3: Improve CrateGraph Testability

Make CrateGraph independent of git2 by extracting git operations to a trait.

Implementation Plan

Week 1-2: Foundation

  • Create src/domain/ with core business models
  • Define service traits in src/services/traits.rs
  • Create src/testing/ with test utilities and mocks
  • Add builder patterns for test fixtures

Week 3-4: Refactor CrateGraph

  • Extract git operations to GitService trait
  • Make CrateGraph::affected_packages use trait instead of git2::Repository
  • Write comprehensive tests for dependency graph logic
  • Add property-based tests for change detection

Week 5-6: Refactor Publish Command

  • Extract publish planning logic (pure functions)
  • Implement service interfaces for GitHub, Cargo registry
  • Refactor publish/mod.rs to use services
  • Write unit tests for publish planning
  • Write integration tests with mocked services

Week 7-8: Refactor Tests Command

  • Extract test execution planning
  • Abstract database and container management
  • Implement testable parallel execution
  • Add tests for test filtering and selection

Week 9-10: Remaining Commands

  • Apply pattern to check_workspace, fix_lock_files, etc.
  • Update all commands to use CommandContext
  • Ensure 80%+ test coverage for business logic

Success Metrics

  • 80%+ unit test coverage for business logic (not including I/O)
  • All commands have integration tests with mocked external services
  • Test execution time < 30s
  • Can test error conditions without real external services
  • New features can be developed with TDD

Breaking Changes

  • Internal refactoring only; CLI interface remains the same
  • May need to update some internal imports if used by external consumers

Labels

refactoring, testing, priority:high

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions