testcontainers_modules/surrealdb/
mod.rs1use std::{borrow::Cow, collections::HashMap};
2
3use testcontainers::{
4 core::{ContainerPort, WaitFor},
5 Image,
6};
7
8const NAME: &str = "surrealdb/surrealdb";
9const TAG: &str = "v2.2";
10
11pub const SURREALDB_PORT: ContainerPort = ContainerPort::Tcp(8000);
16
17#[derive(Debug, Clone)]
49pub struct SurrealDb {
50 env_vars: HashMap<String, String>,
51}
52
53impl SurrealDb {
54 pub fn with_user(mut self, user: &str) -> Self {
56 self.env_vars
57 .insert("SURREAL_USER".to_owned(), user.to_owned());
58 self
59 }
60
61 pub fn with_password(mut self, password: &str) -> Self {
63 self.env_vars
64 .insert("SURREAL_PASS".to_owned(), password.to_owned());
65 self
66 }
67
68 pub fn with_unauthenticated(mut self) -> Self {
70 self.env_vars
71 .insert("SURREAL_UNAUTHENTICATED".to_owned(), "true".to_string());
72 self
73 }
74
75 pub fn with_strict(mut self, strict: bool) -> Self {
77 self.env_vars
78 .insert("SURREAL_STRICT".to_owned(), strict.to_string());
79 self
80 }
81
82 pub fn with_all_capabilities(mut self, allow_all: bool) -> Self {
84 self.env_vars
85 .insert("SURREAL_CAPS_ALLOW_ALL".to_owned(), allow_all.to_string());
86 self
87 }
88}
89
90impl Default for SurrealDb {
91 fn default() -> Self {
92 let mut env_vars = HashMap::new();
93 env_vars.insert("SURREAL_USER".to_owned(), "root".to_owned());
94 env_vars.insert("SURREAL_PASS".to_owned(), "root".to_owned());
95 env_vars.insert("SURREAL_AUTH".to_owned(), "true".to_owned());
96 env_vars.insert("SURREAL_CAPS_ALLOW_ALL".to_owned(), "true".to_owned());
97 env_vars.insert("SURREAL_PATH".to_owned(), "memory".to_owned());
98
99 Self { env_vars }
100 }
101}
102
103impl Image for SurrealDb {
104 fn name(&self) -> &str {
105 NAME
106 }
107
108 fn tag(&self) -> &str {
109 TAG
110 }
111
112 fn ready_conditions(&self) -> Vec<WaitFor> {
113 vec![WaitFor::message_on_stdout("Started web server on ")]
114 }
115
116 fn env_vars(
117 &self,
118 ) -> impl IntoIterator<Item = (impl Into<Cow<'_, str>>, impl Into<Cow<'_, str>>)> {
119 &self.env_vars
120 }
121
122 fn cmd(&self) -> impl IntoIterator<Item = impl Into<Cow<'_, str>>> {
123 ["start"]
124 }
125
126 fn expose_ports(&self) -> &[ContainerPort] {
127 &[SURREALDB_PORT]
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use serde::{Deserialize, Serialize};
134 use surrealdb::{
135 engine::remote::ws::{Client, Ws},
136 opt::auth::Root,
137 Surreal,
138 };
139 use testcontainers::runners::AsyncRunner;
140
141 use super::*;
142
143 #[derive(Debug, Serialize, Deserialize)]
144 struct Name {
145 first: String,
146 last: String,
147 }
148
149 #[derive(Debug, Serialize, Deserialize)]
150 struct Person {
151 title: String,
152 name: Name,
153 marketing: bool,
154 }
155
156 #[tokio::test]
157 async fn surrealdb_select() -> Result<(), Box<dyn std::error::Error + 'static>> {
158 let _ = pretty_env_logger::try_init();
159 let node = SurrealDb::default().start().await?;
160 let host_port = node.get_host_port_ipv4(SURREALDB_PORT).await?;
161 let url = format!("127.0.0.1:{host_port}");
162
163 let db: Surreal<Client> = Surreal::init();
164 db.connect::<Ws>(url).await.unwrap();
165 db.signin(Root {
166 username: "root",
167 password: "root",
168 })
169 .await
170 .unwrap();
171
172 db.use_ns("test").use_db("test").await.unwrap();
173
174 db.create::<Option<Person>>(("person", "tobie"))
175 .content(Person {
176 title: "Founder & CEO".to_string(),
177 name: Name {
178 first: "Tobie".to_string(),
179 last: "Morgan Hitchcock".to_string(),
180 },
181 marketing: true,
182 })
183 .await
184 .unwrap();
185
186 let result = db
187 .select::<Option<Person>>(("person", "tobie"))
188 .await
189 .unwrap();
190
191 assert!(result.is_some());
192 let result = result.unwrap();
193
194 assert_eq!(result.title, "Founder & CEO");
195 assert_eq!(result.name.first, "Tobie");
196 assert_eq!(result.name.last, "Morgan Hitchcock");
197 assert!(result.marketing);
198 Ok(())
199 }
200
201 #[tokio::test]
202 async fn surrealdb_no_auth() -> Result<(), Box<dyn std::error::Error + 'static>> {
203 let _ = pretty_env_logger::try_init();
204 let node = SurrealDb::default().with_unauthenticated().start().await?;
205 let host_port = node.get_host_port_ipv4(SURREALDB_PORT).await?;
206 let url = format!("127.0.0.1:{host_port}");
207
208 let db: Surreal<Client> = Surreal::init();
209 db.connect::<Ws>(url).await.unwrap();
210 db.use_ns("test").use_db("test").await.unwrap();
211
212 db.create::<Option<Person>>(("person", "tobie"))
213 .content(Person {
214 title: "Founder & CEO".to_string(),
215 name: Name {
216 first: "Tobie".to_string(),
217 last: "Morgan Hitchcock".to_string(),
218 },
219 marketing: true,
220 })
221 .await
222 .unwrap();
223
224 let result = db
225 .select::<Option<Person>>(("person", "tobie"))
226 .await
227 .unwrap();
228
229 assert!(result.is_some());
230 let result = result.unwrap();
231
232 assert_eq!(result.title, "Founder & CEO");
233 assert_eq!(result.name.first, "Tobie");
234 assert_eq!(result.name.last, "Morgan Hitchcock");
235 assert!(result.marketing);
236 Ok(())
237 }
238}