Async client for the Kite Connect WebSocket API
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add tests for order model

+124 -43
+3
Cargo.toml
··· 22 22 tokio-stream = { version = "0.1.14", features = ["full"] } 23 23 url = "2.4.1" 24 24 serde_with = "3.4.0" 25 + chrono = { version = "0.4.31", features = ["serde"] } 25 26 26 27 [dev-dependencies] 27 28 tokio = { version = "1", features = ["test-util"] } 28 29 chrono = { version = "0.4.31", features = ["serde"] } 29 30 base64 = "0.21.5" 31 + sha2 = "0.10" 32 + hex = "0.4.3"
+121 -43
src/models/order.rs
··· 1 - use std::time::Duration; 2 - 1 + use chrono::NaiveDateTime; 3 2 use serde::{Deserialize, Serialize}; 4 3 use serde_json::Value; 5 4 use serde_with::{serde_as, DefaultOnNull}; 6 5 7 6 use crate::Exchange; 8 7 9 - #[derive(Debug, Deserialize, Serialize, Clone)] 8 + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd)] 10 9 #[serde(rename_all = "UPPERCASE")] 11 10 pub enum OrderTransactionType { 12 11 Buy, 13 12 Sell, 14 13 } 15 14 16 - #[derive(Debug, Deserialize, Serialize, Clone)] 15 + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd)] 17 16 pub enum OrderValidity { 18 17 DAY, 19 18 IOC, 20 19 TTL, 21 20 } 22 21 23 - #[derive(Debug, Deserialize, Serialize, Clone)] 22 + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd)] 24 23 #[serde(rename_all = "UPPERCASE")] 25 24 pub enum OrderStatus { 26 25 COMPLETE, ··· 29 28 UPDATE, 30 29 } 31 30 31 + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd)] 32 + #[serde(transparent)] 33 + pub struct TimeStamp(i64); 34 + 35 + impl From<String> for TimeStamp { 36 + fn from(value: String) -> Self { 37 + let secs = NaiveDateTime::parse_from_str(&value, "%Y-%m-%d %H:%M:%S") 38 + .unwrap() 39 + .timestamp(); 40 + TimeStamp(secs) 41 + } 42 + } 43 + 44 + impl From<TimeStamp> for String { 45 + fn from(value: TimeStamp) -> Self { 46 + NaiveDateTime::from_timestamp_opt(value.0, 0) 47 + .unwrap_or_default() 48 + .format("%Y-%m-%d %H:%M:%S") 49 + .to_string() 50 + } 51 + } 52 + 32 53 #[serde_with::serde_as] 33 - #[derive(Debug, Deserialize, Serialize, Clone)] 54 + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] 34 55 pub struct Order { 35 - account_id: String, 36 - 37 - order_id: String, 56 + pub order_id: String, 38 57 39 58 #[serde_as(as = "DefaultOnNull")] 40 - exchange_order_id: Option<String>, 59 + pub exchange_order_id: Option<String>, 41 60 42 61 #[serde_as(as = "DefaultOnNull")] 43 - parent_order_id: Option<String>, 62 + pub parent_order_id: Option<String>, 44 63 45 - placed_by: String, 46 - app_id: u64, 64 + pub placed_by: String, 65 + pub app_id: u64, 47 66 48 - status: OrderStatus, 67 + pub status: OrderStatus, 49 68 50 69 #[serde_as(as = "DefaultOnNull")] 51 - status_message: Option<String>, 70 + pub status_message: Option<String>, 52 71 53 72 #[serde_as(as = "DefaultOnNull")] 54 - status_message_raw: Option<String>, 73 + pub status_message_raw: Option<String>, 55 74 56 - tradingsymbol: String, 57 - instrument_token: u32, 75 + pub tradingsymbol: String, 76 + pub instrument_token: u32, 58 77 59 78 #[serde_as(as = "serde_with::FromInto<String>")] 60 - exchange: Exchange, 79 + pub exchange: Exchange, 61 80 62 - order_type: String, 63 - transaction_type: OrderTransactionType, 81 + pub order_type: String, 82 + pub transaction_type: OrderTransactionType, 64 83 65 - validity: OrderValidity, 66 - variety: String, 67 - product: Option<String>, 84 + pub validity: OrderValidity, 85 + pub variety: String, 86 + pub product: Option<String>, 68 87 69 88 #[serde(default)] 70 - average_price: f64, 89 + pub average_price: f64, 71 90 72 91 #[serde(default)] 73 - disclosed_quantity: f64, 92 + pub disclosed_quantity: f64, 74 93 75 - price: f64, 76 - quantity: u64, 77 - filled_quantity: u64, 94 + pub price: f64, 95 + pub quantity: u64, 96 + pub filled_quantity: u64, 78 97 79 98 #[serde(default)] 80 - unfilled_quantity: u64, 99 + pub unfilled_quantity: u64, 81 100 82 101 #[serde(default)] 83 - pending_quantity: u64, 102 + pub pending_quantity: u64, 84 103 85 104 #[serde(default)] 86 - cancelled_quantity: u64, 105 + pub cancelled_quantity: u64, 87 106 88 107 #[serde(default)] 89 - trigger_price: f64, 108 + pub trigger_price: f64, 90 109 91 - user_id: String, 110 + pub user_id: String, 92 111 93 - #[serde_as(as = "serde_with::DurationSeconds<String>")] 94 - order_timestamp: Duration, 95 - #[serde_as(as = "serde_with::DurationSeconds<String>")] 96 - exchange_timestamp: Duration, 97 - #[serde_as(as = "serde_with::DurationSeconds<String>")] 98 - exchange_update_timestamp: Duration, 112 + #[serde_as(as = "serde_with::FromInto<String>")] 113 + pub order_timestamp: TimeStamp, 114 + #[serde_as(as = "serde_with::FromInto<String>")] 115 + pub exchange_timestamp: TimeStamp, 116 + #[serde_as(as = "serde_with::FromInto<String>")] 117 + pub exchange_update_timestamp: TimeStamp, 99 118 100 - checksum: String, 119 + pub checksum: String, 101 120 #[serde(default)] 102 - meta: Option<Value>, 121 + pub meta: Option<serde_json::Map<String, Value>>, 103 122 104 123 #[serde_as(as = "DefaultOnNull")] 105 124 #[serde(default)] 106 - tag: Option<String> 125 + pub tag: Option<String>, 126 + } 127 + 128 + #[cfg(test)] 129 + mod tests { 130 + 131 + use sha2::{Digest, Sha256}; 132 + 133 + use super::*; 134 + 135 + #[test] 136 + fn test_order() { 137 + let postback_json = include_str!("../../kiteconnect-mocks/postback.json"); 138 + let exp_order = Order { 139 + order_id: "220303000308932".to_string(), 140 + exchange_order_id: Some("1000000001482421".to_string()), 141 + parent_order_id: None, 142 + placed_by: "AB1234".to_string(), 143 + app_id: 1234, 144 + status: OrderStatus::COMPLETE, 145 + status_message: None, 146 + status_message_raw: None, 147 + tradingsymbol: "SBIN".to_string(), 148 + instrument_token: 779521, 149 + exchange: Exchange::NSE, 150 + order_type: "MARKET".to_string(), 151 + transaction_type: OrderTransactionType::Buy, 152 + validity: OrderValidity::DAY, 153 + variety: "regular".to_string(), 154 + product: Some("CNC".to_string()), 155 + average_price: 470.0, 156 + disclosed_quantity: 0.0, 157 + price: 0.0, 158 + quantity: 1, 159 + filled_quantity: 1, 160 + unfilled_quantity: 0, 161 + pending_quantity: 0, 162 + cancelled_quantity: 0, 163 + trigger_price: 0.0, 164 + user_id: "AB1234".to_string(), 165 + order_timestamp: TimeStamp(1646299465), 166 + exchange_timestamp: TimeStamp(1646299465), 167 + exchange_update_timestamp: TimeStamp(1646299465), 168 + checksum: 169 + "2011845d9348bd6795151bf4258102a03431e3bb12a79c0df73fcb4b7fde4b5d" 170 + .to_string(), 171 + meta: Some(serde_json::Map::new()), 172 + tag: None, 173 + }; 174 + let order = serde_json::from_str::<Order>(postback_json).unwrap(); 175 + assert_eq!(order.clone(), exp_order); 176 + 177 + let mut hasher = Sha256::new(); 178 + hasher.update(order.order_id.as_bytes()); 179 + hasher.update(Into::<String>::into(order.order_timestamp)); 180 + hasher.update(b"0hdv7iw5examplesecret"); 181 + let expected = hasher.finalize(); 182 + let actual = hex::decode(order.checksum).unwrap(); 183 + assert_eq!(expected[..], actual[..]); 184 + } 107 185 }