assemblyline_client/
lib.rs1#![warn(missing_docs, non_ascii_idents, trivial_numeric_casts,
2 unused_crate_dependencies, noop_method_call, single_use_lifetimes, trivial_casts,
3 unused_lifetimes, nonstandard_style, variant_size_differences)]
4#![deny(keyword_idents)]
5#![allow(clippy::needless_return)]
7mod types;
12mod connection;
13mod modules;
14
15use std::sync::Arc;
16
17use modules::file::File;
18use modules::help::Help;
19use modules::alert::Alert;
20use modules::bundle::Bundle;
21use modules::error::Error;
22use modules::search::Search;
23use modules::ingest::Ingest;
24use modules::submit::Submit;
25pub use types::{Authentication, JsonMap};
26pub use connection::{Connection, TLSSettings};
27
28
29pub struct Client {
31 pub alert: Alert,
36 pub bundle: Bundle,
38 pub error: Error,
40 pub file: File,
42 pub help: Help,
45 pub ingest: Ingest,
48 pub search: Search,
55 pub submit: Submit,
61 }
65
66impl Client {
67 pub async fn connect(server: String, auth: Authentication) -> Result<Self, types::Error> {
69 let connection = Arc::new(Connection::connect(server, auth, None, connection::TLSSettings::Native, Default::default(), None).await?);
70 Ok(Self {
71 alert: Alert::new(connection.clone()),
72 bundle: Bundle::new(connection.clone()),
73 error: Error::new(connection.clone()),
74 file: File::new(connection.clone()),
75 help: Help::new(connection.clone()),
76 ingest: Ingest::new(connection.clone()),
77 search: Search::new(connection.clone()),
78 submit: Submit::new(connection)
79 })
81 }
82
83 pub async fn from_connection(connection: Arc<Connection>) -> Result<Self, types::Error> {
85 Ok(Self {
86 alert: Alert::new(connection.clone()),
87 bundle: Bundle::new(connection.clone()),
88 error: Error::new(connection.clone()),
89 file: File::new(connection.clone()),
90 help: Help::new(connection.clone()),
91 ingest: Ingest::new(connection.clone()),
92 search: Search::new(connection.clone()),
93 submit: Submit::new(connection)
94 })
96 }
97}
98
99
100#[cfg(test)]
101mod tests {
102
103 use std::sync::Arc;
104
105 use assemblyline_models::datastore::submission::{SubmissionParams, SubmissionState, ServiceSelection};
106 use assemblyline_models::types::ClassificationString;
107 use rand::Rng;
108
109 use crate::{Authentication, Client, Connection};
110
111 fn init() {
112 let _ = env_logger::builder().is_test(true).try_init();
113 }
114
115 pub (crate) async fn prepare_client() -> Client {
116 init();
117 let url = std::env::var("ASSEMBLYLINE_URL").unwrap();
118 let username = std::env::var("ASSEMBLYLINE_USER").unwrap();
119 let key = std::env::var("ASSEMBLYLINE_KEY").unwrap();
120
121 let connection = Connection::connect(
122 url,
123 Authentication::ApiKey { username, key },
124 Some(2),
125 crate::TLSSettings::Native,
126 Default::default(),
127 Some(30.0)
128 ).await.unwrap();
129 Client::from_connection(Arc::new(connection)).await.unwrap()
130 }
132
133 fn random_body() -> Vec<u8> {
134 let mut out = vec![];
135 let mut prng = rand::rng();
136 let length = 128 + prng.random_range(0..256);
137 while out.len() < length {
138 out.push(prng.random());
139 }
140 out
141 }
142
143 #[tokio::test]
144 async fn submit_content() {
145 let client = prepare_client().await;
146 assemblyline_models::disable_global_classification();
147
148 let result = client.submit.single()
149 .metadata_item("testbatch".to_owned(), "0".to_owned())
150 .params(SubmissionParams{ ttl: 1, ..SubmissionParams::new(ClassificationString::new_unchecked("U".to_string()))})
151 .fname("test-file".to_owned())
152 .content(random_body()).await.unwrap();
153
154 assert_eq!(result.state, SubmissionState::Submitted);
155 }
156
157
158 #[tokio::test]
159 async fn search_single_page() {
160 let client = prepare_client().await;
161 assemblyline_models::disable_global_classification();
162 let batch: u64 = rand::rng().random();
163 let batch: String = batch.to_string();
164
165 let _result = client.ingest.single()
166 .metadata_item("testbatch".to_owned(), batch.clone())
167 .notification_queue(batch.clone())
168 .params(SubmissionParams{ priority: 300, ttl: 1, services: ServiceSelection{ selected: vec!["Characterize".into()], ..Default::default()}, ..SubmissionParams::new(ClassificationString::new_unchecked("U".to_owned()))})
169 .fname("test-file".to_owned())
170 .content(random_body()).await.unwrap();
171
172 for _ in 0..100 {
173 match client.ingest.get_message(&batch).await.unwrap() {
174 Some(message) => {
175 assert_eq!(message.submission.metadata.get("testbatch").unwrap(), &batch);
176 break;
177 },
178 None => { tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; },
179 }
180 }
181
182 for _ in 0..10 {
183 let result = client.search.submission(format!("metadata.testbatch: {batch}"))
184 .search().await.unwrap();
185
186 if result.items.len() == 1 {
187 return
188 }
189
190 tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
191 }
192 panic!()
193 }
194
195
196
197}