gitlab_runner_mock/
lib.rs

1// Required for serde_json::json macro on the massive job json
2#![recursion_limit = "256"]
3
4use std::sync::Arc;
5use std::sync::Mutex;
6use url::Url;
7use wiremock::matchers::{body_json_schema, method, path, path_regex};
8use wiremock::{Mock, MockServer};
9
10mod job;
11mod variables;
12pub use job::{
13    MockJob, MockJobArtifactWhen, MockJobBuilder, MockJobState, MockJobStepName, MockJobStepWhen,
14};
15
16mod api;
17use api::JobArtifactsDownloader;
18use api::JobArtifactsUploader;
19use api::JobTraceResponder;
20use api::JobUpdateResponder;
21use api::{JobRequest, JobRequestResponder};
22
23#[derive(Default)]
24struct JobData {
25    last_id: u64,
26    jobs: Vec<MockJob>,
27}
28
29#[derive(Default, Clone)]
30struct ExpectedMetadata {
31    version: Option<String>,
32    revision: Option<String>,
33    platform: Option<String>,
34    architecture: Option<String>,
35}
36
37#[derive(Default, Clone)]
38struct ArtifactDownloadsCounter {
39    count: u32,
40    failure_count: u32,
41}
42
43struct Inner {
44    server: MockServer,
45    runner_token: String,
46    jobs: Mutex<JobData>,
47    update_interval: Mutex<u32>,
48    expected_metadata: Mutex<ExpectedMetadata>,
49    artifact_downloads_counter: Mutex<ArtifactDownloadsCounter>,
50}
51
52#[derive(Clone)]
53pub struct GitlabRunnerMock {
54    inner: Arc<Inner>,
55}
56
57impl GitlabRunnerMock {
58    pub async fn start() -> Self {
59        let m = MockServer::start().await;
60        let jobs = JobData {
61            last_id: 265,
62            ..Default::default()
63        };
64        let inner = Inner {
65            server: m,
66            runner_token: "fakerunnertoken".to_string(),
67            jobs: Mutex::new(jobs),
68            update_interval: Mutex::new(3),
69            expected_metadata: Mutex::new(ExpectedMetadata::default()),
70            artifact_downloads_counter: Mutex::new(ArtifactDownloadsCounter::default()),
71        };
72        let server = Self {
73            inner: Arc::new(inner),
74        };
75
76        Mock::given(method("POST"))
77            .and(path("api/v4/jobs/request"))
78            .and(body_json_schema::<JobRequest>)
79            .respond_with(JobRequestResponder::new(server.clone()))
80            .mount(&server.inner.server)
81            .await;
82
83        Mock::given(method("PUT"))
84            .and(path_regex(r"^/api/v4/jobs/\d+$"))
85            //.and(body_json_schema::<JobUpdate>)
86            .respond_with(JobUpdateResponder::new(server.clone()))
87            .mount(&server.inner.server)
88            .await;
89
90        Mock::given(method("PATCH"))
91            .and(path_regex(r"^/api/v4/jobs/\d+/trace"))
92            .respond_with(JobTraceResponder::new(server.clone()))
93            .mount(&server.inner.server)
94            .await;
95
96        Mock::given(method("POST"))
97            .and(path_regex(r"^/api/v4/jobs/\d+/artifacts"))
98            .respond_with(JobArtifactsUploader::new(server.clone()))
99            .mount(&server.inner.server)
100            .await;
101
102        Mock::given(method("GET"))
103            .and(path_regex(r"^/api/v4/jobs/\d+/artifacts"))
104            .respond_with(JobArtifactsDownloader::new(server.clone()))
105            .mount(&server.inner.server)
106            .await;
107
108        server
109    }
110
111    pub fn uri(&self) -> Url {
112        self.inner.server.uri().parse().expect("uri is not a url")
113    }
114
115    pub fn runner_token(&self) -> &str {
116        &self.inner.runner_token
117    }
118
119    pub fn add_dummy_job(&self, name: String) -> MockJob {
120        let mut jobs = self.inner.jobs.lock().unwrap();
121        jobs.last_id += 1;
122
123        let job = MockJob::new(name, jobs.last_id);
124        jobs.jobs.push(job.clone());
125        job
126    }
127
128    pub fn add_completed_job(&self, name: String, artifact: Vec<u8>) -> MockJob {
129        let mut jobs = self.inner.jobs.lock().unwrap();
130        jobs.last_id += 1;
131
132        let job = MockJob::new_completed(name, jobs.last_id, artifact);
133
134        jobs.jobs.push(job.clone());
135
136        job
137    }
138
139    pub fn job_builder(&self, name: String) -> MockJobBuilder {
140        let mut jobs = self.inner.jobs.lock().unwrap();
141        jobs.last_id += 1;
142
143        MockJobBuilder::new(name, jobs.last_id)
144    }
145
146    pub fn enqueue_job(&self, job: MockJob) {
147        let mut jobs = self.inner.jobs.lock().unwrap();
148        if jobs.jobs.iter().any(|j| j.id() == job.id()) {
149            panic!("Trying to requeue already queued job");
150        }
151
152        jobs.jobs.push(job);
153    }
154
155    fn grab_pending_job(&self) -> Option<MockJob> {
156        let jobs = self.inner.jobs.lock().unwrap();
157        for job in jobs.jobs.iter() {
158            if job.state() == MockJobState::Pending
159                && job.dependencies().iter().all(|d| d.state().finished())
160            {
161                return Some(job.clone());
162            }
163        }
164        None
165    }
166
167    pub fn get_job(&self, id: u64) -> Option<MockJob> {
168        let jobs = self.inner.jobs.lock().unwrap();
169        for job in jobs.jobs.iter() {
170            if job.id() == id {
171                return Some(job.clone());
172            }
173        }
174        None
175    }
176
177    pub async fn n_requests(&self) -> usize {
178        let requests = self.inner.server.received_requests().await.unwrap();
179        requests
180            .iter()
181            .filter(|r| r.url.path() == "/api/v4/jobs/request")
182            .count()
183    }
184
185    pub fn update_interval(&self) -> u32 {
186        *self.inner.update_interval.lock().unwrap()
187    }
188
189    pub fn set_update_interval(&self, interval: u32) {
190        *self.inner.update_interval.lock().unwrap() = interval;
191    }
192
193    pub(crate) fn expected_metadata(&self) -> ExpectedMetadata {
194        self.inner.expected_metadata.lock().unwrap().clone()
195    }
196
197    pub fn set_expected_metadata_version<S: Into<String>>(&self, version: S) {
198        self.inner.expected_metadata.lock().unwrap().version = Some(version.into());
199    }
200
201    pub fn set_expected_metadata_revision<S: Into<String>>(&self, revision: S) {
202        self.inner.expected_metadata.lock().unwrap().revision = Some(revision.into());
203    }
204
205    pub fn set_expected_metadata_platform<S: Into<String>>(&self, platform: S) {
206        self.inner.expected_metadata.lock().unwrap().platform = Some(platform.into());
207    }
208
209    pub fn set_expected_metadata_architecture<S: Into<String>>(&self, architecture: S) {
210        self.inner.expected_metadata.lock().unwrap().architecture = Some(architecture.into());
211    }
212
213    pub fn set_inject_artifact_download_failures(&self, failure_count: u32) {
214        self.inner
215            .artifact_downloads_counter
216            .lock()
217            .unwrap()
218            .failure_count = failure_count;
219    }
220}