interprex-test 6.1.0

Stateful in-memory Interprex provider for consumer tests
Documentation
use async_trait::async_trait;
use interprex::{
    AppliedSourceRequirements, AppliedSourceRequirementsProvider, CodeHostingProvider, CommitRange,
    ProviderError, Repository, RepositoryFacts, RepositorySettings, Result,
    SourceCodeConfigurationProvider,
};
use secrecy::SecretString;

use crate::state::{FakeProvider, missing};

#[async_trait]
impl CodeHostingProvider for FakeProvider {
    async fn repository(&self, repository: &Repository) -> Result<RepositoryFacts> {
        self.state
            .read()
            .await
            .repositories
            .get(repository)
            .map(|(facts, _)| facts.clone())
            .ok_or_else(|| missing(repository.to_string()))
    }

    async fn settings(&self, repository: &Repository) -> Result<RepositorySettings> {
        self.state
            .read()
            .await
            .repositories
            .get(repository)
            .map(|(_, settings)| settings.clone())
            .ok_or_else(|| missing(repository.to_string()))
    }

    async fn apply_settings(
        &self,
        repository: &Repository,
        settings: &RepositorySettings,
    ) -> Result<RepositorySettings> {
        let mut state = self.state.write().await;
        let (_, current) = state
            .repositories
            .get_mut(repository)
            .ok_or_else(|| missing(repository.to_string()))?;
        *current = settings.clone();
        Ok(settings.clone())
    }

    async fn put_secret(
        &self,
        repository: &Repository,
        name: &str,
        _value: SecretString,
    ) -> Result<()> {
        self.state
            .write()
            .await
            .secret_names
            .entry(repository.clone())
            .or_default()
            .push(name.to_owned());
        Ok(())
    }
}

#[async_trait]
impl SourceCodeConfigurationProvider for FakeProvider {
    type Ruleset = serde_json::Value;

    async fn read_rulesets(&self, _repository: &Repository) -> Result<Vec<Self::Ruleset>> {
        Err(unsupported("read source rulesets"))
    }

    async fn apply_ruleset(
        &self,
        _repository: &Repository,
        _ruleset: &Self::Ruleset,
    ) -> Result<Self::Ruleset> {
        Err(unsupported("apply source ruleset"))
    }
}

#[async_trait]
impl AppliedSourceRequirementsProvider for FakeProvider {
    async fn applied_requirements(
        &self,
        repository: &Repository,
        target_branch: &str,
        commit_range: &CommitRange,
    ) -> Result<AppliedSourceRequirements> {
        for (value, field) in [
            (target_branch, "target branch"),
            (&commit_range.base_sha, "base sha"),
            (&commit_range.head_sha, "head sha"),
        ] {
            if value.is_empty() {
                return Err(ProviderError::InvalidInput {
                    provider: "fake",
                    fact: format!("{field} must not be empty"),
                });
            }
        }
        let key = (
            repository.clone(),
            target_branch.to_owned(),
            commit_range.base_sha.clone(),
            commit_range.head_sha.clone(),
        );
        let state = self.state.read().await;
        if let Some(error) = state.applied_requirement_errors.get(&key) {
            return Err(error.clone());
        }
        state
            .applied_requirements
            .get(&key)
            .cloned()
            .ok_or_else(|| ProviderError::NotFound {
                entity: format!(
                    "applied requirements for {repository}:{target_branch} at {}..{}",
                    commit_range.base_sha, commit_range.head_sha
                ),
            })
    }
}

fn unsupported(operation: &'static str) -> ProviderError {
    ProviderError::Unsupported {
        provider: "fake",
        operation,
    }
}