Skip to content

Commit 521b87f

Browse files
authored
Merge pull request #12 from supabase/test/unit
Add unit tests
2 parents 4774bb1 + 4db484b commit 521b87f

File tree

3 files changed

+489
-74
lines changed

3 files changed

+489
-74
lines changed

src/builder.rs

Lines changed: 175 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,16 @@
1+
extern crate reqwest;
2+
13
use reqwest::{
24
header::{HeaderMap, HeaderValue},
35
Client, Error, Method, Response,
46
};
57

6-
macro_rules! filter {
7-
( $( $op:ident ),* ) => {
8-
$(
9-
pub fn $op(mut self, column: &str, param: &str) -> Self {
10-
self.queries.push((column.to_string(),
11-
format!("{}.{}", stringify!($op), param)));
12-
self
13-
}
14-
)*
15-
}
16-
}
17-
188
#[derive(Default)]
199
pub struct Builder {
2010
method: Method,
2111
url: String,
2212
schema: Option<String>,
23-
queries: Vec<(String, String)>,
13+
pub(crate) queries: Vec<(String, String)>,
2414
headers: HeaderMap,
2515
body: Option<String>,
2616
is_rpc: bool,
@@ -30,21 +20,32 @@ pub struct Builder {
3020
// TODO: Exact, planned, estimated count (HEAD verb)
3121
// TODO: Response format
3222
// TODO: Embedded resources
23+
// TODO: Content type (csv, etc.)
3324
impl Builder {
34-
pub fn new(url: &str, schema: Option<String>) -> Self {
35-
Builder {
25+
pub fn new<S>(url: S, schema: Option<String>) -> Self
26+
where
27+
S: Into<String>,
28+
{
29+
let mut builder = Builder {
3630
method: Method::GET,
37-
url: url.to_string(),
31+
url: url.into(),
3832
schema,
3933
headers: HeaderMap::new(),
4034
..Default::default()
41-
}
35+
};
36+
builder
37+
.headers
38+
.insert("Accept", HeaderValue::from_static("application/json"));
39+
builder
4240
}
4341

44-
pub fn auth(mut self, token: &str) -> Self {
42+
pub fn auth<S>(mut self, token: S) -> Self
43+
where
44+
S: Into<String>,
45+
{
4546
self.headers.append(
4647
"Authorization",
47-
HeaderValue::from_str(&format!("Bearer {}", token)).unwrap(),
48+
HeaderValue::from_str(&format!("Bearer {}", token.into())).unwrap(),
4849
);
4950
self
5051
}
@@ -55,33 +56,42 @@ impl Builder {
5556
// TODO: JSON columns
5657
// TODO: Computed (virtual) columns
5758
// TODO: Investigate character corner cases (Unicode, [ .,:()])
58-
pub fn select(mut self, column: &str) -> Self {
59+
pub fn select<S>(mut self, column: S) -> Self
60+
where
61+
S: Into<String>,
62+
{
5963
self.method = Method::GET;
60-
self.queries
61-
.push(("select".to_string(), column.to_string()));
64+
self.queries.push(("select".to_string(), column.into()));
6265
self
6366
}
6467

6568
// TODO: desc/asc
6669
// TODO: nullsfirst/nullslast
6770
// TODO: Multiple columns
6871
// TODO: Computed columns
69-
pub fn order(mut self, column: &str) -> Self {
70-
self.queries.push(("order".to_string(), column.to_string()));
72+
pub fn order<S>(mut self, column: S) -> Self
73+
where
74+
S: Into<String>,
75+
{
76+
self.queries.push(("order".to_string(), column.into()));
7177
self
7278
}
7379

7480
pub fn limit(mut self, count: usize) -> Self {
75-
self.headers.append(
76-
"Content-Range",
81+
self.headers
82+
.insert("Range-Unit", HeaderValue::from_static("items"));
83+
self.headers.insert(
84+
"Range",
7785
HeaderValue::from_str(&format!("0-{}", count - 1)).unwrap(),
7886
);
7987
self
8088
}
8189

8290
pub fn range(mut self, low: usize, high: usize) -> Self {
83-
self.headers.append(
84-
"Content-Range",
91+
self.headers
92+
.insert("Range-Unit", HeaderValue::from_static("items"));
93+
self.headers.insert(
94+
"Range",
8595
HeaderValue::from_str(&format!("{}-{}", low, high)).unwrap(),
8696
);
8797
self
@@ -98,48 +108,56 @@ impl Builder {
98108
// TODO: Write-only tables
99109
// TODO: URL-encoded payload
100110
// TODO: Allow specifying columns
101-
pub fn insert(mut self, body: &str) -> Self {
111+
pub fn insert<S>(mut self, body: S) -> Self
112+
where
113+
S: Into<String>,
114+
{
102115
self.method = Method::POST;
103116
self.headers
104-
.append("Prefer", HeaderValue::from_static("return=representation"));
105-
self.body = Some(body.to_string());
117+
.insert("Prefer", HeaderValue::from_static("return=representation"));
118+
self.body = Some(body.into());
106119
self
107120
}
108121

109-
pub fn insert_csv(mut self, body: &str) -> Self {
110-
self.headers
111-
.append("Content-Type", HeaderValue::from_static("text/csv"));
112-
self.insert(body)
113-
}
114-
115122
// TODO: Allow Prefer: resolution=ignore-duplicates
116123
// TODO: on_conflict (make UPSERT work on UNIQUE columns)
117-
pub fn upsert(mut self, body: &str) -> Self {
124+
pub fn upsert<S>(mut self, body: S) -> Self
125+
where
126+
S: Into<String>,
127+
{
118128
self.method = Method::POST;
119129
self.headers.append(
120130
"Prefer",
121131
// Maybe check if this works as intended...
122132
HeaderValue::from_static("return=representation; resolution=merge-duplicates"),
123133
);
124-
self.body = Some(body.to_string());
134+
self.body = Some(body.into());
125135
self
126136
}
127137

128-
pub fn single_upsert(mut self, primary_column: &str, key: &str, body: &str) -> Self {
138+
pub fn single_upsert<S, T, U>(mut self, primary_column: S, key: T, body: U) -> Self
139+
where
140+
S: Into<String>,
141+
T: Into<String>,
142+
U: Into<String>,
143+
{
129144
self.method = Method::PUT;
130145
self.headers
131146
.append("Prefer", HeaderValue::from_static("return=representation"));
132147
self.queries
133-
.push((primary_column.to_string(), format!("eq.{}", key)));
134-
self.body = Some(body.to_string());
148+
.push((primary_column.into(), format!("eq.{}", key.into())));
149+
self.body = Some(body.into());
135150
self
136151
}
137152

138-
pub fn update(mut self, body: &str) -> Self {
153+
pub fn update<S>(mut self, body: S) -> Self
154+
where
155+
S: Into<String>,
156+
{
139157
self.method = Method::PATCH;
140158
self.headers
141159
.append("Prefer", HeaderValue::from_static("return=representation"));
142-
self.body = Some(body.to_string());
160+
self.body = Some(body.into());
143161
self
144162
}
145163

@@ -150,32 +168,20 @@ impl Builder {
150168
self
151169
}
152170

153-
// It's unfortunate that `in` is a keyword, otherwise it'd belong in the
154-
// collection of filters below
155-
filter!(
156-
eq, gt, gte, lt, lte, neq, like, ilike, is, fts, plfts, phfts, wfts, cs, cd, ov, sl, sr,
157-
nxr, nxl, adj, not
158-
);
159-
160-
pub fn in_(mut self, column: &str, param: &str) -> Self {
161-
self.queries
162-
.push((column.to_string(), format!("in.{}", param)));
163-
self
164-
}
165-
166-
pub fn rpc(mut self, params: &str) -> Self {
171+
pub fn rpc<S>(mut self, params: S) -> Self
172+
where
173+
S: Into<String>,
174+
{
167175
self.method = Method::POST;
168-
self.body = Some(params.to_string());
176+
self.body = Some(params.into());
169177
self.is_rpc = true;
170178
self
171179
}
172180

173181
pub async fn execute(mut self) -> Result<Response, Error> {
174182
let mut req = Client::new().request(self.method.clone(), &self.url);
175183
if let Some(schema) = self.schema {
176-
// NOTE: Upstream bug: RPC only works with Accept-Profile
177-
// Will change when upstream is fixed
178-
let key = if !self.is_rpc || self.method == Method::GET || self.method == Method::HEAD {
184+
let key = if self.method == Method::GET || self.method == Method::HEAD {
179185
"Accept-Profile"
180186
} else {
181187
"Content-Profile"
@@ -188,8 +194,113 @@ impl Builder {
188194
req = req.body(body);
189195
}
190196

191-
let resp = req.send().await?;
197+
req.send().await
198+
}
199+
}
200+
201+
#[cfg(test)]
202+
mod tests {
203+
use super::*;
204+
205+
const TABLE_URL: &str = "http://localhost:3000/table";
206+
const RPC_URL: &str = "http://localhost/rpc";
207+
208+
#[test]
209+
fn only_accept_json() {
210+
let builder = Builder::new(TABLE_URL, None);
211+
assert_eq!(
212+
builder.headers.get("Accept").unwrap(),
213+
HeaderValue::from_static("application/json")
214+
);
215+
}
216+
217+
#[test]
218+
fn auth_with_token() {
219+
let builder = Builder::new(TABLE_URL, None).auth("$Up3rS3crET");
220+
assert_eq!(
221+
builder.headers.get("Authorization").unwrap(),
222+
HeaderValue::from_static("Bearer $Up3rS3crET")
223+
);
224+
}
225+
226+
#[test]
227+
fn select_assert_query() {
228+
let builder = Builder::new(TABLE_URL, None).select("some_table");
229+
assert_eq!(builder.method, Method::GET);
230+
assert_eq!(
231+
builder
232+
.queries
233+
.contains(&("select".to_string(), "some_table".to_string())),
234+
true
235+
);
236+
}
237+
238+
#[test]
239+
fn order_assert_query() {
240+
let builder = Builder::new(TABLE_URL, None).order("id");
241+
assert_eq!(
242+
builder
243+
.queries
244+
.contains(&("order".to_string(), "id".to_string())),
245+
true
246+
);
247+
}
248+
249+
#[test]
250+
fn limit_assert_range_header() {
251+
let builder = Builder::new(TABLE_URL, None).limit(20);
252+
assert_eq!(
253+
builder.headers.get("Range").unwrap(),
254+
HeaderValue::from_static("0-19")
255+
);
256+
}
257+
258+
#[test]
259+
fn range_assert_range_header() {
260+
let builder = Builder::new(TABLE_URL, None).range(10, 20);
261+
assert_eq!(
262+
builder.headers.get("Range").unwrap(),
263+
HeaderValue::from_static("10-20")
264+
);
265+
}
266+
267+
#[test]
268+
fn single_assert_accept_header() {
269+
let builder = Builder::new(TABLE_URL, None).single();
270+
assert_eq!(
271+
builder.headers.get("Accept").unwrap(),
272+
HeaderValue::from_static("application/vnd.pgrst.object+json")
273+
);
274+
}
275+
276+
#[test]
277+
fn upsert_assert_prefer_header() {
278+
let builder = Builder::new(TABLE_URL, None).upsert("ignored");
279+
assert_eq!(
280+
builder.headers.get("Prefer").unwrap(),
281+
HeaderValue::from_static("return=representation; resolution=merge-duplicates")
282+
);
283+
}
284+
285+
#[test]
286+
fn single_upsert_assert_prefer_header() {
287+
let builder = Builder::new(TABLE_URL, None).single_upsert("ignored", "ignored", "ignored");
288+
assert_eq!(
289+
builder.headers.get("Prefer").unwrap(),
290+
HeaderValue::from_static("return=representation")
291+
);
292+
}
293+
294+
#[test]
295+
fn not_rpc_should_not_have_flag() {
296+
let builder = Builder::new(TABLE_URL, None).select("ignored");
297+
assert_eq!(builder.is_rpc, false);
298+
}
192299

193-
Ok(resp)
300+
#[test]
301+
fn rpc_should_have_body_and_flag() {
302+
let builder = Builder::new(RPC_URL, None).rpc("{\"a\": 1, \"b\": 2}");
303+
assert_eq!(builder.body.unwrap(), "{\"a\": 1, \"b\": 2}");
304+
assert_eq!(builder.is_rpc, true);
194305
}
195306
}

0 commit comments

Comments
 (0)