@@ -76,3 +76,232 @@ impl HomeServerCert {
7676 . map ( Some )
7777 }
7878}
79+
80+ #[ cfg( test) ]
81+ mod tests {
82+ use chrono:: { NaiveDate , Utc } ;
83+ use sqlx:: { Pool , Postgres , query} ;
84+
85+ use super :: * ;
86+ use crate :: crypto:: ed25519:: { DigitalPublicKey , DigitalSignature , generate_keypair} ;
87+
88+ /// Helper function to update fixture with real ED25519 keys and mock
89+ /// certificates
90+ // TODO: use real certs
91+ async fn setup_real_keys_mock_certs ( pool : & Pool < Postgres > ) {
92+ // Generate keypairs functionally and convert to PEM
93+ let public_key_updates: Vec < ( i64 , String ) > = ( 0 ..6 )
94+ . map ( |_| generate_keypair ( ) . 1 ) // Take only public key
95+ . map ( |pubkey| pubkey. public_key_info ( ) . to_pem ( polyproto:: der:: pem:: LineEnding :: LF ) )
96+ . collect :: < Result < Vec < _ > , _ > > ( )
97+ . expect ( "Failed to encode public keys to PEM" )
98+ . into_iter ( )
99+ . zip ( [ 100i64 , 101 , 102 , 103 , 200 , 201 ] )
100+ . map ( |( pem, id) | ( id, pem) )
101+ . collect ( ) ;
102+
103+ // Apply updates to database
104+ for ( id, pem) in public_key_updates {
105+ query ! ( "UPDATE public_keys SET pubkey = $1 WHERE id = $2" , pem, id)
106+ . execute ( pool)
107+ . await
108+ . unwrap_or_else ( |_| panic ! ( "Failed to update public key {}" , id) ) ;
109+ }
110+
111+ // Generate mock certificates functionally
112+ let mock_cert_data = [
113+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy8Dbv8prpJ/0kKhlGeJY" ,
114+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1gKdWHX6Zv8ZLNqXwC7D" ,
115+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2hLdVGY7Wx9YMNpXzE8G" ,
116+ ] ;
117+
118+ let certificate_updates: Vec < ( i64 , String ) > = mock_cert_data
119+ . iter ( )
120+ . map ( |& data| {
121+ format ! ( "-----BEGIN CERTIFICATE-----\n {}\n -----END CERTIFICATE-----" , data)
122+ } )
123+ . zip ( [ 100i64 , 101 , 102 ] )
124+ . map ( |( pem, id) | ( id, pem) )
125+ . collect ( ) ;
126+
127+ // Apply certificate updates to database
128+ for ( id, pem) in certificate_updates {
129+ query ! ( "UPDATE idcert SET pem_encoded = $1 WHERE idcsr_id = $2" , pem, id)
130+ . execute ( pool)
131+ . await
132+ . unwrap_or_else ( |_| panic ! ( "Failed to update certificate {}" , id) ) ;
133+ }
134+ }
135+
136+ #[ sqlx:: test( fixtures( "../../fixtures/idcert_integration_tests.sql" ) ) ]
137+ async fn test_get_idcert_by_nonexistent_domain ( pool : Pool < Postgres > ) {
138+ setup_real_keys_mock_certs ( & pool) . await ;
139+ let db = Database { pool } ;
140+
141+ let domain = DomainName :: new ( "nonexistent.com" ) . unwrap ( ) ;
142+ let timestamp = Utc :: now ( ) . naive_utc ( ) ;
143+
144+ let result = HomeServerCert :: get_idcert_by :: < DigitalSignature , DigitalPublicKey > (
145+ & db, & domain, & timestamp,
146+ )
147+ . await
148+ . unwrap ( ) ;
149+
150+ assert ! ( result. is_none( ) ) ;
151+ }
152+
153+ #[ sqlx:: test( fixtures( "../../fixtures/idcert_integration_tests.sql" ) ) ]
154+ async fn test_get_idcert_by_expired_certificate ( pool : Pool < Postgres > ) {
155+ setup_real_keys_mock_certs ( & pool) . await ;
156+ let db = Database { pool } ;
157+
158+ // expired.net has a certificate that's already expired
159+ let domain = DomainName :: new ( "expired.net" ) . unwrap ( ) ;
160+ let timestamp = Utc :: now ( ) . naive_utc ( ) ;
161+
162+ let result = HomeServerCert :: get_idcert_by :: < DigitalSignature , DigitalPublicKey > (
163+ & db, & domain, & timestamp,
164+ )
165+ . await
166+ . unwrap ( ) ;
167+
168+ assert ! ( result. is_none( ) ) ;
169+ }
170+
171+ #[ sqlx:: test( fixtures( "../../fixtures/idcert_integration_tests.sql" ) ) ]
172+ async fn test_get_idcert_by_future_timestamp ( pool : Pool < Postgres > ) {
173+ setup_real_keys_mock_certs ( & pool) . await ;
174+ let db = Database { pool } ;
175+
176+ let domain = DomainName :: new ( "example.com" ) . unwrap ( ) ;
177+ // Set timestamp far in the future, beyond certificate validity
178+ let future_timestamp =
179+ NaiveDate :: from_ymd_opt ( 2030 , 1 , 1 ) . unwrap ( ) . and_hms_opt ( 0 , 0 , 0 ) . unwrap ( ) ;
180+
181+ let result = HomeServerCert :: get_idcert_by :: < DigitalSignature , DigitalPublicKey > (
182+ & db,
183+ & domain,
184+ & future_timestamp,
185+ )
186+ . await
187+ . unwrap ( ) ;
188+
189+ assert ! ( result. is_none( ) ) ;
190+ }
191+
192+ #[ sqlx:: test( fixtures( "../../fixtures/idcert_integration_tests.sql" ) ) ]
193+ async fn test_get_idcert_by_past_timestamp ( pool : Pool < Postgres > ) {
194+ setup_real_keys_mock_certs ( & pool) . await ;
195+ let db = Database { pool } ;
196+
197+ let domain = DomainName :: new ( "example.com" ) . unwrap ( ) ;
198+ // Set timestamp in the past, before certificate validity
199+ let past_timestamp =
200+ NaiveDate :: from_ymd_opt ( 2020 , 1 , 1 ) . unwrap ( ) . and_hms_opt ( 0 , 0 , 0 ) . unwrap ( ) ;
201+
202+ let result = HomeServerCert :: get_idcert_by :: < DigitalSignature , DigitalPublicKey > (
203+ & db,
204+ & domain,
205+ & past_timestamp,
206+ )
207+ . await
208+ . unwrap ( ) ;
209+
210+ assert ! ( result. is_none( ) ) ;
211+ }
212+
213+ #[ tokio:: test]
214+ async fn test_get_idcert_by_domain_case_sensitivity ( ) {
215+ // Test domain validation behavior
216+ let domain_exact = DomainName :: new ( "example.com" ) ;
217+ assert ! ( domain_exact. is_ok( ) , "Lowercase domain should be valid" ) ;
218+
219+ // Test that uppercase domain names are invalid per domain validation rules
220+ let domain_upper_result = DomainName :: new ( "EXAMPLE.COM" ) ;
221+ assert ! (
222+ domain_upper_result. is_err( ) ,
223+ "Uppercase domain names should be rejected by DomainName validation"
224+ ) ;
225+
226+ println ! ( "Domain case sensitivity validation works correctly" ) ;
227+ }
228+
229+ #[ sqlx:: test( fixtures( "../../fixtures/idcert_integration_tests.sql" ) ) ]
230+ async fn test_get_idcert_by_multiple_domains ( pool : Pool < Postgres > ) {
231+ setup_real_keys_mock_certs ( & pool) . await ;
232+ let db = Database { pool } ;
233+
234+ let timestamp = Utc :: now ( ) . naive_utc ( ) ;
235+
236+ // Test example.com
237+ let domain1 = DomainName :: new ( "example.com" ) . unwrap ( ) ;
238+ let result1 = HomeServerCert :: get_idcert_by :: < DigitalSignature , DigitalPublicKey > (
239+ & db, & domain1, & timestamp,
240+ )
241+ . await ;
242+
243+ // Test test.org
244+ let domain2 = DomainName :: new ( "test.org" ) . unwrap ( ) ;
245+ let result2 = HomeServerCert :: get_idcert_by :: < DigitalSignature , DigitalPublicKey > (
246+ & db, & domain2, & timestamp,
247+ )
248+ . await ;
249+
250+ // Both should find database records but fail on certificate parsing for now
251+ // TODO
252+ assert ! ( result1. is_err( ) ) ;
253+ assert ! ( result2. is_err( ) ) ;
254+ }
255+
256+ #[ sqlx:: test( fixtures( "../../fixtures/idcert_integration_tests.sql" ) ) ]
257+ async fn test_get_idcert_by_database_edge_cases ( pool : Pool < Postgres > ) {
258+ setup_real_keys_mock_certs ( & pool) . await ;
259+ let db = Database { pool } ;
260+
261+ // Test with subdomain that doesn't exist
262+ let subdomain = DomainName :: new ( "sub.example.com" ) . unwrap ( ) ;
263+ let timestamp = Utc :: now ( ) . naive_utc ( ) ;
264+
265+ let result_subdomain = HomeServerCert :: get_idcert_by :: < DigitalSignature , DigitalPublicKey > (
266+ & db, & subdomain, & timestamp,
267+ )
268+ . await
269+ . unwrap ( ) ;
270+
271+ assert ! ( result_subdomain. is_none( ) ) ;
272+
273+ // Test with empty domain components (this should fail domain creation)
274+ let empty_domain_result = DomainName :: new ( "" ) ;
275+ assert ! ( empty_domain_result. is_err( ) ) ;
276+ }
277+
278+ #[ tokio:: test]
279+ async fn test_real_ed25519_key_generation_and_pem_encoding ( ) {
280+ let ( _private_key, public_key) = generate_keypair ( ) ;
281+
282+ // Test PEM encoding/decoding pipeline functionally
283+ let pem_data = public_key
284+ . public_key_info ( )
285+ . to_pem ( polyproto:: der:: pem:: LineEnding :: LF )
286+ . expect ( "Failed to encode public key to PEM" ) ;
287+
288+ // Verify PEM structure functionally
289+ [
290+ ( "-----BEGIN PUBLIC KEY-----" , pem_data. starts_with ( "-----BEGIN PUBLIC KEY-----" ) ) ,
291+ ( "-----END PUBLIC KEY-----\n " , pem_data. ends_with ( "-----END PUBLIC KEY-----\n " ) ) ,
292+ ]
293+ . iter ( )
294+ . for_each ( |( expected, valid) | {
295+ assert ! ( * valid, "PEM structure validation failed for: {}" , expected) ;
296+ } ) ;
297+
298+ // Test round-trip: PEM -> PublicKeyInfo -> DigitalPublicKey -> bytes
299+ let original_bytes = public_key. key . to_bytes ( ) ;
300+ let reconstructed_bytes = PublicKeyInfo :: from_pem ( & pem_data)
301+ . and_then ( |info| DigitalPublicKey :: try_from_public_key_info ( info) )
302+ . map ( |key| key. key . to_bytes ( ) )
303+ . expect ( "Failed to reconstruct key from PEM" ) ;
304+
305+ assert_eq ! ( original_bytes, reconstructed_bytes, "Round-trip key conversion failed" ) ;
306+ }
307+ }
0 commit comments