Compare commits
59 Commits
0172253d20
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9a2b1c6c4 | ||
|
47a2fbf0cc
|
|||
|
7067902752
|
|||
|
ba1cf0182b
|
|||
|
5cccec88c9
|
|||
|
bbcd3f00a9
|
|||
| 2e339dd73b | |||
|
f988cf0f26
|
|||
|
ff82dc2012
|
|||
|
|
89a692ad90 | ||
|
|
d77ec5fb34 | ||
|
|
ec90e2dca7 | ||
|
5a4e6c9623
|
|||
|
9a6a06ee20
|
|||
|
5556e48fc0
|
|||
|
af573cac2a
|
|||
|
447cf6a2b4
|
|||
|
203bf9cb66
|
|||
|
4f9bc34d23
|
|||
|
a2aa9507a9
|
|||
|
c791b985f0
|
|||
|
5c517a748c
|
|||
|
892c687077
|
|||
|
c6d5b98e86
|
|||
|
67e5114ec2
|
|||
|
fdc331865e
|
|||
|
f388dccc08
|
|||
|
64fad3b9bc
|
|||
|
3be7b09da8
|
|||
|
5f3123cd79
|
|||
|
d8f7febfe1
|
|||
|
0bfbb44e3e
|
|||
|
|
c5161f828d | ||
|
|
71c64167f0 | ||
|
|
4d3e00fd94 | ||
| 9e96e74c87 | |||
| ddb869c341 | |||
| f17f542e8e | |||
| a2e6400a38 | |||
| 89f5ff6bc7 | |||
| eff1a901eb | |||
| 7add62b245 | |||
| 5fa9b08347 | |||
| 7bb5f83a56 | |||
| caf4ad1e64 | |||
| bc6ecc39f4 | |||
| 5e70d70758 | |||
| 1329589bd6 | |||
| c6dc8f6090 | |||
| 0ee275d58f | |||
| 8dfa664a06 | |||
| d7f0dd5950 | |||
| 8b9467bd39 | |||
| c74d5936d2 | |||
| 8abcf44866 | |||
| d2217509f2 | |||
| c68cd75015 | |||
| e1a25bfaf2 | |||
| ad9e2b3671 |
@@ -0,0 +1,11 @@
|
|||||||
|
### AI assistance (if any):
|
||||||
|
- List tools here and files touched by them
|
||||||
|
|
||||||
|
### Authorship & Understanding
|
||||||
|
|
||||||
|
- [ ] I wrote or heavily modified this code myself
|
||||||
|
- [ ] I understand how it works end-to-end
|
||||||
|
- [ ] I can maintain this code in the future
|
||||||
|
- [ ] No undisclosed AI-generated code was used
|
||||||
|
- [ ] If AI assistance was used, it is documented below
|
||||||
|
|
||||||
+100
@@ -5,6 +5,106 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## v0.7.1 (2026-02-04)
|
||||||
|
|
||||||
|
### Feat
|
||||||
|
|
||||||
|
- Added support for a system-wide notification popup mechanism that works across Servarrs
|
||||||
|
- Implemented a 'config-path' command to print out the default Managarr configuration file path to help address #54
|
||||||
|
- Full support for filtering disks and aggregating root folders in the UI's 'Stats' block
|
||||||
|
- proper collapsing of root folder paths in the stats layer of the UI
|
||||||
|
- Added config option to filter for specific disk space paths to display in the UI (CLI is unaffected)
|
||||||
|
- Improved disk-space UI and CLI that shows the actual path being monitored instead of just a disk number
|
||||||
|
- Implemented the forgotten lidarr list disk-space command
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
- Improved the system notification feature so it can persist between modals
|
||||||
|
- Sonarr API updated to somtimes allow either seeders or leechers to be null
|
||||||
|
- Improved the first-time run behavior so that it outputs the default configuration file it tries to load to help users locate the file on first-runs
|
||||||
|
- 'managarr config-path' should work without a pre-existing config already in place [#54]
|
||||||
|
|
||||||
|
### Refactor
|
||||||
|
|
||||||
|
- Removed the filtering of monitored_storage_paths from the networking module and migrated all of it to the UI
|
||||||
|
|
||||||
|
## v0.7.0 (2026-01-21)
|
||||||
|
|
||||||
|
### Feat
|
||||||
|
|
||||||
|
- Blocklist support in Lidarr in both the CLI and TUI
|
||||||
|
- CLI and TUI support for track history and track details in Lidarr
|
||||||
|
- Lidarr UI support for album details popup
|
||||||
|
- Implemented TUI handler support for the Album Details popup in Lidarr
|
||||||
|
- Bulk added CLI support for tracks and album functionalities in Lidarr
|
||||||
|
- Implemented the manual artist discography search tab in Lidarr's artist details UI
|
||||||
|
- Lidarr CLI support for downloading a release
|
||||||
|
- CLI support for searching for discography releases in Lidarr
|
||||||
|
- Added TUI and CLI support for viewing Artist history in Lidarr
|
||||||
|
- Full Lidarr system support for both the CLI and TUI
|
||||||
|
- Full CLI and TUI support for the Lidarr Indexers tab
|
||||||
|
- Full support for adding a root folder in Lidarr from both the CLI and TUI
|
||||||
|
- naive lidarr root folder tab implementation. Needs improved add logic
|
||||||
|
- Downloads tab support in Lidarr
|
||||||
|
- Created a History tab in the Radarr UI and created a list history command and mark-history-item-as-failed command for Radarr
|
||||||
|
- Implemented the Lidarr History tab and CLI support
|
||||||
|
- TUI support for deleting a Lidarr album from the artist details popup
|
||||||
|
- CLI support for deleting an album from Lidarr
|
||||||
|
- Completed support for viewing Lidarr artist details
|
||||||
|
- Full CLI and TUI support for adding an artist to Lidarr
|
||||||
|
- Include the Lidarr artist disambiguation in the title of the Edit Artist popup
|
||||||
|
- Initial Lidarr support for searching for new artists
|
||||||
|
- Lidarr CLI commands to list quality profiles and metadata profiles
|
||||||
|
- Improved CLI readability by creating a separate Global Options section for global flags
|
||||||
|
- CLI support for deleting a tag in Lidarr
|
||||||
|
- Lidarr CLI support for listing and adding tags
|
||||||
|
- Added CLI and TUI support for editing Lidarr artists
|
||||||
|
- Support for updating all Lidarr artists in both the CLI and TUI
|
||||||
|
- Added Lidarr CLI support for fetching the host config and the security config
|
||||||
|
- Created Lidarr commands: 'get artist-details' and 'get system-status'
|
||||||
|
- Fetch the artist members as part of the artist details query
|
||||||
|
- Support for toggling the monitoring of a given artist via the CLI and TUI
|
||||||
|
- Full support for deleting an artist via CLI and TUI
|
||||||
|
- TUI support for Lidarr library
|
||||||
|
- CLI support for listing artists
|
||||||
|
- Improved UI speed and responsiveness
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
- Sonarr network wasn't checking for the user to be using the sorting block when populating season details
|
||||||
|
- Sonarr CLI was not properly filtering out episode and season releases when manually searching for releases
|
||||||
|
- Sonarr manual search TUI and CLI incorrectly displaying the same unfiltered results for both season and episode searches
|
||||||
|
- Slowed down the automatic text scrolling in tables so the text is readable
|
||||||
|
- 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
|
||||||
|
- Bug in submitting the update series prompt in the series details UI in Sonarr
|
||||||
|
- Don't include Lidarr artist disambiguation in Edit popup title when it is empty
|
||||||
|
- 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
|
||||||
|
- Added the correct keybinding context to the Lidarr edit artist popup
|
||||||
|
- Improved fault tolerance for search result tables and test all indexer results tables
|
||||||
|
- Prevented additional empty slice errors in indexer tables
|
||||||
|
- 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
|
||||||
|
- Fixed an issue with the Managarr table that would incorrectly try to display things before is_loading was ready
|
||||||
|
- Fixed a bug where the edit collection popup would not display when opening it from collection details
|
||||||
|
|
||||||
|
### Refactor
|
||||||
|
|
||||||
|
- Refactored the SonarrEvent enum to not unnecessarily wrap dual series_id and season_number values in a tuple when both values can be passed directly
|
||||||
|
- Improved and simplified the implementation of history details for both Sonarr and Lidarr
|
||||||
|
- Let serde serialize Add Series and Add Movie enums instead of calling to_string up front
|
||||||
|
- Use is_multiple_of for the tick counter in the UI module
|
||||||
|
- Updated all model tests to use purpose-built assertions to improve readability and maintainability
|
||||||
|
- Updated all handler tests to use purpose built assertions to improve readability and maintainability
|
||||||
|
- Used is_multiple_of to make life easier and cleaner in the app module
|
||||||
|
- Refactored all cli tests to use purpose-built assertions
|
||||||
|
- Improved test assertions in the app module
|
||||||
|
- Created dedicated proptests and assertions to clean up the handler unit tests
|
||||||
|
- 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
|
||||||
|
- Simplified both the table_handler macro and the stateful_table implementation
|
||||||
|
- Improved error handling for the tail-logs subcommand to propagate errors up the stack instead of exiting there.
|
||||||
|
- 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
|
||||||
|
- Improved error handling project-wide and cleaned up some regexes with unnecessary escapes (tail_logs and interpolate_env_vars)
|
||||||
|
- Refactored to use more idiomatic let-else statements where applicable
|
||||||
|
|
||||||
## v0.6.3 (2025-12-13)
|
## v0.6.3 (2025-12-13)
|
||||||
|
|
||||||
### Fix
|
### Fix
|
||||||
|
|||||||
@@ -91,5 +91,12 @@ Then, you can run workflows locally without having to commit and see if the GitH
|
|||||||
act -W .github/workflows/release.yml --input_type bump=minor
|
act -W .github/workflows/release.yml --input_type bump=minor
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Authorship Policy
|
||||||
|
|
||||||
|
All code in this repository is written and reviewed by humans. AI-generated code (e.g., Copilot, ChatGPT,
|
||||||
|
Claude, etc.) is not permitted unless explicitly disclosed and approved.
|
||||||
|
|
||||||
|
Submissions must certify that the contributor understands and can maintain the code they submit.
|
||||||
|
|
||||||
## Questions? Reach out to me!
|
## Questions? Reach out to me!
|
||||||
If you encounter any questions while developing Managarr, please don't hesitate to reach out to me at alex.j.tusa@gmail.com. I'm happy to help contributors, new and experienced in any way I can!
|
If you encounter any questions while developing Managarr, please don't hesitate to reach out to me at alex.j.tusa@gmail.com. I'm happy to help contributors, new and experienced in any way I can!
|
||||||
|
|||||||
Generated
+1049
-617
File diff suppressed because it is too large
Load Diff
+35
-35
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "managarr"
|
name = "managarr"
|
||||||
version = "0.6.3"
|
version = "0.7.1"
|
||||||
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
|
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
|
||||||
description = "A TUI and CLI to manage your Servarrs"
|
description = "A TUI and CLI to manage your Servarrs"
|
||||||
keywords = ["managarr", "ratatui", "dashboard", "servarr", "tui"]
|
keywords = ["managarr", "ratatui", "dashboard", "servarr", "tui"]
|
||||||
@@ -20,67 +20,67 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.68"
|
anyhow = "1.0.100"
|
||||||
backtrace = "0.3.74"
|
backtrace = "0.3.76"
|
||||||
bimap = { version = "0.6.3", features = ["serde"] }
|
bimap = { version = "0.6.3", features = ["serde"] }
|
||||||
chrono = { version = "0.4.38", features = ["serde"] }
|
chrono = { version = "0.4.43", features = ["serde"] }
|
||||||
confy = { version = "0.6.0", default-features = false, features = [
|
confy = { version = "2.0.0", default-features = false, features = [
|
||||||
"yaml_conf",
|
"yaml_conf",
|
||||||
] }
|
] }
|
||||||
crossterm = "0.28.1"
|
crossterm = "0.28.1"
|
||||||
derivative = "2.2.0"
|
derivative = "2.2.0"
|
||||||
human-panic = "2.0.2"
|
human-panic = "2.0.6"
|
||||||
indoc = "2.0.0"
|
indoc = "2.0.7"
|
||||||
log = "0.4.17"
|
log = "0.4.29"
|
||||||
log4rs = { version = "1.2.0", features = ["file_appender"] }
|
log4rs = { version = "1.4.0", features = ["file_appender"] }
|
||||||
regex = "1.11.1"
|
regex = "1.12.2"
|
||||||
reqwest = { version = "0.12.9", features = ["json"] }
|
reqwest = { version = "0.13.2", features = ["json"] }
|
||||||
serde_yaml = "0.9.16"
|
serde_yaml = "0.9.34"
|
||||||
serde_json = "1.0.91"
|
serde_json = "1.0.149"
|
||||||
serde = { version = "1.0.214", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
strum = { version = "0.26.3", features = ["derive"] }
|
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.49.0", features = ["full"] }
|
||||||
tokio-util = "0.7.8"
|
tokio-util = "0.7.18"
|
||||||
ratatui = { version = "0.30.0", features = [
|
ratatui = { version = "0.30.0", features = [
|
||||||
"all-widgets",
|
"all-widgets",
|
||||||
"unstable-widget-ref",
|
"unstable-widget-ref",
|
||||||
] }
|
] }
|
||||||
urlencoding = "2.1.2"
|
urlencoding = "2.1.3"
|
||||||
clap = { version = "4.5.20", features = [
|
clap = { version = "4.5.56", features = [
|
||||||
"derive",
|
"derive",
|
||||||
"cargo",
|
"cargo",
|
||||||
"env",
|
"env",
|
||||||
"wrap_help",
|
"wrap_help",
|
||||||
] }
|
] }
|
||||||
clap_complete = "4.5.33"
|
clap_complete = "4.5.65"
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
ctrlc = "3.4.5"
|
ctrlc = "3.5.1"
|
||||||
colored = "3.0.0"
|
colored = "3.1.1"
|
||||||
async-trait = "0.1.83"
|
async-trait = "0.1.89"
|
||||||
dirs-next = "2.0.0"
|
dirs-next = "2.0.0"
|
||||||
managarr-tree-widget = "0.25.0"
|
managarr-tree-widget = "0.25.0"
|
||||||
indicatif = "0.17.9"
|
indicatif = "0.17.11"
|
||||||
derive_setters = "0.1.6"
|
derive_setters = "0.1.9"
|
||||||
deunicode = "1.6.0"
|
deunicode = "1.6.2"
|
||||||
openssl = { version = "0.10.70", features = ["vendored"] }
|
openssl = { version = "0.10.75", features = ["vendored"] }
|
||||||
veil = "0.2.0"
|
veil = "0.2.0"
|
||||||
validate_theme_derive = "0.1.0"
|
validate_theme_derive = "0.1.0"
|
||||||
enum_display_style_derive = "0.1.0"
|
enum_display_style_derive = "0.1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
assert_cmd = "2.0.16"
|
assert_cmd = "2.1.2"
|
||||||
mockall = "0.13.0"
|
mockall = "0.13.1"
|
||||||
mockito = "1.0.0"
|
mockito = "1.7.1"
|
||||||
pretty_assertions = "1.3.0"
|
pretty_assertions = "1.4.1"
|
||||||
proptest = "1.6.0"
|
proptest = "1.9.0"
|
||||||
rstest = "0.25.0"
|
rstest = "0.25.0"
|
||||||
serial_test = "3.2.0"
|
serial_test = "3.3.1"
|
||||||
assertables = "9.8.2"
|
assertables = "9.8.4"
|
||||||
insta = "1.41.1"
|
insta = "1.46.1"
|
||||||
|
|
||||||
[dev-dependencies.cargo-husky]
|
[dev-dependencies.cargo-husky]
|
||||||
version = "1"
|
version = "1.5.0"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["user-hooks"]
|
features = ["user-hooks"]
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ RUN mv target/release/managarr .
|
|||||||
|
|
||||||
FROM debian:stable-slim
|
FROM debian:stable-slim
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Copy the compiled binary from the builder container
|
# Copy the compiled binary from the builder container
|
||||||
COPY --from=builder --chown=nonroot:nonroot /usr/src/managarr-temp/managarr /usr/local/bin
|
COPY --from=builder --chown=nonroot:nonroot /usr/src/managarr-temp/managarr /usr/local/bin
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||

|

|
||||||

|

|
||||||

|

|
||||||

|
|
||||||
[](https://crates.io/crates/managarr)
|
[](https://crates.io/crates/managarr)
|
||||||

|

|
||||||
[](https://codecov.io/gh/Dark-Alex-17/managarr)
|
[](https://codecov.io/gh/Dark-Alex-17/managarr)
|
||||||
@@ -12,17 +11,16 @@
|
|||||||

|

|
||||||
[](https://matrix.to/#/#managarr:matrix.org)
|
[](https://matrix.to/#/#managarr:matrix.org)
|
||||||
|
|
||||||
|
|
||||||
Managarr is a TUI and CLI to help you manage your HTPC (Home Theater PC). Built with 🤎 in Rust!
|
Managarr is a TUI and CLI to help you manage your HTPC (Home Theater PC). Built with 🤎 in Rust!
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## What Servarrs are supported?
|
## What Servarrs are supported?
|
||||||
|
|
||||||
- [x]  [Radarr](https://wiki.servarr.com/radarr)
|
- [x]  [Radarr](https://wiki.servarr.com/radarr)
|
||||||
- [x]  [Sonarr](https://wiki.servarr.com/en/sonarr)
|
- [x]  [Sonarr](https://wiki.servarr.com/en/sonarr)
|
||||||
|
- [x]  [Lidarr](https://wiki.servarr.com/en/lidarr)
|
||||||
- [ ]  [Readarr](https://wiki.servarr.com/en/readarr)
|
- [ ]  [Readarr](https://wiki.servarr.com/en/readarr)
|
||||||
- [ ]  [Lidarr](https://wiki.servarr.com/en/lidarr)
|
|
||||||
- [ ]  [Prowlarr](https://wiki.servarr.com/en/prowlarr)
|
- [ ]  [Prowlarr](https://wiki.servarr.com/en/prowlarr)
|
||||||
- [ ]  [Whisparr](https://wiki.servarr.com/whisparr)
|
- [ ]  [Whisparr](https://wiki.servarr.com/whisparr)
|
||||||
- [ ]  [Bazarr](https://www.bazarr.media/)
|
- [ ]  [Bazarr](https://www.bazarr.media/)
|
||||||
@@ -56,9 +54,9 @@ Run Managarr as a docker container by mounting your `config.yml` file to `/root/
|
|||||||
docker run --rm -it -v /home/aclarke/.config/managarr/config.yml:/root/.config/managarr/config.yml darkalex17/managarr:latest
|
docker run --rm -it -v /home/aclarke/.config/managarr/config.yml:/root/.config/managarr/config.yml darkalex17/managarr:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also clone this repo and run `make docker` to build a docker image locally and run it using the above command.
|
You can also clone this repo and run `just build-docker` to build a docker image locally and run it using the above command.
|
||||||
|
|
||||||
Please note that you will need to create and popular your configuration file first before starting the container. Otherwise, the container will fail to start.
|
Please note that you will need to create and populate your configuration file first before starting the container. Otherwise, the container will fail to start.
|
||||||
|
|
||||||
**Note:** If you run into errors using relative file paths when mounting the volume with the configuration file, try using an absolute path.
|
**Note:** If you run into errors using relative file paths when mounting the volume with the configuration file, try using an absolute path.
|
||||||
|
|
||||||
@@ -96,7 +94,7 @@ of Chocolatey packages take quite some time, and thus the package may not be ava
|
|||||||
choco install managarr
|
choco install managarr
|
||||||
|
|
||||||
# Some newer releases may require a version number, so you can specify it like so:
|
# Some newer releases may require a version number, so you can specify it like so:
|
||||||
choco install managarr --version=0.5.0
|
choco install managarr --version=0.7.0
|
||||||
```
|
```
|
||||||
|
|
||||||
To upgrade to the latest and greatest version of Managarr:
|
To upgrade to the latest and greatest version of Managarr:
|
||||||
@@ -104,7 +102,7 @@ To upgrade to the latest and greatest version of Managarr:
|
|||||||
choco upgrade managarr
|
choco upgrade managarr
|
||||||
|
|
||||||
# To upgrade to a specific version:
|
# To upgrade to a specific version:
|
||||||
choco upgrade managarr --version=0.5.0
|
choco upgrade managarr --version=0.7.0
|
||||||
```
|
```
|
||||||
|
|
||||||
### Manual
|
### Manual
|
||||||
@@ -182,14 +180,30 @@ Key:
|
|||||||
| ✅ | ✅ | View and browse logs, tasks, events queues, and updates |
|
| ✅ | ✅ | View and browse logs, tasks, events queues, and updates |
|
||||||
| ✅ | ✅ | Manually trigger scheduled tasks |
|
| ✅ | ✅ | Manually trigger scheduled tasks |
|
||||||
|
|
||||||
|
### Lidarr
|
||||||
|
|
||||||
|
| TUI | CLI | Feature |
|
||||||
|
|-----|-----|----------------------------------------------------------------------------------------------------------------|
|
||||||
|
| ✅ | ✅ | View your library, downloads, blocklist, tracks |
|
||||||
|
| ✅ | ✅ | View details of a specific artists, albums, or tracks including description, history, downloaded file info |
|
||||||
|
| 🚫 | ✅ | View your host and security configs from the CLI to programmatically fetch the API token, among other settings |
|
||||||
|
| ✅ | ✅ | Search your library |
|
||||||
|
| ✅ | ✅ | Add artists to your library |
|
||||||
|
| ✅ | ✅ | Delete artists, downloads, indexers, root folders, and track files |
|
||||||
|
| ✅ | ✅ | Trigger automatic searches for artists or albums |
|
||||||
|
| ✅ | ✅ | Trigger refresh and disk scan for artists and downloads |
|
||||||
|
| ✅ | ✅ | Manually search for full artist discographies or albums |
|
||||||
|
| ✅ | ✅ | Edit your artists and indexers |
|
||||||
|
| ✅ | ✅ | Manage your tags |
|
||||||
|
| ✅ | ✅ | Manage your root folders |
|
||||||
|
| ✅ | ✅ | Manage your blocklist |
|
||||||
|
| ✅ | ✅ | View and browse logs, tasks, events queues, and updates |
|
||||||
|
| ✅ | ✅ | Manually trigger scheduled tasks |
|
||||||
|
|
||||||
### Readarr
|
### Readarr
|
||||||
|
|
||||||
- [ ] Support for Readarr
|
- [ ] Support for Readarr
|
||||||
|
|
||||||
### Lidarr
|
|
||||||
|
|
||||||
- [ ] Support for Lidarr
|
|
||||||
|
|
||||||
### Whisparr
|
### Whisparr
|
||||||
|
|
||||||
- [ ] Support for Whisparr
|
- [ ] Support for Whisparr
|
||||||
@@ -231,7 +245,7 @@ To see all available commands, simply run `managarr --help`:
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ managarr --help
|
$ managarr --help
|
||||||
managarr 0.5.1
|
managarr 0.7.0
|
||||||
Alex Clarke <alex.j.tusa@gmail.com>
|
Alex Clarke <alex.j.tusa@gmail.com>
|
||||||
|
|
||||||
A TUI and CLI to manage your Servarrs
|
A TUI and CLI to manage your Servarrs
|
||||||
@@ -241,20 +255,35 @@ Usage: managarr [OPTIONS] [COMMAND]
|
|||||||
Commands:
|
Commands:
|
||||||
radarr Commands for manging your Radarr instance
|
radarr Commands for manging your Radarr instance
|
||||||
sonarr Commands for manging your Sonarr instance
|
sonarr Commands for manging your Sonarr instance
|
||||||
|
lidarr Commands for manging your Lidarr instance
|
||||||
completions Generate shell completions for the Managarr CLI
|
completions Generate shell completions for the Managarr CLI
|
||||||
tail-logs Tail Managarr logs
|
tail-logs Tail Managarr logs
|
||||||
|
config-path Print the full path to the default configuration file.
|
||||||
|
This file can be changed to another location using the '--config-file' flag
|
||||||
help Print this message or the help of the given subcommand(s)
|
help Print this message or the help of the given subcommand(s)
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--disable-spinner Disable the spinner (can sometimes make parsing output challenging) [env: MANAGARR_DISABLE_SPINNER=]
|
-h, --help Print help
|
||||||
--config-file <CONFIG_FILE> The Managarr configuration file to use [env: MANAGARR_CONFIG_FILE=]
|
-V, --version Print version
|
||||||
--themes-file <THEMES_FILE> The Managarr themes file to use [env: MANAGARR_THEMES_FILE=]
|
|
||||||
--theme <THEME> The name of the Managarr theme to use [env: MANAGARR_THEME=]
|
Global Options:
|
||||||
--servarr-name <SERVARR_NAME> For multi-instance configurations, you need to specify the name of the instance configuration that you want to use.
|
--disable-spinner Disable the spinner (can sometimes make parsing output
|
||||||
This is useful when you have multiple instances of the same Servarr defined in your config file.
|
challenging) [env: MANAGARR_DISABLE_SPINNER=]
|
||||||
By default, if left empty, the first configured Servarr instance listed in the config file will be used.
|
--config-file <CONFIG_FILE> The Managarr configuration file to use; defaults to the
|
||||||
-h, --help Print help
|
path shown by 'managarr config-path' [env:
|
||||||
-V, --version Print version
|
MANAGARR_CONFIG_FILE=]
|
||||||
|
--themes-file <THEMES_FILE> The Managarr themes file to use [env:
|
||||||
|
MANAGARR_THEMES_FILE=]
|
||||||
|
--theme <THEME> The name of the Managarr theme to use [env:
|
||||||
|
MANAGARR_THEME=]
|
||||||
|
--servarr-name <SERVARR_NAME> For multi-instance configurations, you need to specify
|
||||||
|
the name of the instance configuration that you want to
|
||||||
|
use.
|
||||||
|
|
||||||
|
This is useful when you have multiple instances of the
|
||||||
|
same Servarr defined in your config file.
|
||||||
|
By default, if left empty, the first configured Servarr
|
||||||
|
instance listed in the config file will be used.
|
||||||
```
|
```
|
||||||
|
|
||||||
All subcommands also have detailed help menus to show you how to use them. For example, to see all available commands for Sonarr, you would run:
|
All subcommands also have detailed help menus to show you how to use them. For example, to see all available commands for Sonarr, you would run:
|
||||||
@@ -283,12 +312,21 @@ Commands:
|
|||||||
test-all-indexers Test all Sonarr indexers
|
test-all-indexers Test all Sonarr indexers
|
||||||
toggle-episode-monitoring Toggle monitoring for the specified episode
|
toggle-episode-monitoring Toggle monitoring for the specified episode
|
||||||
toggle-season-monitoring Toggle monitoring for the specified season that corresponds to the specified series ID
|
toggle-season-monitoring Toggle monitoring for the specified season that corresponds to the specified series ID
|
||||||
|
toggle-series-monitoring Toggle monitoring for the specified series corresponding to the given series ID
|
||||||
help Print this message or the help of the given subcommand(s)
|
help Print this message or the help of the given subcommand(s)
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--disable-spinner Disable the spinner (can sometimes make parsing output challenging) [env: MANAGARR_DISABLE_SPINNER=]
|
-h, --help Print help
|
||||||
--config <CONFIG> The Managarr configuration file to use [env: MANAGARR_CONFIG_FILE=]
|
|
||||||
-h, --help Print help
|
Global Options:
|
||||||
|
--disable-spinner Disable the spinner (can sometimes make parsing output challenging) [env: MANAGARR_DISABLE_SPINNER=]
|
||||||
|
--config-file <CONFIG_FILE> The Managarr configuration file to use [env: MANAGARR_CONFIG_FILE=]
|
||||||
|
--themes-file <THEMES_FILE> The Managarr themes file to use [env: MANAGARR_THEMES_FILE=]
|
||||||
|
--theme <THEME> The name of the Managarr theme to use [env: MANAGARR_THEME=]
|
||||||
|
--servarr-name <SERVARR_NAME> For multi-instance configurations, you need to specify the name of the instance configuration that you want to use.
|
||||||
|
|
||||||
|
This is useful when you have multiple instances of the same Servarr defined in your config file.
|
||||||
|
By default, if left empty, the first configured Servarr instance listed in the config file will be used.
|
||||||
```
|
```
|
||||||
|
|
||||||
**Pro Tip:** The CLI is even more powerful and useful when used in conjunction with the `jq` CLI tool. This allows you to parse the JSON response from the Managarr CLI and use it in your scripts; For example, to extract the `movieId` of the movie "Ad Astra", you would run:
|
**Pro Tip:** The CLI is even more powerful and useful when used in conjunction with the `jq` CLI tool. This allows you to parse the JSON response from the Managarr CLI and use it in your scripts; For example, to extract the `movieId` of the movie "Ad Astra", you would run:
|
||||||
@@ -302,21 +340,11 @@ $ managarr radarr list movies | jq '.[] | select(.title == "Ad Astra") | .id'
|
|||||||
Managarr assumes reasonable defaults to connect to each service (i.e. Radarr is on localhost:7878),
|
Managarr assumes reasonable defaults to connect to each service (i.e. Radarr is on localhost:7878),
|
||||||
but all servers will require you to input the API token.
|
but all servers will require you to input the API token.
|
||||||
|
|
||||||
The configuration file is located somewhere different for each OS.
|
The configuration file is located somewhere different for each OS, so run the following command to print out the default
|
||||||
|
location of the `managarr` configuration file for your system:
|
||||||
|
|
||||||
### Linux
|
```shell
|
||||||
```
|
managarr config-path
|
||||||
$HOME/.config/managarr/config.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mac
|
|
||||||
```
|
|
||||||
$HOME/Library/Application Support/managarr/config.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
### Windows
|
|
||||||
```
|
|
||||||
%APPDATA%/Roaming/managarr/config.yml
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Specify Which Configuration File to Use
|
## Specify Which Configuration File to Use
|
||||||
@@ -335,43 +363,102 @@ radarr:
|
|||||||
- host: 192.168.0.78
|
- host: 192.168.0.78
|
||||||
port: 7878
|
port: 7878
|
||||||
api_token: someApiToken1234567890
|
api_token: someApiToken1234567890
|
||||||
ssl_cert_path: /path/to/radarr.crt # Required to enable SSL
|
ssl_cert_path: /path/to/radarr.crt # Use the specified SSL certificate to connect to this Servarr
|
||||||
sonarr:
|
# Enables SSL regardless of the value of the 'ssl'
|
||||||
- uri: http://htpc.local/sonarr # Example of using the 'uri' key instead of 'host' and 'port'
|
# See the SSL Configuration section below for more information
|
||||||
|
|
||||||
|
- host: 192.168.0.79
|
||||||
|
port: 7878
|
||||||
api_token: someApiToken1234567890
|
api_token: someApiToken1234567890
|
||||||
|
ssl: true # Use SSL to connect to this Servarr
|
||||||
|
# This will assume that you have the SSL certificate installed to your system trust store
|
||||||
|
# See the SSL Configuration section below for more information
|
||||||
|
|
||||||
|
- uri: http://htpc.local/radarr # Example of using the 'uri' key instead of 'host' and 'port'
|
||||||
|
api_token: someApiToken1234567890
|
||||||
|
|
||||||
|
sonarr:
|
||||||
|
- host: 192.168.0.89
|
||||||
|
port: 8989
|
||||||
|
api_token_file: /root/.config/sonarr_api_token # Example of loading the API token from a file instead of hardcoding it in the configuration file
|
||||||
|
|
||||||
- name: Anime Sonarr # An example of a custom name for a secondary Sonarr instance
|
- name: Anime Sonarr # An example of a custom name for a secondary Sonarr instance
|
||||||
host: 192.168.0.89
|
host: 192.168.1.89
|
||||||
port: 8989
|
port: 8989
|
||||||
api_token: someApiToken1234567890
|
api_token: someApiToken1234567890
|
||||||
readarr:
|
|
||||||
- host: 192.168.0.87
|
|
||||||
port: 8787
|
|
||||||
api_token_file: /root/.config/readarr_api_token # Example of loading the API token from a file instead of hardcoding it in the configuration file
|
|
||||||
lidarr:
|
lidarr:
|
||||||
- host: 192.168.0.86
|
- host: 192.168.0.86
|
||||||
port: 8686
|
port: 8686
|
||||||
api_token: ${MY_LIDARR_API_TOKEN} # Example of configuring using environment variables
|
api_token: ${MY_LIDARR_API_TOKEN} # Example of configuring using environment variables
|
||||||
whisparr:
|
monitored_storage_paths: # Filter which Root Folders or Disk Storage you want displayed in the UI's 'Stats' block
|
||||||
- host: 192.168.0.69
|
# Note: Setting these values does not affect what shows up in the 'Root Folders' tab of the UI.
|
||||||
port: 6969
|
- /nfs # An example disk (i.e. '<servarr> list disk-space' command) you want displayed in the UI under 'Storage:'
|
||||||
|
- /media # An example root folder you want displayed in the UI
|
||||||
|
# Root folders collapse up to the super-directory to reduce duplication in the UI. For example:
|
||||||
|
# if you have root folders '/media/tv', '/media/cartoons' and '/media/reality', and you set this
|
||||||
|
# monitored path, the UI will show '/media/[tv,cartoons,reality]' under Root Folders
|
||||||
|
|
||||||
|
- host: 192.168.1.86
|
||||||
|
port: 8686
|
||||||
api_token: someApiToken1234567890
|
api_token: someApiToken1234567890
|
||||||
ssl_cert_path: /path/to/whisparr.crt
|
ssl_cert_path: /path/to/lidarr_1.crt
|
||||||
custom_headers: # Example of adding custom headers to all requests to the Servarr instance
|
custom_headers: # Example of adding custom headers to all requests to the Servarr instance
|
||||||
traefik-auth-bypass-key: someBypassKey1234567890
|
traefik-auth-bypass-key: someBypassKey1234567890
|
||||||
SOME-OTHER-CUSTOM-HEADER: ${MY_CUSTOM_HEADER_VALUE}
|
SOME-OTHER-CUSTOM-HEADER: ${MY_CUSTOM_HEADER_VALUE}
|
||||||
bazarr:
|
```
|
||||||
- host: 192.168.0.67
|
|
||||||
port: 6767
|
### SSL Configuration
|
||||||
api_token: someApiToken1234567890
|
If your Servarr is using SSL or self-signed certificates, you may need to specify additional configuration options to connect without issues.
|
||||||
prowlarr:
|
|
||||||
- host: 192.168.0.96
|
|
||||||
port: 9696
|
**If your Servarr's domain CA is installed in the system's trust store:**
|
||||||
api_token: someApiToken1234567890
|
Then you can simply specify `ssl: true` and Managarr will be able to connect to your Servarr:
|
||||||
tautulli:
|
|
||||||
- host: 192.168.0.81
|
```yaml
|
||||||
port: 8181
|
radarr:
|
||||||
api_token: someApiToken1234567890
|
- host: 192.168.0.78
|
||||||
|
port: 7878
|
||||||
|
api_token: yourApiTokenHere
|
||||||
|
ssl: true
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
**If your Servarr's domain CA is not installed:**
|
||||||
|
You'll either need to specify the path to the certificate via the `ssl_cert_path` property, or you'll need to install the certificate into your system store.
|
||||||
|
|
||||||
|
To acquire the cert for your Servarr's domain, you can use the following command:
|
||||||
|
```shell
|
||||||
|
openssl s_client -show-certs -connect <your-servarr-domain.com>:<port> </dev/null |\
|
||||||
|
sed -n -e '/-.BEGIN/,/-.END/ p' > /path/to/your/servarr.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, you can either specify `ssl_cert_path: /path/to/your/servarr.pem`:
|
||||||
|
|
||||||
|
Example configuration with a certificate that's not installed to the system trust store:
|
||||||
|
```yaml
|
||||||
|
radarr:
|
||||||
|
- host: 192.168.0.78
|
||||||
|
port: 7878
|
||||||
|
api_token: yourApiTokenHere
|
||||||
|
ssl_cert_path: /path/to/your/certificate.crt
|
||||||
|
```
|
||||||
|
|
||||||
|
Or install the certificate into your system's trust store.
|
||||||
|
|
||||||
|
For example, if you're on a Debian-based system and have `ca-certificates` installed (`sudo apt install ca-certificates`):
|
||||||
|
```shell
|
||||||
|
sudo mv /path/to/your/servarr.pem /usr/local/share/ca-certificates/servarr.pem
|
||||||
|
sudo update-ca-certificates
|
||||||
|
```
|
||||||
|
|
||||||
|
Example configuration with a certificate that is installed to the system trust store:
|
||||||
|
```yaml
|
||||||
|
radarr:
|
||||||
|
- host: 192.168.0.78
|
||||||
|
port: 7878
|
||||||
|
api_token: yourApiTokenHere
|
||||||
|
ssl: true
|
||||||
```
|
```
|
||||||
|
|
||||||
### Example Multi-Instance Configuration:
|
### Example Multi-Instance Configuration:
|
||||||
@@ -428,9 +515,6 @@ Managarr supports using environment variables on startup so you don't have to al
|
|||||||
| `MANAGARR_CONFIG_FILE` | Set the path to the config file | `--config` |
|
| `MANAGARR_CONFIG_FILE` | Set the path to the config file | `--config` |
|
||||||
| `MANAGARR_DISABLE_SPINNER` | Disable the CLI spinner (this can be useful when scripting and parsing output) | `--disable-spinner` |
|
| `MANAGARR_DISABLE_SPINNER` | Disable the CLI spinner (this can be useful when scripting and parsing output) | `--disable-spinner` |
|
||||||
|
|
||||||
## Track What I'm Currently Working On
|
|
||||||
To see what feature(s) I'm currently working on, check out my [Wekan Board](https://wekan.alexjclarke.com/b/dHoGjBb44MHM9HSv4/managarr).
|
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
### Radarr
|
### Radarr
|
||||||
@@ -446,6 +530,13 @@ To see what feature(s) I'm currently working on, check out my [Wekan Board](http
|
|||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
### Lidarr
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
### General
|
### General
|
||||||

|

|
||||||

|

|
||||||
@@ -461,8 +552,8 @@ To see what feature(s) I'm currently working on, check out my [Wekan Board](http
|
|||||||
## Servarr Requirements
|
## Servarr Requirements
|
||||||
* [Radarr >= 5.3.6.8612](https://radarr.video/docs/api/)
|
* [Radarr >= 5.3.6.8612](https://radarr.video/docs/api/)
|
||||||
* [Sonarr >= v4](https://sonarr.tv/docs/api/)
|
* [Sonarr >= v4](https://sonarr.tv/docs/api/)
|
||||||
* [Readarr v1](https://readarr.com/docs/api/)
|
|
||||||
* [Lidarr v1](https://lidarr.audio/docs/api/)
|
* [Lidarr v1](https://lidarr.audio/docs/api/)
|
||||||
|
* [Readarr v1](https://readarr.com/docs/api/)
|
||||||
* [Whisparr >= v3](https://whisparr.com/docs/api/)
|
* [Whisparr >= v3](https://whisparr.com/docs/api/)
|
||||||
* [Prowlarr v1](https://prowlarr.com/docs/api/)
|
* [Prowlarr v1](https://prowlarr.com/docs/api/)
|
||||||
* [Bazarr v1.1.4](http://localhost:6767/api)
|
* [Bazarr v1.1.4](http://localhost:6767/api)
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
VERSION := "latest"
|
VERSION := "latest"
|
||||||
IMG_NAME := "darkalex17/managarr"
|
IMG_NAME := "darkalex17/managarr"
|
||||||
IMAGE := "{{IMG_NAME}}:{{VERSION}}"
|
|
||||||
|
|
||||||
|
|
||||||
# List all recipes
|
# List all recipes
|
||||||
default:
|
default:
|
||||||
@@ -87,5 +85,5 @@ build build_type='debug':
|
|||||||
|
|
||||||
# Build the docker image
|
# Build the docker image
|
||||||
[group: 'build']
|
[group: 'build']
|
||||||
build-docker:
|
build-docker version=VERSION:
|
||||||
@DOCKER_BUILDKIT=1 docker build --rm -t {{IMAGE}}
|
@DOCKER_BUILDKIT=1 docker build --rm -t {{IMG_NAME}}:{{version}} .
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
tab_spaces=2
|
tab_spaces=2
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
reorder_imports = true
|
reorder_imports = true
|
||||||
imports_granularity = "Crate"
|
imports_granularity = "Crate"
|
||||||
group_imports = "StdExternalCrate"
|
group_imports = "StdExternalCrate"
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 194 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 204 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 252 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 187 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 319 KiB |
+126
-1
@@ -447,6 +447,78 @@ mod tests {
|
|||||||
assert_none!(config.port);
|
assert_none!(config.port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_deserialize_optional_env_var_bool_is_bool() {
|
||||||
|
let yaml_data = r#"
|
||||||
|
host: localhost
|
||||||
|
api_token: "test123"
|
||||||
|
ssl: true
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||||
|
|
||||||
|
assert_some_eq_x!(&config.ssl, &true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_deserialize_optional_env_var_bool_is_string() {
|
||||||
|
let yaml_data = r#"
|
||||||
|
host: localhost
|
||||||
|
api_token: "test123"
|
||||||
|
ssl: "true"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||||
|
|
||||||
|
assert_some_eq_x!(&config.ssl, &true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_deserialize_optional_env_var_bool_is_present() {
|
||||||
|
unsafe { std::env::set_var("TEST_VAR_DESERIALIZE_OPTION_BOOL", "true") };
|
||||||
|
let yaml_data = r#"
|
||||||
|
host: localhost
|
||||||
|
api_token: "test123"
|
||||||
|
ssl: ${TEST_VAR_DESERIALIZE_OPTION_BOOL}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||||
|
|
||||||
|
assert_some_eq_x!(&config.ssl, &true);
|
||||||
|
unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_OPTION_BOOL") };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_deserialize_optional_env_var_bool_defaults_to_false() {
|
||||||
|
unsafe { std::env::set_var("TEST_VAR_DESERIALIZE_OPTION_BOOL_FALSEY", "test") };
|
||||||
|
let yaml_data = r#"
|
||||||
|
host: localhost
|
||||||
|
api_token: "test123"
|
||||||
|
ssl: ${TEST_VAR_DESERIALIZE_OPTION_BOOL_FALSEY}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||||
|
|
||||||
|
assert_some_eq_x!(&config.ssl, &false);
|
||||||
|
unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_OPTION_BOOL_FALSEY") };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deserialize_optional_env_var_bool_empty() {
|
||||||
|
let yaml_data = r#"
|
||||||
|
host: localhost
|
||||||
|
api_token: "test123"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||||
|
|
||||||
|
assert_none!(config.ssl);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[serial]
|
#[serial]
|
||||||
fn test_deserialize_optional_env_var_header_map_is_present() {
|
fn test_deserialize_optional_env_var_header_map_is_present() {
|
||||||
@@ -507,6 +579,56 @@ mod tests {
|
|||||||
assert_none!(config.custom_headers);
|
assert_none!(config.custom_headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_deserialize_optional_env_var_string_vec_is_present() {
|
||||||
|
unsafe { std::env::set_var("TEST_VAR_DESERIALIZE_STRING_VEC_OPTION", "/path1") };
|
||||||
|
let expected_monitored_paths = ["/path1", "/path2"];
|
||||||
|
let yaml_data = r#"
|
||||||
|
monitored_storage_paths:
|
||||||
|
- ${TEST_VAR_DESERIALIZE_STRING_VEC_OPTION}
|
||||||
|
- /path2
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||||
|
|
||||||
|
assert_some_eq_x!(&config.monitored_storage_paths, &expected_monitored_paths);
|
||||||
|
unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_STRING_VEC_OPTION") };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_deserialize_optional_env_var_string_vec_does_not_overwrite_non_env_value() {
|
||||||
|
unsafe {
|
||||||
|
std::env::set_var(
|
||||||
|
"TEST_VAR_DESERIALIZE_STRING_VEC_OPTION_NO_OVERWRITE",
|
||||||
|
"/path3",
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let expected_monitored_paths = ["/path1", "/path2"];
|
||||||
|
let yaml_data = r#"
|
||||||
|
monitored_storage_paths:
|
||||||
|
- /path1
|
||||||
|
- /path2
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||||
|
|
||||||
|
assert_some_eq_x!(&config.monitored_storage_paths, &expected_monitored_paths);
|
||||||
|
unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_STRING_VEC_OPTION_NO_OVERWRITE") };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deserialize_optional_env_var_string_vec_empty() {
|
||||||
|
let yaml_data = r#"
|
||||||
|
api_token: "test123"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||||
|
|
||||||
|
assert_none!(config.monitored_storage_paths);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[serial]
|
#[serial]
|
||||||
fn test_deserialize_optional_u16_env_var_is_present() {
|
fn test_deserialize_optional_u16_env_var_is_present() {
|
||||||
@@ -620,10 +742,11 @@ mod tests {
|
|||||||
let api_token = "thisisatest".to_owned();
|
let api_token = "thisisatest".to_owned();
|
||||||
let api_token_file = "/root/.config/api_token".to_owned();
|
let api_token_file = "/root/.config/api_token".to_owned();
|
||||||
let ssl_cert_path = "/some/path".to_owned();
|
let ssl_cert_path = "/some/path".to_owned();
|
||||||
|
let monitored_storage = vec!["/path1".to_owned(), "/path2".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!(
|
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\"}}) }}"
|
"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: Some(true), ssl_cert_path: Some(\"{ssl_cert_path}\"), custom_headers: Some({{\"x-custom-header\": \"value\"}}), monitored_storage_paths: Some([\"/path1\", \"/path2\"]) }}"
|
||||||
);
|
);
|
||||||
let servarr_config = ServarrConfig {
|
let servarr_config = ServarrConfig {
|
||||||
name: Some(name),
|
name: Some(name),
|
||||||
@@ -634,7 +757,9 @@ mod tests {
|
|||||||
api_token: Some(api_token),
|
api_token: Some(api_token),
|
||||||
api_token_file: Some(api_token_file),
|
api_token_file: Some(api_token_file),
|
||||||
ssl_cert_path: Some(ssl_cert_path),
|
ssl_cert_path: Some(ssl_cert_path),
|
||||||
|
ssl: Some(true),
|
||||||
custom_headers: Some(custom_headers),
|
custom_headers: Some(custom_headers),
|
||||||
|
monitored_storage_paths: Some(monitored_storage),
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_str_eq!(format!("{servarr_config:?}"), expected_str);
|
assert_str_eq!(format!("{servarr_config:?}"), expected_str);
|
||||||
|
|||||||
@@ -124,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),
|
||||||
|
];
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ mod test {
|
|||||||
BARE_POPUP_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES,
|
BARE_POPUP_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES,
|
||||||
ContextClueProvider, DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
|
ContextClueProvider, DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
|
||||||
ROOT_FOLDERS_CONTEXT_CLUES, SERVARR_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
|
ROOT_FOLDERS_CONTEXT_CLUES, SERVARR_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
|
||||||
ServarrContextClueProvider,
|
SYSTEM_TASKS_CONTEXT_CLUES, ServarrContextClueProvider,
|
||||||
};
|
};
|
||||||
use crate::app::{App, key_binding::DEFAULT_KEYBINDINGS};
|
use crate::app::{App, key_binding::DEFAULT_KEYBINDINGS};
|
||||||
use crate::models::servarr_data::ActiveKeybindingBlock;
|
use crate::models::servarr_data::ActiveKeybindingBlock;
|
||||||
@@ -268,6 +268,21 @@ mod test {
|
|||||||
assert_none!(system_context_clues_iter.next());
|
assert_none!(system_context_clues_iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_system_tasks_context_clues() {
|
||||||
|
let mut system_tasks_context_clues_iter = SYSTEM_TASKS_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
|
assert_some_eq_x!(
|
||||||
|
system_tasks_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.submit, "start task")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
system_tasks_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||||
|
);
|
||||||
|
assert_none!(system_tasks_context_clues_iter.next());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_servarr_context_clue_provider_delegates_to_radarr_provider() {
|
fn test_servarr_context_clue_provider_delegates_to_radarr_provider() {
|
||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
@@ -275,10 +290,7 @@ mod test {
|
|||||||
|
|
||||||
let context_clues = ServarrContextClueProvider::get_context_clues(&mut app);
|
let context_clues = ServarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
assert_some_eq_x!(
|
assert_some_eq_x!(context_clues, &SYSTEM_TASKS_CONTEXT_CLUES,);
|
||||||
context_clues,
|
|
||||||
&crate::app::radarr::radarr_context_clues::SYSTEM_TASKS_CONTEXT_CLUES,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -288,10 +300,7 @@ mod test {
|
|||||||
|
|
||||||
let context_clues = ServarrContextClueProvider::get_context_clues(&mut app);
|
let context_clues = ServarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
assert_some_eq_x!(
|
assert_some_eq_x!(context_clues, &SYSTEM_TASKS_CONTEXT_CLUES,);
|
||||||
context_clues,
|
|
||||||
&crate::app::sonarr::sonarr_context_clues::SYSTEM_TASKS_CONTEXT_CLUES,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::app::context_clues::{
|
use crate::app::context_clues::{
|
||||||
BARE_POPUP_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, ContextClue, ContextClueProvider,
|
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::models::Route;
|
use crate::models::Route;
|
||||||
use crate::models::servarr_data::lidarr::lidarr_data::{
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
ADD_ARTIST_BLOCKS, ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, EDIT_ARTIST_BLOCKS,
|
ADD_ARTIST_BLOCKS, ADD_ROOT_FOLDER_BLOCKS, ALBUM_DETAILS_BLOCKS, ARTIST_DETAILS_BLOCKS,
|
||||||
|
ActiveLidarrBlock, EDIT_ARTIST_BLOCKS, EDIT_INDEXER_BLOCKS, INDEXER_SETTINGS_BLOCKS,
|
||||||
|
TRACK_DETAILS_BLOCKS,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -56,6 +59,105 @@ pub static ARTIST_DETAILS_CONTEXT_CLUES: [ContextClue; 8] = [
|
|||||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.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.update, DEFAULT_KEYBINDINGS.update.desc),
|
||||||
|
(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.esc, "cancel filter/close"),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub static MANUAL_ARTIST_SEARCH_CONTEXT_CLUES: [ContextClue; 7] = [
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||||
|
),
|
||||||
|
(DEFAULT_KEYBINDINGS.edit, "edit artist"),
|
||||||
|
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||||
|
(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc),
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search,
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||||
|
),
|
||||||
|
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
||||||
|
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub static ALBUM_DETAILS_CONTEXT_CLUES: [ContextClue; 6] = [
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||||
|
),
|
||||||
|
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search,
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||||
|
),
|
||||||
|
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||||
|
(DEFAULT_KEYBINDINGS.submit, "track details"),
|
||||||
|
(DEFAULT_KEYBINDINGS.delete, "delete track"),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub static ALBUM_HISTORY_CONTEXT_CLUES: [ContextClue; 7] = [
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||||
|
),
|
||||||
|
(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.submit, "details"),
|
||||||
|
(DEFAULT_KEYBINDINGS.esc, "cancel filter/close"),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub static MANUAL_ALBUM_SEARCH_CONTEXT_CLUES: [ContextClue; 5] = [
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search,
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||||
|
),
|
||||||
|
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
||||||
|
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||||
|
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub static TRACK_DETAILS_CONTEXT_CLUES: [ContextClue; 2] = [
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||||
|
),
|
||||||
|
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub static TRACK_HISTORY_CONTEXT_CLUES: [ContextClue; 6] = [
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||||
|
),
|
||||||
|
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
||||||
|
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
||||||
|
(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc),
|
||||||
|
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||||
|
(DEFAULT_KEYBINDINGS.esc, "cancel filter/close"),
|
||||||
|
];
|
||||||
|
|
||||||
pub(in crate::app) struct LidarrContextClueProvider;
|
pub(in crate::app) struct LidarrContextClueProvider;
|
||||||
|
|
||||||
impl ContextClueProvider for LidarrContextClueProvider {
|
impl ContextClueProvider for LidarrContextClueProvider {
|
||||||
@@ -70,10 +172,35 @@ impl ContextClueProvider for LidarrContextClueProvider {
|
|||||||
.lidarr_data
|
.lidarr_data
|
||||||
.artist_info_tabs
|
.artist_info_tabs
|
||||||
.get_active_route_contextual_help(),
|
.get_active_route_contextual_help(),
|
||||||
ActiveLidarrBlock::AddArtistSearchInput | ActiveLidarrBlock::AddArtistEmptySearchResults => {
|
_ if ALBUM_DETAILS_BLOCKS.contains(&active_lidarr_block) => app
|
||||||
Some(&BARE_POPUP_CONTEXT_CLUES)
|
.data
|
||||||
}
|
.lidarr_data
|
||||||
_ if EDIT_ARTIST_BLOCKS.contains(&active_lidarr_block) => {
|
.album_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.expect("album_details_modal is empty")
|
||||||
|
.album_details_tabs
|
||||||
|
.get_active_route_contextual_help(),
|
||||||
|
_ if TRACK_DETAILS_BLOCKS.contains(&active_lidarr_block) => app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.expect("album_details_modal is empty")
|
||||||
|
.track_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.expect("track_details_modal is empty")
|
||||||
|
.track_details_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)
|
Some(&CONFIRMATION_PROMPT_CONTEXT_CLUES)
|
||||||
}
|
}
|
||||||
ActiveLidarrBlock::AddArtistPrompt
|
ActiveLidarrBlock::AddArtistPrompt
|
||||||
@@ -87,6 +214,7 @@ impl ContextClueProvider for LidarrContextClueProvider {
|
|||||||
_ if ADD_ARTIST_BLOCKS.contains(&active_lidarr_block) => {
|
_ if ADD_ARTIST_BLOCKS.contains(&active_lidarr_block) => {
|
||||||
Some(&ADD_ARTIST_SEARCH_RESULTS_CONTEXT_CLUES)
|
Some(&ADD_ARTIST_SEARCH_RESULTS_CONTEXT_CLUES)
|
||||||
}
|
}
|
||||||
|
ActiveLidarrBlock::SystemTasks => Some(&SYSTEM_TASKS_CONTEXT_CLUES),
|
||||||
_ => app
|
_ => app
|
||||||
.data
|
.data
|
||||||
.lidarr_data
|
.lidarr_data
|
||||||
|
|||||||
@@ -3,15 +3,20 @@ mod tests {
|
|||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::app::context_clues::{
|
use crate::app::context_clues::{
|
||||||
BARE_POPUP_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, ContextClue, ContextClueProvider,
|
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::lidarr::lidarr_context_clues::{
|
use crate::app::lidarr::lidarr_context_clues::{
|
||||||
ADD_ARTIST_SEARCH_RESULTS_CONTEXT_CLUES, ARTIST_DETAILS_CONTEXT_CLUES, ARTISTS_CONTEXT_CLUES,
|
ADD_ARTIST_SEARCH_RESULTS_CONTEXT_CLUES, ALBUM_DETAILS_CONTEXT_CLUES,
|
||||||
LidarrContextClueProvider,
|
ALBUM_HISTORY_CONTEXT_CLUES, ARTIST_DETAILS_CONTEXT_CLUES, ARTIST_HISTORY_CONTEXT_CLUES,
|
||||||
|
ARTISTS_CONTEXT_CLUES, LidarrContextClueProvider, MANUAL_ALBUM_SEARCH_CONTEXT_CLUES,
|
||||||
|
MANUAL_ARTIST_SEARCH_CONTEXT_CLUES, TRACK_DETAILS_CONTEXT_CLUES, TRACK_HISTORY_CONTEXT_CLUES,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_data::lidarr::lidarr_data::{
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
ActiveLidarrBlock, EDIT_ARTIST_BLOCKS, LidarrData,
|
ADD_ROOT_FOLDER_BLOCKS, ActiveLidarrBlock, EDIT_ARTIST_BLOCKS, EDIT_INDEXER_BLOCKS,
|
||||||
|
INDEXER_SETTINGS_BLOCKS, LidarrData,
|
||||||
};
|
};
|
||||||
|
use crate::models::servarr_data::lidarr::modals::{AlbumDetailsModal, TrackDetailsModal};
|
||||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
|
|
||||||
@@ -143,8 +148,263 @@ mod tests {
|
|||||||
LidarrContextClueProvider::get_context_clues(&mut app);
|
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.update, DEFAULT_KEYBINDINGS.update.desc)
|
||||||
|
);
|
||||||
|
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.esc, "cancel filter/close")
|
||||||
|
);
|
||||||
|
assert_none!(artist_history_context_clues_iter.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_manual_artist_search_context_clues() {
|
||||||
|
let mut manual_artist_search_context_clues_iter = MANUAL_ARTIST_SEARCH_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
|
assert_some_eq_x!(
|
||||||
|
manual_artist_search_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
manual_artist_search_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.edit, "edit artist")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
manual_artist_search_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
manual_artist_search_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
manual_artist_search_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search,
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search.desc
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
manual_artist_search_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
manual_artist_search_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||||
|
);
|
||||||
|
assert_none!(manual_artist_search_context_clues_iter.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_album_details_context_clues() {
|
||||||
|
let mut album_details_context_clues_iter = ALBUM_DETAILS_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
|
assert_some_eq_x!(
|
||||||
|
album_details_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
album_details_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
album_details_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search,
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search.desc
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
album_details_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
album_details_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.submit, "track details")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
album_details_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.delete, "delete track")
|
||||||
|
);
|
||||||
|
assert_none!(album_details_context_clues_iter.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_album_history_context_clues() {
|
||||||
|
let mut album_history_context_clues_iter = ALBUM_HISTORY_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
|
assert_some_eq_x!(
|
||||||
|
album_history_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
album_history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
album_history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
album_history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
album_history_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search,
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search.desc
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
album_history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
album_history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.esc, "cancel filter/close")
|
||||||
|
);
|
||||||
|
assert_none!(album_history_context_clues_iter.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_manual_album_search_context_clues() {
|
||||||
|
let mut manual_album_search_context_clues_iter = MANUAL_ALBUM_SEARCH_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
|
assert_some_eq_x!(
|
||||||
|
manual_album_search_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
manual_album_search_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search,
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search.desc
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
manual_album_search_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
manual_album_search_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
manual_album_search_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||||
|
);
|
||||||
|
assert_none!(manual_album_search_context_clues_iter.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_track_details_context_clues() {
|
||||||
|
let mut track_details_context_clues_iter = TRACK_DETAILS_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
|
assert_some_eq_x!(
|
||||||
|
track_details_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
track_details_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||||
|
);
|
||||||
|
assert_none!(track_details_context_clues_iter.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_track_history_context_clues() {
|
||||||
|
let mut track_history_context_clues_iter = TRACK_HISTORY_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
|
assert_some_eq_x!(
|
||||||
|
track_history_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
track_history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
track_history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
track_history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
track_history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
track_history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.esc, "cancel filter/close")
|
||||||
|
);
|
||||||
|
assert_none!(track_history_context_clues_iter.next());
|
||||||
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(0, ActiveLidarrBlock::ArtistDetails, &ARTIST_DETAILS_CONTEXT_CLUES)]
|
#[case(0, ActiveLidarrBlock::ArtistDetails, &ARTIST_DETAILS_CONTEXT_CLUES)]
|
||||||
|
#[case(1, ActiveLidarrBlock::ArtistHistory, &ARTIST_HISTORY_CONTEXT_CLUES)]
|
||||||
|
#[case(2, ActiveLidarrBlock::ManualArtistSearch, &MANUAL_ARTIST_SEARCH_CONTEXT_CLUES)]
|
||||||
fn test_lidarr_context_clue_provider_artist_info_tabs(
|
fn test_lidarr_context_clue_provider_artist_info_tabs(
|
||||||
#[case] index: usize,
|
#[case] index: usize,
|
||||||
#[case] active_lidarr_block: ActiveLidarrBlock,
|
#[case] active_lidarr_block: ActiveLidarrBlock,
|
||||||
@@ -160,6 +420,57 @@ mod tests {
|
|||||||
assert_some_eq_x!(context_clues, expected_context_clues);
|
assert_some_eq_x!(context_clues, expected_context_clues);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(0, ActiveLidarrBlock::AlbumDetails, &ALBUM_DETAILS_CONTEXT_CLUES)]
|
||||||
|
#[case(1, ActiveLidarrBlock::AlbumHistory, &ALBUM_HISTORY_CONTEXT_CLUES)]
|
||||||
|
#[case(2, ActiveLidarrBlock::ManualAlbumSearch, &MANUAL_ALBUM_SEARCH_CONTEXT_CLUES)]
|
||||||
|
fn test_lidarr_context_clue_provider_album_details_tabs(
|
||||||
|
#[case] index: usize,
|
||||||
|
#[case] active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
#[case] expected_context_clues: &[ContextClue],
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
let mut album_details_modal = AlbumDetailsModal::default();
|
||||||
|
album_details_modal.album_details_tabs.set_index(index);
|
||||||
|
let lidarr_data = LidarrData {
|
||||||
|
album_details_modal: Some(album_details_modal),
|
||||||
|
..LidarrData::default()
|
||||||
|
};
|
||||||
|
app.data.lidarr_data = lidarr_data;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(0, ActiveLidarrBlock::TrackDetails, &TRACK_DETAILS_CONTEXT_CLUES)]
|
||||||
|
#[case(1, ActiveLidarrBlock::TrackHistory, &TRACK_HISTORY_CONTEXT_CLUES)]
|
||||||
|
fn test_lidarr_context_clue_provider_track_details_tabs(
|
||||||
|
#[case] index: usize,
|
||||||
|
#[case] active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
#[case] expected_context_clues: &[ContextClue],
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
let mut track_details_modal = TrackDetailsModal::default();
|
||||||
|
track_details_modal.track_details_tabs.set_index(index);
|
||||||
|
let album_details_modal = AlbumDetailsModal {
|
||||||
|
track_details_modal: Some(track_details_modal),
|
||||||
|
..AlbumDetailsModal::default()
|
||||||
|
};
|
||||||
|
let lidarr_data = LidarrData {
|
||||||
|
album_details_modal: Some(album_details_modal),
|
||||||
|
..LidarrData::default()
|
||||||
|
};
|
||||||
|
app.data.lidarr_data = lidarr_data;
|
||||||
|
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]
|
#[test]
|
||||||
fn test_lidarr_context_clue_provider_artists_block() {
|
fn test_lidarr_context_clue_provider_artists_block() {
|
||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
@@ -204,7 +515,8 @@ mod tests {
|
|||||||
fn test_lidarr_context_clue_provider_bare_popup_context_clues(
|
fn test_lidarr_context_clue_provider_bare_popup_context_clues(
|
||||||
#[values(
|
#[values(
|
||||||
ActiveLidarrBlock::AddArtistSearchInput,
|
ActiveLidarrBlock::AddArtistSearchInput,
|
||||||
ActiveLidarrBlock::AddArtistEmptySearchResults
|
ActiveLidarrBlock::AddArtistEmptySearchResults,
|
||||||
|
ActiveLidarrBlock::TestAllIndexers
|
||||||
)]
|
)]
|
||||||
active_lidarr_block: ActiveLidarrBlock,
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
) {
|
) {
|
||||||
@@ -218,7 +530,12 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lidarr_context_clue_provider_confirmation_prompt_popup_clues_edit_indexer_blocks() {
|
fn test_lidarr_context_clue_provider_confirmation_prompt_popup_clues_edit_indexer_blocks() {
|
||||||
for active_lidarr_block in EDIT_ARTIST_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();
|
let mut app = App::test_default();
|
||||||
app.push_navigation_stack(active_lidarr_block.into());
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
|
||||||
@@ -259,4 +576,14 @@ mod tests {
|
|||||||
|
|
||||||
assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
|
assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
|
use crate::models::lidarr_models::{Album, Artist, LidarrRelease};
|
||||||
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
||||||
|
use crate::models::servarr_data::lidarr::modals::AlbumDetailsModal;
|
||||||
|
use crate::models::servarr_models::Indexer;
|
||||||
use crate::network::NetworkEvent;
|
use crate::network::NetworkEvent;
|
||||||
use crate::network::lidarr_network::LidarrEvent;
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::artist;
|
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::{
|
||||||
|
album, artist, track,
|
||||||
|
};
|
||||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
@@ -12,6 +17,7 @@ mod tests {
|
|||||||
async fn test_dispatch_by_lidarr_block_artists() {
|
async fn test_dispatch_by_lidarr_block_artists() {
|
||||||
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
app.network_tx = Some(tx);
|
app.network_tx = Some(tx);
|
||||||
|
|
||||||
app
|
app
|
||||||
@@ -29,7 +35,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetTags.into());
|
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetTags.into());
|
||||||
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::ListArtists.into());
|
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::ListArtists.into());
|
||||||
assert!(!app.data.sonarr_data.prompt_confirm);
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
assert_eq!(app.tick_count, 0);
|
assert_eq!(app.tick_count, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +43,7 @@ mod tests {
|
|||||||
async fn test_dispatch_by_lidarr_block_artist_details() {
|
async fn test_dispatch_by_lidarr_block_artist_details() {
|
||||||
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
app.data.lidarr_data.artists.set_items(vec![artist()]);
|
app.data.lidarr_data.artists.set_items(vec![artist()]);
|
||||||
app.network_tx = Some(tx);
|
app.network_tx = Some(tx);
|
||||||
|
|
||||||
@@ -46,14 +53,266 @@ mod tests {
|
|||||||
|
|
||||||
assert!(app.is_loading);
|
assert!(app.is_loading);
|
||||||
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetAlbums(1).into());
|
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_blocklist_block() {
|
||||||
|
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::Blocklist)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetBlocklist.into());
|
||||||
assert!(!app.data.sonarr_data.prompt_confirm);
|
assert!(!app.data.sonarr_data.prompt_confirm);
|
||||||
assert_eq!(app.tick_count, 0);
|
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_manual_artist_search_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::ManualArtistSearch)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert_eq!(
|
||||||
|
rx.recv().await.unwrap(),
|
||||||
|
LidarrEvent::GetDiscographyReleases(1).into()
|
||||||
|
);
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_manual_artist_search_block_discography_releases_non_empty() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.discography_releases
|
||||||
|
.set_items(vec![LidarrRelease::default()]);
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_lidarr_block(&ActiveLidarrBlock::ManualArtistSearch)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(!app.is_loading);
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_album_details_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.data.lidarr_data.albums.set_items(vec![Album {
|
||||||
|
id: 1,
|
||||||
|
..Album::default()
|
||||||
|
}]);
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_lidarr_block(&ActiveLidarrBlock::AlbumDetails)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert_eq!(
|
||||||
|
rx.recv().await.unwrap(),
|
||||||
|
LidarrEvent::GetTracks(1, 1).into()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
rx.recv().await.unwrap(),
|
||||||
|
LidarrEvent::GetTrackFiles(1).into()
|
||||||
|
);
|
||||||
|
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_album_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.data.lidarr_data.albums.set_items(vec![Album {
|
||||||
|
id: 1,
|
||||||
|
..Album::default()
|
||||||
|
}]);
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_lidarr_block(&ActiveLidarrBlock::AlbumHistory)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert_eq!(
|
||||||
|
rx.recv().await.unwrap(),
|
||||||
|
LidarrEvent::GetAlbumHistory(1, 1).into()
|
||||||
|
);
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_album_history_block_no_op_when_albums_table_is_empty() {
|
||||||
|
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.data.lidarr_data.artists.set_items(vec![Artist {
|
||||||
|
id: 1,
|
||||||
|
..Artist::default()
|
||||||
|
}]);
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_lidarr_block(&ActiveLidarrBlock::AlbumHistory)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(!app.is_loading);
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_manual_album_search_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.data.lidarr_data.albums.set_items(vec![Album {
|
||||||
|
id: 1,
|
||||||
|
..Album::default()
|
||||||
|
}]);
|
||||||
|
app.data.lidarr_data.album_details_modal = Some(AlbumDetailsModal::default());
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_lidarr_block(&ActiveLidarrBlock::ManualAlbumSearch)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert_eq!(
|
||||||
|
rx.recv().await.unwrap(),
|
||||||
|
LidarrEvent::GetAlbumReleases(1, 1).into()
|
||||||
|
);
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_manual_album_search_block_is_loading() {
|
||||||
|
let mut app = App {
|
||||||
|
is_loading: true,
|
||||||
|
..App::test_default()
|
||||||
|
};
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_lidarr_block(&ActiveLidarrBlock::ManualAlbumSearch)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_manual_album_search_block_album_releases_non_empty() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
let mut album_details_modal = AlbumDetailsModal::default();
|
||||||
|
album_details_modal
|
||||||
|
.album_releases
|
||||||
|
.set_items(vec![LidarrRelease::default()]);
|
||||||
|
app.data.lidarr_data.album_details_modal = Some(album_details_modal);
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_lidarr_block(&ActiveLidarrBlock::ManualAlbumSearch)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(!app.is_loading);
|
||||||
|
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]
|
#[tokio::test]
|
||||||
async fn test_dispatch_by_lidarr_block_add_artist_search_results() {
|
async fn test_dispatch_by_lidarr_block_add_artist_search_results() {
|
||||||
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
app.network_tx = Some(tx);
|
app.network_tx = Some(tx);
|
||||||
app.data.lidarr_data.add_artist_search = Some("test artist".into());
|
app.data.lidarr_data.add_artist_search = Some("test artist".into());
|
||||||
|
|
||||||
@@ -74,6 +333,7 @@ mod tests {
|
|||||||
async fn test_dispatch_by_history_block() {
|
async fn test_dispatch_by_history_block() {
|
||||||
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
app.network_tx = Some(tx);
|
app.network_tx = Some(tx);
|
||||||
|
|
||||||
app
|
app
|
||||||
@@ -89,6 +349,376 @@ mod tests {
|
|||||||
assert_eq!(app.tick_count, 0);
|
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_dispatch_by_track_details_block() {
|
||||||
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
app.network_tx = Some(tx);
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_lidarr_block(&ActiveLidarrBlock::TrackDetails)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert_eq!(
|
||||||
|
rx.recv().await.unwrap(),
|
||||||
|
LidarrEvent::GetTrackDetails(1).into()
|
||||||
|
);
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_track_history_block() {
|
||||||
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
app.network_tx = Some(tx);
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_lidarr_block(&ActiveLidarrBlock::TrackHistory)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert_eq!(
|
||||||
|
rx.recv().await.unwrap(),
|
||||||
|
LidarrEvent::GetTrackHistory(1, 1, 1).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]
|
#[tokio::test]
|
||||||
async fn test_extract_add_new_artist_search_query() {
|
async fn test_extract_add_new_artist_search_query() {
|
||||||
let app = App::test_default_fully_populated();
|
let app = App::test_default_fully_populated();
|
||||||
@@ -98,6 +728,14 @@ mod tests {
|
|||||||
assert_str_eq!(query, "Test Artist");
|
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]
|
#[tokio::test]
|
||||||
async fn test_extract_artist_id() {
|
async fn test_extract_artist_id() {
|
||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
@@ -105,4 +743,41 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(app.extract_artist_id().await, 1);
|
assert_eq!(app.extract_artist_id().await, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_extract_album_id() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.albums.set_items(vec![album()]);
|
||||||
|
|
||||||
|
assert_eq!(app.extract_album_id().await, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_extract_track_id() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
let mut album_details_modal = AlbumDetailsModal::default();
|
||||||
|
album_details_modal.tracks.set_items(vec![track()]);
|
||||||
|
app.data.lidarr_data.album_details_modal = Some(album_details_modal);
|
||||||
|
|
||||||
|
assert_eq!(app.extract_track_id().await, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[should_panic(expected = "album_details_modal is empty")]
|
||||||
|
async fn test_extract_track_id_panics_when_album_details_modal_is_not_set() {
|
||||||
|
let app = App::test_default();
|
||||||
|
|
||||||
|
app.extract_track_id().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+153
-3
@@ -1,10 +1,9 @@
|
|||||||
|
use super::App;
|
||||||
use crate::{
|
use crate::{
|
||||||
models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock,
|
models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock,
|
||||||
network::lidarr_network::LidarrEvent,
|
network::lidarr_network::LidarrEvent,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::App;
|
|
||||||
|
|
||||||
pub mod lidarr_context_clues;
|
pub mod lidarr_context_clues;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -28,11 +27,79 @@ impl App<'_> {
|
|||||||
.dispatch_network_event(LidarrEvent::ListArtists.into())
|
.dispatch_network_event(LidarrEvent::ListArtists.into())
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
ActiveLidarrBlock::Blocklist => {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetBlocklist.into())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::Downloads => {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetDownloads(500).into())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
ActiveLidarrBlock::ArtistDetails => {
|
ActiveLidarrBlock::ArtistDetails => {
|
||||||
self
|
self
|
||||||
.dispatch_network_event(LidarrEvent::GetAlbums(self.extract_artist_id().await).into())
|
.dispatch_network_event(LidarrEvent::GetAlbums(self.extract_artist_id().await).into())
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
ActiveLidarrBlock::ArtistHistory => {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(
|
||||||
|
LidarrEvent::GetArtistHistory(self.extract_artist_id().await).into(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::ManualArtistSearch => {
|
||||||
|
if self.data.lidarr_data.discography_releases.is_empty() {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(
|
||||||
|
LidarrEvent::GetDiscographyReleases(self.extract_artist_id().await).into(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AlbumDetails => {
|
||||||
|
let artist_id = self.extract_artist_id().await;
|
||||||
|
let album_id = self.extract_album_id().await;
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetTracks(artist_id, album_id).into())
|
||||||
|
.await;
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetTrackFiles(album_id).into())
|
||||||
|
.await;
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetDownloads(500).into())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AlbumHistory => {
|
||||||
|
if !self.data.lidarr_data.albums.is_empty() {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(
|
||||||
|
LidarrEvent::GetAlbumHistory(
|
||||||
|
self.extract_artist_id().await,
|
||||||
|
self.extract_album_id().await,
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::ManualAlbumSearch => {
|
||||||
|
match self.data.lidarr_data.album_details_modal.as_ref() {
|
||||||
|
Some(album_details_modal) if album_details_modal.album_releases.is_empty() => {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(
|
||||||
|
LidarrEvent::GetAlbumReleases(
|
||||||
|
self.extract_artist_id().await,
|
||||||
|
self.extract_album_id().await,
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
ActiveLidarrBlock::AddArtistSearchResults => {
|
ActiveLidarrBlock::AddArtistSearchResults => {
|
||||||
self
|
self
|
||||||
.dispatch_network_event(
|
.dispatch_network_event(
|
||||||
@@ -45,6 +112,69 @@ impl App<'_> {
|
|||||||
.dispatch_network_event(LidarrEvent::GetHistory(500).into())
|
.dispatch_network_event(LidarrEvent::GetHistory(500).into())
|
||||||
.await
|
.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;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::TrackDetails => {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(
|
||||||
|
LidarrEvent::GetTrackDetails(self.extract_track_id().await).into(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::TrackHistory => {
|
||||||
|
let artist_id = self.extract_artist_id().await;
|
||||||
|
let album_id = self.extract_album_id().await;
|
||||||
|
let track_id = self.extract_track_id().await;
|
||||||
|
self
|
||||||
|
.dispatch_network_event(
|
||||||
|
LidarrEvent::GetTrackHistory(artist_id, album_id, track_id).into(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +188,7 @@ impl App<'_> {
|
|||||||
.lidarr_data
|
.lidarr_data
|
||||||
.add_artist_search
|
.add_artist_search
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("add_artist_search should be set")
|
.expect("Add artist search is empty")
|
||||||
.text
|
.text
|
||||||
.clone()
|
.clone()
|
||||||
}
|
}
|
||||||
@@ -67,6 +197,26 @@ impl App<'_> {
|
|||||||
self.data.lidarr_data.artists.current_selection().id
|
self.data.lidarr_data.artists.current_selection().id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn extract_album_id(&self) -> i64 {
|
||||||
|
self.data.lidarr_data.albums.current_selection().id
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn extract_track_id(&self) -> i64 {
|
||||||
|
self
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.expect("album_details_modal is empty")
|
||||||
|
.tracks
|
||||||
|
.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) {
|
async fn check_for_lidarr_prompt_action(&mut self) {
|
||||||
if self.data.lidarr_data.prompt_confirm {
|
if self.data.lidarr_data.prompt_confirm {
|
||||||
self.data.lidarr_data.prompt_confirm = false;
|
self.data.lidarr_data.prompt_confirm = false;
|
||||||
|
|||||||
+54
-4
@@ -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::Notification;
|
||||||
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, LidarrData};
|
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};
|
||||||
@@ -38,6 +39,7 @@ pub struct App<'a> {
|
|||||||
pub server_tabs: TabState,
|
pub server_tabs: TabState,
|
||||||
pub keymapping_table: Option<StatefulTable<KeybindingItem>>,
|
pub keymapping_table: Option<StatefulTable<KeybindingItem>>,
|
||||||
pub error: HorizontallyScrollableText,
|
pub error: HorizontallyScrollableText,
|
||||||
|
pub notification: Option<Notification>,
|
||||||
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,
|
||||||
@@ -254,6 +256,7 @@ impl Default for App<'_> {
|
|||||||
cancellation_token: CancellationToken::new(),
|
cancellation_token: CancellationToken::new(),
|
||||||
keymapping_table: None,
|
keymapping_table: None,
|
||||||
error: HorizontallyScrollableText::default(),
|
error: HorizontallyScrollableText::default(),
|
||||||
|
notification: None,
|
||||||
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,
|
||||||
@@ -346,11 +349,11 @@ pub struct AppConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AppConfig {
|
impl AppConfig {
|
||||||
pub fn validate(&self) {
|
pub fn validate(&self, config_path: &str) {
|
||||||
if self.lidarr.is_none() && 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(format!(
|
||||||
"No Servarr configuration provided in the specified configuration file".to_owned(),
|
"No Servarrs are configured in the file: {config_path}"
|
||||||
);
|
));
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,6 +431,8 @@ pub struct ServarrConfig {
|
|||||||
pub api_token: Option<String>,
|
pub api_token: Option<String>,
|
||||||
#[serde(default, deserialize_with = "deserialize_optional_env_var")]
|
#[serde(default, deserialize_with = "deserialize_optional_env_var")]
|
||||||
pub api_token_file: Option<String>,
|
pub api_token_file: Option<String>,
|
||||||
|
#[serde(default, deserialize_with = "deserialize_optional_env_var_bool")]
|
||||||
|
pub ssl: Option<bool>,
|
||||||
#[serde(default, deserialize_with = "deserialize_optional_env_var")]
|
#[serde(default, deserialize_with = "deserialize_optional_env_var")]
|
||||||
pub ssl_cert_path: Option<String>,
|
pub ssl_cert_path: Option<String>,
|
||||||
#[serde(
|
#[serde(
|
||||||
@@ -436,6 +441,8 @@ pub struct ServarrConfig {
|
|||||||
serialize_with = "serialize_header_map"
|
serialize_with = "serialize_header_map"
|
||||||
)]
|
)]
|
||||||
pub custom_headers: Option<HeaderMap>,
|
pub custom_headers: Option<HeaderMap>,
|
||||||
|
#[serde(default, deserialize_with = "deserialize_optional_env_var_string_vec")]
|
||||||
|
pub monitored_storage_paths: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServarrConfig {
|
impl ServarrConfig {
|
||||||
@@ -481,7 +488,9 @@ impl Default for ServarrConfig {
|
|||||||
api_token: Some(String::new()),
|
api_token: Some(String::new()),
|
||||||
api_token_file: None,
|
api_token_file: None,
|
||||||
ssl_cert_path: None,
|
ssl_cert_path: None,
|
||||||
|
ssl: None,
|
||||||
custom_headers: None,
|
custom_headers: None,
|
||||||
|
monitored_storage_paths: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -526,6 +535,29 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn deserialize_optional_env_var_bool<'de, D>(deserializer: D) -> Result<Option<bool>, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum StringOrBool {
|
||||||
|
Bool(bool),
|
||||||
|
String(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
match StringOrBool::deserialize(deserializer)? {
|
||||||
|
StringOrBool::Bool(b) => Ok(Some(b)),
|
||||||
|
StringOrBool::String(s) => {
|
||||||
|
let val = interpolate_env_vars(&s)
|
||||||
|
.to_lowercase()
|
||||||
|
.parse()
|
||||||
|
.unwrap_or(false);
|
||||||
|
Ok(Some(val))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn deserialize_optional_env_var_header_map<'de, D>(
|
fn deserialize_optional_env_var_header_map<'de, D>(
|
||||||
deserializer: D,
|
deserializer: D,
|
||||||
) -> Result<Option<HeaderMap>, D::Error>
|
) -> Result<Option<HeaderMap>, D::Error>
|
||||||
@@ -548,6 +580,24 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn deserialize_optional_env_var_string_vec<'de, D>(
|
||||||
|
deserializer: D,
|
||||||
|
) -> Result<Option<Vec<String>>, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let opt: Option<Vec<String>> = Option::deserialize(deserializer)?;
|
||||||
|
match opt {
|
||||||
|
Some(vec) => Ok(Some(
|
||||||
|
vec
|
||||||
|
.into_iter()
|
||||||
|
.map(|it| interpolate_env_vars(&it))
|
||||||
|
.collect(),
|
||||||
|
)),
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn deserialize_u16_env_var<'de, D>(deserializer: D) -> Result<Option<u16>, D::Error>
|
fn deserialize_u16_env_var<'de, D>(deserializer: D) -> Result<Option<u16>, D::Error>
|
||||||
where
|
where
|
||||||
D: serde::Deserializer<'de>,
|
D: serde::Deserializer<'de>,
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::app::context_clues::{
|
use crate::app::context_clues::{
|
||||||
BARE_POPUP_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, ContextClue, ContextClueProvider,
|
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::models::Route;
|
use crate::models::Route;
|
||||||
@@ -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"),
|
||||||
|
|||||||
@@ -3,14 +3,15 @@ mod tests {
|
|||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::app::context_clues::{
|
use crate::app::context_clues::{
|
||||||
BARE_POPUP_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES,
|
BARE_POPUP_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES,
|
||||||
ContextClue, ContextClueProvider, 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::{
|
||||||
ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES, COLLECTION_DETAILS_CONTEXT_CLUES,
|
ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES, COLLECTION_DETAILS_CONTEXT_CLUES,
|
||||||
COLLECTIONS_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, RadarrContextClueProvider, SYSTEM_TASKS_CONTEXT_CLUES,
|
MOVIE_DETAILS_CONTEXT_CLUES, RadarrContextClueProvider,
|
||||||
};
|
};
|
||||||
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;
|
||||||
@@ -459,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,
|
||||||
|
|||||||
@@ -147,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();
|
||||||
|
|||||||
@@ -58,21 +58,19 @@ impl App<'_> {
|
|||||||
}
|
}
|
||||||
ActiveSonarrBlock::SeasonHistory => {
|
ActiveSonarrBlock::SeasonHistory => {
|
||||||
if !self.data.sonarr_data.seasons.is_empty() {
|
if !self.data.sonarr_data.seasons.is_empty() {
|
||||||
|
let (series_id, season_number) = self.extract_series_id_season_number_tuple().await;
|
||||||
self
|
self
|
||||||
.dispatch_network_event(
|
.dispatch_network_event(SonarrEvent::GetSeasonHistory(series_id, season_number).into())
|
||||||
SonarrEvent::GetSeasonHistory(self.extract_series_id_season_number_tuple().await)
|
|
||||||
.into(),
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ActiveSonarrBlock::ManualSeasonSearch => {
|
ActiveSonarrBlock::ManualSeasonSearch => {
|
||||||
match self.data.sonarr_data.season_details_modal.as_ref() {
|
match self.data.sonarr_data.season_details_modal.as_ref() {
|
||||||
Some(season_details_modal) if season_details_modal.season_releases.is_empty() => {
|
Some(season_details_modal) if season_details_modal.season_releases.is_empty() => {
|
||||||
|
let (series_id, season_number) = self.extract_series_id_season_number_tuple().await;
|
||||||
self
|
self
|
||||||
.dispatch_network_event(
|
.dispatch_network_event(
|
||||||
SonarrEvent::GetSeasonReleases(self.extract_series_id_season_number_tuple().await)
|
SonarrEvent::GetSeasonReleases(series_id, season_number).into(),
|
||||||
.into(),
|
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::app::context_clues::{
|
use crate::app::context_clues::{
|
||||||
BARE_POPUP_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, ContextClueProvider,
|
BARE_POPUP_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, ContextClueProvider,
|
||||||
|
SYSTEM_TASKS_CONTEXT_CLUES,
|
||||||
};
|
};
|
||||||
use crate::app::{App, context_clues::ContextClue, key_binding::DEFAULT_KEYBINDINGS};
|
use crate::app::{App, context_clues::ContextClue, key_binding::DEFAULT_KEYBINDINGS};
|
||||||
use crate::models::Route;
|
use crate::models::Route;
|
||||||
@@ -163,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 {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ mod tests {
|
|||||||
BARE_POPUP_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES,
|
BARE_POPUP_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES,
|
||||||
ContextClue, ContextClueProvider, DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES,
|
ContextClue, ContextClueProvider, DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES,
|
||||||
INDEXERS_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::{
|
||||||
SELECTABLE_EPISODE_DETAILS_CONTEXT_CLUES, SonarrContextClueProvider,
|
SELECTABLE_EPISODE_DETAILS_CONTEXT_CLUES, SonarrContextClueProvider,
|
||||||
@@ -15,7 +16,7 @@ mod tests {
|
|||||||
ADD_SERIES_SEARCH_RESULTS_CONTEXT_CLUES, EPISODE_DETAILS_CONTEXT_CLUES,
|
ADD_SERIES_SEARCH_RESULTS_CONTEXT_CLUES, EPISODE_DETAILS_CONTEXT_CLUES,
|
||||||
MANUAL_EPISODE_SEARCH_CONTEXT_CLUES, MANUAL_SEASON_SEARCH_CONTEXT_CLUES,
|
MANUAL_EPISODE_SEARCH_CONTEXT_CLUES, MANUAL_SEASON_SEARCH_CONTEXT_CLUES,
|
||||||
SEASON_DETAILS_CONTEXT_CLUES, SEASON_HISTORY_CONTEXT_CLUES, SERIES_CONTEXT_CLUES,
|
SEASON_DETAILS_CONTEXT_CLUES, SEASON_HISTORY_CONTEXT_CLUES, SERIES_CONTEXT_CLUES,
|
||||||
SERIES_DETAILS_CONTEXT_CLUES, SERIES_HISTORY_CONTEXT_CLUES, SYSTEM_TASKS_CONTEXT_CLUES,
|
SERIES_DETAILS_CONTEXT_CLUES, SERIES_HISTORY_CONTEXT_CLUES,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ mod tests {
|
|||||||
assert!(app.is_loading);
|
assert!(app.is_loading);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sync_network_rx.recv().await.unwrap(),
|
sync_network_rx.recv().await.unwrap(),
|
||||||
SonarrEvent::GetSeasonHistory((1, 1)).into()
|
SonarrEvent::GetSeasonHistory(1, 1).into()
|
||||||
);
|
);
|
||||||
assert!(!app.data.sonarr_data.prompt_confirm);
|
assert!(!app.data.sonarr_data.prompt_confirm);
|
||||||
assert_eq!(app.tick_count, 0);
|
assert_eq!(app.tick_count, 0);
|
||||||
@@ -175,7 +175,7 @@ mod tests {
|
|||||||
assert!(app.is_loading);
|
assert!(app.is_loading);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sync_network_rx.recv().await.unwrap(),
|
sync_network_rx.recv().await.unwrap(),
|
||||||
SonarrEvent::GetSeasonReleases((1, 1)).into()
|
SonarrEvent::GetSeasonReleases(1, 1).into()
|
||||||
);
|
);
|
||||||
assert!(!app.data.sonarr_data.prompt_confirm);
|
assert!(!app.data.sonarr_data.prompt_confirm);
|
||||||
assert_eq!(app.tick_count, 0);
|
assert_eq!(app.tick_count, 0);
|
||||||
|
|||||||
+36
-1
@@ -8,12 +8,18 @@ mod tests {
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::cli::lidarr::LidarrCommand;
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
use crate::{
|
use crate::{
|
||||||
Cli,
|
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,
|
Serdeable,
|
||||||
|
lidarr_models::{
|
||||||
|
BlocklistItem as LidarrBlocklistItem, BlocklistResponse as LidarrBlocklistResponse,
|
||||||
|
LidarrSerdeable,
|
||||||
|
},
|
||||||
radarr_models::{
|
radarr_models::{
|
||||||
BlocklistItem as RadarrBlocklistItem, BlocklistResponse as RadarrBlocklistResponse,
|
BlocklistItem as RadarrBlocklistItem, BlocklistResponse as RadarrBlocklistResponse,
|
||||||
RadarrSerdeable,
|
RadarrSerdeable,
|
||||||
@@ -182,5 +188,34 @@ mod tests {
|
|||||||
assert_ok!(&result);
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement test_cli_handler_delegates_lidarr_commands_to_the_lidarr_cli_handler
|
#[tokio::test]
|
||||||
|
async fn test_cli_handler_delegates_lidarr_commands_to_the_lidarr_cli_handler() {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(LidarrEvent::GetBlocklist.into()))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::BlocklistResponse(
|
||||||
|
LidarrBlocklistResponse {
|
||||||
|
records: vec![LidarrBlocklistItem::default()],
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(LidarrEvent::ClearBlocklist.into()))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let clear_blocklist_command = LidarrCommand::ClearBlocklist.into();
|
||||||
|
|
||||||
|
let result = handle_command(&app_arc, clear_blocklist_command, &mut mock_network).await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{ArgAction, Subcommand, arg};
|
use clap::{ArgAction, Subcommand};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use super::LidarrCommand;
|
use super::LidarrCommand;
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{CliCommandHandler, Command},
|
cli::{CliCommandHandler, Command},
|
||||||
models::lidarr_models::{AddArtistBody, AddArtistOptions, MonitorType, NewItemMonitorType},
|
models::lidarr_models::{
|
||||||
|
AddArtistBody, AddArtistOptions, AddLidarrRootFolderBody, MonitorType, NewItemMonitorType,
|
||||||
|
},
|
||||||
network::{NetworkTrait, lidarr_network::LidarrEvent},
|
network::{NetworkTrait, lidarr_network::LidarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -75,6 +77,46 @@ pub enum LidarrAddCommand {
|
|||||||
)]
|
)]
|
||||||
no_search_for_missing_albums: bool,
|
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")]
|
#[command(about = "Add new tag")]
|
||||||
Tag {
|
Tag {
|
||||||
#[arg(long, help = "The name of the tag to be added", required = true)]
|
#[arg(long, help = "The name of the tag to be added", required = true)]
|
||||||
@@ -142,6 +184,31 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrAddCommand> for LidarrAddCommandHan
|
|||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
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 } => {
|
LidarrAddCommand::Tag { name } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
|
|||||||
@@ -27,6 +27,53 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use pretty_assertions::assert_eq;
|
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]
|
#[test]
|
||||||
fn test_add_tag_requires_arguments() {
|
fn test_add_tag_requires_arguments() {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "add", "tag"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "add", "tag"]);
|
||||||
@@ -381,7 +428,8 @@ mod tests {
|
|||||||
use crate::cli::lidarr::add_command_handler::{LidarrAddCommand, LidarrAddCommandHandler};
|
use crate::cli::lidarr::add_command_handler::{LidarrAddCommand, LidarrAddCommandHandler};
|
||||||
use crate::models::Serdeable;
|
use crate::models::Serdeable;
|
||||||
use crate::models::lidarr_models::{
|
use crate::models::lidarr_models::{
|
||||||
AddArtistBody, AddArtistOptions, LidarrSerdeable, MonitorType, NewItemMonitorType,
|
AddArtistBody, AddArtistOptions, AddLidarrRootFolderBody, LidarrSerdeable, MonitorType,
|
||||||
|
NewItemMonitorType,
|
||||||
};
|
};
|
||||||
use crate::network::lidarr_network::LidarrEvent;
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -389,6 +437,50 @@ mod tests {
|
|||||||
network::{MockNetworkTrait, NetworkEvent},
|
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]
|
#[tokio::test]
|
||||||
async fn test_handle_add_tag_command() {
|
async fn test_handle_add_tag_command() {
|
||||||
let expected_tag_name = "test".to_owned();
|
let expected_tag_name = "test".to_owned();
|
||||||
|
|||||||
@@ -28,6 +28,20 @@ pub enum LidarrDeleteCommand {
|
|||||||
#[arg(long, help = "Add a list exclusion for this album")]
|
#[arg(long, help = "Add a list exclusion for this album")]
|
||||||
add_list_exclusion: bool,
|
add_list_exclusion: bool,
|
||||||
},
|
},
|
||||||
|
#[command(about = "Delete the specified item from the Lidarr blocklist")]
|
||||||
|
BlocklistItem {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The ID of the blocklist item to remove from the blocklist",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
blocklist_item_id: i64,
|
||||||
|
},
|
||||||
|
#[command(about = "Delete the specified track file from disk")]
|
||||||
|
TrackFile {
|
||||||
|
#[arg(long, help = "The ID of the track file to delete", required = true)]
|
||||||
|
track_file_id: i64,
|
||||||
|
},
|
||||||
#[command(about = "Delete an artist from your Lidarr library")]
|
#[command(about = "Delete an artist from your Lidarr library")]
|
||||||
Artist {
|
Artist {
|
||||||
#[arg(long, help = "The ID of the artist to delete", required = true)]
|
#[arg(long, help = "The ID of the artist to delete", required = true)]
|
||||||
@@ -37,6 +51,21 @@ pub enum LidarrDeleteCommand {
|
|||||||
#[arg(long, help = "Add a list exclusion for this artist")]
|
#[arg(long, help = "Add a list exclusion for this artist")]
|
||||||
add_list_exclusion: bool,
|
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")]
|
#[command(about = "Delete the tag with the specified ID")]
|
||||||
Tag {
|
Tag {
|
||||||
#[arg(long, help = "The ID of the tag to delete", required = true)]
|
#[arg(long, help = "The ID of the tag to delete", required = true)]
|
||||||
@@ -87,6 +116,20 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrDeleteCommand> for LidarrDeleteComm
|
|||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
|
LidarrDeleteCommand::BlocklistItem { blocklist_item_id } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::DeleteBlocklistItem(blocklist_item_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrDeleteCommand::TrackFile { track_file_id } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::DeleteTrackFile(track_file_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
LidarrDeleteCommand::Artist {
|
LidarrDeleteCommand::Artist {
|
||||||
artist_id,
|
artist_id,
|
||||||
delete_files_from_disk,
|
delete_files_from_disk,
|
||||||
@@ -103,6 +146,27 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrDeleteCommand> for LidarrDeleteComm
|
|||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
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 } => {
|
LidarrDeleteCommand::Tag { tag_id } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
|
|||||||
@@ -86,6 +86,76 @@ mod tests {
|
|||||||
assert_eq!(delete_command, expected_args);
|
assert_eq!(delete_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_blocklist_item_requires_arguments() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "blocklist-item"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_blocklist_item_success() {
|
||||||
|
let expected_args = LidarrDeleteCommand::BlocklistItem {
|
||||||
|
blocklist_item_id: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"delete",
|
||||||
|
"blocklist-item",
|
||||||
|
"--blocklist-item-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_track_file_requires_arguments() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "track-file"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_track_file_success() {
|
||||||
|
let expected_args = LidarrDeleteCommand::TrackFile { track_file_id: 1 };
|
||||||
|
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"delete",
|
||||||
|
"track-file",
|
||||||
|
"--track-file-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]
|
#[test]
|
||||||
fn test_delete_artist_requires_arguments() {
|
fn test_delete_artist_requires_arguments() {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "artist"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "artist"]);
|
||||||
@@ -145,6 +215,107 @@ mod tests {
|
|||||||
assert_eq!(delete_command, expected_args);
|
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]
|
#[test]
|
||||||
fn test_delete_tag_requires_arguments() {
|
fn test_delete_tag_requires_arguments() {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "tag"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "tag"]);
|
||||||
@@ -226,6 +397,63 @@ mod tests {
|
|||||||
assert_ok!(&result);
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_delete_blocklist_item_command() {
|
||||||
|
let expected_blocklist_item_id = 1;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::DeleteBlocklistItem(expected_blocklist_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 delete_blocklist_item_command = LidarrDeleteCommand::BlocklistItem {
|
||||||
|
blocklist_item_id: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = LidarrDeleteCommandHandler::with(
|
||||||
|
&app_arc,
|
||||||
|
delete_blocklist_item_command,
|
||||||
|
&mut mock_network,
|
||||||
|
)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_delete_track_file_command() {
|
||||||
|
let expected_track_file_id = 1;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::DeleteTrackFile(expected_track_file_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_track_file_command = LidarrDeleteCommand::TrackFile { track_file_id: 1 };
|
||||||
|
|
||||||
|
let result =
|
||||||
|
LidarrDeleteCommandHandler::with(&app_arc, delete_track_file_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_handle_delete_artist_command() {
|
async fn test_handle_delete_artist_command() {
|
||||||
let expected_delete_artist_params = DeleteParams {
|
let expected_delete_artist_params = DeleteParams {
|
||||||
@@ -260,6 +488,84 @@ mod tests {
|
|||||||
assert_ok!(&result);
|
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]
|
#[tokio::test]
|
||||||
async fn test_handle_delete_tag_command() {
|
async fn test_handle_delete_tag_command() {
|
||||||
let expected_tag_id = 1;
|
let expected_tag_id = 1;
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ use anyhow::Result;
|
|||||||
use clap::{ArgAction, ArgGroup, Subcommand};
|
use clap::{ArgAction, ArgGroup, Subcommand};
|
||||||
use tokio::sync::Mutex;
|
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::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{CliCommandHandler, Command, mutex_flags_or_option},
|
cli::{CliCommandHandler, Command, mutex_flags_or_option},
|
||||||
@@ -11,14 +15,46 @@ use crate::{
|
|||||||
network::{NetworkTrait, lidarr_network::LidarrEvent},
|
network::{NetworkTrait, lidarr_network::LidarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::LidarrCommand;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "edit_command_handler_tests.rs"]
|
#[path = "edit_command_handler_tests.rs"]
|
||||||
mod edit_command_handler_tests;
|
mod edit_command_handler_tests;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
||||||
pub enum LidarrEditCommand {
|
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(
|
#[command(
|
||||||
about = "Edit preferences for the specified artist",
|
about = "Edit preferences for the specified artist",
|
||||||
group(
|
group(
|
||||||
@@ -80,6 +116,97 @@ pub enum LidarrEditCommand {
|
|||||||
#[arg(long, help = "Clear all tags on this artist", conflicts_with = "tag")]
|
#[arg(long, help = "Clear all tags on this artist", conflicts_with = "tag")]
|
||||||
clear_tags: bool,
|
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 {
|
impl From<LidarrEditCommand> for Command {
|
||||||
@@ -109,6 +236,34 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrEditCommand> for LidarrEditCommandH
|
|||||||
|
|
||||||
async fn handle(self) -> Result<String> {
|
async fn handle(self) -> Result<String> {
|
||||||
let result = match self.command {
|
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 {
|
LidarrEditCommand::Artist {
|
||||||
artist_id,
|
artist_id,
|
||||||
enable_monitoring,
|
enable_monitoring,
|
||||||
@@ -139,6 +294,48 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrEditCommand> for LidarrEditCommandH
|
|||||||
.await?;
|
.await?;
|
||||||
"Artist Updated".to_owned()
|
"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)
|
Ok(result)
|
||||||
|
|||||||
@@ -32,6 +32,96 @@ mod tests {
|
|||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use rstest::rstest;
|
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]
|
#[test]
|
||||||
fn test_edit_artist_requires_arguments() {
|
fn test_edit_artist_requires_arguments() {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "edit", "artist"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "edit", "artist"]);
|
||||||
@@ -249,6 +339,253 @@ mod tests {
|
|||||||
};
|
};
|
||||||
assert_eq!(edit_command, expected_args);
|
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 {
|
mod handler {
|
||||||
@@ -258,6 +595,7 @@ mod tests {
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::models::servarr_models::{EditIndexerParams, IndexerSettings};
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{
|
cli::{
|
||||||
@@ -271,6 +609,63 @@ mod tests {
|
|||||||
network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent},
|
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]
|
#[tokio::test]
|
||||||
async fn test_handle_edit_artist_command() {
|
async fn test_handle_edit_artist_command() {
|
||||||
let expected_edit_artist_params = EditArtistParams {
|
let expected_edit_artist_params = EditArtistParams {
|
||||||
@@ -405,5 +800,59 @@ mod tests {
|
|||||||
|
|
||||||
assert_ok!(&result);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ pub enum LidarrGetCommand {
|
|||||||
)]
|
)]
|
||||||
album_id: i64,
|
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")]
|
#[command(about = "Get detailed information for the artist with the given ID")]
|
||||||
ArtistDetails {
|
ArtistDetails {
|
||||||
#[arg(
|
#[arg(
|
||||||
@@ -42,6 +44,15 @@ pub enum LidarrGetCommand {
|
|||||||
SecurityConfig,
|
SecurityConfig,
|
||||||
#[command(about = "Get the system status")]
|
#[command(about = "Get the system status")]
|
||||||
SystemStatus,
|
SystemStatus,
|
||||||
|
#[command(about = "Get detailed information for the track with the given ID")]
|
||||||
|
TrackDetails {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The Lidarr ID of the track whose details you wish to fetch",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
track_id: i64,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<LidarrGetCommand> for Command {
|
impl From<LidarrGetCommand> for Command {
|
||||||
@@ -78,6 +89,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrGetCommand> for LidarrGetCommandHan
|
|||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
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 } => {
|
LidarrGetCommand::ArtistDetails { artist_id } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
@@ -106,6 +124,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrGetCommand> for LidarrGetCommandHan
|
|||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
|
LidarrGetCommand::TrackDetails { track_id } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetTrackDetails(track_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
|
|||||||
@@ -49,6 +49,14 @@ mod tests {
|
|||||||
assert_ok!(&result);
|
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]
|
#[test]
|
||||||
fn test_artist_details_requires_artist_id() {
|
fn test_artist_details_requires_artist_id() {
|
||||||
let result =
|
let result =
|
||||||
@@ -98,6 +106,32 @@ mod tests {
|
|||||||
|
|
||||||
assert_ok!(&result);
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_track_details_requires_track_id() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "get", "track-details"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_track_details_requirements_satisfied() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"get",
|
||||||
|
"track-details",
|
||||||
|
"--track-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod handler {
|
mod handler {
|
||||||
@@ -143,6 +177,34 @@ mod tests {
|
|||||||
assert_ok!(&result);
|
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]
|
#[tokio::test]
|
||||||
async fn test_handle_get_artist_details_command() {
|
async fn test_handle_get_artist_details_command() {
|
||||||
let expected_artist_id = 1;
|
let expected_artist_id = 1;
|
||||||
@@ -237,5 +299,31 @@ mod tests {
|
|||||||
|
|
||||||
assert_ok!(&result);
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_get_track_details_command() {
|
||||||
|
let expected_track_id = 1;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::GetTrackDetails(expected_track_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_track_details_command = LidarrGetCommand::TrackDetails { track_id: 1 };
|
||||||
|
|
||||||
|
let result =
|
||||||
|
LidarrGetCommandHandler::with(&app_arc, get_track_details_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,16 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use clap::error::ErrorKind;
|
use clap::error::ErrorKind;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_commands_that_have_no_arg_requirements(
|
||||||
|
#[values("clear-blocklist", "test-all-indexers")] subcommand: &str,
|
||||||
|
) {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", subcommand]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_list_artists_has_no_arg_requirements() {
|
fn test_list_artists_has_no_arg_requirements() {
|
||||||
@@ -50,6 +60,55 @@ mod tests {
|
|||||||
assert_err!(&result);
|
assert_err!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_download_release_requires_guid() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"download-release",
|
||||||
|
"--indexer-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_download_release_requires_indexer_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"download-release",
|
||||||
|
"--guid",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_download_release_requirements_satisfied() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"download-release",
|
||||||
|
"--guid",
|
||||||
|
"1",
|
||||||
|
"--indexer-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_toggle_artist_monitoring_requires_artist_id() {
|
fn test_toggle_artist_monitoring_requires_artist_id() {
|
||||||
let result =
|
let result =
|
||||||
@@ -124,6 +183,44 @@ mod tests {
|
|||||||
assert_ok!(&result);
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_start_task_requires_task_name() {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "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]
|
#[test]
|
||||||
fn test_mark_history_item_as_failed_requires_history_item_id() {
|
fn test_mark_history_item_as_failed_requires_history_item_id() {
|
||||||
let result =
|
let result =
|
||||||
@@ -148,6 +245,30 @@ mod tests {
|
|||||||
|
|
||||||
assert_ok!(&result);
|
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 {
|
mod handler {
|
||||||
@@ -158,9 +279,15 @@ mod tests {
|
|||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use crate::cli::lidarr::add_command_handler::LidarrAddCommand;
|
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::get_command_handler::LidarrGetCommand;
|
||||||
|
use crate::cli::lidarr::manual_search_command_handler::LidarrManualSearchCommand;
|
||||||
use crate::cli::lidarr::refresh_command_handler::LidarrRefreshCommand;
|
use crate::cli::lidarr::refresh_command_handler::LidarrRefreshCommand;
|
||||||
use crate::cli::lidarr::trigger_automatic_search_command_handler::LidarrTriggerAutomaticSearchCommand;
|
use crate::cli::lidarr::trigger_automatic_search_command_handler::LidarrTriggerAutomaticSearchCommand;
|
||||||
|
use crate::models::lidarr_models::{
|
||||||
|
BlocklistItem, BlocklistResponse, LidarrReleaseDownloadBody, LidarrTaskName,
|
||||||
|
};
|
||||||
|
use crate::models::servarr_models::IndexerSettings;
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{
|
cli::{
|
||||||
@@ -259,6 +386,64 @@ mod tests {
|
|||||||
assert_ok!(&result);
|
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]
|
#[tokio::test]
|
||||||
async fn test_lidarr_cli_handler_delegates_list_commands_to_the_list_command_handler() {
|
async fn test_lidarr_cli_handler_delegates_list_commands_to_the_list_command_handler() {
|
||||||
let mut mock_network = MockNetworkTrait::new();
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
@@ -294,9 +479,133 @@ mod tests {
|
|||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let refresh_series_command = LidarrCommand::Refresh(LidarrRefreshCommand::AllArtists);
|
let refresh_artist_command = LidarrCommand::Refresh(LidarrRefreshCommand::AllArtists);
|
||||||
|
|
||||||
let result = LidarrCliHandler::with(&app_arc, refresh_series_command, &mut mock_network)
|
let result = LidarrCliHandler::with(&app_arc, refresh_artist_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_lidarr_cli_handler_delegates_manual_search_commands_to_the_manual_search_command_handler()
|
||||||
|
{
|
||||||
|
let expected_artist_id = 1;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::GetDiscographyReleases(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 manual_episode_search_command =
|
||||||
|
LidarrCommand::ManualSearch(LidarrManualSearchCommand::Discography { artist_id: 1 });
|
||||||
|
|
||||||
|
let result =
|
||||||
|
LidarrCliHandler::with(&app_arc, manual_episode_search_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_handle_clear_blocklist_command() {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(LidarrEvent::GetBlocklist.into()))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::BlocklistResponse(
|
||||||
|
BlocklistResponse {
|
||||||
|
records: vec![BlocklistItem::default()],
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(LidarrEvent::ClearBlocklist.into()))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let claer_blocklist_command = LidarrCommand::ClearBlocklist;
|
||||||
|
|
||||||
|
let result = LidarrCliHandler::with(&app_arc, claer_blocklist_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_download_release_command() {
|
||||||
|
let expected_release_download_body = LidarrReleaseDownloadBody {
|
||||||
|
guid: "guid".to_owned(),
|
||||||
|
indexer_id: 1,
|
||||||
|
};
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::DownloadRelease(expected_release_download_body).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let download_release_command = LidarrCommand::DownloadRelease {
|
||||||
|
guid: "guid".to_owned(),
|
||||||
|
indexer_id: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = LidarrCliHandler::with(&app_arc, download_release_command, &mut mock_network)
|
||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@@ -359,13 +668,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_lidarr_cli_handler_delegates_trigger_automatic_search_commands_to_the_trigger_automatic_search_command_handler()
|
async fn test_start_task_command() {
|
||||||
{
|
let expected_task_name = LidarrTaskName::ApplicationUpdateCheck;
|
||||||
let mut mock_network = MockNetworkTrait::new();
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
LidarrEvent::TriggerAutomaticArtistSearch(1).into(),
|
LidarrEvent::StartTask(expected_task_name).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -374,18 +683,60 @@ mod tests {
|
|||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let trigger_automatic_search_command =
|
let start_task_command = LidarrCommand::StartTask {
|
||||||
LidarrCommand::TriggerAutomaticSearch(LidarrTriggerAutomaticSearchCommand::Artist {
|
task_name: LidarrTaskName::ApplicationUpdateCheck,
|
||||||
artist_id: 1,
|
};
|
||||||
});
|
|
||||||
|
|
||||||
let result = LidarrCliHandler::with(
|
let result = LidarrCliHandler::with(&app_arc, start_task_command, &mut mock_network)
|
||||||
&app_arc,
|
.handle()
|
||||||
trigger_automatic_search_command,
|
.await;
|
||||||
&mut mock_network,
|
|
||||||
)
|
assert_ok!(&result);
|
||||||
.handle()
|
}
|
||||||
.await;
|
|
||||||
|
#[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);
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{Subcommand, arg};
|
use clap::Subcommand;
|
||||||
|
use serde_json::json;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use super::LidarrCommand;
|
||||||
|
use crate::models::Serdeable;
|
||||||
|
use crate::models::lidarr_models::{LidarrHistoryItem, LidarrSerdeable};
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{CliCommandHandler, Command},
|
cli::{CliCommandHandler, Command},
|
||||||
network::{NetworkTrait, lidarr_network::LidarrEvent},
|
network::{NetworkTrait, lidarr_network::LidarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::LidarrCommand;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "list_command_handler_tests.rs"]
|
#[path = "list_command_handler_tests.rs"]
|
||||||
mod list_command_handler_tests;
|
mod list_command_handler_tests;
|
||||||
@@ -27,19 +29,121 @@ pub enum LidarrListCommand {
|
|||||||
)]
|
)]
|
||||||
artist_id: i64,
|
artist_id: i64,
|
||||||
},
|
},
|
||||||
|
#[command(
|
||||||
|
about = "Fetch all history events for the given album corresponding to the artist with the given ID."
|
||||||
|
)]
|
||||||
|
AlbumHistory {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The Lidarr artist ID of the artist whose history you wish to fetch and list",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
artist_id: i64,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The Lidarr album ID to fetch history events for",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
album_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")]
|
#[command(about = "List all artists in your Lidarr library")]
|
||||||
Artists,
|
Artists,
|
||||||
|
#[command(about = "List all items in the Lidarr blocklist")]
|
||||||
|
Blocklist,
|
||||||
|
#[command(about = "List disk space details for all provisioned root folders in Lidarr")]
|
||||||
|
DiskSpace,
|
||||||
|
#[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")]
|
#[command(about = "Fetch all Lidarr history events")]
|
||||||
History {
|
History {
|
||||||
#[arg(long, help = "How many history events to fetch", default_value_t = 500)]
|
#[arg(long, help = "How many history events to fetch", default_value_t = 500)]
|
||||||
events: u64,
|
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")]
|
#[command(about = "List all Lidarr metadata profiles")]
|
||||||
MetadataProfiles,
|
MetadataProfiles,
|
||||||
#[command(about = "List all Lidarr quality profiles")]
|
#[command(about = "List all Lidarr quality profiles")]
|
||||||
QualityProfiles,
|
QualityProfiles,
|
||||||
|
#[command(about = "List all queued events")]
|
||||||
|
QueuedEvents,
|
||||||
|
#[command(about = "List all root folders in Lidarr")]
|
||||||
|
RootFolders,
|
||||||
#[command(about = "List all Lidarr tags")]
|
#[command(about = "List all Lidarr tags")]
|
||||||
Tags,
|
Tags,
|
||||||
|
#[command(about = "List all Lidarr tasks")]
|
||||||
|
Tasks,
|
||||||
|
#[command(about = "Fetch all history events for the track with the given ID")]
|
||||||
|
TrackHistory {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The artist ID that the track belongs to",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
artist_id: i64,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The album ID that the track is a part of",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
album_id: i64,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The Lidarr ID of the track whose history you wish to fetch",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
track_id: i64,
|
||||||
|
},
|
||||||
|
#[command(
|
||||||
|
about = "List the tracks for the album that corresponds to the artist with the given ID"
|
||||||
|
)]
|
||||||
|
Tracks {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The Lidarr artist ID of the artist whose tracks you wish to fetch",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
artist_id: i64,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The Lidarr album ID whose tracks you wish to fetch",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
album_id: i64,
|
||||||
|
},
|
||||||
|
#[command(about = "List the track files for the album with the given ID")]
|
||||||
|
TrackFiles {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The Lidarr ID of the album whose track files you wish to fetch",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
album_id: i64,
|
||||||
|
},
|
||||||
|
#[command(about = "List all Lidarr updates")]
|
||||||
|
Updates,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<LidarrListCommand> for Command {
|
impl From<LidarrListCommand> for Command {
|
||||||
@@ -49,7 +153,7 @@ impl From<LidarrListCommand> for Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) struct LidarrListCommandHandler<'a, 'b> {
|
pub(super) struct LidarrListCommandHandler<'a, 'b> {
|
||||||
_app: &'a Arc<Mutex<App<'b>>>,
|
app: &'a Arc<Mutex<App<'b>>>,
|
||||||
command: LidarrListCommand,
|
command: LidarrListCommand,
|
||||||
network: &'a mut dyn NetworkTrait,
|
network: &'a mut dyn NetworkTrait,
|
||||||
}
|
}
|
||||||
@@ -61,7 +165,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrListCommand> for LidarrListCommandH
|
|||||||
network: &'a mut dyn NetworkTrait,
|
network: &'a mut dyn NetworkTrait,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
LidarrListCommandHandler {
|
LidarrListCommandHandler {
|
||||||
_app: app,
|
app,
|
||||||
command,
|
command,
|
||||||
network,
|
network,
|
||||||
}
|
}
|
||||||
@@ -76,6 +180,23 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrListCommand> for LidarrListCommandH
|
|||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
|
LidarrListCommand::AlbumHistory {
|
||||||
|
artist_id,
|
||||||
|
album_id,
|
||||||
|
} => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetAlbumHistory(artist_id, album_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 => {
|
LidarrListCommand::Artists => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
@@ -83,6 +204,27 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrListCommand> for LidarrListCommandH
|
|||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
|
LidarrListCommand::Blocklist => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetBlocklist.into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrListCommand::DiskSpace => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetDiskSpace.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 } => {
|
LidarrListCommand::History { events: items } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
@@ -90,6 +232,30 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrListCommand> for LidarrListCommandH
|
|||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
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 => {
|
LidarrListCommand::MetadataProfiles => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
@@ -104,6 +270,20 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrListCommand> for LidarrListCommandH
|
|||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
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 => {
|
LidarrListCommand::Tags => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
@@ -111,6 +291,58 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrListCommand> for LidarrListCommandH
|
|||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
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::TrackHistory {
|
||||||
|
artist_id,
|
||||||
|
album_id,
|
||||||
|
track_id,
|
||||||
|
} => {
|
||||||
|
match self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetTrackHistory(artist_id, album_id, track_id).into())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::LidarrHistoryItems(history_vec))) => {
|
||||||
|
let history_items_vec: Vec<LidarrHistoryItem> = history_vec
|
||||||
|
.into_iter()
|
||||||
|
.filter(|it| it.track_id == track_id)
|
||||||
|
.collect();
|
||||||
|
serde_json::to_string_pretty(&history_items_vec)?
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
_ => serde_json::to_string_pretty(&json!({"message": "Failed to parse response"}))?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LidarrListCommand::Tracks {
|
||||||
|
artist_id,
|
||||||
|
album_id,
|
||||||
|
} => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetTracks(artist_id, album_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrListCommand::TrackFiles { album_id } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetTrackFiles(album_id).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)
|
Ok(result)
|
||||||
|
|||||||
@@ -25,7 +25,20 @@ mod tests {
|
|||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn test_list_commands_have_no_arg_requirements(
|
fn test_list_commands_have_no_arg_requirements(
|
||||||
#[values("artists", "metadata-profiles", "quality-profiles", "tags")] subcommand: &str,
|
#[values(
|
||||||
|
"artists",
|
||||||
|
"blocklist",
|
||||||
|
"disk-space",
|
||||||
|
"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]);
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "list", subcommand]);
|
||||||
|
|
||||||
@@ -58,6 +71,114 @@ mod tests {
|
|||||||
assert_eq!(album_command, expected_args);
|
assert_eq!(album_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_album_history_requires_artist_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"list",
|
||||||
|
"album-history",
|
||||||
|
"--album-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_album_history_requires_album_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"list",
|
||||||
|
"album-history",
|
||||||
|
"--artist-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_album_history_requirements_satisfied() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"list",
|
||||||
|
"album-history",
|
||||||
|
"--artist-id",
|
||||||
|
"1",
|
||||||
|
"--album-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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]
|
#[test]
|
||||||
fn test_list_history_events_flag_requires_arguments() {
|
fn test_list_history_events_flag_requires_arguments() {
|
||||||
let result =
|
let result =
|
||||||
@@ -80,12 +201,223 @@ mod tests {
|
|||||||
};
|
};
|
||||||
assert_eq!(history_command, expected_args);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_track_history_requires_artist_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"list",
|
||||||
|
"track-history",
|
||||||
|
"--album-id",
|
||||||
|
"1",
|
||||||
|
"--track-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_track_history_requires_album_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"list",
|
||||||
|
"track-history",
|
||||||
|
"--artist-id",
|
||||||
|
"1",
|
||||||
|
"--track-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_track_history_requires_track_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"list",
|
||||||
|
"track-history",
|
||||||
|
"--artist-id",
|
||||||
|
"1",
|
||||||
|
"--album-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_track_history_success() {
|
||||||
|
let expected_args = LidarrListCommand::TrackHistory {
|
||||||
|
artist_id: 1,
|
||||||
|
album_id: 1,
|
||||||
|
track_id: 1,
|
||||||
|
};
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"list",
|
||||||
|
"track-history",
|
||||||
|
"--artist-id",
|
||||||
|
"1",
|
||||||
|
"--album-id",
|
||||||
|
"1",
|
||||||
|
"--track-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::List(track_history_command))) =
|
||||||
|
result.unwrap().command
|
||||||
|
else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(track_history_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_tracks_requires_artist_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"list",
|
||||||
|
"tracks",
|
||||||
|
"--album-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_tracks_requires_album_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"list",
|
||||||
|
"tracks",
|
||||||
|
"--artist-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_tracks_success() {
|
||||||
|
let expected_args = LidarrListCommand::Tracks {
|
||||||
|
artist_id: 1,
|
||||||
|
album_id: 1,
|
||||||
|
};
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"list",
|
||||||
|
"tracks",
|
||||||
|
"--artist-id",
|
||||||
|
"1",
|
||||||
|
"--album-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::List(tracks_command))) = result.unwrap().command
|
||||||
|
else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(tracks_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_track_files_requires_album_id() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "list", "track-files"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_track_files_success() {
|
||||||
|
let expected_args = LidarrListCommand::TrackFiles { album_id: 1 };
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"list",
|
||||||
|
"track-files",
|
||||||
|
"--album-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::List(track_files_command))) = result.unwrap().command
|
||||||
|
else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(track_files_command, expected_args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod handler {
|
mod handler {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use mockall::predicate::eq;
|
use mockall::predicate::eq;
|
||||||
|
use pretty_assertions::assert_str_eq;
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
@@ -93,8 +425,9 @@ mod tests {
|
|||||||
use crate::cli::CliCommandHandler;
|
use crate::cli::CliCommandHandler;
|
||||||
use crate::cli::lidarr::list_command_handler::{LidarrListCommand, LidarrListCommandHandler};
|
use crate::cli::lidarr::list_command_handler::{LidarrListCommand, LidarrListCommandHandler};
|
||||||
use crate::models::Serdeable;
|
use crate::models::Serdeable;
|
||||||
use crate::models::lidarr_models::LidarrSerdeable;
|
use crate::models::lidarr_models::{LidarrHistoryItem, LidarrSerdeable};
|
||||||
use crate::network::lidarr_network::LidarrEvent;
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::lidarr_history_item;
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
network::{MockNetworkTrait, NetworkEvent},
|
network::{MockNetworkTrait, NetworkEvent},
|
||||||
@@ -102,9 +435,16 @@ mod tests {
|
|||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(LidarrListCommand::Artists, LidarrEvent::ListArtists)]
|
#[case(LidarrListCommand::Artists, LidarrEvent::ListArtists)]
|
||||||
|
#[case(LidarrListCommand::Blocklist, LidarrEvent::GetBlocklist)]
|
||||||
|
#[case(LidarrListCommand::DiskSpace, LidarrEvent::GetDiskSpace)]
|
||||||
|
#[case(LidarrListCommand::Indexers, LidarrEvent::GetIndexers)]
|
||||||
#[case(LidarrListCommand::MetadataProfiles, LidarrEvent::GetMetadataProfiles)]
|
#[case(LidarrListCommand::MetadataProfiles, LidarrEvent::GetMetadataProfiles)]
|
||||||
#[case(LidarrListCommand::QualityProfiles, LidarrEvent::GetQualityProfiles)]
|
#[case(LidarrListCommand::QualityProfiles, LidarrEvent::GetQualityProfiles)]
|
||||||
|
#[case(LidarrListCommand::QueuedEvents, LidarrEvent::GetQueuedEvents)]
|
||||||
|
#[case(LidarrListCommand::RootFolders, LidarrEvent::GetRootFolders)]
|
||||||
#[case(LidarrListCommand::Tags, LidarrEvent::GetTags)]
|
#[case(LidarrListCommand::Tags, LidarrEvent::GetTags)]
|
||||||
|
#[case(LidarrListCommand::Tasks, LidarrEvent::GetTasks)]
|
||||||
|
#[case(LidarrListCommand::Updates, LidarrEvent::GetUpdates)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_handle_list_command(
|
async fn test_handle_list_command(
|
||||||
#[case] list_command: LidarrListCommand,
|
#[case] list_command: LidarrListCommand,
|
||||||
@@ -151,6 +491,88 @@ mod tests {
|
|||||||
assert_ok!(&result);
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_list_album_history_command() {
|
||||||
|
let expected_artist_id = 1;
|
||||||
|
let expected_album_id = 1;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::GetAlbumHistory(expected_artist_id, 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 list_album_history_command = LidarrListCommand::AlbumHistory {
|
||||||
|
artist_id: 1,
|
||||||
|
album_id: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result =
|
||||||
|
LidarrListCommandHandler::with(&app_arc, list_album_history_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]
|
#[tokio::test]
|
||||||
async fn test_handle_list_history_command() {
|
async fn test_handle_list_history_command() {
|
||||||
let expected_events = 1000;
|
let expected_events = 1000;
|
||||||
@@ -176,5 +598,131 @@ mod tests {
|
|||||||
|
|
||||||
assert_ok!(&result);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_list_track_history_command() {
|
||||||
|
let expected_artist_id = 1;
|
||||||
|
let expected_album_id = 1;
|
||||||
|
let expected_track_id = 1;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::GetTrackHistory(expected_artist_id, expected_album_id, expected_track_id)
|
||||||
|
.into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::LidarrHistoryItems(
|
||||||
|
vec![
|
||||||
|
lidarr_history_item(),
|
||||||
|
LidarrHistoryItem {
|
||||||
|
track_id: 2,
|
||||||
|
..lidarr_history_item()
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let list_track_history_command = LidarrListCommand::TrackHistory {
|
||||||
|
artist_id: expected_artist_id,
|
||||||
|
album_id: expected_album_id,
|
||||||
|
track_id: expected_track_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result =
|
||||||
|
LidarrListCommandHandler::with(&app_arc, list_track_history_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
assert_str_eq!(
|
||||||
|
result.unwrap(),
|
||||||
|
serde_json::to_string_pretty(&[lidarr_history_item()]).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_list_tracks_command() {
|
||||||
|
let expected_artist_id = 1;
|
||||||
|
let expected_album_id = 1;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::GetTracks(expected_artist_id, 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 list_tracks_command = LidarrListCommand::Tracks {
|
||||||
|
artist_id: 1,
|
||||||
|
album_id: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = LidarrListCommandHandler::with(&app_arc, list_tracks_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_list_track_files_command() {
|
||||||
|
let expected_album_id = 1;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::GetTrackFiles(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 list_track_files_command = LidarrListCommand::TrackFiles { album_id: 1 };
|
||||||
|
|
||||||
|
let result =
|
||||||
|
LidarrListCommandHandler::with(&app_arc, list_track_files_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
use crate::app::App;
|
||||||
|
use crate::cli::lidarr::LidarrCommand;
|
||||||
|
use crate::cli::{CliCommandHandler, Command};
|
||||||
|
use crate::models::Serdeable;
|
||||||
|
use crate::models::lidarr_models::{LidarrRelease, LidarrSerdeable};
|
||||||
|
use crate::network::NetworkTrait;
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::Subcommand;
|
||||||
|
use serde_json::json;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "manual_search_command_handler_tests.rs"]
|
||||||
|
mod manual_search_command_handler_tests;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
||||||
|
pub enum LidarrManualSearchCommand {
|
||||||
|
#[command(
|
||||||
|
about = "Trigger a manual search of releases for the given album corresponding to the artist with the given ID"
|
||||||
|
)]
|
||||||
|
Album {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The Lidarr ID of the artist whose releases you wish to fetch and list",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
artist_id: i64,
|
||||||
|
#[arg(long, help = "The Lidarr album ID to search for", required = true)]
|
||||||
|
album_id: i64,
|
||||||
|
},
|
||||||
|
#[command(
|
||||||
|
about = "Trigger a manual search of discography releases for the given artist corresponding to the artist with the given ID."
|
||||||
|
)]
|
||||||
|
Discography {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The Lidarr ID of the artist whose discography releases you wish to fetch and list",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
artist_id: i64,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LidarrManualSearchCommand> for Command {
|
||||||
|
fn from(value: LidarrManualSearchCommand) -> Self {
|
||||||
|
Command::Lidarr(LidarrCommand::ManualSearch(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct LidarrManualSearchCommandHandler<'a, 'b> {
|
||||||
|
_app: &'a Arc<Mutex<App<'b>>>,
|
||||||
|
command: LidarrManualSearchCommand,
|
||||||
|
network: &'a mut dyn NetworkTrait,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrManualSearchCommand>
|
||||||
|
for LidarrManualSearchCommandHandler<'a, 'b>
|
||||||
|
{
|
||||||
|
fn with(
|
||||||
|
_app: &'a Arc<Mutex<App<'b>>>,
|
||||||
|
command: LidarrManualSearchCommand,
|
||||||
|
network: &'a mut dyn NetworkTrait,
|
||||||
|
) -> Self {
|
||||||
|
LidarrManualSearchCommandHandler {
|
||||||
|
_app,
|
||||||
|
command,
|
||||||
|
network,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle(self) -> Result<String> {
|
||||||
|
let result = match self.command {
|
||||||
|
LidarrManualSearchCommand::Album {
|
||||||
|
artist_id,
|
||||||
|
album_id,
|
||||||
|
} => {
|
||||||
|
println!("Searching for album releases. This may take a minute...");
|
||||||
|
match self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetAlbumReleases(artist_id, album_id).into())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Releases(releases_vec))) => {
|
||||||
|
let albums_vec: Vec<LidarrRelease> = releases_vec
|
||||||
|
.into_iter()
|
||||||
|
.filter(|release| !release.discography)
|
||||||
|
.collect();
|
||||||
|
serde_json::to_string_pretty(&albums_vec)?
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
_ => serde_json::to_string_pretty(&json!({"message": "Failed to parse response"}))?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LidarrManualSearchCommand::Discography { artist_id } => {
|
||||||
|
println!("Searching for artist discography releases. This may take a minute...");
|
||||||
|
match self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetDiscographyReleases(artist_id).into())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Releases(releases_vec))) => {
|
||||||
|
let discography_vec: Vec<LidarrRelease> = releases_vec
|
||||||
|
.into_iter()
|
||||||
|
.filter(|release| release.discography)
|
||||||
|
.collect();
|
||||||
|
serde_json::to_string_pretty(&discography_vec)?
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
_ => serde_json::to_string_pretty(&json!({"message": "Failed to parse response"}))?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::cli::Command;
|
||||||
|
use crate::cli::lidarr::LidarrCommand;
|
||||||
|
use crate::cli::lidarr::manual_search_command_handler::LidarrManualSearchCommand;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_manual_search_command_from() {
|
||||||
|
let command = LidarrManualSearchCommand::Discography { artist_id: 1 };
|
||||||
|
|
||||||
|
let result = Command::from(command.clone());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
Command::Lidarr(LidarrCommand::ManualSearch(command))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
mod cli {
|
||||||
|
use crate::Cli;
|
||||||
|
use clap::CommandFactory;
|
||||||
|
use clap::error::ErrorKind;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_manual_album_search_requires_artist_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"manual-search",
|
||||||
|
"album",
|
||||||
|
"--album-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_manual_album_search_requires_album_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"manual-search",
|
||||||
|
"album",
|
||||||
|
"--artist-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_manual_album_search_requirements_satisfied() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"manual-search",
|
||||||
|
"album",
|
||||||
|
"--artist-id",
|
||||||
|
"1",
|
||||||
|
"--album-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_manual_discography_search_requires_artist_id() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "manual-search", "discography"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_manual_discography_search_requirements_satisfied() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"manual-search",
|
||||||
|
"discography",
|
||||||
|
"--artist-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod handler {
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::cli::CliCommandHandler;
|
||||||
|
use crate::cli::lidarr::manual_search_command_handler::{
|
||||||
|
LidarrManualSearchCommand, LidarrManualSearchCommandHandler,
|
||||||
|
};
|
||||||
|
use crate::models::Serdeable;
|
||||||
|
use crate::models::lidarr_models::{LidarrRelease, LidarrSerdeable};
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::{
|
||||||
|
torrent_release, usenet_release,
|
||||||
|
};
|
||||||
|
use crate::network::{MockNetworkTrait, NetworkEvent};
|
||||||
|
use mockall::predicate::eq;
|
||||||
|
use pretty_assertions::assert_str_eq;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_manual_album_search_command() {
|
||||||
|
let expected_releases = [torrent_release()];
|
||||||
|
let expected_artist_id = 1;
|
||||||
|
let expected_album_id = 1;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::GetAlbumReleases(expected_artist_id, expected_album_id).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Releases(vec![
|
||||||
|
torrent_release(),
|
||||||
|
LidarrRelease {
|
||||||
|
discography: true,
|
||||||
|
..usenet_release()
|
||||||
|
},
|
||||||
|
])))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let manual_album_search_command = LidarrManualSearchCommand::Album {
|
||||||
|
artist_id: 1,
|
||||||
|
album_id: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = LidarrManualSearchCommandHandler::with(
|
||||||
|
&app_arc,
|
||||||
|
manual_album_search_command,
|
||||||
|
&mut mock_network,
|
||||||
|
)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
assert_str_eq!(
|
||||||
|
result.unwrap(),
|
||||||
|
serde_json::to_string_pretty(&expected_releases).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_manual_discography_search_command() {
|
||||||
|
let expected_releases = [LidarrRelease {
|
||||||
|
discography: true,
|
||||||
|
..usenet_release()
|
||||||
|
}];
|
||||||
|
let expected_artist_id = 1;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::GetDiscographyReleases(expected_artist_id).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Releases(vec![
|
||||||
|
torrent_release(),
|
||||||
|
LidarrRelease {
|
||||||
|
discography: true,
|
||||||
|
..usenet_release()
|
||||||
|
},
|
||||||
|
])))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let manual_discography_search_command =
|
||||||
|
LidarrManualSearchCommand::Discography { artist_id: 1 };
|
||||||
|
|
||||||
|
let result = LidarrManualSearchCommandHandler::with(
|
||||||
|
&app_arc,
|
||||||
|
manual_discography_search_command,
|
||||||
|
&mut mock_network,
|
||||||
|
)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
assert_str_eq!(
|
||||||
|
result.unwrap(),
|
||||||
|
serde_json::to_string_pretty(&expected_releases).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+87
-3
@@ -2,7 +2,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use add_command_handler::{LidarrAddCommand, LidarrAddCommandHandler};
|
use add_command_handler::{LidarrAddCommand, LidarrAddCommandHandler};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{Subcommand, arg};
|
use clap::Subcommand;
|
||||||
use delete_command_handler::{LidarrDeleteCommand, LidarrDeleteCommandHandler};
|
use delete_command_handler::{LidarrDeleteCommand, LidarrDeleteCommandHandler};
|
||||||
use edit_command_handler::{LidarrEditCommand, LidarrEditCommandHandler};
|
use edit_command_handler::{LidarrEditCommand, LidarrEditCommandHandler};
|
||||||
use get_command_handler::{LidarrGetCommand, LidarrGetCommandHandler};
|
use get_command_handler::{LidarrGetCommand, LidarrGetCommandHandler};
|
||||||
@@ -14,16 +14,20 @@ use trigger_automatic_search_command_handler::{
|
|||||||
LidarrTriggerAutomaticSearchCommand, LidarrTriggerAutomaticSearchCommandHandler,
|
LidarrTriggerAutomaticSearchCommand, LidarrTriggerAutomaticSearchCommandHandler,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::{CliCommandHandler, Command};
|
||||||
|
use crate::cli::lidarr::manual_search_command_handler::{
|
||||||
|
LidarrManualSearchCommand, LidarrManualSearchCommandHandler,
|
||||||
|
};
|
||||||
|
use crate::models::lidarr_models::{LidarrReleaseDownloadBody, LidarrTaskName};
|
||||||
use crate::network::lidarr_network::LidarrEvent;
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
use crate::{app::App, network::NetworkTrait};
|
use crate::{app::App, network::NetworkTrait};
|
||||||
|
|
||||||
use super::{CliCommandHandler, Command};
|
|
||||||
|
|
||||||
mod add_command_handler;
|
mod add_command_handler;
|
||||||
mod delete_command_handler;
|
mod delete_command_handler;
|
||||||
mod edit_command_handler;
|
mod edit_command_handler;
|
||||||
mod get_command_handler;
|
mod get_command_handler;
|
||||||
mod list_command_handler;
|
mod list_command_handler;
|
||||||
|
mod manual_search_command_handler;
|
||||||
mod refresh_command_handler;
|
mod refresh_command_handler;
|
||||||
mod trigger_automatic_search_command_handler;
|
mod trigger_automatic_search_command_handler;
|
||||||
|
|
||||||
@@ -63,11 +67,26 @@ pub enum LidarrCommand {
|
|||||||
about = "Commands to refresh the data in your Lidarr instance"
|
about = "Commands to refresh the data in your Lidarr instance"
|
||||||
)]
|
)]
|
||||||
Refresh(LidarrRefreshCommand),
|
Refresh(LidarrRefreshCommand),
|
||||||
|
#[command(subcommand, about = "Commands to manually search for releases")]
|
||||||
|
ManualSearch(LidarrManualSearchCommand),
|
||||||
#[command(
|
#[command(
|
||||||
subcommand,
|
subcommand,
|
||||||
about = "Commands to trigger automatic searches for releases of different resources in your Lidarr instance"
|
about = "Commands to trigger automatic searches for releases of different resources in your Lidarr instance"
|
||||||
)]
|
)]
|
||||||
TriggerAutomaticSearch(LidarrTriggerAutomaticSearchCommand),
|
TriggerAutomaticSearch(LidarrTriggerAutomaticSearchCommand),
|
||||||
|
#[command(about = "Clear the Lidarr blocklist")]
|
||||||
|
ClearBlocklist,
|
||||||
|
#[command(about = "Manually download the given release")]
|
||||||
|
DownloadRelease {
|
||||||
|
#[arg(long, help = "The GUID of the release to download", required = true)]
|
||||||
|
guid: String,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The indexer ID to download the release from",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
indexer_id: i64,
|
||||||
|
},
|
||||||
#[command(about = "Mark the Lidarr history item with the given ID as 'failed'")]
|
#[command(about = "Mark the Lidarr history item with the given ID as 'failed'")]
|
||||||
MarkHistoryItemAsFailed {
|
MarkHistoryItemAsFailed {
|
||||||
#[arg(
|
#[arg(
|
||||||
@@ -86,6 +105,25 @@ pub enum LidarrCommand {
|
|||||||
)]
|
)]
|
||||||
query: String,
|
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(
|
#[command(
|
||||||
about = "Toggle monitoring for the specified album corresponding to the given album ID"
|
about = "Toggle monitoring for the specified album corresponding to the given album ID"
|
||||||
)]
|
)]
|
||||||
@@ -167,6 +205,11 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrCommand> for LidarrCliHandler<'a, '
|
|||||||
.handle()
|
.handle()
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
LidarrCommand::ManualSearch(manual_search_command) => {
|
||||||
|
LidarrManualSearchCommandHandler::with(self.app, manual_search_command, self.network)
|
||||||
|
.handle()
|
||||||
|
.await?
|
||||||
|
}
|
||||||
LidarrCommand::TriggerAutomaticSearch(trigger_automatic_search_command) => {
|
LidarrCommand::TriggerAutomaticSearch(trigger_automatic_search_command) => {
|
||||||
LidarrTriggerAutomaticSearchCommandHandler::with(
|
LidarrTriggerAutomaticSearchCommandHandler::with(
|
||||||
self.app,
|
self.app,
|
||||||
@@ -176,6 +219,25 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrCommand> for LidarrCliHandler<'a, '
|
|||||||
.handle()
|
.handle()
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
LidarrCommand::ClearBlocklist => {
|
||||||
|
self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetBlocklist.into())
|
||||||
|
.await?;
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::ClearBlocklist.into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrCommand::DownloadRelease { guid, indexer_id } => {
|
||||||
|
let params = LidarrReleaseDownloadBody { guid, indexer_id };
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::DownloadRelease(params).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
LidarrCommand::MarkHistoryItemAsFailed { history_item_id } => {
|
LidarrCommand::MarkHistoryItemAsFailed { history_item_id } => {
|
||||||
let _ = self
|
let _ = self
|
||||||
.network
|
.network
|
||||||
@@ -190,6 +252,28 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrCommand> for LidarrCliHandler<'a, '
|
|||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
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 } => {
|
LidarrCommand::ToggleAlbumMonitoring { album_id } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ pub enum LidarrRefreshCommand {
|
|||||||
)]
|
)]
|
||||||
artist_id: i64,
|
artist_id: i64,
|
||||||
},
|
},
|
||||||
|
#[command(about = "Refresh all downloads in Lidarr")]
|
||||||
|
Downloads,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<LidarrRefreshCommand> for Command {
|
impl From<LidarrRefreshCommand> for Command {
|
||||||
@@ -73,6 +75,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrRefreshCommand>
|
|||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
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)
|
Ok(result)
|
||||||
|
|||||||
@@ -22,11 +22,14 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use clap::{Parser, error::ErrorKind};
|
use clap::{Parser, error::ErrorKind};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
#[test]
|
#[rstest]
|
||||||
fn test_refresh_all_artists_has_no_arg_requirements() {
|
fn test_refresh_commands_have_no_arg_requirements(
|
||||||
|
#[values("all-artists", "downloads")] subcommand: &str,
|
||||||
|
) {
|
||||||
let result =
|
let result =
|
||||||
Cli::command().try_get_matches_from(["managarr", "lidarr", "refresh", "all-artists"]);
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "refresh", subcommand]);
|
||||||
|
|
||||||
assert_ok!(&result);
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
@@ -67,6 +70,7 @@ mod tests {
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use mockall::predicate::eq;
|
use mockall::predicate::eq;
|
||||||
|
use rstest::rstest;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
@@ -80,12 +84,18 @@ mod tests {
|
|||||||
network::{MockNetworkTrait, NetworkEvent},
|
network::{MockNetworkTrait, NetworkEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(LidarrRefreshCommand::AllArtists, LidarrEvent::UpdateAllArtists)]
|
||||||
|
#[case(LidarrRefreshCommand::Downloads, LidarrEvent::UpdateDownloads)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_handle_refresh_all_artists_command() {
|
async fn test_handle_refresh_command(
|
||||||
|
#[case] refresh_command: LidarrRefreshCommand,
|
||||||
|
#[case] expected_sonarr_event: LidarrEvent,
|
||||||
|
) {
|
||||||
let mut mock_network = MockNetworkTrait::new();
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(LidarrEvent::UpdateAllArtists.into()))
|
.with(eq::<NetworkEvent>(expected_sonarr_event.into()))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
@@ -93,7 +103,6 @@ mod tests {
|
|||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let refresh_command = LidarrRefreshCommand::AllArtists;
|
|
||||||
|
|
||||||
let result = LidarrRefreshCommandHandler::with(&app_arc, refresh_command, &mut mock_network)
|
let result = LidarrRefreshCommandHandler::with(&app_arc, refresh_command, &mut mock_network)
|
||||||
.handle()
|
.handle()
|
||||||
|
|||||||
@@ -18,6 +18,15 @@ mod trigger_automatic_search_command_handler_tests;
|
|||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
||||||
pub enum LidarrTriggerAutomaticSearchCommand {
|
pub enum LidarrTriggerAutomaticSearchCommand {
|
||||||
|
#[command(about = "Trigger an automatic search for the album with the specified ID")]
|
||||||
|
Album {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The Lidarr ID of the album you want to trigger an automatic search for",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
album_id: i64,
|
||||||
|
},
|
||||||
#[command(about = "Trigger an automatic search for the artist with the specified ID")]
|
#[command(about = "Trigger an automatic search for the artist with the specified ID")]
|
||||||
Artist {
|
Artist {
|
||||||
#[arg(
|
#[arg(
|
||||||
@@ -58,6 +67,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrTriggerAutomaticSearchCommand>
|
|||||||
|
|
||||||
async fn handle(self) -> Result<String> {
|
async fn handle(self) -> Result<String> {
|
||||||
let result = match self.command {
|
let result = match self.command {
|
||||||
|
LidarrTriggerAutomaticSearchCommand::Album { album_id } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::TriggerAutomaticAlbumSearch(album_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
LidarrTriggerAutomaticSearchCommand::Artist { artist_id } => {
|
LidarrTriggerAutomaticSearchCommand::Artist { artist_id } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
|
|||||||
@@ -28,6 +28,36 @@ mod tests {
|
|||||||
use clap::error::ErrorKind;
|
use clap::error::ErrorKind;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trigger_automatic_album_search_requires_album_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"trigger-automatic-search",
|
||||||
|
"album",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trigger_automatic_album_search_with_album_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"trigger-automatic-search",
|
||||||
|
"album",
|
||||||
|
"--album-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_trigger_automatic_artist_search_requires_artist_id() {
|
fn test_trigger_automatic_artist_search_requires_artist_id() {
|
||||||
let result = Cli::command().try_get_matches_from([
|
let result = Cli::command().try_get_matches_from([
|
||||||
@@ -75,6 +105,35 @@ mod tests {
|
|||||||
network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent},
|
network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_trigger_automatic_album_search_command() {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::TriggerAutomaticAlbumSearch(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::Album { album_id: 1 };
|
||||||
|
|
||||||
|
let result = LidarrTriggerAutomaticSearchCommandHandler::with(
|
||||||
|
&app_arc,
|
||||||
|
trigger_automatic_search_command,
|
||||||
|
&mut mock_network,
|
||||||
|
)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_handle_trigger_automatic_artist_search_command() {
|
async fn test_handle_trigger_automatic_artist_search_command() {
|
||||||
let mut mock_network = MockNetworkTrait::new();
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
|||||||
+8
-1
@@ -1,8 +1,9 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{Subcommand, command};
|
use clap::Subcommand;
|
||||||
use clap_complete::Shell;
|
use clap_complete::Shell;
|
||||||
|
use indoc::indoc;
|
||||||
use lidarr::{LidarrCliHandler, LidarrCommand};
|
use lidarr::{LidarrCliHandler, LidarrCommand};
|
||||||
use radarr::{RadarrCliHandler, RadarrCommand};
|
use radarr::{RadarrCliHandler, RadarrCommand};
|
||||||
use sonarr::{SonarrCliHandler, SonarrCommand};
|
use sonarr::{SonarrCliHandler, SonarrCommand};
|
||||||
@@ -43,6 +44,12 @@ pub enum Command {
|
|||||||
#[arg(long, help = "Disable colored log output")]
|
#[arg(long, help = "Disable colored log output")]
|
||||||
no_color: bool,
|
no_color: bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[command(about = indoc!{"
|
||||||
|
Print the full path to the default configuration file.
|
||||||
|
This file can be changed to another location using the '--config-file' flag
|
||||||
|
"})]
|
||||||
|
ConfigPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait CliCommandHandler<'a, 'b, T: Into<Command>> {
|
pub trait CliCommandHandler<'a, 'b, T: Into<Command>> {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{ArgAction, Subcommand, arg, command};
|
use clap::{ArgAction, Subcommand};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use super::RadarrCommand;
|
use super::RadarrCommand;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{Subcommand, command};
|
use clap::Subcommand;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{Subcommand, command};
|
use clap::Subcommand;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -111,6 +111,29 @@ mod tests {
|
|||||||
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]
|
||||||
fn test_list_logs_default_values() {
|
fn test_list_logs_default_values() {
|
||||||
let expected_args = RadarrListCommand::Logs {
|
let expected_args = RadarrListCommand::Logs {
|
||||||
@@ -233,6 +256,32 @@ mod tests {
|
|||||||
assert_ok!(&result);
|
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]
|
||||||
async fn test_handle_list_logs_command() {
|
async fn test_handle_list_logs_command() {
|
||||||
let expected_events = 1000;
|
let expected_events = 1000;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -31,6 +31,31 @@ mod tests {
|
|||||||
assert_ok!(&result);
|
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]
|
||||||
fn test_download_release_requires_movie_id() {
|
fn test_download_release_requires_movie_id() {
|
||||||
let result = Cli::command().try_get_matches_from([
|
let result = Cli::command().try_get_matches_from([
|
||||||
@@ -327,6 +352,36 @@ mod tests {
|
|||||||
assert_ok!(&result);
|
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]
|
||||||
async fn test_download_release_command() {
|
async fn test_download_release_command() {
|
||||||
let expected_release_download_body = RadarrReleaseDownloadBody {
|
let expected_release_download_body = RadarrReleaseDownloadBody {
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ use crate::{
|
|||||||
cli::{CliCommandHandler, Command, mutex_flags_or_option},
|
cli::{CliCommandHandler, Command, mutex_flags_or_option},
|
||||||
models::{
|
models::{
|
||||||
Serdeable,
|
Serdeable,
|
||||||
servarr_models::EditIndexerParams,
|
servarr_models::{EditIndexerParams, IndexerSettings},
|
||||||
sonarr_models::{EditSeriesParams, IndexerSettings, SeriesType, SonarrSerdeable},
|
sonarr_models::{EditSeriesParams, SeriesType, SonarrSerdeable},
|
||||||
},
|
},
|
||||||
network::{NetworkTrait, sonarr_network::SonarrEvent},
|
network::{NetworkTrait, sonarr_network::SonarrEvent},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -622,8 +622,8 @@ mod tests {
|
|||||||
},
|
},
|
||||||
models::{
|
models::{
|
||||||
Serdeable,
|
Serdeable,
|
||||||
servarr_models::EditIndexerParams,
|
servarr_models::{EditIndexerParams, IndexerSettings},
|
||||||
sonarr_models::{EditSeriesParams, IndexerSettings, SeriesType, SonarrSerdeable},
|
sonarr_models::{EditSeriesParams, SeriesType, SonarrSerdeable},
|
||||||
},
|
},
|
||||||
network::{MockNetworkTrait, NetworkEvent, sonarr_network::SonarrEvent},
|
network::{MockNetworkTrait, NetworkEvent, sonarr_network::SonarrEvent},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrListCommand> for SonarrListCommandH
|
|||||||
} => {
|
} => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(SonarrEvent::GetSeasonHistory((series_id, season_number)).into())
|
.handle_network_event(SonarrEvent::GetSeasonHistory(series_id, season_number).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -543,7 +543,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
SonarrEvent::GetSeasonHistory((expected_series_id, expected_season_number)).into(),
|
SonarrEvent::GetSeasonHistory(expected_series_id, expected_season_number).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
|
|||||||
@@ -2,16 +2,18 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
|
use serde_json::json;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use super::SonarrCommand;
|
||||||
|
use crate::models::Serdeable;
|
||||||
|
use crate::models::sonarr_models::{SonarrRelease, SonarrSerdeable};
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{CliCommandHandler, Command},
|
cli::{CliCommandHandler, Command},
|
||||||
network::{NetworkTrait, sonarr_network::SonarrEvent},
|
network::{NetworkTrait, sonarr_network::SonarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::SonarrCommand;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "manual_search_command_handler_tests.rs"]
|
#[path = "manual_search_command_handler_tests.rs"]
|
||||||
mod manual_search_command_handler_tests;
|
mod manual_search_command_handler_tests;
|
||||||
@@ -28,7 +30,7 @@ pub enum SonarrManualSearchCommand {
|
|||||||
episode_id: i64,
|
episode_id: i64,
|
||||||
},
|
},
|
||||||
#[command(
|
#[command(
|
||||||
about = "Trigger a manual search of releases for the given season corresponding to the series with the given ID.\nNote that when downloading a season release, ensure that the release includes 'fullSeason: true', otherwise you'll run into issues"
|
about = "Trigger a manual search of full-season releases (full_season: true) for the given season corresponding to the series with the given ID"
|
||||||
)]
|
)]
|
||||||
Season {
|
Season {
|
||||||
#[arg(
|
#[arg(
|
||||||
@@ -73,22 +75,42 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrManualSearchCommand>
|
|||||||
let result = match self.command {
|
let result = match self.command {
|
||||||
SonarrManualSearchCommand::Episode { episode_id } => {
|
SonarrManualSearchCommand::Episode { episode_id } => {
|
||||||
println!("Searching for episode releases. This may take a minute...");
|
println!("Searching for episode releases. This may take a minute...");
|
||||||
let resp = self
|
match self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(SonarrEvent::GetEpisodeReleases(episode_id).into())
|
.handle_network_event(SonarrEvent::GetEpisodeReleases(episode_id).into())
|
||||||
.await?;
|
.await
|
||||||
serde_json::to_string_pretty(&resp)?
|
{
|
||||||
|
Ok(Serdeable::Sonarr(SonarrSerdeable::Releases(releases_vec))) => {
|
||||||
|
let seasons_vec: Vec<SonarrRelease> = releases_vec
|
||||||
|
.into_iter()
|
||||||
|
.filter(|release| !release.full_season)
|
||||||
|
.collect();
|
||||||
|
serde_json::to_string_pretty(&seasons_vec)?
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
_ => serde_json::to_string_pretty(&json!({"message": "Unexpected response format"}))?,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SonarrManualSearchCommand::Season {
|
SonarrManualSearchCommand::Season {
|
||||||
series_id,
|
series_id,
|
||||||
season_number,
|
season_number,
|
||||||
} => {
|
} => {
|
||||||
println!("Searching for season releases. This may take a minute...");
|
println!("Searching for season releases. This may take a minute...");
|
||||||
let resp = self
|
match self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(SonarrEvent::GetSeasonReleases((series_id, season_number)).into())
|
.handle_network_event(SonarrEvent::GetSeasonReleases(series_id, season_number).into())
|
||||||
.await?;
|
.await
|
||||||
serde_json::to_string_pretty(&resp)?
|
{
|
||||||
|
Ok(Serdeable::Sonarr(SonarrSerdeable::Releases(releases_vec))) => {
|
||||||
|
let seasons_vec: Vec<SonarrRelease> = releases_vec
|
||||||
|
.into_iter()
|
||||||
|
.filter(|release| release.full_season)
|
||||||
|
.collect();
|
||||||
|
serde_json::to_string_pretty(&seasons_vec)?
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
_ => serde_json::to_string_pretty(&json!({"message": "Failed to parse response"}))?,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -108,9 +108,13 @@ mod tests {
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use mockall::predicate::eq;
|
use mockall::predicate::eq;
|
||||||
use serde_json::json;
|
use pretty_assertions::assert_str_eq;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::models::sonarr_models::SonarrRelease;
|
||||||
|
use crate::network::sonarr_network::sonarr_network_test_utils::test_utils::{
|
||||||
|
torrent_release, usenet_release,
|
||||||
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{
|
cli::{
|
||||||
@@ -134,9 +138,13 @@ mod tests {
|
|||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
Ok(Serdeable::Sonarr(SonarrSerdeable::Value(
|
Ok(Serdeable::Sonarr(SonarrSerdeable::Releases(vec![
|
||||||
json!({"testResponse": "response"}),
|
torrent_release(),
|
||||||
)))
|
SonarrRelease {
|
||||||
|
full_season: true,
|
||||||
|
..usenet_release()
|
||||||
|
},
|
||||||
|
])))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let manual_episode_search_command = SonarrManualSearchCommand::Episode { episode_id: 1 };
|
let manual_episode_search_command = SonarrManualSearchCommand::Episode { episode_id: 1 };
|
||||||
@@ -150,23 +158,35 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert_ok!(&result);
|
assert_ok!(&result);
|
||||||
|
assert_str_eq!(
|
||||||
|
result.unwrap(),
|
||||||
|
serde_json::to_string_pretty(&[torrent_release()]).unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_manual_season_search_command() {
|
async fn test_manual_season_search_command() {
|
||||||
|
let expected_release = SonarrRelease {
|
||||||
|
full_season: true,
|
||||||
|
..usenet_release()
|
||||||
|
};
|
||||||
let expected_series_id = 1;
|
let expected_series_id = 1;
|
||||||
let expected_season_number = 1;
|
let expected_season_number = 1;
|
||||||
let mut mock_network = MockNetworkTrait::new();
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
SonarrEvent::GetSeasonReleases((expected_series_id, expected_season_number)).into(),
|
SonarrEvent::GetSeasonReleases(expected_series_id, expected_season_number).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
Ok(Serdeable::Sonarr(SonarrSerdeable::Value(
|
Ok(Serdeable::Sonarr(SonarrSerdeable::Releases(vec![
|
||||||
json!({"testResponse": "response"}),
|
torrent_release(),
|
||||||
)))
|
SonarrRelease {
|
||||||
|
full_season: true,
|
||||||
|
..usenet_release()
|
||||||
|
},
|
||||||
|
])))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let manual_season_search_command = SonarrManualSearchCommand::Season {
|
let manual_season_search_command = SonarrManualSearchCommand::Season {
|
||||||
@@ -183,6 +203,10 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert_ok!(&result);
|
assert_ok!(&result);
|
||||||
|
assert_str_eq!(
|
||||||
|
result.unwrap(),
|
||||||
|
serde_json::to_string_pretty(&[expected_release]).unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrCommand> for SonarrCliHandler<'a, '
|
|||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(
|
.handle_network_event(
|
||||||
SonarrEvent::ToggleSeasonMonitoring((series_id, season_number)).into(),
|
SonarrEvent::ToggleSeasonMonitoring(series_id, season_number).into(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
|||||||
@@ -266,9 +266,10 @@ mod tests {
|
|||||||
},
|
},
|
||||||
models::{
|
models::{
|
||||||
Serdeable,
|
Serdeable,
|
||||||
|
servarr_models::IndexerSettings,
|
||||||
sonarr_models::{
|
sonarr_models::{
|
||||||
BlocklistItem, BlocklistResponse, IndexerSettings, Series, SonarrReleaseDownloadBody,
|
BlocklistItem, BlocklistResponse, Series, SonarrReleaseDownloadBody, SonarrSerdeable,
|
||||||
SonarrSerdeable, SonarrTaskName,
|
SonarrTaskName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
network::{MockNetworkTrait, NetworkEvent, sonarr_network::SonarrEvent},
|
network::{MockNetworkTrait, NetworkEvent, sonarr_network::SonarrEvent},
|
||||||
@@ -754,7 +755,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
SonarrEvent::ToggleSeasonMonitoring((expected_series_id, expected_season_number)).into(),
|
SonarrEvent::ToggleSeasonMonitoring(expected_series_id, expected_season_number).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrTriggerAutomaticSearchCommand>
|
|||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(
|
.handle_network_event(
|
||||||
SonarrEvent::TriggerAutomaticSeasonSearch((series_id, season_number)).into(),
|
SonarrEvent::TriggerAutomaticSeasonSearch(series_id, season_number).into(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
SonarrEvent::TriggerAutomaticSeasonSearch((expected_series_id, expected_season_number))
|
SonarrEvent::TriggerAutomaticSeasonSearch(expected_series_id, expected_season_number)
|
||||||
.into(),
|
.into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ mod tests {
|
|||||||
use crate::handlers::{handle_events, populate_keymapping_table};
|
use crate::handlers::{handle_events, populate_keymapping_table};
|
||||||
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::lidarr::lidarr_data::ActiveLidarrBlock;
|
||||||
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 crate::models::servarr_data::{ActiveKeybindingBlock, Notification};
|
||||||
use crate::models::servarr_models::KeybindingItem;
|
use crate::models::servarr_models::KeybindingItem;
|
||||||
use crate::models::stateful_table::StatefulTable;
|
use crate::models::stateful_table::StatefulTable;
|
||||||
|
|
||||||
@@ -174,6 +174,26 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_handle_clear_notification() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.notification = Some(Notification::new(
|
||||||
|
"Test".to_owned(),
|
||||||
|
"Test".to_owned(),
|
||||||
|
true,
|
||||||
|
));
|
||||||
|
app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
|
||||||
|
app.push_navigation_stack(ActiveRadarrBlock::MovieDetails.into());
|
||||||
|
|
||||||
|
handle_events(DEFAULT_KEYBINDINGS.esc.key, &mut app);
|
||||||
|
|
||||||
|
assert_none!(app.notification);
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveRadarrBlock::MovieDetails.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn test_handle_prompt_toggle_left_right_radarr(#[values(Key::Left, Key::Right)] key: Key) {
|
fn test_handle_prompt_toggle_left_right_radarr(#[values(Key::Left, Key::Right)] key: Key) {
|
||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
@@ -284,6 +304,39 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_handle_events_esc_clears_notification() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.notification = Some(Notification::new(
|
||||||
|
"Download Result".to_owned(),
|
||||||
|
"Download request sent successfully".to_owned(),
|
||||||
|
true,
|
||||||
|
));
|
||||||
|
app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
|
||||||
|
|
||||||
|
handle_events(DEFAULT_KEYBINDINGS.esc.key, &mut app);
|
||||||
|
|
||||||
|
assert_none!(app.notification);
|
||||||
|
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_handle_events_esc_does_not_clear_notification_when_none() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.movies
|
||||||
|
.set_items(vec![Movie::default()]);
|
||||||
|
app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
|
||||||
|
app.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into());
|
||||||
|
|
||||||
|
handle_events(DEFAULT_KEYBINDINGS.esc.key, &mut app);
|
||||||
|
|
||||||
|
assert_none!(app.notification);
|
||||||
|
assert_navigation_popped!(app, ActiveRadarrBlock::Movies.into());
|
||||||
|
}
|
||||||
|
|
||||||
fn context_clue_to_keybinding_item(key: &KeyBinding, desc: &&str) -> KeybindingItem {
|
fn context_clue_to_keybinding_item(key: &KeyBinding, desc: &&str) -> KeybindingItem {
|
||||||
let (key, alt_key) = if key.alt.is_some() {
|
let (key, alt_key) = if key.alt.is_some() {
|
||||||
(key.key.to_string(), key.alt.as_ref().unwrap().to_string())
|
(key.key.to_string(), key.alt.as_ref().unwrap().to_string())
|
||||||
|
|||||||
@@ -0,0 +1,615 @@
|
|||||||
|
#[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::blocklist::{BlocklistHandler, blocklist_sorting_options};
|
||||||
|
use crate::models::lidarr_models::{Artist, BlocklistItem};
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, BLOCKLIST_BLOCKS};
|
||||||
|
use crate::models::servarr_models::{Quality, QualityWrapper};
|
||||||
|
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::artist;
|
||||||
|
|
||||||
|
mod test_handle_delete {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_blocklist_item_prompt() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
app.data.lidarr_data.blocklist.set_items(blocklist_vec());
|
||||||
|
|
||||||
|
BlocklistHandler::new(DELETE_KEY, &mut app, ActiveLidarrBlock::Blocklist, None).handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::DeleteBlocklistItemPrompt.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_blocklist_item_no_op_when_not_ready() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
app.data.lidarr_data.blocklist.set_items(blocklist_vec());
|
||||||
|
|
||||||
|
BlocklistHandler::new(DELETE_KEY, &mut app, ActiveLidarrBlock::Blocklist, None).handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Blocklist.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_blocklist_tab_left(#[values(true, false)] is_ready: bool) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
app.is_loading = is_ready;
|
||||||
|
app.data.lidarr_data.main_tabs.set_index(2);
|
||||||
|
|
||||||
|
BlocklistHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.left.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
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_blocklist_tab_right(#[values(true, false)] is_ready: bool) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
app.is_loading = is_ready;
|
||||||
|
app.data.lidarr_data.main_tabs.set_index(2);
|
||||||
|
|
||||||
|
BlocklistHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.right.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
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_blocklist_left_right_prompt_toggle(
|
||||||
|
#[values(
|
||||||
|
ActiveLidarrBlock::DeleteBlocklistItemPrompt,
|
||||||
|
ActiveLidarrBlock::BlocklistClearAllItemsPrompt
|
||||||
|
)]
|
||||||
|
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::Blocklist.into());
|
||||||
|
|
||||||
|
BlocklistHandler::new(key, &mut app, active_lidarr_block, None).handle();
|
||||||
|
|
||||||
|
assert!(app.data.lidarr_data.prompt_confirm);
|
||||||
|
|
||||||
|
BlocklistHandler::new(key, &mut app, active_lidarr_block, None).handle();
|
||||||
|
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_submit {
|
||||||
|
use crate::assert_navigation_popped;
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_submit() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.blocklist.set_items(blocklist_vec());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
|
||||||
|
BlocklistHandler::new(SUBMIT_KEY, &mut app, ActiveLidarrBlock::Blocklist, None).handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::BlocklistItemDetails.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_submit_no_op_when_not_ready() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.data.lidarr_data.blocklist.set_items(blocklist_vec());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
|
||||||
|
BlocklistHandler::new(SUBMIT_KEY, &mut app, ActiveLidarrBlock::Blocklist, None).handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Blocklist.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
ActiveLidarrBlock::DeleteBlocklistItemPrompt,
|
||||||
|
LidarrEvent::DeleteBlocklistItem(3)
|
||||||
|
)]
|
||||||
|
#[case(
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
ActiveLidarrBlock::BlocklistClearAllItemsPrompt,
|
||||||
|
LidarrEvent::ClearBlocklist
|
||||||
|
)]
|
||||||
|
fn test_blocklist_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.blocklist.set_items(blocklist_vec());
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
app.push_navigation_stack(base_route.into());
|
||||||
|
app.push_navigation_stack(prompt_block.into());
|
||||||
|
|
||||||
|
BlocklistHandler::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]
|
||||||
|
fn test_blocklist_prompt_decline_submit(
|
||||||
|
#[values(
|
||||||
|
ActiveLidarrBlock::DeleteBlocklistItemPrompt,
|
||||||
|
ActiveLidarrBlock::BlocklistClearAllItemsPrompt
|
||||||
|
)]
|
||||||
|
prompt_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.blocklist.set_items(blocklist_vec());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
app.push_navigation_stack(prompt_block.into());
|
||||||
|
|
||||||
|
BlocklistHandler::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, ActiveLidarrBlock::Blocklist.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::Blocklist,
|
||||||
|
ActiveLidarrBlock::DeleteBlocklistItemPrompt
|
||||||
|
)]
|
||||||
|
#[case(
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
ActiveLidarrBlock::BlocklistClearAllItemsPrompt
|
||||||
|
)]
|
||||||
|
fn test_blocklist_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;
|
||||||
|
|
||||||
|
BlocklistHandler::new(ESC_KEY, &mut app, prompt_block, None).handle();
|
||||||
|
|
||||||
|
assert_navigation_popped!(app, base_block.into());
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_esc_blocklist_item_details() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::BlocklistItemDetails.into());
|
||||||
|
|
||||||
|
BlocklistHandler::new(
|
||||||
|
ESC_KEY,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::BlocklistItemDetails,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::Blocklist.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::Blocklist.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
|
||||||
|
BlocklistHandler::new(ESC_KEY, &mut app, ActiveLidarrBlock::Blocklist, None).handle();
|
||||||
|
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::Blocklist.into());
|
||||||
|
assert_is_empty!(app.error.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_key_char {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::{assert_navigation_popped, assert_navigation_pushed};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_refresh_blocklist_key() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.blocklist.set_items(blocklist_vec());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
|
||||||
|
BlocklistHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::Blocklist.into());
|
||||||
|
assert!(app.should_refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_refresh_blocklist_key_no_op_when_not_ready() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.data.lidarr_data.blocklist.set_items(blocklist_vec());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
|
||||||
|
BlocklistHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Blocklist.into());
|
||||||
|
assert!(!app.should_refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clear_blocklist_key() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
app.data.lidarr_data.blocklist.set_items(blocklist_vec());
|
||||||
|
|
||||||
|
BlocklistHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.clear.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::BlocklistClearAllItemsPrompt.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clear_blocklist_key_no_op_when_not_ready() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
app.data.lidarr_data.blocklist.set_items(blocklist_vec());
|
||||||
|
|
||||||
|
BlocklistHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.clear.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Blocklist.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
ActiveLidarrBlock::DeleteBlocklistItemPrompt,
|
||||||
|
LidarrEvent::DeleteBlocklistItem(3)
|
||||||
|
)]
|
||||||
|
#[case(
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
ActiveLidarrBlock::BlocklistClearAllItemsPrompt,
|
||||||
|
LidarrEvent::ClearBlocklist
|
||||||
|
)]
|
||||||
|
fn test_blocklist_prompt_confirm(
|
||||||
|
#[case] base_route: ActiveLidarrBlock,
|
||||||
|
#[case] prompt_block: ActiveLidarrBlock,
|
||||||
|
#[case] expected_action: LidarrEvent,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.blocklist.set_items(blocklist_vec());
|
||||||
|
app.push_navigation_stack(base_route.into());
|
||||||
|
app.push_navigation_stack(prompt_block.into());
|
||||||
|
|
||||||
|
BlocklistHandler::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_blocklist_sorting_options_artist_name() {
|
||||||
|
let expected_cmp_fn: fn(&BlocklistItem, &BlocklistItem) -> Ordering = |a, b| {
|
||||||
|
a.artist
|
||||||
|
.artist_name
|
||||||
|
.text
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.artist.artist_name.text.to_lowercase())
|
||||||
|
};
|
||||||
|
let mut expected_blocklist_vec = blocklist_vec();
|
||||||
|
expected_blocklist_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = blocklist_sorting_options()[0].clone();
|
||||||
|
let mut sorted_blocklist_vec = blocklist_vec();
|
||||||
|
sorted_blocklist_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_blocklist_vec, expected_blocklist_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Artist Name");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_sorting_options_source_title() {
|
||||||
|
let expected_cmp_fn: fn(&BlocklistItem, &BlocklistItem) -> Ordering = |a, b| {
|
||||||
|
a.source_title
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.source_title.to_lowercase())
|
||||||
|
};
|
||||||
|
let mut expected_blocklist_vec = blocklist_vec();
|
||||||
|
expected_blocklist_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = blocklist_sorting_options()[1].clone();
|
||||||
|
let mut sorted_blocklist_vec = blocklist_vec();
|
||||||
|
sorted_blocklist_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_blocklist_vec, expected_blocklist_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Source Title");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_sorting_options_quality() {
|
||||||
|
let expected_cmp_fn: fn(&BlocklistItem, &BlocklistItem) -> Ordering = |a, b| {
|
||||||
|
a.quality
|
||||||
|
.quality
|
||||||
|
.name
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.quality.quality.name.to_lowercase())
|
||||||
|
};
|
||||||
|
let mut expected_blocklist_vec = blocklist_vec();
|
||||||
|
expected_blocklist_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = blocklist_sorting_options()[2].clone();
|
||||||
|
let mut sorted_blocklist_vec = blocklist_vec();
|
||||||
|
sorted_blocklist_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_blocklist_vec, expected_blocklist_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Quality");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_sorting_options_date() {
|
||||||
|
let expected_cmp_fn: fn(&BlocklistItem, &BlocklistItem) -> Ordering =
|
||||||
|
|a, b| a.date.cmp(&b.date);
|
||||||
|
let mut expected_blocklist_vec = blocklist_vec();
|
||||||
|
expected_blocklist_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = blocklist_sorting_options()[3].clone();
|
||||||
|
let mut sorted_blocklist_vec = blocklist_vec();
|
||||||
|
sorted_blocklist_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_blocklist_vec, expected_blocklist_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Date");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_handler_accepts() {
|
||||||
|
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
|
||||||
|
if BLOCKLIST_BLOCKS.contains(&active_lidarr_block) {
|
||||||
|
assert!(BlocklistHandler::accepts(active_lidarr_block));
|
||||||
|
} else {
|
||||||
|
assert!(!BlocklistHandler::accepts(active_lidarr_block));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_blocklist_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 = BlocklistHandler::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_blocklist_item_id() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.blocklist.set_items(blocklist_vec());
|
||||||
|
|
||||||
|
let blocklist_item_id = BlocklistHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.extract_blocklist_item_id();
|
||||||
|
|
||||||
|
assert_eq!(blocklist_item_id, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_handler_not_ready_when_loading() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
app.is_loading = true;
|
||||||
|
|
||||||
|
let handler = BlocklistHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_handler_not_ready_when_blocklist_is_empty() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
app.is_loading = false;
|
||||||
|
|
||||||
|
let handler = BlocklistHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_blocklist_handler_ready_when_not_loading_and_blocklist_is_not_empty() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Blocklist.into());
|
||||||
|
app.is_loading = false;
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.blocklist
|
||||||
|
.set_items(vec![BlocklistItem::default()]);
|
||||||
|
|
||||||
|
let handler = BlocklistHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn blocklist_vec() -> Vec<BlocklistItem> {
|
||||||
|
vec![
|
||||||
|
BlocklistItem {
|
||||||
|
id: 3,
|
||||||
|
source_title: "test 1".to_owned(),
|
||||||
|
quality: QualityWrapper {
|
||||||
|
quality: Quality {
|
||||||
|
name: "Lossless".to_owned(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
date: DateTime::from(DateTime::parse_from_rfc3339("2024-01-10T07:28:45Z").unwrap()),
|
||||||
|
artist: Artist {
|
||||||
|
artist_name: "test 3".into(),
|
||||||
|
..artist()
|
||||||
|
},
|
||||||
|
..BlocklistItem::default()
|
||||||
|
},
|
||||||
|
BlocklistItem {
|
||||||
|
id: 2,
|
||||||
|
source_title: "test 2".to_owned(),
|
||||||
|
quality: QualityWrapper {
|
||||||
|
quality: Quality {
|
||||||
|
name: "Lossy".to_owned(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
date: DateTime::from(DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap()),
|
||||||
|
artist: Artist {
|
||||||
|
artist_name: "test 2".into(),
|
||||||
|
..artist()
|
||||||
|
},
|
||||||
|
..BlocklistItem::default()
|
||||||
|
},
|
||||||
|
BlocklistItem {
|
||||||
|
id: 1,
|
||||||
|
source_title: "test 3".to_owned(),
|
||||||
|
quality: QualityWrapper {
|
||||||
|
quality: Quality {
|
||||||
|
name: "Lossless".to_owned(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
date: DateTime::from(DateTime::parse_from_rfc3339("2024-03-10T07:28:45Z").unwrap()),
|
||||||
|
artist: Artist {
|
||||||
|
artist_name: "".into(),
|
||||||
|
..artist()
|
||||||
|
},
|
||||||
|
..BlocklistItem::default()
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
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::lidarr_models::BlocklistItem;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, BLOCKLIST_BLOCKS};
|
||||||
|
use crate::models::stateful_table::SortOption;
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "blocklist_handler_tests.rs"]
|
||||||
|
mod blocklist_handler_tests;
|
||||||
|
|
||||||
|
pub(super) struct BlocklistHandler<'a, 'b> {
|
||||||
|
key: Key,
|
||||||
|
app: &'a mut App<'b>,
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
_context: Option<ActiveLidarrBlock>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlocklistHandler<'_, '_> {
|
||||||
|
fn extract_blocklist_item_id(&self) -> i64 {
|
||||||
|
self.app.data.lidarr_data.blocklist.current_selection().id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for BlocklistHandler<'a, 'b> {
|
||||||
|
fn handle(&mut self) {
|
||||||
|
let blocklist_table_handling_config =
|
||||||
|
TableHandlingConfig::new(ActiveLidarrBlock::Blocklist.into())
|
||||||
|
.sorting_block(ActiveLidarrBlock::BlocklistSortPrompt.into())
|
||||||
|
.sort_options(blocklist_sorting_options());
|
||||||
|
|
||||||
|
if !handle_table(
|
||||||
|
self,
|
||||||
|
|app| &mut app.data.lidarr_data.blocklist,
|
||||||
|
blocklist_table_handling_config,
|
||||||
|
) {
|
||||||
|
self.handle_key_event();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accepts(active_block: ActiveLidarrBlock) -> bool {
|
||||||
|
BLOCKLIST_BLOCKS.contains(&active_block)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(
|
||||||
|
key: Key,
|
||||||
|
app: &'a mut App<'b>,
|
||||||
|
active_block: ActiveLidarrBlock,
|
||||||
|
context: Option<ActiveLidarrBlock>,
|
||||||
|
) -> Self {
|
||||||
|
BlocklistHandler {
|
||||||
|
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.blocklist.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::Blocklist {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::DeleteBlocklistItemPrompt.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_left_right_action(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::Blocklist => handle_change_tab_left_right_keys(self.app, self.key),
|
||||||
|
ActiveLidarrBlock::DeleteBlocklistItemPrompt
|
||||||
|
| ActiveLidarrBlock::BlocklistClearAllItemsPrompt => handle_prompt_toggle(self.app, self.key),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_submit(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::DeleteBlocklistItemPrompt => {
|
||||||
|
if self.app.data.lidarr_data.prompt_confirm {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action = Some(LidarrEvent::DeleteBlocklistItem(
|
||||||
|
self.extract_blocklist_item_id(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::BlocklistClearAllItemsPrompt => {
|
||||||
|
if self.app.data.lidarr_data.prompt_confirm {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action = Some(LidarrEvent::ClearBlocklist);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::Blocklist => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::BlocklistItemDetails.into());
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_esc(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::DeleteBlocklistItemPrompt
|
||||||
|
| ActiveLidarrBlock::BlocklistClearAllItemsPrompt => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = false;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::BlocklistItemDetails | ActiveLidarrBlock::BlocklistSortPrompt => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
_ => handle_clear_errors(self.app),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_char_key_event(&mut self) {
|
||||||
|
let key = self.key;
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::Blocklist => match self.key {
|
||||||
|
_ if matches_key!(refresh, key) => {
|
||||||
|
self.app.should_refresh = true;
|
||||||
|
}
|
||||||
|
_ if matches_key!(clear, key) => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::BlocklistClearAllItemsPrompt.into());
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
ActiveLidarrBlock::DeleteBlocklistItemPrompt => {
|
||||||
|
if matches_key!(confirm, key) {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action = Some(LidarrEvent::DeleteBlocklistItem(
|
||||||
|
self.extract_blocklist_item_id(),
|
||||||
|
));
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::BlocklistClearAllItemsPrompt => {
|
||||||
|
if matches_key!(confirm, key) {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action = Some(LidarrEvent::ClearBlocklist);
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn blocklist_sorting_options() -> Vec<SortOption<BlocklistItem>> {
|
||||||
|
vec![
|
||||||
|
SortOption {
|
||||||
|
name: "Artist Name",
|
||||||
|
cmp_fn: Some(|a, b| {
|
||||||
|
a.artist
|
||||||
|
.artist_name
|
||||||
|
.text
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.artist.artist_name.text.to_lowercase())
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Source Title",
|
||||||
|
cmp_fn: Some(|a, b| {
|
||||||
|
a.source_title
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.source_title.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,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::Blocklist.into()
|
||||||
|
);
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::Blocklist.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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,7 +29,7 @@ mod tests {
|
|||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
||||||
app.is_loading = is_ready;
|
app.is_loading = is_ready;
|
||||||
app.data.lidarr_data.main_tabs.set_index(1);
|
app.data.lidarr_data.main_tabs.set_index(3);
|
||||||
|
|
||||||
HistoryHandler::new(
|
HistoryHandler::new(
|
||||||
DEFAULT_KEYBINDINGS.left.key,
|
DEFAULT_KEYBINDINGS.left.key,
|
||||||
@@ -41,9 +41,9 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
app.data.lidarr_data.main_tabs.get_active_route(),
|
app.data.lidarr_data.main_tabs.get_active_route(),
|
||||||
ActiveLidarrBlock::Artists.into()
|
ActiveLidarrBlock::Blocklist.into()
|
||||||
);
|
);
|
||||||
assert_navigation_pushed!(app, ActiveLidarrBlock::Artists.into());
|
assert_navigation_pushed!(app, ActiveLidarrBlock::Blocklist.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
@@ -51,7 +51,7 @@ mod tests {
|
|||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
||||||
app.is_loading = is_ready;
|
app.is_loading = is_ready;
|
||||||
app.data.lidarr_data.main_tabs.set_index(1);
|
app.data.lidarr_data.main_tabs.set_index(3);
|
||||||
|
|
||||||
HistoryHandler::new(
|
HistoryHandler::new(
|
||||||
DEFAULT_KEYBINDINGS.right.key,
|
DEFAULT_KEYBINDINGS.right.key,
|
||||||
@@ -63,9 +63,12 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
app.data.lidarr_data.main_tabs.get_active_route(),
|
app.data.lidarr_data.main_tabs.get_active_route(),
|
||||||
ActiveLidarrBlock::Artists.into()
|
ActiveLidarrBlock::RootFolders.into()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveLidarrBlock::RootFolders.into()
|
||||||
);
|
);
|
||||||
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Artists.into());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(5);
|
||||||
|
|
||||||
|
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(5);
|
||||||
|
|
||||||
|
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,468 @@
|
|||||||
|
use crate::app::App;
|
||||||
|
use crate::event::Key;
|
||||||
|
use crate::handlers::lidarr_handlers::history::history_sorting_options;
|
||||||
|
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||||
|
use crate::handlers::{KeyEventHandler, handle_prompt_toggle};
|
||||||
|
use crate::matches_key;
|
||||||
|
use crate::models::Route;
|
||||||
|
use crate::models::lidarr_models::{
|
||||||
|
LidarrHistoryItem, LidarrRelease, LidarrReleaseDownloadBody, Track,
|
||||||
|
};
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{ALBUM_DETAILS_BLOCKS, ActiveLidarrBlock};
|
||||||
|
use crate::models::stateful_table::SortOption;
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use serde_json::Number;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "album_details_handler_tests.rs"]
|
||||||
|
mod album_details_handler_tests;
|
||||||
|
|
||||||
|
pub(in crate::handlers::lidarr_handlers) struct AlbumDetailsHandler<'a, 'b> {
|
||||||
|
key: Key,
|
||||||
|
app: &'a mut App<'b>,
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
_context: Option<ActiveLidarrBlock>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AlbumDetailsHandler<'_, '_> {
|
||||||
|
fn extract_track_file_id(&self) -> i64 {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.expect("Album details have not been loaded")
|
||||||
|
.tracks
|
||||||
|
.current_selection()
|
||||||
|
.track_file_id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_album_id(&self) -> i64 {
|
||||||
|
self.app.data.lidarr_data.albums.current_selection().id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for AlbumDetailsHandler<'a, 'b> {
|
||||||
|
fn handle(&mut self) {
|
||||||
|
let tracks_table_handling_config =
|
||||||
|
TableHandlingConfig::new(ActiveLidarrBlock::AlbumDetails.into())
|
||||||
|
.searching_block(ActiveLidarrBlock::SearchTracks.into())
|
||||||
|
.search_error_block(ActiveLidarrBlock::SearchTracksError.into())
|
||||||
|
.search_field_fn(|track: &Track| &track.title);
|
||||||
|
let album_history_table_handling_config =
|
||||||
|
TableHandlingConfig::new(ActiveLidarrBlock::AlbumHistory.into())
|
||||||
|
.sorting_block(ActiveLidarrBlock::AlbumHistorySortPrompt.into())
|
||||||
|
.sort_options(history_sorting_options())
|
||||||
|
.searching_block(ActiveLidarrBlock::SearchAlbumHistory.into())
|
||||||
|
.search_error_block(ActiveLidarrBlock::SearchAlbumHistoryError.into())
|
||||||
|
.search_field_fn(|history_item: &LidarrHistoryItem| &history_item.source_title.text)
|
||||||
|
.filtering_block(ActiveLidarrBlock::FilterAlbumHistory.into())
|
||||||
|
.filter_error_block(ActiveLidarrBlock::FilterAlbumHistoryError.into())
|
||||||
|
.filter_field_fn(|history_item: &LidarrHistoryItem| &history_item.source_title.text);
|
||||||
|
let album_releases_table_handling_config =
|
||||||
|
TableHandlingConfig::new(ActiveLidarrBlock::ManualAlbumSearch.into())
|
||||||
|
.sorting_block(ActiveLidarrBlock::ManualAlbumSearchSortPrompt.into())
|
||||||
|
.sort_options(releases_sorting_options());
|
||||||
|
|
||||||
|
if !handle_table(
|
||||||
|
self,
|
||||||
|
|app| {
|
||||||
|
&mut app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.expect("Album details modal is undefined")
|
||||||
|
.tracks
|
||||||
|
},
|
||||||
|
tracks_table_handling_config,
|
||||||
|
) && !handle_table(
|
||||||
|
self,
|
||||||
|
|app| {
|
||||||
|
&mut app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.expect("Album details modal is undefined")
|
||||||
|
.album_history
|
||||||
|
},
|
||||||
|
album_history_table_handling_config,
|
||||||
|
) && !handle_table(
|
||||||
|
self,
|
||||||
|
|app| {
|
||||||
|
&mut app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.expect("Album details modal is undefined")
|
||||||
|
.album_releases
|
||||||
|
},
|
||||||
|
album_releases_table_handling_config,
|
||||||
|
) {
|
||||||
|
self.handle_key_event();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accepts(active_block: ActiveLidarrBlock) -> bool {
|
||||||
|
ALBUM_DETAILS_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>,
|
||||||
|
) -> AlbumDetailsHandler<'a, 'b> {
|
||||||
|
AlbumDetailsHandler {
|
||||||
|
key,
|
||||||
|
app,
|
||||||
|
active_lidarr_block: active_block,
|
||||||
|
_context: context,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_key(&self) -> Key {
|
||||||
|
self.key
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_ready(&self) -> bool {
|
||||||
|
if self.app.is_loading {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(album_details_modal) = &self.app.data.lidarr_data.album_details_modal else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::AlbumDetails => !album_details_modal.tracks.is_empty(),
|
||||||
|
ActiveLidarrBlock::AlbumHistory => !album_details_modal.album_history.is_empty(),
|
||||||
|
ActiveLidarrBlock::ManualAlbumSearch => !album_details_modal.album_releases.is_empty(),
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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::AlbumDetails {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::DeleteTrackFilePrompt.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_left_right_action(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::AlbumDetails
|
||||||
|
| ActiveLidarrBlock::AlbumHistory
|
||||||
|
| ActiveLidarrBlock::ManualAlbumSearch => match self.key {
|
||||||
|
_ if matches_key!(left, self.key) => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.album_details_tabs
|
||||||
|
.previous();
|
||||||
|
self.app.pop_and_push_navigation_stack(
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.album_details_tabs
|
||||||
|
.get_active_route(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ if matches_key!(right, self.key) => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.album_details_tabs
|
||||||
|
.next();
|
||||||
|
self.app.pop_and_push_navigation_stack(
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.album_details_tabs
|
||||||
|
.get_active_route(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
ActiveLidarrBlock::AutomaticallySearchAlbumPrompt
|
||||||
|
| ActiveLidarrBlock::ManualAlbumSearchConfirmPrompt
|
||||||
|
| ActiveLidarrBlock::DeleteTrackFilePrompt => {
|
||||||
|
handle_prompt_toggle(self.app, self.key);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_submit(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::AlbumDetails
|
||||||
|
if self.app.data.lidarr_data.album_details_modal.is_some()
|
||||||
|
&& !self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.tracks
|
||||||
|
.is_empty() =>
|
||||||
|
{
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::TrackDetails.into())
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AlbumHistory => self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::AlbumHistoryDetails.into()),
|
||||||
|
ActiveLidarrBlock::DeleteTrackFilePrompt => {
|
||||||
|
if self.app.data.lidarr_data.prompt_confirm {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action =
|
||||||
|
Some(LidarrEvent::DeleteTrackFile(self.extract_track_file_id()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AutomaticallySearchAlbumPrompt => {
|
||||||
|
if self.app.data.lidarr_data.prompt_confirm {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action = Some(
|
||||||
|
LidarrEvent::TriggerAutomaticAlbumSearch(self.extract_album_id()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::ManualAlbumSearch => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::ManualAlbumSearchConfirmPrompt.into());
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::ManualAlbumSearchConfirmPrompt => {
|
||||||
|
if self.app.data.lidarr_data.prompt_confirm {
|
||||||
|
let LidarrRelease {
|
||||||
|
guid, indexer_id, ..
|
||||||
|
} = self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.album_releases
|
||||||
|
.current_selection();
|
||||||
|
let params = LidarrReleaseDownloadBody {
|
||||||
|
guid: guid.clone(),
|
||||||
|
indexer_id: *indexer_id,
|
||||||
|
};
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action =
|
||||||
|
Some(LidarrEvent::DownloadRelease(params));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_esc(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::AlbumDetails | ActiveLidarrBlock::ManualAlbumSearch => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
self.app.data.lidarr_data.album_details_modal = None;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AlbumHistoryDetails => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AlbumHistory => {
|
||||||
|
if self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.album_history
|
||||||
|
.filtered_items
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.album_history
|
||||||
|
.filtered_items = None;
|
||||||
|
} else {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
self.app.data.lidarr_data.album_details_modal = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AutomaticallySearchAlbumPrompt
|
||||||
|
| ActiveLidarrBlock::ManualAlbumSearchConfirmPrompt
|
||||||
|
| ActiveLidarrBlock::DeleteTrackFilePrompt => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = false;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_char_key_event(&mut self) {
|
||||||
|
let key = self.key;
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::AlbumDetails
|
||||||
|
| ActiveLidarrBlock::AlbumHistory
|
||||||
|
| ActiveLidarrBlock::ManualAlbumSearch => match self.key {
|
||||||
|
_ if matches_key!(refresh, self.key) => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.pop_and_push_navigation_stack(self.active_lidarr_block.into());
|
||||||
|
}
|
||||||
|
_ if matches_key!(auto_search, self.key) => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::AutomaticallySearchAlbumPrompt.into());
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
ActiveLidarrBlock::AutomaticallySearchAlbumPrompt if matches_key!(confirm, key) => {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action = Some(
|
||||||
|
LidarrEvent::TriggerAutomaticAlbumSearch(self.extract_album_id()),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::DeleteTrackFilePrompt if matches_key!(confirm, key) => {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action =
|
||||||
|
Some(LidarrEvent::DeleteTrackFile(self.extract_track_file_id()));
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::ManualAlbumSearchConfirmPrompt if matches_key!(confirm, key) => {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
let LidarrRelease {
|
||||||
|
guid, indexer_id, ..
|
||||||
|
} = self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.album_releases
|
||||||
|
.current_selection();
|
||||||
|
let params = LidarrReleaseDownloadBody {
|
||||||
|
guid: guid.clone(),
|
||||||
|
indexer_id: *indexer_id,
|
||||||
|
};
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action =
|
||||||
|
Some(LidarrEvent::DownloadRelease(params));
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::handlers::lidarr_handlers::library) fn releases_sorting_options()
|
||||||
|
-> Vec<SortOption<LidarrRelease>> {
|
||||||
|
vec![
|
||||||
|
SortOption {
|
||||||
|
name: "Source",
|
||||||
|
cmp_fn: Some(|a, b| a.protocol.cmp(&b.protocol)),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Age",
|
||||||
|
cmp_fn: Some(|a, b| a.age.cmp(&b.age)),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Rejected",
|
||||||
|
cmp_fn: Some(|a, b| a.rejected.cmp(&b.rejected)),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Title",
|
||||||
|
cmp_fn: Some(|a, b| {
|
||||||
|
a.title
|
||||||
|
.text
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.title.text.to_lowercase())
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Indexer",
|
||||||
|
cmp_fn: Some(|a, b| a.indexer.to_lowercase().cmp(&b.indexer.to_lowercase())),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Size",
|
||||||
|
cmp_fn: Some(|a, b| a.size.cmp(&b.size)),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Peers",
|
||||||
|
cmp_fn: Some(|a, b| {
|
||||||
|
let default_number = Number::from(i64::MAX);
|
||||||
|
let seeder_a = a
|
||||||
|
.seeders
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&default_number)
|
||||||
|
.as_u64()
|
||||||
|
.unwrap();
|
||||||
|
let seeder_b = b
|
||||||
|
.seeders
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&default_number)
|
||||||
|
.as_u64()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
seeder_a.cmp(&seeder_b)
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Quality",
|
||||||
|
cmp_fn: Some(|a, b| a.quality.cmp(&b.quality)),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,20 @@
|
|||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::event::Key;
|
use crate::event::Key;
|
||||||
use crate::handlers::lidarr_handlers::library::delete_album_handler::DeleteAlbumHandler;
|
use crate::handlers::lidarr_handlers::history::history_sorting_options;
|
||||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||||
use crate::handlers::{KeyEventHandler, handle_prompt_toggle};
|
use crate::handlers::{KeyEventHandler, handle_prompt_toggle};
|
||||||
use crate::matches_key;
|
use crate::matches_key;
|
||||||
use crate::models::lidarr_models::Album;
|
use crate::models::lidarr_models::{
|
||||||
|
Album, LidarrHistoryItem, LidarrRelease, LidarrReleaseDownloadBody,
|
||||||
|
};
|
||||||
use crate::models::servarr_data::lidarr::lidarr_data::{
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, DELETE_ALBUM_SELECTION_BLOCKS,
|
ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, DELETE_ALBUM_SELECTION_BLOCKS,
|
||||||
EDIT_ARTIST_SELECTION_BLOCKS,
|
EDIT_ARTIST_SELECTION_BLOCKS,
|
||||||
};
|
};
|
||||||
|
use crate::models::stateful_table::SortOption;
|
||||||
use crate::models::{BlockSelectionState, Route};
|
use crate::models::{BlockSelectionState, Route};
|
||||||
use crate::network::lidarr_network::LidarrEvent;
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use serde_json::Number;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "artist_details_handler_tests.rs"]
|
#[path = "artist_details_handler_tests.rs"]
|
||||||
@@ -20,7 +24,7 @@ pub struct ArtistDetailsHandler<'a, 'b> {
|
|||||||
key: Key,
|
key: Key,
|
||||||
app: &'a mut App<'b>,
|
app: &'a mut App<'b>,
|
||||||
active_lidarr_block: ActiveLidarrBlock,
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
context: Option<ActiveLidarrBlock>,
|
_context: Option<ActiveLidarrBlock>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArtistDetailsHandler<'_, '_> {
|
impl ArtistDetailsHandler<'_, '_> {
|
||||||
@@ -41,23 +45,41 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for ArtistDetailsHandler
|
|||||||
.search_error_block(ActiveLidarrBlock::SearchAlbumsError.into())
|
.search_error_block(ActiveLidarrBlock::SearchAlbumsError.into())
|
||||||
.search_field_fn(|album: &Album| &album.title.text);
|
.search_field_fn(|album: &Album| &album.title.text);
|
||||||
|
|
||||||
|
let artist_history_table_handling_config =
|
||||||
|
TableHandlingConfig::new(ActiveLidarrBlock::ArtistHistory.into())
|
||||||
|
.sorting_block(ActiveLidarrBlock::ArtistHistorySortPrompt.into())
|
||||||
|
.sort_options(history_sorting_options())
|
||||||
|
.searching_block(ActiveLidarrBlock::SearchArtistHistory.into())
|
||||||
|
.search_error_block(ActiveLidarrBlock::SearchArtistHistoryError.into())
|
||||||
|
.search_field_fn(|history_item: &LidarrHistoryItem| &history_item.source_title.text)
|
||||||
|
.filtering_block(ActiveLidarrBlock::FilterArtistHistory.into())
|
||||||
|
.filter_error_block(ActiveLidarrBlock::FilterArtistHistoryError.into())
|
||||||
|
.filter_field_fn(|history_item: &LidarrHistoryItem| &history_item.source_title.text);
|
||||||
|
|
||||||
|
let artist_releases_table_handling_config =
|
||||||
|
TableHandlingConfig::new(ActiveLidarrBlock::ManualArtistSearch.into())
|
||||||
|
.sorting_block(ActiveLidarrBlock::ManualArtistSearchSortPrompt.into())
|
||||||
|
.sort_options(releases_sorting_options());
|
||||||
|
|
||||||
if !handle_table(
|
if !handle_table(
|
||||||
self,
|
self,
|
||||||
|app| &mut app.data.lidarr_data.albums,
|
|app| &mut app.data.lidarr_data.albums,
|
||||||
albums_table_handling_config,
|
albums_table_handling_config,
|
||||||
|
) && !handle_table(
|
||||||
|
self,
|
||||||
|
|app| &mut app.data.lidarr_data.artist_history,
|
||||||
|
artist_history_table_handling_config,
|
||||||
|
) && !handle_table(
|
||||||
|
self,
|
||||||
|
|app| &mut app.data.lidarr_data.discography_releases,
|
||||||
|
artist_releases_table_handling_config,
|
||||||
) {
|
) {
|
||||||
match self.active_lidarr_block {
|
self.handle_key_event();
|
||||||
_ if DeleteAlbumHandler::accepts(self.active_lidarr_block) => {
|
|
||||||
DeleteAlbumHandler::new(self.key, self.app, self.active_lidarr_block, self.context)
|
|
||||||
.handle();
|
|
||||||
}
|
|
||||||
_ => self.handle_key_event(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn accepts(active_block: ActiveLidarrBlock) -> bool {
|
fn accepts(active_block: ActiveLidarrBlock) -> bool {
|
||||||
DeleteAlbumHandler::accepts(active_block) || ARTIST_DETAILS_BLOCKS.contains(&active_block)
|
ARTIST_DETAILS_BLOCKS.contains(&active_block)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ignore_special_keys(&self) -> bool {
|
fn ignore_special_keys(&self) -> bool {
|
||||||
@@ -68,13 +90,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for ArtistDetailsHandler
|
|||||||
key: Key,
|
key: Key,
|
||||||
app: &'a mut App<'b>,
|
app: &'a mut App<'b>,
|
||||||
active_block: ActiveLidarrBlock,
|
active_block: ActiveLidarrBlock,
|
||||||
context: Option<ActiveLidarrBlock>,
|
_context: Option<ActiveLidarrBlock>,
|
||||||
) -> ArtistDetailsHandler<'a, 'b> {
|
) -> ArtistDetailsHandler<'a, 'b> {
|
||||||
ArtistDetailsHandler {
|
ArtistDetailsHandler {
|
||||||
key,
|
key,
|
||||||
app,
|
app,
|
||||||
active_lidarr_block: active_block,
|
active_lidarr_block: active_block,
|
||||||
context,
|
_context,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +105,17 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for ArtistDetailsHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_ready(&self) -> bool {
|
fn is_ready(&self) -> bool {
|
||||||
!self.app.is_loading
|
if self.app.is_loading {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::ArtistHistory => !self.app.data.lidarr_data.artist_history.is_empty(),
|
||||||
|
ActiveLidarrBlock::ManualArtistSearch => {
|
||||||
|
!self.app.data.lidarr_data.discography_releases.is_empty()
|
||||||
|
}
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_scroll_up(&mut self) {}
|
fn handle_scroll_up(&mut self) {}
|
||||||
@@ -106,8 +138,36 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for ArtistDetailsHandler
|
|||||||
|
|
||||||
fn handle_left_right_action(&mut self) {
|
fn handle_left_right_action(&mut self) {
|
||||||
match self.active_lidarr_block {
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::ArtistDetails
|
||||||
|
| ActiveLidarrBlock::ArtistHistory
|
||||||
|
| ActiveLidarrBlock::ManualArtistSearch => match self.key {
|
||||||
|
_ if matches_key!(left, self.key) => {
|
||||||
|
self.app.data.lidarr_data.artist_info_tabs.previous();
|
||||||
|
self.app.pop_and_push_navigation_stack(
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.artist_info_tabs
|
||||||
|
.get_active_route(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ if matches_key!(right, self.key) => {
|
||||||
|
self.app.data.lidarr_data.artist_info_tabs.next();
|
||||||
|
self.app.pop_and_push_navigation_stack(
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.artist_info_tabs
|
||||||
|
.get_active_route(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
ActiveLidarrBlock::UpdateAndScanArtistPrompt
|
ActiveLidarrBlock::UpdateAndScanArtistPrompt
|
||||||
| ActiveLidarrBlock::AutomaticallySearchArtistPrompt => {
|
| ActiveLidarrBlock::AutomaticallySearchArtistPrompt
|
||||||
|
| ActiveLidarrBlock::ManualArtistSearchConfirmPrompt => {
|
||||||
handle_prompt_toggle(self.app, self.key);
|
handle_prompt_toggle(self.app, self.key);
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
@@ -116,6 +176,39 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for ArtistDetailsHandler
|
|||||||
|
|
||||||
fn handle_submit(&mut self) {
|
fn handle_submit(&mut self) {
|
||||||
match self.active_lidarr_block {
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::ArtistDetails if !self.app.data.lidarr_data.albums.is_empty() => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::AlbumDetails.into());
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::ArtistHistory if !self.app.data.lidarr_data.artist_history.is_empty() => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::ArtistHistoryDetails.into());
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::ManualArtistSearch => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::ManualArtistSearchConfirmPrompt.into());
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::ManualArtistSearchConfirmPrompt => {
|
||||||
|
if self.app.data.lidarr_data.prompt_confirm {
|
||||||
|
let LidarrRelease {
|
||||||
|
guid, indexer_id, ..
|
||||||
|
} = self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.discography_releases
|
||||||
|
.current_selection()
|
||||||
|
.clone();
|
||||||
|
let params = LidarrReleaseDownloadBody { guid, indexer_id };
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action =
|
||||||
|
Some(LidarrEvent::DownloadRelease(params));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
ActiveLidarrBlock::AutomaticallySearchArtistPrompt => {
|
ActiveLidarrBlock::AutomaticallySearchArtistPrompt => {
|
||||||
if self.app.data.lidarr_data.prompt_confirm {
|
if self.app.data.lidarr_data.prompt_confirm {
|
||||||
self.app.data.lidarr_data.prompt_confirm_action = Some(
|
self.app.data.lidarr_data.prompt_confirm_action = Some(
|
||||||
@@ -140,11 +233,30 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for ArtistDetailsHandler
|
|||||||
fn handle_esc(&mut self) {
|
fn handle_esc(&mut self) {
|
||||||
match self.active_lidarr_block {
|
match self.active_lidarr_block {
|
||||||
ActiveLidarrBlock::UpdateAndScanArtistPrompt
|
ActiveLidarrBlock::UpdateAndScanArtistPrompt
|
||||||
| ActiveLidarrBlock::AutomaticallySearchArtistPrompt => {
|
| ActiveLidarrBlock::AutomaticallySearchArtistPrompt
|
||||||
|
| ActiveLidarrBlock::ManualArtistSearchConfirmPrompt => {
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
self.app.data.lidarr_data.prompt_confirm = false;
|
self.app.data.lidarr_data.prompt_confirm = false;
|
||||||
}
|
}
|
||||||
ActiveLidarrBlock::ArtistDetails => {
|
ActiveLidarrBlock::ArtistHistoryDetails => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::ArtistHistory => {
|
||||||
|
if self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.artist_history
|
||||||
|
.filtered_items
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
self.app.data.lidarr_data.artist_history.reset_filter();
|
||||||
|
} else {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
self.app.data.lidarr_data.reset_artist_info_tabs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::ArtistDetails | ActiveLidarrBlock::ManualArtistSearch => {
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
self.app.data.lidarr_data.reset_artist_info_tabs();
|
self.app.data.lidarr_data.reset_artist_info_tabs();
|
||||||
}
|
}
|
||||||
@@ -194,6 +306,34 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for ArtistDetailsHandler
|
|||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
|
ActiveLidarrBlock::ArtistHistory | ActiveLidarrBlock::ManualArtistSearch => match self.key {
|
||||||
|
_ if matches_key!(refresh, key) => self
|
||||||
|
.app
|
||||||
|
.pop_and_push_navigation_stack(self.active_lidarr_block.into()),
|
||||||
|
_ if matches_key!(auto_search, key) => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::AutomaticallySearchArtistPrompt.into());
|
||||||
|
}
|
||||||
|
_ if matches_key!(edit, key) => {
|
||||||
|
self.app.push_navigation_stack(
|
||||||
|
(
|
||||||
|
ActiveLidarrBlock::EditArtistPrompt,
|
||||||
|
Some(self.active_lidarr_block),
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
self.app.data.lidarr_data.edit_artist_modal = Some((&self.app.data.lidarr_data).into());
|
||||||
|
self.app.data.lidarr_data.selected_block =
|
||||||
|
BlockSelectionState::new(EDIT_ARTIST_SELECTION_BLOCKS);
|
||||||
|
}
|
||||||
|
_ if matches_key!(update, key) => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::UpdateAndScanArtistPrompt.into());
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
ActiveLidarrBlock::AutomaticallySearchArtistPrompt => {
|
ActiveLidarrBlock::AutomaticallySearchArtistPrompt => {
|
||||||
if matches_key!(confirm, key) {
|
if matches_key!(confirm, key) {
|
||||||
self.app.data.lidarr_data.prompt_confirm = true;
|
self.app.data.lidarr_data.prompt_confirm = true;
|
||||||
@@ -213,6 +353,25 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for ArtistDetailsHandler
|
|||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ActiveLidarrBlock::ManualArtistSearchConfirmPrompt => {
|
||||||
|
if matches_key!(confirm, key) {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
let LidarrRelease {
|
||||||
|
guid, indexer_id, ..
|
||||||
|
} = self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.discography_releases
|
||||||
|
.current_selection()
|
||||||
|
.clone();
|
||||||
|
let params = LidarrReleaseDownloadBody { guid, indexer_id };
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action =
|
||||||
|
Some(LidarrEvent::DownloadRelease(params));
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,3 +384,61 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for ArtistDetailsHandler
|
|||||||
self.app.get_current_route()
|
self.app.get_current_route()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn releases_sorting_options() -> Vec<SortOption<LidarrRelease>> {
|
||||||
|
vec![
|
||||||
|
SortOption {
|
||||||
|
name: "Source",
|
||||||
|
cmp_fn: Some(|a, b| a.protocol.cmp(&b.protocol)),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Age",
|
||||||
|
cmp_fn: Some(|a, b| a.age.cmp(&b.age)),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Rejected",
|
||||||
|
cmp_fn: Some(|a, b| a.rejected.cmp(&b.rejected)),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Title",
|
||||||
|
cmp_fn: Some(|a, b| {
|
||||||
|
a.title
|
||||||
|
.text
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.title.text.to_lowercase())
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Indexer",
|
||||||
|
cmp_fn: Some(|a, b| a.indexer.to_lowercase().cmp(&b.indexer.to_lowercase())),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Size",
|
||||||
|
cmp_fn: Some(|a, b| a.size.cmp(&b.size)),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Peers",
|
||||||
|
cmp_fn: Some(|a, b| {
|
||||||
|
let default_number = Number::from(i64::MAX);
|
||||||
|
let seeder_a = a
|
||||||
|
.seeders
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&default_number)
|
||||||
|
.as_u64()
|
||||||
|
.unwrap();
|
||||||
|
let seeder_b = b
|
||||||
|
.seeders
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&default_number)
|
||||||
|
.as_u64()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
seeder_a.cmp(&seeder_b)
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
SortOption {
|
||||||
|
name: "Quality",
|
||||||
|
cmp_fn: Some(|a, b| a.quality.cmp(&b.quality)),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
|
use serde_json::Number;
|
||||||
|
use std::cmp::Ordering;
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
use crate::handlers::KeyEventHandler;
|
use crate::handlers::KeyEventHandler;
|
||||||
use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler;
|
use crate::handlers::lidarr_handlers::library::artist_details_handler::{
|
||||||
use crate::models::servarr_data::lidarr::lidarr_data::{
|
ArtistDetailsHandler, releases_sorting_options,
|
||||||
ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, DELETE_ALBUM_BLOCKS,
|
|
||||||
};
|
};
|
||||||
|
use crate::models::HorizontallyScrollableText;
|
||||||
|
use crate::models::lidarr_models::{LidarrHistoryItem, LidarrRelease};
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
|
ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock,
|
||||||
|
};
|
||||||
|
use crate::models::servarr_models::{Quality, QualityWrapper};
|
||||||
|
|
||||||
mod test_handle_delete {
|
mod test_handle_delete {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -49,6 +56,8 @@ mod tests {
|
|||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
|
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
|
use crate::assert_navigation_pushed;
|
||||||
use crate::event::Key;
|
use crate::event::Key;
|
||||||
use crate::handlers::KeyEventHandler;
|
use crate::handlers::KeyEventHandler;
|
||||||
use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler;
|
use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler;
|
||||||
@@ -58,7 +67,8 @@ mod tests {
|
|||||||
fn test_left_right_prompt_toggle(
|
fn test_left_right_prompt_toggle(
|
||||||
#[values(
|
#[values(
|
||||||
ActiveLidarrBlock::UpdateAndScanArtistPrompt,
|
ActiveLidarrBlock::UpdateAndScanArtistPrompt,
|
||||||
ActiveLidarrBlock::AutomaticallySearchArtistPrompt
|
ActiveLidarrBlock::AutomaticallySearchArtistPrompt,
|
||||||
|
ActiveLidarrBlock::ManualArtistSearchConfirmPrompt
|
||||||
)]
|
)]
|
||||||
active_lidarr_block: ActiveLidarrBlock,
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
#[values(Key::Left, Key::Right)] key: Key,
|
#[values(Key::Left, Key::Right)] key: Key,
|
||||||
@@ -75,18 +85,66 @@ mod tests {
|
|||||||
|
|
||||||
assert!(!app.data.lidarr_data.prompt_confirm);
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(ActiveLidarrBlock::ArtistDetails, ActiveLidarrBlock::ArtistHistory)]
|
||||||
|
#[case(
|
||||||
|
ActiveLidarrBlock::ArtistHistory,
|
||||||
|
ActiveLidarrBlock::ManualArtistSearch
|
||||||
|
)]
|
||||||
|
#[case(
|
||||||
|
ActiveLidarrBlock::ManualArtistSearch,
|
||||||
|
ActiveLidarrBlock::ArtistDetails
|
||||||
|
)]
|
||||||
|
fn test_artist_details_tabs_left_right_action(
|
||||||
|
#[case] left_block: ActiveLidarrBlock,
|
||||||
|
#[case] right_block: ActiveLidarrBlock,
|
||||||
|
#[values(true, false)] is_loading: bool,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.is_loading = is_loading;
|
||||||
|
app.push_navigation_stack(right_block.into());
|
||||||
|
app.data.lidarr_data.artist_info_tabs.index = app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.artist_info_tabs
|
||||||
|
.tabs
|
||||||
|
.iter()
|
||||||
|
.position(|tab_route| tab_route.route == right_block.into())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(DEFAULT_KEYBINDINGS.left.key, &mut app, right_block, None).handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
app.data.lidarr_data.artist_info_tabs.get_active_route()
|
||||||
|
);
|
||||||
|
assert_navigation_pushed!(app, left_block.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(DEFAULT_KEYBINDINGS.right.key, &mut app, left_block, None).handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
app.data.lidarr_data.artist_info_tabs.get_active_route()
|
||||||
|
);
|
||||||
|
assert_navigation_pushed!(app, right_block.into());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod test_handle_submit {
|
mod test_handle_submit {
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
use crate::assert_navigation_popped;
|
|
||||||
use crate::event::Key;
|
use crate::event::Key;
|
||||||
use crate::handlers::KeyEventHandler;
|
use crate::handlers::KeyEventHandler;
|
||||||
use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler;
|
use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler;
|
||||||
|
use crate::models::lidarr_models::{LidarrHistoryItem, LidarrReleaseDownloadBody};
|
||||||
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
||||||
use crate::network::lidarr_network::LidarrEvent;
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::artist;
|
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::{
|
||||||
|
artist, torrent_release,
|
||||||
|
};
|
||||||
|
use crate::{assert_navigation_popped, assert_navigation_pushed};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
|
|
||||||
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
|
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
|
||||||
@@ -138,6 +196,147 @@ mod tests {
|
|||||||
assert_navigation_popped!(app, ActiveLidarrBlock::ArtistDetails.into());
|
assert_navigation_popped!(app, ActiveLidarrBlock::ArtistDetails.into());
|
||||||
assert_none!(app.data.lidarr_data.prompt_confirm_action);
|
assert_none!(app.data.lidarr_data.prompt_confirm_action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artist_history_submit() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.artist_history
|
||||||
|
.set_items(vec![LidarrHistoryItem::default()]);
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(SUBMIT_KEY, &mut app, ActiveLidarrBlock::ArtistHistory, None)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::ArtistHistoryDetails.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artist_history_submit_no_op_when_artist_history_is_empty() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistHistory.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(SUBMIT_KEY, &mut app, ActiveLidarrBlock::ArtistHistory, None)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveLidarrBlock::ArtistHistory.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artist_history_submit_no_op_when_not_ready() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(SUBMIT_KEY, &mut app, ActiveLidarrBlock::ArtistHistory, None)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Artists.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_manual_artist_search_submit() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.discography_releases
|
||||||
|
.set_items(vec![torrent_release()]);
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ManualArtistSearch.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(
|
||||||
|
SUBMIT_KEY,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::ManualArtistSearch,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(
|
||||||
|
app,
|
||||||
|
ActiveLidarrBlock::ManualArtistSearchConfirmPrompt.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_manual_artist_search_submit_no_op_when_not_ready() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ManualArtistSearch.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(
|
||||||
|
SUBMIT_KEY,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::ManualArtistSearch,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveLidarrBlock::ManualArtistSearch.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_manual_artist_search_confirm_prompt_confirm_submit() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
let release = torrent_release();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.discography_releases
|
||||||
|
.set_items(vec![release.clone()]);
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ManualArtistSearch.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ManualArtistSearchConfirmPrompt.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(
|
||||||
|
SUBMIT_KEY,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::ManualArtistSearchConfirmPrompt,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert!(app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::ManualArtistSearch.into());
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.prompt_confirm_action,
|
||||||
|
Some(LidarrEvent::DownloadRelease(LidarrReleaseDownloadBody {
|
||||||
|
guid: release.guid,
|
||||||
|
indexer_id: release.indexer_id,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_manual_artist_search_confirm_prompt_decline_submit() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.discography_releases
|
||||||
|
.set_items(vec![torrent_release()]);
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ManualArtistSearch.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ManualArtistSearchConfirmPrompt.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(
|
||||||
|
SUBMIT_KEY,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::ManualArtistSearchConfirmPrompt,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::ManualArtistSearch.into());
|
||||||
|
assert_none!(app.data.lidarr_data.prompt_confirm_action);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod test_handle_esc {
|
mod test_handle_esc {
|
||||||
@@ -147,16 +346,61 @@ mod tests {
|
|||||||
use crate::event::Key;
|
use crate::event::Key;
|
||||||
use crate::handlers::KeyEventHandler;
|
use crate::handlers::KeyEventHandler;
|
||||||
use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler;
|
use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler;
|
||||||
|
use crate::models::lidarr_models::LidarrHistoryItem;
|
||||||
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
||||||
|
use crate::models::stateful_table::StatefulTable;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use ratatui::widgets::TableState;
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
|
|
||||||
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
|
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artist_history_details_block_esc() {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistHistory.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistHistoryDetails.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(
|
||||||
|
ESC_KEY,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::ArtistHistoryDetails,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::ArtistHistory.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artist_history_esc_resets_filter_if_one_is_set_instead_of_closing_the_window() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.artist_history = StatefulTable {
|
||||||
|
filter: Some("Test".into()),
|
||||||
|
filtered_items: Some(vec![LidarrHistoryItem::default()]),
|
||||||
|
filtered_state: Some(TableState::default()),
|
||||||
|
..StatefulTable::default()
|
||||||
|
};
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistHistory.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(ESC_KEY, &mut app, ActiveLidarrBlock::ArtistHistory, None).handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveLidarrBlock::ArtistHistory.into()
|
||||||
|
);
|
||||||
|
assert_none!(app.data.lidarr_data.artist_history.filter);
|
||||||
|
assert_none!(app.data.lidarr_data.artist_history.filtered_items);
|
||||||
|
assert_none!(app.data.lidarr_data.artist_history.filtered_state);
|
||||||
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn test_artist_details_esc(
|
fn test_artist_details_esc(
|
||||||
#[values(
|
#[values(
|
||||||
ActiveLidarrBlock::AutomaticallySearchArtistPrompt,
|
ActiveLidarrBlock::AutomaticallySearchArtistPrompt,
|
||||||
ActiveLidarrBlock::UpdateAndScanArtistPrompt
|
ActiveLidarrBlock::UpdateAndScanArtistPrompt,
|
||||||
|
ActiveLidarrBlock::ManualArtistSearchConfirmPrompt
|
||||||
)]
|
)]
|
||||||
prompt_block: ActiveLidarrBlock,
|
prompt_block: ActiveLidarrBlock,
|
||||||
#[values(true, false)] is_ready: bool,
|
#[values(true, false)] is_ready: bool,
|
||||||
@@ -172,6 +416,31 @@ mod tests {
|
|||||||
assert!(!app.data.lidarr_data.prompt_confirm);
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
assert_navigation_popped!(app, ActiveLidarrBlock::ArtistDetails.into());
|
assert_navigation_popped!(app, ActiveLidarrBlock::ArtistDetails.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_artist_details_blocks_esc(
|
||||||
|
#[values(
|
||||||
|
ActiveLidarrBlock::ArtistDetails,
|
||||||
|
ActiveLidarrBlock::ArtistHistory,
|
||||||
|
ActiveLidarrBlock::ManualArtistSearch
|
||||||
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.artist_history.filter = None;
|
||||||
|
app.data.lidarr_data.artist_history.filtered_items = None;
|
||||||
|
app.data.lidarr_data.artist_history.filtered_state = None;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(ESC_KEY, &mut app, active_lidarr_block, None).handle();
|
||||||
|
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::Artists.into());
|
||||||
|
assert_is_empty!(app.data.lidarr_data.albums);
|
||||||
|
assert_is_empty!(app.data.lidarr_data.discography_releases);
|
||||||
|
assert_is_empty!(app.data.lidarr_data.artist_history);
|
||||||
|
assert_eq!(app.data.lidarr_data.artist_info_tabs.index, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod test_handle_char_key_event {
|
mod test_handle_char_key_event {
|
||||||
@@ -180,18 +449,24 @@ mod tests {
|
|||||||
use crate::assert_navigation_pushed;
|
use crate::assert_navigation_pushed;
|
||||||
use crate::handlers::KeyEventHandler;
|
use crate::handlers::KeyEventHandler;
|
||||||
use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler;
|
use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler;
|
||||||
use crate::models::lidarr_models::Artist;
|
use crate::models::lidarr_models::{Artist, LidarrReleaseDownloadBody};
|
||||||
use crate::models::servarr_data::lidarr::lidarr_data::{
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
ActiveLidarrBlock, EDIT_ARTIST_SELECTION_BLOCKS,
|
ActiveLidarrBlock, EDIT_ARTIST_SELECTION_BLOCKS,
|
||||||
};
|
};
|
||||||
use crate::network::lidarr_network::LidarrEvent;
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::torrent_release;
|
||||||
use crate::{assert_modal_absent, assert_modal_present, assert_navigation_popped};
|
use crate::{assert_modal_absent, assert_modal_present, assert_navigation_popped};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn test_artist_details_edit_key(
|
fn test_artist_details_edit_key(
|
||||||
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
|
#[values(
|
||||||
|
ActiveLidarrBlock::ArtistDetails,
|
||||||
|
ActiveLidarrBlock::ArtistHistory,
|
||||||
|
ActiveLidarrBlock::ManualArtistSearch
|
||||||
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
) {
|
) {
|
||||||
let mut app = App::test_default_fully_populated();
|
let mut app = App::test_default_fully_populated();
|
||||||
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
@@ -209,12 +484,12 @@ mod tests {
|
|||||||
app,
|
app,
|
||||||
(
|
(
|
||||||
ActiveLidarrBlock::EditArtistPrompt,
|
ActiveLidarrBlock::EditArtistPrompt,
|
||||||
Some(ActiveLidarrBlock::ArtistDetails)
|
Some(active_lidarr_block)
|
||||||
)
|
)
|
||||||
.into()
|
.into()
|
||||||
);
|
);
|
||||||
assert_modal_present!(app.data.lidarr_data.edit_artist_modal);
|
assert_modal_present!(app.data.lidarr_data.edit_artist_modal);
|
||||||
assert!(app.data.lidarr_data.edit_artist_modal.is_some());
|
assert_some!(app.data.lidarr_data.edit_artist_modal);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
app.data.lidarr_data.selected_block.blocks,
|
app.data.lidarr_data.selected_block.blocks,
|
||||||
EDIT_ARTIST_SELECTION_BLOCKS
|
EDIT_ARTIST_SELECTION_BLOCKS
|
||||||
@@ -223,7 +498,12 @@ mod tests {
|
|||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn test_artist_details_edit_key_no_op_when_not_ready(
|
fn test_artist_details_edit_key_no_op_when_not_ready(
|
||||||
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
|
#[values(
|
||||||
|
ActiveLidarrBlock::ArtistDetails,
|
||||||
|
ActiveLidarrBlock::ArtistHistory,
|
||||||
|
ActiveLidarrBlock::ManualArtistSearch
|
||||||
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
) {
|
) {
|
||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
app.is_loading = true;
|
app.is_loading = true;
|
||||||
@@ -315,7 +595,12 @@ mod tests {
|
|||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn test_artist_details_auto_search_key(
|
fn test_artist_details_auto_search_key(
|
||||||
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
|
#[values(
|
||||||
|
ActiveLidarrBlock::ArtistDetails,
|
||||||
|
ActiveLidarrBlock::ArtistHistory,
|
||||||
|
ActiveLidarrBlock::ManualArtistSearch
|
||||||
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
) {
|
) {
|
||||||
let mut app = App::test_default_fully_populated();
|
let mut app = App::test_default_fully_populated();
|
||||||
app.push_navigation_stack(active_lidarr_block.into());
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
@@ -336,7 +621,12 @@ mod tests {
|
|||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn test_artist_details_auto_search_key_no_op_when_not_ready(
|
fn test_artist_details_auto_search_key_no_op_when_not_ready(
|
||||||
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
|
#[values(
|
||||||
|
ActiveLidarrBlock::ArtistDetails,
|
||||||
|
ActiveLidarrBlock::ArtistHistory,
|
||||||
|
ActiveLidarrBlock::ManualArtistSearch
|
||||||
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
) {
|
) {
|
||||||
let mut app = App::test_default_fully_populated();
|
let mut app = App::test_default_fully_populated();
|
||||||
app.is_loading = true;
|
app.is_loading = true;
|
||||||
@@ -355,7 +645,12 @@ mod tests {
|
|||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn test_artist_details_update_key(
|
fn test_artist_details_update_key(
|
||||||
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
|
#[values(
|
||||||
|
ActiveLidarrBlock::ArtistDetails,
|
||||||
|
ActiveLidarrBlock::ArtistHistory,
|
||||||
|
ActiveLidarrBlock::ManualArtistSearch
|
||||||
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
) {
|
) {
|
||||||
let mut app = App::test_default_fully_populated();
|
let mut app = App::test_default_fully_populated();
|
||||||
app.push_navigation_stack(active_lidarr_block.into());
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
@@ -373,7 +668,12 @@ mod tests {
|
|||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn test_artist_details_update_key_no_op_when_not_ready(
|
fn test_artist_details_update_key_no_op_when_not_ready(
|
||||||
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
|
#[values(
|
||||||
|
ActiveLidarrBlock::ArtistDetails,
|
||||||
|
ActiveLidarrBlock::ArtistHistory,
|
||||||
|
ActiveLidarrBlock::ManualArtistSearch
|
||||||
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
) {
|
) {
|
||||||
let mut app = App::test_default_fully_populated();
|
let mut app = App::test_default_fully_populated();
|
||||||
app.is_loading = true;
|
app.is_loading = true;
|
||||||
@@ -392,7 +692,12 @@ mod tests {
|
|||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn test_artist_details_refresh_key(
|
fn test_artist_details_refresh_key(
|
||||||
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
|
#[values(
|
||||||
|
ActiveLidarrBlock::ArtistDetails,
|
||||||
|
ActiveLidarrBlock::ArtistHistory,
|
||||||
|
ActiveLidarrBlock::ManualArtistSearch
|
||||||
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
) {
|
) {
|
||||||
let mut app = App::test_default_fully_populated();
|
let mut app = App::test_default_fully_populated();
|
||||||
app.is_routing = false;
|
app.is_routing = false;
|
||||||
@@ -413,7 +718,12 @@ mod tests {
|
|||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn test_artist_details_refresh_key_no_op_when_not_ready(
|
fn test_artist_details_refresh_key_no_op_when_not_ready(
|
||||||
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
|
#[values(
|
||||||
|
ActiveLidarrBlock::ArtistDetails,
|
||||||
|
ActiveLidarrBlock::ArtistHistory,
|
||||||
|
ActiveLidarrBlock::ManualArtistSearch
|
||||||
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
) {
|
) {
|
||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
app.is_loading = true;
|
app.is_loading = true;
|
||||||
@@ -444,7 +754,8 @@ mod tests {
|
|||||||
fn test_artist_details_prompt_confirm_key(
|
fn test_artist_details_prompt_confirm_key(
|
||||||
#[case] prompt_block: ActiveLidarrBlock,
|
#[case] prompt_block: ActiveLidarrBlock,
|
||||||
#[case] expected_action: LidarrEvent,
|
#[case] expected_action: LidarrEvent,
|
||||||
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
|
#[values(ActiveLidarrBlock::ArtistDetails, ActiveLidarrBlock::ArtistHistory)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
) {
|
) {
|
||||||
let mut app = App::test_default_fully_populated();
|
let mut app = App::test_default_fully_populated();
|
||||||
app.push_navigation_stack(active_lidarr_block.into());
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
@@ -465,15 +776,43 @@ mod tests {
|
|||||||
&expected_action
|
&expected_action
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_manual_artist_search_confirm_prompt_confirm_key() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
let release = torrent_release();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.discography_releases
|
||||||
|
.set_items(vec![release.clone()]);
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ManualArtistSearch.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ManualArtistSearchConfirmPrompt.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.confirm.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::ManualArtistSearchConfirmPrompt,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert!(app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::ManualArtistSearch.into());
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.prompt_confirm_action,
|
||||||
|
Some(LidarrEvent::DownloadRelease(LidarrReleaseDownloadBody {
|
||||||
|
guid: release.guid,
|
||||||
|
indexer_id: release.indexer_id,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_artist_details_handler_accepts() {
|
fn test_artist_details_handler_accepts() {
|
||||||
let mut artist_details_blocks = ARTIST_DETAILS_BLOCKS.clone().to_vec();
|
|
||||||
artist_details_blocks.extend(DELETE_ALBUM_BLOCKS);
|
|
||||||
|
|
||||||
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
|
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
|
||||||
if artist_details_blocks.contains(&active_lidarr_block) {
|
if ARTIST_DETAILS_BLOCKS.contains(&active_lidarr_block) {
|
||||||
assert!(ArtistDetailsHandler::accepts(active_lidarr_block));
|
assert!(ArtistDetailsHandler::accepts(active_lidarr_block));
|
||||||
} else {
|
} else {
|
||||||
assert!(!ArtistDetailsHandler::accepts(active_lidarr_block));
|
assert!(!ArtistDetailsHandler::accepts(active_lidarr_block));
|
||||||
@@ -562,4 +901,262 @@ mod tests {
|
|||||||
|
|
||||||
assert!(handler.is_ready());
|
assert!(handler.is_ready());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artist_details_handler_is_not_ready_when_not_loading_and_artist_history_is_none() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
||||||
|
|
||||||
|
let handler = ArtistDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::ArtistHistory,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artist_details_handler_ready_when_not_loading_and_artist_history_is_non_empty() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.artist_history
|
||||||
|
.set_items(vec![LidarrHistoryItem::default()]);
|
||||||
|
|
||||||
|
let handler = ArtistDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::ArtistHistory,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artist_details_handler_is_not_ready_when_not_loading_and_discography_releases_is_empty() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
||||||
|
|
||||||
|
let handler = ArtistDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::ManualArtistSearch,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artist_details_handler_ready_when_not_loading_and_discography_releases_is_non_empty() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.discography_releases
|
||||||
|
.set_items(vec![LidarrRelease::default()]);
|
||||||
|
|
||||||
|
let handler = ArtistDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::ManualArtistSearch,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_releases_sorting_options_source() {
|
||||||
|
let expected_cmp_fn: fn(&LidarrRelease, &LidarrRelease) -> Ordering =
|
||||||
|
|a, b| a.protocol.cmp(&b.protocol);
|
||||||
|
let mut expected_releases_vec = release_vec();
|
||||||
|
expected_releases_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = releases_sorting_options()[0].clone();
|
||||||
|
let mut sorted_releases_vec = release_vec();
|
||||||
|
sorted_releases_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_releases_vec, expected_releases_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Source");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_releases_sorting_options_age() {
|
||||||
|
let expected_cmp_fn: fn(&LidarrRelease, &LidarrRelease) -> Ordering = |a, b| a.age.cmp(&b.age);
|
||||||
|
let mut expected_releases_vec = release_vec();
|
||||||
|
expected_releases_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = releases_sorting_options()[1].clone();
|
||||||
|
let mut sorted_releases_vec = release_vec();
|
||||||
|
sorted_releases_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_releases_vec, expected_releases_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Age");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_releases_sorting_options_rejected() {
|
||||||
|
let expected_cmp_fn: fn(&LidarrRelease, &LidarrRelease) -> Ordering =
|
||||||
|
|a, b| a.rejected.cmp(&b.rejected);
|
||||||
|
let mut expected_releases_vec = release_vec();
|
||||||
|
expected_releases_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = releases_sorting_options()[2].clone();
|
||||||
|
let mut sorted_releases_vec = release_vec();
|
||||||
|
sorted_releases_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_releases_vec, expected_releases_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Rejected");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_releases_sorting_options_title() {
|
||||||
|
let expected_cmp_fn: fn(&LidarrRelease, &LidarrRelease) -> Ordering = |a, b| {
|
||||||
|
a.title
|
||||||
|
.text
|
||||||
|
.to_lowercase()
|
||||||
|
.cmp(&b.title.text.to_lowercase())
|
||||||
|
};
|
||||||
|
let mut expected_releases_vec = release_vec();
|
||||||
|
expected_releases_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = releases_sorting_options()[3].clone();
|
||||||
|
let mut sorted_releases_vec = release_vec();
|
||||||
|
sorted_releases_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_releases_vec, expected_releases_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Title");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_releases_sorting_options_indexer() {
|
||||||
|
let expected_cmp_fn: fn(&LidarrRelease, &LidarrRelease) -> Ordering =
|
||||||
|
|a, b| a.indexer.to_lowercase().cmp(&b.indexer.to_lowercase());
|
||||||
|
let mut expected_releases_vec = release_vec();
|
||||||
|
expected_releases_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = releases_sorting_options()[4].clone();
|
||||||
|
let mut sorted_releases_vec = release_vec();
|
||||||
|
sorted_releases_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_releases_vec, expected_releases_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Indexer");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_releases_sorting_options_size() {
|
||||||
|
let expected_cmp_fn: fn(&LidarrRelease, &LidarrRelease) -> Ordering =
|
||||||
|
|a, b| a.size.cmp(&b.size);
|
||||||
|
let mut expected_releases_vec = release_vec();
|
||||||
|
expected_releases_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = releases_sorting_options()[5].clone();
|
||||||
|
let mut sorted_releases_vec = release_vec();
|
||||||
|
sorted_releases_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_releases_vec, expected_releases_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Size");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_releases_sorting_options_peers() {
|
||||||
|
let expected_cmp_fn: fn(&LidarrRelease, &LidarrRelease) -> Ordering = |a, b| {
|
||||||
|
let default_number = Number::from(i64::MAX);
|
||||||
|
let seeder_a = a
|
||||||
|
.seeders
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&default_number)
|
||||||
|
.as_u64()
|
||||||
|
.unwrap();
|
||||||
|
let seeder_b = b
|
||||||
|
.seeders
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&default_number)
|
||||||
|
.as_u64()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
seeder_a.cmp(&seeder_b)
|
||||||
|
};
|
||||||
|
let mut expected_releases_vec = release_vec();
|
||||||
|
expected_releases_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = releases_sorting_options()[6].clone();
|
||||||
|
let mut sorted_releases_vec = release_vec();
|
||||||
|
sorted_releases_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_releases_vec, expected_releases_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Peers");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_releases_sorting_options_quality() {
|
||||||
|
let expected_cmp_fn: fn(&LidarrRelease, &LidarrRelease) -> Ordering =
|
||||||
|
|a, b| a.quality.cmp(&b.quality);
|
||||||
|
let mut expected_releases_vec = release_vec();
|
||||||
|
expected_releases_vec.sort_by(expected_cmp_fn);
|
||||||
|
|
||||||
|
let sort_option = releases_sorting_options()[7].clone();
|
||||||
|
let mut sorted_releases_vec = release_vec();
|
||||||
|
sorted_releases_vec.sort_by(sort_option.cmp_fn.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(sorted_releases_vec, expected_releases_vec);
|
||||||
|
assert_str_eq!(sort_option.name, "Quality");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn release_vec() -> Vec<LidarrRelease> {
|
||||||
|
let release_a = LidarrRelease {
|
||||||
|
protocol: "Protocol A".to_owned(),
|
||||||
|
age: 1,
|
||||||
|
title: HorizontallyScrollableText::from("Title A"),
|
||||||
|
indexer: "Indexer A".to_owned(),
|
||||||
|
size: 1,
|
||||||
|
rejected: true,
|
||||||
|
seeders: Some(Number::from(1)),
|
||||||
|
quality: QualityWrapper {
|
||||||
|
quality: Quality {
|
||||||
|
name: "Quality A".to_owned(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
..LidarrRelease::default()
|
||||||
|
};
|
||||||
|
let release_b = LidarrRelease {
|
||||||
|
protocol: "Protocol B".to_owned(),
|
||||||
|
age: 2,
|
||||||
|
title: HorizontallyScrollableText::from("title B"),
|
||||||
|
indexer: "indexer B".to_owned(),
|
||||||
|
size: 2,
|
||||||
|
rejected: false,
|
||||||
|
seeders: Some(Number::from(2)),
|
||||||
|
quality: QualityWrapper {
|
||||||
|
quality: Quality {
|
||||||
|
name: "Quality B".to_owned(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
..LidarrRelease::default()
|
||||||
|
};
|
||||||
|
let release_c = LidarrRelease {
|
||||||
|
protocol: "Protocol C".to_owned(),
|
||||||
|
age: 3,
|
||||||
|
title: HorizontallyScrollableText::from("Title C"),
|
||||||
|
indexer: "Indexer C".to_owned(),
|
||||||
|
size: 3,
|
||||||
|
rejected: false,
|
||||||
|
seeders: None,
|
||||||
|
quality: QualityWrapper {
|
||||||
|
quality: Quality {
|
||||||
|
name: "Quality C".to_owned(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
..LidarrRelease::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
vec![release_a, release_b, release_c]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,13 +13,15 @@ mod tests {
|
|||||||
use crate::handlers::lidarr_handlers::library::{LibraryHandler, artists_sorting_options};
|
use crate::handlers::lidarr_handlers::library::{LibraryHandler, artists_sorting_options};
|
||||||
use crate::models::lidarr_models::{Album, Artist, ArtistStatistics, ArtistStatus};
|
use crate::models::lidarr_models::{Album, Artist, ArtistStatistics, ArtistStatus};
|
||||||
use crate::models::servarr_data::lidarr::lidarr_data::{
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
ADD_ARTIST_BLOCKS, ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, DELETE_ALBUM_BLOCKS,
|
ADD_ARTIST_BLOCKS, ALBUM_DETAILS_BLOCKS, ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock,
|
||||||
DELETE_ARTIST_BLOCKS, EDIT_ARTIST_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS, LIBRARY_BLOCKS,
|
DELETE_ALBUM_BLOCKS, DELETE_ARTIST_BLOCKS, EDIT_ARTIST_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS,
|
||||||
|
LIBRARY_BLOCKS, TRACK_DETAILS_BLOCKS,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_data::lidarr::modals::EditArtistModal;
|
use crate::models::servarr_data::lidarr::modals::EditArtistModal;
|
||||||
use crate::network::lidarr_network::LidarrEvent;
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
use crate::{
|
use crate::{
|
||||||
assert_modal_absent, assert_modal_present, assert_navigation_popped, assert_navigation_pushed,
|
assert_modal_absent, assert_modal_present, assert_navigation_popped, assert_navigation_pushed,
|
||||||
|
test_handler_delegation,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -31,10 +33,15 @@ mod tests {
|
|||||||
library_handler_blocks.extend(DELETE_ALBUM_BLOCKS);
|
library_handler_blocks.extend(DELETE_ALBUM_BLOCKS);
|
||||||
library_handler_blocks.extend(EDIT_ARTIST_BLOCKS);
|
library_handler_blocks.extend(EDIT_ARTIST_BLOCKS);
|
||||||
library_handler_blocks.extend(ADD_ARTIST_BLOCKS);
|
library_handler_blocks.extend(ADD_ARTIST_BLOCKS);
|
||||||
|
library_handler_blocks.extend(ALBUM_DETAILS_BLOCKS);
|
||||||
|
library_handler_blocks.extend(TRACK_DETAILS_BLOCKS);
|
||||||
|
|
||||||
ActiveLidarrBlock::iter().for_each(|lidarr_block| {
|
ActiveLidarrBlock::iter().for_each(|lidarr_block| {
|
||||||
if library_handler_blocks.contains(&lidarr_block) {
|
if library_handler_blocks.contains(&lidarr_block) {
|
||||||
assert!(LibraryHandler::accepts(lidarr_block));
|
assert!(
|
||||||
|
LibraryHandler::accepts(lidarr_block),
|
||||||
|
"{lidarr_block} is not accepted by the LibraryHandler"
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
assert!(!LibraryHandler::accepts(lidarr_block));
|
assert!(!LibraryHandler::accepts(lidarr_block));
|
||||||
}
|
}
|
||||||
@@ -640,6 +647,54 @@ mod tests {
|
|||||||
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Artists.into());
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Artists.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_delegates_album_details_blocks_to_album_details_handler(
|
||||||
|
#[values(
|
||||||
|
ActiveLidarrBlock::AlbumDetails,
|
||||||
|
ActiveLidarrBlock::AlbumHistory,
|
||||||
|
ActiveLidarrBlock::SearchTracks,
|
||||||
|
ActiveLidarrBlock::SearchTracksError,
|
||||||
|
ActiveLidarrBlock::AutomaticallySearchAlbumPrompt,
|
||||||
|
ActiveLidarrBlock::SearchAlbumHistory,
|
||||||
|
ActiveLidarrBlock::SearchAlbumHistoryError,
|
||||||
|
ActiveLidarrBlock::FilterAlbumHistory,
|
||||||
|
ActiveLidarrBlock::FilterAlbumHistoryError,
|
||||||
|
ActiveLidarrBlock::AlbumHistorySortPrompt,
|
||||||
|
ActiveLidarrBlock::AlbumHistoryDetails,
|
||||||
|
ActiveLidarrBlock::ManualAlbumSearch,
|
||||||
|
ActiveLidarrBlock::ManualAlbumSearchSortPrompt,
|
||||||
|
ActiveLidarrBlock::DeleteTrackFilePrompt
|
||||||
|
)]
|
||||||
|
active_sonarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
test_handler_delegation!(
|
||||||
|
LibraryHandler,
|
||||||
|
ActiveLidarrBlock::Artists,
|
||||||
|
active_sonarr_block
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_delegates_track_details_blocks_to_track_details_handler(
|
||||||
|
#[values(
|
||||||
|
ActiveLidarrBlock::TrackDetails,
|
||||||
|
ActiveLidarrBlock::TrackHistory,
|
||||||
|
ActiveLidarrBlock::TrackHistoryDetails,
|
||||||
|
ActiveLidarrBlock::SearchTrackHistory,
|
||||||
|
ActiveLidarrBlock::SearchTrackHistoryError,
|
||||||
|
ActiveLidarrBlock::FilterTrackHistory,
|
||||||
|
ActiveLidarrBlock::FilterTrackHistoryError,
|
||||||
|
ActiveLidarrBlock::TrackHistorySortPrompt
|
||||||
|
)]
|
||||||
|
active_sonarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
test_handler_delegation!(
|
||||||
|
LibraryHandler,
|
||||||
|
ActiveLidarrBlock::AlbumDetails,
|
||||||
|
active_sonarr_block
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edit_key() {
|
fn test_edit_key() {
|
||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
|
|||||||
@@ -19,11 +19,16 @@ use super::handle_change_tab_left_right_keys;
|
|||||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||||
|
|
||||||
mod add_artist_handler;
|
mod add_artist_handler;
|
||||||
|
mod album_details_handler;
|
||||||
mod artist_details_handler;
|
mod artist_details_handler;
|
||||||
mod delete_album_handler;
|
mod delete_album_handler;
|
||||||
mod delete_artist_handler;
|
mod delete_artist_handler;
|
||||||
mod edit_artist_handler;
|
mod edit_artist_handler;
|
||||||
|
mod track_details_handler;
|
||||||
|
|
||||||
|
use crate::handlers::lidarr_handlers::library::album_details_handler::AlbumDetailsHandler;
|
||||||
|
use crate::handlers::lidarr_handlers::library::delete_album_handler::DeleteAlbumHandler;
|
||||||
|
use crate::handlers::lidarr_handlers::library::track_details_handler::TrackDetailsHandler;
|
||||||
use crate::models::Route;
|
use crate::models::Route;
|
||||||
pub(in crate::handlers::lidarr_handlers) use add_artist_handler::AddArtistHandler;
|
pub(in crate::handlers::lidarr_handlers) use add_artist_handler::AddArtistHandler;
|
||||||
pub(in crate::handlers::lidarr_handlers) use artist_details_handler::ArtistDetailsHandler;
|
pub(in crate::handlers::lidarr_handlers) use artist_details_handler::ArtistDetailsHandler;
|
||||||
@@ -81,6 +86,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for LibraryHandler<'a, '
|
|||||||
ArtistDetailsHandler::new(self.key, self.app, self.active_lidarr_block, self.context)
|
ArtistDetailsHandler::new(self.key, self.app, self.active_lidarr_block, self.context)
|
||||||
.handle();
|
.handle();
|
||||||
}
|
}
|
||||||
|
_ if DeleteAlbumHandler::accepts(self.active_lidarr_block) => {
|
||||||
|
DeleteAlbumHandler::new(self.key, self.app, self.active_lidarr_block, self.context)
|
||||||
|
.handle();
|
||||||
|
}
|
||||||
|
_ if AlbumDetailsHandler::accepts(self.active_lidarr_block) => {
|
||||||
|
AlbumDetailsHandler::new(self.key, self.app, self.active_lidarr_block, self.context)
|
||||||
|
.handle();
|
||||||
|
}
|
||||||
|
_ if TrackDetailsHandler::accepts(self.active_lidarr_block) => {
|
||||||
|
TrackDetailsHandler::new(self.key, self.app, self.active_lidarr_block, self.context)
|
||||||
|
.handle();
|
||||||
|
}
|
||||||
_ => self.handle_key_event(),
|
_ => self.handle_key_event(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,8 +106,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for LibraryHandler<'a, '
|
|||||||
fn accepts(active_block: ActiveLidarrBlock) -> bool {
|
fn accepts(active_block: ActiveLidarrBlock) -> bool {
|
||||||
AddArtistHandler::accepts(active_block)
|
AddArtistHandler::accepts(active_block)
|
||||||
|| DeleteArtistHandler::accepts(active_block)
|
|| DeleteArtistHandler::accepts(active_block)
|
||||||
|
|| DeleteAlbumHandler::accepts(active_block)
|
||||||
|| EditArtistHandler::accepts(active_block)
|
|| EditArtistHandler::accepts(active_block)
|
||||||
|| ArtistDetailsHandler::accepts(active_block)
|
|| ArtistDetailsHandler::accepts(active_block)
|
||||||
|
|| AlbumDetailsHandler::accepts(active_block)
|
||||||
|
|| TrackDetailsHandler::accepts(active_block)
|
||||||
|| LIBRARY_BLOCKS.contains(&active_block)
|
|| LIBRARY_BLOCKS.contains(&active_block)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,225 @@
|
|||||||
|
use crate::app::App;
|
||||||
|
use crate::event::Key;
|
||||||
|
use crate::handlers::KeyEventHandler;
|
||||||
|
use crate::handlers::lidarr_handlers::history::history_sorting_options;
|
||||||
|
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||||
|
use crate::matches_key;
|
||||||
|
use crate::models::Route;
|
||||||
|
use crate::models::lidarr_models::LidarrHistoryItem;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, TRACK_DETAILS_BLOCKS};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "track_details_handler_tests.rs"]
|
||||||
|
mod track_details_handler_tests;
|
||||||
|
|
||||||
|
pub(super) struct TrackDetailsHandler<'a, 'b> {
|
||||||
|
key: Key,
|
||||||
|
app: &'a mut App<'b>,
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
_context: Option<ActiveLidarrBlock>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for TrackDetailsHandler<'a, 'b> {
|
||||||
|
fn handle(&mut self) {
|
||||||
|
let track_history_table_handling_config =
|
||||||
|
TableHandlingConfig::new(ActiveLidarrBlock::TrackHistory.into())
|
||||||
|
.sorting_block(ActiveLidarrBlock::TrackHistorySortPrompt.into())
|
||||||
|
.sort_options(history_sorting_options())
|
||||||
|
.searching_block(ActiveLidarrBlock::SearchTrackHistory.into())
|
||||||
|
.search_error_block(ActiveLidarrBlock::SearchTrackHistoryError.into())
|
||||||
|
.search_field_fn(|history_item: &LidarrHistoryItem| &history_item.source_title.text)
|
||||||
|
.filtering_block(ActiveLidarrBlock::FilterTrackHistory.into())
|
||||||
|
.filter_error_block(ActiveLidarrBlock::FilterTrackHistoryError.into())
|
||||||
|
.filter_field_fn(|history_item: &LidarrHistoryItem| &history_item.source_title.text);
|
||||||
|
|
||||||
|
if !handle_table(
|
||||||
|
self,
|
||||||
|
|app| {
|
||||||
|
&mut app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.expect("Album details modal is undefined")
|
||||||
|
.track_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.expect("Track details modal is undefined")
|
||||||
|
.track_history
|
||||||
|
},
|
||||||
|
track_history_table_handling_config,
|
||||||
|
) {
|
||||||
|
self.handle_key_event();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accepts(active_block: ActiveLidarrBlock) -> bool {
|
||||||
|
TRACK_DETAILS_BLOCKS.contains(&active_block)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(
|
||||||
|
key: Key,
|
||||||
|
app: &'a mut App<'b>,
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
_context: Option<ActiveLidarrBlock>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
key,
|
||||||
|
app,
|
||||||
|
active_lidarr_block,
|
||||||
|
_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 {
|
||||||
|
if self.app.is_loading {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(album_details_modal) = self.app.data.lidarr_data.album_details_modal.as_ref() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(track_details_modal) = &album_details_modal.track_details_modal else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::TrackDetails => !track_details_modal.track_details.is_empty(),
|
||||||
|
ActiveLidarrBlock::TrackHistory => !track_details_modal.track_history.is_empty(),
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::TrackDetails | ActiveLidarrBlock::TrackHistory => match self.key {
|
||||||
|
_ if matches_key!(left, self.key) => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.track_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.track_details_tabs
|
||||||
|
.previous();
|
||||||
|
self.app.pop_and_push_navigation_stack(
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.track_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.track_details_tabs
|
||||||
|
.get_active_route(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ if matches_key!(right, self.key) => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.track_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.track_details_tabs
|
||||||
|
.next();
|
||||||
|
self.app.pop_and_push_navigation_stack(
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.track_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.track_details_tabs
|
||||||
|
.get_active_route(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_submit(&mut self) {
|
||||||
|
if self.active_lidarr_block == ActiveLidarrBlock::TrackHistory {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::TrackHistoryDetails.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_esc(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::TrackDetails | ActiveLidarrBlock::TrackHistory => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.track_details_modal = None;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::TrackHistoryDetails => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_char_key_event(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::TrackDetails | ActiveLidarrBlock::TrackHistory => match self.key {
|
||||||
|
_ if matches_key!(refresh, self.key) => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.pop_and_push_navigation_stack(self.active_lidarr_block.into());
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app_mut(&mut self) -> &mut App<'b> {
|
||||||
|
self.app
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_route(&self) -> Route {
|
||||||
|
self.app.get_current_route()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,407 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
|
use crate::assert_navigation_pushed;
|
||||||
|
use crate::handlers::KeyEventHandler;
|
||||||
|
use crate::handlers::lidarr_handlers::library::track_details_handler::TrackDetailsHandler;
|
||||||
|
use crate::models::ScrollableText;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, TRACK_DETAILS_BLOCKS};
|
||||||
|
use crate::models::stateful_table::StatefulTable;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
|
mod test_handle_left_right_actions {
|
||||||
|
use super::*;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(ActiveLidarrBlock::TrackDetails, ActiveLidarrBlock::TrackHistory)]
|
||||||
|
#[case(ActiveLidarrBlock::TrackHistory, ActiveLidarrBlock::TrackDetails)]
|
||||||
|
fn test_track_details_tabs_left_right_action(
|
||||||
|
#[case] left_block: ActiveLidarrBlock,
|
||||||
|
#[case] right_block: ActiveLidarrBlock,
|
||||||
|
#[values(true, false)] is_ready: bool,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::AlbumDetails.into());
|
||||||
|
app.is_loading = is_ready;
|
||||||
|
app.push_navigation_stack(right_block.into());
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.track_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.track_details_tabs
|
||||||
|
.index = app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.track_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.track_details_tabs
|
||||||
|
.tabs
|
||||||
|
.iter()
|
||||||
|
.position(|tab_route| tab_route.route == right_block.into())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
TrackDetailsHandler::new(DEFAULT_KEYBINDINGS.left.key, &mut app, right_block, None).handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.track_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.track_details_tabs
|
||||||
|
.get_active_route()
|
||||||
|
);
|
||||||
|
assert_navigation_pushed!(app, left_block.into());
|
||||||
|
|
||||||
|
TrackDetailsHandler::new(DEFAULT_KEYBINDINGS.right.key, &mut app, left_block, None).handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.track_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.track_details_tabs
|
||||||
|
.get_active_route()
|
||||||
|
);
|
||||||
|
assert_navigation_pushed!(app, right_block.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_submit {
|
||||||
|
use super::*;
|
||||||
|
use crate::event::Key;
|
||||||
|
use crate::models::stateful_table::StatefulTable;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_track_history_submit() {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
|
||||||
|
TrackDetailsHandler::new(SUBMIT_KEY, &mut app, ActiveLidarrBlock::TrackHistory, None)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::TrackHistoryDetails.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_track_history_submit_no_op_when_track_history_is_empty() {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.track_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.track_history = StatefulTable::default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::TrackHistory.into());
|
||||||
|
|
||||||
|
TrackDetailsHandler::new(SUBMIT_KEY, &mut app, ActiveLidarrBlock::TrackHistory, None)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveLidarrBlock::TrackHistory.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_track_history_submit_no_op_when_not_ready() {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::TrackHistory.into());
|
||||||
|
|
||||||
|
TrackDetailsHandler::new(SUBMIT_KEY, &mut app, ActiveLidarrBlock::TrackHistory, None)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveLidarrBlock::TrackHistory.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_esc {
|
||||||
|
use super::*;
|
||||||
|
use crate::assert_navigation_popped;
|
||||||
|
use crate::event::Key;
|
||||||
|
|
||||||
|
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_track_history_details_block_esc() {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::TrackHistory.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::TrackHistoryDetails.into());
|
||||||
|
|
||||||
|
TrackDetailsHandler::new(
|
||||||
|
ESC_KEY,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::TrackHistoryDetails,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::TrackHistory.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_track_details_tabs_esc(
|
||||||
|
#[values(ActiveLidarrBlock::TrackDetails, ActiveLidarrBlock::TrackHistory)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::AlbumDetails.into());
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
|
||||||
|
TrackDetailsHandler::new(ESC_KEY, &mut app, active_lidarr_block, None).handle();
|
||||||
|
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::AlbumDetails.into());
|
||||||
|
assert_none!(
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.track_details_modal
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_key_char {
|
||||||
|
use super::*;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_refresh_key(
|
||||||
|
#[values(ActiveLidarrBlock::TrackDetails, ActiveLidarrBlock::TrackHistory)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
app.is_routing = false;
|
||||||
|
|
||||||
|
TrackDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.key,
|
||||||
|
&mut app,
|
||||||
|
active_lidarr_block,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, active_lidarr_block.into());
|
||||||
|
assert!(app.is_routing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_refresh_key_no_op_when_not_ready(
|
||||||
|
#[values(ActiveLidarrBlock::TrackDetails, ActiveLidarrBlock::TrackHistory)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
app.is_routing = false;
|
||||||
|
|
||||||
|
TrackDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.key,
|
||||||
|
&mut app,
|
||||||
|
active_lidarr_block,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), active_lidarr_block.into());
|
||||||
|
assert!(!app.is_routing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_track_details_handler_accepts() {
|
||||||
|
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
|
||||||
|
if TRACK_DETAILS_BLOCKS.contains(&active_lidarr_block) {
|
||||||
|
assert!(TrackDetailsHandler::accepts(active_lidarr_block));
|
||||||
|
} else {
|
||||||
|
assert!(!TrackDetailsHandler::accepts(active_lidarr_block));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_track_details_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 = TrackDetailsHandler::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_track_details_handler_is_not_ready_when_loading() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::TrackDetails.into());
|
||||||
|
app.is_loading = true;
|
||||||
|
|
||||||
|
let handler = TrackDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::TrackDetails,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_track_details_handler_is_not_ready_when_album_details_modal_is_empty() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::TrackDetails.into());
|
||||||
|
app.is_loading = false;
|
||||||
|
|
||||||
|
let handler = TrackDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::TrackDetails,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_track_details_handler_is_not_ready_when_track_details_modal_is_empty() {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.track_details_modal = None;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::TrackDetails.into());
|
||||||
|
app.is_loading = false;
|
||||||
|
|
||||||
|
let handler = TrackDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::TrackDetails,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_track_details_handler_is_not_ready_when_track_details_is_empty() {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.track_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.track_details = ScrollableText::default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::TrackDetails.into());
|
||||||
|
app.is_loading = false;
|
||||||
|
|
||||||
|
let handler = TrackDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::TrackDetails,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_track_details_handler_is_not_ready_when_track_history_table_is_empty() {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.album_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.track_details_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.track_history = StatefulTable::default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::TrackHistory.into());
|
||||||
|
app.is_loading = false;
|
||||||
|
|
||||||
|
let handler = TrackDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::TrackHistory,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_track_details_handler_is_ready(
|
||||||
|
#[values(
|
||||||
|
ActiveLidarrBlock::TrackDetails,
|
||||||
|
ActiveLidarrBlock::TrackHistory,
|
||||||
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
app.is_loading = false;
|
||||||
|
|
||||||
|
let handler = TrackDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
active_lidarr_block,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(handler.is_ready());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,13 +2,10 @@
|
|||||||
mod tests {
|
mod tests {
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
use crate::assert_navigation_pushed;
|
|
||||||
use crate::handlers::KeyEventHandler;
|
use crate::handlers::KeyEventHandler;
|
||||||
use crate::handlers::lidarr_handlers::{LidarrHandler, handle_change_tab_left_right_keys};
|
use crate::handlers::lidarr_handlers::{LidarrHandler, handle_change_tab_left_right_keys};
|
||||||
use crate::models::lidarr_models::Artist;
|
|
||||||
use crate::models::lidarr_models::LidarrHistoryItem;
|
|
||||||
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
||||||
use crate::models::servarr_data::lidarr::modals::EditArtistModal;
|
use crate::{assert_navigation_pushed, test_handler_delegation};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
@@ -55,8 +52,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(0, ActiveLidarrBlock::History, ActiveLidarrBlock::History)]
|
#[case(0, ActiveLidarrBlock::System, ActiveLidarrBlock::Downloads)]
|
||||||
#[case(1, ActiveLidarrBlock::Artists, ActiveLidarrBlock::Artists)]
|
#[case(1, ActiveLidarrBlock::Artists, ActiveLidarrBlock::Blocklist)]
|
||||||
|
#[case(2, ActiveLidarrBlock::Downloads, ActiveLidarrBlock::History)]
|
||||||
|
#[case(3, ActiveLidarrBlock::Blocklist, ActiveLidarrBlock::RootFolders)]
|
||||||
|
#[case(4, ActiveLidarrBlock::History, ActiveLidarrBlock::Indexers)]
|
||||||
|
#[case(5, ActiveLidarrBlock::RootFolders, ActiveLidarrBlock::System)]
|
||||||
|
#[case(6, ActiveLidarrBlock::Indexers, ActiveLidarrBlock::Artists)]
|
||||||
fn test_lidarr_handler_change_tab_left_right_keys(
|
fn test_lidarr_handler_change_tab_left_right_keys(
|
||||||
#[case] index: usize,
|
#[case] index: usize,
|
||||||
#[case] left_block: ActiveLidarrBlock,
|
#[case] left_block: ActiveLidarrBlock,
|
||||||
@@ -85,8 +87,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(0, ActiveLidarrBlock::History, ActiveLidarrBlock::History)]
|
#[case(0, ActiveLidarrBlock::System, ActiveLidarrBlock::Downloads)]
|
||||||
#[case(1, ActiveLidarrBlock::Artists, ActiveLidarrBlock::Artists)]
|
#[case(1, ActiveLidarrBlock::Artists, ActiveLidarrBlock::Blocklist)]
|
||||||
|
#[case(2, ActiveLidarrBlock::Downloads, ActiveLidarrBlock::History)]
|
||||||
|
#[case(3, ActiveLidarrBlock::Blocklist, ActiveLidarrBlock::RootFolders)]
|
||||||
|
#[case(4, ActiveLidarrBlock::History, ActiveLidarrBlock::Indexers)]
|
||||||
|
#[case(5, ActiveLidarrBlock::RootFolders, ActiveLidarrBlock::System)]
|
||||||
|
#[case(6, ActiveLidarrBlock::Indexers, ActiveLidarrBlock::Artists)]
|
||||||
fn test_lidarr_handler_change_tab_left_right_keys_alt_navigation(
|
fn test_lidarr_handler_change_tab_left_right_keys_alt_navigation(
|
||||||
#[case] index: usize,
|
#[case] index: usize,
|
||||||
#[case] left_block: ActiveLidarrBlock,
|
#[case] left_block: ActiveLidarrBlock,
|
||||||
@@ -116,7 +123,12 @@ mod tests {
|
|||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(0, ActiveLidarrBlock::Artists)]
|
#[case(0, ActiveLidarrBlock::Artists)]
|
||||||
#[case(1, ActiveLidarrBlock::History)]
|
#[case(1, ActiveLidarrBlock::Downloads)]
|
||||||
|
#[case(2, ActiveLidarrBlock::Blocklist)]
|
||||||
|
#[case(3, ActiveLidarrBlock::History)]
|
||||||
|
#[case(4, ActiveLidarrBlock::RootFolders)]
|
||||||
|
#[case(5, ActiveLidarrBlock::Indexers)]
|
||||||
|
#[case(6, ActiveLidarrBlock::System)]
|
||||||
fn test_lidarr_handler_change_tab_left_right_keys_alt_navigation_no_op_when_ignoring_quit_key(
|
fn test_lidarr_handler_change_tab_left_right_keys_alt_navigation_no_op_when_ignoring_quit_key(
|
||||||
#[case] index: usize,
|
#[case] index: usize,
|
||||||
#[case] block: ActiveLidarrBlock,
|
#[case] block: ActiveLidarrBlock,
|
||||||
@@ -165,25 +177,45 @@ mod tests {
|
|||||||
)]
|
)]
|
||||||
active_lidarr_block: ActiveLidarrBlock,
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
) {
|
) {
|
||||||
let mut app = App::test_default();
|
test_handler_delegation!(
|
||||||
app
|
LidarrHandler,
|
||||||
.data
|
ActiveLidarrBlock::Artists,
|
||||||
.lidarr_data
|
active_lidarr_block
|
||||||
.artists
|
);
|
||||||
.set_items(vec![Artist::default()]);
|
}
|
||||||
app.data.lidarr_data.edit_artist_modal = Some(EditArtistModal::default());
|
|
||||||
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
|
||||||
app.push_navigation_stack(active_lidarr_block.into());
|
|
||||||
|
|
||||||
LidarrHandler::new(
|
#[rstest]
|
||||||
DEFAULT_KEYBINDINGS.esc.key,
|
fn test_delegates_downloads_blocks_to_downloads_handler(
|
||||||
&mut app,
|
#[values(
|
||||||
active_lidarr_block,
|
ActiveLidarrBlock::Downloads,
|
||||||
None,
|
ActiveLidarrBlock::DeleteDownloadPrompt,
|
||||||
)
|
ActiveLidarrBlock::UpdateDownloadsPrompt
|
||||||
.handle();
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
test_handler_delegation!(
|
||||||
|
LidarrHandler,
|
||||||
|
ActiveLidarrBlock::Downloads,
|
||||||
|
active_lidarr_block
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Artists.into());
|
#[rstest]
|
||||||
|
fn test_delegates_blocklist_blocks_to_blocklist_handler(
|
||||||
|
#[values(
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
ActiveLidarrBlock::BlocklistItemDetails,
|
||||||
|
ActiveLidarrBlock::DeleteBlocklistItemPrompt,
|
||||||
|
ActiveLidarrBlock::BlocklistClearAllItemsPrompt,
|
||||||
|
ActiveLidarrBlock::BlocklistSortPrompt
|
||||||
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
test_handler_delegation!(
|
||||||
|
LidarrHandler,
|
||||||
|
ActiveLidarrBlock::Blocklist,
|
||||||
|
active_lidarr_block
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
@@ -199,23 +231,66 @@ mod tests {
|
|||||||
)]
|
)]
|
||||||
active_lidarr_block: ActiveLidarrBlock,
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
) {
|
) {
|
||||||
let mut app = App::test_default();
|
test_handler_delegation!(
|
||||||
app
|
LidarrHandler,
|
||||||
.data
|
ActiveLidarrBlock::History,
|
||||||
.lidarr_data
|
active_lidarr_block
|
||||||
.history
|
);
|
||||||
.set_items(vec![LidarrHistoryItem::default()]);
|
}
|
||||||
app.push_navigation_stack(ActiveLidarrBlock::History.into());
|
|
||||||
app.push_navigation_stack(active_lidarr_block.into());
|
|
||||||
|
|
||||||
LidarrHandler::new(
|
#[rstest]
|
||||||
DEFAULT_KEYBINDINGS.esc.key,
|
fn test_delegates_root_folders_blocks_to_root_folders_handler(
|
||||||
&mut app,
|
#[values(
|
||||||
active_lidarr_block,
|
ActiveLidarrBlock::RootFolders,
|
||||||
None,
|
ActiveLidarrBlock::AddRootFolderPrompt,
|
||||||
)
|
ActiveLidarrBlock::DeleteRootFolderPrompt
|
||||||
.handle();
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
test_handler_delegation!(
|
||||||
|
LidarrHandler,
|
||||||
|
ActiveLidarrBlock::RootFolders,
|
||||||
|
active_lidarr_block
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
assert_eq!(app.get_current_route(), ActiveLidarrBlock::History.into());
|
#[rstest]
|
||||||
|
fn test_delegates_indexers_blocks_to_indexers_handler(
|
||||||
|
#[values(
|
||||||
|
ActiveLidarrBlock::DeleteIndexerPrompt,
|
||||||
|
ActiveLidarrBlock::Indexers,
|
||||||
|
ActiveLidarrBlock::AllIndexerSettingsPrompt,
|
||||||
|
ActiveLidarrBlock::IndexerSettingsConfirmPrompt,
|
||||||
|
ActiveLidarrBlock::IndexerSettingsMaximumSizeInput,
|
||||||
|
ActiveLidarrBlock::IndexerSettingsMinimumAgeInput,
|
||||||
|
ActiveLidarrBlock::IndexerSettingsRetentionInput,
|
||||||
|
ActiveLidarrBlock::IndexerSettingsRssSyncIntervalInput
|
||||||
|
)]
|
||||||
|
active_sonarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
test_handler_delegation!(
|
||||||
|
LidarrHandler,
|
||||||
|
ActiveLidarrBlock::Indexers,
|
||||||
|
active_sonarr_block
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_delegates_system_blocks_to_system_handler(
|
||||||
|
#[values(
|
||||||
|
ActiveLidarrBlock::System,
|
||||||
|
ActiveLidarrBlock::SystemLogs,
|
||||||
|
ActiveLidarrBlock::SystemQueuedEvents,
|
||||||
|
ActiveLidarrBlock::SystemTasks,
|
||||||
|
ActiveLidarrBlock::SystemTaskStartConfirmPrompt,
|
||||||
|
ActiveLidarrBlock::SystemUpdates
|
||||||
|
)]
|
||||||
|
active_sonarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
test_handler_delegation!(
|
||||||
|
LidarrHandler,
|
||||||
|
ActiveLidarrBlock::System,
|
||||||
|
active_sonarr_block
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,24 @@
|
|||||||
use history::HistoryHandler;
|
use history::HistoryHandler;
|
||||||
|
use indexers::IndexersHandler;
|
||||||
use library::LibraryHandler;
|
use library::LibraryHandler;
|
||||||
|
|
||||||
use super::KeyEventHandler;
|
use super::KeyEventHandler;
|
||||||
|
use crate::handlers::lidarr_handlers::blocklist::BlocklistHandler;
|
||||||
|
use crate::handlers::lidarr_handlers::downloads::DownloadsHandler;
|
||||||
|
use crate::handlers::lidarr_handlers::root_folders::RootFoldersHandler;
|
||||||
|
use crate::handlers::lidarr_handlers::system::SystemHandler;
|
||||||
use crate::models::Route;
|
use crate::models::Route;
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App, event::Key, matches_key, models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock,
|
app::App, event::Key, matches_key, models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod blocklist;
|
||||||
|
mod downloads;
|
||||||
mod history;
|
mod history;
|
||||||
|
mod indexers;
|
||||||
mod library;
|
mod library;
|
||||||
|
mod root_folders;
|
||||||
|
mod system;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "lidarr_handler_tests.rs"]
|
#[path = "lidarr_handler_tests.rs"]
|
||||||
@@ -27,9 +37,25 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for LidarrHandler<'a, 'b
|
|||||||
_ if LibraryHandler::accepts(self.active_lidarr_block) => {
|
_ if LibraryHandler::accepts(self.active_lidarr_block) => {
|
||||||
LibraryHandler::new(self.key, self.app, self.active_lidarr_block, self.context).handle();
|
LibraryHandler::new(self.key, self.app, self.active_lidarr_block, self.context).handle();
|
||||||
}
|
}
|
||||||
|
_ if DownloadsHandler::accepts(self.active_lidarr_block) => {
|
||||||
|
DownloadsHandler::new(self.key, self.app, self.active_lidarr_block, self.context).handle();
|
||||||
|
}
|
||||||
|
_ if BlocklistHandler::accepts(self.active_lidarr_block) => {
|
||||||
|
BlocklistHandler::new(self.key, self.app, self.active_lidarr_block, self.context).handle();
|
||||||
|
}
|
||||||
_ if HistoryHandler::accepts(self.active_lidarr_block) => {
|
_ if HistoryHandler::accepts(self.active_lidarr_block) => {
|
||||||
HistoryHandler::new(self.key, self.app, self.active_lidarr_block, self.context).handle();
|
HistoryHandler::new(self.key, self.app, self.active_lidarr_block, self.context).handle();
|
||||||
}
|
}
|
||||||
|
_ if RootFoldersHandler::accepts(self.active_lidarr_block) => {
|
||||||
|
RootFoldersHandler::new(self.key, self.app, self.active_lidarr_block, self.context)
|
||||||
|
.handle();
|
||||||
|
}
|
||||||
|
_ if IndexersHandler::accepts(self.active_lidarr_block) => {
|
||||||
|
IndexersHandler::new(self.key, self.app, self.active_lidarr_block, self.context).handle();
|
||||||
|
}
|
||||||
|
_ if SystemHandler::accepts(self.active_lidarr_block) => {
|
||||||
|
SystemHandler::new(self.key, self.app, self.active_lidarr_block, self.context).handle();
|
||||||
|
}
|
||||||
_ => self.handle_key_event(),
|
_ => self.handle_key_event(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,10 +64,6 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for LidarrHandler<'a, 'b
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ignore_special_keys(&self) -> bool {
|
|
||||||
self.app.ignore_special_keys_for_textbox_input
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new(
|
fn new(
|
||||||
key: Key,
|
key: Key,
|
||||||
app: &'a mut App<'b>,
|
app: &'a mut App<'b>,
|
||||||
@@ -60,6 +82,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for LidarrHandler<'a, 'b
|
|||||||
self.key
|
self.key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ignore_special_keys(&self) -> bool {
|
||||||
|
self.app.ignore_special_keys_for_textbox_input
|
||||||
|
}
|
||||||
|
|
||||||
fn is_ready(&self) -> bool {
|
fn is_ready(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,533 @@
|
|||||||
|
use crate::app::App;
|
||||||
|
use crate::event::Key;
|
||||||
|
use crate::handlers::{KeyEventHandler, handle_prompt_toggle};
|
||||||
|
use crate::models::lidarr_models::AddLidarrRootFolderBody;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{ADD_ROOT_FOLDER_BLOCKS, ActiveLidarrBlock};
|
||||||
|
use crate::models::servarr_data::lidarr::modals::AddRootFolderModal;
|
||||||
|
use crate::models::{Route, Scrollable};
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use crate::{handle_text_box_keys, handle_text_box_left_right_keys, matches_key};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "add_root_folder_handler_tests.rs"]
|
||||||
|
mod add_root_folder_handler_tests;
|
||||||
|
|
||||||
|
pub(super) struct AddRootFolderHandler<'a, 'b> {
|
||||||
|
key: Key,
|
||||||
|
app: &'a mut App<'b>,
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
context: Option<ActiveLidarrBlock>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddRootFolderHandler<'_, '_> {
|
||||||
|
fn build_add_root_folder_body(&mut self) -> AddLidarrRootFolderBody {
|
||||||
|
let add_root_folder_modal = self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.take()
|
||||||
|
.expect("AddRootFolderModal is None");
|
||||||
|
|
||||||
|
let tags = add_root_folder_modal.tags.text.clone();
|
||||||
|
|
||||||
|
let AddRootFolderModal {
|
||||||
|
name,
|
||||||
|
path,
|
||||||
|
monitor_list,
|
||||||
|
monitor_new_items_list,
|
||||||
|
quality_profile_list,
|
||||||
|
metadata_profile_list,
|
||||||
|
..
|
||||||
|
} = add_root_folder_modal;
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
AddLidarrRootFolderBody {
|
||||||
|
name: name.text,
|
||||||
|
path: path.text,
|
||||||
|
default_quality_profile_id: quality_profile_id,
|
||||||
|
default_metadata_profile_id: metadata_profile_id,
|
||||||
|
default_monitor_option: *monitor_list.current_selection(),
|
||||||
|
default_new_item_monitor_option: *monitor_new_items_list.current_selection(),
|
||||||
|
default_tags: Vec::new(),
|
||||||
|
tag_input_string: Some(tags),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for AddRootFolderHandler<'a, 'b> {
|
||||||
|
fn accepts(active_block: ActiveLidarrBlock) -> bool {
|
||||||
|
ADD_ROOT_FOLDER_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>,
|
||||||
|
) -> AddRootFolderHandler<'a, 'b> {
|
||||||
|
AddRootFolderHandler {
|
||||||
|
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.add_root_folder_modal.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_scroll_up(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::AddRootFolderSelectMonitor => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.monitor_list
|
||||||
|
.scroll_up(),
|
||||||
|
ActiveLidarrBlock::AddRootFolderSelectMonitorNewItems => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.monitor_new_items_list
|
||||||
|
.scroll_up(),
|
||||||
|
ActiveLidarrBlock::AddRootFolderSelectQualityProfile => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.quality_profile_list
|
||||||
|
.scroll_up(),
|
||||||
|
ActiveLidarrBlock::AddRootFolderSelectMetadataProfile => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.metadata_profile_list
|
||||||
|
.scroll_up(),
|
||||||
|
ActiveLidarrBlock::AddRootFolderPrompt => self.app.data.lidarr_data.selected_block.up(),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_scroll_down(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::AddRootFolderSelectMonitor => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.monitor_list
|
||||||
|
.scroll_down(),
|
||||||
|
ActiveLidarrBlock::AddRootFolderSelectMonitorNewItems => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.monitor_new_items_list
|
||||||
|
.scroll_down(),
|
||||||
|
ActiveLidarrBlock::AddRootFolderSelectQualityProfile => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.quality_profile_list
|
||||||
|
.scroll_down(),
|
||||||
|
ActiveLidarrBlock::AddRootFolderSelectMetadataProfile => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.metadata_profile_list
|
||||||
|
.scroll_down(),
|
||||||
|
ActiveLidarrBlock::AddRootFolderPrompt => self.app.data.lidarr_data.selected_block.down(),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_home(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::AddRootFolderSelectMonitor => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.monitor_list
|
||||||
|
.scroll_to_top(),
|
||||||
|
ActiveLidarrBlock::AddRootFolderSelectMonitorNewItems => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.monitor_new_items_list
|
||||||
|
.scroll_to_top(),
|
||||||
|
ActiveLidarrBlock::AddRootFolderSelectQualityProfile => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.quality_profile_list
|
||||||
|
.scroll_to_top(),
|
||||||
|
ActiveLidarrBlock::AddRootFolderSelectMetadataProfile => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.metadata_profile_list
|
||||||
|
.scroll_to_top(),
|
||||||
|
ActiveLidarrBlock::AddRootFolderNameInput => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.name
|
||||||
|
.scroll_home(),
|
||||||
|
ActiveLidarrBlock::AddRootFolderPathInput => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.path
|
||||||
|
.scroll_home(),
|
||||||
|
ActiveLidarrBlock::AddRootFolderTagsInput => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.tags
|
||||||
|
.scroll_home(),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_end(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::AddRootFolderSelectMonitor => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.monitor_list
|
||||||
|
.scroll_to_bottom(),
|
||||||
|
ActiveLidarrBlock::AddRootFolderSelectMonitorNewItems => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.monitor_new_items_list
|
||||||
|
.scroll_to_bottom(),
|
||||||
|
ActiveLidarrBlock::AddRootFolderSelectQualityProfile => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.quality_profile_list
|
||||||
|
.scroll_to_bottom(),
|
||||||
|
ActiveLidarrBlock::AddRootFolderSelectMetadataProfile => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.metadata_profile_list
|
||||||
|
.scroll_to_bottom(),
|
||||||
|
ActiveLidarrBlock::AddRootFolderNameInput => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.name
|
||||||
|
.reset_offset(),
|
||||||
|
ActiveLidarrBlock::AddRootFolderPathInput => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.path
|
||||||
|
.reset_offset(),
|
||||||
|
ActiveLidarrBlock::AddRootFolderTagsInput => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_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::AddRootFolderPrompt => handle_prompt_toggle(self.app, self.key),
|
||||||
|
ActiveLidarrBlock::AddRootFolderNameInput => {
|
||||||
|
handle_text_box_left_right_keys!(
|
||||||
|
self,
|
||||||
|
self.key,
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AddRootFolderPathInput => {
|
||||||
|
handle_text_box_left_right_keys!(
|
||||||
|
self,
|
||||||
|
self.key,
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.path
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AddRootFolderTagsInput => {
|
||||||
|
handle_text_box_left_right_keys!(
|
||||||
|
self,
|
||||||
|
self.key,
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.tags
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_submit(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::AddRootFolderPrompt => {
|
||||||
|
match self.app.data.lidarr_data.selected_block.get_active_block() {
|
||||||
|
ActiveLidarrBlock::AddRootFolderConfirmPrompt => {
|
||||||
|
if self.app.data.lidarr_data.prompt_confirm {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action = Some(LidarrEvent::AddRootFolder(
|
||||||
|
self.build_add_root_folder_body(),
|
||||||
|
));
|
||||||
|
self.app.should_refresh = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AddRootFolderSelectMonitor
|
||||||
|
| ActiveLidarrBlock::AddRootFolderSelectMonitorNewItems
|
||||||
|
| ActiveLidarrBlock::AddRootFolderSelectQualityProfile
|
||||||
|
| ActiveLidarrBlock::AddRootFolderSelectMetadataProfile => {
|
||||||
|
self.app.push_navigation_stack(
|
||||||
|
(
|
||||||
|
self.app.data.lidarr_data.selected_block.get_active_block(),
|
||||||
|
self.context,
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AddRootFolderNameInput
|
||||||
|
| ActiveLidarrBlock::AddRootFolderPathInput
|
||||||
|
| ActiveLidarrBlock::AddRootFolderTagsInput => {
|
||||||
|
self.app.push_navigation_stack(
|
||||||
|
(
|
||||||
|
self.app.data.lidarr_data.selected_block.get_active_block(),
|
||||||
|
self.context,
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
self.app.ignore_special_keys_for_textbox_input = true;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AddRootFolderSelectMonitor
|
||||||
|
| ActiveLidarrBlock::AddRootFolderSelectMonitorNewItems
|
||||||
|
| ActiveLidarrBlock::AddRootFolderSelectQualityProfile
|
||||||
|
| ActiveLidarrBlock::AddRootFolderSelectMetadataProfile => self.app.pop_navigation_stack(),
|
||||||
|
ActiveLidarrBlock::AddRootFolderNameInput
|
||||||
|
| ActiveLidarrBlock::AddRootFolderPathInput
|
||||||
|
| ActiveLidarrBlock::AddRootFolderTagsInput => {
|
||||||
|
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::AddRootFolderNameInput
|
||||||
|
| ActiveLidarrBlock::AddRootFolderPathInput
|
||||||
|
| ActiveLidarrBlock::AddRootFolderTagsInput => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
self.app.ignore_special_keys_for_textbox_input = false;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AddRootFolderPrompt => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
self.app.data.lidarr_data.add_root_folder_modal = None;
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = false;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AddRootFolderSelectMonitor
|
||||||
|
| ActiveLidarrBlock::AddRootFolderSelectMonitorNewItems
|
||||||
|
| ActiveLidarrBlock::AddRootFolderSelectQualityProfile
|
||||||
|
| ActiveLidarrBlock::AddRootFolderSelectMetadataProfile => self.app.pop_navigation_stack(),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_char_key_event(&mut self) {
|
||||||
|
let key = self.key;
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::AddRootFolderNameInput => {
|
||||||
|
handle_text_box_keys!(
|
||||||
|
self,
|
||||||
|
key,
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AddRootFolderPathInput => {
|
||||||
|
handle_text_box_keys!(
|
||||||
|
self,
|
||||||
|
key,
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.path
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AddRootFolderTagsInput => {
|
||||||
|
handle_text_box_keys!(
|
||||||
|
self,
|
||||||
|
key,
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_root_folder_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.tags
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AddRootFolderPrompt => {
|
||||||
|
if self.app.data.lidarr_data.selected_block.get_active_block()
|
||||||
|
== ActiveLidarrBlock::AddRootFolderConfirmPrompt
|
||||||
|
&& matches_key!(confirm, key)
|
||||||
|
{
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action = Some(LidarrEvent::AddRootFolder(
|
||||||
|
self.build_add_root_folder_body(),
|
||||||
|
));
|
||||||
|
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,173 @@
|
|||||||
|
use add_root_folder_handler::AddRootFolderHandler;
|
||||||
|
|
||||||
|
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::servarr_data::lidarr::lidarr_data::{
|
||||||
|
ADD_ROOT_FOLDER_BLOCKS, ADD_ROOT_FOLDER_SELECTION_BLOCKS, ActiveLidarrBlock, ROOT_FOLDERS_BLOCKS,
|
||||||
|
};
|
||||||
|
use crate::models::servarr_data::lidarr::modals::AddRootFolderModal;
|
||||||
|
use crate::models::{BlockSelectionState, Route};
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
|
||||||
|
mod add_root_folder_handler;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "root_folders_handler_tests.rs"]
|
||||||
|
mod root_folders_handler_tests;
|
||||||
|
|
||||||
|
pub(super) struct RootFoldersHandler<'a, 'b> {
|
||||||
|
key: Key,
|
||||||
|
app: &'a mut App<'b>,
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
context: Option<ActiveLidarrBlock>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RootFoldersHandler<'_, '_> {
|
||||||
|
fn extract_root_folder_id(&self) -> i64 {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.root_folders
|
||||||
|
.current_selection()
|
||||||
|
.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for RootFoldersHandler<'a, 'b> {
|
||||||
|
fn handle(&mut self) {
|
||||||
|
let root_folders_table_handling_config =
|
||||||
|
TableHandlingConfig::new(ActiveLidarrBlock::RootFolders.into());
|
||||||
|
|
||||||
|
if AddRootFolderHandler::accepts(self.active_lidarr_block) {
|
||||||
|
return AddRootFolderHandler::new(self.key, self.app, self.active_lidarr_block, self.context)
|
||||||
|
.handle();
|
||||||
|
}
|
||||||
|
|
||||||
|
if !handle_table(
|
||||||
|
self,
|
||||||
|
|app| &mut app.data.lidarr_data.root_folders,
|
||||||
|
root_folders_table_handling_config,
|
||||||
|
) {
|
||||||
|
self.handle_key_event();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accepts(active_block: ActiveLidarrBlock) -> bool {
|
||||||
|
ROOT_FOLDERS_BLOCKS.contains(&active_block) || ADD_ROOT_FOLDER_BLOCKS.contains(&active_block)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(
|
||||||
|
key: Key,
|
||||||
|
app: &'a mut App<'b>,
|
||||||
|
active_block: ActiveLidarrBlock,
|
||||||
|
context: Option<ActiveLidarrBlock>,
|
||||||
|
) -> RootFoldersHandler<'a, 'b> {
|
||||||
|
RootFoldersHandler {
|
||||||
|
key,
|
||||||
|
app,
|
||||||
|
active_lidarr_block: active_block,
|
||||||
|
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.root_folders.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::RootFolders {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::DeleteRootFolderPrompt.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_left_right_action(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::RootFolders => handle_change_tab_left_right_keys(self.app, self.key),
|
||||||
|
ActiveLidarrBlock::DeleteRootFolderPrompt => handle_prompt_toggle(self.app, self.key),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_submit(&mut self) {
|
||||||
|
if self.active_lidarr_block == ActiveLidarrBlock::DeleteRootFolderPrompt {
|
||||||
|
if self.app.data.lidarr_data.prompt_confirm {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action =
|
||||||
|
Some(LidarrEvent::DeleteRootFolder(self.extract_root_folder_id()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_esc(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::DeleteRootFolderPrompt => {
|
||||||
|
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::RootFolders => match self.key {
|
||||||
|
_ if matches_key!(refresh, key) => {
|
||||||
|
self.app.should_refresh = true;
|
||||||
|
}
|
||||||
|
_ if matches_key!(add, key) => {
|
||||||
|
self.app.data.lidarr_data.add_root_folder_modal =
|
||||||
|
Some(AddRootFolderModal::from(&self.app.data.lidarr_data));
|
||||||
|
self.app.data.lidarr_data.selected_block =
|
||||||
|
BlockSelectionState::new(ADD_ROOT_FOLDER_SELECTION_BLOCKS);
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::AddRootFolderPrompt.into());
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
ActiveLidarrBlock::DeleteRootFolderPrompt => {
|
||||||
|
if matches_key!(confirm, key) {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action =
|
||||||
|
Some(LidarrEvent::DeleteRootFolder(self.extract_root_folder_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,480 @@
|
|||||||
|
#[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_present;
|
||||||
|
use crate::assert_navigation_pushed;
|
||||||
|
use crate::event::Key;
|
||||||
|
use crate::handlers::KeyEventHandler;
|
||||||
|
use crate::handlers::lidarr_handlers::root_folders::RootFoldersHandler;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
|
ADD_ROOT_FOLDER_BLOCKS, ActiveLidarrBlock, ROOT_FOLDERS_BLOCKS,
|
||||||
|
};
|
||||||
|
use crate::models::servarr_models::RootFolder;
|
||||||
|
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::root_folder;
|
||||||
|
|
||||||
|
mod test_handle_delete {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_root_folder_prompt() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::RootFolders.into());
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.root_folders
|
||||||
|
.set_items(vec![RootFolder::default()]);
|
||||||
|
|
||||||
|
RootFoldersHandler::new(DELETE_KEY, &mut app, ActiveLidarrBlock::RootFolders, None).handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::DeleteRootFolderPrompt.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_root_folder_prompt_no_op_when_not_ready() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::RootFolders.into());
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.root_folders
|
||||||
|
.set_items(vec![RootFolder::default()]);
|
||||||
|
|
||||||
|
RootFoldersHandler::new(DELETE_KEY, &mut app, ActiveLidarrBlock::RootFolders, None).handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveLidarrBlock::RootFolders.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_root_folders_tab_left(#[values(true, false)] is_ready: bool) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::RootFolders.into());
|
||||||
|
app.is_loading = is_ready;
|
||||||
|
app.data.lidarr_data.main_tabs.set_index(4);
|
||||||
|
|
||||||
|
RootFoldersHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.left.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::RootFolders,
|
||||||
|
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_root_folders_tab_right(#[values(true, false)] is_ready: bool) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::RootFolders.into());
|
||||||
|
app.is_loading = is_ready;
|
||||||
|
app.data.lidarr_data.main_tabs.set_index(4);
|
||||||
|
|
||||||
|
RootFoldersHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.right.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::RootFolders,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.main_tabs.get_active_route(),
|
||||||
|
ActiveLidarrBlock::Indexers.into()
|
||||||
|
);
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::Indexers.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_left_right_delete_root_folder_prompt_toggle(
|
||||||
|
#[values(DEFAULT_KEYBINDINGS.left.key, DEFAULT_KEYBINDINGS.right.key)] key: Key,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::RootFolders.into());
|
||||||
|
|
||||||
|
RootFoldersHandler::new(
|
||||||
|
key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::DeleteRootFolderPrompt,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert!(app.data.lidarr_data.prompt_confirm);
|
||||||
|
|
||||||
|
RootFoldersHandler::new(
|
||||||
|
key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::DeleteRootFolderPrompt,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_submit {
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::assert_navigation_popped;
|
||||||
|
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::root_folder;
|
||||||
|
|
||||||
|
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_root_folder_prompt_confirm_submit() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.root_folders
|
||||||
|
.set_items(vec![root_folder()]);
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::RootFolders.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::DeleteRootFolderPrompt.into());
|
||||||
|
|
||||||
|
RootFoldersHandler::new(
|
||||||
|
SUBMIT_KEY,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::DeleteRootFolderPrompt,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert!(app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
&app.data.lidarr_data.prompt_confirm_action,
|
||||||
|
&LidarrEvent::DeleteRootFolder(1)
|
||||||
|
);
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::RootFolders.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_root_folder_prompt_decline_submit() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.root_folders
|
||||||
|
.set_items(vec![RootFolder::default()]);
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::RootFolders.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::DeleteRootFolderPrompt.into());
|
||||||
|
|
||||||
|
RootFoldersHandler::new(
|
||||||
|
SUBMIT_KEY,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::DeleteRootFolderPrompt,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_none!(app.data.lidarr_data.prompt_confirm_action);
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::RootFolders.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_esc {
|
||||||
|
use super::*;
|
||||||
|
use crate::assert_navigation_popped;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_root_folder_prompt_block_esc() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::RootFolders.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::DeleteRootFolderPrompt.into());
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
|
||||||
|
RootFoldersHandler::new(
|
||||||
|
ESC_KEY,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::DeleteRootFolderPrompt,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::RootFolders.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::RootFolders.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::RootFolders.into());
|
||||||
|
|
||||||
|
RootFoldersHandler::new(ESC_KEY, &mut app, ActiveLidarrBlock::RootFolders, None).handle();
|
||||||
|
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::RootFolders.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::root_folder;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_root_folder_add() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::RootFolders.into());
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.root_folders
|
||||||
|
.set_items(vec![RootFolder::default()]);
|
||||||
|
|
||||||
|
RootFoldersHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.add.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::RootFolders,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::AddRootFolderPrompt.into());
|
||||||
|
assert_modal_present!(app.data.lidarr_data.add_root_folder_modal);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_root_folder_add_no_op_when_not_ready() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::RootFolders.into());
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.root_folders
|
||||||
|
.set_items(vec![RootFolder::default()]);
|
||||||
|
|
||||||
|
RootFoldersHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.add.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::RootFolders,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveLidarrBlock::RootFolders.into()
|
||||||
|
);
|
||||||
|
assert_none!(app.data.lidarr_data.add_root_folder_modal);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_refresh_root_folders_key() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.root_folders
|
||||||
|
.set_items(vec![RootFolder::default()]);
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::RootFolders.into());
|
||||||
|
|
||||||
|
RootFoldersHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::RootFolders,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::RootFolders.into());
|
||||||
|
assert!(app.should_refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_refresh_root_folders_key_no_op_when_not_ready() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.root_folders
|
||||||
|
.set_items(vec![RootFolder::default()]);
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::RootFolders.into());
|
||||||
|
|
||||||
|
RootFoldersHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::RootFolders,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveLidarrBlock::RootFolders.into()
|
||||||
|
);
|
||||||
|
assert!(!app.should_refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_root_folder_prompt_confirm() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.root_folders
|
||||||
|
.set_items(vec![root_folder()]);
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::RootFolders.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::DeleteRootFolderPrompt.into());
|
||||||
|
|
||||||
|
RootFoldersHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.confirm.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::DeleteRootFolderPrompt,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert!(app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
&app.data.lidarr_data.prompt_confirm_action,
|
||||||
|
&LidarrEvent::DeleteRootFolder(1)
|
||||||
|
);
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::RootFolders.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_root_folders_handler_accepts() {
|
||||||
|
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
|
||||||
|
if ROOT_FOLDERS_BLOCKS.contains(&active_lidarr_block)
|
||||||
|
|| ADD_ROOT_FOLDER_BLOCKS.contains(&active_lidarr_block)
|
||||||
|
{
|
||||||
|
assert!(RootFoldersHandler::accepts(active_lidarr_block));
|
||||||
|
} else {
|
||||||
|
assert!(!RootFoldersHandler::accepts(active_lidarr_block));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_root_folders_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 = RootFoldersHandler::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_root_folder_id() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.root_folders
|
||||||
|
.set_items(vec![root_folder()]);
|
||||||
|
|
||||||
|
let root_folder_id = RootFoldersHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::DeleteRootFolderPrompt,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.extract_root_folder_id();
|
||||||
|
|
||||||
|
assert_eq!(root_folder_id, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_root_folders_handler_not_ready_when_loading() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::RootFolders.into());
|
||||||
|
app.is_loading = true;
|
||||||
|
|
||||||
|
let handler = RootFoldersHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::RootFolders,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_root_folders_handler_not_ready_when_root_folders_is_empty() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::RootFolders.into());
|
||||||
|
app.is_loading = false;
|
||||||
|
|
||||||
|
let handler = RootFoldersHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::RootFolders,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_root_folders_handler_ready_when_not_loading_and_root_folders_is_not_empty() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::RootFolders.into());
|
||||||
|
app.is_loading = false;
|
||||||
|
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.root_folders
|
||||||
|
.set_items(vec![RootFolder::default()]);
|
||||||
|
let handler = RootFoldersHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::RootFolders,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(handler.is_ready());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
use crate::app::App;
|
||||||
|
use crate::event::Key;
|
||||||
|
use crate::handlers::lidarr_handlers::handle_change_tab_left_right_keys;
|
||||||
|
use crate::handlers::lidarr_handlers::system::system_details_handler::SystemDetailsHandler;
|
||||||
|
use crate::handlers::{KeyEventHandler, handle_clear_errors};
|
||||||
|
use crate::matches_key;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
||||||
|
use crate::models::{Route, Scrollable};
|
||||||
|
|
||||||
|
mod system_details_handler;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "system_handler_tests.rs"]
|
||||||
|
mod system_handler_tests;
|
||||||
|
|
||||||
|
pub(super) struct SystemHandler<'a, 'b> {
|
||||||
|
key: Key,
|
||||||
|
app: &'a mut App<'b>,
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
context: Option<ActiveLidarrBlock>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for SystemHandler<'a, 'b> {
|
||||||
|
fn handle(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
_ if SystemDetailsHandler::accepts(self.active_lidarr_block) => {
|
||||||
|
SystemDetailsHandler::new(self.key, self.app, self.active_lidarr_block, self.context)
|
||||||
|
.handle()
|
||||||
|
}
|
||||||
|
_ => self.handle_key_event(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accepts(active_block: ActiveLidarrBlock) -> bool {
|
||||||
|
SystemDetailsHandler::accepts(active_block) || active_block == ActiveLidarrBlock::System
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(
|
||||||
|
key: Key,
|
||||||
|
app: &'a mut App<'b>,
|
||||||
|
active_block: ActiveLidarrBlock,
|
||||||
|
context: Option<ActiveLidarrBlock>,
|
||||||
|
) -> SystemHandler<'a, 'b> {
|
||||||
|
SystemHandler {
|
||||||
|
key,
|
||||||
|
app,
|
||||||
|
active_lidarr_block: active_block,
|
||||||
|
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.logs.is_empty()
|
||||||
|
&& !self.app.data.lidarr_data.queued_events.is_empty()
|
||||||
|
&& !self.app.data.lidarr_data.tasks.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::System {
|
||||||
|
handle_change_tab_left_right_keys(self.app, self.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_submit(&mut self) {}
|
||||||
|
|
||||||
|
fn handle_esc(&mut self) {
|
||||||
|
handle_clear_errors(self.app)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_char_key_event(&mut self) {
|
||||||
|
if self.active_lidarr_block == ActiveLidarrBlock::System {
|
||||||
|
let key = self.key;
|
||||||
|
match self.key {
|
||||||
|
_ if matches_key!(refresh, key) => {
|
||||||
|
self.app.should_refresh = true;
|
||||||
|
}
|
||||||
|
_ if matches_key!(events, key) => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::SystemQueuedEvents.into());
|
||||||
|
}
|
||||||
|
_ if matches_key!(logs, key) => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::SystemLogs.into());
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.log_details
|
||||||
|
.set_items(self.app.data.lidarr_data.logs.items.to_vec());
|
||||||
|
self.app.data.lidarr_data.log_details.scroll_to_bottom();
|
||||||
|
}
|
||||||
|
_ if matches_key!(tasks, key) => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::SystemTasks.into());
|
||||||
|
}
|
||||||
|
_ if matches_key!(update, key) => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::SystemUpdates.into());
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app_mut(&mut self) -> &mut App<'b> {
|
||||||
|
self.app
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_route(&self) -> Route {
|
||||||
|
self.app.get_current_route()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
use crate::app::App;
|
||||||
|
use crate::event::Key;
|
||||||
|
use crate::handlers::{KeyEventHandler, handle_prompt_toggle};
|
||||||
|
use crate::matches_key;
|
||||||
|
use crate::models::lidarr_models::LidarrTaskName;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, SYSTEM_DETAILS_BLOCKS};
|
||||||
|
use crate::models::stateful_list::StatefulList;
|
||||||
|
use crate::models::{Route, Scrollable};
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "system_details_handler_tests.rs"]
|
||||||
|
mod system_details_handler_tests;
|
||||||
|
|
||||||
|
pub(super) struct SystemDetailsHandler<'a, 'b> {
|
||||||
|
key: Key,
|
||||||
|
app: &'a mut App<'b>,
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
_context: Option<ActiveLidarrBlock>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SystemDetailsHandler<'_, '_> {
|
||||||
|
fn extract_task_name(&self) -> LidarrTaskName {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.tasks
|
||||||
|
.current_selection()
|
||||||
|
.task_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for SystemDetailsHandler<'a, 'b> {
|
||||||
|
fn accepts(active_block: ActiveLidarrBlock) -> bool {
|
||||||
|
SYSTEM_DETAILS_BLOCKS.contains(&active_block)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(
|
||||||
|
key: Key,
|
||||||
|
app: &'a mut App<'b>,
|
||||||
|
active_block: ActiveLidarrBlock,
|
||||||
|
context: Option<ActiveLidarrBlock>,
|
||||||
|
) -> SystemDetailsHandler<'a, 'b> {
|
||||||
|
SystemDetailsHandler {
|
||||||
|
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.log_details.is_empty()
|
||||||
|
|| !self.app.data.lidarr_data.tasks.is_empty()
|
||||||
|
|| !self.app.data.lidarr_data.updates.is_empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_scroll_up(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::SystemLogs => self.app.data.lidarr_data.log_details.scroll_up(),
|
||||||
|
ActiveLidarrBlock::SystemTasks => self.app.data.lidarr_data.tasks.scroll_up(),
|
||||||
|
ActiveLidarrBlock::SystemUpdates => self.app.data.lidarr_data.updates.scroll_up(),
|
||||||
|
ActiveLidarrBlock::SystemQueuedEvents => self.app.data.lidarr_data.queued_events.scroll_up(),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_scroll_down(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::SystemLogs => self.app.data.lidarr_data.log_details.scroll_down(),
|
||||||
|
ActiveLidarrBlock::SystemTasks => self.app.data.lidarr_data.tasks.scroll_down(),
|
||||||
|
ActiveLidarrBlock::SystemUpdates => self.app.data.lidarr_data.updates.scroll_down(),
|
||||||
|
ActiveLidarrBlock::SystemQueuedEvents => {
|
||||||
|
self.app.data.lidarr_data.queued_events.scroll_down()
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_home(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::SystemLogs => self.app.data.lidarr_data.log_details.scroll_to_top(),
|
||||||
|
ActiveLidarrBlock::SystemTasks => self.app.data.lidarr_data.tasks.scroll_to_top(),
|
||||||
|
ActiveLidarrBlock::SystemUpdates => self.app.data.lidarr_data.updates.scroll_to_top(),
|
||||||
|
ActiveLidarrBlock::SystemQueuedEvents => {
|
||||||
|
self.app.data.lidarr_data.queued_events.scroll_to_top()
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_end(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::SystemLogs => self.app.data.lidarr_data.log_details.scroll_to_bottom(),
|
||||||
|
ActiveLidarrBlock::SystemTasks => self.app.data.lidarr_data.tasks.scroll_to_bottom(),
|
||||||
|
ActiveLidarrBlock::SystemUpdates => self.app.data.lidarr_data.updates.scroll_to_bottom(),
|
||||||
|
ActiveLidarrBlock::SystemQueuedEvents => {
|
||||||
|
self.app.data.lidarr_data.queued_events.scroll_to_bottom()
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_delete(&mut self) {}
|
||||||
|
|
||||||
|
fn handle_left_right_action(&mut self) {
|
||||||
|
let key = self.key;
|
||||||
|
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::SystemLogs => match self.key {
|
||||||
|
_ if matches_key!(left, key) => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.log_details
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.for_each(|log| log.scroll_right());
|
||||||
|
}
|
||||||
|
_ if matches_key!(right, key) => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.log_details
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.for_each(|log| log.scroll_left());
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
ActiveLidarrBlock::SystemTaskStartConfirmPrompt => handle_prompt_toggle(self.app, self.key),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_submit(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::SystemTasks => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::SystemTaskStartConfirmPrompt.into());
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::SystemTaskStartConfirmPrompt => {
|
||||||
|
if self.app.data.lidarr_data.prompt_confirm {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action =
|
||||||
|
Some(LidarrEvent::StartTask(self.extract_task_name()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_esc(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::SystemLogs => {
|
||||||
|
self.app.data.lidarr_data.log_details = StatefulList::default();
|
||||||
|
self.app.pop_navigation_stack()
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::SystemQueuedEvents
|
||||||
|
| ActiveLidarrBlock::SystemTasks
|
||||||
|
| ActiveLidarrBlock::SystemUpdates => self.app.pop_navigation_stack(),
|
||||||
|
ActiveLidarrBlock::SystemTaskStartConfirmPrompt => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = false;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_char_key_event(&mut self) {
|
||||||
|
if SYSTEM_DETAILS_BLOCKS.contains(&self.active_lidarr_block) && matches_key!(refresh, self.key)
|
||||||
|
{
|
||||||
|
self.app.should_refresh = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.active_lidarr_block == ActiveLidarrBlock::SystemTaskStartConfirmPrompt
|
||||||
|
&& matches_key!(confirm, self.key)
|
||||||
|
{
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action =
|
||||||
|
Some(LidarrEvent::StartTask(self.extract_task_name()));
|
||||||
|
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,580 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
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::system::SystemHandler;
|
||||||
|
use crate::models::lidarr_models::LidarrTask;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
|
ActiveLidarrBlock, SYSTEM_DETAILS_BLOCKS,
|
||||||
|
};
|
||||||
|
use crate::models::servarr_models::QueueEvent;
|
||||||
|
use crate::test_handler_delegation;
|
||||||
|
|
||||||
|
mod test_handle_left_right_action {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::assert_navigation_pushed;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_system_tab_left(#[values(true, false)] is_ready: bool) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::System.into());
|
||||||
|
app.is_loading = is_ready;
|
||||||
|
app.data.lidarr_data.main_tabs.set_index(6);
|
||||||
|
|
||||||
|
SystemHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.left.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::System,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.main_tabs.get_active_route(),
|
||||||
|
ActiveLidarrBlock::Indexers.into()
|
||||||
|
);
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::Indexers.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_system_tab_right(#[values(true, false)] is_ready: bool) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::System.into());
|
||||||
|
app.is_loading = is_ready;
|
||||||
|
app.data.lidarr_data.main_tabs.set_index(6);
|
||||||
|
|
||||||
|
SystemHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.right.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::System,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.main_tabs.get_active_route(),
|
||||||
|
ActiveLidarrBlock::Artists.into()
|
||||||
|
);
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::Artists.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_esc {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::assert_navigation_popped;
|
||||||
|
|
||||||
|
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_default_esc(#[values(true, false)] is_loading: bool) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = is_loading;
|
||||||
|
app.error = "test error".to_owned().into();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::System.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::System.into());
|
||||||
|
|
||||||
|
SystemHandler::new(ESC_KEY, &mut app, ActiveLidarrBlock::System, None).handle();
|
||||||
|
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::System.into());
|
||||||
|
assert_is_empty!(app.error.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_key_char {
|
||||||
|
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||||
|
|
||||||
|
use crate::models::HorizontallyScrollableText;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_update_system_key() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::System.into());
|
||||||
|
app.data.lidarr_data.logs.set_items(vec![
|
||||||
|
HorizontallyScrollableText::from("test 1"),
|
||||||
|
HorizontallyScrollableText::from("test 2"),
|
||||||
|
]);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.queued_events
|
||||||
|
.set_items(vec![QueueEvent::default()]);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.tasks
|
||||||
|
.set_items(vec![LidarrTask::default()]);
|
||||||
|
|
||||||
|
SystemHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.update.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::System,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::SystemUpdates.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_update_system_key_no_op_if_not_ready() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::System.into());
|
||||||
|
app.data.lidarr_data.logs.set_items(vec![
|
||||||
|
HorizontallyScrollableText::from("test 1"),
|
||||||
|
HorizontallyScrollableText::from("test 2"),
|
||||||
|
]);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.queued_events
|
||||||
|
.set_items(vec![QueueEvent::default()]);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.tasks
|
||||||
|
.set_items(vec![LidarrTask::default()]);
|
||||||
|
|
||||||
|
SystemHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.update.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::System,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::System.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_queued_events_key() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::System.into());
|
||||||
|
app.data.lidarr_data.logs.set_items(vec![
|
||||||
|
HorizontallyScrollableText::from("test 1"),
|
||||||
|
HorizontallyScrollableText::from("test 2"),
|
||||||
|
]);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.queued_events
|
||||||
|
.set_items(vec![QueueEvent::default()]);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.tasks
|
||||||
|
.set_items(vec![LidarrTask::default()]);
|
||||||
|
|
||||||
|
SystemHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.events.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::System,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::SystemQueuedEvents.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_queued_events_key_no_op_if_not_ready() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::System.into());
|
||||||
|
app.data.lidarr_data.logs.set_items(vec![
|
||||||
|
HorizontallyScrollableText::from("test 1"),
|
||||||
|
HorizontallyScrollableText::from("test 2"),
|
||||||
|
]);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.queued_events
|
||||||
|
.set_items(vec![QueueEvent::default()]);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.tasks
|
||||||
|
.set_items(vec![LidarrTask::default()]);
|
||||||
|
|
||||||
|
SystemHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.events.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::System,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::System.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_refresh_system_key() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.logs.set_items(vec![
|
||||||
|
HorizontallyScrollableText::from("test 1"),
|
||||||
|
HorizontallyScrollableText::from("test 2"),
|
||||||
|
]);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.queued_events
|
||||||
|
.set_items(vec![QueueEvent::default()]);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.tasks
|
||||||
|
.set_items(vec![LidarrTask::default()]);
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::System.into());
|
||||||
|
|
||||||
|
SystemHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::System,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::System.into());
|
||||||
|
assert!(app.should_refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_refresh_system_key_no_op_if_not_ready() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::System.into());
|
||||||
|
app.data.lidarr_data.logs.set_items(vec![
|
||||||
|
HorizontallyScrollableText::from("test 1"),
|
||||||
|
HorizontallyScrollableText::from("test 2"),
|
||||||
|
]);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.queued_events
|
||||||
|
.set_items(vec![QueueEvent::default()]);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.tasks
|
||||||
|
.set_items(vec![LidarrTask::default()]);
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::System.into());
|
||||||
|
|
||||||
|
SystemHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::System,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::System.into());
|
||||||
|
assert!(!app.should_refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_logs_key() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::System.into());
|
||||||
|
app.data.lidarr_data.logs.set_items(vec![
|
||||||
|
HorizontallyScrollableText::from("test 1"),
|
||||||
|
HorizontallyScrollableText::from("test 2"),
|
||||||
|
]);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.queued_events
|
||||||
|
.set_items(vec![QueueEvent::default()]);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.tasks
|
||||||
|
.set_items(vec![LidarrTask::default()]);
|
||||||
|
|
||||||
|
SystemHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.logs.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::System,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::SystemLogs.into());
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.log_details.items,
|
||||||
|
app.data.lidarr_data.logs.items
|
||||||
|
);
|
||||||
|
assert_str_eq!(
|
||||||
|
app.data.lidarr_data.log_details.current_selection().text,
|
||||||
|
"test 2"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_logs_key_no_op_when_not_ready() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::System.into());
|
||||||
|
app.data.lidarr_data.logs.set_items(vec![
|
||||||
|
HorizontallyScrollableText::from("test 1"),
|
||||||
|
HorizontallyScrollableText::from("test 2"),
|
||||||
|
]);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.queued_events
|
||||||
|
.set_items(vec![QueueEvent::default()]);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.tasks
|
||||||
|
.set_items(vec![LidarrTask::default()]);
|
||||||
|
|
||||||
|
SystemHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.logs.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::System,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::System.into());
|
||||||
|
assert_is_empty!(app.data.lidarr_data.log_details);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tasks_key() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::System.into());
|
||||||
|
app.data.lidarr_data.logs.set_items(vec![
|
||||||
|
HorizontallyScrollableText::from("test 1"),
|
||||||
|
HorizontallyScrollableText::from("test 2"),
|
||||||
|
]);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.queued_events
|
||||||
|
.set_items(vec![QueueEvent::default()]);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.tasks
|
||||||
|
.set_items(vec![LidarrTask::default()]);
|
||||||
|
|
||||||
|
SystemHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.tasks.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::System,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::SystemTasks.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tasks_key_no_op_when_not_ready() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::System.into());
|
||||||
|
app.data.lidarr_data.logs.set_items(vec![
|
||||||
|
HorizontallyScrollableText::from("test 1"),
|
||||||
|
HorizontallyScrollableText::from("test 2"),
|
||||||
|
]);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.queued_events
|
||||||
|
.set_items(vec![QueueEvent::default()]);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.tasks
|
||||||
|
.set_items(vec![LidarrTask::default()]);
|
||||||
|
|
||||||
|
SystemHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.tasks.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::System,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::System.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_delegates_system_details_blocks_to_system_details_handler(
|
||||||
|
#[values(
|
||||||
|
ActiveLidarrBlock::SystemLogs,
|
||||||
|
ActiveLidarrBlock::SystemQueuedEvents,
|
||||||
|
ActiveLidarrBlock::SystemTasks,
|
||||||
|
ActiveLidarrBlock::SystemTaskStartConfirmPrompt,
|
||||||
|
ActiveLidarrBlock::SystemUpdates
|
||||||
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
test_handler_delegation!(
|
||||||
|
SystemHandler,
|
||||||
|
ActiveLidarrBlock::System,
|
||||||
|
active_lidarr_block
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_system_handler_accepts() {
|
||||||
|
let mut system_blocks = vec![ActiveLidarrBlock::System];
|
||||||
|
system_blocks.extend(SYSTEM_DETAILS_BLOCKS);
|
||||||
|
|
||||||
|
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
|
||||||
|
if system_blocks.contains(&active_lidarr_block) {
|
||||||
|
assert!(SystemHandler::accepts(active_lidarr_block));
|
||||||
|
} else {
|
||||||
|
assert!(!SystemHandler::accepts(active_lidarr_block));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_system_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 = SystemHandler::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_system_handler_is_not_ready_when_loading() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::System.into());
|
||||||
|
app.is_loading = true;
|
||||||
|
|
||||||
|
let system_handler = SystemHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.update.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::System,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!system_handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_system_handler_is_not_ready_when_logs_is_empty() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::System.into());
|
||||||
|
app.is_loading = false;
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.tasks
|
||||||
|
.set_items(vec![LidarrTask::default()]);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.queued_events
|
||||||
|
.set_items(vec![QueueEvent::default()]);
|
||||||
|
|
||||||
|
let system_handler = SystemHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.update.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::System,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!system_handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_system_handler_is_not_ready_when_tasks_is_empty() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::System.into());
|
||||||
|
app.is_loading = false;
|
||||||
|
app.data.lidarr_data.logs.set_items(vec!["test".into()]);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.queued_events
|
||||||
|
.set_items(vec![QueueEvent::default()]);
|
||||||
|
|
||||||
|
let system_handler = SystemHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.update.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::System,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!system_handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_system_handler_is_not_ready_when_queued_events_is_empty() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::System.into());
|
||||||
|
app.is_loading = false;
|
||||||
|
app.data.lidarr_data.logs.set_items(vec!["test".into()]);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.tasks
|
||||||
|
.set_items(vec![LidarrTask::default()]);
|
||||||
|
|
||||||
|
let system_handler = SystemHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.update.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::System,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!system_handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_system_handler_is_ready_when_all_required_tables_are_not_empty() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::System.into());
|
||||||
|
app.is_loading = false;
|
||||||
|
app.data.lidarr_data.logs.set_items(vec!["test".into()]);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.tasks
|
||||||
|
.set_items(vec![LidarrTask::default()]);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.queued_events
|
||||||
|
.set_items(vec![QueueEvent::default()]);
|
||||||
|
|
||||||
|
let system_handler = SystemHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.update.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::System,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(system_handler.is_ready());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -116,6 +116,8 @@ pub fn handle_events(key: Key, app: &mut App<'_>) {
|
|||||||
} else {
|
} else {
|
||||||
app.keymapping_table = None;
|
app.keymapping_table = None;
|
||||||
}
|
}
|
||||||
|
} else if matches_key!(esc, key) && app.notification.is_some() {
|
||||||
|
app.notification.take();
|
||||||
} else {
|
} else {
|
||||||
match app.get_current_route() {
|
match app.get_current_route() {
|
||||||
_ if app.keymapping_table.is_some() => {
|
_ if app.keymapping_table.is_some() => {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user