From 213f94c6e715f0e4eba48437c4f364b94c4f1558 Mon Sep 17 00:00:00 2001 From: Florian Briand Date: Sat, 5 Oct 2024 11:39:51 +0200 Subject: [PATCH 1/6] feat: raw (non-deserialized) implementation of the 01 group of LireCartePS --- crates/fsv/src/fsv_parsing/blocks.rs | 2 + crates/fsv/src/fsv_parsing/groups/mod.rs | 6 + .../fsv_parsing/groups/ssv_lire_carte_ps.rs | 145 ++++++++++++++++++ .../src/fsv_parsing/groups/ssv_lire_config.rs | 3 +- crates/fsv/src/ssv/mod.rs | 11 +- 5 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 crates/fsv/src/fsv_parsing/groups/ssv_lire_carte_ps.rs diff --git a/crates/fsv/src/fsv_parsing/blocks.rs b/crates/fsv/src/fsv_parsing/blocks.rs index fb47a70..46af3e8 100644 --- a/crates/fsv/src/fsv_parsing/blocks.rs +++ b/crates/fsv/src/fsv_parsing/blocks.rs @@ -54,6 +54,8 @@ pub struct GroupId( #[deku(ctx = "group_id: u16", id = "group_id")] #[allow(non_camel_case_types)] pub enum DataGroup { + #[deku(id = 1)] + LireCartePS_Group1_Holder(groups::ssv_lire_carte_ps::group_1_holder::Holder), #[deku(id = 60)] LireConfig_Group60_ConfigHeader(groups::ssv_lire_config::group_60_header_config::ConfigHeader), #[deku(id = 61)] diff --git a/crates/fsv/src/fsv_parsing/groups/mod.rs b/crates/fsv/src/fsv_parsing/groups/mod.rs index b564ac8..03e1fbb 100644 --- a/crates/fsv/src/fsv_parsing/groups/mod.rs +++ b/crates/fsv/src/fsv_parsing/groups/mod.rs @@ -4,6 +4,7 @@ use deku::{deku_derive, DekuError}; use super::{ size_read, map_bytes_to_lossy_string }; +pub mod ssv_lire_carte_ps; pub mod ssv_lire_config; /// # Convert a DataField to a specific type @@ -43,6 +44,11 @@ pub struct NumericString( #[deku(map = "map_from_data_field")] String ); +impl From<&str> for NumericString { + fn from(s: &str) -> Self { + NumericString(s.to_string()) + } +} #[deku_derive(DekuRead)] #[derive(Debug, Clone, PartialEq)] diff --git a/crates/fsv/src/fsv_parsing/groups/ssv_lire_carte_ps.rs b/crates/fsv/src/fsv_parsing/groups/ssv_lire_carte_ps.rs new file mode 100644 index 0000000..5c45c26 --- /dev/null +++ b/crates/fsv/src/fsv_parsing/groups/ssv_lire_carte_ps.rs @@ -0,0 +1,145 @@ +//! # Structures de parsing des données de la fonction SSV_LireCartePS + +use deku::deku_derive; + +use crate::fsv_parsing::groups::NumericString; + +/// # Titulaire +/// 1 occurence +pub mod group_1_holder { + + use crate::fsv_parsing::groups::AlphaNumericString; + + use super::*; + + /// Groupe 1 - Titulaire + #[deku_derive(DekuRead)] + #[derive(Debug, PartialEq)] + pub struct Holder { + pub card_type: CardPSType, + pub national_id_type: NationalIDType, + pub national_id: AlphaNumericString, // /!\ CE and not CA + pub national_id_key: AlphaNumericString, + pub civility_code: CivilityCode, + pub holder_lastname: AlphaNumericString, // /!\ CE and not CA + pub holder_firstname: AlphaNumericString, // /!\ CE and not CA + pub category_card: AlphaNumericString, + } + + // Fields + #[deku_derive(DekuRead)] + #[derive(Debug, PartialEq)] + pub struct CardPSType(pub NumericString); + + #[deku_derive(DekuRead)] + #[derive(Debug, PartialEq)] + pub struct NationalIDType(pub NumericString); + + #[deku_derive(DekuRead)] + #[derive(Debug, PartialEq)] + pub struct CivilityCode(pub NumericString); + + +} + +#[cfg(test)] +mod tests { + use deku::DekuContainerRead as _; + + use crate::fsv_parsing::blocks::BlockHeader; + + use super::*; + + mod data { + pub const BUFFER: &[u8] = &[ + 0, 1, // Block ID + 53, // Block size + 1, // Type de carte PS, 2 CN + 48, // 0 + 1, // Type d'identification nationale, 1 CN + 56, // 8 + 11, // N° d'identification nationale, 8-30 CE + 57, 57, 55, 48, 48, 53, 57, 51, 54, 56, 54, + 1, // Clé du N° d'identification nationale, 1 CN + 54, // 6 + 2, // Code civilité, 2 CN + 51, 49, // 31 + 23, // Nom du PS, 27 CE + 80, 72, 65, 82, 77, 79, 70, 70, 73, 67, + 69, 32, 82, 80, 80, 83, 48, 48, 53, 57, + 51, 54, 56, + 7, // Prénom du PS, 27 CE + 71, 73, 76, 66, 69, 82, 84, + // ??? Missing ??? Catégorie Carte, 1 CA + 0, 2, // Block ID + 93, // Block size + 1, // N° logique de la situation de facturation du PS, 1 CB + 1, + 1, // Mode d'exercice, 2 CN + 48, + 1, // Statut d'exercice, 3 CN + 49, + 2, // Secteur d'activité, 3 CN + 56, 54, + 1, // Type d'identification structure, 1 CN + 49, + 9, // N° d'identification structure, 14 CA + 48, 66, 48, 50, 52, 54, 50, 56, 54, + 1, // Clé du N° d'identification structure, 1 CN + 54, + 34, // Raison sociale structure, 40 CE + 80, 72, 65, 82, 77, 65, 67, 73, 69, 32, + 68, 69, 32, 76, 65, 32, 71, 65, 82, 69, + 32, 82, 79, 85, 84, 73, 69, 82, 69, 50, + 52, 54, 50, 56, + 8, // N° d'identification de facturation du PS, 8 CN + 48, 48, 50, 48, 57, 51, 54, 56, + 1, // Clé du N° d'identification de facturation du PS, 1 CN + 48, + 0, // N° d'identification du PS remplaçant, 30 CA + 1, // Clé du N° d'identification du PS remplaçant, 1 CN + 48, + 1, // Code conventionnel, 1 CN + 49, + 2, // Code spécialité, 2 CN + 53, 48, + 2, // Code zone tarifaire, 2 CN + 49, 48, + 2, // Code zone IK + 48, 48, + 1, // Code agrément 1, 1 CN + 48, + 1, // Code agrément 2, 1 CN + 48, + 1, // Code agrément 3, 1 CN + 48, + 1, // Habilitation à signer une Facture // 1 CN + 49, + 1, // Habilitation à signer un lot // 1 CN + 49 + ]; + } + + #[test] + fn test_lire_carte_ps_first_header() { + // env_logger::init(); // Uncomment and run with RUST_LOG=trace for deku debugging + let ((_rest, _offset), block_header) = BlockHeader::from_bytes((data::BUFFER, 0)).unwrap(); + assert_eq!(block_header.group_id.0, 1, "Header ID"); + assert_eq!(block_header.data_size, 53, "Header Size"); + } + + #[test] + fn test_group_1_holder() { + env_logger::init(); // Uncomment and run with RUST_LOG=trace for deku debugging + let offset = 3*8; + let (_rest, holder) = group_1_holder::Holder::from_bytes((data::BUFFER, offset)).unwrap(); + assert_eq!(holder.card_type.0.0, "0", "Card type"); + assert_eq!(holder.national_id_type.0.0, "8", "National ID type"); + assert_eq!(holder.national_id.0, "99700593686", "National Id"); + assert_eq!(holder.national_id_key.0, "6", "National ID Key"); + assert_eq!(holder.civility_code.0.0, "31", "Civility Code"); + assert_eq!(holder.holder_lastname.0, "PHARMOFFICE RPPS0059368", "Holder Lastname"); + assert_eq!(holder.holder_firstname.0, "GILBERT", "Holder Firstname"); + assert!(holder.category_card.0.is_empty(), "Category card"); + } +} \ No newline at end of file diff --git a/crates/fsv/src/fsv_parsing/groups/ssv_lire_config.rs b/crates/fsv/src/fsv_parsing/groups/ssv_lire_config.rs index 5619dc6..7296b49 100644 --- a/crates/fsv/src/fsv_parsing/groups/ssv_lire_config.rs +++ b/crates/fsv/src/fsv_parsing/groups/ssv_lire_config.rs @@ -209,7 +209,7 @@ mod tests { let ((_rest, _offset), block_header) = BlockHeader::from_bytes((buffer, offset)).unwrap(); assert_eq!(block_header.group_id.0, 60, "Header ID"); - // assert_eq!(block_header.data_size, 15, "Header Size"); + assert_eq!(block_header.data_size, 15, "Header Size"); } #[test] @@ -289,6 +289,7 @@ mod tests { assert_eq!(content.name.0.0, "Gemalto PC Twin Reader (645D94C3) 00 00", "Reader Name"); assert_eq!(content.card_type.0.0, "2", "Card Type"); }, + _ => panic!("Unexpected data block type"), } } } diff --git a/crates/fsv/src/ssv/mod.rs b/crates/fsv/src/ssv/mod.rs index 375327c..c087647 100644 --- a/crates/fsv/src/ssv/mod.rs +++ b/crates/fsv/src/ssv/mod.rs @@ -76,7 +76,7 @@ impl SSV { /// # Read the CPS card /// Implement: SSV_LireCartePS - pub fn read_professional_card(&self, pin_code: &str) -> Result<(), Error> { + pub fn read_professional_card(&self, pin_code: &str) -> Result { let pcsc_reader_name = "Gemalto PC Twin Reader (645D94C3) 00 00"; let pin_code = CString::new(pin_code).expect("CString::new failed"); @@ -111,12 +111,13 @@ impl SSV { let error = SSVErrorCodes::from(result); return Err(Error::SSVError(error)); } - // Print 10 bytes of the buffer - let buffer = unsafe { std::slice::from_raw_parts(out_buffer_ptr as *const u8, 10) }; - println!("{:?}", buffer); + // Parse the buffer into a Data struct + let buffer = unsafe { std::slice::from_raw_parts(out_buffer_ptr as *const u8, out_buffer_size) }; + let (_rest, cps_blocks) = Data::from_bytes((buffer, 0)).unwrap(); + // Free memory unsafe { libc::free(out_buffer_ptr) }; - Ok(()) + Ok(cps_blocks) } /// # Get the configuration of the SSV library -- 2.45.2 From a53360d114fe2d87a97267724878d46a2f1e79e4 Mon Sep 17 00:00:00 2001 From: Florian Briand Date: Sun, 6 Oct 2024 14:49:28 +0200 Subject: [PATCH 2/6] feat: use an enum instead of raw ID for CPS type --- crates/fsv/src/fsv_parsing/groups/mod.rs | 20 ++++++++++++++++- .../fsv_parsing/groups/ssv_lire_carte_ps.rs | 22 ++++++++++++++++--- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/crates/fsv/src/fsv_parsing/groups/mod.rs b/crates/fsv/src/fsv_parsing/groups/mod.rs index 03e1fbb..132acea 100644 --- a/crates/fsv/src/fsv_parsing/groups/mod.rs +++ b/crates/fsv/src/fsv_parsing/groups/mod.rs @@ -17,10 +17,19 @@ where { let text = String::from_utf8(data_field.data) .map_err(|e| DekuError::Parse(e.to_string().into()))?; - T::from_str(&text) + text.parse::() .map_err(|e| DekuError::Parse(e.to_string().into())) } +/// # Extract an enum id from a string +/// Deku enums only supports numbers as id, and we usually extract strings +/// from data fields. This function is used as a context function to convert +/// a string, such as obtained with NumericString, to an enum id +pub fn extract_enum_id_from_str(id_string: &str, default: T) -> T + where T: FromStr { + id_string.parse::().unwrap_or(default) +} + // ------------------- DATA FIELD TYPES ------------------- /// # Data field structure @@ -105,4 +114,13 @@ mod test { assert_eq!(software_version.version, "07"); assert_eq!(software_version.revision, "20"); } + + #[test] + fn test_map_from_data_field() { + let data_field = DataField { + data: vec![48, 55], + }; + let id: u8 = map_from_data_field(data_field).unwrap(); + assert_eq!(id, 7); + } } \ No newline at end of file diff --git a/crates/fsv/src/fsv_parsing/groups/ssv_lire_carte_ps.rs b/crates/fsv/src/fsv_parsing/groups/ssv_lire_carte_ps.rs index 5c45c26..64677e1 100644 --- a/crates/fsv/src/fsv_parsing/groups/ssv_lire_carte_ps.rs +++ b/crates/fsv/src/fsv_parsing/groups/ssv_lire_carte_ps.rs @@ -8,7 +8,7 @@ use crate::fsv_parsing::groups::NumericString; /// 1 occurence pub mod group_1_holder { - use crate::fsv_parsing::groups::AlphaNumericString; + use crate::fsv_parsing::groups::{ extract_enum_id_from_str, AlphaNumericString }; use super::*; @@ -16,6 +16,9 @@ pub mod group_1_holder { #[deku_derive(DekuRead)] #[derive(Debug, PartialEq)] pub struct Holder { + #[deku(temp)] + card_type_raw: NumericString, + #[deku(ctx = "extract_enum_id_from_str::(&*card_type_raw.0, 255)")] pub card_type: CardPSType, pub national_id_type: NationalIDType, pub national_id: AlphaNumericString, // /!\ CE and not CA @@ -29,7 +32,19 @@ pub mod group_1_holder { // Fields #[deku_derive(DekuRead)] #[derive(Debug, PartialEq)] - pub struct CardPSType(pub NumericString); + #[deku(ctx = "id: u8", id = "id")] + pub enum CardPSType { + #[deku(id = 0)] + CPS, // Carte de Professionnel de Santé (CPS) + #[deku(id = 1)] + CPF, // Carte de Professionnel de Santé en Formation (CPF) + #[deku(id = 2)] + CPE, // Carte de Personnel / Directeur⋅ice d'Établissement de Santé (CDE/CPE) + #[deku(id = 3)] + CPA, // Carte de Personnel / Directeur⋅ice Autorisé⋅e (CDA/CPA) + #[deku(id = 4)] + CPM, // Carte de Personne Morale + } #[deku_derive(DekuRead)] #[derive(Debug, PartialEq)] @@ -45,6 +60,7 @@ pub mod group_1_holder { #[cfg(test)] mod tests { use deku::DekuContainerRead as _; + use group_1_holder::CardPSType; use crate::fsv_parsing::blocks::BlockHeader; @@ -133,7 +149,7 @@ mod tests { env_logger::init(); // Uncomment and run with RUST_LOG=trace for deku debugging let offset = 3*8; let (_rest, holder) = group_1_holder::Holder::from_bytes((data::BUFFER, offset)).unwrap(); - assert_eq!(holder.card_type.0.0, "0", "Card type"); + assert_eq!(holder.card_type, CardPSType::CPS, "Card type"); assert_eq!(holder.national_id_type.0.0, "8", "National ID type"); assert_eq!(holder.national_id.0, "99700593686", "National Id"); assert_eq!(holder.national_id_key.0, "6", "National ID Key"); -- 2.45.2 From f186fb20ce286cd2a1b4c01e948060fcfaadc65a Mon Sep 17 00:00:00 2001 From: Florian Briand Date: Mon, 7 Oct 2024 12:11:08 +0200 Subject: [PATCH 3/6] feat: Take the size into account for Block reading, to handle missing fields --- crates/fsv/src/fsv_parsing/blocks.rs | 29 ++++++---- crates/fsv/src/fsv_parsing/mod.rs | 87 ++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 10 deletions(-) diff --git a/crates/fsv/src/fsv_parsing/blocks.rs b/crates/fsv/src/fsv_parsing/blocks.rs index 46af3e8..6dd2372 100644 --- a/crates/fsv/src/fsv_parsing/blocks.rs +++ b/crates/fsv/src/fsv_parsing/blocks.rs @@ -1,6 +1,6 @@ use deku::deku_derive; -use super::{ groups, size_read }; +use super::{ groups, size_read, read_with_size }; #[derive(Debug, PartialEq)] #[deku_derive(DekuRead)] @@ -19,7 +19,7 @@ pub struct Data { /// The `DataBlock` are the main structures inside a `Data` struct pub struct DataBlock { pub header: BlockHeader, - #[deku(ctx = "header.group_id.0")] + #[deku(ctx = "header.group_id.0, header.data_size")] pub content: DataGroup, } @@ -32,7 +32,7 @@ pub struct BlockHeader { pub group_id: GroupId, #[deku(reader = "size_read(deku::reader)")] - pub data_size: u64, // This field is not really used, but we have to parse it to move the reader cursor + pub data_size: u64, } #[derive(Debug, PartialEq)] @@ -51,19 +51,29 @@ pub struct GroupId( /// correct data structure, able to parse the data contained in #[derive(Debug, PartialEq)] #[deku_derive(DekuRead)] -#[deku(ctx = "group_id: u16", id = "group_id")] +#[deku(ctx = "group_id: u16, data_size: u64", id = "group_id")] #[allow(non_camel_case_types)] pub enum DataGroup { #[deku(id = 1)] - LireCartePS_Group1_Holder(groups::ssv_lire_carte_ps::group_1_holder::Holder), + LireCartePS_Group1_Holder( + #[deku(reader = "read_with_size(deku::reader, data_size as usize)")] + groups::ssv_lire_carte_ps::group_1_holder::Holder), #[deku(id = 60)] - LireConfig_Group60_ConfigHeader(groups::ssv_lire_config::group_60_header_config::ConfigHeader), + LireConfig_Group60_ConfigHeader( + #[deku(reader = "read_with_size(deku::reader, data_size as usize)")] + groups::ssv_lire_config::group_60_header_config::ConfigHeader), #[deku(id = 61)] - LireConfig_Group61_ReaderConfig(groups::ssv_lire_config::group_61_reader_config::ReaderConfig), + LireConfig_Group61_ReaderConfig( + #[deku(reader = "read_with_size(deku::reader, data_size as usize)")] + groups::ssv_lire_config::group_61_reader_config::ReaderConfig), #[deku(id = 64)] - LireConfig_Group64_SVComponentsConfig(groups::ssv_lire_config::group_64_sv_config::SVComponentsConfig), + LireConfig_Group64_SVComponentsConfig( + #[deku(reader = "read_with_size(deku::reader, data_size as usize)")] + groups::ssv_lire_config::group_64_sv_config::SVComponentsConfig), #[deku(id = 67)] - LireConfig_Group67_PCSCReaderConfig(groups::ssv_lire_config::group_67_pcsc_config::PCSCReaderConfig), + LireConfig_Group67_PCSCReaderConfig( + #[deku(reader = "read_with_size(deku::reader, data_size as usize)")] + groups::ssv_lire_config::group_67_pcsc_config::PCSCReaderConfig), } #[cfg(test)] @@ -221,5 +231,4 @@ mod tests { let (_rest, val) = deku_testing::DekuTestWithGroupId::from_bytes((buffer, 0)).unwrap(); assert_eq!(val.group_id.0, 1910, "EX2: ID"); } - } \ No newline at end of file diff --git a/crates/fsv/src/fsv_parsing/mod.rs b/crates/fsv/src/fsv_parsing/mod.rs index 26d749f..6ad7b91 100644 --- a/crates/fsv/src/fsv_parsing/mod.rs +++ b/crates/fsv/src/fsv_parsing/mod.rs @@ -33,6 +33,26 @@ fn size_read(reader: &mut Reader) -> Result Ok(size) } +/// Deku Reader taking an expected size into account +/// This function limit the reading to the size given in input +fn read_with_size( + reader: &mut Reader, + size: usize +) -> Result + where T: for<'a> DekuContainerRead<'a> +{ + let max_size = core::mem::size_of::(); + let mut buf = vec![0; max_size]; + let buf: &mut [u8] = &mut buf; + let ret = reader.read_bytes(size, buf)?; + let (_rest, block) = match ret { + ReaderRet::Bytes => { + T::from_bytes((buf, 0))? + }, + _ => return Err(DekuError::Parse("Unexpected result reading size bytes: got bits".into())), + }; + Ok(block) +} /// # Map bytes to a lossy string /// This function is used to map bytes to a string, ignoring invalid UTF-8 characters @@ -43,3 +63,70 @@ fn map_bytes_to_lossy_string(data: &[u8]) -> Result { let version: String = String::from_utf8_lossy(data).to_string(); Ok(version) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_read_with_size_reader() { + #[derive(Debug, PartialEq)] + #[deku_derive(DekuRead)] + struct Data { + #[deku(read_all)] + pub blocks: Vec, + } + #[derive(Debug, PartialEq)] + #[deku_derive(DekuRead)] + struct Block { + pub size: u8, + pub id: u8, + #[deku(ctx = "*id,*size-1")] // size-1 to remove the ID size + pub data: BlockType, + } + #[derive(Debug, PartialEq)] + #[deku_derive(DekuRead)] + #[deku(ctx = "id: u8, size: u8", id = "id")] + enum BlockType { + #[deku(id = 1)] + Block1( + #[deku(reader = "read_with_size(deku::reader, size as usize)")] + Block1 + ), + #[deku(id = 2)] + Block2( + #[deku(reader = "read_with_size(deku::reader, size as usize)")] + Block2 + ), + } + #[derive(Debug, PartialEq)] + #[deku_derive(DekuRead)] + struct Block1 { + pub field1_size: u8, + pub field1: u16, + pub field2_size: u8, + pub field2: u64, + } + #[derive(Debug, PartialEq)] + #[deku_derive(DekuRead)] + struct Block2; + + let buffer = &[ + // 1st Block, type 1 + 4, // Size: 4 + 1, // ID: 1 + 2, 0x12, 0x34, // Field 1, size 2 + // No Field 2 + // 2nd Block, type 1 + 6, // Size: Y + 1, // ID: 2 + 1, 0x56, // Field 1, size 1 (casted into u16) + 2, 0x78, 0x9A // Field 2, size 2 + ]; + + let (_rest, val) = Data::from_bytes((buffer, 0)).unwrap(); + assert_eq!(val.blocks.len(), 2); + assert_eq!(val.blocks[0].size, 4); + assert_eq!(val.blocks[1].size, 6); + } +} \ No newline at end of file -- 2.45.2 From 91d574e31e03c67881a22a3eb9d59c58391951ca Mon Sep 17 00:00:00 2001 From: Florian Briand Date: Mon, 7 Oct 2024 12:12:18 +0200 Subject: [PATCH 4/6] feat: Une enums for deserializable fields in CPS Holder group (01) --- .../fsv_parsing/groups/ssv_lire_carte_ps.rs | 139 +++++++++++++++++- 1 file changed, 131 insertions(+), 8 deletions(-) diff --git a/crates/fsv/src/fsv_parsing/groups/ssv_lire_carte_ps.rs b/crates/fsv/src/fsv_parsing/groups/ssv_lire_carte_ps.rs index 64677e1..fc6f22a 100644 --- a/crates/fsv/src/fsv_parsing/groups/ssv_lire_carte_ps.rs +++ b/crates/fsv/src/fsv_parsing/groups/ssv_lire_carte_ps.rs @@ -20,13 +20,22 @@ pub mod group_1_holder { card_type_raw: NumericString, #[deku(ctx = "extract_enum_id_from_str::(&*card_type_raw.0, 255)")] pub card_type: CardPSType, + #[deku(temp)] + national_id_type_raw: NumericString, + #[deku(ctx = "extract_enum_id_from_str::(&*national_id_type_raw.0, 255)")] pub national_id_type: NationalIDType, + // TODO: handle national_id depending on national_id_type pub national_id: AlphaNumericString, // /!\ CE and not CA pub national_id_key: AlphaNumericString, + #[deku(temp)] + civility_code_raw: NumericString, + #[deku(ctx = "extract_enum_id_from_str::(&*civility_code_raw.0, 255)")] pub civility_code: CivilityCode, pub holder_lastname: AlphaNumericString, // /!\ CE and not CA pub holder_firstname: AlphaNumericString, // /!\ CE and not CA - pub category_card: AlphaNumericString, + #[deku(temp)] + category_card_size: u8, + pub category_card: CategoryCard, } // Fields @@ -48,19 +57,133 @@ pub mod group_1_holder { #[deku_derive(DekuRead)] #[derive(Debug, PartialEq)] - pub struct NationalIDType(pub NumericString); + #[deku(ctx = "id: u8", id = "id")] + pub enum NationalIDType { + #[deku(id = 0)] + ADELI, // N° ADELI + #[deku(id = 1)] + ADELICabinet, // Id Cabinet ADELI + N° employé + #[deku(id = 2)] + DRASS, // N° DRASS + #[deku(id = 3)] + FINESS, // N° FINESS + N° employé + #[deku(id = 4)] + SIREN, // N° SIREN + N° employé + #[deku(id = 5)] + SIRET, // N° SIRET + N° employé + #[deku(id = 6)] + RPPSCabinet, // Id Cabinet RPPS + N° employé + #[deku(id = 8)] + RPPS, // N° RPPS + #[deku(id = 9)] + ADELIEtudiantMedecin, // N° Etudiant Médecin type ADELI sur 9 caractères (information transmise par l’ANS) + } #[deku_derive(DekuRead)] #[derive(Debug, PartialEq)] - pub struct CivilityCode(pub NumericString); + #[deku(ctx = "id: u8", id = "id")] + pub enum CivilityCode { + #[deku(id = 1)] + Adjudant, + #[deku(id = 2)] + Amiral, + #[deku(id = 3)] + Aspirant, + #[deku(id = 4)] + Aumonier, + #[deku(id = 5)] + Capitaine, + #[deku(id = 6)] + Cardinal, + #[deku(id = 7)] + Chanoine, + #[deku(id = 8)] + Colonel, + #[deku(id = 9)] + Commandant, + #[deku(id = 10)] + Commissaire, + #[deku(id = 11)] + Conseiller, + #[deku(id = 12)] + Directeur, + #[deku(id = 13)] + Docteur, + #[deku(id = 14)] + Douanier, + #[deku(id = 15)] + Epouxse, + #[deku(id = 16)] + Eveque, + #[deku(id = 17)] + General, + #[deku(id = 18)] + Gouverneur, + #[deku(id = 19)] + Ingenieur, + #[deku(id = 20)] + Inspecteur, + #[deku(id = 21)] + Lieutenant, + #[deku(id = 22)] + Madame, + #[deku(id = 23)] + Mademoiselle, + #[deku(id = 24)] + Maitre, + #[deku(id = 25)] + Marechal, + #[deku(id = 26)] + Medecin, + #[deku(id = 27)] + Mesdames, + #[deku(id = 28)] + Mesdemoiselles, + #[deku(id = 29)] + Messieurs, + #[deku(id = 30)] + Monseigneur, + #[deku(id = 31)] + Monsieur, + #[deku(id = 32)] + NotreDame, + #[deku(id = 33)] + Pasteur, + #[deku(id = 34)] + Prefet, + #[deku(id = 35)] + President, + #[deku(id = 36)] + Professeur, + #[deku(id = 37)] + Recteur, + #[deku(id = 38)] + Sergent, + #[deku(id = 39)] + SousPrefet, + #[deku(id = 40)] + Technicien, + #[deku(id = 41)] + Veuve, + } - + #[deku_derive(DekuRead)] + #[derive(Debug, PartialEq)] + #[deku(id_type="u8")] + pub enum CategoryCard { + #[deku(id = 74)] // T + Test, + #[deku(id = 72)] // R + Reelle, + #[deku(id_pat = "_")] + Unknown, + } } #[cfg(test)] mod tests { use deku::DekuContainerRead as _; - use group_1_holder::CardPSType; + use group_1_holder::{CardPSType, CategoryCard, CivilityCode, NationalIDType}; use crate::fsv_parsing::blocks::BlockHeader; @@ -150,12 +273,12 @@ mod tests { let offset = 3*8; let (_rest, holder) = group_1_holder::Holder::from_bytes((data::BUFFER, offset)).unwrap(); assert_eq!(holder.card_type, CardPSType::CPS, "Card type"); - assert_eq!(holder.national_id_type.0.0, "8", "National ID type"); + assert_eq!(holder.national_id_type, NationalIDType::RPPS, "National ID type"); assert_eq!(holder.national_id.0, "99700593686", "National Id"); assert_eq!(holder.national_id_key.0, "6", "National ID Key"); - assert_eq!(holder.civility_code.0.0, "31", "Civility Code"); + assert_eq!(holder.civility_code, CivilityCode::Monsieur, "Civility Code"); assert_eq!(holder.holder_lastname.0, "PHARMOFFICE RPPS0059368", "Holder Lastname"); assert_eq!(holder.holder_firstname.0, "GILBERT", "Holder Firstname"); - assert!(holder.category_card.0.is_empty(), "Category card"); + assert_eq!(holder.category_card, CategoryCard::Unknown, "Category card"); } } \ No newline at end of file -- 2.45.2 From c37b7f241d09c4a570e1c193920c3955363b5f25 Mon Sep 17 00:00:00 2001 From: Florian Briand Date: Mon, 7 Oct 2024 19:24:55 +0200 Subject: [PATCH 5/6] feat: implement group2, CPS situation for SSV_LireCartePS --- crates/fsv/src/fsv_parsing/blocks.rs | 4 + crates/fsv/src/fsv_parsing/groups/mod.rs | 33 +- .../fsv_parsing/groups/ssv_lire_carte_ps.rs | 479 +++++++++++++++++- crates/fsv/src/ssv/mod.rs | 27 +- 4 files changed, 522 insertions(+), 21 deletions(-) diff --git a/crates/fsv/src/fsv_parsing/blocks.rs b/crates/fsv/src/fsv_parsing/blocks.rs index 6dd2372..9676854 100644 --- a/crates/fsv/src/fsv_parsing/blocks.rs +++ b/crates/fsv/src/fsv_parsing/blocks.rs @@ -58,6 +58,10 @@ pub enum DataGroup { LireCartePS_Group1_Holder( #[deku(reader = "read_with_size(deku::reader, data_size as usize)")] groups::ssv_lire_carte_ps::group_1_holder::Holder), + #[deku(id = 2)] + LireCartePS_Group2_Situation( + #[deku(reader = "read_with_size(deku::reader, data_size as usize)")] + groups::ssv_lire_carte_ps::group_2_situation::Situation), #[deku(id = 60)] LireConfig_Group60_ConfigHeader( #[deku(reader = "read_with_size(deku::reader, data_size as usize)")] diff --git a/crates/fsv/src/fsv_parsing/groups/mod.rs b/crates/fsv/src/fsv_parsing/groups/mod.rs index 132acea..3779bf0 100644 --- a/crates/fsv/src/fsv_parsing/groups/mod.rs +++ b/crates/fsv/src/fsv_parsing/groups/mod.rs @@ -21,6 +21,16 @@ where .map_err(|e| DekuError::Parse(e.to_string().into())) } +/// # Extract raw bytes from a DataField, through a Vec +/// This function is used as deku map function to extract raw bytes +/// from a DataField +fn map_raw_from_data_field(data_field: DataField) -> Result +where + T: From>, +{ + Ok(data_field.data.into()) +} + /// # Extract an enum id from a string /// Deku enums only supports numbers as id, and we usually extract strings /// from data fields. This function is used as a context function to convert @@ -47,11 +57,11 @@ struct DataField { #[deku_derive(DekuRead)] #[derive(Debug, Clone, PartialEq)] -/// # Numeric string +/// # Data field: Numeric string (x CN) /// TODO: check if all the characters are numeric pub struct NumericString( #[deku(map = "map_from_data_field")] - String + pub String ); impl From<&str> for NumericString { fn from(s: &str) -> Self { @@ -61,9 +71,10 @@ impl From<&str> for NumericString { #[deku_derive(DekuRead)] #[derive(Debug, Clone, PartialEq)] +/// # Data field: Alphanumeric string (x CA/CE) pub struct AlphaNumericString( #[deku(map = "map_from_data_field")] - String + pub String ); impl From<&str> for AlphaNumericString { fn from(s: &str) -> Self { @@ -71,6 +82,22 @@ impl From<&str> for AlphaNumericString { } } +#[deku_derive(DekuRead)] +#[derive(Debug, Clone, PartialEq)] +/// # Data field: Raw bytes (x CB) +pub struct RawBytes( + #[deku(map = "map_raw_from_data_field")] + pub Vec +); + +#[deku_derive(DekuRead)] +#[derive(Debug, Clone, PartialEq)] +/// # Data field: Raw byte (1 CB) +pub struct RawByte( + #[deku(map = "|x: DataField| -> Result { Ok(x.data[0]) }")] + pub u8 +); + #[deku_derive(DekuRead)] #[derive(Debug, Clone, PartialEq)] #[deku(endian = "big")] diff --git a/crates/fsv/src/fsv_parsing/groups/ssv_lire_carte_ps.rs b/crates/fsv/src/fsv_parsing/groups/ssv_lire_carte_ps.rs index fc6f22a..6fbe567 100644 --- a/crates/fsv/src/fsv_parsing/groups/ssv_lire_carte_ps.rs +++ b/crates/fsv/src/fsv_parsing/groups/ssv_lire_carte_ps.rs @@ -1,15 +1,14 @@ //! # Structures de parsing des données de la fonction SSV_LireCartePS +#![allow(clippy::explicit_auto_deref)] // False positive on ctx attributes when using extract_enum_id_from_str use deku::deku_derive; -use crate::fsv_parsing::groups::NumericString; +use crate::fsv_parsing::groups::{ extract_enum_id_from_str, AlphaNumericString, NumericString, RawByte }; /// # Titulaire /// 1 occurence pub mod group_1_holder { - use crate::fsv_parsing::groups::{ extract_enum_id_from_str, AlphaNumericString }; - use super::*; /// Groupe 1 - Titulaire @@ -17,24 +16,24 @@ pub mod group_1_holder { #[derive(Debug, PartialEq)] pub struct Holder { #[deku(temp)] - card_type_raw: NumericString, + card_type_raw: NumericString, // Champ 1 : Type de carte PS (2 CN) #[deku(ctx = "extract_enum_id_from_str::(&*card_type_raw.0, 255)")] pub card_type: CardPSType, #[deku(temp)] - national_id_type_raw: NumericString, + national_id_type_raw: NumericString, // Champ 2 : Type d’identification nationale (1 CN) #[deku(ctx = "extract_enum_id_from_str::(&*national_id_type_raw.0, 255)")] pub national_id_type: NationalIDType, // TODO: handle national_id depending on national_id_type - pub national_id: AlphaNumericString, // /!\ CE and not CA - pub national_id_key: AlphaNumericString, + pub national_id: AlphaNumericString, // /!\ CE and not CA - Champ 3 : N° d’identification nationale (8-30 CE) + pub national_id_key: AlphaNumericString, // Champ 4 : Clé du N° d’identification nationale (1 CN) #[deku(temp)] - civility_code_raw: NumericString, + civility_code_raw: NumericString, // Champ 5 : Code civilité (2 CN) #[deku(ctx = "extract_enum_id_from_str::(&*civility_code_raw.0, 255)")] pub civility_code: CivilityCode, - pub holder_lastname: AlphaNumericString, // /!\ CE and not CA - pub holder_firstname: AlphaNumericString, // /!\ CE and not CA + pub holder_lastname: AlphaNumericString, // /!\ CE and not CA - Champ 6 : Nom du PS (27 CE) + pub holder_firstname: AlphaNumericString, // /!\ CE and not CA - Champ 7 : Prénom du PS (27 CE) #[deku(temp)] - category_card_size: u8, + category_card_size: u8, // Champ 8 : Catégorie Carte (1 CA) pub category_card: CategoryCard, } @@ -180,10 +179,432 @@ pub mod group_1_holder { } } +/// # Situation +/// 1-16 occurences +pub mod group_2_situation { + + use super::*; + + /// Groupe 2 - Situation du PS + #[deku_derive(DekuRead)] + #[derive(Debug, PartialEq)] + pub struct Situation { + pub id: RawByte, // Champ 1 : N° logique de la situation de facturation du PS (1 CB) + #[deku(temp)] + practice_mode_raw: NumericString, // Champ 2 : Mode d’exercice (2 CN) + #[deku(ctx = "extract_enum_id_from_str::(&*practice_mode_raw.0, 255)")] + pub practice_mode: PracticeMode, + // #[deku(temp)] + pub practice_status_raw: NumericString, // Champ 3 : Statut d’exercice (3 CN) + // #[deku(ctx = "extract_enum_id_from_str::(&*practice_status_raw.0, 255)")] + // pub practice_status: PracticeStatus, + #[deku(temp)] + activity_sector_raw: NumericString, // Champ 4 : Secteur d’activité (3 CN) + #[deku(ctx = "extract_enum_id_from_str::(&*activity_sector_raw.0, 255)")] + pub activity_sector: ActivitySector, + #[deku(temp)] + structure_id_type_raw: NumericString, // Champ 5 : Type d’identification structure (1 CN) + #[deku(ctx = "extract_enum_id_from_str::(&*structure_id_type_raw.0, 255)")] + pub structure_id_type: StructureIDType, + pub structure_id: AlphaNumericString, // Champ 6 : N° d’identification structure (14 CA) + pub structure_id_key: NumericString, // Champ 7 : Clé du n° d’identification structure (1 CN) + pub structure_name: AlphaNumericString, // Champ 8 : Raison sociale structure (40 CE) + pub ps_billing_number: NumericString, // Champ 9 : N° d’identification de facturation du PS (8 CN) + pub ps_billing_number_key: NumericString, // Champ 10 : Clé du n° d’identification de facturation du PS (1 CN) + pub ps_replacement_number: AlphaNumericString, // Champ 11 : N° d’identification du PS remplaçant (30 CA) -- TODO OPTIONNEL + pub ps_replacement_number_key: NumericString, // Champ 12 : Clé du n° d’identification du PS remplaçant (1 CN) -- TODO OPTIONNEL + #[deku(temp)] + convention_code_raw: NumericString, // Champ 13 : Code conventionnel (1 CN) + #[deku(ctx = "extract_enum_id_from_str::(&*convention_code_raw.0, 255)")] + pub convention_code: ConventionCode, + #[deku(temp)] + specialty_code_raw: NumericString, // Champ 14 : Code spécialité (2 CN) + #[deku(ctx = "extract_enum_id_from_str::(&*specialty_code_raw.0, 255)")] + pub specialty_code: SpecialtyCode, + // #[deku(temp)] + pub rate_zone_code_raw: NumericString, // Champ 15 : Code zone tarifaire (2 CN) + // #[deku(ctx = "extract_enum_id_from_str::(&*rate_zone_code_raw.0, 255)")] + // pub rate_zone_code: RateZoneCode, // CF p53-55 - Attribution complexe, dépendant du practice_status, du specialty_code et du convention_code + #[deku(temp)] + ik_zone_code_raw: NumericString, // Champ 16 : Code zone IK - Indemnité kilométrique (2 CN) + #[deku(ctx = "extract_enum_id_from_str::(&*ik_zone_code_raw.0, 255)")] + pub ik_zone_code: IkZoneCode, + #[deku(temp)] + approval_code_1_raw: NumericString, // Champ 17 : Code agrément 1 (1 CN) + #[deku(ctx = "extract_enum_id_from_str::(&*approval_code_1_raw.0, 255)")] + pub approval_code_1: ApprovalCode, + pub approval_code_2_raw: NumericString, // Champ 18 : Code agrément 2 (1 CN) - Non utilisé pour le moment + pub approval_code_3_raw: NumericString, // Champ 19 : Code agrément 3 (1 CN) - Non utilisé pour le moment + pub invoice_signature_permission: NumericString, // Champ 20 : Habilitation à signer une facture (1 CN) + pub lot_signature_permission: NumericString, // Champ 21 : Habilitation à signer un lot (1 CN) + } + + // Fields + #[deku_derive(DekuRead)] + #[derive(Debug, PartialEq)] + #[deku(ctx = "id: u8", id = "id")] + /// Mode d'exercice + pub enum PracticeMode { + #[deku(id = 0)] + Liberal, // Libéral, exploitant, commerçant + #[deku(id = 1)] + Salarie, + #[deku(id = 4)] + Remplacant, + #[deku(id = 7)] + Benevole, + } + // + // #[deku_derive(DekuRead)] + // #[derive(Debug, PartialEq)] + // #[deku(ctx = "id: u8", id = "id")] + // /// Statut d'exercice + // pub enum PracticeStatus { + // // Cf. TAB-Statuts géré par l’ANS + // } + + #[deku_derive(DekuRead)] + #[derive(Debug, PartialEq)] + #[deku(ctx = "id: u8", id = "id")] + /// Secteur d'activité + pub enum ActivitySector { + #[deku(id = 10)] + EtablissementPublicDeSante, // Etablissement Public de santé + #[deku(id = 11)] + HopitauxMilitaires, // Hôpitaux Militaires + #[deku(id = 16)] + EtablissementPrivePSPH, // Etablissement Privé PSPH + #[deku(id = 17)] + EtablissementPriveNonPSPH, // Etablissement Privé Non PSPH + #[deku(id = 25)] + DispensaireDeSoins, // Dispensaire de soins + #[deku(id = 26)] + AutresStructuresDeSoinsArmee, // Autres structures de soins relevant du Service de santé des armées + #[deku(id = 31)] + CabinetIndividuel, // Cabinet individuel + #[deku(id = 32)] + CabinetDeGroupe, // Cabinet de Groupe + #[deku(id = 33)] + ExerciceEnSociete, // Exercice en Société + #[deku(id = 34)] + SecteurPrivePHTempsPlein, // Secteur privé PH temps plein + #[deku(id = 35)] + TransportSanitaire, // Transport sanitaire + #[deku(id = 37)] + EntrepriseDInterim, // Entreprise d'intérim + #[deku(id = 41)] + EtablissementDeSoinsEtPrevention, // Etablissement de Soins et Prévention + #[deku(id = 42)] + PreventionEtSoinsEnEntreprise, // Prévention. Et Soins en Entreprise + #[deku(id = 43)] + SanteScolaireEtUniversitaire, // Santé scolaire & universitaire + #[deku(id = 44)] + RecrutementEtGestionRH, // Recrutement & gestion RH + #[deku(id = 45)] + PMIPlanificationFamiliale, // P.M.I. Planification familiale + #[deku(id = 51)] + EtablissementPourHandicapes, // Etablissement pour Handicapés + #[deku(id = 52)] + ComMarketingConsultingMedia, // Com/Marketing/Consulting/Media + #[deku(id = 53)] + EtablissementPersonnesAgees, // Etablissement Personnes Agées + #[deku(id = 54)] + EtablissementAideALaFamille, // Etablissement Aide à la famille + #[deku(id = 55)] + EtablissementDEnseignement, // Etablissement d'enseignement + #[deku(id = 56)] + EtablissementsDeProtectionDeLEnfance, // Etablissements de protection de l'enfance + #[deku(id = 57)] + EtablissementsDHebergementEtDeReadaptation, // Etablissements d'hébergement et de réadaptation + #[deku(id = 58)] + Recherche, // Recherche + #[deku(id = 61)] + AssurancePrivee, // Assurance Privée + #[deku(id = 62)] + OrganismeDeSecuriteSociale, // Organisme de Sécurité Sociale + #[deku(id = 65)] + MinistereEtServicesDeconcentres, // Ministère & Serv. Déconcentrés + #[deku(id = 66)] + CollectivitesTerritoriales, // Collectivités Territoriales + #[deku(id = 68)] + AssoEtOrgaHumanitaire, // Asso et orga humanitaire + #[deku(id = 71)] + LABM, // LABM + #[deku(id = 75)] + AutreEtablissementSanitaire, // Autre établissement Sanitaire + #[deku(id = 81)] + ProdEtComGrosBienMed, // Prod. & Com. Gros Bien Med. + #[deku(id = 85)] + CommDetailDeBiensMedicaux, // Comm. Détail de biens médicaux + #[deku(id = 86)] + PharmacieDOfficine, // Pharmacie d'officine + #[deku(id = 87)] + CentreDeDialyse, // Centre de dialyse + #[deku(id = 88)] + ParaPharmacie, // Para-pharmacie + #[deku(id = 91)] + AutreSecteurDActivite, // Autre secteur d'activité + #[deku(id = 92)] + SecteurNonDefini, // Secteur non défini + #[deku(id = 93)] + CentreAntiCancer, // Centre anti-cancer + #[deku(id = 94)] + CentreDeTransfusionSanguine, // Centre de transfusion sanguine + #[deku(id = 95)] + ChaineDuMedicament, // Répart. Distrib. Fab. Exploit. Import Médicaments + #[deku(id = 96)] + IncendiesEtSecours, // Incendies et secours + #[deku(id = 97)] + EntreprisesIndustriellesNonPharma, // Entreprises industrielles et tertiaires hors industries pharmaceutiques + #[deku(id = 98)] + EntiteDUnTOM, // Entité d'un TOM + #[deku(id = 99)] + ChaineDuDispositifMedical, // Fab. Exploit. Import. Médicaments et Dispositifs Médicaux + } + + #[deku_derive(DekuRead)] + #[derive(Debug, PartialEq)] + #[deku(ctx = "id: u8", id = "id")] + /// Type d'identification structure + pub enum StructureIDType { + #[deku(id = 0)] + ADELICabinet, // Id Cabinet ADELI + #[deku(id = 1)] + FINESS, // N° FINESS + #[deku(id = 2)] + SIREN, // N° SIREN + #[deku(id = 3)] + SIRET, // N° SIRET + #[deku(id = 4)] + RPPSCabinet, // Id Cabinet RPPS + } + + #[deku_derive(DekuRead)] + #[derive(Debug, PartialEq)] + #[deku(ctx = "id: u8", id = "id")] + /// Code conventionnel + /// Dictionnaire des données FSV, p37 + pub enum ConventionCode { + #[deku(id = 0)] + NonConventionne, + #[deku(id = 1)] + Conventionne, + #[deku(id = 2)] + ConventionneAvecDepassement, + #[deku(id = 3)] + ConventionneAvecHonorairesLibres, + } + + #[deku_derive(DekuRead)] + #[derive(Debug, PartialEq)] + #[deku(ctx = "id: u8", id = "id")] + /// Code spécialité + /// Dictionnaire des données FSV, p43 + pub enum SpecialtyCode { + #[deku(id = 1)] + MedecineGenerale, // Médecine générale + #[deku(id = 2)] + AnesthesieReanimation, // Anesthésie-Réanimation + #[deku(id = 3)] + Cardiologie, // Cardiologie + #[deku(id = 4)] + ChirurgieGenerale, // Chirurgie Générale + #[deku(id = 5)] + DermatoVenerologie, // Dermatologie et Vénérologie + #[deku(id = 6)] + Radiologie, // Radiologie + #[deku(id = 7)] + GynecologieObstetrique, // Gynécologie obstétrique + #[deku(id = 8)] + GastroEnterologieHepatologie, // Gastro-Entérologie et Hépatologie + #[deku(id = 9)] + MedecineInterne, // Médecine interne + #[deku(id = 10)] + NeuroChirurgie, // Neuro-Chirurgie + #[deku(id = 11)] + OtoRhinoLaryngologie, // Oto-Rhino-Laryngologie + #[deku(id = 12)] + Pediatrie, // Pédiatrie + #[deku(id = 13)] + Pneumologie, // Pneumologie + #[deku(id = 14)] + Rhumatologie, // Rhumatologie + #[deku(id = 15)] + Ophtalmologie, // Ophtalmologie + #[deku(id = 16)] + ChirurgieUrologique, // Chirurgie urologique + #[deku(id = 17)] + NeuroPsychiatrie, // Neuro-Psychiatrie + #[deku(id = 18)] + Stomatologie, // Stomatologie + #[deku(id = 19)] + ChirurgienDentiste, // Chirurgien dentiste + #[deku(id = 20)] + ReanimationMedicale, // Réanimation médicale + #[deku(id = 21)] + SageFemme, // Sage-femme + #[deku(id = 22)] + SpécialisteEnMGDiplome, // Spécialiste en médecine générale avec diplôme + #[deku(id = 23)] + SpécialisteEnMGReconnu, // Spécialiste en médecine générale reconnu par l’Ordre + #[deku(id = 24)] + Infirmier, // Infirmier + #[deku(id = 25)] + Psychologue, // Psychologue + #[deku(id = 26)] + MasseurKinesitherapeute, // Masseur Kinésithérapeute + #[deku(id = 27)] + PedicurePodologue, // Pédicure Podologue + #[deku(id = 28)] + Orthophoniste, // Orthophoniste + #[deku(id = 29)] + Orthoptiste, // Orthoptiste + #[deku(id = 30)] + LaboAnalysesMedicales, // Laboratoire d'analyses médicales + #[deku(id = 31)] + ReeducationReadaptationFonctionnelle, // Rééducation Réadaptation fonctionnelle + #[deku(id = 32)] + Neurologie, // Neurologie + #[deku(id = 33)] + Psychiatrie, // Psychiatrie + #[deku(id = 34)] + Geriatrie, // Gériatrie + #[deku(id = 35)] + Nephrologie, // Néphrologie + #[deku(id = 36)] + ChirurgieDentaireSpecialiteODF, // Chirurgie Dentaire spécialité O.D.F + #[deku(id = 37)] + AnatomoCytoPathologie, // Anatomo-Cyto-Pathologie + #[deku(id = 38)] + MedecinBiologiste, // Médecin biologiste + #[deku(id = 39)] + LaboPolyvalent, // Laboratoire polyvalent + #[deku(id = 40)] + LaboAnatomoCytoPathologique, // Laboratoire d’anatomo-cyto-pathologique + #[deku(id = 41)] + ChirurgieOrthopediqueTraumatologie, // Chirurgie Orthopédique et Traumatologie + #[deku(id = 42)] + EndocrinologieMetabolisme, // Endocrinologie et Métabolisme + #[deku(id = 43)] + ChirurgieInfantile, // Chirurgie infantile + #[deku(id = 44)] + ChirurgieMaxilloFaciale, // Chirurgie maxillo-faciale + #[deku(id = 45)] + ChirurgieMaxilloFacialeStomatologie, // Chirurgie maxillo-faciale et stomatologie + #[deku(id = 46)] + ChirurgiePlastiqueReconstructriceEsthetique, // Chirurgie plastique reconstructrice et esthétique + #[deku(id = 47)] + ChirurgieThoraciqueCardioVasculaire, // Chirurgie thoracique et cardio-vasculaire + #[deku(id = 48)] + ChirurgieVasculaire, // Chirurgie vasculaire + #[deku(id = 49)] + ChirurgieVisceraleDigestive, // Chirurgie viscérale et digestive + #[deku(id = 50)] + PharmacieDOfficine, // Pharmacie d’officine + #[deku(id = 51)] + PharmacieMutualiste, // Pharmacie Mutualiste + #[deku(id = 53)] + ChirurgienDentisteSpecialiteCO, // Chirurgien dentiste spécialité C.O. + #[deku(id = 54)] + ChirurgienDentisteSpecialiteMBD, // Chirurgien dentiste spécialité M.B.D. + #[deku(id = 60)] + PrestataireDeTypeSociete, // Prestataire de type société + #[deku(id = 61)] + PrestataireArtisan, // Prestataire artisan + #[deku(id = 62)] + PrestataireDeTypeAssociation, // Prestataire de type association + #[deku(id = 63)] + Orthesiste, // Orthésiste + #[deku(id = 64)] + Opticien, // Opticien + #[deku(id = 65)] + Audioprothesiste, // Audioprothésiste + #[deku(id = 66)] + EpithesisteOculariste, // Épithésiste Oculariste + #[deku(id = 67)] + PodoOrthesiste, // Podo-orthésiste + #[deku(id = 68)] + Orthoprothesiste, // Orthoprothésiste + #[deku(id = 69)] + ChirurgieOrale, // Chirurgie orale + #[deku(id = 70)] + GynecologieMedicale, // Gynécologie médicale + #[deku(id = 71)] + Hematologie, // Hématologie + #[deku(id = 72)] + MedecineNucleaire, // Médecine nucléaire + #[deku(id = 73)] + OncologieMedicale, // Oncologie médicale + #[deku(id = 74)] + OncologieRadiotherapique, // Oncologie radiothérapique + #[deku(id = 75)] + PsychiatrieEnfantAdolescent, // Psychiatrie de l’enfant et de l’adolescent + #[deku(id = 76)] + Radiotherapie, // Radiothérapie + #[deku(id = 77)] + Obstetrique, // Obstétrique + #[deku(id = 78)] + GenetiqueMedicale, // Génétique médicale + #[deku(id = 79)] + ObstetriqueGynecologieMedicale, // Obstétrique et Gynécologie médicale + #[deku(id = 80)] + SantePubliqueMedecineSociale, // Santé publique et médecine sociale + #[deku(id = 81)] + MaladiesInfectieusesTropicales, // Médecine des Maladies infectieuses et tropicales + #[deku(id = 82)] + MedecineLegaleExpertisesMedicales, // Médecine légale et expertises médicales + #[deku(id = 83)] + MedecineDurgence, // Médecine d’urgence + #[deku(id = 84)] + MedecineVasculaire, // Médecine vasculaire + #[deku(id = 85)] + Allergologie, // Allergologie + #[deku(id = 86)] + IPA, // Infirmier exerçant en Pratiques Avancées (IPA) + } + + #[deku_derive(DekuRead)] + #[derive(Debug, PartialEq)] + #[deku(ctx = "id: u8", id = "id")] + /// Code zone IK - Indemnité kilométrique + /// Dictionnaire des données FSV, p44 + pub enum IkZoneCode { + #[deku(id = 0)] + PasDIndemniteKilometrique, // Pas d'indemnité kilométrique + #[deku(id = 1)] + IndemniteKilometriquePlaine, // Indemnité kilométrique plaine + #[deku(id = 2)] + IndemniteKilometriqueMontagne, // Indemnité kilométrique montagne + } + + #[deku_derive(DekuRead)] + #[derive(Debug, PartialEq)] + #[deku(ctx = "id: u8", id = "id")] + /// Code agrément + /// Dictionnaire des données FSV, p44 + pub enum ApprovalCode { + #[deku(id = 0)] + PasAgrementRadio, // Pas d'agrément radio + #[deku(id = 1)] + AgrementDOuDDASS, // Agrément D ou DDASS + #[deku(id = 2)] + AgrementABCEF, // Agrément A, B, C, E, F + #[deku(id = 3)] + AgrementGHJ, // Agrément G, H, J + #[deku(id = 4)] + AgrementK, // Agrément K + #[deku(id = 5)] + AgrementL, // Agrément L + #[deku(id = 6)] + AgrementM, // Agrément M + } +} + #[cfg(test)] mod tests { use deku::DekuContainerRead as _; use group_1_holder::{CardPSType, CategoryCard, CivilityCode, NationalIDType}; + use group_2_situation::{ActivitySector, ApprovalCode, ConventionCode, IkZoneCode, PracticeMode, SpecialtyCode, StructureIDType}; use crate::fsv_parsing::blocks::BlockHeader; @@ -223,7 +644,7 @@ mod tests { 1, // Type d'identification structure, 1 CN 49, 9, // N° d'identification structure, 14 CA - 48, 66, 48, 50, 52, 54, 50, 56, 54, + 48, 66, 48, 50, 52, 54, 50, 56, 54, // 0B0246286 1, // Clé du N° d'identification structure, 1 CN 54, 34, // Raison sociale structure, 40 CE @@ -232,7 +653,7 @@ mod tests { 32, 82, 79, 85, 84, 73, 69, 82, 69, 50, 52, 54, 50, 56, 8, // N° d'identification de facturation du PS, 8 CN - 48, 48, 50, 48, 57, 51, 54, 56, + 48, 48, 50, 48, 57, 51, 54, 56, // 00209368 1, // Clé du N° d'identification de facturation du PS, 1 CN 48, 0, // N° d'identification du PS remplaçant, 30 CA @@ -255,7 +676,7 @@ mod tests { 1, // Habilitation à signer une Facture // 1 CN 49, 1, // Habilitation à signer un lot // 1 CN - 49 + 49, ]; } @@ -269,7 +690,7 @@ mod tests { #[test] fn test_group_1_holder() { - env_logger::init(); // Uncomment and run with RUST_LOG=trace for deku debugging + // env_logger::init(); // Uncomment and run with RUST_LOG=trace for deku debugging let offset = 3*8; let (_rest, holder) = group_1_holder::Holder::from_bytes((data::BUFFER, offset)).unwrap(); assert_eq!(holder.card_type, CardPSType::CPS, "Card type"); @@ -281,4 +702,32 @@ mod tests { assert_eq!(holder.holder_firstname.0, "GILBERT", "Holder Firstname"); assert_eq!(holder.category_card, CategoryCard::Unknown, "Category card"); } + + #[test] + fn test_group_2_situation() { + // env_logger::init(); // Uncomment and run with RUST_LOG=trace for deku debugging + let offset = 3*8 + 53*8 + 3*8; + let (_rest, situation) = group_2_situation::Situation::from_bytes((data::BUFFER, offset)).unwrap(); + assert_eq!(situation.id.0, 1, "ID"); + assert_eq!(situation.practice_mode, PracticeMode::Liberal, "Practice mode"); + assert_eq!(situation.practice_status_raw.0, "1", "Practice status raw"); + assert_eq!(situation.activity_sector, ActivitySector::PharmacieDOfficine, "Activity sector"); + assert_eq!(situation.structure_id_type, StructureIDType::FINESS, "Structure ID type"); + assert_eq!(situation.structure_id.0, "0B0246286", "Structure ID"); + assert_eq!(situation.structure_id_key.0, "6", "Structure ID key"); + assert_eq!(situation.structure_name.0, "PHARMACIE DE LA GARE ROUTIERE24628", "Structure name"); + assert_eq!(situation.ps_billing_number.0, "00209368", "PS billing number"); + assert_eq!(situation.ps_billing_number_key.0, "0", "PS billing number key"); + assert_eq!(situation.ps_replacement_number.0, "", "PS replacement number"); + assert_eq!(situation.ps_replacement_number_key.0, "0", "PS replacement number key"); + assert_eq!(situation.convention_code, ConventionCode::Conventionne, "Convention code"); + assert_eq!(situation.specialty_code, SpecialtyCode::PharmacieDOfficine, "Specialty code"); + assert_eq!(situation.rate_zone_code_raw.0, "10", "Rate zone code raw"); + assert_eq!(situation.ik_zone_code, IkZoneCode::PasDIndemniteKilometrique, "IK zone code"); + assert_eq!(situation.approval_code_1, ApprovalCode::PasAgrementRadio, "Approval code 1"); + assert_eq!(situation.approval_code_2_raw.0, "0", "Approval code 2 raw"); + assert_eq!(situation.approval_code_3_raw.0, "0", "Approval code 3 raw"); + assert_eq!(situation.invoice_signature_permission.0, "1", "Invoice signature permission"); + assert_eq!(situation.lot_signature_permission.0, "1", "Lot signature permission"); + } } \ No newline at end of file diff --git a/crates/fsv/src/ssv/mod.rs b/crates/fsv/src/ssv/mod.rs index c087647..a8f4385 100644 --- a/crates/fsv/src/ssv/mod.rs +++ b/crates/fsv/src/ssv/mod.rs @@ -158,7 +158,7 @@ mod tests { use utils::config::load_config; use anyhow::{bail, Result}; - use crate::fsv_parsing::blocks::DataGroup; + use crate::fsv_parsing::{blocks::DataGroup, groups::ssv_lire_carte_ps::{group_1_holder::CardPSType, group_2_situation::{PracticeMode, SpecialtyCode}}}; use super::*; @@ -184,11 +184,32 @@ mod tests { } #[test] - #[ignore="WARNING: Read the card with PIN 1234 - Risk of blocking the card"] + #[ignore=" + WARNING: Read the card with PIN 1234 - Risk of blocking the card + WARNING: This test will only work with GILBERT's PHARMOFFICE card (titulaire kit pharmacie) + "] fn test_read_professional_card_good_pin() -> Result<()> { let lib = setup::init()?; let pin_code = "1234"; - lib.read_professional_card(pin_code)?; + let cps_blocks = lib.read_professional_card(pin_code)?; + // Check the first group is the holder group + let holder_group = cps_blocks.blocks.first().unwrap(); + assert_eq!(holder_group.header.group_id.0, 1); + let holder_content = match &holder_group.content { + DataGroup::LireCartePS_Group1_Holder(content) => { content }, + _ => bail!("Wrong group type"), + }; + assert_eq!(holder_content.card_type, CardPSType::CPS, "Card type"); + assert_eq!(holder_content.holder_firstname.0, "GILBERT", "Holder firstname"); + // Check the second group is a situation group + let situation_group = cps_blocks.blocks.get(1).unwrap(); + assert_eq!(situation_group.header.group_id.0, 2); + let situation_content = match &situation_group.content { + DataGroup::LireCartePS_Group2_Situation(content) => { content }, + _ => bail!("Wrong group type"), + }; + assert_eq!(situation_content.practice_mode, PracticeMode::Liberal, "Practice mode"); + assert_eq!(situation_content.specialty_code, SpecialtyCode::PharmacieDOfficine, "Specialty code"); Ok(()) } -- 2.45.2 From 0f53a99e56fc3c10722e2028fb0b4e99523f31f3 Mon Sep 17 00:00:00 2001 From: Florian Briand Date: Mon, 7 Oct 2024 20:57:49 +0200 Subject: [PATCH 6/6] feat: add integration tests with snapshot for some CPS cards in kits --- Cargo.lock | 25 +++ README.md | 14 ++ crates/fsv/Cargo.toml | 8 + crates/fsv/src/ssv/mod.rs | 2 +- .../test_ssv__read_cps_default_kit_doc.snap | 97 ++++++++++ ...est_ssv__read_cps_pharma_kit_adjointe.snap | 97 ++++++++++ ...t_ssv__read_cps_pharma_kit_employe_aa.snap | 97 ++++++++++ ...t_ssv__read_cps_pharma_kit_infirmiere.snap | 97 ++++++++++ ...st_ssv__read_cps_pharma_kit_titulaire.snap | 97 ++++++++++ crates/fsv/tests/test_ssv.rs | 174 ++++++++++++++++++ 10 files changed, 707 insertions(+), 1 deletion(-) create mode 100644 crates/fsv/tests/snapshots/test_ssv__read_cps_default_kit_doc.snap create mode 100644 crates/fsv/tests/snapshots/test_ssv__read_cps_pharma_kit_adjointe.snap create mode 100644 crates/fsv/tests/snapshots/test_ssv__read_cps_pharma_kit_employe_aa.snap create mode 100644 crates/fsv/tests/snapshots/test_ssv__read_cps_pharma_kit_infirmiere.snap create mode 100644 crates/fsv/tests/snapshots/test_ssv__read_cps_pharma_kit_titulaire.snap create mode 100644 crates/fsv/tests/test_ssv.rs diff --git a/Cargo.lock b/Cargo.lock index 6813700..41f1f68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2020,6 +2020,7 @@ dependencies = [ "deku", "env_logger", "fsv-sys", + "insta", "libc", "log", "num_enum", @@ -2843,6 +2844,18 @@ dependencies = [ "libc", ] +[[package]] +name = "insta" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6593a41c7a73841868772495db7dc1e8ecab43bb5c0b6da2059246c4b506ab60" +dependencies = [ + "console", + "lazy_static", + "linked-hash-map", + "similar", +] + [[package]] name = "instant" version = "0.1.13" @@ -3137,6 +3150,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -5384,6 +5403,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +[[package]] +name = "similar" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" + [[package]] name = "siphasher" version = "0.3.11" diff --git a/README.md b/README.md index fe83c6a..d607622 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,20 @@ cargo tauri dev --no-watch > - le `backend` n'est rechargé que si des modifications sont détectées dans le dossier précisé par `-w crates/backend` > - le rechargement du `desktop` est désactivé par l'option `--no-watch` ; en effet, le rechargement du `frontend` est déjà pris en charge par `bun` et ne nécessite pas de rechargement du `desktop` +## Tests + +Les tests unitaires et d'intégration utilisants des cartes CPS ou Vitale réelles sont désactivés par défaut et doivent être +[explicitement activés](https://doc.rust-lang.org/book/ch11-02-running-tests.html#ignoring-some-tests-unless-specifically-requested) pour s'exécuter. + +### Snapshots + +Pour certains modules, des tests d'intégration s'appuient sur la crate [`insta`](https://insta.rs) pour générer et comparer des snapshots. +Pour faciliter la gestion de ces tests, il est recommandé d'installer `cargo-insta` : + +```bash +cargo install cargo-insta +``` + ## Build Pour packager le client `desktop`, il est nécessaire de faire appel à la CLI Tauri, qui se charge de gérer le build du `frontend` et son intégration au bundle : diff --git a/crates/fsv/Cargo.toml b/crates/fsv/Cargo.toml index dad118b..e6fdf3b 100644 --- a/crates/fsv/Cargo.toml +++ b/crates/fsv/Cargo.toml @@ -18,3 +18,11 @@ utils = { path = "../utils" } #[dev-dependencies] log = "0.4.22" env_logger = "0.11.5" + +[dev-dependencies] +insta = "1.40.0" + +[profile.dev.package] +# Optimize insta (snapshot testing library) for faster compile times +insta.opt-level = 3 +similar.opt-level = 3 diff --git a/crates/fsv/src/ssv/mod.rs b/crates/fsv/src/ssv/mod.rs index a8f4385..dbf7fc9 100644 --- a/crates/fsv/src/ssv/mod.rs +++ b/crates/fsv/src/ssv/mod.rs @@ -14,7 +14,7 @@ use fsv_sys::{ mod errors_ssv; -use errors_ssv::SSVErrorCodes; +pub use errors_ssv::SSVErrorCodes; use crate::fsv_parsing::prelude::*; #[derive(Error, Debug)] diff --git a/crates/fsv/tests/snapshots/test_ssv__read_cps_default_kit_doc.snap b/crates/fsv/tests/snapshots/test_ssv__read_cps_default_kit_doc.snap new file mode 100644 index 0000000..9b543c8 --- /dev/null +++ b/crates/fsv/tests/snapshots/test_ssv__read_cps_default_kit_doc.snap @@ -0,0 +1,97 @@ +--- +source: crates/fsv/tests/test_ssv.rs +expression: cps_blocks +--- +Data { + blocks: [ + DataBlock { + header: BlockHeader { + group_id: GroupId( + 1, + ), + data_size: 36, + }, + content: LireCartePS_Group1_Holder( + Holder { + card_type: CPS, + national_id_type: RPPS, + national_id: AlphaNumericString( + "99700619010", + ), + national_id_key: AlphaNumericString( + "0", + ), + civility_code: Monsieur, + holder_lastname: AlphaNumericString( + "DOC0061901", + ), + holder_firstname: AlphaNumericString( + "KIT", + ), + category_card: Unknown, + }, + ), + }, + DataBlock { + header: BlockHeader { + group_id: GroupId( + 2, + ), + data_size: 84, + }, + content: LireCartePS_Group2_Situation( + Situation { + id: RawByte( + 2, + ), + practice_mode: Liberal, + practice_status_raw: NumericString( + "1", + ), + activity_sector: CabinetIndividuel, + structure_id_type: RPPSCabinet, + structure_id: AlphaNumericString( + "99700619010009", + ), + structure_id_key: NumericString( + "0", + ), + structure_name: AlphaNumericString( + "CABINET M DOC0061901", + ), + ps_billing_number: NumericString( + "00102901", + ), + ps_billing_number_key: NumericString( + "6", + ), + ps_replacement_number: AlphaNumericString( + "", + ), + ps_replacement_number_key: NumericString( + "0", + ), + convention_code: Conventionne, + specialty_code: MedecineGenerale, + rate_zone_code_raw: NumericString( + "24", + ), + ik_zone_code: IndemniteKilometriquePlaine, + approval_code_1: PasAgrementRadio, + approval_code_2_raw: NumericString( + "0", + ), + approval_code_3_raw: NumericString( + "0", + ), + invoice_signature_permission: NumericString( + "1", + ), + lot_signature_permission: NumericString( + "1", + ), + }, + ), + }, + ], +} diff --git a/crates/fsv/tests/snapshots/test_ssv__read_cps_pharma_kit_adjointe.snap b/crates/fsv/tests/snapshots/test_ssv__read_cps_pharma_kit_adjointe.snap new file mode 100644 index 0000000..c64f770 --- /dev/null +++ b/crates/fsv/tests/snapshots/test_ssv__read_cps_pharma_kit_adjointe.snap @@ -0,0 +1,97 @@ +--- +source: crates/fsv/tests/test_ssv.rs +expression: cps_blocks +--- +Data { + blocks: [ + DataBlock { + header: BlockHeader { + group_id: GroupId( + 1, + ), + data_size: 55, + }, + content: LireCartePS_Group1_Holder( + Holder { + card_type: CPS, + national_id_type: RPPS, + national_id: AlphaNumericString( + "99700593694", + ), + national_id_key: AlphaNumericString( + "4", + ), + civility_code: Madame, + holder_lastname: AlphaNumericString( + "ADJOINTPHARM RPPS0059369", + ), + holder_firstname: AlphaNumericString( + "PATRICIA", + ), + category_card: Unknown, + }, + ), + }, + DataBlock { + header: BlockHeader { + group_id: GroupId( + 2, + ), + data_size: 93, + }, + content: LireCartePS_Group2_Situation( + Situation { + id: RawByte( + 1, + ), + practice_mode: Salarie, + practice_status_raw: NumericString( + "2", + ), + activity_sector: PharmacieDOfficine, + structure_id_type: FINESS, + structure_id: AlphaNumericString( + "0B0246286", + ), + structure_id_key: NumericString( + "6", + ), + structure_name: AlphaNumericString( + "PHARMACIE DE LA GARE ROUTIERE24628", + ), + ps_billing_number: NumericString( + "00209368", + ), + ps_billing_number_key: NumericString( + "0", + ), + ps_replacement_number: AlphaNumericString( + "", + ), + ps_replacement_number_key: NumericString( + "0", + ), + convention_code: Conventionne, + specialty_code: PharmacieDOfficine, + rate_zone_code_raw: NumericString( + "10", + ), + ik_zone_code: PasDIndemniteKilometrique, + approval_code_1: PasAgrementRadio, + approval_code_2_raw: NumericString( + "0", + ), + approval_code_3_raw: NumericString( + "0", + ), + invoice_signature_permission: NumericString( + "1", + ), + lot_signature_permission: NumericString( + "1", + ), + }, + ), + }, + ], +} diff --git a/crates/fsv/tests/snapshots/test_ssv__read_cps_pharma_kit_employe_aa.snap b/crates/fsv/tests/snapshots/test_ssv__read_cps_pharma_kit_employe_aa.snap new file mode 100644 index 0000000..a580c03 --- /dev/null +++ b/crates/fsv/tests/snapshots/test_ssv__read_cps_pharma_kit_employe_aa.snap @@ -0,0 +1,97 @@ +--- +source: crates/fsv/tests/test_ssv.rs +expression: cps_blocks +--- +Data { + blocks: [ + DataBlock { + header: BlockHeader { + group_id: GroupId( + 1, + ), + data_size: 49, + }, + content: LireCartePS_Group1_Holder( + Holder { + card_type: CPE, + national_id_type: FINESS, + national_id: AlphaNumericString( + "0B0246286/CPET00001", + ), + national_id_key: AlphaNumericString( + "6", + ), + civility_code: Monsieur, + holder_lastname: AlphaNumericString( + "EMPLOYE246280001", + ), + holder_firstname: AlphaNumericString( + "AA", + ), + category_card: Unknown, + }, + ), + }, + DataBlock { + header: BlockHeader { + group_id: GroupId( + 2, + ), + data_size: 93, + }, + content: LireCartePS_Group2_Situation( + Situation { + id: RawByte( + 1, + ), + practice_mode: Salarie, + practice_status_raw: NumericString( + "0", + ), + activity_sector: PharmacieDOfficine, + structure_id_type: FINESS, + structure_id: AlphaNumericString( + "0B0246286", + ), + structure_id_key: NumericString( + "6", + ), + structure_name: AlphaNumericString( + "PHARMACIE DE LA GARE ROUTIERE24628", + ), + ps_billing_number: NumericString( + "00209368", + ), + ps_billing_number_key: NumericString( + "0", + ), + ps_replacement_number: AlphaNumericString( + "", + ), + ps_replacement_number_key: NumericString( + "0", + ), + convention_code: Conventionne, + specialty_code: PharmacieDOfficine, + rate_zone_code_raw: NumericString( + "10", + ), + ik_zone_code: PasDIndemniteKilometrique, + approval_code_1: PasAgrementRadio, + approval_code_2_raw: NumericString( + "0", + ), + approval_code_3_raw: NumericString( + "0", + ), + invoice_signature_permission: NumericString( + "1", + ), + lot_signature_permission: NumericString( + "0", + ), + }, + ), + }, + ], +} diff --git a/crates/fsv/tests/snapshots/test_ssv__read_cps_pharma_kit_infirmiere.snap b/crates/fsv/tests/snapshots/test_ssv__read_cps_pharma_kit_infirmiere.snap new file mode 100644 index 0000000..4dcce64 --- /dev/null +++ b/crates/fsv/tests/snapshots/test_ssv__read_cps_pharma_kit_infirmiere.snap @@ -0,0 +1,97 @@ +--- +source: crates/fsv/tests/test_ssv.rs +expression: cps_blocks +--- +Data { + blocks: [ + DataBlock { + header: BlockHeader { + group_id: GroupId( + 1, + ), + data_size: 50, + }, + content: LireCartePS_Group1_Holder( + Holder { + card_type: CPS, + national_id_type: RPPS, + national_id: AlphaNumericString( + "99700520499", + ), + national_id_key: AlphaNumericString( + "9", + ), + civility_code: Madame, + holder_lastname: AlphaNumericString( + "INFIRMIERE RPPS0052049", + ), + holder_firstname: AlphaNumericString( + "ALINE", + ), + category_card: Unknown, + }, + ), + }, + DataBlock { + header: BlockHeader { + group_id: GroupId( + 2, + ), + data_size: 93, + }, + content: LireCartePS_Group2_Situation( + Situation { + id: RawByte( + 1, + ), + practice_mode: Liberal, + practice_status_raw: NumericString( + "1", + ), + activity_sector: CabinetIndividuel, + structure_id_type: RPPSCabinet, + structure_id: AlphaNumericString( + "99700520499002", + ), + structure_id_key: NumericString( + "0", + ), + structure_name: AlphaNumericString( + "CABINET MME INFIRMIERE0052049", + ), + ps_billing_number: NumericString( + "00602049", + ), + ps_billing_number_key: NumericString( + "9", + ), + ps_replacement_number: AlphaNumericString( + "", + ), + ps_replacement_number_key: NumericString( + "0", + ), + convention_code: Conventionne, + specialty_code: Infirmier, + rate_zone_code_raw: NumericString( + "20", + ), + ik_zone_code: IndemniteKilometriqueMontagne, + approval_code_1: PasAgrementRadio, + approval_code_2_raw: NumericString( + "0", + ), + approval_code_3_raw: NumericString( + "0", + ), + invoice_signature_permission: NumericString( + "1", + ), + lot_signature_permission: NumericString( + "1", + ), + }, + ), + }, + ], +} diff --git a/crates/fsv/tests/snapshots/test_ssv__read_cps_pharma_kit_titulaire.snap b/crates/fsv/tests/snapshots/test_ssv__read_cps_pharma_kit_titulaire.snap new file mode 100644 index 0000000..eda78c8 --- /dev/null +++ b/crates/fsv/tests/snapshots/test_ssv__read_cps_pharma_kit_titulaire.snap @@ -0,0 +1,97 @@ +--- +source: crates/fsv/tests/test_ssv.rs +expression: cps_blocks +--- +Data { + blocks: [ + DataBlock { + header: BlockHeader { + group_id: GroupId( + 1, + ), + data_size: 53, + }, + content: LireCartePS_Group1_Holder( + Holder { + card_type: CPS, + national_id_type: RPPS, + national_id: AlphaNumericString( + "99700593686", + ), + national_id_key: AlphaNumericString( + "6", + ), + civility_code: Monsieur, + holder_lastname: AlphaNumericString( + "PHARMOFFICE RPPS0059368", + ), + holder_firstname: AlphaNumericString( + "GILBERT", + ), + category_card: Unknown, + }, + ), + }, + DataBlock { + header: BlockHeader { + group_id: GroupId( + 2, + ), + data_size: 93, + }, + content: LireCartePS_Group2_Situation( + Situation { + id: RawByte( + 1, + ), + practice_mode: Liberal, + practice_status_raw: NumericString( + "1", + ), + activity_sector: PharmacieDOfficine, + structure_id_type: FINESS, + structure_id: AlphaNumericString( + "0B0246286", + ), + structure_id_key: NumericString( + "6", + ), + structure_name: AlphaNumericString( + "PHARMACIE DE LA GARE ROUTIERE24628", + ), + ps_billing_number: NumericString( + "00209368", + ), + ps_billing_number_key: NumericString( + "0", + ), + ps_replacement_number: AlphaNumericString( + "", + ), + ps_replacement_number_key: NumericString( + "0", + ), + convention_code: Conventionne, + specialty_code: PharmacieDOfficine, + rate_zone_code_raw: NumericString( + "10", + ), + ik_zone_code: PasDIndemniteKilometrique, + approval_code_1: PasAgrementRadio, + approval_code_2_raw: NumericString( + "0", + ), + approval_code_3_raw: NumericString( + "0", + ), + invoice_signature_permission: NumericString( + "1", + ), + lot_signature_permission: NumericString( + "1", + ), + }, + ), + }, + ], +} diff --git a/crates/fsv/tests/test_ssv.rs b/crates/fsv/tests/test_ssv.rs new file mode 100644 index 0000000..8fc921b --- /dev/null +++ b/crates/fsv/tests/test_ssv.rs @@ -0,0 +1,174 @@ +use anyhow::Result; + +use common::read_cps; +use fsv::ssv::{Error, SSVErrorCodes, SSV}; +use insta::assert_debug_snapshot; + +mod common { + + use super::*; + + use std::env; + + use fsv::fsv_parsing::Data; + use fsv_sys::SupportedFsvVersion; + use utils::config::load_config; + + pub fn init() -> Result { + load_config(None)?; + let sesam_ini_path = env::var("SESAM_INI_PATH").expect("SESAM_INI_PATH must be set"); + let lib = SSV::new(SupportedFsvVersion::V1_40_13)?; + lib.init_library(&sesam_ini_path)?; + Ok(lib) + } + + pub fn read_cps() -> Result { + let lib = init()?; + Ok(lib.read_professional_card("1234")?) + } +} + +// ---------------- PHARMA KIT ---------------- + +#[test] +#[ignore] +fn test_read_cps_pharma_kit_titulaire() -> Result<()> { + let cps_blocks = read_cps()?; + assert_debug_snapshot!(cps_blocks); + Ok(()) +} + +#[test] +#[ignore] +fn test_read_cps_pharma_kit_adjointe() -> Result<()> { + let cps_blocks = read_cps()?; + assert_debug_snapshot!(cps_blocks); + Ok(()) +} + +#[test] +#[ignore] +fn test_read_cps_pharma_kit_employe_aa() -> Result<()> { + let cps_blocks = read_cps()?; + assert_debug_snapshot!(cps_blocks); + Ok(()) +} + +#[test] +#[ignore] +fn test_read_cps_pharma_kit_infirmiere() -> Result<()> { + let cps_blocks = read_cps()?; + assert_debug_snapshot!(cps_blocks); + Ok(()) +} + +// ---------------- DEFAULT KIT ---------------- + +#[test] +#[ignore] +fn test_read_cps_default_kit_doc() -> Result<()> { + let cps_blocks = read_cps()?; + assert_debug_snapshot!(cps_blocks); + Ok(()) +} + +#[test] +#[ignore] +fn test_read_cps_default_kit_directeur() -> Result<()> { + let error = read_cps().expect_err("This card should be opposed and not be readable"); + match error.downcast_ref::() { + Some(Error::SSVError(e)) => { + match e { + SSVErrorCodes::CPSInvalid => { Ok(()) }, + _ => panic!("Expected SSVErrorCodes::CPSInvalid, got {:?}", e), + } + }, + _ => panic!("Expected Error::SSVError got {:?}", error), + } +} + +#[test] +#[ignore] +fn test_read_cps_default_kit_employee_opposee() -> Result<()> { + let error = read_cps().expect_err("This card should be opposed and not be readable"); + match error.downcast_ref::() { + Some(Error::SSVError(e)) => { + match e { + SSVErrorCodes::SSVInternalError => { Ok(()) }, + _ => panic!("Expected SSVErrorCodes::SSVInternalError, got {:?}", e), + } + }, + _ => panic!("Expected Error::SSVError got {:?}", error), + } +} + +#[test] +#[ignore] +fn test_read_cps_default_kit_doc_maximaxima() -> Result<()> { + // TODO : debug this card (ConventionCode value is 9) + let cps_blocks = read_cps()?; + assert_debug_snapshot!(cps_blocks); + Ok(()) +} + +#[test] +#[ignore] +fn test_read_cps_default_kit_orthophoniste() -> Result<()> { + let error = read_cps().expect_err("This card should be opposed and not be readable"); + match error.downcast_ref::() { + Some(Error::SSVError(e)) => { + match e { + SSVErrorCodes::SSVInternalError => { Ok(()) }, + _ => panic!("Expected SSVErrorCodes::SSVInternalError, got {:?}", e), + } + }, + _ => panic!("Expected Error::SSVError got {:?}", error), + } +} + +// ---------------- CENTRE SANTÉ ---------------- + +#[test] +#[ignore] +fn test_read_cps_centre_sante_kit_directeur() -> Result<()> { + // TODO : debug this card (ConventionCode value is 9) + let cps_blocks = read_cps()?; + assert_debug_snapshot!(cps_blocks); + Ok(()) +} + +#[test] +#[ignore] +fn test_read_cps_centre_sante_kit_doc() -> Result<()> { + // TODO : debug this card (ConventionCode value is 9) + let cps_blocks = read_cps()?; + assert_debug_snapshot!(cps_blocks); + Ok(()) +} + +#[test] +#[ignore] +fn test_read_cps_centre_sante_kit_dentiste() -> Result<()> { + // TODO : debug this card (ConventionCode value is 9) + let cps_blocks = read_cps()?; + assert_debug_snapshot!(cps_blocks); + Ok(()) +} + +#[test] +#[ignore] +fn test_read_cps_centre_sante_kit_assist_vladimir() -> Result<()> { + // TODO : debug this card (ConventionCode value is 9) + let cps_blocks = read_cps()?; + assert_debug_snapshot!(cps_blocks); + Ok(()) +} + +#[test] +#[ignore] +fn test_read_cps_centre_sante_kit_assist_marie() -> Result<()> { + // TODO : debug this card (ConventionCode value is 9) + let cps_blocks = read_cps()?; + assert_debug_snapshot!(cps_blocks); + Ok(()) +} \ No newline at end of file -- 2.45.2