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