Skip to content

Commit 99803d9

Browse files
committed
chore(outbound-pg): Add basic integration test
Signed-off-by: Konstantin Shabanov <[email protected]>
1 parent 903a1d5 commit 99803d9

File tree

8 files changed

+216
-1
lines changed

8 files changed

+216
-1
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ default = []
8484
e2e-tests = []
8585
outbound-redis-tests = []
8686
config-provider-tests = []
87+
outbound-pg-tests = []
8788

8889
[workspace]
8990
members = [

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ test-outbound-redis:
4141
test-config-provider:
4242
RUST_LOG=$(LOG_LEVEL) cargo test --test integration --features config-provider-tests --no-fail-fast -- integration_tests::config_provider_tests --nocapture
4343

44+
.PHONY: test-outbound-pg
45+
test-outbound-pg:
46+
RUST_LOG=$(LOG_LEVEL) cargo test --test integration --features outbound-pg-tests --no-fail-fast -- --nocapture
47+
4448
.PHONY: test-sdk-go
4549
test-sdk-go:
4650
$(MAKE) -C sdk/go test

build.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const RUST_HTTP_INTEGRATION_TEST: &str = "tests/http/simple-spin-rust";
1111
const RUST_HTTP_INTEGRATION_ENV_TEST: &str = "tests/http/headers-env-routes-test";
1212
const RUST_HTTP_VAULT_CONFIG_TEST: &str = "tests/http/vault-config-test";
1313
const RUST_OUTBOUND_REDIS_INTEGRATION_TEST: &str = "tests/outbound-redis/http-rust-outbound-redis";
14+
const RUST_OUTBOUND_PG_INTEGRATION_TEST: &str = "tests/outbound-pg/http-rust-outbound-pg";
1415

1516
fn main() {
1617
let mut config = vergen::Config::default();
@@ -72,6 +73,7 @@ error: the `wasm32-wasi` target is not installed
7273
cargo_build(RUST_HTTP_INTEGRATION_ENV_TEST);
7374
cargo_build(RUST_HTTP_VAULT_CONFIG_TEST);
7475
cargo_build(RUST_OUTBOUND_REDIS_INTEGRATION_TEST);
76+
cargo_build(RUST_OUTBOUND_PG_INTEGRATION_TEST);
7577
}
7678

7779
fn build_wasm_test_program(name: &'static str, root: &'static str) {

tests/integration.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ mod integration_tests {
604604
}
605605

606606
#[cfg(feature = "outbound-redis-tests")]
607-
mod outbound_pg_tests {
607+
mod outbound_redis_tests {
608608
use super::*;
609609

610610
const RUST_OUTBOUND_REDIS_INTEGRATION_TEST: &str =
@@ -649,6 +649,32 @@ mod integration_tests {
649649
Ok(())
650650
}
651651

652+
#[cfg(feature = "outbound-pg-tests")]
653+
mod outbound_pg_tests {
654+
use super::*;
655+
656+
const RUST_OUTBOUND_PG_INTEGRATION_TEST: &str = "tests/outbound-pg/http-rust-outbound-pg";
657+
658+
#[tokio::test]
659+
async fn test_outbound_pg_rust_local() -> Result<()> {
660+
let s = SpinTestController::with_manifest(
661+
&format!(
662+
"{}/{}",
663+
RUST_OUTBOUND_PG_INTEGRATION_TEST, DEFAULT_MANIFEST_LOCATION
664+
),
665+
&[],
666+
&[],
667+
None,
668+
)
669+
.await?;
670+
671+
assert_status(&s, "/test_read_types", 200).await?;
672+
assert_status(&s, "/pg_backend_pid", 200).await?;
673+
674+
Ok(())
675+
}
676+
}
677+
652678
#[tokio::test]
653679
async fn test_static_assets_without_bindle() -> Result<()> {
654680
let s = SpinTestController::with_manifest(
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[build]
2+
target = "wasm32-wasi"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[package]
2+
name = "http-rust-outbound-pg"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
crate-type = [ "cdylib" ]
8+
9+
[dependencies]
10+
# Useful crate to handle errors.
11+
anyhow = "1"
12+
# Crate to simplify working with bytes.
13+
bytes = "1"
14+
# General-purpose crate with common HTTP types.
15+
http = "0.2"
16+
# The Spin SDK.
17+
spin-sdk = { path = "../../../sdk/rust" }
18+
# Crate that generates Rust Wasm bindings from a WebAssembly interface.
19+
wit-bindgen-rust = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "cb871cfa1ee460b51eb1d144b175b9aab9c50aba" }
20+
21+
[workspace]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
spin_version = "1"
2+
authors = ["Fermyon Engineering <[email protected]>"]
3+
name = "rust-outbound-pg-example"
4+
trigger = { type = "http", base = "/" }
5+
version = "0.1.0"
6+
7+
[[component]]
8+
environment = { DB_URL = "host=localhost user=postgres dbname=spin_dev" }
9+
id = "outbound-pg"
10+
source = "target/wasm32-wasi/release/http_rust_outbound_pg.wasm"
11+
[component.trigger]
12+
route = "/..."
13+
[component.build]
14+
command = "cargo build --target wasm32-wasi --release"
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
#![allow(dead_code)]
2+
use anyhow::{anyhow, Result};
3+
use spin_sdk::{
4+
http::{Request, Response},
5+
http_component, pg,
6+
};
7+
8+
// The environment variable set in `spin.toml` that points to the
9+
// address of the Pg server that the component will write to
10+
const DB_URL_ENV: &str = "DB_URL";
11+
12+
#[derive(Debug, Clone)]
13+
struct Row {
14+
id: i32,
15+
rvarchar: String,
16+
rtext: String,
17+
}
18+
19+
#[http_component]
20+
fn process(req: Request) -> Result<Response> {
21+
match req.uri().path() {
22+
"/test_read_types" => test_read_types(req),
23+
"/pg_backend_pid" => pg_backend_pid(req),
24+
_ => Ok(http::Response::builder()
25+
.status(404)
26+
.body(Some("Not found".into()))?),
27+
}
28+
}
29+
30+
fn test_read_types(_req: Request) -> Result<Response> {
31+
let address = std::env::var(DB_URL_ENV)?;
32+
33+
let create_table_sql = r#"
34+
CREATE TEMPORARY TABLE test_read_types (
35+
id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
36+
rvarchar varchar(40) NOT NULL,
37+
rtext text NOT NULL
38+
);
39+
"#;
40+
41+
pg::execute(&address, create_table_sql, &[])
42+
.map_err(|e| anyhow!("Error executing Postgres command: {:?}", e))?;
43+
44+
let insert_sql = r#"
45+
INSERT INTO test_read_types (rvarchar, rtext) VALUES
46+
(
47+
'rvarchar',
48+
'rtext'
49+
);
50+
"#;
51+
52+
pg::execute(&address, insert_sql, &[])
53+
.map_err(|e| anyhow!("Error executing Postgres command: {:?}", e))?;
54+
55+
let sql = "SELECT id, rvarchar, rtext FROM test_read_types";
56+
57+
let rowset = pg::query(&address, sql, &[])
58+
.map_err(|e| anyhow!("Error executing Postgres command: {:?}", e))?;
59+
60+
let column_summary = rowset
61+
.columns
62+
.iter()
63+
.map(format_col)
64+
.collect::<Vec<_>>()
65+
.join(", ");
66+
67+
let mut response_lines = vec![];
68+
69+
for row in rowset.rows {
70+
let id = as_int(&row[0])?;
71+
let rvarchar = as_owned_string(&row[1])?;
72+
let rtext = as_owned_string(&row[2])?;
73+
74+
let row = Row {
75+
id,
76+
rvarchar,
77+
rtext,
78+
};
79+
80+
response_lines.push(format!("row: {:#?}", row));
81+
}
82+
83+
let response = format!(
84+
"Found {} rows(s) as follows:\n{}\n\n(Column info: {})\n",
85+
response_lines.len(),
86+
response_lines.join("\n"),
87+
column_summary,
88+
);
89+
90+
Ok(http::Response::builder()
91+
.status(200)
92+
.body(Some(response.into()))?)
93+
}
94+
95+
fn pg_backend_pid(_req: Request) -> Result<Response> {
96+
let address = std::env::var(DB_URL_ENV)?;
97+
let sql = "SELECT pg_backend_pid()";
98+
99+
let get_pid = || {
100+
let rowset = pg::query(&address, sql, &[])
101+
.map_err(|e| anyhow!("Error executing Postgres query: {:?}", e))?;
102+
103+
let row = &rowset.rows[0];
104+
as_int(&row[0])
105+
};
106+
107+
assert_eq!(get_pid()?, get_pid()?);
108+
109+
let response = format!("pg_backend_pid: {}\n", get_pid()?);
110+
111+
Ok(http::Response::builder()
112+
.status(200)
113+
.body(Some(response.into()))?)
114+
}
115+
116+
fn as_owned_string(value: &pg::DbValue) -> anyhow::Result<String> {
117+
match value {
118+
pg::DbValue::Str(s) => Ok(s.to_owned()),
119+
_ => Err(anyhow!("Expected string from database but got {:?}", value)),
120+
}
121+
}
122+
123+
fn as_int(value: &pg::DbValue) -> anyhow::Result<i32> {
124+
match value {
125+
pg::DbValue::Int32(n) => Ok(*n),
126+
_ => Err(anyhow!(
127+
"Expected integer from database but got {:?}",
128+
value
129+
)),
130+
}
131+
}
132+
133+
fn as_bigint(value: &pg::DbValue) -> anyhow::Result<i64> {
134+
match value {
135+
pg::DbValue::Int64(n) => Ok(*n),
136+
_ => Err(anyhow!(
137+
"Expected integer from database but got {:?}",
138+
value
139+
)),
140+
}
141+
}
142+
143+
fn format_col(column: &pg::Column) -> String {
144+
format!("{}:{:?}", column.name, column.data_type)
145+
}

0 commit comments

Comments
 (0)