Skip to content

Commit 2aa1232

Browse files
authored
Add testing capabilities (#20)
* chore: Change the dependency for supabase-wrappers to their repos 'main' branch in order to use the latest pgrx version * feat: Add testing and a test to create a test foreign table * feat,fix: Fix testcontainer startup and listening. Add test for insertion into table and select * fix: More code reuse for testing * chore: Add github action for building and testing * fix: Fix test/build workflow. It doesn't do integration tests now * feat: Add tests for all crud things currently supported * fix: Remove unused/commented out code. Remove apt update from workflow * fix: Run apt command as sudo * fix: Add readline to the packages for apt to install * feat: Add integration tests back in
1 parent 169d0ac commit 2aa1232

File tree

3 files changed

+191
-3
lines changed

3 files changed

+191
-3
lines changed

.github/workflows/build_test.yaml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Cargo Build & Test
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
env:
8+
CARGO_TERM_COLOR: always
9+
10+
jobs:
11+
build_and_test:
12+
name: etcd_fdw Build and Test
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- name: Cache build/deps
18+
uses: actions/cache@v4
19+
with:
20+
path: |
21+
~/.cargo/bin/
22+
~/.cargo/registry/index/
23+
~/.cargo/registry/cache/
24+
~/.cargo/git/db/
25+
target/
26+
key: cargo-${{ hashFiles('**/Cargo.lock') }}
27+
28+
- name: Cache pgrx
29+
uses: actions/cache@v4
30+
with:
31+
path: |
32+
~/.pgrx/
33+
key: pgrx-0.16.0
34+
35+
- name: Install latest stable toolchain
36+
uses: actions-rs/toolchain@v1
37+
with:
38+
toolchain: stable
39+
override: true
40+
components: rustfmt, clippy
41+
- run: sudo apt install build-essential bison flex clang protobuf-compiler libreadline8 libreadline-dev -y
42+
- run: cargo install cargo-pgrx --version 0.16.0
43+
- run: cargo pgrx init
44+
- run: cargo build --verbose
45+
- run: cargo test --verbose
46+
- run: cargo pgrx test --verbose
47+
48+

Cargo.toml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,23 @@ pg14 = ["pgrx/pg14", "pgrx-tests/pg14", "supabase-wrappers/pg14"]
1717
pg15 = ["pgrx/pg15", "pgrx-tests/pg15", "supabase-wrappers/pg15"]
1818
pg16 = ["pgrx/pg16", "pgrx-tests/pg16", "supabase-wrappers/pg16"]
1919
pg17 = ["pgrx/pg17", "pgrx-tests/pg17", "supabase-wrappers/pg17"]
20+
pg18 = ["pgrx/pg18", "pgrx-tests/pg18", "supabase-wrappers/pg18"]
2021
pg_test = []
2122

2223
[dependencies]
2324
etcd-client = { version = "0.16", features = ["tls"] }
2425
futures = "0.3.31"
25-
pgrx = {version="=0.14.3"}
26-
supabase-wrappers = {version="0.1.23", default-features = false}
26+
pgrx = {version="=0.16.0"}
27+
supabase-wrappers = {git="https://github.com/supabase/wrappers.git", branch="main",default-features = false}
2728
thiserror = "2.0.16"
2829
tokio = { version = "1.47.1", features = ["full"] }
30+
testcontainers = { version = "0.25.0", features = ["blocking"] }
31+
serde = { version = "1.0.226", features = ["derive"] }
2932

3033
[dev-dependencies]
31-
pgrx-tests = "=0.14.3"
34+
pgrx-tests = "=0.16.0"
35+
testcontainers = { version = "0.25.0", features = ["blocking"] }
36+
serde = { version = "1.0.226", features = ["derive"] }
3237

3338
[profile.dev]
3439
panic = "unwind"

src/lib.rs

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use etcd_client::{Client, ConnectOptions, TlsOptions, Identity, Certificate, Err
22
use std::time::Duration;
33
use pgrx::pg_sys::panic::ErrorReport;
44
use pgrx::PgSqlErrorCode;
5+
use pgrx::*;
56
use supabase_wrappers::prelude::*;
67
use thiserror::Error;
78

@@ -424,3 +425,137 @@ impl ForeignDataWrapper<EtcdFdwError> for EtcdFdw {
424425
Ok(())
425426
}
426427
}
428+
429+
#[cfg(test)]
430+
pub mod pg_test {
431+
432+
pub fn setup(_options: Vec<&str>) {
433+
// perform one-off initialization when the pg_test framework starts
434+
}
435+
436+
pub fn postgresql_conf_options() -> Vec<&'static str> {
437+
// return any postgresql.conf settings that are required for your tests
438+
vec![]
439+
}
440+
}
441+
442+
#[pg_schema]
443+
#[cfg(any(test, feature = "pg_test"))]
444+
mod tests {
445+
use std::time::Duration;
446+
447+
use super::*;
448+
use testcontainers::{
449+
core::{IntoContainerPort, WaitFor},
450+
runners::SyncRunner,
451+
Container, GenericImage, ImageExt,
452+
};
453+
454+
const CMD: [&'static str; 5] = [
455+
"/usr/local/bin/etcd",
456+
"--listen-client-urls",
457+
"http://0.0.0.0:2379",
458+
"--advertise-client-urls",
459+
"http://0.0.0.0:2379",
460+
];
461+
462+
fn create_container() -> (Container<GenericImage>, String) {
463+
let container = GenericImage::new("quay.io/coreos/etcd", "v3.6.4")
464+
.with_exposed_port(2379.tcp())
465+
.with_wait_for(WaitFor::message_on_either_std(
466+
"ready to serve client requests",
467+
))
468+
.with_privileged(true)
469+
.with_cmd(CMD)
470+
.with_startup_timeout(Duration::from_secs(90))
471+
.start()
472+
.expect("An etcd image was supposed to be started");
473+
474+
let host = container
475+
.get_host()
476+
.expect("Host-address should be available");
477+
478+
let port = container
479+
.get_host_port_ipv4(2379.tcp())
480+
.expect("Exposed host port should be available");
481+
482+
let url = format!("{}:{}", host, port);
483+
(container, url)
484+
}
485+
486+
fn create_fdt(url: String) -> () {
487+
Spi::run("CREATE FOREIGN DATA WRAPPER etcd_fdw handler etcd_fdw_handler validator etcd_fdw_validator;").expect("FDW should have been created");
488+
489+
// Create a server
490+
Spi::run(
491+
format!(
492+
"CREATE SERVER etcd_test_server FOREIGN DATA WRAPPER etcd_fdw options(connstr '{}')",
493+
url
494+
)
495+
.as_str(),
496+
)
497+
.expect("Server should have been created");
498+
499+
// Create a foreign table
500+
Spi::run("CREATE FOREIGN TABLE test (key text, value text) server etcd_test_server options (rowid_column 'key')").expect("Test table should have been created");
501+
}
502+
503+
#[pg_test]
504+
fn test_create_table() {
505+
let (_container, url) = create_container();
506+
507+
create_fdt(url);
508+
}
509+
#[pg_test]
510+
fn test_insert_select() {
511+
let (_container, url) = create_container();
512+
513+
create_fdt(url);
514+
515+
// Insert into the foreign table
516+
Spi::run("INSERT INTO test (key, value) VALUES ('foo','bar'),('bar','baz')")
517+
.expect("INSERT should work");
518+
519+
let query_result = Spi::get_two::<String, String>("SELECT * FROM test WHERE key='foo'")
520+
.expect("Select should work");
521+
522+
assert_eq!((Some(format!("foo")), Some(format!("bar"))), query_result);
523+
let query_result = Spi::get_two::<String, String>("SELECT * FROM test WHERE key='bar'")
524+
.expect("SELECT should work");
525+
526+
assert_eq!((Some(format!("bar")), Some(format!("baz"))), query_result);
527+
}
528+
529+
#[pg_test]
530+
fn test_update() {
531+
let (_container, url) = create_container();
532+
533+
create_fdt(url);
534+
535+
Spi::run("INSERT INTO test (key, value) VALUES ('foo','bar'),('bar','baz')")
536+
.expect("INSERT should work");
537+
538+
Spi::run("UPDATE test SET value='test_successful'").expect("UPDATE should work");
539+
540+
let query_result =
541+
Spi::get_one::<String>("SELECT value FROM test;").expect("SELECT should work");
542+
543+
assert_eq!(Some(format!("test_successful")), query_result);
544+
}
545+
546+
#[pg_test]
547+
fn test_delete() {
548+
let (_container, url) = create_container();
549+
550+
create_fdt(url);
551+
552+
Spi::run("INSERT INTO test (key, value) VALUES ('foo','bar'),('bar','baz')")
553+
.expect("INSERT should work");
554+
555+
Spi::run("DELETE FROM test").expect("DELETE should work");
556+
557+
let query_result = Spi::get_one::<String>("SELECT value FROM test;");
558+
559+
assert_eq!(Err(spi::SpiError::InvalidPosition), query_result);
560+
}
561+
}

0 commit comments

Comments
 (0)