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 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 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 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}