Improve tests

This commit is contained in:
eraden 2022-04-17 22:15:09 +02:00
parent 38bed6f835
commit 4097e92268
6 changed files with 83 additions and 40 deletions

1
.env
View File

@ -1,3 +1,4 @@
DATABASE_URL=postgres://postgres@localhost/bazzar DATABASE_URL=postgres://postgres@localhost/bazzar
PASS_SALT=18CHwV7eGFAea16z+qMKZg PASS_SALT=18CHwV7eGFAea16z+qMKZg
RUST_LOG=debug RUST_LOG=debug
KEY_SECRET="NEPJs#8jjn8SK8GC7QEC^*P844UgsyEbQB8mRWXkT%3mPrwewZoc25MMby9H#R*w2KzaQgMkk#Pif$kxrLy*N5L!Ch%jxbWoa%gb"

View File

@ -17,9 +17,11 @@
justify-content: space-between; justify-content: space-between;
width: 600px; width: 600px;
} }
fieldset > label { fieldset > label {
width: 45%; width: 45%;
} }
fieldset > input, fieldset > textarea { fieldset > input, fieldset > textarea {
width: 54%; width: 54%;
} }
@ -31,7 +33,15 @@
<body> <body>
<div style="display: flex;justify-content: space-between;"> <div style="display: flex;justify-content: space-between;">
<div style="width: 49%"> <div style="width: 49%">
<form> <form style="width: 100%">
<fieldset>
<label for="op">Operation</label>
<select name="op" id="op">
<option value="auto-login">Auto login</option>
<option value="get-products">Get products</option>
<option value="create-product">Create product</option>
</select>
</fieldset>
<fieldset> <fieldset>
<label for="method">Method</label> <label for="method">Method</label>
<select id="method"> <select id="method">
@ -51,7 +61,11 @@
</form> </form>
</div> </div>
<div style="width: 49%"> <div style="width: 49%">
<pre style="background: black; width: 100%; min-height: 300px"><code id="output" class="language-json"></code></pre> <pre style="background: black; width: 100%; min-height: 300px"><code
id="output"
class="language-json"
style="overflow: auto"
></code></pre>
</div> </div>
</div> </div>
<script> <script>
@ -62,6 +76,46 @@
const urlEl = form.querySelector('#path'); const urlEl = form.querySelector('#path');
const paramsEl = form.querySelector('#params'); const paramsEl = form.querySelector('#params');
const mthEl = form.querySelector('#method'); const mthEl = form.querySelector('#method');
const opEL = form.querySelector('#op');
const send = (method, path, params) => {
const rest = method === 'GET'
? {}
: { body: JSON.stringify(params), headers: { 'Content-Type': 'application/json' } };
path = method === 'GET'
? `${ path }?${ JSON.stringify(params) }`
: path;
fetch(`${ path }`, { ...rest, method })
.then(async (res) => {
if (res.status < 400) {
const json = await res.json();
out.innerHTML = Prism.highlight(JSON.stringify(json, null, 4), Prism.languages.json, 'json');
} else {
out.innerHTML = await res.text();
}
})
}
opEL.addEventListener('change', () => {
switch (opEL.value) {
case 'auto-login':
paramsEl.value = `login=Eraden\npassword=text`
return send(mthEl.value = 'POST', urlEl.value = '/admin/sign-in', { login: 'Eraden', password: 'test' })
case 'get-products':
return send(mthEl.value = 'GET', urlEl.value = '/admin/api/v1/products', {})
case 'create-product':
const p = {
name: 'Foo',
short_description: 'asd',
long_description: 'asjdoiajd ajio djaso idja s',
price_major: 12,
price_minor: 0,
};
paramsEl.value = Object.entries(p).map((k, v) => `${ k }=${ v }}`).join('\n');
return send(mthEl.value = 'POST', urlEl.value = '/admin/api/v1/product', p)
}
})
form.addEventListener('submit', (ev) => { form.addEventListener('submit', (ev) => {
ev.preventDefault(); ev.preventDefault();
@ -74,21 +128,15 @@
paramsEl.value.split("\n").forEach(s => { paramsEl.value.split("\n").forEach(s => {
if (!s.length) return; if (!s.length) return;
let [k, ...v] = s.split("="); let [k, ...v] = s.split("=");
params[k] = Array(v).join('='); v = Array(v).join('=')
try {
v = JSON.parse(v);
} catch (_) {
}
params[k] = v;
}); });
const rest = method === 'GET' send(method, path, params)
? {}
: { body: JSON.stringify(params), headers: { 'Content-Type': 'application/json' } };
path = method === 'GET'
? `${ path }?${ JSON.stringify(params) }`
: path;
fetch(`${ path }`, { ...rest, method })
.then(res => res.json())
.then(json => {
out.innerHTML = Prism.highlight(JSON.stringify(json), Prism.languages.json, 'json');
})
}); });
</script> </script>
</body> </body>

View File

