@@ -2,11 +2,47 @@ use std::collections::HashMap;
2
2
use std:: fs;
3
3
4
4
use chrono:: NaiveDate ;
5
+ use libxml:: tree:: Document ;
5
6
use rust_decimal:: Decimal ;
6
7
use rust_decimal_macros:: dec;
7
8
8
9
use xbuilder:: prelude:: * ;
9
10
11
+ use libxml:: schemas:: SchemaParserContext ;
12
+ use libxml:: schemas:: SchemaValidationContext ;
13
+ use xsender:: prelude:: Credentials ;
14
+ use xsender:: prelude:: DocumentType ;
15
+ use xsender:: prelude:: FileSender ;
16
+ use xsender:: prelude:: SendFileAggregatedResponse ;
17
+ use xsender:: prelude:: UblFile ;
18
+ use xsender:: prelude:: Urls ;
19
+ use xsigner:: RsaKeyPair ;
20
+ use xsigner:: XSigner ;
21
+
22
+ const INVOICE_XSD : & str = "tests/resources/xsd/2.1/maindoc/UBL-Invoice-2.1.xsd" ;
23
+ const CREDIT_NOTE_XSD : & str = "tests/resources/xsd/2.1/maindoc/UBL-CreditNote-2.1.xsd" ;
24
+ const DEBIT_NOTE_XSD : & str = "tests/resources/xsd/2.1/maindoc/UBL-DebitNote-2.1.xsd" ;
25
+ const _DESPATCH_ADVICE_XSD: & str = "tests/resources/xsd/2.1/maindoc/UBL-DespatchAdvice-2.1.xsd" ;
26
+ const _VOIDED_DOCUMENTS_XSD: & str = "tests/resources/xsd/2.0/maindoc/UBLPE-VoidedDocuments-1.0.xsd" ;
27
+ const _SUMMARY_DOCUMENTS_XSD: & str =
28
+ "tests/resources/xsd/2.0/maindoc/UBLPE-SummaryDocuments-1.0.xsd" ;
29
+ const _PERCEPTION_XSD: & str = "tests/resources/xsd/2.0/maindoc/UBLPE-Perception-1.0.xsd" ;
30
+ const _RETENTION_XSD: & str = "tests/resources/xsd/2.0/maindoc/UBLPE-Retention-1.0.xsd" ;
31
+
32
+ lazy_static:: lazy_static! {
33
+ pub static ref CLIENT : FileSender = FileSender {
34
+ urls: Urls {
35
+ invoice: "https://e-beta.sunat.gob.pe/ol-ti-itcpfegem-beta/billService" . to_string( ) ,
36
+ perception_retention: "https://e-beta.sunat.gob.pe/ol-ti-itemision-otroscpe-gem-beta/billService" . to_string( ) ,
37
+ despatch: "https://api-cpe.sunat.gob.pe/v1/contribuyente/gem" . to_string( ) ,
38
+ } ,
39
+ credentials: Credentials {
40
+ username: "12345678959MODDATOS" . to_string( ) ,
41
+ password: "MODDATOS" . to_string( ) ,
42
+ } ,
43
+ } ;
44
+ }
45
+
10
46
pub fn defaults_base ( ) -> Defaults {
11
47
Defaults {
12
48
date : NaiveDate :: from_ymd_opt ( 2019 , 12 , 24 ) . unwrap ( ) ,
@@ -179,40 +215,65 @@ pub fn detalle_base() -> Detalle {
179
215
}
180
216
}
181
217
218
+ fn sign_xml ( xml : & str ) -> Document {
219
+ let private_key_from_file = fs:: read_to_string ( "tests/resources/certificates/private.key" )
220
+ . expect ( "Could not read private.key" ) ;
221
+ let certificate_from_file = fs:: read_to_string ( "tests/resources/certificates/public.cer" )
222
+ . expect ( "Could not read public.cer" ) ;
223
+
224
+ let rsa_key_pair =
225
+ RsaKeyPair :: from_pkcs1_pem_and_certificate ( & private_key_from_file, & certificate_from_file)
226
+ . expect ( "Could not initialize RsaKeyPair" ) ;
227
+
228
+ let signer = XSigner :: from_string ( xml) . expect ( "Could parse xml" ) ;
229
+ signer. sign ( & rsa_key_pair) . expect ( "Could not sign document" ) ;
230
+
231
+ signer. xml_document
232
+ }
233
+
182
234
#[ allow( dead_code) ]
183
- pub fn assert_invoice ( invoice : & mut Invoice , snapshot_filename : & str ) {
235
+ pub async fn assert_invoice ( invoice : & mut Invoice , snapshot_filename : & str ) {
184
236
let defaults = defaults_base ( ) ;
185
237
invoice. enrich ( & defaults) ;
186
238
187
- let result = invoice. render ( ) ;
188
- assert ! ( result. is_ok( ) ) ;
239
+ let xml = invoice. render ( ) . expect ( "Could not render invoice" ) ;
240
+
241
+ assert_snapshot ( & xml, snapshot_filename) ;
189
242
190
- assert_snapshot ( result. ok ( ) . unwrap ( ) , snapshot_filename)
243
+ let xml_signed = sign_xml ( & xml) ;
244
+ assert_xsd ( & xml_signed, INVOICE_XSD ) ;
245
+ assert_sunat ( & xml_signed) . await ;
191
246
}
192
247
193
248
#[ allow( dead_code) ]
194
- pub fn assert_credit_note ( credit_note : & mut CreditNote , snapshot_filename : & str ) {
249
+ pub async fn assert_credit_note ( credit_note : & mut CreditNote , snapshot_filename : & str ) {
195
250
let defaults = defaults_base ( ) ;
196
251
credit_note. enrich ( & defaults) ;
197
252
198
- let result = credit_note. render ( ) ;
199
- assert ! ( result. is_ok( ) ) ;
253
+ let xml = credit_note. render ( ) . expect ( "Could not render credit note" ) ;
254
+
255
+ assert_snapshot ( & xml, snapshot_filename) ;
200
256
201
- assert_snapshot ( result. ok ( ) . unwrap ( ) , snapshot_filename)
257
+ let xml_signed = sign_xml ( & xml) ;
258
+ assert_xsd ( & xml_signed, CREDIT_NOTE_XSD ) ;
259
+ assert_sunat ( & xml_signed) . await ;
202
260
}
203
261
204
262
#[ allow( dead_code) ]
205
- pub fn assert_debit_note ( debit_note : & mut DebitNote , snapshot_filename : & str ) {
263
+ pub async fn assert_debit_note ( debit_note : & mut DebitNote , snapshot_filename : & str ) {
206
264
let defaults = defaults_base ( ) ;
207
265
debit_note. enrich ( & defaults) ;
208
266
209
- let result = debit_note. render ( ) ;
210
- assert ! ( result. is_ok( ) ) ;
267
+ let xml = debit_note. render ( ) . expect ( "Could not render debit note" ) ;
211
268
212
- assert_snapshot ( result. ok ( ) . unwrap ( ) , snapshot_filename)
269
+ assert_snapshot ( & xml, snapshot_filename) ;
270
+
271
+ let xml_signed = sign_xml ( & xml) ;
272
+ assert_xsd ( & xml_signed, DEBIT_NOTE_XSD ) ;
273
+ assert_sunat ( & xml_signed) . await ;
213
274
}
214
275
215
- fn assert_snapshot ( expected : String , snapshot_filename : & str ) {
276
+ fn assert_snapshot ( expected : & str , snapshot_filename : & str ) {
216
277
let snapshot_file_content = fs:: read_to_string ( snapshot_filename) ;
217
278
assert ! ( snapshot_file_content. is_ok( ) ) ;
218
279
@@ -223,3 +284,65 @@ fn assert_snapshot(expected: String, snapshot_filename: &str) {
223
284
snapshot_filename
224
285
) ;
225
286
}
287
+
288
+ fn assert_xsd ( xml : & Document , schema : & str ) {
289
+ let mut xsdparser = SchemaParserContext :: from_file ( schema) ;
290
+ let xsd = SchemaValidationContext :: from_parser ( & mut xsdparser) ;
291
+
292
+ if let Err ( errors) = xsd {
293
+ for err in & errors {
294
+ println ! ( "{}" , err. message. as_ref( ) . unwrap( ) ) ;
295
+ }
296
+
297
+ panic ! ( "Failed to parse schema" ) ;
298
+ }
299
+
300
+ let mut xsd = xsd. unwrap ( ) ;
301
+
302
+ if let Err ( errors) = xsd. validate_document ( xml) {
303
+ for err in & errors {
304
+ println ! ( "{}" , err. message. as_ref( ) . unwrap( ) ) ;
305
+ }
306
+
307
+ panic ! ( "Invalid XML accoding to XSD schema" ) ;
308
+ }
309
+ }
310
+
311
+ async fn assert_sunat ( xml : & Document ) {
312
+ let xml_file = UblFile {
313
+ file_content : xml. to_string ( ) ,
314
+ } ;
315
+
316
+ let result = CLIENT
317
+ . send_file ( & xml_file)
318
+ . await
319
+ . expect ( "Could not get a valid response" ) ;
320
+
321
+ let documet_type = xml_file
322
+ . metadata ( )
323
+ . expect ( "Could not extract xml metadata" )
324
+ . document_type ;
325
+
326
+ match documet_type. as_str ( ) {
327
+ DocumentType :: VOIDED_DOCUMENTS | DocumentType :: SUMMARY_DOCUMENTS => {
328
+ let result = match result. response {
329
+ SendFileAggregatedResponse :: Cdr ( _, _) => false ,
330
+ SendFileAggregatedResponse :: Ticket ( _) => true ,
331
+ SendFileAggregatedResponse :: Error ( _) => false ,
332
+ } ;
333
+ assert ! ( result)
334
+ }
335
+ _ => {
336
+ let result = match result. response {
337
+ SendFileAggregatedResponse :: Cdr ( _, cdr_metadata) => {
338
+ assert_eq ! ( "0" , cdr_metadata. response_code) ;
339
+ assert_eq ! ( Vec :: <String >:: new( ) , cdr_metadata. notes) ;
340
+ true
341
+ }
342
+ SendFileAggregatedResponse :: Ticket ( _) => false ,
343
+ SendFileAggregatedResponse :: Error ( _) => false ,
344
+ } ;
345
+ assert ! ( result)
346
+ }
347
+ } ;
348
+ }
0 commit comments