Compare commits

..

78 Commits

Author SHA1 Message Date
d7f0dd5950 feat: Added TUI and CLI support for viewing Artist history in Lidarr 2026-01-14 16:09:37 -07:00
8b9467bd39 feat: Full Lidarr system support for both the CLI and TUI 2026-01-14 14:50:33 -07:00
c74d5936d2 feat: Full CLI and TUI support for the Lidarr Indexers tab 2026-01-14 13:30:51 -07:00
8abcf44866 feat: Full support for adding a root folder in Lidarr from both the CLI and TUI 2026-01-14 09:06:27 -07:00
d2217509f2 feat: naive lidarr root folder tab implementation. Needs improved add logic 2026-01-13 14:33:12 -07:00
c68cd75015 feat: Downloads tab support in Lidarr 2026-01-13 13:40:18 -07:00
e1a25bfaf2 refactor: Improved and simplified the implementation of history details for both Sonarr and Lidarr 2026-01-13 12:36:21 -07:00
ad9e2b3671 feat: Created a History tab in the Radarr UI and created a list history command and mark-history-item-as-failed command for Radarr 2026-01-13 12:35:54 -07:00
0172253d20 fix: Slowed down the automatic text scrolling in tables so the text is readable 2026-01-12 15:28:41 -07:00
47fdee190a fix: Expanded the history item details size so that it can include all the available information for a given item; was previously being cut off on some screens 2026-01-12 15:28:03 -07:00
68b08d1cd7 feat: Implemented the Lidarr History tab and CLI support 2026-01-12 14:43:05 -07:00
f31810e48a feat: TUI support for deleting a Lidarr album from the artist details popup 2026-01-09 17:21:10 -07:00
09bee7473f feat: CLI support for deleting an album from Lidarr 2026-01-09 16:33:32 -07:00
b2814371f0 feat: Completed support for viewing Lidarr artist details 2026-01-09 16:22:03 -07:00
269057867f fix: Bug in submitting the update series prompt in the series details UI in Sonarr 2026-01-09 14:01:46 -07:00
450fdd7106 test: Added tests for the Lidarr context clues for the add artist popup 2026-01-08 15:20:17 -07:00
c624d1b9e4 feat: Full CLI and TUI support for adding an artist to Lidarr 2026-01-08 15:16:01 -07:00
e94f78dc7b refactor: Let serde serialize Add Series and Add Movie enums instead of calling to_string up front 2026-01-08 10:52:30 -07:00
b1a6db21f1 fix: Don't include Lidarr artist disambiguation in Edit popup title when it is empty 2026-01-08 10:09:15 -07:00
ca208ff5e4 fix: Refactored how quality profiles, language profiles, and metadata profiles are populated for each servarr so they sort using the ID to mimic the web UI better 2026-01-08 10:05:15 -07:00
1a43d1ec7c feat: Include the Lidarr artist disambiguation in the title of the Edit Artist popup 2026-01-08 09:28:56 -07:00
4abf705cb5 fix: Added the correct keybinding context to the Lidarr edit artist popup 2026-01-08 09:28:12 -07:00
cf98b10d77 style: Modified the LineGauge style so its more readable in the UI 2026-01-07 17:19:32 -07:00
f0ed71b436 build: Upgraded to Ratatui v0.30.0 and fixed a new security vulnerability [#13] 2026-01-07 17:15:54 -07:00
243de47cae feat: Initial Lidarr support for searching for new artists 2026-01-07 15:53:18 -07:00
d3947d9e15 fix: Improved fault tolerance for search result tables and test all indexer results tables 2026-01-07 14:58:32 -07:00
64d8c65831 fix: Prevented additional empty slice errors in indexer tables 2026-01-07 14:09:12 -07:00
60c4cf1098 fix: Fixed a bug in all Servarr implementations to not try to get the current selection of a search table when an error is returned from the API 2026-01-07 14:00:02 -07:00
9cc3ccb419 feat: Lidarr CLI commands to list quality profiles and metadata profiles 2026-01-07 13:15:50 -07:00
45c61369c8 feat: Improved CLI readability by creating a separate Global Options section for global flags 2026-01-07 13:08:23 -07:00
a8609e08c5 feat: CLI support for deleting a tag in Lidarr 2026-01-07 12:50:23 -07:00
a18b047f4f feat: Lidarr CLI support for listing and adding tags 2026-01-07 12:20:39 -07:00
b1afdaf541 feat: Added CLI and TUI support for editing Lidarr artists 2026-01-07 12:01:03 -07:00
3c1634d1e3 testing 2026-01-07 10:45:49 -07:00
9b4eda6a9d feat: Support for updating all Lidarr artists in both the CLI and TUI 2026-01-06 12:47:10 -07:00
96308afeee feat: Added Lidarr CLI support for fetching the host config and the security config 2026-01-06 11:00:19 -07:00
4e13d5d34d style: Applied formatting for the lidarr_command_tests 2026-01-06 10:25:05 -07:00
b4a99d1665 feat: Created Lidarr commands: 'get artist-details' and 'get system-status' 2026-01-06 10:24:51 -07:00
a012f6ecd5 feat: Fetch the artist members as part of the artist details query 2026-01-06 10:10:28 -07:00
5afee1998b feat: Support for toggling the monitoring of a given artist via the CLI and TUI 2026-01-06 09:40:16 -07:00
059fa48bd9 style: Applied uniform formatting across all new Lidarr files 2026-01-05 15:46:16 -07:00
6771a0ab38 feat: Full support for deleting an artist via CLI and TUI 2026-01-05 15:44:51 -07:00
bc3aeefa6e feat: TUI support for Lidarr library 2026-01-05 13:10:30 -07:00
e61537942b test: Implemented tests for the Lidarr list artists command 2026-01-05 11:28:35 -07:00
5d09b2402c feat: CLI support for listing artists 2026-01-05 10:58:48 -07:00
368f7505ff feat: Improved UI speed and responsiveness
Check / stable / fmt (push) Has been cancelled
Check / beta / clippy (push) Has been cancelled
Check / stable / clippy (push) Has been cancelled
Check / nightly / doc (push) Has been cancelled
Check / 1.89.0 / check (push) Has been cancelled
Test Suite / ubuntu / beta (push) Has been cancelled
Test Suite / ubuntu / stable (push) Has been cancelled
Test Suite / macos-latest / stable (push) Has been cancelled
Test Suite / windows-latest / stable (push) Has been cancelled
Test Suite / ubuntu / stable / coverage (push) Has been cancelled
2025-12-19 13:41:14 -07:00
6a9fd0999c build: Added additional compile-time optimizations for speed improvements and a smaller binary 2025-12-17 16:45:51 -07:00
Alex Clarke
d8ac94d067 Merge pull request #51 from Dark-Alex-17/test-refactoring
Refactored all tests and wrote snapshot tests to finally cover the UI module
2025-12-16 16:41:04 -07:00
0532d59746 test: Wrote snapshot tests for all Sonarr UI 2025-12-16 14:12:10 -07:00
e0fcbc71e1 ci: updated Cargo Husky hooks to not use the Makefile 2025-12-15 20:21:14 -07:00
c072c57bbb style: Applied uniform formatting across all new UI test files 2025-12-15 20:18:28 -07:00
aadd6c8abf fix: Fixed an issue with the Managarr table that would incorrectly try to display things before is_loading was ready 2025-12-15 20:17:28 -07:00
316ed64315 test: Created snapshot tests for all Radarr UI modules 2025-12-15 20:17:09 -07:00
7084ca1be2 ci: Added more aliases and commands to the Justfile for snapshot tests 2025-12-15 20:16:10 -07:00
317daddb8e fix: Fixed a bug where the edit collection popup would not display when opening it from collection details 2025-12-15 20:15:52 -07:00
8ef291efd8 ci: Fixed some typos in the justfile 2025-12-15 13:22:00 -07:00
92be9c50bf docs: Updated contributing docs to mention how to use Just 2025-12-15 13:18:54 -07:00
f0e5ecd5de ci: Migrated to using Just instead of using Makefiles and included a helix config 2025-12-15 13:16:16 -07:00
e2c44583e8 style: Applied formatting to recently merged in files 2025-12-15 08:37:54 -07:00
5da741f3a9 ci: Updated Cargo.lock after main merge 2025-12-15 08:37:28 -07:00
35c5eb65cb Merge remote-tracking branch 'origin/main' into test-refactoring
# Conflicts:
#	Cargo.lock
#	src/cli/mod.rs
#	src/cli/radarr/add_command_handler.rs
#	src/cli/radarr/get_command_handler.rs
#	src/cli/radarr/list_command_handler.rs
2025-12-15 07:55:14 -07:00
7e53a26e5f Merge remote-tracking branch 'origin/main' into test-refactoring 2025-12-15 07:52:47 -07:00
82f30f126d test: Implemented UI snapshot tests 2025-12-12 15:44:11 -07:00
c3fa689617 refactor: Use is_multiple_of for the tick counter in the UI module 2025-12-09 14:30:17 -07:00
b51e42b4b2 refactor: Updated all model tests to use purpose-built assertions to improve readability and maintainability 2025-12-09 14:29:35 -07:00
d4bea91186 refactor: Updated all handler tests to use purpose built assertions to improve readability and maintainability 2025-12-09 14:28:47 -07:00
d47dadeb88 refactor: Used is_multiple_of to make life easier and cleaner in the app module 2025-12-09 14:28:09 -07:00
b807904c6c refactor: Refactored all cli tests to use purpose-built assertions 2025-12-08 17:07:31 -07:00
ee1bee22eb refactor: Improved test assertions in the app module 2025-12-08 16:47:40 -07:00
f6c4c1623f style: Removed unused imports after massive handler testing refactor 2025-12-04 23:03:48 -07:00
49fd086b92 refactor: Created dedicated proptests and assertions to clean up the handler unit tests 2025-12-04 22:57:06 -07:00
35dce0bf01 refactor: Migrated the handle_table_events macro into a trait for better IDE support, created a TableEventAdapter wrapper for the KeyEventHandlers to make it so that the trait can be used properly and a simple function to replace the previous call to the handle_table_events macro 2025-12-04 16:03:58 -07:00
71240373c0 refactor: Simplified both the table_handler macro and the stateful_table implementation 2025-12-04 11:34:45 -07:00
659023d561 refactor: Improved error handling for the tail-logs subcommand to propagate errors up the stack instead of exiting there. 2025-12-04 10:10:19 -07:00
a0073b65ad refactor: Added accessor methods to servarr_data structs, replaced for loops with functional iterator chains, eliminated mutable state tracking, and updated network module to use get_or_insert_default() for modal options 2025-12-04 10:02:32 -07:00
cba53e0841 refactor: Improved error handling project-wide and cleaned up some regexes with unnecessary escapes (tail_logs and interpolate_env_vars) 2025-12-04 09:03:58 -07:00
e50fb88bfc refactor: Refactored to use more idiomatic let-else statements where applicable 2025-12-03 15:23:07 -07:00
ad58912baf test: Updated Rust edition to 2024 and refactored network module tests to be more idiomatic 2025-12-03 14:49:27 -07:00
906 changed files with 76386 additions and 11225 deletions
+2 -5
View File
@@ -7,8 +7,5 @@ echo "Running pre-push hook:"
echo "Executing: cargo fmt" echo "Executing: cargo fmt"
cargo fmt cargo fmt
echo "Executing: make lint" echo "Executing: cargo clippy --all"
make lint cargo clippy --all
echo "Executing: cargo test"
cargo test
+4 -4
View File
@@ -7,8 +7,8 @@ echo "Running pre-push hook:"
echo "Executing: cargo fmt --check" echo "Executing: cargo fmt --check"
cargo fmt --check cargo fmt --check
echo "Executing: make lint" echo "Executing: cargo clippy --all"
make lint cargo clippy --all
echo "Executing: cargo test" echo "Executing: cargo test --all"
cargo test cargo test --all
+5
View File
@@ -0,0 +1,5 @@
[keys.normal.backspace]
b = ":sh zellij run -x '4%%' --width '92%%' -f -n Build -- just build"
r = ":sh zellij run -x '3%%' -y '8%%' --width '95%%' --height '90%%' -fc -n 'Run' -- just run"
t = ":sh zellij run -x '4%%' --width '92%%' -f -n Tests -- just test"
l = ":sh zellij run -x '4%%' --width '92%%' -f -n Lint -- just lint"
+2 -1
View File
@@ -65,7 +65,8 @@ cz commit
1. Clone this repo 1. Clone this repo
2. Run `cargo test` to set up hooks 2. Run `cargo test` to set up hooks
3. Make changes 3. Make changes
4. Run the application using `make run` or `cargo run` 4. Run the application using `just run` or `just run`
- Install `just` (`cargo install just`) if you haven't already to use the [justfile](./justfile) in this project.
5. Commit changes. This will trigger pre-commit hooks that will run format, test and lint. If there are errors or warnings from Clippy, please fix them. 5. Commit changes. This will trigger pre-commit hooks that will run format, test and lint. If there are errors or warnings from Clippy, please fix them.
6. Push your code to a new branch named after the feature/bug/etc. you're adding. This will trigger pre-push hooks that will run lint and test. 6. Push your code to a new branch named after the feature/bug/etc. you're adding. This will trigger pre-push hooks that will run lint and test.
7. Create a PR 7. Create a PR
Generated
+1118 -392
View File
File diff suppressed because it is too large Load Diff
+9 -5
View File
@@ -8,7 +8,7 @@ documentation = "https://github.com/Dark-Alex-17/managarr"
repository = "https://github.com/Dark-Alex-17/managarr" repository = "https://github.com/Dark-Alex-17/managarr"
homepage = "https://github.com/Dark-Alex-17/managarr" homepage = "https://github.com/Dark-Alex-17/managarr"
readme = "README.md" readme = "README.md"
edition = "2021" edition = "2024"
license = "MIT" license = "MIT"
rust-version = "1.89.0" rust-version = "1.89.0"
exclude = [".github", "CONTRIBUTING.md", "*.log", "tags"] exclude = [".github", "CONTRIBUTING.md", "*.log", "tags"]
@@ -42,7 +42,7 @@ strum = { version = "0.26.3", features = ["derive"] }
strum_macros = "0.26.4" strum_macros = "0.26.4"
tokio = { version = "1.44.2", features = ["full"] } tokio = { version = "1.44.2", features = ["full"] }
tokio-util = "0.7.8" tokio-util = "0.7.8"
ratatui = { version = "0.29.0", features = [ ratatui = { version = "0.30.0", features = [
"all-widgets", "all-widgets",
"unstable-widget-ref", "unstable-widget-ref",
] } ] }
@@ -59,11 +59,10 @@ ctrlc = "3.4.5"
colored = "3.0.0" colored = "3.0.0"
async-trait = "0.1.83" async-trait = "0.1.83"
dirs-next = "2.0.0" dirs-next = "2.0.0"
managarr-tree-widget = "0.24.0" managarr-tree-widget = "0.25.0"
indicatif = "0.17.9" indicatif = "0.17.9"
derive_setters = "0.1.6" derive_setters = "0.1.6"
deunicode = "1.6.0" deunicode = "1.6.0"
paste = "1.0.15"
openssl = { version = "0.10.70", features = ["vendored"] } openssl = { version = "0.10.70", features = ["vendored"] }
veil = "0.2.0" veil = "0.2.0"
validate_theme_derive = "0.1.0" validate_theme_derive = "0.1.0"
@@ -74,8 +73,11 @@ assert_cmd = "2.0.16"
mockall = "0.13.0" mockall = "0.13.0"
mockito = "1.0.0" mockito = "1.0.0"
pretty_assertions = "1.3.0" pretty_assertions = "1.3.0"
proptest = "1.6.0"
rstest = "0.25.0" rstest = "0.25.0"
serial_test = "3.2.0" serial_test = "3.2.0"
assertables = "9.8.2"
insta = "1.41.1"
[dev-dependencies.cargo-husky] [dev-dependencies.cargo-husky]
version = "1" version = "1"
@@ -89,4 +91,6 @@ name = "managarr"
[profile.release] [profile.release]
lto = true lto = true
codegen-units = 1 codegen-units = 3
opt-level = "s"
strip = true
-47
View File
@@ -1,47 +0,0 @@
#!make
VERSION := latest
IMG_NAME := darkalex17/managarr
IMAGE := ${IMG_NAME}:${VERSION}
default: run
.PHONY: test test-cov build run lint lint-fix fmt analyze sonar release delete-tag
test:
@cargo test --all
## Run all tests with coverage - `cargo install cargo-tarpaulin`
test-cov:
@cargo tarpaulin
build: test
@cargo build --release
docker:
@DOCKER_BUILDKIT=1 docker build --rm -t ${IMAGE} .
run:
@CARGO_INCREMENTAL=1 cargo fmt && make lint && cargo run
lint:
@find . | grep '\.\/src\/.*\.rs$$' | xargs touch && CARGO_INCREMENTAL=0 cargo clippy --all-targets --workspace
lint-fix:
@cargo fix
fmt:
@cargo fmt
minimal-versions:
@cargo +nightly update -Zdirect-minimal-versions
## Analyze for unsafe usage - `cargo install cargo-geiger`
analyze:
@cargo geiger
release:
@git tag -a ${V} -m "Release ${V}" && git push origin ${V}
delete-tag:
@git tag -d ${V} && git push --delete origin ${V}
-1
View File
@@ -12,4 +12,3 @@ coverage:
ignore: ignore:
- "**/*_tests.rs" - "**/*_tests.rs"
- "src/ui"
+91
View File
@@ -0,0 +1,91 @@
VERSION := "latest"
IMG_NAME := "darkalex17/managarr"
IMAGE := "{{IMG_NAME}}:{{VERSION}}"
# List all recipes
default:
@just --list
# Format all files
[group: 'style']
fmt:
@cargo fmt --all
alias clippy := lint
# Run Clippy to inspect all files
[group: 'style']
lint:
@cargo clippy --all
alias clippy-fix := lint-fix
# Automatically fix clippy issues where possible
[group: 'style']
lint-fix:
@cargo fix
# Analyze the project for unsafe usage
[group: 'style']
@analyze:
#!/usr/bin/env bash
cargo geiger -h > /dev/null 2>&1 | cargo install cargo-geiger
cargo geiger
# Run all tests
[group: 'test']
test:
@cargo test --all
# Run all tests with coverage
[group:'test']
@test-cov:
#!/usr/bin/env bash
cargo tarpaulin -h > /dev/null 2>&1 || cargo install cargo-tarpaulin
cargo tarpaulin
# Run all doc tests
[group: 'test']
doctest:
@cargo test --all --doc
# Run all proptests
[group: 'test']
proptest:
@cargo test proptest
alias test-snapshots := snapshot-tests
# Run all snapshot tests
[group: 'test']
snapshot-tests:
@cargo test snapshot
alias review := snapshot-review
# Review snapshot test changes
[group: 'test']
@snapshot-review:
#!/usr/bin/env bash
cargo insta -h > /dev/null 2>&1 || cargo install cargo-insta
cargo insta review
alias clean-orphaned-snapshots := snapshot-delete-unreferenced
# Delete any unreferenced snapshots
[group: 'test']
@snapshot-delete-unreferenced:
#!/usr/bin/env bash
cargo insta -h > /dev/null 2>&1 || cargo install cargo-insta
cargo insta test --unreferenced=delete
# Build and run the binary for the current system
run:
@cargo run
# Build the project for the current system architecture
[group: 'build']
[arg('build_type', pattern="debug|release")]
build build_type='debug':
@cargo build {{ if build_type == "release" { "--release" } else { "" } }}
# Build the docker image
[group: 'build']
build-docker:
@DOCKER_BUILDKIT=1 docker build --rm -t {{IMAGE}}
@@ -0,0 +1,7 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 56330c025ad79db641d0eb9f429ab74e95822e1fb015b58f0e158ea674cd42a1 # shrinks to list_size = 1, page_ops = 1
@@ -0,0 +1,9 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc fb4b58aa3015a125fc33a78dfaf27981db4191247151b327a351fc445e07c231 # shrinks to input = "j"
cc d6ec17d4d3f635f0a095ade650a316d26abc1f9fe2b6d9cf67bf2f8b4ebedb60 # shrinks to backspace_count = 0
cc cd46ee46e18cf86c940fb89c7206f0b482909880b8f2eabe3dd20682b9912c8a # shrinks to input = "h"
@@ -0,0 +1,9 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 24ae243412a324cb46c36cb4f629ddd4c9326b1479d1186d9b5545ac5e86dbba # shrinks to num_scroll_attempts = 0
cc c06a1cc1e4740b2498c50d7be64715bf09ef3ac4cf3bb3642f960578a3e06c74 # shrinks to is_loading = false, num_items = 1
cc 930207899afea2d389c7fa3974e31c2eb1803e71bcbd8179246c795903905ec7 # shrinks to parent_width = 20, parent_height = 12, percent_x = 1, percent_y = 1
+62 -35
View File
@@ -7,12 +7,13 @@ mod tests {
use serial_test::serial; use serial_test::serial;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use crate::app::{interpolate_env_vars, App, AppConfig, Data, ServarrConfig}; use crate::app::{App, AppConfig, Data, ServarrConfig, interpolate_env_vars};
use crate::models::servarr_data::lidarr::lidarr_data::LidarrData;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData};
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SonarrData}; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SonarrData};
use crate::models::{HorizontallyScrollableText, TabRoute}; use crate::models::{HorizontallyScrollableText, TabRoute};
use crate::network::radarr_network::RadarrEvent;
use crate::network::NetworkEvent; use crate::network::NetworkEvent;
use crate::network::radarr_network::RadarrEvent;
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
#[test] #[test]
@@ -35,6 +36,7 @@ mod tests {
theme: None, theme: None,
radarr: Some(vec![radarr_config_1.clone(), radarr_config_2.clone()]), radarr: Some(vec![radarr_config_1.clone(), radarr_config_2.clone()]),
sonarr: Some(vec![sonarr_config_1.clone(), sonarr_config_2.clone()]), sonarr: Some(vec![sonarr_config_1.clone(), sonarr_config_2.clone()]),
lidarr: None,
}; };
let expected_tab_routes = vec![ let expected_tab_routes = vec![
TabRoute { TabRoute {
@@ -69,17 +71,18 @@ mod tests {
CancellationToken::new(), CancellationToken::new(),
); );
assert!(app.navigation_stack.is_empty()); assert_is_empty!(app.navigation_stack);
assert_eq!(app.get_current_route(), ActiveSonarrBlock::default().into()); assert_eq!(app.get_current_route(), ActiveSonarrBlock::default().into());
assert!(app.network_tx.is_some()); assert_some!(app.network_tx);
assert!(!app.cancellation_token.is_cancelled()); assert!(!app.cancellation_token.is_cancelled());
assert!(app.is_first_render); assert!(app.is_first_render);
assert_eq!(app.error, HorizontallyScrollableText::default()); assert_eq!(app.error, HorizontallyScrollableText::default());
assert_eq!(app.server_tabs.index, 0); assert_eq!(app.server_tabs.index, 0);
assert_eq!(app.server_tabs.tabs, expected_tab_routes); assert_eq!(app.server_tabs.tabs, expected_tab_routes);
assert_eq!(app.tick_until_poll, 400); assert_eq!(app.tick_until_poll, 400);
assert_eq!(app.ticks_until_scroll, 4); assert_eq!(app.ticks_until_scroll, 64);
assert_eq!(app.tick_count, 0); assert_eq!(app.tick_count, 0);
assert_eq!(app.ui_scroll_tick_count, 0);
assert!(!app.is_loading); assert!(!app.is_loading);
assert!(!app.is_routing); assert!(!app.is_routing);
assert!(!app.should_refresh); assert!(!app.should_refresh);
@@ -91,14 +94,14 @@ mod tests {
fn test_app_default() { fn test_app_default() {
let app = App::default(); let app = App::default();
assert!(app.navigation_stack.is_empty()); assert_is_empty!(app.navigation_stack);
assert!(app.network_tx.is_none()); assert_none!(app.network_tx);
assert!(!app.cancellation_token.is_cancelled()); assert!(!app.cancellation_token.is_cancelled());
assert!(app.is_first_render); assert!(app.is_first_render);
assert_eq!(app.error, HorizontallyScrollableText::default()); assert_eq!(app.error, HorizontallyScrollableText::default());
assert_eq!(app.server_tabs.index, 0); assert_eq!(app.server_tabs.index, 0);
assert_eq!(app.tick_until_poll, 400); assert_eq!(app.tick_until_poll, 400);
assert_eq!(app.ticks_until_scroll, 4); assert_eq!(app.ticks_until_scroll, 64);
assert_eq!(app.tick_count, 0); assert_eq!(app.tick_count, 0);
assert!(!app.is_loading); assert!(!app.is_loading);
assert!(!app.is_routing); assert!(!app.is_routing);
@@ -183,6 +186,7 @@ mod tests {
..SonarrData::default() ..SonarrData::default()
}; };
let data = Data { let data = Data {
lidarr_data: LidarrData::default(),
radarr_data, radarr_data,
sonarr_data, sonarr_data,
}; };
@@ -240,6 +244,27 @@ mod tests {
assert_eq!(app.tick_count, 0); assert_eq!(app.tick_count, 0);
} }
#[test]
fn test_on_ui_scroll_tick() {
let mut app = App {
ticks_until_scroll: 1,
..App::default()
};
assert_eq!(app.ui_scroll_tick_count, 0);
assert_eq!(app.tick_count, 0);
app.on_ui_scroll_tick();
assert_eq!(app.ui_scroll_tick_count, 1);
assert_eq!(app.tick_count, 0);
app.on_ui_scroll_tick();
assert_eq!(app.ui_scroll_tick_count, 0);
assert_eq!(app.tick_count, 0);
}
#[tokio::test] #[tokio::test]
async fn test_on_tick_first_render() { async fn test_on_tick_first_render() {
let (sync_network_tx, mut sync_network_rx) = mpsc::channel::<NetworkEvent>(500); let (sync_network_tx, mut sync_network_rx) = mpsc::channel::<NetworkEvent>(500);
@@ -326,23 +351,23 @@ mod tests {
fn test_app_config_default() { fn test_app_config_default() {
let app_config = AppConfig::default(); let app_config = AppConfig::default();
assert!(app_config.radarr.is_none()); assert_none!(app_config.radarr);
assert!(app_config.sonarr.is_none()); assert_none!(app_config.sonarr);
} }
#[test] #[test]
fn test_servarr_config_default() { fn test_servarr_config_default() {
let servarr_config = ServarrConfig::default(); let servarr_config = ServarrConfig::default();
assert_eq!(servarr_config.name, None); assert_none!(servarr_config.name);
assert_eq!(servarr_config.host, Some("localhost".to_string())); assert_some_eq_x!(&servarr_config.host, "localhost");
assert_eq!(servarr_config.port, None); assert_none!(servarr_config.port);
assert_eq!(servarr_config.uri, None); assert_none!(servarr_config.uri);
assert_eq!(servarr_config.weight, None); assert_none!(servarr_config.weight);
assert_eq!(servarr_config.api_token, Some(String::new())); assert_some_eq_x!(&servarr_config.api_token, "");
assert_eq!(servarr_config.api_token_file, None); assert_none!(servarr_config.api_token_file);
assert_eq!(servarr_config.ssl_cert_path, None); assert_none!(servarr_config.ssl_cert_path);
assert_eq!(servarr_config.custom_headers, None); assert_none!(servarr_config.custom_headers);
} }
#[test] #[test]
@@ -367,11 +392,11 @@ mod tests {
assert!(custom.is_object()); assert!(custom.is_object());
let obj = custom.as_object().unwrap(); let obj = custom.as_object().unwrap();
assert_eq!(obj.get("x-api-key").unwrap(), "abc123"); assert_some_eq_x!(obj.get("x-api-key"), "abc123");
assert_eq!(obj.get("header-1").unwrap(), "test"); assert_some_eq_x!(obj.get("header-1"), "test");
assert!(obj.get("X-Api-Key").is_none()); assert_none!(obj.get("X-Api-Key"));
assert!(obj.get("HEADER-1").is_none()); assert_none!(obj.get("HEADER-1"));
} }
#[test] #[test]
@@ -392,7 +417,7 @@ mod tests {
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap(); let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
assert_eq!(config.host, Some("localhost".to_string())); assert_some_eq_x!(&config.host, "localhost");
unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_OPTION") }; unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_OPTION") };
} }
@@ -407,7 +432,7 @@ mod tests {
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap(); let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
assert_eq!(config.host, Some("www.example.com".to_string())); assert_some_eq_x!(&config.host, "www.example.com");
unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_OPTION_NO_OVERWRITE") }; unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_OPTION_NO_OVERWRITE") };
} }
@@ -419,7 +444,7 @@ mod tests {
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap(); let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
assert_eq!(config.port, None); assert_none!(config.port);
} }
#[test] #[test]
@@ -440,7 +465,7 @@ mod tests {
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap(); let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
assert_eq!(config.custom_headers, Some(expected_custom_headers)); assert_some_eq_x!(&config.custom_headers, &expected_custom_headers);
unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_HEADER_OPTION") }; unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_HEADER_OPTION") };
} }
@@ -467,7 +492,7 @@ mod tests {
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap(); let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
assert_eq!(config.custom_headers, Some(expected_custom_headers)); assert_some_eq_x!(&config.custom_headers, &expected_custom_headers);
unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_HEADER_OPTION_NO_OVERWRITE") }; unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_HEADER_OPTION_NO_OVERWRITE") };
} }
@@ -479,7 +504,7 @@ mod tests {
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap(); let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
assert_eq!(config.custom_headers, None); assert_none!(config.custom_headers);
} }
#[test] #[test]
@@ -493,7 +518,7 @@ mod tests {
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap(); let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
assert_eq!(config.port, Some(1)); assert_some_eq_x!(config.port, 1);
unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_OPTION_U16") }; unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_OPTION_U16") };
} }
@@ -508,7 +533,7 @@ mod tests {
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap(); let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
assert_eq!(config.port, Some(1234)); assert_some_eq_x!(config.port, 1234);
unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_OPTION_U16_UNUSED") }; unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_OPTION_U16_UNUSED") };
} }
@@ -520,9 +545,9 @@ mod tests {
"#; "#;
let result: Result<ServarrConfig, _> = serde_yaml::from_str(yaml_data); let result: Result<ServarrConfig, _> = serde_yaml::from_str(yaml_data);
assert!(result.is_err()); assert_err!(&result);
let err = result.unwrap_err().to_string(); let err = result.unwrap_err().to_string();
assert!(err.contains("invalid digit found in string")); assert_contains!(err, "invalid digit found in string");
} }
#[test] #[test]
@@ -533,7 +558,7 @@ mod tests {
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap(); let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
assert_eq!(config.port, None); assert_none!(config.port);
} }
#[test] #[test]
@@ -597,7 +622,9 @@ mod tests {
let ssl_cert_path = "/some/path".to_owned(); let ssl_cert_path = "/some/path".to_owned();
let mut custom_headers = HeaderMap::new(); let mut custom_headers = HeaderMap::new();
custom_headers.insert("X-Custom-Header", "value".parse().unwrap()); custom_headers.insert("X-Custom-Header", "value".parse().unwrap());
let expected_str = format!("ServarrConfig {{ name: Some(\"{name}\"), host: Some(\"{host}\"), port: Some({port}), uri: Some(\"{uri}\"), weight: Some({weight}), api_token: Some(\"***********\"), api_token_file: Some(\"{api_token_file}\"), ssl_cert_path: Some(\"{ssl_cert_path}\"), custom_headers: Some({{\"x-custom-header\": \"value\"}}) }}"); let expected_str = format!(
"ServarrConfig {{ name: Some(\"{name}\"), host: Some(\"{host}\"), port: Some({port}), uri: Some(\"{uri}\"), weight: Some({weight}), api_token: Some(\"***********\"), api_token_file: Some(\"{api_token_file}\"), ssl_cert_path: Some(\"{ssl_cert_path}\"), custom_headers: Some({{\"x-custom-header\": \"value\"}}) }}"
);
let servarr_config = ServarrConfig { let servarr_config = ServarrConfig {
name: Some(name), name: Some(name),
host: Some(host), host: Some(host),
+21 -2
View File
@@ -1,7 +1,8 @@
use crate::app::key_binding::{KeyBinding, DEFAULT_KEYBINDINGS}; use crate::app::App;
use crate::app::key_binding::{DEFAULT_KEYBINDINGS, KeyBinding};
use crate::app::lidarr::lidarr_context_clues::LidarrContextClueProvider;
use crate::app::radarr::radarr_context_clues::RadarrContextClueProvider; use crate::app::radarr::radarr_context_clues::RadarrContextClueProvider;
use crate::app::sonarr::sonarr_context_clues::SonarrContextClueProvider; use crate::app::sonarr::sonarr_context_clues::SonarrContextClueProvider;
use crate::app::App;
use crate::models::Route; use crate::models::Route;
#[cfg(test)] #[cfg(test)]
@@ -21,6 +22,7 @@ impl ContextClueProvider for ServarrContextClueProvider {
match app.get_current_route() { match app.get_current_route() {
Route::Radarr(_, _) => RadarrContextClueProvider::get_context_clues(app), Route::Radarr(_, _) => RadarrContextClueProvider::get_context_clues(app),
Route::Sonarr(_, _) => SonarrContextClueProvider::get_context_clues(app), Route::Sonarr(_, _) => SonarrContextClueProvider::get_context_clues(app),
Route::Lidarr(_, _) => LidarrContextClueProvider::get_context_clues(app),
_ => None, _ => None,
} }
} }
@@ -100,6 +102,18 @@ pub static INDEXERS_CONTEXT_CLUES: [ContextClue; 6] = [
), ),
]; ];
pub static HISTORY_CONTEXT_CLUES: [ContextClue; 6] = [
(DEFAULT_KEYBINDINGS.submit, "details"),
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc),
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
(DEFAULT_KEYBINDINGS.esc, "cancel filter"),
];
pub static SYSTEM_CONTEXT_CLUES: [ContextClue; 5] = [ pub static SYSTEM_CONTEXT_CLUES: [ContextClue; 5] = [
(DEFAULT_KEYBINDINGS.tasks, "open tasks"), (DEFAULT_KEYBINDINGS.tasks, "open tasks"),
(DEFAULT_KEYBINDINGS.events, "open events"), (DEFAULT_KEYBINDINGS.events, "open events"),
@@ -110,3 +124,8 @@ pub static SYSTEM_CONTEXT_CLUES: [ContextClue; 5] = [
DEFAULT_KEYBINDINGS.refresh.desc, DEFAULT_KEYBINDINGS.refresh.desc,
), ),
]; ];
pub static SYSTEM_TASKS_CONTEXT_CLUES: [ContextClue; 2] = [
(DEFAULT_KEYBINDINGS.submit, "start task"),
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
];
+229 -190
View File
@@ -1,239 +1,286 @@
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use pretty_assertions::{assert_eq, assert_str_eq};
use crate::app::context_clues::{ use crate::app::context_clues::{
ContextClueProvider, ServarrContextClueProvider, BARE_POPUP_CONTEXT_CLUES, BARE_POPUP_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES,
BLOCKLIST_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, ContextClueProvider, DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
INDEXERS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES, SERVARR_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES, SERVARR_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
SYSTEM_CONTEXT_CLUES, SYSTEM_TASKS_CONTEXT_CLUES, ServarrContextClueProvider,
}; };
use crate::app::{key_binding::DEFAULT_KEYBINDINGS, App}; use crate::app::{App, key_binding::DEFAULT_KEYBINDINGS};
use crate::models::servarr_data::ActiveKeybindingBlock;
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock; use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
use crate::models::servarr_data::ActiveKeybindingBlock;
#[test] #[test]
fn test_servarr_context_clues() { fn test_servarr_context_clues() {
let mut servarr_context_clues_iter = SERVARR_CONTEXT_CLUES.iter(); let mut servarr_context_clues_iter = SERVARR_CONTEXT_CLUES.iter();
let (key_binding, description) = servarr_context_clues_iter.next().unwrap(); assert_some_eq_x!(
servarr_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.up); &(DEFAULT_KEYBINDINGS.up, "scroll up")
assert_str_eq!(*description, "scroll up"); );
assert_some_eq_x!(
let (key_binding, description) = servarr_context_clues_iter.next().unwrap(); servarr_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.down, "scroll down")
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.down); );
assert_str_eq!(*description, "scroll down"); assert_some_eq_x!(
servarr_context_clues_iter.next(),
let (key_binding, description) = servarr_context_clues_iter.next().unwrap(); &(DEFAULT_KEYBINDINGS.left, "previous tab")
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.left); assert_some_eq_x!(
assert_str_eq!(*description, "previous tab"); servarr_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.right, "next tab")
let (key_binding, description) = servarr_context_clues_iter.next().unwrap(); );
assert_some_eq_x!(
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.right); servarr_context_clues_iter.next(),
assert_str_eq!(*description, "next tab"); &(DEFAULT_KEYBINDINGS.pg_up, DEFAULT_KEYBINDINGS.pg_up.desc)
);
let (key_binding, description) = servarr_context_clues_iter.next().unwrap(); assert_some_eq_x!(
servarr_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.pg_up); &(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.pg_up.desc); DEFAULT_KEYBINDINGS.pg_down,
DEFAULT_KEYBINDINGS.pg_down.desc
let (key_binding, description) = servarr_context_clues_iter.next().unwrap(); )
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.pg_down); assert_some_eq_x!(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.pg_down.desc); servarr_context_clues_iter.next(),
&(
let (key_binding, description) = servarr_context_clues_iter.next().unwrap(); DEFAULT_KEYBINDINGS.next_servarr,
DEFAULT_KEYBINDINGS.next_servarr.desc
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.next_servarr); )
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.next_servarr.desc); );
assert_some_eq_x!(
let (key_binding, description) = servarr_context_clues_iter.next().unwrap(); servarr_context_clues_iter.next(),
&(
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.previous_servarr); DEFAULT_KEYBINDINGS.previous_servarr,
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.previous_servarr.desc); DEFAULT_KEYBINDINGS.previous_servarr.desc
)
let (key_binding, description) = servarr_context_clues_iter.next().unwrap(); );
assert_some_eq_x!(
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.quit); servarr_context_clues_iter.next(),
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.quit.desc); &(DEFAULT_KEYBINDINGS.quit, DEFAULT_KEYBINDINGS.quit.desc)
);
let (key_binding, description) = servarr_context_clues_iter.next().unwrap(); assert_some_eq_x!(
servarr_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.help); &(DEFAULT_KEYBINDINGS.help, DEFAULT_KEYBINDINGS.help.desc)
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.help.desc); );
assert_eq!(servarr_context_clues_iter.next(), None); assert_none!(servarr_context_clues_iter.next());
} }
#[test] #[test]
fn test_bare_popup_context_clues() { fn test_bare_popup_context_clues() {
let mut bare_popup_context_clues_iter = BARE_POPUP_CONTEXT_CLUES.iter(); let mut bare_popup_context_clues_iter = BARE_POPUP_CONTEXT_CLUES.iter();
let (key_binding, description) = bare_popup_context_clues_iter.next().unwrap(); assert_some_eq_x!(
bare_popup_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc); &(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc); );
assert_eq!(bare_popup_context_clues_iter.next(), None); assert_none!(bare_popup_context_clues_iter.next());
} }
#[test] #[test]
fn test_downloads_context_clues() { fn test_downloads_context_clues() {
let mut downloads_context_clues_iter = DOWNLOADS_CONTEXT_CLUES.iter(); let mut downloads_context_clues_iter = DOWNLOADS_CONTEXT_CLUES.iter();
let (key_binding, description) = downloads_context_clues_iter.next().unwrap(); assert_some_eq_x!(
downloads_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh); &(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc); DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc
let (key_binding, description) = downloads_context_clues_iter.next().unwrap(); )
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete); assert_some_eq_x!(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc); downloads_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc)
let (key_binding, description) = downloads_context_clues_iter.next().unwrap(); );
assert_some_eq_x!(
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update); downloads_context_clues_iter.next(),
assert_str_eq!(*description, "update downloads"); &(DEFAULT_KEYBINDINGS.update, "update downloads")
assert_eq!(downloads_context_clues_iter.next(), None); );
assert_none!(downloads_context_clues_iter.next());
} }
#[test] #[test]
fn test_blocklist_context_clues() { fn test_blocklist_context_clues() {
let mut blocklist_context_clues_iter = BLOCKLIST_CONTEXT_CLUES.iter(); let mut blocklist_context_clues_iter = BLOCKLIST_CONTEXT_CLUES.iter();
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap(); assert_some_eq_x!(
blocklist_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh); &(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc); DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap(); )
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort); assert_some_eq_x!(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc); blocklist_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap(); );
assert_some_eq_x!(
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit); blocklist_context_clues_iter.next(),
assert_str_eq!(*description, "details"); &(DEFAULT_KEYBINDINGS.submit, "details")
);
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap(); assert_some_eq_x!(
blocklist_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete); &(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc)
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc); );
assert_some_eq_x!(
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap(); blocklist_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.clear, "clear blocklist")
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.clear); );
assert_str_eq!(*description, "clear blocklist"); assert_none!(blocklist_context_clues_iter.next());
assert_eq!(blocklist_context_clues_iter.next(), None);
} }
#[test] #[test]
fn test_confirmation_prompt_context_clues() { fn test_confirmation_prompt_context_clues() {
let mut confirmation_prompt_context_clues_iter = CONFIRMATION_PROMPT_CONTEXT_CLUES.iter(); let mut confirmation_prompt_context_clues_iter = CONFIRMATION_PROMPT_CONTEXT_CLUES.iter();
let (key_binding, description) = confirmation_prompt_context_clues_iter.next().unwrap(); assert_some_eq_x!(
confirmation_prompt_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.confirm); &(DEFAULT_KEYBINDINGS.confirm, "submit")
assert_str_eq!(*description, "submit"); );
assert_some_eq_x!(
let (key_binding, description) = confirmation_prompt_context_clues_iter.next().unwrap(); confirmation_prompt_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.esc, "cancel")
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc); );
assert_str_eq!(*description, "cancel"); assert_none!(confirmation_prompt_context_clues_iter.next());
assert_eq!(confirmation_prompt_context_clues_iter.next(), None);
} }
#[test] #[test]
fn test_root_folders_context_clues() { fn test_root_folders_context_clues() {
let mut root_folders_context_clues_iter = ROOT_FOLDERS_CONTEXT_CLUES.iter(); let mut root_folders_context_clues_iter = ROOT_FOLDERS_CONTEXT_CLUES.iter();
let (key_binding, description) = root_folders_context_clues_iter.next().unwrap(); assert_some_eq_x!(
root_folders_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.add); &(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc)
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.add.desc); );
assert_some_eq_x!(
let (key_binding, description) = root_folders_context_clues_iter.next().unwrap(); root_folders_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc)
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete); );
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc); assert_some_eq_x!(
root_folders_context_clues_iter.next(),
let (key_binding, description) = root_folders_context_clues_iter.next().unwrap(); &(
DEFAULT_KEYBINDINGS.refresh,
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh); DEFAULT_KEYBINDINGS.refresh.desc
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc); )
assert_eq!(root_folders_context_clues_iter.next(), None); );
assert_none!(root_folders_context_clues_iter.next());
} }
#[test] #[test]
fn test_indexers_context_clues() { fn test_indexers_context_clues() {
let mut indexers_context_clues_iter = INDEXERS_CONTEXT_CLUES.iter(); let mut indexers_context_clues_iter = INDEXERS_CONTEXT_CLUES.iter();
let (key_binding, description) = indexers_context_clues_iter.next().unwrap(); assert_some_eq_x!(
indexers_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.submit, "edit indexer")
);
assert_some_eq_x!(
indexers_context_clues_iter.next(),
&(
DEFAULT_KEYBINDINGS.settings,
DEFAULT_KEYBINDINGS.settings.desc
)
);
assert_some_eq_x!(
indexers_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc)
);
assert_some_eq_x!(
indexers_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.test, "test indexer")
);
assert_some_eq_x!(
indexers_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.test_all, "test all indexers")
);
assert_some_eq_x!(
indexers_context_clues_iter.next(),
&(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc
)
);
assert_none!(indexers_context_clues_iter.next());
}
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit); #[test]
assert_str_eq!(*description, "edit indexer"); fn test_history_context_clues() {
let mut history_context_clues_iter = HISTORY_CONTEXT_CLUES.iter();
let (key_binding, description) = indexers_context_clues_iter.next().unwrap(); assert_some_eq_x!(
history_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.settings); &(DEFAULT_KEYBINDINGS.submit, "details")
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.settings.desc); );
assert_some_eq_x!(
let (key_binding, description) = indexers_context_clues_iter.next().unwrap(); history_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete); );
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc); assert_some_eq_x!(
history_context_clues_iter.next(),
let (key_binding, description) = indexers_context_clues_iter.next().unwrap(); &(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.test); assert_some_eq_x!(
assert_str_eq!(*description, "test indexer"); history_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc)
let (key_binding, description) = indexers_context_clues_iter.next().unwrap(); );
assert_some_eq_x!(
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.test_all); history_context_clues_iter.next(),
assert_str_eq!(*description, "test all indexers"); &(
DEFAULT_KEYBINDINGS.refresh,
let (key_binding, description) = indexers_context_clues_iter.next().unwrap(); DEFAULT_KEYBINDINGS.refresh.desc
)
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh); );
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc); assert_some_eq_x!(
assert_eq!(indexers_context_clues_iter.next(), None); history_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.esc, "cancel filter")
);
assert_none!(history_context_clues_iter.next());
} }
#[test] #[test]
fn test_system_context_clues() { fn test_system_context_clues() {
let mut system_context_clues_iter = SYSTEM_CONTEXT_CLUES.iter(); let mut system_context_clues_iter = SYSTEM_CONTEXT_CLUES.iter();
let (key_binding, description) = system_context_clues_iter.next().unwrap(); assert_some_eq_x!(
system_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.tasks, "open tasks")
);
assert_some_eq_x!(
system_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.events, "open events")
);
assert_some_eq_x!(
system_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.logs, "open logs")
);
assert_some_eq_x!(
system_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.update, "open updates")
);
assert_some_eq_x!(
system_context_clues_iter.next(),
&(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc
)
);
assert_none!(system_context_clues_iter.next());
}
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.tasks); #[test]
assert_str_eq!(*description, "open tasks"); fn test_system_tasks_context_clues() {
let mut system_tasks_context_clues_iter = SYSTEM_TASKS_CONTEXT_CLUES.iter();
let (key_binding, description) = system_context_clues_iter.next().unwrap(); assert_some_eq_x!(
system_tasks_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.events); &(DEFAULT_KEYBINDINGS.submit, "start task")
assert_str_eq!(*description, "open events"); );
assert_some_eq_x!(
let (key_binding, description) = system_context_clues_iter.next().unwrap(); system_tasks_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.logs); );
assert_str_eq!(*description, "open logs"); assert_none!(system_tasks_context_clues_iter.next());
let (key_binding, description) = system_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update);
assert_str_eq!(*description, "open updates");
let (key_binding, description) = system_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
assert_eq!(system_context_clues_iter.next(), None);
} }
#[test] #[test]
@@ -243,11 +290,7 @@ mod test {
let context_clues = ServarrContextClueProvider::get_context_clues(&mut app); let context_clues = ServarrContextClueProvider::get_context_clues(&mut app);
assert!(context_clues.is_some()); assert_some_eq_x!(context_clues, &SYSTEM_TASKS_CONTEXT_CLUES,);
assert_eq!(
&crate::app::radarr::radarr_context_clues::SYSTEM_TASKS_CONTEXT_CLUES,
context_clues.unwrap()
);
} }
#[test] #[test]
@@ -257,11 +300,7 @@ mod test {
let context_clues = ServarrContextClueProvider::get_context_clues(&mut app); let context_clues = ServarrContextClueProvider::get_context_clues(&mut app);
assert!(context_clues.is_some()); assert_some_eq_x!(context_clues, &SYSTEM_TASKS_CONTEXT_CLUES,);
assert_eq!(
&crate::app::sonarr::sonarr_context_clues::SYSTEM_TASKS_CONTEXT_CLUES,
context_clues.unwrap()
);
} }
#[test] #[test]
@@ -271,6 +310,6 @@ mod test {
let context_clues = ServarrContextClueProvider::get_context_clues(&mut app); let context_clues = ServarrContextClueProvider::get_context_clues(&mut app);
assert!(context_clues.is_none()); assert_none!(context_clues);
} }
} }
+1 -1
View File
@@ -3,7 +3,7 @@ mod test {
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest; use rstest::rstest;
use crate::app::key_binding::{KeyBinding, DEFAULT_KEYBINDINGS}; use crate::app::key_binding::{DEFAULT_KEYBINDINGS, KeyBinding};
use crate::event::Key; use crate::event::Key;
use crate::matches_key; use crate::matches_key;
+124
View File
@@ -0,0 +1,124 @@
use crate::app::App;
use crate::app::context_clues::{
BARE_POPUP_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, ContextClue, ContextClueProvider,
SYSTEM_TASKS_CONTEXT_CLUES,
};
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::models::Route;
use crate::models::servarr_data::lidarr::lidarr_data::{
ADD_ARTIST_BLOCKS, ADD_ROOT_FOLDER_BLOCKS, ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock,
EDIT_ARTIST_BLOCKS, EDIT_INDEXER_BLOCKS, INDEXER_SETTINGS_BLOCKS,
};
#[cfg(test)]
#[path = "lidarr_context_clues_tests.rs"]
mod lidarr_context_clues_tests;
pub static ARTISTS_CONTEXT_CLUES: [ContextClue; 10] = [
(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc),
(
DEFAULT_KEYBINDINGS.toggle_monitoring,
DEFAULT_KEYBINDINGS.toggle_monitoring.desc,
),
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc),
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc),
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
(DEFAULT_KEYBINDINGS.update, "update all"),
(DEFAULT_KEYBINDINGS.esc, "cancel filter"),
];
pub static ADD_ARTIST_SEARCH_RESULTS_CONTEXT_CLUES: [ContextClue; 2] = [
(DEFAULT_KEYBINDINGS.submit, "details"),
(DEFAULT_KEYBINDINGS.esc, "edit search"),
];
pub static ARTIST_DETAILS_CONTEXT_CLUES: [ContextClue; 8] = [
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
(DEFAULT_KEYBINDINGS.edit, "edit artist"),
(DEFAULT_KEYBINDINGS.delete, "delete album"),
(
DEFAULT_KEYBINDINGS.toggle_monitoring,
"toggle album monitoring",
),
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc),
(
DEFAULT_KEYBINDINGS.auto_search,
DEFAULT_KEYBINDINGS.auto_search.desc,
),
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
];
pub static ARTIST_HISTORY_CONTEXT_CLUES: [ContextClue; 9] = [
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
(DEFAULT_KEYBINDINGS.edit, "edit artist"),
(DEFAULT_KEYBINDINGS.submit, "details"),
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc),
(
DEFAULT_KEYBINDINGS.auto_search,
DEFAULT_KEYBINDINGS.auto_search.desc,
),
(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc),
(DEFAULT_KEYBINDINGS.esc, "cancel filter/close"),
];
pub(in crate::app) struct LidarrContextClueProvider;
impl ContextClueProvider for LidarrContextClueProvider {
fn get_context_clues(app: &mut App<'_>) -> Option<&'static [ContextClue]> {
let Route::Lidarr(active_lidarr_block, _context_option) = app.get_current_route() else {
panic!("LidarrContextClueProvider::get_context_clues called with non-Lidarr route");
};
match active_lidarr_block {
_ if ARTIST_DETAILS_BLOCKS.contains(&active_lidarr_block) => app
.data
.lidarr_data
.artist_info_tabs
.get_active_route_contextual_help(),
ActiveLidarrBlock::AddArtistSearchInput
| ActiveLidarrBlock::AddArtistEmptySearchResults
| ActiveLidarrBlock::TestAllIndexers
| ActiveLidarrBlock::SystemLogs
| ActiveLidarrBlock::SystemUpdates => Some(&BARE_POPUP_CONTEXT_CLUES),
_ if EDIT_ARTIST_BLOCKS.contains(&active_lidarr_block)
|| EDIT_INDEXER_BLOCKS.contains(&active_lidarr_block)
|| INDEXER_SETTINGS_BLOCKS.contains(&active_lidarr_block)
|| ADD_ROOT_FOLDER_BLOCKS.contains(&active_lidarr_block) =>
{
Some(&CONFIRMATION_PROMPT_CONTEXT_CLUES)
}
ActiveLidarrBlock::AddArtistPrompt
| ActiveLidarrBlock::AddArtistSelectMonitor
| ActiveLidarrBlock::AddArtistSelectMonitorNewItems
| ActiveLidarrBlock::AddArtistSelectQualityProfile
| ActiveLidarrBlock::AddArtistSelectMetadataProfile
| ActiveLidarrBlock::AddArtistSelectRootFolder
| ActiveLidarrBlock::AddArtistTagsInput
| ActiveLidarrBlock::AddArtistAlreadyInLibrary => Some(&CONFIRMATION_PROMPT_CONTEXT_CLUES),
_ if ADD_ARTIST_BLOCKS.contains(&active_lidarr_block) => {
Some(&ADD_ARTIST_SEARCH_RESULTS_CONTEXT_CLUES)
}
ActiveLidarrBlock::SystemTasks => Some(&SYSTEM_TASKS_CONTEXT_CLUES),
_ => app
.data
.lidarr_data
.main_tabs
.get_active_route_contextual_help(),
}
}
}
@@ -0,0 +1,329 @@
#[cfg(test)]
mod tests {
use crate::app::App;
use crate::app::context_clues::{
BARE_POPUP_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, ContextClue, ContextClueProvider,
SYSTEM_TASKS_CONTEXT_CLUES,
};
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::lidarr::lidarr_context_clues::{
ADD_ARTIST_SEARCH_RESULTS_CONTEXT_CLUES, ARTIST_DETAILS_CONTEXT_CLUES,
ARTIST_HISTORY_CONTEXT_CLUES, ARTISTS_CONTEXT_CLUES, LidarrContextClueProvider,
};
use crate::models::servarr_data::lidarr::lidarr_data::{
ADD_ROOT_FOLDER_BLOCKS, ActiveLidarrBlock, EDIT_ARTIST_BLOCKS, EDIT_INDEXER_BLOCKS,
INDEXER_SETTINGS_BLOCKS, LidarrData,
};
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use rstest::rstest;
#[test]
fn test_artists_context_clues() {
let mut artists_context_clues_iter = ARTISTS_CONTEXT_CLUES.iter();
assert_some_eq_x!(
artists_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc)
);
assert_some_eq_x!(
artists_context_clues_iter.next(),
&(
DEFAULT_KEYBINDINGS.toggle_monitoring,
DEFAULT_KEYBINDINGS.toggle_monitoring.desc
)
);
assert_some_eq_x!(
artists_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
);
assert_some_eq_x!(
artists_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc)
);
assert_some_eq_x!(
artists_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc)
);
assert_some_eq_x!(
artists_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
);
assert_some_eq_x!(
artists_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc)
);
assert_some_eq_x!(
artists_context_clues_iter.next(),
&(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc
)
);
assert_some_eq_x!(
artists_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.update, "update all")
);
assert_some_eq_x!(
artists_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.esc, "cancel filter")
);
assert_none!(artists_context_clues_iter.next());
}
#[test]
fn test_artist_details_context_clues() {
let mut artist_details_context_clues_iter = ARTIST_DETAILS_CONTEXT_CLUES.iter();
assert_some_eq_x!(
artist_details_context_clues_iter.next(),
&(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
)
);
assert_some_eq_x!(
artist_details_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.edit, "edit artist")
);
assert_some_eq_x!(
artist_details_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.delete, "delete album")
);
assert_some_eq_x!(
artist_details_context_clues_iter.next(),
&(
DEFAULT_KEYBINDINGS.toggle_monitoring,
"toggle album monitoring",
)
);
assert_some_eq_x!(
artist_details_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
);
assert_some_eq_x!(
artist_details_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc)
);
assert_some_eq_x!(
artist_details_context_clues_iter.next(),
&(
DEFAULT_KEYBINDINGS.auto_search,
DEFAULT_KEYBINDINGS.auto_search.desc,
)
);
assert_some_eq_x!(
artist_details_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
);
assert_none!(artist_details_context_clues_iter.next());
}
#[test]
fn test_add_artist_search_results_context_clues() {
let mut add_artist_search_results_context_clues_iter =
ADD_ARTIST_SEARCH_RESULTS_CONTEXT_CLUES.iter();
assert_some_eq_x!(
add_artist_search_results_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.submit, "details")
);
assert_some_eq_x!(
add_artist_search_results_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.esc, "edit search")
);
assert_none!(add_artist_search_results_context_clues_iter.next());
}
#[test]
#[should_panic(
expected = "LidarrContextClueProvider::get_context_clues called with non-Lidarr route"
)]
fn test_lidarr_context_clue_provider_get_context_clues_non_lidarr_route() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveRadarrBlock::default().into());
LidarrContextClueProvider::get_context_clues(&mut app);
}
#[test]
fn test_artist_history_context_clues() {
let mut artist_history_context_clues_iter = ARTIST_HISTORY_CONTEXT_CLUES.iter();
assert_some_eq_x!(
artist_history_context_clues_iter.next(),
&(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc
)
);
assert_some_eq_x!(
artist_history_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.edit, "edit artist")
);
assert_some_eq_x!(
artist_history_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.submit, "details")
);
assert_some_eq_x!(
artist_history_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
);
assert_some_eq_x!(
artist_history_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
);
assert_some_eq_x!(
artist_history_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc)
);
assert_some_eq_x!(
artist_history_context_clues_iter.next(),
&(
DEFAULT_KEYBINDINGS.auto_search,
DEFAULT_KEYBINDINGS.auto_search.desc
)
);
assert_some_eq_x!(
artist_history_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc)
);
assert_some_eq_x!(
artist_history_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.esc, "cancel filter/close")
);
assert_none!(artist_history_context_clues_iter.next());
}
#[rstest]
#[case(0, ActiveLidarrBlock::ArtistDetails, &ARTIST_DETAILS_CONTEXT_CLUES)]
fn test_lidarr_context_clue_provider_artist_info_tabs(
#[case] index: usize,
#[case] active_lidarr_block: ActiveLidarrBlock,
#[case] expected_context_clues: &[ContextClue],
) {
let mut app = App::test_default();
app.data.lidarr_data = LidarrData::default();
app.data.lidarr_data.artist_info_tabs.set_index(index);
app.push_navigation_stack(active_lidarr_block.into());
let context_clues = LidarrContextClueProvider::get_context_clues(&mut app);
assert_some_eq_x!(context_clues, expected_context_clues);
}
#[test]
fn test_lidarr_context_clue_provider_artists_block() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
let context_clues = LidarrContextClueProvider::get_context_clues(&mut app);
assert_some_eq_x!(context_clues, &ARTISTS_CONTEXT_CLUES);
}
#[test]
fn test_lidarr_context_clue_provider_artists_sort_prompt_block() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::ArtistsSortPrompt.into());
let context_clues = LidarrContextClueProvider::get_context_clues(&mut app);
assert_some_eq_x!(context_clues, &ARTISTS_CONTEXT_CLUES);
}
#[test]
fn test_lidarr_context_clue_provider_search_artists_block() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::SearchArtists.into());
let context_clues = LidarrContextClueProvider::get_context_clues(&mut app);
assert_some_eq_x!(context_clues, &ARTISTS_CONTEXT_CLUES);
}
#[test]
fn test_lidarr_context_clue_provider_filter_artists_block() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::FilterArtists.into());
let context_clues = LidarrContextClueProvider::get_context_clues(&mut app);
assert_some_eq_x!(context_clues, &ARTISTS_CONTEXT_CLUES);
}
#[rstest]
fn test_lidarr_context_clue_provider_bare_popup_context_clues(
#[values(
ActiveLidarrBlock::AddArtistSearchInput,
ActiveLidarrBlock::AddArtistEmptySearchResults,
ActiveLidarrBlock::TestAllIndexers
)]
active_lidarr_block: ActiveLidarrBlock,
) {
let mut app = App::test_default();
app.push_navigation_stack(active_lidarr_block.into());
let context_clues = LidarrContextClueProvider::get_context_clues(&mut app);
assert_some_eq_x!(context_clues, &BARE_POPUP_CONTEXT_CLUES);
}
#[test]
fn test_lidarr_context_clue_provider_confirmation_prompt_popup_clues_edit_indexer_blocks() {
let mut blocks = EDIT_ARTIST_BLOCKS.to_vec();
blocks.extend(ADD_ROOT_FOLDER_BLOCKS);
blocks.extend(INDEXER_SETTINGS_BLOCKS);
blocks.extend(EDIT_INDEXER_BLOCKS);
for active_lidarr_block in blocks {
let mut app = App::test_default();
app.push_navigation_stack(active_lidarr_block.into());
let context_clues = LidarrContextClueProvider::get_context_clues(&mut app);
assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
}
}
#[test]
fn test_lidarr_context_clue_provider_add_artist_search_results_context_clues() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::AddArtistSearchResults.into());
let context_clues = LidarrContextClueProvider::get_context_clues(&mut app);
assert_some_eq_x!(context_clues, &ADD_ARTIST_SEARCH_RESULTS_CONTEXT_CLUES);
}
#[rstest]
fn test_lidarr_context_clue_provider_confirmation_prompt_context_clues_add_artist_blocks(
#[values(
ActiveLidarrBlock::AddArtistPrompt,
ActiveLidarrBlock::AddArtistSelectMonitor,
ActiveLidarrBlock::AddArtistSelectMonitorNewItems,
ActiveLidarrBlock::AddArtistSelectQualityProfile,
ActiveLidarrBlock::AddArtistSelectMetadataProfile,
ActiveLidarrBlock::AddArtistSelectRootFolder,
ActiveLidarrBlock::AddArtistTagsInput,
ActiveLidarrBlock::AddArtistAlreadyInLibrary
)]
active_lidarr_block: ActiveLidarrBlock,
) {
let mut app = App::test_default();
app.push_navigation_stack(active_lidarr_block.into());
let context_clues = LidarrContextClueProvider::get_context_clues(&mut app);
assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
}
#[test]
fn test_sonarr_context_clue_provider_system_tasks_clues() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::SystemTasks.into());
let context_clues = LidarrContextClueProvider::get_context_clues(&mut app);
assert_some_eq_x!(context_clues, &SYSTEM_TASKS_CONTEXT_CLUES);
}
}
+507
View File
@@ -0,0 +1,507 @@
#[cfg(test)]
mod tests {
use crate::app::App;
use crate::models::lidarr_models::Artist;
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
use crate::models::servarr_models::Indexer;
use crate::network::NetworkEvent;
use crate::network::lidarr_network::LidarrEvent;
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::artist;
use pretty_assertions::{assert_eq, assert_str_eq};
use tokio::sync::mpsc;
#[tokio::test]
async fn test_dispatch_by_lidarr_block_artists() {
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
let mut app = App::test_default();
app.data.lidarr_data.prompt_confirm = true;
app.network_tx = Some(tx);
app
.dispatch_by_lidarr_block(&ActiveLidarrBlock::Artists)
.await;
assert!(app.is_loading);
assert_eq!(
rx.recv().await.unwrap(),
LidarrEvent::GetQualityProfiles.into()
);
assert_eq!(
rx.recv().await.unwrap(),
LidarrEvent::GetMetadataProfiles.into()
);
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetTags.into());
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::ListArtists.into());
assert!(!app.data.lidarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_lidarr_block_artist_details() {
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
let mut app = App::test_default();
app.data.lidarr_data.prompt_confirm = true;
app.data.lidarr_data.artists.set_items(vec![artist()]);
app.network_tx = Some(tx);
app
.dispatch_by_lidarr_block(&ActiveLidarrBlock::ArtistDetails)
.await;
assert!(app.is_loading);
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetAlbums(1).into());
assert!(!app.data.lidarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_artist_history_block() {
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
let mut app = App::test_default();
app.data.lidarr_data.prompt_confirm = true;
app.network_tx = Some(tx);
app.data.lidarr_data.artists.set_items(vec![Artist {
id: 1,
..Artist::default()
}]);
app
.dispatch_by_lidarr_block(&ActiveLidarrBlock::ArtistHistory)
.await;
assert!(app.is_loading);
assert_eq!(
rx.recv().await.unwrap(),
LidarrEvent::GetArtistHistory(1).into()
);
assert!(!app.data.lidarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_downloads_block() {
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
let mut app = App::test_default();
app.data.lidarr_data.prompt_confirm = true;
app.network_tx = Some(tx);
app
.dispatch_by_lidarr_block(&ActiveLidarrBlock::Downloads)
.await;
assert!(app.is_loading);
assert_eq!(
rx.recv().await.unwrap(),
LidarrEvent::GetDownloads(500).into()
);
assert!(!app.data.lidarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_lidarr_block_add_artist_search_results() {
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
let mut app = App::test_default();
app.data.lidarr_data.prompt_confirm = true;
app.network_tx = Some(tx);
app.data.lidarr_data.add_artist_search = Some("test artist".into());
app
.dispatch_by_lidarr_block(&ActiveLidarrBlock::AddArtistSearchResults)
.await;
assert!(app.is_loading);
assert_eq!(
rx.recv().await.unwrap(),
LidarrEvent::SearchNewArtist("test artist".to_owned()).into()
);
assert!(!app.data.lidarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_history_block() {
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
let mut app = App::test_default();
app.data.lidarr_data.prompt_confirm = true;
app.network_tx = Some(tx);
app
.dispatch_by_lidarr_block(&ActiveLidarrBlock::History)
.await;
assert!(app.is_loading);
assert_eq!(
rx.recv().await.unwrap(),
LidarrEvent::GetHistory(500).into()
);
assert!(!app.data.lidarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_root_folders_block() {
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
let mut app = App::test_default();
app.data.lidarr_data.prompt_confirm = true;
app.network_tx = Some(tx);
app
.dispatch_by_lidarr_block(&ActiveLidarrBlock::RootFolders)
.await;
assert!(app.is_loading);
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetRootFolders.into());
assert!(!app.data.lidarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_indexers_block() {
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
let mut app = App::test_default();
app.data.lidarr_data.prompt_confirm = true;
app.network_tx = Some(tx);
app
.dispatch_by_lidarr_block(&ActiveLidarrBlock::Indexers)
.await;
assert!(app.is_loading);
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetTags.into());
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetIndexers.into());
assert!(!app.data.lidarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_all_indexer_settings_block() {
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
let mut app = App::test_default();
app.data.lidarr_data.prompt_confirm = true;
app.network_tx = Some(tx);
app
.dispatch_by_lidarr_block(&ActiveLidarrBlock::AllIndexerSettingsPrompt)
.await;
assert!(app.is_loading);
assert_eq!(
rx.recv().await.unwrap(),
LidarrEvent::GetAllIndexerSettings.into()
);
assert!(!app.data.lidarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_test_indexer_block() {
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
let mut app = App::test_default();
app.data.lidarr_data.prompt_confirm = true;
app.network_tx = Some(tx);
app.data.lidarr_data.indexers.set_items(vec![Indexer {
id: 1,
..Indexer::default()
}]);
app
.dispatch_by_lidarr_block(&ActiveLidarrBlock::TestIndexer)
.await;
assert!(app.is_loading);
assert!(!app.data.lidarr_data.prompt_confirm);
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::TestIndexer(1).into());
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_test_all_indexers_block() {
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
let mut app = App::test_default();
app.data.lidarr_data.prompt_confirm = true;
app.network_tx = Some(tx);
app
.dispatch_by_lidarr_block(&ActiveLidarrBlock::TestAllIndexers)
.await;
assert!(app.is_loading);
assert!(!app.data.lidarr_data.prompt_confirm);
assert_eq!(
rx.recv().await.unwrap(),
LidarrEvent::TestAllIndexers.into()
);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_system_block() {
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
let mut app = App::test_default();
app.data.lidarr_data.prompt_confirm = true;
app.network_tx = Some(tx);
app
.dispatch_by_lidarr_block(&ActiveLidarrBlock::System)
.await;
assert!(app.is_loading);
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetTasks.into());
assert_eq!(
rx.recv().await.unwrap(),
LidarrEvent::GetQueuedEvents.into()
);
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetLogs(500).into());
assert!(!app.data.lidarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_system_updates_block() {
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
let mut app = App::test_default();
app.data.lidarr_data.prompt_confirm = true;
app.network_tx = Some(tx);
app
.dispatch_by_lidarr_block(&ActiveLidarrBlock::SystemUpdates)
.await;
assert!(app.is_loading);
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetUpdates.into());
assert!(!app.data.lidarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_check_for_lidarr_prompt_action_no_prompt_confirm() {
let mut app = App::test_default();
app.data.lidarr_data.prompt_confirm = false;
app.check_for_lidarr_prompt_action().await;
assert!(!app.data.lidarr_data.prompt_confirm);
assert!(!app.should_refresh);
}
#[tokio::test]
async fn test_check_for_lidarr_prompt_action() {
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
let mut app = App::test_default();
app.data.lidarr_data.prompt_confirm = true;
app.network_tx = Some(tx);
app.data.lidarr_data.prompt_confirm_action = Some(LidarrEvent::GetStatus);
app.check_for_lidarr_prompt_action().await;
assert!(!app.data.lidarr_data.prompt_confirm);
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetStatus.into());
assert!(app.should_refresh);
assert_eq!(app.data.lidarr_data.prompt_confirm_action, None);
}
#[tokio::test]
async fn test_lidarr_refresh_metadata() {
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
let mut app = App::test_default();
app.data.lidarr_data.prompt_confirm = true;
app.network_tx = Some(tx);
app.is_routing = true;
app.refresh_lidarr_metadata().await;
assert_eq!(
rx.recv().await.unwrap(),
LidarrEvent::GetQualityProfiles.into()
);
assert_eq!(
rx.recv().await.unwrap(),
LidarrEvent::GetMetadataProfiles.into()
);
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetTags.into());
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetRootFolders.into());
assert_eq!(
rx.recv().await.unwrap(),
LidarrEvent::GetDownloads(500).into()
);
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetDiskSpace.into());
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetStatus.into());
assert!(app.is_loading);
}
#[tokio::test]
async fn test_lidarr_on_tick_first_render() {
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
let mut app = App::test_default();
app.data.lidarr_data.prompt_confirm = true;
app.network_tx = Some(tx);
app.is_first_render = true;
app.lidarr_on_tick(ActiveLidarrBlock::Downloads).await;
assert_eq!(
rx.recv().await.unwrap(),
LidarrEvent::GetQualityProfiles.into()
);
assert_eq!(
rx.recv().await.unwrap(),
LidarrEvent::GetMetadataProfiles.into()
);
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetTags.into());
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetRootFolders.into());
assert_eq!(
rx.recv().await.unwrap(),
LidarrEvent::GetDownloads(500).into()
);
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetDiskSpace.into());
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetStatus.into());
assert!(app.is_loading);
assert!(!app.data.lidarr_data.prompt_confirm);
assert!(!app.is_first_render);
}
#[tokio::test]
async fn test_lidarr_on_tick_routing() {
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
let mut app = App::test_default();
app.data.lidarr_data.prompt_confirm = true;
app.network_tx = Some(tx);
app.is_routing = true;
app.should_refresh = true;
app.is_first_render = false;
app.tick_count = 1;
app.lidarr_on_tick(ActiveLidarrBlock::Downloads).await;
assert_eq!(
rx.recv().await.unwrap(),
LidarrEvent::GetDownloads(500).into()
);
assert!(!app.data.lidarr_data.prompt_confirm);
}
#[tokio::test]
async fn test_lidarr_on_tick_routing_while_long_request_is_running_should_cancel_request() {
let (tx, _) = mpsc::channel::<NetworkEvent>(500);
let mut app = App::test_default();
app.data.lidarr_data.prompt_confirm = true;
app.network_tx = Some(tx);
app.is_routing = true;
app.should_refresh = false;
app.is_first_render = false;
app.tick_count = 1;
app.lidarr_on_tick(ActiveLidarrBlock::Downloads).await;
assert!(app.cancellation_token.is_cancelled());
}
#[tokio::test]
async fn test_lidarr_on_tick_should_refresh() {
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
let mut app = App::test_default();
app.data.lidarr_data.prompt_confirm = true;
app.network_tx = Some(tx);
app.should_refresh = true;
app.is_first_render = false;
app.tick_count = 1;
app.lidarr_on_tick(ActiveLidarrBlock::Downloads).await;
assert_eq!(
rx.recv().await.unwrap(),
LidarrEvent::GetDownloads(500).into()
);
assert!(app.should_refresh);
assert!(!app.data.lidarr_data.prompt_confirm);
}
#[tokio::test]
async fn test_lidarr_on_tick_should_refresh_does_not_cancel_prompt_requests() {
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
let mut app = App::test_default();
app.data.lidarr_data.prompt_confirm = true;
app.network_tx = Some(tx);
app.is_loading = true;
app.is_routing = true;
app.should_refresh = true;
app.is_first_render = false;
app.tick_count = 1;
app.lidarr_on_tick(ActiveLidarrBlock::Downloads).await;
assert_eq!(
rx.recv().await.unwrap(),
LidarrEvent::GetDownloads(500).into()
);
assert!(app.is_loading);
assert!(app.should_refresh);
assert!(!app.data.lidarr_data.prompt_confirm);
assert!(!app.cancellation_token.is_cancelled());
}
#[tokio::test]
async fn test_lidarr_on_tick_network_tick_frequency() {
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
let mut app = App::test_default();
app.data.lidarr_data.prompt_confirm = true;
app.network_tx = Some(tx);
app.tick_count = 2;
app.tick_until_poll = 2;
app.lidarr_on_tick(ActiveLidarrBlock::Downloads).await;
assert_eq!(
rx.recv().await.unwrap(),
LidarrEvent::GetQualityProfiles.into()
);
assert_eq!(
rx.recv().await.unwrap(),
LidarrEvent::GetMetadataProfiles.into()
);
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetTags.into());
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetRootFolders.into());
assert_eq!(
rx.recv().await.unwrap(),
LidarrEvent::GetDownloads(500).into()
);
assert!(app.is_loading);
}
#[tokio::test]
async fn test_extract_add_new_artist_search_query() {
let app = App::test_default_fully_populated();
let query = app.extract_add_new_artist_search_query().await;
assert_str_eq!(query, "Test Artist");
}
#[tokio::test]
#[should_panic(expected = "Add artist search is empty")]
async fn test_extract_add_new_artist_search_query_panics_when_the_query_is_not_set() {
let app = App::test_default();
app.extract_add_new_artist_search_query().await;
}
#[tokio::test]
async fn test_extract_artist_id() {
let mut app = App::test_default();
app.data.lidarr_data.artists.set_items(vec![artist()]);
assert_eq!(app.extract_artist_id().await, 1);
}
#[tokio::test]
async fn test_extract_lidarr_indexer_id() {
let mut app = App::test_default();
app.data.lidarr_data.indexers.set_items(vec![Indexer {
id: 1,
..Indexer::default()
}]);
assert_eq!(app.extract_lidarr_indexer_id().await, 1);
}
}
+191
View File
@@ -0,0 +1,191 @@
use crate::{
models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock,
network::lidarr_network::LidarrEvent,
};
use super::App;
pub mod lidarr_context_clues;
#[cfg(test)]
#[path = "lidarr_tests.rs"]
mod lidarr_tests;
impl App<'_> {
pub(super) async fn dispatch_by_lidarr_block(&mut self, active_lidarr_block: &ActiveLidarrBlock) {
match active_lidarr_block {
ActiveLidarrBlock::Artists => {
self
.dispatch_network_event(LidarrEvent::GetQualityProfiles.into())
.await;
self
.dispatch_network_event(LidarrEvent::GetMetadataProfiles.into())
.await;
self
.dispatch_network_event(LidarrEvent::GetTags.into())
.await;
self
.dispatch_network_event(LidarrEvent::ListArtists.into())
.await;
}
ActiveLidarrBlock::Downloads => {
self
.dispatch_network_event(LidarrEvent::GetDownloads(500).into())
.await;
}
ActiveLidarrBlock::ArtistDetails => {
self
.dispatch_network_event(LidarrEvent::GetAlbums(self.extract_artist_id().await).into())
.await;
}
ActiveLidarrBlock::ArtistHistory => {
self
.dispatch_network_event(
LidarrEvent::GetArtistHistory(self.extract_artist_id().await).into(),
)
.await;
}
ActiveLidarrBlock::AddArtistSearchResults => {
self
.dispatch_network_event(
LidarrEvent::SearchNewArtist(self.extract_add_new_artist_search_query().await).into(),
)
.await;
}
ActiveLidarrBlock::History => {
self
.dispatch_network_event(LidarrEvent::GetHistory(500).into())
.await
}
ActiveLidarrBlock::RootFolders => {
self
.dispatch_network_event(LidarrEvent::GetRootFolders.into())
.await;
}
ActiveLidarrBlock::Indexers => {
self
.dispatch_network_event(LidarrEvent::GetTags.into())
.await;
self
.dispatch_network_event(LidarrEvent::GetIndexers.into())
.await;
}
ActiveLidarrBlock::AllIndexerSettingsPrompt => {
self
.dispatch_network_event(LidarrEvent::GetAllIndexerSettings.into())
.await;
}
ActiveLidarrBlock::TestIndexer => {
self
.dispatch_network_event(
LidarrEvent::TestIndexer(self.extract_lidarr_indexer_id().await).into(),
)
.await;
}
ActiveLidarrBlock::TestAllIndexers => {
self
.dispatch_network_event(LidarrEvent::TestAllIndexers.into())
.await;
}
ActiveLidarrBlock::System => {
self
.dispatch_network_event(LidarrEvent::GetTasks.into())
.await;
self
.dispatch_network_event(LidarrEvent::GetQueuedEvents.into())
.await;
self
.dispatch_network_event(LidarrEvent::GetLogs(500).into())
.await;
}
ActiveLidarrBlock::SystemUpdates => {
self
.dispatch_network_event(LidarrEvent::GetUpdates.into())
.await;
}
_ => (),
}
self.check_for_lidarr_prompt_action().await;
self.reset_tick_count();
}
async fn extract_add_new_artist_search_query(&self) -> String {
self
.data
.lidarr_data
.add_artist_search
.as_ref()
.expect("Add artist search is empty")
.text
.clone()
}
async fn extract_artist_id(&self) -> i64 {
self.data.lidarr_data.artists.current_selection().id
}
async fn extract_lidarr_indexer_id(&self) -> i64 {
self.data.lidarr_data.indexers.current_selection().id
}
async fn check_for_lidarr_prompt_action(&mut self) {
if self.data.lidarr_data.prompt_confirm {
self.data.lidarr_data.prompt_confirm = false;
if let Some(lidarr_event) = self.data.lidarr_data.prompt_confirm_action.take() {
self.dispatch_network_event(lidarr_event.into()).await;
self.should_refresh = true;
}
}
}
pub(super) async fn lidarr_on_tick(&mut self, active_lidarr_block: ActiveLidarrBlock) {
if self.is_first_render {
self.refresh_lidarr_metadata().await;
self.dispatch_by_lidarr_block(&active_lidarr_block).await;
self.is_first_render = false;
return;
}
if self.should_refresh {
self.dispatch_by_lidarr_block(&active_lidarr_block).await;
self.refresh_lidarr_metadata().await;
}
if self.is_routing {
if !self.should_refresh {
self.cancellation_token.cancel();
} else {
self.dispatch_by_lidarr_block(&active_lidarr_block).await;
}
}
if self.tick_count.is_multiple_of(self.tick_until_poll) {
self.refresh_lidarr_metadata().await;
}
}
async fn refresh_lidarr_metadata(&mut self) {
self
.dispatch_network_event(LidarrEvent::GetQualityProfiles.into())
.await;
self
.dispatch_network_event(LidarrEvent::GetMetadataProfiles.into())
.await;
self
.dispatch_network_event(LidarrEvent::GetTags.into())
.await;
self
.dispatch_network_event(LidarrEvent::GetRootFolders.into())
.await;
self
.dispatch_network_event(LidarrEvent::GetDownloads(500).into())
.await;
self
.dispatch_network_event(LidarrEvent::GetDiskSpace.into())
.await;
self
.dispatch_network_event(LidarrEvent::GetStatus.into())
.await;
}
}
+119 -29
View File
@@ -1,4 +1,4 @@
use anyhow::{anyhow, Error, Result}; use anyhow::{Error, Result, anyhow};
use colored::Colorize; use colored::Colorize;
use itertools::Itertools; use itertools::Itertools;
use log::{debug, error}; use log::{debug, error};
@@ -13,6 +13,7 @@ use tokio_util::sync::CancellationToken;
use veil::Redact; use veil::Redact;
use crate::cli::Command; use crate::cli::Command;
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, LidarrData};
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData};
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SonarrData}; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SonarrData};
use crate::models::servarr_models::KeybindingItem; use crate::models::servarr_models::KeybindingItem;
@@ -21,11 +22,11 @@ use crate::models::{HorizontallyScrollableText, Route, TabRoute, TabState};
use crate::network::NetworkEvent; use crate::network::NetworkEvent;
#[cfg(test)] #[cfg(test)]
#[path = "app_tests.rs"]
mod app_tests; mod app_tests;
pub mod context_clues; pub mod context_clues;
pub mod key_binding; pub mod key_binding;
mod key_binding_tests; mod key_binding_tests;
pub mod lidarr;
pub mod radarr; pub mod radarr;
pub mod sonarr; pub mod sonarr;
@@ -40,6 +41,7 @@ pub struct App<'a> {
pub tick_until_poll: u64, pub tick_until_poll: u64,
pub ticks_until_scroll: u64, pub ticks_until_scroll: u64,
pub tick_count: u64, pub tick_count: u64,
pub ui_scroll_tick_count: u64,
pub is_routing: bool, pub is_routing: bool,
pub is_loading: bool, pub is_loading: bool,
pub should_refresh: bool, pub should_refresh: bool,
@@ -57,42 +59,63 @@ impl App<'_> {
let mut server_tabs = Vec::new(); let mut server_tabs = Vec::new();
if let Some(radarr_configs) = config.radarr { if let Some(radarr_configs) = config.radarr {
let mut idx = 0; let mut unnamed_idx = 0;
for radarr_config in radarr_configs { let radarr_tabs = radarr_configs.into_iter().map(|radarr_config| {
let name = if let Some(name) = radarr_config.name.clone() { let name = if let Some(name) = radarr_config.name.clone() {
name name
} else { } else {
idx += 1; unnamed_idx += 1;
format!("Radarr {idx}") format!("Radarr {unnamed_idx}")
}; };
server_tabs.push(TabRoute { TabRoute {
title: name, title: name,
route: ActiveRadarrBlock::Movies.into(), route: ActiveRadarrBlock::Movies.into(),
contextual_help: None, contextual_help: None,
config: Some(radarr_config), config: Some(radarr_config),
});
} }
});
server_tabs.extend(radarr_tabs);
} }
if let Some(sonarr_configs) = config.sonarr { if let Some(sonarr_configs) = config.sonarr {
let mut idx = 0; let mut unnamed_idx = 0;
let sonarr_tabs = sonarr_configs.into_iter().map(|sonarr_config| {
for sonarr_config in sonarr_configs {
let name = if let Some(name) = sonarr_config.name.clone() { let name = if let Some(name) = sonarr_config.name.clone() {
name name
} else { } else {
idx += 1; unnamed_idx += 1;
format!("Sonarr {idx}") format!("Sonarr {unnamed_idx}")
}; };
server_tabs.push(TabRoute { TabRoute {
title: name, title: name,
route: ActiveSonarrBlock::Series.into(), route: ActiveSonarrBlock::Series.into(),
contextual_help: None, contextual_help: None,
config: Some(sonarr_config), config: Some(sonarr_config),
});
} }
});
server_tabs.extend(sonarr_tabs);
}
if let Some(lidarr_configs) = config.lidarr {
let mut unnamed_idx = 0;
let lidarr_tabs = lidarr_configs.into_iter().map(|lidarr_config| {
let name = if let Some(name) = lidarr_config.name.clone() {
name
} else {
unnamed_idx += 1;
format!("Lidarr {unnamed_idx}")
};
TabRoute {
title: name,
route: ActiveLidarrBlock::Artists.into(),
contextual_help: None,
config: Some(lidarr_config),
}
});
server_tabs.extend(lidarr_tabs);
} }
let weight_sorted_tabs = server_tabs let weight_sorted_tabs = server_tabs
@@ -132,19 +155,27 @@ impl App<'_> {
self.is_loading = true; self.is_loading = true;
} }
if let Some(network_tx) = &self.network_tx { if let Some(network_tx) = &self.network_tx
if let Err(e) = network_tx.send(action).await { && let Err(e) = network_tx.send(action).await
{
self.is_loading = false; self.is_loading = false;
error!("Failed to send event. {e:?}"); error!("Failed to send event. {e:?}");
self.handle_error(anyhow!(e)); self.handle_error(anyhow!(e));
} }
} }
}
pub fn reset_tick_count(&mut self) { pub fn reset_tick_count(&mut self) {
self.tick_count = 0; self.tick_count = 0;
} }
pub fn on_ui_scroll_tick(&mut self) {
if self.ui_scroll_tick_count == self.ticks_until_scroll {
self.ui_scroll_tick_count = 0;
} else {
self.ui_scroll_tick_count += 1;
}
}
#[allow(dead_code)] #[allow(dead_code)]
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.reset_tick_count(); self.reset_tick_count();
@@ -167,6 +198,7 @@ impl App<'_> {
match self.get_current_route() { match self.get_current_route() {
Route::Radarr(active_radarr_block, _) => self.radarr_on_tick(active_radarr_block).await, Route::Radarr(active_radarr_block, _) => self.radarr_on_tick(active_radarr_block).await,
Route::Sonarr(active_sonarr_block, _) => self.sonarr_on_tick(active_sonarr_block).await, Route::Sonarr(active_sonarr_block, _) => self.sonarr_on_tick(active_sonarr_block).await,
Route::Lidarr(active_lidarr_block, _) => self.lidarr_on_tick(active_lidarr_block).await,
_ => (), _ => (),
} }
@@ -203,10 +235,14 @@ impl App<'_> {
} }
pub fn get_current_route(&self) -> Route { pub fn get_current_route(&self) -> Route {
*self *self.navigation_stack.last().unwrap_or(
.navigation_stack &self
.last() .server_tabs
.unwrap_or(&self.server_tabs.tabs.first().unwrap().route) .tabs
.first()
.expect("At least one server tab must exist")
.route,
)
} }
} }
@@ -221,8 +257,9 @@ impl Default for App<'_> {
is_first_render: true, is_first_render: true,
server_tabs: TabState::new(Vec::new()), server_tabs: TabState::new(Vec::new()),
tick_until_poll: 400, tick_until_poll: 400,
ticks_until_scroll: 4, ticks_until_scroll: 64,
tick_count: 0, tick_count: 0,
ui_scroll_tick_count: 0,
is_loading: false, is_loading: false,
is_routing: false, is_routing: false,
should_refresh: false, should_refresh: false,
@@ -250,6 +287,43 @@ impl App<'_> {
contextual_help: None, contextual_help: None,
config: Some(ServarrConfig::default()), config: Some(ServarrConfig::default()),
}, },
TabRoute {
title: "Lidarr".to_owned(),
route: ActiveLidarrBlock::Artists.into(),
contextual_help: None,
config: Some(ServarrConfig::default()),
},
]),
..App::default()
}
}
pub fn test_default_fully_populated() -> Self {
App {
data: Data {
lidarr_data: LidarrData::test_default_fully_populated(),
radarr_data: RadarrData::test_default_fully_populated(),
sonarr_data: SonarrData::test_default_fully_populated(),
},
server_tabs: TabState::new(vec![
TabRoute {
title: "Radarr".to_owned(),
route: ActiveRadarrBlock::Movies.into(),
contextual_help: None,
config: Some(ServarrConfig::default()),
},
TabRoute {
title: "Sonarr".to_owned(),
route: ActiveSonarrBlock::Series.into(),
contextual_help: None,
config: Some(ServarrConfig::default()),
},
TabRoute {
title: "Lidarr".to_owned(),
route: ActiveLidarrBlock::Artists.into(),
contextual_help: None,
config: Some(ServarrConfig::default()),
},
]), ]),
..App::default() ..App::default()
} }
@@ -258,6 +332,7 @@ impl App<'_> {
#[derive(Default)] #[derive(Default)]
pub struct Data<'a> { pub struct Data<'a> {
pub lidarr_data: LidarrData<'a>,
pub radarr_data: RadarrData<'a>, pub radarr_data: RadarrData<'a>,
pub sonarr_data: SonarrData<'a>, pub sonarr_data: SonarrData<'a>,
} }
@@ -265,13 +340,14 @@ pub struct Data<'a> {
#[derive(Debug, Deserialize, Serialize, Default, Clone)] #[derive(Debug, Deserialize, Serialize, Default, Clone)]
pub struct AppConfig { pub struct AppConfig {
pub theme: Option<String>, pub theme: Option<String>,
pub lidarr: Option<Vec<ServarrConfig>>,
pub radarr: Option<Vec<ServarrConfig>>, pub radarr: Option<Vec<ServarrConfig>>,
pub sonarr: Option<Vec<ServarrConfig>>, pub sonarr: Option<Vec<ServarrConfig>>,
} }
impl AppConfig { impl AppConfig {
pub fn validate(&self) { pub fn validate(&self) {
if self.radarr.is_none() && self.sonarr.is_none() { if self.lidarr.is_none() && self.radarr.is_none() && self.sonarr.is_none() {
log_and_print_error( log_and_print_error(
"No Servarr configuration provided in the specified configuration file".to_owned(), "No Servarr configuration provided in the specified configuration file".to_owned(),
); );
@@ -285,6 +361,10 @@ impl AppConfig {
if let Some(sonarr_configs) = &self.sonarr { if let Some(sonarr_configs) = &self.sonarr {
sonarr_configs.iter().for_each(|config| config.validate()); sonarr_configs.iter().for_each(|config| config.validate());
} }
if let Some(lidarr_configs) = &self.lidarr {
lidarr_configs.iter().for_each(|config| config.validate());
}
} }
pub fn verify_config_present_for_cli(&self, command: &Command) { pub fn verify_config_present_for_cli(&self, command: &Command) {
@@ -302,6 +382,10 @@ impl AppConfig {
msg("Sonarr"); msg("Sonarr");
process::exit(1); process::exit(1);
} }
Command::Lidarr(_) if self.lidarr.is_none() => {
msg("Lidarr");
process::exit(1);
}
_ => (), _ => (),
} }
} }
@@ -318,6 +402,12 @@ impl AppConfig {
sonarr_config.post_process_initialization(); sonarr_config.post_process_initialization();
} }
} }
if let Some(lidarr_configs) = self.lidarr.as_mut() {
for lidarr_config in lidarr_configs {
lidarr_config.post_process_initialization();
}
}
} }
} }
@@ -477,16 +567,16 @@ where
fn interpolate_env_vars(s: &str) -> String { fn interpolate_env_vars(s: &str) -> String {
let result = s.to_string(); let result = s.to_string();
let scrubbing_regex = Regex::new(r#"[\s\{\}!\$^\(\)\[\]\\\|`'"]+"#).unwrap(); let scrubbing_regex = Regex::new(r#"[\s{}!$^()\[\]\\|`'"]+"#).unwrap();
let var_regex = Regex::new(r"\$\{(.*?)\}").unwrap(); let var_regex = Regex::new(r"\$\{(.*?)}").unwrap();
var_regex var_regex
.replace_all(s, |caps: &regex::Captures<'_>| { .replace_all(s, |caps: &regex::Captures<'_>| {
if let Some(mat) = caps.get(1) { if let Some(mat) = caps.get(1)
if let Ok(value) = std::env::var(mat.as_str()) { && let Ok(value) = std::env::var(mat.as_str())
{
return scrubbing_regex.replace_all(&value, "").to_string(); return scrubbing_regex.replace_all(&value, "").to_string();
} }
}
scrubbing_regex.replace_all(&result, "").to_string() scrubbing_regex.replace_all(&result, "").to_string()
}) })
+5
View File
@@ -62,6 +62,11 @@ impl App<'_> {
.dispatch_network_event(RadarrEvent::GetDownloads(500).into()) .dispatch_network_event(RadarrEvent::GetDownloads(500).into())
.await; .await;
} }
ActiveRadarrBlock::History => {
self
.dispatch_network_event(RadarrEvent::GetHistory(500).into())
.await;
}
ActiveRadarrBlock::Indexers => { ActiveRadarrBlock::Indexers => {
self self
.dispatch_network_event(RadarrEvent::GetTags.into()) .dispatch_network_event(RadarrEvent::GetTags.into())
+5 -9
View File
@@ -1,13 +1,14 @@
use crate::app::App;
use crate::app::context_clues::{ use crate::app::context_clues::{
ContextClue, ContextClueProvider, BARE_POPUP_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, BARE_POPUP_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, ContextClue, ContextClueProvider,
SYSTEM_TASKS_CONTEXT_CLUES,
}; };
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::models::Route;
use crate::models::servarr_data::radarr::radarr_data::{ use crate::models::servarr_data::radarr::radarr_data::{
ActiveRadarrBlock, ADD_MOVIE_BLOCKS, EDIT_COLLECTION_BLOCKS, EDIT_INDEXER_BLOCKS, ADD_MOVIE_BLOCKS, ActiveRadarrBlock, EDIT_COLLECTION_BLOCKS, EDIT_INDEXER_BLOCKS,
EDIT_MOVIE_BLOCKS, INDEXER_SETTINGS_BLOCKS, MOVIE_DETAILS_BLOCKS, EDIT_MOVIE_BLOCKS, INDEXER_SETTINGS_BLOCKS, MOVIE_DETAILS_BLOCKS,
}; };
use crate::models::Route;
#[cfg(test)] #[cfg(test)]
#[path = "radarr_context_clues_tests.rs"] #[path = "radarr_context_clues_tests.rs"]
@@ -82,11 +83,6 @@ pub static ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES: [ContextClue; 2] = [
(DEFAULT_KEYBINDINGS.esc, "edit search"), (DEFAULT_KEYBINDINGS.esc, "edit search"),
]; ];
pub static SYSTEM_TASKS_CONTEXT_CLUES: [ContextClue; 2] = [
(DEFAULT_KEYBINDINGS.submit, "start task"),
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
];
pub static COLLECTION_DETAILS_CONTEXT_CLUES: [ContextClue; 3] = [ pub static COLLECTION_DETAILS_CONTEXT_CLUES: [ContextClue; 3] = [
(DEFAULT_KEYBINDINGS.submit, "show overview/add movie"), (DEFAULT_KEYBINDINGS.submit, "show overview/add movie"),
(DEFAULT_KEYBINDINGS.edit, "edit collection"), (DEFAULT_KEYBINDINGS.edit, "edit collection"),
+198 -221
View File
@@ -1,126 +1,119 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::app::App;
use crate::app::context_clues::{ use crate::app::context_clues::{
ContextClue, ContextClueProvider, BARE_POPUP_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, BARE_POPUP_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES,
CONFIRMATION_PROMPT_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES, ContextClue, ContextClueProvider, DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES,
ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
SYSTEM_TASKS_CONTEXT_CLUES,
}; };
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::radarr_context_clues::{ use crate::app::radarr::radarr_context_clues::{
RadarrContextClueProvider, ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES, COLLECTIONS_CONTEXT_CLUES, ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES, COLLECTION_DETAILS_CONTEXT_CLUES,
COLLECTION_DETAILS_CONTEXT_CLUES, LIBRARY_CONTEXT_CLUES, MANUAL_MOVIE_SEARCH_CONTEXT_CLUES, COLLECTIONS_CONTEXT_CLUES, LIBRARY_CONTEXT_CLUES, MANUAL_MOVIE_SEARCH_CONTEXT_CLUES,
MOVIE_DETAILS_CONTEXT_CLUES, SYSTEM_TASKS_CONTEXT_CLUES, MOVIE_DETAILS_CONTEXT_CLUES, RadarrContextClueProvider,
}; };
use crate::app::App;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData};
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock; use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::assert_eq;
use rstest::rstest; use rstest::rstest;
#[test] #[test]
fn test_library_context_clues() { fn test_library_context_clues() {
let mut library_context_clues_iter = LIBRARY_CONTEXT_CLUES.iter(); let mut library_context_clues_iter = LIBRARY_CONTEXT_CLUES.iter();
let (key_binding, description) = library_context_clues_iter.next().unwrap(); assert_some_eq_x!(
library_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.add); &(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc)
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.add.desc); );
assert_some_eq_x!(
let (key_binding, description) = library_context_clues_iter.next().unwrap(); library_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc)
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit); );
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.edit.desc); assert_some_eq_x!(
library_context_clues_iter.next(),
let (key_binding, description) = library_context_clues_iter.next().unwrap(); &(
DEFAULT_KEYBINDINGS.toggle_monitoring,
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.toggle_monitoring); DEFAULT_KEYBINDINGS.toggle_monitoring.desc
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.toggle_monitoring.desc); )
);
let (key_binding, description) = library_context_clues_iter.next().unwrap(); assert_some_eq_x!(
library_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort); &(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc); );
assert_some_eq_x!(
let (key_binding, description) = library_context_clues_iter.next().unwrap(); library_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc)
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete); );
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc); assert_some_eq_x!(
library_context_clues_iter.next(),
let (key_binding, description) = library_context_clues_iter.next().unwrap(); &(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search); assert_some_eq_x!(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.search.desc); library_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc)
let (key_binding, description) = library_context_clues_iter.next().unwrap(); );
assert_some_eq_x!(
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.filter); library_context_clues_iter.next(),
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.filter.desc); &(
DEFAULT_KEYBINDINGS.refresh,
let (key_binding, description) = library_context_clues_iter.next().unwrap(); DEFAULT_KEYBINDINGS.refresh.desc
)
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh); );
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc); assert_some_eq_x!(
library_context_clues_iter.next(),
let (key_binding, description) = library_context_clues_iter.next().unwrap(); &(DEFAULT_KEYBINDINGS.update, "update all")
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update); assert_some_eq_x!(
assert_str_eq!(*description, "update all"); library_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.submit, "details")
let (key_binding, description) = library_context_clues_iter.next().unwrap(); );
assert_some_eq_x!(
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit); library_context_clues_iter.next(),
assert_str_eq!(*description, "details"); &(DEFAULT_KEYBINDINGS.esc, "cancel filter")
);
let (key_binding, description) = library_context_clues_iter.next().unwrap(); assert_none!(library_context_clues_iter.next());
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
assert_str_eq!(*description, "cancel filter");
assert_eq!(library_context_clues_iter.next(), None);
} }
#[test] #[test]
fn test_collections_context_clues() { fn test_collections_context_clues() {
let mut collections_context_clues = COLLECTIONS_CONTEXT_CLUES.iter(); let mut collections_context_clues = COLLECTIONS_CONTEXT_CLUES.iter();
let (key_binding, description) = collections_context_clues.next().unwrap(); assert_some_eq_x!(
collections_context_clues.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search); &(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.search.desc); );
assert_some_eq_x!(
let (key_binding, description) = collections_context_clues.next().unwrap(); collections_context_clues.next(),
&(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc)
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit); );
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.edit.desc); assert_some_eq_x!(
collections_context_clues.next(),
let (key_binding, description) = collections_context_clues.next().unwrap(); &(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort); assert_some_eq_x!(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc); collections_context_clues.next(),
&(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc)
let (key_binding, description) = collections_context_clues.next().unwrap(); );
assert_some_eq_x!(
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.filter); collections_context_clues.next(),
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.filter.desc); &(
DEFAULT_KEYBINDINGS.refresh,
let (key_binding, description) = collections_context_clues.next().unwrap(); DEFAULT_KEYBINDINGS.refresh.desc
)
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh); );
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc); assert_some_eq_x!(
collections_context_clues.next(),
let (key_binding, description) = collections_context_clues.next().unwrap(); &(DEFAULT_KEYBINDINGS.update, "update all")
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update); assert_some_eq_x!(
assert_str_eq!(*description, "update all"); collections_context_clues.next(),
&(DEFAULT_KEYBINDINGS.submit, "details")
let (key_binding, description) = collections_context_clues.next().unwrap(); );
assert_some_eq_x!(
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit); collections_context_clues.next(),
assert_str_eq!(*description, "details"); &(DEFAULT_KEYBINDINGS.esc, "cancel filter")
);
let (key_binding, description) = collections_context_clues.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
assert_str_eq!(*description, "cancel filter");
assert_eq!(collections_context_clues.next(), None); assert_eq!(collections_context_clues.next(), None);
} }
@@ -128,30 +121,32 @@ mod tests {
fn test_movie_details_context_clues() { fn test_movie_details_context_clues() {
let mut movie_details_context_clues_iter = MOVIE_DETAILS_CONTEXT_CLUES.iter(); let mut movie_details_context_clues_iter = MOVIE_DETAILS_CONTEXT_CLUES.iter();
let (key_binding, description) = movie_details_context_clues_iter.next().unwrap(); assert_some_eq_x!(
movie_details_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh); &(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc); DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc
let (key_binding, description) = movie_details_context_clues_iter.next().unwrap(); )
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update); assert_some_eq_x!(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.update.desc); movie_details_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc)
let (key_binding, description) = movie_details_context_clues_iter.next().unwrap(); );
assert_some_eq_x!(
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit); movie_details_context_clues_iter.next(),
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.edit.desc); &(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc)
);
let (key_binding, description) = movie_details_context_clues_iter.next().unwrap(); assert_some_eq_x!(
movie_details_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search); &(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc); DEFAULT_KEYBINDINGS.auto_search,
DEFAULT_KEYBINDINGS.auto_search.desc
let (key_binding, description) = movie_details_context_clues_iter.next().unwrap(); )
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc); assert_some_eq_x!(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc); movie_details_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
);
assert_eq!(movie_details_context_clues_iter.next(), None); assert_eq!(movie_details_context_clues_iter.next(), None);
} }
@@ -159,41 +154,40 @@ mod tests {
fn test_manual_movie_search_context_clues() { fn test_manual_movie_search_context_clues() {
let mut manual_movie_search_context_clues_iter = MANUAL_MOVIE_SEARCH_CONTEXT_CLUES.iter(); let mut manual_movie_search_context_clues_iter = MANUAL_MOVIE_SEARCH_CONTEXT_CLUES.iter();
let (key_binding, description) = manual_movie_search_context_clues_iter.next().unwrap(); assert_some_eq_x!(
manual_movie_search_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh); &(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc); DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc
let (key_binding, description) = manual_movie_search_context_clues_iter.next().unwrap(); )
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update); assert_some_eq_x!(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.update.desc); manual_movie_search_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc)
let (key_binding, description) = manual_movie_search_context_clues_iter.next().unwrap(); );
assert_some_eq_x!(
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit); manual_movie_search_context_clues_iter.next(),
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.edit.desc); &(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc)
);
let (key_binding, description) = manual_movie_search_context_clues_iter.next().unwrap(); assert_some_eq_x!(
manual_movie_search_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort); &(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc); );
assert_some_eq_x!(
let (key_binding, description) = manual_movie_search_context_clues_iter.next().unwrap(); manual_movie_search_context_clues_iter.next(),
&(
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search); DEFAULT_KEYBINDINGS.auto_search,
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc); DEFAULT_KEYBINDINGS.auto_search.desc
)
let (key_binding, description) = manual_movie_search_context_clues_iter.next().unwrap(); );
assert_some_eq_x!(
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit); manual_movie_search_context_clues_iter.next(),
assert_str_eq!(*description, "details"); &(DEFAULT_KEYBINDINGS.submit, "details")
);
let (key_binding, description) = manual_movie_search_context_clues_iter.next().unwrap(); assert_some_eq_x!(
manual_movie_search_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc); &(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc); );
assert_eq!(manual_movie_search_context_clues_iter.next(), None); assert_eq!(manual_movie_search_context_clues_iter.next(), None);
} }
@@ -202,15 +196,14 @@ mod tests {
let mut add_movie_search_results_context_clues_iter = let mut add_movie_search_results_context_clues_iter =
ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES.iter(); ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES.iter();
let (key_binding, description) = add_movie_search_results_context_clues_iter.next().unwrap(); assert_some_eq_x!(
add_movie_search_results_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit); &(DEFAULT_KEYBINDINGS.submit, "details")
assert_str_eq!(*description, "details"); );
assert_some_eq_x!(
let (key_binding, description) = add_movie_search_results_context_clues_iter.next().unwrap(); add_movie_search_results_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.esc, "edit search")
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc); );
assert_str_eq!(*description, "edit search");
assert_eq!(add_movie_search_results_context_clues_iter.next(), None); assert_eq!(add_movie_search_results_context_clues_iter.next(), None);
} }
@@ -218,15 +211,14 @@ mod tests {
fn test_system_tasks_context_clues() { fn test_system_tasks_context_clues() {
let mut system_tasks_context_clues_iter = SYSTEM_TASKS_CONTEXT_CLUES.iter(); let mut system_tasks_context_clues_iter = SYSTEM_TASKS_CONTEXT_CLUES.iter();
let (key_binding, description) = system_tasks_context_clues_iter.next().unwrap(); assert_some_eq_x!(
system_tasks_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit); &(DEFAULT_KEYBINDINGS.submit, "start task")
assert_str_eq!(*description, "start task"); );
assert_some_eq_x!(
let (key_binding, description) = system_tasks_context_clues_iter.next().unwrap(); system_tasks_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc); );
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
assert_eq!(system_tasks_context_clues_iter.next(), None); assert_eq!(system_tasks_context_clues_iter.next(), None);
} }
@@ -234,20 +226,18 @@ mod tests {
fn test_collection_details_context_clues() { fn test_collection_details_context_clues() {
let mut collection_details_context_clues_iter = COLLECTION_DETAILS_CONTEXT_CLUES.iter(); let mut collection_details_context_clues_iter = COLLECTION_DETAILS_CONTEXT_CLUES.iter();
let (key_binding, description) = collection_details_context_clues_iter.next().unwrap(); assert_some_eq_x!(
collection_details_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit); &(DEFAULT_KEYBINDINGS.submit, "show overview/add movie")
assert_str_eq!(*description, "show overview/add movie"); );
assert_some_eq_x!(
let (key_binding, description) = collection_details_context_clues_iter.next().unwrap(); collection_details_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.edit, "edit collection")
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit); );
assert_str_eq!(*description, "edit collection"); assert_some_eq_x!(
collection_details_context_clues_iter.next(),
let (key_binding, description) = collection_details_context_clues_iter.next().unwrap(); &(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
assert_eq!(collection_details_context_clues_iter.next(), None); assert_eq!(collection_details_context_clues_iter.next(), None);
} }
@@ -283,8 +273,7 @@ mod tests {
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app); let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
assert!(context_clues.is_some()); assert_some_eq_x!(context_clues, &BARE_POPUP_CONTEXT_CLUES);
assert_eq!(&BARE_POPUP_CONTEXT_CLUES, context_clues.unwrap());
} }
#[rstest] #[rstest]
@@ -306,8 +295,7 @@ mod tests {
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app); let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
assert!(context_clues.is_some()); assert_some_eq_x!(context_clues, expected_context_clues);
assert_eq!(expected_context_clues, context_clues.unwrap());
} }
#[rstest] #[rstest]
@@ -329,8 +317,7 @@ mod tests {
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app); let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
assert!(context_clues.is_some()); assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
assert_eq!(&CONFIRMATION_PROMPT_CONTEXT_CLUES, context_clues.unwrap());
} }
#[rstest] #[rstest]
@@ -352,8 +339,7 @@ mod tests {
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app); let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
assert!(context_clues.is_some()); assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
assert_eq!(&CONFIRMATION_PROMPT_CONTEXT_CLUES, context_clues.unwrap());
} }
#[rstest] #[rstest]
@@ -379,8 +365,7 @@ mod tests {
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app); let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
assert!(context_clues.is_some()); assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
assert_eq!(&CONFIRMATION_PROMPT_CONTEXT_CLUES, context_clues.unwrap());
} }
#[rstest] #[rstest]
@@ -405,8 +390,7 @@ mod tests {
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app); let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
assert!(context_clues.is_some()); assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
assert_eq!(&CONFIRMATION_PROMPT_CONTEXT_CLUES, context_clues.unwrap());
} }
#[rstest] #[rstest]
@@ -428,8 +412,7 @@ mod tests {
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app); let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
assert!(context_clues.is_some()); assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
assert_eq!(&CONFIRMATION_PROMPT_CONTEXT_CLUES, context_clues.unwrap());
} }
#[rstest] #[rstest]
@@ -446,11 +429,7 @@ mod tests {
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app); let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
assert!(context_clues.is_some()); assert_some_eq_x!(context_clues, &ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES);
assert_eq!(
&ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES,
context_clues.unwrap()
);
} }
#[test] #[test]
@@ -461,8 +440,7 @@ mod tests {
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app); let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
assert!(context_clues.is_some()); assert_some_eq_x!(context_clues, &COLLECTION_DETAILS_CONTEXT_CLUES);
assert_eq!(&COLLECTION_DETAILS_CONTEXT_CLUES, context_clues.unwrap());
} }
#[test] #[test]
@@ -474,8 +452,7 @@ mod tests {
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app); let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
assert!(context_clues.is_some()); assert_some_eq_x!(context_clues, &SYSTEM_TASKS_CONTEXT_CLUES);
assert_eq!(&SYSTEM_TASKS_CONTEXT_CLUES, context_clues.unwrap());
} }
#[rstest] #[rstest]
@@ -483,9 +460,10 @@ mod tests {
#[case(1, ActiveRadarrBlock::Collections, &COLLECTIONS_CONTEXT_CLUES)] #[case(1, ActiveRadarrBlock::Collections, &COLLECTIONS_CONTEXT_CLUES)]
#[case(2, ActiveRadarrBlock::Downloads, &DOWNLOADS_CONTEXT_CLUES)] #[case(2, ActiveRadarrBlock::Downloads, &DOWNLOADS_CONTEXT_CLUES)]
#[case(3, ActiveRadarrBlock::Blocklist, &BLOCKLIST_CONTEXT_CLUES)] #[case(3, ActiveRadarrBlock::Blocklist, &BLOCKLIST_CONTEXT_CLUES)]
#[case(4, ActiveRadarrBlock::RootFolders, &ROOT_FOLDERS_CONTEXT_CLUES)] #[case(4, ActiveRadarrBlock::History, &HISTORY_CONTEXT_CLUES)]
#[case(5, ActiveRadarrBlock::Indexers, &INDEXERS_CONTEXT_CLUES)] #[case(5, ActiveRadarrBlock::RootFolders, &ROOT_FOLDERS_CONTEXT_CLUES)]
#[case(6, ActiveRadarrBlock::System, &SYSTEM_CONTEXT_CLUES)] #[case(6, ActiveRadarrBlock::Indexers, &INDEXERS_CONTEXT_CLUES)]
#[case(7, ActiveRadarrBlock::System, &SYSTEM_CONTEXT_CLUES)]
fn test_radarr_context_clue_provider_radarr_blocks_context_clues( fn test_radarr_context_clue_provider_radarr_blocks_context_clues(
#[case] index: usize, #[case] index: usize,
#[case] active_radarr_block: ActiveRadarrBlock, #[case] active_radarr_block: ActiveRadarrBlock,
@@ -498,7 +476,6 @@ mod tests {
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app); let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
assert!(context_clues.is_some()); assert_some_eq_x!(context_clues, expected_context_clues);
assert_eq!(expected_context_clues, context_clues.unwrap());
} }
} }
+23 -5
View File
@@ -3,15 +3,16 @@ mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use crate::app::radarr::ActiveRadarrBlock;
use crate::app::App; use crate::app::App;
use crate::app::radarr::ActiveRadarrBlock;
use crate::models::radarr_models::{ use crate::models::radarr_models::{
AddMovieBody, AddMovieOptions, Collection, CollectionMovie, Credit, Movie, RadarrRelease, AddMovieBody, AddMovieOptions, Collection, CollectionMovie, Credit, MinimumAvailability, Movie,
MovieMonitor, RadarrRelease,
}; };
use crate::models::servarr_data::radarr::modals::MovieDetailsModal; use crate::models::servarr_data::radarr::modals::MovieDetailsModal;
use crate::models::servarr_models::Indexer; use crate::models::servarr_models::Indexer;
use crate::network::radarr_network::RadarrEvent;
use crate::network::NetworkEvent; use crate::network::NetworkEvent;
use crate::network::radarr_network::RadarrEvent;
#[tokio::test] #[tokio::test]
async fn test_dispatch_by_blocklist_block() { async fn test_dispatch_by_blocklist_block() {
@@ -88,13 +89,13 @@ mod tests {
tmdb_id: 1234, tmdb_id: 1234,
title: "Test".to_owned(), title: "Test".to_owned(),
root_folder_path: "/nfs2".to_owned(), root_folder_path: "/nfs2".to_owned(),
minimum_availability: "announced".to_owned(), minimum_availability: MinimumAvailability::Announced,
monitored: true, monitored: true,
quality_profile_id: 2222, quality_profile_id: 2222,
tags: vec![1, 2], tags: vec![1, 2],
tag_input_string: None, tag_input_string: None,
add_options: AddMovieOptions { add_options: AddMovieOptions {
monitor: "movieOnly".to_owned(), monitor: MovieMonitor::MovieOnly,
search_for_movie: true, search_for_movie: true,
}, },
}; };
@@ -146,6 +147,23 @@ mod tests {
assert_eq!(app.tick_count, 0); assert_eq!(app.tick_count, 0);
} }
#[tokio::test]
async fn test_dispatch_by_history_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app
.dispatch_by_radarr_block(&ActiveRadarrBlock::History)
.await;
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
RadarrEvent::GetHistory(500).into()
);
assert!(!app.data.radarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test] #[tokio::test]
async fn test_dispatch_by_root_folders_block() { async fn test_dispatch_by_root_folders_block() {
let (mut app, mut sync_network_rx) = construct_app_unit(); let (mut app, mut sync_network_rx) = construct_app_unit();
+4 -5
View File
@@ -94,9 +94,10 @@ impl App<'_> {
.await; .await;
} }
ActiveSonarrBlock::ManualEpisodeSearch => { ActiveSonarrBlock::ManualEpisodeSearch => {
if let Some(season_details_modal) = self.data.sonarr_data.season_details_modal.as_ref() { if let Some(season_details_modal) = self.data.sonarr_data.season_details_modal.as_ref()
if let Some(episode_details_modal) = season_details_modal.episode_details_modal.as_ref() { && let Some(episode_details_modal) = season_details_modal.episode_details_modal.as_ref()
if episode_details_modal.episode_releases.is_empty() { && episode_details_modal.episode_releases.is_empty()
{
self self
.dispatch_network_event( .dispatch_network_event(
SonarrEvent::GetEpisodeReleases(self.extract_episode_id().await).into(), SonarrEvent::GetEpisodeReleases(self.extract_episode_id().await).into(),
@@ -104,8 +105,6 @@ impl App<'_> {
.await; .await;
} }
} }
}
}
ActiveSonarrBlock::Downloads => { ActiveSonarrBlock::Downloads => {
self self
.dispatch_network_event(SonarrEvent::GetDownloads(500).into()) .dispatch_network_event(SonarrEvent::GetDownloads(500).into())
+5 -21
View File
@@ -1,12 +1,13 @@
use crate::app::context_clues::{ use crate::app::context_clues::{
ContextClueProvider, BARE_POPUP_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, BARE_POPUP_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, ContextClueProvider,
SYSTEM_TASKS_CONTEXT_CLUES,
}; };
use crate::app::{context_clues::ContextClue, key_binding::DEFAULT_KEYBINDINGS, App}; use crate::app::{App, context_clues::ContextClue, key_binding::DEFAULT_KEYBINDINGS};
use crate::models::Route;
use crate::models::servarr_data::sonarr::sonarr_data::{ use crate::models::servarr_data::sonarr::sonarr_data::{
ActiveSonarrBlock, ADD_SERIES_BLOCKS, EDIT_INDEXER_BLOCKS, EDIT_SERIES_BLOCKS, ADD_SERIES_BLOCKS, ActiveSonarrBlock, EDIT_INDEXER_BLOCKS, EDIT_SERIES_BLOCKS,
EPISODE_DETAILS_BLOCKS, INDEXER_SETTINGS_BLOCKS, SEASON_DETAILS_BLOCKS, SERIES_DETAILS_BLOCKS, EPISODE_DETAILS_BLOCKS, INDEXER_SETTINGS_BLOCKS, SEASON_DETAILS_BLOCKS, SERIES_DETAILS_BLOCKS,
}; };
use crate::models::Route;
#[cfg(test)] #[cfg(test)]
#[path = "sonarr_context_clues_tests.rs"] #[path = "sonarr_context_clues_tests.rs"]
@@ -57,18 +58,6 @@ pub static SERIES_DETAILS_CONTEXT_CLUES: [ContextClue; 8] = [
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc), (DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
]; ];
pub static HISTORY_CONTEXT_CLUES: [ContextClue; 6] = [
(DEFAULT_KEYBINDINGS.submit, "details"),
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc),
(
DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc,
),
(DEFAULT_KEYBINDINGS.esc, "cancel filter"),
];
pub static SERIES_HISTORY_CONTEXT_CLUES: [ContextClue; 9] = [ pub static SERIES_HISTORY_CONTEXT_CLUES: [ContextClue; 9] = [
( (
DEFAULT_KEYBINDINGS.refresh, DEFAULT_KEYBINDINGS.refresh,
@@ -175,11 +164,6 @@ pub static SELECTABLE_EPISODE_DETAILS_CONTEXT_CLUES: [ContextClue; 4] = [
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc), (DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
]; ];
pub static SYSTEM_TASKS_CONTEXT_CLUES: [ContextClue; 2] = [
(DEFAULT_KEYBINDINGS.submit, "start task"),
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
];
pub(in crate::app) struct SonarrContextClueProvider; pub(in crate::app) struct SonarrContextClueProvider;
impl ContextClueProvider for SonarrContextClueProvider { impl ContextClueProvider for SonarrContextClueProvider {
+343 -388
View File
@@ -1,28 +1,27 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::app::context_clues::{ use crate::app::context_clues::{
ContextClue, ContextClueProvider, BARE_POPUP_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, BARE_POPUP_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES,
CONFIRMATION_PROMPT_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES, ContextClue, ContextClueProvider, DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES,
ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
SYSTEM_TASKS_CONTEXT_CLUES,
}; };
use crate::app::sonarr::sonarr_context_clues::{ use crate::app::sonarr::sonarr_context_clues::{
SonarrContextClueProvider, SELECTABLE_EPISODE_DETAILS_CONTEXT_CLUES, SELECTABLE_EPISODE_DETAILS_CONTEXT_CLUES, SonarrContextClueProvider,
}; };
use crate::app::{ use crate::app::{
App,
key_binding::DEFAULT_KEYBINDINGS, key_binding::DEFAULT_KEYBINDINGS,
sonarr::sonarr_context_clues::{ sonarr::sonarr_context_clues::{
ADD_SERIES_SEARCH_RESULTS_CONTEXT_CLUES, EPISODE_DETAILS_CONTEXT_CLUES, ADD_SERIES_SEARCH_RESULTS_CONTEXT_CLUES, EPISODE_DETAILS_CONTEXT_CLUES,
HISTORY_CONTEXT_CLUES, MANUAL_EPISODE_SEARCH_CONTEXT_CLUES, MANUAL_EPISODE_SEARCH_CONTEXT_CLUES, MANUAL_SEASON_SEARCH_CONTEXT_CLUES,
MANUAL_SEASON_SEARCH_CONTEXT_CLUES, SEASON_DETAILS_CONTEXT_CLUES, SEASON_DETAILS_CONTEXT_CLUES, SEASON_HISTORY_CONTEXT_CLUES, SERIES_CONTEXT_CLUES,
SEASON_HISTORY_CONTEXT_CLUES, SERIES_CONTEXT_CLUES, SERIES_DETAILS_CONTEXT_CLUES, SERIES_DETAILS_CONTEXT_CLUES, SERIES_HISTORY_CONTEXT_CLUES,
SERIES_HISTORY_CONTEXT_CLUES, SYSTEM_TASKS_CONTEXT_CLUES,
}, },
App,
}; };
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use crate::models::servarr_data::sonarr::modals::{EpisodeDetailsModal, SeasonDetailsModal}; use crate::models::servarr_data::sonarr::modals::{EpisodeDetailsModal, SeasonDetailsModal};
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SonarrData}; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SonarrData};
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest; use rstest::rstest;
#[test] #[test]
@@ -30,417 +29,388 @@ mod tests {
let mut add_series_search_results_context_clues_iter = let mut add_series_search_results_context_clues_iter =
ADD_SERIES_SEARCH_RESULTS_CONTEXT_CLUES.iter(); ADD_SERIES_SEARCH_RESULTS_CONTEXT_CLUES.iter();
let (key_binding, description) = add_series_search_results_context_clues_iter.next().unwrap(); assert_some_eq_x!(
add_series_search_results_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit); &(DEFAULT_KEYBINDINGS.submit, "details")
assert_str_eq!(*description, "details"); );
assert_some_eq_x!(
let (key_binding, description) = add_series_search_results_context_clues_iter.next().unwrap(); add_series_search_results_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.esc, "edit search")
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc); );
assert_str_eq!(*description, "edit search"); assert_none!(add_series_search_results_context_clues_iter.next());
assert_eq!(add_series_search_results_context_clues_iter.next(), None);
} }
#[test] #[test]
fn test_series_context_clues() { fn test_series_context_clues() {
let mut series_context_clues_iter = SERIES_CONTEXT_CLUES.iter(); let mut series_context_clues_iter = SERIES_CONTEXT_CLUES.iter();
let (key_binding, description) = series_context_clues_iter.next().unwrap(); assert_some_eq_x!(
series_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.add); &(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc)
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.add.desc); );
assert_some_eq_x!(
let (key_binding, description) = series_context_clues_iter.next().unwrap(); series_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc)
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit); );
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.edit.desc); assert_some_eq_x!(
series_context_clues_iter.next(),
let (key_binding, description) = series_context_clues_iter.next().unwrap(); &(
DEFAULT_KEYBINDINGS.toggle_monitoring,
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.toggle_monitoring); DEFAULT_KEYBINDINGS.toggle_monitoring.desc
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.toggle_monitoring.desc); )
);
let (key_binding, description) = series_context_clues_iter.next().unwrap(); assert_some_eq_x!(
series_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort); &(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc); );
assert_some_eq_x!(
let (key_binding, description) = series_context_clues_iter.next().unwrap(); series_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc)
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete); );
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc); assert_some_eq_x!(
series_context_clues_iter.next(),
let (key_binding, description) = series_context_clues_iter.next().unwrap(); &(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search); assert_some_eq_x!(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.search.desc); series_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc)
let (key_binding, description) = series_context_clues_iter.next().unwrap(); );
assert_some_eq_x!(
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.filter); series_context_clues_iter.next(),
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.filter.desc); &(
DEFAULT_KEYBINDINGS.refresh,
let (key_binding, description) = series_context_clues_iter.next().unwrap(); DEFAULT_KEYBINDINGS.refresh.desc
)
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh); );
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc); assert_some_eq_x!(
series_context_clues_iter.next(),
let (key_binding, description) = series_context_clues_iter.next().unwrap(); &(DEFAULT_KEYBINDINGS.update, "update all")
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update); assert_some_eq_x!(
assert_str_eq!(*description, "update all"); series_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.submit, "details")
let (key_binding, description) = series_context_clues_iter.next().unwrap(); );
assert_some_eq_x!(
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit); series_context_clues_iter.next(),
assert_str_eq!(*description, "details"); &(DEFAULT_KEYBINDINGS.esc, "cancel filter")
);
let (key_binding, description) = series_context_clues_iter.next().unwrap(); assert_none!(series_context_clues_iter.next());
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
assert_str_eq!(*description, "cancel filter");
assert_eq!(series_context_clues_iter.next(), None);
} }
#[test] #[test]
fn test_series_history_context_clues() { fn test_series_history_context_clues() {
let mut series_history_context_clues_iter = SERIES_HISTORY_CONTEXT_CLUES.iter(); let mut series_history_context_clues_iter = SERIES_HISTORY_CONTEXT_CLUES.iter();
let (key_binding, description) = series_history_context_clues_iter.next().unwrap(); assert_some_eq_x!(
series_history_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh); &(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc); DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc
let (key_binding, description) = series_history_context_clues_iter.next().unwrap(); )
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit); assert_some_eq_x!(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.edit.desc); series_history_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc)
let (key_binding, description) = series_history_context_clues_iter.next().unwrap(); );
assert_some_eq_x!(
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit); series_history_context_clues_iter.next(),
assert_str_eq!(*description, "details"); &(DEFAULT_KEYBINDINGS.submit, "details")
);
let (key_binding, description) = series_history_context_clues_iter.next().unwrap(); assert_some_eq_x!(
series_history_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort); &(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc); );
assert_some_eq_x!(
let (key_binding, description) = series_history_context_clues_iter.next().unwrap(); series_history_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search); );
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.search.desc); assert_some_eq_x!(
series_history_context_clues_iter.next(),
let (key_binding, description) = series_history_context_clues_iter.next().unwrap(); &(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc)
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.filter); assert_some_eq_x!(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.filter.desc); series_history_context_clues_iter.next(),
&(
let (key_binding, description) = series_history_context_clues_iter.next().unwrap(); DEFAULT_KEYBINDINGS.auto_search,
DEFAULT_KEYBINDINGS.auto_search.desc
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search); )
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc); );
assert_some_eq_x!(
let (key_binding, description) = series_history_context_clues_iter.next().unwrap(); series_history_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc)
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update); );
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.update.desc); assert_some_eq_x!(
series_history_context_clues_iter.next(),
let (key_binding, description) = series_history_context_clues_iter.next().unwrap(); &(DEFAULT_KEYBINDINGS.esc, "cancel filter/close")
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc); assert_none!(series_history_context_clues_iter.next());
assert_str_eq!(*description, "cancel filter/close");
assert_eq!(series_history_context_clues_iter.next(), None);
}
#[test]
fn test_history_context_clues() {
let mut history_context_clues_iter = HISTORY_CONTEXT_CLUES.iter();
let (key_binding, description) = history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
assert_str_eq!(*description, "details");
let (key_binding, description) = history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
let (key_binding, description) = history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.search.desc);
let (key_binding, description) = history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.filter);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.filter.desc);
let (key_binding, description) = history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
let (key_binding, description) = history_context_clues_iter.next().unwrap();
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
assert_str_eq!(*description, "cancel filter");
assert_eq!(history_context_clues_iter.next(), None);
} }
#[test] #[test]
fn test_series_details_context_clues() { fn test_series_details_context_clues() {
let mut series_details_context_clues_iter = SERIES_DETAILS_CONTEXT_CLUES.iter(); let mut series_details_context_clues_iter = SERIES_DETAILS_CONTEXT_CLUES.iter();
let (key_binding, description) = series_details_context_clues_iter.next().unwrap(); assert_some_eq_x!(
series_details_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh); &(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc); DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc
let (key_binding, description) = series_details_context_clues_iter.next().unwrap(); )
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit); assert_some_eq_x!(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.edit.desc); series_details_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc)
let (key_binding, description) = series_details_context_clues_iter.next().unwrap(); );
assert_some_eq_x!(
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.toggle_monitoring); series_details_context_clues_iter.next(),
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.toggle_monitoring.desc); &(
DEFAULT_KEYBINDINGS.toggle_monitoring,
let (key_binding, description) = series_details_context_clues_iter.next().unwrap(); DEFAULT_KEYBINDINGS.toggle_monitoring.desc
)
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit); );
assert_str_eq!(*description, "season details"); assert_some_eq_x!(
series_details_context_clues_iter.next(),
let (key_binding, description) = series_details_context_clues_iter.next().unwrap(); &(DEFAULT_KEYBINDINGS.submit, "season details")
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search); assert_some_eq_x!(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.search.desc); series_details_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
let (key_binding, description) = series_details_context_clues_iter.next().unwrap(); );
assert_some_eq_x!(
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update); series_details_context_clues_iter.next(),
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.update.desc); &(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc)
);
let (key_binding, description) = series_details_context_clues_iter.next().unwrap(); assert_some_eq_x!(
series_details_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search); &(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc); DEFAULT_KEYBINDINGS.auto_search,
DEFAULT_KEYBINDINGS.auto_search.desc
let (key_binding, description) = series_details_context_clues_iter.next().unwrap(); )
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc); assert_some_eq_x!(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc); series_details_context_clues_iter.next(),
assert_eq!(series_details_context_clues_iter.next(), None); &(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
);
assert_none!(series_details_context_clues_iter.next());
} }
#[test] #[test]
fn test_season_details_context_clues() { fn test_season_details_context_clues() {
let mut season_details_context_clues_iter = SEASON_DETAILS_CONTEXT_CLUES.iter(); let mut season_details_context_clues_iter = SEASON_DETAILS_CONTEXT_CLUES.iter();
let (key_binding, description) = season_details_context_clues_iter.next().unwrap(); assert_some_eq_x!(
season_details_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh); &(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc); DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc
let (key_binding, description) = season_details_context_clues_iter.next().unwrap(); )
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.toggle_monitoring); assert_some_eq_x!(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.toggle_monitoring.desc); season_details_context_clues_iter.next(),
&(
let (key_binding, description) = season_details_context_clues_iter.next().unwrap(); DEFAULT_KEYBINDINGS.toggle_monitoring,
DEFAULT_KEYBINDINGS.toggle_monitoring.desc
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search); )
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.search.desc); );
assert_some_eq_x!(
let (key_binding, description) = season_details_context_clues_iter.next().unwrap(); season_details_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search); );
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc); assert_some_eq_x!(
season_details_context_clues_iter.next(),
let (key_binding, description) = season_details_context_clues_iter.next().unwrap(); &(
DEFAULT_KEYBINDINGS.auto_search,
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc); DEFAULT_KEYBINDINGS.auto_search.desc
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc); )
);
let (key_binding, description) = season_details_context_clues_iter.next().unwrap(); assert_some_eq_x!(
season_details_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit); &(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
assert_str_eq!(*description, "episode details"); );
assert_some_eq_x!(
let (key_binding, description) = season_details_context_clues_iter.next().unwrap(); season_details_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.submit, "episode details")
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete); );
assert_str_eq!(*description, "delete episode"); assert_some_eq_x!(
season_details_context_clues_iter.next(),
assert_eq!(season_details_context_clues_iter.next(), None); &(DEFAULT_KEYBINDINGS.delete, "delete episode")
);
assert_none!(season_details_context_clues_iter.next());
} }
#[test] #[test]
fn test_season_history_context_clues() { fn test_season_history_context_clues() {
let mut season_history_context_clues_iter = SEASON_HISTORY_CONTEXT_CLUES.iter(); let mut season_history_context_clues_iter = SEASON_HISTORY_CONTEXT_CLUES.iter();
let (key_binding, description) = season_history_context_clues_iter.next().unwrap(); assert_some_eq_x!(
season_history_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh); &(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc); DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc
let (key_binding, description) = season_history_context_clues_iter.next().unwrap(); )
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort); assert_some_eq_x!(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc); season_history_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
let (key_binding, description) = season_history_context_clues_iter.next().unwrap(); );
assert_some_eq_x!(
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search); season_history_context_clues_iter.next(),
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.search.desc); &(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
);
let (key_binding, description) = season_history_context_clues_iter.next().unwrap(); assert_some_eq_x!(
season_history_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.filter); &(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc)
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.filter.desc); );
assert_some_eq_x!(
let (key_binding, description) = season_history_context_clues_iter.next().unwrap(); season_history_context_clues_iter.next(),
&(
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search); DEFAULT_KEYBINDINGS.auto_search,
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc); DEFAULT_KEYBINDINGS.auto_search.desc
)
let (key_binding, description) = season_history_context_clues_iter.next().unwrap(); );
assert_some_eq_x!(
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit); season_history_context_clues_iter.next(),
assert_str_eq!(*description, "details"); &(DEFAULT_KEYBINDINGS.submit, "details")
);
let (key_binding, description) = season_history_context_clues_iter.next().unwrap(); assert_some_eq_x!(
season_history_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc); &(DEFAULT_KEYBINDINGS.esc, "cancel filter/close")
assert_str_eq!(*description, "cancel filter/close"); );
assert_eq!(season_history_context_clues_iter.next(), None); assert_none!(season_history_context_clues_iter.next());
} }
#[test] #[test]
fn test_manual_season_search_context_clues() { fn test_manual_season_search_context_clues() {
let mut manual_season_search_context_clues_iter = MANUAL_SEASON_SEARCH_CONTEXT_CLUES.iter(); let mut manual_season_search_context_clues_iter = MANUAL_SEASON_SEARCH_CONTEXT_CLUES.iter();
let (key_binding, description) = manual_season_search_context_clues_iter.next().unwrap(); assert_some_eq_x!(
manual_season_search_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh); &(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc); DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc
let (key_binding, description) = manual_season_search_context_clues_iter.next().unwrap(); )
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search); assert_some_eq_x!(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc); manual_season_search_context_clues_iter.next(),
&(
let (key_binding, description) = manual_season_search_context_clues_iter.next().unwrap(); DEFAULT_KEYBINDINGS.auto_search,
DEFAULT_KEYBINDINGS.auto_search.desc
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort); )
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc); );
assert_some_eq_x!(
let (key_binding, description) = manual_season_search_context_clues_iter.next().unwrap(); manual_season_search_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit); );
assert_str_eq!(*description, "details"); assert_some_eq_x!(
manual_season_search_context_clues_iter.next(),
let (key_binding, description) = manual_season_search_context_clues_iter.next().unwrap(); &(DEFAULT_KEYBINDINGS.submit, "details")
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc); assert_some_eq_x!(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc); manual_season_search_context_clues_iter.next(),
assert_eq!(manual_season_search_context_clues_iter.next(), None); &(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
);
assert_none!(manual_season_search_context_clues_iter.next());
} }
#[test] #[test]
fn test_manual_episode_search_context_clues() { fn test_manual_episode_search_context_clues() {
let mut manual_episode_search_context_clues_iter = MANUAL_EPISODE_SEARCH_CONTEXT_CLUES.iter(); let mut manual_episode_search_context_clues_iter = MANUAL_EPISODE_SEARCH_CONTEXT_CLUES.iter();
let (key_binding, description) = manual_episode_search_context_clues_iter.next().unwrap(); assert_some_eq_x!(
manual_episode_search_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh); &(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc); DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc
let (key_binding, description) = manual_episode_search_context_clues_iter.next().unwrap(); )
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search); assert_some_eq_x!(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc); manual_episode_search_context_clues_iter.next(),
&(
let (key_binding, description) = manual_episode_search_context_clues_iter.next().unwrap(); DEFAULT_KEYBINDINGS.auto_search,
DEFAULT_KEYBINDINGS.auto_search.desc
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort); )
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc); );
assert_some_eq_x!(
let (key_binding, description) = manual_episode_search_context_clues_iter.next().unwrap(); manual_episode_search_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit); );
assert_str_eq!(*description, "details"); assert_some_eq_x!(
manual_episode_search_context_clues_iter.next(),
let (key_binding, description) = manual_episode_search_context_clues_iter.next().unwrap(); &(DEFAULT_KEYBINDINGS.submit, "details")
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc); assert_some_eq_x!(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc); manual_episode_search_context_clues_iter.next(),
assert_eq!(manual_episode_search_context_clues_iter.next(), None); &(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
);
assert_none!(manual_episode_search_context_clues_iter.next());
} }
#[test] #[test]
fn test_episode_details_context_clues() { fn test_episode_details_context_clues() {
let mut episode_details_context_clues_iter = EPISODE_DETAILS_CONTEXT_CLUES.iter(); let mut episode_details_context_clues_iter = EPISODE_DETAILS_CONTEXT_CLUES.iter();
let (key_binding, description) = episode_details_context_clues_iter.next().unwrap(); assert_some_eq_x!(
episode_details_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh); &(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc); DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc
let (key_binding, description) = episode_details_context_clues_iter.next().unwrap(); )
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search); assert_some_eq_x!(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc); episode_details_context_clues_iter.next(),
&(
let (key_binding, description) = episode_details_context_clues_iter.next().unwrap(); DEFAULT_KEYBINDINGS.auto_search,
DEFAULT_KEYBINDINGS.auto_search.desc
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc); )
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc); );
assert_eq!(episode_details_context_clues_iter.next(), None); assert_some_eq_x!(
episode_details_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
);
assert_none!(episode_details_context_clues_iter.next());
} }
#[test] #[test]
fn test_selectable_episode_details_context_clues() { fn test_selectable_episode_details_context_clues() {
let mut episode_details_context_clues_iter = SELECTABLE_EPISODE_DETAILS_CONTEXT_CLUES.iter(); let mut episode_details_context_clues_iter = SELECTABLE_EPISODE_DETAILS_CONTEXT_CLUES.iter();
let (key_binding, description) = episode_details_context_clues_iter.next().unwrap(); assert_some_eq_x!(
episode_details_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh); &(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc); DEFAULT_KEYBINDINGS.refresh,
DEFAULT_KEYBINDINGS.refresh.desc
let (key_binding, description) = episode_details_context_clues_iter.next().unwrap(); )
);
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search); assert_some_eq_x!(
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc); episode_details_context_clues_iter.next(),
&(
let (key_binding, description) = episode_details_context_clues_iter.next().unwrap(); DEFAULT_KEYBINDINGS.auto_search,
DEFAULT_KEYBINDINGS.auto_search.desc
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit); )
assert_str_eq!(*description, "details"); );
assert_some_eq_x!(
let (key_binding, description) = episode_details_context_clues_iter.next().unwrap(); episode_details_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.submit, "details")
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc); );
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc); assert_some_eq_x!(
assert_eq!(episode_details_context_clues_iter.next(), None); episode_details_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
);
assert_none!(episode_details_context_clues_iter.next());
} }
#[test] #[test]
fn test_system_tasks_context_clues() { fn test_system_tasks_context_clues() {
let mut system_tasks_context_clues_iter = SYSTEM_TASKS_CONTEXT_CLUES.iter(); let mut system_tasks_context_clues_iter = SYSTEM_TASKS_CONTEXT_CLUES.iter();
let (key_binding, description) = system_tasks_context_clues_iter.next().unwrap(); assert_some_eq_x!(
system_tasks_context_clues_iter.next(),
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit); &(DEFAULT_KEYBINDINGS.submit, "start task")
assert_str_eq!(*description, "start task"); );
assert_some_eq_x!(
let (key_binding, description) = system_tasks_context_clues_iter.next().unwrap(); system_tasks_context_clues_iter.next(),
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc); );
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc); assert_none!(system_tasks_context_clues_iter.next());
assert_eq!(system_tasks_context_clues_iter.next(), None);
} }
#[test] #[test]
@@ -451,7 +421,6 @@ mod tests {
let mut app = App::test_default(); let mut app = App::test_default();
app.push_navigation_stack(ActiveRadarrBlock::default().into()); app.push_navigation_stack(ActiveRadarrBlock::default().into());
// This should panic because the route is not a Sonarr route
SonarrContextClueProvider::get_context_clues(&mut app); SonarrContextClueProvider::get_context_clues(&mut app);
} }
@@ -470,8 +439,7 @@ mod tests {
let context_clues = SonarrContextClueProvider::get_context_clues(&mut app); let context_clues = SonarrContextClueProvider::get_context_clues(&mut app);
assert!(context_clues.is_some()); assert_some_eq_x!(context_clues, expected_context_clues);
assert_eq!(expected_context_clues, context_clues.unwrap());
} }
#[rstest] #[rstest]
@@ -495,8 +463,7 @@ mod tests {
let context_clues = SonarrContextClueProvider::get_context_clues(&mut app); let context_clues = SonarrContextClueProvider::get_context_clues(&mut app);
assert!(context_clues.is_some()); assert_some_eq_x!(context_clues, expected_context_clues);
assert_eq!(expected_context_clues, context_clues.unwrap());
} }
#[rstest] #[rstest]
@@ -524,8 +491,7 @@ mod tests {
let context_clues = SonarrContextClueProvider::get_context_clues(&mut app); let context_clues = SonarrContextClueProvider::get_context_clues(&mut app);
assert!(context_clues.is_some()); assert_some_eq_x!(context_clues, expected_context_clues);
assert_eq!(expected_context_clues, context_clues.unwrap());
} }
#[rstest] #[rstest]
@@ -544,8 +510,7 @@ mod tests {
let context_clues = SonarrContextClueProvider::get_context_clues(&mut app); let context_clues = SonarrContextClueProvider::get_context_clues(&mut app);
assert!(context_clues.is_some()); assert_some_eq_x!(context_clues, &BARE_POPUP_CONTEXT_CLUES);
assert_eq!(context_clues.unwrap(), &BARE_POPUP_CONTEXT_CLUES);
} }
#[rstest] #[rstest]
@@ -567,8 +532,7 @@ mod tests {
let context_clues = SonarrContextClueProvider::get_context_clues(&mut app); let context_clues = SonarrContextClueProvider::get_context_clues(&mut app);
assert!(context_clues.is_some()); assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
assert_eq!(context_clues.unwrap(), &CONFIRMATION_PROMPT_CONTEXT_CLUES);
} }
#[rstest] #[rstest]
@@ -593,8 +557,7 @@ mod tests {
let context_clues = SonarrContextClueProvider::get_context_clues(&mut app); let context_clues = SonarrContextClueProvider::get_context_clues(&mut app);
assert!(context_clues.is_some()); assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
assert_eq!(context_clues.unwrap(), &CONFIRMATION_PROMPT_CONTEXT_CLUES);
} }
#[rstest] #[rstest]
@@ -614,8 +577,7 @@ mod tests {
let context_clues = SonarrContextClueProvider::get_context_clues(&mut app); let context_clues = SonarrContextClueProvider::get_context_clues(&mut app);
assert!(context_clues.is_some()); assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
assert_eq!(context_clues.unwrap(), &CONFIRMATION_PROMPT_CONTEXT_CLUES);
} }
#[rstest] #[rstest]
@@ -638,8 +600,7 @@ mod tests {
let context_clues = SonarrContextClueProvider::get_context_clues(&mut app); let context_clues = SonarrContextClueProvider::get_context_clues(&mut app);
assert!(context_clues.is_some()); assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
assert_eq!(context_clues.unwrap(), &CONFIRMATION_PROMPT_CONTEXT_CLUES);
} }
#[rstest] #[rstest]
@@ -655,11 +616,7 @@ mod tests {
let context_clues = SonarrContextClueProvider::get_context_clues(&mut app); let context_clues = SonarrContextClueProvider::get_context_clues(&mut app);
assert!(context_clues.is_some()); assert_some_eq_x!(context_clues, &ADD_SERIES_SEARCH_RESULTS_CONTEXT_CLUES);
assert_eq!(
context_clues.unwrap(),
&ADD_SERIES_SEARCH_RESULTS_CONTEXT_CLUES
);
} }
#[test] #[test]
@@ -669,8 +626,7 @@ mod tests {
app.push_navigation_stack(ActiveSonarrBlock::SystemTasks.into()); app.push_navigation_stack(ActiveSonarrBlock::SystemTasks.into());
let context_clues = SonarrContextClueProvider::get_context_clues(&mut app); let context_clues = SonarrContextClueProvider::get_context_clues(&mut app);
assert!(context_clues.is_some()); assert_some_eq_x!(context_clues, &SYSTEM_TASKS_CONTEXT_CLUES);
assert_eq!(context_clues.unwrap(), &SYSTEM_TASKS_CONTEXT_CLUES);
} }
#[rstest] #[rstest]
@@ -693,7 +649,6 @@ mod tests {
let context_clues = SonarrContextClueProvider::get_context_clues(&mut app); let context_clues = SonarrContextClueProvider::get_context_clues(&mut app);
assert!(context_clues.is_some()); assert_some_eq_x!(context_clues, expected_context_clues);
assert_eq!(expected_context_clues, context_clues.unwrap());
} }
} }
+1 -1
View File
@@ -16,7 +16,7 @@ mod tests {
}, },
sonarr_models::{Season, Series, SonarrRelease}, sonarr_models::{Season, Series, SonarrRelease},
}, },
network::{sonarr_network::SonarrEvent, NetworkEvent}, network::{NetworkEvent, sonarr_network::SonarrEvent},
}; };
#[tokio::test] #[tokio::test]
+21 -12
View File
@@ -2,16 +2,18 @@
mod tests { mod tests {
use std::sync::Arc; use std::sync::Arc;
use clap::{error::ErrorKind, CommandFactory}; use clap::{CommandFactory, error::ErrorKind};
use mockall::predicate::eq; use mockall::predicate::eq;
use rstest::rstest; use rstest::rstest;
use serde_json::json; use serde_json::json;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use crate::{ use crate::{
Cli,
app::App, app::App,
cli::{handle_command, mutex_flags_or_option, radarr::RadarrCommand, sonarr::SonarrCommand}, cli::{handle_command, mutex_flags_or_option, radarr::RadarrCommand, sonarr::SonarrCommand},
models::{ models::{
Serdeable,
radarr_models::{ radarr_models::{
BlocklistItem as RadarrBlocklistItem, BlocklistResponse as RadarrBlocklistResponse, BlocklistItem as RadarrBlocklistItem, BlocklistResponse as RadarrBlocklistResponse,
RadarrSerdeable, RadarrSerdeable,
@@ -20,12 +22,10 @@ mod tests {
BlocklistItem as SonarrBlocklistItem, BlocklistResponse as SonarrBlocklistResponse, BlocklistItem as SonarrBlocklistItem, BlocklistResponse as SonarrBlocklistResponse,
SonarrSerdeable, SonarrSerdeable,
}, },
Serdeable,
}, },
network::{ network::{
radarr_network::RadarrEvent, sonarr_network::SonarrEvent, MockNetworkTrait, NetworkEvent, MockNetworkTrait, NetworkEvent, radarr_network::RadarrEvent, sonarr_network::SonarrEvent,
}, },
Cli,
}; };
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
@@ -33,7 +33,7 @@ mod tests {
fn test_servarr_subcommand_requires_subcommand(#[values("radarr", "sonarr")] subcommand: &str) { fn test_servarr_subcommand_requires_subcommand(#[values("radarr", "sonarr")] subcommand: &str) {
let result = Cli::command().try_get_matches_from(["managarr", subcommand]); let result = Cli::command().try_get_matches_from(["managarr", subcommand]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
@@ -45,21 +45,28 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "all-indexer-settings"]); Cli::command().try_get_matches_from(["managarr", "radarr", "get", "all-indexer-settings"]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
fn test_sonarr_subcommand_delegates_to_sonarr() { fn test_sonarr_subcommand_delegates_to_sonarr() {
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "series"]); let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "series"]);
assert!(result.is_ok()); assert_ok!(&result);
}
#[test]
fn test_lidarr_subcommand_delegates_to_lidarr() {
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "list", "artists"]);
assert_ok!(&result);
} }
#[test] #[test]
fn test_completions_requires_argument() { fn test_completions_requires_argument() {
let result = Cli::command().try_get_matches_from(["managarr", "completions"]); let result = Cli::command().try_get_matches_from(["managarr", "completions"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
@@ -70,7 +77,7 @@ mod tests {
fn test_completions_invalid_argument() { fn test_completions_invalid_argument() {
let result = Cli::command().try_get_matches_from(["managarr", "completions", "test"]); let result = Cli::command().try_get_matches_from(["managarr", "completions", "test"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue); assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
} }
@@ -78,7 +85,7 @@ mod tests {
fn test_completions_satisfied_with_argument() { fn test_completions_satisfied_with_argument() {
let result = Cli::command().try_get_matches_from(["managarr", "completions", "bash"]); let result = Cli::command().try_get_matches_from(["managarr", "completions", "bash"]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[rstest] #[rstest]
@@ -141,7 +148,7 @@ mod tests {
let result = handle_command(&app_arc, clear_blocklist_command, &mut mock_network).await; let result = handle_command(&app_arc, clear_blocklist_command, &mut mock_network).await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -172,6 +179,8 @@ mod tests {
let result = handle_command(&app_arc, clear_blocklist_command, &mut mock_network).await; let result = handle_command(&app_arc, clear_blocklist_command, &mut mock_network).await;
assert!(result.is_ok()); assert_ok!(&result);
} }
// TODO: Implement test_cli_handler_delegates_lidarr_commands_to_the_lidarr_cli_handler
} }
+223
View File
@@ -0,0 +1,223 @@
use std::sync::Arc;
use anyhow::Result;
use clap::{ArgAction, Subcommand, arg};
use tokio::sync::Mutex;
use super::LidarrCommand;
use crate::{
app::App,
cli::{CliCommandHandler, Command},
models::lidarr_models::{
AddArtistBody, AddArtistOptions, AddLidarrRootFolderBody, MonitorType, NewItemMonitorType,
},
network::{NetworkTrait, lidarr_network::LidarrEvent},
};
#[cfg(test)]
#[path = "add_command_handler_tests.rs"]
mod add_command_handler_tests;
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
pub enum LidarrAddCommand {
#[command(about = "Add a new artist to your Lidarr library")]
Artist {
#[arg(
long,
help = "The MusicBrainz foreign artist ID of the artist you wish to add to your library",
required = true
)]
foreign_artist_id: String,
#[arg(long, help = "The name of the artist", required = true)]
artist_name: String,
#[arg(
long,
help = "The root folder path where all artist data and metadata should live",
required = true
)]
root_folder_path: String,
#[arg(
long,
help = "The ID of the quality profile to use for this artist",
required = true
)]
quality_profile_id: i64,
#[arg(
long,
help = "The ID of the metadata profile to use for this artist",
required = true
)]
metadata_profile_id: i64,
#[arg(long, help = "Disable monitoring for this artist")]
disable_monitoring: bool,
#[arg(
long,
help = "Tag IDs to tag the artist with",
value_parser,
action = ArgAction::Append
)]
tag: Vec<i64>,
#[arg(
long,
help = "What Lidarr should monitor for this artist",
value_enum,
default_value_t = MonitorType::default()
)]
monitor: MonitorType,
#[arg(
long,
help = "How Lidarr should monitor new items for this artist",
value_enum,
default_value_t = NewItemMonitorType::default()
)]
monitor_new_items: NewItemMonitorType,
#[arg(
long,
help = "Tell Lidarr to not start a search for missing albums once the artist is added to your library"
)]
no_search_for_missing_albums: bool,
},
#[command(about = "Add a new root folder")]
RootFolder {
#[arg(long, help = "The name of the root folder", required = true)]
name: String,
#[arg(long, help = "The path of the new root folder", required = true)]
root_folder_path: String,
#[arg(
long,
help = "The ID of the default quality profile for artists in this root folder",
required = true
)]
quality_profile_id: i64,
#[arg(
long,
help = "The ID of the default metadata profile for artists in this root folder",
required = true
)]
metadata_profile_id: i64,
#[arg(
long,
help = "The default monitor option for artists in this root folder",
value_enum,
default_value_t = MonitorType::default()
)]
monitor: MonitorType,
#[arg(
long,
help = "The default monitor new items option for artists in this root folder",
value_enum,
default_value_t = NewItemMonitorType::default()
)]
monitor_new_items: NewItemMonitorType,
#[arg(
long,
help = "Default tag IDs for artists in this root folder",
value_parser,
action = ArgAction::Append
)]
tag: Vec<i64>,
},
#[command(about = "Add new tag")]
Tag {
#[arg(long, help = "The name of the tag to be added", required = true)]
name: String,
},
}
impl From<LidarrAddCommand> for Command {
fn from(value: LidarrAddCommand) -> Self {
Command::Lidarr(LidarrCommand::Add(value))
}
}
pub(super) struct LidarrAddCommandHandler<'a, 'b> {
_app: &'a Arc<Mutex<App<'b>>>,
command: LidarrAddCommand,
network: &'a mut dyn NetworkTrait,
}
impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrAddCommand> for LidarrAddCommandHandler<'a, 'b> {
fn with(
app: &'a Arc<Mutex<App<'b>>>,
command: LidarrAddCommand,
network: &'a mut dyn NetworkTrait,
) -> Self {
LidarrAddCommandHandler {
_app: app,
command,
network,
}
}
async fn handle(self) -> Result<String> {
let result = match self.command {
LidarrAddCommand::Artist {
foreign_artist_id,
artist_name,
root_folder_path,
quality_profile_id,
metadata_profile_id,
disable_monitoring,
tag: tags,
monitor,
monitor_new_items,
no_search_for_missing_albums,
} => {
let body = AddArtistBody {
foreign_artist_id,
artist_name,
monitored: !disable_monitoring,
root_folder_path,
quality_profile_id,
metadata_profile_id,
tags,
tag_input_string: None,
add_options: AddArtistOptions {
monitor,
monitor_new_items,
search_for_missing_albums: !no_search_for_missing_albums,
},
};
let resp = self
.network
.handle_network_event(LidarrEvent::AddArtist(body).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrAddCommand::RootFolder {
name,
root_folder_path,
quality_profile_id,
metadata_profile_id,
monitor,
monitor_new_items,
tag: tags,
} => {
let add_root_folder_body = AddLidarrRootFolderBody {
name,
path: root_folder_path,
default_quality_profile_id: quality_profile_id,
default_metadata_profile_id: metadata_profile_id,
default_monitor_option: monitor,
default_new_item_monitor_option: monitor_new_items,
default_tags: tags,
tag_input_string: None,
};
let resp = self
.network
.handle_network_event(LidarrEvent::AddRootFolder(add_root_folder_body).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrAddCommand::Tag { name } => {
let resp = self
.network
.handle_network_event(LidarrEvent::AddTag(name).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
};
Ok(result)
}
}
+561
View File
@@ -0,0 +1,561 @@
#[cfg(test)]
mod tests {
use clap::{CommandFactory, Parser, error::ErrorKind};
use crate::{
Cli,
cli::{
Command,
lidarr::{LidarrCommand, add_command_handler::LidarrAddCommand},
},
models::lidarr_models::{MonitorType, NewItemMonitorType},
};
use pretty_assertions::assert_eq;
#[test]
fn test_lidarr_add_command_from() {
let command = LidarrAddCommand::Tag {
name: String::new(),
};
let result = Command::from(command.clone());
assert_eq!(result, Command::Lidarr(LidarrCommand::Add(command)));
}
mod cli {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_add_root_folder_requires_arguments() {
let result =
Cli::command().try_get_matches_from(["managarr", "lidarr", "add", "root-folder"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_add_root_folder_success() {
let expected_args = LidarrAddCommand::RootFolder {
name: "Music".to_owned(),
root_folder_path: "/nfs/test".to_owned(),
quality_profile_id: 1,
metadata_profile_id: 1,
monitor: MonitorType::All,
monitor_new_items: NewItemMonitorType::All,
tag: vec![],
};
let result = Cli::try_parse_from([
"managarr",
"lidarr",
"add",
"root-folder",
"--name",
"Music",
"--root-folder-path",
"/nfs/test",
"--quality-profile-id",
"1",
"--metadata-profile-id",
"1",
]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::Add(add_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(add_command, expected_args);
}
#[test]
fn test_add_tag_requires_arguments() {
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "add", "tag"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_add_tag_success() {
let expected_args = LidarrAddCommand::Tag {
name: "test".to_owned(),
};
let result = Cli::try_parse_from(["managarr", "lidarr", "add", "tag", "--name", "test"]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::Add(add_command))) = result.unwrap().command else {
panic!("Unexpected command type")
};
assert_eq!(add_command, expected_args);
}
#[test]
fn test_add_artist_requires_arguments() {
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "add", "artist"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_add_artist_requires_foreign_artist_id() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"add",
"artist",
"--artist-name",
"Test",
"--root-folder-path",
"/music",
"--quality-profile-id",
"1",
"--metadata-profile-id",
"1",
]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_add_artist_requires_artist_name() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"add",
"artist",
"--foreign-artist-id",
"test-id",
"--root-folder-path",
"/music",
"--quality-profile-id",
"1",
"--metadata-profile-id",
"1",
]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_add_artist_requires_root_folder_path() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"add",
"artist",
"--foreign-artist-id",
"test-id",
"--artist-name",
"Test",
"--quality-profile-id",
"1",
"--metadata-profile-id",
"1",
]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_add_artist_requires_quality_profile_id() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"add",
"artist",
"--foreign-artist-id",
"test-id",
"--artist-name",
"Test",
"--root-folder-path",
"/music",
"--metadata-profile-id",
"1",
]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_add_artist_requires_metadata_profile_id() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"add",
"artist",
"--foreign-artist-id",
"test-id",
"--artist-name",
"Test",
"--root-folder-path",
"/music",
"--quality-profile-id",
"1",
]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_add_artist_success_with_required_args_only() {
let expected_args = LidarrAddCommand::Artist {
foreign_artist_id: "test-id".to_owned(),
artist_name: "Test Artist".to_owned(),
root_folder_path: "/music".to_owned(),
quality_profile_id: 1,
metadata_profile_id: 1,
disable_monitoring: false,
tag: vec![],
monitor: MonitorType::default(),
monitor_new_items: NewItemMonitorType::default(),
no_search_for_missing_albums: false,
};
let result = Cli::try_parse_from([
"managarr",
"lidarr",
"add",
"artist",
"--foreign-artist-id",
"test-id",
"--artist-name",
"Test Artist",
"--root-folder-path",
"/music",
"--quality-profile-id",
"1",
"--metadata-profile-id",
"1",
]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::Add(add_command))) = result.unwrap().command else {
panic!("Unexpected command type")
};
assert_eq!(add_command, expected_args);
}
#[test]
fn test_add_artist_success_with_all_args() {
let expected_args = LidarrAddCommand::Artist {
foreign_artist_id: "test-id".to_owned(),
artist_name: "Test Artist".to_owned(),
root_folder_path: "/music".to_owned(),
quality_profile_id: 1,
metadata_profile_id: 2,
disable_monitoring: true,
tag: vec![1, 2],
monitor: MonitorType::Future,
monitor_new_items: NewItemMonitorType::New,
no_search_for_missing_albums: true,
};
let result = Cli::try_parse_from([
"managarr",
"lidarr",
"add",
"artist",
"--foreign-artist-id",
"test-id",
"--artist-name",
"Test Artist",
"--root-folder-path",
"/music",
"--quality-profile-id",
"1",
"--metadata-profile-id",
"2",
"--disable-monitoring",
"--tag",
"1",
"--tag",
"2",
"--monitor",
"future",
"--monitor-new-items",
"new",
"--no-search-for-missing-albums",
]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::Add(add_command))) = result.unwrap().command else {
panic!("Unexpected command type")
};
assert_eq!(add_command, expected_args);
}
#[test]
fn test_add_artist_monitor_type_validation() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"add",
"artist",
"--foreign-artist-id",
"test-id",
"--artist-name",
"Test Artist",
"--root-folder-path",
"/music",
"--quality-profile-id",
"1",
"--metadata-profile-id",
"2",
"--monitor",
"test",
]);
assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
}
#[test]
fn test_add_artist_new_item_monitor_type_validation() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"add",
"artist",
"--foreign-artist-id",
"test-id",
"--artist-name",
"Test Artist",
"--root-folder-path",
"/music",
"--quality-profile-id",
"1",
"--metadata-profile-id",
"2",
"--monitor-new-items",
"test",
]);
assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
}
#[test]
fn test_add_artist_tags_is_repeatable() {
let expected_args = LidarrAddCommand::Artist {
foreign_artist_id: "test-id".to_owned(),
artist_name: "Test Artist".to_owned(),
root_folder_path: "/music".to_owned(),
quality_profile_id: 1,
metadata_profile_id: 2,
disable_monitoring: false,
tag: vec![1, 2],
monitor: MonitorType::default(),
monitor_new_items: NewItemMonitorType::default(),
no_search_for_missing_albums: false,
};
let result = Cli::try_parse_from([
"managarr",
"lidarr",
"add",
"artist",
"--foreign-artist-id",
"test-id",
"--artist-name",
"Test Artist",
"--root-folder-path",
"/music",
"--quality-profile-id",
"1",
"--metadata-profile-id",
"2",
"--tag",
"1",
"--tag",
"2",
]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::Add(add_command))) = result.unwrap().command else {
panic!("Unexpected command type")
};
assert_eq!(add_command, expected_args);
}
}
mod handler {
use std::sync::Arc;
use mockall::predicate::eq;
use serde_json::json;
use tokio::sync::Mutex;
use crate::cli::CliCommandHandler;
use crate::cli::lidarr::add_command_handler::{LidarrAddCommand, LidarrAddCommandHandler};
use crate::models::Serdeable;
use crate::models::lidarr_models::{
AddArtistBody, AddArtistOptions, AddLidarrRootFolderBody, LidarrSerdeable, MonitorType,
NewItemMonitorType,
};
use crate::network::lidarr_network::LidarrEvent;
use crate::{
app::App,
network::{MockNetworkTrait, NetworkEvent},
};
#[tokio::test]
async fn test_handle_add_root_folder_command() {
let expected_root_folder_path = "/nfs/test".to_owned();
let expected_add_root_folder_body = AddLidarrRootFolderBody {
name: "Music".to_owned(),
path: expected_root_folder_path.clone(),
default_quality_profile_id: 1,
default_metadata_profile_id: 1,
default_monitor_option: MonitorType::All,
default_new_item_monitor_option: NewItemMonitorType::All,
default_tags: vec![1, 2],
tag_input_string: None,
};
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::AddRootFolder(expected_add_root_folder_body.clone()).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let add_root_folder_command = LidarrAddCommand::RootFolder {
name: "Music".to_owned(),
root_folder_path: expected_root_folder_path,
quality_profile_id: 1,
metadata_profile_id: 1,
monitor: MonitorType::All,
monitor_new_items: NewItemMonitorType::All,
tag: vec![1, 2],
};
let result =
LidarrAddCommandHandler::with(&app_arc, add_root_folder_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_add_tag_command() {
let expected_tag_name = "test".to_owned();
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::AddTag(expected_tag_name.clone()).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let add_tag_command = LidarrAddCommand::Tag {
name: expected_tag_name,
};
let result = LidarrAddCommandHandler::with(&app_arc, add_tag_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_add_artist_command() {
let expected_body = AddArtistBody {
foreign_artist_id: "test-id".to_owned(),
artist_name: "Test Artist".to_owned(),
monitored: false,
root_folder_path: "/music".to_owned(),
quality_profile_id: 1,
metadata_profile_id: 1,
tags: vec![1, 2],
tag_input_string: None,
add_options: AddArtistOptions {
monitor: MonitorType::All,
monitor_new_items: NewItemMonitorType::All,
search_for_missing_albums: false,
},
};
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::AddArtist(expected_body).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let add_artist_command = LidarrAddCommand::Artist {
foreign_artist_id: "test-id".to_owned(),
artist_name: "Test Artist".to_owned(),
root_folder_path: "/music".to_owned(),
quality_profile_id: 1,
metadata_profile_id: 1,
disable_monitoring: true,
tag: vec![1, 2],
monitor: MonitorType::All,
monitor_new_items: NewItemMonitorType::All,
no_search_for_missing_albums: true,
};
let result = LidarrAddCommandHandler::with(&app_arc, add_artist_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
}
}
+153
View File
@@ -0,0 +1,153 @@
use std::sync::Arc;
use anyhow::Result;
use clap::Subcommand;
use tokio::sync::Mutex;
use crate::{
app::App,
cli::{CliCommandHandler, Command},
models::lidarr_models::DeleteParams,
network::{NetworkTrait, lidarr_network::LidarrEvent},
};
use super::LidarrCommand;
#[cfg(test)]
#[path = "delete_command_handler_tests.rs"]
mod delete_command_handler_tests;
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
pub enum LidarrDeleteCommand {
#[command(about = "Delete an album from your Lidarr library")]
Album {
#[arg(long, help = "The ID of the album to delete", required = true)]
album_id: i64,
#[arg(long, help = "Delete the album files from disk as well")]
delete_files_from_disk: bool,
#[arg(long, help = "Add a list exclusion for this album")]
add_list_exclusion: bool,
},
#[command(about = "Delete an artist from your Lidarr library")]
Artist {
#[arg(long, help = "The ID of the artist to delete", required = true)]
artist_id: i64,
#[arg(long, help = "Delete the artist files from disk as well")]
delete_files_from_disk: bool,
#[arg(long, help = "Add a list exclusion for this artist")]
add_list_exclusion: bool,
},
#[command(about = "Delete the specified download")]
Download {
#[arg(long, help = "The ID of the download to delete", required = true)]
download_id: i64,
},
#[command(about = "Delete the indexer with the given ID")]
Indexer {
#[arg(long, help = "The ID of the indexer to delete", required = true)]
indexer_id: i64,
},
#[command(about = "Delete the root folder with the given ID")]
RootFolder {
#[arg(long, help = "The ID of the root folder to delete", required = true)]
root_folder_id: i64,
},
#[command(about = "Delete the tag with the specified ID")]
Tag {
#[arg(long, help = "The ID of the tag to delete", required = true)]
tag_id: i64,
},
}
impl From<LidarrDeleteCommand> for Command {
fn from(value: LidarrDeleteCommand) -> Self {
Command::Lidarr(LidarrCommand::Delete(value))
}
}
pub(super) struct LidarrDeleteCommandHandler<'a, 'b> {
_app: &'a Arc<Mutex<App<'b>>>,
command: LidarrDeleteCommand,
network: &'a mut dyn NetworkTrait,
}
impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrDeleteCommand> for LidarrDeleteCommandHandler<'a, 'b> {
fn with(
_app: &'a Arc<Mutex<App<'b>>>,
command: LidarrDeleteCommand,
network: &'a mut dyn NetworkTrait,
) -> Self {
LidarrDeleteCommandHandler {
_app,
command,
network,
}
}
async fn handle(self) -> Result<String> {
let result = match self.command {
LidarrDeleteCommand::Album {
album_id,
delete_files_from_disk,
add_list_exclusion,
} => {
let delete_album_params = DeleteParams {
id: album_id,
delete_files: delete_files_from_disk,
add_import_list_exclusion: add_list_exclusion,
};
let resp = self
.network
.handle_network_event(LidarrEvent::DeleteAlbum(delete_album_params).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrDeleteCommand::Artist {
artist_id,
delete_files_from_disk,
add_list_exclusion,
} => {
let delete_artist_params = DeleteParams {
id: artist_id,
delete_files: delete_files_from_disk,
add_import_list_exclusion: add_list_exclusion,
};
let resp = self
.network
.handle_network_event(LidarrEvent::DeleteArtist(delete_artist_params).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrDeleteCommand::Download { download_id } => {
let resp = self
.network
.handle_network_event(LidarrEvent::DeleteDownload(download_id).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrDeleteCommand::Indexer { indexer_id } => {
let resp = self
.network
.handle_network_event(LidarrEvent::DeleteIndexer(indexer_id).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrDeleteCommand::RootFolder { root_folder_id } => {
let resp = self
.network
.handle_network_event(LidarrEvent::DeleteRootFolder(root_folder_id).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrDeleteCommand::Tag { tag_id } => {
let resp = self
.network
.handle_network_event(LidarrEvent::DeleteTag(tag_id).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
};
Ok(result)
}
}
@@ -0,0 +1,468 @@
#[cfg(test)]
mod tests {
use crate::{
Cli,
cli::{
Command,
lidarr::{LidarrCommand, delete_command_handler::LidarrDeleteCommand},
},
};
use clap::{CommandFactory, Parser, error::ErrorKind};
use pretty_assertions::assert_eq;
#[test]
fn test_lidarr_delete_command_from() {
let command = LidarrDeleteCommand::Artist {
artist_id: 1,
delete_files_from_disk: false,
add_list_exclusion: false,
};
let result = Command::from(command.clone());
assert_eq!(result, Command::Lidarr(LidarrCommand::Delete(command)));
}
mod cli {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_delete_album_requires_arguments() {
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "album"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_delete_album_defaults() {
let expected_args = LidarrDeleteCommand::Album {
album_id: 1,
delete_files_from_disk: false,
add_list_exclusion: false,
};
let result =
Cli::try_parse_from(["managarr", "lidarr", "delete", "album", "--album-id", "1"]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::Delete(delete_command))) = result.unwrap().command
else {
panic!("Unexpected command type");
};
assert_eq!(delete_command, expected_args);
}
#[test]
fn test_delete_album_all_args_defined() {
let expected_args = LidarrDeleteCommand::Album {
album_id: 1,
delete_files_from_disk: true,
add_list_exclusion: true,
};
let result = Cli::try_parse_from([
"managarr",
"lidarr",
"delete",
"album",
"--album-id",
"1",
"--delete-files-from-disk",
"--add-list-exclusion",
]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::Delete(delete_command))) = result.unwrap().command
else {
panic!("Unexpected command type");
};
assert_eq!(delete_command, expected_args);
}
#[test]
fn test_delete_artist_requires_arguments() {
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "artist"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_delete_artist_defaults() {
let expected_args = LidarrDeleteCommand::Artist {
artist_id: 1,
delete_files_from_disk: false,
add_list_exclusion: false,
};
let result =
Cli::try_parse_from(["managarr", "lidarr", "delete", "artist", "--artist-id", "1"]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::Delete(delete_command))) = result.unwrap().command
else {
panic!("Unexpected command type");
};
assert_eq!(delete_command, expected_args);
}
#[test]
fn test_delete_artist_all_args_defined() {
let expected_args = LidarrDeleteCommand::Artist {
artist_id: 1,
delete_files_from_disk: true,
add_list_exclusion: true,
};
let result = Cli::try_parse_from([
"managarr",
"lidarr",
"delete",
"artist",
"--artist-id",
"1",
"--delete-files-from-disk",
"--add-list-exclusion",
]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::Delete(delete_command))) = result.unwrap().command
else {
panic!("Unexpected command type");
};
assert_eq!(delete_command, expected_args);
}
#[test]
fn test_delete_download_requires_arguments() {
let result =
Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "download"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_delete_download_success() {
let expected_args = LidarrDeleteCommand::Download { download_id: 1 };
let result = Cli::try_parse_from([
"managarr",
"lidarr",
"delete",
"download",
"--download-id",
"1",
]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::Delete(delete_command))) = result.unwrap().command
else {
panic!("Unexpected command type");
};
assert_eq!(delete_command, expected_args);
}
#[test]
fn test_delete_indexer_requires_arguments() {
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "indexer"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_delete_indexer_success() {
let expected_args = LidarrDeleteCommand::Indexer { indexer_id: 1 };
let result = Cli::try_parse_from([
"managarr",
"lidarr",
"delete",
"indexer",
"--indexer-id",
"1",
]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::Delete(delete_command))) = result.unwrap().command
else {
panic!("Unexpected command type");
};
assert_eq!(delete_command, expected_args);
}
#[test]
fn test_delete_root_folder_requires_arguments() {
let result =
Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "root-folder"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_delete_root_folder_success() {
let expected_args = LidarrDeleteCommand::RootFolder { root_folder_id: 1 };
let result = Cli::try_parse_from([
"managarr",
"lidarr",
"delete",
"root-folder",
"--root-folder-id",
"1",
]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::Delete(delete_command))) = result.unwrap().command
else {
panic!("Unexpected command type");
};
assert_eq!(delete_command, expected_args);
}
#[test]
fn test_delete_tag_requires_arguments() {
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "tag"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_delete_tag_success() {
let expected_args = LidarrDeleteCommand::Tag { tag_id: 1 };
let result = Cli::try_parse_from(["managarr", "lidarr", "delete", "tag", "--tag-id", "1"]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::Delete(delete_command))) = result.unwrap().command
else {
panic!("Unexpected command type");
};
assert_eq!(delete_command, expected_args);
}
}
mod handler {
use std::sync::Arc;
use mockall::predicate::eq;
use serde_json::json;
use tokio::sync::Mutex;
use crate::{
app::App,
cli::{
CliCommandHandler,
lidarr::delete_command_handler::{LidarrDeleteCommand, LidarrDeleteCommandHandler},
},
models::{
Serdeable,
lidarr_models::{DeleteParams, LidarrSerdeable},
},
network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent},
};
#[tokio::test]
async fn test_handle_delete_album_command() {
let expected_delete_album_params = DeleteParams {
id: 1,
delete_files: true,
add_import_list_exclusion: true,
};
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::DeleteAlbum(expected_delete_album_params).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let delete_album_command = LidarrDeleteCommand::Album {
album_id: 1,
delete_files_from_disk: true,
add_list_exclusion: true,
};
let result =
LidarrDeleteCommandHandler::with(&app_arc, delete_album_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_delete_artist_command() {
let expected_delete_artist_params = DeleteParams {
id: 1,
delete_files: true,
add_import_list_exclusion: true,
};
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::DeleteArtist(expected_delete_artist_params).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let delete_artist_command = LidarrDeleteCommand::Artist {
artist_id: 1,
delete_files_from_disk: true,
add_list_exclusion: true,
};
let result =
LidarrDeleteCommandHandler::with(&app_arc, delete_artist_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_delete_download_command() {
let expected_download_id = 1;
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::DeleteDownload(expected_download_id).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let delete_download_command = LidarrDeleteCommand::Download { download_id: 1 };
let result =
LidarrDeleteCommandHandler::with(&app_arc, delete_download_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_delete_indexer_command() {
let expected_indexer_id = 1;
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::DeleteIndexer(expected_indexer_id).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let delete_indexer_command = LidarrDeleteCommand::Indexer { indexer_id: 1 };
let result =
LidarrDeleteCommandHandler::with(&app_arc, delete_indexer_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_delete_root_folder_command() {
let expected_root_folder_id = 1;
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::DeleteRootFolder(expected_root_folder_id).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let delete_root_folder_command = LidarrDeleteCommand::RootFolder { root_folder_id: 1 };
let result =
LidarrDeleteCommandHandler::with(&app_arc, delete_root_folder_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_delete_tag_command() {
let expected_tag_id = 1;
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::DeleteTag(expected_tag_id).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let delete_tag_command = LidarrDeleteCommand::Tag { tag_id: 1 };
let result =
LidarrDeleteCommandHandler::with(&app_arc, delete_tag_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
}
}
+343
View File
@@ -0,0 +1,343 @@
use std::sync::Arc;
use anyhow::Result;
use clap::{ArgAction, ArgGroup, Subcommand};
use tokio::sync::Mutex;
use super::LidarrCommand;
use crate::models::Serdeable;
use crate::models::lidarr_models::LidarrSerdeable;
use crate::models::servarr_models::{EditIndexerParams, IndexerSettings};
use crate::{
app::App,
cli::{CliCommandHandler, Command, mutex_flags_or_option},
models::lidarr_models::{EditArtistParams, NewItemMonitorType},
network::{NetworkTrait, lidarr_network::LidarrEvent},
};
#[cfg(test)]
#[path = "edit_command_handler_tests.rs"]
mod edit_command_handler_tests;
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
pub enum LidarrEditCommand {
#[command(
about = "Edit and indexer settings that apply to all indexers",
group(
ArgGroup::new("edit_settings")
.args([
"maximum_size",
"minimum_age",
"retention",
"rss_sync_interval",
]).required(true)
.multiple(true))
)]
AllIndexerSettings {
#[arg(
long,
help = "The maximum size for a release to be grabbed in MB. Set to zero to set to unlimited"
)]
maximum_size: Option<i64>,
#[arg(
long,
help = "Usenet only: Minimum age in minutes of NZBs before they are grabbed. Use this to give new releases time to propagate to your usenet provider."
)]
minimum_age: Option<i64>,
#[arg(
long,
help = "Usenet only: The retention time in days to retain releases. Set to zero to set for unlimited retention"
)]
retention: Option<i64>,
#[arg(
long,
help = "The RSS sync interval in minutes. Set to zero to disable (this will stop all automatic release grabbing)"
)]
rss_sync_interval: Option<i64>,
},
#[command(
about = "Edit preferences for the specified artist",
group(
ArgGroup::new("edit_artist")
.args([
"enable_monitoring",
"disable_monitoring",
"monitor_new_items",
"quality_profile_id",
"metadata_profile_id",
"root_folder_path",
"tag",
"clear_tags"
]).required(true)
.multiple(true))
)]
Artist {
#[arg(
long,
help = "The ID of the artist whose settings you want to edit",
required = true
)]
artist_id: i64,
#[arg(
long,
help = "Enable monitoring of this artist in Lidarr so Lidarr will automatically download releases from this artist if they are available",
conflicts_with = "disable_monitoring"
)]
enable_monitoring: bool,
#[arg(
long,
help = "Disable monitoring of this artist so Lidarr does not automatically download releases from this artist if they are available",
conflicts_with = "enable_monitoring"
)]
disable_monitoring: bool,
#[arg(
long,
help = "How Lidarr should monitor new albums from this artist",
value_enum
)]
monitor_new_items: Option<NewItemMonitorType>,
#[arg(long, help = "The ID of the quality profile to use for this artist")]
quality_profile_id: Option<i64>,
#[arg(long, help = "The ID of the metadata profile to use for this artist")]
metadata_profile_id: Option<i64>,
#[arg(
long,
help = "The root folder path where all artist data and metadata should live"
)]
root_folder_path: Option<String>,
#[arg(
long,
help = "Tag IDs to tag this artist with",
value_parser,
action = ArgAction::Append,
conflicts_with = "clear_tags"
)]
tag: Option<Vec<i64>>,
#[arg(long, help = "Clear all tags on this artist", conflicts_with = "tag")]
clear_tags: bool,
},
#[command(
about = "Edit preferences for the specified indexer",
group(
ArgGroup::new("edit_indexer")
.args([
"name",
"enable_rss",
"disable_rss",
"enable_automatic_search",
"disable_automatic_search",
"enable_interactive_search",
"disable_automatic_search",
"url",
"api_key",
"seed_ratio",
"tag",
"priority",
"clear_tags"
]).required(true)
.multiple(true))
)]
Indexer {
#[arg(
long,
help = "The ID of the indexer whose settings you wish to edit",
required = true
)]
indexer_id: i64,
#[arg(long, help = "The name of the indexer")]
name: Option<String>,
#[arg(
long,
help = "Indicate to Lidarr that this indexer should be used when Lidarr periodically looks for releases via RSS Sync",
conflicts_with = "disable_rss"
)]
enable_rss: bool,
#[arg(
long,
help = "Disable using this indexer when Lidarr periodically looks for releases via RSS Sync",
conflicts_with = "enable_rss"
)]
disable_rss: bool,
#[arg(
long,
help = "Indicate to Lidarr that this indexer should be used when automatic searches are performed via the UI or by Lidarr",
conflicts_with = "disable_automatic_search"
)]
enable_automatic_search: bool,
#[arg(
long,
help = "Disable using this indexer whenever automatic searches are performed via the UI or by Lidarr",
conflicts_with = "enable_automatic_search"
)]
disable_automatic_search: bool,
#[arg(
long,
help = "Indicate to Lidarr that this indexer should be used when an interactive search is used",
conflicts_with = "disable_interactive_search"
)]
enable_interactive_search: bool,
#[arg(
long,
help = "Disable using this indexer whenever an interactive search is performed",
conflicts_with = "enable_interactive_search"
)]
disable_interactive_search: bool,
#[arg(long, help = "The URL of the indexer")]
url: Option<String>,
#[arg(long, help = "The API key used to access the indexer's API")]
api_key: Option<String>,
#[arg(
long,
help = "The ratio a torrent should reach before stopping; Empty uses the download client's default. Ratio should be at least 1.0 and follow the indexer's rules"
)]
seed_ratio: Option<String>,
#[arg(
long,
help = "Only use this indexer for series with at least one matching tag ID. Leave blank to use with all series.",
value_parser,
action = ArgAction::Append,
conflicts_with = "clear_tags"
)]
tag: Option<Vec<i64>>,
#[arg(
long,
help = "Indexer Priority from 1 (Highest) to 50 (Lowest). Default: 25. Used when grabbing releases as a tiebreaker for otherwise equal releases, Lidarr will still use all enabled indexers for RSS Sync and Searching"
)]
priority: Option<i64>,
#[arg(long, help = "Clear all tags on this indexer", conflicts_with = "tag")]
clear_tags: bool,
},
}
impl From<LidarrEditCommand> for Command {
fn from(value: LidarrEditCommand) -> Self {
Command::Lidarr(LidarrCommand::Edit(value))
}
}
pub(super) struct LidarrEditCommandHandler<'a, 'b> {
_app: &'a Arc<Mutex<App<'b>>>,
command: LidarrEditCommand,
network: &'a mut dyn NetworkTrait,
}
impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrEditCommand> for LidarrEditCommandHandler<'a, 'b> {
fn with(
_app: &'a Arc<Mutex<App<'b>>>,
command: LidarrEditCommand,
network: &'a mut dyn NetworkTrait,
) -> Self {
LidarrEditCommandHandler {
_app,
command,
network,
}
}
async fn handle(self) -> Result<String> {
let result = match self.command {
LidarrEditCommand::AllIndexerSettings {
maximum_size,
minimum_age,
retention,
rss_sync_interval,
} => {
if let Serdeable::Lidarr(LidarrSerdeable::IndexerSettings(previous_indexer_settings)) = self
.network
.handle_network_event(LidarrEvent::GetAllIndexerSettings.into())
.await?
{
let params = IndexerSettings {
id: 1,
maximum_size: maximum_size.unwrap_or(previous_indexer_settings.maximum_size),
minimum_age: minimum_age.unwrap_or(previous_indexer_settings.minimum_age),
retention: retention.unwrap_or(previous_indexer_settings.retention),
rss_sync_interval: rss_sync_interval
.unwrap_or(previous_indexer_settings.rss_sync_interval),
};
self
.network
.handle_network_event(LidarrEvent::EditAllIndexerSettings(params).into())
.await?;
"All indexer settings updated".to_owned()
} else {
String::new()
}
}
LidarrEditCommand::Artist {
artist_id,
enable_monitoring,
disable_monitoring,
monitor_new_items,
quality_profile_id,
metadata_profile_id,
root_folder_path,
tag,
clear_tags,
} => {
let monitored_value = mutex_flags_or_option(enable_monitoring, disable_monitoring);
let edit_artist_params = EditArtistParams {
artist_id,
monitored: monitored_value,
monitor_new_items,
quality_profile_id,
metadata_profile_id,
root_folder_path,
tags: tag,
tag_input_string: None,
clear_tags,
};
self
.network
.handle_network_event(LidarrEvent::EditArtist(edit_artist_params).into())
.await?;
"Artist Updated".to_owned()
}
LidarrEditCommand::Indexer {
indexer_id,
name,
enable_rss,
disable_rss,
enable_automatic_search,
disable_automatic_search,
enable_interactive_search,
disable_interactive_search,
url,
api_key,
seed_ratio,
tag,
priority,
clear_tags,
} => {
let rss_value = mutex_flags_or_option(enable_rss, disable_rss);
let automatic_search_value =
mutex_flags_or_option(enable_automatic_search, disable_automatic_search);
let interactive_search_value =
mutex_flags_or_option(enable_interactive_search, disable_interactive_search);
let edit_indexer_params = EditIndexerParams {
indexer_id,
name,
enable_rss: rss_value,
enable_automatic_search: automatic_search_value,
enable_interactive_search: interactive_search_value,
url,
api_key,
seed_ratio,
tags: tag,
tag_input_string: None,
priority,
clear_tags,
};
self
.network
.handle_network_event(LidarrEvent::EditIndexer(edit_indexer_params).into())
.await?;
"Indexer updated".to_owned()
}
};
Ok(result)
}
}
@@ -0,0 +1,858 @@
#[cfg(test)]
mod tests {
use crate::cli::{
Command,
lidarr::{LidarrCommand, edit_command_handler::LidarrEditCommand},
};
#[test]
fn test_lidarr_edit_command_from() {
let command = LidarrEditCommand::Artist {
artist_id: 1,
enable_monitoring: false,
disable_monitoring: false,
monitor_new_items: None,
quality_profile_id: None,
metadata_profile_id: None,
root_folder_path: None,
tag: None,
clear_tags: false,
};
let result = Command::from(command.clone());
assert_eq!(result, Command::Lidarr(LidarrCommand::Edit(command)));
}
mod cli {
use crate::{Cli, models::lidarr_models::NewItemMonitorType};
use super::*;
use clap::{CommandFactory, Parser, error::ErrorKind};
use pretty_assertions::assert_eq;
use rstest::rstest;
#[test]
fn test_edit_all_indexer_settings_requires_arguments() {
let result =
Cli::command().try_get_matches_from(["managarr", "lidarr", "edit", "all-indexer-settings"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[rstest]
fn test_edit_all_indexer_settings_assert_argument_flags_require_args(
#[values(
"--maximum-size",
"--minimum-age",
"--retention",
"--rss-sync-interval"
)]
flag: &str,
) {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"edit",
"all-indexer-settings",
flag,
]);
assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
}
#[test]
fn test_edit_all_indexer_settings_only_requires_at_least_one_argument() {
let expected_args = LidarrEditCommand::AllIndexerSettings {
maximum_size: Some(1),
minimum_age: None,
retention: None,
rss_sync_interval: None,
};
let result = Cli::try_parse_from([
"managarr",
"lidarr",
"edit",
"all-indexer-settings",
"--maximum-size",
"1",
]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args);
}
#[test]
fn test_edit_all_indexer_settings_all_arguments_defined() {
let expected_args = LidarrEditCommand::AllIndexerSettings {
maximum_size: Some(1),
minimum_age: Some(1),
retention: Some(1),
rss_sync_interval: Some(1),
};
let result = Cli::try_parse_from([
"managarr",
"lidarr",
"edit",
"all-indexer-settings",
"--maximum-size",
"1",
"--minimum-age",
"1",
"--retention",
"1",
"--rss-sync-interval",
"1",
]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args);
}
#[test]
fn test_edit_artist_requires_arguments() {
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "edit", "artist"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_edit_artist_with_artist_id_still_requires_arguments() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"edit",
"artist",
"--artist-id",
"1",
]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_edit_artist_monitoring_flags_conflict() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"edit",
"artist",
"--artist-id",
"1",
"--enable-monitoring",
"--disable-monitoring",
]);
assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
}
#[test]
fn test_edit_artist_tag_flags_conflict() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"edit",
"artist",
"--artist-id",
"1",
"--tag",
"1",
"--clear-tags",
]);
assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
}
#[rstest]
fn test_edit_artist_assert_argument_flags_require_args(
#[values(
"--monitor-new-items",
"--quality-profile-id",
"--metadata-profile-id",
"--root-folder-path",
"--tag"
)]
flag: &str,
) {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"edit",
"artist",
"--artist-id",
"1",
flag,
]);
assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
}
#[test]
fn test_edit_artist_monitor_new_items_validation() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"edit",
"artist",
"--artist-id",
"1",
"--monitor-new-items",
"test",
]);
assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
}
#[test]
fn test_edit_artist_only_requires_at_least_one_argument_plus_artist_id() {
let expected_args = LidarrEditCommand::Artist {
artist_id: 1,
enable_monitoring: false,
disable_monitoring: false,
monitor_new_items: None,
quality_profile_id: None,
metadata_profile_id: None,
root_folder_path: Some("/nfs/test".to_owned()),
tag: None,
clear_tags: false,
};
let result = Cli::try_parse_from([
"managarr",
"lidarr",
"edit",
"artist",
"--artist-id",
"1",
"--root-folder-path",
"/nfs/test",
]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args);
}
#[test]
fn test_edit_artist_tag_argument_is_repeatable() {
let expected_args = LidarrEditCommand::Artist {
artist_id: 1,
enable_monitoring: false,
disable_monitoring: false,
monitor_new_items: None,
quality_profile_id: None,
metadata_profile_id: None,
root_folder_path: None,
tag: Some(vec![1, 2]),
clear_tags: false,
};
let result = Cli::try_parse_from([
"managarr",
"lidarr",
"edit",
"artist",
"--artist-id",
"1",
"--tag",
"1",
"--tag",
"2",
]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args);
}
#[test]
fn test_edit_artist_all_arguments_defined() {
let expected_args = LidarrEditCommand::Artist {
artist_id: 1,
enable_monitoring: true,
disable_monitoring: false,
monitor_new_items: Some(NewItemMonitorType::New),
quality_profile_id: Some(1),
metadata_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()),
tag: Some(vec![1, 2]),
clear_tags: false,
};
let result = Cli::try_parse_from([
"managarr",
"lidarr",
"edit",
"artist",
"--artist-id",
"1",
"--enable-monitoring",
"--monitor-new-items",
"new",
"--quality-profile-id",
"1",
"--metadata-profile-id",
"1",
"--root-folder-path",
"/nfs/test",
"--tag",
"1",
"--tag",
"2",
]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args);
}
#[test]
fn test_edit_indexer_requires_arguments() {
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "edit", "indexer"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_edit_indexer_with_indexer_id_still_requires_arguments() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"edit",
"indexer",
"--indexer-id",
"1",
]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_edit_indexer_rss_flags_conflict() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"edit",
"indexer",
"--indexer-id",
"1",
"--enable-rss",
"--disable-rss",
]);
assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
}
#[test]
fn test_edit_indexer_automatic_search_flags_conflict() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"edit",
"indexer",
"--indexer-id",
"1",
"--enable-automatic-search",
"--disable-automatic-search",
]);
assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
}
#[test]
fn test_edit_indexer_interactive_search_flags_conflict() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"edit",
"indexer",
"--indexer-id",
"1",
"--enable-interactive-search",
"--disable-interactive-search",
]);
assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
}
#[test]
fn test_edit_indexer_tag_flags_conflict() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"edit",
"indexer",
"--indexer-id",
"1",
"--tag",
"1",
"--clear-tags",
]);
assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
}
#[rstest]
fn test_edit_indexer_assert_argument_flags_require_args(
#[values("--name", "--url", "--api-key", "--seed-ratio", "--tag", "--priority")] flag: &str,
) {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"edit",
"indexer",
"--indexer-id",
"1",
flag,
]);
assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
}
#[test]
fn test_edit_indexer_only_requires_at_least_one_argument_plus_indexer_id() {
let expected_args = LidarrEditCommand::Indexer {
indexer_id: 1,
name: Some("Test".to_owned()),
enable_rss: false,
disable_rss: false,
enable_automatic_search: false,
disable_automatic_search: false,
enable_interactive_search: false,
disable_interactive_search: false,
url: None,
api_key: None,
seed_ratio: None,
tag: None,
priority: None,
clear_tags: false,
};
let result = Cli::try_parse_from([
"managarr",
"lidarr",
"edit",
"indexer",
"--indexer-id",
"1",
"--name",
"Test",
]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args);
}
#[test]
fn test_edit_indexer_tag_argument_is_repeatable() {
let expected_args = LidarrEditCommand::Indexer {
indexer_id: 1,
name: None,
enable_rss: false,
disable_rss: false,
enable_automatic_search: false,
disable_automatic_search: false,
enable_interactive_search: false,
disable_interactive_search: false,
url: None,
api_key: None,
seed_ratio: None,
tag: Some(vec![1, 2]),
priority: None,
clear_tags: false,
};
let result = Cli::try_parse_from([
"managarr",
"lidarr",
"edit",
"indexer",
"--indexer-id",
"1",
"--tag",
"1",
"--tag",
"2",
]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args);
}
#[test]
fn test_edit_indexer_all_arguments_defined() {
let expected_args = LidarrEditCommand::Indexer {
indexer_id: 1,
name: Some("Test".to_owned()),
enable_rss: true,
disable_rss: false,
enable_automatic_search: true,
disable_automatic_search: false,
enable_interactive_search: true,
disable_interactive_search: false,
url: Some("http://test.com".to_owned()),
api_key: Some("testKey".to_owned()),
seed_ratio: Some("1.2".to_owned()),
tag: Some(vec![1, 2]),
priority: Some(25),
clear_tags: false,
};
let result = Cli::try_parse_from([
"managarr",
"lidarr",
"edit",
"indexer",
"--indexer-id",
"1",
"--name",
"Test",
"--enable-rss",
"--enable-automatic-search",
"--enable-interactive-search",
"--url",
"http://test.com",
"--api-key",
"testKey",
"--seed-ratio",
"1.2",
"--tag",
"1",
"--tag",
"2",
"--priority",
"25",
]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args);
}
}
mod handler {
use std::sync::Arc;
use mockall::predicate::eq;
use serde_json::json;
use tokio::sync::Mutex;
use crate::models::servarr_models::{EditIndexerParams, IndexerSettings};
use crate::{
app::App,
cli::{
CliCommandHandler,
lidarr::edit_command_handler::{LidarrEditCommand, LidarrEditCommandHandler},
},
models::{
Serdeable,
lidarr_models::{EditArtistParams, LidarrSerdeable, NewItemMonitorType},
},
network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent},
};
#[tokio::test]
async fn test_handle_edit_all_indexer_settings_command() {
let expected_edit_all_indexer_settings = IndexerSettings {
id: 1,
maximum_size: 1,
minimum_age: 1,
retention: 1,
rss_sync_interval: 1,
};
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::GetAllIndexerSettings.into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::IndexerSettings(
IndexerSettings {
id: 1,
maximum_size: 2,
minimum_age: 2,
retention: 2,
rss_sync_interval: 2,
},
)))
});
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let edit_all_indexer_settings_command = LidarrEditCommand::AllIndexerSettings {
maximum_size: Some(1),
minimum_age: Some(1),
retention: Some(1),
rss_sync_interval: Some(1),
};
let result = LidarrEditCommandHandler::with(
&app_arc,
edit_all_indexer_settings_command,
&mut mock_network,
)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_edit_artist_command() {
let expected_edit_artist_params = EditArtistParams {
artist_id: 1,
monitored: Some(true),
monitor_new_items: Some(NewItemMonitorType::New),
quality_profile_id: Some(1),
metadata_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()),
tags: Some(vec![1, 2]),
tag_input_string: None,
clear_tags: false,
};
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::EditArtist(expected_edit_artist_params).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let edit_artist_command = LidarrEditCommand::Artist {
artist_id: 1,
enable_monitoring: true,
disable_monitoring: false,
monitor_new_items: Some(NewItemMonitorType::New),
quality_profile_id: Some(1),
metadata_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()),
tag: Some(vec![1, 2]),
clear_tags: false,
};
let result = LidarrEditCommandHandler::with(&app_arc, edit_artist_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_edit_artist_command_handles_disable_monitoring_flag_properly() {
let expected_edit_artist_params = EditArtistParams {
artist_id: 1,
monitored: Some(false),
monitor_new_items: Some(NewItemMonitorType::None),
quality_profile_id: Some(1),
metadata_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()),
tags: Some(vec![1, 2]),
tag_input_string: None,
clear_tags: false,
};
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::EditArtist(expected_edit_artist_params).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let edit_artist_command = LidarrEditCommand::Artist {
artist_id: 1,
enable_monitoring: false,
disable_monitoring: true,
monitor_new_items: Some(NewItemMonitorType::None),
quality_profile_id: Some(1),
metadata_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()),
tag: Some(vec![1, 2]),
clear_tags: false,
};
let result = LidarrEditCommandHandler::with(&app_arc, edit_artist_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_edit_artist_command_no_monitoring_boolean_flags_returns_none_value() {
let expected_edit_artist_params = EditArtistParams {
artist_id: 1,
monitored: None,
monitor_new_items: Some(NewItemMonitorType::All),
quality_profile_id: Some(1),
metadata_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()),
tags: Some(vec![1, 2]),
tag_input_string: None,
clear_tags: false,
};
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::EditArtist(expected_edit_artist_params).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let edit_artist_command = LidarrEditCommand::Artist {
artist_id: 1,
enable_monitoring: false,
disable_monitoring: false,
monitor_new_items: Some(NewItemMonitorType::All),
quality_profile_id: Some(1),
metadata_profile_id: Some(1),
root_folder_path: Some("/nfs/test".to_owned()),
tag: Some(vec![1, 2]),
clear_tags: false,
};
let result = LidarrEditCommandHandler::with(&app_arc, edit_artist_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_edit_indexer_command() {
let expected_edit_indexer_params = EditIndexerParams {
indexer_id: 1,
name: Some("Test".to_owned()),
enable_rss: Some(true),
enable_automatic_search: Some(true),
enable_interactive_search: Some(true),
url: Some("http://test.com".to_owned()),
api_key: Some("testKey".to_owned()),
seed_ratio: Some("1.2".to_owned()),
tags: Some(vec![1, 2]),
tag_input_string: None,
priority: Some(25),
clear_tags: false,
};
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::EditIndexer(expected_edit_indexer_params).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let edit_indexer_command = LidarrEditCommand::Indexer {
indexer_id: 1,
name: Some("Test".to_owned()),
enable_rss: true,
disable_rss: false,
enable_automatic_search: true,
disable_automatic_search: false,
enable_interactive_search: true,
disable_interactive_search: false,
url: Some("http://test.com".to_owned()),
api_key: Some("testKey".to_owned()),
seed_ratio: Some("1.2".to_owned()),
tag: Some(vec![1, 2]),
priority: Some(25),
clear_tags: false,
};
let result =
LidarrEditCommandHandler::with(&app_arc, edit_indexer_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
}
}
+122
View File
@@ -0,0 +1,122 @@
use std::sync::Arc;
use anyhow::Result;
use clap::Subcommand;
use tokio::sync::Mutex;
use crate::{
app::App,
cli::{CliCommandHandler, Command},
network::{NetworkTrait, lidarr_network::LidarrEvent},
};
use super::LidarrCommand;
#[cfg(test)]
#[path = "get_command_handler_tests.rs"]
mod get_command_handler_tests;
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
pub enum LidarrGetCommand {
#[command(about = "Get detailed information for the album with the given ID")]
AlbumDetails {
#[arg(
long,
help = "The Lidarr ID of the album whose details you wish to fetch",
required = true
)]
album_id: i64,
},
#[command(about = "Get the shared settings for all indexers")]
AllIndexerSettings,
#[command(about = "Get detailed information for the artist with the given ID")]
ArtistDetails {
#[arg(
long,
help = "The Lidarr ID of the artist whose details you wish to fetch",
required = true
)]
artist_id: i64,
},
#[command(about = "Fetch the host config for your Lidarr instance")]
HostConfig,
#[command(about = "Fetch the security config for your Lidarr instance")]
SecurityConfig,
#[command(about = "Get the system status")]
SystemStatus,
}
impl From<LidarrGetCommand> for Command {
fn from(value: LidarrGetCommand) -> Self {
Command::Lidarr(LidarrCommand::Get(value))
}
}
pub(super) struct LidarrGetCommandHandler<'a, 'b> {
_app: &'a Arc<Mutex<App<'b>>>,
command: LidarrGetCommand,
network: &'a mut dyn NetworkTrait,
}
impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrGetCommand> for LidarrGetCommandHandler<'a, 'b> {
fn with(
_app: &'a Arc<Mutex<App<'b>>>,
command: LidarrGetCommand,
network: &'a mut dyn NetworkTrait,
) -> Self {
LidarrGetCommandHandler {
_app,
command,
network,
}
}
async fn handle(self) -> Result<String> {
let result = match self.command {
LidarrGetCommand::AlbumDetails { album_id } => {
let resp = self
.network
.handle_network_event(LidarrEvent::GetAlbumDetails(album_id).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrGetCommand::AllIndexerSettings => {
let resp = self
.network
.handle_network_event(LidarrEvent::GetAllIndexerSettings.into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrGetCommand::ArtistDetails { artist_id } => {
let resp = self
.network
.handle_network_event(LidarrEvent::GetArtistDetails(artist_id).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrGetCommand::HostConfig => {
let resp = self
.network
.handle_network_event(LidarrEvent::GetHostConfig.into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrGetCommand::SecurityConfig => {
let resp = self
.network
.handle_network_event(LidarrEvent::GetSecurityConfig.into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrGetCommand::SystemStatus => {
let resp = self
.network
.handle_network_event(LidarrEvent::GetStatus.into())
.await?;
serde_json::to_string_pretty(&resp)?
}
};
Ok(result)
}
}
+277
View File
@@ -0,0 +1,277 @@
#[cfg(test)]
mod tests {
use crate::Cli;
use crate::cli::{
Command,
lidarr::{LidarrCommand, get_command_handler::LidarrGetCommand},
};
use clap::CommandFactory;
use pretty_assertions::assert_eq;
#[test]
fn test_lidarr_get_command_from() {
let command = LidarrGetCommand::SystemStatus;
let result = Command::from(command.clone());
assert_eq!(result, Command::Lidarr(LidarrCommand::Get(command)));
}
mod cli {
use clap::error::ErrorKind;
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn test_album_details_requires_album_id() {
let result =
Cli::command().try_get_matches_from(["managarr", "lidarr", "get", "album-details"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_album_details_requirements_satisfied() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"get",
"album-details",
"--album-id",
"1",
]);
assert_ok!(&result);
}
#[test]
fn test_all_indexer_settings_has_no_arg_requirements() {
let result =
Cli::command().try_get_matches_from(["managarr", "lidarr", "get", "all-indexer-settings"]);
assert_ok!(&result);
}
#[test]
fn test_artist_details_requires_artist_id() {
let result =
Cli::command().try_get_matches_from(["managarr", "lidarr", "get", "artist-details"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_artist_details_requirements_satisfied() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"get",
"artist-details",
"--artist-id",
"1",
]);
assert_ok!(&result);
}
#[test]
fn test_host_config_has_no_arg_requirements() {
let result =
Cli::command().try_get_matches_from(["managarr", "lidarr", "get", "host-config"]);
assert_ok!(&result);
}
#[test]
fn test_security_config_has_no_arg_requirements() {
let result =
Cli::command().try_get_matches_from(["managarr", "lidarr", "get", "security-config"]);
assert_ok!(&result);
}
#[test]
fn test_system_status_has_no_arg_requirements() {
let result =
Cli::command().try_get_matches_from(["managarr", "lidarr", "get", "system-status"]);
assert_ok!(&result);
}
}
mod handler {
use std::sync::Arc;
use mockall::predicate::eq;
use serde_json::json;
use tokio::sync::Mutex;
use crate::{
app::App,
cli::{
CliCommandHandler,
lidarr::get_command_handler::{LidarrGetCommand, LidarrGetCommandHandler},
},
models::{Serdeable, lidarr_models::LidarrSerdeable},
network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent},
};
#[tokio::test]
async fn test_handle_get_album_details_command() {
let expected_album_id = 1;
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::GetAlbumDetails(expected_album_id).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let get_album_details_command = LidarrGetCommand::AlbumDetails { album_id: 1 };
let result =
LidarrGetCommandHandler::with(&app_arc, get_album_details_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_get_all_indexer_settings_command() {
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::GetAllIndexerSettings.into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let get_all_indexer_settings_command = LidarrGetCommand::AllIndexerSettings;
let result = LidarrGetCommandHandler::with(
&app_arc,
get_all_indexer_settings_command,
&mut mock_network,
)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_get_artist_details_command() {
let expected_artist_id = 1;
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::GetArtistDetails(expected_artist_id).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let get_artist_details_command = LidarrGetCommand::ArtistDetails { artist_id: 1 };
let result =
LidarrGetCommandHandler::with(&app_arc, get_artist_details_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_get_host_config_command() {
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(LidarrEvent::GetHostConfig.into()))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let get_host_config_command = LidarrGetCommand::HostConfig;
let result =
LidarrGetCommandHandler::with(&app_arc, get_host_config_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_get_security_config_command() {
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(LidarrEvent::GetSecurityConfig.into()))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let get_security_config_command = LidarrGetCommand::SecurityConfig;
let result =
LidarrGetCommandHandler::with(&app_arc, get_security_config_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_get_system_status_command() {
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(LidarrEvent::GetStatus.into()))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let get_system_status_command = LidarrGetCommand::SystemStatus;
let result =
LidarrGetCommandHandler::with(&app_arc, get_system_status_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
}
}
+631
View File
@@ -0,0 +1,631 @@
#[cfg(test)]
mod tests {
use crate::Cli;
use crate::cli::{
Command,
lidarr::{LidarrCommand, list_command_handler::LidarrListCommand},
};
use clap::CommandFactory;
use pretty_assertions::assert_eq;
#[test]
fn test_lidarr_command_from() {
let command = LidarrCommand::List(LidarrListCommand::Artists);
let result = Command::from(command.clone());
assert_eq!(result, Command::Lidarr(command));
}
mod cli {
use super::*;
use clap::error::ErrorKind;
use pretty_assertions::assert_eq;
use rstest::rstest;
#[rstest]
fn test_commands_that_have_no_arg_requirements(
#[values("test-all-indexers")] subcommand: &str,
) {
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", subcommand]);
assert_ok!(&result);
}
#[test]
fn test_list_artists_has_no_arg_requirements() {
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "list", "artists"]);
assert_ok!(&result);
}
#[test]
fn test_lidarr_list_subcommand_requires_subcommand() {
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "list"]);
assert_err!(&result);
}
#[test]
fn test_lidarr_add_subcommand_requires_subcommand() {
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "add"]);
assert_err!(&result);
}
#[test]
fn test_lidarr_delete_subcommand_requires_subcommand() {
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "delete"]);
assert_err!(&result);
}
#[test]
fn test_toggle_artist_monitoring_requires_artist_id() {
let result =
Cli::command().try_get_matches_from(["managarr", "lidarr", "toggle-artist-monitoring"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_toggle_artist_monitoring_requirements_satisfied() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"toggle-artist-monitoring",
"--artist-id",
"1",
]);
assert_ok!(&result);
}
#[test]
fn test_toggle_album_monitoring_requires_album_id() {
let result =
Cli::command().try_get_matches_from(["managarr", "lidarr", "toggle-album-monitoring"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_toggle_album_monitoring_requirements_satisfied() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"toggle-album-monitoring",
"--album-id",
"1",
]);
assert_ok!(&result);
}
#[test]
fn test_search_new_artist_requires_query() {
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "search-new-artist"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_search_new_artist_requirements_satisfied() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"search-new-artist",
"--query",
"test query",
]);
assert_ok!(&result);
}
#[test]
fn test_start_task_requires_task_name() {
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "start-task"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_start_task_task_name_validation() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"start-task",
"--task-name",
"test",
]);
assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
}
#[test]
fn test_start_task_requirements_satisfied() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"start-task",
"--task-name",
"application-update-check",
]);
assert_ok!(&result);
}
#[test]
fn test_mark_history_item_as_failed_requires_history_item_id() {
let result =
Cli::command().try_get_matches_from(["managarr", "lidarr", "mark-history-item-as-failed"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_mark_history_item_as_failed_requirements_satisfied() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"mark-history-item-as-failed",
"--history-item-id",
"1",
]);
assert_ok!(&result);
}
#[test]
fn test_test_indexer_requires_indexer_id() {
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "test-indexer"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_test_indexer_requirements_satisfied() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"test-indexer",
"--indexer-id",
"1",
]);
assert_ok!(&result);
}
}
mod handler {
use std::sync::Arc;
use mockall::predicate::eq;
use serde_json::json;
use tokio::sync::Mutex;
use crate::cli::lidarr::add_command_handler::LidarrAddCommand;
use crate::cli::lidarr::edit_command_handler::LidarrEditCommand;
use crate::cli::lidarr::get_command_handler::LidarrGetCommand;
use crate::cli::lidarr::refresh_command_handler::LidarrRefreshCommand;
use crate::cli::lidarr::trigger_automatic_search_command_handler::LidarrTriggerAutomaticSearchCommand;
use crate::models::lidarr_models::LidarrTaskName;
use crate::models::servarr_models::IndexerSettings;
use crate::{
app::App,
cli::{
CliCommandHandler,
lidarr::{
LidarrCliHandler, LidarrCommand, delete_command_handler::LidarrDeleteCommand,
list_command_handler::LidarrListCommand,
},
},
models::{
Serdeable,
lidarr_models::{Artist, DeleteParams, LidarrSerdeable},
},
network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent},
};
#[tokio::test]
async fn test_lidarr_cli_handler_delegates_add_commands_to_the_add_command_handler() {
let expected_tag_name = "test".to_owned();
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::AddTag(expected_tag_name.clone()).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let add_tag_command = LidarrCommand::Add(LidarrAddCommand::Tag {
name: expected_tag_name,
});
let result = LidarrCliHandler::with(&app_arc, add_tag_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_lidarr_cli_handler_delegates_get_commands_to_the_get_command_handler() {
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(LidarrEvent::GetStatus.into()))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let get_system_status_command = LidarrCommand::Get(LidarrGetCommand::SystemStatus);
let result = LidarrCliHandler::with(&app_arc, get_system_status_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_lidarr_cli_handler_delegates_delete_commands_to_the_delete_command_handler() {
let expected_delete_artist_params = DeleteParams {
id: 1,
delete_files: true,
add_import_list_exclusion: true,
};
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::DeleteArtist(expected_delete_artist_params).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let delete_artist_command = LidarrCommand::Delete(LidarrDeleteCommand::Artist {
artist_id: 1,
delete_files_from_disk: true,
add_list_exclusion: true,
});
let result = LidarrCliHandler::with(&app_arc, delete_artist_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_lidarr_cli_handler_delegates_edit_commands_to_the_edit_command_handler() {
let expected_edit_all_indexer_settings = IndexerSettings {
id: 1,
maximum_size: 1,
minimum_age: 1,
retention: 1,
rss_sync_interval: 1,
};
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::GetAllIndexerSettings.into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::IndexerSettings(
IndexerSettings {
id: 1,
maximum_size: 2,
minimum_age: 2,
retention: 2,
rss_sync_interval: 2,
},
)))
});
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let edit_all_indexer_settings_command =
LidarrCommand::Edit(LidarrEditCommand::AllIndexerSettings {
maximum_size: Some(1),
minimum_age: Some(1),
retention: Some(1),
rss_sync_interval: Some(1),
});
let result = LidarrCliHandler::with(
&app_arc,
edit_all_indexer_settings_command,
&mut mock_network,
)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_lidarr_cli_handler_delegates_list_commands_to_the_list_command_handler() {
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(LidarrEvent::ListArtists.into()))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Artists(vec![
Artist::default(),
])))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let list_artists_command = LidarrCommand::List(LidarrListCommand::Artists);
let result = LidarrCliHandler::with(&app_arc, list_artists_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_lidarr_cli_handler_delegates_refresh_commands_to_the_refresh_command_handler() {
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(LidarrEvent::UpdateAllArtists.into()))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let refresh_series_command = LidarrCommand::Refresh(LidarrRefreshCommand::AllArtists);
let result = LidarrCliHandler::with(&app_arc, refresh_series_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_lidarr_cli_handler_delegates_trigger_automatic_search_commands_to_the_trigger_automatic_search_command_handler()
{
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::TriggerAutomaticArtistSearch(1).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let trigger_automatic_search_command =
LidarrCommand::TriggerAutomaticSearch(LidarrTriggerAutomaticSearchCommand::Artist {
artist_id: 1,
});
let result = LidarrCliHandler::with(
&app_arc,
trigger_automatic_search_command,
&mut mock_network,
)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_toggle_artist_monitoring_command() {
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::ToggleArtistMonitoring(1).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let toggle_artist_monitoring_command = LidarrCommand::ToggleArtistMonitoring { artist_id: 1 };
let result = LidarrCliHandler::with(
&app_arc,
toggle_artist_monitoring_command,
&mut mock_network,
)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_search_new_artist_command() {
let expected_query = "test artist".to_owned();
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::SearchNewArtist(expected_query.clone()).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let search_new_artist_command = LidarrCommand::SearchNewArtist {
query: expected_query,
};
let result = LidarrCliHandler::with(&app_arc, search_new_artist_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_start_task_command() {
let expected_task_name = LidarrTaskName::ApplicationUpdateCheck;
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::StartTask(expected_task_name).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let start_task_command = LidarrCommand::StartTask {
task_name: LidarrTaskName::ApplicationUpdateCheck,
};
let result = LidarrCliHandler::with(&app_arc, start_task_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_test_indexer_command() {
let expected_indexer_id = 1;
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::TestIndexer(expected_indexer_id).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let test_indexer_command = LidarrCommand::TestIndexer { indexer_id: 1 };
let result = LidarrCliHandler::with(&app_arc, test_indexer_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_test_all_indexers_command() {
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(LidarrEvent::TestAllIndexers.into()))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let test_all_indexers_command = LidarrCommand::TestAllIndexers;
let result = LidarrCliHandler::with(&app_arc, test_all_indexers_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_mark_history_item_as_failed_command() {
let expected_history_item_id = 1i64;
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::MarkHistoryItemAsFailed(expected_history_item_id).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let mark_history_item_as_failed_command = LidarrCommand::MarkHistoryItemAsFailed {
history_item_id: expected_history_item_id,
};
let result = LidarrCliHandler::with(
&app_arc,
mark_history_item_as_failed_command,
&mut mock_network,
)
.handle()
.await;
assert_ok!(&result);
}
}
}
+218
View File
@@ -0,0 +1,218 @@
use std::sync::Arc;
use anyhow::Result;
use clap::{Subcommand, arg};
use tokio::sync::Mutex;
use crate::{
app::App,
cli::{CliCommandHandler, Command},
network::{NetworkTrait, lidarr_network::LidarrEvent},
};
use super::LidarrCommand;
#[cfg(test)]
#[path = "list_command_handler_tests.rs"]
mod list_command_handler_tests;
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
pub enum LidarrListCommand {
#[command(about = "List all albums for the artist with the given ID")]
Albums {
#[arg(
long,
help = "The Lidarr ID of the artist whose albums you want to list",
required = true
)]
artist_id: i64,
},
#[command(about = "Fetch all history events for the artist with the given ID")]
ArtistHistory {
#[arg(
long,
help = "The Lidarr ID of the artist whose history you wish to fetch",
required = true
)]
artist_id: i64,
},
#[command(about = "List all artists in your Lidarr library")]
Artists,
#[command(about = "List all active downloads in Lidarr")]
Downloads {
#[arg(long, help = "How many downloads to fetch", default_value_t = 500)]
count: u64,
},
#[command(about = "Fetch all Lidarr history events")]
History {
#[arg(long, help = "How many history events to fetch", default_value_t = 500)]
events: u64,
},
#[command(about = "List all Lidarr indexers")]
Indexers,
#[command(about = "Fetch Lidarr logs")]
Logs {
#[arg(long, help = "How many log events to fetch", default_value_t = 500)]
events: u64,
#[arg(
long,
help = "Output the logs in the same format as they appear in the log files"
)]
output_in_log_format: bool,
},
#[command(about = "List all Lidarr metadata profiles")]
MetadataProfiles,
#[command(about = "List all Lidarr quality profiles")]
QualityProfiles,
#[command(about = "List all queued events")]
QueuedEvents,
#[command(about = "List all root folders in Lidarr")]
RootFolders,
#[command(about = "List all Lidarr tags")]
Tags,
#[command(about = "List all Lidarr tasks")]
Tasks,
#[command(about = "List all Lidarr updates")]
Updates,
}
impl From<LidarrListCommand> for Command {
fn from(value: LidarrListCommand) -> Self {
Command::Lidarr(LidarrCommand::List(value))
}
}
pub(super) struct LidarrListCommandHandler<'a, 'b> {
app: &'a Arc<Mutex<App<'b>>>,
command: LidarrListCommand,
network: &'a mut dyn NetworkTrait,
}
impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrListCommand> for LidarrListCommandHandler<'a, 'b> {
fn with(
app: &'a Arc<Mutex<App<'b>>>,
command: LidarrListCommand,
network: &'a mut dyn NetworkTrait,
) -> Self {
LidarrListCommandHandler {
app,
command,
network,
}
}
async fn handle(self) -> Result<String> {
let result = match self.command {
LidarrListCommand::Albums { artist_id } => {
let resp = self
.network
.handle_network_event(LidarrEvent::GetAlbums(artist_id).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrListCommand::ArtistHistory { artist_id } => {
let resp = self
.network
.handle_network_event(LidarrEvent::GetArtistHistory(artist_id).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrListCommand::Artists => {
let resp = self
.network
.handle_network_event(LidarrEvent::ListArtists.into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrListCommand::Downloads { count } => {
let resp = self
.network
.handle_network_event(LidarrEvent::GetDownloads(count).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrListCommand::History { events: items } => {
let resp = self
.network
.handle_network_event(LidarrEvent::GetHistory(items).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrListCommand::Indexers => {
let resp = self
.network
.handle_network_event(LidarrEvent::GetIndexers.into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrListCommand::Logs {
events,
output_in_log_format,
} => {
let logs = self
.network
.handle_network_event(LidarrEvent::GetLogs(events).into())
.await?;
if output_in_log_format {
let log_lines = &self.app.lock().await.data.sonarr_data.logs.items;
serde_json::to_string_pretty(log_lines)?
} else {
serde_json::to_string_pretty(&logs)?
}
}
LidarrListCommand::MetadataProfiles => {
let resp = self
.network
.handle_network_event(LidarrEvent::GetMetadataProfiles.into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrListCommand::QualityProfiles => {
let resp = self
.network
.handle_network_event(LidarrEvent::GetQualityProfiles.into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrListCommand::QueuedEvents => {
let resp = self
.network
.handle_network_event(LidarrEvent::GetQueuedEvents.into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrListCommand::RootFolders => {
let resp = self
.network
.handle_network_event(LidarrEvent::GetRootFolders.into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrListCommand::Tags => {
let resp = self
.network
.handle_network_event(LidarrEvent::GetTags.into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrListCommand::Tasks => {
let resp = self
.network
.handle_network_event(LidarrEvent::GetTasks.into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrListCommand::Updates => {
let resp = self
.network
.handle_network_event(LidarrEvent::GetUpdates.into())
.await?;
serde_json::to_string_pretty(&resp)?
}
};
Ok(result)
}
}
@@ -0,0 +1,357 @@
#[cfg(test)]
mod tests {
use crate::Cli;
use crate::cli::{
Command,
lidarr::{LidarrCommand, list_command_handler::LidarrListCommand},
};
use clap::CommandFactory;
use pretty_assertions::assert_eq;
#[test]
fn test_lidarr_list_command_from() {
let command = LidarrListCommand::Artists;
let result = Command::from(command.clone());
assert_eq!(result, Command::Lidarr(LidarrCommand::List(command)));
}
mod cli {
use super::*;
use clap::{Parser, error::ErrorKind};
use pretty_assertions::assert_eq;
use rstest::rstest;
#[rstest]
fn test_list_commands_have_no_arg_requirements(
#[values(
"artists",
"indexers",
"metadata-profiles",
"quality-profiles",
"queued-events",
"tags",
"tasks",
"updates",
"root-folders"
)]
subcommand: &str,
) {
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "list", subcommand]);
assert_ok!(&result);
}
#[test]
fn test_list_albums_requires_artist_id() {
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "list", "albums"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_list_albums_with_artist_id() {
let expected_args = LidarrListCommand::Albums { artist_id: 1 };
let result =
Cli::try_parse_from(["managarr", "lidarr", "list", "albums", "--artist-id", "1"]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::List(album_command))) = result.unwrap().command
else {
panic!("Unexpected command type");
};
assert_eq!(album_command, expected_args);
}
#[test]
fn test_list_artist_history_requires_artist_id() {
let result =
Cli::command().try_get_matches_from(["managarr", "lidarr", "list", "artist-history"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_list_artist_history_success() {
let expected_args = LidarrListCommand::ArtistHistory { artist_id: 1 };
let result = Cli::try_parse_from([
"managarr",
"lidarr",
"list",
"artist-history",
"--artist-id",
"1",
]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::List(artist_command))) = result.unwrap().command
else {
panic!("Unexpected command type");
};
assert_eq!(artist_command, expected_args);
}
#[test]
fn test_list_downloads_count_flag_requires_arguments() {
let result =
Cli::command().try_get_matches_from(["managarr", "lidarr", "list", "downloads", "--count"]);
assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
}
#[test]
fn test_list_downloads_default_values() {
let expected_args = LidarrListCommand::Downloads { count: 500 };
let result = Cli::try_parse_from(["managarr", "lidarr", "list", "downloads"]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::List(downloads_command))) = result.unwrap().command
else {
panic!("Unexpected command type");
};
assert_eq!(downloads_command, expected_args);
}
#[test]
fn test_list_history_events_flag_requires_arguments() {
let result =
Cli::command().try_get_matches_from(["managarr", "lidarr", "list", "history", "--events"]);
assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
}
#[test]
fn test_list_history_default_values() {
let expected_args = LidarrListCommand::History { events: 500 };
let result = Cli::try_parse_from(["managarr", "lidarr", "list", "history"]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::List(history_command))) = result.unwrap().command
else {
panic!("Unexpected command type");
};
assert_eq!(history_command, expected_args);
}
#[test]
fn test_list_logs_events_flag_requires_arguments() {
let result =
Cli::command().try_get_matches_from(["managarr", "lidarr", "list", "logs", "--events"]);
assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
}
#[test]
fn test_list_logs_default_values() {
let expected_args = LidarrListCommand::Logs {
events: 500,
output_in_log_format: false,
};
let result = Cli::try_parse_from(["managarr", "lidarr", "list", "logs"]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::List(logs_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(logs_command, expected_args);
}
}
mod handler {
use std::sync::Arc;
use mockall::predicate::eq;
use rstest::rstest;
use serde_json::json;
use tokio::sync::Mutex;
use crate::cli::CliCommandHandler;
use crate::cli::lidarr::list_command_handler::{LidarrListCommand, LidarrListCommandHandler};
use crate::models::Serdeable;
use crate::models::lidarr_models::LidarrSerdeable;
use crate::network::lidarr_network::LidarrEvent;
use crate::{
app::App,
network::{MockNetworkTrait, NetworkEvent},
};
#[rstest]
#[case(LidarrListCommand::Artists, LidarrEvent::ListArtists)]
#[case(LidarrListCommand::Indexers, LidarrEvent::GetIndexers)]
#[case(LidarrListCommand::MetadataProfiles, LidarrEvent::GetMetadataProfiles)]
#[case(LidarrListCommand::QualityProfiles, LidarrEvent::GetQualityProfiles)]
#[case(LidarrListCommand::QueuedEvents, LidarrEvent::GetQueuedEvents)]
#[case(LidarrListCommand::RootFolders, LidarrEvent::GetRootFolders)]
#[case(LidarrListCommand::Tags, LidarrEvent::GetTags)]
#[case(LidarrListCommand::Tasks, LidarrEvent::GetTasks)]
#[case(LidarrListCommand::Updates, LidarrEvent::GetUpdates)]
#[tokio::test]
async fn test_handle_list_command(
#[case] list_command: LidarrListCommand,
#[case] expected_lidarr_event: LidarrEvent,
) {
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(expected_lidarr_event.into()))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let result = LidarrListCommandHandler::with(&app_arc, list_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_list_albums_command() {
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(LidarrEvent::GetAlbums(1).into()))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let list_command = LidarrListCommand::Albums { artist_id: 1 };
let result = LidarrListCommandHandler::with(&app_arc, list_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_list_artist_history_command() {
let expected_artist_id = 1;
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::GetArtistHistory(expected_artist_id).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let list_artist_history_command = LidarrListCommand::ArtistHistory { artist_id: 1 };
let result =
LidarrListCommandHandler::with(&app_arc, list_artist_history_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_list_downloads_command() {
let expected_count = 1000;
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::GetDownloads(expected_count).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let list_downloads_command = LidarrListCommand::Downloads { count: 1000 };
let result =
LidarrListCommandHandler::with(&app_arc, list_downloads_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_list_history_command() {
let expected_events = 1000;
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::GetHistory(expected_events).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let list_history_command = LidarrListCommand::History { events: 1000 };
let result =
LidarrListCommandHandler::with(&app_arc, list_history_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_list_logs_command() {
let expected_events = 1000;
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::GetLogs(expected_events).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let list_logs_command = LidarrListCommand::Logs {
events: 1000,
output_in_log_format: false,
};
let result = LidarrListCommandHandler::with(&app_arc, list_logs_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
}
}
+252
View File
@@ -0,0 +1,252 @@
use std::sync::Arc;
use add_command_handler::{LidarrAddCommand, LidarrAddCommandHandler};
use anyhow::Result;
use clap::{Subcommand, arg};
use delete_command_handler::{LidarrDeleteCommand, LidarrDeleteCommandHandler};
use edit_command_handler::{LidarrEditCommand, LidarrEditCommandHandler};
use get_command_handler::{LidarrGetCommand, LidarrGetCommandHandler};
use list_command_handler::{LidarrListCommand, LidarrListCommandHandler};
use refresh_command_handler::{LidarrRefreshCommand, LidarrRefreshCommandHandler};
use serde_json::json;
use tokio::sync::Mutex;
use trigger_automatic_search_command_handler::{
LidarrTriggerAutomaticSearchCommand, LidarrTriggerAutomaticSearchCommandHandler,
};
use super::{CliCommandHandler, Command};
use crate::models::lidarr_models::LidarrTaskName;
use crate::network::lidarr_network::LidarrEvent;
use crate::{app::App, network::NetworkTrait};
mod add_command_handler;
mod delete_command_handler;
mod edit_command_handler;
mod get_command_handler;
mod list_command_handler;
mod refresh_command_handler;
mod trigger_automatic_search_command_handler;
#[cfg(test)]
#[path = "lidarr_command_tests.rs"]
mod lidarr_command_tests;
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
pub enum LidarrCommand {
#[command(
subcommand,
about = "Commands to add or create new resources within your Lidarr instance"
)]
Add(LidarrAddCommand),
#[command(
subcommand,
about = "Commands to delete resources from your Lidarr instance"
)]
Delete(LidarrDeleteCommand),
#[command(
subcommand,
about = "Commands to edit resources in your Lidarr instance"
)]
Edit(LidarrEditCommand),
#[command(
subcommand,
about = "Commands to fetch details of the resources in your Lidarr instance"
)]
Get(LidarrGetCommand),
#[command(
subcommand,
about = "Commands to list attributes from your Lidarr instance"
)]
List(LidarrListCommand),
#[command(
subcommand,
about = "Commands to refresh the data in your Lidarr instance"
)]
Refresh(LidarrRefreshCommand),
#[command(
subcommand,
about = "Commands to trigger automatic searches for releases of different resources in your Lidarr instance"
)]
TriggerAutomaticSearch(LidarrTriggerAutomaticSearchCommand),
#[command(about = "Mark the Lidarr history item with the given ID as 'failed'")]
MarkHistoryItemAsFailed {
#[arg(
long,
help = "The Lidarr ID of the history item you wish to mark as 'failed'",
required = true
)]
history_item_id: i64,
},
#[command(about = "Search for a new artist to add to Lidarr")]
SearchNewArtist {
#[arg(
long,
help = "The name of the artist you want to search for",
required = true
)]
query: String,
},
#[command(about = "Start the specified Lidarr task")]
StartTask {
#[arg(
long,
help = "The name of the task to trigger",
value_enum,
required = true
)]
task_name: LidarrTaskName,
},
#[command(
about = "Test the indexer with the given ID. Note that a successful test returns an empty JSON body; i.e. '{}'"
)]
TestIndexer {
#[arg(long, help = "The ID of the indexer to test", required = true)]
indexer_id: i64,
},
#[command(about = "Test all Lidarr indexers")]
TestAllIndexers,
#[command(
about = "Toggle monitoring for the specified album corresponding to the given album ID"
)]
ToggleAlbumMonitoring {
#[arg(
long,
help = "The Lidarr ID of the album to toggle monitoring on",
required = true
)]
album_id: i64,
},
#[command(
about = "Toggle monitoring for the specified artist corresponding to the given artist ID"
)]
ToggleArtistMonitoring {
#[arg(
long,
help = "The Lidarr ID of the artist to toggle monitoring on",
required = true
)]
artist_id: i64,
},
}
impl From<LidarrCommand> for Command {
fn from(lidarr_command: LidarrCommand) -> Command {
Command::Lidarr(lidarr_command)
}
}
pub(super) struct LidarrCliHandler<'a, 'b> {
app: &'a Arc<Mutex<App<'b>>>,
command: LidarrCommand,
network: &'a mut dyn NetworkTrait,
}
impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrCommand> for LidarrCliHandler<'a, 'b> {
fn with(
app: &'a Arc<Mutex<App<'b>>>,
command: LidarrCommand,
network: &'a mut dyn NetworkTrait,
) -> Self {
LidarrCliHandler {
app,
command,
network,
}
}
async fn handle(self) -> Result<String> {
let result = match self.command {
LidarrCommand::Add(add_command) => {
LidarrAddCommandHandler::with(self.app, add_command, self.network)
.handle()
.await?
}
LidarrCommand::Delete(delete_command) => {
LidarrDeleteCommandHandler::with(self.app, delete_command, self.network)
.handle()
.await?
}
LidarrCommand::Edit(edit_command) => {
LidarrEditCommandHandler::with(self.app, edit_command, self.network)
.handle()
.await?
}
LidarrCommand::Get(get_command) => {
LidarrGetCommandHandler::with(self.app, get_command, self.network)
.handle()
.await?
}
LidarrCommand::List(list_command) => {
LidarrListCommandHandler::with(self.app, list_command, self.network)
.handle()
.await?
}
LidarrCommand::Refresh(refresh_command) => {
LidarrRefreshCommandHandler::with(self.app, refresh_command, self.network)
.handle()
.await?
}
LidarrCommand::TriggerAutomaticSearch(trigger_automatic_search_command) => {
LidarrTriggerAutomaticSearchCommandHandler::with(
self.app,
trigger_automatic_search_command,
self.network,
)
.handle()
.await?
}
LidarrCommand::MarkHistoryItemAsFailed { history_item_id } => {
let _ = self
.network
.handle_network_event(LidarrEvent::MarkHistoryItemAsFailed(history_item_id).into())
.await?;
serde_json::to_string_pretty(&json!({"message": "Lidarr history item marked as 'failed'"}))?
}
LidarrCommand::SearchNewArtist { query } => {
let resp = self
.network
.handle_network_event(LidarrEvent::SearchNewArtist(query).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrCommand::StartTask { task_name } => {
let resp = self
.network
.handle_network_event(LidarrEvent::StartTask(task_name).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrCommand::TestIndexer { indexer_id } => {
let resp = self
.network
.handle_network_event(LidarrEvent::TestIndexer(indexer_id).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrCommand::TestAllIndexers => {
println!("Testing all Lidarr indexers. This may take a minute...");
let resp = self
.network
.handle_network_event(LidarrEvent::TestAllIndexers.into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrCommand::ToggleAlbumMonitoring { album_id } => {
let resp = self
.network
.handle_network_event(LidarrEvent::ToggleAlbumMonitoring(album_id).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrCommand::ToggleArtistMonitoring { artist_id } => {
let resp = self
.network
.handle_network_event(LidarrEvent::ToggleArtistMonitoring(artist_id).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
};
Ok(result)
}
}
+89
View File
@@ -0,0 +1,89 @@
use std::sync::Arc;
use clap::Subcommand;
use tokio::sync::Mutex;
use crate::{
app::App,
cli::{CliCommandHandler, Command},
network::{NetworkTrait, lidarr_network::LidarrEvent},
};
use super::LidarrCommand;
#[cfg(test)]
#[path = "refresh_command_handler_tests.rs"]
mod refresh_command_handler_tests;
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
pub enum LidarrRefreshCommand {
#[command(about = "Refresh all artist data for all artists in your Lidarr library")]
AllArtists,
#[command(about = "Refresh artist data and scan disk for the artist with the given ID")]
Artist {
#[arg(
long,
help = "The ID of the artist to refresh information on and to scan the disk for",
required = true
)]
artist_id: i64,
},
#[command(about = "Refresh all downloads in Lidarr")]
Downloads,
}
impl From<LidarrRefreshCommand> for Command {
fn from(value: LidarrRefreshCommand) -> Self {
Command::Lidarr(LidarrCommand::Refresh(value))
}
}
pub(super) struct LidarrRefreshCommandHandler<'a, 'b> {
_app: &'a Arc<Mutex<App<'b>>>,
command: LidarrRefreshCommand,
network: &'a mut dyn NetworkTrait,
}
impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrRefreshCommand>
for LidarrRefreshCommandHandler<'a, 'b>
{
fn with(
_app: &'a Arc<Mutex<App<'b>>>,
command: LidarrRefreshCommand,
network: &'a mut dyn NetworkTrait,
) -> Self {
LidarrRefreshCommandHandler {
_app,
command,
network,
}
}
async fn handle(self) -> anyhow::Result<String> {
let result = match self.command {
LidarrRefreshCommand::AllArtists => {
let resp = self
.network
.handle_network_event(LidarrEvent::UpdateAllArtists.into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrRefreshCommand::Artist { artist_id } => {
let resp = self
.network
.handle_network_event(LidarrEvent::UpdateAndScanArtist(artist_id).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
LidarrRefreshCommand::Downloads => {
let resp = self
.network
.handle_network_event(LidarrEvent::UpdateDownloads.into())
.await?;
serde_json::to_string_pretty(&resp)?
}
};
Ok(result)
}
}
@@ -0,0 +1,138 @@
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use crate::Cli;
use crate::cli::{
Command,
lidarr::{LidarrCommand, refresh_command_handler::LidarrRefreshCommand},
};
use clap::CommandFactory;
#[test]
fn test_lidarr_refresh_command_from() {
let command = LidarrRefreshCommand::AllArtists;
let result = Command::from(command.clone());
assert_eq!(result, Command::Lidarr(LidarrCommand::Refresh(command)));
}
mod cli {
use super::*;
use clap::{Parser, error::ErrorKind};
use pretty_assertions::assert_eq;
use rstest::rstest;
#[rstest]
fn test_refresh_commands_have_no_arg_requirements(
#[values("all-artists", "downloads")] subcommand: &str,
) {
let result =
Cli::command().try_get_matches_from(["managarr", "lidarr", "refresh", subcommand]);
assert_ok!(&result);
}
#[test]
fn test_refresh_artist_requires_artist_id() {
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "refresh", "artist"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_refresh_artist_with_artist_id() {
let expected_args = LidarrRefreshCommand::Artist { artist_id: 1 };
let result = Cli::try_parse_from([
"managarr",
"lidarr",
"refresh",
"artist",
"--artist-id",
"1",
]);
assert_ok!(&result);
let Some(Command::Lidarr(LidarrCommand::Refresh(refresh_command))) = result.unwrap().command
else {
panic!("Unexpected command type");
};
assert_eq!(refresh_command, expected_args);
}
}
mod handler {
use std::sync::Arc;
use mockall::predicate::eq;
use rstest::rstest;
use serde_json::json;
use tokio::sync::Mutex;
use crate::{app::App, cli::lidarr::refresh_command_handler::LidarrRefreshCommandHandler};
use crate::{
cli::{CliCommandHandler, lidarr::refresh_command_handler::LidarrRefreshCommand},
network::lidarr_network::LidarrEvent,
};
use crate::{
models::{Serdeable, lidarr_models::LidarrSerdeable},
network::{MockNetworkTrait, NetworkEvent},
};
#[rstest]
#[case(LidarrRefreshCommand::AllArtists, LidarrEvent::UpdateAllArtists)]
#[case(LidarrRefreshCommand::Downloads, LidarrEvent::UpdateDownloads)]
#[tokio::test]
async fn test_handle_refresh_command(
#[case] refresh_command: LidarrRefreshCommand,
#[case] expected_sonarr_event: LidarrEvent,
) {
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(expected_sonarr_event.into()))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let result = LidarrRefreshCommandHandler::with(&app_arc, refresh_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_refresh_artist_command() {
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::UpdateAndScanArtist(1).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let refresh_command = LidarrRefreshCommand::Artist { artist_id: 1 };
let result = LidarrRefreshCommandHandler::with(&app_arc, refresh_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
}
}
@@ -0,0 +1,72 @@
use std::sync::Arc;
use anyhow::Result;
use clap::Subcommand;
use tokio::sync::Mutex;
use crate::{
app::App,
cli::{CliCommandHandler, Command},
network::{NetworkTrait, lidarr_network::LidarrEvent},
};
use super::LidarrCommand;
#[cfg(test)]
#[path = "trigger_automatic_search_command_handler_tests.rs"]
mod trigger_automatic_search_command_handler_tests;
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
pub enum LidarrTriggerAutomaticSearchCommand {
#[command(about = "Trigger an automatic search for the artist with the specified ID")]
Artist {
#[arg(
long,
help = "The ID of the artist you want to trigger an automatic search for",
required = true
)]
artist_id: i64,
},
}
impl From<LidarrTriggerAutomaticSearchCommand> for Command {
fn from(value: LidarrTriggerAutomaticSearchCommand) -> Self {
Command::Lidarr(LidarrCommand::TriggerAutomaticSearch(value))
}
}
pub(super) struct LidarrTriggerAutomaticSearchCommandHandler<'a, 'b> {
_app: &'a Arc<Mutex<App<'b>>>,
command: LidarrTriggerAutomaticSearchCommand,
network: &'a mut dyn NetworkTrait,
}
impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrTriggerAutomaticSearchCommand>
for LidarrTriggerAutomaticSearchCommandHandler<'a, 'b>
{
fn with(
_app: &'a Arc<Mutex<App<'b>>>,
command: LidarrTriggerAutomaticSearchCommand,
network: &'a mut dyn NetworkTrait,
) -> Self {
LidarrTriggerAutomaticSearchCommandHandler {
_app,
command,
network,
}
}
async fn handle(self) -> Result<String> {
let result = match self.command {
LidarrTriggerAutomaticSearchCommand::Artist { artist_id } => {
let resp = self
.network
.handle_network_event(LidarrEvent::TriggerAutomaticArtistSearch(artist_id).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
};
Ok(result)
}
}
@@ -0,0 +1,107 @@
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use crate::Cli;
use crate::cli::{
Command,
lidarr::{
LidarrCommand, trigger_automatic_search_command_handler::LidarrTriggerAutomaticSearchCommand,
},
};
use clap::CommandFactory;
#[test]
fn test_lidarr_trigger_automatic_search_command_from() {
let command = LidarrTriggerAutomaticSearchCommand::Artist { artist_id: 1 };
let result = Command::from(command.clone());
assert_eq!(
result,
Command::Lidarr(LidarrCommand::TriggerAutomaticSearch(command))
);
}
mod cli {
use super::*;
use clap::error::ErrorKind;
use pretty_assertions::assert_eq;
#[test]
fn test_trigger_automatic_artist_search_requires_artist_id() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"trigger-automatic-search",
"artist",
]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_trigger_automatic_artist_search_with_artist_id() {
let result = Cli::command().try_get_matches_from([
"managarr",
"lidarr",
"trigger-automatic-search",
"artist",
"--artist-id",
"1",
]);
assert_ok!(&result);
}
}
mod handler {
use std::sync::Arc;
use mockall::predicate::eq;
use serde_json::json;
use tokio::sync::Mutex;
use crate::cli::lidarr::trigger_automatic_search_command_handler::{
LidarrTriggerAutomaticSearchCommand, LidarrTriggerAutomaticSearchCommandHandler,
};
use crate::{app::App, cli::CliCommandHandler};
use crate::{
models::{Serdeable, lidarr_models::LidarrSerdeable},
network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent},
};
#[tokio::test]
async fn test_handle_trigger_automatic_artist_search_command() {
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
LidarrEvent::TriggerAutomaticArtistSearch(1).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let trigger_automatic_search_command =
LidarrTriggerAutomaticSearchCommand::Artist { artist_id: 1 };
let result = LidarrTriggerAutomaticSearchCommandHandler::with(
&app_arc,
trigger_automatic_search_command,
&mut mock_network,
)
.handle()
.await;
assert_ok!(&result);
}
}
}
+11 -1
View File
@@ -1,14 +1,16 @@
use std::sync::Arc; use std::sync::Arc;
use anyhow::Result; use anyhow::Result;
use clap::Subcommand; use clap::{Subcommand, command};
use clap_complete::Shell; use clap_complete::Shell;
use lidarr::{LidarrCliHandler, LidarrCommand};
use radarr::{RadarrCliHandler, RadarrCommand}; use radarr::{RadarrCliHandler, RadarrCommand};
use sonarr::{SonarrCliHandler, SonarrCommand}; use sonarr::{SonarrCliHandler, SonarrCommand};
use tokio::sync::Mutex; use tokio::sync::Mutex;
use crate::{app::App, network::NetworkTrait}; use crate::{app::App, network::NetworkTrait};
pub mod lidarr;
pub mod radarr; pub mod radarr;
pub mod sonarr; pub mod sonarr;
@@ -24,6 +26,9 @@ pub enum Command {
#[command(subcommand, about = "Commands for manging your Sonarr instance")] #[command(subcommand, about = "Commands for manging your Sonarr instance")]
Sonarr(SonarrCommand), Sonarr(SonarrCommand),
#[command(subcommand, about = "Commands for manging your Lidarr instance")]
Lidarr(LidarrCommand),
#[command( #[command(
arg_required_else_help = true, arg_required_else_help = true,
about = "Generate shell completions for the Managarr CLI" about = "Generate shell completions for the Managarr CLI"
@@ -61,6 +66,11 @@ pub(crate) async fn handle_command(
.handle() .handle()
.await? .await?
} }
Command::Lidarr(lidarr_command) => {
LidarrCliHandler::with(app, lidarr_command, network)
.handle()
.await?
}
_ => String::new(), _ => String::new(),
}; };
+4 -4
View File
@@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use anyhow::Result; use anyhow::Result;
use clap::{ArgAction, Subcommand}; use clap::{ArgAction, Subcommand, arg, command};
use tokio::sync::Mutex; use tokio::sync::Mutex;
use super::RadarrCommand; use super::RadarrCommand;
@@ -10,7 +10,7 @@ use crate::{
app::App, app::App,
cli::{CliCommandHandler, Command}, cli::{CliCommandHandler, Command},
models::radarr_models::{AddMovieBody, AddMovieOptions, MinimumAvailability, MovieMonitor}, models::radarr_models::{AddMovieBody, AddMovieOptions, MinimumAvailability, MovieMonitor},
network::{radarr_network::RadarrEvent, NetworkTrait}, network::{NetworkTrait, radarr_network::RadarrEvent},
}; };
#[cfg(test)] #[cfg(test)]
@@ -122,12 +122,12 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrAddCommand> for RadarrAddCommandHan
title: String::new(), title: String::new(),
root_folder_path, root_folder_path,
quality_profile_id, quality_profile_id,
minimum_availability: minimum_availability.to_string(), minimum_availability,
monitored: !disable_monitoring, monitored: !disable_monitoring,
tags, tags,
tag_input_string: None, tag_input_string: None,
add_options: AddMovieOptions { add_options: AddMovieOptions {
monitor: monitor.to_string(), monitor,
search_for_movie: !no_search_for_movie, search_for_movie: !no_search_for_movie,
}, },
}; };
+41 -36
View File
@@ -1,14 +1,14 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use clap::{error::ErrorKind, CommandFactory, Parser}; use clap::{CommandFactory, Parser, error::ErrorKind};
use crate::{ use crate::{
Cli,
cli::{ cli::{
radarr::{add_command_handler::RadarrAddCommand, RadarrCommand},
Command, Command,
radarr::{RadarrCommand, add_command_handler::RadarrAddCommand},
}, },
models::radarr_models::{MinimumAvailability, MovieMonitor}, models::radarr_models::{MinimumAvailability, MovieMonitor},
Cli,
}; };
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
@@ -32,7 +32,7 @@ mod tests {
fn test_add_movie_requires_arguments() { fn test_add_movie_requires_arguments() {
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "add", "movie"]); let result = Cli::command().try_get_matches_from(["managarr", "radarr", "add", "movie"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -52,7 +52,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -72,7 +72,7 @@ mod tests {
"/test", "/test",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -92,7 +92,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -117,7 +117,7 @@ mod tests {
flag, flag,
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue); assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
} }
@@ -136,7 +136,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
@@ -156,7 +156,7 @@ mod tests {
"test", "test",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue); assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
} }
@@ -177,7 +177,7 @@ mod tests {
"test", "test",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue); assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
} }
@@ -207,11 +207,12 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command { let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command else {
panic!("Unexpected command type")
};
assert_eq!(add_command, expected_args); assert_eq!(add_command, expected_args);
} }
}
#[test] #[test]
fn test_add_movie_tags_is_repeatable() { fn test_add_movie_tags_is_repeatable() {
@@ -243,11 +244,12 @@ mod tests {
"2", "2",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command { let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command else {
panic!("Unexpected command type")
};
assert_eq!(add_command, expected_args); assert_eq!(add_command, expected_args);
} }
}
#[test] #[test]
fn test_add_movie_all_args_defined() { fn test_add_movie_all_args_defined() {
@@ -285,18 +287,19 @@ mod tests {
"--no-search-for-movie", "--no-search-for-movie",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command { let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command else {
panic!("Unexpected command type")
};
assert_eq!(add_command, expected_args); assert_eq!(add_command, expected_args);
} }
}
#[test] #[test]
fn test_add_root_folder_requires_arguments() { fn test_add_root_folder_requires_arguments() {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "radarr", "add", "root-folder"]); Cli::command().try_get_matches_from(["managarr", "radarr", "add", "root-folder"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -318,18 +321,19 @@ mod tests {
"/nfs/test", "/nfs/test",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command { let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command else {
panic!("Unexpected command type")
};
assert_eq!(add_command, expected_args); assert_eq!(add_command, expected_args);
} }
}
#[test] #[test]
fn test_add_tag_requires_arguments() { fn test_add_tag_requires_arguments() {
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "add", "tag"]); let result = Cli::command().try_get_matches_from(["managarr", "radarr", "add", "tag"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -344,25 +348,26 @@ mod tests {
let result = Cli::try_parse_from(["managarr", "radarr", "add", "tag", "--name", "test"]); let result = Cli::try_parse_from(["managarr", "radarr", "add", "tag", "--name", "test"]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command { let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command else {
panic!("Unexpected command type")
};
assert_eq!(add_command, expected_args); assert_eq!(add_command, expected_args);
} }
} }
}
mod handler { mod handler {
use std::sync::Arc; use std::sync::Arc;
use crate::{ use crate::{
app::App, app::App,
cli::{radarr::add_command_handler::RadarrAddCommandHandler, CliCommandHandler}, cli::{CliCommandHandler, radarr::add_command_handler::RadarrAddCommandHandler},
models::{ models::{
radarr_models::{AddMovieBody, AddMovieOptions, RadarrSerdeable},
Serdeable, Serdeable,
radarr_models::{AddMovieBody, AddMovieOptions, RadarrSerdeable},
}, },
network::{radarr_network::RadarrEvent, MockNetworkTrait, NetworkEvent}, network::{MockNetworkTrait, NetworkEvent, radarr_network::RadarrEvent},
}; };
use super::*; use super::*;
@@ -379,12 +384,12 @@ mod tests {
title: String::new(), title: String::new(),
root_folder_path: "/test".to_owned(), root_folder_path: "/test".to_owned(),
quality_profile_id: 1, quality_profile_id: 1,
minimum_availability: "released".to_owned(), minimum_availability: MinimumAvailability::Released,
monitored: false, monitored: false,
tags: vec![1, 2], tags: vec![1, 2],
tag_input_string: None, tag_input_string: None,
add_options: AddMovieOptions { add_options: AddMovieOptions {
monitor: "movieAndCollection".to_owned(), monitor: MovieMonitor::MovieAndCollection,
search_for_movie: false, search_for_movie: false,
}, },
}; };
@@ -416,7 +421,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -447,7 +452,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -474,7 +479,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
} }
} }
+1 -1
View File
@@ -8,7 +8,7 @@ use crate::{
app::App, app::App,
cli::{CliCommandHandler, Command}, cli::{CliCommandHandler, Command},
models::radarr_models::DeleteMovieParams, models::radarr_models::DeleteMovieParams,
network::{radarr_network::RadarrEvent, NetworkTrait}, network::{NetworkTrait, radarr_network::RadarrEvent},
}; };
use super::RadarrCommand; use super::RadarrCommand;
+55 -48
View File
@@ -1,13 +1,13 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{ use crate::{
cli::{
radarr::{delete_command_handler::RadarrDeleteCommand, RadarrCommand},
Command,
},
Cli, Cli,
cli::{
Command,
radarr::{RadarrCommand, delete_command_handler::RadarrDeleteCommand},
},
}; };
use clap::{error::ErrorKind, CommandFactory, Parser}; use clap::{CommandFactory, Parser, error::ErrorKind};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
#[test] #[test]
@@ -30,7 +30,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "blocklist-item"]); Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "blocklist-item"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -52,20 +52,21 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
{ else {
panic!("Unexpected command type");
};
assert_eq!(delete_command, expected_args); assert_eq!(delete_command, expected_args);
} }
}
#[test] #[test]
fn test_delete_download_requires_arguments() { fn test_delete_download_requires_arguments() {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "download"]); Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "download"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -85,19 +86,20 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
{ else {
panic!("Unexpected command type");
};
assert_eq!(delete_command, expected_args); assert_eq!(delete_command, expected_args);
} }
}
#[test] #[test]
fn test_delete_indexer_requires_arguments() { fn test_delete_indexer_requires_arguments() {
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "indexer"]); let result = Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "indexer"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -117,19 +119,20 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
{ else {
panic!("Unexpected command type");
};
assert_eq!(delete_command, expected_args); assert_eq!(delete_command, expected_args);
} }
}
#[test] #[test]
fn test_delete_movie_requires_arguments() { fn test_delete_movie_requires_arguments() {
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "movie"]); let result = Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "movie"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -147,13 +150,14 @@ mod tests {
let result = let result =
Cli::try_parse_from(["managarr", "radarr", "delete", "movie", "--movie-id", "1"]); Cli::try_parse_from(["managarr", "radarr", "delete", "movie", "--movie-id", "1"]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
{ else {
panic!("Unexpected command type");
};
assert_eq!(delete_command, expected_args); assert_eq!(delete_command, expected_args);
} }
}
#[test] #[test]
fn test_delete_movie_all_args_defined() { fn test_delete_movie_all_args_defined() {
@@ -174,20 +178,21 @@ mod tests {
"--add-list-exclusion", "--add-list-exclusion",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
{ else {
panic!("Unexpected command type");
};
assert_eq!(delete_command, expected_args); assert_eq!(delete_command, expected_args);
} }
}
#[test] #[test]
fn test_delete_root_folder_requires_arguments() { fn test_delete_root_folder_requires_arguments() {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "root-folder"]); Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "root-folder"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -207,19 +212,20 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
{ else {
panic!("Unexpected command type");
};
assert_eq!(delete_command, expected_args); assert_eq!(delete_command, expected_args);
} }
}
#[test] #[test]
fn test_delete_tag_requires_arguments() { fn test_delete_tag_requires_arguments() {
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "tag"]); let result = Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "tag"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -232,14 +238,15 @@ mod tests {
let result = Cli::try_parse_from(["managarr", "radarr", "delete", "tag", "--tag-id", "1"]); let result = Cli::try_parse_from(["managarr", "radarr", "delete", "tag", "--tag-id", "1"]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
{ else {
panic!("Unexpected command type");
};
assert_eq!(delete_command, expected_args); assert_eq!(delete_command, expected_args);
} }
} }
}
mod handler { mod handler {
use std::sync::Arc; use std::sync::Arc;
@@ -251,14 +258,14 @@ mod tests {
use crate::{ use crate::{
app::App, app::App,
cli::{ cli::{
radarr::delete_command_handler::{RadarrDeleteCommand, RadarrDeleteCommandHandler},
CliCommandHandler, CliCommandHandler,
radarr::delete_command_handler::{RadarrDeleteCommand, RadarrDeleteCommandHandler},
}, },
models::{ models::{
radarr_models::{DeleteMovieParams, RadarrSerdeable},
Serdeable, Serdeable,
radarr_models::{DeleteMovieParams, RadarrSerdeable},
}, },
network::{radarr_network::RadarrEvent, MockNetworkTrait, NetworkEvent}, network::{MockNetworkTrait, NetworkEvent, radarr_network::RadarrEvent},
}; };
#[tokio::test] #[tokio::test]
@@ -289,7 +296,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -315,7 +322,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -341,7 +348,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -375,7 +382,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -401,7 +408,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -427,7 +434,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
} }
} }
+3 -3
View File
@@ -6,15 +6,15 @@ use tokio::sync::Mutex;
use crate::{ use crate::{
app::App, app::App,
cli::{mutex_flags_or_default, mutex_flags_or_option, CliCommandHandler, Command}, cli::{CliCommandHandler, Command, mutex_flags_or_default, mutex_flags_or_option},
models::{ models::{
Serdeable,
radarr_models::{ radarr_models::{
EditCollectionParams, EditMovieParams, IndexerSettings, MinimumAvailability, RadarrSerdeable, EditCollectionParams, EditMovieParams, IndexerSettings, MinimumAvailability, RadarrSerdeable,
}, },
servarr_models::EditIndexerParams, servarr_models::EditIndexerParams,
Serdeable,
}, },
network::{radarr_network::RadarrEvent, NetworkTrait}, network::{NetworkTrait, radarr_network::RadarrEvent},
}; };
use super::RadarrCommand; use super::RadarrCommand;
+85 -75
View File
@@ -1,13 +1,13 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{ use crate::{
cli::{
radarr::{edit_command_handler::RadarrEditCommand, RadarrCommand},
Command,
},
Cli, Cli,
cli::{
Command,
radarr::{RadarrCommand, edit_command_handler::RadarrEditCommand},
},
}; };
use clap::{error::ErrorKind, CommandFactory, Parser}; use clap::{CommandFactory, Parser, error::ErrorKind};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
#[test] #[test]
@@ -42,7 +42,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "radarr", "edit", "all-indexer-settings"]); Cli::command().try_get_matches_from(["managarr", "radarr", "edit", "all-indexer-settings"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -60,7 +60,7 @@ mod tests {
"--disable-allow-hardcoded-subs", "--disable-allow-hardcoded-subs",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict); assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
} }
@@ -75,7 +75,7 @@ mod tests {
"--disable-prefer-indexer-flags", "--disable-prefer-indexer-flags",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict); assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
} }
@@ -99,7 +99,7 @@ mod tests {
flag, flag,
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue); assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
} }
@@ -126,12 +126,13 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command { let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args); assert_eq!(edit_command, expected_args);
} }
}
#[test] #[test]
fn test_edit_all_indexer_settings_all_arguments_defined() { fn test_edit_all_indexer_settings_all_arguments_defined() {
@@ -168,19 +169,20 @@ mod tests {
"test", "test",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command { let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args); assert_eq!(edit_command, expected_args);
} }
}
#[test] #[test]
fn test_edit_collection_requires_arguments() { fn test_edit_collection_requires_arguments() {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "radarr", "edit", "collection"]); Cli::command().try_get_matches_from(["managarr", "radarr", "edit", "collection"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -198,7 +200,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -218,7 +220,7 @@ mod tests {
"--disable-monitoring", "--disable-monitoring",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict); assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
} }
@@ -235,7 +237,7 @@ mod tests {
"--disable-search-on-add", "--disable-search-on-add",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict); assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
} }
@@ -252,7 +254,7 @@ mod tests {
"test", "test",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue); assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
} }
@@ -270,7 +272,7 @@ mod tests {
flag, flag,
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue); assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
} }
@@ -298,12 +300,13 @@ mod tests {
"/test", "/test",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command { let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args); assert_eq!(edit_command, expected_args);
} }
}
#[test] #[test]
fn test_edit_collection_all_arguments_defined() { fn test_edit_collection_all_arguments_defined() {
@@ -334,18 +337,19 @@ mod tests {
"--search-on-add", "--search-on-add",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command { let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args); assert_eq!(edit_command, expected_args);
} }
}
#[test] #[test]
fn test_edit_indexer_requires_arguments() { fn test_edit_indexer_requires_arguments() {
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "edit", "indexer"]); let result = Cli::command().try_get_matches_from(["managarr", "radarr", "edit", "indexer"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -363,7 +367,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -383,7 +387,7 @@ mod tests {
"--disable-rss", "--disable-rss",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict); assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
} }
@@ -400,7 +404,7 @@ mod tests {
"--disable-automatic-search", "--disable-automatic-search",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict); assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
} }
@@ -417,7 +421,7 @@ mod tests {
"--disable-interactive-search", "--disable-interactive-search",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict); assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
} }
@@ -435,7 +439,7 @@ mod tests {
"--clear-tags", "--clear-tags",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict); assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
} }
@@ -453,7 +457,7 @@ mod tests {
flag, flag,
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue); assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
} }
@@ -487,12 +491,13 @@ mod tests {
"Test", "Test",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command { let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args); assert_eq!(edit_command, expected_args);
} }
}
#[test] #[test]
fn test_edit_indexer_tag_argument_is_repeatable() { fn test_edit_indexer_tag_argument_is_repeatable() {
@@ -526,12 +531,13 @@ mod tests {
"2", "2",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command { let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args); assert_eq!(edit_command, expected_args);
} }
}
#[test] #[test]
fn test_edit_indexer_all_arguments_defined() { fn test_edit_indexer_all_arguments_defined() {
@@ -578,18 +584,19 @@ mod tests {
"25", "25",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command { let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args); assert_eq!(edit_command, expected_args);
} }
}
#[test] #[test]
fn test_edit_movie_requires_arguments() { fn test_edit_movie_requires_arguments() {
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "edit", "movie"]); let result = Cli::command().try_get_matches_from(["managarr", "radarr", "edit", "movie"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -607,7 +614,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -627,7 +634,7 @@ mod tests {
"--disable-monitoring", "--disable-monitoring",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict); assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
} }
@@ -645,7 +652,7 @@ mod tests {
"--clear-tags", "--clear-tags",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict); assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
} }
@@ -669,7 +676,7 @@ mod tests {
flag, flag,
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue); assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
} }
@@ -686,7 +693,7 @@ mod tests {
"test", "test",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue); assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
} }
@@ -714,12 +721,13 @@ mod tests {
"/nfs/test", "/nfs/test",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command { let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args); assert_eq!(edit_command, expected_args);
} }
}
#[test] #[test]
fn test_edit_movie_tag_argument_is_repeatable() { fn test_edit_movie_tag_argument_is_repeatable() {
@@ -747,12 +755,13 @@ mod tests {
"2", "2",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command { let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args); assert_eq!(edit_command, expected_args);
} }
}
#[test] #[test]
fn test_edit_movie_all_arguments_defined() { fn test_edit_movie_all_arguments_defined() {
@@ -787,13 +796,14 @@ mod tests {
"2", "2",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command { let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args); assert_eq!(edit_command, expected_args);
} }
} }
}
mod handler { mod handler {
use std::sync::Arc; use std::sync::Arc;
@@ -805,18 +815,18 @@ mod tests {
use crate::{ use crate::{
app::App, app::App,
cli::{ cli::{
radarr::edit_command_handler::{RadarrEditCommand, RadarrEditCommandHandler},
CliCommandHandler, CliCommandHandler,
radarr::edit_command_handler::{RadarrEditCommand, RadarrEditCommandHandler},
}, },
models::{ models::{
Serdeable,
radarr_models::{ radarr_models::{
EditCollectionParams, EditMovieParams, IndexerSettings, MinimumAvailability, EditCollectionParams, EditMovieParams, IndexerSettings, MinimumAvailability,
RadarrSerdeable, RadarrSerdeable,
}, },
servarr_models::EditIndexerParams, servarr_models::EditIndexerParams,
Serdeable,
}, },
network::{radarr_network::RadarrEvent, MockNetworkTrait, NetworkEvent}, network::{MockNetworkTrait, NetworkEvent, radarr_network::RadarrEvent},
}; };
#[tokio::test] #[tokio::test]
@@ -887,7 +897,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -958,12 +968,12 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
async fn test_handle_edit_all_indexer_settings_command_unprovided_values_default_to_previous_values( async fn test_handle_edit_all_indexer_settings_command_unprovided_values_default_to_previous_values()
) { {
let expected_edit_all_indexer_settings = IndexerSettings { let expected_edit_all_indexer_settings = IndexerSettings {
allow_hardcoded_subs: true, allow_hardcoded_subs: true,
availability_delay: 2, availability_delay: 2,
@@ -1030,7 +1040,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -1072,7 +1082,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -1114,7 +1124,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -1156,7 +1166,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -1210,7 +1220,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -1264,7 +1274,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -1318,7 +1328,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -1361,7 +1371,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -1404,7 +1414,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -1447,7 +1457,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
} }
} }
+2 -2
View File
@@ -1,13 +1,13 @@
use std::sync::Arc; use std::sync::Arc;
use anyhow::Result; use anyhow::Result;
use clap::Subcommand; use clap::{Subcommand, command};
use tokio::sync::Mutex; use tokio::sync::Mutex;
use crate::{ use crate::{
app::App, app::App,
cli::{CliCommandHandler, Command}, cli::{CliCommandHandler, Command},
network::{radarr_network::RadarrEvent, NetworkTrait}, network::{NetworkTrait, radarr_network::RadarrEvent},
}; };
use super::RadarrCommand; use super::RadarrCommand;
+21 -21
View File
@@ -1,12 +1,12 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use clap::error::ErrorKind;
use clap::CommandFactory; use clap::CommandFactory;
use clap::error::ErrorKind;
use crate::cli::radarr::get_command_handler::RadarrGetCommand;
use crate::cli::radarr::RadarrCommand;
use crate::cli::Command;
use crate::Cli; use crate::Cli;
use crate::cli::Command;
use crate::cli::radarr::RadarrCommand;
use crate::cli::radarr::get_command_handler::RadarrGetCommand;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
#[test] #[test]
@@ -27,7 +27,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "all-indexer-settings"]); Cli::command().try_get_matches_from(["managarr", "radarr", "get", "all-indexer-settings"]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
@@ -35,7 +35,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "host-config"]); Cli::command().try_get_matches_from(["managarr", "radarr", "get", "host-config"]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
@@ -43,7 +43,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "movie-details"]); Cli::command().try_get_matches_from(["managarr", "radarr", "get", "movie-details"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -61,7 +61,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
@@ -69,7 +69,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "movie-history"]); Cli::command().try_get_matches_from(["managarr", "radarr", "get", "movie-history"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -87,7 +87,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
@@ -95,7 +95,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "security-config"]); Cli::command().try_get_matches_from(["managarr", "radarr", "get", "security-config"]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
@@ -103,7 +103,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "system-status"]); Cli::command().try_get_matches_from(["managarr", "radarr", "get", "system-status"]);
assert!(result.is_ok()); assert_ok!(&result);
} }
} }
@@ -117,11 +117,11 @@ mod tests {
use crate::{ use crate::{
app::App, app::App,
cli::{ cli::{
radarr::get_command_handler::{RadarrGetCommand, RadarrGetCommandHandler},
CliCommandHandler, CliCommandHandler,
radarr::get_command_handler::{RadarrGetCommand, RadarrGetCommandHandler},
}, },
models::{radarr_models::RadarrSerdeable, Serdeable}, models::{Serdeable, radarr_models::RadarrSerdeable},
network::{radarr_network::RadarrEvent, MockNetworkTrait, NetworkEvent}, network::{MockNetworkTrait, NetworkEvent, radarr_network::RadarrEvent},
}; };
#[tokio::test] #[tokio::test]
@@ -149,7 +149,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -172,7 +172,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -198,7 +198,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -224,7 +224,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -247,7 +247,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -270,7 +270,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
} }
} }
+14 -2
View File
@@ -1,13 +1,13 @@
use std::sync::Arc; use std::sync::Arc;
use anyhow::Result; use anyhow::Result;
use clap::Subcommand; use clap::{Subcommand, command};
use tokio::sync::Mutex; use tokio::sync::Mutex;
use crate::{ use crate::{
app::App, app::App,
cli::{CliCommandHandler, Command}, cli::{CliCommandHandler, Command},
network::{radarr_network::RadarrEvent, NetworkTrait}, network::{NetworkTrait, radarr_network::RadarrEvent},
}; };
use super::RadarrCommand; use super::RadarrCommand;
@@ -29,6 +29,11 @@ pub enum RadarrListCommand {
}, },
#[command(about = "List disk space details for all provisioned root folders in Radarr")] #[command(about = "List disk space details for all provisioned root folders in Radarr")]
DiskSpace, DiskSpace,
#[command(about = "Fetch all Radarr history events")]
History {
#[arg(long, help = "How many history events to fetch", default_value_t = 500)]
events: u64,
},
#[command(about = "List all Radarr indexers")] #[command(about = "List all Radarr indexers")]
Indexers, Indexers,
#[command(about = "Fetch Radarr logs")] #[command(about = "Fetch Radarr logs")]
@@ -121,6 +126,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrListCommand> for RadarrListCommandH
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
RadarrListCommand::History { events: items } => {
let resp = self
.network
.handle_network_event(RadarrEvent::GetHistory(items).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
RadarrListCommand::Indexers => { RadarrListCommand::Indexers => {
let resp = self let resp = self
.network .network
+77 -22
View File
@@ -1,12 +1,12 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use clap::error::ErrorKind;
use clap::CommandFactory; use clap::CommandFactory;
use clap::error::ErrorKind;
use crate::cli::radarr::list_command_handler::RadarrListCommand;
use crate::cli::radarr::RadarrCommand;
use crate::cli::Command;
use crate::Cli; use crate::Cli;
use crate::cli::Command;
use crate::cli::radarr::RadarrCommand;
use crate::cli::radarr::list_command_handler::RadarrListCommand;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
#[test] #[test]
@@ -43,7 +43,7 @@ mod tests {
) { ) {
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "list", subcommand]); let result = Cli::command().try_get_matches_from(["managarr", "radarr", "list", subcommand]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
@@ -51,7 +51,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "radarr", "list", "movie-credits"]); Cli::command().try_get_matches_from(["managarr", "radarr", "list", "movie-credits"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -63,7 +63,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "radarr", "list", "downloads", "--count"]); Cli::command().try_get_matches_from(["managarr", "radarr", "list", "downloads", "--count"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue); assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
} }
@@ -72,7 +72,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "radarr", "list", "logs", "--events"]); Cli::command().try_get_matches_from(["managarr", "radarr", "list", "logs", "--events"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue); assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
} }
@@ -88,23 +88,50 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::List(credits_command))) = result.unwrap().command { let Some(Command::Radarr(RadarrCommand::List(credits_command))) = result.unwrap().command
else {
panic!("Unexpected command type");
};
assert_eq!(credits_command, expected_args); assert_eq!(credits_command, expected_args);
} }
}
#[test] #[test]
fn test_list_downloads_default_values() { fn test_list_downloads_default_values() {
let expected_args = RadarrListCommand::Downloads { count: 500 }; let expected_args = RadarrListCommand::Downloads { count: 500 };
let result = Cli::try_parse_from(["managarr", "radarr", "list", "downloads"]); let result = Cli::try_parse_from(["managarr", "radarr", "list", "downloads"]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::List(refresh_command))) = result.unwrap().command { let Some(Command::Radarr(RadarrCommand::List(refresh_command))) = result.unwrap().command
else {
panic!("Unexpected command type");
};
assert_eq!(refresh_command, expected_args); assert_eq!(refresh_command, expected_args);
} }
#[test]
fn test_list_history_events_flag_requires_arguments() {
let result =
Cli::command().try_get_matches_from(["managarr", "radarr", "list", "history", "--events"]);
assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
}
#[test]
fn test_list_history_default_values() {
let expected_args = RadarrListCommand::History { events: 500 };
let result = Cli::try_parse_from(["managarr", "radarr", "list", "history"]);
assert_ok!(&result);
let Some(Command::Radarr(RadarrCommand::List(history_command))) = result.unwrap().command
else {
panic!("Unexpected command type");
};
assert_eq!(history_command, expected_args);
} }
#[test] #[test]
@@ -115,13 +142,15 @@ mod tests {
}; };
let result = Cli::try_parse_from(["managarr", "radarr", "list", "logs"]); let result = Cli::try_parse_from(["managarr", "radarr", "list", "logs"]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::List(refresh_command))) = result.unwrap().command { let Some(Command::Radarr(RadarrCommand::List(refresh_command))) = result.unwrap().command
else {
panic!("Unexpected command type");
};
assert_eq!(refresh_command, expected_args); assert_eq!(refresh_command, expected_args);
} }
} }
}
mod handler { mod handler {
use std::sync::Arc; use std::sync::Arc;
@@ -135,8 +164,8 @@ mod tests {
use crate::{ use crate::{
app::App, app::App,
cli::radarr::list_command_handler::{RadarrListCommand, RadarrListCommandHandler}, cli::radarr::list_command_handler::{RadarrListCommand, RadarrListCommandHandler},
models::{radarr_models::RadarrSerdeable, Serdeable}, models::{Serdeable, radarr_models::RadarrSerdeable},
network::{radarr_network::RadarrEvent, MockNetworkTrait, NetworkEvent}, network::{MockNetworkTrait, NetworkEvent, radarr_network::RadarrEvent},
}; };
#[rstest] #[rstest]
@@ -172,7 +201,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -198,7 +227,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -224,7 +253,33 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_list_history_command() {
let expected_events = 1000;
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
RadarrEvent::GetHistory(expected_events).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Radarr(RadarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let list_history_command = RadarrListCommand::History { events: 1000 };
let result =
RadarrListCommandHandler::with(&app_arc, list_history_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -252,7 +307,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
} }
} }
+19 -1
View File
@@ -13,8 +13,8 @@ use crate::app::App;
use crate::cli::CliCommandHandler; use crate::cli::CliCommandHandler;
use crate::models::radarr_models::{RadarrReleaseDownloadBody, RadarrTaskName}; use crate::models::radarr_models::{RadarrReleaseDownloadBody, RadarrTaskName};
use crate::network::radarr_network::RadarrEvent;
use crate::network::NetworkTrait; use crate::network::NetworkTrait;
use crate::network::radarr_network::RadarrEvent;
use anyhow::Result; use anyhow::Result;
use super::Command; use super::Command;
@@ -64,6 +64,15 @@ pub enum RadarrCommand {
Refresh(RadarrRefreshCommand), Refresh(RadarrRefreshCommand),
#[command(about = "Clear the blocklist")] #[command(about = "Clear the blocklist")]
ClearBlocklist, ClearBlocklist,
#[command(about = "Mark the Radarr history item with the given ID as 'failed'")]
MarkHistoryItemAsFailed {
#[arg(
long,
help = "The Radarr ID of the history item you wish to mark as 'failed'",
required = true
)]
history_item_id: i64,
},
#[command(about = "Manually download the given release for the specified movie ID")] #[command(about = "Manually download the given release for the specified movie ID")]
DownloadRelease { DownloadRelease {
#[arg(long, help = "The GUID of the release to download", required = true)] #[arg(long, help = "The GUID of the release to download", required = true)]
@@ -208,6 +217,15 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrCommand> for RadarrCliHandler<'a, '
.await?; .await?;
serde_json::to_string_pretty(&resp)? serde_json::to_string_pretty(&resp)?
} }
RadarrCommand::MarkHistoryItemAsFailed { history_item_id } => {
let _ = self
.network
.handle_network_event(RadarrEvent::MarkHistoryItemAsFailed(history_item_id).into())
.await?;
serde_json::to_string_pretty(
&serde_json::json!({"message": "Radarr history item marked as 'failed'"}),
)?
}
RadarrCommand::DownloadRelease { RadarrCommand::DownloadRelease {
guid, guid,
indexer_id, indexer_id,
+99 -44
View File
@@ -1,11 +1,11 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use clap::error::ErrorKind;
use clap::CommandFactory; use clap::CommandFactory;
use clap::error::ErrorKind;
use crate::cli::radarr::RadarrCommand;
use crate::cli::Command;
use crate::Cli; use crate::Cli;
use crate::cli::Command;
use crate::cli::radarr::RadarrCommand;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
#[test] #[test]
@@ -28,7 +28,32 @@ mod tests {
) { ) {
let result = Cli::command().try_get_matches_from(["managarr", "radarr", subcommand]); let result = Cli::command().try_get_matches_from(["managarr", "radarr", subcommand]);
assert!(result.is_ok()); assert_ok!(&result);
}
#[test]
fn test_mark_history_item_as_failed_requires_history_item_id() {
let result =
Cli::command().try_get_matches_from(["managarr", "radarr", "mark-history-item-as-failed"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_mark_history_item_as_failed_requirements_satisfied() {
let result = Cli::command().try_get_matches_from([
"managarr",
"radarr",
"mark-history-item-as-failed",
"--history-item-id",
"1",
]);
assert_ok!(&result);
} }
#[test] #[test]
@@ -43,7 +68,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -62,7 +87,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -81,7 +106,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -102,14 +127,14 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
fn test_manual_search_requires_movie_id() { fn test_manual_search_requires_movie_id() {
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "manual-search"]); let result = Cli::command().try_get_matches_from(["managarr", "radarr", "manual-search"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -126,14 +151,14 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
fn test_search_new_movie_requires_query() { fn test_search_new_movie_requires_query() {
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "search-new-movie"]); let result = Cli::command().try_get_matches_from(["managarr", "radarr", "search-new-movie"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -150,14 +175,14 @@ mod tests {
"halo", "halo",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
fn test_start_task_requires_task_name() { fn test_start_task_requires_task_name() {
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "start-task"]); let result = Cli::command().try_get_matches_from(["managarr", "radarr", "start-task"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -174,7 +199,7 @@ mod tests {
"test", "test",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue); assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
} }
@@ -188,14 +213,14 @@ mod tests {
"application-check-update", "application-check-update",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
fn test_test_indexer_requires_indexer_id() { fn test_test_indexer_requires_indexer_id() {
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "test-indexer"]); let result = Cli::command().try_get_matches_from(["managarr", "radarr", "test-indexer"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -212,7 +237,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
@@ -220,7 +245,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "radarr", "toggle-movie-monitoring"]); Cli::command().try_get_matches_from(["managarr", "radarr", "toggle-movie-monitoring"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -237,7 +262,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
@@ -245,7 +270,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "radarr", "trigger-automatic-search"]); Cli::command().try_get_matches_from(["managarr", "radarr", "trigger-automatic-search"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -262,7 +287,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
} }
@@ -276,22 +301,22 @@ mod tests {
use crate::{ use crate::{
app::App, app::App,
cli::{ cli::{
radarr::{
add_command_handler::RadarrAddCommand, delete_command_handler::RadarrDeleteCommand,
edit_command_handler::RadarrEditCommand, get_command_handler::RadarrGetCommand,
list_command_handler::RadarrListCommand, refresh_command_handler::RadarrRefreshCommand,
RadarrCliHandler, RadarrCommand,
},
CliCommandHandler, CliCommandHandler,
radarr::{
RadarrCliHandler, RadarrCommand, add_command_handler::RadarrAddCommand,
delete_command_handler::RadarrDeleteCommand, edit_command_handler::RadarrEditCommand,
get_command_handler::RadarrGetCommand, list_command_handler::RadarrListCommand,
refresh_command_handler::RadarrRefreshCommand,
},
}, },
models::{ models::{
Serdeable,
radarr_models::{ radarr_models::{
BlocklistItem, BlocklistResponse, IndexerSettings, RadarrReleaseDownloadBody, BlocklistItem, BlocklistResponse, IndexerSettings, RadarrReleaseDownloadBody,
RadarrSerdeable, RadarrTaskName, RadarrSerdeable, RadarrTaskName,
}, },
Serdeable,
}, },
network::{radarr_network::RadarrEvent, MockNetworkTrait, NetworkEvent}, network::{MockNetworkTrait, NetworkEvent, radarr_network::RadarrEvent},
}; };
#[tokio::test] #[tokio::test]
@@ -324,7 +349,37 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
}
#[tokio::test]
async fn test_mark_history_item_as_failed_command() {
let expected_history_item_id = 1;
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
RadarrEvent::MarkHistoryItemAsFailed(expected_history_item_id).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Radarr(RadarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let mark_history_item_as_failed_command =
RadarrCommand::MarkHistoryItemAsFailed { history_item_id: 1 };
let result = RadarrCliHandler::with(
&app_arc,
mark_history_item_as_failed_command,
&mut mock_network,
)
.handle()
.await;
assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -357,7 +412,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -382,7 +437,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -409,7 +464,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -436,7 +491,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -461,7 +516,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -483,7 +538,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -509,7 +564,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -538,7 +593,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -565,7 +620,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -594,7 +649,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -666,7 +721,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -695,7 +750,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -721,7 +776,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -747,7 +802,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
} }
} }
+1 -1
View File
@@ -7,7 +7,7 @@ use tokio::sync::Mutex;
use crate::{ use crate::{
app::App, app::App,
cli::{CliCommandHandler, Command}, cli::{CliCommandHandler, Command},
network::{radarr_network::RadarrEvent, NetworkTrait}, network::{NetworkTrait, radarr_network::RadarrEvent},
}; };
use super::RadarrCommand; use super::RadarrCommand;
+15 -15
View File
@@ -1,12 +1,12 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use clap::error::ErrorKind;
use clap::CommandFactory; use clap::CommandFactory;
use clap::error::ErrorKind;
use crate::cli::radarr::refresh_command_handler::RadarrRefreshCommand;
use crate::cli::radarr::RadarrCommand;
use crate::cli::Command;
use crate::Cli; use crate::Cli;
use crate::cli::Command;
use crate::cli::radarr::RadarrCommand;
use crate::cli::radarr::refresh_command_handler::RadarrRefreshCommand;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
#[test] #[test]
@@ -31,14 +31,14 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "radarr", "refresh", subcommand]); Cli::command().try_get_matches_from(["managarr", "radarr", "refresh", subcommand]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
fn test_refresh_movie_requires_movie_id() { fn test_refresh_movie_requires_movie_id() {
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "refresh", "movie"]); let result = Cli::command().try_get_matches_from(["managarr", "radarr", "refresh", "movie"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -51,15 +51,15 @@ mod tests {
let result = let result =
Cli::try_parse_from(["managarr", "radarr", "refresh", "movie", "--movie-id", "1"]); Cli::try_parse_from(["managarr", "radarr", "refresh", "movie", "--movie-id", "1"]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Radarr(RadarrCommand::Refresh(refresh_command))) = let Some(Command::Radarr(RadarrCommand::Refresh(refresh_command))) = result.unwrap().command
result.unwrap().command else {
{ panic!("Unexpected command type");
};
assert_eq!(refresh_command, expected_args); assert_eq!(refresh_command, expected_args);
} }
} }
}
mod handler { mod handler {
use rstest::rstest; use rstest::rstest;
@@ -73,8 +73,8 @@ mod tests {
use crate::{ use crate::{
app::App, app::App,
cli::radarr::refresh_command_handler::{RadarrRefreshCommand, RadarrRefreshCommandHandler}, cli::radarr::refresh_command_handler::{RadarrRefreshCommand, RadarrRefreshCommandHandler},
models::{radarr_models::RadarrSerdeable, Serdeable}, models::{Serdeable, radarr_models::RadarrSerdeable},
network::{radarr_network::RadarrEvent, MockNetworkTrait, NetworkEvent}, network::{MockNetworkTrait, NetworkEvent, radarr_network::RadarrEvent},
}; };
#[rstest] #[rstest]
@@ -102,7 +102,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -128,7 +128,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
} }
} }
+3 -3
View File
@@ -10,7 +10,7 @@ use crate::{
app::App, app::App,
cli::{CliCommandHandler, Command}, cli::{CliCommandHandler, Command},
models::sonarr_models::{AddSeriesBody, AddSeriesOptions, SeriesMonitor, SeriesType}, models::sonarr_models::{AddSeriesBody, AddSeriesOptions, SeriesMonitor, SeriesType},
network::{sonarr_network::SonarrEvent, NetworkTrait}, network::{NetworkTrait, sonarr_network::SonarrEvent},
}; };
#[cfg(test)] #[cfg(test)]
@@ -137,12 +137,12 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrAddCommand> for SonarrAddCommandHan
root_folder_path, root_folder_path,
quality_profile_id, quality_profile_id,
language_profile_id, language_profile_id,
series_type: series_type.to_string(), series_type,
season_folder: !disable_season_folders, season_folder: !disable_season_folders,
tags, tags,
tag_input_string: None, tag_input_string: None,
add_options: AddSeriesOptions { add_options: AddSeriesOptions {
monitor: monitor.to_string(), monitor,
search_for_cutoff_unmet_episodes: !no_search_for_series, search_for_cutoff_unmet_episodes: !no_search_for_series,
search_for_missing_episodes: !no_search_for_series, search_for_missing_episodes: !no_search_for_series,
}, },
+45 -40
View File
@@ -1,14 +1,14 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use clap::{error::ErrorKind, CommandFactory, Parser}; use clap::{CommandFactory, Parser, error::ErrorKind};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use crate::{ use crate::{
cli::{
sonarr::{add_command_handler::SonarrAddCommand, SonarrCommand},
Command,
},
Cli, Cli,
cli::{
Command,
sonarr::{SonarrCommand, add_command_handler::SonarrAddCommand},
},
}; };
#[test] #[test]
@@ -34,7 +34,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "sonarr", "add", "root-folder"]); Cli::command().try_get_matches_from(["managarr", "sonarr", "add", "root-folder"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -56,18 +56,19 @@ mod tests {
"/nfs/test", "/nfs/test",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command { let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(add_command, expected_args); assert_eq!(add_command, expected_args);
} }
}
#[test] #[test]
fn test_add_series_requires_arguments() { fn test_add_series_requires_arguments() {
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "add", "series"]); let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "add", "series"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -91,7 +92,7 @@ mod tests {
"test", "test",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -115,7 +116,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -139,7 +140,7 @@ mod tests {
"test", "test",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -163,7 +164,7 @@ mod tests {
"test", "test",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -187,7 +188,7 @@ mod tests {
"test", "test",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -216,7 +217,7 @@ mod tests {
flag, flag,
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue); assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
} }
@@ -239,7 +240,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
@@ -263,7 +264,7 @@ mod tests {
"test", "test",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue); assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
} }
@@ -288,7 +289,7 @@ mod tests {
"test", "test",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue); assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
} }
@@ -325,11 +326,12 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command { let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(add_command, expected_args); assert_eq!(add_command, expected_args);
} }
}
#[test] #[test]
fn test_add_series_tags_is_repeatable() { fn test_add_series_tags_is_repeatable() {
@@ -368,11 +370,12 @@ mod tests {
"2", "2",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command { let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(add_command, expected_args); assert_eq!(add_command, expected_args);
} }
}
#[test] #[test]
fn test_add_series_all_args_defined() { fn test_add_series_all_args_defined() {
@@ -418,17 +421,18 @@ mod tests {
"--no-search-for-series", "--no-search-for-series",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command { let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(add_command, expected_args); assert_eq!(add_command, expected_args);
} }
}
#[test] #[test]
fn test_add_tag_requires_arguments() { fn test_add_tag_requires_arguments() {
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "add", "tag"]); let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "add", "tag"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -443,27 +447,28 @@ mod tests {
let result = Cli::try_parse_from(["managarr", "sonarr", "add", "tag", "--name", "test"]); let result = Cli::try_parse_from(["managarr", "sonarr", "add", "tag", "--name", "test"]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command { let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(add_command, expected_args); assert_eq!(add_command, expected_args);
} }
} }
}
mod handler { mod handler {
use std::sync::Arc; use std::sync::Arc;
use crate::{ use crate::{
app::App, app::App,
cli::{sonarr::add_command_handler::SonarrAddCommandHandler, CliCommandHandler}, cli::{CliCommandHandler, sonarr::add_command_handler::SonarrAddCommandHandler},
models::{ models::{
Serdeable,
sonarr_models::{ sonarr_models::{
AddSeriesBody, AddSeriesOptions, SeriesMonitor, SeriesType, SonarrSerdeable, AddSeriesBody, AddSeriesOptions, SeriesMonitor, SeriesType, SonarrSerdeable,
}, },
Serdeable,
}, },
network::{sonarr_network::SonarrEvent, MockNetworkTrait, NetworkEvent}, network::{MockNetworkTrait, NetworkEvent, sonarr_network::SonarrEvent},
}; };
use super::*; use super::*;
@@ -501,7 +506,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -512,13 +517,13 @@ mod tests {
root_folder_path: "/test".to_owned(), root_folder_path: "/test".to_owned(),
quality_profile_id: 1, quality_profile_id: 1,
language_profile_id: 1, language_profile_id: 1,
series_type: "anime".to_owned(), series_type: SeriesType::Anime,
monitored: false, monitored: false,
tags: vec![1, 2], tags: vec![1, 2],
tag_input_string: None, tag_input_string: None,
season_folder: false, season_folder: false,
add_options: AddSeriesOptions { add_options: AddSeriesOptions {
monitor: "future".to_owned(), monitor: SeriesMonitor::Future,
search_for_cutoff_unmet_episodes: false, search_for_cutoff_unmet_episodes: false,
search_for_missing_episodes: false, search_for_missing_episodes: false,
}, },
@@ -554,7 +559,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -581,7 +586,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
} }
} }
+1 -1
View File
@@ -8,7 +8,7 @@ use crate::{
app::App, app::App,
cli::{CliCommandHandler, Command}, cli::{CliCommandHandler, Command},
models::sonarr_models::DeleteSeriesParams, models::sonarr_models::DeleteSeriesParams,
network::{sonarr_network::SonarrEvent, NetworkTrait}, network::{NetworkTrait, sonarr_network::SonarrEvent},
}; };
use super::SonarrCommand; use super::SonarrCommand;
+62 -54
View File
@@ -1,13 +1,13 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{ use crate::{
cli::{
sonarr::{delete_command_handler::SonarrDeleteCommand, SonarrCommand},
Command,
},
Cli, Cli,
cli::{
Command,
sonarr::{SonarrCommand, delete_command_handler::SonarrDeleteCommand},
},
}; };
use clap::{error::ErrorKind, CommandFactory, Parser}; use clap::{CommandFactory, Parser, error::ErrorKind};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
#[test] #[test]
@@ -30,7 +30,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "blocklist-item"]); Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "blocklist-item"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -52,20 +52,21 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
{ else {
panic!("Unexpected command type");
};
assert_eq!(delete_command, expected_args); assert_eq!(delete_command, expected_args);
} }
}
#[test] #[test]
fn test_delete_download_requires_arguments() { fn test_delete_download_requires_arguments() {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "download"]); Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "download"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -85,20 +86,21 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
{ else {
panic!("Unexpected command type");
};
assert_eq!(delete_command, expected_args); assert_eq!(delete_command, expected_args);
} }
}
#[test] #[test]
fn test_delete_episode_file_requires_arguments() { fn test_delete_episode_file_requires_arguments() {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "episode-file"]); Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "episode-file"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -118,19 +120,20 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
{ else {
panic!("Unexpected command type");
};
assert_eq!(delete_command, expected_args); assert_eq!(delete_command, expected_args);
} }
}
#[test] #[test]
fn test_delete_indexer_requires_arguments() { fn test_delete_indexer_requires_arguments() {
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "indexer"]); let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "indexer"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -150,20 +153,21 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
{ else {
panic!("Unexpected command type");
};
assert_eq!(delete_command, expected_args); assert_eq!(delete_command, expected_args);
} }
}
#[test] #[test]
fn test_delete_root_folder_requires_arguments() { fn test_delete_root_folder_requires_arguments() {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "root-folder"]); Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "root-folder"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -183,19 +187,20 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
{ else {
panic!("Unexpected command type");
};
assert_eq!(delete_command, expected_args); assert_eq!(delete_command, expected_args);
} }
}
#[test] #[test]
fn test_delete_series_requires_arguments() { fn test_delete_series_requires_arguments() {
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "series"]); let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "series"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -213,13 +218,14 @@ mod tests {
let result = let result =
Cli::try_parse_from(["managarr", "sonarr", "delete", "series", "--series-id", "1"]); Cli::try_parse_from(["managarr", "sonarr", "delete", "series", "--series-id", "1"]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
{ else {
panic!("Unexpected command type");
};
assert_eq!(delete_command, expected_args); assert_eq!(delete_command, expected_args);
} }
}
#[test] #[test]
fn test_delete_series_all_args_defined() { fn test_delete_series_all_args_defined() {
@@ -240,19 +246,20 @@ mod tests {
"--add-list-exclusion", "--add-list-exclusion",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
{ else {
panic!("Unexpected command type");
};
assert_eq!(delete_command, expected_args); assert_eq!(delete_command, expected_args);
} }
}
#[test] #[test]
fn test_delete_tag_requires_arguments() { fn test_delete_tag_requires_arguments() {
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "tag"]); let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "tag"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -265,14 +272,15 @@ mod tests {
let result = Cli::try_parse_from(["managarr", "sonarr", "delete", "tag", "--tag-id", "1"]); let result = Cli::try_parse_from(["managarr", "sonarr", "delete", "tag", "--tag-id", "1"]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
{ else {
panic!("Unexpected command type");
};
assert_eq!(delete_command, expected_args); assert_eq!(delete_command, expected_args);
} }
} }
}
mod handler { mod handler {
use std::sync::Arc; use std::sync::Arc;
@@ -284,14 +292,14 @@ mod tests {
use crate::{ use crate::{
app::App, app::App,
cli::{ cli::{
sonarr::delete_command_handler::{SonarrDeleteCommand, SonarrDeleteCommandHandler},
CliCommandHandler, CliCommandHandler,
sonarr::delete_command_handler::{SonarrDeleteCommand, SonarrDeleteCommandHandler},
}, },
models::{ models::{
sonarr_models::{DeleteSeriesParams, SonarrSerdeable},
Serdeable, Serdeable,
sonarr_models::{DeleteSeriesParams, SonarrSerdeable},
}, },
network::{sonarr_network::SonarrEvent, MockNetworkTrait, NetworkEvent}, network::{MockNetworkTrait, NetworkEvent, sonarr_network::SonarrEvent},
}; };
#[tokio::test] #[tokio::test]
@@ -322,7 +330,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -348,7 +356,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -374,7 +382,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -400,7 +408,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -426,7 +434,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -460,7 +468,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -486,7 +494,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
} }
} }
+1 -1
View File
@@ -8,7 +8,7 @@ use crate::{
app::App, app::App,
cli::{CliCommandHandler, Command}, cli::{CliCommandHandler, Command},
models::sonarr_models::SonarrReleaseDownloadBody, models::sonarr_models::SonarrReleaseDownloadBody,
network::{sonarr_network::SonarrEvent, NetworkTrait}, network::{NetworkTrait, sonarr_network::SonarrEvent},
}; };
use super::SonarrCommand; use super::SonarrCommand;
@@ -1,11 +1,11 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{ use crate::{
cli::{
sonarr::{download_command_handler::SonarrDownloadCommand, SonarrCommand},
Command,
},
Cli, Cli,
cli::{
Command,
sonarr::{SonarrCommand, download_command_handler::SonarrDownloadCommand},
},
}; };
use clap::CommandFactory; use clap::CommandFactory;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
@@ -41,7 +41,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -61,7 +61,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -81,7 +81,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -103,7 +103,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
@@ -121,7 +121,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -143,7 +143,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -165,7 +165,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -187,7 +187,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -211,7 +211,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
@@ -227,7 +227,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -247,7 +247,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -267,7 +267,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -289,7 +289,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
} }
@@ -303,14 +303,14 @@ mod tests {
use crate::{ use crate::{
app::App, app::App,
cli::{ cli::{
sonarr::download_command_handler::{SonarrDownloadCommand, SonarrDownloadCommandHandler},
CliCommandHandler, CliCommandHandler,
sonarr::download_command_handler::{SonarrDownloadCommand, SonarrDownloadCommandHandler},
}, },
models::{ models::{
sonarr_models::{SonarrReleaseDownloadBody, SonarrSerdeable},
Serdeable, Serdeable,
sonarr_models::{SonarrReleaseDownloadBody, SonarrSerdeable},
}, },
network::{sonarr_network::SonarrEvent, MockNetworkTrait, NetworkEvent}, network::{MockNetworkTrait, NetworkEvent, sonarr_network::SonarrEvent},
}; };
#[tokio::test] #[tokio::test]
@@ -345,7 +345,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -382,7 +382,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -417,7 +417,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
} }
} }
+4 -4
View File
@@ -6,13 +6,13 @@ use tokio::sync::Mutex;
use crate::{ use crate::{
app::App, app::App,
cli::{mutex_flags_or_option, CliCommandHandler, Command}, cli::{CliCommandHandler, Command, mutex_flags_or_option},
models::{ models::{
servarr_models::EditIndexerParams,
sonarr_models::{EditSeriesParams, IndexerSettings, SeriesType, SonarrSerdeable},
Serdeable, Serdeable,
servarr_models::{EditIndexerParams, IndexerSettings},
sonarr_models::{EditSeriesParams, SeriesType, SonarrSerdeable},
}, },
network::{sonarr_network::SonarrEvent, NetworkTrait}, network::{NetworkTrait, sonarr_network::SonarrEvent},
}; };
use super::SonarrCommand; use super::SonarrCommand;
+60 -52
View File
@@ -1,8 +1,8 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::cli::{ use crate::cli::{
sonarr::{edit_command_handler::SonarrEditCommand, SonarrCommand},
Command, Command,
sonarr::{SonarrCommand, edit_command_handler::SonarrEditCommand},
}; };
#[test] #[test]
@@ -20,10 +20,10 @@ mod tests {
} }
mod cli { mod cli {
use crate::{models::sonarr_models::SeriesType, Cli}; use crate::{Cli, models::sonarr_models::SeriesType};
use super::*; use super::*;
use clap::{error::ErrorKind, CommandFactory, Parser}; use clap::{CommandFactory, Parser, error::ErrorKind};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest; use rstest::rstest;
@@ -32,7 +32,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "sonarr", "edit", "all-indexer-settings"]); Cli::command().try_get_matches_from(["managarr", "sonarr", "edit", "all-indexer-settings"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -57,7 +57,7 @@ mod tests {
flag, flag,
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue); assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
} }
@@ -78,12 +78,13 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command { let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args); assert_eq!(edit_command, expected_args);
} }
}
#[test] #[test]
fn test_edit_all_indexer_settings_all_arguments_defined() { fn test_edit_all_indexer_settings_all_arguments_defined() {
@@ -108,18 +109,19 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command { let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args); assert_eq!(edit_command, expected_args);
} }
}
#[test] #[test]
fn test_edit_indexer_requires_arguments() { fn test_edit_indexer_requires_arguments() {
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "edit", "indexer"]); let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "edit", "indexer"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -137,7 +139,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -157,7 +159,7 @@ mod tests {
"--disable-rss", "--disable-rss",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict); assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
} }
@@ -174,7 +176,7 @@ mod tests {
"--disable-automatic-search", "--disable-automatic-search",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict); assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
} }
@@ -191,7 +193,7 @@ mod tests {
"--disable-interactive-search", "--disable-interactive-search",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict); assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
} }
@@ -209,7 +211,7 @@ mod tests {
"--clear-tags", "--clear-tags",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict); assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
} }
@@ -227,7 +229,7 @@ mod tests {
flag, flag,
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue); assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
} }
@@ -261,12 +263,13 @@ mod tests {
"Test", "Test",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command { let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args); assert_eq!(edit_command, expected_args);
} }
}
#[test] #[test]
fn test_edit_indexer_tag_argument_is_repeatable() { fn test_edit_indexer_tag_argument_is_repeatable() {
@@ -300,12 +303,13 @@ mod tests {
"2", "2",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command { let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args); assert_eq!(edit_command, expected_args);
} }
}
#[test] #[test]
fn test_edit_indexer_all_arguments_defined() { fn test_edit_indexer_all_arguments_defined() {
@@ -352,18 +356,19 @@ mod tests {
"25", "25",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command { let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args); assert_eq!(edit_command, expected_args);
} }
}
#[test] #[test]
fn test_edit_series_requires_arguments() { fn test_edit_series_requires_arguments() {
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "edit", "series"]); let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "edit", "series"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -381,7 +386,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -401,7 +406,7 @@ mod tests {
"--disable-monitoring", "--disable-monitoring",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict); assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
} }
@@ -418,7 +423,7 @@ mod tests {
"--disable-season-folders", "--disable-season-folders",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict); assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
} }
@@ -436,7 +441,7 @@ mod tests {
"--clear-tags", "--clear-tags",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict); assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
} }
@@ -461,7 +466,7 @@ mod tests {
flag, flag,
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue); assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
} }
@@ -478,7 +483,7 @@ mod tests {
"test", "test",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue); assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
} }
@@ -509,12 +514,13 @@ mod tests {
"/nfs/test", "/nfs/test",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command { let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args); assert_eq!(edit_command, expected_args);
} }
}
#[test] #[test]
fn test_edit_series_tag_argument_is_repeatable() { fn test_edit_series_tag_argument_is_repeatable() {
@@ -545,12 +551,13 @@ mod tests {
"2", "2",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command { let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args); assert_eq!(edit_command, expected_args);
} }
}
#[test] #[test]
fn test_edit_series_all_arguments_defined() { fn test_edit_series_all_arguments_defined() {
@@ -591,13 +598,14 @@ mod tests {
"2", "2",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command { let Some(Command::Sonarr(SonarrCommand::Edit(edit_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(edit_command, expected_args); assert_eq!(edit_command, expected_args);
} }
} }
}
mod handler { mod handler {
use std::sync::Arc; use std::sync::Arc;
@@ -609,15 +617,15 @@ mod tests {
use crate::{ use crate::{
app::App, app::App,
cli::{ cli::{
sonarr::edit_command_handler::{SonarrEditCommand, SonarrEditCommandHandler},
CliCommandHandler, CliCommandHandler,
sonarr::edit_command_handler::{SonarrEditCommand, SonarrEditCommandHandler},
}, },
models::{ models::{
servarr_models::EditIndexerParams,
sonarr_models::{EditSeriesParams, IndexerSettings, SeriesType, SonarrSerdeable},
Serdeable, Serdeable,
servarr_models::{EditIndexerParams, IndexerSettings},
sonarr_models::{EditSeriesParams, SeriesType, SonarrSerdeable},
}, },
network::{sonarr_network::SonarrEvent, MockNetworkTrait, NetworkEvent}, network::{MockNetworkTrait, NetworkEvent, sonarr_network::SonarrEvent},
}; };
#[tokio::test] #[tokio::test]
@@ -674,7 +682,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -728,7 +736,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -776,7 +784,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -824,7 +832,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -872,7 +880,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
} }
} }
+1 -1
View File
@@ -7,7 +7,7 @@ use tokio::sync::Mutex;
use crate::{ use crate::{
app::App, app::App,
cli::{CliCommandHandler, Command}, cli::{CliCommandHandler, Command},
network::{sonarr_network::SonarrEvent, NetworkTrait}, network::{NetworkTrait, sonarr_network::SonarrEvent},
}; };
use super::SonarrCommand; use super::SonarrCommand;
+21 -21
View File
@@ -1,10 +1,10 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::cli::{
sonarr::{get_command_handler::SonarrGetCommand, SonarrCommand},
Command,
};
use crate::Cli; use crate::Cli;
use crate::cli::{
Command,
sonarr::{SonarrCommand, get_command_handler::SonarrGetCommand},
};
use clap::CommandFactory; use clap::CommandFactory;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
@@ -28,7 +28,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "sonarr", "get", "all-indexer-settings"]); Cli::command().try_get_matches_from(["managarr", "sonarr", "get", "all-indexer-settings"]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
@@ -36,7 +36,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "sonarr", "get", "system-status"]); Cli::command().try_get_matches_from(["managarr", "sonarr", "get", "system-status"]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
@@ -44,7 +44,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "sonarr", "get", "episode-details"]); Cli::command().try_get_matches_from(["managarr", "sonarr", "get", "episode-details"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -62,7 +62,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
@@ -70,7 +70,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "sonarr", "get", "host-config"]); Cli::command().try_get_matches_from(["managarr", "sonarr", "get", "host-config"]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
@@ -78,7 +78,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "sonarr", "get", "security-config"]); Cli::command().try_get_matches_from(["managarr", "sonarr", "get", "security-config"]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
@@ -86,7 +86,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "sonarr", "get", "series-details"]); Cli::command().try_get_matches_from(["managarr", "sonarr", "get", "series-details"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -104,7 +104,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
} }
@@ -118,11 +118,11 @@ mod tests {
use crate::{ use crate::{
app::App, app::App,
cli::{ cli::{
sonarr::get_command_handler::{SonarrGetCommand, SonarrGetCommandHandler},
CliCommandHandler, CliCommandHandler,
sonarr::get_command_handler::{SonarrGetCommand, SonarrGetCommandHandler},
}, },
models::{sonarr_models::SonarrSerdeable, Serdeable}, models::{Serdeable, sonarr_models::SonarrSerdeable},
network::{sonarr_network::SonarrEvent, MockNetworkTrait, NetworkEvent}, network::{MockNetworkTrait, NetworkEvent, sonarr_network::SonarrEvent},
}; };
#[tokio::test] #[tokio::test]
@@ -150,7 +150,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -176,7 +176,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -199,7 +199,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -222,7 +222,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -248,7 +248,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -271,7 +271,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
} }
} }
+1 -1
View File
@@ -7,7 +7,7 @@ use tokio::sync::Mutex;
use crate::{ use crate::{
app::App, app::App,
cli::{CliCommandHandler, Command}, cli::{CliCommandHandler, Command},
network::{sonarr_network::SonarrEvent, NetworkTrait}, network::{NetworkTrait, sonarr_network::SonarrEvent},
}; };
use super::SonarrCommand; use super::SonarrCommand;
+61 -52
View File
@@ -1,10 +1,10 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::cli::{
sonarr::{list_command_handler::SonarrListCommand, SonarrCommand},
Command,
};
use crate::Cli; use crate::Cli;
use crate::cli::{
Command,
sonarr::{SonarrCommand, list_command_handler::SonarrListCommand},
};
use clap::CommandFactory; use clap::CommandFactory;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
@@ -19,7 +19,7 @@ mod tests {
mod cli { mod cli {
use super::*; use super::*;
use clap::{error::ErrorKind, Parser}; use clap::{Parser, error::ErrorKind};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest; use rstest::rstest;
@@ -42,14 +42,14 @@ mod tests {
) { ) {
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "list", subcommand]); let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "list", subcommand]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
fn test_list_episodes_requires_series_id() { fn test_list_episodes_requires_series_id() {
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "episodes"]); let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "episodes"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -61,7 +61,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "episode-files"]); Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "episode-files"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -73,7 +73,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "episode-history"]); Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "episode-history"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -92,21 +92,22 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::List(episode_history_command))) = let Some(Command::Sonarr(SonarrCommand::List(episode_history_command))) =
result.unwrap().command result.unwrap().command
{ else {
panic!("Unexpected command type");
};
assert_eq!(episode_history_command, expected_args); assert_eq!(episode_history_command, expected_args);
} }
}
#[test] #[test]
fn test_list_downloads_count_flag_requires_arguments() { fn test_list_downloads_count_flag_requires_arguments() {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "downloads", "--count"]); Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "downloads", "--count"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue); assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
} }
@@ -115,20 +116,21 @@ mod tests {
let expected_args = SonarrListCommand::Downloads { count: 500 }; let expected_args = SonarrListCommand::Downloads { count: 500 };
let result = Cli::try_parse_from(["managarr", "sonarr", "list", "downloads"]); let result = Cli::try_parse_from(["managarr", "sonarr", "list", "downloads"]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::List(downloads_command))) = result.unwrap().command let Some(Command::Sonarr(SonarrCommand::List(downloads_command))) = result.unwrap().command
{ else {
panic!("Unexpected command type");
};
assert_eq!(downloads_command, expected_args); assert_eq!(downloads_command, expected_args);
} }
}
#[test] #[test]
fn test_list_history_events_flag_requires_arguments() { fn test_list_history_events_flag_requires_arguments() {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "history", "--events"]); Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "history", "--events"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue); assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
} }
@@ -137,19 +139,21 @@ mod tests {
let expected_args = SonarrListCommand::History { events: 500 }; let expected_args = SonarrListCommand::History { events: 500 };
let result = Cli::try_parse_from(["managarr", "sonarr", "list", "history"]); let result = Cli::try_parse_from(["managarr", "sonarr", "list", "history"]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::List(history_command))) = result.unwrap().command { let Some(Command::Sonarr(SonarrCommand::List(history_command))) = result.unwrap().command
else {
panic!("Unexpected command type");
};
assert_eq!(history_command, expected_args); assert_eq!(history_command, expected_args);
} }
}
#[test] #[test]
fn test_list_logs_events_flag_requires_arguments() { fn test_list_logs_events_flag_requires_arguments() {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "logs", "--events"]); Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "logs", "--events"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue); assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
} }
@@ -161,12 +165,13 @@ mod tests {
}; };
let result = Cli::try_parse_from(["managarr", "sonarr", "list", "logs"]); let result = Cli::try_parse_from(["managarr", "sonarr", "list", "logs"]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::List(logs_command))) = result.unwrap().command { let Some(Command::Sonarr(SonarrCommand::List(logs_command))) = result.unwrap().command else {
panic!("Unexpected command type");
};
assert_eq!(logs_command, expected_args); assert_eq!(logs_command, expected_args);
} }
}
#[test] #[test]
fn test_list_episodes_success() { fn test_list_episodes_success() {
@@ -174,13 +179,14 @@ mod tests {
let result = let result =
Cli::try_parse_from(["managarr", "sonarr", "list", "episodes", "--series-id", "1"]); Cli::try_parse_from(["managarr", "sonarr", "list", "episodes", "--series-id", "1"]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::List(episodes_command))) = result.unwrap().command let Some(Command::Sonarr(SonarrCommand::List(episodes_command))) = result.unwrap().command
{ else {
panic!("Unexpected command type");
};
assert_eq!(episodes_command, expected_args); assert_eq!(episodes_command, expected_args);
} }
}
#[test] #[test]
fn test_list_episode_files_success() { fn test_list_episode_files_success() {
@@ -194,14 +200,15 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::List(episode_files_command))) = let Some(Command::Sonarr(SonarrCommand::List(episode_files_command))) =
result.unwrap().command result.unwrap().command
{ else {
panic!("Unexpected command type");
};
assert_eq!(episode_files_command, expected_args); assert_eq!(episode_files_command, expected_args);
} }
}
#[test] #[test]
fn test_season_history_requires_series_id() { fn test_season_history_requires_series_id() {
@@ -214,7 +221,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -232,7 +239,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -252,7 +259,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
@@ -260,7 +267,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "series-history"]); Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "series-history"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -279,13 +286,15 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::List(series_command))) = result.unwrap().command { let Some(Command::Sonarr(SonarrCommand::List(series_command))) = result.unwrap().command
else {
panic!("Unexpected command type");
};
assert_eq!(series_command, expected_args); assert_eq!(series_command, expected_args);
} }
} }
}
mod handler { mod handler {
@@ -296,10 +305,10 @@ mod tests {
use serde_json::json; use serde_json::json;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use crate::cli::sonarr::list_command_handler::{SonarrListCommand, SonarrListCommandHandler};
use crate::cli::CliCommandHandler; use crate::cli::CliCommandHandler;
use crate::models::sonarr_models::SonarrSerdeable; use crate::cli::sonarr::list_command_handler::{SonarrListCommand, SonarrListCommandHandler};
use crate::models::Serdeable; use crate::models::Serdeable;
use crate::models::sonarr_models::SonarrSerdeable;
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use crate::{ use crate::{
app::App, app::App,
@@ -339,7 +348,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -365,7 +374,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -391,7 +400,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -417,7 +426,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -443,7 +452,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -471,7 +480,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -497,7 +506,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -523,7 +532,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -553,7 +562,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
} }
} }
@@ -7,7 +7,7 @@ use tokio::sync::Mutex;
use crate::{ use crate::{
app::App, app::App,
cli::{CliCommandHandler, Command}, cli::{CliCommandHandler, Command},
network::{sonarr_network::SonarrEvent, NetworkTrait}, network::{NetworkTrait, sonarr_network::SonarrEvent},
}; };
use super::SonarrCommand; use super::SonarrCommand;
@@ -1,10 +1,10 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::cli::{
sonarr::{manual_search_command_handler::SonarrManualSearchCommand, SonarrCommand},
Command,
};
use crate::Cli; use crate::Cli;
use crate::cli::{
Command,
sonarr::{SonarrCommand, manual_search_command_handler::SonarrManualSearchCommand},
};
use clap::CommandFactory; use clap::CommandFactory;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
@@ -36,7 +36,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -54,7 +54,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -74,7 +74,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
@@ -82,7 +82,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "sonarr", "manual-search", "episode"]); Cli::command().try_get_matches_from(["managarr", "sonarr", "manual-search", "episode"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -100,7 +100,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
} }
@@ -114,13 +114,13 @@ mod tests {
use crate::{ use crate::{
app::App, app::App,
cli::{ cli::{
CliCommandHandler,
sonarr::manual_search_command_handler::{ sonarr::manual_search_command_handler::{
SonarrManualSearchCommand, SonarrManualSearchCommandHandler, SonarrManualSearchCommand, SonarrManualSearchCommandHandler,
}, },
CliCommandHandler,
}, },
models::{sonarr_models::SonarrSerdeable, Serdeable}, models::{Serdeable, sonarr_models::SonarrSerdeable},
network::{sonarr_network::SonarrEvent, MockNetworkTrait, NetworkEvent}, network::{MockNetworkTrait, NetworkEvent, sonarr_network::SonarrEvent},
}; };
#[tokio::test] #[tokio::test]
@@ -149,7 +149,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -182,7 +182,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
} }
} }
+3 -2
View File
@@ -10,6 +10,7 @@ use get_command_handler::{SonarrGetCommand, SonarrGetCommandHandler};
use list_command_handler::{SonarrListCommand, SonarrListCommandHandler}; use list_command_handler::{SonarrListCommand, SonarrListCommandHandler};
use manual_search_command_handler::{SonarrManualSearchCommand, SonarrManualSearchCommandHandler}; use manual_search_command_handler::{SonarrManualSearchCommand, SonarrManualSearchCommandHandler};
use refresh_command_handler::{SonarrRefreshCommand, SonarrRefreshCommandHandler}; use refresh_command_handler::{SonarrRefreshCommand, SonarrRefreshCommandHandler};
use serde_json::json;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use trigger_automatic_search_command_handler::{ use trigger_automatic_search_command_handler::{
SonarrTriggerAutomaticSearchCommand, SonarrTriggerAutomaticSearchCommandHandler, SonarrTriggerAutomaticSearchCommand, SonarrTriggerAutomaticSearchCommandHandler,
@@ -18,7 +19,7 @@ use trigger_automatic_search_command_handler::{
use crate::{ use crate::{
app::App, app::App,
models::sonarr_models::SonarrTaskName, models::sonarr_models::SonarrTaskName,
network::{sonarr_network::SonarrEvent, NetworkTrait}, network::{NetworkTrait, sonarr_network::SonarrEvent},
}; };
use super::{CliCommandHandler, Command}; use super::{CliCommandHandler, Command};
@@ -251,7 +252,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrCommand> for SonarrCliHandler<'a, '
.network .network
.handle_network_event(SonarrEvent::MarkHistoryItemAsFailed(history_item_id).into()) .handle_network_event(SonarrEvent::MarkHistoryItemAsFailed(history_item_id).into())
.await?; .await?;
"Sonarr history item marked as 'failed'".to_owned() serde_json::to_string_pretty(&json!({"message": "Sonarr history item marked as 'failed'"}))?
} }
SonarrCommand::SearchNewSeries { query } => { SonarrCommand::SearchNewSeries { query } => {
let resp = self let resp = self
+1 -1
View File
@@ -6,7 +6,7 @@ use tokio::sync::Mutex;
use crate::{ use crate::{
app::App, app::App,
cli::{CliCommandHandler, Command}, cli::{CliCommandHandler, Command},
network::{sonarr_network::SonarrEvent, NetworkTrait}, network::{NetworkTrait, sonarr_network::SonarrEvent},
}; };
use super::SonarrCommand; use super::SonarrCommand;
+16 -16
View File
@@ -2,11 +2,11 @@
mod tests { mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use crate::cli::{
sonarr::{refresh_command_handler::SonarrRefreshCommand, SonarrCommand},
Command,
};
use crate::Cli; use crate::Cli;
use crate::cli::{
Command,
sonarr::{SonarrCommand, refresh_command_handler::SonarrRefreshCommand},
};
use clap::CommandFactory; use clap::CommandFactory;
#[test] #[test]
@@ -20,7 +20,7 @@ mod tests {
mod cli { mod cli {
use super::*; use super::*;
use clap::{error::ErrorKind, Parser}; use clap::{Parser, error::ErrorKind};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest; use rstest::rstest;
@@ -31,14 +31,14 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "sonarr", "refresh", subcommand]); Cli::command().try_get_matches_from(["managarr", "sonarr", "refresh", subcommand]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
fn test_refresh_series_requires_series_id() { fn test_refresh_series_requires_series_id() {
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "refresh", "series"]); let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "refresh", "series"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -57,15 +57,15 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
if let Some(Command::Sonarr(SonarrCommand::Refresh(refresh_command))) = let Some(Command::Sonarr(SonarrCommand::Refresh(refresh_command))) = result.unwrap().command
result.unwrap().command else {
{ panic!("Unexpected command type");
};
assert_eq!(refresh_command, expected_args); assert_eq!(refresh_command, expected_args);
} }
} }
}
mod handler { mod handler {
use rstest::rstest; use rstest::rstest;
@@ -77,11 +77,11 @@ mod tests {
use crate::{app::App, cli::sonarr::refresh_command_handler::SonarrRefreshCommandHandler}; use crate::{app::App, cli::sonarr::refresh_command_handler::SonarrRefreshCommandHandler};
use crate::{ use crate::{
cli::{sonarr::refresh_command_handler::SonarrRefreshCommand, CliCommandHandler}, cli::{CliCommandHandler, sonarr::refresh_command_handler::SonarrRefreshCommand},
network::sonarr_network::SonarrEvent, network::sonarr_network::SonarrEvent,
}; };
use crate::{ use crate::{
models::{sonarr_models::SonarrSerdeable, Serdeable}, models::{Serdeable, sonarr_models::SonarrSerdeable},
network::{MockNetworkTrait, NetworkEvent}, network::{MockNetworkTrait, NetworkEvent},
}; };
@@ -109,7 +109,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -135,7 +135,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
} }
} }
+52 -51
View File
@@ -1,10 +1,10 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::cli::{
sonarr::{list_command_handler::SonarrListCommand, SonarrCommand},
Command,
};
use crate::Cli; use crate::Cli;
use crate::cli::{
Command,
sonarr::{SonarrCommand, list_command_handler::SonarrListCommand},
};
use clap::CommandFactory; use clap::CommandFactory;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
@@ -29,7 +29,7 @@ mod tests {
) { ) {
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", subcommand]); let result = Cli::command().try_get_matches_from(["managarr", "sonarr", subcommand]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
@@ -37,7 +37,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "sonarr", "mark-history-item-as-failed"]); Cli::command().try_get_matches_from(["managarr", "sonarr", "mark-history-item-as-failed"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -54,14 +54,14 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
fn test_search_new_series_requires_query() { fn test_search_new_series_requires_query() {
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "search-new-series"]); let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "search-new-series"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -78,14 +78,14 @@ mod tests {
"halo", "halo",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
fn test_start_task_requires_task_name() { fn test_start_task_requires_task_name() {
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "start-task"]); let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "start-task"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -102,7 +102,7 @@ mod tests {
"test", "test",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue); assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
} }
@@ -116,14 +116,14 @@ mod tests {
"application-update-check", "application-update-check",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
fn test_test_indexer_requires_indexer_id() { fn test_test_indexer_requires_indexer_id() {
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "test-indexer"]); let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "test-indexer"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -140,7 +140,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
@@ -148,7 +148,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "sonarr", "toggle-episode-monitoring"]); Cli::command().try_get_matches_from(["managarr", "sonarr", "toggle-episode-monitoring"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -165,7 +165,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
@@ -178,7 +178,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -195,7 +195,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -214,7 +214,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
@@ -222,7 +222,7 @@ mod tests {
let result = let result =
Cli::command().try_get_matches_from(["managarr", "sonarr", "toggle-series-monitoring"]); Cli::command().try_get_matches_from(["managarr", "sonarr", "toggle-series-monitoring"]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -239,7 +239,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
} }
@@ -253,25 +253,26 @@ mod tests {
use crate::{ use crate::{
app::App, app::App,
cli::{ cli::{
CliCommandHandler,
sonarr::{ sonarr::{
add_command_handler::SonarrAddCommand, delete_command_handler::SonarrDeleteCommand, SonarrCliHandler, SonarrCommand, add_command_handler::SonarrAddCommand,
delete_command_handler::SonarrDeleteCommand,
download_command_handler::SonarrDownloadCommand, edit_command_handler::SonarrEditCommand, download_command_handler::SonarrDownloadCommand, edit_command_handler::SonarrEditCommand,
get_command_handler::SonarrGetCommand, list_command_handler::SonarrListCommand, get_command_handler::SonarrGetCommand, list_command_handler::SonarrListCommand,
manual_search_command_handler::SonarrManualSearchCommand, manual_search_command_handler::SonarrManualSearchCommand,
refresh_command_handler::SonarrRefreshCommand, refresh_command_handler::SonarrRefreshCommand,
trigger_automatic_search_command_handler::SonarrTriggerAutomaticSearchCommand, trigger_automatic_search_command_handler::SonarrTriggerAutomaticSearchCommand,
SonarrCliHandler, SonarrCommand,
}, },
CliCommandHandler,
}, },
models::{ models::{
sonarr_models::{
BlocklistItem, BlocklistResponse, IndexerSettings, Series, SonarrReleaseDownloadBody,
SonarrSerdeable, SonarrTaskName,
},
Serdeable, Serdeable,
servarr_models::IndexerSettings,
sonarr_models::{
BlocklistItem, BlocklistResponse, Series, SonarrReleaseDownloadBody, SonarrSerdeable,
SonarrTaskName,
}, },
network::{sonarr_network::SonarrEvent, MockNetworkTrait, NetworkEvent}, },
network::{MockNetworkTrait, NetworkEvent, sonarr_network::SonarrEvent},
}; };
#[tokio::test] #[tokio::test]
@@ -304,7 +305,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -334,7 +335,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -361,7 +362,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -390,7 +391,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -426,7 +427,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -484,12 +485,12 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
async fn test_sonarr_cli_handler_delegates_manual_search_commands_to_the_manual_search_command_handler( async fn test_sonarr_cli_handler_delegates_manual_search_commands_to_the_manual_search_command_handler()
) { {
let expected_episode_id = 1; let expected_episode_id = 1;
let mut mock_network = MockNetworkTrait::new(); let mut mock_network = MockNetworkTrait::new();
mock_network mock_network
@@ -512,12 +513,12 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
async fn test_sonarr_cli_handler_delegates_trigger_automatic_search_commands_to_the_trigger_automatic_search_command_handler( async fn test_sonarr_cli_handler_delegates_trigger_automatic_search_commands_to_the_trigger_automatic_search_command_handler()
) { {
let expected_episode_id = 1; let expected_episode_id = 1;
let mut mock_network = MockNetworkTrait::new(); let mut mock_network = MockNetworkTrait::new();
mock_network mock_network
@@ -542,7 +543,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -564,7 +565,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -586,7 +587,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -612,7 +613,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -639,7 +640,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -666,7 +667,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -691,7 +692,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -713,7 +714,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -743,7 +744,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -776,7 +777,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -805,7 +806,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
} }
} }
@@ -7,7 +7,7 @@ use tokio::sync::Mutex;
use crate::{ use crate::{
app::App, app::App,
cli::{CliCommandHandler, Command}, cli::{CliCommandHandler, Command},
network::{sonarr_network::SonarrEvent, NetworkTrait}, network::{NetworkTrait, sonarr_network::SonarrEvent},
}; };
use super::SonarrCommand; use super::SonarrCommand;
@@ -1,12 +1,12 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::cli::{
sonarr::{
trigger_automatic_search_command_handler::SonarrTriggerAutomaticSearchCommand, SonarrCommand,
},
Command,
};
use crate::Cli; use crate::Cli;
use crate::cli::{
Command,
sonarr::{
SonarrCommand, trigger_automatic_search_command_handler::SonarrTriggerAutomaticSearchCommand,
},
};
use clap::CommandFactory; use clap::CommandFactory;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
@@ -36,7 +36,7 @@ mod tests {
"series", "series",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -54,7 +54,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
@@ -68,7 +68,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -86,7 +86,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -106,7 +106,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
#[test] #[test]
@@ -118,7 +118,7 @@ mod tests {
"episode", "episode",
]); ]);
assert!(result.is_err()); assert_err!(&result);
assert_eq!( assert_eq!(
result.unwrap_err().kind(), result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument ErrorKind::MissingRequiredArgument
@@ -136,7 +136,7 @@ mod tests {
"1", "1",
]); ]);
assert!(result.is_ok()); assert_ok!(&result);
} }
} }
@@ -150,13 +150,13 @@ mod tests {
use crate::{ use crate::{
app::App, app::App,
cli::{ cli::{
CliCommandHandler,
sonarr::trigger_automatic_search_command_handler::{ sonarr::trigger_automatic_search_command_handler::{
SonarrTriggerAutomaticSearchCommand, SonarrTriggerAutomaticSearchCommandHandler, SonarrTriggerAutomaticSearchCommand, SonarrTriggerAutomaticSearchCommandHandler,
}, },
CliCommandHandler,
}, },
models::{sonarr_models::SonarrSerdeable, Serdeable}, models::{Serdeable, sonarr_models::SonarrSerdeable},
network::{sonarr_network::SonarrEvent, MockNetworkTrait, NetworkEvent}, network::{MockNetworkTrait, NetworkEvent, sonarr_network::SonarrEvent},
}; };
#[tokio::test] #[tokio::test]
@@ -186,7 +186,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -220,7 +220,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
#[tokio::test] #[tokio::test]
@@ -250,7 +250,7 @@ mod tests {
.handle() .handle()
.await; .await;
assert!(result.is_ok()); assert_ok!(&result);
} }
} }
} }
+9 -5
View File
@@ -1,3 +1,4 @@
use anyhow::Result;
use std::sync::mpsc; use std::sync::mpsc;
use std::sync::mpsc::Receiver; use std::sync::mpsc::Receiver;
use std::thread; use std::thread;
@@ -28,8 +29,9 @@ impl Events {
let timeout = tick_rate let timeout = tick_rate
.checked_sub(last_tick.elapsed()) .checked_sub(last_tick.elapsed())
.unwrap_or_else(|| Duration::from_secs(0)); .unwrap_or_else(|| Duration::from_secs(0));
if event::poll(timeout).unwrap() { if event::poll(timeout).unwrap()
if let CrosstermEvent::Key(key_event) = event::read().unwrap() { && let CrosstermEvent::Key(key_event) = event::read().unwrap()
{
// Only process the key event if it's a press event // Only process the key event if it's a press event
// Source: https://ratatui.rs/faq/ Why am I getting duplicate key events on Windows? // Source: https://ratatui.rs/faq/ Why am I getting duplicate key events on Windows?
if key_event.kind == KeyEventKind::Press { if key_event.kind == KeyEventKind::Press {
@@ -37,7 +39,6 @@ impl Events {
tx.send(InputEvent::KeyEvent(key)).unwrap(); tx.send(InputEvent::KeyEvent(key)).unwrap();
} }
} }
}
if last_tick.elapsed() >= tick_rate { if last_tick.elapsed() >= tick_rate {
tx.send(InputEvent::Tick).unwrap(); tx.send(InputEvent::Tick).unwrap();
@@ -49,7 +50,10 @@ impl Events {
Events { rx } Events { rx }
} }
pub fn next(&self) -> Result<InputEvent<Key>, mpsc::RecvError> { pub fn next(&self) -> Result<Option<InputEvent<Key>>> {
self.rx.recv() match self.rx.try_recv() {
Ok(event) => Ok(Some(event)),
_ => Ok(None),
}
} }
} }
+192
View File
@@ -0,0 +1,192 @@
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
use crate::app::App;
use crate::handlers::handler_test_utils::test_utils::proptest_helpers::*;
use crate::models::radarr_models::Movie;
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use crate::models::stateful_table::StatefulTable;
use crate::models::{Paginated, Scrollable};
proptest! {
#[test]
fn test_table_index_selection_safety(
list_size in list_size(),
index in 0usize..1000
) {
let mut table = StatefulTable::<Movie>::default();
let movies: Vec<Movie> = (0..list_size).map(|i| {
let mut movie = Movie::default();
movie.id = i as i64;
movie
}).collect();
table.set_items(movies);
if index < list_size {
table.select_index(Some(index));
let selected = table.current_selection();
prop_assert_eq!(selected.id, index as i64);
} else {
table.select_index(Some(index));
}
}
#[test]
fn test_table_scroll_consistency(
list_size in list_size(),
scroll_amount in 0usize..20
) {
let mut table = StatefulTable::<Movie>::default();
let movies: Vec<Movie> = (0..list_size).map(|i| {
let mut movie = Movie::default();
movie.id = i as i64;
movie
}).collect();
table.set_items(movies);
let initial_id = table.current_selection().id;
for _ in 0..scroll_amount {
table.scroll_down();
}
let after_down_id = table.current_selection().id;
prop_assert!(after_down_id >= initial_id);
prop_assert!(after_down_id < list_size as i64);
for _ in 0..scroll_amount {
table.scroll_up();
}
prop_assert!(table.current_selection().id <= initial_id);
}
#[test]
fn test_empty_table_safety(_scroll_ops in 0usize..50) {
let table = StatefulTable::<Movie>::default();
prop_assert!(table.is_empty());
prop_assert!(table.items.is_empty());
}
#[test]
fn test_navigation_consistency(pushes in 1usize..20) {
let mut app = App::test_default();
let initial_route = app.get_current_route();
let routes = vec![
ActiveRadarrBlock::Movies,
ActiveRadarrBlock::Collections,
ActiveRadarrBlock::Downloads,
ActiveRadarrBlock::Blocklist,
];
for i in 0..pushes {
let route = routes[i % routes.len()];
app.push_navigation_stack(route.into());
}
let last_pushed = routes[(pushes - 1) % routes.len()];
prop_assert_eq!(app.get_current_route(), last_pushed.into());
for _ in 0..pushes {
app.pop_navigation_stack();
}
prop_assert_eq!(app.get_current_route(), initial_route);
}
#[test]
fn test_string_input_safety(input in text_input_string()) {
let _lowercase = input.to_lowercase();
let _uppercase = input.to_uppercase();
let _trimmed = input.trim();
let _len = input.len();
let _chars: Vec<char> = input.chars().collect();
prop_assert!(true);
}
#[test]
fn test_table_data_integrity(
list_size in 1usize..100
) {
let mut table = StatefulTable::<Movie>::default();
let movies: Vec<Movie> = (0..list_size).map(|i| {
let mut movie = Movie::default();
movie.id = i as i64;
movie.title = format!("Movie {}", i).into();
movie
}).collect();
table.set_items(movies.clone());
let original_count = table.items.len();
prop_assert_eq!(table.items.len(), original_count);
for movie in &movies {
prop_assert!(table.items.iter().any(|m| m.id == movie.id));
}
}
#[test]
fn test_page_navigation_bounds(
list_size in list_size(),
page_ops in 0usize..10
) {
let mut table = StatefulTable::<Movie>::default();
let movies: Vec<Movie> = (0..list_size).map(|i| {
let mut movie = Movie::default();
movie.id = i as i64;
movie
}).collect();
table.set_items(movies);
for i in 0..page_ops {
if i % 2 == 0 {
table.page_down();
} else {
table.page_up();
}
let current = table.current_selection();
prop_assert!(current.id >= 0);
prop_assert!(current.id < list_size as i64);
}
}
#[test]
fn test_table_filter_size_invariant(
list_size in list_size(),
filter_term in text_input_string()
) {
let mut table = StatefulTable::<Movie>::default();
let movies: Vec<Movie> = (0..list_size).map(|i| {
let mut movie = Movie::default();
movie.id = i as i64;
movie.title = format!("Test Movie {}", i % 10).into();
movie
}).collect();
table.set_items(movies.clone());
let original_size = table.items.len();
if !filter_term.is_empty() {
let filtered: Vec<Movie> = movies.into_iter()
.filter(|m| m.title.text.to_lowercase().contains(&filter_term.to_lowercase()))
.collect();
table.set_items(filtered);
}
prop_assert!(table.items.len() <= original_size);
if !table.items.is_empty() {
let current = table.current_selection();
prop_assert!(current.id >= 0);
}
}
}
}
+37 -84
View File
@@ -330,90 +330,7 @@ mod test_utils {
#[macro_export] #[macro_export]
macro_rules! test_handler_delegation { macro_rules! test_handler_delegation {
($handler:ident, $base:expr, $active_block:expr) => { ($handler:ident, $base:expr, $active_block:expr) => {
let mut app = App::test_default(); let mut app = App::test_default_fully_populated();
app.data.sonarr_data.history.set_items(vec![
$crate::models::sonarr_models::SonarrHistoryItem::default(),
]);
app
.data
.sonarr_data
.root_folders
.set_items(vec![$crate::models::servarr_models::RootFolder::default()]);
app
.data
.sonarr_data
.indexers
.set_items(vec![$crate::models::servarr_models::Indexer::default()]);
app
.data
.sonarr_data
.blocklist
.set_items(vec![$crate::models::sonarr_models::BlocklistItem::default()]);
app.data.sonarr_data.add_searched_series =
Some($crate::models::stateful_table::StatefulTable::default());
app
.data
.radarr_data
.movies
.set_items(vec![$crate::models::radarr_models::Movie::default()]);
app
.data
.radarr_data
.collections
.set_items(vec![$crate::models::radarr_models::Collection::default()]);
app.data.radarr_data.collection_movies.set_items(vec![
$crate::models::radarr_models::CollectionMovie::default(),
]);
app
.data
.radarr_data
.indexers
.set_items(vec![$crate::models::servarr_models::Indexer::default()]);
app
.data
.radarr_data
.root_folders
.set_items(vec![$crate::models::servarr_models::RootFolder::default()]);
app
.data
.radarr_data
.blocklist
.set_items(vec![$crate::models::radarr_models::BlocklistItem::default()]);
app.data.radarr_data.add_searched_movies =
Some($crate::models::stateful_table::StatefulTable::default());
let mut movie_details_modal =
$crate::models::servarr_data::radarr::modals::MovieDetailsModal::default();
movie_details_modal.movie_history.set_items(vec![
$crate::models::radarr_models::MovieHistoryItem::default(),
]);
movie_details_modal
.movie_cast
.set_items(vec![$crate::models::radarr_models::Credit::default()]);
movie_details_modal
.movie_crew
.set_items(vec![$crate::models::radarr_models::Credit::default()]);
movie_details_modal
.movie_releases
.set_items(vec![$crate::models::radarr_models::RadarrRelease::default()]);
app.data.radarr_data.movie_details_modal = Some(movie_details_modal);
let mut season_details_modal =
$crate::models::servarr_data::sonarr::modals::SeasonDetailsModal::default();
season_details_modal.season_history.set_items(vec![
$crate::models::sonarr_models::SonarrHistoryItem::default(),
]);
season_details_modal.episode_details_modal =
Some($crate::models::servarr_data::sonarr::modals::EpisodeDetailsModal::default());
app.data.sonarr_data.season_details_modal = Some(season_details_modal);
let mut series_history = $crate::models::stateful_table::StatefulTable::default();
series_history.set_items(vec![
$crate::models::sonarr_models::SonarrHistoryItem::default(),
]);
app.data.sonarr_data.series_history = Some(series_history);
app
.data
.sonarr_data
.series
.set_items(vec![$crate::models::sonarr_models::Series::default()]);
app.push_navigation_stack($base.into()); app.push_navigation_stack($base.into());
app.push_navigation_stack($active_block.into()); app.push_navigation_stack($active_block.into());
@@ -452,4 +369,40 @@ mod test_utils {
assert!(app.should_refresh); assert!(app.should_refresh);
}; };
} }
#[macro_export]
macro_rules! assert_modal_present {
($modal:expr) => {
assert!($modal.is_some(), "Expected modal to be present");
};
}
#[macro_export]
macro_rules! assert_modal_absent {
($modal:expr) => {
assert!($modal.is_none(), "Expected modal to be absent");
};
}
#[macro_export]
macro_rules! assert_navigation_pushed {
($app:expr, $expected_route:expr) => {
pretty_assertions::assert_eq!(
$app.get_current_route(),
$expected_route,
"Expected route to be pushed onto navigation stack"
);
};
}
#[macro_export]
macro_rules! assert_navigation_popped {
($app:expr, $expected_route:expr) => {
pretty_assertions::assert_eq!(
$app.get_current_route(),
$expected_route,
"Expected route after popping navigation stack"
);
};
}
} }
+35 -26
View File
@@ -1,27 +1,31 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::assert_modal_absent;
use crate::assert_navigation_pushed;
use crate::models::radarr_models::Movie; use crate::models::radarr_models::Movie;
use crate::models::sonarr_models::Series; use crate::models::sonarr_models::Series;
use crate::{assert_modal_present, assert_navigation_popped};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest; use rstest::rstest;
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
use crate::app::App;
use crate::app::context_clues::SERVARR_CONTEXT_CLUES; use crate::app::context_clues::SERVARR_CONTEXT_CLUES;
use crate::app::key_binding::{KeyBinding, DEFAULT_KEYBINDINGS}; use crate::app::key_binding::{DEFAULT_KEYBINDINGS, KeyBinding};
use crate::app::radarr::radarr_context_clues::{ use crate::app::radarr::radarr_context_clues::{
LIBRARY_CONTEXT_CLUES, MOVIE_DETAILS_CONTEXT_CLUES, LIBRARY_CONTEXT_CLUES, MOVIE_DETAILS_CONTEXT_CLUES,
}; };
use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle}; use crate::handlers::{handle_clear_errors, handle_prompt_toggle};
use crate::handlers::{handle_events, populate_keymapping_table}; use crate::handlers::{handle_events, populate_keymapping_table};
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData};
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
use crate::models::servarr_data::ActiveKeybindingBlock;
use crate::models::servarr_models::KeybindingItem;
use crate::models::stateful_table::StatefulTable;
use crate::models::HorizontallyScrollableText; use crate::models::HorizontallyScrollableText;
use crate::models::Route; use crate::models::Route;
use crate::models::servarr_data::ActiveKeybindingBlock;
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData};
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
use crate::models::servarr_models::KeybindingItem;
use crate::models::stateful_table::StatefulTable;
#[test] #[test]
fn test_handle_clear_errors() { fn test_handle_clear_errors() {
@@ -30,7 +34,7 @@ mod tests {
handle_clear_errors(&mut app); handle_clear_errors(&mut app);
assert!(app.error.text.is_empty()); assert_is_empty!(app.error.text);
} }
#[rstest] #[rstest]
@@ -53,15 +57,20 @@ mod tests {
handle_events(DEFAULT_KEYBINDINGS.esc.key, &mut app); handle_events(DEFAULT_KEYBINDINGS.esc.key, &mut app);
assert_eq!(app.get_current_route(), base_block); assert_navigation_popped!(app, base_block);
} }
#[rstest] #[rstest]
#[case(0, ActiveSonarrBlock::Series, ActiveSonarrBlock::Series)] #[case(0, ActiveLidarrBlock::Artists, ActiveSonarrBlock::Series)]
#[case(1, ActiveRadarrBlock::Movies, ActiveRadarrBlock::Movies)] #[case(1, ActiveRadarrBlock::Movies, ActiveLidarrBlock::Artists)]
fn test_handle_change_tabs<T>(#[case] index: usize, #[case] left_block: T, #[case] right_block: T) #[case(2, ActiveSonarrBlock::Series, ActiveRadarrBlock::Movies)]
where fn test_handle_change_tabs<T, U>(
#[case] index: usize,
#[case] left_block: T,
#[case] right_block: U,
) where
T: Into<Route> + Copy, T: Into<Route> + Copy,
U: Into<Route> + Copy,
{ {
let mut app = App::test_default(); let mut app = App::test_default();
app.error = "Test".into(); app.error = "Test".into();
@@ -70,7 +79,7 @@ mod tests {
handle_events(DEFAULT_KEYBINDINGS.previous_servarr.key, &mut app); handle_events(DEFAULT_KEYBINDINGS.previous_servarr.key, &mut app);
assert_eq!(app.server_tabs.get_active_route(), left_block.into()); assert_eq!(app.server_tabs.get_active_route(), left_block.into());
assert_eq!(app.get_current_route(), left_block.into()); assert_navigation_pushed!(app, left_block.into());
assert!(app.is_first_render); assert!(app.is_first_render);
assert_eq!(app.error, HorizontallyScrollableText::default()); assert_eq!(app.error, HorizontallyScrollableText::default());
assert!(app.cancellation_token.is_cancelled()); assert!(app.cancellation_token.is_cancelled());
@@ -83,7 +92,7 @@ mod tests {
handle_events(DEFAULT_KEYBINDINGS.next_servarr.key, &mut app); handle_events(DEFAULT_KEYBINDINGS.next_servarr.key, &mut app);
assert_eq!(app.server_tabs.get_active_route(), right_block.into()); assert_eq!(app.server_tabs.get_active_route(), right_block.into());
assert_eq!(app.get_current_route(), right_block.into()); assert_navigation_pushed!(app, right_block.into());
assert!(app.is_first_render); assert!(app.is_first_render);
assert_eq!(app.error, HorizontallyScrollableText::default()); assert_eq!(app.error, HorizontallyScrollableText::default());
assert!(app.cancellation_token.is_cancelled()); assert!(app.cancellation_token.is_cancelled());
@@ -100,7 +109,7 @@ mod tests {
handle_events(DEFAULT_KEYBINDINGS.help.key, &mut app); handle_events(DEFAULT_KEYBINDINGS.help.key, &mut app);
assert!(app.keymapping_table.is_some()); assert_modal_present!(app.keymapping_table);
assert_eq!( assert_eq!(
expected_keybinding_items, expected_keybinding_items,
app.keymapping_table.unwrap().items app.keymapping_table.unwrap().items
@@ -115,12 +124,12 @@ mod tests {
handle_events(DEFAULT_KEYBINDINGS.help.key, &mut app); handle_events(DEFAULT_KEYBINDINGS.help.key, &mut app);
assert!(app.keymapping_table.is_none()); assert_modal_absent!(app.keymapping_table);
} }
#[test] #[test]
fn test_handle_empties_keybindings_table_on_help_button_press_when_keybindings_table_is_already_populated( fn test_handle_empties_keybindings_table_on_help_button_press_when_keybindings_table_is_already_populated()
) { {
let mut app = App::test_default(); let mut app = App::test_default();
let keybinding_items = Vec::from(SERVARR_CONTEXT_CLUES) let keybinding_items = Vec::from(SERVARR_CONTEXT_CLUES)
.iter() .iter()
@@ -133,7 +142,7 @@ mod tests {
handle_events(DEFAULT_KEYBINDINGS.help.key, &mut app); handle_events(DEFAULT_KEYBINDINGS.help.key, &mut app);
assert!(app.keymapping_table.is_none()); assert_modal_absent!(app.keymapping_table);
} }
#[test] #[test]
@@ -158,7 +167,7 @@ mod tests {
handle_events(DEFAULT_KEYBINDINGS.down.key, &mut app); handle_events(DEFAULT_KEYBINDINGS.down.key, &mut app);
assert!(app.keymapping_table.is_some()); assert_modal_present!(app.keymapping_table);
assert_eq!( assert_eq!(
&expected_selection, &expected_selection,
app.keymapping_table.unwrap().current_selection() app.keymapping_table.unwrap().current_selection()
@@ -219,7 +228,7 @@ mod tests {
populate_keymapping_table(&mut app); populate_keymapping_table(&mut app);
assert!(app.keymapping_table.is_some()); assert_modal_present!(app.keymapping_table);
assert_eq!( assert_eq!(
expected_keybinding_items, expected_keybinding_items,
app.keymapping_table.unwrap().items app.keymapping_table.unwrap().items
@@ -243,7 +252,7 @@ mod tests {
populate_keymapping_table(&mut app); populate_keymapping_table(&mut app);
assert!(app.keymapping_table.is_some()); assert_modal_present!(app.keymapping_table);
assert_eq!( assert_eq!(
expected_keybinding_items, expected_keybinding_items,
app.keymapping_table.unwrap().items app.keymapping_table.unwrap().items
@@ -251,8 +260,8 @@ mod tests {
} }
#[test] #[test]
fn test_populate_keymapping_table_populates_delegated_servarr_context_provider_options_before_global_options( fn test_populate_keymapping_table_populates_delegated_servarr_context_provider_options_before_global_options()
) { {
let mut expected_keybinding_items = MOVIE_DETAILS_CONTEXT_CLUES let mut expected_keybinding_items = MOVIE_DETAILS_CONTEXT_CLUES
.iter() .iter()
.map(|(key, desc)| context_clue_to_keybinding_item(key, desc)) .map(|(key, desc)| context_clue_to_keybinding_item(key, desc))
@@ -268,7 +277,7 @@ mod tests {
populate_keymapping_table(&mut app); populate_keymapping_table(&mut app);
assert!(app.keymapping_table.is_some()); assert_modal_present!(app.keymapping_table);
assert_eq!( assert_eq!(
expected_keybinding_items, expected_keybinding_items,
app.keymapping_table.unwrap().items app.keymapping_table.unwrap().items
+15 -13
View File
@@ -1,10 +1,9 @@
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::KeyEventHandler; use crate::handlers::KeyEventHandler;
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
use crate::models::Route;
use crate::models::servarr_data::ActiveKeybindingBlock; use crate::models::servarr_data::ActiveKeybindingBlock;
use crate::models::servarr_models::KeybindingItem;
#[cfg(test)] #[cfg(test)]
#[path = "keybinding_handler_tests.rs"] #[path = "keybinding_handler_tests.rs"]
@@ -15,20 +14,15 @@ pub(super) struct KeybindingHandler<'a, 'b> {
app: &'a mut App<'b>, app: &'a mut App<'b>,
} }
impl KeybindingHandler<'_, '_> {
handle_table_events!(
self,
keybindings,
self.app.keymapping_table.as_mut().unwrap(),
KeybindingItem
);
}
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveKeybindingBlock> for KeybindingHandler<'a, 'b> { impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveKeybindingBlock> for KeybindingHandler<'a, 'b> {
fn handle(&mut self) { fn handle(&mut self) {
let keybinding_table_handling_config = TableHandlingConfig::new(self.app.get_current_route()); let keybinding_table_handling_config = TableHandlingConfig::new(self.app.get_current_route());
if !self.handle_keybindings_table_events(keybinding_table_handling_config) { if !handle_table(
self,
|app| app.keymapping_table.as_mut().unwrap(),
keybinding_table_handling_config,
) {
self.handle_key_event(); self.handle_key_event();
} }
} }
@@ -77,4 +71,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveKeybindingBlock> for KeybindingHandle
} }
fn handle_char_key_event(&mut self) {} fn handle_char_key_event(&mut self) {}
fn app_mut(&mut self) -> &mut App<'b> {
self.app
}
fn current_route(&self) -> Route {
self.app.get_current_route()
}
} }
+3 -2
View File
@@ -1,7 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::event::Key; use crate::event::Key;
use crate::handlers::KeyEventHandler; use crate::handlers::KeyEventHandler;
use crate::handlers::KeybindingHandler; use crate::handlers::KeybindingHandler;
@@ -11,6 +11,7 @@ mod tests {
mod test_handle_esc { mod test_handle_esc {
use super::*; use super::*;
use crate::assert_modal_absent;
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
@@ -25,7 +26,7 @@ mod tests {
KeybindingHandler::new(ESC_KEY, &mut app, ActiveKeybindingBlock::Help, None).handle(); KeybindingHandler::new(ESC_KEY, &mut app, ActiveKeybindingBlock::Help, None).handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
assert!(app.keymapping_table.is_none()); assert_modal_absent!(app.keymapping_table);
} }
} }
@@ -0,0 +1,481 @@
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::App;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::assert_navigation_pushed;
use crate::event::Key;
use crate::handlers::KeyEventHandler;
use crate::handlers::lidarr_handlers::downloads::DownloadsHandler;
use crate::models::lidarr_models::DownloadRecord;
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, DOWNLOADS_BLOCKS};
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::download_record;
mod test_handle_delete {
use pretty_assertions::assert_eq;
use super::*;
const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key;
#[test]
fn test_delete_download_prompt() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
app
.data
.lidarr_data
.downloads
.set_items(vec![DownloadRecord::default()]);
DownloadsHandler::new(DELETE_KEY, &mut app, ActiveLidarrBlock::Downloads, None).handle();
assert_navigation_pushed!(app, ActiveLidarrBlock::DeleteDownloadPrompt.into());
}
#[test]
fn test_delete_download_prompt_no_op_when_not_ready() {
let mut app = App::test_default();
app.is_loading = true;
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
app
.data
.lidarr_data
.downloads
.set_items(vec![DownloadRecord::default()]);
DownloadsHandler::new(DELETE_KEY, &mut app, ActiveLidarrBlock::Downloads, None).handle();
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Downloads.into());
}
}
mod test_handle_left_right_action {
use pretty_assertions::assert_eq;
use rstest::rstest;
use super::*;
use crate::assert_navigation_pushed;
#[rstest]
fn test_downloads_tab_left(#[values(true, false)] is_ready: bool) {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
app.is_loading = is_ready;
app.data.lidarr_data.main_tabs.set_index(1);
DownloadsHandler::new(
DEFAULT_KEYBINDINGS.left.key,
&mut app,
ActiveLidarrBlock::Downloads,
None,
)
.handle();
assert_eq!(
app.data.lidarr_data.main_tabs.get_active_route(),
ActiveLidarrBlock::Artists.into()
);
assert_navigation_pushed!(app, ActiveLidarrBlock::Artists.into());
}
#[rstest]
fn test_downloads_tab_right(#[values(true, false)] is_ready: bool) {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
app.is_loading = is_ready;
app.data.lidarr_data.main_tabs.set_index(1);
DownloadsHandler::new(
DEFAULT_KEYBINDINGS.right.key,
&mut app,
ActiveLidarrBlock::Downloads,
None,
)
.handle();
assert_eq!(
app.data.lidarr_data.main_tabs.get_active_route(),
ActiveLidarrBlock::History.into()
);
assert_navigation_pushed!(app, ActiveLidarrBlock::History.into());
}
#[rstest]
fn test_downloads_left_right_prompt_toggle(
#[values(
ActiveLidarrBlock::DeleteDownloadPrompt,
ActiveLidarrBlock::UpdateDownloadsPrompt
)]
active_lidarr_block: ActiveLidarrBlock,
#[values(DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.right.key)] key: Key,
) {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
DownloadsHandler::new(key, &mut app, active_lidarr_block, None).handle();
assert!(app.data.lidarr_data.prompt_confirm);
DownloadsHandler::new(key, &mut app, active_lidarr_block, None).handle();
assert!(!app.data.lidarr_data.prompt_confirm);
}
}
mod test_handle_submit {
use rstest::rstest;
use crate::network::lidarr_network::LidarrEvent;
use super::*;
use crate::assert_navigation_popped;
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::download_record;
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
#[rstest]
#[case(
ActiveLidarrBlock::Downloads,
ActiveLidarrBlock::DeleteDownloadPrompt,
LidarrEvent::DeleteDownload(1)
)]
#[case(
ActiveLidarrBlock::Downloads,
ActiveLidarrBlock::UpdateDownloadsPrompt,
LidarrEvent::UpdateDownloads
)]
fn test_downloads_prompt_confirm_submit(
#[case] base_route: ActiveLidarrBlock,
#[case] prompt_block: ActiveLidarrBlock,
#[case] expected_action: LidarrEvent,
) {
let mut app = App::test_default();
app
.data
.lidarr_data
.downloads
.set_items(vec![download_record()]);
app.data.lidarr_data.prompt_confirm = true;
app.push_navigation_stack(base_route.into());
app.push_navigation_stack(prompt_block.into());
DownloadsHandler::new(SUBMIT_KEY, &mut app, prompt_block, None).handle();
assert!(app.data.lidarr_data.prompt_confirm);
assert_some_eq_x!(
&app.data.lidarr_data.prompt_confirm_action,
&expected_action
);
assert_navigation_popped!(app, base_route.into());
}
#[rstest]
#[case(ActiveLidarrBlock::Downloads, ActiveLidarrBlock::DeleteDownloadPrompt)]
#[case(ActiveLidarrBlock::Downloads, ActiveLidarrBlock::UpdateDownloadsPrompt)]
fn test_downloads_prompt_decline_submit(
#[case] base_route: ActiveLidarrBlock,
#[case] prompt_block: ActiveLidarrBlock,
) {
let mut app = App::test_default();
app
.data
.lidarr_data
.downloads
.set_items(vec![DownloadRecord::default()]);
app.push_navigation_stack(base_route.into());
app.push_navigation_stack(prompt_block.into());
DownloadsHandler::new(SUBMIT_KEY, &mut app, prompt_block, None).handle();
assert!(!app.data.lidarr_data.prompt_confirm);
assert_none!(app.data.lidarr_data.prompt_confirm_action);
assert_navigation_popped!(app, base_route.into());
}
}
mod test_handle_esc {
use rstest::rstest;
use super::*;
use crate::assert_navigation_popped;
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
#[rstest]
#[case(ActiveLidarrBlock::Downloads, ActiveLidarrBlock::DeleteDownloadPrompt)]
#[case(ActiveLidarrBlock::Downloads, ActiveLidarrBlock::UpdateDownloadsPrompt)]
fn test_downloads_prompt_blocks_esc(
#[case] base_block: ActiveLidarrBlock,
#[case] prompt_block: ActiveLidarrBlock,
) {
let mut app = App::test_default();
app.push_navigation_stack(base_block.into());
app.push_navigation_stack(prompt_block.into());
app.data.lidarr_data.prompt_confirm = true;
DownloadsHandler::new(ESC_KEY, &mut app, prompt_block, None).handle();
assert_navigation_popped!(app, base_block.into());
assert!(!app.data.lidarr_data.prompt_confirm);
}
#[rstest]
fn test_default_esc(#[values(true, false)] is_ready: bool) {
let mut app = App::test_default();
app.is_loading = is_ready;
app.error = "test error".to_owned().into();
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
DownloadsHandler::new(ESC_KEY, &mut app, ActiveLidarrBlock::Downloads, None).handle();
assert_navigation_popped!(app, ActiveLidarrBlock::Downloads.into());
assert_is_empty!(app.error.text);
}
}
mod test_handle_key_char {
use super::*;
use crate::assert_navigation_popped;
use crate::network::lidarr_network::LidarrEvent;
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::download_record;
use pretty_assertions::assert_eq;
use rstest::rstest;
#[test]
fn test_update_downloads_key() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
app
.data
.lidarr_data
.downloads
.set_items(vec![DownloadRecord::default()]);
DownloadsHandler::new(
DEFAULT_KEYBINDINGS.update.key,
&mut app,
ActiveLidarrBlock::Downloads,
None,
)
.handle();
assert_navigation_pushed!(app, ActiveLidarrBlock::UpdateDownloadsPrompt.into());
}
#[test]
fn test_update_downloads_key_no_op_when_not_ready() {
let mut app = App::test_default();
app.is_loading = true;
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
app
.data
.lidarr_data
.downloads
.set_items(vec![DownloadRecord::default()]);
DownloadsHandler::new(
DEFAULT_KEYBINDINGS.update.key,
&mut app,
ActiveLidarrBlock::Downloads,
None,
)
.handle();
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Downloads.into());
}
#[test]
fn test_refresh_downloads_key() {
let mut app = App::test_default();
app
.data
.lidarr_data
.downloads
.set_items(vec![DownloadRecord::default()]);
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
DownloadsHandler::new(
DEFAULT_KEYBINDINGS.refresh.key,
&mut app,
ActiveLidarrBlock::Downloads,
None,
)
.handle();
assert_navigation_pushed!(app, ActiveLidarrBlock::Downloads.into());
assert!(app.should_refresh);
}
#[test]
fn test_refresh_downloads_key_no_op_when_not_ready() {
let mut app = App::test_default();
app.is_loading = true;
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
app
.data
.lidarr_data
.downloads
.set_items(vec![DownloadRecord::default()]);
DownloadsHandler::new(
DEFAULT_KEYBINDINGS.refresh.key,
&mut app,
ActiveLidarrBlock::Downloads,
None,
)
.handle();
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Downloads.into());
assert!(!app.should_refresh);
}
#[rstest]
#[case(
ActiveLidarrBlock::Downloads,
ActiveLidarrBlock::DeleteDownloadPrompt,
LidarrEvent::DeleteDownload(1)
)]
#[case(
ActiveLidarrBlock::Downloads,
ActiveLidarrBlock::UpdateDownloadsPrompt,
LidarrEvent::UpdateDownloads
)]
fn test_downloads_prompt_confirm_submit(
#[case] base_route: ActiveLidarrBlock,
#[case] prompt_block: ActiveLidarrBlock,
#[case] expected_action: LidarrEvent,
) {
let mut app = App::test_default();
app
.data
.lidarr_data
.downloads
.set_items(vec![download_record()]);
app.push_navigation_stack(base_route.into());
app.push_navigation_stack(prompt_block.into());
DownloadsHandler::new(
DEFAULT_KEYBINDINGS.confirm.key,
&mut app,
prompt_block,
None,
)
.handle();
assert!(app.data.lidarr_data.prompt_confirm);
assert_some_eq_x!(
&app.data.lidarr_data.prompt_confirm_action,
&expected_action
);
assert_navigation_popped!(app, base_route.into());
}
}
#[test]
fn test_downloads_handler_accepts() {
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
if DOWNLOADS_BLOCKS.contains(&active_lidarr_block) {
assert!(DownloadsHandler::accepts(active_lidarr_block));
} else {
assert!(!DownloadsHandler::accepts(active_lidarr_block));
}
})
}
#[rstest]
fn test_downloads_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = DownloadsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_extract_download_id() {
let mut app = App::test_default();
app
.data
.lidarr_data
.downloads
.set_items(vec![download_record()]);
let download_id = DownloadsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::Downloads,
None,
)
.extract_download_id();
assert_eq!(download_id, 1);
}
#[test]
fn test_downloads_handler_not_ready_when_loading() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
app.is_loading = true;
let handler = DownloadsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::Downloads,
None,
);
assert!(!handler.is_ready());
}
#[test]
fn test_downloads_handler_not_ready_when_downloads_is_empty() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
app.is_loading = false;
let handler = DownloadsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::Downloads,
None,
);
assert!(!handler.is_ready());
}
#[test]
fn test_downloads_handler_ready_when_not_loading_and_downloads_is_not_empty() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Downloads.into());
app.is_loading = false;
app
.data
.lidarr_data
.downloads
.set_items(vec![DownloadRecord::default()]);
let handler = DownloadsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::Downloads,
None,
);
assert!(handler.is_ready());
}
}
@@ -0,0 +1,171 @@
use crate::app::App;
use crate::event::Key;
use crate::handlers::lidarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
use crate::handlers::{KeyEventHandler, handle_clear_errors, handle_prompt_toggle};
use crate::matches_key;
use crate::models::Route;
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, DOWNLOADS_BLOCKS};
use crate::network::lidarr_network::LidarrEvent;
#[cfg(test)]
#[path = "downloads_handler_tests.rs"]
mod downloads_handler_tests;
pub(super) struct DownloadsHandler<'a, 'b> {
key: Key,
app: &'a mut App<'b>,
active_lidarr_block: ActiveLidarrBlock,
_context: Option<ActiveLidarrBlock>,
}
impl DownloadsHandler<'_, '_> {
fn extract_download_id(&self) -> i64 {
self.app.data.lidarr_data.downloads.current_selection().id
}
}
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for DownloadsHandler<'a, 'b> {
fn handle(&mut self) {
let download_table_handling_config =
TableHandlingConfig::new(ActiveLidarrBlock::Downloads.into());
if !handle_table(
self,
|app| &mut app.data.lidarr_data.downloads,
download_table_handling_config,
) {
self.handle_key_event();
}
}
fn accepts(active_block: ActiveLidarrBlock) -> bool {
DOWNLOADS_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
active_block: ActiveLidarrBlock,
_context: Option<ActiveLidarrBlock>,
) -> DownloadsHandler<'a, 'b> {
DownloadsHandler {
key,
app,
active_lidarr_block: active_block,
_context,
}
}
fn get_key(&self) -> Key {
self.key
}
fn is_ready(&self) -> bool {
!self.app.is_loading && !self.app.data.lidarr_data.downloads.is_empty()
}
fn handle_scroll_up(&mut self) {}
fn handle_scroll_down(&mut self) {}
fn handle_home(&mut self) {}
fn handle_end(&mut self) {}
fn handle_delete(&mut self) {
if self.active_lidarr_block == ActiveLidarrBlock::Downloads {
self
.app
.push_navigation_stack(ActiveLidarrBlock::DeleteDownloadPrompt.into())
}
}
fn handle_left_right_action(&mut self) {
match self.active_lidarr_block {
ActiveLidarrBlock::Downloads => handle_change_tab_left_right_keys(self.app, self.key),
ActiveLidarrBlock::DeleteDownloadPrompt | ActiveLidarrBlock::UpdateDownloadsPrompt => {
handle_prompt_toggle(self.app, self.key)
}
_ => (),
}
}
fn handle_submit(&mut self) {
match self.active_lidarr_block {
ActiveLidarrBlock::DeleteDownloadPrompt => {
if self.app.data.lidarr_data.prompt_confirm {
self.app.data.lidarr_data.prompt_confirm_action =
Some(LidarrEvent::DeleteDownload(self.extract_download_id()));
}
self.app.pop_navigation_stack();
}
ActiveLidarrBlock::UpdateDownloadsPrompt => {
if self.app.data.lidarr_data.prompt_confirm {
self.app.data.lidarr_data.prompt_confirm_action = Some(LidarrEvent::UpdateDownloads);
}
self.app.pop_navigation_stack();
}
_ => (),
}
}
fn handle_esc(&mut self) {
match self.active_lidarr_block {
ActiveLidarrBlock::DeleteDownloadPrompt | ActiveLidarrBlock::UpdateDownloadsPrompt => {
self.app.pop_navigation_stack();
self.app.data.lidarr_data.prompt_confirm = false;
}
_ => handle_clear_errors(self.app),
}
}
fn handle_char_key_event(&mut self) {
let key = self.key;
match self.active_lidarr_block {
ActiveLidarrBlock::Downloads => match self.key {
_ if matches_key!(update, key) => {
self
.app
.push_navigation_stack(ActiveLidarrBlock::UpdateDownloadsPrompt.into());
}
_ if matches_key!(refresh, key) => {
self.app.should_refresh = true;
}
_ => (),
},
ActiveLidarrBlock::DeleteDownloadPrompt => {
if matches_key!(confirm, key) {
self.app.data.lidarr_data.prompt_confirm = true;
self.app.data.lidarr_data.prompt_confirm_action =
Some(LidarrEvent::DeleteDownload(self.extract_download_id()));
self.app.pop_navigation_stack();
}
}
ActiveLidarrBlock::UpdateDownloadsPrompt => {
if matches_key!(confirm, key) {
self.app.data.lidarr_data.prompt_confirm = true;
self.app.data.lidarr_data.prompt_confirm_action = Some(LidarrEvent::UpdateDownloads);
self.app.pop_navigation_stack();
}
}
_ => (),
}
}
fn app_mut(&mut self) -> &mut App<'b> {
self.app
}
fn current_route(&self) -> Route {
self.app.get_current_route()
}
}
@@ -0,0 +1,397 @@
#[cfg(test)]
mod tests {
use std::cmp::Ordering;
use chrono::DateTime;
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::App;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::assert_navigation_pushed;
use crate::event::Key;
use crate::handlers::KeyEventHandler;
use crate::handlers::lidarr_handlers::history::{HistoryHandler, history_sorting_options};
use crate::models::lidarr_models::{LidarrHistoryEventType, LidarrHistoryItem};
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, HISTORY_BLOCKS};
use crate::models::servarr_models::{Quality, QualityWrapper};
mod test_handle_left_right_action {
use pretty_assertions::assert_eq;
use rstest::rstest;
use super::*;
use crate::assert_navigation_pushed;
#[rstest]
fn test_history_tab_left(#[values(true, false)] is_ready: bool) {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::History.into());
app.is_loading = is_ready;
app.data.lidarr_data.main_tabs.set_index(2);
HistoryHandler::new(
DEFAULT_KEYBINDINGS.left.key,
&mut app,
ActiveLidarrBlock::History,
None,
)
.handle();
assert_eq!(
app.data.lidarr_data.main_tabs.get_active_route(),
ActiveLidarrBlock::Downloads.into()
);
assert_navigation_pushed!(app, ActiveLidarrBlock::Downloads.into());
}
#[rstest]
fn test_history_tab_right(#[values(true, false)] is_ready: bool) {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::History.into());
app.is_loading = is_ready;
app.data.lidarr_data.main_tabs.set_index(2);
HistoryHandler::new(
DEFAULT_KEYBINDINGS.right.key,
&mut app,
ActiveLidarrBlock::History,
None,
)
.handle();
assert_eq!(
app.data.lidarr_data.main_tabs.get_active_route(),
ActiveLidarrBlock::RootFolders.into()
);
assert_eq!(
app.get_current_route(),
ActiveLidarrBlock::RootFolders.into()
);
}
}
mod test_handle_submit {
use pretty_assertions::assert_eq;
use super::*;
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
#[test]
fn test_history_submit() {
let mut app = App::test_default();
app.data.lidarr_data.history.set_items(history_vec());
app.push_navigation_stack(ActiveLidarrBlock::History.into());
HistoryHandler::new(SUBMIT_KEY, &mut app, ActiveLidarrBlock::History, None).handle();
assert_navigation_pushed!(app, ActiveLidarrBlock::HistoryItemDetails.into());
}
#[test]
fn test_history_submit_no_op_when_not_ready() {
let mut app = App::test_default();
app.is_loading = true;
app.data.lidarr_data.history.set_items(history_vec());
app.push_navigation_stack(ActiveLidarrBlock::History.into());
HistoryHandler::new(SUBMIT_KEY, &mut app, ActiveLidarrBlock::History, None).handle();
assert_eq!(app.get_current_route(), ActiveLidarrBlock::History.into());
}
}
mod test_handle_esc {
use pretty_assertions::assert_eq;
use rstest::rstest;
use super::*;
use crate::assert_navigation_popped;
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
#[test]
fn test_esc_history_item_details() {
let mut app = App::test_default();
app
.data
.lidarr_data
.history
.set_items(vec![LidarrHistoryItem::default()]);
app.push_navigation_stack(ActiveLidarrBlock::History.into());
app.push_navigation_stack(ActiveLidarrBlock::HistoryItemDetails.into());
HistoryHandler::new(
ESC_KEY,
&mut app,
ActiveLidarrBlock::HistoryItemDetails,
None,
)
.handle();
assert_navigation_popped!(app, ActiveLidarrBlock::History.into());
}
#[rstest]
fn test_default_esc(#[values(true, false)] is_ready: bool) {
let mut app = App::test_default();
app.is_loading = is_ready;
app.error = "test error".to_owned().into();
app.push_navigation_stack(ActiveLidarrBlock::History.into());
app.push_navigation_stack(ActiveLidarrBlock::History.into());
app
.data
.lidarr_data
.history
.set_items(vec![LidarrHistoryItem::default()]);
HistoryHandler::new(ESC_KEY, &mut app, ActiveLidarrBlock::History, None).handle();
assert_eq!(app.get_current_route(), ActiveLidarrBlock::History.into());
assert_is_empty!(app.error.text);
}
}
mod test_handle_key_char {
use pretty_assertions::assert_eq;
use super::*;
use crate::assert_navigation_pushed;
#[test]
fn test_refresh_history_key() {
let mut app = App::test_default();
app.data.lidarr_data.history.set_items(history_vec());
app.push_navigation_stack(ActiveLidarrBlock::History.into());
HistoryHandler::new(
DEFAULT_KEYBINDINGS.refresh.key,
&mut app,
ActiveLidarrBlock::History,
None,
)
.handle();
assert_navigation_pushed!(app, ActiveLidarrBlock::History.into());
assert!(app.should_refresh);
}
#[test]
fn test_refresh_history_key_no_op_when_not_ready() {
let mut app = App::test_default();
app.is_loading = true;
app.data.lidarr_data.history.set_items(history_vec());
app.push_navigation_stack(ActiveLidarrBlock::History.into());
HistoryHandler::new(
DEFAULT_KEYBINDINGS.refresh.key,
&mut app,
ActiveLidarrBlock::History,
None,
)
.handle();
assert_eq!(app.get_current_route(), ActiveLidarrBlock::History.into());
assert!(!app.should_refresh);
}
}
#[test]
fn test_history_sorting_options_source_title() {
let expected_cmp_fn: fn(&LidarrHistoryItem, &LidarrHistoryItem) -> Ordering = |a, b| {
a.source_title
.text
.to_lowercase()
.cmp(&b.source_title.text.to_lowercase())
};
let mut expected_history_vec = history_vec();
expected_history_vec.sort_by(expected_cmp_fn);
let sort_option = history_sorting_options()[0].clone();
let mut sorted_history_vec = history_vec();
sorted_history_vec.sort_by(sort_option.cmp_fn.unwrap());
assert_eq!(sorted_history_vec, expected_history_vec);
assert_str_eq!(sort_option.name, "Source Title");
}
#[test]
fn test_history_sorting_options_event_type() {
let expected_cmp_fn: fn(&LidarrHistoryItem, &LidarrHistoryItem) -> Ordering = |a, b| {
a.event_type
.to_string()
.to_lowercase()
.cmp(&b.event_type.to_string().to_lowercase())
};
let mut expected_history_vec = history_vec();
expected_history_vec.sort_by(expected_cmp_fn);
let sort_option = history_sorting_options()[1].clone();
let mut sorted_history_vec = history_vec();
sorted_history_vec.sort_by(sort_option.cmp_fn.unwrap());
assert_eq!(sorted_history_vec, expected_history_vec);
assert_str_eq!(sort_option.name, "Event Type");
}
#[test]
fn test_history_sorting_options_quality() {
let expected_cmp_fn: fn(&LidarrHistoryItem, &LidarrHistoryItem) -> Ordering = |a, b| {
a.quality
.quality
.name
.to_lowercase()
.cmp(&b.quality.quality.name.to_lowercase())
};
let mut expected_history_vec = history_vec();
expected_history_vec.sort_by(expected_cmp_fn);
let sort_option = history_sorting_options()[2].clone();
let mut sorted_history_vec = history_vec();
sorted_history_vec.sort_by(sort_option.cmp_fn.unwrap());
assert_eq!(sorted_history_vec, expected_history_vec);
assert_str_eq!(sort_option.name, "Quality");
}
#[test]
fn test_history_sorting_options_date() {
let expected_cmp_fn: fn(&LidarrHistoryItem, &LidarrHistoryItem) -> Ordering =
|a, b| a.date.cmp(&b.date);
let mut expected_history_vec = history_vec();
expected_history_vec.sort_by(expected_cmp_fn);
let sort_option = history_sorting_options()[3].clone();
let mut sorted_history_vec = history_vec();
sorted_history_vec.sort_by(sort_option.cmp_fn.unwrap());
assert_eq!(sorted_history_vec, expected_history_vec);
assert_str_eq!(sort_option.name, "Date");
}
#[test]
fn test_history_handler_accepts() {
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
if HISTORY_BLOCKS.contains(&active_lidarr_block) {
assert!(HistoryHandler::accepts(active_lidarr_block));
} else {
assert!(!HistoryHandler::accepts(active_lidarr_block));
}
})
}
#[rstest]
fn test_history_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = HistoryHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_history_handler_not_ready_when_loading() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::History.into());
app.is_loading = true;
let handler = HistoryHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::History,
None,
);
assert!(!handler.is_ready());
}
#[test]
fn test_history_handler_not_ready_when_history_is_empty() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::History.into());
app.is_loading = false;
let handler = HistoryHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::History,
None,
);
assert!(!handler.is_ready());
}
#[test]
fn test_history_handler_ready_when_not_loading_and_history_is_not_empty() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::History.into());
app.is_loading = false;
app
.data
.lidarr_data
.history
.set_items(vec![LidarrHistoryItem::default()]);
let handler = HistoryHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::History,
None,
);
assert!(handler.is_ready());
}
fn history_vec() -> Vec<LidarrHistoryItem> {
vec![
LidarrHistoryItem {
id: 3,
source_title: "test 1".into(),
event_type: LidarrHistoryEventType::Grabbed,
quality: QualityWrapper {
quality: Quality {
name: "FLAC".to_owned(),
},
},
date: DateTime::from(DateTime::parse_from_rfc3339("2024-01-10T07:28:45Z").unwrap()),
..LidarrHistoryItem::default()
},
LidarrHistoryItem {
id: 2,
source_title: "test 2".into(),
event_type: LidarrHistoryEventType::DownloadImported,
quality: QualityWrapper {
quality: Quality {
name: "MP3-320".to_owned(),
},
},
date: DateTime::from(DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap()),
..LidarrHistoryItem::default()
},
LidarrHistoryItem {
id: 1,
source_title: "test 3".into(),
event_type: LidarrHistoryEventType::TrackFileDeleted,
quality: QualityWrapper {
quality: Quality {
name: "FLAC".to_owned(),
},
},
date: DateTime::from(DateTime::parse_from_rfc3339("2024-03-10T07:28:45Z").unwrap()),
..LidarrHistoryItem::default()
},
]
}
}
+165
View File
@@ -0,0 +1,165 @@
use crate::app::App;
use crate::event::Key;
use crate::handlers::lidarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
use crate::handlers::{KeyEventHandler, handle_clear_errors};
use crate::matches_key;
use crate::models::Route;
use crate::models::lidarr_models::LidarrHistoryItem;
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, HISTORY_BLOCKS};
use crate::models::stateful_table::SortOption;
#[cfg(test)]
#[path = "history_handler_tests.rs"]
mod history_handler_tests;
pub(super) struct HistoryHandler<'a, 'b> {
key: Key,
app: &'a mut App<'b>,
active_lidarr_block: ActiveLidarrBlock,
_context: Option<ActiveLidarrBlock>,
}
impl HistoryHandler<'_, '_> {}
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for HistoryHandler<'a, 'b> {
fn handle(&mut self) {
let history_table_handling_config = TableHandlingConfig::new(ActiveLidarrBlock::History.into())
.sorting_block(ActiveLidarrBlock::HistorySortPrompt.into())
.sort_options(history_sorting_options())
.searching_block(ActiveLidarrBlock::SearchHistory.into())
.search_error_block(ActiveLidarrBlock::SearchHistoryError.into())
.search_field_fn(|history| &history.source_title.text)
.filtering_block(ActiveLidarrBlock::FilterHistory.into())
.filter_error_block(ActiveLidarrBlock::FilterHistoryError.into())
.filter_field_fn(|history| &history.source_title.text);
if !handle_table(
self,
|app| &mut app.data.lidarr_data.history,
history_table_handling_config,
) {
self.handle_key_event();
}
}
fn accepts(active_block: ActiveLidarrBlock) -> bool {
HISTORY_BLOCKS.contains(&active_block)
}
fn new(
key: Key,
app: &'a mut App<'b>,
active_block: ActiveLidarrBlock,
context: Option<ActiveLidarrBlock>,
) -> Self {
HistoryHandler {
key,
app,
active_lidarr_block: active_block,
_context: context,
}
}
fn get_key(&self) -> Key {
self.key
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn is_ready(&self) -> bool {
!self.app.is_loading && !self.app.data.lidarr_data.history.is_empty()
}
fn handle_scroll_up(&mut self) {}
fn handle_scroll_down(&mut self) {}
fn handle_home(&mut self) {}
fn handle_end(&mut self) {}
fn handle_delete(&mut self) {}
fn handle_left_right_action(&mut self) {
if self.active_lidarr_block == ActiveLidarrBlock::History {
handle_change_tab_left_right_keys(self.app, self.key)
}
}
fn handle_submit(&mut self) {
if self.active_lidarr_block == ActiveLidarrBlock::History {
self
.app
.push_navigation_stack(ActiveLidarrBlock::HistoryItemDetails.into());
}
}
fn handle_esc(&mut self) {
if self.active_lidarr_block == ActiveLidarrBlock::HistoryItemDetails {
self.app.pop_navigation_stack();
} else {
handle_clear_errors(self.app);
}
}
fn handle_char_key_event(&mut self) {
let key = self.key;
if self.active_lidarr_block == ActiveLidarrBlock::History {
match self.key {
_ if matches_key!(refresh, key) => {
self.app.should_refresh = true;
}
_ => (),
}
}
}
fn app_mut(&mut self) -> &mut App<'b> {
self.app
}
fn current_route(&self) -> Route {
self.app.get_current_route()
}
}
pub(in crate::handlers::lidarr_handlers) fn history_sorting_options()
-> Vec<SortOption<LidarrHistoryItem>> {
vec![
SortOption {
name: "Source Title",
cmp_fn: Some(|a, b| {
a.source_title
.text
.to_lowercase()
.cmp(&b.source_title.text.to_lowercase())
}),
},
SortOption {
name: "Event Type",
cmp_fn: Some(|a, b| {
a.event_type
.to_string()
.to_lowercase()
.cmp(&b.event_type.to_string().to_lowercase())
}),
},
SortOption {
name: "Quality",
cmp_fn: Some(|a, b| {
a.quality
.quality
.name
.to_lowercase()
.cmp(&b.quality.quality.name.to_lowercase())
}),
},
SortOption {
name: "Date",
cmp_fn: Some(|a, b| a.date.cmp(&b.date)),
},
]
}
@@ -0,0 +1,533 @@
use crate::app::App;
use crate::event::Key;
use crate::handlers::{KeyEventHandler, handle_prompt_toggle};
use crate::models::Route;
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, EDIT_INDEXER_BLOCKS};
use crate::models::servarr_data::modals::EditIndexerModal;
use crate::models::servarr_models::EditIndexerParams;
use crate::network::lidarr_network::LidarrEvent;
use crate::{
handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys, matches_key,
};
#[cfg(test)]
#[path = "edit_indexer_handler_tests.rs"]
mod edit_indexer_handler_tests;
pub(super) struct EditIndexerHandler<'a, 'b> {
key: Key,
app: &'a mut App<'b>,
active_lidarr_block: ActiveLidarrBlock,
_context: Option<ActiveLidarrBlock>,
}
impl EditIndexerHandler<'_, '_> {
fn build_edit_indexer_params(&mut self) -> EditIndexerParams {
let edit_indexer_modal = self
.app
.data
.lidarr_data
.edit_indexer_modal
.take()
.expect("EditIndexerModal is None");
let indexer_id = self.app.data.lidarr_data.indexers.current_selection().id;
let tags = edit_indexer_modal.tags.text;
let EditIndexerModal {
name,
enable_rss,
enable_automatic_search,
enable_interactive_search,
url,
api_key,
seed_ratio,
priority,
..
} = edit_indexer_modal;
EditIndexerParams {
indexer_id,
name: Some(name.text),
enable_rss,
enable_automatic_search,
enable_interactive_search,
url: Some(url.text),
api_key: Some(api_key.text),
seed_ratio: Some(seed_ratio.text),
tags: None,
tag_input_string: Some(tags),
priority: Some(priority),
clear_tags: false,
}
}
}
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for EditIndexerHandler<'a, 'b> {
fn accepts(active_block: ActiveLidarrBlock) -> bool {
EDIT_INDEXER_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
active_block: ActiveLidarrBlock,
_context: Option<ActiveLidarrBlock>,
) -> EditIndexerHandler<'a, 'b> {
EditIndexerHandler {
key,
app,
active_lidarr_block: active_block,
_context,
}
}
fn get_key(&self) -> Key {
self.key
}
fn is_ready(&self) -> bool {
!self.app.is_loading && self.app.data.lidarr_data.edit_indexer_modal.is_some()
}
fn handle_scroll_up(&mut self) {
match self.active_lidarr_block {
ActiveLidarrBlock::EditIndexerPrompt => {
self.app.data.lidarr_data.selected_block.up();
}
ActiveLidarrBlock::EditIndexerPriorityInput => {
self
.app
.data
.lidarr_data
.edit_indexer_modal
.as_mut()
.unwrap()
.priority += 1;
}
_ => (),
}
}
fn handle_scroll_down(&mut self) {
match self.active_lidarr_block {
ActiveLidarrBlock::EditIndexerPrompt => {
self.app.data.lidarr_data.selected_block.down();
}
ActiveLidarrBlock::EditIndexerPriorityInput => {
let edit_indexer_modal = self
.app
.data
.lidarr_data
.edit_indexer_modal
.as_mut()
.unwrap();
if edit_indexer_modal.priority > 1 {
edit_indexer_modal.priority -= 1;
}
}
_ => (),
}
}
fn handle_home(&mut self) {
match self.active_lidarr_block {
ActiveLidarrBlock::EditIndexerNameInput => {
self
.app
.data
.lidarr_data
.edit_indexer_modal
.as_mut()
.unwrap()
.name
.scroll_home();
}
ActiveLidarrBlock::EditIndexerUrlInput => {
self
.app
.data
.lidarr_data
.edit_indexer_modal
.as_mut()
.unwrap()
.url
.scroll_home();
}
ActiveLidarrBlock::EditIndexerApiKeyInput => {
self
.app
.data
.lidarr_data
.edit_indexer_modal
.as_mut()
.unwrap()
.api_key
.scroll_home();
}
ActiveLidarrBlock::EditIndexerSeedRatioInput => {
self
.app
.data
.lidarr_data
.edit_indexer_modal
.as_mut()
.unwrap()
.seed_ratio
.scroll_home();
}
ActiveLidarrBlock::EditIndexerTagsInput => {
self
.app
.data
.lidarr_data
.edit_indexer_modal
.as_mut()
.unwrap()
.tags
.scroll_home();
}
_ => (),
}
}
fn handle_end(&mut self) {
match self.active_lidarr_block {
ActiveLidarrBlock::EditIndexerNameInput => {
self
.app
.data
.lidarr_data
.edit_indexer_modal
.as_mut()
.unwrap()
.name
.reset_offset();
}
ActiveLidarrBlock::EditIndexerUrlInput => {
self
.app
.data
.lidarr_data
.edit_indexer_modal
.as_mut()
.unwrap()
.url
.reset_offset();
}
ActiveLidarrBlock::EditIndexerApiKeyInput => {
self
.app
.data
.lidarr_data
.edit_indexer_modal
.as_mut()
.unwrap()
.api_key
.reset_offset();
}
ActiveLidarrBlock::EditIndexerSeedRatioInput => {
self
.app
.data
.lidarr_data
.edit_indexer_modal
.as_mut()
.unwrap()
.seed_ratio
.reset_offset();
}
ActiveLidarrBlock::EditIndexerTagsInput => {
self
.app
.data
.lidarr_data
.edit_indexer_modal
.as_mut()
.unwrap()
.tags
.reset_offset();
}
_ => (),
}
}
fn handle_delete(&mut self) {}
fn handle_left_right_action(&mut self) {
match self.active_lidarr_block {
ActiveLidarrBlock::EditIndexerPrompt => {
handle_prompt_left_right_keys!(
self,
ActiveLidarrBlock::EditIndexerConfirmPrompt,
lidarr_data
);
}
ActiveLidarrBlock::EditIndexerNameInput => {
handle_text_box_left_right_keys!(
self,
self.key,
self
.app
.data
.lidarr_data
.edit_indexer_modal
.as_mut()
.unwrap()
.name
);
}
ActiveLidarrBlock::EditIndexerUrlInput => {
handle_text_box_left_right_keys!(
self,
self.key,
self
.app
.data
.lidarr_data
.edit_indexer_modal
.as_mut()
.unwrap()
.url
);
}
ActiveLidarrBlock::EditIndexerApiKeyInput => {
handle_text_box_left_right_keys!(
self,
self.key,
self
.app
.data
.lidarr_data
.edit_indexer_modal
.as_mut()
.unwrap()
.api_key
);
}
ActiveLidarrBlock::EditIndexerSeedRatioInput => {
handle_text_box_left_right_keys!(
self,
self.key,
self
.app
.data
.lidarr_data
.edit_indexer_modal
.as_mut()
.unwrap()
.seed_ratio
);
}
ActiveLidarrBlock::EditIndexerTagsInput => {
handle_text_box_left_right_keys!(
self,
self.key,
self
.app
.data
.lidarr_data
.edit_indexer_modal
.as_mut()
.unwrap()
.tags
);
}
_ => (),
}
}
fn handle_submit(&mut self) {
match self.active_lidarr_block {
ActiveLidarrBlock::EditIndexerPrompt => {
let selected_block = self.app.data.lidarr_data.selected_block.get_active_block();
match selected_block {
ActiveLidarrBlock::EditIndexerConfirmPrompt => {
if self.app.data.lidarr_data.prompt_confirm {
self.app.data.lidarr_data.prompt_confirm_action =
Some(LidarrEvent::EditIndexer(self.build_edit_indexer_params()));
self.app.should_refresh = true;
} else {
self.app.data.lidarr_data.edit_indexer_modal = None;
}
self.app.pop_navigation_stack();
}
ActiveLidarrBlock::EditIndexerNameInput
| ActiveLidarrBlock::EditIndexerUrlInput
| ActiveLidarrBlock::EditIndexerApiKeyInput
| ActiveLidarrBlock::EditIndexerSeedRatioInput
| ActiveLidarrBlock::EditIndexerTagsInput => {
self.app.push_navigation_stack(selected_block.into());
self.app.ignore_special_keys_for_textbox_input = true;
}
ActiveLidarrBlock::EditIndexerPriorityInput => self
.app
.push_navigation_stack(ActiveLidarrBlock::EditIndexerPriorityInput.into()),
ActiveLidarrBlock::EditIndexerToggleEnableRss => {
let indexer = self
.app
.data
.lidarr_data
.edit_indexer_modal
.as_mut()
.unwrap();
indexer.enable_rss = Some(!indexer.enable_rss.unwrap_or_default());
}
ActiveLidarrBlock::EditIndexerToggleEnableAutomaticSearch => {
let indexer = self
.app
.data
.lidarr_data
.edit_indexer_modal
.as_mut()
.unwrap();
indexer.enable_automatic_search =
Some(!indexer.enable_automatic_search.unwrap_or_default());
}
ActiveLidarrBlock::EditIndexerToggleEnableInteractiveSearch => {
let indexer = self
.app
.data
.lidarr_data
.edit_indexer_modal
.as_mut()
.unwrap();
indexer.enable_interactive_search =
Some(!indexer.enable_interactive_search.unwrap_or_default());
}
_ => (),
}
}
ActiveLidarrBlock::EditIndexerNameInput
| ActiveLidarrBlock::EditIndexerUrlInput
| ActiveLidarrBlock::EditIndexerApiKeyInput
| ActiveLidarrBlock::EditIndexerSeedRatioInput
| ActiveLidarrBlock::EditIndexerTagsInput => {
self.app.pop_navigation_stack();
self.app.ignore_special_keys_for_textbox_input = false;
}
ActiveLidarrBlock::EditIndexerPriorityInput => self.app.pop_navigation_stack(),
_ => (),
}
}
fn handle_esc(&mut self) {
match self.active_lidarr_block {
ActiveLidarrBlock::EditIndexerPrompt => {
self.app.pop_navigation_stack();
self.app.data.lidarr_data.prompt_confirm = false;
self.app.data.lidarr_data.edit_indexer_modal = None;
}
ActiveLidarrBlock::EditIndexerNameInput
| ActiveLidarrBlock::EditIndexerUrlInput
| ActiveLidarrBlock::EditIndexerApiKeyInput
| ActiveLidarrBlock::EditIndexerSeedRatioInput
| ActiveLidarrBlock::EditIndexerPriorityInput
| ActiveLidarrBlock::EditIndexerTagsInput => {
self.app.pop_navigation_stack();
self.app.ignore_special_keys_for_textbox_input = false;
}
_ => self.app.pop_navigation_stack(),
}
}
fn handle_char_key_event(&mut self) {
match self.active_lidarr_block {
ActiveLidarrBlock::EditIndexerNameInput => {
handle_text_box_keys!(
self,
self.key,
self
.app
.data
.lidarr_data
.edit_indexer_modal
.as_mut()
.unwrap()
.name
);
}
ActiveLidarrBlock::EditIndexerUrlInput => {
handle_text_box_keys!(
self,
self.key,
self
.app
.data
.lidarr_data
.edit_indexer_modal
.as_mut()
.unwrap()
.url
);
}
ActiveLidarrBlock::EditIndexerApiKeyInput => {
handle_text_box_keys!(
self,
self.key,
self
.app
.data
.lidarr_data
.edit_indexer_modal
.as_mut()
.unwrap()
.api_key
);
}
ActiveLidarrBlock::EditIndexerSeedRatioInput => {
handle_text_box_keys!(
self,
self.key,
self
.app
.data
.lidarr_data
.edit_indexer_modal
.as_mut()
.unwrap()
.seed_ratio
);
}
ActiveLidarrBlock::EditIndexerTagsInput => {
handle_text_box_keys!(
self,
self.key,
self
.app
.data
.lidarr_data
.edit_indexer_modal
.as_mut()
.unwrap()
.tags
);
}
ActiveLidarrBlock::EditIndexerPrompt => {
if self.app.data.lidarr_data.selected_block.get_active_block()
== ActiveLidarrBlock::EditIndexerConfirmPrompt
&& matches_key!(confirm, self.key)
{
self.app.data.lidarr_data.prompt_confirm = true;
self.app.data.lidarr_data.prompt_confirm_action =
Some(LidarrEvent::EditIndexer(self.build_edit_indexer_params()));
self.app.should_refresh = true;
self.app.pop_navigation_stack();
}
}
_ => (),
}
}
fn app_mut(&mut self) -> &mut App<'b> {
self.app
}
fn current_route(&self) -> Route {
self.app.get_current_route()
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,209 @@
use crate::app::App;
use crate::event::Key;
use crate::handlers::{KeyEventHandler, handle_prompt_toggle};
use crate::models::Route;
use crate::models::servarr_data::lidarr::lidarr_data::{
ActiveLidarrBlock, INDEXER_SETTINGS_BLOCKS,
};
use crate::models::servarr_models::IndexerSettings;
use crate::network::lidarr_network::LidarrEvent;
use crate::{handle_prompt_left_right_keys, matches_key};
#[cfg(test)]
#[path = "edit_indexer_settings_handler_tests.rs"]
mod edit_indexer_settings_handler_tests;
pub(super) struct IndexerSettingsHandler<'a, 'b> {
key: Key,
app: &'a mut App<'b>,
active_lidarr_block: ActiveLidarrBlock,
_context: Option<ActiveLidarrBlock>,
}
impl IndexerSettingsHandler<'_, '_> {
fn build_edit_indexer_settings_params(&mut self) -> IndexerSettings {
self
.app
.data
.lidarr_data
.indexer_settings
.take()
.expect("IndexerSettings is None")
}
}
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for IndexerSettingsHandler<'a, 'b> {
fn accepts(active_block: ActiveLidarrBlock) -> bool {
INDEXER_SETTINGS_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
active_block: ActiveLidarrBlock,
_context: Option<ActiveLidarrBlock>,
) -> IndexerSettingsHandler<'a, 'b> {
IndexerSettingsHandler {
key,
app,
active_lidarr_block: active_block,
_context,
}
}
fn get_key(&self) -> Key {
self.key
}
fn is_ready(&self) -> bool {
!self.app.is_loading && self.app.data.lidarr_data.indexer_settings.is_some()
}
fn handle_scroll_up(&mut self) {
let indexer_settings = self.app.data.lidarr_data.indexer_settings.as_mut().unwrap();
match self.active_lidarr_block {
ActiveLidarrBlock::AllIndexerSettingsPrompt => {
self.app.data.lidarr_data.selected_block.up();
}
ActiveLidarrBlock::IndexerSettingsMinimumAgeInput => {
indexer_settings.minimum_age += 1;
}
ActiveLidarrBlock::IndexerSettingsRetentionInput => {
indexer_settings.retention += 1;
}
ActiveLidarrBlock::IndexerSettingsMaximumSizeInput => {
indexer_settings.maximum_size += 1;
}
ActiveLidarrBlock::IndexerSettingsRssSyncIntervalInput => {
indexer_settings.rss_sync_interval += 1;
}
_ => (),
}
}
fn handle_scroll_down(&mut self) {
let indexer_settings = self.app.data.lidarr_data.indexer_settings.as_mut().unwrap();
match self.active_lidarr_block {
ActiveLidarrBlock::AllIndexerSettingsPrompt => {
self.app.data.lidarr_data.selected_block.down()
}
ActiveLidarrBlock::IndexerSettingsMinimumAgeInput => {
if indexer_settings.minimum_age > 0 {
indexer_settings.minimum_age -= 1;
}
}
ActiveLidarrBlock::IndexerSettingsRetentionInput => {
if indexer_settings.retention > 0 {
indexer_settings.retention -= 1;
}
}
ActiveLidarrBlock::IndexerSettingsMaximumSizeInput => {
if indexer_settings.maximum_size > 0 {
indexer_settings.maximum_size -= 1;
}
}
ActiveLidarrBlock::IndexerSettingsRssSyncIntervalInput => {
if indexer_settings.rss_sync_interval > 0 {
indexer_settings.rss_sync_interval -= 1;
}
}
_ => (),
}
}
fn handle_home(&mut self) {}
fn handle_end(&mut self) {}
fn handle_delete(&mut self) {}
fn handle_left_right_action(&mut self) {
if self.active_lidarr_block == ActiveLidarrBlock::AllIndexerSettingsPrompt {
handle_prompt_left_right_keys!(
self,
ActiveLidarrBlock::IndexerSettingsConfirmPrompt,
lidarr_data
);
}
}
fn handle_submit(&mut self) {
match self.active_lidarr_block {
ActiveLidarrBlock::AllIndexerSettingsPrompt => {
match self.app.data.lidarr_data.selected_block.get_active_block() {
ActiveLidarrBlock::IndexerSettingsConfirmPrompt => {
if self.app.data.lidarr_data.prompt_confirm {
self.app.data.lidarr_data.prompt_confirm_action = Some(
LidarrEvent::EditAllIndexerSettings(self.build_edit_indexer_settings_params()),
);
self.app.should_refresh = true;
} else {
self.app.data.lidarr_data.indexer_settings = None;
}
self.app.pop_navigation_stack();
}
ActiveLidarrBlock::IndexerSettingsMinimumAgeInput
| ActiveLidarrBlock::IndexerSettingsRetentionInput
| ActiveLidarrBlock::IndexerSettingsMaximumSizeInput
| ActiveLidarrBlock::IndexerSettingsRssSyncIntervalInput => {
self.app.push_navigation_stack(
(
self.app.data.lidarr_data.selected_block.get_active_block(),
None,
)
.into(),
)
}
_ => (),
}
}
ActiveLidarrBlock::IndexerSettingsMinimumAgeInput
| ActiveLidarrBlock::IndexerSettingsRetentionInput
| ActiveLidarrBlock::IndexerSettingsMaximumSizeInput
| ActiveLidarrBlock::IndexerSettingsRssSyncIntervalInput => self.app.pop_navigation_stack(),
_ => (),
}
}
fn handle_esc(&mut self) {
match self.active_lidarr_block {
ActiveLidarrBlock::AllIndexerSettingsPrompt => {
self.app.pop_navigation_stack();
self.app.data.lidarr_data.prompt_confirm = false;
self.app.data.lidarr_data.indexer_settings = None;
}
_ => self.app.pop_navigation_stack(),
}
}
fn handle_char_key_event(&mut self) {
if self.active_lidarr_block == ActiveLidarrBlock::AllIndexerSettingsPrompt
&& self.app.data.lidarr_data.selected_block.get_active_block()
== ActiveLidarrBlock::IndexerSettingsConfirmPrompt
&& matches_key!(confirm, self.key)
{
self.app.data.lidarr_data.prompt_confirm = true;
self.app.data.lidarr_data.prompt_confirm_action = Some(LidarrEvent::EditAllIndexerSettings(
self.build_edit_indexer_settings_params(),
));
self.app.should_refresh = true;
self.app.pop_navigation_stack();
}
}
fn app_mut(&mut self) -> &mut App<'b> {
self.app
}
fn current_route(&self) -> Route {
self.app.get_current_route()
}
}
@@ -0,0 +1,609 @@
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::App;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::assert_modal_absent;
use crate::assert_navigation_pushed;
use crate::event::Key;
use crate::handlers::KeyEventHandler;
use crate::handlers::lidarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler;
use crate::models::servarr_data::lidarr::lidarr_data::{
ActiveLidarrBlock, INDEXER_SETTINGS_BLOCKS,
};
use crate::models::servarr_models::IndexerSettings;
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::indexer_settings;
mod test_handle_scroll_up_and_down {
use pretty_assertions::assert_eq;
use rstest::rstest;
use crate::models::BlockSelectionState;
use crate::models::servarr_data::lidarr::lidarr_data::INDEXER_SETTINGS_SELECTION_BLOCKS;
use crate::models::servarr_models::IndexerSettings;
use super::*;
macro_rules! test_i64_counter_scroll_value {
($block:expr, $key:expr, $data_ref:ident, $negatives:literal) => {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.data.lidarr_data.indexer_settings = Some(IndexerSettings::default());
IndexerSettingsHandler::new($key, &mut app, $block, None).handle();
if $key == Key::Up {
assert_eq!(
app
.data
.lidarr_data
.indexer_settings
.as_ref()
.unwrap()
.$data_ref,
1
);
} else {
if $negatives {
assert_eq!(
app
.data
.lidarr_data
.indexer_settings
.as_ref()
.unwrap()
.$data_ref,
-1
);
} else {
assert_eq!(
app
.data
.lidarr_data
.indexer_settings
.as_ref()
.unwrap()
.$data_ref,
0
);
IndexerSettingsHandler::new(Key::Up, &mut app, $block, None).handle();
assert_eq!(
app
.data
.lidarr_data
.indexer_settings
.as_ref()
.unwrap()
.$data_ref,
1
);
IndexerSettingsHandler::new($key, &mut app, $block, None).handle();
assert_eq!(
app
.data
.lidarr_data
.indexer_settings
.as_ref()
.unwrap()
.$data_ref,
0
);
}
}
};
}
#[rstest]
fn test_edit_indexer_settings_prompt_scroll(#[values(Key::Up, Key::Down)] key: Key) {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.data.lidarr_data.indexer_settings = Some(IndexerSettings::default());
app.data.lidarr_data.selected_block =
BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS);
app.data.lidarr_data.selected_block.down();
IndexerSettingsHandler::new(
key,
&mut app,
ActiveLidarrBlock::AllIndexerSettingsPrompt,
None,
)
.handle();
if key == Key::Up {
assert_eq!(
app.data.lidarr_data.selected_block.get_active_block(),
ActiveLidarrBlock::IndexerSettingsMinimumAgeInput
);
} else {
assert_eq!(
app.data.lidarr_data.selected_block.get_active_block(),
ActiveLidarrBlock::IndexerSettingsMaximumSizeInput
);
}
}
#[rstest]
fn test_edit_indexer_settings_prompt_scroll_no_op_when_not_ready(
#[values(Key::Up, Key::Down)] key: Key,
) {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.is_loading = true;
app.data.lidarr_data.indexer_settings = Some(IndexerSettings::default());
app.data.lidarr_data.selected_block =
BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS);
app.data.lidarr_data.selected_block.down();
IndexerSettingsHandler::new(
key,
&mut app,
ActiveLidarrBlock::AllIndexerSettingsPrompt,
None,
)
.handle();
assert_eq!(
app.data.lidarr_data.selected_block.get_active_block(),
ActiveLidarrBlock::IndexerSettingsRetentionInput
);
}
#[rstest]
fn test_edit_indexer_settings_minimum_age_scroll(#[values(Key::Up, Key::Down)] key: Key) {
test_i64_counter_scroll_value!(
ActiveLidarrBlock::IndexerSettingsMinimumAgeInput,
key,
minimum_age,
false
);
}
#[rstest]
fn test_edit_indexer_settings_retention_scroll(#[values(Key::Up, Key::Down)] key: Key) {
test_i64_counter_scroll_value!(
ActiveLidarrBlock::IndexerSettingsRetentionInput,
key,
retention,
false
);
}
#[rstest]
fn test_edit_indexer_settings_maximum_size_scroll(#[values(Key::Up, Key::Down)] key: Key) {
test_i64_counter_scroll_value!(
ActiveLidarrBlock::IndexerSettingsMaximumSizeInput,
key,
maximum_size,
false
);
}
#[rstest]
fn test_edit_indexer_settings_rss_sync_interval_scroll(#[values(Key::Up, Key::Down)] key: Key) {
test_i64_counter_scroll_value!(
ActiveLidarrBlock::IndexerSettingsRssSyncIntervalInput,
key,
rss_sync_interval,
false
);
}
}
mod test_handle_left_right_action {
use crate::models::servarr_data::lidarr::lidarr_data::INDEXER_SETTINGS_SELECTION_BLOCKS;
use crate::models::BlockSelectionState;
use rstest::rstest;
use super::*;
#[rstest]
fn test_left_right_prompt_toggle(#[values(Key::Left, Key::Right)] key: Key) {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.data.lidarr_data.selected_block =
BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS);
app.data.lidarr_data.selected_block.y = INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1;
IndexerSettingsHandler::new(
key,
&mut app,
ActiveLidarrBlock::AllIndexerSettingsPrompt,
None,
)
.handle();
assert!(app.data.lidarr_data.prompt_confirm);
IndexerSettingsHandler::new(
key,
&mut app,
ActiveLidarrBlock::AllIndexerSettingsPrompt,
None,
)
.handle();
assert!(!app.data.lidarr_data.prompt_confirm);
}
}
mod test_handle_submit {
use pretty_assertions::assert_eq;
use rstest::rstest;
use crate::{
assert_navigation_popped,
models::{
BlockSelectionState, servarr_data::lidarr::lidarr_data::INDEXER_SETTINGS_SELECTION_BLOCKS,
servarr_models::IndexerSettings,
},
network::lidarr_network::LidarrEvent,
};
use super::*;
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
#[test]
fn test_edit_indexer_settings_prompt_prompt_decline_submit() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.push_navigation_stack(ActiveLidarrBlock::AllIndexerSettingsPrompt.into());
app.data.lidarr_data.selected_block =
BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS);
app
.data
.lidarr_data
.selected_block
.set_index(0, INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1);
app.data.lidarr_data.indexer_settings = Some(IndexerSettings::default());
IndexerSettingsHandler::new(
SUBMIT_KEY,
&mut app,
ActiveLidarrBlock::AllIndexerSettingsPrompt,
None,
)
.handle();
assert_navigation_popped!(app, ActiveLidarrBlock::Indexers.into());
assert_none!(app.data.lidarr_data.prompt_confirm_action);
assert!(!app.should_refresh);
assert_none!(app.data.lidarr_data.indexer_settings);
}
#[test]
fn test_edit_indexer_settings_prompt_prompt_confirmation_submit() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.push_navigation_stack(ActiveLidarrBlock::AllIndexerSettingsPrompt.into());
app.data.lidarr_data.selected_block =
BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS);
app
.data
.lidarr_data
.selected_block
.set_index(0, INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1);
app.data.lidarr_data.indexer_settings = Some(indexer_settings());
app.data.lidarr_data.prompt_confirm = true;
IndexerSettingsHandler::new(
SUBMIT_KEY,
&mut app,
ActiveLidarrBlock::AllIndexerSettingsPrompt,
None,
)
.handle();
assert_navigation_popped!(app, ActiveLidarrBlock::Indexers.into());
assert_some_eq_x!(
&app.data.lidarr_data.prompt_confirm_action,
&LidarrEvent::EditAllIndexerSettings(indexer_settings())
);
assert_modal_absent!(app.data.lidarr_data.indexer_settings);
assert!(app.should_refresh);
}
#[test]
fn test_edit_indexer_settings_prompt_prompt_confirmation_submit_no_op_when_not_ready() {
let mut app = App::test_default();
app.is_loading = true;
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.push_navigation_stack(ActiveLidarrBlock::AllIndexerSettingsPrompt.into());
app.data.lidarr_data.indexer_settings = Some(IndexerSettings::default());
app.data.lidarr_data.prompt_confirm = true;
IndexerSettingsHandler::new(
SUBMIT_KEY,
&mut app,
ActiveLidarrBlock::AllIndexerSettingsPrompt,
None,
)
.handle();
assert_eq!(
app.get_current_route(),
ActiveLidarrBlock::AllIndexerSettingsPrompt.into()
);
assert!(!app.should_refresh);
}
#[rstest]
#[case(ActiveLidarrBlock::IndexerSettingsMinimumAgeInput, 0)]
#[case(ActiveLidarrBlock::IndexerSettingsRetentionInput, 1)]
#[case(ActiveLidarrBlock::IndexerSettingsMaximumSizeInput, 2)]
#[case(ActiveLidarrBlock::IndexerSettingsRssSyncIntervalInput, 3)]
fn test_edit_indexer_settings_prompt_submit_selected_block(
#[case] selected_block: ActiveLidarrBlock,
#[case] y_index: usize,
) {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.data.lidarr_data.indexer_settings = Some(IndexerSettings::default());
app.push_navigation_stack(ActiveLidarrBlock::AllIndexerSettingsPrompt.into());
app.data.lidarr_data.selected_block =
BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS);
app.data.lidarr_data.selected_block.set_index(0, y_index);
IndexerSettingsHandler::new(
SUBMIT_KEY,
&mut app,
ActiveLidarrBlock::AllIndexerSettingsPrompt,
None,
)
.handle();
assert_navigation_pushed!(app, selected_block.into());
}
#[rstest]
fn test_edit_indexer_settings_prompt_submit_selected_block_no_op_when_not_ready(
#[values(0, 1, 2, 3, 4)] y_index: usize,
) {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.is_loading = true;
app.data.lidarr_data.indexer_settings = Some(IndexerSettings::default());
app.push_navigation_stack(ActiveLidarrBlock::AllIndexerSettingsPrompt.into());
app.data.lidarr_data.selected_block =
BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS);
app.data.lidarr_data.selected_block.set_index(0, y_index);
IndexerSettingsHandler::new(
SUBMIT_KEY,
&mut app,
ActiveLidarrBlock::AllIndexerSettingsPrompt,
None,
)
.handle();
assert_eq!(
app.get_current_route(),
ActiveLidarrBlock::AllIndexerSettingsPrompt.into()
);
}
#[rstest]
fn test_edit_indexer_settings_selected_block_submit(
#[values(
ActiveLidarrBlock::IndexerSettingsMinimumAgeInput,
ActiveLidarrBlock::IndexerSettingsRetentionInput,
ActiveLidarrBlock::IndexerSettingsMaximumSizeInput,
ActiveLidarrBlock::IndexerSettingsRssSyncIntervalInput
)]
active_lidarr_block: ActiveLidarrBlock,
) {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.data.lidarr_data.indexer_settings = Some(IndexerSettings::default());
app.push_navigation_stack(ActiveLidarrBlock::AllIndexerSettingsPrompt.into());
app.push_navigation_stack(active_lidarr_block.into());
IndexerSettingsHandler::new(SUBMIT_KEY, &mut app, active_lidarr_block, None).handle();
assert_navigation_popped!(app, ActiveLidarrBlock::AllIndexerSettingsPrompt.into());
}
}
mod test_handle_esc {
use rstest::rstest;
use crate::models::servarr_models::IndexerSettings;
use super::*;
use crate::assert_navigation_popped;
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
#[rstest]
fn test_edit_indexer_settings_prompt_esc(#[values(true, false)] is_ready: bool) {
let mut app = App::test_default();
app.is_loading = is_ready;
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.push_navigation_stack(ActiveLidarrBlock::AllIndexerSettingsPrompt.into());
app.data.lidarr_data.indexer_settings = Some(IndexerSettings::default());
IndexerSettingsHandler::new(
ESC_KEY,
&mut app,
ActiveLidarrBlock::AllIndexerSettingsPrompt,
None,
)
.handle();
assert_navigation_popped!(app, ActiveLidarrBlock::Indexers.into());
assert!(!app.data.lidarr_data.prompt_confirm);
assert_none!(app.data.lidarr_data.indexer_settings);
}
#[rstest]
fn test_edit_indexer_settings_selected_blocks_esc(
#[values(
ActiveLidarrBlock::IndexerSettingsMinimumAgeInput,
ActiveLidarrBlock::IndexerSettingsRetentionInput,
ActiveLidarrBlock::IndexerSettingsMaximumSizeInput,
ActiveLidarrBlock::IndexerSettingsRssSyncIntervalInput
)]
active_lidarr_block: ActiveLidarrBlock,
) {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.push_navigation_stack(active_lidarr_block.into());
app.data.lidarr_data.indexer_settings = Some(IndexerSettings::default());
IndexerSettingsHandler::new(ESC_KEY, &mut app, active_lidarr_block, None).handle();
assert_navigation_popped!(app, ActiveLidarrBlock::Indexers.into());
assert_some_eq_x!(
&app.data.lidarr_data.indexer_settings,
&IndexerSettings::default()
);
}
}
mod test_handle_key_char {
use crate::{
assert_navigation_popped,
models::{
BlockSelectionState, servarr_data::lidarr::lidarr_data::INDEXER_SETTINGS_SELECTION_BLOCKS,
},
network::lidarr_network::LidarrEvent,
};
use super::*;
#[test]
fn test_edit_indexer_settings_prompt_prompt_confirmation_confirm() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.push_navigation_stack(ActiveLidarrBlock::AllIndexerSettingsPrompt.into());
app.data.lidarr_data.selected_block =
BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS);
app
.data
.lidarr_data
.selected_block
.set_index(0, INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1);
app.data.lidarr_data.indexer_settings = Some(indexer_settings());
IndexerSettingsHandler::new(
DEFAULT_KEYBINDINGS.confirm.key,
&mut app,
ActiveLidarrBlock::AllIndexerSettingsPrompt,
None,
)
.handle();
assert_navigation_popped!(app, ActiveLidarrBlock::Indexers.into());
assert_some_eq_x!(
&app.data.lidarr_data.prompt_confirm_action,
&LidarrEvent::EditAllIndexerSettings(indexer_settings())
);
assert_modal_absent!(app.data.lidarr_data.indexer_settings);
assert!(app.should_refresh);
}
}
#[test]
fn test_indexer_settings_handler_accepts() {
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
if INDEXER_SETTINGS_BLOCKS.contains(&active_lidarr_block) {
assert!(IndexerSettingsHandler::accepts(active_lidarr_block));
} else {
assert!(!IndexerSettingsHandler::accepts(active_lidarr_block));
}
})
}
#[rstest]
fn test_indexer_settings_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = IndexerSettingsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_build_edit_indexer_settings_params() {
let mut app = App::test_default();
app.data.lidarr_data.indexer_settings = Some(indexer_settings());
let actual_indexer_settings = IndexerSettingsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::AllIndexerSettingsPrompt,
None,
)
.build_edit_indexer_settings_params();
assert_eq!(actual_indexer_settings, indexer_settings());
assert_modal_absent!(app.data.lidarr_data.indexer_settings);
}
#[test]
fn test_edit_indexer_settings_handler_not_ready_when_loading() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.is_loading = true;
let handler = IndexerSettingsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::AllIndexerSettingsPrompt,
None,
);
assert!(!handler.is_ready());
}
#[test]
fn test_edit_indexer_settings_handler_not_ready_when_indexer_settings_is_none() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.is_loading = false;
let handler = IndexerSettingsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::AllIndexerSettingsPrompt,
None,
);
assert!(!handler.is_ready());
}
#[test]
fn test_edit_indexer_settings_handler_ready_when_not_loading_and_indexer_settings_is_some() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.is_loading = false;
app.data.lidarr_data.indexer_settings = Some(IndexerSettings::default());
let handler = IndexerSettingsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::AllIndexerSettingsPrompt,
None,
);
assert!(handler.is_ready());
}
}
@@ -0,0 +1,717 @@
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::App;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::assert_navigation_pushed;
use crate::event::Key;
use crate::handlers::KeyEventHandler;
use crate::handlers::lidarr_handlers::indexers::IndexersHandler;
use crate::models::servarr_data::lidarr::lidarr_data::{
ActiveLidarrBlock, EDIT_INDEXER_BLOCKS, INDEXER_SETTINGS_BLOCKS, INDEXERS_BLOCKS,
};
use crate::models::servarr_models::Indexer;
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::indexer;
use crate::test_handler_delegation;
mod test_handle_delete {
use pretty_assertions::assert_eq;
use super::*;
const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key;
#[test]
fn test_delete_indexer_prompt() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app
.data
.lidarr_data
.indexers
.set_items(vec![Indexer::default()]);
IndexersHandler::new(DELETE_KEY, &mut app, ActiveLidarrBlock::Indexers, None).handle();
assert_navigation_pushed!(app, ActiveLidarrBlock::DeleteIndexerPrompt.into());
}
#[test]
fn test_delete_indexer_prompt_no_op_when_not_ready() {
let mut app = App::test_default();
app.is_loading = true;
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app
.data
.lidarr_data
.indexers
.set_items(vec![Indexer::default()]);
IndexersHandler::new(DELETE_KEY, &mut app, ActiveLidarrBlock::Indexers, None).handle();
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Indexers.into());
}
}
mod test_handle_left_right_action {
use pretty_assertions::assert_eq;
use rstest::rstest;
use super::*;
#[rstest]
fn test_indexers_tab_left(#[values(true, false)] is_ready: bool) {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.is_loading = is_ready;
app.data.lidarr_data.main_tabs.set_index(4);
IndexersHandler::new(
DEFAULT_KEYBINDINGS.left.key,
&mut app,
ActiveLidarrBlock::Indexers,
None,
)
.handle();
assert_eq!(
app.data.lidarr_data.main_tabs.get_active_route(),
ActiveLidarrBlock::RootFolders.into()
);
assert_navigation_pushed!(app, ActiveLidarrBlock::RootFolders.into());
}
#[rstest]
fn test_indexers_tab_right(#[values(true, false)] is_ready: bool) {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.is_loading = is_ready;
app.data.lidarr_data.main_tabs.set_index(4);
IndexersHandler::new(
DEFAULT_KEYBINDINGS.right.key,
&mut app,
ActiveLidarrBlock::Indexers,
None,
)
.handle();
assert_eq!(
app.data.lidarr_data.main_tabs.get_active_route(),
ActiveLidarrBlock::System.into()
);
assert_navigation_pushed!(app, ActiveLidarrBlock::System.into());
}
#[rstest]
fn test_left_right_delete_indexer_prompt_toggle(
#[values(DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.right.key)] key: Key,
) {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
IndexersHandler::new(key, &mut app, ActiveLidarrBlock::DeleteIndexerPrompt, None).handle();
assert!(app.data.lidarr_data.prompt_confirm);
IndexersHandler::new(key, &mut app, ActiveLidarrBlock::DeleteIndexerPrompt, None).handle();
assert!(!app.data.lidarr_data.prompt_confirm);
}
}
mod test_handle_submit {
use super::*;
use crate::assert_navigation_popped;
use crate::models::servarr_data::lidarr::lidarr_data::{
EDIT_INDEXER_NZB_SELECTION_BLOCKS, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, LidarrData,
};
use crate::models::servarr_data::modals::EditIndexerModal;
use crate::models::servarr_models::{Indexer, IndexerField};
use crate::network::lidarr_network::LidarrEvent;
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::indexer;
use bimap::BiMap;
use pretty_assertions::assert_eq;
use serde_json::{Number, Value};
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
#[rstest]
fn test_edit_indexer_submit(#[values(true, false)] torrent_protocol: bool) {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
let protocol = if torrent_protocol {
"torrent".to_owned()
} else {
"usenet".to_owned()
};
let mut expected_edit_indexer_modal = EditIndexerModal {
name: "Test".into(),
enable_rss: Some(true),
enable_automatic_search: Some(true),
enable_interactive_search: Some(true),
url: "https://test.com".into(),
api_key: "1234".into(),
tags: "usenet, test".into(),
..EditIndexerModal::default()
};
let mut lidarr_data = LidarrData {
tags_map: BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".to_owned())]),
..LidarrData::default()
};
let mut fields = vec![
IndexerField {
name: Some("baseUrl".to_owned()),
value: Some(Value::String("https://test.com".to_owned())),
},
IndexerField {
name: Some("apiKey".to_owned()),
value: Some(Value::String("1234".to_owned())),
},
];
if torrent_protocol {
fields.push(IndexerField {
name: Some("seedCriteria.seedRatio".to_owned()),
value: Some(Value::from(1.2f64)),
});
expected_edit_indexer_modal.seed_ratio = "1.2".into();
}
let indexer = Indexer {
name: Some("Test".to_owned()),
enable_rss: true,
enable_automatic_search: true,
enable_interactive_search: true,
protocol,
tags: vec![Number::from(1), Number::from(2)],
fields: Some(fields),
..Indexer::default()
};
lidarr_data.indexers.set_items(vec![indexer]);
app.data.lidarr_data = lidarr_data;
IndexersHandler::new(SUBMIT_KEY, &mut app, ActiveLidarrBlock::Indexers, None).handle();
assert_navigation_pushed!(app, ActiveLidarrBlock::EditIndexerPrompt.into());
assert_some_eq_x!(
&app.data.lidarr_data.edit_indexer_modal,
&EditIndexerModal::from(&app.data.lidarr_data)
);
assert_some_eq_x!(
&app.data.lidarr_data.edit_indexer_modal,
&expected_edit_indexer_modal
);
if torrent_protocol {
assert_eq!(
app.data.lidarr_data.selected_block.blocks,
EDIT_INDEXER_TORRENT_SELECTION_BLOCKS
);
} else {
assert_eq!(
app.data.lidarr_data.selected_block.blocks,
EDIT_INDEXER_NZB_SELECTION_BLOCKS
);
}
}
#[test]
fn test_edit_indexer_submit_no_op_when_not_ready() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.is_loading = true;
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app
.data
.lidarr_data
.indexers
.set_items(vec![Indexer::default()]);
IndexersHandler::new(SUBMIT_KEY, &mut app, ActiveLidarrBlock::Indexers, None).handle();
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Indexers.into());
assert_none!(app.data.lidarr_data.edit_indexer_modal);
}
#[test]
fn test_delete_indexer_prompt_confirm_submit() {
let mut app = App::test_default();
app.data.lidarr_data.indexers.set_items(vec![indexer()]);
app.data.lidarr_data.prompt_confirm = true;
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.push_navigation_stack(ActiveLidarrBlock::DeleteIndexerPrompt.into());
IndexersHandler::new(
SUBMIT_KEY,
&mut app,
ActiveLidarrBlock::DeleteIndexerPrompt,
None,
)
.handle();
assert!(app.data.lidarr_data.prompt_confirm);
assert_some_eq_x!(
&app.data.lidarr_data.prompt_confirm_action,
&LidarrEvent::DeleteIndexer(1)
);
assert_navigation_popped!(app, ActiveLidarrBlock::Indexers.into());
}
#[test]
fn test_prompt_decline_submit() {
let mut app = App::test_default();
app
.data
.lidarr_data
.indexers
.set_items(vec![Indexer::default()]);
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.push_navigation_stack(ActiveLidarrBlock::DeleteIndexerPrompt.into());
IndexersHandler::new(
SUBMIT_KEY,
&mut app,
ActiveLidarrBlock::DeleteIndexerPrompt,
None,
)
.handle();
assert!(!app.data.lidarr_data.prompt_confirm);
assert_none!(app.data.lidarr_data.prompt_confirm_action);
assert_navigation_popped!(app, ActiveLidarrBlock::Indexers.into());
}
}
mod test_handle_esc {
use super::*;
use crate::assert_navigation_popped;
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
#[rstest]
fn test_delete_indexer_prompt_block_esc(#[values(true, false)] is_ready: bool) {
let mut app = App::test_default();
app.is_loading = is_ready;
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.push_navigation_stack(ActiveLidarrBlock::DeleteIndexerPrompt.into());
app.data.lidarr_data.prompt_confirm = true;
IndexersHandler::new(
ESC_KEY,
&mut app,
ActiveLidarrBlock::DeleteIndexerPrompt,
None,
)
.handle();
assert_navigation_popped!(app, ActiveLidarrBlock::Indexers.into());
assert!(!app.data.lidarr_data.prompt_confirm);
}
#[rstest]
fn test_test_indexer_esc(#[values(true, false)] is_ready: bool) {
let mut app = App::test_default();
app.is_loading = is_ready;
app.data.lidarr_data.indexer_test_errors = Some("test result".to_owned());
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.push_navigation_stack(ActiveLidarrBlock::TestIndexer.into());
IndexersHandler::new(ESC_KEY, &mut app, ActiveLidarrBlock::TestIndexer, None).handle();
assert_navigation_popped!(app, ActiveLidarrBlock::Indexers.into());
assert_none!(app.data.lidarr_data.indexer_test_errors);
}
#[rstest]
fn test_default_esc(#[values(true, false)] is_ready: bool) {
let mut app = App::test_default();
app.is_loading = is_ready;
app.error = "test error".to_owned().into();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
IndexersHandler::new(ESC_KEY, &mut app, ActiveLidarrBlock::Indexers, None).handle();
assert_navigation_popped!(app, ActiveLidarrBlock::Indexers.into());
assert_is_empty!(app.error.text);
}
}
mod test_handle_key_char {
use pretty_assertions::assert_eq;
use super::*;
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::indexer;
use crate::{
assert_navigation_popped,
models::servarr_data::lidarr::lidarr_data::INDEXER_SETTINGS_SELECTION_BLOCKS,
network::lidarr_network::LidarrEvent,
};
#[test]
fn test_refresh_indexers_key() {
let mut app = App::test_default();
app
.data
.lidarr_data
.indexers
.set_items(vec![Indexer::default()]);
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
IndexersHandler::new(
DEFAULT_KEYBINDINGS.refresh.key,
&mut app,
ActiveLidarrBlock::Indexers,
None,
)
.handle();
assert_navigation_pushed!(app, ActiveLidarrBlock::Indexers.into());
assert!(app.should_refresh);
}
#[test]
fn test_refresh_indexers_key_no_op_when_not_ready() {
let mut app = App::test_default();
app.is_loading = true;
app
.data
.lidarr_data
.indexers
.set_items(vec![Indexer::default()]);
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
IndexersHandler::new(
DEFAULT_KEYBINDINGS.refresh.key,
&mut app,
ActiveLidarrBlock::Indexers,
None,
)
.handle();
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Indexers.into());
assert!(!app.should_refresh);
}
#[test]
fn test_indexer_settings_key() {
let mut app = App::test_default();
app
.data
.lidarr_data
.indexers
.set_items(vec![Indexer::default()]);
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app
.data
.lidarr_data
.indexers
.set_items(vec![Indexer::default()]);
IndexersHandler::new(
DEFAULT_KEYBINDINGS.settings.key,
&mut app,
ActiveLidarrBlock::Indexers,
None,
)
.handle();
assert_navigation_pushed!(app, ActiveLidarrBlock::AllIndexerSettingsPrompt.into());
assert_eq!(
app.data.lidarr_data.selected_block.blocks,
INDEXER_SETTINGS_SELECTION_BLOCKS
);
}
#[test]
fn test_indexer_settings_key_no_op_when_not_ready() {
let mut app = App::test_default();
app.is_loading = true;
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app
.data
.lidarr_data
.indexers
.set_items(vec![Indexer::default()]);
IndexersHandler::new(
DEFAULT_KEYBINDINGS.settings.key,
&mut app,
ActiveLidarrBlock::Indexers,
None,
)
.handle();
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Indexers.into());
}
#[test]
fn test_test_key() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app
.data
.lidarr_data
.indexers
.set_items(vec![Indexer::default()]);
IndexersHandler::new(
DEFAULT_KEYBINDINGS.test.key,
&mut app,
ActiveLidarrBlock::Indexers,
None,
)
.handle();
assert_navigation_pushed!(app, ActiveLidarrBlock::TestIndexer.into());
}
#[test]
fn test_test_key_no_op_when_not_ready() {
let mut app = App::test_default();
app.is_loading = true;
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app
.data
.lidarr_data
.indexers
.set_items(vec![Indexer::default()]);
IndexersHandler::new(
DEFAULT_KEYBINDINGS.test.key,
&mut app,
ActiveLidarrBlock::Indexers,
None,
)
.handle();
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Indexers.into());
}
#[test]
fn test_test_all_key() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app
.data
.lidarr_data
.indexers
.set_items(vec![Indexer::default()]);
IndexersHandler::new(
DEFAULT_KEYBINDINGS.test_all.key,
&mut app,
ActiveLidarrBlock::Indexers,
None,
)
.handle();
assert_navigation_pushed!(app, ActiveLidarrBlock::TestAllIndexers.into());
}
#[test]
fn test_test_all_key_no_op_when_not_ready() {
let mut app = App::test_default();
app.is_loading = true;
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app
.data
.lidarr_data
.indexers
.set_items(vec![Indexer::default()]);
IndexersHandler::new(
DEFAULT_KEYBINDINGS.test_all.key,
&mut app,
ActiveLidarrBlock::Indexers,
None,
)
.handle();
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Indexers.into());
}
#[test]
fn test_delete_indexer_prompt_confirm() {
let mut app = App::test_default();
app.data.lidarr_data.indexers.set_items(vec![indexer()]);
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.push_navigation_stack(ActiveLidarrBlock::DeleteIndexerPrompt.into());
IndexersHandler::new(
DEFAULT_KEYBINDINGS.confirm.key,
&mut app,
ActiveLidarrBlock::DeleteIndexerPrompt,
None,
)
.handle();
assert!(app.data.lidarr_data.prompt_confirm);
assert_some_eq_x!(
&app.data.lidarr_data.prompt_confirm_action,
&LidarrEvent::DeleteIndexer(1)
);
assert_navigation_popped!(app, ActiveLidarrBlock::Indexers.into());
}
}
#[rstest]
fn test_delegates_edit_indexer_blocks_to_edit_indexer_handler(
#[values(
ActiveLidarrBlock::EditIndexerPrompt,
ActiveLidarrBlock::EditIndexerConfirmPrompt,
ActiveLidarrBlock::EditIndexerApiKeyInput,
ActiveLidarrBlock::EditIndexerNameInput,
ActiveLidarrBlock::EditIndexerSeedRatioInput,
ActiveLidarrBlock::EditIndexerToggleEnableRss,
ActiveLidarrBlock::EditIndexerToggleEnableAutomaticSearch,
ActiveLidarrBlock::EditIndexerToggleEnableInteractiveSearch,
ActiveLidarrBlock::EditIndexerUrlInput,
ActiveLidarrBlock::EditIndexerTagsInput
)]
active_lidarr_block: ActiveLidarrBlock,
) {
test_handler_delegation!(
IndexersHandler,
ActiveLidarrBlock::Indexers,
active_lidarr_block
);
}
#[rstest]
fn test_delegates_indexer_settings_blocks_to_indexer_settings_handler(
#[values(
ActiveLidarrBlock::AllIndexerSettingsPrompt,
ActiveLidarrBlock::IndexerSettingsConfirmPrompt,
ActiveLidarrBlock::IndexerSettingsMaximumSizeInput,
ActiveLidarrBlock::IndexerSettingsMinimumAgeInput,
ActiveLidarrBlock::IndexerSettingsRetentionInput,
ActiveLidarrBlock::IndexerSettingsRssSyncIntervalInput
)]
active_lidarr_block: ActiveLidarrBlock,
) {
test_handler_delegation!(
IndexersHandler,
ActiveLidarrBlock::Indexers,
active_lidarr_block
);
}
#[test]
fn test_delegates_test_all_indexers_block_to_test_all_indexers_handler() {
test_handler_delegation!(
IndexersHandler,
ActiveLidarrBlock::Indexers,
ActiveLidarrBlock::TestAllIndexers
);
}
#[test]
fn test_indexers_handler_accepts() {
let mut indexers_blocks = Vec::new();
indexers_blocks.extend(INDEXERS_BLOCKS);
indexers_blocks.extend(INDEXER_SETTINGS_BLOCKS);
indexers_blocks.extend(EDIT_INDEXER_BLOCKS);
indexers_blocks.push(ActiveLidarrBlock::TestAllIndexers);
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
if indexers_blocks.contains(&active_lidarr_block) {
assert!(IndexersHandler::accepts(active_lidarr_block));
} else {
assert!(!IndexersHandler::accepts(active_lidarr_block));
}
})
}
#[rstest]
fn test_indexers_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = IndexersHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_extract_indexer_id() {
let mut app = App::test_default();
app.data.lidarr_data.indexers.set_items(vec![indexer()]);
let indexer_id = IndexersHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::Indexers,
None,
)
.extract_indexer_id();
assert_eq!(indexer_id, 1);
}
#[test]
fn test_indexers_handler_not_ready_when_loading() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.is_loading = true;
let handler = IndexersHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::Indexers,
None,
);
assert!(!handler.is_ready());
}
#[test]
fn test_indexers_handler_not_ready_when_indexers_is_empty() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.is_loading = false;
let handler = IndexersHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::Indexers,
None,
);
assert!(!handler.is_ready());
}
#[test]
fn test_indexers_handler_ready_when_not_loading_and_indexers_is_not_empty() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.is_loading = false;
app
.data
.lidarr_data
.indexers
.set_items(vec![Indexer::default()]);
let handler = IndexersHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::Indexers,
None,
);
assert!(handler.is_ready());
}
}
@@ -0,0 +1,217 @@
use crate::app::App;
use crate::event::Key;
use crate::handlers::lidarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::lidarr_handlers::indexers::edit_indexer_handler::EditIndexerHandler;
use crate::handlers::lidarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler;
use crate::handlers::lidarr_handlers::indexers::test_all_indexers_handler::TestAllIndexersHandler;
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
use crate::handlers::{KeyEventHandler, handle_clear_errors, handle_prompt_toggle};
use crate::matches_key;
use crate::models::servarr_data::lidarr::lidarr_data::{
ActiveLidarrBlock, EDIT_INDEXER_NZB_SELECTION_BLOCKS, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS,
INDEXER_SETTINGS_SELECTION_BLOCKS, INDEXERS_BLOCKS,
};
use crate::models::{BlockSelectionState, Route};
use crate::network::lidarr_network::LidarrEvent;
mod edit_indexer_handler;
mod edit_indexer_settings_handler;
mod test_all_indexers_handler;
#[cfg(test)]
#[path = "indexers_handler_tests.rs"]
mod indexers_handler_tests;
pub(super) struct IndexersHandler<'a, 'b> {
key: Key,
app: &'a mut App<'b>,
active_lidarr_block: ActiveLidarrBlock,
context: Option<ActiveLidarrBlock>,
}
impl IndexersHandler<'_, '_> {
fn extract_indexer_id(&self) -> i64 {
self.app.data.lidarr_data.indexers.current_selection().id
}
}
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for IndexersHandler<'a, 'b> {
fn handle(&mut self) {
let indexers_table_handling_config =
TableHandlingConfig::new(ActiveLidarrBlock::Indexers.into());
if !handle_table(
self,
|app| &mut app.data.lidarr_data.indexers,
indexers_table_handling_config,
) {
match self.active_lidarr_block {
_ if EditIndexerHandler::accepts(self.active_lidarr_block) => {
EditIndexerHandler::new(self.key, self.app, self.active_lidarr_block, self.context)
.handle()
}
_ if IndexerSettingsHandler::accepts(self.active_lidarr_block) => {
IndexerSettingsHandler::new(self.key, self.app, self.active_lidarr_block, self.context)
.handle()
}
_ if TestAllIndexersHandler::accepts(self.active_lidarr_block) => {
TestAllIndexersHandler::new(self.key, self.app, self.active_lidarr_block, self.context)
.handle()
}
_ => self.handle_key_event(),
}
}
}
fn accepts(active_block: ActiveLidarrBlock) -> bool {
EditIndexerHandler::accepts(active_block)
|| IndexerSettingsHandler::accepts(active_block)
|| TestAllIndexersHandler::accepts(active_block)
|| INDEXERS_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
active_block: ActiveLidarrBlock,
context: Option<ActiveLidarrBlock>,
) -> IndexersHandler<'a, 'b> {
IndexersHandler {
key,
app,
active_lidarr_block: active_block,
context,
}
}
fn get_key(&self) -> Key {
self.key
}
fn is_ready(&self) -> bool {
!self.app.is_loading && !self.app.data.lidarr_data.indexers.is_empty()
}
fn handle_scroll_up(&mut self) {}
fn handle_scroll_down(&mut self) {}
fn handle_home(&mut self) {}
fn handle_end(&mut self) {}
fn handle_delete(&mut self) {
if self.active_lidarr_block == ActiveLidarrBlock::Indexers {
self
.app
.push_navigation_stack(ActiveLidarrBlock::DeleteIndexerPrompt.into());
}
}
fn handle_left_right_action(&mut self) {
match self.active_lidarr_block {
ActiveLidarrBlock::Indexers => handle_change_tab_left_right_keys(self.app, self.key),
ActiveLidarrBlock::DeleteIndexerPrompt => handle_prompt_toggle(self.app, self.key),
_ => (),
}
}
fn handle_submit(&mut self) {
match self.active_lidarr_block {
ActiveLidarrBlock::DeleteIndexerPrompt => {
if self.app.data.lidarr_data.prompt_confirm {
self.app.data.lidarr_data.prompt_confirm_action =
Some(LidarrEvent::DeleteIndexer(self.extract_indexer_id()));
}
self.app.pop_navigation_stack();
}
ActiveLidarrBlock::Indexers => {
self
.app
.push_navigation_stack(ActiveLidarrBlock::EditIndexerPrompt.into());
self.app.data.lidarr_data.edit_indexer_modal = Some((&self.app.data.lidarr_data).into());
let protocol = &self
.app
.data
.lidarr_data
.indexers
.current_selection()
.protocol;
if protocol == "torrent" {
self.app.data.lidarr_data.selected_block =
BlockSelectionState::new(EDIT_INDEXER_TORRENT_SELECTION_BLOCKS);
} else {
self.app.data.lidarr_data.selected_block =
BlockSelectionState::new(EDIT_INDEXER_NZB_SELECTION_BLOCKS);
}
}
_ => (),
}
}
fn handle_esc(&mut self) {
match self.active_lidarr_block {
ActiveLidarrBlock::DeleteIndexerPrompt => {
self.app.pop_navigation_stack();
self.app.data.lidarr_data.prompt_confirm = false;
}
ActiveLidarrBlock::TestIndexer => {
self.app.pop_navigation_stack();
self.app.data.lidarr_data.indexer_test_errors = None;
}
_ => handle_clear_errors(self.app),
}
}
fn handle_char_key_event(&mut self) {
let key = self.key;
match self.active_lidarr_block {
ActiveLidarrBlock::Indexers => match self.key {
_ if matches_key!(refresh, key) => {
self.app.should_refresh = true;
}
_ if matches_key!(test, key) => {
self
.app
.push_navigation_stack(ActiveLidarrBlock::TestIndexer.into());
}
_ if matches_key!(test_all, key) => {
self
.app
.push_navigation_stack(ActiveLidarrBlock::TestAllIndexers.into());
}
_ if matches_key!(settings, key) => {
self
.app
.push_navigation_stack(ActiveLidarrBlock::AllIndexerSettingsPrompt.into());
self.app.data.lidarr_data.selected_block =
BlockSelectionState::new(INDEXER_SETTINGS_SELECTION_BLOCKS);
}
_ => (),
},
ActiveLidarrBlock::DeleteIndexerPrompt => {
if matches_key!(confirm, key) {
self.app.data.lidarr_data.prompt_confirm = true;
self.app.data.lidarr_data.prompt_confirm_action =
Some(LidarrEvent::DeleteIndexer(self.extract_indexer_id()));
self.app.pop_navigation_stack();
}
}
_ => (),
}
}
fn app_mut(&mut self) -> &mut App<'b> {
self.app
}
fn current_route(&self) -> Route {
self.app.get_current_route()
}
}
@@ -0,0 +1,108 @@
use crate::app::App;
use crate::event::Key;
use crate::handlers::KeyEventHandler;
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
use crate::models::Route;
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
#[cfg(test)]
#[path = "test_all_indexers_handler_tests.rs"]
mod test_all_indexers_handler_tests;
pub(super) struct TestAllIndexersHandler<'a, 'b> {
key: Key,
app: &'a mut App<'b>,
active_lidarr_block: ActiveLidarrBlock,
_context: Option<ActiveLidarrBlock>,
}
impl TestAllIndexersHandler<'_, '_> {}
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for TestAllIndexersHandler<'a, 'b> {
fn handle(&mut self) {
let indexer_test_all_results_table_handling_config =
TableHandlingConfig::new(ActiveLidarrBlock::TestAllIndexers.into());
if !handle_table(
self,
|app| {
app
.data
.lidarr_data
.indexer_test_all_results
.as_mut()
.unwrap()
},
indexer_test_all_results_table_handling_config,
) {
self.handle_key_event();
}
}
fn accepts(active_block: ActiveLidarrBlock) -> bool {
active_block == ActiveLidarrBlock::TestAllIndexers
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
active_block: ActiveLidarrBlock,
_context: Option<ActiveLidarrBlock>,
) -> TestAllIndexersHandler<'a, 'b> {
TestAllIndexersHandler {
key,
app,
active_lidarr_block: active_block,
_context,
}
}
fn get_key(&self) -> Key {
self.key
}
fn is_ready(&self) -> bool {
let table_is_ready = if let Some(table) = &self.app.data.lidarr_data.indexer_test_all_results {
!table.is_empty()
} else {
false
};
!self.app.is_loading && table_is_ready
}
fn handle_scroll_up(&mut self) {}
fn handle_scroll_down(&mut self) {}
fn handle_home(&mut self) {}
fn handle_end(&mut self) {}
fn handle_delete(&mut self) {}
fn handle_left_right_action(&mut self) {}
fn handle_submit(&mut self) {}
fn handle_esc(&mut self) {
if self.active_lidarr_block == ActiveLidarrBlock::TestAllIndexers {
self.app.pop_navigation_stack();
self.app.data.lidarr_data.indexer_test_all_results = None;
}
}
fn handle_char_key_event(&mut self) {}
fn app_mut(&mut self) -> &mut App<'b> {
self.app
}
fn current_route(&self) -> Route {
self.app.get_current_route()
}
}
@@ -0,0 +1,133 @@
#[cfg(test)]
mod tests {
use crate::app::App;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::assert_navigation_popped;
use crate::handlers::KeyEventHandler;
use crate::handlers::lidarr_handlers::indexers::test_all_indexers_handler::TestAllIndexersHandler;
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
use crate::models::servarr_data::modals::IndexerTestResultModalItem;
use crate::models::stateful_table::StatefulTable;
use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator;
mod test_handle_esc {
use super::*;
const ESC_KEY: crate::event::Key = DEFAULT_KEYBINDINGS.esc.key;
#[rstest]
fn test_test_all_indexers_prompt_esc(#[values(true, false)] is_ready: bool) {
let mut app = App::test_default();
app.is_loading = is_ready;
app.push_navigation_stack(ActiveLidarrBlock::Indexers.into());
app.push_navigation_stack(ActiveLidarrBlock::TestAllIndexers.into());
app.data.lidarr_data.indexer_test_all_results = Some(StatefulTable::default());
TestAllIndexersHandler::new(ESC_KEY, &mut app, ActiveLidarrBlock::TestAllIndexers, None)
.handle();
assert_navigation_popped!(app, ActiveLidarrBlock::Indexers.into());
assert_none!(app.data.lidarr_data.indexer_test_all_results);
}
}
#[test]
fn test_test_all_indexers_handler_accepts() {
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
if active_lidarr_block == ActiveLidarrBlock::TestAllIndexers {
assert!(TestAllIndexersHandler::accepts(active_lidarr_block));
} else {
assert!(!TestAllIndexersHandler::accepts(active_lidarr_block));
}
})
}
#[rstest]
fn test_test_all_indexers_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = TestAllIndexersHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_test_all_indexers_handler_not_ready_when_loading() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::TestAllIndexers.into());
app.is_loading = true;
let handler = TestAllIndexersHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::TestAllIndexers,
None,
);
assert!(!handler.is_ready());
}
#[test]
fn test_test_all_indexers_handler_not_ready_when_results_is_none() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::TestAllIndexers.into());
app.is_loading = false;
let handler = TestAllIndexersHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::TestAllIndexers,
None,
);
assert!(!handler.is_ready());
}
#[test]
fn test_test_all_indexers_handler_not_ready_when_results_is_empty() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::TestAllIndexers.into());
app.is_loading = false;
app.data.lidarr_data.indexer_test_all_results = Some(StatefulTable::default());
let handler = TestAllIndexersHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::TestAllIndexers,
None,
);
assert!(!handler.is_ready());
}
#[test]
fn test_test_all_indexers_handler_ready_when_not_loading_and_results_is_not_empty() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveLidarrBlock::TestAllIndexers.into());
app.is_loading = false;
let mut results = StatefulTable::default();
results.set_items(vec![IndexerTestResultModalItem::default()]);
app.data.lidarr_data.indexer_test_all_results = Some(results);
let handler = TestAllIndexersHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveLidarrBlock::TestAllIndexers,
None,
);
assert!(handler.is_ready());
}
}
@@ -0,0 +1,616 @@
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
use crate::handlers::{KeyEventHandler, handle_prompt_toggle};
use crate::models::lidarr_models::{AddArtistBody, AddArtistOptions, AddArtistSearchResult};
use crate::models::servarr_data::lidarr::lidarr_data::{
ADD_ARTIST_BLOCKS, ADD_ARTIST_SELECTION_BLOCKS, ActiveLidarrBlock,
};
use crate::models::servarr_data::lidarr::modals::AddArtistModal;
use crate::models::{BlockSelectionState, Route, Scrollable};
use crate::network::lidarr_network::LidarrEvent;
use crate::{App, Key, handle_text_box_keys, handle_text_box_left_right_keys, matches_key};
#[cfg(test)]
#[path = "add_artist_handler_tests.rs"]
mod add_artist_handler_tests;
pub struct AddArtistHandler<'a, 'b> {
key: Key,
app: &'a mut App<'b>,
active_lidarr_block: ActiveLidarrBlock,
_context: Option<ActiveLidarrBlock>,
}
impl AddArtistHandler<'_, '_> {
fn build_add_artist_body(&mut self) -> AddArtistBody {
let add_artist_modal = self
.app
.data
.lidarr_data
.add_artist_modal
.take()
.expect("AddArtistModal is None");
let tags = add_artist_modal.tags.text;
let AddArtistModal {
root_folder_list,
monitor_list,
monitor_new_items_list,
quality_profile_list,
metadata_profile_list,
..
} = add_artist_modal;
let (foreign_artist_id, artist_name) = {
let AddArtistSearchResult {
foreign_artist_id,
artist_name,
..
} = self
.app
.data
.lidarr_data
.add_searched_artists
.as_ref()
.unwrap()
.current_selection();
(foreign_artist_id.clone(), artist_name.text.clone())
};
let quality_profile = quality_profile_list.current_selection();
let quality_profile_id = *self
.app
.data
.lidarr_data
.quality_profile_map
.iter()
.filter(|(_, value)| *value == quality_profile)
.map(|(key, _)| key)
.next()
.unwrap();
let metadata_profile = metadata_profile_list.current_selection();
let metadata_profile_id = *self
.app
.data
.lidarr_data
.metadata_profile_map
.iter()
.filter(|(_, value)| *value == metadata_profile)
.map(|(key, _)| key)
.next()
.unwrap();
let path = root_folder_list.current_selection().path.clone();
let monitor = *monitor_list.current_selection();
let monitor_new_items = *monitor_new_items_list.current_selection();
AddArtistBody {
foreign_artist_id,
artist_name,
monitored: true,
root_folder_path: path,
quality_profile_id,
metadata_profile_id,
tags: Vec::new(),
tag_input_string: Some(tags),
add_options: AddArtistOptions {
monitor,
monitor_new_items,
search_for_missing_albums: true,
},
}
}
}
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for AddArtistHandler<'a, 'b> {
fn handle(&mut self) {
let add_artist_table_handling_config =
TableHandlingConfig::new(ActiveLidarrBlock::AddArtistSearchResults.into());
if !handle_table(
self,
|app| {
app
.data
.lidarr_data
.add_searched_artists
.as_mut()
.expect("add_searched_artists should be initialized")
},
add_artist_table_handling_config,
) {
self.handle_key_event();
}
}
fn accepts(active_block: ActiveLidarrBlock) -> bool {
ADD_ARTIST_BLOCKS.contains(&active_block)
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new(
key: Key,
app: &'a mut App<'b>,
active_block: ActiveLidarrBlock,
context: Option<ActiveLidarrBlock>,
) -> AddArtistHandler<'a, 'b> {
AddArtistHandler {
key,
app,
active_lidarr_block: active_block,
_context: context,
}
}
fn get_key(&self) -> Key {
self.key
}
fn is_ready(&self) -> bool {
!self.app.is_loading
}
fn handle_scroll_up(&mut self) {
match self.active_lidarr_block {
ActiveLidarrBlock::AddArtistSelectMonitor => self
.app
.data
.lidarr_data
.add_artist_modal
.as_mut()
.unwrap()
.monitor_list
.scroll_up(),
ActiveLidarrBlock::AddArtistSelectMonitorNewItems => self
.app
.data
.lidarr_data
.add_artist_modal
.as_mut()
.unwrap()
.monitor_new_items_list
.scroll_up(),
ActiveLidarrBlock::AddArtistSelectQualityProfile => self
.app
.data
.lidarr_data
.add_artist_modal
.as_mut()
.unwrap()
.quality_profile_list
.scroll_up(),
ActiveLidarrBlock::AddArtistSelectMetadataProfile => self
.app
.data
.lidarr_data
.add_artist_modal
.as_mut()
.unwrap()
.metadata_profile_list
.scroll_up(),
ActiveLidarrBlock::AddArtistSelectRootFolder => self
.app
.data
.lidarr_data
.add_artist_modal
.as_mut()
.unwrap()
.root_folder_list
.scroll_up(),
ActiveLidarrBlock::AddArtistPrompt => self.app.data.lidarr_data.selected_block.up(),
_ => (),
}
}
fn handle_scroll_down(&mut self) {
match self.active_lidarr_block {
ActiveLidarrBlock::AddArtistSelectMonitor => self
.app
.data
.lidarr_data
.add_artist_modal
.as_mut()
.unwrap()
.monitor_list
.scroll_down(),
ActiveLidarrBlock::AddArtistSelectMonitorNewItems => self
.app
.data
.lidarr_data
.add_artist_modal
.as_mut()
.unwrap()
.monitor_new_items_list
.scroll_down(),
ActiveLidarrBlock::AddArtistSelectQualityProfile => self
.app
.data
.lidarr_data
.add_artist_modal
.as_mut()
.unwrap()
.quality_profile_list
.scroll_down(),
ActiveLidarrBlock::AddArtistSelectMetadataProfile => self
.app
.data
.lidarr_data
.add_artist_modal
.as_mut()
.unwrap()
.metadata_profile_list
.scroll_down(),
ActiveLidarrBlock::AddArtistSelectRootFolder => self
.app
.data
.lidarr_data
.add_artist_modal
.as_mut()
.unwrap()
.root_folder_list
.scroll_down(),
ActiveLidarrBlock::AddArtistPrompt => self.app.data.lidarr_data.selected_block.down(),
_ => (),
}
}
fn handle_home(&mut self) {
match self.active_lidarr_block {
ActiveLidarrBlock::AddArtistSelectMonitor => self
.app
.data
.lidarr_data
.add_artist_modal
.as_mut()
.unwrap()
.monitor_list
.scroll_to_top(),
ActiveLidarrBlock::AddArtistSelectMonitorNewItems => self
.app
.data
.lidarr_data
.add_artist_modal
.as_mut()
.unwrap()
.monitor_new_items_list
.scroll_to_top(),
ActiveLidarrBlock::AddArtistSelectQualityProfile => self
.app
.data
.lidarr_data
.add_artist_modal
.as_mut()
.unwrap()
.quality_profile_list
.scroll_to_top(),
ActiveLidarrBlock::AddArtistSelectMetadataProfile => self
.app
.data
.lidarr_data
.add_artist_modal
.as_mut()
.unwrap()
.metadata_profile_list
.scroll_to_top(),
ActiveLidarrBlock::AddArtistSelectRootFolder => self
.app
.data
.lidarr_data
.add_artist_modal
.as_mut()
.unwrap()
.root_folder_list
.scroll_to_top(),
ActiveLidarrBlock::AddArtistSearchInput => self
.app
.data
.lidarr_data
.add_artist_search
.as_mut()
.unwrap()
.scroll_home(),
ActiveLidarrBlock::AddArtistTagsInput => self
.app
.data
.lidarr_data
.add_artist_modal
.as_mut()
.unwrap()
.tags
.scroll_home(),
_ => (),
}
}
fn handle_end(&mut self) {
match self.active_lidarr_block {
ActiveLidarrBlock::AddArtistSelectMonitor => self
.app
.data
.lidarr_data
.add_artist_modal
.as_mut()
.unwrap()
.monitor_list
.scroll_to_bottom(),
ActiveLidarrBlock::AddArtistSelectMonitorNewItems => self
.app
.data
.lidarr_data
.add_artist_modal
.as_mut()
.unwrap()
.monitor_new_items_list
.scroll_to_bottom(),
ActiveLidarrBlock::AddArtistSelectQualityProfile => self
.app
.data
.lidarr_data
.add_artist_modal
.as_mut()
.unwrap()
.quality_profile_list
.scroll_to_bottom(),
ActiveLidarrBlock::AddArtistSelectMetadataProfile => self
.app
.data
.lidarr_data
.add_artist_modal
.as_mut()
.unwrap()
.metadata_profile_list
.scroll_to_bottom(),
ActiveLidarrBlock::AddArtistSelectRootFolder => self
.app
.data
.lidarr_data
.add_artist_modal
.as_mut()
.unwrap()
.root_folder_list
.scroll_to_bottom(),
ActiveLidarrBlock::AddArtistSearchInput => self
.app
.data
.lidarr_data
.add_artist_search
.as_mut()
.unwrap()
.reset_offset(),
ActiveLidarrBlock::AddArtistTagsInput => self
.app
.data
.lidarr_data
.add_artist_modal
.as_mut()
.unwrap()
.tags
.reset_offset(),
_ => (),
}
}
fn handle_delete(&mut self) {}
fn handle_left_right_action(&mut self) {
match self.active_lidarr_block {
ActiveLidarrBlock::AddArtistPrompt => handle_prompt_toggle(self.app, self.key),
ActiveLidarrBlock::AddArtistSearchInput => {
handle_text_box_left_right_keys!(
self,
self.key,
self
.app
.data
.lidarr_data
.add_artist_search
.as_mut()
.unwrap()
)
}
ActiveLidarrBlock::AddArtistTagsInput => {
handle_text_box_left_right_keys!(
self,
self.key,
self
.app
.data
.lidarr_data
.add_artist_modal
.as_mut()
.unwrap()
.tags
)
}
_ => (),
}
}
fn handle_submit(&mut self) {
match self.active_lidarr_block {
ActiveLidarrBlock::AddArtistSearchInput
if !self
.app
.data
.lidarr_data
.add_artist_search
.as_ref()
.unwrap()
.text
.is_empty() =>
{
self
.app
.push_navigation_stack(ActiveLidarrBlock::AddArtistSearchResults.into());
self.app.ignore_special_keys_for_textbox_input = false;
}
ActiveLidarrBlock::AddArtistSearchResults
if self.app.data.lidarr_data.add_searched_artists.is_some() =>
{
let foreign_artist_id = self
.app
.data
.lidarr_data
.add_searched_artists
.as_ref()
.unwrap()
.current_selection()
.foreign_artist_id
.clone();
if self
.app
.data
.lidarr_data
.artists
.items
.iter()
.any(|artist| artist.foreign_artist_id == foreign_artist_id)
{
self
.app
.push_navigation_stack(ActiveLidarrBlock::AddArtistAlreadyInLibrary.into());
} else {
self
.app
.push_navigation_stack(ActiveLidarrBlock::AddArtistPrompt.into());
self.app.data.lidarr_data.add_artist_modal = Some((&self.app.data.lidarr_data).into());
self.app.data.lidarr_data.selected_block =
BlockSelectionState::new(ADD_ARTIST_SELECTION_BLOCKS);
}
}
ActiveLidarrBlock::AddArtistPrompt => {
match self.app.data.lidarr_data.selected_block.get_active_block() {
ActiveLidarrBlock::AddArtistConfirmPrompt => {
if self.app.data.lidarr_data.prompt_confirm {
self.app.data.lidarr_data.prompt_confirm_action =
Some(LidarrEvent::AddArtist(self.build_add_artist_body()));
}
self.app.pop_navigation_stack();
}
ActiveLidarrBlock::AddArtistSelectMonitor
| ActiveLidarrBlock::AddArtistSelectMonitorNewItems
| ActiveLidarrBlock::AddArtistSelectQualityProfile
| ActiveLidarrBlock::AddArtistSelectMetadataProfile
| ActiveLidarrBlock::AddArtistSelectRootFolder => self.app.push_navigation_stack(
self
.app
.data
.lidarr_data
.selected_block
.get_active_block()
.into(),
),
ActiveLidarrBlock::AddArtistTagsInput => {
self.app.push_navigation_stack(
self
.app
.data
.lidarr_data
.selected_block
.get_active_block()
.into(),
);
self.app.ignore_special_keys_for_textbox_input = true;
}
_ => (),
}
}
ActiveLidarrBlock::AddArtistSelectMonitor
| ActiveLidarrBlock::AddArtistSelectMonitorNewItems
| ActiveLidarrBlock::AddArtistSelectQualityProfile
| ActiveLidarrBlock::AddArtistSelectMetadataProfile
| ActiveLidarrBlock::AddArtistSelectRootFolder => self.app.pop_navigation_stack(),
ActiveLidarrBlock::AddArtistTagsInput => {
self.app.pop_navigation_stack();
self.app.ignore_special_keys_for_textbox_input = false;
}
_ => (),
}
}
fn handle_esc(&mut self) {
match self.active_lidarr_block {
ActiveLidarrBlock::AddArtistSearchInput => {
self.app.pop_navigation_stack();
self.app.data.lidarr_data.add_artist_search = None;
self.app.ignore_special_keys_for_textbox_input = false;
}
ActiveLidarrBlock::AddArtistSearchResults
| ActiveLidarrBlock::AddArtistEmptySearchResults => {
self.app.pop_navigation_stack();
self.app.data.lidarr_data.add_searched_artists = None;
self.app.ignore_special_keys_for_textbox_input = true;
}
ActiveLidarrBlock::AddArtistPrompt => {
self.app.pop_navigation_stack();
self.app.data.lidarr_data.add_artist_modal = None;
self.app.data.lidarr_data.prompt_confirm = false;
}
ActiveLidarrBlock::AddArtistSelectMonitor
| ActiveLidarrBlock::AddArtistSelectMonitorNewItems
| ActiveLidarrBlock::AddArtistSelectQualityProfile
| ActiveLidarrBlock::AddArtistSelectMetadataProfile
| ActiveLidarrBlock::AddArtistAlreadyInLibrary
| ActiveLidarrBlock::AddArtistSelectRootFolder => self.app.pop_navigation_stack(),
ActiveLidarrBlock::AddArtistTagsInput => {
self.app.pop_navigation_stack();
self.app.ignore_special_keys_for_textbox_input = false;
}
_ => (),
}
}
fn handle_char_key_event(&mut self) {
let key = self.key;
match self.active_lidarr_block {
ActiveLidarrBlock::AddArtistSearchInput => {
handle_text_box_keys!(
self,
key,
self
.app
.data
.lidarr_data
.add_artist_search
.as_mut()
.unwrap()
)
}
ActiveLidarrBlock::AddArtistTagsInput => {
handle_text_box_keys!(
self,
key,
self
.app
.data
.lidarr_data
.add_artist_modal
.as_mut()
.unwrap()
.tags
)
}
ActiveLidarrBlock::AddArtistPrompt => {
if self.app.data.lidarr_data.selected_block.get_active_block()
== ActiveLidarrBlock::AddArtistConfirmPrompt
&& matches_key!(confirm, key)
{
self.app.data.lidarr_data.prompt_confirm = true;
self.app.data.lidarr_data.prompt_confirm_action =
Some(LidarrEvent::AddArtist(self.build_add_artist_body()));
self.app.pop_navigation_stack();
}
}
_ => (),
}
}
fn app_mut(&mut self) -> &mut App<'b> {
self.app
}
fn current_route(&self) -> Route {
self.app.get_current_route()
}
}

Some files were not shown because too many files have changed in this diff Show More