gitlab_runner_mock/
lib.rs1#![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 .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}