fix: replace username with first and last name and use user_id
UUID for primary keys
This commit is contained in:
parent
07be1ecf20
commit
50bd3db7b3
66 changed files with 719 additions and 873 deletions
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT EXISTS (\n SELECT 1\n FROM user_query\n WHERE\n username = $1\n );",
|
||||
"query": "SELECT EXISTS (\n SELECT 1\n FROM user_query\n WHERE\n user_id = $1\n );",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
@ -11,12 +11,12 @@
|
|||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "ed4bd44b2a0595cd80b36ce70b30ab55af1af12c1f22a2d4a2baf8af4569cf73"
|
||||
"hash": "004d12b7ccb1b21c39ef6de716953bf039bdba5096ae139be7656170ff45613f"
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO verification_otp (secret, created_at, purpose, username)\n VALUES ($1, $2, $3, $4);",
|
||||
"query": "INSERT INTO verification_otp (secret, created_at, purpose, user_id)\n VALUES ($1, $2, $3, $4);",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
|
@ -8,10 +8,10 @@
|
|||
"Varchar",
|
||||
"Timestamptz",
|
||||
"Text",
|
||||
"Text"
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "487deedfaaf10c4ab02fc223b8fec4bf359b082331adfa788096742673168be3"
|
||||
"hash": "0fbaa8084440adce8a6162d67e3e57b6062cc17bbbbeb5ea3e1e58db17ac8240"
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE\n user_query\n SET\n user_id = $1, version = $2, first_name = $3, email = $4,\n hashed_password = $5, is_admin = $6, is_verified = $7, deleted = $8,\n last_name=$9;",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Int8",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Bool",
|
||||
"Bool",
|
||||
"Bool",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "14e8d7a1c8f80701b76b2bac69b1ecd99f7694d620f1945ad5c4ae474a17be1b"
|
||||
}
|
|
@ -9,7 +9,7 @@
|
|||
"Text",
|
||||
"Text",
|
||||
"Uuid",
|
||||
"Text"
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
{
|
||||
"ordinal": 3,
|
||||
"name": "owner",
|
||||
"type_info": "Text"
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT \n view_id, version\n FROM\n user_query\n WHERE\n view_id = $1;",
|
||||
"query": "SELECT \n user_id, version\n FROM\n user_query\n WHERE\n user_id = $1;",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "view_id",
|
||||
"type_info": "Text"
|
||||
"name": "user_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
|
@ -16,7 +16,7 @@
|
|||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
|
@ -24,5 +24,5 @@
|
|||
false
|
||||
]
|
||||
},
|
||||
"hash": "fa1c65d51e3e0521d6a20998f4b80b615c9e0ffe9ceda82e0bce410c9aca39a0"
|
||||
"hash": "6523c83a859d7ca283d209133a10d4ac74b6b0358bdf1e17f1a54e2cc02e305b"
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT EXISTS (\n SELECT 1\n FROM verification_otp\n WHERE\n username = $1\n AND\n purpose = $2\n AND\n secret = $3\n );",
|
||||
"query": "SELECT EXISTS (\n SELECT 1\n FROM verification_otp\n WHERE\n user_id = $1\n AND\n purpose = $2\n AND\n secret = $3\n );",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
@ -11,7 +11,7 @@
|
|||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Uuid",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
|
@ -20,5 +20,5 @@
|
|||
null
|
||||
]
|
||||
},
|
||||
"hash": "3edf94a78114819085b573ff51702faee1c444c24bbf6d33ec1b4b245dd9f675"
|
||||
"hash": "70e6216e30f90175d4c3bad51ff51f5fa2b6f965d868b15c53264cb8cd6b4053"
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE\n user_query\n SET\n view_id = $1, version = $2, username = $3, email = $4,\n hashed_password = $5, is_admin = $6, is_verified = $7, deleted = $8;",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int8",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Bool",
|
||||
"Bool",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "750f96296e6d2c0b6d79c6f21e5b665369d5062a42c2c405f43d56a614a1e0ea"
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO user_query (\n view_id, version, username, email,\n hashed_password, is_admin, is_verified, deleted\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8\n );",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int8",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Bool",
|
||||
"Bool",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "7a401e98fff59eb8c0fe1744ccdea68842eaf86ba312c2eb324854041beb9de7"
|
||||
}
|
|
@ -1,42 +1,52 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT \n username, email, hashed_password, is_admin, is_verified, deleted\n FROM\n user_query\n WHERE\n view_id = $1;",
|
||||
"query": "SELECT \n first_name, last_name, user_id, email, hashed_password, is_admin, is_verified, deleted\n FROM\n user_query\n WHERE\n user_id = $1;",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "username",
|
||||
"name": "first_name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "email",
|
||||
"name": "last_name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "user_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "email",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "hashed_password",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"ordinal": 5,
|
||||
"name": "is_admin",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"ordinal": 6,
|
||||
"name": "is_verified",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"ordinal": 7,
|
||||
"name": "deleted",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
|
@ -45,8 +55,10 @@
|
|||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "803f868ee4a79fefdd85c5d0a034a5925c3bb7de87828095971cb19bc1fc7550"
|
||||
"hash": "7a59c989d043c249cd04fe24544cf9ea55e1329ce4b53889947478c2e766ea1a"
|
||||
}
|
|
@ -9,7 +9,7 @@
|
|||
"Text",
|
||||
"Text",
|
||||
"Uuid",
|
||||
"Text"
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM\n verification_otp\n WHERE\n username = $1\n AND\n purpose = $2\n AND\n secret = $3;",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c54d1b89ee39ceb11326e82962eef3d8f588f6252ba4bbac99fb73cdbbcd2204"
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT\n secret\n FROM\n verification_otp\n WHERE\n username = $1\n AND\n purpose = $2;",
|
||||
"query": "SELECT\n secret\n FROM\n verification_otp\n WHERE\n user_id = $1\n AND\n purpose = $2;",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
@ -11,7 +11,7 @@
|
|||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Uuid",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
|
@ -19,5 +19,5 @@
|
|||
false
|
||||
]
|
||||
},
|
||||
"hash": "7c48c7569b16e9600aa31fbf14b9ceeb195da7cde0082be861369ef4f997c534"
|
||||
"hash": "ec80b5dc41697e7df7112962aba8185040fc0a101edea4b7e0c23f6468300196"
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO user_query (\n version, first_name, last_name, email,\n hashed_password, is_admin, is_verified, deleted, user_id\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9\n );",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Bool",
|
||||
"Bool",
|
||||
"Bool",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f1b7a6581e20b9f1c0dabeb7a2479d70ab005cf787a0ed13260e65f7ea949136"
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM\n verification_otp\n WHERE\n user_id = $1\n AND\n purpose = $2\n AND\n secret = $3;",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "ffb4315a9b95ed39964c2a0a5a9dc45e4ab7941ced043931568023238d5127f7"
|
||||
}
|
|
@ -17,17 +17,16 @@ CREATE TABLE IF NOT EXISTS events
|
|||
|
||||
CREATE TABLE IF NOT EXISTS user_query
|
||||
(
|
||||
view_id text NOT NULL,
|
||||
version bigint CHECK (version >= 0) NOT NULL,
|
||||
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
first_name TEXT NOT NULL,
|
||||
last_name TEXT NOT NULL,
|
||||
user_id UUID NOT NULL UNIQUE,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
hashed_password TEXT NOT NULL,
|
||||
is_admin BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
is_verified BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
|
||||
PRIMARY KEY (view_id)
|
||||
PRIMARY KEY (user_id)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS user_username_index ON user_query (username);
|
||||
|
|
|
@ -6,6 +6,6 @@ CREATE TABLE IF NOT EXISTS verification_otp (
|
|||
secret VARCHAR(32) NOT NULL UNIQUE,
|
||||
created_at timestamp with time zone DEFAULT (CURRENT_TIMESTAMP),
|
||||
purpose TEXT NOT NULL,
|
||||
username TEXT NOT NULL,
|
||||
user_id UUID NOT NULL,
|
||||
ID SERIAL PRIMARY KEY NOT NULL
|
||||
);
|
||||
|
|
|
@ -15,8 +15,3 @@ CREATE TABLE IF NOT EXISTS cqrs_inventory_category_query
|
|||
|
||||
PRIMARY KEY (category_id)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS
|
||||
cqrs_inventory_store_query_category_id_index
|
||||
ON
|
||||
cqrs_inventory_category_query (category_id);
|
||||
|
|
|
@ -8,11 +8,9 @@ CREATE TABLE IF NOT EXISTS cqrs_inventory_store_query
|
|||
|
||||
name TEXT NOT NULL,
|
||||
address TEXT,
|
||||
owner TEXT NOT NULL,
|
||||
owner UUID NOT NULL,
|
||||
store_id UUID NOT NULL UNIQUE,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
|
||||
PRIMARY KEY (store_id)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS store_store_id_index ON cqrs_inventory_store_query (store_id);
|
||||
|
|
|
@ -11,12 +11,12 @@ use crate::identity::application::port::output::db::{create_verification_secret:
|
|||
impl CreateVerificationSecretOutDBPort for DBOutPostgresAdapter {
|
||||
async fn create_verification_secret(&self, msg: CreateSecretMsg) -> OutDBPortResult<()> {
|
||||
sqlx::query!(
|
||||
"INSERT INTO verification_otp (secret, created_at, purpose, username)
|
||||
"INSERT INTO verification_otp (secret, created_at, purpose, user_id)
|
||||
VALUES ($1, $2, $3, $4);",
|
||||
&msg.secret,
|
||||
OffsetDateTime::now_utc(),
|
||||
REGISTRATION_SECRET_PURPOSE,
|
||||
&msg.username,
|
||||
&msg.user_id,
|
||||
)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
|
@ -26,6 +26,8 @@ impl CreateVerificationSecretOutDBPort for DBOutPostgresAdapter {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::utils::uuid::tests::UUID;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[actix_rt::test]
|
||||
|
@ -40,7 +42,7 @@ mod tests {
|
|||
|
||||
let msg = CreateSecretMsgBuilder::default()
|
||||
.secret("secret".into())
|
||||
.username("username".into())
|
||||
.user_id(UUID.clone())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
|
|
|
@ -12,12 +12,12 @@ impl DeleteVerificationSecretOutDBPort for DBOutPostgresAdapter {
|
|||
"DELETE FROM
|
||||
verification_otp
|
||||
WHERE
|
||||
username = $1
|
||||
user_id = $1
|
||||
AND
|
||||
purpose = $2
|
||||
AND
|
||||
secret = $3;",
|
||||
msg.username,
|
||||
msg.user_id,
|
||||
REGISTRATION_SECRET_PURPOSE,
|
||||
msg.secret,
|
||||
)
|
||||
|
@ -30,13 +30,16 @@ impl DeleteVerificationSecretOutDBPort for DBOutPostgresAdapter {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::identity::application::port::output::db::{
|
||||
create_verification_secret::*, verification_secret_exists::*,
|
||||
use crate::{
|
||||
identity::application::port::output::db::{
|
||||
create_verification_secret::*, verification_secret_exists::*,
|
||||
},
|
||||
utils::uuid::tests::UUID,
|
||||
};
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_postgres_delete_verification_secret() {
|
||||
let username = "batman";
|
||||
let user_id = UUID;
|
||||
let secret = "bsdasdf";
|
||||
let settings = crate::settings::tests::get_settings().await;
|
||||
settings.create_db().await;
|
||||
|
@ -46,7 +49,7 @@ mod tests {
|
|||
.unwrap(),
|
||||
);
|
||||
let msg = VerifySecretExistsMsgBuilder::default()
|
||||
.username(username.into())
|
||||
.user_id(user_id.clone())
|
||||
.secret(secret.into())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
@ -56,7 +59,7 @@ mod tests {
|
|||
|
||||
let create_msg = CreateSecretMsgBuilder::default()
|
||||
.secret(secret.into())
|
||||
.username(username.into())
|
||||
.user_id(user_id.clone())
|
||||
.build()
|
||||
.unwrap();
|
||||
db.create_verification_secret(create_msg).await.unwrap();
|
||||
|
|
|
@ -33,6 +33,8 @@ impl EmailExistsOutDBPort for DBOutPostgresAdapter {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::utils::uuid::tests::*;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_postgres_email_exists() {
|
||||
let email = "foo@exmaple.com";
|
||||
|
@ -49,12 +51,13 @@ mod tests {
|
|||
|
||||
sqlx::query!(
|
||||
"INSERT INTO user_query
|
||||
(view_id, version, username, email, hashed_password)
|
||||
VALUES ($1, $2, $3, $4, $5);",
|
||||
"1",
|
||||
(user_id, version, first_name, email, hashed_password, last_name)
|
||||
VALUES ($1, $2, $3, $4, $5, $6);",
|
||||
&UUID,
|
||||
1,
|
||||
"foo",
|
||||
email,
|
||||
"passwd",
|
||||
"passwd"
|
||||
)
|
||||
.execute(&db.pool)
|
||||
|
|
|
@ -2,13 +2,15 @@
|
|||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::errors::*;
|
||||
use super::DBOutPostgresAdapter;
|
||||
use crate::identity::application::port::output::db::{errors::*, get_verification_secret::*};
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl GetVerificationSecretOutDBPort for DBOutPostgresAdapter {
|
||||
async fn get_verification_secret(&self, username: &str) -> OutDBPortResult<String> {
|
||||
async fn get_verification_secret(&self, user_id: &Uuid) -> OutDBPortResult<String> {
|
||||
struct Secret {
|
||||
secret: String,
|
||||
}
|
||||
|
@ -19,10 +21,10 @@ impl GetVerificationSecretOutDBPort for DBOutPostgresAdapter {
|
|||
FROM
|
||||
verification_otp
|
||||
WHERE
|
||||
username = $1
|
||||
user_id = $1
|
||||
AND
|
||||
purpose = $2;",
|
||||
username,
|
||||
user_id,
|
||||
REGISTRATION_SECRET_PURPOSE,
|
||||
)
|
||||
.fetch_one(&self.pool)
|
||||
|
@ -35,11 +37,14 @@ impl GetVerificationSecretOutDBPort for DBOutPostgresAdapter {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::identity::application::port::output::db::create_verification_secret::*;
|
||||
use crate::{
|
||||
identity::application::port::output::db::create_verification_secret::*,
|
||||
utils::uuid::tests::UUID,
|
||||
};
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_postgres_get_verification_secret() {
|
||||
let username = "batman";
|
||||
let user_id = UUID;
|
||||
let secret = "bsdasdf";
|
||||
let settings = crate::settings::tests::get_settings().await;
|
||||
settings.create_db().await;
|
||||
|
@ -49,19 +54,19 @@ mod tests {
|
|||
.unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
db.get_verification_secret(username).await.err(),
|
||||
db.get_verification_secret(&user_id).await.err(),
|
||||
Some(OutDBPortError::VerificationOTPSecretNotFound)
|
||||
);
|
||||
|
||||
let create_msg = CreateSecretMsgBuilder::default()
|
||||
.secret(secret.into())
|
||||
.username(username.into())
|
||||
.user_id(user_id.clone())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
db.create_verification_secret(create_msg).await.unwrap();
|
||||
|
||||
assert_eq!(db.get_verification_secret(username).await.unwrap(), secret);
|
||||
assert_eq!(db.get_verification_secret(&user_id).await.unwrap(), secret);
|
||||
|
||||
settings.drop_db().await;
|
||||
}
|
||||
|
|
|
@ -14,8 +14,8 @@ pub mod delete_verification_secret;
|
|||
pub mod email_exists;
|
||||
mod errors;
|
||||
pub mod get_verification_secret;
|
||||
pub mod user_id_exists;
|
||||
pub mod user_view;
|
||||
pub mod username_exists;
|
||||
pub mod verification_secret_exists;
|
||||
|
||||
#[derive(Clone)]
|
||||
|
|
|
@ -2,22 +2,24 @@
|
|||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::DBOutPostgresAdapter;
|
||||
use crate::identity::application::port::output::db::{
|
||||
errors::*, username_exists::UsernameExistsOutDBPort,
|
||||
errors::*, user_id_exists::UserIDExistsOutDBPort,
|
||||
};
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl UsernameExistsOutDBPort for DBOutPostgresAdapter {
|
||||
async fn username_exists(&self, username: &str) -> OutDBPortResult<bool> {
|
||||
impl UserIDExistsOutDBPort for DBOutPostgresAdapter {
|
||||
async fn user_id_exists(&self, user_id: &Uuid) -> OutDBPortResult<bool> {
|
||||
let res = sqlx::query!(
|
||||
"SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM user_query
|
||||
WHERE
|
||||
username = $1
|
||||
user_id = $1
|
||||
);",
|
||||
username
|
||||
user_id,
|
||||
)
|
||||
.fetch_one(&self.pool)
|
||||
.await?;
|
||||
|
@ -33,9 +35,12 @@ impl UsernameExistsOutDBPort for DBOutPostgresAdapter {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::utils::uuid::tests::UUID;
|
||||
|
||||
use crate::identity::domain::aggregate::*;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_postgres_username_exists() {
|
||||
let username = "foo@exmaple.com";
|
||||
async fn test_postgres_user_id_exists() {
|
||||
let settings = crate::settings::tests::get_settings().await;
|
||||
settings.create_db().await;
|
||||
let db = super::DBOutPostgresAdapter::new(
|
||||
|
@ -44,25 +49,28 @@ mod tests {
|
|||
.unwrap(),
|
||||
);
|
||||
|
||||
let user = User::default();
|
||||
|
||||
// state doesn't exist
|
||||
assert!(!db.username_exists(username).await.unwrap());
|
||||
assert!(!db.user_id_exists(&UUID).await.unwrap());
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO user_query
|
||||
(view_id, version, username, email, hashed_password)
|
||||
VALUES ($1, $2, $3, $4, $5);",
|
||||
"1",
|
||||
(version, user_id, email, hashed_password, first_name, last_name)
|
||||
VALUES ($1, $2, $3, $4, $5, $6);",
|
||||
1,
|
||||
username,
|
||||
"foo",
|
||||
"passwd"
|
||||
UUID,
|
||||
user.email(),
|
||||
user.hashed_password(),
|
||||
user.first_name(),
|
||||
user.last_name(),
|
||||
)
|
||||
.execute(&db.pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// state exists
|
||||
assert!(db.username_exists(username).await.unwrap());
|
||||
assert!(db.user_id_exists(&UUID).await.unwrap());
|
||||
|
||||
settings.drop_db().await;
|
||||
}
|
|
@ -6,18 +6,24 @@ use async_trait::async_trait;
|
|||
use cqrs_es::persist::GenericQuery;
|
||||
use cqrs_es::persist::{PersistenceError, ViewContext, ViewRepository};
|
||||
use cqrs_es::{EventEnvelope, Query, View};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::errors::*;
|
||||
use super::DBOutPostgresAdapter;
|
||||
use crate::identity::application::services::events::UserEvent;
|
||||
use crate::identity::domain::aggregate::User;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::utils::parse_aggregate_id::parse_aggregate_id;
|
||||
|
||||
pub const NEW_USER_NON_UUID: &str = "new_user_non_uuid-asdfa";
|
||||
|
||||
// The view for a User query, for a standard http application this should
|
||||
// be designed to reflect the response dto that will be returned to a user.
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct UserView {
|
||||
username: String,
|
||||
first_name: String,
|
||||
last_name: String,
|
||||
user_id: Uuid,
|
||||
email: String,
|
||||
hashed_password: String,
|
||||
is_admin: bool,
|
||||
|
@ -32,12 +38,14 @@ impl View<User> for UserView {
|
|||
fn update(&mut self, event: &EventEnvelope<User>) {
|
||||
match &event.payload {
|
||||
UserEvent::UserRegistered(val) => {
|
||||
self.username = val.username().into();
|
||||
self.email = val.email().into();
|
||||
self.hashed_password = val.hashed_password().into();
|
||||
self.is_admin = val.is_admin().to_owned();
|
||||
self.is_verified = val.is_verified().to_owned();
|
||||
self.deleted = false;
|
||||
self.first_name = val.first_name().into();
|
||||
self.last_name = val.last_name().into();
|
||||
self.user_id = val.user_id().clone();
|
||||
}
|
||||
UserEvent::UserDeleted => self.deleted = true,
|
||||
UserEvent::Loggedin(_) => (),
|
||||
|
@ -58,16 +66,21 @@ impl View<User> for UserView {
|
|||
|
||||
#[async_trait]
|
||||
impl ViewRepository<UserView, User> for DBOutPostgresAdapter {
|
||||
async fn load(&self, view_id: &str) -> Result<Option<UserView>, PersistenceError> {
|
||||
async fn load(&self, user_id: &str) -> Result<Option<UserView>, PersistenceError> {
|
||||
let user_id = match parse_aggregate_id(user_id, NEW_USER_NON_UUID)? {
|
||||
Some((val, _)) => return Ok(Some(val)),
|
||||
None => Uuid::parse_str(user_id).unwrap(),
|
||||
};
|
||||
|
||||
let res = sqlx::query_as!(
|
||||
UserView,
|
||||
"SELECT
|
||||
username, email, hashed_password, is_admin, is_verified, deleted
|
||||
first_name, last_name, user_id, email, hashed_password, is_admin, is_verified, deleted
|
||||
FROM
|
||||
user_query
|
||||
WHERE
|
||||
view_id = $1;",
|
||||
view_id
|
||||
user_id = $1;",
|
||||
user_id
|
||||
)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
|
@ -77,17 +90,22 @@ impl ViewRepository<UserView, User> for DBOutPostgresAdapter {
|
|||
|
||||
async fn load_with_context(
|
||||
&self,
|
||||
view_id: &str,
|
||||
user_id: &str,
|
||||
) -> Result<Option<(UserView, ViewContext)>, PersistenceError> {
|
||||
let user_id = match parse_aggregate_id(user_id, NEW_USER_NON_UUID)? {
|
||||
Some(val) => return Ok(Some(val)),
|
||||
None => Uuid::parse_str(user_id).unwrap(),
|
||||
};
|
||||
|
||||
let res = sqlx::query_as!(
|
||||
UserView,
|
||||
"SELECT
|
||||
username, email, hashed_password, is_admin, is_verified, deleted
|
||||
first_name, last_name, user_id, email, hashed_password, is_admin, is_verified, deleted
|
||||
FROM
|
||||
user_query
|
||||
WHERE
|
||||
view_id = $1;",
|
||||
view_id
|
||||
user_id = $1;",
|
||||
user_id
|
||||
)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
|
@ -95,24 +113,24 @@ impl ViewRepository<UserView, User> for DBOutPostgresAdapter {
|
|||
|
||||
struct Context {
|
||||
version: i64,
|
||||
view_id: String,
|
||||
user_id: String,
|
||||
}
|
||||
|
||||
let ctx = sqlx::query_as!(
|
||||
Context,
|
||||
"SELECT
|
||||
view_id, version
|
||||
user_id, version
|
||||
FROM
|
||||
user_query
|
||||
WHERE
|
||||
view_id = $1;",
|
||||
view_id
|
||||
user_id = $1;",
|
||||
user_id
|
||||
)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_err(PostgresAggregateError::from)?;
|
||||
|
||||
let view_context = ViewContext::new(ctx.view_id, ctx.version);
|
||||
let view_context = ViewContext::new(ctx.user_id.to_string(), ctx.version);
|
||||
Ok(Some((res, view_context)))
|
||||
}
|
||||
|
||||
|
@ -126,19 +144,20 @@ impl ViewRepository<UserView, User> for DBOutPostgresAdapter {
|
|||
let version = context.version + 1;
|
||||
sqlx::query!(
|
||||
"INSERT INTO user_query (
|
||||
view_id, version, username, email,
|
||||
hashed_password, is_admin, is_verified, deleted
|
||||
version, first_name, last_name, email,
|
||||
hashed_password, is_admin, is_verified, deleted, user_id
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9
|
||||
);",
|
||||
context.view_instance_id,
|
||||
version,
|
||||
view.username,
|
||||
view.first_name,
|
||||
view.last_name,
|
||||
view.email,
|
||||
view.hashed_password,
|
||||
view.is_admin,
|
||||
view.is_verified,
|
||||
view.deleted,
|
||||
view.user_id,
|
||||
)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
|
@ -150,16 +169,18 @@ impl ViewRepository<UserView, User> for DBOutPostgresAdapter {
|
|||
"UPDATE
|
||||
user_query
|
||||
SET
|
||||
view_id = $1, version = $2, username = $3, email = $4,
|
||||
hashed_password = $5, is_admin = $6, is_verified = $7, deleted = $8;",
|
||||
context.view_instance_id,
|
||||
user_id = $1, version = $2, first_name = $3, email = $4,
|
||||
hashed_password = $5, is_admin = $6, is_verified = $7, deleted = $8,
|
||||
last_name=$9;",
|
||||
view.user_id,
|
||||
version,
|
||||
view.username,
|
||||
view.first_name,
|
||||
view.email,
|
||||
view.hashed_password,
|
||||
view.is_admin,
|
||||
view.is_verified,
|
||||
view.deleted,
|
||||
view.last_name,
|
||||
)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
|
|
|
@ -16,13 +16,13 @@ impl VerificationSecretExistsOutDBPort for DBOutPostgresAdapter {
|
|||
SELECT 1
|
||||
FROM verification_otp
|
||||
WHERE
|
||||
username = $1
|
||||
user_id = $1
|
||||
AND
|
||||
purpose = $2
|
||||
AND
|
||||
secret = $3
|
||||
);",
|
||||
msg.username,
|
||||
msg.user_id,
|
||||
REGISTRATION_SECRET_PURPOSE,
|
||||
msg.secret,
|
||||
)
|
||||
|
@ -39,11 +39,14 @@ impl VerificationSecretExistsOutDBPort for DBOutPostgresAdapter {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::identity::application::port::output::db::create_verification_secret::*;
|
||||
use crate::{
|
||||
identity::application::port::output::db::create_verification_secret::*,
|
||||
utils::uuid::tests::UUID,
|
||||
};
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_postgres_verification_secret_exists() {
|
||||
let username = "batman";
|
||||
let user_id = UUID;
|
||||
let secret = "bsdasdf";
|
||||
let settings = crate::settings::tests::get_settings().await;
|
||||
settings.create_db().await;
|
||||
|
@ -53,7 +56,7 @@ mod tests {
|
|||
.unwrap(),
|
||||
);
|
||||
let msg = VerifySecretExistsMsgBuilder::default()
|
||||
.username(username.into())
|
||||
.user_id(user_id.clone())
|
||||
.secret(secret.into())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
@ -63,7 +66,7 @@ mod tests {
|
|||
|
||||
let create_msg = CreateSecretMsgBuilder::default()
|
||||
.secret(secret.into())
|
||||
.username(username.into())
|
||||
.user_id(user_id.clone())
|
||||
.build()
|
||||
.unwrap();
|
||||
db.create_verification_secret(create_msg).await.unwrap();
|
||||
|
|
|
@ -12,17 +12,17 @@ impl AccountValidationLinkOutMailerPort for LettreMailer {
|
|||
async fn account_validation_link(
|
||||
&self,
|
||||
to: &str,
|
||||
username: &str,
|
||||
first_name: &str,
|
||||
validation_secret: &str,
|
||||
) -> OutMailerPortResult<()> {
|
||||
let email = Message::builder()
|
||||
.from(self.from.parse().unwrap())
|
||||
.reply_to(self.reply_to.parse().unwrap())
|
||||
.to(format!("{username} <{to}>").parse().unwrap())
|
||||
.to(format!("{first_name} <{to}>").parse().unwrap())
|
||||
.subject("Please verify your account on Vanikam") // TODO: use better title
|
||||
.header(ContentType::TEXT_PLAIN)
|
||||
.body(format!(
|
||||
r#"Hello {username},
|
||||
r#"Hello {first_name},
|
||||
Please click here to verify your Vanikam account: {validation_secret}
|
||||
Warm regards,
|
||||
Vanikam Admin
|
||||
|
|
|
@ -1,325 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use async_trait::async_trait;
|
||||
use cqrs_es::Aggregate;
|
||||
|
||||
use crate::identity::application::services::errors::*;
|
||||
use crate::identity::application::services::events::UserEvent;
|
||||
use crate::identity::application::services::UserCommand;
|
||||
use crate::identity::application::services::UserServicesInterface;
|
||||
use crate::identity::domain::aggregate::User;
|
||||
use crate::identity::domain::aggregate::UserBuilder;
|
||||
|
||||
#[async_trait]
|
||||
impl Aggregate for User {
|
||||
type Command = UserCommand;
|
||||
type Event = UserEvent;
|
||||
type Error = IdentityError;
|
||||
type Services = std::sync::Arc<dyn UserServicesInterface>;
|
||||
|
||||
// This identifier should be unique to the system.
|
||||
fn aggregate_type() -> String {
|
||||
"account".to_string()
|
||||
}
|
||||
|
||||
// The aggregate logic goes here. Note that this will be the _bulk_ of a CQRS system
|
||||
// so expect to use helper functions elsewhere to keep the code clean.
|
||||
async fn handle(
|
||||
&self,
|
||||
command: Self::Command,
|
||||
services: &Self::Services,
|
||||
) -> Result<Vec<Self::Event>, Self::Error> {
|
||||
match command {
|
||||
UserCommand::RegisterUser(cmd) => {
|
||||
let res = services.register_user().register_user(cmd).await?;
|
||||
Ok(vec![UserEvent::UserRegistered(res)])
|
||||
}
|
||||
UserCommand::DeleteUser(cmd) => {
|
||||
services.delete_user().delete_user(cmd).await;
|
||||
Ok(vec![UserEvent::UserDeleted])
|
||||
}
|
||||
UserCommand::Login(cmd) => {
|
||||
let res = services.login().login(cmd).await;
|
||||
Ok(vec![UserEvent::Loggedin(res)])
|
||||
}
|
||||
UserCommand::UpdatePassword(cmd) => {
|
||||
let res = services.update_password().update_password(cmd).await;
|
||||
Ok(vec![UserEvent::PasswordUpdated(res)])
|
||||
}
|
||||
UserCommand::UpdateEmail(cmd) => {
|
||||
let res = services.update_email().update_email(cmd).await?;
|
||||
Ok(vec![UserEvent::EmailUpdated(res)])
|
||||
}
|
||||
UserCommand::MarkUserVerified(cmd) => {
|
||||
services
|
||||
.mark_user_verified()
|
||||
.mark_user_verified(cmd)
|
||||
.await?;
|
||||
Ok(vec![UserEvent::UserVerified])
|
||||
}
|
||||
UserCommand::SetAdmin(cmd) => {
|
||||
let res = services.set_user_admin().set_user_admin(cmd).await;
|
||||
Ok(vec![UserEvent::UserPromotedToAdmin(res)])
|
||||
}
|
||||
UserCommand::ResendVerificationEmail(cmd) => {
|
||||
services
|
||||
.resend_verification_email()
|
||||
.resend_verification_email(cmd)
|
||||
.await?;
|
||||
Ok(vec![UserEvent::VerificationEmailResent])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply(&mut self, event: Self::Event) {
|
||||
match event {
|
||||
UserEvent::UserRegistered(e) => {
|
||||
*self = UserBuilder::default()
|
||||
.username(e.username().into())
|
||||
.email(e.email().into())
|
||||
.hashed_password(e.hashed_password().into())
|
||||
.is_admin(e.is_admin().to_owned())
|
||||
.email_verified(e.email_verified().to_owned())
|
||||
.is_verified(e.is_verified().to_owned())
|
||||
.deleted(false)
|
||||
.build()
|
||||
.unwrap();
|
||||
}
|
||||
UserEvent::UserDeleted => {
|
||||
self.set_deleted(true);
|
||||
}
|
||||
UserEvent::Loggedin(_) => (),
|
||||
UserEvent::PasswordUpdated(_) => (),
|
||||
UserEvent::EmailUpdated(e) => {
|
||||
self.set_email(e.new_email().into());
|
||||
self.set_email_verified(false);
|
||||
}
|
||||
UserEvent::UserVerified => {
|
||||
self.set_is_verified(true);
|
||||
self.set_email_verified(true);
|
||||
}
|
||||
UserEvent::UserPromotedToAdmin(_) => {
|
||||
self.set_is_admin(true);
|
||||
}
|
||||
UserEvent::VerificationEmailResent => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//// The aggregate tests are the most important part of a CQRS system.
|
||||
//// The simplicity and flexibility of these tests are a good part of what
|
||||
//// makes an event sourced system so friendly to changing business requirements.
|
||||
//#[cfg(test)]
|
||||
//mod aggregate_tests {
|
||||
// use async_trait::async_trait;
|
||||
// use std::sync::Mutex;
|
||||
//
|
||||
// use cqrs_es::test::TestFramework;
|
||||
//
|
||||
// use crate::domain::aggregate::User;
|
||||
// use crate::domain::commands::UserCommand;
|
||||
// use crate::domain::events::UserEvent;
|
||||
// use crate::services::{AtmError, UserApi, UserServices, CheckingError};
|
||||
//
|
||||
// // A test framework that will apply our events and command
|
||||
// // and verify that the logic works as expected.
|
||||
// type AccountTestFramework = TestFramework<User>;
|
||||
//
|
||||
// #[test]
|
||||
// fn test_deposit_money() {
|
||||
// let expected = UserEvent::CustomerDepositedMoney {
|
||||
// amount: 200.0,
|
||||
// balance: 200.0,
|
||||
// };
|
||||
// let command = UserCommand::DepositMoney { amount: 200.0 };
|
||||
// let services = UserServices::new(Box::new(MockUserServices::default()));
|
||||
// // Obtain a new test framework
|
||||
// AccountTestFramework::with(services)
|
||||
// // In a test case with no previous events
|
||||
// .given_no_previous_events()
|
||||
// // Wnen we fire this command
|
||||
// .when(command)
|
||||
// // then we expect these results
|
||||
// .then_expect_events(vec![expected]);
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn test_deposit_money_with_balance() {
|
||||
// let previous = UserEvent::CustomerDepositedMoney {
|
||||
// amount: 200.0,
|
||||
// balance: 200.0,
|
||||
// };
|
||||
// let expected = UserEvent::CustomerDepositedMoney {
|
||||
// amount: 200.0,
|
||||
// balance: 400.0,
|
||||
// };
|
||||
// let command = UserCommand::DepositMoney { amount: 200.0 };
|
||||
// let services = UserServices::new(Box::new(MockUserServices::default()));
|
||||
//
|
||||
// AccountTestFramework::with(services)
|
||||
// // Given this previously applied event
|
||||
// .given(vec![previous])
|
||||
// // When we fire this command
|
||||
// .when(command)
|
||||
// // Then we expect this resultant event
|
||||
// .then_expect_events(vec![expected]);
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn test_withdraw_money() {
|
||||
// let previous = UserEvent::CustomerDepositedMoney {
|
||||
// amount: 200.0,
|
||||
// balance: 200.0,
|
||||
// };
|
||||
// let expected = UserEvent::CustomerWithdrewCash {
|
||||
// amount: 100.0,
|
||||
// balance: 100.0,
|
||||
// };
|
||||
// let services = MockUserServices::default();
|
||||
// services.set_atm_withdrawal_response(Ok(()));
|
||||
// let command = UserCommand::WithdrawMoney {
|
||||
// amount: 100.0,
|
||||
// atm_id: "ATM34f1ba3c".to_string(),
|
||||
// };
|
||||
//
|
||||
// AccountTestFramework::with(UserServices::new(Box::new(services)))
|
||||
// .given(vec![previous])
|
||||
// .when(command)
|
||||
// .then_expect_events(vec![expected]);
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn test_withdraw_money_client_error() {
|
||||
// let previous = UserEvent::CustomerDepositedMoney {
|
||||
// amount: 200.0,
|
||||
// balance: 200.0,
|
||||
// };
|
||||
// let services = MockUserServices::default();
|
||||
// services.set_atm_withdrawal_response(Err(AtmError));
|
||||
// let command = UserCommand::WithdrawMoney {
|
||||
// amount: 100.0,
|
||||
// atm_id: "ATM34f1ba3c".to_string(),
|
||||
// };
|
||||
//
|
||||
// let services = UserServices::new(Box::new(services));
|
||||
// AccountTestFramework::with(services)
|
||||
// .given(vec![previous])
|
||||
// .when(command)
|
||||
// .then_expect_error_message("atm rule violation");
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn test_withdraw_money_funds_not_available() {
|
||||
// let command = UserCommand::WithdrawMoney {
|
||||
// amount: 200.0,
|
||||
// atm_id: "ATM34f1ba3c".to_string(),
|
||||
// };
|
||||
//
|
||||
// let services = UserServices::new(Box::new(MockUserServices::default()));
|
||||
// AccountTestFramework::with(services)
|
||||
// .given_no_previous_events()
|
||||
// .when(command)
|
||||
// // Here we expect an error rather than any events
|
||||
// .then_expect_error_message("funds not available")
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn test_wrote_check() {
|
||||
// let previous = UserEvent::CustomerDepositedMoney {
|
||||
// amount: 200.0,
|
||||
// balance: 200.0,
|
||||
// };
|
||||
// let expected = UserEvent::CustomerWroteCheck {
|
||||
// check_number: "1170".to_string(),
|
||||
// amount: 100.0,
|
||||
// balance: 100.0,
|
||||
// };
|
||||
// let services = MockUserServices::default();
|
||||
// services.set_validate_check_response(Ok(()));
|
||||
// let services = UserServices::new(Box::new(services));
|
||||
// let command = UserCommand::WriteCheck {
|
||||
// check_number: "1170".to_string(),
|
||||
// amount: 100.0,
|
||||
// };
|
||||
//
|
||||
// AccountTestFramework::with(services)
|
||||
// .given(vec![previous])
|
||||
// .when(command)
|
||||
// .then_expect_events(vec![expected]);
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn test_wrote_check_bad_check() {
|
||||
// let previous = UserEvent::CustomerDepositedMoney {
|
||||
// amount: 200.0,
|
||||
// balance: 200.0,
|
||||
// };
|
||||
// let services = MockUserServices::default();
|
||||
// services.set_validate_check_response(Err(CheckingError));
|
||||
// let services = UserServices::new(Box::new(services));
|
||||
// let command = UserCommand::WriteCheck {
|
||||
// check_number: "1170".to_string(),
|
||||
// amount: 100.0,
|
||||
// };
|
||||
//
|
||||
// AccountTestFramework::with(services)
|
||||
// .given(vec![previous])
|
||||
// .when(command)
|
||||
// .then_expect_error_message("check invalid");
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn test_wrote_check_funds_not_available() {
|
||||
// let command = UserCommand::WriteCheck {
|
||||
// check_number: "1170".to_string(),
|
||||
// amount: 100.0,
|
||||
// };
|
||||
//
|
||||
// let services = UserServices::new(Box::new(MockUserServices::default()));
|
||||
// AccountTestFramework::with(services)
|
||||
// .given_no_previous_events()
|
||||
// .when(command)
|
||||
// .then_expect_error_message("funds not available")
|
||||
// }
|
||||
//
|
||||
// pub struct MockUserServices {
|
||||
// atm_withdrawal_response: Mutex<Option<Result<(), AtmError>>>,
|
||||
// validate_check_response: Mutex<Option<Result<(), CheckingError>>>,
|
||||
// }
|
||||
//
|
||||
// impl Default for MockUserServices {
|
||||
// fn default() -> Self {
|
||||
// Self {
|
||||
// atm_withdrawal_response: Mutex::new(None),
|
||||
// validate_check_response: Mutex::new(None),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl MockUserServices {
|
||||
// fn set_atm_withdrawal_response(&self, response: Result<(), AtmError>) {
|
||||
// *self.atm_withdrawal_response.lock().unwrap() = Some(response);
|
||||
// }
|
||||
// fn set_validate_check_response(&self, response: Result<(), CheckingError>) {
|
||||
// *self.validate_check_response.lock().unwrap() = Some(response);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #[async_trait]
|
||||
// impl UserApi for MockUserServices {
|
||||
// async fn atm_withdrawal(&self, _atm_id: &str, _amount: f64) -> Result<(), AtmError> {
|
||||
// self.atm_withdrawal_response.lock().unwrap().take().unwrap()
|
||||