22mod tests {
33 use backend:: app:: AppError ;
44 use backend:: auth:: PermissionLevel ;
5- use backend:: user_state:: UserState ;
6- use proptest :: prelude :: * ;
5+ use backend:: user_state:: arbitrary :: arbitrary_user_state_with_id ;
6+ use backend :: user_state :: { UserState , read_user_state_from_db } ;
77 use sqlx:: PgPool ;
8-
9- struct UserStateTestFixture {
10- pool : PgPool ,
8+ use test_strategy:: proptest;
9+
10+ async fn get_pool ( ) -> PgPool {
11+ let database_url =
12+ std:: env:: var ( "DATABASE_URL" ) . expect ( "DATABASE_URL must be set for tests" ) ;
13+ PgPool :: connect ( & database_url)
14+ . await
15+ . expect ( "Failed to connect to database" )
1116 }
1217
13- impl UserStateTestFixture {
14- async fn setup ( ) -> Self {
15- let database_url =
16- std:: env:: var ( "DATABASE_URL" ) . expect ( "DATABASE_URL must be set for tests" ) ;
17-
18- let pool = PgPool :: connect ( & database_url) . await . expect ( "Failed to connect to database" ) ;
19-
20- Self { pool }
21- }
22-
23- async fn cleanup ( & self , user_ids : & [ & str ] ) {
24- // TODO: find saner way to setup and cleanup a test db
25- let _ = sqlx:: query ( "DELETE FROM permissions WHERE subject = ANY($1)" )
26- . bind ( user_ids)
27- . execute ( & self . pool )
28- . await ;
29-
30- let _ = sqlx:: query ( "DELETE FROM users WHERE id = ANY($1)" )
31- . bind ( user_ids)
32- . execute ( & self . pool )
33- . await ;
34- }
18+ async fn cleanup ( pool : & PgPool , user_ids : & [ & str ] ) {
19+ // TODO: find saner way to setup and cleanup a test db
20+ let _ = sqlx:: query ( "DELETE FROM permissions WHERE subject = ANY($1)" )
21+ . bind ( user_ids)
22+ . execute ( pool)
23+ . await ;
24+
25+ let _ = sqlx:: query ( "DELETE FROM users WHERE id = ANY($1)" )
26+ . bind ( user_ids)
27+ . execute ( pool)
28+ . await ;
3529 }
3630
3731 /// Writes user state to the database. This is only for testing purposes.
@@ -44,7 +38,7 @@ mod tests {
4438 /// Note: The owner of a document is determined by who has the 'own' permission.
4539 /// If the doc has an owner specified, that user gets 'own' permission.
4640 /// The requesting user gets their specified permission level.
47- pub async fn write_user_state_to_db (
41+ async fn write_user_state_to_db (
4842 user_id : String ,
4943 db : & PgPool ,
5044 state : & UserState ,
@@ -68,7 +62,7 @@ mod tests {
6862 user_id. clone ( )
6963 } else {
7064 // No owner specified and user doesn't own - skip this invalid case
71- return user_id. clone ( ) ;
65+ user_id. clone ( )
7266 }
7367 } ) ;
7468
@@ -147,61 +141,40 @@ mod tests {
147141 Ok ( ( ) )
148142 }
149143
150- proptest ! {
151- #[ test]
152- fn generates_user_states_always_true( _state in any:: <UserState >( ) ) {
153- prop_assert!( true ) ;
154- }
155- }
156-
157- #[ tokio:: test]
158- async fn user_state_roundtrip ( ) {
159- use backend:: user_state:: arbitrary:: arbitrary_user_state_with_id;
160- use backend:: user_state:: read_user_state_from_db;
161- use proptest:: strategy:: ValueTree ;
162- use proptest:: test_runner:: TestRunner ;
163-
164- // Skip test if DATABASE_URL is not set
165- if std:: env:: var ( "DATABASE_URL" ) . is_err ( ) {
166- eprintln ! ( "Skipping user_state_roundtrip: DATABASE_URL not set" ) ;
167- return ;
168- }
169-
170- let fixture = UserStateTestFixture :: setup ( ) . await ;
171-
172- // Generate test cases synchronously
173- let mut runner = TestRunner :: default ( ) ;
174- let strategy = arbitrary_user_state_with_id ( ) ;
175-
176- for _ in 0 ..256 {
177- let ( user_id, state) =
178- strategy. new_tree ( & mut runner) . expect ( "Failed to generate test case" ) . current ( ) ;
179-
180- // Write state to db
181- write_user_state_to_db ( user_id. clone ( ) , & fixture. pool , & state)
182- . await
183- . expect ( "Failed to write user state" ) ;
184-
185- // Read state back from db
186- let read_state = read_user_state_from_db ( user_id. clone ( ) , & fixture. pool )
187- . await
188- . expect ( "Failed to read user state" ) ;
189-
190- // Cleanup test data
191- let user_ids: Vec < & str > = std:: iter:: once ( user_id. as_str ( ) )
192- . chain (
193- state. documents . iter ( ) . filter_map ( |d| d. owner . as_ref ( ) . map ( |o| o. id . as_str ( ) ) ) ,
194- )
195- . collect ( ) ;
196- fixture. cleanup ( & user_ids) . await ;
144+ #[ proptest( async = "tokio" ) ]
145+ async fn user_state_roundtrip (
146+ #[ strategy( arbitrary_user_state_with_id( ) ) ] user_id_and_state : ( String , UserState ) ,
147+ ) {
148+ let ( user_id, state) = user_id_and_state;
149+ let pool = get_pool ( ) . await ;
150+
151+ // Write state to db
152+ write_user_state_to_db ( user_id. clone ( ) , & pool, & state)
153+ . await
154+ . expect ( "Failed to write user state" ) ;
155+
156+ // Read state back from db
157+ let read_state = read_user_state_from_db ( user_id. clone ( ) , & pool)
158+ . await
159+ . expect ( "Failed to read user state" ) ;
160+
161+ // Cleanup test data
162+ let user_ids: Vec < & str > = std:: iter:: once ( user_id. as_str ( ) )
163+ . chain (
164+ state
165+ . documents
166+ . iter ( )
167+ . filter_map ( |d| d. owner . as_ref ( ) . map ( |o| o. id . as_str ( ) ) ) ,
168+ )
169+ . collect ( ) ;
170+ cleanup ( & pool, & user_ids) . await ;
197171
198- // Sort both document lists by ref_id for comparison (read returns sorted by created_at DESC)
199- let mut expected_docs = state. documents . clone ( ) ;
200- let mut actual_docs = read_state. documents . clone ( ) ;
201- expected_docs. sort_by ( |a, b| a. ref_id . cmp ( & b. ref_id ) ) ;
202- actual_docs. sort_by ( |a, b| a. ref_id . cmp ( & b. ref_id ) ) ;
172+ // Sort both document lists by ref_id for comparison (read returns sorted by created_at DESC)
173+ let mut expected_docs = state. documents . clone ( ) ;
174+ let mut actual_docs = read_state. documents . clone ( ) ;
175+ expected_docs. sort_by ( |a, b| a. ref_id . cmp ( & b. ref_id ) ) ;
176+ actual_docs. sort_by ( |a, b| a. ref_id . cmp ( & b. ref_id ) ) ;
203177
204- assert_eq ! ( expected_docs, actual_docs, "UserState roundtrip failed" ) ;
205- }
178+ proptest:: prop_assert_eq!( expected_docs, actual_docs) ;
206179 }
207180}
0 commit comments