@ -152,7 +152,11 @@ impl Config {
} }
async fn server(opts: ServerOpts) -> Result<()> { async fn server(opts: ServerOpts) -> Result<()> {
let secret_key = Key::generate(); let secret_key = {
let key_secret = std::env::var("KEY_SECRET")
.expect("session requires secret key with 64 or more characters");
Key::from(key_secret.as_bytes())
};
let redis_connection_string = "127.0.0.1:6379"; let redis_connection_string = "127.0.0.1:6379";
let config = Arc::new(Config::load()); let config = Arc::new(Config::load());

View File

@ -1,6 +1,6 @@
use std::fmt::Formatter; use std::fmt::Formatter;
use derive_more::{Deref, Display}; use derive_more::{Deref, Display, From};
use serde::de::{Error, Visitor}; use serde::de::{Error, Visitor};
use serde::{Deserialize, Deserializer, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
@ -61,29 +61,21 @@ pub enum ShoppingCartState {
Closed, Closed,
} }
#[derive(sqlx::Type, Serialize, Deserialize, Deref)] #[derive(sqlx::Type, Serialize, Deserialize, Deref, From)]
#[sqlx(transparent)] #[sqlx(transparent)]
#[serde(transparent)] #[serde(transparent)]
pub struct PriceMajor(NonNegative); pub struct PriceMajor(NonNegative);
#[derive(sqlx::Type, Serialize, Deserialize, Deref)] #[derive(sqlx::Type, Serialize, Deserialize, Deref, From)]
#[sqlx(transparent)] #[sqlx(transparent)]
#[serde(transparent)] #[serde(transparent)]
pub struct PriceMinor(NonNegative); pub struct PriceMinor(NonNegative);
#[derive(sqlx::Type, Serialize, Deserialize, Deref)] #[derive(sqlx::Type, Serialize, Deserialize, Deref, From)]
#[sqlx(transparent)] #[sqlx(transparent)]
#[serde(transparent)] #[serde(transparent)]
pub struct Quantity(NonNegative); pub struct Quantity(NonNegative);
impl TryFrom<NonNegative> for Quantity {
type Error = TransformError;
fn try_from(value: NonNegative) -> Result<Self, Self::Error> {
Ok(Self(value))
}
}
impl TryFrom<i32> for Quantity { impl TryFrom<i32> for Quantity {
type Error = TransformError; type Error = TransformError;
@ -112,7 +104,7 @@ impl<'de> serde::Deserialize<'de> for Email {
type Value = String; type Value = String;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("this is not valid e-mail address") formatter.write_str("valid e-mail address")
} }
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
@ -158,7 +150,7 @@ impl<'de> serde::Deserialize<'de> for NonNegative {
type Value = i32; type Value = i32;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("this is not valid e-mail address") formatter.write_str("value equal or greater than 0")
} }
fn visit_i32<E>(self, v: i32) -> Result<Self::Value, E> fn visit_i32<E>(self, v: i32) -> Result<Self::Value, E>

View File

@ -14,7 +14,7 @@ use actix_web::web::{Data, Json, ServiceConfig};
use actix_web::{delete, get, patch, post, HttpResponse}; use actix_web::{delete, get, patch, post, HttpResponse};
use serde::Deserialize; use serde::Deserialize;
#[get("products")] #[get("/products")]
async fn products(session: Session, db: Data<Addr<Database>>) -> routes::Result<HttpResponse> { async fn products(session: Session, db: Data<Addr<Database>>) -> routes::Result<HttpResponse> {
session.require_admin()?; session.require_admin()?;
@ -32,7 +32,7 @@ pub struct UpdateProduct {
pub price_minor: PriceMinor, pub price_minor: PriceMinor,
} }
#[patch("product")] #[patch("/product")]
async fn update_product( async fn update_product(
session: Session, session: Session,
db: Data<Addr<Database>>, db: Data<Addr<Database>>,
@ -56,7 +56,6 @@ async fn update_product(
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct CreateProduct { pub struct CreateProduct {
pub id: ProductId,
pub name: ProductName, pub name: ProductName,
pub short_description: ProductShortDesc, pub short_description: ProductShortDesc,
pub long_description: ProductLongDesc, pub long_description: ProductLongDesc,
@ -65,7 +64,7 @@ pub struct CreateProduct {
pub price_minor: PriceMinor, pub price_minor: PriceMinor,
} }
#[post("product")] #[post("/product")]
async fn create_product( async fn create_product(
session: Session, session: Session,
db: Data<Addr<Database>>, db: Data<Addr<Database>>,
@ -91,7 +90,7 @@ pub struct DeleteProduct {
pub id: ProductId, pub id: ProductId,
} }
#[delete("product")] #[delete("/product")]
async fn delete_product( async fn delete_product(
session: Session, session: Session,
db: Data<Addr<Database>>, db: Data<Addr<Database>>,

View File

@ -11,7 +11,7 @@ use actix_web::web::{Data, Json, ServiceConfig};
use actix_web::{delete, get, patch, post, HttpResponse}; use actix_web::{delete, get, patch, post, HttpResponse};
use serde::Deserialize; use serde::Deserialize;
#[get("stocks")] #[get("/stocks")]
async fn stocks(session: Session, db: Data<Addr<Database>>) -> routes::Result<HttpResponse> { async fn stocks(session: Session, db: Data<Addr<Database>>) -> routes::Result<HttpResponse> {
session.require_admin()?; session.require_admin()?;
@ -26,7 +26,7 @@ pub struct UpdateStock {
pub quantity_unit: QuantityUnit, pub quantity_unit: QuantityUnit,
} }
#[patch("stock")] #[patch("/stock")]
async fn update_stock( async fn update_stock(
session: Session, session: Session,
db: Data<Addr<Database>>, db: Data<Addr<Database>>,
@ -47,13 +47,12 @@ async fn update_stock(
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct CreateStock { pub struct CreateStock {
pub id: StockId,
pub product_id: ProductId, pub product_id: ProductId,
pub quantity: Quantity, pub quantity: Quantity,
pub quantity_unit: QuantityUnit, pub quantity_unit: QuantityUnit,
} }
#[post("stock")] #[post("/stock")]
async fn create_stock( async fn create_stock(
session: Session, session: Session,
db: Data<Addr<Database>>, db: Data<Addr<Database>>,
@ -76,7 +75,7 @@ pub struct DeleteStock {
pub id: StockId, pub id: StockId,
} }
#[delete("stock")] #[delete("/stock")]
async fn delete_stock( async fn delete_stock(
session: Session, session: Session,
db: Data<Addr<Database>>, db: Data<Addr<Database>>,