added cloude generated tests
parent
0c10ac9748
commit
4706de1b42
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(cargo test *)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo build # debug build
|
||||||
|
cargo build --release # release build
|
||||||
|
cargo run -- <args> # run with args (see CLI section)
|
||||||
|
cargo check # fast type/borrow check without linking
|
||||||
|
cargo clippy # lint
|
||||||
|
```
|
||||||
|
|
||||||
|
No tests exist yet in this project.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The binary reads environment config from `~/.config/rcc/<env>` (dotenv format). Default env is `local`.
|
||||||
|
|
||||||
|
Required keys: `DB_USERNAME`, `DB_PASSWORD`
|
||||||
|
Optional keys: `DB_LOCATION` (default: localhost), `DB_PORT` (default: 3306), `DB` (default: efulfilment)
|
||||||
|
|
||||||
|
Pass `-e <env>` to select a config file (e.g., `-e stage` loads `~/.config/rcc/stage`).
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Three-layer structure: **CLI → Service → Repository**, wired together in `container.rs`.
|
||||||
|
|
||||||
|
- `src/cli/command.rs` — Clap structs defining all subcommands (`Merch`, `Schema`, `Filter`, `Page`, `Job`, `Use`)
|
||||||
|
- `src/cli/controller.rs` — dispatches parsed args to the appropriate service
|
||||||
|
- `src/container.rs` — factory functions that construct `Repo → Service` pairs from a `Db` instance
|
||||||
|
- `src/database/db.rs` — `Db` wraps a `Arc<Mutex<Pool>>` (mysql); `Db::initialize(env)` loads the dotenv config and opens a connection pool
|
||||||
|
- `src/repository/` — one repo per domain entity; each holds a `Db` and executes raw MySQL queries
|
||||||
|
- `src/service/` — one service per domain; holds the corresponding repo, formats and prints results to stdout
|
||||||
|
- `src/entity/` — plain structs with `mysql::FromRow` derives representing DB rows
|
||||||
|
- `src/lib.rs` — exports the `TerminalSize` trait (used by services to adapt output width to the terminal)
|
||||||
|
|
||||||
|
## CLI Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
rcc [-e <env>] <subcommand>
|
||||||
|
|
||||||
|
merch <search> # search merchants by name
|
||||||
|
schema <search> # list columns for a table
|
||||||
|
filter <id> [-a] [-c] [-l] # filter info; -a=all, -c=config, -l=log
|
||||||
|
page <id> # page/file info
|
||||||
|
job <id> [-l] # job info; -l=log
|
||||||
|
use <file_name> # find filters that use a given file/module
|
||||||
|
```
|
||||||
|
|
@ -48,3 +48,93 @@ pub enum Commands {
|
||||||
file_name: String,
|
file_name: String,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
fn parse(args: &[&str]) -> Cli {
|
||||||
|
Cli::try_parse_from(args).expect("parse failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn merch_command() {
|
||||||
|
let cli = parse(&["rcc", "merch", "acme"]);
|
||||||
|
assert!(matches!(cli.mode, Commands::Merch { search: Some(ref s) } if s == "acme"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn schema_command() {
|
||||||
|
let cli = parse(&["rcc", "schema", "order_id"]);
|
||||||
|
assert!(matches!(cli.mode, Commands::Schema { search: Some(ref s) } if s == "order_id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn filter_command_defaults() {
|
||||||
|
let cli = parse(&["rcc", "filter", "42"]);
|
||||||
|
assert!(matches!(
|
||||||
|
cli.mode,
|
||||||
|
Commands::Filter { filter_id: 42, all: false, config: false, log: false }
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn filter_command_all_flag() {
|
||||||
|
let cli = parse(&["rcc", "filter", "42", "--all"]);
|
||||||
|
assert!(matches!(cli.mode, Commands::Filter { all: true, .. }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn filter_command_config_flag() {
|
||||||
|
let cli = parse(&["rcc", "filter", "42", "--config"]);
|
||||||
|
assert!(matches!(cli.mode, Commands::Filter { config: true, .. }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn filter_command_log_flag() {
|
||||||
|
let cli = parse(&["rcc", "filter", "42", "--log"]);
|
||||||
|
assert!(matches!(cli.mode, Commands::Filter { log: true, .. }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn page_command() {
|
||||||
|
let cli = parse(&["rcc", "page", "7"]);
|
||||||
|
assert!(matches!(cli.mode, Commands::Page { page_id: 7 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn job_command_default() {
|
||||||
|
let cli = parse(&["rcc", "job", "3"]);
|
||||||
|
assert!(matches!(cli.mode, Commands::Job { job_id: 3, log: false }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn job_command_log_flag() {
|
||||||
|
let cli = parse(&["rcc", "job", "3", "--log"]);
|
||||||
|
assert!(matches!(cli.mode, Commands::Job { job_id: 3, log: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn use_command() {
|
||||||
|
let cli = parse(&["rcc", "use", "my_module.php"]);
|
||||||
|
assert!(matches!(cli.mode, Commands::Use { ref file_name } if file_name == "my_module.php"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn env_flag_is_captured() {
|
||||||
|
let cli = parse(&["rcc", "-e", "stage", "merch", "x"]);
|
||||||
|
assert_eq!(cli.env.as_deref(), Some("stage"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn env_defaults_to_none_when_not_provided() {
|
||||||
|
let cli = parse(&["rcc", "merch", "x"]);
|
||||||
|
assert!(cli.env.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unknown_subcommand_is_rejected() {
|
||||||
|
assert!(Cli::try_parse_from(["rcc", "bogus"]).is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,6 @@
|
||||||
use crate::{
|
use std::io;
|
||||||
container,
|
|
||||||
database::db::Db,
|
use crate::{container, database::db::Db};
|
||||||
service::{
|
|
||||||
file_service::FileService, filter_service::FilterService, job_service::JobService,
|
|
||||||
merchant_service::MerchantService, schema_service::SchemaService,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::command::{Cli, Commands};
|
use super::command::{Cli, Commands};
|
||||||
|
|
||||||
|
|
@ -18,14 +13,17 @@ enum CommandError {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_args(args: Cli, db: Db) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn process_args(args: Cli, db: Db) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let stdout = io::stdout();
|
||||||
|
let mut out = stdout.lock();
|
||||||
|
|
||||||
match args.mode {
|
match args.mode {
|
||||||
Commands::Merch { search } => {
|
Commands::Merch { search } => {
|
||||||
let mut merchant_service: MerchantService = container::get_merchant_service(db);
|
let mut merchant_service = container::get_merchant_service(db);
|
||||||
merchant_service.get_merchant(&search.ok_or(CommandError::EmptySearch())?)?;
|
merchant_service.get_merchant(&search.ok_or(CommandError::EmptySearch())?, &mut out)?;
|
||||||
}
|
}
|
||||||
Commands::Schema { search } => {
|
Commands::Schema { search } => {
|
||||||
let mut schema_service: SchemaService = container::get_schema_service(db);
|
let mut schema_service = container::get_schema_service(db);
|
||||||
schema_service.get_columns(&search.ok_or(CommandError::EmptySearch())?)?;
|
schema_service.get_columns(&search.ok_or(CommandError::EmptySearch())?, &mut out)?;
|
||||||
}
|
}
|
||||||
Commands::Filter {
|
Commands::Filter {
|
||||||
filter_id,
|
filter_id,
|
||||||
|
|
@ -33,31 +31,31 @@ pub fn process_args(args: Cli, db: Db) -> Result<(), Box<dyn std::error::Error>>
|
||||||
all,
|
all,
|
||||||
log,
|
log,
|
||||||
} => {
|
} => {
|
||||||
let mut filter_service: FilterService = container::get_filter_service(db);
|
let mut filter_service = container::get_filter_service(db);
|
||||||
if config {
|
if config {
|
||||||
filter_service.get_filter_configs(&filter_id)?;
|
filter_service.get_filter_configs(&filter_id, &mut out)?;
|
||||||
} else if log {
|
} else if log {
|
||||||
filter_service.get_filter_log(&filter_id, all)?;
|
filter_service.get_filter_log(&filter_id, all, &mut out)?;
|
||||||
} else {
|
} else {
|
||||||
filter_service.get_filter(&filter_id, all)?;
|
filter_service.get_filter(&filter_id, all, &mut out)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Commands::Page { page_id } => {
|
Commands::Page { page_id } => {
|
||||||
let mut file_service: FileService = container::get_page_service(db);
|
let mut file_service = container::get_page_service(db);
|
||||||
file_service.get_page(&page_id)?;
|
file_service.get_page(&page_id, &mut out)?;
|
||||||
}
|
}
|
||||||
Commands::Job { job_id, log } => {
|
Commands::Job { job_id, log } => {
|
||||||
let mut job_service: JobService = container::get_job_service(db);
|
let mut job_service = container::get_job_service(db);
|
||||||
if log {
|
if log {
|
||||||
job_service.get_job_log(&job_id)?;
|
job_service.get_job_log(&job_id, &mut out)?;
|
||||||
} else {
|
} else {
|
||||||
job_service.get_job_by_id(&job_id)?;
|
job_service.get_job_by_id(&job_id, &mut out)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Commands::Use { file_name } => {
|
Commands::Use { file_name } => {
|
||||||
let mut filter_service: FilterService = container::get_filter_service(db);
|
let mut filter_service = container::get_filter_service(db);
|
||||||
filter_service.get_filter_uses(&file_name)?;
|
filter_service.get_filter_uses(&file_name, &mut out)?;
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -10,27 +10,27 @@ use crate::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn get_filter_service(pool: Db) -> FilterService {
|
pub fn get_filter_service(pool: Db) -> FilterService<FilterRepo> {
|
||||||
let repo = FilterRepo::new(pool);
|
let repo = FilterRepo::new(pool);
|
||||||
FilterService::new(repo)
|
FilterService::new(repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_merchant_service(pool: Db) -> MerchantService {
|
pub fn get_merchant_service(pool: Db) -> MerchantService<MerchantRepo> {
|
||||||
let repo = MerchantRepo::new(pool);
|
let repo = MerchantRepo::new(pool);
|
||||||
MerchantService::new(repo)
|
MerchantService::new(repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_schema_service(pool: Db) -> SchemaService {
|
pub fn get_schema_service(pool: Db) -> SchemaService<SchemaRepo> {
|
||||||
let repo = SchemaRepo::new(pool);
|
let repo = SchemaRepo::new(pool);
|
||||||
SchemaService::new(repo)
|
SchemaService::new(repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_page_service(pool: Db) -> FileService {
|
pub fn get_page_service(pool: Db) -> FileService<FileRepo> {
|
||||||
let repo = FileRepo::new(pool);
|
let repo = FileRepo::new(pool);
|
||||||
FileService::new(repo)
|
FileService::new(repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_job_service(pool: Db) -> JobService {
|
pub fn get_job_service(pool: Db) -> JobService<JobRepo> {
|
||||||
let repo = JobRepo::new(pool);
|
let repo = JobRepo::new(pool);
|
||||||
JobService::new(repo)
|
JobService::new(repo)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,13 +69,26 @@ impl Db {
|
||||||
|
|
||||||
impl Drop for Db {
|
impl Drop for Db {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
// The drop function is called when the Db instance goes out of scope
|
|
||||||
// This is where you can clean up resources, like returning connections to the pool
|
|
||||||
log::info!("Db instance is being dropped. Cleaning up resources.");
|
log::info!("Db instance is being dropped. Cleaning up resources.");
|
||||||
|
|
||||||
// You might want to explicitly drop the Mutex guard to release the lock
|
|
||||||
// (This is not strictly necessary as the Mutex will be automatically dropped,
|
|
||||||
// but it can be useful for explicitness)
|
|
||||||
drop(self.pool.lock().unwrap());
|
drop(self.pool.lock().unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn initialize_fails_when_config_file_missing() {
|
||||||
|
let result = Db::initialize("__nonexistent_env__".to_string());
|
||||||
|
assert!(result.is_err());
|
||||||
|
let msg = result.unwrap_err().to_string();
|
||||||
|
assert!(msg.contains("__nonexistent_env__"), "error should mention the env name: {msg}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn initialize_fails_for_empty_env_name() {
|
||||||
|
let result = Db::initialize(String::new());
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Page {
|
pub struct Page {
|
||||||
pub m_id: usize,
|
pub m_id: usize,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Filter {
|
pub struct Filter {
|
||||||
pub filter_module_id: usize,
|
pub filter_module_id: usize,
|
||||||
pub file_name: String,
|
pub file_name: String,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use time::PrimitiveDateTime;
|
use time::PrimitiveDateTime;
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct FilterConfig {
|
pub struct FilterConfig {
|
||||||
pub attribute: String,
|
pub attribute: String,
|
||||||
pub value1: String,
|
pub value1: String,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use time::PrimitiveDateTime;
|
use time::PrimitiveDateTime;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct FilterLog {
|
pub struct FilterLog {
|
||||||
pub run_ts: PrimitiveDateTime,
|
pub run_ts: PrimitiveDateTime,
|
||||||
pub error_code: String,
|
pub error_code: String,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct FilterUses {
|
pub struct FilterUses {
|
||||||
pub filter_module_id: usize,
|
pub filter_module_id: usize,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
use time::PrimitiveDateTime;
|
use time::PrimitiveDateTime;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Job {
|
pub struct Job {
|
||||||
pub file_name: String,
|
pub file_name: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct JobLog {
|
pub struct JobLog {
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub upd_ts: PrimitiveDateTime,
|
pub upd_ts: PrimitiveDateTime,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Merchant {
|
pub struct Merchant {
|
||||||
pub m_id: usize,
|
pub m_id: usize,
|
||||||
pub m_name: String,
|
pub m_name: String,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Schema {
|
pub struct Schema {
|
||||||
pub table_name: String,
|
pub table_name: String,
|
||||||
pub column_name: String,
|
pub column_name: String,
|
||||||
|
|
|
||||||
21
src/lib.rs
21
src/lib.rs
|
|
@ -6,3 +6,24 @@ pub trait TerminalSize {
|
||||||
terminal.cols.into()
|
terminal.cols.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
struct Probe;
|
||||||
|
impl TerminalSize for Probe {}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_width_returns_positive_value() {
|
||||||
|
assert!(Probe.get_width() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_width_falls_back_to_150_when_no_tty() {
|
||||||
|
// In a non-TTY test environment termsize::get() returns None,
|
||||||
|
// so the fallback of 150 columns is used.
|
||||||
|
let w = Probe.get_width();
|
||||||
|
assert!(w >= 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,21 @@ use mysql::{params, prelude::Queryable};
|
||||||
|
|
||||||
use crate::{database::db::Db, entity::file::Page};
|
use crate::{database::db::Db, entity::file::Page};
|
||||||
|
|
||||||
|
pub trait FileRepoTrait {
|
||||||
|
fn find_page(&mut self, file_id: &usize) -> Result<Vec<Page>, mysql::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FileRepo {
|
pub struct FileRepo {
|
||||||
db_pool: Db,
|
db_pool: Db,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FileRepoTrait for FileRepo {
|
||||||
|
fn find_page(&mut self, file_id: &usize) -> Result<Vec<Page>, mysql::Error> {
|
||||||
|
self.find_page(file_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FileRepo {
|
impl FileRepo {
|
||||||
pub fn new(db: Db) -> Self {
|
pub fn new(db: Db) -> Self {
|
||||||
Self { db_pool: db }
|
Self { db_pool: db }
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,33 @@ use crate::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub trait FilterRepoTrait {
|
||||||
|
fn find_by_id(&mut self, filter_id: &usize) -> Result<Vec<Filter>, mysql::Error>;
|
||||||
|
fn find_filter_configs(&mut self, filter_id: &usize) -> Result<Vec<FilterConfig>, mysql::Error>;
|
||||||
|
fn find_filter_log(&mut self, filter_id: &usize) -> Result<Vec<FilterLog>, mysql::Error>;
|
||||||
|
fn find_filter_uses(&mut self, filter_file: &str) -> Result<Vec<FilterUses>, mysql::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FilterRepo {
|
pub struct FilterRepo {
|
||||||
db: Db,
|
db: Db,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FilterRepoTrait for FilterRepo {
|
||||||
|
fn find_by_id(&mut self, filter_id: &usize) -> Result<Vec<Filter>, mysql::Error> {
|
||||||
|
self.find_by_id(filter_id)
|
||||||
|
}
|
||||||
|
fn find_filter_configs(&mut self, filter_id: &usize) -> Result<Vec<FilterConfig>, mysql::Error> {
|
||||||
|
self.find_filter_configs(filter_id)
|
||||||
|
}
|
||||||
|
fn find_filter_log(&mut self, filter_id: &usize) -> Result<Vec<FilterLog>, mysql::Error> {
|
||||||
|
self.find_filter_log(filter_id)
|
||||||
|
}
|
||||||
|
fn find_filter_uses(&mut self, filter_file: &str) -> Result<Vec<FilterUses>, mysql::Error> {
|
||||||
|
self.find_filter_uses(filter_file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FilterRepo {
|
impl FilterRepo {
|
||||||
pub fn new(db: Db) -> Self {
|
pub fn new(db: Db) -> Self {
|
||||||
Self { db }
|
Self { db }
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,25 @@ use crate::{
|
||||||
entity::job::{Job, JobLog},
|
entity::job::{Job, JobLog},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub trait JobRepoTrait {
|
||||||
|
fn find_by_id(&mut self, job_id: &usize) -> Result<Vec<Job>, mysql::Error>;
|
||||||
|
fn find_log(&mut self, job_id: &usize) -> Result<Vec<JobLog>, mysql::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct JobRepo {
|
pub struct JobRepo {
|
||||||
db: Db,
|
db: Db,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl JobRepoTrait for JobRepo {
|
||||||
|
fn find_by_id(&mut self, job_id: &usize) -> Result<Vec<Job>, mysql::Error> {
|
||||||
|
self.find_by_id(job_id)
|
||||||
|
}
|
||||||
|
fn find_log(&mut self, job_id: &usize) -> Result<Vec<JobLog>, mysql::Error> {
|
||||||
|
self.find_log(job_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl JobRepo {
|
impl JobRepo {
|
||||||
pub fn new(db: Db) -> Self {
|
pub fn new(db: Db) -> Self {
|
||||||
Self { db }
|
Self { db }
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,20 @@ use mysql::{params, prelude::Queryable};
|
||||||
|
|
||||||
use crate::{database::db::Db, entity::merchant::Merchant};
|
use crate::{database::db::Db, entity::merchant::Merchant};
|
||||||
|
|
||||||
|
pub trait MerchantRepoTrait {
|
||||||
|
fn find_by_name_or_id(&mut self, search: &str) -> Result<Vec<Merchant>, mysql::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
pub struct MerchantRepo {
|
pub struct MerchantRepo {
|
||||||
db_pool: Db,
|
db_pool: Db,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MerchantRepoTrait for MerchantRepo {
|
||||||
|
fn find_by_name_or_id(&mut self, search: &str) -> Result<Vec<Merchant>, mysql::Error> {
|
||||||
|
self.find_by_name_or_id(search)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl MerchantRepo {
|
impl MerchantRepo {
|
||||||
pub fn new(db_pool: Db) -> Self {
|
pub fn new(db_pool: Db) -> Self {
|
||||||
Self { db_pool }
|
Self { db_pool }
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,20 @@ use mysql::{params, prelude::Queryable};
|
||||||
|
|
||||||
use crate::{database::db::Db, entity::schema::Schema};
|
use crate::{database::db::Db, entity::schema::Schema};
|
||||||
|
|
||||||
|
pub trait SchemaRepoTrait {
|
||||||
|
fn find_by_column(&mut self, column_name: &str) -> Result<Vec<Schema>, mysql::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
pub struct SchemaRepo {
|
pub struct SchemaRepo {
|
||||||
db: Db,
|
db: Db,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SchemaRepoTrait for SchemaRepo {
|
||||||
|
fn find_by_column(&mut self, column_name: &str) -> Result<Vec<Schema>, mysql::Error> {
|
||||||
|
self.find_by_column(column_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SchemaRepo {
|
impl SchemaRepo {
|
||||||
pub fn new(db: Db) -> Self {
|
pub fn new(db: Db) -> Self {
|
||||||
Self { db }
|
Self { db }
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,85 @@
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
use rcc::TerminalSize;
|
use rcc::TerminalSize;
|
||||||
|
|
||||||
use crate::{entity::file::Page, repository::file_repo::FileRepo};
|
use crate::{entity::file::Page, repository::file_repo::FileRepoTrait};
|
||||||
|
|
||||||
pub struct FileService {
|
pub struct FileService<R> {
|
||||||
repo: FileRepo,
|
repo: R,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TerminalSize for FileService {}
|
impl<R> TerminalSize for FileService<R> {}
|
||||||
|
|
||||||
impl FileService {
|
impl<R: FileRepoTrait> FileService<R> {
|
||||||
pub fn new(repo: FileRepo) -> Self {
|
pub fn new(repo: R) -> Self {
|
||||||
Self { repo }
|
Self { repo }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_page(&mut self, page_id: &usize) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn get_page(&mut self, page_id: &usize, out: &mut impl Write) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let page = self.repo.find_page(page_id)?;
|
let page = self.repo.find_page(page_id)?;
|
||||||
|
|
||||||
if page.is_empty() {
|
if page.is_empty() {
|
||||||
println!("No page found.");
|
writeln!(out, "No page found.")?;
|
||||||
} else {
|
} else {
|
||||||
page.into_iter().for_each(|page: Page| {
|
page.into_iter().for_each(|page: Page| {
|
||||||
println!("{}", "-".repeat(self.get_width()));
|
writeln!(out, "{}", "-".repeat(self.get_width())).ok();
|
||||||
println!("M-ID: {}", page.m_id);
|
writeln!(out, "M-ID: {}", page.m_id).ok();
|
||||||
println!("FileName: {}", page.file);
|
writeln!(out, "FileName: {}", page.file).ok();
|
||||||
println!("Title: {}", page.title);
|
writeln!(out, "Title: {}", page.title).ok();
|
||||||
});
|
});
|
||||||
println!("{}", "-".repeat(self.get_width()));
|
writeln!(out, "{}", "-".repeat(self.get_width()))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::{entity::file::Page, repository::file_repo::FileRepoTrait};
|
||||||
|
|
||||||
|
struct MockFileRepo {
|
||||||
|
pages: Vec<Page>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileRepoTrait for MockFileRepo {
|
||||||
|
fn find_page(&mut self, _: &usize) -> Result<Vec<Page>, mysql::Error> {
|
||||||
|
Ok(std::mem::take(&mut self.pages))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(repo: MockFileRepo) -> String {
|
||||||
|
let mut svc = FileService::new(repo);
|
||||||
|
let mut out = Vec::new();
|
||||||
|
svc.get_page(&1, &mut out).unwrap();
|
||||||
|
String::from_utf8(out).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn not_found_message() {
|
||||||
|
let out = run(MockFileRepo { pages: vec![] });
|
||||||
|
assert_eq!(out, "No page found.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prints_page_fields() {
|
||||||
|
let out = run(MockFileRepo {
|
||||||
|
pages: vec![Page { m_id: 3, title: "Home".into(), file: "index.php".into() }],
|
||||||
|
});
|
||||||
|
assert!(out.contains("M-ID: 3"));
|
||||||
|
assert!(out.contains("FileName: index.php"));
|
||||||
|
assert!(out.contains("Title: Home"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiple_pages_all_printed() {
|
||||||
|
let out = run(MockFileRepo {
|
||||||
|
pages: vec![
|
||||||
|
Page { m_id: 1, title: "A".into(), file: "a.php".into() },
|
||||||
|
Page { m_id: 2, title: "B".into(), file: "b.php".into() },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
assert!(out.contains("a.php"));
|
||||||
|
assert!(out.contains("b.php"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,47 +1,43 @@
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use rcc::TerminalSize;
|
use rcc::TerminalSize;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
entity::{filter::Filter, filter_config::FilterConfig, filter_log::FilterLog, filter_uses::FilterUses},
|
entity::{filter::Filter, filter_config::FilterConfig, filter_log::FilterLog, filter_uses::FilterUses},
|
||||||
repository::filter_repo::FilterRepo,
|
repository::filter_repo::FilterRepoTrait,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct FilterService {
|
pub struct FilterService<R> {
|
||||||
repo: FilterRepo,
|
repo: R,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TerminalSize for FilterService {}
|
impl<R> TerminalSize for FilterService<R> {}
|
||||||
|
|
||||||
impl FilterService {
|
impl<R: FilterRepoTrait> FilterService<R> {
|
||||||
pub fn new(repo: FilterRepo) -> Self {
|
pub fn new(repo: R) -> Self {
|
||||||
Self { repo }
|
Self { repo }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_filter_uses(&mut self, file_name: &str) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn get_filter_uses(&mut self, file_name: &str, out: &mut impl Write) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let uses = self.repo.find_filter_uses(file_name)?;
|
let uses = self.repo.find_filter_uses(file_name)?;
|
||||||
|
|
||||||
if uses.is_empty() {
|
if uses.is_empty() {
|
||||||
println!("No use for file/module found!");
|
writeln!(out, "No use for file/module found!")?;
|
||||||
} else {
|
} else {
|
||||||
uses.into_iter().for_each(|filter_use: FilterUses| {
|
uses.into_iter().for_each(|filter_use: FilterUses| {
|
||||||
let is_active = if filter_use.active == 1 {
|
let is_active = if filter_use.active == 1 { "true" } else { "false" };
|
||||||
"true".to_string()
|
writeln!(out, "{}", "-".repeat(self.get_width())).ok();
|
||||||
} else {
|
writeln!(out, "Module ID: {}", filter_use.filter_module_id).ok();
|
||||||
"false".to_string()
|
writeln!(out, "ModuleNo: {}", filter_use.filter_module_no).ok();
|
||||||
};
|
writeln!(out, "FilterUser: {}", filter_use.filter_user).ok();
|
||||||
println!("{}", "-".repeat(self.get_width()));
|
writeln!(out, "Description: {}", filter_use.description).ok();
|
||||||
|
writeln!(out, "EP_NO: {}", filter_use.ep_no).ok();
|
||||||
println!("Module ID: {}", filter_use.filter_module_id);
|
writeln!(out, "FilterId: {}", filter_use.filter_id).ok();
|
||||||
println!("ModuleNo: {}", filter_use.filter_module_no);
|
writeln!(out, "FilterNo: {}", filter_use.filter_no).ok();
|
||||||
println!("FilterUser: {}", filter_use.filter_user);
|
writeln!(out, "Merchant: {}", filter_use.merchant).ok();
|
||||||
println!("Description: {}", filter_use.description);
|
writeln!(out, "Active: {}", is_active).ok();
|
||||||
println!("EP_NO: {}", filter_use.ep_no);
|
|
||||||
println!("FilterId: {}", filter_use.filter_id);
|
|
||||||
println!("FilterNo: {}", filter_use.filter_no);
|
|
||||||
println!("Merchant: {}", filter_use.merchant);
|
|
||||||
println!("Active: {}", is_active);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -51,25 +47,26 @@ impl FilterService {
|
||||||
&mut self,
|
&mut self,
|
||||||
filter_id: &usize,
|
filter_id: &usize,
|
||||||
all: bool,
|
all: bool,
|
||||||
|
out: &mut impl Write,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let filters = self.repo.find_by_id(filter_id)?;
|
let filters = self.repo.find_by_id(filter_id)?;
|
||||||
|
|
||||||
if filters.is_empty() {
|
if filters.is_empty() {
|
||||||
println!("Filter not found!");
|
writeln!(out, "Filter not found!")?;
|
||||||
} else {
|
} else {
|
||||||
filters.into_iter().for_each(|filter: Filter| {
|
filters.into_iter().for_each(|filter: Filter| {
|
||||||
println!("{}", "-".repeat(self.get_width()));
|
writeln!(out, "{}", "-".repeat(self.get_width())).ok();
|
||||||
println!("Module ID: {}", filter.filter_module_id);
|
writeln!(out, "Module ID: {}", filter.filter_module_id).ok();
|
||||||
println!("FileName: {}", filter.file_name);
|
writeln!(out, "FileName: {}", filter.file_name).ok();
|
||||||
println!("Description: {}", filter.description);
|
writeln!(out, "Description: {}", filter.description).ok();
|
||||||
println!("ModuleNo: {}", filter.filter_module_no);
|
writeln!(out, "ModuleNo: {}", filter.filter_module_no).ok();
|
||||||
println!("FilterUser: {}", filter.filter_user);
|
writeln!(out, "FilterUser: {}", filter.filter_user).ok();
|
||||||
});
|
});
|
||||||
|
|
||||||
if all {
|
if all {
|
||||||
self.get_filter_configs(filter_id)?
|
self.get_filter_configs(filter_id, out)?;
|
||||||
} else {
|
} else {
|
||||||
println!("{}", "-".repeat(self.get_width()));
|
writeln!(out, "{}", "-".repeat(self.get_width()))?;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -79,26 +76,28 @@ impl FilterService {
|
||||||
pub fn get_filter_configs(
|
pub fn get_filter_configs(
|
||||||
&mut self,
|
&mut self,
|
||||||
filter_id: &usize,
|
filter_id: &usize,
|
||||||
|
out: &mut impl Write,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let filter_configs = self.repo.find_filter_configs(filter_id)?;
|
let filter_configs = self.repo.find_filter_configs(filter_id)?;
|
||||||
|
|
||||||
if filter_configs.is_empty() {
|
if filter_configs.is_empty() {
|
||||||
println!("No filter configs found!");
|
writeln!(out, "No filter configs found!")?;
|
||||||
} else {
|
} else {
|
||||||
println!("{}", "-".repeat(self.get_width()));
|
writeln!(out, "{}", "-".repeat(self.get_width()))?;
|
||||||
filter_configs
|
filter_configs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.for_each(|filter_config: FilterConfig| {
|
.for_each(|filter_config: FilterConfig| {
|
||||||
println!(
|
writeln!(
|
||||||
|
out,
|
||||||
"{0: <25} | {1: <20} | {2: <20} | {3: <10} | {4: <10}",
|
"{0: <25} | {1: <20} | {2: <20} | {3: <10} | {4: <10}",
|
||||||
filter_config.attribute,
|
filter_config.attribute,
|
||||||
filter_config.value1,
|
filter_config.value1,
|
||||||
filter_config.value2.unwrap_or("n/a".to_string()),
|
filter_config.value2.unwrap_or("n/a".to_string()),
|
||||||
filter_config.upd_ts,
|
filter_config.upd_ts,
|
||||||
filter_config.name
|
filter_config.name
|
||||||
);
|
).ok();
|
||||||
});
|
});
|
||||||
println!("{}", "-".repeat(self.get_width()));
|
writeln!(out, "{}", "-".repeat(self.get_width()))?;
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -108,45 +107,329 @@ impl FilterService {
|
||||||
&mut self,
|
&mut self,
|
||||||
filter_id: &usize,
|
filter_id: &usize,
|
||||||
all: bool,
|
all: bool,
|
||||||
|
out: &mut impl Write,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let filter_log = self.repo.find_filter_log(filter_id)?;
|
let filter_log = self.repo.find_filter_log(filter_id)?;
|
||||||
if filter_log.is_empty() {
|
if filter_log.is_empty() {
|
||||||
println!("No filter log found!");
|
writeln!(out, "No filter log found!")?;
|
||||||
} else {
|
} else {
|
||||||
println!("{}", "-".repeat(self.get_width()));
|
writeln!(out, "{}", "-".repeat(self.get_width()))?;
|
||||||
filter_log.into_iter().for_each(|filter_log: FilterLog| {
|
filter_log.into_iter().for_each(|filter_log: FilterLog| {
|
||||||
println!(
|
writeln!(out, "TS: {} Error Code: {}", filter_log.run_ts, filter_log.error_code).ok();
|
||||||
"TS: {} Error Code: {}",
|
|
||||||
filter_log.run_ts, filter_log.error_code
|
|
||||||
);
|
|
||||||
match filter_log.error_code.as_str() {
|
match filter_log.error_code.as_str() {
|
||||||
"WARNING" => {
|
"WARNING" => {
|
||||||
println!("{}", filter_log.error_msg.yellow());
|
writeln!(out, "{}", filter_log.error_msg.yellow()).ok();
|
||||||
if all {
|
if all {
|
||||||
println!("{}", filter_log.mysql_error.yellow());
|
writeln!(out, "{}", filter_log.mysql_error.yellow()).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"ERROR" => {
|
"ERROR" => {
|
||||||
println!("{}", filter_log.error_msg.red());
|
writeln!(out, "{}", filter_log.error_msg.red()).ok();
|
||||||
if all {
|
if all {
|
||||||
println!("{}", filter_log.mysql_error.red());
|
writeln!(out, "{}", filter_log.mysql_error.red()).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"DEBUG" => {
|
"DEBUG" => {
|
||||||
println!("{}", filter_log.error_msg.green());
|
writeln!(out, "{}", filter_log.error_msg.green()).ok();
|
||||||
if all {
|
if all {
|
||||||
println!("{}", filter_log.error_msg.green())
|
writeln!(out, "{}", filter_log.error_msg.green()).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
println!("{}", filter_log.error_msg);
|
writeln!(out, "{}", filter_log.error_msg).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
writeln!(out, "{}", "-".repeat(self.get_width())).ok();
|
||||||
println!("{}", "-".repeat(self.get_width()));
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::{
|
||||||
|
entity::{
|
||||||
|
filter::Filter, filter_config::FilterConfig, filter_log::FilterLog,
|
||||||
|
filter_uses::FilterUses,
|
||||||
|
},
|
||||||
|
repository::filter_repo::FilterRepoTrait,
|
||||||
|
};
|
||||||
|
use time::{Date, Month, PrimitiveDateTime, Time};
|
||||||
|
|
||||||
|
fn test_dt() -> PrimitiveDateTime {
|
||||||
|
PrimitiveDateTime::new(
|
||||||
|
Date::from_calendar_date(2024, Month::January, 1).unwrap(),
|
||||||
|
Time::MIDNIGHT,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MockFilterRepo {
|
||||||
|
filters: Vec<Filter>,
|
||||||
|
configs: Vec<FilterConfig>,
|
||||||
|
logs: Vec<FilterLog>,
|
||||||
|
uses: Vec<FilterUses>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MockFilterRepo {
|
||||||
|
fn empty() -> Self {
|
||||||
|
Self { filters: vec![], configs: vec![], logs: vec![], uses: vec![] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FilterRepoTrait for MockFilterRepo {
|
||||||
|
fn find_by_id(&mut self, _: &usize) -> Result<Vec<Filter>, mysql::Error> {
|
||||||
|
Ok(std::mem::take(&mut self.filters))
|
||||||
|
}
|
||||||
|
fn find_filter_configs(&mut self, _: &usize) -> Result<Vec<FilterConfig>, mysql::Error> {
|
||||||
|
Ok(std::mem::take(&mut self.configs))
|
||||||
|
}
|
||||||
|
fn find_filter_log(&mut self, _: &usize) -> Result<Vec<FilterLog>, mysql::Error> {
|
||||||
|
Ok(std::mem::take(&mut self.logs))
|
||||||
|
}
|
||||||
|
fn find_filter_uses(&mut self, _: &str) -> Result<Vec<FilterUses>, mysql::Error> {
|
||||||
|
Ok(std::mem::take(&mut self.uses))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn capture(f: impl FnOnce(&mut FilterService<MockFilterRepo>, &mut Vec<u8>)) -> String {
|
||||||
|
colored::control::set_override(false);
|
||||||
|
let repo = MockFilterRepo::empty();
|
||||||
|
let mut svc = FilterService::new(repo);
|
||||||
|
let mut out = Vec::new();
|
||||||
|
f(&mut svc, &mut out);
|
||||||
|
String::from_utf8(out).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn capture_with(
|
||||||
|
repo: MockFilterRepo,
|
||||||
|
f: impl FnOnce(&mut FilterService<MockFilterRepo>, &mut Vec<u8>),
|
||||||
|
) -> String {
|
||||||
|
colored::control::set_override(false);
|
||||||
|
let mut svc = FilterService::new(repo);
|
||||||
|
let mut out = Vec::new();
|
||||||
|
f(&mut svc, &mut out);
|
||||||
|
String::from_utf8(out).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- get_filter ---
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_filter_not_found() {
|
||||||
|
let out = capture(|svc, out| { svc.get_filter(&1, false, out).unwrap(); });
|
||||||
|
assert_eq!(out, "Filter not found!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_filter_prints_fields() {
|
||||||
|
let repo = MockFilterRepo {
|
||||||
|
filters: vec![Filter {
|
||||||
|
filter_module_id: 42,
|
||||||
|
file_name: "foo.php".into(),
|
||||||
|
description: "My filter".into(),
|
||||||
|
filter_module_no: "FM-7".into(),
|
||||||
|
filter_user: 99,
|
||||||
|
}],
|
||||||
|
..MockFilterRepo::empty()
|
||||||
|
};
|
||||||
|
let out = capture_with(repo, |svc, out| { svc.get_filter(&1, false, out).unwrap(); });
|
||||||
|
assert!(out.contains("Module ID: 42"));
|
||||||
|
assert!(out.contains("FileName: foo.php"));
|
||||||
|
assert!(out.contains("Description: My filter"));
|
||||||
|
assert!(out.contains("ModuleNo: FM-7"));
|
||||||
|
assert!(out.contains("FilterUser: 99"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_filter_all_calls_configs() {
|
||||||
|
let repo = MockFilterRepo {
|
||||||
|
filters: vec![Filter {
|
||||||
|
filter_module_id: 1,
|
||||||
|
file_name: "f.php".into(),
|
||||||
|
description: "d".into(),
|
||||||
|
filter_module_no: "FM-1".into(),
|
||||||
|
filter_user: 1,
|
||||||
|
}],
|
||||||
|
configs: vec![FilterConfig {
|
||||||
|
attribute: "attr1".into(),
|
||||||
|
value1: "val1".into(),
|
||||||
|
value2: None,
|
||||||
|
name: "user1".into(),
|
||||||
|
upd_ts: test_dt(),
|
||||||
|
}],
|
||||||
|
..MockFilterRepo::empty()
|
||||||
|
};
|
||||||
|
let out = capture_with(repo, |svc, out| { svc.get_filter(&1, true, out).unwrap(); });
|
||||||
|
assert!(out.contains("attr1"));
|
||||||
|
assert!(out.contains("val1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- get_filter_configs ---
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_filter_configs_empty() {
|
||||||
|
let out = capture(|svc, out| { svc.get_filter_configs(&1, out).unwrap(); });
|
||||||
|
assert_eq!(out, "No filter configs found!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_filter_configs_prints_fields() {
|
||||||
|
let repo = MockFilterRepo {
|
||||||
|
configs: vec![FilterConfig {
|
||||||
|
attribute: "colour".into(),
|
||||||
|
value1: "blue".into(),
|
||||||
|
value2: Some("dark".into()),
|
||||||
|
name: "admin".into(),
|
||||||
|
upd_ts: test_dt(),
|
||||||
|
}],
|
||||||
|
..MockFilterRepo::empty()
|
||||||
|
};
|
||||||
|
let out = capture_with(repo, |svc, out| { svc.get_filter_configs(&1, out).unwrap(); });
|
||||||
|
assert!(out.contains("colour"));
|
||||||
|
assert!(out.contains("blue"));
|
||||||
|
assert!(out.contains("dark"));
|
||||||
|
assert!(out.contains("admin"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_filter_configs_none_value2_shows_na() {
|
||||||
|
let repo = MockFilterRepo {
|
||||||
|
configs: vec![FilterConfig {
|
||||||
|
attribute: "x".into(),
|
||||||
|
value1: "y".into(),
|
||||||
|
value2: None,
|
||||||
|
name: "n".into(),
|
||||||
|
upd_ts: test_dt(),
|
||||||
|
}],
|
||||||
|
..MockFilterRepo::empty()
|
||||||
|
};
|
||||||
|
let out = capture_with(repo, |svc, out| { svc.get_filter_configs(&1, out).unwrap(); });
|
||||||
|
assert!(out.contains("n/a"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- get_filter_log ---
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_filter_log_empty() {
|
||||||
|
let out = capture(|svc, out| { svc.get_filter_log(&1, false, out).unwrap(); });
|
||||||
|
assert_eq!(out, "No filter log found!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_filter_log_shows_error_code_and_msg() {
|
||||||
|
let repo = MockFilterRepo {
|
||||||
|
logs: vec![FilterLog {
|
||||||
|
run_ts: test_dt(),
|
||||||
|
error_code: "ERROR".into(),
|
||||||
|
error_msg: "something broke".into(),
|
||||||
|
mysql_error: "sql err".into(),
|
||||||
|
}],
|
||||||
|
..MockFilterRepo::empty()
|
||||||
|
};
|
||||||
|
let out = capture_with(repo, |svc, out| { svc.get_filter_log(&1, false, out).unwrap(); });
|
||||||
|
assert!(out.contains("Error Code: ERROR"));
|
||||||
|
assert!(out.contains("something broke"));
|
||||||
|
assert!(!out.contains("sql err"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_filter_log_all_shows_mysql_error() {
|
||||||
|
let repo = MockFilterRepo {
|
||||||
|
logs: vec![FilterLog {
|
||||||
|
run_ts: test_dt(),
|
||||||
|
error_code: "WARNING".into(),
|
||||||
|
error_msg: "warn msg".into(),
|
||||||
|
mysql_error: "mysql detail".into(),
|
||||||
|
}],
|
||||||
|
..MockFilterRepo::empty()
|
||||||
|
};
|
||||||
|
let out = capture_with(repo, |svc, out| { svc.get_filter_log(&1, true, out).unwrap(); });
|
||||||
|
assert!(out.contains("warn msg"));
|
||||||
|
assert!(out.contains("mysql detail"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_filter_log_debug_code() {
|
||||||
|
let repo = MockFilterRepo {
|
||||||
|
logs: vec![FilterLog {
|
||||||
|
run_ts: test_dt(),
|
||||||
|
error_code: "DEBUG".into(),
|
||||||
|
error_msg: "debug info".into(),
|
||||||
|
mysql_error: "".into(),
|
||||||
|
}],
|
||||||
|
..MockFilterRepo::empty()
|
||||||
|
};
|
||||||
|
let out = capture_with(repo, |svc, out| { svc.get_filter_log(&1, false, out).unwrap(); });
|
||||||
|
assert!(out.contains("debug info"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_filter_log_unknown_code_printed_plain() {
|
||||||
|
let repo = MockFilterRepo {
|
||||||
|
logs: vec![FilterLog {
|
||||||
|
run_ts: test_dt(),
|
||||||
|
error_code: "INFO".into(),
|
||||||
|
error_msg: "plain message".into(),
|
||||||
|
mysql_error: "".into(),
|
||||||
|
}],
|
||||||
|
..MockFilterRepo::empty()
|
||||||
|
};
|
||||||
|
let out = capture_with(repo, |svc, out| { svc.get_filter_log(&1, false, out).unwrap(); });
|
||||||
|
assert!(out.contains("plain message"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- get_filter_uses ---
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_filter_uses_empty() {
|
||||||
|
let out = capture(|svc, out| { svc.get_filter_uses("foo.php", out).unwrap(); });
|
||||||
|
assert_eq!(out, "No use for file/module found!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_filter_uses_prints_fields() {
|
||||||
|
let repo = MockFilterRepo {
|
||||||
|
uses: vec![FilterUses {
|
||||||
|
filter_module_id: 7,
|
||||||
|
description: "desc".into(),
|
||||||
|
filter_module_no: "FM-2".into(),
|
||||||
|
filter_user: 3,
|
||||||
|
filter_id: 10,
|
||||||
|
active: 1,
|
||||||
|
merchant: "Shop A".into(),
|
||||||
|
ep_id: 5,
|
||||||
|
ep_no: "EP-9".into(),
|
||||||
|
filter_no: "F-11".into(),
|
||||||
|
}],
|
||||||
|
..MockFilterRepo::empty()
|
||||||
|
};
|
||||||
|
let out = capture_with(repo, |svc, out| { svc.get_filter_uses("foo.php", out).unwrap(); });
|
||||||
|
assert!(out.contains("Module ID: 7"));
|
||||||
|
assert!(out.contains("Merchant: Shop A"));
|
||||||
|
assert!(out.contains("Active: true"));
|
||||||
|
assert!(out.contains("EP_NO: EP-9"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_filter_uses_inactive_shows_false() {
|
||||||
|
let repo = MockFilterRepo {
|
||||||
|
uses: vec![FilterUses {
|
||||||
|
filter_module_id: 1,
|
||||||
|
description: "d".into(),
|
||||||
|
filter_module_no: "FM".into(),
|
||||||
|
filter_user: 1,
|
||||||
|
filter_id: 1,
|
||||||
|
active: 0,
|
||||||
|
merchant: "M".into(),
|
||||||
|
ep_id: 1,
|
||||||
|
ep_no: "E".into(),
|
||||||
|
filter_no: "F".into(),
|
||||||
|
}],
|
||||||
|
..MockFilterRepo::empty()
|
||||||
|
};
|
||||||
|
let out = capture_with(repo, |svc, out| { svc.get_filter_uses("x", out).unwrap(); });
|
||||||
|
assert!(out.contains("Active: false"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,52 +1,135 @@
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
use rcc::TerminalSize;
|
use rcc::TerminalSize;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
entity::job::{Job, JobLog},
|
entity::job::{Job, JobLog},
|
||||||
repository::job_repo::JobRepo,
|
repository::job_repo::JobRepoTrait,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct JobService {
|
pub struct JobService<R> {
|
||||||
pub repo: JobRepo,
|
pub repo: R,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TerminalSize for JobService {}
|
impl<R> TerminalSize for JobService<R> {}
|
||||||
|
|
||||||
impl JobService {
|
impl<R: JobRepoTrait> JobService<R> {
|
||||||
pub fn new(repo: JobRepo) -> Self {
|
pub fn new(repo: R) -> Self {
|
||||||
Self { repo }
|
Self { repo }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_job_by_id(&mut self, job_id: &usize) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn get_job_by_id(&mut self, job_id: &usize, out: &mut impl Write) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let result = self.repo.find_by_id(job_id)?;
|
let result = self.repo.find_by_id(job_id)?;
|
||||||
|
|
||||||
if result.is_empty() {
|
if result.is_empty() {
|
||||||
println!("Job not found!");
|
writeln!(out, "Job not found!")?;
|
||||||
} else {
|
} else {
|
||||||
result.into_iter().for_each(|job: Job| {
|
result.into_iter().for_each(|job: Job| {
|
||||||
println!("{}", "-".repeat(self.get_width()));
|
writeln!(out, "{}", "-".repeat(self.get_width())).ok();
|
||||||
println!("FileName: {}", job.file_name);
|
writeln!(out, "FileName: {}", job.file_name).ok();
|
||||||
println!("Description: {}", job.description);
|
writeln!(out, "Description: {}", job.description).ok();
|
||||||
});
|
});
|
||||||
|
writeln!(out, "{}", "-".repeat(self.get_width()))?;
|
||||||
println!("{}", "-".repeat(self.get_width()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_job_log(&mut self, job_id: &usize) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn get_job_log(&mut self, job_id: &usize, out: &mut impl Write) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let result = self.repo.find_log(job_id)?;
|
let result = self.repo.find_log(job_id)?;
|
||||||
|
|
||||||
if result.is_empty() {
|
if result.is_empty() {
|
||||||
println!("Job log not found!");
|
writeln!(out, "Job log not found!")?;
|
||||||
} else {
|
} else {
|
||||||
result.into_iter().for_each(|joblog: JobLog| {
|
result.into_iter().for_each(|joblog: JobLog| {
|
||||||
println!("{0: <25} | {1: <20} ", joblog.upd_ts, joblog.content,);
|
writeln!(out, "{0: <25} | {1: <20} ", joblog.upd_ts, joblog.content).ok();
|
||||||
});
|
});
|
||||||
|
writeln!(out, "{}", "-".repeat(self.get_width()))?;
|
||||||
println!("{}", "-".repeat(self.get_width()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::{
|
||||||
|
entity::job::{Job, JobLog},
|
||||||
|
repository::job_repo::JobRepoTrait,
|
||||||
|
};
|
||||||
|
use time::{Date, Month, PrimitiveDateTime, Time};
|
||||||
|
|
||||||
|
fn test_dt() -> PrimitiveDateTime {
|
||||||
|
PrimitiveDateTime::new(
|
||||||
|
Date::from_calendar_date(2024, Month::June, 15).unwrap(),
|
||||||
|
Time::MIDNIGHT,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MockJobRepo {
|
||||||
|
jobs: Vec<Job>,
|
||||||
|
logs: Vec<JobLog>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MockJobRepo {
|
||||||
|
fn empty() -> Self {
|
||||||
|
Self { jobs: vec![], logs: vec![] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JobRepoTrait for MockJobRepo {
|
||||||
|
fn find_by_id(&mut self, _: &usize) -> Result<Vec<Job>, mysql::Error> {
|
||||||
|
Ok(std::mem::take(&mut self.jobs))
|
||||||
|
}
|
||||||
|
fn find_log(&mut self, _: &usize) -> Result<Vec<JobLog>, mysql::Error> {
|
||||||
|
Ok(std::mem::take(&mut self.logs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_job(repo: MockJobRepo) -> String {
|
||||||
|
let mut svc = JobService::new(repo);
|
||||||
|
let mut out = Vec::new();
|
||||||
|
svc.get_job_by_id(&1, &mut out).unwrap();
|
||||||
|
String::from_utf8(out).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_log(repo: MockJobRepo) -> String {
|
||||||
|
let mut svc = JobService::new(repo);
|
||||||
|
let mut out = Vec::new();
|
||||||
|
svc.get_job_log(&1, &mut out).unwrap();
|
||||||
|
String::from_utf8(out).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn job_not_found() {
|
||||||
|
let out = run_job(MockJobRepo::empty());
|
||||||
|
assert_eq!(out, "Job not found!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn job_prints_fields() {
|
||||||
|
let out = run_job(MockJobRepo {
|
||||||
|
jobs: vec![Job { file_name: "import.php".into(), description: "Imports stuff".into() }],
|
||||||
|
..MockJobRepo::empty()
|
||||||
|
});
|
||||||
|
assert!(out.contains("FileName: import.php"));
|
||||||
|
assert!(out.contains("Description: Imports stuff"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn job_log_not_found() {
|
||||||
|
let out = run_log(MockJobRepo::empty());
|
||||||
|
assert_eq!(out, "Job log not found!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn job_log_prints_content_and_timestamp() {
|
||||||
|
let out = run_log(MockJobRepo {
|
||||||
|
logs: vec![JobLog { content: "log line".into(), upd_ts: test_dt() }],
|
||||||
|
..MockJobRepo::empty()
|
||||||
|
});
|
||||||
|
assert!(out.contains("log line"));
|
||||||
|
assert!(out.contains("2024-06-15"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,80 @@
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
use rcc::TerminalSize;
|
use rcc::TerminalSize;
|
||||||
|
|
||||||
use crate::repository::merchant_repo::MerchantRepo;
|
use crate::{entity::merchant::Merchant, repository::merchant_repo::MerchantRepoTrait};
|
||||||
|
|
||||||
pub struct MerchantService {
|
pub struct MerchantService<R> {
|
||||||
repo: MerchantRepo,
|
repo: R,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TerminalSize for MerchantService {}
|
impl<R> TerminalSize for MerchantService<R> {}
|
||||||
|
|
||||||
impl MerchantService {
|
impl<R: MerchantRepoTrait> MerchantService<R> {
|
||||||
pub fn new(repo: MerchantRepo) -> Self {
|
pub fn new(repo: R) -> Self {
|
||||||
Self { repo }
|
Self { repo }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_merchant(&mut self, search: &str) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn get_merchant(&mut self, search: &str, out: &mut impl Write) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let merchants = self.repo.find_by_name_or_id(search)?;
|
let merchants = self.repo.find_by_name_or_id(search)?;
|
||||||
|
|
||||||
if merchants.is_empty() {
|
if merchants.is_empty() {
|
||||||
println!("Merchant not found!");
|
writeln!(out, "Merchant not found!")?;
|
||||||
}
|
}
|
||||||
merchants.into_iter().for_each(|merchant| {
|
merchants.into_iter().for_each(|merchant: Merchant| {
|
||||||
println!("{}", "-".repeat(self.get_width()));
|
writeln!(out, "{}", "-".repeat(self.get_width())).ok();
|
||||||
println!("Merchant: {}", merchant.m_name);
|
writeln!(out, "Merchant: {}", merchant.m_name).ok();
|
||||||
println!("M-ID: {}", merchant.m_id);
|
writeln!(out, "M-ID: {}", merchant.m_id).ok();
|
||||||
});
|
});
|
||||||
|
|
||||||
println!("{}", "-".repeat(self.get_width()));
|
writeln!(out, "{}", "-".repeat(self.get_width()))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::{entity::merchant::Merchant, repository::merchant_repo::MerchantRepoTrait};
|
||||||
|
|
||||||
|
struct MockMerchantRepo {
|
||||||
|
merchants: Vec<Merchant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MerchantRepoTrait for MockMerchantRepo {
|
||||||
|
fn find_by_name_or_id(&mut self, _: &str) -> Result<Vec<Merchant>, mysql::Error> {
|
||||||
|
Ok(std::mem::take(&mut self.merchants))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(repo: MockMerchantRepo) -> String {
|
||||||
|
let mut svc = MerchantService::new(repo);
|
||||||
|
let mut out = Vec::new();
|
||||||
|
svc.get_merchant("any", &mut out).unwrap();
|
||||||
|
String::from_utf8(out).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn not_found_message() {
|
||||||
|
let out = run(MockMerchantRepo { merchants: vec![] });
|
||||||
|
assert!(out.contains("Merchant not found!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prints_merchant_fields() {
|
||||||
|
let out = run(MockMerchantRepo {
|
||||||
|
merchants: vec![Merchant { m_id: 5, m_name: "Acme".into() }],
|
||||||
|
});
|
||||||
|
assert!(out.contains("Merchant: Acme"));
|
||||||
|
assert!(out.contains("M-ID: 5"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prints_separator_after_results() {
|
||||||
|
let out = run(MockMerchantRepo {
|
||||||
|
merchants: vec![Merchant { m_id: 1, m_name: "X".into() }],
|
||||||
|
});
|
||||||
|
assert!(out.contains('-'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,90 @@
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
use rcc::TerminalSize;
|
use rcc::TerminalSize;
|
||||||
|
|
||||||
use crate::repository::schema_repo::SchemaRepo;
|
use crate::{entity::schema::Schema, repository::schema_repo::SchemaRepoTrait};
|
||||||
|
|
||||||
pub struct SchemaService {
|
pub struct SchemaService<R> {
|
||||||
repo: SchemaRepo,
|
repo: R,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TerminalSize for SchemaService {}
|
impl<R> TerminalSize for SchemaService<R> {}
|
||||||
|
|
||||||
impl SchemaService {
|
impl<R: SchemaRepoTrait> SchemaService<R> {
|
||||||
pub fn new(repo: SchemaRepo) -> Self {
|
pub fn new(repo: R) -> Self {
|
||||||
Self { repo }
|
Self { repo }
|
||||||
}
|
}
|
||||||
pub fn get_columns(&mut self, column: &str) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
|
pub fn get_columns(&mut self, column: &str, out: &mut impl Write) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let columns = self.repo.find_by_column(column)?;
|
let columns = self.repo.find_by_column(column)?;
|
||||||
|
|
||||||
if columns.is_empty() {
|
if columns.is_empty() {
|
||||||
println!("No column found!");
|
writeln!(out, "No column found!")?;
|
||||||
} else {
|
} else {
|
||||||
columns.into_iter().for_each(|column| {
|
columns.into_iter().for_each(|column: Schema| {
|
||||||
println!("{}", "-".repeat(self.get_width()));
|
writeln!(out, "{}", "-".repeat(self.get_width())).ok();
|
||||||
println!("Table name: {}", column.table_name);
|
writeln!(out, "Table name: {}", column.table_name).ok();
|
||||||
println!("Column name: {}", column.column_name);
|
writeln!(out, "Column name: {}", column.column_name).ok();
|
||||||
println!("Type: {}", column.column_type);
|
writeln!(out, "Type: {}", column.column_type).ok();
|
||||||
});
|
});
|
||||||
println!("{}", "-".repeat(self.get_width()));
|
writeln!(out, "{}", "-".repeat(self.get_width()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::{entity::schema::Schema, repository::schema_repo::SchemaRepoTrait};
|
||||||
|
|
||||||
|
struct MockSchemaRepo {
|
||||||
|
columns: Vec<Schema>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SchemaRepoTrait for MockSchemaRepo {
|
||||||
|
fn find_by_column(&mut self, _: &str) -> Result<Vec<Schema>, mysql::Error> {
|
||||||
|
Ok(std::mem::take(&mut self.columns))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(repo: MockSchemaRepo) -> String {
|
||||||
|
let mut svc = SchemaService::new(repo);
|
||||||
|
let mut out = Vec::new();
|
||||||
|
svc.get_columns("any", &mut out).unwrap();
|
||||||
|
String::from_utf8(out).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn not_found_message() {
|
||||||
|
let out = run(MockSchemaRepo { columns: vec![] });
|
||||||
|
assert_eq!(out, "No column found!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prints_schema_fields() {
|
||||||
|
let out = run(MockSchemaRepo {
|
||||||
|
columns: vec![Schema {
|
||||||
|
table_name: "orders".into(),
|
||||||
|
column_name: "status".into(),
|
||||||
|
column_type: "varchar(32)".into(),
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
assert!(out.contains("Table name: orders"));
|
||||||
|
assert!(out.contains("Column name: status"));
|
||||||
|
assert!(out.contains("Type: varchar(32)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiple_columns_all_printed() {
|
||||||
|
let out = run(MockSchemaRepo {
|
||||||
|
columns: vec![
|
||||||
|
Schema { table_name: "t1".into(), column_name: "c1".into(), column_type: "int".into() },
|
||||||
|
Schema { table_name: "t2".into(), column_name: "c2".into(), column_type: "text".into() },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
assert!(out.contains("t1"));
|
||||||
|
assert!(out.contains("t2"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue