719 lines
26 KiB
Rust
719 lines
26 KiB
Rust
use std::{ffi::CStr, ffi::CString, os::raw::c_char, slice};
|
|
|
|
use anyhow::Result;
|
|
use reqwest::Url;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[cfg(not(test))]
|
|
use log::{error, info};
|
|
#[cfg(test)]
|
|
use std::{println as info, println as error};
|
|
|
|
use crate::{
|
|
cache::file_cache_dir, cache::from_file_cache, cache::update_file_cache, log_server_error,
|
|
result::FFIResult,
|
|
};
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
pub struct MerchandiseList {
|
|
pub id: Option<i32>,
|
|
pub shop_id: i32,
|
|
pub form_list: Vec<Merchandise>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
pub struct Merchandise {
|
|
pub mod_name: String,
|
|
pub local_form_id: u32,
|
|
pub name: String,
|
|
pub quantity: u32,
|
|
pub form_type: u32,
|
|
pub is_food: bool,
|
|
pub price: u32,
|
|
}
|
|
|
|
impl MerchandiseList {
|
|
pub fn from_game(shop_id: i32, merch_records: &[RawMerchandise]) -> Self {
|
|
Self {
|
|
id: None,
|
|
shop_id,
|
|
form_list: merch_records
|
|
.iter()
|
|
.map(|rec| Merchandise {
|
|
mod_name: unsafe { CStr::from_ptr(rec.mod_name) }
|
|
.to_string_lossy()
|
|
.to_string(),
|
|
local_form_id: rec.local_form_id,
|
|
name: unsafe { CStr::from_ptr(rec.name) }
|
|
.to_string_lossy()
|
|
.to_string(),
|
|
quantity: rec.quantity,
|
|
form_type: rec.form_type,
|
|
is_food: rec.is_food == 1,
|
|
price: rec.price,
|
|
})
|
|
.collect(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
#[repr(C)]
|
|
pub struct RawMerchandise {
|
|
pub mod_name: *const c_char,
|
|
pub local_form_id: u32,
|
|
pub name: *const c_char,
|
|
pub quantity: u32,
|
|
pub form_type: u32,
|
|
pub is_food: u8,
|
|
pub price: u32,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
#[repr(C)]
|
|
pub struct RawMerchandiseVec {
|
|
pub ptr: *mut RawMerchandise,
|
|
pub len: usize,
|
|
pub cap: usize,
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn create_merchandise_list(
|
|
api_url: *const c_char,
|
|
api_key: *const c_char,
|
|
shop_id: i32,
|
|
raw_merchandise_ptr: *const RawMerchandise,
|
|
raw_merchandise_len: usize,
|
|
) -> FFIResult<i32> {
|
|
let api_url = unsafe { CStr::from_ptr(api_url) }.to_string_lossy();
|
|
let api_key = unsafe { CStr::from_ptr(api_key) }.to_string_lossy();
|
|
info!("create_merchandise_list api_url: {:?}, api_key: {:?}, shop_id: {:?}, raw_merchandise_len: {:?}", api_url, api_key, shop_id, raw_merchandise_len);
|
|
let raw_merchandise_slice = unsafe {
|
|
assert!(!raw_merchandise_ptr.is_null());
|
|
slice::from_raw_parts(raw_merchandise_ptr, raw_merchandise_len)
|
|
};
|
|
|
|
fn inner(
|
|
api_url: &str,
|
|
api_key: &str,
|
|
shop_id: i32,
|
|
raw_merchandise_slice: &[RawMerchandise],
|
|
) -> Result<MerchandiseList> {
|
|
#[cfg(not(test))]
|
|
let url = Url::parse(api_url)?.join("v1/merchandise_lists")?;
|
|
#[cfg(test)]
|
|
let url = Url::parse(&mockito::server_url())?.join("v1/merchandise_lists")?;
|
|
|
|
let merchandise_list = MerchandiseList::from_game(shop_id, raw_merchandise_slice);
|
|
info!(
|
|
"created merchandise_list from game: shop_id: {}",
|
|
&merchandise_list.shop_id
|
|
);
|
|
let client = reqwest::blocking::Client::new();
|
|
let resp = client
|
|
.post(url)
|
|
.header("Api-Key", api_key)
|
|
.json(&merchandise_list)
|
|
.send()?;
|
|
info!("create merchandise_list response from api: {:?}", &resp);
|
|
let bytes = resp.bytes()?;
|
|
let json: MerchandiseList = serde_json::from_slice(&bytes)?;
|
|
if let Some(id) = json.id {
|
|
update_file_cache(
|
|
&file_cache_dir(api_url)?.join(format!("merchandise_list_{}.json", id)),
|
|
&bytes,
|
|
)?;
|
|
}
|
|
Ok(json)
|
|
}
|
|
|
|
match inner(&api_url, &api_key, shop_id, raw_merchandise_slice) {
|
|
Ok(merchandise_list) => {
|
|
if let Some(id) = merchandise_list.id {
|
|
FFIResult::Ok(id)
|
|
} else {
|
|
error!(
|
|
"create_merchandise failed. API did not return an interior ref list with an ID"
|
|
);
|
|
let err_string =
|
|
CString::new("API did not return an interior ref list with an ID".to_string())
|
|
.expect("could not create CString")
|
|
.into_raw();
|
|
// TODO: also need to drop this CString once C++ is done reading it
|
|
FFIResult::Err(err_string)
|
|
}
|
|
}
|
|
Err(err) => {
|
|
error!("create_merchandise_list failed. {}", err);
|
|
// TODO: also need to drop this CString once C++ is done reading it
|
|
let err_string = CString::new(err.to_string())
|
|
.expect("could not create CString")
|
|
.into_raw();
|
|
FFIResult::Err(err_string)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn update_merchandise_list(
|
|
api_url: *const c_char,
|
|
api_key: *const c_char,
|
|
shop_id: i32,
|
|
raw_merchandise_ptr: *const RawMerchandise,
|
|
raw_merchandise_len: usize,
|
|
) -> FFIResult<i32> {
|
|
let api_url = unsafe { CStr::from_ptr(api_url) }.to_string_lossy();
|
|
let api_key = unsafe { CStr::from_ptr(api_key) }.to_string_lossy();
|
|
info!("create_merchandise_list api_url: {:?}, api_key: {:?}, shop_id: {:?}, raw_merchandise_len: {:?}", api_url, api_key, shop_id, raw_merchandise_len);
|
|
let raw_merchandise_slice = unsafe {
|
|
assert!(!raw_merchandise_ptr.is_null());
|
|
slice::from_raw_parts(raw_merchandise_ptr, raw_merchandise_len)
|
|
};
|
|
|
|
fn inner(
|
|
api_url: &str,
|
|
api_key: &str,
|
|
shop_id: i32,
|
|
raw_merchandise_slice: &[RawMerchandise],
|
|
) -> Result<MerchandiseList> {
|
|
#[cfg(not(test))]
|
|
let url = Url::parse(api_url)?.join(&format!("v1/shops/{}/merchandise_list", shop_id))?;
|
|
#[cfg(test)]
|
|
let url = Url::parse(&mockito::server_url())?
|
|
.join(&format!("v1/shops/{}/merchandise_list", shop_id))?;
|
|
|
|
let merchandise_list = MerchandiseList::from_game(shop_id, raw_merchandise_slice);
|
|
info!(
|
|
"created merchandise_list from game: shop_id: {}",
|
|
&merchandise_list.shop_id
|
|
);
|
|
let client = reqwest::blocking::Client::new();
|
|
let resp = client
|
|
.patch(url)
|
|
.header("Api-Key", api_key)
|
|
.json(&merchandise_list)
|
|
.send()?;
|
|
info!("update merchandise_list response from api: {:?}", &resp);
|
|
let bytes = resp.bytes()?;
|
|
let json: MerchandiseList = serde_json::from_slice(&bytes)?;
|
|
if let Some(id) = json.id {
|
|
update_file_cache(
|
|
&file_cache_dir(api_url)?.join(format!("shops_{}_merchandise_list.json", id)),
|
|
&bytes,
|
|
)?;
|
|
}
|
|
Ok(json)
|
|
}
|
|
|
|
match inner(&api_url, &api_key, shop_id, raw_merchandise_slice) {
|
|
Ok(merchandise_list) => {
|
|
if let Some(id) = merchandise_list.id {
|
|
FFIResult::Ok(id)
|
|
} else {
|
|
error!(
|
|
"update_merchandise failed. API did not return an interior ref list with an ID"
|
|
);
|
|
let err_string =
|
|
CString::new("API did not return an interior ref list with an ID".to_string())
|
|
.expect("could not create CString")
|
|
.into_raw();
|
|
// TODO: also need to drop this CString once C++ is done reading it
|
|
FFIResult::Err(err_string)
|
|
}
|
|
}
|
|
Err(err) => {
|
|
error!("update_merchandise_list failed. {}", err);
|
|
// TODO: also need to drop this CString once C++ is done reading it
|
|
let err_string = CString::new(err.to_string())
|
|
.expect("could not create CString")
|
|
.into_raw();
|
|
FFIResult::Err(err_string)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn get_merchandise_list(
|
|
api_url: *const c_char,
|
|
api_key: *const c_char,
|
|
merchandise_list_id: i32,
|
|
) -> FFIResult<RawMerchandiseVec> {
|
|
info!("get_merchandise_list begin");
|
|
let api_url = unsafe { CStr::from_ptr(api_url) }.to_string_lossy();
|
|
let api_key = unsafe { CStr::from_ptr(api_key) }.to_string_lossy();
|
|
info!(
|
|
"get_merchandise_list api_url: {:?}, api_key: {:?}, merchandise_list_id: {:?}",
|
|
api_url, api_key, merchandise_list_id
|
|
);
|
|
|
|
fn inner(api_url: &str, api_key: &str, merchandise_list_id: i32) -> Result<MerchandiseList> {
|
|
#[cfg(not(test))]
|
|
let url =
|
|
Url::parse(api_url)?.join(&format!("v1/merchandise_lists/{}", merchandise_list_id))?;
|
|
#[cfg(test)]
|
|
let url = Url::parse(&mockito::server_url())?
|
|
.join(&format!("v1/merchandise_lists/{}", merchandise_list_id))?;
|
|
info!("api_url: {:?}", url);
|
|
|
|
let client = reqwest::blocking::Client::new();
|
|
let cache_path =
|
|
file_cache_dir(api_url)?.join(format!("merchandise_list_{}.json", merchandise_list_id));
|
|
|
|
match client.get(url).header("Api-Key", api_key).send() {
|
|
Ok(resp) => {
|
|
info!("get_merchandise_list response from api: {:?}", &resp);
|
|
if resp.status().is_success() {
|
|
let bytes = resp.bytes()?;
|
|
update_file_cache(&cache_path, &bytes)?;
|
|
let json = serde_json::from_slice(&bytes)?;
|
|
Ok(json)
|
|
} else {
|
|
log_server_error(resp);
|
|
from_file_cache(&cache_path)
|
|
}
|
|
}
|
|
Err(err) => {
|
|
error!("get_merchandise_list api request error: {}", err);
|
|
from_file_cache(&cache_path)
|
|
}
|
|
}
|
|
}
|
|
|
|
match inner(&api_url, &api_key, merchandise_list_id) {
|
|
Ok(merchandise_list) => {
|
|
let (ptr, len, cap) = merchandise_list
|
|
.form_list
|
|
.into_iter()
|
|
.map(|merchandise| RawMerchandise {
|
|
mod_name: CString::new(merchandise.mod_name)
|
|
.unwrap_or_default()
|
|
.into_raw(),
|
|
local_form_id: merchandise.local_form_id,
|
|
name: CString::new(merchandise.name)
|
|
.unwrap_or_default()
|
|
.into_raw(),
|
|
quantity: merchandise.quantity,
|
|
form_type: merchandise.form_type,
|
|
is_food: merchandise.is_food as u8,
|
|
price: merchandise.price,
|
|
})
|
|
.collect::<Vec<RawMerchandise>>()
|
|
.into_raw_parts();
|
|
// TODO: need to pass this back into Rust once C++ is done with it so it can be manually dropped and the CStrings dropped from raw pointers.
|
|
FFIResult::Ok(RawMerchandiseVec { ptr, len, cap })
|
|
}
|
|
Err(err) => {
|
|
error!("merchandise_list failed. {}", err);
|
|
// TODO: how to do error handling?
|
|
let err_string = CString::new(err.to_string())
|
|
.expect("could not create CString")
|
|
.into_raw();
|
|
// TODO: also need to drop this CString once C++ is done reading it
|
|
FFIResult::Err(err_string)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn get_merchandise_list_by_shop_id(
|
|
api_url: *const c_char,
|
|
api_key: *const c_char,
|
|
shop_id: i32,
|
|
) -> FFIResult<RawMerchandiseVec> {
|
|
let api_url = unsafe { CStr::from_ptr(api_url) }.to_string_lossy();
|
|
let api_key = unsafe { CStr::from_ptr(api_key) }.to_string_lossy();
|
|
info!(
|
|
"get_merchandise_list_by_shop_id api_url: {:?}, api_key: {:?}, shop_id: {:?}",
|
|
api_url, api_key, shop_id
|
|
);
|
|
|
|
fn inner(api_url: &str, api_key: &str, shop_id: i32) -> Result<MerchandiseList> {
|
|
#[cfg(not(test))]
|
|
let url = Url::parse(api_url)?.join(&format!("v1/shops/{}/merchandise_list", shop_id))?;
|
|
#[cfg(test)]
|
|
let url = Url::parse(&mockito::server_url())?
|
|
.join(&format!("v1/shops/{}/merchandise_list", shop_id))?;
|
|
info!("api_url: {:?}", url);
|
|
|
|
let client = reqwest::blocking::Client::new();
|
|
let cache_path =
|
|
file_cache_dir(api_url)?.join(format!("shops_{}_merchandise_list.json", shop_id));
|
|
|
|
match client.get(url).header("Api-Key", api_key).send() {
|
|
Ok(resp) => {
|
|
info!(
|
|
"get_merchandise_list_by_shop_id response from api: {:?}",
|
|
&resp
|
|
);
|
|
if resp.status().is_success() {
|
|
let bytes = resp.bytes()?;
|
|
update_file_cache(&cache_path, &bytes)?;
|
|
let json = serde_json::from_slice(&bytes)?;
|
|
Ok(json)
|
|
} else {
|
|
log_server_error(resp);
|
|
from_file_cache(&cache_path)
|
|
}
|
|
}
|
|
Err(err) => {
|
|
error!("get_merchandise_list_by_shop_id api request error: {}", err);
|
|
from_file_cache(&cache_path)
|
|
}
|
|
}
|
|
}
|
|
|
|
match inner(&api_url, &api_key, shop_id) {
|
|
Ok(merchandise_list) => {
|
|
let (ptr, len, cap) = merchandise_list
|
|
.form_list
|
|
.into_iter()
|
|
.map(|merchandise| RawMerchandise {
|
|
mod_name: CString::new(merchandise.mod_name)
|
|
.unwrap_or_default()
|
|
.into_raw(),
|
|
local_form_id: merchandise.local_form_id,
|
|
name: CString::new(merchandise.name)
|
|
.unwrap_or_default()
|
|
.into_raw(),
|
|
quantity: merchandise.quantity,
|
|
form_type: merchandise.form_type,
|
|
is_food: merchandise.is_food as u8,
|
|
price: merchandise.price,
|
|
})
|
|
.collect::<Vec<RawMerchandise>>()
|
|
.into_raw_parts();
|
|
// TODO: need to pass this back into Rust once C++ is done with it so it can be manually dropped and the CStrings dropped from raw pointers.
|
|
FFIResult::Ok(RawMerchandiseVec { ptr, len, cap })
|
|
}
|
|
Err(err) => {
|
|
error!("get_merchandise_list_by_shop_id failed. {}", err);
|
|
// TODO: how to do error handling?
|
|
let err_string = CString::new(err.to_string())
|
|
.expect("could not create CString")
|
|
.into_raw();
|
|
// TODO: also need to drop this CString once C++ is done reading it
|
|
FFIResult::Err(err_string)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::ffi::CString;
|
|
|
|
use super::*;
|
|
use mockito::mock;
|
|
|
|
#[test]
|
|
fn test_create_merchandise_list() {
|
|
let mock = mock("POST", "/v1/merchandise_lists")
|
|
.with_status(201)
|
|
.with_header("content-type", "application/json")
|
|
.with_body(r#"{ "created_at": "2020-08-18T00:00:00.000", "id": 1, "shop_id": 1, "form_list": [], "updated_at": "2020-08-18T00:00:00.000" }"#)
|
|
.create();
|
|
|
|
let api_url = CString::new("url").unwrap().into_raw();
|
|
let api_key = CString::new("api-key").unwrap().into_raw();
|
|
let (ptr, len, _cap) = vec![RawMerchandise {
|
|
mod_name: CString::new("Skyrim.esm").unwrap().into_raw(),
|
|
local_form_id: 1,
|
|
name: CString::new("Iron Sword").unwrap().into_raw(),
|
|
quantity: 1,
|
|
form_type: 1,
|
|
is_food: 0,
|
|
price: 100,
|
|
}]
|
|
.into_raw_parts();
|
|
let result = create_merchandise_list(api_url, api_key, 1, ptr, len);
|
|
mock.assert();
|
|
match result {
|
|
FFIResult::Ok(merchandise_list_id) => {
|
|
assert_eq!(merchandise_list_id, 1);
|
|
}
|
|
FFIResult::Err(error) => {
|
|
panic!("create_merchandise_list returned error: {:?}", unsafe {
|
|
CStr::from_ptr(error).to_string_lossy()
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_create_interior_ref_list_server_error() {
|
|
let mock = mock("POST", "/v1/merchandise_lists")
|
|
.with_status(500)
|
|
.with_body("Internal Server Error")
|
|
.create();
|
|
|
|
let api_url = CString::new("url").unwrap().into_raw();
|
|
let api_key = CString::new("api-key").unwrap().into_raw();
|
|
let (ptr, len, _cap) = vec![RawMerchandise {
|
|
mod_name: CString::new("Skyrim.esm").unwrap().into_raw(),
|
|
local_form_id: 1,
|
|
name: CString::new("Iron Sword").unwrap().into_raw(),
|
|
quantity: 1,
|
|
form_type: 1,
|
|
is_food: 0,
|
|
price: 100,
|
|
}]
|
|
.into_raw_parts();
|
|
let result = create_merchandise_list(api_url, api_key, 1, ptr, len);
|
|
mock.assert();
|
|
match result {
|
|
FFIResult::Ok(merchandise_list_id) => panic!(
|
|
"create_merchandise_list returned Ok result: {:?}",
|
|
merchandise_list_id
|
|
),
|
|
FFIResult::Err(error) => {
|
|
assert_eq!(
|
|
unsafe { CStr::from_ptr(error).to_string_lossy() },
|
|
"expected value at line 1 column 1"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_update_merchandise_list() {
|
|
let mock = mock("PATCH", "/v1/shops/1/merchandise_list")
|
|
.with_status(201)
|
|
.with_header("content-type", "application/json")
|
|
.with_body(r#"{ "created_at": "2020-08-18T00:00:00.000", "id": 1, "shop_id": 1, "form_list": [], "updated_at": "2020-08-18T00:00:00.000" }"#)
|
|
.create();
|
|
|
|
let api_url = CString::new("url").unwrap().into_raw();
|
|
let api_key = CString::new("api-key").unwrap().into_raw();
|
|
let (ptr, len, _cap) = vec![RawMerchandise {
|
|
mod_name: CString::new("Skyrim.esm").unwrap().into_raw(),
|
|
local_form_id: 1,
|
|
name: CString::new("Iron Sword").unwrap().into_raw(),
|
|
quantity: 1,
|
|
form_type: 1,
|
|
is_food: 0,
|
|
price: 100,
|
|
}]
|
|
.into_raw_parts();
|
|
let result = update_merchandise_list(api_url, api_key, 1, ptr, len);
|
|
mock.assert();
|
|
match result {
|
|
FFIResult::Ok(merchandise_list_id) => {
|
|
assert_eq!(merchandise_list_id, 1);
|
|
}
|
|
FFIResult::Err(error) => {
|
|
panic!("update_merchandise_list returned error: {:?}", unsafe {
|
|
CStr::from_ptr(error).to_string_lossy()
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_update_interior_ref_list_server_error() {
|
|
let mock = mock("PATCH", "/v1/shops/1/merchandise_list")
|
|
.with_status(500)
|
|
.with_body("Internal Server Error")
|
|
.create();
|
|
|
|
let api_url = CString::new("url").unwrap().into_raw();
|
|
let api_key = CString::new("api-key").unwrap().into_raw();
|
|
let (ptr, len, _cap) = vec![RawMerchandise {
|
|
mod_name: CString::new("Skyrim.esm").unwrap().into_raw(),
|
|
local_form_id: 1,
|
|
name: CString::new("Iron Sword").unwrap().into_raw(),
|
|
quantity: 1,
|
|
form_type: 1,
|
|
is_food: 0,
|
|
price: 100,
|
|
}]
|
|
.into_raw_parts();
|
|
let result = update_merchandise_list(api_url, api_key, 1, ptr, len);
|
|
mock.assert();
|
|
match result {
|
|
FFIResult::Ok(merchandise_list_id) => panic!(
|
|
"update_merchandise_list returned Ok result: {:?}",
|
|
merchandise_list_id
|
|
),
|
|
FFIResult::Err(error) => {
|
|
assert_eq!(
|
|
unsafe { CStr::from_ptr(error).to_string_lossy() },
|
|
"expected value at line 1 column 1"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
#[test]
|
|
fn test_get_merchandise_list() {
|
|
let mock = mock("GET", "/v1/merchandise_lists/1")
|
|
.with_status(201)
|
|
.with_header("content-type", "application/json")
|
|
.with_body(
|
|
r#"{
|
|
"created_at": "2020-08-18T00:00:00.000",
|
|
"id": 1,
|
|
"shop_id": 1,
|
|
"form_list": [
|
|
{
|
|
"mod_name": "Skyrim.esm",
|
|
"local_form_id": 1,
|
|
"name": "Iron Sword",
|
|
"quantity": 1,
|
|
"form_type": 1,
|
|
"is_food": false,
|
|
"price": 100
|
|
}
|
|
],
|
|
"updated_at": "2020-08-18T00:00:00.000"
|
|
}"#,
|
|
)
|
|
.create();
|
|
|
|
let api_url = CString::new("url").unwrap().into_raw();
|
|
let api_key = CString::new("api-key").unwrap().into_raw();
|
|
let result = get_merchandise_list(api_url, api_key, 1);
|
|
mock.assert();
|
|
match result {
|
|
FFIResult::Ok(raw_merchandise_vec) => {
|
|
assert_eq!(raw_merchandise_vec.len, 1);
|
|
let raw_merchandise_slice = unsafe {
|
|
assert!(!raw_merchandise_vec.ptr.is_null());
|
|
slice::from_raw_parts(raw_merchandise_vec.ptr, raw_merchandise_vec.len)
|
|
};
|
|
let raw_merchandise = &raw_merchandise_slice[0];
|
|
assert_eq!(
|
|
unsafe { CStr::from_ptr(raw_merchandise.mod_name) }
|
|
.to_string_lossy()
|
|
.to_string(),
|
|
"Skyrim.esm".to_string(),
|
|
);
|
|
assert_eq!(raw_merchandise.local_form_id, 1);
|
|
assert_eq!(
|
|
unsafe { CStr::from_ptr(raw_merchandise.name) }
|
|
.to_string_lossy()
|
|
.to_string(),
|
|
"Iron Sword".to_string(),
|
|
);
|
|
assert_eq!(raw_merchandise.quantity, 1);
|
|
assert_eq!(raw_merchandise.form_type, 1);
|
|
assert_eq!(raw_merchandise.is_food, 0);
|
|
assert_eq!(raw_merchandise.price, 100);
|
|
}
|
|
FFIResult::Err(error) => panic!("get_merchandise_list returned error: {:?}", unsafe {
|
|
CStr::from_ptr(error).to_string_lossy()
|
|
}),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_merchandise_list_server_error() {
|
|
let mock = mock("GET", "/v1/merchandise_lists/1")
|
|
.with_status(500)
|
|
.with_body("Internal Server Error")
|
|
.create();
|
|
|
|
let api_url = CString::new("url").unwrap().into_raw();
|
|
let api_key = CString::new("api-key").unwrap().into_raw();
|
|
let result = get_merchandise_list(api_url, api_key, 1);
|
|
mock.assert();
|
|
match result {
|
|
FFIResult::Ok(raw_merchandise_vec) => panic!(
|
|
"get_merchandise_list returned Ok result: {:#x?}",
|
|
raw_merchandise_vec
|
|
),
|
|
FFIResult::Err(error) => {
|
|
assert_eq!(
|
|
unsafe { CStr::from_ptr(error).to_string_lossy() },
|
|
"EOF while parsing a value at line 1 column 0" // empty tempfile
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_merchandise_list_by_shop_id() {
|
|
let mock = mock("GET", "/v1/shops/1/merchandise_list")
|
|
.with_status(201)
|
|
.with_header("content-type", "application/json")
|
|
.with_body(
|
|
r#"{
|
|
"created_at": "2020-08-18T00:00:00.000",
|
|
"id": 1,
|
|
"shop_id": 1,
|
|
"form_list": [
|
|
{
|
|
"mod_name": "Skyrim.esm",
|
|
"local_form_id": 1,
|
|
"name": "Iron Sword",
|
|
"quantity": 1,
|
|
"form_type": 1,
|
|
"is_food": false,
|
|
"price": 100
|
|
}
|
|
],
|
|
"updated_at": "2020-08-18T00:00:00.000"
|
|
}"#,
|
|
)
|
|
.create();
|
|
|
|
let api_url = CString::new("url").unwrap().into_raw();
|
|
let api_key = CString::new("api-key").unwrap().into_raw();
|
|
let result = get_merchandise_list_by_shop_id(api_url, api_key, 1);
|
|
mock.assert();
|
|
match result {
|
|
FFIResult::Ok(raw_merchandise_vec) => {
|
|
assert_eq!(raw_merchandise_vec.len, 1);
|
|
let raw_merchandise_slice = unsafe {
|
|
assert!(!raw_merchandise_vec.ptr.is_null());
|
|
slice::from_raw_parts(raw_merchandise_vec.ptr, raw_merchandise_vec.len)
|
|
};
|
|
let raw_merchandise = &raw_merchandise_slice[0];
|
|
assert_eq!(
|
|
unsafe { CStr::from_ptr(raw_merchandise.mod_name) }
|
|
.to_string_lossy()
|
|
.to_string(),
|
|
"Skyrim.esm".to_string(),
|
|
);
|
|
assert_eq!(raw_merchandise.local_form_id, 1);
|
|
assert_eq!(
|
|
unsafe { CStr::from_ptr(raw_merchandise.name) }
|
|
.to_string_lossy()
|
|
.to_string(),
|
|
"Iron Sword".to_string(),
|
|
);
|
|
assert_eq!(raw_merchandise.quantity, 1);
|
|
assert_eq!(raw_merchandise.form_type, 1);
|
|
assert_eq!(raw_merchandise.is_food, 0);
|
|
assert_eq!(raw_merchandise.price, 100);
|
|
}
|
|
FFIResult::Err(error) => panic!(
|
|
"get_merchandise_list_by_shop_id returned error: {:?}",
|
|
unsafe { CStr::from_ptr(error).to_string_lossy() }
|
|
),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_merchandise_list_server_error_by_shop_id() {
|
|
let mock = mock("GET", "/v1/shops/1/merchandise_list")
|
|
.with_status(500)
|
|
.with_body("Internal Server Error")
|
|
.create();
|
|
|
|
let api_url = CString::new("url").unwrap().into_raw();
|
|
let api_key = CString::new("api-key").unwrap().into_raw();
|
|
let result = get_merchandise_list_by_shop_id(api_url, api_key, 1);
|
|
mock.assert();
|
|
match result {
|
|
FFIResult::Ok(raw_merchandise_vec) => panic!(
|
|
"get_merchandise_list_by_shop_id returned Ok result: {:#x?}",
|
|
raw_merchandise_vec
|
|
),
|
|
FFIResult::Err(error) => {
|
|
assert_eq!(
|
|
unsafe { CStr::from_ptr(error).to_string_lossy() },
|
|
"EOF while parsing a value at line 1 column 0" // empty tempfile
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|