Skip to main content

interprex_test/
state.rs

1use std::{
2    collections::{BTreeMap, BTreeSet},
3    sync::Arc,
4};
5
6use bytes::Bytes;
7use interprex::{
8    AssetId, ChangeRequest, ChangeRequestNumber, CheckOutcome, CheckRun, DispatchInputs, Issue,
9    IssueNumber, Label, ProviderError, Release, Repository, RepositoryFacts, RepositorySettings,
10    ReviewRequestTarget, ReviewTarget, Ruleset, RunId, WorkflowRun,
11};
12use tokio::sync::RwLock;
13
14#[derive(Clone, Debug, Default)]
15pub struct FakeProvider {
16    pub(crate) state: Arc<RwLock<State>>,
17}
18
19#[derive(Debug, Default)]
20pub(crate) struct State {
21    pub(crate) repositories: BTreeMap<Repository, (RepositoryFacts, RepositorySettings)>,
22    pub(crate) rulesets: BTreeMap<Repository, Vec<Ruleset>>,
23    pub(crate) secret_names: BTreeMap<Repository, Vec<String>>,
24    pub(crate) issues: BTreeMap<(Repository, IssueNumber), Issue>,
25    pub(crate) labels: BTreeMap<Repository, Vec<Label>>,
26    pub(crate) change_requests: BTreeMap<(Repository, ChangeRequestNumber), ChangeRequest>,
27    pub(crate) review_target_observations: Vec<(Repository, ReviewRequestTarget, ReviewTarget)>,
28    pub(crate) check_runs: BTreeMap<(Repository, String), Vec<CheckRun>>,
29    pub(crate) published_checks: Vec<(Repository, String, CheckOutcome)>,
30    pub(crate) dispatches: Vec<(Repository, String, String, DispatchInputs)>,
31    pub(crate) runs: BTreeMap<(Repository, RunId), WorkflowRun>,
32    pub(crate) cancelled_runs: Vec<(Repository, RunId)>,
33    pub(crate) releases: BTreeMap<(Repository, String), Release>,
34    pub(crate) assets: BTreeMap<(Repository, AssetId), Vec<Bytes>>,
35    pub(crate) next_release_id: u64,
36    pub(crate) next_asset_id: u64,
37}
38
39impl FakeProvider {
40    #[must_use]
41    pub fn new() -> Self {
42        Self::default()
43    }
44
45    pub async fn seed_repository(&self, facts: RepositoryFacts, settings: RepositorySettings) {
46        self.state
47            .write()
48            .await
49            .repositories
50            .insert(facts.repository.clone(), (facts, settings));
51    }
52
53    pub async fn seed_issue(&self, repository: Repository, issue: Issue) {
54        self.state
55            .write()
56            .await
57            .issues
58            .insert((repository, issue.number), issue);
59    }
60
61    /// Seeds one change request into the repository it targets.
62    ///
63    /// The head it proposes is `change_request.head`, which names the
64    /// repository holding that branch and so can be a fork of `repository`.
65    pub async fn seed_change_request(&self, repository: Repository, change_request: ChangeRequest) {
66        self.state
67            .write()
68            .await
69            .change_requests
70            .insert((repository, change_request.number), change_request);
71    }
72
73    /// Seeds the provider observation returned for one review-request target.
74    ///
75    /// The target category is part of the lookup key while `observed` supplies
76    /// the actual actor or team category. Keeping them separate lets tests
77    /// model a request target that resolves to the wrong kind without the fake
78    /// inferring identity facts from the requested enum variant.
79    pub async fn seed_review_request_target(
80        &self,
81        repository: Repository,
82        target: ReviewRequestTarget,
83        observed: ReviewTarget,
84    ) {
85        let mut state = self.state.write().await;
86        state
87            .review_target_observations
88            .retain(|(seeded_repository, seeded_target, _)| {
89                seeded_repository != &repository || seeded_target != &target
90            });
91        state
92            .review_target_observations
93            .push((repository, target, observed));
94    }
95
96    /// Seeds observed checks, each on the commit it names, replacing whatever
97    /// was already seeded on the commits this call names.
98    ///
99    /// The commit comes from every run's own `head_sha`, so no seeded
100    /// observation can place a run on a commit it does not name.
101    pub async fn seed_check_runs(&self, repository: Repository, runs: Vec<CheckRun>) {
102        let mut state = self.state.write().await;
103        let mut replaced = BTreeSet::new();
104        for run in runs {
105            let key = (repository.clone(), run.head_sha.clone());
106            if replaced.insert(key.clone()) {
107                state.check_runs.insert(key.clone(), Vec::new());
108            }
109            state.check_runs.entry(key).or_default().push(run);
110        }
111    }
112
113    pub async fn seed_run(&self, repository: Repository, run: WorkflowRun) {
114        self.state
115            .write()
116            .await
117            .runs
118            .insert((repository, run.id), run);
119    }
120
121    pub async fn seed_release(&self, repository: Repository, release: Release) {
122        self.state
123            .write()
124            .await
125            .releases
126            .insert((repository, release.tag.clone()), release);
127    }
128
129    pub async fn published_checks(&self) -> Vec<(Repository, String, CheckOutcome)> {
130        self.state.read().await.published_checks.clone()
131    }
132
133    pub async fn dispatches(&self) -> Vec<(Repository, String, String, DispatchInputs)> {
134        self.state.read().await.dispatches.clone()
135    }
136}
137
138pub(crate) fn missing(entity: impl Into<String>) -> ProviderError {
139    ProviderError::NotFound {
140        entity: entity.into(),
141    }
142}