From e32ba5bd0dc2fc0e43cab94dcf668d6a667f1cc1 Mon Sep 17 00:00:00 2001 From: Toby Martin Date: Thu, 1 Oct 2026 20:54:37 +0000 Subject: [PATCH] test(orm): align release fixtures and execute PostgreSQL count policies --- .github/workflows/release.yml | 2 +- CHANGELOG.md | 4 + .../fixtures/cross-crate-relations/Cargo.lock | 4 +- .../fixtures/backend-coexistence/Cargo.lock | 6 +- .../fixtures/federation-end-to-end/Cargo.lock | 4 +- .../migration-api-compatibility/Cargo.lock | 4 +- .../repository-aggregate-consumer/Cargo.lock | 4 +- crates/graphql-orm/tests/repository_counts.rs | 96 ++++++++++++++++--- .../graphql-orm/runtime-and-writes.md | 8 ++ 9 files changed, 106 insertions(+), 26 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e888e93e..da05aeef 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -155,7 +155,7 @@ jobs: cargo test -p graphql-orm --no-default-features --features sqlite,field-case-pascal,argument-case-snake --test federation_entity_keys_case_features --locked cargo check -p graphql-orm --no-default-features --features postgres --locked cargo test -p graphql-orm --no-default-features --features postgres --test federation_entity_keys_postgres --locked - cargo test -p graphql-orm --no-default-features --features postgres --test projection_visibility --test cross_crate_relations_fixture --locked + cargo test -p graphql-orm --no-default-features --features postgres --test projection_visibility --test cross_crate_relations_fixture --test repository_counts --locked cargo test -p graphql-orm --no-default-features --features postgres --test grouped_aggregates_postgres --locked -- --ignored --test-threads=1 cargo run --manifest-path crates/graphql-orm/tests/fixtures/repository-aggregate-consumer/Cargo.toml --locked --no-default-features --features sqlite --example policy_projections cargo run --manifest-path crates/graphql-orm/tests/fixtures/repository-aggregate-consumer/Cargo.toml --locked --no-default-features --features sqlite --example complete_events diff --git a/CHANGELOG.md b/CHANGELOG.md index 72628fe0..eca8ba56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ for historical context. ## 0.35.2 - 2026-10-01 +- Keep standalone fixture lockfiles aligned with this package set and execute + transaction count authorization regressions on test-owned PostgreSQL as well + as SQLite during release validation. + Companion macros crate: `graphql-orm-macros` **0.35.2** (alignment only). - Allow transaction-bound repository `count` and `exists` with an installed diff --git a/crates/graphql-orm-macros/fixtures/cross-crate-relations/Cargo.lock b/crates/graphql-orm-macros/fixtures/cross-crate-relations/Cargo.lock index 8e06e115..2c8a3bb8 100644 --- a/crates/graphql-orm-macros/fixtures/cross-crate-relations/Cargo.lock +++ b/crates/graphql-orm-macros/fixtures/cross-crate-relations/Cargo.lock @@ -1028,7 +1028,7 @@ dependencies = [ [[package]] name = "graphql-orm" -version = "0.35.1" +version = "0.35.2" dependencies = [ "async-graphql", "chacha20poly1305", @@ -1054,7 +1054,7 @@ dependencies = [ [[package]] name = "graphql-orm-macros" -version = "0.35.1" +version = "0.35.2" dependencies = [ "convert_case", "proc-macro2", diff --git a/crates/graphql-orm/tests/fixtures/backend-coexistence/Cargo.lock b/crates/graphql-orm/tests/fixtures/backend-coexistence/Cargo.lock index 203def38..4ab287c2 100644 --- a/crates/graphql-orm/tests/fixtures/backend-coexistence/Cargo.lock +++ b/crates/graphql-orm/tests/fixtures/backend-coexistence/Cargo.lock @@ -1350,7 +1350,7 @@ dependencies = [ [[package]] name = "graphql-orm" -version = "0.35.1" +version = "0.35.2" dependencies = [ "agql-auth", "async-graphql", @@ -1377,7 +1377,7 @@ dependencies = [ [[package]] name = "graphql-orm-ai" -version = "0.106.0" +version = "0.106.1" dependencies = [ "agql-auth", "async-graphql", @@ -1420,7 +1420,7 @@ dependencies = [ [[package]] name = "graphql-orm-macros" -version = "0.35.1" +version = "0.35.2" dependencies = [ "convert_case", "proc-macro2", diff --git a/crates/graphql-orm/tests/fixtures/federation-end-to-end/Cargo.lock b/crates/graphql-orm/tests/fixtures/federation-end-to-end/Cargo.lock index ba322910..f116de50 100644 --- a/crates/graphql-orm/tests/fixtures/federation-end-to-end/Cargo.lock +++ b/crates/graphql-orm/tests/fixtures/federation-end-to-end/Cargo.lock @@ -2508,7 +2508,7 @@ dependencies = [ [[package]] name = "graphql-orm" -version = "0.35.1" +version = "0.35.2" dependencies = [ "async-graphql", "chacha20poly1305", @@ -2531,7 +2531,7 @@ dependencies = [ [[package]] name = "graphql-orm-macros" -version = "0.35.1" +version = "0.35.2" dependencies = [ "convert_case 0.7.1", "proc-macro2", diff --git a/crates/graphql-orm/tests/fixtures/migration-api-compatibility/Cargo.lock b/crates/graphql-orm/tests/fixtures/migration-api-compatibility/Cargo.lock index 05d83ffe..13852ec0 100644 --- a/crates/graphql-orm/tests/fixtures/migration-api-compatibility/Cargo.lock +++ b/crates/graphql-orm/tests/fixtures/migration-api-compatibility/Cargo.lock @@ -1004,7 +1004,7 @@ dependencies = [ [[package]] name = "graphql-orm" -version = "0.35.1" +version = "0.35.2" dependencies = [ "async-graphql", "chacha20poly1305", @@ -1030,7 +1030,7 @@ dependencies = [ [[package]] name = "graphql-orm-macros" -version = "0.35.1" +version = "0.35.2" dependencies = [ "convert_case", "proc-macro2", diff --git a/crates/graphql-orm/tests/fixtures/repository-aggregate-consumer/Cargo.lock b/crates/graphql-orm/tests/fixtures/repository-aggregate-consumer/Cargo.lock index 4b63d5f9..b74527bc 100644 --- a/crates/graphql-orm/tests/fixtures/repository-aggregate-consumer/Cargo.lock +++ b/crates/graphql-orm/tests/fixtures/repository-aggregate-consumer/Cargo.lock @@ -1004,7 +1004,7 @@ dependencies = [ [[package]] name = "graphql-orm" -version = "0.35.1" +version = "0.35.2" dependencies = [ "async-graphql", "chacha20poly1305", @@ -1030,7 +1030,7 @@ dependencies = [ [[package]] name = "graphql-orm-macros" -version = "0.35.1" +version = "0.35.2" dependencies = [ "convert_case", "proc-macro2", diff --git a/crates/graphql-orm/tests/repository_counts.rs b/crates/graphql-orm/tests/repository_counts.rs index 10f7052e..47fb4278 100644 --- a/crates/graphql-orm/tests/repository_counts.rs +++ b/crates/graphql-orm/tests/repository_counts.rs @@ -1,9 +1,24 @@ -#![cfg(feature = "sqlite")] +#![cfg(any(feature = "sqlite", feature = "postgres"))] use graphql_orm::prelude::*; +#[cfg(feature = "postgres")] +#[path = "support/owned_postgres.rs"] +mod owned_postgres; +#[cfg(feature = "sqlite")] +type Backend = SqliteBackend; +#[cfg(feature = "postgres")] +type Backend = PostgresBackend; + #[derive(RepositoryEntity, Clone, Debug, serde::Serialize, serde::Deserialize)] -#[repository_entity(backend = "sqlite", table = "count_rows", plural = "CountRows")] +#[cfg_attr( + feature = "sqlite", + repository_entity(backend = "sqlite", table = "count_rows", plural = "CountRows") +)] +#[cfg_attr( + feature = "postgres", + repository_entity(backend = "postgres", table = "count_rows", plural = "CountRows") +)] struct CountRow { #[primary_key] #[graphql_orm(auto_generated = false)] @@ -23,7 +38,14 @@ enum Visibility { } #[derive(RepositoryEntity, Clone, Debug, serde::Serialize, serde::Deserialize)] -#[repository_entity(backend = "sqlite", table = "other_rows", plural = "OtherRows")] +#[cfg_attr( + feature = "sqlite", + repository_entity(backend = "sqlite", table = "other_rows", plural = "OtherRows") +)] +#[cfg_attr( + feature = "postgres", + repository_entity(backend = "postgres", table = "other_rows", plural = "OtherRows") +)] struct OtherRow { #[primary_key] #[graphql_orm(auto_generated = false)] @@ -31,11 +53,11 @@ struct OtherRow { id: String, } -impl RowPolicy for Visibility { +impl RowPolicy for Visibility { fn read_visibility<'a>( &'a self, ctx: Option<&'a async_graphql::Context<'_>>, - _db: &'a Database, + _db: &'a Database, _entity: &'static str, _key: Option<&'static str>, surface: EntityAccessSurface, @@ -47,7 +69,7 @@ impl RowPolicy for Visibility { Self::Unrestricted => ReadVisibility::Unrestricted, Self::CallbackOnly => ReadVisibility::CallbackOnly, Self::WrongEntity => { - ReadVisibility::Complete(ReadPredicate::from_filter::( + ReadVisibility::Complete(ReadPredicate::from_filter::( &OtherRowWhereInput { id: Some(StringFilter { eq: Some("1".into()), @@ -59,7 +81,7 @@ impl RowPolicy for Visibility { } Self::Complete | Self::Prefilter => { let predicate = - ReadPredicate::from_filter::(&CountRowWhereInput { + ReadPredicate::from_filter::(&CountRowWhereInput { tenant: Some(StringFilter { eq: Some("alpha".into()), ..Default::default() @@ -79,7 +101,7 @@ impl RowPolicy for Visibility { fn can_read_row<'a>( &'a self, _ctx: Option<&'a async_graphql::Context<'_>>, - _db: &'a Database, + _db: &'a Database, _entity: &'static str, _key: Option<&'static str>, _surface: EntityAccessSurface, @@ -91,7 +113,7 @@ impl RowPolicy for Visibility { fn can_write_row<'a>( &'a self, _ctx: Option<&'a async_graphql::Context<'_>>, - _db: &'a Database, + _db: &'a Database, _entity: &'static str, _key: Option<&'static str>, _surface: EntityAccessSurface, @@ -103,11 +125,11 @@ impl RowPolicy for Visibility { struct DenyEntity; -impl EntityPolicy for DenyEntity { +impl EntityPolicy for DenyEntity { fn can_access_entity<'a>( &'a self, _ctx: Option<&'a async_graphql::Context<'_>>, - _db: &'a Database, + _db: &'a Database, _entity: &'static str, _key: Option<&'static str>, _kind: EntityAccessKind, @@ -117,8 +139,42 @@ impl EntityPolicy for DenyEntity { } } -async fn fixture(visibility: Option) -> Database { - let mut db = Database::::connect_sqlite("sqlite::memory:") +struct Fixture { + db: Database, + #[cfg(feature = "postgres")] + owned: owned_postgres::OwnedPostgres, +} +impl std::ops::Deref for Fixture { + type Target = Database; + fn deref(&self) -> &Self::Target { + &self.db + } +} +impl std::ops::DerefMut for Fixture { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.db + } +} +impl Fixture { + async fn finish(self) { + self.db.pool().close().await; + #[cfg(feature = "postgres")] + { + let mut owned = self.owned; + owned.cleanup().unwrap(); + } + } +} + +async fn fixture(visibility: Option) -> Fixture { + #[cfg(feature = "sqlite")] + let mut db = Database::::connect_sqlite("sqlite::memory:") + .await + .unwrap(); + #[cfg(feature = "postgres")] + let owned = owned_postgres::OwnedPostgres::start("repository-counts").unwrap(); + #[cfg(feature = "postgres")] + let mut db = Database::::connect_postgres(&owned.url) .await .unwrap(); sqlx::query("CREATE TABLE count_rows(id TEXT PRIMARY KEY, tenant TEXT NOT NULL)") @@ -132,7 +188,11 @@ async fn fixture(visibility: Option) -> Database { if let Some(policy) = visibility { db.set_row_policy(policy); } - db + Fixture { + db, + #[cfg(feature = "postgres")] + owned, + } } #[tokio::test] @@ -145,6 +205,7 @@ async fn unrestricted_policy_counts_inside_repository_transaction() { .await .unwrap(); assert_eq!(total, 3); + db.finish().await; } #[tokio::test] @@ -171,6 +232,7 @@ async fn complete_visibility_intersects_caller_filter_and_exists() { .await .unwrap(); assert_eq!(result, (2, false)); + db.finish().await; } #[tokio::test] @@ -184,6 +246,7 @@ async fn callback_and_prefilter_counts_remain_rejected() { .await .is_err() ); + db.finish().await; } } @@ -197,6 +260,7 @@ async fn no_policy_count_keeps_existing_behavior() { .await .unwrap(); assert_eq!(count, 3); + db.finish().await; } #[tokio::test] @@ -209,6 +273,7 @@ async fn wrong_entity_predicate_is_rejected_before_sql() { .await .is_err() ); + db.finish().await; } #[tokio::test] @@ -222,6 +287,7 @@ async fn unrestricted_visibility_does_not_override_entity_denial() { .await .is_err() ); + db.finish().await; } #[tokio::test] @@ -241,6 +307,7 @@ async fn counts_use_the_current_policy_instead_of_reusing_visibility() { .await .unwrap(); assert_eq!((before, after), (3, 2)); + db.finish().await; } #[tokio::test] @@ -270,4 +337,5 @@ async fn count_sees_writes_in_the_same_transaction_and_rollback_keeps_them_priva .await .unwrap(); assert_eq!(after, 2); + db.finish().await; } diff --git a/docs/reference/graphql-orm/runtime-and-writes.md b/docs/reference/graphql-orm/runtime-and-writes.md index 9fe2e4d3..bef7d326 100644 --- a/docs/reference/graphql-orm/runtime-and-writes.md +++ b/docs/reference/graphql-orm/runtime-and-writes.md @@ -364,3 +364,11 @@ impl Job { ``` If a computed field needs database access, use request-scoped `DataLoader` or another batching mechanism. Generated relation fields already use the relation batching runtime; custom computed fields should follow the same rule to avoid N+1 behavior. + +Transaction count regressions run on memory SQLite and individually owned +PostgreSQL containers (never an ambient application database): + +```bash +cargo test -p graphql-orm --locked --no-default-features --features sqlite --test repository_counts +cargo test -p graphql-orm --locked --no-default-features --features postgres --test repository_counts -- --test-threads=1 +```