diff --git a/crates/smoketests/modules/Cargo.lock b/crates/smoketests/modules/Cargo.lock index e26948a73f8..6b9601755fc 100644 --- a/crates/smoketests/modules/Cargo.lock +++ b/crates/smoketests/modules/Cargo.lock @@ -559,6 +559,110 @@ dependencies = [ "spacetimedb", ] +[[package]] +name = "smoketest-module-auto-migration-add-columns" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + +[[package]] +name = "smoketest-module-auto-migration-add-columns-again" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + +[[package]] +name = "smoketest-module-auto-migration-add-table-initial" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + +[[package]] +name = "smoketest-module-auto-migration-add-table-updated" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + +[[package]] +name = "smoketest-module-auto-migration-drop-event-table-after" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + +[[package]] +name = "smoketest-module-auto-migration-drop-event-table-before" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + +[[package]] +name = "smoketest-module-auto-migration-event-table-after" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + +[[package]] +name = "smoketest-module-auto-migration-event-table-before" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + +[[package]] +name = "smoketest-module-auto-migration-incompatible" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + +[[package]] +name = "smoketest-module-auto-migration-simple" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + +[[package]] +name = "smoketest-module-auto-migration-with-primary-key" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + +[[package]] +name = "smoketest-module-auto-migration-without-primary-key" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + +[[package]] +name = "smoketest-module-auto-migration-without-primary-key-v2" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + [[package]] name = "smoketest-module-autoinc-basic" version = "0.1.0" @@ -585,6 +689,14 @@ dependencies = [ "spacetimedb", ] +[[package]] +name = "smoketest-module-call-many" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + [[package]] name = "smoketest-module-call-reducer-procedure" version = "0.1.0" @@ -665,6 +777,14 @@ dependencies = [ "spacetimedb", ] +[[package]] +name = "smoketest-module-fail-initial-publish-broken" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + [[package]] name = "smoketest-module-fail-initial-publish-fixed" version = "0.1.0" @@ -697,6 +817,62 @@ dependencies = [ "spacetimedb", ] +[[package]] +name = "smoketest-module-http-egress" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + +[[package]] +name = "smoketest-module-http-routes" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + +[[package]] +name = "smoketest-module-http-routes-example" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + +[[package]] +name = "smoketest-module-http-routes-full-uri" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + +[[package]] +name = "smoketest-module-http-routes-request-body" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + +[[package]] +name = "smoketest-module-http-routes-strict-non-root" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + +[[package]] +name = "smoketest-module-http-routes-strict-root" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + [[package]] name = "smoketest-module-logs-level-filter" version = "0.1.0" @@ -753,6 +929,14 @@ dependencies = [ "spacetimedb", ] +[[package]] +name = "smoketest-module-noop" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + [[package]] name = "smoketest-module-panic" version = "0.1.0" @@ -817,6 +1001,14 @@ dependencies = [ "spacetimedb", ] +[[package]] +name = "smoketest-module-rls-broken-private-table" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + [[package]] name = "smoketest-module-rls-no-filter" version = "0.1.0" @@ -941,6 +1133,14 @@ dependencies = [ "spacetimedb", ] +[[package]] +name = "smoketest-module-views-broken-return-type" +version = "0.1.0" +dependencies = [ + "log", + "spacetimedb", +] + [[package]] name = "smoketest-module-views-callable" version = "0.1.0" diff --git a/crates/smoketests/modules/Cargo.toml b/crates/smoketests/modules/Cargo.toml index c5a3faffebb..63dc67687eb 100644 --- a/crates/smoketests/modules/Cargo.toml +++ b/crates/smoketests/modules/Cargo.toml @@ -15,7 +15,7 @@ members = [ # Views tests "views-basic", # "views-broken-namespace" - intentionally broken, uses runtime compilation - # "views-broken-return-type" - intentionally broken, uses runtime compilation + "views-broken-return-type", "views-sql", "views-auto-migrate", "views-auto-migrate-updated", @@ -35,6 +35,7 @@ members = [ # Security and permissions "rls", + "rls-broken-private-table", "rls-no-filter", "rls-with-filter", "permissions-private", @@ -44,6 +45,31 @@ members = [ "call-reducer-procedure", "call-reducer-procedure-identity-arg", "call-empty", + "call-many", + + # Auto-migration tests + "auto-migration-simple", + "auto-migration-incompatible", + "auto-migration-add-table-initial", + "auto-migration-add-table-updated", + "auto-migration-add-columns", + "auto-migration-add-columns-again", + "auto-migration-with-primary-key", + "auto-migration-without-primary-key", + "auto-migration-without-primary-key-v2", + "auto-migration-event-table-before", + "auto-migration-event-table-after", + "auto-migration-drop-event-table-before", + "auto-migration-drop-event-table-after", + + # HTTP tests + "http-egress", + "http-routes", + "http-routes-example", + "http-routes-strict-root", + "http-routes-strict-non-root", + "http-routes-full-uri", + "http-routes-request-body", # SQL format tests "sql-format", @@ -92,7 +118,8 @@ members = [ "namespaces", "new-user-flow", "module-nested-op", - # "fail-initial-publish-broken" - intentionally broken, uses runtime compilation + "noop", + "fail-initial-publish-broken", "fail-initial-publish-fixed", # Auto-increment tests (all 10 integer types in one module each) diff --git a/crates/smoketests/modules/auto-migration-add-columns-again/Cargo.toml b/crates/smoketests/modules/auto-migration-add-columns-again/Cargo.toml new file mode 100644 index 00000000000..2c04cf007a3 --- /dev/null +++ b/crates/smoketests/modules/auto-migration-add-columns-again/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "smoketest-module-auto-migration-add-columns-again" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true +log.workspace = true diff --git a/crates/smoketests/modules/auto-migration-add-columns-again/src/lib.rs b/crates/smoketests/modules/auto-migration-add-columns-again/src/lib.rs new file mode 100644 index 00000000000..02fa2d79a65 --- /dev/null +++ b/crates/smoketests/modules/auto-migration-add-columns-again/src/lib.rs @@ -0,0 +1,29 @@ +use spacetimedb::{log, ReducerContext, Table}; + +#[derive(Debug)] +#[spacetimedb::table(accessor = person)] +pub struct Person { + name: String, + age: u16, + #[default(19)] + mass: u16, + #[default(160)] + height: u32, +} + +#[spacetimedb::reducer] +pub fn add_person(ctx: &ReducerContext, name: String) { + ctx.db.person().insert(Person { + name, + age: 70, + mass: 180, + height: 72, + }); +} + +#[spacetimedb::reducer] +pub fn print_persons(ctx: &ReducerContext, prefix: String) { + for person in ctx.db.person().iter() { + log::info!("{}: {:?}", prefix, person); + } +} diff --git a/crates/smoketests/modules/auto-migration-add-columns/Cargo.toml b/crates/smoketests/modules/auto-migration-add-columns/Cargo.toml new file mode 100644 index 00000000000..5ec5422266d --- /dev/null +++ b/crates/smoketests/modules/auto-migration-add-columns/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "smoketest-module-auto-migration-add-columns" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true +log.workspace = true diff --git a/crates/smoketests/modules/auto-migration-add-columns/src/lib.rs b/crates/smoketests/modules/auto-migration-add-columns/src/lib.rs new file mode 100644 index 00000000000..e948bbe38db --- /dev/null +++ b/crates/smoketests/modules/auto-migration-add-columns/src/lib.rs @@ -0,0 +1,33 @@ +use spacetimedb::{log, ReducerContext, Table}; + +#[derive(Debug)] +#[spacetimedb::table(accessor = person)] +pub struct Person { + #[index(btree)] + name: String, + #[default(0)] + age: u16, + #[default(19)] + mass: u16, +} + +#[spacetimedb::reducer] +pub fn add_person(ctx: &ReducerContext, name: String) { + ctx.db.person().insert(Person { + name, + age: 70, + mass: 180, + }); +} + +#[spacetimedb::reducer] +pub fn print_persons(ctx: &ReducerContext, prefix: String) { + for person in ctx.db.person().iter() { + log::info!("{}: {:?}", prefix, person); + } +} + +#[spacetimedb::reducer(client_disconnected)] +pub fn identity_disconnected(_ctx: &ReducerContext) { + log::info!("FIRST_UPDATE: client disconnected"); +} diff --git a/crates/smoketests/modules/auto-migration-add-table-initial/Cargo.toml b/crates/smoketests/modules/auto-migration-add-table-initial/Cargo.toml new file mode 100644 index 00000000000..e3136adbe73 --- /dev/null +++ b/crates/smoketests/modules/auto-migration-add-table-initial/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "smoketest-module-auto-migration-add-table-initial" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true +log.workspace = true diff --git a/crates/smoketests/modules/auto-migration-add-table-initial/src/lib.rs b/crates/smoketests/modules/auto-migration-add-table-initial/src/lib.rs new file mode 100644 index 00000000000..b74a0577e15 --- /dev/null +++ b/crates/smoketests/modules/auto-migration-add-table-initial/src/lib.rs @@ -0,0 +1,53 @@ +use spacetimedb::{log, ReducerContext, SpacetimeType, Table}; +use PersonKind::*; + +#[spacetimedb::table(accessor = person, public)] +pub struct Person { + name: String, + kind: PersonKind, +} + +#[spacetimedb::reducer] +pub fn add_person(ctx: &ReducerContext, name: String, kind: String) { + let kind = kind_from_string(kind); + ctx.db.person().insert(Person { name, kind }); +} + +#[spacetimedb::reducer] +pub fn print_persons(ctx: &ReducerContext, prefix: String) { + for person in ctx.db.person().iter() { + let kind = kind_to_string(person.kind); + log::info!("{prefix}: {} - {kind}", person.name); + } +} + +#[spacetimedb::table(accessor = point_mass)] +pub struct PointMass { + mass: f64, + position: Vector2, +} + +#[derive(SpacetimeType, Clone, Copy)] +pub struct Vector2 { + x: f64, + y: f64, +} + +#[spacetimedb::table(accessor = person_info)] +pub struct PersonInfo { + #[primary_key] + id: u64, +} + +#[derive(SpacetimeType, Clone, Copy, PartialEq, Eq)] +pub enum PersonKind { + Student, +} + +fn kind_from_string(_: String) -> PersonKind { + Student +} + +fn kind_to_string(Student: PersonKind) -> &'static str { + "Student" +} diff --git a/crates/smoketests/modules/auto-migration-add-table-updated/Cargo.toml b/crates/smoketests/modules/auto-migration-add-table-updated/Cargo.toml new file mode 100644 index 00000000000..d5cd10e7ea7 --- /dev/null +++ b/crates/smoketests/modules/auto-migration-add-table-updated/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "smoketest-module-auto-migration-add-table-updated" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true +log.workspace = true diff --git a/crates/smoketests/modules/auto-migration-add-table-updated/src/lib.rs b/crates/smoketests/modules/auto-migration-add-table-updated/src/lib.rs new file mode 100644 index 00000000000..ef936e2b692 --- /dev/null +++ b/crates/smoketests/modules/auto-migration-add-table-updated/src/lib.rs @@ -0,0 +1,79 @@ +use spacetimedb::{log, ReducerContext, SpacetimeType, Table}; +use PersonKind::*; + +#[spacetimedb::table(accessor = person, public)] +pub struct Person { + name: String, + kind: PersonKind, +} + +#[spacetimedb::reducer] +pub fn add_person(ctx: &ReducerContext, name: String, kind: String) { + let kind = kind_from_string(kind); + ctx.db.person().insert(Person { name, kind }); +} + +#[spacetimedb::reducer] +pub fn print_persons(ctx: &ReducerContext, prefix: String) { + for person in ctx.db.person().iter() { + let kind = kind_to_string(person.kind); + log::info!("{prefix}: {} - {kind}", person.name); + } +} + +#[spacetimedb::table(accessor = point_mass)] +pub struct PointMass { + mass: f64, + position: Vector2, +} + +#[derive(SpacetimeType, Clone, Copy)] +pub struct Vector2 { + x: f64, + y: f64, +} + +#[spacetimedb::table(accessor = person_info)] +pub struct PersonInfo { + #[primary_key] + #[auto_inc] + id: u64, +} + +#[derive(SpacetimeType, Clone, Copy, PartialEq, Eq)] +pub enum PersonKind { + Student, + Professor, +} + +fn kind_from_string(kind: String) -> PersonKind { + match &*kind { + "Student" => Student, + "Professor" => Professor, + _ => panic!(), + } +} + +fn kind_to_string(kind: PersonKind) -> &'static str { + match kind { + Student => "Student", + Professor => "Professor", + } +} + +#[spacetimedb::table(accessor = book, public)] +pub struct Book { + isbn: String, +} + +#[spacetimedb::reducer] +pub fn add_book(ctx: &ReducerContext, isbn: String) { + ctx.db.book().insert(Book { isbn }); +} + +#[spacetimedb::reducer] +pub fn print_books(ctx: &ReducerContext, prefix: String) { + for book in ctx.db.book().iter() { + log::info!("{}: {}", prefix, book.isbn); + } +} diff --git a/crates/smoketests/modules/auto-migration-drop-event-table-after/Cargo.toml b/crates/smoketests/modules/auto-migration-drop-event-table-after/Cargo.toml new file mode 100644 index 00000000000..4a0d71553b6 --- /dev/null +++ b/crates/smoketests/modules/auto-migration-drop-event-table-after/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "smoketest-module-auto-migration-drop-event-table-after" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true +log.workspace = true diff --git a/crates/smoketests/modules/auto-migration-drop-event-table-after/src/lib.rs b/crates/smoketests/modules/auto-migration-drop-event-table-after/src/lib.rs new file mode 100644 index 00000000000..c4977172cae --- /dev/null +++ b/crates/smoketests/modules/auto-migration-drop-event-table-after/src/lib.rs @@ -0,0 +1,11 @@ +use spacetimedb::{ReducerContext, Table}; + +#[spacetimedb::table(accessor = person, public)] +pub struct Person { + name: String, +} + +#[spacetimedb::reducer] +pub fn add_person(ctx: &ReducerContext, name: String) { + ctx.db.person().insert(Person { name }); +} diff --git a/crates/smoketests/modules/auto-migration-drop-event-table-before/Cargo.toml b/crates/smoketests/modules/auto-migration-drop-event-table-before/Cargo.toml new file mode 100644 index 00000000000..90e4382cf60 --- /dev/null +++ b/crates/smoketests/modules/auto-migration-drop-event-table-before/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "smoketest-module-auto-migration-drop-event-table-before" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true +log.workspace = true diff --git a/crates/smoketests/modules/auto-migration-drop-event-table-before/src/lib.rs b/crates/smoketests/modules/auto-migration-drop-event-table-before/src/lib.rs new file mode 100644 index 00000000000..33b737ab9df --- /dev/null +++ b/crates/smoketests/modules/auto-migration-drop-event-table-before/src/lib.rs @@ -0,0 +1,25 @@ +use spacetimedb::{ReducerContext, Table}; + +#[spacetimedb::table(accessor = person, public)] +pub struct Person { + name: String, +} + +#[spacetimedb::table(accessor = some_event, public, event)] +pub struct SomeEvent { + account_id: u32, + name: String, +} + +#[spacetimedb::reducer] +pub fn add_person(ctx: &ReducerContext, name: String) { + ctx.db.person().insert(Person { name }); +} + +#[spacetimedb::reducer] +pub fn emit_event(ctx: &ReducerContext) { + ctx.db.some_event().insert(SomeEvent { + account_id: 7, + name: "alpha".to_string(), + }); +} diff --git a/crates/smoketests/modules/auto-migration-event-table-after/Cargo.toml b/crates/smoketests/modules/auto-migration-event-table-after/Cargo.toml new file mode 100644 index 00000000000..83da04df530 --- /dev/null +++ b/crates/smoketests/modules/auto-migration-event-table-after/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "smoketest-module-auto-migration-event-table-after" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true +log.workspace = true diff --git a/crates/smoketests/modules/auto-migration-event-table-after/src/lib.rs b/crates/smoketests/modules/auto-migration-event-table-after/src/lib.rs new file mode 100644 index 00000000000..c48d240b15f --- /dev/null +++ b/crates/smoketests/modules/auto-migration-event-table-after/src/lib.rs @@ -0,0 +1,13 @@ +use spacetimedb::{table, SpacetimeType}; + +#[derive(SpacetimeType)] +struct SomeProduct { + a: u32, + b: u64, + c: u128, +} + +#[table(accessor = some_event, public, event)] +struct SomeEvent { + prod: SomeProduct, +} diff --git a/crates/smoketests/modules/auto-migration-event-table-before/Cargo.toml b/crates/smoketests/modules/auto-migration-event-table-before/Cargo.toml new file mode 100644 index 00000000000..c823f521b83 --- /dev/null +++ b/crates/smoketests/modules/auto-migration-event-table-before/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "smoketest-module-auto-migration-event-table-before" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true +log.workspace = true diff --git a/crates/smoketests/modules/auto-migration-event-table-before/src/lib.rs b/crates/smoketests/modules/auto-migration-event-table-before/src/lib.rs new file mode 100644 index 00000000000..fed5208d432 --- /dev/null +++ b/crates/smoketests/modules/auto-migration-event-table-before/src/lib.rs @@ -0,0 +1,13 @@ +use spacetimedb::{table, SpacetimeType}; + +#[derive(SpacetimeType)] +struct SomeProduct { + a: u32, + b: u64, +} + +#[table(accessor = some_event, public, event)] +struct SomeEvent { + foo: String, + prod: SomeProduct, +} diff --git a/crates/smoketests/modules/auto-migration-incompatible/Cargo.toml b/crates/smoketests/modules/auto-migration-incompatible/Cargo.toml new file mode 100644 index 00000000000..2a5ef41d797 --- /dev/null +++ b/crates/smoketests/modules/auto-migration-incompatible/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "smoketest-module-auto-migration-incompatible" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true +log.workspace = true diff --git a/crates/smoketests/modules/auto-migration-incompatible/src/lib.rs b/crates/smoketests/modules/auto-migration-incompatible/src/lib.rs new file mode 100644 index 00000000000..aa32801c053 --- /dev/null +++ b/crates/smoketests/modules/auto-migration-incompatible/src/lib.rs @@ -0,0 +1,19 @@ +use spacetimedb::{log, ReducerContext, Table}; + +#[spacetimedb::table(accessor = person)] +pub struct Person { + name: String, + age: u128, +} + +#[spacetimedb::reducer] +pub fn add_person(ctx: &ReducerContext, name: String) { + ctx.db.person().insert(Person { name, age: 70 }); +} + +#[spacetimedb::reducer] +pub fn print_persons(ctx: &ReducerContext, prefix: String) { + for person in ctx.db.person().iter() { + log::info!("{}: {}", prefix, person.name); + } +} diff --git a/crates/smoketests/modules/auto-migration-simple/Cargo.toml b/crates/smoketests/modules/auto-migration-simple/Cargo.toml new file mode 100644 index 00000000000..1059adb92f9 --- /dev/null +++ b/crates/smoketests/modules/auto-migration-simple/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "smoketest-module-auto-migration-simple" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true +log.workspace = true diff --git a/crates/smoketests/modules/auto-migration-simple/src/lib.rs b/crates/smoketests/modules/auto-migration-simple/src/lib.rs new file mode 100644 index 00000000000..0322d4e9b47 --- /dev/null +++ b/crates/smoketests/modules/auto-migration-simple/src/lib.rs @@ -0,0 +1,18 @@ +use spacetimedb::{log, ReducerContext, Table}; + +#[spacetimedb::table(accessor = person)] +pub struct Person { + name: String, +} + +#[spacetimedb::reducer] +pub fn add_person(ctx: &ReducerContext, name: String) { + ctx.db.person().insert(Person { name }); +} + +#[spacetimedb::reducer] +pub fn print_persons(ctx: &ReducerContext, prefix: String) { + for person in ctx.db.person().iter() { + log::info!("{}: {}", prefix, person.name); + } +} diff --git a/crates/smoketests/modules/auto-migration-with-primary-key/Cargo.toml b/crates/smoketests/modules/auto-migration-with-primary-key/Cargo.toml new file mode 100644 index 00000000000..4672d5aa2e2 --- /dev/null +++ b/crates/smoketests/modules/auto-migration-with-primary-key/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "smoketest-module-auto-migration-with-primary-key" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true +log.workspace = true diff --git a/crates/smoketests/modules/auto-migration-with-primary-key/src/lib.rs b/crates/smoketests/modules/auto-migration-with-primary-key/src/lib.rs new file mode 100644 index 00000000000..13971597bb9 --- /dev/null +++ b/crates/smoketests/modules/auto-migration-with-primary-key/src/lib.rs @@ -0,0 +1,12 @@ +use spacetimedb::{ReducerContext, Table}; + +#[spacetimedb::table(accessor = person, public)] +pub struct Person { + #[primary_key] + name: String, +} + +#[spacetimedb::reducer] +pub fn add(ctx: &ReducerContext, name: String) { + ctx.db.person().insert(Person { name }); +} diff --git a/crates/smoketests/modules/auto-migration-without-primary-key-v2/Cargo.toml b/crates/smoketests/modules/auto-migration-without-primary-key-v2/Cargo.toml new file mode 100644 index 00000000000..05be2d05624 --- /dev/null +++ b/crates/smoketests/modules/auto-migration-without-primary-key-v2/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "smoketest-module-auto-migration-without-primary-key-v2" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true +log.workspace = true diff --git a/crates/smoketests/modules/auto-migration-without-primary-key-v2/src/lib.rs b/crates/smoketests/modules/auto-migration-without-primary-key-v2/src/lib.rs new file mode 100644 index 00000000000..0da075617b1 --- /dev/null +++ b/crates/smoketests/modules/auto-migration-without-primary-key-v2/src/lib.rs @@ -0,0 +1,14 @@ +use spacetimedb::{ReducerContext, Table}; + +#[spacetimedb::table(accessor = person, public)] +pub struct Person { + name: String, +} + +#[spacetimedb::reducer] +pub fn add(ctx: &ReducerContext, name: String) { + ctx.db.person().insert(Person { name }); +} + +#[spacetimedb::reducer] +pub fn noop(_ctx: &ReducerContext) {} diff --git a/crates/smoketests/modules/auto-migration-without-primary-key/Cargo.toml b/crates/smoketests/modules/auto-migration-without-primary-key/Cargo.toml new file mode 100644 index 00000000000..9c40d1eb012 --- /dev/null +++ b/crates/smoketests/modules/auto-migration-without-primary-key/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "smoketest-module-auto-migration-without-primary-key" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true +log.workspace = true diff --git a/crates/smoketests/modules/auto-migration-without-primary-key/src/lib.rs b/crates/smoketests/modules/auto-migration-without-primary-key/src/lib.rs new file mode 100644 index 00000000000..58422214d80 --- /dev/null +++ b/crates/smoketests/modules/auto-migration-without-primary-key/src/lib.rs @@ -0,0 +1,11 @@ +use spacetimedb::{ReducerContext, Table}; + +#[spacetimedb::table(accessor = person, public)] +pub struct Person { + name: String, +} + +#[spacetimedb::reducer] +pub fn add(ctx: &ReducerContext, name: String) { + ctx.db.person().insert(Person { name }); +} diff --git a/crates/smoketests/modules/call-many/Cargo.toml b/crates/smoketests/modules/call-many/Cargo.toml new file mode 100644 index 00000000000..3d617f26bf8 --- /dev/null +++ b/crates/smoketests/modules/call-many/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "smoketest-module-call-many" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true +log.workspace = true diff --git a/crates/smoketests/modules/call-many/src/lib.rs b/crates/smoketests/modules/call-many/src/lib.rs new file mode 100644 index 00000000000..8b5feefd098 --- /dev/null +++ b/crates/smoketests/modules/call-many/src/lib.rs @@ -0,0 +1,111 @@ +use spacetimedb::{log, ProcedureContext, ReducerContext}; + +#[spacetimedb::reducer] +pub fn say_reducer_0(_ctx: &ReducerContext) { + log::info!("Hello from reducer 0!"); +} + +#[spacetimedb::procedure] +pub fn say_procedure_0(_ctx: &mut ProcedureContext) { + log::info!("Hello from procedure 0!"); +} + +#[spacetimedb::reducer] +pub fn say_reducer_1(_ctx: &ReducerContext) { + log::info!("Hello from reducer 1!"); +} + +#[spacetimedb::procedure] +pub fn say_procedure_1(_ctx: &mut ProcedureContext) { + log::info!("Hello from procedure 1!"); +} + +#[spacetimedb::reducer] +pub fn say_reducer_2(_ctx: &ReducerContext) { + log::info!("Hello from reducer 2!"); +} + +#[spacetimedb::procedure] +pub fn say_procedure_2(_ctx: &mut ProcedureContext) { + log::info!("Hello from procedure 2!"); +} + +#[spacetimedb::reducer] +pub fn say_reducer_3(_ctx: &ReducerContext) { + log::info!("Hello from reducer 3!"); +} + +#[spacetimedb::procedure] +pub fn say_procedure_3(_ctx: &mut ProcedureContext) { + log::info!("Hello from procedure 3!"); +} + +#[spacetimedb::reducer] +pub fn say_reducer_4(_ctx: &ReducerContext) { + log::info!("Hello from reducer 4!"); +} + +#[spacetimedb::procedure] +pub fn say_procedure_4(_ctx: &mut ProcedureContext) { + log::info!("Hello from procedure 4!"); +} + +#[spacetimedb::reducer] +pub fn say_reducer_5(_ctx: &ReducerContext) { + log::info!("Hello from reducer 5!"); +} + +#[spacetimedb::procedure] +pub fn say_procedure_5(_ctx: &mut ProcedureContext) { + log::info!("Hello from procedure 5!"); +} + +#[spacetimedb::reducer] +pub fn say_reducer_6(_ctx: &ReducerContext) { + log::info!("Hello from reducer 6!"); +} + +#[spacetimedb::procedure] +pub fn say_procedure_6(_ctx: &mut ProcedureContext) { + log::info!("Hello from procedure 6!"); +} + +#[spacetimedb::reducer] +pub fn say_reducer_7(_ctx: &ReducerContext) { + log::info!("Hello from reducer 7!"); +} + +#[spacetimedb::procedure] +pub fn say_procedure_7(_ctx: &mut ProcedureContext) { + log::info!("Hello from procedure 7!"); +} + +#[spacetimedb::reducer] +pub fn say_reducer_8(_ctx: &ReducerContext) { + log::info!("Hello from reducer 8!"); +} + +#[spacetimedb::procedure] +pub fn say_procedure_8(_ctx: &mut ProcedureContext) { + log::info!("Hello from procedure 8!"); +} + +#[spacetimedb::reducer] +pub fn say_reducer_9(_ctx: &ReducerContext) { + log::info!("Hello from reducer 9!"); +} + +#[spacetimedb::procedure] +pub fn say_procedure_9(_ctx: &mut ProcedureContext) { + log::info!("Hello from procedure 9!"); +} + +#[spacetimedb::reducer] +pub fn say_reducer_10(_ctx: &ReducerContext) { + log::info!("Hello from reducer 10!"); +} + +#[spacetimedb::procedure] +pub fn say_procedure_10(_ctx: &mut ProcedureContext) { + log::info!("Hello from procedure 10!"); +} diff --git a/crates/smoketests/modules/http-egress/Cargo.toml b/crates/smoketests/modules/http-egress/Cargo.toml new file mode 100644 index 00000000000..28fcbdc46d5 --- /dev/null +++ b/crates/smoketests/modules/http-egress/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "smoketest-module-http-egress" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true +log.workspace = true diff --git a/crates/smoketests/modules/http-egress/src/lib.rs b/crates/smoketests/modules/http-egress/src/lib.rs new file mode 100644 index 00000000000..f137b1e296e --- /dev/null +++ b/crates/smoketests/modules/http-egress/src/lib.rs @@ -0,0 +1,16 @@ +use spacetimedb::ProcedureContext; + +#[spacetimedb::procedure] +pub fn request_disallowed_ip(ctx: &mut ProcedureContext, url: String) -> Result<(), String> { + match ctx.http.get(url) { + Ok(_) => Err("request unexpectedly succeeded".to_owned()), + Err(err) => { + let message = err.to_string(); + if message.contains("refusing to connect to private or special-purpose addresses") { + Ok(()) + } else { + Err(format!("unexpected error from http request: {message}")) + } + } + } +} diff --git a/crates/smoketests/modules/http-routes-example/Cargo.toml b/crates/smoketests/modules/http-routes-example/Cargo.toml new file mode 100644 index 00000000000..bd13a911518 --- /dev/null +++ b/crates/smoketests/modules/http-routes-example/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "smoketest-module-http-routes-example" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true +log.workspace = true diff --git a/crates/smoketests/modules/http-routes-example/src/lib.rs b/crates/smoketests/modules/http-routes-example/src/lib.rs new file mode 100644 index 00000000000..f8ce47a7720 --- /dev/null +++ b/crates/smoketests/modules/http-routes-example/src/lib.rs @@ -0,0 +1,48 @@ +use std::str::FromStr; + +use spacetimedb::http::{Body, HandlerContext, Request, Response, Router}; +use spacetimedb::Table; + +#[spacetimedb::table(accessor = data)] +struct Data { + #[primary_key] + #[auto_inc] + id: u64, + body: Vec, +} + +#[spacetimedb::http::handler] +fn insert(ctx: &mut HandlerContext, request: Request) -> Response { + let body: Vec = request.into_body().into_bytes().into(); + let id = ctx.with_tx(|tx| { + tx.db + .data() + .insert(Data { + id: 0, + body: body.clone(), + }) + .id + }); + Response::new(Body::from_bytes(format!("{id}"))) +} + +#[spacetimedb::http::handler] +fn retrieve(ctx: &mut HandlerContext, request: Request) -> Response { + let id = request + .uri() + .query() + .and_then(|query| query.strip_prefix("id=")) + .and_then(|id| u64::from_str(id).ok()) + .unwrap(); + let body = ctx.with_tx(|tx| tx.db.data().id().find(id).map(|data| data.body)); + if let Some(body) = body { + Response::new(Body::from_bytes(body)) + } else { + Response::builder().status(404).body(Body::empty()).unwrap() + } +} + +#[spacetimedb::http::router] +fn router() -> Router { + Router::new().post("/insert", insert).get("/retrieve", retrieve) +} diff --git a/crates/smoketests/modules/http-routes-full-uri/Cargo.toml b/crates/smoketests/modules/http-routes-full-uri/Cargo.toml new file mode 100644 index 00000000000..550d375d654 --- /dev/null +++ b/crates/smoketests/modules/http-routes-full-uri/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "smoketest-module-http-routes-full-uri" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true +log.workspace = true diff --git a/crates/smoketests/modules/http-routes-full-uri/src/lib.rs b/crates/smoketests/modules/http-routes-full-uri/src/lib.rs new file mode 100644 index 00000000000..013d8d383f0 --- /dev/null +++ b/crates/smoketests/modules/http-routes-full-uri/src/lib.rs @@ -0,0 +1,11 @@ +use spacetimedb::http::{Body, HandlerContext, Request, Response, Router}; + +#[spacetimedb::http::handler] +fn echo_uri(_ctx: &mut HandlerContext, req: Request) -> Response { + Response::new(Body::from_bytes(req.uri().to_string())) +} + +#[spacetimedb::http::router] +fn router() -> Router { + Router::new().get("/echo-uri", echo_uri) +} diff --git a/crates/smoketests/modules/http-routes-request-body/Cargo.toml b/crates/smoketests/modules/http-routes-request-body/Cargo.toml new file mode 100644 index 00000000000..ef85e54ce7c --- /dev/null +++ b/crates/smoketests/modules/http-routes-request-body/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "smoketest-module-http-routes-request-body" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true +log.workspace = true diff --git a/crates/smoketests/modules/http-routes-request-body/src/lib.rs b/crates/smoketests/modules/http-routes-request-body/src/lib.rs new file mode 100644 index 00000000000..cc9cafa3048 --- /dev/null +++ b/crates/smoketests/modules/http-routes-request-body/src/lib.rs @@ -0,0 +1,31 @@ +use spacetimedb::http::{Body, HandlerContext, Request, Response, Router}; + +#[spacetimedb::http::handler] +fn reverse_bytes(_ctx: &mut HandlerContext, req: Request) -> Response { + let mut reversed = req.into_body().into_bytes().to_vec(); + reversed.reverse(); + Response::new(Body::from_bytes(reversed)) +} + +#[spacetimedb::http::handler] +fn reverse_words(_ctx: &mut HandlerContext, req: Request) -> Response { + let body = match req.into_body().into_string() { + Ok(body) => body, + Err(_) => { + return Response::builder() + .status(400) + .body(Body::from_bytes("request body must be valid UTF-8")) + .expect("response builder should not fail"); + } + }; + + let reversed = body.split(' ').rev().collect::>().join(" "); + Response::new(Body::from_bytes(reversed)) +} + +#[spacetimedb::http::router] +fn router() -> Router { + Router::new() + .post("/reverse-bytes", reverse_bytes) + .post("/reverse-words", reverse_words) +} diff --git a/crates/smoketests/modules/http-routes-strict-non-root/Cargo.toml b/crates/smoketests/modules/http-routes-strict-non-root/Cargo.toml new file mode 100644 index 00000000000..97bace5306e --- /dev/null +++ b/crates/smoketests/modules/http-routes-strict-non-root/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "smoketest-module-http-routes-strict-non-root" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true +log.workspace = true diff --git a/crates/smoketests/modules/http-routes-strict-non-root/src/lib.rs b/crates/smoketests/modules/http-routes-strict-non-root/src/lib.rs new file mode 100644 index 00000000000..2db1e8023e5 --- /dev/null +++ b/crates/smoketests/modules/http-routes-strict-non-root/src/lib.rs @@ -0,0 +1,16 @@ +use spacetimedb::http::{Body, HandlerContext, Request, Response, Router}; + +#[spacetimedb::http::handler] +fn foo(_ctx: &mut HandlerContext, _req: Request) -> Response { + Response::new(Body::from_bytes("foo")) +} + +#[spacetimedb::http::handler] +fn foo_slash(_ctx: &mut HandlerContext, _req: Request) -> Response { + Response::new(Body::from_bytes("foo-slash")) +} + +#[spacetimedb::http::router] +fn router() -> Router { + Router::new().get("/foo", foo).get("/foo/", foo_slash) +} diff --git a/crates/smoketests/modules/http-routes-strict-root/Cargo.toml b/crates/smoketests/modules/http-routes-strict-root/Cargo.toml new file mode 100644 index 00000000000..6a50ce3e286 --- /dev/null +++ b/crates/smoketests/modules/http-routes-strict-root/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "smoketest-module-http-routes-strict-root" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true +log.workspace = true diff --git a/crates/smoketests/modules/http-routes-strict-root/src/lib.rs b/crates/smoketests/modules/http-routes-strict-root/src/lib.rs new file mode 100644 index 00000000000..390d9fe14b2 --- /dev/null +++ b/crates/smoketests/modules/http-routes-strict-root/src/lib.rs @@ -0,0 +1,30 @@ +use spacetimedb::http::{Body, HandlerContext, Request, Response, Router}; + +#[spacetimedb::http::handler] +fn empty_root(_ctx: &mut HandlerContext, _req: Request) -> Response { + Response::new(Body::from_bytes("empty")) +} + +#[spacetimedb::http::handler] +fn slash_root(_ctx: &mut HandlerContext, _req: Request) -> Response { + Response::new(Body::from_bytes("slash")) +} + +#[spacetimedb::http::handler] +fn foo(_ctx: &mut HandlerContext, _req: Request) -> Response { + Response::new(Body::from_bytes("foo")) +} + +#[spacetimedb::http::handler] +fn foo_slash(_ctx: &mut HandlerContext, _req: Request) -> Response { + Response::new(Body::from_bytes("foo-slash")) +} + +#[spacetimedb::http::router] +fn router() -> Router { + Router::new() + .get("", empty_root) + .get("/", slash_root) + .get("/foo", foo) + .get("/foo/", foo_slash) +} diff --git a/crates/smoketests/modules/http-routes/Cargo.toml b/crates/smoketests/modules/http-routes/Cargo.toml new file mode 100644 index 00000000000..1a8e87717a7 --- /dev/null +++ b/crates/smoketests/modules/http-routes/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "smoketest-module-http-routes" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true +log.workspace = true diff --git a/crates/smoketests/modules/http-routes/src/lib.rs b/crates/smoketests/modules/http-routes/src/lib.rs new file mode 100644 index 00000000000..f42d1834217 --- /dev/null +++ b/crates/smoketests/modules/http-routes/src/lib.rs @@ -0,0 +1,80 @@ +use spacetimedb::http::{Body, HandlerContext, Request, Response, Router}; +use spacetimedb::Table; + +#[spacetimedb::table(accessor = entries, public)] +pub struct Entry { + id: u64, + value: String, +} + +#[spacetimedb::http::handler] +fn get_simple(_ctx: &mut HandlerContext, _req: Request) -> Response { + Response::new(Body::from_bytes("ok")) +} + +#[spacetimedb::http::handler] +fn post_insert(ctx: &mut HandlerContext, _req: Request) -> Response { + ctx.with_tx(|tx| { + let id = tx.db.entries().iter().count() as u64; + tx.db.entries().insert(Entry { + id, + value: "posted".to_string(), + }); + }); + Response::new(Body::from_bytes("inserted")) +} + +#[spacetimedb::http::handler] +fn get_count(ctx: &mut HandlerContext, _req: Request) -> Response { + let count = ctx.with_tx(|tx| tx.db.entries().iter().count()); + Response::new(Body::from_bytes(count.to_string())) +} + +#[spacetimedb::http::handler] +fn any_handler(_ctx: &mut HandlerContext, _req: Request) -> Response { + Response::new(Body::from_bytes("any")) +} + +#[spacetimedb::http::handler] +fn header_echo(_ctx: &mut HandlerContext, req: Request) -> Response { + let value = req + .headers() + .get("x-echo") + .and_then(|value| value.to_str().ok()) + .unwrap_or(""); + Response::new(Body::from_bytes(value.to_string())) +} + +#[spacetimedb::http::handler] +fn set_response_header(_ctx: &mut HandlerContext, _req: Request) -> Response { + Response::builder() + .header("x-response", "set") + .body(Body::from_bytes("header-set")) + .expect("response builder should not fail") +} + +#[spacetimedb::http::handler] +fn body_handler(_ctx: &mut HandlerContext, _req: Request) -> Response { + Response::new(Body::from_bytes("non-empty")) +} + +#[spacetimedb::http::handler] +fn teapot(_ctx: &mut HandlerContext, _req: Request) -> Response { + Response::builder() + .status(418) + .body(Body::from_bytes("teapot")) + .expect("response builder should not fail") +} + +#[spacetimedb::http::router] +fn router() -> Router { + Router::new() + .get("/get", get_simple) + .post("/post", post_insert) + .get("/count", get_count) + .any("/any", any_handler) + .get("/header", header_echo) + .get("/set-header", set_response_header) + .get("/body", body_handler) + .get("/teapot", teapot) +} diff --git a/crates/smoketests/modules/noop/Cargo.toml b/crates/smoketests/modules/noop/Cargo.toml new file mode 100644 index 00000000000..c0f24646d6c --- /dev/null +++ b/crates/smoketests/modules/noop/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "smoketest-module-noop" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true +log.workspace = true diff --git a/crates/smoketests/modules/noop/src/lib.rs b/crates/smoketests/modules/noop/src/lib.rs new file mode 100644 index 00000000000..1467ef527fa --- /dev/null +++ b/crates/smoketests/modules/noop/src/lib.rs @@ -0,0 +1,4 @@ +use spacetimedb::ReducerContext; + +#[spacetimedb::reducer] +pub fn noop(_ctx: &ReducerContext) {} diff --git a/crates/smoketests/modules/rls-broken-private-table/Cargo.toml b/crates/smoketests/modules/rls-broken-private-table/Cargo.toml new file mode 100644 index 00000000000..f0cd150c7df --- /dev/null +++ b/crates/smoketests/modules/rls-broken-private-table/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "smoketest-module-rls-broken-private-table" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true +log.workspace = true diff --git a/crates/smoketests/modules/rls-broken-private-table/src/lib.rs b/crates/smoketests/modules/rls-broken-private-table/src/lib.rs new file mode 100644 index 00000000000..7333718cdde --- /dev/null +++ b/crates/smoketests/modules/rls-broken-private-table/src/lib.rs @@ -0,0 +1,9 @@ +use spacetimedb::{client_visibility_filter, Filter, Identity}; + +#[spacetimedb::table(accessor = user)] +pub struct User { + identity: Identity, +} + +#[client_visibility_filter] +const PERSON_FILTER: Filter = Filter::Sql("SELECT * FROM \"user\" WHERE identity = :sender"); diff --git a/crates/smoketests/modules/views-broken-return-type/src/lib.rs b/crates/smoketests/modules/views-broken-return-type/src/lib.rs index bab05cbee44..f39983c0fce 100644 --- a/crates/smoketests/modules/views-broken-return-type/src/lib.rs +++ b/crates/smoketests/modules/views-broken-return-type/src/lib.rs @@ -8,6 +8,6 @@ pub enum ABC { } #[spacetimedb::view(accessor = person, public)] -pub fn person(ctx: &ViewContext) -> Option { +pub fn person(_ctx: &ViewContext) -> Option { None } diff --git a/crates/smoketests/tests/smoketests/auto_migration.rs b/crates/smoketests/tests/smoketests/auto_migration.rs index 70f84b394b6..fea22c94e63 100644 --- a/crates/smoketests/tests/smoketests/auto_migration.rs +++ b/crates/smoketests/tests/smoketests/auto_migration.rs @@ -1,55 +1,12 @@ use spacetimedb_smoketests::{require_local_server, Smoketest}; -const MODULE_CODE_SIMPLE: &str = r#" -use spacetimedb::{log, ReducerContext, Table}; - -#[spacetimedb::table(accessor = person)] -pub struct Person { - name: String, -} - -#[spacetimedb::reducer] -pub fn add_person(ctx: &ReducerContext, name: String) { - ctx.db.person().insert(Person { name }); -} - -#[spacetimedb::reducer] -pub fn print_persons(ctx: &ReducerContext, prefix: String) { - for person in ctx.db.person().iter() { - log::info!("{}: {}", prefix, person.name); - } -} -"#; - -const MODULE_CODE_UPDATED_INCOMPATIBLE: &str = r#" -use spacetimedb::{log, ReducerContext, Table}; - -#[spacetimedb::table(accessor = person)] -pub struct Person { - name: String, - age: u128, -} - -#[spacetimedb::reducer] -pub fn add_person(ctx: &ReducerContext, name: String) { - ctx.db.person().insert(Person { name, age: 70 }); -} - -#[spacetimedb::reducer] -pub fn print_persons(ctx: &ReducerContext, prefix: String) { - for person in ctx.db.person().iter() { - log::info!("{}: {}", prefix, person.name); - } -} -"#; - /// Tests that a module with invalid schema changes cannot be published without -c or a migration. #[test] fn test_reject_schema_changes() { - let mut test = Smoketest::builder().module_code(MODULE_CODE_SIMPLE).build(); + let mut test = Smoketest::builder().precompiled_module("auto-migration-simple").build(); // Try to update with incompatible schema (adding column without default) - test.write_module_code(MODULE_CODE_UPDATED_INCOMPATIBLE).unwrap(); + test.use_precompiled_module("auto-migration-incompatible"); let result = test.publish().current_database().unwrap().run(); assert!( @@ -58,148 +15,12 @@ fn test_reject_schema_changes() { ); } -const MODULE_CODE_INIT: &str = r#" -use spacetimedb::{log, ReducerContext, Table, SpacetimeType}; -use PersonKind::*; - -#[spacetimedb::table(accessor = person, public)] -pub struct Person { - name: String, - kind: PersonKind, -} - -#[spacetimedb::reducer] -pub fn add_person(ctx: &ReducerContext, name: String, kind: String) { - let kind = kind_from_string(kind); - ctx.db.person().insert(Person { name, kind }); -} - -#[spacetimedb::reducer] -pub fn print_persons(ctx: &ReducerContext, prefix: String) { - for person in ctx.db.person().iter() { - let kind = kind_to_string(person.kind); - log::info!("{prefix}: {} - {kind}", person.name); - } -} - -#[spacetimedb::table(accessor = point_mass)] -pub struct PointMass { - mass: f64, - position: Vector2, -} - -#[derive(SpacetimeType, Clone, Copy)] -pub struct Vector2 { - x: f64, - y: f64, -} - -#[spacetimedb::table(accessor = person_info)] -pub struct PersonInfo { - #[primary_key] - id: u64, -} - -#[derive(SpacetimeType, Clone, Copy, PartialEq, Eq)] -pub enum PersonKind { - Student, -} - -fn kind_from_string(_: String) -> PersonKind { - Student -} - -fn kind_to_string(Student: PersonKind) -> &'static str { - "Student" -} -"#; - -const MODULE_CODE_UPDATED: &str = r#" -use spacetimedb::{log, ReducerContext, Table, SpacetimeType}; -use PersonKind::*; - -#[spacetimedb::table(accessor = person, public)] -pub struct Person { - name: String, - kind: PersonKind, -} - -#[spacetimedb::reducer] -pub fn add_person(ctx: &ReducerContext, name: String, kind: String) { - let kind = kind_from_string(kind); - ctx.db.person().insert(Person { name, kind }); -} - -#[spacetimedb::reducer] -pub fn print_persons(ctx: &ReducerContext, prefix: String) { - for person in ctx.db.person().iter() { - let kind = kind_to_string(person.kind); - log::info!("{prefix}: {} - {kind}", person.name); - } -} - -#[spacetimedb::table(accessor = point_mass)] -pub struct PointMass { - mass: f64, - position: Vector2, -} - -#[derive(SpacetimeType, Clone, Copy)] -pub struct Vector2 { - x: f64, - y: f64, -} - -#[spacetimedb::table(accessor = person_info)] -pub struct PersonInfo { - #[primary_key] - #[auto_inc] - id: u64, -} - -#[derive(SpacetimeType, Clone, Copy, PartialEq, Eq)] -pub enum PersonKind { - Student, - Professor, -} - -fn kind_from_string(kind: String) -> PersonKind { - match &*kind { - "Student" => Student, - "Professor" => Professor, - _ => panic!(), - } -} - -fn kind_to_string(kind: PersonKind) -> &'static str { - match kind { - Student => "Student", - Professor => "Professor", - } -} - -#[spacetimedb::table(accessor = book, public)] -pub struct Book { - isbn: String, -} - -#[spacetimedb::reducer] -pub fn add_book(ctx: &ReducerContext, isbn: String) { - ctx.db.book().insert(Book { isbn }); -} - -#[spacetimedb::reducer] -pub fn print_books(ctx: &ReducerContext, prefix: String) { - for book in ctx.db.book().iter() { - log::info!("{}: {}", prefix, book.isbn); - } -} -"#; - /// Tests uploading a module with a schema change that should not require clearing the database. #[test] fn test_add_table_auto_migration() { - let mut test = Smoketest::builder().module_code(MODULE_CODE_INIT).build(); + let mut test = Smoketest::builder() + .precompiled_module("auto-migration-add-table-initial") + .build(); let sub = test .subscribe(&["select * from person"]) @@ -231,7 +52,7 @@ fn test_add_table_auto_migration() { ); // Update module without clearing database - test.write_module_code(MODULE_CODE_UPDATED).unwrap(); + test.use_precompiled_module("auto-migration-add-table-updated"); test.publish().current_database().unwrap().run().unwrap(); // Add new data with updated schema @@ -278,71 +99,12 @@ fn test_add_table_auto_migration() { ); } -const MODULE_CODE_ADD_TABLE_COLUMNS_UPDATED: &str = r#" -use spacetimedb::{log, ReducerContext, Table}; - -#[derive(Debug)] -#[spacetimedb::table(accessor = person)] -pub struct Person { - #[index(btree)] - name: String, - #[default(0)] - age: u16, - #[default(19)] - mass: u16, -} - -#[spacetimedb::reducer] -pub fn add_person(ctx: &ReducerContext, name: String) { - ctx.db.person().insert(Person { name, age: 70, mass: 180 }); -} - -#[spacetimedb::reducer] -pub fn print_persons(ctx: &ReducerContext, prefix: String) { - for person in ctx.db.person().iter() { - log::info!("{}: {:?}", prefix, person); - } -} - -#[spacetimedb::reducer(client_disconnected)] -pub fn identity_disconnected(_ctx: &ReducerContext) { - log::info!("FIRST_UPDATE: client disconnected"); -} -"#; - -const MODULE_CODE_ADD_TABLE_COLUMNS_UPDATED_AGAIN: &str = r#" -use spacetimedb::{log, ReducerContext, Table}; - -#[derive(Debug)] -#[spacetimedb::table(accessor = person)] -pub struct Person { - name: String, - age: u16, - #[default(19)] - mass: u16, - #[default(160)] - height: u32, -} - -#[spacetimedb::reducer] -pub fn add_person(ctx: &ReducerContext, name: String) { - ctx.db.person().insert(Person { name, age: 70, mass: 180, height: 72 }); -} - -#[spacetimedb::reducer] -pub fn print_persons(ctx: &ReducerContext, prefix: String) { - for person in ctx.db.person().iter() { - log::info!("{}: {:?}", prefix, person); - } -} -"#; - /// Verify schema upgrades that add columns with defaults (twice). #[test] fn test_add_table_columns() { const NUM_SUBSCRIBERS: usize = 20; - let mut test = Smoketest::builder().module_code(MODULE_CODE_SIMPLE).build(); + let mut test = Smoketest::builder().precompiled_module("auto-migration-simple").build(); // Subscribe to person table changes multiple times to simulate active clients let mut subs = Vec::with_capacity(NUM_SUBSCRIBERS); @@ -355,7 +117,7 @@ fn test_add_table_columns() { test.call("add_person", &["Robert"]).unwrap(); // First upgrade: add age & mass columns - test.write_module_code(MODULE_CODE_ADD_TABLE_COLUMNS_UPDATED).unwrap(); + test.use_precompiled_module("auto-migration-add-columns"); let identity = test.database_identity.clone().unwrap(); test.publish().name(&identity).break_clients(true).run().unwrap(); test.call("print_persons", &["FIRST_UPDATE"]).unwrap(); @@ -393,8 +155,7 @@ fn test_add_table_columns() { } // Second upgrade - test.write_module_code(MODULE_CODE_ADD_TABLE_COLUMNS_UPDATED_AGAIN) - .unwrap(); + test.use_precompiled_module("auto-migration-add-columns-again"); test.publish().name(&identity).break_clients(true).run().unwrap(); test.call("print_persons", &["UPDATE_2"]).unwrap(); @@ -410,52 +171,6 @@ fn test_add_table_columns() { // --- Issue #3934: Removing a primary key breaks subsequent publishes --- -const MODULE_CODE_WITH_PK: &str = r#" -use spacetimedb::{ReducerContext, Table}; - -#[spacetimedb::table(accessor = person, public)] -pub struct Person { - #[primary_key] - name: String, -} - -#[spacetimedb::reducer] -pub fn add(ctx: &ReducerContext, name: String) { - ctx.db.person().insert(Person { name }); -} -"#; - -const MODULE_CODE_WITHOUT_PK: &str = r#" -use spacetimedb::{ReducerContext, Table}; - -#[spacetimedb::table(accessor = person, public)] -pub struct Person { - name: String, -} - -#[spacetimedb::reducer] -pub fn add(ctx: &ReducerContext, name: String) { - ctx.db.person().insert(Person { name }); -} -"#; - -const MODULE_CODE_WITHOUT_PK_V2: &str = r#" -use spacetimedb::{ReducerContext, Table}; - -#[spacetimedb::table(accessor = person, public)] -pub struct Person { - name: String, -} - -#[spacetimedb::reducer] -pub fn add(ctx: &ReducerContext, name: String) { - ctx.db.person().insert(Person { name }); -} - -#[spacetimedb::reducer] -pub fn noop(_ctx: &ReducerContext) {} -"#; - /// Regression test for . /// /// Removing a `#[primary_key]` annotation and re-publishing succeeds, @@ -468,7 +183,9 @@ pub fn noop(_ctx: &ReducerContext) {} /// `table_primary_key` in `st_table`. #[test] fn test_remove_primary_key_issue_3934() { - let mut test = Smoketest::builder().module_code(MODULE_CODE_WITH_PK).build(); + let mut test = Smoketest::builder() + .precompiled_module("auto-migration-with-primary-key") + .build(); // Step 1: Publish with primary key. let identity = test @@ -477,7 +194,7 @@ fn test_remove_primary_key_issue_3934() { .expect("database should be published after build"); // Step 2: Remove primary key. Should succeed. - test.write_module_code(MODULE_CODE_WITHOUT_PK).unwrap(); + test.use_precompiled_module("auto-migration-without-primary-key"); test.publish() .name(&identity) .break_clients(true) @@ -485,7 +202,7 @@ fn test_remove_primary_key_issue_3934() { .expect("Removing primary key should succeed"); // Step 3: Trivial change (add a reducer). This is where #3934 crashes. - test.write_module_code(MODULE_CODE_WITHOUT_PK_V2).unwrap(); + test.use_precompiled_module("auto-migration-without-primary-key-v2"); test.publish() .name(&identity) .break_clients(true) @@ -493,42 +210,10 @@ fn test_remove_primary_key_issue_3934() { .expect("Publish after PK removal should succeed (issue #3934)"); } -const MODULE_CODE_WITH_EVENT_TABLE_BEFORE: &str = r#" -use spacetimedb::{table, SpacetimeType}; - -#[derive(SpacetimeType)] -struct SomeProduct { - a: u32, - b: u64, -} - -#[table(accessor = some_event, public, event)] -struct SomeEvent { - foo: String, - prod: SomeProduct, -} -"#; - -const MODULE_CODE_WITH_EVENT_TABLE_AFTER: &str = r#" -use spacetimedb::{table, SpacetimeType}; - -#[derive(SpacetimeType)] -struct SomeProduct { - a: u32, - b: u64, - c: u128, -} - -#[table(accessor = some_event, public, event)] -struct SomeEvent { - prod: SomeProduct, -} -"#; - #[test] fn automigrate_reschema_event_table_arbitrarily() { let mut test = Smoketest::builder() - .module_code(MODULE_CODE_WITH_EVENT_TABLE_BEFORE) + .precompiled_module("auto-migration-event-table-before") .build(); // Step 1: publish with event table. @@ -538,7 +223,7 @@ fn automigrate_reschema_event_table_arbitrarily() { .expect("database should be published after build"); // Step 2: Reschema event table. Should work fine, even though we'd reject this change for a non-event table. - test.write_module_code(MODULE_CODE_WITH_EVENT_TABLE_AFTER).unwrap(); + test.use_precompiled_module("auto-migration-event-table-after"); test.publish() .name(&identity) .break_clients(true) @@ -546,7 +231,7 @@ fn automigrate_reschema_event_table_arbitrarily() { .expect("Changing schema of event table should succeed"); // Step 3: Reschema event table right back. Should still work fine. - test.write_module_code(MODULE_CODE_WITH_EVENT_TABLE_BEFORE).unwrap(); + test.use_precompiled_module("auto-migration-event-table-before"); test.publish() .name(&identity) .break_clients(true) @@ -554,45 +239,6 @@ fn automigrate_reschema_event_table_arbitrarily() { .expect("Changing schema of event table should succeed"); } -const MODULE_CODE_DROP_EVENT_TABLE_BEFORE: &str = r#" -use spacetimedb::{ReducerContext, Table}; - -#[spacetimedb::table(accessor = person, public)] -pub struct Person { - name: String, -} - -#[spacetimedb::table(accessor = some_event, public, event)] -pub struct SomeEvent { - account_id: u32, - name: String, -} - -#[spacetimedb::reducer] -pub fn add_person(ctx: &ReducerContext, name: String) { - ctx.db.person().insert(Person { name }); -} - -#[spacetimedb::reducer] -pub fn emit_event(ctx: &ReducerContext) { - ctx.db.some_event().insert(SomeEvent { account_id: 7, name: "alpha".to_string() }); -} -"#; - -const MODULE_CODE_DROP_EVENT_TABLE_AFTER: &str = r#" -use spacetimedb::{ReducerContext, Table}; - -#[spacetimedb::table(accessor = person, public)] -pub struct Person { - name: String, -} - -#[spacetimedb::reducer] -pub fn add_person(ctx: &ReducerContext, name: String) { - ctx.db.person().insert(Person { name }); -} -"#; - /// Regression test: dropping an event table must not brick commitlog replay. /// /// Dropping an event table deletes its `st_table`, `st_column` and `st_event_table` rows @@ -607,7 +253,7 @@ pub fn add_person(ctx: &ReducerContext, name: String) { fn automigrate_drop_event_table_replays_after_restart() { require_local_server!(); let mut test = Smoketest::builder() - .module_code(MODULE_CODE_DROP_EVENT_TABLE_BEFORE) + .precompiled_module("auto-migration-drop-event-table-before") .build(); let identity = test @@ -620,7 +266,7 @@ fn automigrate_drop_event_table_replays_after_restart() { test.call("emit_event", &[]).unwrap(); // Drop the event table. - test.write_module_code(MODULE_CODE_DROP_EVENT_TABLE_AFTER).unwrap(); + test.use_precompiled_module("auto-migration-drop-event-table-after"); test.publish() .name(&identity) .break_clients(true) diff --git a/crates/smoketests/tests/smoketests/call.rs b/crates/smoketests/tests/smoketests/call.rs index d41e2fb7e3a..74fdd8b20c0 100644 --- a/crates/smoketests/tests/smoketests/call.rs +++ b/crates/smoketests/tests/smoketests/call.rs @@ -163,38 +163,10 @@ The database has no procedures." ); } -/// Generate module code with many reducers and procedures -fn generate_many_module_code() -> String { - let mut code = String::from( - r#" -use spacetimedb::{log, ProcedureContext, ReducerContext}; -"#, - ); - - for i in 0..11 { - code.push_str(&format!( - r#" -#[spacetimedb::reducer] -pub fn say_reducer_{i}(_ctx: &ReducerContext) {{ - log::info!("Hello from reducer {i}!"); -}} - -#[spacetimedb::procedure] -pub fn say_procedure_{i}(_ctx: &mut ProcedureContext) {{ - log::info!("Hello from procedure {i}!"); -}} -"# - )); - } - - code -} - /// Check calling into a database with many reducers/procedures raises error with listing #[test] fn test_call_many_errors() { - let module_code = generate_many_module_code(); - let test = Smoketest::builder().module_code(&module_code).build(); + let test = Smoketest::builder().precompiled_module("call-many").build(); let identity = test.database_identity.as_ref().unwrap(); diff --git a/crates/smoketests/tests/smoketests/cli/list.rs b/crates/smoketests/tests/smoketests/cli/list.rs index 8832a25c181..79ccb2996a1 100644 --- a/crates/smoketests/tests/smoketests/cli/list.rs +++ b/crates/smoketests/tests/smoketests/cli/list.rs @@ -23,7 +23,10 @@ fn assert_success(output: &Output, context: &str) { #[test] fn cli_list_shows_database_names_and_identities() { require_local_server!(); - let mut test = Smoketest::builder().autopublish(false).build(); + let mut test = Smoketest::builder() + .precompiled_module("noop") + .autopublish(false) + .build(); let primary_name = format!("list-db-{}", std::process::id()); let alias_name = format!("{primary_name}-alias"); diff --git a/crates/smoketests/tests/smoketests/domains.rs b/crates/smoketests/tests/smoketests/domains.rs index c04440ded2a..3f43471968a 100644 --- a/crates/smoketests/tests/smoketests/domains.rs +++ b/crates/smoketests/tests/smoketests/domains.rs @@ -3,7 +3,10 @@ use spacetimedb_smoketests::Smoketest; /// Tests the functionality of the rename command #[test] fn test_set_name() { - let mut test = Smoketest::builder().autopublish(false).build(); + let mut test = Smoketest::builder() + .precompiled_module("noop") + .autopublish(false) + .build(); let orig_name = format!("domains-set-name-{}", std::process::id()); test.publish().name(&orig_name).run().unwrap(); @@ -31,7 +34,10 @@ fn test_set_name() { /// Test how we treat the / character in published names #[test] fn test_subdomain_behavior() { - let mut test = Smoketest::builder().autopublish(false).build(); + let mut test = Smoketest::builder() + .precompiled_module("noop") + .autopublish(false) + .build(); let root_name = format!("domains-subdomain-behavior-{}", std::process::id()); test.publish().name(&root_name).run().unwrap(); @@ -50,7 +56,10 @@ fn test_subdomain_behavior() { /// Test that we can't rename to a name already in use #[test] fn test_set_to_existing_name() { - let mut test = Smoketest::builder().autopublish(false).build(); + let mut test = Smoketest::builder() + .precompiled_module("noop") + .autopublish(false) + .build(); // Publish first database (no name) test.publish().run().unwrap(); @@ -78,7 +87,10 @@ fn test_set_to_existing_name() { /// Test that we can rename to a list of names via the API #[test] fn test_replace_names() { - let mut test = Smoketest::builder().autopublish(false).build(); + let mut test = Smoketest::builder() + .precompiled_module("noop") + .autopublish(false) + .build(); let orig_name = format!("domains-replace-names-{}", std::process::id()); let alt_name1 = format!("domains-replace-names-{}-alt1", std::process::id()); diff --git a/crates/smoketests/tests/smoketests/fail_initial_publish.rs b/crates/smoketests/tests/smoketests/fail_initial_publish.rs index 4d8664cfa51..3f5af853978 100644 --- a/crates/smoketests/tests/smoketests/fail_initial_publish.rs +++ b/crates/smoketests/tests/smoketests/fail_initial_publish.rs @@ -1,26 +1,12 @@ use spacetimedb_smoketests::Smoketest; -/// Module code with a bug: `Person` is the wrong table name, should be `person` -const MODULE_CODE_BROKEN: &str = r#" -use spacetimedb::{client_visibility_filter, Filter}; - -#[spacetimedb::table(accessor = person, public)] -pub struct Person { - name: String, -} - -#[client_visibility_filter] -// Bug: `Person` is the wrong table name, should be `person`. -const HIDE_PEOPLE_EXCEPT_ME: Filter = Filter::Sql("SELECT * FROM Person WHERE name = 'me'"); -"#; - const FIXED_QUERY: &str = r#""sql": "SELECT * FROM person WHERE name = 'me'""#; /// This tests that publishing an invalid module does not leave a broken entry in the control DB. #[test] fn test_fail_initial_publish() { let mut test = Smoketest::builder() - .module_code(MODULE_CODE_BROKEN) + .precompiled_module("fail-initial-publish-broken") .autopublish(false) .build(); @@ -60,7 +46,7 @@ fn test_fail_initial_publish() { // Publishing the broken code again fails, but the database still exists afterwards, // with the previous version of the module code. - test.write_module_code(MODULE_CODE_BROKEN).unwrap(); + test.use_precompiled_module("fail-initial-publish-broken"); let result = test.publish().name(&name).run(); assert!(result.is_err(), "Expected publish to fail with broken module"); diff --git a/crates/smoketests/tests/smoketests/http_egress.rs b/crates/smoketests/tests/smoketests/http_egress.rs index 8fe24193d25..847d03134bb 100644 --- a/crates/smoketests/tests/smoketests/http_egress.rs +++ b/crates/smoketests/tests/smoketests/http_egress.rs @@ -5,29 +5,6 @@ use std::time::{Duration, Instant}; use spacetimedb_smoketests::{require_local_server, Smoketest}; -fn module_code_http_disallowed_ip(addr: &str, port: u16) -> String { - format!( - r#" -use spacetimedb::ProcedureContext; - -#[spacetimedb::procedure] -pub fn request_disallowed_ip(ctx: &mut ProcedureContext) -> Result<(), String> {{ - match ctx.http.get("http://{addr}:{port}/") {{ - Ok(_) => Err("request unexpectedly succeeded".to_owned()), - Err(err) => {{ - let message = err.to_string(); - if message.contains("refusing to connect to private or special-purpose addresses") {{ - Ok(()) - }} else {{ - Err(format!("unexpected error from http request: {{message}}")) - }} - }} - }} -}} -"# - ) -} - fn spawn_redirect_server(location: &str) -> (u16, JoinHandle>) { let listener = TcpListener::bind(("127.0.0.1", 0)).expect("failed to bind test redirect server"); listener @@ -66,10 +43,9 @@ fn spawn_redirect_server(location: &str) -> (u16, JoinHandle #[test] fn test_http_disallowed_ip_is_blocked() { - let module_code = module_code_http_disallowed_ip("10.0.0.1", 80); - let test = Smoketest::builder().module_code(&module_code).build(); + let test = Smoketest::builder().precompiled_module("http-egress").build(); - let output = test.call_output("request_disallowed_ip", &[]); + let output = test.call_output("request_disallowed_ip", &["http://10.0.0.1:80/"]); let stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); assert!( @@ -84,10 +60,10 @@ fn test_http_disallowed_ip_is_blocked() { fn test_http_redirect_to_disallowed_ip_is_blocked() { require_local_server!(); let (port, redirect_server) = spawn_redirect_server("http://10.0.0.1:80/"); - let module_code = module_code_http_disallowed_ip("localhost", port); - let test = Smoketest::builder().module_code(&module_code).build(); + let test = Smoketest::builder().precompiled_module("http-egress").build(); + let url = format!("http://localhost:{port}/"); - let output = test.call_output("request_disallowed_ip", &[]); + let output = test.call_output("request_disallowed_ip", &[&url]); let stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); assert!( diff --git a/crates/smoketests/tests/smoketests/http_routes.rs b/crates/smoketests/tests/smoketests/http_routes.rs index c51ae66b0a7..d5ebf77bce2 100644 --- a/crates/smoketests/tests/smoketests/http_routes.rs +++ b/crates/smoketests/tests/smoketests/http_routes.rs @@ -4,234 +4,6 @@ use spacetimedb_smoketests::{ }; use std::{fs, path::Path}; -const MODULE_CODE: &str = r#" -use spacetimedb::http::{Body, HandlerContext, Request, Response, Router}; -use spacetimedb::Table; - -#[spacetimedb::table(accessor = entries, public)] -pub struct Entry { - id: u64, - value: String, -} - -#[spacetimedb::http::handler] -fn get_simple(_ctx: &mut HandlerContext, _req: Request) -> Response { - Response::new(Body::from_bytes("ok")) -} - -#[spacetimedb::http::handler] -fn post_insert(ctx: &mut HandlerContext, _req: Request) -> Response { - ctx.with_tx(|tx| { - let id = tx.db.entries().iter().count() as u64; - tx.db.entries().insert(Entry { - id, - value: "posted".to_string(), - }); - }); - Response::new(Body::from_bytes("inserted")) -} - -#[spacetimedb::http::handler] -fn get_count(ctx: &mut HandlerContext, _req: Request) -> Response { - let count = ctx.with_tx(|tx| tx.db.entries().iter().count()); - Response::new(Body::from_bytes(count.to_string())) -} - -#[spacetimedb::http::handler] -fn any_handler(_ctx: &mut HandlerContext, _req: Request) -> Response { - Response::new(Body::from_bytes("any")) -} - -#[spacetimedb::http::handler] -fn header_echo(_ctx: &mut HandlerContext, req: Request) -> Response { - let value = req - .headers() - .get("x-echo") - .and_then(|value| value.to_str().ok()) - .unwrap_or(""); - Response::new(Body::from_bytes(value.to_string())) -} - -#[spacetimedb::http::handler] -fn set_response_header(_ctx: &mut HandlerContext, _req: Request) -> Response { - Response::builder() - .header("x-response", "set") - .body(Body::from_bytes("header-set")) - .expect("response builder should not fail") -} - -#[spacetimedb::http::handler] -fn body_handler(_ctx: &mut HandlerContext, _req: Request) -> Response { - Response::new(Body::from_bytes("non-empty")) -} - -#[spacetimedb::http::handler] -fn teapot(_ctx: &mut HandlerContext, _req: Request) -> Response { - Response::builder() - .status(418) - .body(Body::from_bytes("teapot")) - .expect("response builder should not fail") -} - -#[spacetimedb::http::router] -fn router() -> Router { - Router::new() - .get("/get", get_simple) - .post("/post", post_insert) - .get("/count", get_count) - .any("/any", any_handler) - .get("/header", header_echo) - .get("/set-header", set_response_header) - .get("/body", body_handler) - .get("/teapot", teapot) -} -"#; - -const EXAMPLE_MODULE_CODE: &str = r#" -use std::str::FromStr; - -use spacetimedb::http::{Body, HandlerContext, Request, Response, Router}; -use spacetimedb::Table; - -#[spacetimedb::table(accessor = data)] -struct Data { - #[primary_key] - #[auto_inc] - id: u64, - body: Vec, -} - -#[spacetimedb::http::handler] -fn insert(ctx: &mut HandlerContext, request: Request) -> Response { - let body: Vec = request.into_body().into_bytes().into(); - let id = ctx.with_tx(|tx| tx.db.data().insert(Data { id: 0, body: body.clone() }).id); - Response::new(Body::from_bytes(format!("{id}"))) -} - -#[spacetimedb::http::handler] -fn retrieve(ctx: &mut HandlerContext, request: Request) -> Response { - let id = request - .uri() - .query() - .and_then(|query| query.strip_prefix("id=")) - .and_then(|id| u64::from_str(id).ok()) - .unwrap(); - let body = ctx.with_tx(|tx| tx.db.data().id().find(id).map(|data| data.body)); - if let Some(body) = body { - Response::new(Body::from_bytes(body)) - } else { - Response::builder().status(404).body(Body::empty()).unwrap() - } -} - -#[spacetimedb::http::router] -fn router() -> Router { - Router::new().post("/insert", insert).get("/retrieve", retrieve) -} -"#; - -const STRICT_ROOT_ROUTING_MODULE_CODE: &str = r#" -use spacetimedb::http::{Body, HandlerContext, Request, Response, Router}; - -#[spacetimedb::http::handler] -fn empty_root(_ctx: &mut HandlerContext, _req: Request) -> Response { - Response::new(Body::from_bytes("empty")) -} - -#[spacetimedb::http::handler] -fn slash_root(_ctx: &mut HandlerContext, _req: Request) -> Response { - Response::new(Body::from_bytes("slash")) -} - -#[spacetimedb::http::handler] -fn foo(_ctx: &mut HandlerContext, _req: Request) -> Response { - Response::new(Body::from_bytes("foo")) -} - -#[spacetimedb::http::handler] -fn foo_slash(_ctx: &mut HandlerContext, _req: Request) -> Response { - Response::new(Body::from_bytes("foo-slash")) -} - -#[spacetimedb::http::router] -fn router() -> Router { - Router::new() - .get("", empty_root) - .get("/", slash_root) - .get("/foo", foo) - .get("/foo/", foo_slash) -} -"#; - -const STRICT_NON_ROOT_ROUTING_MODULE_CODE: &str = r#" -use spacetimedb::http::{Body, HandlerContext, Request, Response, Router}; - -#[spacetimedb::http::handler] -fn foo(_ctx: &mut HandlerContext, _req: Request) -> Response { - Response::new(Body::from_bytes("foo")) -} - -#[spacetimedb::http::handler] -fn foo_slash(_ctx: &mut HandlerContext, _req: Request) -> Response { - Response::new(Body::from_bytes("foo-slash")) -} - -#[spacetimedb::http::router] -fn router() -> Router { - Router::new() - .get("/foo", foo) - .get("/foo/", foo_slash) -} -"#; - -const FULL_URI_MODULE_CODE: &str = r#" -use spacetimedb::http::{Body, HandlerContext, Request, Response, Router}; - -#[spacetimedb::http::handler] -fn echo_uri(_ctx: &mut HandlerContext, req: Request) -> Response { - Response::new(Body::from_bytes(req.uri().to_string())) -} - -#[spacetimedb::http::router] -fn router() -> Router { - Router::new().get("/echo-uri", echo_uri) -} -"#; - -const HANDLE_REQUEST_BODY_MODULE_CODE: &str = r#" -use spacetimedb::http::{Body, HandlerContext, Request, Response, Router}; - -#[spacetimedb::http::handler] -fn reverse_bytes(_ctx: &mut HandlerContext, req: Request) -> Response { - let mut reversed = req.into_body().into_bytes().to_vec(); - reversed.reverse(); - Response::new(Body::from_bytes(reversed)) -} - -#[spacetimedb::http::handler] -fn reverse_words(_ctx: &mut HandlerContext, req: Request) -> Response { - let body = match req.into_body().into_string() { - Ok(body) => body, - Err(_) => { - return Response::builder() - .status(400) - .body(Body::from_bytes("request body must be valid UTF-8")) - .expect("response builder should not fail"); - } - }; - - let reversed = body.split(' ').rev().collect::>().join(" "); - Response::new(Body::from_bytes(reversed)) -} - -#[spacetimedb::http::router] -fn router() -> Router { - Router::new() - .post("/reverse-bytes", reverse_bytes) - .post("/reverse-words", reverse_words) -} -"#; - const CPP_MODULE_CODE: &str = r#"#include "spacetimedb.h" using namespace SpacetimeDB; @@ -1084,8 +856,8 @@ fn extract_code_blocks(doc_path: &Path, regex_src: &str, language_name: &str) -> blocks.join("\n\n") } -fn rust_http_test(module_code: &str) -> (Smoketest, String) { - let test = Smoketest::builder().module_code(module_code).build(); +fn rust_http_test(module: &str) -> (Smoketest, String) { + let test = Smoketest::builder().precompiled_module(module).build(); let identity = test .database_identity .as_ref() @@ -1354,37 +1126,37 @@ fn assert_handle_request_body(server_url: &str, identity: &str) { #[test] fn http_routes_end_to_end() { - let (test, identity) = rust_http_test(MODULE_CODE); + let (test, identity) = rust_http_test("http-routes"); assert_http_routes_end_to_end(&test.server_url, &identity); } #[test] fn http_routes_pr_example_round_trip() { - let (test, identity) = rust_http_test(EXAMPLE_MODULE_CODE); + let (test, identity) = rust_http_test("http-routes-example"); assert_http_routes_pr_example_round_trip(&test.server_url, &identity); } #[test] fn http_routes_are_strict_for_non_root_paths() { - let (test, identity) = rust_http_test(STRICT_NON_ROOT_ROUTING_MODULE_CODE); + let (test, identity) = rust_http_test("http-routes-strict-non-root"); assert_http_routes_are_strict_for_non_root_paths(&test.server_url, &identity); } #[test] fn http_routes_are_strict_for_root_paths() { - let (test, identity) = rust_http_test(STRICT_ROOT_ROUTING_MODULE_CODE); + let (test, identity) = rust_http_test("http-routes-strict-root"); assert_http_routes_are_strict_for_root_paths(&test.server_url, &identity); } #[test] fn http_handler_observes_full_external_uri() { - let (test, identity) = rust_http_test(FULL_URI_MODULE_CODE); + let (test, identity) = rust_http_test("http-routes-full-uri"); assert_http_handler_observes_full_external_uri(&test.server_url, &identity); } #[test] fn handle_request_body() { - let (test, identity) = rust_http_test(HANDLE_REQUEST_BODY_MODULE_CODE); + let (test, identity) = rust_http_test("http-routes-request-body"); assert_handle_request_body(&test.server_url, &identity); } diff --git a/crates/smoketests/tests/smoketests/permissions.rs b/crates/smoketests/tests/smoketests/permissions.rs index d1c9c92b19f..35a9f8a7c37 100644 --- a/crates/smoketests/tests/smoketests/permissions.rs +++ b/crates/smoketests/tests/smoketests/permissions.rs @@ -189,7 +189,7 @@ fn test_private_table() { /// Ensure that you cannot delete a database that you do not own #[test] fn test_cannot_delete_others_database() { - let test = Smoketest::builder().build(); + let test = Smoketest::builder().precompiled_module("noop").build(); let identity = test.database_identity.as_ref().unwrap().clone(); diff --git a/crates/smoketests/tests/smoketests/publish_upgrade_prompt.rs b/crates/smoketests/tests/smoketests/publish_upgrade_prompt.rs index 127860003a8..c1994f4cddf 100644 --- a/crates/smoketests/tests/smoketests/publish_upgrade_prompt.rs +++ b/crates/smoketests/tests/smoketests/publish_upgrade_prompt.rs @@ -2,13 +2,6 @@ use std::path::PathBuf; use spacetimedb_smoketests::{random_string, workspace_root, Smoketest}; -const MODULE_CODE: &str = r#" -use spacetimedb::{reducer, ReducerContext}; - -#[reducer] -pub fn noop(_ctx: &ReducerContext) {} -"#; - fn old_fixture_wasm() -> PathBuf { workspace_root() .join("crates") @@ -30,8 +23,8 @@ fn upgrade_prompt_on_publish() { let initial_identity = test.publish().name(&db_name).run().unwrap(); assert_eq!(test.database_identity.as_deref(), Some(initial_identity.as_str())); - // Switch back to source-built module, which uses current bindings. - test.write_module_code(MODULE_CODE).unwrap(); + // Switch to a module precompiled with the current bindings. + test.use_precompiled_module("noop"); let deny_err = test .publish() @@ -68,8 +61,8 @@ fn upgrade_prompt_suppressed_by_yes_flag() { let initial_identity = test.publish().name(&db_name).run().unwrap(); assert_eq!(test.database_identity.as_deref(), Some(initial_identity.as_str())); - // Switch back to source-built module, which uses current bindings. - test.write_module_code(MODULE_CODE).unwrap(); + // Switch to a module precompiled with the current bindings. + test.use_precompiled_module("noop"); // With --yes, the upgrade prompt should be suppressed and publish should succeed. let accepted_identity = test.publish().name(&db_name).run().unwrap(); diff --git a/crates/smoketests/tests/smoketests/rls.rs b/crates/smoketests/tests/smoketests/rls.rs index 1e309c26700..14c24da5a39 100644 --- a/crates/smoketests/tests/smoketests/rls.rs +++ b/crates/smoketests/tests/smoketests/rls.rs @@ -29,24 +29,11 @@ fn test_rls_rules() { ); } -/// Module code with RLS on a private table (intentionally broken) -const MODULE_CODE_BROKEN_RLS: &str = r#" -use spacetimedb::{client_visibility_filter, Filter, Identity}; - -#[spacetimedb::table(accessor = user)] -pub struct User { - identity: Identity, -} - -#[client_visibility_filter] -const PERSON_FILTER: Filter = Filter::Sql("SELECT * FROM \"user\" WHERE identity = :sender"); -"#; - /// Tests that publishing an RLS rule on a private table fails #[test] fn test_publish_fails_for_rls_on_private_table() { let mut test = Smoketest::builder() - .module_code(MODULE_CODE_BROKEN_RLS) + .precompiled_module("rls-broken-private-table") .autopublish(false) .build(); diff --git a/crates/smoketests/tests/smoketests/templates.rs b/crates/smoketests/tests/smoketests/templates.rs index 37b23f1a51a..5623e3baab4 100644 --- a/crates/smoketests/tests/smoketests/templates.rs +++ b/crates/smoketests/tests/smoketests/templates.rs @@ -1178,86 +1178,84 @@ fn test_basic_cs_init_default_dotnet_selection() -> Result<()> { Ok(()) } -/// Tests all templates discovered in the `templates/` directory. +/// Runs the init + publish + client-test cycle for one registered template. /// -/// For each template the test: -/// 1. Runs `spacetime init --template ` into a temp directory -/// 2. Wires local SDK dependencies so the template builds against the current source -/// 3. Publishes the server module and verifies it succeeds -/// 4. Type-checks / builds the client code -/// -/// All templates are exercised even if some fail; a summary is printed at the -/// end and the test fails if any template did. -#[test] -fn test_all_templates() { - let templates = get_templates(); - assert!(!templates.is_empty(), "No templates found in templates/"); +/// Each template is a separate Rust test so nextest can hash it independently +/// into a partition and report or rerun failures independently. +fn run_registered_template(template_id: &str) { + let template = get_templates() + .into_iter() + .find(|template| template.id == template_id) + .unwrap_or_else(|| panic!("Registered template not found: {template_id}")); - let has_typescript = templates.iter().any(|t| t.server_lang.as_deref() == Some("typescript")); - let has_csharp = templates.iter().any(|t| t.server_lang.as_deref() == Some("csharp")); - - // Guard checks - verify required tools are available before starting. - if has_typescript { - spacetimedb_smoketests::require_pnpm!(); - } - if has_csharp { - spacetimedb_smoketests::require_dotnet!(); - } - - // Build the TypeScript SDK once up-front if any TypeScript templates exist. - if has_typescript { - build_typescript_sdk().expect("Failed to build TypeScript SDK"); + match template.server_lang.as_deref() { + Some("typescript") => { + spacetimedb_smoketests::require_pnpm!(); + build_typescript_sdk().expect("Failed to build TypeScript SDK"); + } + Some("csharp") => { + spacetimedb_smoketests::require_dotnet!(); + } + _ => {} } - // One shared server for all templates. let test = Smoketest::builder().autopublish(false).build(); + test_template(&test, &template).unwrap_or_else(|error| panic!("Template {template_id} failed: {error:#}")); +} - let mut results: Vec<(String, Result<()>)> = Vec::new(); - - for template in &templates { - let result = test_template(&test, template); - let passed = result.is_ok(); - eprintln!( - "[TEMPLATES] {} {}", - if passed { "[PASS]" } else { "[FAIL]" }, - template.id - ); - results.push((template.id.clone(), result)); - } +macro_rules! template_tests { + ($($test_name:ident => $template_id:literal),+ $(,)?) => { + const REGISTERED_TEMPLATE_IDS: &[&str] = &[$($template_id),+]; - // Print summary. - eprintln!("\n{}", "=".repeat(60)); - eprintln!("TEMPLATE TEST SUMMARY"); - eprintln!("{}", "=".repeat(60)); - for (id, result) in &results { - eprintln!( - "{:40} {}", - id, - if result.is_ok() { - "[PASS]".to_string() - } else { - format!("[FAIL]: {:#}", result.as_ref().unwrap_err()) + $( + #[test] + fn $test_name() { + run_registered_template($template_id); } - ); - } - let passed = results.iter().filter(|(_, r)| r.is_ok()).count(); - let total = results.len(); - eprintln!("{}", "=".repeat(60)); - eprintln!("TOTAL: {}/{} passed", passed, total); + )+ + }; +} - // Fail if any template failed. - let failures: Vec<_> = results +template_tests! { + test_template_angular_ts => "angular-ts", + test_template_astro_ts => "astro-ts", + test_template_basic_cpp => "basic-cpp", + test_template_basic_cs => "basic-cs", + test_template_basic_rs => "basic-rs", + test_template_basic_ts => "basic-ts", + test_template_browser_ts => "browser-ts", + test_template_bun_ts => "bun-ts", + test_template_chat_console_cs => "chat-console-cs", + test_template_chat_console_rs => "chat-console-rs", + test_template_chat_react_ts => "chat-react-ts", + test_template_deno_ts => "deno-ts", + test_template_hangman_react_ts => "hangman-react-ts", + test_template_llm_chat_ts => "llm-chat-ts", + test_template_money_exchange_react_ts => "money-exchange-react-ts", + test_template_nextjs_ts => "nextjs-ts", + test_template_nodejs_ts => "nodejs-ts", + test_template_nuxt_ts => "nuxt-ts", + test_template_react_ts => "react-ts", + test_template_remix_ts => "remix-ts", + test_template_solid_ts => "solid-ts", + test_template_svelte_ts => "svelte-ts", + test_template_tanstack_ts => "tanstack-ts", + test_template_vue_ts => "vue-ts", +} + +#[test] +fn test_template_registry_matches_discovered_templates() { + let discovered = get_templates() + .into_iter() + .map(|template| template.id) + .collect::>(); + let registered = REGISTERED_TEMPLATE_IDS .iter() - .filter(|(_, r)| r.is_err()) - .map(|(id, r)| format!(" {}: {:#}", id, r.as_ref().unwrap_err())) - .collect(); - - if !failures.is_empty() { - panic!( - "{}/{} template(s) failed:\n{}", - failures.len(), - total, - failures.join("\n") - ); - } + .map(|template_id| (*template_id).to_owned()) + .collect::>(); + + assert_eq!( + discovered, registered, + "Every discovered template must have its own nextest-visible test" + ); } diff --git a/crates/smoketests/tests/smoketests/timestamp_route.rs b/crates/smoketests/tests/smoketests/timestamp_route.rs index d106e1a4cbb..3a6e2af1b91 100644 --- a/crates/smoketests/tests/smoketests/timestamp_route.rs +++ b/crates/smoketests/tests/smoketests/timestamp_route.rs @@ -9,7 +9,10 @@ fn test_timestamp_route() { // which is not available when smoketests use SpacetimeAuth login. require_server_issued_login!(); - let mut test = Smoketest::builder().autopublish(false).build(); + let mut test = Smoketest::builder() + .precompiled_module("noop") + .autopublish(false) + .build(); let name = random_string(); diff --git a/crates/smoketests/tests/smoketests/views.rs b/crates/smoketests/tests/smoketests/views.rs index 517242edba6..d4c4b1cf394 100644 --- a/crates/smoketests/tests/smoketests/views.rs +++ b/crates/smoketests/tests/smoketests/views.rs @@ -311,8 +311,7 @@ fn test_fail_publish_namespace_collision() { #[test] fn test_fail_publish_wrong_return_type() { let mut test = Smoketest::builder() - // Can't be precompiled because the code is intentionally broken - .module_code(include_str!("../../modules/views-broken-return-type/src/lib.rs")) + .precompiled_module("views-broken-return-type") .autopublish(false) .build();