diff --git a/.gitignore b/.gitignore index 60d0ea3..3d863fa 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,29 @@ ## Ignore File for backend service .cargo/config.toml +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +**/node_modules +**/.pnp +**.pnp.js + +# testing +**/coverage + +# production +**/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* +**.vscode + + diff --git a/Cargo.toml b/Cargo.toml index 05c0603..43aee1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,8 @@ members=[ "crates/libs/entity", "crates/libs/migration", "crates/services/engine", - "crates/services/api", "crates/workshop/webdriver", + "crates/services/api", "crates/workshop/webdriver", "crates/workshop/chrome", + "crates/macro/entity-macro", ] [workspace.package] @@ -27,6 +28,8 @@ entity = { path = "crates/libs/entity", default-features = true } migration = { path = "crates/libs/migration", default-features = true } engine = { path = "crates/services/engine", default-features = true } api = { path = "crates/services/api", default-features = true } +chrome = { path = "crates/workshop/chrome", default-features = true } +entity_macro = { path = "crates/macro/entity-macro", default-features = true } thiserror = "1.0.31" jsonwebtoken = "9.2.0" diff --git a/config/default.toml b/config/default.toml deleted file mode 100644 index 9ad0f30..0000000 --- a/config/default.toml +++ /dev/null @@ -1,8 +0,0 @@ -debug = false - -[connection] -db_backend = "Postgres" -database="postgres://jukbbjhs:T16p5KyqgrrERxCaZRP_nlxgGOFXvRhM@otto.db.elephantsql.com:5432/jukbbjhs" -selenium="http://34.72.113.188:4444/wd/hub" - - diff --git a/config/development.toml b/config/development.toml deleted file mode 100644 index e69de29..0000000 diff --git a/crates/libs/cerium/src/client/driver/web.rs b/crates/libs/cerium/src/client/driver/web.rs index 2996ff1..8e203d5 100644 --- a/crates/libs/cerium/src/client/driver/web.rs +++ b/crates/libs/cerium/src/client/driver/web.rs @@ -20,6 +20,7 @@ impl WebDriver { /// fn main() -> CeriumResult<()> { /// let _driver = WD::default(); /// let driver = WebDriver::new(_driver)?; + /// Ok(()) /// } /// ``` /// @@ -29,6 +30,10 @@ impl WebDriver { let helper = WebDriver { driver }; Ok(helper) } + + pub async fn session_id(&self) -> CeriumResult { + Ok(self.driver.session_id().await?.clone().to_string()) + } pub async fn default() -> CeriumResult { let mut caps = DesiredCapabilities::firefox(); diff --git a/crates/libs/cerium/src/client/storage/s3.rs b/crates/libs/cerium/src/client/storage/s3.rs index b7266f7..55c14dc 100644 --- a/crates/libs/cerium/src/client/storage/s3.rs +++ b/crates/libs/cerium/src/client/storage/s3.rs @@ -11,7 +11,19 @@ pub struct S3Client { region: Region, } +/// S3Client represents a client for interacting with Amazon S3 storage. impl S3Client { + /// Creates a new S3Client instance. + /// + /// # Arguments + /// + /// * `access_key` - The access key for authenticating with Amazon S3. + /// * `secret_key` - The secret key for authenticating with Amazon S3. + /// * `base_url` - The base URL for the Amazon S3 endpoint. + /// + /// # Returns + /// + /// A CeriumResult containing the new S3Client instance if successful, or an error if the creation fails. pub fn new(access_key: &str, secret_key: &str, base_url: &str) -> CeriumResult { let region = Region::Custom { region: "orca".to_string(), @@ -24,36 +36,61 @@ impl S3Client { credentials, region, }) - - // let response_data = bucket.put_object(format!("{id}.png").as_str(), content.as_slice()).await.expect("Got error"); - // - // let base_url = base_url.parse::()?; - // let static_provider = StaticProvider::new(access_key, secret_key, None); - // let client = Client::new(base_url.clone(), Some(Box::new(static_provider)), None, None)?; - // Ok(Self { - // client, - // }) } - /// Get Bucket will return the Bucket object for the Any bucket based action + /// Get Bucket will return the Bucket object for the Any bucket based action. + /// + /// # Arguments + /// + /// * `bucket_name` - The name of the bucket. + /// + /// # Returns + /// + /// A CeriumResult containing the Bucket object if successful, or an error if the retrieval fails. pub fn get_bucket(&self, bucket_name: &str) -> CeriumResult { let mut bucket = Bucket::new(bucket_name, self.region.clone(), self.credentials.clone())?; bucket.set_path_style(); Ok(bucket) } + /// Asynchronously lists the buckets. + /// + /// # Returns + /// + /// A CeriumResult indicating success or failure. pub async fn list_bucket(&self) -> CeriumResult<()> { // let _bucket_obj = self.get_bucket(bucket)? // let buckets =self.client.list_buckets(&args).await?; Ok(()) } + /// Asynchronously creates a new object in the specified bucket. + /// + /// # Arguments + /// + /// * `bucket` - The name of the bucket. + /// * `key` - The key of the object. + /// * `data` - The data of the object. + /// + /// # Returns + /// + /// A CeriumResult indicating success or failure. pub async fn create(&self, bucket: &str, key: &str, data: &[u8]) -> CeriumResult<()> { let _bucket_obj = self.get_bucket(bucket)?; let result = _bucket_obj.put_object(key, data).await?; Ok(()) } + /// Asynchronously deletes an object from the specified bucket. + /// + /// # Arguments + /// + /// * `bucket` - The name of the bucket. + /// * `key` - The key of the object. + /// + /// # Returns + /// + /// A CeriumResult indicating success or failure. pub async fn delete(&self, bucket: &String, key: &String) -> CeriumResult<()> { let _bucket_obj = self.get_bucket(bucket)?; let response = _bucket_obj.delete_object(key).await?; @@ -61,98 +98,3 @@ impl S3Client { Ok(()) } } -// -// #[cfg(test)] -// mod tests { -// use crate::client::storage::s3::S3Client; -// -// // S3Client can be created with valid access_key, secret_key, and base_url -// #[tokio::test] -// async fn test_s3client_creation_with_valid_credentials() { -// let access_key = "minioadmin"; -// let secret_key = "minioadmin"; -// let base_url = "http://localhost:9000"; -// -// let result = S3Client::new(access_key, secret_key, base_url); -// -// -// assert!(result.is_ok()); -// // let r = result.unwrap().list_bucket().await; -// let r = result.unwrap().create("orca", "id.png", "jj").await; -// assert!(r.is_ok()); -// } -// -// // S3Client can delete an object from a bucket with a valid key -// #[tokio::test] -// async fn test_s3client_delete_object_with_valid_key() { -// let access_key = "valid_access_key"; -// let secret_key = "valid_secret_key"; -// let base_url = "valid_base_url"; -// let bucket = String::from("valid_bucket"); -// let key = String::from("valid_key"); -// -// let s3_client = S3Client::new(access_key, secret_key, base_url).unwrap(); -// let result = s3_client.delete(&bucket, &key).await; -// -// assert!(result.is_ok()); -// } -// -// // S3Client cannot be created with invalid access_key, secret_key, or base_url -// #[test] -// fn test_s3client_creation_with_invalid_credentials() { -// let access_key = "invalid_access_key"; -// let secret_key = "invalid_secret_key"; -// let base_url = "invalid_base_url"; -// -// let result = S3Client::new(access_key, secret_key, base_url); -// -// assert!(result.is_err()); -// } -// -// // S3Client cannot delete an object from a non-existent bucket -// #[tokio::test] -// async fn test_s3client_delete_object_from_nonexistent_bucket() { -// let access_key = "valid_access_key"; -// let secret_key = "valid_secret_key"; -// let base_url = "valid_base_url"; -// let bucket = String::from("nonexistent_bucket"); -// let key = String::from("valid_key"); -// -// let s3_client = S3Client::new(access_key, secret_key, base_url).unwrap(); -// let result = s3_client.delete(&bucket, &key).await; -// -// assert!(result.is_err()); -// } -// -// // S3Client cannot delete a non-existent object from a bucket -// #[tokio::test] -// async fn test_s3client_delete_nonexistent_object_from_bucket() { -// let access_key = "valid_access_key"; -// let secret_key = "valid_secret_key"; -// let base_url = "valid_base_url"; -// let bucket = String::from("valid_bucket"); -// let key = String::from("nonexistent_key"); -// -// let s3_client = S3Client::new(access_key, secret_key, base_url).unwrap(); -// let result = s3_client.delete(&bucket, &key).await; -// -// assert!(result.is_err()); -// } -// -// // S3Client can handle errors returned by the S3 service -// #[tokio::test] -// async fn test_s3client_handle_s3_service_errors() { -// let access_key = "valid_access_key"; -// let secret_key = "valid_secret_key"; -// let base_url = "valid_base_url"; -// let bucket = String::from("valid_bucket"); -// let key = String::from("valid_key"); -// -// let s3_client = S3Client::new(access_key, secret_key, base_url).unwrap(); -// let result = s3_client.delete(&bucket, &key).await; -// -// assert!(result.is_err()); -// } -// -// -// } diff --git a/crates/libs/entity/Cargo.toml b/crates/libs/entity/Cargo.toml index 1ed9144..cedd78d 100644 --- a/crates/libs/entity/Cargo.toml +++ b/crates/libs/entity/Cargo.toml @@ -16,4 +16,5 @@ serde.workspace = true serde_json.workspace = true sea-orm.workspace=true sea-query.workspace=true -chrono.workspace=true \ No newline at end of file +chrono.workspace=true +entity_macro.workspace = true \ No newline at end of file diff --git a/crates/libs/entity/src/account/accounts.rs b/crates/libs/entity/src/account/accounts.rs new file mode 100644 index 0000000..9da2ba9 --- /dev/null +++ b/crates/libs/entity/src/account/accounts.rs @@ -0,0 +1,22 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.6.0 + +use sea_orm::entity::prelude::*; +use sea_orm::EntityTrait; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize)] +#[sea_orm(table_name = "account")] +pub struct Model { + #[serde(skip_deserializing)] + #[sea_orm(primary_key)] + pub id: Uuid, + pub name: String, + pub domain: String, + pub email: String, + pub owner: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/libs/entity/src/account/mod.rs b/crates/libs/entity/src/account/mod.rs new file mode 100644 index 0000000..5184f86 --- /dev/null +++ b/crates/libs/entity/src/account/mod.rs @@ -0,0 +1 @@ +pub mod accounts; \ No newline at end of file diff --git a/crates/libs/entity/src/api/component.rs b/crates/libs/entity/src/api/component.rs new file mode 100644 index 0000000..407439b --- /dev/null +++ b/crates/libs/entity/src/api/component.rs @@ -0,0 +1,134 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Root { + pub id: i32, + pub openapi: String, + pub info: Info, + pub external_docs: ExternalDocs, + pub servers: Vec, + pub tags: Vec, + pub paths: Vec, + pub components: Components, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Path { + pub id: i32, + pub path: String, + pub method: String, + pub tags: Vec, + pub summary: String, + pub description: String, + pub operation_id: String, + pub parameters: Vec, + pub responses: Value, + pub request_body: Value, + pub security: Vec, +} + + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Parameter { + pub name: String, + #[serde(rename = "in")] + pub in_field: String, + pub description: String, + pub required: bool, + pub explode: bool, + pub schema: Schema, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Schema { + #[serde(rename = "type")] + pub type_field: String, + pub default: Option, + pub format: Option, + #[serde(rename = "enum")] + pub enum_field: Option>, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Info { + pub title: String, + pub description: String, + pub terms_of_service: String, + pub contact: Contact, + pub license: License, + pub version: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Contact { + pub email: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct License { + pub name: String, + pub url: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ExternalDocs { + pub description: String, + pub url: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Server { + pub url: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Tag { + pub name: String, + pub description: String, + pub external_docs: Option, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ExternalDocs2 { + pub description: String, + pub url: String, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Components { + pub schemas: Value, +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Schemas { + // #[serde(rename = "Order")] + // pub order: Order, + // #[serde(rename = "Customer")] + // pub customer: Customer, + // #[serde(rename = "Address")] + // pub address: Address2, + // #[serde(rename = "Category")] + // pub category: Category, + // #[serde(rename = "User")] + // pub user: User2, + // #[serde(rename = "Tag")] + // pub tag: Tag2, + // #[serde(rename = "Pet")] + // pub pet: Pet2, + // #[serde(rename = "ApiResponse")] + // pub api_response: ApiResponse, +} \ No newline at end of file diff --git a/crates/libs/entity/src/api/mod.rs b/crates/libs/entity/src/api/mod.rs new file mode 100644 index 0000000..70ec722 --- /dev/null +++ b/crates/libs/entity/src/api/mod.rs @@ -0,0 +1 @@ +mod component; \ No newline at end of file diff --git a/crates/libs/entity/src/app/app.rs b/crates/libs/entity/src/app/app.rs index 602c0bd..e07b3b7 100644 --- a/crates/libs/entity/src/app/app.rs +++ b/crates/libs/entity/src/app/app.rs @@ -13,8 +13,22 @@ pub struct Model { pub name: String, pub description: Option, // pub created_by: Uuid, + // pub updated_by: Uuid, + // pub created_at: DateTimeWithTimeZone, + // pub updated_at: DateTimeWithTimeZone, } +// impl ActiveModelBehavior for ActiveModel { +// async fn before_save(mut self, _db: &C, _insert: bool) -> Result +// where +// C: ConnectionTrait, +// { +// self.updated_at = Set(Utc::now().into()); +// Ok(self) +// } +// +// } + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} diff --git a/crates/libs/entity/src/lib.rs b/crates/libs/entity/src/lib.rs index bfe3f2f..af1d5ff 100644 --- a/crates/libs/entity/src/lib.rs +++ b/crates/libs/entity/src/lib.rs @@ -8,3 +8,5 @@ pub mod common; pub mod core; pub mod prelude; pub mod test; +pub mod account; +pub mod api; diff --git a/crates/libs/entity/src/test/ui/case/case_block.rs b/crates/libs/entity/src/test/ui/case/case_block.rs index 7e0afad..55dac03 100644 --- a/crates/libs/entity/src/test/ui/case/case_block.rs +++ b/crates/libs/entity/src/test/ui/case/case_block.rs @@ -56,7 +56,8 @@ pub struct Model { #[serde(skip_deserializing)] pub execution_order: i32, pub kind: BlockKind, - // pub name: Option, + pub name: Option, + pub desc: Option, #[sea_orm(column_name = "type")] pub type_field: BlockType, pub reference: Option, diff --git a/crates/libs/entity/src/test/ui/log/item_log.rs b/crates/libs/entity/src/test/ui/log/item_log.rs new file mode 100644 index 0000000..eab5bce --- /dev/null +++ b/crates/libs/entity/src/test/ui/log/item_log.rs @@ -0,0 +1,99 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.6.0 + +use sea_orm::{EntityTrait, NotSet}; +use sea_orm::ActiveValue::Set; +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Deserialize, Serialize)] +#[sea_orm(rs_type = "String", db_type = "String(Some(5))", enum_name = "item_log_status")] +pub enum ItemLogStatus { + #[sea_orm(string_value = "SUCC")] + #[serde(rename = "Scheduled")] + Success, + #[sea_orm(string_value = "FAIL")] + #[serde(rename = "Trigger")] + Failed, + #[sea_orm(string_value = "SKIP")] + #[serde(rename = "Skipped")] + Skipped, + #[sea_orm(string_value = "RUN")] + #[serde(rename = "Running")] + Running, +} + +#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Deserialize, Serialize)] +#[sea_orm(rs_type = "String", db_type = "String(Some(5))", enum_name = "item_log_type")] +pub enum ItemLogType { + #[sea_orm(string_value = "A")] + #[serde(rename = "Action")] + Action, + #[sea_orm(string_value = "AG")] + #[serde(rename = "ActionGroup")] + ActionGroup, + #[sea_orm(string_value = "AS")] + #[serde(rename = "Assertion")] + Assertion, + #[sea_orm(string_value = "TC")] + #[serde(rename = "TestCase")] + TestCase, +} + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize)] +#[sea_orm(table_name = "item_log")] +pub struct Model { + #[serde(skip_deserializing)] + #[sea_orm(primary_key)] + pub id: i64, + pub ref_id: Uuid, + pub ref_type: ItemLogType, + + pub step_id: Uuid, + pub has_screenshot: bool, + pub has_recording: bool, + pub execution_time: i64, + pub status: ItemLogStatus, + pub log_id: Option, + pub created_at: DateTimeWithTimeZone, + pub created_by: String, + pub finished_at: DateTimeWithTimeZone, +} + +pub fn new(ref_id: Uuid, ref_type: ItemLogType, step_id: Uuid, log_id: Option) -> ActiveModel { + ActiveModel { + id: NotSet, + ref_id: Set(ref_id), + ref_type: Set(ref_type), + step_id: Set(step_id), + has_screenshot: Set(false), + has_recording: Set(false), + execution_time: Set(0), + status: Set(ItemLogStatus::Running), + log_id: Set(log_id), + created_at: Set(chrono::Utc::now().into()), + created_by: Set("System".to_string()), + finished_at: Set(chrono::Utc::now().into()), + } +} + + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(belongs_to = "Entity", from = "Column::Id", to = "Column::LogId")] + SelfReferencing, +} + +impl ActiveModelBehavior for ActiveModel {} + +pub struct SelfReferencingLink; + +impl Linked for SelfReferencingLink { + type FromEntity = Entity; + + type ToEntity = Entity; + + fn link(&self) -> Vec { + vec![Relation::SelfReferencing.def()] + } +} + diff --git a/crates/libs/entity/src/test/ui/log/mod.rs b/crates/libs/entity/src/test/ui/log/mod.rs new file mode 100644 index 0000000..9832ba7 --- /dev/null +++ b/crates/libs/entity/src/test/ui/log/mod.rs @@ -0,0 +1,5 @@ +pub use item_log::Model as ItemLog; + +pub mod item_log; + + diff --git a/crates/libs/entity/src/test/ui/mod.rs b/crates/libs/entity/src/test/ui/mod.rs index 58ca900..101dab2 100644 --- a/crates/libs/entity/src/test/ui/mod.rs +++ b/crates/libs/entity/src/test/ui/mod.rs @@ -1,6 +1,12 @@ +pub use request::Model as ExecutionRequest; + pub mod action; pub mod case; pub mod elements; pub mod screen; pub mod suit; -mod object_repository; +pub mod object_repository; +pub mod log; +pub mod request; + + diff --git a/crates/libs/entity/src/test/ui/request.rs b/crates/libs/entity/src/test/ui/request.rs new file mode 100644 index 0000000..8daa43d --- /dev/null +++ b/crates/libs/entity/src/test/ui/request.rs @@ -0,0 +1,111 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.6.0 + +use sea_orm::{EntityTrait, NotSet}; +use sea_orm::ActiveValue::Set; +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Deserialize, Serialize)] +#[sea_orm( +rs_type = "String", +db_type = "String(Some(10))", +enum_name = "execution_kind" +)] +pub enum ExecutionKind { + #[sea_orm(string_value = "Scheduled")] + #[serde(rename = "Scheduled")] + Scheduled, + #[sea_orm(string_value = "Trigger")] + #[serde(rename = "Trigger")] + Trigger, +} + +#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Deserialize, Serialize)] +#[sea_orm( +rs_type = "String", +db_type = "String(Some(10))", +enum_name = "execution_type" +)] +pub enum ExecutionType { + #[sea_orm(string_value = "TestCase")] + #[serde(rename = "TestCase")] + TestCase, + #[sea_orm(string_value = "TestSuite")] + #[serde(rename = "TestSuite")] + TestSuite, +} + +#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Deserialize, Serialize)] +#[sea_orm( +rs_type = "String", +db_type = "String(Some(10))", +enum_name = "execution_status" +)] +pub enum ExecutionStatus { + #[sea_orm(string_value = "Started")] + #[serde(rename = "Started")] + Started, + #[sea_orm(string_value = "Running")] + #[serde(rename = "Running")] + Running, + #[sea_orm(string_value = "Completed")] + #[serde(rename = "Completed")] + Completed, + #[sea_orm(string_value = "Failed")] + #[serde(rename = "Failed")] + Failed, +} + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize)] +#[sea_orm(table_name = "execution_request")] +pub struct Model { + #[serde(skip_deserializing)] + #[sea_orm(primary_key)] + pub id: i64, + pub description: Option, + pub is_dry_run: bool, + pub ref_id: Uuid, + pub ref_type: ExecutionType, + + pub kind: ExecutionKind, + pub status: ExecutionStatus, + pub args: Option, + + pub log_id: i32, + pub created_at: DateTimeWithTimeZone, + pub created_by: Option, + pub finished_at: DateTimeWithTimeZone, + pub updated_at: DateTimeWithTimeZone, +} + +pub fn new( + ref_id: Uuid, + ref_type: ExecutionType, + kind: ExecutionKind, + status: ExecutionStatus, + log_id: i32, + is_dry_run: bool, + desc: Option, +) -> ActiveModel { + ActiveModel { + id: NotSet, + description: Set(desc), + is_dry_run: Set(is_dry_run), + ref_id: Set(ref_id), + ref_type: Set(ref_type), + kind: Set(kind), + status: Set(status), + args: NotSet, + log_id: Set(log_id), + created_at: Set(chrono::Utc::now().into()), + created_by: NotSet, + finished_at: Set(chrono::Utc::now().into()), + updated_at: Set(chrono::Utc::now().into()), + } +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + + +impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/libs/migration/src/migration001.rs b/crates/libs/migration/src/migration001.rs index 8d68bf5..393fd0b 100644 --- a/crates/libs/migration/src/migration001.rs +++ b/crates/libs/migration/src/migration001.rs @@ -1,16 +1,19 @@ -use entity::admin::user; use sea_orm_migration::prelude::*; -use crate::sea_orm::{ConnectionTrait, Statement}; +use entity::admin::user; use entity::app::app; use entity::command; use entity::prelude::{case, case_block, data_binding}; -use entity::test::ui::action::{action, data, group as action_group, target}; -use entity::test::ui::suit::{suite, suite_block}; use entity::test::{ datatable, field, profile::{data as profile_data, profile}, }; +use entity::test::ui::action::{action, data, group as action_group, target}; +use entity::test::ui::log::item_log; +use entity::test::ui::request; +use entity::test::ui::suit::{suite, suite_block}; + +use crate::sea_orm::{ConnectionTrait, Statement}; #[derive(DeriveMigrationName)] pub struct Migration; @@ -181,6 +184,8 @@ impl MigrationTrait for Migration { .not_null(), ) .col(ColumnDef::new(case_block::Column::Kind).string().not_null()) + .col(ColumnDef::new(case_block::Column::Name).string()) + .col(ColumnDef::new(case_block::Column::Desc).string()) .col(ColumnDef::new(case_block::Column::Reference).uuid()) .col(ColumnDef::new(case_block::Column::ParentId).uuid()) .col(ColumnDef::new(case_block::Column::CaseId).uuid().not_null()) @@ -452,39 +457,112 @@ impl MigrationTrait for Migration { ) .await?; - // manager.create_table(Table::create() - // .table(target::Entity) - // .if_not_exists() - // .col(ColumnDef::new(target::Column::Id).uuid().not_null().primary_key()) - // .col(ColumnDef::new(target::Column::Kind).string().not_null()) - // .col(ColumnDef::new(target::Column::Value).string().not_null()) - // .col(ColumnDef::new(target::Column::ActionId).uuid().not_null()) - // .foreign_key( - // ForeignKey::create() - // .from(target::Entity, target::Column::ActionId) - // .to(action::Entity, action::Column::Id) - // .on_delete(ForeignKeyAction::Cascade) - // .on_update(ForeignKeyAction::Cascade) - // ) - // .to_owned(), - // ).await?; - // - // manager.create_table(Table::create() - // .table(data::Entity) - // .if_not_exists() - // .col(ColumnDef::new(data::Column::Id).uuid().not_null().primary_key()) - // .col(ColumnDef::new(data::Column::Kind).string().not_null()) - // .col(ColumnDef::new(data::Column::Value).string().not_null()) - // .col(ColumnDef::new(data::Column::ActionId).uuid().not_null()) - // .foreign_key( - // ForeignKey::create() - // .from(data::Entity, data::Column::ActionId) - // .to(action::Entity, action::Column::Id) - // .on_delete(ForeignKeyAction::Cascade) - // .on_update(ForeignKeyAction::Cascade) - // ) - // .to_owned(), - // ).await?; + manager + .create_table( + Table::create() + .table(item_log::Entity) + .if_not_exists() + .col( + ColumnDef::new(item_log::Column::Id) + .integer() + .not_null() + .primary_key(), + ) + .col( + ColumnDef::new(item_log::Column::RefId) + .uuid() + .not_null(), + ) + .col( + ColumnDef::new(item_log::Column::RefType) + .string_len(5) + .not_null(), + ) + .col( + ColumnDef::new(item_log::Column::StepId) + .uuid() + .not_null(), + ) + .col( + ColumnDef::new(item_log::Column::HasScreenshot) + .boolean() + .not_null().default(false), + ) + .col( + ColumnDef::new(item_log::Column::HasRecording) + .boolean() + .not_null().default(false), + ) + .col( + ColumnDef::new(item_log::Column::ExecutionTime) + .integer() + .not_null(), + ) + .col( + ColumnDef::new(item_log::Column::Status) + .string_len(5) + .not_null(), + ) + .col( + ColumnDef::new(item_log::Column::LogId) + .integer() + .not_null(), + ) + .col(ColumnDef::new(item_log::Column::CreatedBy).string().not_null()) + .col(ColumnDef::new(item_log::Column::CreatedAt).timestamp_with_time_zone().not_null()) + .col(ColumnDef::new(item_log::Column::FinishedAt).timestamp_with_time_zone().not_null()) + .to_owned(), + ) + .await?; + + manager + .create_table( + Table::create() + .table(request::Entity) + .if_not_exists() + .col( + ColumnDef::new(request::Column::Id) + .integer() + .not_null() + .primary_key(), + ) + .col( + ColumnDef::new(request::Column::Description) + .string(), + ) + .col( + ColumnDef::new(request::Column::IsDryRun) + .boolean(), + ) + .col( + ColumnDef::new(request::Column::RefType) + .string_len(5).not_null(), + ) + .col( + ColumnDef::new(request::Column::RefId) + .uuid() + .not_null(), + ) + .col( + ColumnDef::new(request::Column::Kind) + .string_len(10) + .not_null(), + ) + .col( + ColumnDef::new(request::Column::Args) + .json(), + ) + .col( + ColumnDef::new(request::Column::LogId) + .integer() + .not_null(), + ) + .col(ColumnDef::new(item_log::Column::CreatedBy).string().not_null()) + .col(ColumnDef::new(item_log::Column::CreatedAt).timestamp_with_time_zone().not_null()) + .col(ColumnDef::new(item_log::Column::FinishedAt).timestamp_with_time_zone().not_null()) + .to_owned(), + ) + .await?; Ok(()) } diff --git a/crates/libs/migration/src/migration002.rs b/crates/libs/migration/src/migration002.rs index 132e0bd..3436d2f 100644 --- a/crates/libs/migration/src/migration002.rs +++ b/crates/libs/migration/src/migration002.rs @@ -135,6 +135,8 @@ impl MigrationTrait for Migration { execution_order: Set(1), kind: Set(BlockKind::Reference), type_field: Set(BlockType::ActionGroup), + name: Set(Some(app_g.name.clone())), + desc: Set(app_g.description.clone()), reference: Set(Some(app_g.clone().id)), case_id: Set(case_m.clone().id), ..Default::default() @@ -146,6 +148,8 @@ impl MigrationTrait for Migration { execution_order: Set(2), kind: Set(BlockKind::Reference), type_field: Set(BlockType::Assertion), + name: Set(Some(assert_g_m.name.clone())), + desc: Set(assert_g_m.description.clone()), reference: Set(Some(assert_g_m.clone().id)), case_id: Set(case_m.clone().id), ..Default::default() @@ -156,6 +160,8 @@ impl MigrationTrait for Migration { id: Set(uuid1.clone()), execution_order: Set(3), kind: Set(BlockKind::SelfReference), + name: Set(Some("This is a condition block".to_string())), + desc: Set(Some("This is a condition block".to_string())), type_field: Set(BlockType::Condition), case_id: Set(case_m.clone().id), ..Default::default() @@ -166,6 +172,8 @@ impl MigrationTrait for Migration { id: Set(uuid2.clone()), execution_order: Set(1), kind: Set(BlockKind::SelfReference), + name: Set(Some("This is yes block".to_string())), + desc: Set(Some("This is yes condition block".to_string())), type_field: Set(BlockType::YesCase), parent_id: Set(Some(uuid1.clone())), case_id: Set(case_m.clone().id), @@ -177,6 +185,8 @@ impl MigrationTrait for Migration { id: Set(uuid3.clone()), execution_order: Set(2), kind: Set(BlockKind::SelfReference), + name: Set(Some("This is no block".to_string())), + desc: Set(Some("This is no condition block".to_string())), type_field: Set(BlockType::NoCase), parent_id: Set(Some(uuid1.clone())), case_id: Set(case_m.clone().id), @@ -189,6 +199,8 @@ impl MigrationTrait for Migration { id: Set(Uuid::new_v4()), execution_order: Set(1), kind: Set(BlockKind::Reference), + name: Set(Some(app_g.name.clone())), + desc: Set(app_g.description.clone()), type_field: Set(BlockType::ActionGroup), reference: Set(Some(app_g.clone().id)), parent_id: Set(Some(uuid2.clone())), diff --git a/crates/macro/entity-macro/src/model/app.rs b/crates/macro/entity-macro/src/model/app.rs new file mode 100644 index 0000000..24b31b8 --- /dev/null +++ b/crates/macro/entity-macro/src/model/app.rs @@ -0,0 +1,44 @@ + +use sea_orm::entity::prelude::*; +use sea_orm::prelude::async_trait::async_trait; +use serde::{Deserialize, Serialize}; + +#[async_trait] +trait CustomActiveModelBehavior: ActiveModelBehavior { + async fn before_save(mut self, _db: &C, _insert: bool) -> Result + where + C: ConnectionTrait, + { + self.updated_at = Set(Utc::now().into()); + Ok(self) + } +} + + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize)] +#[sea_orm(table_name = "application")] +pub struct Model { + #[serde(skip_deserializing)] + #[sea_orm(primary_key)] + pub id: Uuid, + pub name: String, + pub description: Option, + + pub created_by: Uuid, + pub updated_by: Uuid, + pub created_at: DateTimeWithTimeZone, + pub updated_at: DateTimeWithTimeZone, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel { + // async fn before_save(mut self, _db: &C, _insert: bool) -> Result + // where + // C: ConnectionTrait, + // { + // self.updated_at = Set(Utc::now().into()); + // Ok(self) + // } +} \ No newline at end of file diff --git a/crates/macro/entity-macro/src/model/mod.rs b/crates/macro/entity-macro/src/model/mod.rs new file mode 100644 index 0000000..02c0277 --- /dev/null +++ b/crates/macro/entity-macro/src/model/mod.rs @@ -0,0 +1 @@ +pub mod app; \ No newline at end of file diff --git a/crates/macro/entity-macro/tests/check.rs b/crates/macro/entity-macro/tests/check.rs new file mode 100644 index 0000000..4705483 --- /dev/null +++ b/crates/macro/entity-macro/tests/check.rs @@ -0,0 +1,54 @@ + + +mod check { +} + + +#[cfg(test)] +mod tests { + // use sea_orm::{ActiveModelBehavior, DeriveEntityModel, DeriveRelation, EnumIter}; + use sea_orm::prelude::{DateTimeWithTimeZone, Uuid}; + use entity_macro::MyDerive; + use sea_orm::entity::prelude::*; + use sea_orm::EntityTrait; + use serde::{Deserialize, Serialize}; + + // #[derive(MyDerive, Debug)] + // struct CaseController { + // _name: String, + // _age: i32, + #[derive(Clone, Debug, PartialEq, MyDerive, DeriveEntityModel, Deserialize, Serialize)] + #[sea_orm(table_name = "application")] + pub struct Model { + #[serde(skip_deserializing)] + #[sea_orm(primary_key)] + pub id: Uuid, + pub name: String, + pub description: Option, + + pub created_by: Uuid, + pub updated_by: Uuid, + pub created_at: DateTimeWithTimeZone, + pub updated_at: DateTimeWithTimeZone, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel { + // async fn before_save(mut self, _db: &C, _insert: bool) -> Result + // where + // C: ConnectionTrait, + // { + // self.updated_at = Set(Utc::now().into()); + // Ok(self) + // } + } + + + #[test] + fn dry_run_test_controller() { + let c = Model{_name: "mani".to_string(), _age: 32}; + println!("got the controller, c = {:?}", c); + } +} \ No newline at end of file diff --git a/crates/services/api/src/route/auth/mod.rs b/crates/services/api/src/route/auth/mod.rs new file mode 100644 index 0000000..a5e5a77 --- /dev/null +++ b/crates/services/api/src/route/auth/mod.rs @@ -0,0 +1,40 @@ +use axum::{Extension, Json, Router}; +use axum::http::StatusCode; +use axum::response::IntoResponse; +use axum::routing::post; +use serde::{Deserialize, Serialize}; +use serde_json::json; + +use crate::error::InternalResult; +use crate::server::session::OrcaSession; +use crate::service::admin::auth::AuthService; + +pub(crate) fn auth_route() -> Router { + Router::new() + .nest("/auth", + Router::new() + .route("/login", post(user_login)) + // .route("/password/reset", post(user_login)) + // .route("/signup", post(user_login)) + // .route("/google", post(user_login)) + // .route("/sso/callback", post(user_login)) + ) + // .route("/login", post(user_login)) +} + +#[derive(Deserialize, Serialize)] +pub struct UserLogin { + pub email: String, + pub password: String, +} + +/// user_login - login using username and password for the orca application +async fn user_login( + Extension(session): Extension, + Json(body): Json, +) -> InternalResult { + let _result = AuthService::new(session) + .auth_user(body.email, body.password) + .await?; + Ok((StatusCode::OK, Json(json!({"message": "Login Success"})))) +} diff --git a/crates/services/api/src/route/mod.rs b/crates/services/api/src/route/mod.rs index fe86379..148c4b7 100644 --- a/crates/services/api/src/route/mod.rs +++ b/crates/services/api/src/route/mod.rs @@ -1,10 +1,12 @@ use crate::route::public::local_route; use axum::Router; use serde::Deserialize; +use crate::route::auth::auth_route; pub(crate) mod admin; pub(crate) mod app; pub(crate) mod public; +pub(crate) mod auth; fn v1_route() -> Router { Router::new() @@ -16,6 +18,7 @@ pub fn handle_router() -> Router { let api_routes = Router::new().nest("/v1", v1_route()); let routes = Router::new() .nest("/", local_route()) + .nest("/auth", auth_route()) .nest("/api", api_routes); routes } diff --git a/crates/services/api/src/server/middleware/mod.rs b/crates/services/api/src/server/middleware/mod.rs index 440880a..47964fe 100644 --- a/crates/services/api/src/server/middleware/mod.rs +++ b/crates/services/api/src/server/middleware/mod.rs @@ -50,6 +50,7 @@ where fn call(&mut self, mut request: Request) -> Self::Future { let ext = request.extensions_mut(); + info!(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>--------------BEFORE REQUEST------------------>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); let trx = block_on(self.db.begin()).expect("got error on trx"); ext.insert(OrcaSession::new(trx.clone())); let future = self.inner.call(request); @@ -58,6 +59,7 @@ where let headers = response.headers_mut(); trx.commit().await.expect("TODO: panic message"); info!("headers - {:?}", headers); + info!(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>--------------AFTER REQUEST------------------>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); Ok(response) }) } diff --git a/crates/services/api/src/service/admin/auth.rs b/crates/services/api/src/service/admin/auth.rs new file mode 100644 index 0000000..77613e3 --- /dev/null +++ b/crates/services/api/src/service/admin/auth.rs @@ -0,0 +1,36 @@ +use crate::error::{InternalResult, OrcaRepoError}; +use crate::route::Pagination; +use crate::server::session::OrcaSession; +use entity::admin::user; +use entity::admin::user::{ActiveModel, Column as UserColumn, Model}; +use sea_orm::{ActiveModelTrait, ColumnTrait, DatabaseTransaction, EntityTrait, NotSet, QueryFilter, QuerySelect, TryIntoModel}; +use sea_query::Condition; +use tracing::info; + +pub(crate) struct AuthService(OrcaSession); + +impl AuthService { + pub fn new(session: OrcaSession) -> Self { + Self(session) + } + + pub fn trx(&self) -> &DatabaseTransaction { + self.0.trx() + } + + pub async fn auth_user(&self, email: String, password: String) -> InternalResult { + let condition = Condition::all().add(UserColumn::Email.eq(email)); + let user = user::Entity::find().filter(condition).one(self.trx()).await?; + if user.is_none() { + return Err(OrcaRepoError::ModelNotFound( + "User".to_string(), + "email".to_string(), + ))?; + } + let user = user.unwrap(); + if "password".to_string() != password { + return Err(OrcaRepoError::InvalidUsername(user.id))?; + } + return Ok(user); + } +} diff --git a/crates/services/api/src/service/admin/mod.rs b/crates/services/api/src/service/admin/mod.rs index 68a79f5..f09c8f8 100644 --- a/crates/services/api/src/service/admin/mod.rs +++ b/crates/services/api/src/service/admin/mod.rs @@ -1 +1,2 @@ pub(crate) mod user; +pub(crate) mod auth; diff --git a/crates/services/api/src/service/app/case.rs b/crates/services/api/src/service/app/case.rs index 6071588..3770d62 100644 --- a/crates/services/api/src/service/app/case.rs +++ b/crates/services/api/src/service/app/case.rs @@ -1,15 +1,15 @@ use async_recursion::async_recursion; -use sea_orm::ActiveValue::Set; use sea_orm::{ ActiveModelTrait, ColumnTrait, DatabaseTransaction, EntityTrait, IntoActiveModel, ModelTrait, QueryFilter, QueryOrder, QuerySelect, TryIntoModel, }; +use sea_orm::ActiveValue::Set; use sea_query::{Condition, Expr}; use tracing::{debug, info}; use uuid::Uuid; -use cerium::client::driver::web::WebDriver; use cerium::client::Client; +use cerium::client::driver::web::WebDriver; use engine::controller::case::CaseController; use entity::prelude::case::{Column, Entity, Model}; use entity::prelude::case_block::{ @@ -17,7 +17,8 @@ use entity::prelude::case_block::{ Model as BlockModel, SelfReferencingLink, }; use entity::test::history; -use entity::test::history::{ExecutionKind, ExecutionStatus, ExecutionType}; +use entity::test::ui::{ExecutionRequest, request}; +use entity::test::ui::request::{ExecutionKind, ExecutionStatus, ExecutionType, new}; use crate::error::{InternalResult, OrcaRepoError}; use crate::server::session::OrcaSession; @@ -34,23 +35,6 @@ impl CaseService { self.0.trx() } - pub async fn create_history( - &self, - case_id: Uuid, - desc: Option, - ) -> InternalResult { - let history = HistoryService::new(self.0.clone()) - .create_history( - case_id, - ExecutionKind::Trigger, - ExecutionType::TestCase, - desc, - Some(true), - ) - .await?; - Ok(history) - } - /// list all the test suites in the Orca Application pub(crate) async fn list_cases(&self) -> InternalResult> { let cases = Entity::find() @@ -215,23 +199,33 @@ impl CaseService { ))?; } let _case = case.unwrap(); - let history = self - .create_history( - case_id, - Some(format!("Executing - {case_name}", case_name = _case.name)), - ) - .await?; + let er_am = new(case_id, ExecutionType::TestCase, ExecutionKind::Trigger, ExecutionStatus::Started, 0, false, None); + // let er = ExecutionRequest { + // description: Some(format!("Executing - {case_name}", case_name = _case.name)), + // is_dry_run: true, + // ref_id: case_id, + // ref_type: ExecutionType::TestCase, + // kind: ExecutionKind::Trigger, + // status: ExecutionStatus::Started, + // args: None, + // log_id: 0, + // created_at: chrono::Utc::now().into(), + // created_by: Some("system".to_string()), + // finished_at: None, + // updated_at: None, + // }; + let mut er_am = er_am.save(self.trx()).await?; let ui_driver = WebDriver::default().await?; let controller = CaseController::new(self.trx(), ui_driver.clone(), self.1.clone()); - controller.process(&_case).await?; + let er = er_am.clone().try_into_model()?; + controller.process(&_case, &er, None).await?; ui_driver.quit().await?; - let mut _history = history.into_active_model(); - _history.status = Set(ExecutionStatus::Completed); - _history.description = Set(Some(format!( - "Executed - {case_name}", - case_name = _case.name - ))); - _history.save(self.trx()).await?; + + er_am.status = Set(ExecutionStatus::Completed); + er_am.finished_at = Set(chrono::Utc::now().into()); + er_am.updated_at = Set(chrono::Utc::now().into()); + er_am.save(self.trx()).await?; + Ok(()) } diff --git a/crates/services/engine/Cargo.toml b/crates/services/engine/Cargo.toml index 8d8eb50..88eae5b 100644 --- a/crates/services/engine/Cargo.toml +++ b/crates/services/engine/Cargo.toml @@ -32,10 +32,11 @@ serde.workspace = true serde_json.workspace = true thiserror.workspace = true tokio.workspace = true -tracing.workspace=true -tracing-subscriber.workspace=true -rust-s3.workspace=true +tracing.workspace = true +tracing-subscriber.workspace = true +rust-s3.workspace = true thirtyfour.workspace = true anyhow = "1.0.79" +chrono = "0.4.31" diff --git a/crates/services/engine/src/controller/action.rs b/crates/services/engine/src/controller/action.rs index e807921..b484a68 100644 --- a/crates/services/engine/src/controller/action.rs +++ b/crates/services/engine/src/controller/action.rs @@ -1,18 +1,20 @@ use s3::Region; -use sea_orm::prelude::Uuid; -use sea_orm::{ - ColumnTrait, DatabaseTransaction, EntityTrait, ModelTrait, PaginatorTrait, QueryFilter, - QueryOrder, -}; +use sea_orm::{ActiveModelTrait, ColumnTrait, DatabaseTransaction, EntityTrait, IntoActiveModel, ModelTrait, PaginatorTrait, QueryFilter, QueryOrder, TryIntoModel}; +use sea_orm::ActiveValue::Set; +use sea_orm::prelude::{DateTimeWithTimeZone, Uuid}; use thirtyfour::By; use tracing::info; +use cerium::client::Client; use cerium::client::driver::web::WebDriver; use cerium::client::storage::s3::S3Client; -use cerium::client::Client; use entity::prelude::target::ActionTargetKind; +use entity::test::ui::{ExecutionRequest, request}; use entity::test::ui::action::action; use entity::test::ui::action::action::ActionKind; +use entity::test::ui::action::group::{Entity as ActionGroupEntity, Model as ActionGroupModel}; +use entity::test::ui::log::{item_log, ItemLog}; +use entity::test::ui::log::item_log::{ItemLogStatus, ItemLogType, new}; use crate::error::{EngineError, EngineResult}; @@ -24,32 +26,25 @@ pub struct ActionController<'ccl> { } impl<'ccl> ActionController<'ccl> { - /// new - this will create new Action Controller for the application - /// Creates a new instance of `ActionController` with the provided `db` and `drive` references. + /// Constructs a new `ActionController` instance. /// /// # Arguments /// - /// * `db` - A reference to a `DatabaseConnection` object. - /// * `drive` - A reference to a `UIHelper` object. - /// - /// # Example + /// * `db` - A reference to a `DatabaseTransaction` instance. + /// * `driver` - A `WebDriver` instance. + /// * `client` - A `Client` instance. /// - /// ``` - /// use sea_orm::{DatabaseConnection, DatabaseTransaction}; - /// use cerium::client::driver::web::WebDriver; - /// use engine::controller::action::ActionController; - /// use engine::server::driver::UIHelper; + /// # Returns /// - /// let db = DatabaseTransaction::new(); - /// let driver = WebDriver::default(); - /// let controller = ActionController::new(&db, driver); - /// ``` + /// Returns a new `ActionController` instance. pub fn new( db: &'ccl DatabaseTransaction, driver: WebDriver, client: Client, ) -> ActionController<'ccl> { + // Clone the storage client from the provided client let storage_cli = client.storage_cli.clone(); + // Return a new ActionController instance Self { db, driver, @@ -102,11 +97,19 @@ impl<'ccl> ActionController<'ccl> { /// # Example /// /// ```rust + /// use entity::prelude::target::ActionTargetKind; + /// use entity::test::ui::action::action; + /// use entity::test::ui::action::action::ActionKind; + /// /// let action = action::Model { + /// id: Default::default(), + /// execution_order: 1,description: None, + /// kind: ActionKind::Click, + /// data_kind: None, /// data_value: Some("example data".to_string()), /// target_value: Some("example target".to_string()), - /// target_kind: Some(ActionTargetKind::Css), - /// }; + /// action_group_id: Default::default(),data: None,target_kind: Some(ActionTargetKind::Css), + /// target: None,}; /// ui_helper.command_enter(&action).await; /// ``` /// @@ -149,10 +152,15 @@ impl<'ccl> ActionController<'ccl> { /// # Example /// /// ```rust + /// use entity::prelude::target::ActionTargetKind; + /// use entity::test::ui::action::action; + /// use entity::test::ui::action::action::ActionKind; /// let action = action::Model { - /// data_value: Some("button".to_string()), + /// id: Default::default(), + /// execution_order: 0,description: None,kind: + /// ActionKind::Click,data_kind: None,data_value: Some("button".to_string()), /// target_kind: Some(ActionTargetKind::Css), - /// }; + /// target_value: None,action_group_id: Default::default(),data: None,target: None,}; /// /// ui_helper.command_click(&action); /// ``` @@ -228,35 +236,90 @@ impl<'ccl> ActionController<'ccl> { } async fn take_screenshot(&self, id: String) -> EngineResult<()> { + let session_id = self.driver.session_id().await?; let content = self.driver.take_screenshot().await?; - let result = self + let _result = self .storage_cli - .create("orca", format!("{id}.png").as_str(), content.as_slice()) + .create("orca", format!("session/{session_id}/{id}.png").as_str(), content.as_slice()) .await; Ok(()) } - /// run_case - will execute the test case by the case ID - pub async fn execute(&self, id: Uuid) -> EngineResult<()> { - info!("Starting processing {action_id}", action_id = id); + pub async fn execute_action(&self, action: &action::Model, er: &ExecutionRequest, + log: Option<&ItemLog>) -> EngineResult<()> { + let log_id = match log { + Some(l) => Some(l.id), + None => None, + }; + let mut log_am = new(er.ref_id, ItemLogType::Action, action.id, log_id).save(self.db).await?; + info!("[{er}] Trigger Action {action_id}", er=er.ref_id, action_id = action.id); + // let mut log_item = item_log::Model { + // ref_id: er.ref_id, + // ref_type: ItemLogType::Action, + // step_id: action.id, + // has_screenshot: false, + // has_recording: false, + // execution_time: 0, + // status: ItemLogStatus::Running, + // log_id: None, + // created_at: start.into(), + // created_by: "system".to_string(), + // finished_at: chrono::Utc::now().into(), + // ..Default::default() + // }; + // let mut log_item_am = log_item.into_active_model().save(self.db).await?; + let start = chrono::Utc::now(); + info!( + "Executing step == [id] {:?}, [desc] {:?}", + action.id, action.description + ); + self.step_executor(&action).await?; + self.take_screenshot(action.id.to_string()).await?; + info!( + "Done step == [id] {:?}, [desc] {:?}", + action.id, action.description + ); + log_am.execution_time = Set((chrono::Utc::now() - start).num_milliseconds()); + log_am.status = Set(ItemLogStatus::Success); + log_am.finished_at = Set(chrono::Utc::now().into()); + log_am.save(self.db).await?; + Ok(()) + } + + pub async fn execute_action_group(&self, action_group: ActionGroupModel, er: &ExecutionRequest, + log: Option<&ItemLog>) -> EngineResult<()> { let mut action_page = action::Entity::find() - .filter(action::Column::ActionGroupId.eq(id)) + .filter(action::Column::ActionGroupId.eq(action_group.id)) .order_by_asc(action::Column::ExecutionOrder) .paginate(self.db, 50); while let Some(actions) = action_page.fetch_and_next().await? { for action in actions.into_iter() { - info!( - "Executing step == [id] {:?}, [desc] {:?}", - action.id, action.description - ); - self.step_executor(&action).await?; - self.take_screenshot(action.id.to_string()).await?; - info!( - "Done step == [id] {:?}, [desc] {:?}", - action.id, action.description - ); + self.execute_action(&action, er, log).await?; } } Ok(()) } + + /// run_case - will execute the test case by the case ID + pub async fn execute(&self, id: Uuid, er: &ExecutionRequest, + log: Option<&ItemLog>) -> EngineResult<()> { + let start = chrono::Utc::now(); + let mut log_am = new(er.ref_id, ItemLogType::Action, id, None).save(self.db).await?; + info!("[{er}] Trigger Action {action_id}", er=er.ref_id, action_id = id); + let action_group = ActionGroupEntity::find_by_id(id).one(self.db) + .await? + .ok_or(EngineError::MissingParameter("ActionGroup".to_string(), id.into()))?; + if log.is_some() { + log_am.log_id = Set(Some(log.unwrap().id)); + } + let mut log_am = log_am.save(self.db).await?; + let log = log_am.clone().try_into_model()?; + self.execute_action_group(action_group, er, Some(&log)).await?; + + log_am.execution_time = Set((chrono::Utc::now() - start).num_milliseconds()); + log_am.status = Set(ItemLogStatus::Success); + log_am.finished_at = Set(chrono::Utc::now().into()); + log_am.save(self.db).await?; + Ok(()) + } } diff --git a/crates/services/engine/src/controller/case.rs b/crates/services/engine/src/controller/case.rs index 414cc96..e2cfff9 100644 --- a/crates/services/engine/src/controller/case.rs +++ b/crates/services/engine/src/controller/case.rs @@ -1,18 +1,20 @@ -use cerium::client::Client; +use sea_orm::{ActiveModelTrait, ColumnTrait, DatabaseTransaction, EntityTrait, IntoActiveModel, PaginatorTrait, QueryFilter, QueryOrder, TryIntoModel}; +use sea_orm::ActiveValue::Set; use sea_orm::prelude::Uuid; -use sea_orm::{ - ColumnTrait, DatabaseTransaction, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, -}; use tracing::{error, info}; +use cerium::client::Client; use cerium::client::driver::web::WebDriver; use entity::prelude::case::Entity; use entity::prelude::case_block; use entity::prelude::case_block::{BlockKind, BlockType}; +use entity::test::ui::{ExecutionRequest, request}; use entity::test::ui::case::case; +use entity::test::ui::log::{item_log, ItemLog}; +use entity::test::ui::log::item_log::{ItemLogStatus, ItemLogType, new}; use crate::controller::action::ActionController; -use crate::error::EngineResult; +use crate::error::{EngineError, EngineResult}; pub struct CaseController<'ccl> { db: &'ccl DatabaseTransaction, @@ -28,39 +30,82 @@ impl<'ccl> CaseController<'ccl> { ) -> CaseController<'ccl> { Self { db, drive, cli } } - /// run_case - will execute the test case by the case ID - pub async fn run_case(&self, id: Uuid) -> EngineResult<()> { - let case_res = Entity::find_by_id(id).one(self.db).await?; - if case_res.is_none() { - error!("Unable to find the Case - {:?}", id.clone()); - return Ok(()); - } - let case: &case::Model = &case_res.unwrap(); - info!( - "Start Processing Case - [[ {name} || {id} ]]", - name = case.name, - id = case.id - ); - self.process(case).await?; + + + /// run - will execute the test cases based on the execution request + pub async fn run(&self, id: Uuid, er: &ExecutionRequest, log: Option<&ItemLog>) -> EngineResult<()> { + info!("[{er}] Trigger Test Case {action_id}", er=er.ref_id, action_id = id); + let start = chrono::Utc::now(); + let log_id = match log { + Some(l) => Some(l.id), + None => None, + }; + let mut log_am = new(er.ref_id, ItemLogType::ActionGroup, id, log_id).save(self.db).await?; + // let mut log_item = item_log::Model { + // ref_id: er.ref_id, + // ref_type: ItemLogType::TestCase, + // step_id: id, + // has_screenshot: false, + // has_recording: false, + // execution_time: 0, + // status: ItemLogStatus::Running, + // log_id: None, + // created_at: start.into(), + // created_by: "system".to_string(), + // finished_at: chrono::Utc::now().into(), + // ..Default::default() + // }; + // if log.is_some() { + // log_item.log_id = Some(log.unwrap().id); + // } + // let mut log_item_am = log_item.into_active_model().save(self.db).await?; + let case = Entity::find_by_id(id).one(self.db).await? + .ok_or(EngineError::MissingParameter("ActionGroup".to_string(), id.into()))?; + let log = log_am.clone().try_into_model()?; + self.process(&case, er, Some(&log)).await?; + + log_am.execution_time = Set((chrono::Utc::now() - start).num_milliseconds()); + log_am.status = Set(ItemLogStatus::Success); + log_am.finished_at = Set(chrono::Utc::now().into()); + log_am.save(self.db).await?; Ok(()) } + + /// run_case - will execute the test case by the case ID + // pub async fn run_case(&self, id: Uuid) -> EngineResult<()> { + // let case_res = Entity::find_by_id(id).one(self.db).await?; + // if case_res.is_none() { + // error!("Unable to find the Case - {:?}", id.clone()); + // return Ok(()); + // } + // let case: &case::Model = &case_res.unwrap(); + // info!( + // "Start Processing Case - [[ {name} || {id} ]]", + // name = case.name, + // id = case.id + // ); + // self.process(case).await?; + // Ok(()) + // } + /// process will get the block and execute in the batch based on the kind of the block - pub async fn process(&self, case: &case::Model) -> EngineResult<()> { + pub async fn process(&self, case: &case::Model, er: &ExecutionRequest, log: Option<&ItemLog>) -> EngineResult<()> { let mut block_page = case_block::Entity::find() .filter(case_block::Column::CaseId.eq(case.id)) .order_by_asc(case_block::Column::ExecutionOrder) .paginate(self.db, 10); while let Some(blocks) = block_page.fetch_and_next().await? { for block in blocks.into_iter() { - self.switch_block(&block).await?; + self.switch_block(&block, er, log).await?; } } Ok(()) } /// switch_block - function to switch the block based on the type and kind of the block - async fn switch_block(&self, block: &case_block::Model) -> EngineResult<()> { + async fn switch_block(&self, block: &case_block::Model, er: &ExecutionRequest, + log: Option<&ItemLog>) -> EngineResult<()> { let result = match block.kind { // BlockKind::Loop => match block.type_field { // BlockType::InMemory => self.process_action_group(block), @@ -68,35 +113,36 @@ impl<'ccl> CaseController<'ccl> { // _ => todo!("Need to raise a error from here since non other supported"), // }, BlockKind::SelfReference => match block.type_field { - BlockType::Condition => self.process_action_group(block), - BlockType::YesCase => self.process_action_group(block), - BlockType::NoCase => self.process_action_group(block), - BlockType::Loop => self.process_action_group(block), + BlockType::Condition => self.process_action_group(block, er, log), + BlockType::YesCase => self.process_action_group(block, er, log), + BlockType::NoCase => self.process_action_group(block, er, log), + BlockType::Loop => self.process_action_group(block, er, log), _ => todo!("Need to raise a error from here since non other supported"), }, BlockKind::Reference => match block.type_field { - BlockType::ActionGroup => self.process_action_group(block), - BlockType::Assertion => self.process_action_group(block), + BlockType::ActionGroup => self.process_action_group(block, er, log), + BlockType::Assertion => self.process_action_group(block, er, log), _ => todo!("Need to raise a error from here since non other supported"), }, } - .await?; + .await?; Ok(()) } - async fn process_in_memory_loop(&self, block: &case_block::Model) -> () { + async fn process_in_memory_loop(&self, block: &case_block::Model, er: &ExecutionRequest, log: Option<&ItemLog>) -> () { () } - async fn process_datatable_loop(&self, block: &case_block::Model) {} + async fn process_datatable_loop(&self, block: &case_block::Model, er: &ExecutionRequest, log: Option<&ItemLog>) {} - async fn process_condition(&self, block: &case_block::Model) {} + async fn process_condition(&self, block: &case_block::Model, er: &ExecutionRequest, log: Option<&ItemLog>) {} - async fn process_action_group(&self, block: &case_block::Model) -> EngineResult<()> { + async fn process_action_group(&self, block: &case_block::Model, er: &ExecutionRequest, + log: Option<&ItemLog>) -> EngineResult<()> { info!("Starting processing {block_id}", block_id = block.id); let controller = ActionController::new(self.db, self.drive.clone(), self.cli.clone()); let result = controller - .execute(block.reference.unwrap_or_default()) + .execute(block.reference.unwrap_or_default(), er, log) .await?; Ok(result) } diff --git a/docker-compose.yml b/docker-compose.yml index 4e5162f..12eec75 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,9 @@ version: '3' + +networks: + orca: + + services: minio: container_name: minio-ser @@ -58,32 +63,32 @@ services: - RCLONE_CONFIG_S3_PROVIDER=Minio - RCLONE_CONFIG_S3_ENV_AUTH=false - RCLONE_CONFIG_S3_REGION=us-east-1 -# - RCLONE_CONFIG_S3_LOCATION_CONSTRAINT=asia-southeast1 + # - RCLONE_CONFIG_S3_LOCATION_CONSTRAINT=asia-southeast1 - RCLONE_CONFIG_S3_ACL=private - RCLONE_CONFIG_S3_ACCESS_KEY_ID=minioadmin - RCLONE_CONFIG_S3_SECRET_ACCESS_KEY=minioadmin - RCLONE_CONFIG_S3_ENDPOINT=http://minio-ser:9000 -# chrome_video: -# image: selenium/video:ffmpeg-4.3.1-20221208 -# volumes: -# - /tmp/videos:/videos -# depends_on: -# - chrome -# environment: -# - DISPLAY_CONTAINER_NAME=chrome -# - FILE_NAME=chrome_video.mp4 + # chrome_video: + # image: selenium/video:ffmpeg-4.3.1-20221208 + # volumes: + # - /tmp/videos:/videos + # depends_on: + # - chrome + # environment: + # - DISPLAY_CONTAINER_NAME=chrome + # - FILE_NAME=chrome_video.mp4 -# firefox_video: -# platform: linux/amd64 -# image: selenium/video:ffmpeg-4.3.1-20221208 -# volumes: -# - /Users/vasanth/Documents/commit/OpenSource/orcan/orca/to/config/video:/videos -# depends_on: -# - firefox -# environment: -# - DISPLAY_CONTAINER_NAME=firefox -# - FILE_NAME=firefox_video.mp4 + # firefox_video: + # platform: linux/amd64 + # image: selenium/video:ffmpeg-4.3.1-20221208 + # volumes: + # - /Users/vasanth/Documents/commit/OpenSource/orcan/orca/to/config/video:/videos + # depends_on: + # - firefox + # environment: + # - DISPLAY_CONTAINER_NAME=firefox + # - FILE_NAME=firefox_video.mp4 selenium-hub: platform: linux/amd64 @@ -116,6 +121,65 @@ services: container_name: redis-ser command: redis-server --save "" --appendonly no +# +# timescaledb: +# image: timescale/timescaledb:latest-pg12 +# restart: always +# ports: +# - "5432:5432" +# environment: +# POSTGRES_USER: timescaledb +# POSTGRES_PASSWORD: password +# volumes: +# - "./data/timescaledb:/var/lib/postgresql/data" +# +# +# loki: +# image: grafana/loki:2.9.4 +# ports: +# - "3100:3100" +# command: -config.file=/etc/loki/local-config.yaml +# networks: +# - orca +# +# promtail: +# image: grafana/promtail:2.9.4 +# volumes: +# - "/var/orca/log:/var/log" +# command: -config.file=/etc/promtail/config.yml +# networks: +# - orca +# +# grafana: +# environment: +# - GF_PATHS_PROVISIONING=/etc/grafana/provisioning +# - GF_AUTH_ANONYMOUS_ENABLED=true +# - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin +# entrypoint: +# - sh +# - -euc +# - | +# mkdir -p /etc/grafana/provisioning/datasources +# cat < /etc/grafana/provisioning/datasources/ds.yaml +# apiVersion: 1 +# datasources: +# - name: Loki +# type: loki +# access: proxy +# orgId: 1 +# url: http://loki:3100 +# basicAuth: false +# isDefault: true +# version: 1 +# editable: false +# EOF +# /run.sh +# image: grafana/grafana:latest +# ports: +# - "4000:3000" +# networks: +# - orca + volumes: data: \ No newline at end of file