466 lines
No EOL
15 KiB
Rust
466 lines
No EOL
15 KiB
Rust
// File: examples/mock_server.rs
|
|
|
|
//! Comprehensive Mock KRA eTims server for local development
|
|
//!
|
|
//! Run with: cargo run --example mock_server
|
|
//! Then set DEV_API_BASE_URL=http://127.0.0.1:8888 in your .env
|
|
|
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
use tokio::net::TcpListener;
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
println!("╔════════════════════════════════════════════════════════════════╗");
|
|
println!("║ Mock KRA eTIMS Server (Development Only) ║");
|
|
println!("╠════════════════════════════════════════════════════════════════╣");
|
|
println!("║ Company: ITERATE IOT LIMITED (P052038091H) ║");
|
|
println!("║ Listening on: http://127.0.0.1:8888 ║");
|
|
println!("║ ║");
|
|
println!("║ Set in .env: ║");
|
|
println!("║ DEV_API_BASE_URL=http://127.0.0.1:8888 ║");
|
|
println!("║ API_USERNAME=test_user ║");
|
|
println!("║ API_PASSWORD=test_pass ║");
|
|
println!("╚════════════════════════════════════════════════════════════════╝");
|
|
println!();
|
|
|
|
let listener = TcpListener::bind("127.0.0.1:8888").await?;
|
|
println!("🚀 Mock server started. Waiting for requests...\n");
|
|
|
|
loop {
|
|
let (mut socket, addr) = listener.accept().await?;
|
|
|
|
tokio::spawn(async move {
|
|
let mut buf = vec![0u8; 8192];
|
|
|
|
match socket.read(&mut buf).await {
|
|
Ok(n) if n > 0 => {
|
|
let request = String::from_utf8_lossy(&buf[..n]);
|
|
let first_line = request.lines().next().unwrap_or("");
|
|
|
|
println!("📥 [{}] {}", addr, first_line);
|
|
|
|
let (status, body) = route_request(&request);
|
|
|
|
let response = format!(
|
|
"HTTP/1.1 {}\r\n\
|
|
Content-Type: application/json\r\n\
|
|
Content-Length: {}\r\n\
|
|
Access-Control-Allow-Origin: *\r\n\
|
|
Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n\
|
|
Access-Control-Allow-Headers: Content-Type, Authorization\r\n\
|
|
Connection: close\r\n\
|
|
\r\n\
|
|
{}",
|
|
status,
|
|
body.len(),
|
|
body
|
|
);
|
|
|
|
let _ = socket.write_all(response.as_bytes()).await;
|
|
|
|
let preview = if body.len() > 80 {
|
|
format!("{}...", &body[..80])
|
|
} else {
|
|
body.clone()
|
|
};
|
|
println!("📤 [{}] {} - {}", addr, status, preview);
|
|
}
|
|
_ => {}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
fn route_request(request: &str) -> (&'static str, String) {
|
|
let path = request
|
|
.lines()
|
|
.next()
|
|
.and_then(|line| line.split_whitespace().nth(1))
|
|
.unwrap_or("");
|
|
|
|
// Handle OPTIONS preflight
|
|
if request.starts_with("OPTIONS") {
|
|
return ("204 No Content", String::new());
|
|
}
|
|
|
|
match path {
|
|
p if p.contains("/oauth2/v1/generate") => {
|
|
("200 OK", mock_token_response())
|
|
}
|
|
p if p.contains("/selectInitOsdcInfo") => {
|
|
("200 OK", mock_init_osdc_response())
|
|
}
|
|
p if p.contains("/selectCodeList") => {
|
|
("200 OK", mock_code_list_response())
|
|
}
|
|
p if p.contains("/selectItemClsList") => {
|
|
("200 OK", mock_item_cls_response())
|
|
}
|
|
p if p.contains("/selectBhfList") => {
|
|
("200 OK", mock_bhf_list_response())
|
|
}
|
|
p if p.contains("/sendSalesTrns") => {
|
|
("200 OK", mock_send_sales_response())
|
|
}
|
|
p if p.contains("/selectSalesTrns") => {
|
|
("200 OK", mock_select_sales_response())
|
|
}
|
|
p if p.contains("/selectNoticeList") => {
|
|
("200 OK", mock_notice_response())
|
|
}
|
|
p if p.contains("/selectTaxpayerInfo") => {
|
|
("200 OK", mock_taxpayer_response())
|
|
}
|
|
p if p.contains("/selectCustomerList") => {
|
|
("200 OK", mock_customer_response())
|
|
}
|
|
p if p.contains("/selectMoveList") => {
|
|
("200 OK", mock_move_list_response())
|
|
}
|
|
p if p.contains("/saveStockMaster") => {
|
|
("200 OK", mock_save_stock_response())
|
|
}
|
|
p if p.contains("/selectPurchaseTrns") => {
|
|
("200 OK", mock_purchase_response())
|
|
}
|
|
p if p.contains("/saveItem") => {
|
|
("200 OK", mock_save_item_response())
|
|
}
|
|
p if p.contains("/selectImportItemList") => {
|
|
("200 OK", mock_import_items_response())
|
|
}
|
|
_ => {
|
|
println!("⚠️ Unknown endpoint: {}", path);
|
|
("404 Not Found", format!(
|
|
r#"{{"resultCd": "9999", "resultMsg": "Endpoint not found: {}"}}"#,
|
|
path
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn mock_token_response() -> String {
|
|
r#"{
|
|
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.mock_token_for_iterate_iot",
|
|
"token_type": "Bearer",
|
|
"expires_in": 3600,
|
|
"scope": "read write"
|
|
}"#.to_string()
|
|
}
|
|
|
|
fn mock_init_osdc_response() -> String {
|
|
r#"{
|
|
"resultCd": "0000",
|
|
"resultMsg": "Successful",
|
|
"resultDt": "20240207120000",
|
|
"data": {
|
|
"tin": "P052038091H",
|
|
"bhfId": "00",
|
|
"dvcSrlNo": "ITERATE-DEV-001",
|
|
"sdcId": "SDC-ITERATE-001",
|
|
"mrcNo": "MRC-001",
|
|
"lastSaleInvcNo": 0,
|
|
"lastPchsInvcNo": 0,
|
|
"lastSaleRcptNo": 0,
|
|
"lastInvcNo": 0,
|
|
"lastTrainInvcNo": 0,
|
|
"lastProfrmInvcNo": 0,
|
|
"lastCopyInvcNo": 0
|
|
}
|
|
}"#.to_string()
|
|
}
|
|
|
|
fn mock_code_list_response() -> String {
|
|
r#"{
|
|
"resultCd": "0000",
|
|
"resultMsg": "Successful",
|
|
"resultDt": "20240207120000",
|
|
"data": {
|
|
"clsList": [
|
|
{"cdCls": "01", "cdClsNm": "Tax Type", "cdClsDesc": "Tax Type Codes"},
|
|
{"cdCls": "02", "cdClsNm": "Country", "cdClsDesc": "Country Codes"},
|
|
{"cdCls": "03", "cdClsNm": "Quantity Unit", "cdClsDesc": "Quantity Unit Codes"},
|
|
{"cdCls": "04", "cdClsNm": "Package Unit", "cdClsDesc": "Package Unit Codes"},
|
|
{"cdCls": "05", "cdClsNm": "Payment Type", "cdClsDesc": "Payment Type Codes"}
|
|
],
|
|
"dtlList": [
|
|
{"cdCls": "01", "cd": "A", "cdNm": "A-EX", "cdDesc": "Exempt"},
|
|
{"cdCls": "01", "cd": "B", "cdNm": "B-16.00%", "cdDesc": "Standard Rate 16%"},
|
|
{"cdCls": "01", "cd": "C", "cdNm": "C-0%", "cdDesc": "Zero Rated"},
|
|
{"cdCls": "01", "cd": "D", "cdNm": "D-Non-VAT", "cdDesc": "Non-VAT"},
|
|
{"cdCls": "01", "cd": "E", "cdNm": "E-8%", "cdDesc": "8% VAT"},
|
|
{"cdCls": "02", "cd": "KE", "cdNm": "Kenya", "cdDesc": "Republic of Kenya"},
|
|
{"cdCls": "03", "cd": "EA", "cdNm": "Each", "cdDesc": "Each/Piece"},
|
|
{"cdCls": "03", "cd": "KG", "cdNm": "Kilogram", "cdDesc": "Kilogram"},
|
|
{"cdCls": "03", "cd": "LT", "cdNm": "Litre", "cdDesc": "Litre"},
|
|
{"cdCls": "04", "cd": "BX", "cdNm": "Box", "cdDesc": "Box"},
|
|
{"cdCls": "04", "cd": "CT", "cdNm": "Carton", "cdDesc": "Carton"},
|
|
{"cdCls": "05", "cd": "01", "cdNm": "Cash", "cdDesc": "Cash Payment"},
|
|
{"cdCls": "05", "cd": "02", "cdNm": "Credit", "cdDesc": "Credit Payment"},
|
|
{"cdCls": "05", "cd": "03", "cdNm": "Mobile", "cdDesc": "Mobile Money"}
|
|
]
|
|
}
|
|
}"#.to_string()
|
|
}
|
|
|
|
fn mock_item_cls_response() -> String {
|
|
r#"{
|
|
"resultCd": "0000",
|
|
"resultMsg": "Successful",
|
|
"resultDt": "20240207120000",
|
|
"data": {
|
|
"itemClsList": [
|
|
{"itemClsCd": "KE1NTXU000", "itemClsNm": "General Goods", "itemClsLvl": 1, "taxTyCd": "B", "mjrTgYn": "N", "useYn": "Y"},
|
|
{"itemClsCd": "KE2NTXU001", "itemClsNm": "Electronics", "itemClsLvl": 2, "taxTyCd": "B", "mjrTgYn": "N", "useYn": "Y"},
|
|
{"itemClsCd": "KE2NTXU002", "itemClsNm": "Food & Beverages", "itemClsLvl": 2, "taxTyCd": "A", "mjrTgYn": "N", "useYn": "Y"},
|
|
{"itemClsCd": "KE2NTXU003", "itemClsNm": "IoT Devices", "itemClsLvl": 2, "taxTyCd": "B", "mjrTgYn": "N", "useYn": "Y"},
|
|
{"itemClsCd": "KE2NTXU004", "itemClsNm": "Software & Services", "itemClsLvl": 2, "taxTyCd": "B", "mjrTgYn": "N", "useYn": "Y"}
|
|
]
|
|
}
|
|
}"#.to_string()
|
|
}
|
|
|
|
fn mock_bhf_list_response() -> String {
|
|
r#"{
|
|
"resultCd": "0000",
|
|
"resultMsg": "Successful",
|
|
"resultDt": "20240207120000",
|
|
"data": {
|
|
"bhfList": [
|
|
{
|
|
"tin": "P052038091H",
|
|
"bhfId": "00",
|
|
"bhfNm": "ITERATE IOT LIMITED - Head Office",
|
|
"bhfSttsCd": "A",
|
|
"prvncNm": "Nairobi",
|
|
"dstrtNm": "Westlands",
|
|
"sctrNm": "Parklands",
|
|
"locDesc": "Innovation Hub, 4th Floor",
|
|
"mgrNm": "Manager",
|
|
"mgrTelNo": "+254700000000",
|
|
"mgrEmail": "info@iterateiot.com"
|
|
}
|
|
]
|
|
}
|
|
}"#.to_string()
|
|
}
|
|
|
|
fn mock_send_sales_response() -> String {
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
let timestamp = SystemTime::now()
|
|
.duration_since(UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_secs();
|
|
|
|
format!(r#"{{
|
|
"resultCd": "0000",
|
|
"resultMsg": "Successful",
|
|
"resultDt": "20240207120000",
|
|
"data": {{
|
|
"rcptNo": {},
|
|
"intrlData": "MOCK-INTERNAL-DATA-{}",
|
|
"rcptSign": "MOCK-SIGNATURE-{}",
|
|
"sdcDateTime": "20240207120000",
|
|
"sdcId": "SDC-ITERATE-001",
|
|
"mrcNo": "MRC-001",
|
|
"qrCodeUrl": "https://etims.kra.go.ke/verify?code=MOCK{}",
|
|
"totTaxAmt": 160.00,
|
|
"totAmt": 1160.00
|
|
}}
|
|
}}"#, timestamp % 10000, timestamp, timestamp, timestamp)
|
|
}
|
|
|
|
fn mock_select_sales_response() -> String {
|
|
r#"{
|
|
"resultCd": "0000",
|
|
"resultMsg": "Successful",
|
|
"resultDt": "20240207120000",
|
|
"data": {
|
|
"salesList": [
|
|
{
|
|
"invcNo": "INV-2024-0001",
|
|
"salesDt": "20240207",
|
|
"custTin": "A012345678X",
|
|
"custNm": "Test Customer",
|
|
"totItemCnt": 2,
|
|
"totTaxblAmt": 1000.00,
|
|
"totTaxAmt": 160.00,
|
|
"totAmt": 1160.00,
|
|
"rcptTyCd": "S",
|
|
"salesSttsCd": "02"
|
|
},
|
|
{
|
|
"invcNo": "INV-2024-0002",
|
|
"salesDt": "20240207",
|
|
"custTin": "P987654321Y",
|
|
"custNm": "Another Customer",
|
|
"totItemCnt": 1,
|
|
"totTaxblAmt": 500.00,
|
|
"totTaxAmt": 80.00,
|
|
"totAmt": 580.00,
|
|
"rcptTyCd": "S",
|
|
"salesSttsCd": "02"
|
|
}
|
|
]
|
|
}
|
|
}"#.to_string()
|
|
}
|
|
|
|
fn mock_notice_response() -> String {
|
|
r#"{
|
|
"resultCd": "0000",
|
|
"resultMsg": "Successful",
|
|
"resultDt": "20240207120000",
|
|
"data": {
|
|
"noticeList": [
|
|
{
|
|
"noticeNo": "N-2024-001",
|
|
"title": "eTIMS System Update",
|
|
"cont": "The eTIMS system will undergo scheduled maintenance on Sunday.",
|
|
"regDt": "20240201",
|
|
"dtlUrl": "https://etims.kra.go.ke/notices/001"
|
|
},
|
|
{
|
|
"noticeNo": "N-2024-002",
|
|
"title": "New VAT Rates Effective",
|
|
"cont": "Please note the updated VAT rates effective from the new fiscal year.",
|
|
"regDt": "20240115",
|
|
"dtlUrl": "https://etims.kra.go.ke/notices/002"
|
|
}
|
|
]
|
|
}
|
|
}"#.to_string()
|
|
}
|
|
|
|
fn mock_taxpayer_response() -> String {
|
|
r#"{
|
|
"resultCd": "0000",
|
|
"resultMsg": "Successful",
|
|
"resultDt": "20240207120000",
|
|
"data": {
|
|
"tin": "P052038091H",
|
|
"taxprNm": "ITERATE IOT LIMITED",
|
|
"taxprSttsCd": "A",
|
|
"taxprSttsNm": "Active",
|
|
"prvncNm": "Nairobi",
|
|
"dstrtNm": "Westlands",
|
|
"sctrNm": "Parklands",
|
|
"locDesc": "Innovation Hub, 4th Floor",
|
|
"isVatReg": "Y",
|
|
"vatRegDt": "20230101"
|
|
}
|
|
}"#.to_string()
|
|
}
|
|
|
|
fn mock_customer_response() -> String {
|
|
r#"{
|
|
"resultCd": "0000",
|
|
"resultMsg": "Successful",
|
|
"resultDt": "20240207120000",
|
|
"data": {
|
|
"custList": [
|
|
{
|
|
"custNo": "CUST-001",
|
|
"custTin": "A012345678X",
|
|
"custNm": "John Doe",
|
|
"adrs": "123 Main Street, Nairobi",
|
|
"telNo": "+254700111222",
|
|
"email": "john.doe@example.com",
|
|
"useYn": "Y"
|
|
},
|
|
{
|
|
"custNo": "CUST-002",
|
|
"custTin": "P987654321Y",
|
|
"custNm": "ABC Company Ltd",
|
|
"adrs": "456 Business Park, Mombasa",
|
|
"telNo": "+254700333444",
|
|
"email": "info@abccompany.co.ke",
|
|
"useYn": "Y"
|
|
}
|
|
]
|
|
}
|
|
}"#.to_string()
|
|
}
|
|
|
|
fn mock_move_list_response() -> String {
|
|
r#"{
|
|
"resultCd": "0000",
|
|
"resultMsg": "Successful",
|
|
"resultDt": "20240207120000",
|
|
"data": {
|
|
"stockList": [
|
|
{
|
|
"itemCd": "ITEM-001",
|
|
"itemNm": "IoT Sensor Module",
|
|
"rsdQty": 150.0,
|
|
"pkgUnitCd": "EA",
|
|
"lastMvDt": "20240206"
|
|
},
|
|
{
|
|
"itemCd": "ITEM-002",
|
|
"itemNm": "WiFi Gateway",
|
|
"rsdQty": 50.0,
|
|
"pkgUnitCd": "EA",
|
|
"lastMvDt": "20240205"
|
|
}
|
|
]
|
|
}
|
|
}"#.to_string()
|
|
}
|
|
|
|
fn mock_save_stock_response() -> String {
|
|
r#"{
|
|
"resultCd": "0000",
|
|
"resultMsg": "Successful",
|
|
"resultDt": "20240207120000",
|
|
"data": {
|
|
"itemCd": "ITEM-NEW-001",
|
|
"regrId": "SYSTEM",
|
|
"regrNm": "System"
|
|
}
|
|
}"#.to_string()
|
|
}
|
|
|
|
fn mock_purchase_response() -> String {
|
|
r#"{
|
|
"resultCd": "0000",
|
|
"resultMsg": "Successful",
|
|
"resultDt": "20240207120000",
|
|
"data": {
|
|
"purchaseList": [
|
|
{
|
|
"invcNo": "PUR-2024-001",
|
|
"spplrTin": "P111222333Z",
|
|
"spplrNm": "Supplier Company",
|
|
"pchsDt": "20240201",
|
|
"totItemCnt": 5,
|
|
"totTaxblAmt": 50000.00,
|
|
"totTaxAmt": 8000.00,
|
|
"totAmt": 58000.00
|
|
}
|
|
]
|
|
}
|
|
}"#.to_string()
|
|
}
|
|
|
|
fn mock_save_item_response() -> String {
|
|
r#"{
|
|
"resultCd": "0000",
|
|
"resultMsg": "Successful",
|
|
"resultDt": "20240207120000",
|
|
"data": {
|
|
"itemCd": "ITEM-NEW-001"
|
|
}
|
|
}"#.to_string()
|
|
}
|
|
|
|
fn mock_import_items_response() -> String {
|
|
r#"{
|
|
"resultCd": "0000",
|
|
"resultMsg": "Successful",
|
|
"resultDt": "20240207120000",
|
|
"data": {
|
|
"itemList": []
|
|
}
|
|
}"#.to_string()
|
|
} |