Compare commits
391 Commits
v0.4.1
...
bbcd3f00a9
| Author | SHA1 | Date | |
|---|---|---|---|
|
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 | |||
| 0172253d20 | |||
| 47fdee190a | |||
| 68b08d1cd7 | |||
| f31810e48a | |||
| 09bee7473f | |||
| b2814371f0 | |||
| 269057867f | |||
| 450fdd7106 | |||
| c624d1b9e4 | |||
| e94f78dc7b | |||
| b1a6db21f1 | |||
| ca208ff5e4 | |||
| 1a43d1ec7c | |||
| 4abf705cb5 | |||
| cf98b10d77 | |||
| f0ed71b436 | |||
| 243de47cae | |||
| d3947d9e15 | |||
| 64d8c65831 | |||
| 60c4cf1098 | |||
| 9cc3ccb419 | |||
| 45c61369c8 | |||
| a8609e08c5 | |||
| a18b047f4f | |||
| b1afdaf541 | |||
| 3c1634d1e3 | |||
| 9b4eda6a9d | |||
| 96308afeee | |||
| 4e13d5d34d | |||
| b4a99d1665 | |||
| a012f6ecd5 | |||
| 5afee1998b | |||
| 059fa48bd9 | |||
| 6771a0ab38 | |||
| bc3aeefa6e | |||
| e61537942b | |||
| 5d09b2402c | |||
| 368f7505ff | |||
| 6a9fd0999c | |||
|
|
d8ac94d067 | ||
| 0532d59746 | |||
| e0fcbc71e1 | |||
| c072c57bbb | |||
| aadd6c8abf | |||
| 316ed64315 | |||
| 7084ca1be2 | |||
| 317daddb8e | |||
| 8ef291efd8 | |||
| 92be9c50bf | |||
| f0e5ecd5de | |||
| e2c44583e8 | |||
| 5da741f3a9 | |||
| 35c5eb65cb | |||
| 7e53a26e5f | |||
|
|
436b3f85d0 | ||
| 9c1a9cc3c5 | |||
| 82f30f126d | |||
|
|
9599ac28ca | ||
|
|
e71a699ed8 | ||
| ff4eb8ca98 | |||
| b69973b9af | |||
| 3e133fa147 | |||
| ae506789ab | |||
| c3fa689617 | |||
| b51e42b4b2 | |||
| d4bea91186 | |||
| d47dadeb88 | |||
| b807904c6c | |||
| ee1bee22eb | |||
| f6c4c1623f | |||
| 49fd086b92 | |||
| 35dce0bf01 | |||
| 71240373c0 | |||
| 659023d561 | |||
| a0073b65ad | |||
| cba53e0841 | |||
| e50fb88bfc | |||
| ad58912baf | |||
| c4e8d64710 | |||
| ca4319001c | |||
| ebc58b831d | |||
|
|
b1572c903c | ||
|
|
5b73924e2a | ||
| 97ce258a8d | |||
| fedec79a95 | |||
| 8e74709b9c | |||
|
|
be09e1d932 | ||
|
|
d95417d075 | ||
| 2e3e511e3b | |||
| 8555052cc4 | |||
| 80bc6793c7 | |||
| 049a0c5d49 | |||
| ae9cb77e6d | |||
| 126ed5ed72 | |||
| 63ae64cebd | |||
| 0b29351366 | |||
| 4ea39f74fe | |||
| 76fcf5e67f | |||
| 11457736e6 | |||
| e2a6af1cbd | |||
| 20ea15009d | |||
| 00ab0f27f7 | |||
| 1f4870d082 | |||
| e96af7410e | |||
| 345bb8ce03 | |||
| dbcfc77ad4 | |||
| e653532212 | |||
| 8e7e31f64d | |||
| bff3795cc6 | |||
| 8782f1353d | |||
| f08f255a46 | |||
| 02870043ec | |||
| 154e491922 | |||
| ef5e702255 | |||
| cb4cd93bcd | |||
|
|
ee034c9caf | ||
| c133a4ecd2 | |||
| f09a2efa5e | |||
| 49983f4173 | |||
| 0f9894e1be | |||
|
|
8c0dffec31 | ||
|
|
0085d944ea | ||
|
|
89cf0e66a6 | ||
|
|
baef436f78 | ||
| cf00d7992e | |||
| f25829f3c1 | |||
| 8c783bc405 | |||
| 0048d71b74 | |||
| c633347ecc | |||
|
|
ecd6a0ec32 | ||
| 30507d9d01 | |||
| 6245a794d5 | |||
| 5c822e4890 | |||
| cab06fe43f | |||
| b4ff5f3351 | |||
| 0834802481 | |||
| 3afd74dcbf | |||
| b1a0bdfbb6 | |||
| 6d38bc5e1d | |||
| 5ba1ba15c9 | |||
| db05d2abfb | |||
| 1840c4e39a | |||
| c5a3f424d6 | |||
| 04aa6b81b5 | |||
| 5ff3b9b996 | |||
| 228e4a61a4 | |||
| df38ea5413 | |||
| 709f6ca6ca | |||
| b012fc29e4 | |||
| bdad723aef | |||
| f97d46cec3 | |||
| 7381eaef57 | |||
| 72c922b311 | |||
|
|
fd14a8152c | ||
| 5cb60c317d | |||
| 847de75713 | |||
| 58723cf3e8 | |||
| c613168bfb | |||
|
|
6f83de77f2 | ||
|
|
3f6ef3beb4 | ||
| 14e50c1465 | |||
| 0aa9fdca14 | |||
|
|
dc50820abc | ||
|
|
81afce78ad | ||
| 8a0b912601 | |||
| 85105a953e | |||
| 40f3452d08 | |||
| a287a5c903 | |||
| f30e5270d8 | |||
| 104bcd7bb2 | |||
| fd6fcfc98f | |||
| f87e02cd7c | |||
| 9b63b10118 | |||
| 111485e7c4 | |||
|
|
0167753cfe | ||
|
|
73131cc518 | ||
| 25576757bb | |||
| 105c8f3a82 | |||
| 5164d81492 | |||
| 319e5f1ac2 | |||
|
|
b24c2fbeb1 | ||
| bc5053c39c | |||
|
|
f06a031c93 | ||
|
|
8d450dea5a | ||
| c4ace8c53f | |||
| 78f104f558 | |||
| e8a6f740b9 | |||
|
|
6f3c6ec840 | ||
|
|
47a3ef1d8b | ||
|
|
367e9bf33b | ||
|
|
f122b02424 | ||
|
|
3a09c17f0a | ||
|
|
6773abb04e | ||
| b757d66d7a | |||
| 81cb7a750c | |||
| 3be59108a9 | |||
| fac9c45aee | |||
| 184bd2b510 | |||
| fda69178b9 | |||
| 652bbcd5d4 | |||
| fd35106df8 | |||
|
|
5ead5bc3d6 | ||
| 3ce0003315 | |||
|
|
ee94059a15 | ||
| 844742053d | |||
|
|
7ed9cfa018 | ||
|
|
71791afca0 | ||
| a52eddfdac | |||
| 0474978ac0 | |||
| 4b94a0ce2a | |||
| 0d1eac7610 | |||
| b129e5d5a4 | |||
| 9f5c22890d | |||
|
|
74f4a19003 | ||
|
|
717d9872dc | ||
| 439270fe2d | |||
|
|
601fd55435 | ||
|
|
341c5254f1 | ||
| cb2701ada4 | |||
| 28fcccce98 | |||
| 2ba3e56772 | |||
|
|
f3fa3401f1 | ||
|
|
820f339982 | ||
| afd9dd34a7 | |||
| 4a4e5d2cf4 | |||
| 029b00532e | |||
|
|
0b052684c2 | ||
|
|
3dda80b50f | ||
| 9dede1e45d | |||
| 6dc64c6edf | |||
| c21748df1e | |||
| 27d024cca2 | |||
| b0dd2575d9 | |||
| 7a004c2cfc | |||
| 53ab164379 | |||
| bf4fb3a55d | |||
| 2235f1d0d9 | |||
| 17dcb2b10b | |||
| 4c7c62eb0b | |||
| 81dcf4b003 | |||
| 3fffdd20a8 | |||
| 078d75473b | |||
| 8b7ff58c7d | |||
| ff67eebcea | |||
|
|
e585eb46ea | ||
| 01507f88da | |||
| 6344b6dba2 | |||
| d3042cf724 | |||
| fd4b9a4f15 | |||
| 2879a2aa67 | |||
| 5cd8cb66f1 | |||
| 47c206ca1d | |||
| 8f22b64a0a | |||
| 7941b3d16c | |||
| e9ded4bde4 | |||
| 7b02472f67 | |||
| a370d67121 | |||
| 4f6e64083a | |||
| 276a672df4 | |||
| e55a157977 | |||
| e1e91d1add | |||
| 8102b5933f | |||
| bc60128679 | |||
| fc3a7ab789 | |||
| 93d3e6cec7 | |||
| 95779f1ac2 | |||
| 7bc57d7696 | |||
| 7e7b75f378 | |||
| a958350b2d | |||
| c502b3d08f | |||
|
|
e602b66188 | ||
| 12fba15bcf | |||
| 7e36ad4e8a | |||
| 33249f509f | |||
| ed645dd0d5 | |||
| b12c635c27 | |||
| c16ecfb188 | |||
| 18a8b81631 | |||
| 1d404d4d2c | |||
| 42479ced21 | |||
| 1193b8c848 | |||
| ec8d748991 | |||
| bafaf7ca7a | |||
| f7315a3bec | |||
| f655ca989d | |||
| fcb87a6779 | |||
| 924f8d5eff | |||
| 64ecc38073 | |||
| 5f94dbcabe | |||
| 2ecc591966 | |||
| 30ba1f3317 | |||
| 4fdf9b3df1 | |||
| 22fe1a8f73 | |||
| 38c0ad29dd | |||
| 89d106c03e | |||
| 3e36bcf307 | |||
| acf983c07c | |||
| fedb78fb88 | |||
| db64a0968b | |||
| aece20af47 | |||
| 6c5a73f78f | |||
| 906e093152 | |||
| 478b4ae3c0 | |||
| 23971cbb76 | |||
| 43410fac60 | |||
| cb8035a2ce | |||
| 8d071c7674 | |||
| 965c488468 | |||
| ede7f64c4b | |||
| ba38dcdc15 | |||
| 92d9222b05 | |||
| 4c396c3442 | |||
| e1d5139e36 | |||
| 1ad35652f8 | |||
| 9a9b13d604 | |||
| 77b8b61079 | |||
| bdf48d1bf4 | |||
| f8792ea012 | |||
| 4afde8b750 | |||
| f5614995c7 | |||
| 9ea6dbec20 | |||
| d73dfb9fc7 | |||
| a7da73300c | |||
| a308b8fe95 | |||
| 1d1e42aeb1 | |||
| 1f81061152 | |||
| 368d5d3db7 | |||
| 0612a02d68 | |||
| a0d470087b | |||
| 3ecaf04eb4 | |||
| df0811985d | |||
| 057ff0fef1 | |||
| 14c46f88ab | |||
| e38e430c77 | |||
|
|
93cd235aef | ||
|
|
df9bba32cb | ||
|
|
28a8f9b2fa |
@@ -0,0 +1,4 @@
|
|||||||
|
--artifact-server-path=./.act/artifacts
|
||||||
|
--cache-server-path=./.act/cache
|
||||||
|
--container-options --privileged
|
||||||
|
--env ACT=true
|
||||||
@@ -4,8 +4,8 @@ set -e
|
|||||||
|
|
||||||
echo "Running pre-push hook:"
|
echo "Running pre-push hook:"
|
||||||
|
|
||||||
echo "Executing: make lint"
|
echo "Executing: cargo fmt"
|
||||||
make lint
|
cargo fmt
|
||||||
|
|
||||||
echo "Executing: cargo test"
|
echo "Executing: cargo clippy --all"
|
||||||
cargo test
|
cargo clippy --all
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ set -e
|
|||||||
|
|
||||||
echo "Running pre-push hook:"
|
echo "Running pre-push hook:"
|
||||||
|
|
||||||
echo "Executing: make lint"
|
echo "Executing: cargo fmt --check"
|
||||||
make lint
|
cargo fmt --check
|
||||||
|
|
||||||
echo "Executing: cargo test"
|
echo "Executing: cargo clippy --all"
|
||||||
cargo test
|
cargo clippy --all
|
||||||
|
|
||||||
|
echo "Executing: cargo test --all"
|
||||||
|
cargo test --all
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -76,15 +76,15 @@ jobs:
|
|||||||
RUSTDOCFLAGS: --cfg docsrs
|
RUSTDOCFLAGS: --cfg docsrs
|
||||||
msrv:
|
msrv:
|
||||||
# check that we can build using the minimal rust version that is specified by this crate
|
# check that we can build using the minimal rust version that is specified by this crate
|
||||||
name: 1.82.0 / check
|
name: 1.89.0 / check
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install 1.82.0
|
- name: Install 1.89.0
|
||||||
uses: dtolnay/rust-toolchain@master
|
uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: 1.82.0
|
toolchain: 1.89.0
|
||||||
|
|
||||||
- name: cargo +1.82.0 check
|
- name: cargo +1.89.0 check
|
||||||
run: cargo check
|
run: cargo check
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
bump_type:
|
bump_type:
|
||||||
description: "Specify the type of version bump"
|
description: 'Specify the type of version bump'
|
||||||
required: true
|
required: true
|
||||||
default: "patch"
|
default: 'patch'
|
||||||
type: choice
|
type: choice
|
||||||
options:
|
options:
|
||||||
- patch
|
- patch
|
||||||
@@ -18,8 +18,225 @@ on:
|
|||||||
- major
|
- major
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
bump-version:
|
||||||
|
name: bump-version
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Configure SSH for Git
|
||||||
|
if: env.ACT != 'true'
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
echo "${{ secrets.RELEASE_BOT_SSH_KEY }}" > ~/.ssh/id_ed25519
|
||||||
|
chmod 600 ~/.ssh/id_ed25519
|
||||||
|
ssh-keyscan -H github.com >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
if: env.ACT != 'true'
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ssh-key: ${{ secrets.RELEASE_BOT_SSH_KEY }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
if: env.ACT == 'true'
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.10'
|
||||||
|
|
||||||
|
- name: Install Commitizen
|
||||||
|
run: |
|
||||||
|
python3 -m pip install --upgrade pip
|
||||||
|
pip3 install commitizen==4.8.3
|
||||||
|
npm install -g conventional-changelog-cli
|
||||||
|
|
||||||
|
- name: Configure Git user
|
||||||
|
run: |
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
|
||||||
|
- name: Bump version with Commitizen
|
||||||
|
run: |
|
||||||
|
cz bump --yes --increment ${{ github.event.inputs.bump_type }}
|
||||||
|
|
||||||
|
- name: Amend commit message to include '[skip ci]'
|
||||||
|
run: |
|
||||||
|
git commit --amend --no-edit -m "$(git log -1 --pretty=%B) [skip ci]"
|
||||||
|
|
||||||
|
- name: Install Rust stable
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
|
- name: Get the new version tag
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
mkdir -p artifacts
|
||||||
|
NEW_TAG=$(cz version --project)
|
||||||
|
echo "New version: $NEW_TAG"
|
||||||
|
echo "version=$NEW_TAG" >> $GITHUB_ENV
|
||||||
|
echo "$NEW_TAG" > artifacts/release-version
|
||||||
|
|
||||||
|
- name: Get the previous version tag
|
||||||
|
id: prev_version
|
||||||
|
run: |
|
||||||
|
PREV_TAG=$(git describe --tags --abbrev=0 ${GITHUB_SHA}^)
|
||||||
|
echo "Previous tag: $PREV_TAG"
|
||||||
|
echo "prev_version=$PREV_TAG" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Bump Cargo.toml version
|
||||||
|
shell: bash
|
||||||
|
working-directory: ${{ github.workspace }}
|
||||||
|
env:
|
||||||
|
VERSION: ${{ env.version }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
: "${VERSION:?env.version is empty}"
|
||||||
|
|
||||||
|
# Ignore Act's local artifact dir noise
|
||||||
|
echo artifacts/ >> .git/info/exclude || true
|
||||||
|
|
||||||
|
# Edit the version line right after name="managarr"
|
||||||
|
sed -E -i '
|
||||||
|
/^[[:space:]]*name[[:space:]]*=[[:space:]]*"managarr"[[:space:]]*$/ {
|
||||||
|
n
|
||||||
|
s|^[[:space:]]*version[[:space:]]*=[[:space:]]*"[^"]*"|version = "'"$VERSION"'"|
|
||||||
|
}
|
||||||
|
' Cargo.toml
|
||||||
|
|
||||||
|
cargo update || true
|
||||||
|
|
||||||
|
# Git config that helps in containers (Act)
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||||
|
|
||||||
|
# Debug: show what changed
|
||||||
|
git status --porcelain
|
||||||
|
git diff --name-only -- Cargo.toml Cargo.lock || true
|
||||||
|
|
||||||
|
# Only commit if one of these files actually changed
|
||||||
|
if ! git diff --quiet -- Cargo.toml Cargo.lock; then
|
||||||
|
# Stage only modifications of already tracked files (won't pick up artifacts/)
|
||||||
|
git add -u -- Cargo.toml Cargo.lock
|
||||||
|
git commit -m "chore: bump Cargo.toml to $VERSION"
|
||||||
|
else
|
||||||
|
echo "No changes to commit (already at $VERSION)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Bump validate_theme_derive/Cargo.toml version
|
||||||
|
shell: bash
|
||||||
|
working-directory: ${{ github.workspace }}/proc_macros/validate_theme_derive
|
||||||
|
env:
|
||||||
|
VERSION: ${{ env.version }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
: "${VERSION:?env.version is empty}"
|
||||||
|
|
||||||
|
# Ignore Act's local artifact dir noise
|
||||||
|
echo artifacts/ >> .git/info/exclude || true
|
||||||
|
|
||||||
|
# Edit the version line right after name="managarr"
|
||||||
|
sed -E -i '
|
||||||
|
/^[[:space:]]*name[[:space:]]*=[[:space:]]*"managarr"[[:space:]]*$/ {
|
||||||
|
n
|
||||||
|
s|^[[:space:]]*version[[:space:]]*=[[:space:]]*"[^"]*"|version = "'"$VERSION"'"|
|
||||||
|
}
|
||||||
|
' Cargo.toml
|
||||||
|
|
||||||
|
cargo update || true
|
||||||
|
|
||||||
|
# Git config that helps in containers (Act)
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||||
|
|
||||||
|
# Debug: show what changed
|
||||||
|
git status --porcelain
|
||||||
|
git diff --name-only -- Cargo.toml || true
|
||||||
|
|
||||||
|
# Only commit if one of these files actually changed
|
||||||
|
if ! git diff --quiet -- Cargo.toml; then
|
||||||
|
# Stage only modifications of already tracked files (won't pick up artifacts/)
|
||||||
|
git add -u -- Cargo.toml
|
||||||
|
git commit -m "chore: bump validate_theme_derive Cargo.toml to $VERSION"
|
||||||
|
else
|
||||||
|
echo "No changes to commit (already at $VERSION)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Bump enum_display_style_derive/Cargo.toml version
|
||||||
|
shell: bash
|
||||||
|
working-directory: ${{ github.workspace }}/proc_macros/enum_display_style_derive
|
||||||
|
env:
|
||||||
|
VERSION: ${{ env.version }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
: "${VERSION:?env.version is empty}"
|
||||||
|
|
||||||
|
# Ignore Act's local artifact dir noise
|
||||||
|
echo artifacts/ >> .git/info/exclude || true
|
||||||
|
|
||||||
|
# Edit the version line right after name="managarr"
|
||||||
|
sed -E -i '
|
||||||
|
/^[[:space:]]*name[[:space:]]*=[[:space:]]*"managarr"[[:space:]]*$/ {
|
||||||
|
n
|
||||||
|
s|^[[:space:]]*version[[:space:]]*=[[:space:]]*"[^"]*"|version = "'"$VERSION"'"|
|
||||||
|
}
|
||||||
|
' Cargo.toml
|
||||||
|
|
||||||
|
cargo update || true
|
||||||
|
|
||||||
|
# Git config that helps in containers (Act)
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||||
|
|
||||||
|
# Debug: show what changed
|
||||||
|
git status --porcelain
|
||||||
|
git diff --name-only -- Cargo.toml || true
|
||||||
|
|
||||||
|
# Only commit if one of these files actually changed
|
||||||
|
if ! git diff --quiet -- Cargo.toml; then
|
||||||
|
# Stage only modifications of already tracked files (won't pick up artifacts/)
|
||||||
|
git add -u -- Cargo.toml
|
||||||
|
git commit -m "chore: bump enum_display_style_derive Cargo.toml to $VERSION"
|
||||||
|
else
|
||||||
|
echo "No changes to commit (already at $VERSION)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Generate changelog for the version bump
|
||||||
|
id: changelog
|
||||||
|
run: |
|
||||||
|
conventional-changelog -p angular -i CHANGELOG.md --from ${{ env.prev_version }} --to v${{ env.version }} > artifacts/changelog.md
|
||||||
|
|
||||||
|
- name: Push changes
|
||||||
|
if: env.ACT != 'true'
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
git push origin --follow-tags
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
path: artifacts
|
||||||
|
|
||||||
|
- name: Upload the changed Cargo files (Act)
|
||||||
|
if: env.ACT == 'true'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: bumped-cargo-files
|
||||||
|
path: |
|
||||||
|
Cargo.toml
|
||||||
|
Cargo.lock
|
||||||
|
proc_macros/validate_theme_derive/Cargo.toml
|
||||||
|
proc_macros/enum_display_style_derive/Cargo.toml
|
||||||
|
|
||||||
build-release-artifacts:
|
build-release-artifacts:
|
||||||
name: build-release
|
name: build-release
|
||||||
|
needs: [bump-version]
|
||||||
runs-on: ${{ matrix.job.os }}
|
runs-on: ${{ matrix.job.os }}
|
||||||
env:
|
env:
|
||||||
RUST_BACKTRACE: 1
|
RUST_BACKTRACE: 1
|
||||||
@@ -44,7 +261,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check if actor is repository owner
|
- name: Check if actor is repository owner
|
||||||
if: ${{ github.actor != github.repository_owner }}
|
if: ${{ github.actor != github.repository_owner && env.ACT != 'true' }}
|
||||||
run: |
|
run: |
|
||||||
echo "You are not authorized to run this workflow."
|
echo "You are not authorized to run this workflow."
|
||||||
exit 1
|
exit 1
|
||||||
@@ -54,6 +271,19 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Ensure repository is up-to-date
|
||||||
|
if: env.ACT != 'true'
|
||||||
|
run: |
|
||||||
|
git fetch --all
|
||||||
|
git pull
|
||||||
|
|
||||||
|
- name: Get bumped Cargo files (Act)
|
||||||
|
if: env.ACT == 'true'
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: bumped-cargo-files
|
||||||
|
path: ${{ github.workspace }}
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
name: Cache Cargo registry
|
name: Cache Cargo registry
|
||||||
with:
|
with:
|
||||||
@@ -124,8 +354,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
# set the right strip executable
|
# set the right strip executable
|
||||||
STRIP="strip";
|
STRIP="strip";
|
||||||
case ${{ matrix.job.target }} in
|
case ${{ matrix.job.target }} in
|
||||||
arm*-linux-*) STRIP="arm-linux-gnueabihf-strip" ;;
|
arm*-linux-*) STRIP="arm-linux-gnueabihf-strip" ;;
|
||||||
aarch64-*-linux-*) STRIP="aarch64-linux-gnu-strip" ;;
|
aarch64-*-linux-*) STRIP="aarch64-linux-gnu-strip" ;;
|
||||||
esac;
|
esac;
|
||||||
cd target/${{ matrix.job.target }}/release
|
cd target/${{ matrix.job.target }}/release
|
||||||
@@ -145,91 +375,52 @@ jobs:
|
|||||||
cp target/${{ matrix.job.target }}/release/${{ env.RELEASE_NAME }}.sha256 artifacts/
|
cp target/${{ matrix.job.target }}/release/${{ env.RELEASE_NAME }}.sha256 artifacts/
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: artifacts
|
name: artifacts-${{ env.RELEASE_NAME }}
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
overwrite: true
|
||||||
|
|
||||||
publish-github-release:
|
publish-github-release:
|
||||||
name: publish-github-release
|
name: publish-github-release
|
||||||
needs: [build-release-artifacts]
|
needs: [build-release-artifacts]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Configure SSH for Git
|
- name: Check if actor is repository owner
|
||||||
|
if: ${{ github.actor != github.repository_owner && env.ACT != 'true' }}
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ~/.ssh
|
echo "You are not authorized to run this workflow."
|
||||||
echo "${{ secrets.RELEASE_BOT_SSH_KEY }}" > ~/.ssh/id_ed25519
|
exit 1
|
||||||
chmod 600 ~/.ssh/id_ed25519
|
|
||||||
ssh-keyscan -H github.com >> ~/.ssh/known_hosts
|
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
ssh-key: ${{ secrets.RELEASE_BOT_SSH_KEY }}
|
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Download all artifacts
|
- name: Download all artifacts
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: artifacts
|
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Ensure repository is up-to-date
|
||||||
uses: actions/setup-python@v4
|
if: env.ACT != 'true'
|
||||||
with:
|
|
||||||
python-version: "3.10"
|
|
||||||
|
|
||||||
- name: Install Commitizen
|
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
git fetch --all
|
||||||
pip install commitizen
|
git pull
|
||||||
npm install -g conventional-changelog-cli
|
|
||||||
|
|
||||||
- name: Configure Git user
|
- name: Set environment variables
|
||||||
run: |
|
run: |
|
||||||
git config user.name "github-actions[bot]"
|
release_version="$(cat ./artifacts/release-version)"
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Bump version with Commitizen
|
- name: Validate release environment variables
|
||||||
run: |
|
run: |
|
||||||
cz bump --yes --increment ${{ github.event.inputs.bump_type }}
|
echo "Release version: ${{ env.RELEASE_VERSION }}"
|
||||||
|
echo "Changelog body: $(cat artifacts/changelog.md)"
|
||||||
- name: Amend commit message to include '[skip ci]'
|
|
||||||
run: |
|
|
||||||
git commit --amend --no-edit -m "$(git log -1 --pretty=%B) [skip ci]"
|
|
||||||
|
|
||||||
- name: Install Rust stable
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
|
|
||||||
- name: Update the Cargo.lock
|
|
||||||
run: |
|
|
||||||
cargo update
|
|
||||||
git add Cargo.lock
|
|
||||||
git commit -m "chore: Bump the version in Cargo.lock"
|
|
||||||
|
|
||||||
- name: Get the new version tag
|
|
||||||
id: version
|
|
||||||
run: |
|
|
||||||
NEW_TAG=$(cz version --project)
|
|
||||||
echo "New version: $NEW_TAG"
|
|
||||||
echo "version=$NEW_TAG" >> $GITHUB_ENV
|
|
||||||
echo "$NEW_TAG" > artifacts/release-version
|
|
||||||
|
|
||||||
- name: Get the previous version tag
|
|
||||||
id: prev_version
|
|
||||||
run: |
|
|
||||||
PREV_TAG=$(git describe --tags --abbrev=0 ${GITHUB_SHA}^)
|
|
||||||
echo "Previous tag: $PREV_TAG"
|
|
||||||
echo "prev_version=$PREV_TAG" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Generate changelog for the version bump
|
|
||||||
id: changelog
|
|
||||||
run: |
|
|
||||||
changelog=$(conventional-changelog -p angular -i CHANGELOG.md -s --from ${{ env.prev_version }} --to ${{ env.version }})
|
|
||||||
echo "$changelog" > changelog.md
|
|
||||||
echo "changelog_body=$(cat changelog.md)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Create a GitHub Release
|
- name: Create a GitHub Release
|
||||||
|
if: env.ACT != 'true'
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -259,40 +450,181 @@ jobs:
|
|||||||
artifacts/managarr-armv7-gnu.sha256
|
artifacts/managarr-armv7-gnu.sha256
|
||||||
artifacts/managarr-armv7-musl.tar.gz
|
artifacts/managarr-armv7-musl.tar.gz
|
||||||
artifacts/managarr-armv7-musl.sha256
|
artifacts/managarr-armv7-musl.sha256
|
||||||
tag_name: v${{ env.version }}
|
tag_name: v${{ env.RELEASE_VERSION }}
|
||||||
name: "v${{ env.version }}"
|
name: 'v${{ env.RELEASE_VERSION }}'
|
||||||
body: ${{ env.changelog_body }}
|
body_path: artifacts/changelog.md
|
||||||
draft: false
|
draft: false
|
||||||
prerelease: false
|
prerelease: false
|
||||||
|
|
||||||
- name: Push changes
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
git push origin --follow-tags
|
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: artifacts
|
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
overwrite: true
|
||||||
|
|
||||||
|
publish-chocolatey-package:
|
||||||
|
needs: [publish-github-release]
|
||||||
|
name: Publish Chocolatey Package
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: Check if actor is repository owner
|
||||||
|
if: ${{ github.actor != github.repository_owner && env.ACT != 'true' }}
|
||||||
|
run: |
|
||||||
|
echo "You are not authorized to run this workflow."
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Get release artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: artifacts
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Set release assets and version
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
# Read the first column from the SHA256 file
|
||||||
|
$windows_sha = Get-Content ./artifacts/managarr-windows.sha256 | ForEach-Object { $_.Split(' ')[0] }
|
||||||
|
Add-Content -Path $env:GITHUB_ENV -Value "WINDOWS_SHA=$windows_sha"
|
||||||
|
|
||||||
|
# Read the release version from the release-version file
|
||||||
|
$release_version = Get-Content ./artifacts/release-version
|
||||||
|
Add-Content -Path $env:GITHUB_ENV -Value "RELEASE_VERSION=$release_version"
|
||||||
|
|
||||||
|
- name: Validate release environment variables
|
||||||
|
run: |
|
||||||
|
echo "Release SHA windows: ${{ env.WINDOWS_SHA }}"
|
||||||
|
echo "Release version: ${{ env.RELEASE_VERSION }}"
|
||||||
|
|
||||||
|
- name: Package and Publish package to Chocolatey
|
||||||
|
if: env.ACT != 'true'
|
||||||
|
run: |
|
||||||
|
mkdir ./deployment/chocolatey/tools
|
||||||
|
# Run packaging script
|
||||||
|
python "./deployment/chocolatey/packager.py" ${{ env.RELEASE_VERSION }} "./deployment/chocolatey/managarr.nuspec.template" "./deployment/chocolatey/managarr.nuspec" ${{ env.WINDOWS_SHA }}
|
||||||
|
python "./deployment/chocolatey/packager.py" ${{ env.RELEASE_VERSION }} "./deployment/chocolatey/chocolateyinstall.ps1.template" "./deployment/chocolatey/tools/chocolateyinstall.ps1" ${{ env.WINDOWS_SHA }}
|
||||||
|
|
||||||
|
# Publish to Chocolatey
|
||||||
|
cd ./deployment/chocolatey
|
||||||
|
choco pack
|
||||||
|
echo y | choco install managarr -dv -s .
|
||||||
|
$version = managarr --version
|
||||||
|
$version = $version -replace " ", "."
|
||||||
|
choco push $version.nupkg -s https://push.chocolatey.org/ --api-key ${{ secrets.CHOCOLATEY_API_KEY }};
|
||||||
|
|
||||||
|
publish-homebrew-formula:
|
||||||
|
needs: [publish-github-release]
|
||||||
|
name: Update Homebrew formulas
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check if actor is repository owner
|
||||||
|
if: ${{ github.actor != github.repository_owner && env.ACT != 'true' }}
|
||||||
|
run: |
|
||||||
|
echo "You are not authorized to run this workflow."
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Get release artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: artifacts
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Set release assets and version
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
# Set environment variables
|
||||||
|
macos_sha="$(cat ./artifacts/managarr-macos.sha256 | awk '{print $1}')"
|
||||||
|
echo "MACOS_SHA=$macos_sha" >> $GITHUB_ENV
|
||||||
|
macos_sha_arm="$(cat ./artifacts/managarr-macos-arm64.sha256 | awk '{print $1}')"
|
||||||
|
echo "MACOS_SHA_ARM=$macos_sha_arm" >> $GITHUB_ENV
|
||||||
|
linux_sha="$(cat ./artifacts/managarr-linux-musl.sha256 | awk '{print $1}')"
|
||||||
|
echo "LINUX_SHA=$linux_sha" >> $GITHUB_ENV
|
||||||
|
release_version="$(cat ./artifacts/release-version)"
|
||||||
|
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Validate release environment variables
|
||||||
|
run: |
|
||||||
|
echo "Release SHA macos: ${{ env.MACOS_SHA }}"
|
||||||
|
echo "Release SHA macos-arm: ${{ env.MACOS_SHA_ARM }}"
|
||||||
|
echo "Release SHA linux musl: ${{ env.LINUX_SHA }}"
|
||||||
|
echo "Release version: ${{ env.RELEASE_VERSION }}"
|
||||||
|
|
||||||
|
- name: Execute Homebrew packaging script
|
||||||
|
if: env.ACT != 'true'
|
||||||
|
run: |
|
||||||
|
# run packaging script
|
||||||
|
python "./deployment/homebrew/packager.py" ${{ env.RELEASE_VERSION }} "./deployment/homebrew/managarr.rb.template" "./managarr.rb" ${{ env.MACOS_SHA }} ${{ env.MACOS_SHA_ARM }} ${{ env.LINUX_SHA }}
|
||||||
|
|
||||||
|
- name: Push changes to Homebrew tap
|
||||||
|
if: env.ACT != 'true'
|
||||||
|
env:
|
||||||
|
TOKEN: ${{ secrets.MANAGARR_GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
# push to Git
|
||||||
|
git config --global user.name "Dark-Alex-17"
|
||||||
|
git config --global user.email "alex.j.tusa@gmail.com"
|
||||||
|
git clone https://Dark-Alex-17:${{ secrets.MANAGARR_GITHUB_TOKEN }}@github.com/Dark-Alex-17/homebrew-managarr.git
|
||||||
|
rm homebrew-managarr/Formula/managarr.rb
|
||||||
|
cp managarr.rb homebrew-managarr/Formula
|
||||||
|
cd homebrew-managarr
|
||||||
|
git add .
|
||||||
|
git diff-index --quiet HEAD || git commit -am "Update formula for Managarr release ${{ env.RELEASE_VERSION }}"
|
||||||
|
git push https://$TOKEN@github.com/Dark-Alex-17/homebrew-managarr.git
|
||||||
|
|
||||||
publish-docker-image:
|
publish-docker-image:
|
||||||
needs: [build-release-artifacts]
|
needs: [publish-github-release]
|
||||||
name: Publishing Docker image to Docker Hub
|
name: Publishing Docker image to Docker Hub
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Get release artifacts
|
- name: Check if actor is repository owner
|
||||||
uses: actions/download-artifact@v3
|
if: ${{ github.actor != github.repository_owner && env.ACT != 'true' }}
|
||||||
|
run: |
|
||||||
|
echo "You are not authorized to run this workflow."
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Get release artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: artifacts
|
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Ensure repository is up-to-date
|
||||||
|
if: env.ACT != 'true'
|
||||||
|
run: |
|
||||||
|
git fetch --all
|
||||||
|
git pull
|
||||||
|
|
||||||
|
- name: Set version variable
|
||||||
|
run: |
|
||||||
|
version="$(cat artifacts/release-version)"
|
||||||
|
echo "version=$version" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Validate release environment variables
|
- name: Validate release environment variables
|
||||||
run: |
|
run: |
|
||||||
echo "Release version: ${{ env.version }}"
|
echo "Release version: ${{ env.version }}"
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
|
if: env.ACT != 'true'
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
@@ -301,15 +633,11 @@ jobs:
|
|||||||
- name: Push to Docker Hub
|
- name: Push to Docker Hub
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
tags: darkalex17/managarr:latest, darkalex17/managarr:x86_64, darkalex17/managarr:x86_64-${{ env.version }}
|
context: .
|
||||||
push: true
|
file: Dockerfile
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
- name: Push to Docker Hub
|
push: ${{ env.ACT != 'true' }}
|
||||||
uses: docker/build-push-action@v5
|
tags: darkalex17/managarr:latest, darkalex17/managarr:${{ env.version }}
|
||||||
with:
|
|
||||||
file: Dockerfile.arm64
|
|
||||||
tags: darkalex17/managarr:arm64, darkalex17/managarr:arm64-${{ env.version }}
|
|
||||||
push: true
|
|
||||||
|
|
||||||
publish-crate:
|
publish-crate:
|
||||||
needs: publish-github-release
|
needs: publish-github-release
|
||||||
@@ -317,7 +645,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check if actor is repository owner
|
- name: Check if actor is repository owner
|
||||||
if: ${{ github.actor != github.repository_owner }}
|
if: ${{ github.actor != github.repository_owner && env.ACT != 'true' }}
|
||||||
run: |
|
run: |
|
||||||
echo "You are not authorized to run this workflow."
|
echo "You are not authorized to run this workflow."
|
||||||
exit 1
|
exit 1
|
||||||
@@ -327,7 +655,15 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Get bumped Cargo files (Act)
|
||||||
|
if: env.ACT == 'true'
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: bumped-cargo-files
|
||||||
|
path: ${{ github.workspace }}
|
||||||
|
|
||||||
- name: Ensure repository is up-to-date
|
- name: Ensure repository is up-to-date
|
||||||
|
if: env.ACT != 'true'
|
||||||
run: |
|
run: |
|
||||||
git fetch --all
|
git fetch --all
|
||||||
git pull
|
git pull
|
||||||
@@ -347,5 +683,6 @@ jobs:
|
|||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
- uses: katyo/publish-crates@v2
|
- uses: katyo/publish-crates@v2
|
||||||
|
if: env.ACT != 'true'
|
||||||
with:
|
with:
|
||||||
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||||
|
|||||||
@@ -48,51 +48,51 @@ jobs:
|
|||||||
- name: cargo test --locked
|
- name: cargo test --locked
|
||||||
run: cargo test --locked --all-features --all-targets
|
run: cargo test --locked --all-features --all-targets
|
||||||
|
|
||||||
minimal-versions:
|
# minimal-versions:
|
||||||
# This action chooses the oldest version of the dependencies permitted by Cargo.toml to ensure
|
# # This action chooses the oldest version of the dependencies permitted by Cargo.toml to ensure
|
||||||
# that this crate is compatible with the minimal version that this crate and its dependencies
|
# # that this crate is compatible with the minimal version that this crate and its dependencies
|
||||||
# require. This will pickup issues where this create relies on functionality that was introduced
|
# # require. This will pickup issues where this create relies on functionality that was introduced
|
||||||
# later than the actual version specified (e.g., when we choose just a major version, but a
|
# # later than the actual version specified (e.g., when we choose just a major version, but a
|
||||||
# method was added after this version).
|
# # method was added after this version).
|
||||||
#
|
# #
|
||||||
# This particular check can be difficult to get to succeed as often transitive dependencies may
|
# # This particular check can be difficult to get to succeed as often transitive dependencies may
|
||||||
# be incorrectly specified (e.g., a dependency specifies 1.0 but really requires 1.1.5). There
|
# # be incorrectly specified (e.g., a dependency specifies 1.0 but really requires 1.1.5). There
|
||||||
# is an alternative flag available -Zdirect-minimal-versions that uses the minimal versions for
|
# # is an alternative flag available -Zdirect-minimal-versions that uses the minimal versions for
|
||||||
# direct dependencies of this crate, while selecting the maximal versions for the transitive
|
# # direct dependencies of this crate, while selecting the maximal versions for the transitive
|
||||||
# dependencies. Alternatively, you can add a line in your Cargo.toml to artificially increase
|
# # dependencies. Alternatively, you can add a line in your Cargo.toml to artificially increase
|
||||||
# the minimal dependency, which you do with e.g.:
|
# # the minimal dependency, which you do with e.g.:
|
||||||
# ```toml
|
# # ```toml
|
||||||
# # for minimal-versions
|
# # # for minimal-versions
|
||||||
# [target.'cfg(any())'.dependencies]
|
# # [target.'cfg(any())'.dependencies]
|
||||||
# openssl = { version = "0.10.55", optional = true } # needed to allow foo to build with -Zminimal-versions
|
# # openssl = { version = "0.10.55", optional = true } # needed to allow foo to build with -Zminimal-versions
|
||||||
# ```
|
# # ```
|
||||||
# The optional = true is necessary in case that dependency isn't otherwise transitively required
|
# # The optional = true is necessary in case that dependency isn't otherwise transitively required
|
||||||
# by your library, and the target bit is so that this dependency edge never actually affects
|
# # by your library, and the target bit is so that this dependency edge never actually affects
|
||||||
# Cargo build order. See also
|
# # Cargo build order. See also
|
||||||
# https://github.com/jonhoo/fantoccini/blob/fde336472b712bc7ebf5b4e772023a7ba71b2262/Cargo.toml#L47-L49.
|
# # https://github.com/jonhoo/fantoccini/blob/fde336472b712bc7ebf5b4e772023a7ba71b2262/Cargo.toml#L47-L49.
|
||||||
# This action is run on ubuntu with the stable toolchain, as it is not expected to fail
|
# # This action is run on ubuntu with the stable toolchain, as it is not expected to fail
|
||||||
runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
name: ubuntu / stable / minimal-versions
|
# name: ubuntu / stable / minimal-versions
|
||||||
steps:
|
# steps:
|
||||||
- uses: actions/checkout@v4
|
# - uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Rust stable
|
# - name: Install Rust stable
|
||||||
uses: dtolnay/rust-toolchain@stable
|
# uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
- name: Install nightly for -Zdirect-minimal-versions
|
# - name: Install nightly for -Zdirect-minimal-versions
|
||||||
uses: dtolnay/rust-toolchain@nightly
|
# uses: dtolnay/rust-toolchain@nightly
|
||||||
|
|
||||||
- name: rustup default stable
|
# - name: rustup default stable
|
||||||
run: rustup default stable
|
# run: rustup default stable
|
||||||
|
|
||||||
- name: cargo update -Zdirect-minimal-versions
|
# - name: cargo update -Zdirect-minimal-versions
|
||||||
run: cargo +nightly update -Zdirect-minimal-versions
|
# run: cargo +nightly update -Zdirect-minimal-versions
|
||||||
|
|
||||||
- name: cargo test
|
# - name: cargo test
|
||||||
run: cargo test --locked --all-features --all-targets
|
# run: cargo test --locked --all-features --all-targets
|
||||||
|
|
||||||
- name: Cache Cargo dependencies
|
# - name: Cache Cargo dependencies
|
||||||
uses: Swatinem/rust-cache@v2
|
# uses: Swatinem/rust-cache@v2
|
||||||
|
|
||||||
os-check:
|
os-check:
|
||||||
# run cargo test on mac and windows
|
# run cargo test on mac and windows
|
||||||
@@ -175,6 +175,7 @@ jobs:
|
|||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
|
|
||||||
- name: Upload to codecov.io
|
- name: Upload to codecov.io
|
||||||
|
if: env.ACT != 'true'
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@v4
|
||||||
with:
|
with:
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
/target
|
/target
|
||||||
/.idea/
|
/.idea/
|
||||||
/.scannerwork/
|
/.scannerwork/
|
||||||
|
/.act/
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
[keys.normal.backspace]
|
||||||
|
b = ":sh zellij run -x '4%%' --width '92%%' -f -n Build -- just build"
|
||||||
|
r = ":sh zellij run -x '3%%' -y '8%%' --width '95%%' --height '90%%' -fc -n 'Run' -- just run"
|
||||||
|
t = ":sh zellij run -x '4%%' --width '92%%' -f -n Tests -- just test"
|
||||||
|
l = ":sh zellij run -x '4%%' --width '92%%' -f -n Lint -- just lint"
|
||||||
@@ -5,7 +5,274 @@ 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.4.0 (2024-12-14)
|
## 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)
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
- Wrapped all Sonarr use of Language with Option to fix the 'null' array issue in the new Sonarr API
|
||||||
|
|
||||||
|
## v0.6.2 (2025-12-12)
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
- Fixed breaking Sonarr Episode file API calls after recent Sonarr API update
|
||||||
|
|
||||||
|
### Refactor
|
||||||
|
|
||||||
|
- Replaced all modulo usages of tick_until_poll with is_multiple_of
|
||||||
|
|
||||||
|
## v0.6.1 (2025-09-02)
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
- Fixed UI bugs introduced as part of the hotkey refactor
|
||||||
|
|
||||||
|
### Refactor
|
||||||
|
|
||||||
|
- Updated crate to publish properly with the procedural macros
|
||||||
|
|
||||||
|
## v0.6.0 (2025-08-29)
|
||||||
|
|
||||||
|
### Feat
|
||||||
|
|
||||||
|
- Support for custom headers to be added to every request to each server to support alternative authentication mechanisms [#47]
|
||||||
|
- Refactor all keybinding tips into a dynamically changing menu that can be invoked via '?' [#32]
|
||||||
|
- Display total disk usage for series in the Library view to mirror Radarr functionality [#44]
|
||||||
|
- Pagination support for jumping 20 items at a time in all table views [#45]
|
||||||
|
- Support toggling Movie monitoring directly from the library view [#43]
|
||||||
|
- Support toggling Movie monitoring from the CLI
|
||||||
|
- Support toggling Series monitoring directly from the Sonarr library view [#43]
|
||||||
|
- Support toggling Series monitoring from the CLI
|
||||||
|
- Fixed the Radarr downloads tab to display more than 10 downloads at a time and added a new --count flag to the CLI for specifying the number of downloads to return
|
||||||
|
- Fetch more than 10 downloads when listing Sonarr downloads, and add a --count flag to the CLI to specify how many downloads to fetch
|
||||||
|
- Support alternative keymappings for all keys, featuring hjkl movements
|
||||||
|
- Added the Eldritch theme and updated documentation
|
||||||
|
- Write built in themes to the themes file on first run so users can define custom themes
|
||||||
|
- Created a theme validation macro to verify theme configurations before allowing the TUI to start
|
||||||
|
- Initial support for custom user-defined themes
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
- Marked Radarr studios as nullable to prevent crashes
|
||||||
|
- Fixed a bug where the Sonarr API was returning empty values for seeders when searching for season releases
|
||||||
|
- Improve fault tolerance for tag associations in Radarr and Sonarr
|
||||||
|
- Upgraded to the most recent version of Tokio to mitigate CWE-664
|
||||||
|
- Updated all dependencies and updated openssl to the most recent version to mitigate CWE-416
|
||||||
|
- Updated the name of the should_ignore_quit_key to ignore_special_keys_for_textbox_input to give a better idea of what the flag is used for; also added alt keybinding for backspace
|
||||||
|
- Marked videoCodecs as Option to resolve #38
|
||||||
|
- Marked the `Season.statistics` field as `Option` so that a panic does not happen for outdated Sonarr data. This resolves #35
|
||||||
|
- When adding a film from the Collection Details modal, the render order was wrong: Radarr Library -> Collection Table -> Add Movie Prompt (missing the Collection details prompt too). Correct order is: Collection Table -> Collection Details Modal -> Add Movie Modal
|
||||||
|
- Fixed a bug that was rendering encompassing blocks after other widgets were rendered, thus overwriting the custom styles on each previously rendered widget
|
||||||
|
- change the name of the theme configuration file to 'themes'
|
||||||
|
- Ensure key events are only processed on key press to avoid duplicates
|
||||||
|
- Updated ring dependency to mitigate CWE-770
|
||||||
|
- Modified the Sonarr DownloadRecord so that the episode_id is optional to prevent crashes for weird downloads
|
||||||
|
|
||||||
|
### Refactor
|
||||||
|
|
||||||
|
- Network module is now broken out into similar directory structures for each servarr to mimic the rest of the project to make it easier to develop and maintain
|
||||||
|
- Refactored the IndexerTestResut model into the general Servarr models
|
||||||
|
- Renamed 'ctrl-*' keyboard shortcuts to 'C-*' to simplify and shrink the on-screen help
|
||||||
|
- Formatted files using rustfmt
|
||||||
|
- Reformatted code to make the format checks pass
|
||||||
|
- Created a derive macro for defining the display style of Enum models and removed the use of the EnumDisplayStyle trait
|
||||||
|
- Expanded the serde_enum_from macro to further reduce code duplication
|
||||||
|
|
||||||
|
## v0.5.1 (2025-03-01)
|
||||||
|
|
||||||
|
### Feat
|
||||||
|
|
||||||
|
- CLI Support for multiple Servarr instances
|
||||||
|
- Support for multiple servarr definitions - no tests [skip ci]
|
||||||
|
- Support for loading Servarr API tokens from a file
|
||||||
|
- Tweaked the implementation for environment variables in the config a bit
|
||||||
|
- var interpolation
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
- Updated openssl to 0.10.70 to mitigate CVE-2025-24898
|
||||||
|
- Addressed rustfmt complaints
|
||||||
|
- Corrected typo in the managarr.nuspec.template
|
||||||
|
|
||||||
|
### Refactor
|
||||||
|
|
||||||
|
- Updated dependencies
|
||||||
|
- Addressed Cargo fmt complaints
|
||||||
|
- Added a debug line for logging to output the config used when starting Managarr
|
||||||
|
- Updated the 2018 idiom lint to the 2021_compatibility lint
|
||||||
|
- Removed unnecessary clones in the networking module to speed up network request handling
|
||||||
|
- Corrected some clone instead of copy behaviors in the command line handlers
|
||||||
|
- Removed unnecessary clone from stateful table
|
||||||
|
- Removed unnecessary clone call from extract_and_add_tag_ids_vec method
|
||||||
|
- Reduced the number of clones necessary when building modal structs
|
||||||
|
- Refactored a handful of Option calls to use take instead
|
||||||
|
- Renamed KeyEventHandler::with to KeyEventHandler::new to keep with Rust best practices and conventions
|
||||||
|
|
||||||
|
## v0.4.2 (2024-12-21)
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
- Revert failed release [skip ci]
|
||||||
|
- **sonarr**: Pass the series ID alongside all UpdateAndScan events when publishing to the networking channel
|
||||||
|
- **sonarr**: pass the series ID alongside all TriggerAutomaticSeriesSearch events when publishing to the networking channel
|
||||||
|
- **sonarr**: Pass the series ID and season number alongside all TriggerAutomaticSeasonSearch events when publishing to the networking channel
|
||||||
|
- **sonarr**: Pass the episode ID alongside all TriggerAutomaticEpisodeSearch events when publishing to the networking channel
|
||||||
|
- **sonarr**: Pass the episode ID alongside all ToggleEpisodeMonitoring events when publishing to the networking channel
|
||||||
|
- **sonarr**: Pass the series ID and season number alongside all toggle season monitoring events when publishing to the networking channel
|
||||||
|
- **sonarr**: Pass the indexer ID directly alongside all TestIndexer events when publishing to the networking channel
|
||||||
|
- **sonarr**: Provide the task name directly alongside all StartTask events when publishing to the networking channel
|
||||||
|
- **sonarr**: Pass the search query directly to the networking channel when searching for a new series
|
||||||
|
- **sonarr**: Pass the series ID alongside all GetSeriesHistory events when publishing to the networking channel
|
||||||
|
- **sonarr**: Pass the series ID alongside all GetSeriesDetails events when publishing to the networking channel
|
||||||
|
- **sonarr**: Pass series ID and season number alongside all ManualSeasonSearch events when publishing to the networking channel
|
||||||
|
- **sonarr**: Provide the series ID and season number alongside all GetSeasonHistory events when publishing to the networking channel
|
||||||
|
- **sonarr**: Pass the episode ID alongside all ManualEpisodeSearch events when publishing to the networking channel
|
||||||
|
- **sonarr**: Pass events alongside all GetLogs events when publishing to the networking channel
|
||||||
|
- **sonarr**: Pass the episode ID alongside all GetEpisodeHistory events when publishing to the networking channel
|
||||||
|
- **sonarr**: Pass series ID alongside all GetEpisodeFiles events when publishing to the networking channel
|
||||||
|
- **sonarr**: Pass series ID alognside all GetEpisodes events when publishing to the networking channel
|
||||||
|
- **sonarr**: Pass the episode ID alongside all GetEpisodeDetails events when publishing to the networking channel
|
||||||
|
- **sonarr**: Pass history events alongside all GetHistory events when publishing to the networking channel
|
||||||
|
- **sonarr**: Construct and pass edit series parameters alongside all EditSeries events when publishing to the networking channel
|
||||||
|
- **sonarr**: Construct and pass edit indexer parameters alongside all EditIndexer events when publishing to the networking channel
|
||||||
|
- **sonarr**: Construct and pass edit all indexer settings alongside all EditAllIndexerSettings events when publishing to the networking channel
|
||||||
|
- **sonarr**: Construct and pass delete series params alongside all DeleteSeries events when publishing to the networking channel
|
||||||
|
- **sonarr**: Corrected a bug that would cause a crash if a user spams the ESC key while searching for a new series and the search results are still loading
|
||||||
|
- **sonarr**: Pass the root folder ID alongside all DeleteRootFolder events when publishing to the networking channel
|
||||||
|
- **sonarr**: Pass the indexer ID alongside all DeleteIndexer events when publishing to the networking channel
|
||||||
|
- **sonarr**: Pass the episode file ID alongside all DeleteEpisodeFile events when publishing to the networking channel
|
||||||
|
- **sonarr**: Pass the download ID alongside all DeleteDownload events published to the networking channel
|
||||||
|
- **sonarr**: Pass the blocklist item ID alongside the DeleteBlocklistItem event when publishing to the networking channel
|
||||||
|
- **sonarr**: Construct and pass the add series body alongside AddSeries events when publishing to the networking channel
|
||||||
|
- **sonarr**: Construct and pass the AddRootFolderBody alongside all AddRootFolder events when publishing to the networking channel
|
||||||
|
- **radarr**: Pass the movie ID alongside all UpdateAndScan events published to the networking channel
|
||||||
|
- **radarr**: Provide the movie ID alongside all TriggerAutomaticMovieSearch events when publishing to the networking channel
|
||||||
|
- **radarr**: Pass in the indexer id with all TestIndexer events when publishing to the networking channel
|
||||||
|
- **radarr**: Pass in the task name alongside the StartTask event when publishing to the networking channel
|
||||||
|
- **radarr**: Pass in the search query for the SearchNewMovie event when publishing to the networking channel
|
||||||
|
- **radarr**: Pass in the movie ID alongside the GetReleases event when publishing to the networking channel
|
||||||
|
- **radarr**: Pass in the movie ID alongside the GetMovieHistory event when publishing to the networking channel
|
||||||
|
- **radarr**: Pass the movie ID in alongside the GetMovieDetaisl event when publishing to the networking channel
|
||||||
|
- **radarr**: Provide the movie id alongside the GetMovieCredits event when publishing to the networking channel
|
||||||
|
- **radarr**: Pass the number of log events to fetch in with the GetLogs event when publishing to the networking channel
|
||||||
|
- **radarr**: Construct and pass the edit movie parameters alongside the EditMovie event when publishing to the networking channel
|
||||||
|
- **radarr**: Construct and pass params when publishing the EditIndexer event to the networking channel
|
||||||
|
- **radarr**: Construct and pass edit collection parameters alongside the EditCollection event when publishing to the networking channel
|
||||||
|
- **radarr**: Build and pass the edit indexer settings body with the EditAllIndexerSettings event when publishing to the networking channel
|
||||||
|
- **radarr**: Send the parameters alongside the DownloadRelease event when publishing to the networking channel
|
||||||
|
- **radarr**: Pass the root folder ID in with the DeleteRootFolder event when publishing to the networking channel
|
||||||
|
- Pass the delete movie params in with the DeleteMovie event when publishing to the networking channel
|
||||||
|
- Pass the indexer ID in with the DeleteIndexer event when sending to the networking channel
|
||||||
|
- Pass the download ID directly in the DeleteDownload event when publishing into the networking channel
|
||||||
|
- Blocklist Item ID passed in the DeleteBlocklistItem event when sent to the networking channel
|
||||||
|
- AddRootFolderBody now constructed prior to AddRootFolder event being sent down the network channel
|
||||||
|
- Cancel all requests when switching Servarr tabs to both improve performance and fix issue #15
|
||||||
|
- **add_movie_handler_tests**: Added in a forgotten test for the build_add_movie_body function
|
||||||
|
- Missing tagged version of docker builds in release flow
|
||||||
|
- AddMovie Radarr event is now populated in the dispatch thread before being sent to the network thread
|
||||||
|
- dynamically load servarrs in UI based on what configs are provided
|
||||||
|
|
||||||
|
## v0.4.1 (2024-12-14)
|
||||||
|
|
||||||
### Feat
|
### Feat
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,23 @@
|
|||||||
# Contributing
|
# Contributing
|
||||||
Contributors are very welcome! **No contribution is too small and all contributions are valued.**
|
Contributors are very welcome! **No contribution is too small and all contributions are valued.**
|
||||||
|
|
||||||
|
## License and Attribution
|
||||||
|
|
||||||
|
#### _If you plan on contributing to the base project, no attribution is needed!_ Feel free to proceed to the [next steps](CONTRIBUTING.md#Rust).
|
||||||
|
|
||||||
|
Otherwise, below are key points to understand from the [Managarr License, Version 1.0](LICENSE):
|
||||||
|
1. **Non-Commercial Use**:
|
||||||
|
- Managarr is licensed solely for non-commercial purposes. Any commercial use of Managarr (e.g., selling or offering as a paid service) requires separate permission.
|
||||||
|
|
||||||
|
2. **Attribution when Forking and Redistributing Without Contributing back to Main Project**:
|
||||||
|
- **If you fork the project and distribute it separately** (e.g., publish or _publicly_ host it independently from the original project), you are required to provide attribution.
|
||||||
|
- You may credit the original author by using any of the following phrasing:
|
||||||
|
- "Thanks to Alexander J. Clarke (Dark-Alex-17) for creating the original Managarr project!"
|
||||||
|
- "Forked from the Managarr project, created by Alexander J. Clarke (Dark-Alex-17)"
|
||||||
|
- "This software is based on the original Managarr project by Alexander J. Clarke (Dark-Alex-17)"
|
||||||
|
- "Inspired by Alexander J. Clarke (Dark-Alex-17)'s Managarr project"
|
||||||
|
- If changes are made to the base Managarr project, please note those modifications and provide the new attribution accordingly.
|
||||||
|
|
||||||
## Rust
|
## Rust
|
||||||
You'll need to have the stable Rust toolchain installed in order to develop Managarr.
|
You'll need to have the stable Rust toolchain installed in order to develop Managarr.
|
||||||
|
|
||||||
@@ -46,12 +63,40 @@ cz commit
|
|||||||
## Setup workspace
|
## Setup workspace
|
||||||
|
|
||||||
1. Clone this repo
|
1. Clone this repo
|
||||||
2. Run `cargo test` to setup hooks
|
2. Run `cargo test` to set up hooks
|
||||||
3. Make changes
|
3. Make changes
|
||||||
4. Run the application using `make run` or `cargo run`
|
4. Run the application using `just run` or `just run`
|
||||||
|
- Install `just` (`cargo install just`) if you haven't already to use the [justfile](./justfile) in this project.
|
||||||
5. Commit changes. This will trigger pre-commit hooks that will run format, test and lint. If there are errors or warnings from Clippy, please fix them.
|
5. Commit changes. This will trigger pre-commit hooks that will run format, test and lint. If there are errors or warnings from Clippy, please fix them.
|
||||||
6. Push your code to a new branch named after the feature/bug/etc. you're adding. This will trigger pre-push hooks that will run lint and test.
|
6. Push your code to a new branch named after the feature/bug/etc. you're adding. This will trigger pre-push hooks that will run lint and test.
|
||||||
7. Create a PR
|
7. Create a PR
|
||||||
|
|
||||||
|
### CI/CD Testing with Act
|
||||||
|
If you also are planning on testing out your changes before pushing them with [Act](https://github.com/nektos/act), you will need to set up `act`,
|
||||||
|
`docker`, and configure your local system to run different architectures:
|
||||||
|
|
||||||
|
1. Install `docker` by following the instructions on the [official Docker installation page](https://docs.docker.com/get-docker/).
|
||||||
|
2. Install `act` by following the instructions on the [official Act installation page](https://nektosact.com/installation/index.html).
|
||||||
|
3. Install `binfmt` on your system once so that `act` can run the correct architecture for the CI/CD workflows.
|
||||||
|
You can do this by running:
|
||||||
|
```shell
|
||||||
|
sudo docker run --rm --privileged tonistiigi/binfmt --install all
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, you can run workflows locally without having to commit and see if the GitHub action passes or fails.
|
||||||
|
|
||||||
|
**For example**: To test the [release.yml](.github/workflows/release.yml) workflow locally, you can run:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
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!
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "managarr"
|
name = "managarr"
|
||||||
version = "0.4.0"
|
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"]
|
||||||
@@ -8,62 +8,79 @@ documentation = "https://github.com/Dark-Alex-17/managarr"
|
|||||||
repository = "https://github.com/Dark-Alex-17/managarr"
|
repository = "https://github.com/Dark-Alex-17/managarr"
|
||||||
homepage = "https://github.com/Dark-Alex-17/managarr"
|
homepage = "https://github.com/Dark-Alex-17/managarr"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
rust-version = "1.82.0"
|
rust-version = "1.89.0"
|
||||||
exclude = [".github", "CONTRIBUTING.md", "*.log", "tags"]
|
exclude = [".github", "CONTRIBUTING.md", "*.log", "tags"]
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"proc_macros/enum_display_style_derive",
|
||||||
|
"proc_macros/validate_theme_derive",
|
||||||
|
]
|
||||||
|
|
||||||
[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.12.28", 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.36.0", features = ["full"] }
|
tokio = { version = "1.49.0", features = ["full"] }
|
||||||
tokio-util = "0.7.8"
|
tokio-util = "0.7.18"
|
||||||
ratatui = { version = "0.29.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 = ["derive", "cargo", "env"] }
|
clap = { version = "4.5.56", features = [
|
||||||
clap_complete = "4.5.33"
|
"derive",
|
||||||
itertools = "0.13.0"
|
"cargo",
|
||||||
ctrlc = "3.4.5"
|
"env",
|
||||||
colored = "2.1.0"
|
"wrap_help",
|
||||||
async-trait = "0.1.83"
|
] }
|
||||||
|
clap_complete = "4.5.65"
|
||||||
|
itertools = "0.14.0"
|
||||||
|
ctrlc = "3.5.1"
|
||||||
|
colored = "3.1.1"
|
||||||
|
async-trait = "0.1.89"
|
||||||
dirs-next = "2.0.0"
|
dirs-next = "2.0.0"
|
||||||
managarr-tree-widget = "0.24.0"
|
managarr-tree-widget = "0.25.0"
|
||||||
indicatif = "0.17.9"
|
indicatif = "0.17.11"
|
||||||
derive_setters = "0.1.6"
|
derive_setters = "0.1.9"
|
||||||
deunicode = "1.6.0"
|
deunicode = "1.6.2"
|
||||||
paste = "1.0.15"
|
openssl = { version = "0.10.75", features = ["vendored"] }
|
||||||
openssl = { version = "0.10.68", features = ["vendored"] }
|
veil = "0.2.0"
|
||||||
|
validate_theme_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"
|
||||||
rstest = "0.23.0"
|
proptest = "1.9.0"
|
||||||
|
rstest = "0.25.0"
|
||||||
|
serial_test = "3.3.1"
|
||||||
|
assertables = "9.8.4"
|
||||||
|
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"]
|
||||||
|
|
||||||
@@ -74,4 +91,6 @@ name = "managarr"
|
|||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
codegen-units = 1
|
codegen-units = 3
|
||||||
|
opt-level = "s"
|
||||||
|
strip = true
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM messense/rust-musl-cross:x86_64-musl AS builder
|
FROM rust:1.89 AS builder
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
|
|
||||||
# Download and compile Rust dependencies in an empty project and cache as a separate Docker layer
|
# Download and compile Rust dependencies in an empty project and cache as a separate Docker layer
|
||||||
@@ -6,17 +6,18 @@ RUN USER=root cargo new --bin managarr-temp
|
|||||||
|
|
||||||
WORKDIR /usr/src/managarr-temp
|
WORKDIR /usr/src/managarr-temp
|
||||||
COPY Cargo.* .
|
COPY Cargo.* .
|
||||||
|
COPY proc_macros ./proc_macros
|
||||||
RUN cargo build --release
|
RUN cargo build --release
|
||||||
# remove src from empty project
|
# remove src from empty project
|
||||||
RUN rm -r src
|
RUN rm -r src
|
||||||
COPY src ./src
|
COPY src ./src
|
||||||
# remove previous deps
|
# remove previous deps
|
||||||
RUN rm ./target/x86_64-unknown-linux-musl/release/deps/managarr*
|
RUN rm ./target/release/deps/managarr*
|
||||||
|
|
||||||
RUN --mount=type=cache,target=/volume/target \
|
RUN --mount=type=cache,target=/volume/target \
|
||||||
--mount=type=cache,target=/root/.cargo/registry \
|
--mount=type=cache,target=/root/.cargo/registry \
|
||||||
cargo build --release --bin managarr
|
cargo build --release --bin managarr
|
||||||
RUN mv target/x86_64-unknown-linux-musl/release/managarr .
|
RUN mv target/release/managarr .
|
||||||
|
|
||||||
FROM debian:stable-slim
|
FROM debian:stable-slim
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
FROM messense/rust-musl-cross:armv7-musleabihf AS builder
|
|
||||||
WORKDIR /usr/src
|
|
||||||
|
|
||||||
# Download and compile Rust dependencies in an empty project and cache as a separate Docker layer
|
|
||||||
RUN USER=root cargo new --bin managarr-temp
|
|
||||||
RUN apt update && apt install -y libssl-dev pkg-config
|
|
||||||
|
|
||||||
WORKDIR /usr/src/managarr-temp
|
|
||||||
COPY Cargo.* .
|
|
||||||
RUN cargo build --release
|
|
||||||
# remove src from empty project
|
|
||||||
RUN rm -r src
|
|
||||||
COPY src ./src
|
|
||||||
# remove previous deps
|
|
||||||
RUN rm ./target/armv7-unknown-linux-musleabihf/release/deps/managarr*
|
|
||||||
|
|
||||||
RUN --mount=type=cache,target=/volume/target \
|
|
||||||
--mount=type=cache,target=/root/.cargo/registry \
|
|
||||||
cargo build --release --bin managarr
|
|
||||||
RUN mv target/armv7-unknown-linux-musleabihf/release/managarr .
|
|
||||||
|
|
||||||
FROM debian:stable-slim
|
|
||||||
|
|
||||||
# Copy the compiled binary from the builder container
|
|
||||||
COPY --from=builder --chown=nonroot:nonroot /usr/src/managarr-temp/managarr /usr/local/bin
|
|
||||||
|
|
||||||
ENTRYPOINT [ "/usr/local/bin/managarr" ]
|
|
||||||
@@ -1,16 +1,53 @@
|
|||||||
MIT License
|
Managarr License
|
||||||
|
Version 1.0, 2025
|
||||||
|
|
||||||
Copyright (c) 2023 Alexander J. Clarke
|
Copyright (c) 2025 Alexander J. Clarke (Dark-Alex-17)
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to use,
|
||||||
in the Software without restriction, including without limitation the rights
|
copy, modify, merge, publish, and distribute the Software solely for
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
non-commercial purposes, subject to the following conditions:
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
1. Attribution:
|
||||||
copies or substantial portions of the Software.
|
- The above copyright notice, this permission notice, and a prominent notice stating
|
||||||
|
that the Software is part of the "Managarr" project shall be included in all copies or
|
||||||
|
substantial portions of the Software **when the Software is forked and redistributed separately** from the original project.
|
||||||
|
- If you fork the software and **distribute it separately** without merging it back into the original base project (the Managarr repository), you must provide attribution to the original author.
|
||||||
|
You may use any of the following forms of attribution:
|
||||||
|
- "Thanks to Alexander J. Clarke (Dark-Alex-17) for creating the original Managarr project!"
|
||||||
|
- "Forked from the Managarr project, created by Alexander J. Clarke (Dark-Alex-17)"
|
||||||
|
- "This software is based on the original Managarr project by Alexander J. Clarke (Dark-Alex-17)"
|
||||||
|
- "Inspired by Alexander J. Clarke (Dark-Alex-17)'s Managarr project"
|
||||||
|
- If you modify the software, the attribution must also note that changes were made and describe those modifications, if feasible.
|
||||||
|
|
||||||
|
2. Non-Commercial Use Only:
|
||||||
|
The use of this Software for commercial purposes, including but not limited
|
||||||
|
to sale, licensing, or use in any product or service for monetary
|
||||||
|
compensation, is strictly prohibited without prior written permission from
|
||||||
|
Alexander J. Clarke (Dark-Alex-17).
|
||||||
|
|
||||||
|
For avoidance of doubt:
|
||||||
|
- **Allowed:** Private use, educational purposes, research, or any usage
|
||||||
|
that does not directly generate revenue.
|
||||||
|
- **Prohibited:** Selling, sublicensing, or incorporating the Software into
|
||||||
|
commercial products or services.
|
||||||
|
|
||||||
|
3. Modifications and Derivatives:
|
||||||
|
- Any modifications or derivative works based on this Software must clearly
|
||||||
|
document all changes made and prominently credit the original "Managarr"
|
||||||
|
project as described in section 1.
|
||||||
|
- Derivative works must retain this license, the original copyright notice,
|
||||||
|
and all terms and conditions described herein. This applies to the entire
|
||||||
|
derivative work, even if combined with other software.
|
||||||
|
|
||||||
|
4. Warranty Disclaimer:
|
||||||
|
This Software is provided "as is," without warranty of any kind, express
|
||||||
|
or implied, including but not limited to the warranties of merchantability,
|
||||||
|
fitness for a particular purpose, and noninfringement. In no event shall the
|
||||||
|
authors or copyright holders be liable for any claim, damages, or other
|
||||||
|
liability, whether in an action of contract, tort, or otherwise, arising
|
||||||
|
from, out of, or in connection with the Software or the use or other
|
||||||
|
dealings in the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
#!make
|
|
||||||
VERSION := latest
|
|
||||||
IMG_NAME := darkalex17/managarr
|
|
||||||
IMAGE := ${IMG_NAME}:${VERSION}
|
|
||||||
|
|
||||||
default: run
|
|
||||||
|
|
||||||
.PHONY: test test-cov build run lint lint-fix fmt analyze sonar release delete-tag
|
|
||||||
|
|
||||||
test:
|
|
||||||
@cargo test
|
|
||||||
|
|
||||||
## Run all tests with coverage - `cargo install cargo-tarpaulin`
|
|
||||||
test-cov:
|
|
||||||
@cargo tarpaulin
|
|
||||||
|
|
||||||
build: test
|
|
||||||
@cargo build --release
|
|
||||||
|
|
||||||
docker:
|
|
||||||
@DOCKER_BUILDKIT=1 docker build --rm -t ${IMAGE} .
|
|
||||||
|
|
||||||
run:
|
|
||||||
@CARGO_INCREMENTAL=1 cargo fmt && make lint && cargo run
|
|
||||||
|
|
||||||
lint:
|
|
||||||
@find . | grep '\.\/src\/.*\.rs$$' | xargs touch && CARGO_INCREMENTAL=0 cargo clippy --all-targets --workspace
|
|
||||||
|
|
||||||
lint-fix:
|
|
||||||
@cargo fix
|
|
||||||
|
|
||||||
fmt:
|
|
||||||
@cargo fmt
|
|
||||||
|
|
||||||
minimal-versions:
|
|
||||||
@cargo +nightly update -Zdirect-minimal-versions
|
|
||||||
|
|
||||||
## Analyze for unsafe usage - `cargo install cargo-geiger`
|
|
||||||
analyze:
|
|
||||||
@cargo geiger
|
|
||||||
|
|
||||||
release:
|
|
||||||
@git tag -a ${V} -m "Release ${V}" && git push origin ${V}
|
|
||||||
|
|
||||||
delete-tag:
|
|
||||||
@git tag -d ${V} && git push --delete origin ${V}
|
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# managarr - A TUI and CLI to manage your Servarrs
|
# managarr - A TUI and CLI to manage your Servarrs
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
[](https://crates.io/crates/managarr)
|
[](https://crates.io/crates/managarr)
|
||||||
@@ -10,23 +10,24 @@
|
|||||||

|

|
||||||
[](https://github.com/Dark-Alex-17/managarr/releases)
|
[](https://github.com/Dark-Alex-17/managarr/releases)
|
||||||

|

|
||||||
|
[](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/)
|
||||||
- [ ]  [Tautulli](https://tautulli.com/)
|
- [ ]  [Tautulli](https://tautulli.com/)
|
||||||
|
|
||||||
## Try Before You Buy
|
## Try Out the Demo
|
||||||
To try out Managarr before linking it to your HTPC, you can use the purpose built [managarr-demo](https://github.com/Dark-Alex-17/managarr-demo) repository.
|
To try out Managarr before linking it to your HTPC, you can use the purpose built [managarr-demo](https://github.com/Dark-Alex-17/managarr-demo) repository.
|
||||||
Simply run the following command to start a demo:
|
Simply run the following command to start a demo:
|
||||||
|
|
||||||
@@ -34,7 +35,10 @@ Simply run the following command to start a demo:
|
|||||||
curl https://raw.githubusercontent.com/Dark-Alex-17/managarr-demo/main/managarr-demo.sh > /tmp/managarr-demo.sh && bash /tmp/managarr-demo.sh
|
curl https://raw.githubusercontent.com/Dark-Alex-17/managarr-demo/main/managarr-demo.sh > /tmp/managarr-demo.sh && bash /tmp/managarr-demo.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Alternatively, you can try out the demo container without downloading anything by visiting the [Managarr Demo site](https://managarr-demo.alexjclarke.com).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Cargo
|
### Cargo
|
||||||
If you have Cargo installed, then you can install Managarr from Crates.io:
|
If you have Cargo installed, then you can install Managarr from Crates.io:
|
||||||
|
|
||||||
@@ -51,17 +55,57 @@ 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
|
||||||
```
|
```
|
||||||
|
|
||||||
For ARM64 users, you can use the `arm64` tag:
|
You can also clone this repo and run `just build-docker` to build a docker image locally and run it using the above command.
|
||||||
```shell
|
|
||||||
docker run --rm -it -v /home/aclarke/.config/managarr/config.yml:/root/.config/managarr/config.yml darkalex17/managarr:arm64
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also clone this repo and run `make docker` to build a docker image locally and run it using the above command.
|
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.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
**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.
|
||||||
|
|
||||||
|
### Homebrew (Mac and Linux)
|
||||||
|
To install Managarr from Homebrew, install the Managarr tap. Then you'll be able to install Managarr:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
brew tap Dark-Alex-17/managarr
|
||||||
|
brew install managarr
|
||||||
|
|
||||||
|
# If you need to be more specific, use the following:
|
||||||
|
brew install Dark-Alex-17/managarr/managarr
|
||||||
|
```
|
||||||
|
|
||||||
|
To upgrade to a newer version of Managarr:
|
||||||
|
```shell
|
||||||
|
brew upgrade managarr
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nix (Externally Maintained)
|
||||||
|
To install Managarr on NixOS, you can use the following command:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
nix-env --install managarr
|
||||||
|
|
||||||
|
# Alternatively, for non-NixOS users, you can spawn a temporary shell with Managarr available like so:
|
||||||
|
nix-shell -p managarr
|
||||||
|
```
|
||||||
|
|
||||||
|
### Chocolatey (Windows)
|
||||||
|
The Managarr Chocolatey package is located [here](https://community.chocolatey.org/packages/managarr). Please note that validation
|
||||||
|
of Chocolatey packages take quite some time, and thus the package may not be available immediately after a new release.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
choco install managarr
|
||||||
|
|
||||||
|
# Some newer releases may require a version number, so you can specify it like so:
|
||||||
|
choco install managarr --version=0.7.0
|
||||||
|
```
|
||||||
|
|
||||||
|
To upgrade to the latest and greatest version of Managarr:
|
||||||
|
```powershell
|
||||||
|
choco upgrade managarr
|
||||||
|
|
||||||
|
# To upgrade to a specific version:
|
||||||
|
choco upgrade managarr --version=0.7.0
|
||||||
|
```
|
||||||
|
|
||||||
### Manual
|
### Manual
|
||||||
Binaries are available on the [releases](https://github.com/Dark-Alex-17/managarr/releases) page for the following platforms:
|
Binaries are available on the [releases](https://github.com/Dark-Alex-17/managarr/releases) page for the following platforms:
|
||||||
|
|
||||||
@@ -74,16 +118,16 @@ Binaries are available on the [releases](https://github.com/Dark-Alex-17/managar
|
|||||||
#### Windows Instructions
|
#### Windows Instructions
|
||||||
To use a binary from the releases page on Windows, do the following:
|
To use a binary from the releases page on Windows, do the following:
|
||||||
|
|
||||||
1. Download the latest binary [binary](https://github.com/Dark-Alex-17/managarr/releases) for your OS.
|
1. Download the latest [binary](https://github.com/Dark-Alex-17/managarr/releases) for your OS.
|
||||||
2. Use 7-Zip or TarTool to unpack the Tar file.
|
2. Use 7-Zip or TarTool to unpack the Tar file.
|
||||||
3. Run the executable `managarr.exe`!
|
3. Run the executable `managarr.exe`!
|
||||||
|
|
||||||
#### Linux/MacOS Instructions
|
#### Linux/MacOS Instructions
|
||||||
To use a binary from the releases page on Linux/MacOS, do the following:
|
To use a binary from the releases page on Linux/MacOS, do the following:
|
||||||
|
|
||||||
1. Download the latest binary [binary](https://github.com/Dark-Alex-17/managarr/releases) for your OS.
|
1. Download the latest [binary](https://github.com/Dark-Alex-17/managarr/releases) for your OS.
|
||||||
2. `cd` to the directory where you downloaded the binary.
|
2. `cd` to the directory where you downloaded the binary.
|
||||||
3. Extract the binary with `tar -C /usr/local/bin -xzf managarr-<arch>.tar.gz` (NB: This may require `sudo`)
|
3. Extract the binary with `tar -C /usr/local/bin -xzf managarr-<arch>.tar.gz` (Note: This may require `sudo`)
|
||||||
4. Now you can run `managarr`!
|
4. Now you can run `managarr`!
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@@ -121,30 +165,46 @@ Key:
|
|||||||
|
|
||||||
| TUI | CLI | Feature |
|
| TUI | CLI | Feature |
|
||||||
|-----|-----|--------------------------------------------------------------------------------------------------------------------|
|
|-----|-----|--------------------------------------------------------------------------------------------------------------------|
|
||||||
| ✅ | ✅ | View your library, downloads, blocklist, episodes |
|
| ✅ | ✅ | View your library, downloads, blocklist, episodes |
|
||||||
| ✅ | ✅ | View details of a specific series, or episode including description, history, downloaded file info, or the credits |
|
| ✅ | ✅ | View details of a specific series, or episode including description, history, downloaded file info, or the credits |
|
||||||
| 🚫 | ✅ | View your host and security configs from the CLI to programmatically fetch the API token, among other settings |
|
| 🚫 | ✅ | View your host and security configs from the CLI to programmatically fetch the API token, among other settings |
|
||||||
| ✅ | ✅ | Search your library |
|
| ✅ | ✅ | Search your library |
|
||||||
| ✅ | ✅ | Add series to your library |
|
| ✅ | ✅ | Add series to your library |
|
||||||
| ✅ | ✅ | Delete series, downloads, indexers, root folders, and episode files |
|
| ✅ | ✅ | Delete series, downloads, indexers, root folders, and episode files |
|
||||||
| ✅ | ✅ | Trigger automatic searches for series, seasons, or episodes |
|
| ✅ | ✅ | Trigger automatic searches for series, seasons, or episodes |
|
||||||
| ✅ | ✅ | Trigger refresh and disk scan for series and downloads |
|
| ✅ | ✅ | Trigger refresh and disk scan for series and downloads |
|
||||||
| ✅ | ✅ | Manually search for series, seasons, or episodes |
|
| ✅ | ✅ | Manually search for series, seasons, or episodes |
|
||||||
| ✅ | ✅ | Edit your series and indexers |
|
| ✅ | ✅ | Edit your series and indexers |
|
||||||
| ✅ | ✅ | Manage your tags |
|
| ✅ | ✅ | Manage your tags |
|
||||||
| ✅ | ✅ | Manage your root folders |
|
| ✅ | ✅ | Manage your root folders |
|
||||||
| ✅ | ✅ | Manage your blocklist |
|
| ✅ | ✅ | Manage your blocklist |
|
||||||
| ✅ | ✅ | 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
|
||||||
@@ -161,6 +221,19 @@ Key:
|
|||||||
|
|
||||||
- [ ] Support for Tautulli
|
- [ ] Support for Tautulli
|
||||||
|
|
||||||
|
### Themes
|
||||||
|
Managarr ships with a few themes out of the box. Here's a few examples:
|
||||||
|
|
||||||
|
#### Default
|
||||||
|

|
||||||
|
#### Dracula
|
||||||
|

|
||||||
|
#### Watermelon Dark
|
||||||
|

|
||||||
|
|
||||||
|
You can also create your own custom themes as well. To learn more about what themes are built-in to Managarr and how
|
||||||
|
to create your own custom themes, check out the [Themes README](themes/README.md).
|
||||||
|
|
||||||
### The Managarr CLI
|
### The Managarr CLI
|
||||||
Managarr can be used in one of two ways: As a TUI, or as a CLI for managing your Servarrs.
|
Managarr can be used in one of two ways: As a TUI, or as a CLI for managing your Servarrs.
|
||||||
|
|
||||||
@@ -173,7 +246,7 @@ To see all available commands, simply run `managarr --help`:
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ managarr --help
|
$ managarr --help
|
||||||
managarr 0.4.0
|
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
|
||||||
@@ -183,15 +256,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 <CONFIG> The Managarr configuration file to use [env: MANAGARR_CONFIG_FILE=]
|
-V, --version Print version
|
||||||
-h, --help Print help
|
|
||||||
-V, --version Print version
|
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; defaults to the
|
||||||
|
path shown by 'managarr config-path' [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.
|
||||||
```
|
```
|
||||||
|
|
||||||
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:
|
||||||
@@ -220,12 +313,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:
|
||||||
@@ -239,21 +341,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
|
||||||
@@ -262,44 +354,102 @@ where you may have more than one instance of a given Servarr running. Thus, you
|
|||||||
config file using the `--config` flag:
|
config file using the `--config` flag:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
managarr --config /path/to/config.yml
|
managarr --config-file /path/to/config.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
### Example Configuration:
|
### Example Configuration:
|
||||||
```yaml
|
```yaml
|
||||||
|
theme: default
|
||||||
radarr:
|
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 a self-signed SSL certificate to connect to this Servarr
|
||||||
|
# Enables SSL regardless of the value of the 'ssl'
|
||||||
|
|
||||||
|
- host: 192.168.0.79
|
||||||
|
port: 7878
|
||||||
|
api_token: someApiToken1234567890
|
||||||
|
ssl: true # Use SSL to connect to this Servarr (public certs)
|
||||||
|
|
||||||
|
- uri: http://htpc.local/radarr # Example of using the 'uri' key instead of 'host' and 'port'
|
||||||
|
api_token: someApiToken1234567890
|
||||||
|
|
||||||
sonarr:
|
sonarr:
|
||||||
uri: http://htpc.local/sonarr # Example of using the 'uri' key instead of 'host' and 'port'
|
- host: 192.168.0.89
|
||||||
api_token: someApiToken1234567890
|
port: 8989
|
||||||
readarr:
|
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
|
||||||
host: 192.168.0.87
|
|
||||||
port: 8787
|
- name: Anime Sonarr # An example of a custom name for a secondary Sonarr instance
|
||||||
api_token: someApiToken1234567890
|
host: 192.168.1.89
|
||||||
|
port: 8989
|
||||||
|
api_token: someApiToken1234567890
|
||||||
|
|
||||||
lidarr:
|
lidarr:
|
||||||
host: 192.168.0.86
|
- host: 192.168.0.86
|
||||||
port: 8686
|
port: 8686
|
||||||
api_token: someApiToken1234567890
|
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:'
|
||||||
api_token: someApiToken1234567890
|
- /media # An example root folder you want displayed in the UI
|
||||||
ssl_cert_path: /path/to/whisparr.crt
|
# Root folders collapse up to the super-directory to reduce duplication in the UI. For example:
|
||||||
bazarr:
|
# if you have root folders '/media/tv', '/media/cartoons' and '/media/reality', and you set this
|
||||||
host: 192.168.0.67
|
# monitored path, the UI will show '/media/[tv,cartoons,reality]' under Root Folders
|
||||||
port: 6767
|
|
||||||
api_token: someApiToken1234567890
|
- host: 192.168.1.86
|
||||||
prowlarr:
|
port: 8686
|
||||||
host: 192.168.0.96
|
api_token: someApiToken1234567890
|
||||||
port: 9696
|
ssl_cert_path: /path/to/lidarr_1.crt
|
||||||
api_token: someApiToken1234567890
|
custom_headers: # Example of adding custom headers to all requests to the Servarr instance
|
||||||
tautulli:
|
traefik-auth-bypass-key: someBypassKey1234567890
|
||||||
host: 192.168.0.81
|
SOME-OTHER-CUSTOM-HEADER: ${MY_CUSTOM_HEADER_VALUE}
|
||||||
port: 8181
|
```
|
||||||
api_token: someApiToken1234567890
|
|
||||||
|
### Example Multi-Instance Configuration:
|
||||||
|
```yaml
|
||||||
|
theme: default
|
||||||
|
radarr:
|
||||||
|
- host: 192.168.0.78 # No name specified, so this instance's name will default to 'Radarr 1'
|
||||||
|
port: 7878
|
||||||
|
api_token: someApiToken1234567890
|
||||||
|
ssl_cert_path: /path/to/radarr.crt
|
||||||
|
|
||||||
|
- name: International Movies
|
||||||
|
host: 192.168.0.79
|
||||||
|
port: 7878
|
||||||
|
api_token: someApiToken1234567890
|
||||||
|
sonarr:
|
||||||
|
- name: Anime
|
||||||
|
weight: 1 # This instance will be the first tab in the TUI
|
||||||
|
uri: http://htpc.local/sonarr
|
||||||
|
api_token: someApiToken1234567890
|
||||||
|
|
||||||
|
- name: TV Shows
|
||||||
|
weight: 2 # This instance will be the second tab in the TUI
|
||||||
|
host: 192.168.0.89
|
||||||
|
port: 8989
|
||||||
|
api_token: someApiToken1234567890
|
||||||
|
```
|
||||||
|
|
||||||
|
In this configuration, you can see that we have multiple instances of Radarr and Sonarr configured. The `weight` key is
|
||||||
|
used to specify the order in which the tabs will appear in the TUI. The lower the weight, the further to the left the
|
||||||
|
tab will appear. If no weight is specified, then tabs will be ordered in the order they appear in the configuration
|
||||||
|
file.
|
||||||
|
|
||||||
|
When no `name` is specified for a Servarr instance, the name will default to the name of the Servarr with a number
|
||||||
|
appended to it. For example, if you have two Radarr instances and neither has a name, they will be named `Radarr 1` and
|
||||||
|
`Radarr 2`, respectively.
|
||||||
|
|
||||||
|
In this example configuration, the tabs in the TUI would appear as follows:
|
||||||
|
|
||||||
|
`Anime | TV Shows | Radarr 1 | International Movies`
|
||||||
|
|
||||||
|
### Specify Which Servarr Instance to Use in the CLI
|
||||||
|
If you have multiple instances of the same Servarr running, you can specify which instance you want to use by using the `--servarr-name` flag:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
managarr --servarr-name "International Movies"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
@@ -310,10 +460,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 My Progress for the Beta release (With Sonarr Support!)
|
|
||||||
Progress for the beta release can be followed on my [Wekan Board](https://wekan.alexjclarke.com/b/dHoGjBb44MHM9HSv4/managarr)
|
|
||||||
with all items tagged `Beta`.
|
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
### Radarr
|
### Radarr
|
||||||
@@ -329,6 +475,13 @@ with all items tagged `Beta`.
|
|||||||

|

|
||||||

|

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

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

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

|

|
||||||

|

|
||||||
@@ -344,8 +497,8 @@ with all items tagged `Beta`.
|
|||||||
## 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,6 +1,14 @@
|
|||||||
coverage:
|
coverage:
|
||||||
range: "80..100"
|
range: "80..100"
|
||||||
|
status:
|
||||||
|
project:
|
||||||
|
default:
|
||||||
|
threshold: 0
|
||||||
|
target: 80%
|
||||||
|
patch:
|
||||||
|
default:
|
||||||
|
threshold: 0
|
||||||
|
target: 80%
|
||||||
|
|
||||||
ignore:
|
ignore:
|
||||||
- "**/*_tests.rs"
|
- "**/*_tests.rs"
|
||||||
- "src/ui"
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
$ErrorActionPreference = 'Stop';
|
||||||
|
|
||||||
|
$PackageName = 'managarr'
|
||||||
|
$toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
|
||||||
|
$url64 = 'https://github.com/Dark-Alex-17/managarr/releases/download/v$version/managarr-windows.tar.gz'
|
||||||
|
$checksum64 = '$hash_64'
|
||||||
|
|
||||||
|
$packageArgs = @{
|
||||||
|
packageName = $packageName
|
||||||
|
softwareName = $packageName
|
||||||
|
unzipLocation = $toolsDir
|
||||||
|
fileType = 'exe'
|
||||||
|
url = $url64
|
||||||
|
checksum = $checksum64
|
||||||
|
checksumType = 'sha256'
|
||||||
|
|
||||||
|
}
|
||||||
|
Install-ChocolateyZipPackage @packageArgs
|
||||||
|
$File = Get-ChildItem -File -Path $env:ChocolateyInstall\lib\$packageName\tools\ -Filter *.tar
|
||||||
|
Get-ChocolateyUnzip -fileFullPath $File.FullName -destination $env:ChocolateyInstall\lib\$packageName\tools\
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Read this before creating packages: https://chocolatey.org/docs/create-packages -->
|
||||||
|
<!-- It is especially important to read the above link to understand additional requirements when publishing packages to the community feed aka dot org (https://chocolatey.org/packages). -->
|
||||||
|
|
||||||
|
<!-- Test your packages in a test environment: https://github.com/chocolatey/chocolatey-test-environment -->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
This is a nuspec. It mostly adheres to https://docs.nuget.org/create/Nuspec-Reference. Chocolatey uses a special version of NuGet.Core that allows us to do more than was initially possible. As such there are certain things to be aware of:
|
||||||
|
|
||||||
|
* the package xmlns schema url may cause issues with nuget.exe
|
||||||
|
* Any of the following elements can ONLY be used by choco tools - projectSourceUrl, docsUrl, mailingListUrl, bugTrackerUrl, packageSourceUrl, provides, conflicts, replaces
|
||||||
|
* nuget.exe can still install packages with those elements but they are ignored. Any authoring tools or commands will error on those elements
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- You can embed software files directly into packages, as long as you are not bound by distribution rights. -->
|
||||||
|
<!-- * If you are an organization making private packages, you probably have no issues here -->
|
||||||
|
<!-- * If you are releasing to the community feed, you need to consider distribution rights. -->
|
||||||
|
<!-- Do not remove this test for UTF-8: if “Ω” doesn’t appear as greek uppercase omega letter enclosed in quotation marks, you should use an editor that supports UTF-8, not this one. -->
|
||||||
|
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
|
||||||
|
<metadata>
|
||||||
|
<!-- == PACKAGE SPECIFIC SECTION == -->
|
||||||
|
<id>managarr</id>
|
||||||
|
<version>$version</version>
|
||||||
|
|
||||||
|
<!-- == SOFTWARE SPECIFIC SECTION == -->
|
||||||
|
<!-- This section is about the software itself -->
|
||||||
|
<title>Managarr</title>
|
||||||
|
<authors>Alex Clarke</authors>
|
||||||
|
<projectUrl>https://github.com/Dark-Alex-17/managarr</projectUrl>
|
||||||
|
<licenseUrl>https://github.com/Dark-Alex-17/managarr/blob/main/LICENSE</licenseUrl>
|
||||||
|
<requireLicenseAcceptance>true</requireLicenseAcceptance>
|
||||||
|
<projectSourceUrl>https://github.com/Dark-Alex-17/managarr</projectSourceUrl>
|
||||||
|
<docsUrl>https://github.com/Dark-Alex-17/managarr/blob/main/README.md</docsUrl>
|
||||||
|
<bugTrackerUrl>https://github.com/Dark-Alex-17/managarr/issues</bugTrackerUrl>
|
||||||
|
<tags>cli cross-platform terminal servarr tui sonarr radarr rust</tags>
|
||||||
|
<summary>A TUI and CLI for managing *arr servers.</summary>
|
||||||
|
<description>
|
||||||
|
A TUI and CLI for managing *arr servers. Built with love in Rust!
|
||||||
|
|
||||||
|
**Usage**
|
||||||
|
To use, run `managarr` in a terminal.
|
||||||
|
|
||||||
|
For more [documentation and usage](https://github.com/Dark-Alex-17/managarr/blob/main/README.md), see the [official repo](https://github.com/Dark-Alex-17/managarr).
|
||||||
|
|
||||||
|
</description>
|
||||||
|
<releaseNotes>https://github.com/Dark-Alex-17/managarr/releases/tag/v$version/</releaseNotes>
|
||||||
|
</metadata>
|
||||||
|
<files>
|
||||||
|
<!-- this section controls what actually gets packaged into the Chocolatey package -->
|
||||||
|
<file src="tools\**" target="tools" />
|
||||||
|
<!--Building from Linux? You may need this instead: <file src="tools/**" target="tools" />-->
|
||||||
|
</files>
|
||||||
|
</package>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import hashlib
|
||||||
|
import sys
|
||||||
|
from string import Template
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
args = sys.argv
|
||||||
|
version = args[1].replace("v", "")
|
||||||
|
template_file_path = args[2]
|
||||||
|
generated_file_path = args[3]
|
||||||
|
|
||||||
|
# Deployment files
|
||||||
|
hash_64 = args[4].strip()
|
||||||
|
|
||||||
|
print("Generating formula")
|
||||||
|
print(" VERSION: %s" % version)
|
||||||
|
print(" TEMPLATE PATH: %s" % template_file_path)
|
||||||
|
print(" SAVING AT: %s" % generated_file_path)
|
||||||
|
print(" HASH: %s" % hash_64)
|
||||||
|
|
||||||
|
with open(template_file_path, "r", encoding="utf-8") as template_file:
|
||||||
|
template = Template(template_file.read())
|
||||||
|
substitute = template.safe_substitute(version=version, hash_64=hash_64)
|
||||||
|
print("\n================== Generated package file ==================\n")
|
||||||
|
print(substitute)
|
||||||
|
print("\n============================================================\n")
|
||||||
|
|
||||||
|
with open(generated_file_path, "w", encoding="utf-8") as generated_file:
|
||||||
|
generated_file.write(substitute)
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Documentation: https://docs.brew.sh/Formula-Cookbook
|
||||||
|
# https://rubydoc.brew.sh/Formula
|
||||||
|
class Managarr < Formula
|
||||||
|
desc "Managarr is a TUI and CLI to help you manage your HTPC (Home Theater PC)"
|
||||||
|
homepage "https://github.com/Dark-Alex-17/managarr"
|
||||||
|
if OS.mac? and Hardware::CPU.arm?
|
||||||
|
url "https://github.com/Dark-Alex-17/managarr/releases/download/v$version/managarr-macos-arm64.tar.gz"
|
||||||
|
sha256 "$hash_mac_arm"
|
||||||
|
elsif OS.mac? and Hardware::CPU.intel?
|
||||||
|
url "https://github.com/Dark-Alex-17/managarr/releases/download/v$version/managarr-macos.tar.gz"
|
||||||
|
sha256 "$hash_mac"
|
||||||
|
else
|
||||||
|
url "https://github.com/Dark-Alex-17/managarr/releases/download/v$version/managarr-linux-musl.tar.gz"
|
||||||
|
sha256 "$hash_linux"
|
||||||
|
end
|
||||||
|
version "$version"
|
||||||
|
license "MIT"
|
||||||
|
|
||||||
|
def install
|
||||||
|
bin.install "managarr"
|
||||||
|
ohai "You're done! Run with \"managarr\""
|
||||||
|
ohai "For runtime flags, see \"managarr --help\""
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import hashlib
|
||||||
|
import sys
|
||||||
|
from string import Template
|
||||||
|
|
||||||
|
args = sys.argv
|
||||||
|
version = args[1]
|
||||||
|
template_file_path = args[2]
|
||||||
|
generated_file_path = args[3]
|
||||||
|
|
||||||
|
# Deployment files
|
||||||
|
hash_mac = args[4].strip()
|
||||||
|
hash_mac_arm = args[5].strip()
|
||||||
|
hash_linux = args[6].strip()
|
||||||
|
|
||||||
|
print("Generating formula")
|
||||||
|
print(" VERSION: %s" % version)
|
||||||
|
print(" TEMPLATE PATH: %s" % template_file_path)
|
||||||
|
print(" SAVING AT: %s" % generated_file_path)
|
||||||
|
print(" MAC HASH: %s" % hash_mac)
|
||||||
|
print(" MAC ARM HASH: %s" % hash_mac_arm)
|
||||||
|
print(" LINUX HASH: %s" % hash_linux)
|
||||||
|
|
||||||
|
with open(template_file_path, "r") as template_file:
|
||||||
|
template = Template(template_file.read())
|
||||||
|
substitute = template.safe_substitute(version=version, hash_mac=hash_mac, hash_mac_arm=hash_mac_arm, hash_linux=hash_linux)
|
||||||
|
print("\n================== Generated package file ==================\n")
|
||||||
|
print(substitute)
|
||||||
|
print("\n============================================================\n")
|
||||||
|
|
||||||
|
with open(generated_file_path, "w") as generated_file:
|
||||||
|
generated_file.write(substitute)
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
VERSION := "latest"
|
||||||
|
IMG_NAME := "darkalex17/managarr"
|
||||||
|
|
||||||
|
# List all recipes
|
||||||
|
default:
|
||||||
|
@just --list
|
||||||
|
|
||||||
|
# Format all files
|
||||||
|
[group: 'style']
|
||||||
|
fmt:
|
||||||
|
@cargo fmt --all
|
||||||
|
|
||||||
|
alias clippy := lint
|
||||||
|
# Run Clippy to inspect all files
|
||||||
|
[group: 'style']
|
||||||
|
lint:
|
||||||
|
@cargo clippy --all
|
||||||
|
|
||||||
|
alias clippy-fix := lint-fix
|
||||||
|
# Automatically fix clippy issues where possible
|
||||||
|
[group: 'style']
|
||||||
|
lint-fix:
|
||||||
|
@cargo fix
|
||||||
|
|
||||||
|
# Analyze the project for unsafe usage
|
||||||
|
[group: 'style']
|
||||||
|
@analyze:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
cargo geiger -h > /dev/null 2>&1 | cargo install cargo-geiger
|
||||||
|
cargo geiger
|
||||||
|
|
||||||
|
# Run all tests
|
||||||
|
[group: 'test']
|
||||||
|
test:
|
||||||
|
@cargo test --all
|
||||||
|
|
||||||
|
# Run all tests with coverage
|
||||||
|
[group:'test']
|
||||||
|
@test-cov:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
cargo tarpaulin -h > /dev/null 2>&1 || cargo install cargo-tarpaulin
|
||||||
|
cargo tarpaulin
|
||||||
|
|
||||||
|
# Run all doc tests
|
||||||
|
[group: 'test']
|
||||||
|
doctest:
|
||||||
|
@cargo test --all --doc
|
||||||
|
|
||||||
|
# Run all proptests
|
||||||
|
[group: 'test']
|
||||||
|
proptest:
|
||||||
|
@cargo test proptest
|
||||||
|
|
||||||
|
alias test-snapshots := snapshot-tests
|
||||||
|
# Run all snapshot tests
|
||||||
|
[group: 'test']
|
||||||
|
snapshot-tests:
|
||||||
|
@cargo test snapshot
|
||||||
|
|
||||||
|
alias review := snapshot-review
|
||||||
|
# Review snapshot test changes
|
||||||
|
[group: 'test']
|
||||||
|
@snapshot-review:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
cargo insta -h > /dev/null 2>&1 || cargo install cargo-insta
|
||||||
|
cargo insta review
|
||||||
|
|
||||||
|
alias clean-orphaned-snapshots := snapshot-delete-unreferenced
|
||||||
|
# Delete any unreferenced snapshots
|
||||||
|
[group: 'test']
|
||||||
|
@snapshot-delete-unreferenced:
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
cargo insta -h > /dev/null 2>&1 || cargo install cargo-insta
|
||||||
|
cargo insta test --unreferenced=delete
|
||||||
|
|
||||||
|
# Build and run the binary for the current system
|
||||||
|
run:
|
||||||
|
@cargo run
|
||||||
|
|
||||||
|
# Build the project for the current system architecture
|
||||||
|
[group: 'build']
|
||||||
|
[arg('build_type', pattern="debug|release")]
|
||||||
|
build build_type='debug':
|
||||||
|
@cargo build {{ if build_type == "release" { "--release" } else { "" } }}
|
||||||
|
|
||||||
|
# Build the docker image
|
||||||
|
[group: 'build']
|
||||||
|
build-docker:
|
||||||
|
@DOCKER_BUILDKIT=1 docker build --rm -t {{IMG_NAME}}:{{VERSION}} .
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
[package]
|
||||||
|
name = "enum_display_style_derive"
|
||||||
|
version = "0.6.1"
|
||||||
|
edition = "2024"
|
||||||
|
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
|
||||||
|
description = "A proc-macro to derive a `Display` and `FromStr` implementation for enums with a `style` attribute."
|
||||||
|
license = "MIT"
|
||||||
|
documentation = "https://github.com/Dark-Alex-17/managarr"
|
||||||
|
repository = "https://github.com/Dark-Alex-17/managarr"
|
||||||
|
homepage = "https://github.com/Dark-Alex-17/managarr"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
quote = "1.0.39"
|
||||||
|
syn = "2.0.99"
|
||||||
|
darling = "0.20.10"
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
mod macro_models;
|
||||||
|
|
||||||
|
use crate::macro_models::DisplayStyleArgs;
|
||||||
|
use darling::FromVariant;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{Data, DeriveInput, parse_macro_input};
|
||||||
|
|
||||||
|
/// Derive macro for generating a `to_display_str` method for an enum.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// Using default values for the display style:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use enum_display_style_derive::EnumDisplayStyle;
|
||||||
|
///
|
||||||
|
/// #[derive(EnumDisplayStyle)]
|
||||||
|
/// enum Weekend {
|
||||||
|
/// Saturday,
|
||||||
|
/// Sunday,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// assert_eq!(Weekend::Saturday.to_display_str(), "Saturday");
|
||||||
|
/// assert_eq!(Weekend::Sunday.to_display_str(), "Sunday");
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Using custom values for the display style:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use enum_display_style_derive::EnumDisplayStyle;
|
||||||
|
///
|
||||||
|
/// #[derive(EnumDisplayStyle)]
|
||||||
|
/// enum MonitorStatus {
|
||||||
|
/// #[display_style(name = "Monitor Transactions")]
|
||||||
|
/// Active,
|
||||||
|
/// #[display_style(name = "Don't Monitor Transactions")]
|
||||||
|
/// None,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// assert_eq!(MonitorStatus::Active.to_display_str(), "Monitor Transactions");
|
||||||
|
/// assert_eq!(MonitorStatus::None.to_display_str(), "Don't Monitor Transactions");
|
||||||
|
/// ```
|
||||||
|
#[proc_macro_derive(EnumDisplayStyle, attributes(display_style))]
|
||||||
|
pub fn enum_display_style_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
let enum_name = &input.ident;
|
||||||
|
|
||||||
|
let mut match_arms = Vec::new();
|
||||||
|
|
||||||
|
if let Data::Enum(data_enum) = &input.data {
|
||||||
|
let variants = &data_enum.variants;
|
||||||
|
|
||||||
|
for variant in variants {
|
||||||
|
let variant_ident = &variant.ident;
|
||||||
|
let variant_display_name = DisplayStyleArgs::from_variant(variant)
|
||||||
|
.unwrap()
|
||||||
|
.name
|
||||||
|
.unwrap_or_else(|| variant_ident.to_string());
|
||||||
|
|
||||||
|
match_arms.push(quote! {
|
||||||
|
#enum_name::#variant_ident => #variant_display_name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
impl<'a> #enum_name {
|
||||||
|
pub fn to_display_str(self) -> &'a str {
|
||||||
|
match self {
|
||||||
|
#(#match_arms)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
use darling::FromVariant;
|
||||||
|
|
||||||
|
#[derive(Debug, FromVariant)]
|
||||||
|
#[darling(attributes(display_style))]
|
||||||
|
pub struct DisplayStyleArgs {
|
||||||
|
pub name: Option<String>,
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "validate_theme_derive"
|
||||||
|
version = "0.6.1"
|
||||||
|
edition = "2024"
|
||||||
|
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
|
||||||
|
description = "A proc-macro to validate a theme."
|
||||||
|
license = "MIT"
|
||||||
|
documentation = "https://github.com/Dark-Alex-17/managarr"
|
||||||
|
repository = "https://github.com/Dark-Alex-17/managarr"
|
||||||
|
homepage = "https://github.com/Dark-Alex-17/managarr"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
quote = "1.0.39"
|
||||||
|
syn = "2.0.99"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
log = "0.4.17"
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{Data, DeriveInput, Fields, parse_macro_input};
|
||||||
|
|
||||||
|
/// Derive macro for generating a `validate` method for a Theme struct.
|
||||||
|
/// The `validate` method ensures that all values with the `validate` attribute are not `None`.
|
||||||
|
/// Otherwise, an error message it output to both the log file and stdout and the program exits.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// Valid themes pass through the program transitively without any messages being output.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use validate_theme_derive::ValidateTheme;
|
||||||
|
///
|
||||||
|
/// #[derive(ValidateTheme, Default)]
|
||||||
|
/// struct Theme {
|
||||||
|
/// pub name: String,
|
||||||
|
/// #[validate]
|
||||||
|
/// pub good: Option<Style>,
|
||||||
|
/// #[validate]
|
||||||
|
/// pub bad: Option<Style>,
|
||||||
|
/// pub ugly: Option<Style>,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// struct Style {
|
||||||
|
/// color: String,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let theme = Theme {
|
||||||
|
/// good: Some(Style { color: "Green".to_owned() }),
|
||||||
|
/// bad: Some(Style { color: "Red".to_owned() }),
|
||||||
|
/// ..Theme::default()
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// // Since only `good` and `bad` have the `validate` attribute, the `validate` method will only check those fields.
|
||||||
|
/// theme.validate();
|
||||||
|
/// // Since both `good` and `bad` have values, the program will not exit and no message is output.
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Invalid themes will output an error message to both the log file and stdout and the program will exit.
|
||||||
|
///
|
||||||
|
/// ```should_panic
|
||||||
|
/// use validate_theme_derive::ValidateTheme;
|
||||||
|
///
|
||||||
|
/// #[derive(ValidateTheme, Default)]
|
||||||
|
/// struct Theme {
|
||||||
|
/// pub name: String,
|
||||||
|
/// #[validate]
|
||||||
|
/// pub good: Option<Style>,
|
||||||
|
/// #[validate]
|
||||||
|
/// pub bad: Option<Style>,
|
||||||
|
/// pub ugly: Option<Style>,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// struct Style {
|
||||||
|
/// color: String,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let theme = Theme {
|
||||||
|
/// bad: Some(Style { color: "Red".to_owned() }),
|
||||||
|
/// ..Theme::default()
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// // Since `good` has the `validate` attribute and since `good` is `None`, the `validate` method will output an error message and exit the program.
|
||||||
|
/// theme.validate();
|
||||||
|
/// ```
|
||||||
|
#[proc_macro_derive(ValidateTheme, attributes(validate))]
|
||||||
|
pub fn derive_validate_theme(input: TokenStream) -> TokenStream {
|
||||||
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
let struct_name = &input.ident;
|
||||||
|
|
||||||
|
let mut validation_checks = Vec::new();
|
||||||
|
|
||||||
|
if let Data::Struct(data_struct) = &input.data
|
||||||
|
&& let Fields::Named(fields) = &data_struct.fields
|
||||||
|
{
|
||||||
|
for field in &fields.named {
|
||||||
|
let field_name = &field.ident;
|
||||||
|
|
||||||
|
let has_validate_attr = field
|
||||||
|
.attrs
|
||||||
|
.iter()
|
||||||
|
.any(|attr| attr.path().is_ident("validate"));
|
||||||
|
|
||||||
|
if has_validate_attr {
|
||||||
|
validation_checks.push(quote! {
|
||||||
|
if self.#field_name.is_none() {
|
||||||
|
log::error!("{} is missing a color value.", stringify!(#field_name));
|
||||||
|
eprintln!("{} is missing a color value.", stringify!(#field_name));
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
impl #struct_name {
|
||||||
|
pub fn validate(&self) {
|
||||||
|
#(#validation_checks)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Seeds for failure cases proptest has generated in the past. It is
|
||||||
|
# automatically read and these particular cases re-run before any
|
||||||
|
# novel cases are generated.
|
||||||
|
#
|
||||||
|
# It is recommended to check this file in to source control so that
|
||||||
|
# everyone who runs the test benefits from these saved cases.
|
||||||
|
cc 56330c025ad79db641d0eb9f429ab74e95822e1fb015b58f0e158ea674cd42a1 # shrinks to list_size = 1, page_ops = 1
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Seeds for failure cases proptest has generated in the past. It is
|
||||||
|
# automatically read and these particular cases re-run before any
|
||||||
|
# novel cases are generated.
|
||||||
|
#
|
||||||
|
# It is recommended to check this file in to source control so that
|
||||||
|
# everyone who runs the test benefits from these saved cases.
|
||||||
|
cc fb4b58aa3015a125fc33a78dfaf27981db4191247151b327a351fc445e07c231 # shrinks to input = "j"
|
||||||
|
cc d6ec17d4d3f635f0a095ade650a316d26abc1f9fe2b6d9cf67bf2f8b4ebedb60 # shrinks to backspace_count = 0
|
||||||
|
cc cd46ee46e18cf86c940fb89c7206f0b482909880b8f2eabe3dd20682b9912c8a # shrinks to input = "h"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Seeds for failure cases proptest has generated in the past. It is
|
||||||
|
# automatically read and these particular cases re-run before any
|
||||||
|
# novel cases are generated.
|
||||||
|
#
|
||||||
|
# It is recommended to check this file in to source control so that
|
||||||
|
# everyone who runs the test benefits from these saved cases.
|
||||||
|
cc 24ae243412a324cb46c36cb4f629ddd4c9326b1479d1186d9b5545ac5e86dbba # shrinks to num_scroll_attempts = 0
|
||||||
|
cc c06a1cc1e4740b2498c50d7be64715bf09ef3ac4cf3bb3642f960578a3e06c74 # shrinks to is_loading = false, num_items = 1
|
||||||
|
cc 930207899afea2d389c7fa3974e31c2eb1803e71bcbd8179246c795903905ec7 # shrinks to parent_width = 20, parent_height = 12, percent_x = 1, percent_y = 1
|
||||||
@@ -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"
|
||||||
|
|||||||
|
After Width: | Height: | Size: 194 KiB |
|
After Width: | Height: | Size: 204 KiB |
|
After Width: | Height: | Size: 252 KiB |
|
After Width: | Height: | Size: 187 KiB |
|
After Width: | Height: | Size: 319 KiB |
|
Before Width: | Height: | Size: 382 KiB After Width: | Height: | Size: 340 KiB |
|
Before Width: | Height: | Size: 211 KiB After Width: | Height: | Size: 241 KiB |
|
Before Width: | Height: | Size: 203 KiB After Width: | Height: | Size: 220 KiB |
@@ -1,62 +1,121 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||||
|
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
|
||||||
|
use serde_json::Value;
|
||||||
|
use serial_test::serial;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
use crate::app::context_clues::{build_context_clue_string, SERVARR_CONTEXT_CLUES};
|
use crate::app::{App, AppConfig, Data, ServarrConfig, interpolate_env_vars};
|
||||||
use crate::app::{App, AppConfig, Data, ServarrConfig, DEFAULT_ROUTE};
|
use crate::models::servarr_data::lidarr::lidarr_data::LidarrData;
|
||||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData};
|
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData};
|
||||||
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SonarrData};
|
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SonarrData};
|
||||||
use crate::models::{HorizontallyScrollableText, TabRoute};
|
use crate::models::{HorizontallyScrollableText, TabRoute};
|
||||||
use crate::network::radarr_network::RadarrEvent;
|
|
||||||
use crate::network::NetworkEvent;
|
use crate::network::NetworkEvent;
|
||||||
|
use crate::network::radarr_network::RadarrEvent;
|
||||||
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_app_new() {
|
||||||
|
let radarr_config_1 = ServarrConfig {
|
||||||
|
name: Some("Radarr Test".to_owned()),
|
||||||
|
..ServarrConfig::default()
|
||||||
|
};
|
||||||
|
let radarr_config_2 = ServarrConfig {
|
||||||
|
weight: Some(3),
|
||||||
|
..ServarrConfig::default()
|
||||||
|
};
|
||||||
|
let sonarr_config_1 = ServarrConfig {
|
||||||
|
name: Some("Sonarr Test".to_owned()),
|
||||||
|
weight: Some(1),
|
||||||
|
..ServarrConfig::default()
|
||||||
|
};
|
||||||
|
let sonarr_config_2 = ServarrConfig::default();
|
||||||
|
let config = AppConfig {
|
||||||
|
theme: None,
|
||||||
|
radarr: Some(vec![radarr_config_1.clone(), radarr_config_2.clone()]),
|
||||||
|
sonarr: Some(vec![sonarr_config_1.clone(), sonarr_config_2.clone()]),
|
||||||
|
lidarr: None,
|
||||||
|
};
|
||||||
|
let expected_tab_routes = vec![
|
||||||
|
TabRoute {
|
||||||
|
title: "Sonarr Test".to_owned(),
|
||||||
|
route: ActiveSonarrBlock::default().into(),
|
||||||
|
contextual_help: None,
|
||||||
|
config: Some(sonarr_config_1),
|
||||||
|
},
|
||||||
|
TabRoute {
|
||||||
|
title: "Radarr 1".to_owned(),
|
||||||
|
route: ActiveRadarrBlock::default().into(),
|
||||||
|
contextual_help: None,
|
||||||
|
config: Some(radarr_config_2),
|
||||||
|
},
|
||||||
|
TabRoute {
|
||||||
|
title: "Radarr Test".to_owned(),
|
||||||
|
route: ActiveRadarrBlock::default().into(),
|
||||||
|
contextual_help: None,
|
||||||
|
config: Some(radarr_config_1),
|
||||||
|
},
|
||||||
|
TabRoute {
|
||||||
|
title: "Sonarr 1".to_owned(),
|
||||||
|
route: ActiveSonarrBlock::default().into(),
|
||||||
|
contextual_help: None,
|
||||||
|
config: Some(sonarr_config_2),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let app = App::new(
|
||||||
|
mpsc::channel::<NetworkEvent>(500).0,
|
||||||
|
config,
|
||||||
|
CancellationToken::new(),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_is_empty!(app.navigation_stack);
|
||||||
|
assert_eq!(app.get_current_route(), ActiveSonarrBlock::default().into());
|
||||||
|
assert_some!(app.network_tx);
|
||||||
|
assert!(!app.cancellation_token.is_cancelled());
|
||||||
|
assert!(app.is_first_render);
|
||||||
|
assert_eq!(app.error, HorizontallyScrollableText::default());
|
||||||
|
assert_eq!(app.server_tabs.index, 0);
|
||||||
|
assert_eq!(app.server_tabs.tabs, expected_tab_routes);
|
||||||
|
assert_eq!(app.tick_until_poll, 400);
|
||||||
|
assert_eq!(app.ticks_until_scroll, 64);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
assert_eq!(app.ui_scroll_tick_count, 0);
|
||||||
|
assert!(!app.is_loading);
|
||||||
|
assert!(!app.is_routing);
|
||||||
|
assert!(!app.should_refresh);
|
||||||
|
assert!(!app.ignore_special_keys_for_textbox_input);
|
||||||
|
assert!(!app.cli_mode);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_app_default() {
|
fn test_app_default() {
|
||||||
let app = App::default();
|
let app = App::default();
|
||||||
|
|
||||||
assert_eq!(app.navigation_stack, vec![DEFAULT_ROUTE]);
|
assert_is_empty!(app.navigation_stack);
|
||||||
assert!(app.network_tx.is_none());
|
assert_none!(app.network_tx);
|
||||||
assert!(!app.cancellation_token.is_cancelled());
|
assert!(!app.cancellation_token.is_cancelled());
|
||||||
assert!(app.is_first_render);
|
assert!(app.is_first_render);
|
||||||
assert_eq!(app.error, HorizontallyScrollableText::default());
|
assert_eq!(app.error, HorizontallyScrollableText::default());
|
||||||
assert_eq!(app.server_tabs.index, 0);
|
assert_eq!(app.server_tabs.index, 0);
|
||||||
assert_eq!(
|
|
||||||
app.server_tabs.tabs,
|
|
||||||
vec![
|
|
||||||
TabRoute {
|
|
||||||
title: "Radarr",
|
|
||||||
route: ActiveRadarrBlock::Movies.into(),
|
|
||||||
help: format!(
|
|
||||||
"<↑↓> scroll | ←→ change tab | {} ",
|
|
||||||
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
|
|
||||||
),
|
|
||||||
contextual_help: None,
|
|
||||||
},
|
|
||||||
TabRoute {
|
|
||||||
title: "Sonarr",
|
|
||||||
route: ActiveSonarrBlock::Series.into(),
|
|
||||||
help: format!("{} ", build_context_clue_string(&SERVARR_CONTEXT_CLUES)),
|
|
||||||
contextual_help: None,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
);
|
|
||||||
assert_eq!(app.tick_until_poll, 400);
|
assert_eq!(app.tick_until_poll, 400);
|
||||||
assert_eq!(app.ticks_until_scroll, 4);
|
assert_eq!(app.ticks_until_scroll, 64);
|
||||||
assert_eq!(app.tick_count, 0);
|
assert_eq!(app.tick_count, 0);
|
||||||
assert!(!app.is_loading);
|
assert!(!app.is_loading);
|
||||||
assert!(!app.is_routing);
|
assert!(!app.is_routing);
|
||||||
assert!(!app.should_refresh);
|
assert!(!app.should_refresh);
|
||||||
assert!(!app.should_ignore_quit_key);
|
assert!(!app.ignore_special_keys_for_textbox_input);
|
||||||
assert!(!app.cli_mode);
|
assert!(!app.cli_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_navigation_stack_methods() {
|
fn test_navigation_stack_methods() {
|
||||||
let mut app = App::default();
|
let mut app = App::test_default();
|
||||||
|
let default_route = app.server_tabs.tabs.first().unwrap().route;
|
||||||
|
|
||||||
assert_eq!(app.get_current_route(), DEFAULT_ROUTE);
|
assert_eq!(app.get_current_route(), default_route);
|
||||||
|
|
||||||
app.push_navigation_stack(ActiveRadarrBlock::Downloads.into());
|
app.push_navigation_stack(ActiveRadarrBlock::Downloads.into());
|
||||||
|
|
||||||
@@ -75,13 +134,13 @@ mod tests {
|
|||||||
app.is_routing = false;
|
app.is_routing = false;
|
||||||
app.pop_navigation_stack();
|
app.pop_navigation_stack();
|
||||||
|
|
||||||
assert_eq!(app.get_current_route(), DEFAULT_ROUTE);
|
assert_eq!(app.get_current_route(), default_route);
|
||||||
assert!(app.is_routing);
|
assert!(app.is_routing);
|
||||||
|
|
||||||
app.is_routing = false;
|
app.is_routing = false;
|
||||||
app.pop_navigation_stack();
|
app.pop_navigation_stack();
|
||||||
|
|
||||||
assert_eq!(app.get_current_route(), DEFAULT_ROUTE);
|
assert_eq!(app.get_current_route(), default_route);
|
||||||
assert!(app.is_routing);
|
assert!(app.is_routing);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +149,7 @@ mod tests {
|
|||||||
let mut app = App {
|
let mut app = App {
|
||||||
is_loading: true,
|
is_loading: true,
|
||||||
should_refresh: false,
|
should_refresh: false,
|
||||||
..App::default()
|
..App::test_default()
|
||||||
};
|
};
|
||||||
app.cancellation_token.cancel();
|
app.cancellation_token.cancel();
|
||||||
|
|
||||||
@@ -108,7 +167,7 @@ mod tests {
|
|||||||
fn test_reset_tick_count() {
|
fn test_reset_tick_count() {
|
||||||
let mut app = App {
|
let mut app = App {
|
||||||
tick_count: 2,
|
tick_count: 2,
|
||||||
..App::default()
|
..App::test_default()
|
||||||
};
|
};
|
||||||
|
|
||||||
app.reset_tick_count();
|
app.reset_tick_count();
|
||||||
@@ -127,6 +186,7 @@ mod tests {
|
|||||||
..SonarrData::default()
|
..SonarrData::default()
|
||||||
};
|
};
|
||||||
let data = Data {
|
let data = Data {
|
||||||
|
lidarr_data: LidarrData::default(),
|
||||||
radarr_data,
|
radarr_data,
|
||||||
sonarr_data,
|
sonarr_data,
|
||||||
};
|
};
|
||||||
@@ -135,7 +195,7 @@ mod tests {
|
|||||||
error: "Test error".to_owned().into(),
|
error: "Test error".to_owned().into(),
|
||||||
is_first_render: false,
|
is_first_render: false,
|
||||||
data,
|
data,
|
||||||
..App::default()
|
..App::test_default()
|
||||||
};
|
};
|
||||||
|
|
||||||
app.reset();
|
app.reset();
|
||||||
@@ -149,7 +209,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_handle_error() {
|
fn test_handle_error() {
|
||||||
let mut app = App::default();
|
let mut app = App::test_default();
|
||||||
let test_string = "Testing";
|
let test_string = "Testing";
|
||||||
|
|
||||||
app.handle_error(anyhow!(test_string));
|
app.handle_error(anyhow!(test_string));
|
||||||
@@ -168,7 +228,7 @@ mod tests {
|
|||||||
let mut app = App {
|
let mut app = App {
|
||||||
tick_until_poll: 2,
|
tick_until_poll: 2,
|
||||||
network_tx: Some(sync_network_tx),
|
network_tx: Some(sync_network_tx),
|
||||||
..App::default()
|
..App::test_default()
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(app.tick_count, 0);
|
assert_eq!(app.tick_count, 0);
|
||||||
@@ -184,6 +244,27 @@ mod tests {
|
|||||||
assert_eq!(app.tick_count, 0);
|
assert_eq!(app.tick_count, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_on_ui_scroll_tick() {
|
||||||
|
let mut app = App {
|
||||||
|
ticks_until_scroll: 1,
|
||||||
|
..App::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(app.ui_scroll_tick_count, 0);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
|
||||||
|
app.on_ui_scroll_tick();
|
||||||
|
|
||||||
|
assert_eq!(app.ui_scroll_tick_count, 1);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
|
||||||
|
app.on_ui_scroll_tick();
|
||||||
|
|
||||||
|
assert_eq!(app.ui_scroll_tick_count, 0);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_on_tick_first_render() {
|
async fn test_on_tick_first_render() {
|
||||||
let (sync_network_tx, mut sync_network_rx) = mpsc::channel::<NetworkEvent>(500);
|
let (sync_network_tx, mut sync_network_rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
@@ -192,7 +273,7 @@ mod tests {
|
|||||||
tick_until_poll: 2,
|
tick_until_poll: 2,
|
||||||
network_tx: Some(sync_network_tx),
|
network_tx: Some(sync_network_tx),
|
||||||
is_first_render: true,
|
is_first_render: true,
|
||||||
..App::default()
|
..App::test_default()
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(app.tick_count, 0);
|
assert_eq!(app.tick_count, 0);
|
||||||
@@ -213,7 +294,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sync_network_rx.recv().await.unwrap(),
|
sync_network_rx.recv().await.unwrap(),
|
||||||
RadarrEvent::GetDownloads.into()
|
RadarrEvent::GetDownloads(500).into()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sync_network_rx.recv().await.unwrap(),
|
sync_network_rx.recv().await.unwrap(),
|
||||||
@@ -246,7 +327,7 @@ mod tests {
|
|||||||
tick_until_poll: 2,
|
tick_until_poll: 2,
|
||||||
tick_count: 2,
|
tick_count: 2,
|
||||||
is_routing: true,
|
is_routing: true,
|
||||||
..App::default()
|
..App::test_default()
|
||||||
};
|
};
|
||||||
|
|
||||||
app.on_tick().await;
|
app.on_tick().await;
|
||||||
@@ -259,7 +340,7 @@ mod tests {
|
|||||||
tick_until_poll: 2,
|
tick_until_poll: 2,
|
||||||
tick_count: 2,
|
tick_count: 2,
|
||||||
should_refresh: true,
|
should_refresh: true,
|
||||||
..App::default()
|
..App::test_default()
|
||||||
};
|
};
|
||||||
|
|
||||||
app.on_tick().await;
|
app.on_tick().await;
|
||||||
@@ -270,18 +351,417 @@ mod tests {
|
|||||||
fn test_app_config_default() {
|
fn test_app_config_default() {
|
||||||
let app_config = AppConfig::default();
|
let app_config = AppConfig::default();
|
||||||
|
|
||||||
assert!(app_config.radarr.is_none());
|
assert_none!(app_config.radarr);
|
||||||
assert!(app_config.sonarr.is_none());
|
assert_none!(app_config.sonarr);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_servarr_config_default() {
|
fn test_servarr_config_default() {
|
||||||
let servarr_config = ServarrConfig::default();
|
let servarr_config = ServarrConfig::default();
|
||||||
|
|
||||||
assert_eq!(servarr_config.host, Some("localhost".to_string()));
|
assert_none!(servarr_config.name);
|
||||||
assert_eq!(servarr_config.port, None);
|
assert_some_eq_x!(&servarr_config.host, "localhost");
|
||||||
assert_eq!(servarr_config.uri, None);
|
assert_none!(servarr_config.port);
|
||||||
assert!(servarr_config.api_token.is_empty());
|
assert_none!(servarr_config.uri);
|
||||||
assert_eq!(servarr_config.ssl_cert_path, None);
|
assert_none!(servarr_config.weight);
|
||||||
|
assert_some_eq_x!(&servarr_config.api_token, "");
|
||||||
|
assert_none!(servarr_config.api_token_file);
|
||||||
|
assert_none!(servarr_config.ssl_cert_path);
|
||||||
|
assert_none!(servarr_config.custom_headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_header_map_basic() {
|
||||||
|
let mut header_map = HeaderMap::new();
|
||||||
|
header_map.insert(
|
||||||
|
HeaderName::from_static("x-api-key"),
|
||||||
|
HeaderValue::from_static("abc123"),
|
||||||
|
);
|
||||||
|
header_map.insert(
|
||||||
|
HeaderName::from_static("header-1"),
|
||||||
|
HeaderValue::from_static("test"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let config = ServarrConfig {
|
||||||
|
custom_headers: Some(header_map),
|
||||||
|
..ServarrConfig::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let v: Value = serde_json::to_value(&config).expect("serialize ok");
|
||||||
|
let custom = v.get("custom_headers").unwrap();
|
||||||
|
assert!(custom.is_object());
|
||||||
|
let obj = custom.as_object().unwrap();
|
||||||
|
|
||||||
|
assert_some_eq_x!(obj.get("x-api-key"), "abc123");
|
||||||
|
assert_some_eq_x!(obj.get("header-1"), "test");
|
||||||
|
|
||||||
|
assert_none!(obj.get("X-Api-Key"));
|
||||||
|
assert_none!(obj.get("HEADER-1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_header_map_none_is_null() {
|
||||||
|
let config = ServarrConfig::default();
|
||||||
|
let v: Value = serde_json::to_value(&config).expect("serialize ok");
|
||||||
|
assert!(v.get("custom_headers").unwrap().is_null());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_deserialize_optional_env_var_is_present() {
|
||||||
|
unsafe { std::env::set_var("TEST_VAR_DESERIALIZE_OPTION", "localhost") };
|
||||||
|
let yaml_data = r#"
|
||||||
|
host: ${TEST_VAR_DESERIALIZE_OPTION}
|
||||||
|
api_token: "test123"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||||
|
|
||||||
|
assert_some_eq_x!(&config.host, "localhost");
|
||||||
|
unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_OPTION") };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_deserialize_optional_env_var_does_not_overwrite_non_env_value() {
|
||||||
|
unsafe { std::env::set_var("TEST_VAR_DESERIALIZE_OPTION_NO_OVERWRITE", "localhost") };
|
||||||
|
let yaml_data = r#"
|
||||||
|
host: www.example.com
|
||||||
|
api_token: "test123"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||||
|
|
||||||
|
assert_some_eq_x!(&config.host, "www.example.com");
|
||||||
|
unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_OPTION_NO_OVERWRITE") };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deserialize_optional_env_var_empty() {
|
||||||
|
let yaml_data = r#"
|
||||||
|
api_token: "test123"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||||
|
|
||||||
|
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]
|
||||||
|
#[serial]
|
||||||
|
fn test_deserialize_optional_env_var_header_map_is_present() {
|
||||||
|
unsafe { std::env::set_var("TEST_VAR_DESERIALIZE_HEADER_OPTION", "localhost") };
|
||||||
|
let expected_custom_headers = {
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert("X-Api-Host", "localhost".parse().unwrap());
|
||||||
|
headers.insert("api-token", "test123".parse().unwrap());
|
||||||
|
headers
|
||||||
|
};
|
||||||
|
let yaml_data = r#"
|
||||||
|
custom_headers:
|
||||||
|
X-Api-Host: ${TEST_VAR_DESERIALIZE_HEADER_OPTION}
|
||||||
|
api-token: "test123"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||||
|
|
||||||
|
assert_some_eq_x!(&config.custom_headers, &expected_custom_headers);
|
||||||
|
unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_HEADER_OPTION") };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_deserialize_optional_env_var_header_map_does_not_overwrite_non_env_value() {
|
||||||
|
unsafe {
|
||||||
|
std::env::set_var(
|
||||||
|
"TEST_VAR_DESERIALIZE_HEADER_OPTION_NO_OVERWRITE",
|
||||||
|
"localhost",
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let expected_custom_headers = {
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert("X-Api-Host", "www.example.com".parse().unwrap());
|
||||||
|
headers.insert("api-token", "test123".parse().unwrap());
|
||||||
|
headers
|
||||||
|
};
|
||||||
|
let yaml_data = r#"
|
||||||
|
custom_headers:
|
||||||
|
X-Api-Host: www.example.com
|
||||||
|
api-token: "test123"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||||
|
|
||||||
|
assert_some_eq_x!(&config.custom_headers, &expected_custom_headers);
|
||||||
|
unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_HEADER_OPTION_NO_OVERWRITE") };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deserialize_optional_env_var_header_map_empty() {
|
||||||
|
let yaml_data = r#"
|
||||||
|
api_token: "test123"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||||
|
|
||||||
|
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]
|
||||||
|
#[serial]
|
||||||
|
fn test_deserialize_optional_u16_env_var_is_present() {
|
||||||
|
unsafe { std::env::set_var("TEST_VAR_DESERIALIZE_OPTION_U16", "1") };
|
||||||
|
let yaml_data = r#"
|
||||||
|
port: ${TEST_VAR_DESERIALIZE_OPTION_U16}
|
||||||
|
api_token: "test123"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||||
|
|
||||||
|
assert_some_eq_x!(config.port, 1);
|
||||||
|
unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_OPTION_U16") };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_deserialize_optional_u16_env_var_does_not_overwrite_non_env_value() {
|
||||||
|
unsafe { std::env::set_var("TEST_VAR_DESERIALIZE_OPTION_U16_UNUSED", "1") };
|
||||||
|
let yaml_data = r#"
|
||||||
|
port: 1234
|
||||||
|
api_token: "test123"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||||
|
|
||||||
|
assert_some_eq_x!(config.port, 1234);
|
||||||
|
unsafe { std::env::remove_var("TEST_VAR_DESERIALIZE_OPTION_U16_UNUSED") };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deserialize_optional_u16_env_var_invalid_number() {
|
||||||
|
let yaml_data = r#"
|
||||||
|
port: "hi"
|
||||||
|
api_token: "test123"
|
||||||
|
"#;
|
||||||
|
let result: Result<ServarrConfig, _> = serde_yaml::from_str(yaml_data);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
let err = result.unwrap_err().to_string();
|
||||||
|
assert_contains!(err, "invalid digit found in string");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deserialize_optional_u16_env_var_empty() {
|
||||||
|
let yaml_data = r#"
|
||||||
|
api_token: "test123"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let config: ServarrConfig = serde_yaml::from_str(yaml_data).unwrap();
|
||||||
|
|
||||||
|
assert_none!(config.port);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_interpolate_env_vars() {
|
||||||
|
unsafe { std::env::set_var("TEST_VAR_INTERPOLATION", "testing") };
|
||||||
|
|
||||||
|
let var = interpolate_env_vars("${TEST_VAR_INTERPOLATION}");
|
||||||
|
|
||||||
|
assert_str_eq!(var, "testing");
|
||||||
|
unsafe { std::env::remove_var("TEST_VAR_INTERPOLATION") };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_interpolate_env_vars_defaults_to_original_string_if_not_in_yaml_interpolation_format() {
|
||||||
|
let var = interpolate_env_vars("TEST_VAR_INTERPOLATION_NON_YAML");
|
||||||
|
|
||||||
|
assert_str_eq!(var, "TEST_VAR_INTERPOLATION_NON_YAML");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_interpolate_env_vars_scrubs_all_unnecessary_characters() {
|
||||||
|
unsafe {
|
||||||
|
std::env::set_var(
|
||||||
|
"TEST_VAR_INTERPOLATION_UNNECESSARY_CHARACTERS",
|
||||||
|
r#"""
|
||||||
|
`"'https://dontdo:this@testing.com/query?test=%20query#results'"` {([\|$!])}
|
||||||
|
"""#,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let var = interpolate_env_vars("${TEST_VAR_INTERPOLATION_UNNECESSARY_CHARACTERS}");
|
||||||
|
|
||||||
|
assert_str_eq!(
|
||||||
|
var,
|
||||||
|
"https://dontdo:this@testing.com/query?test=%20query#results"
|
||||||
|
);
|
||||||
|
unsafe { std::env::remove_var("TEST_VAR_INTERPOLATION_UNNECESSARY_CHARACTERS") };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_interpolate_env_vars_scrubs_all_unnecessary_characters_from_non_environment_variable() {
|
||||||
|
let var = interpolate_env_vars("https://dontdo:this@testing.com/query?test=%20query#results");
|
||||||
|
|
||||||
|
assert_str_eq!(
|
||||||
|
var,
|
||||||
|
"https://dontdo:this@testing.com/query?test=%20query#results"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_servarr_config_redacted_debug() {
|
||||||
|
let name = "Servarr".to_owned();
|
||||||
|
let host = "localhost".to_owned();
|
||||||
|
let port = 1234;
|
||||||
|
let uri = "http://localhost:1234".to_owned();
|
||||||
|
let weight = 100;
|
||||||
|
let api_token = "thisisatest".to_owned();
|
||||||
|
let api_token_file = "/root/.config/api_token".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();
|
||||||
|
custom_headers.insert("X-Custom-Header", "value".parse().unwrap());
|
||||||
|
let expected_str = format!(
|
||||||
|
"ServarrConfig {{ name: Some(\"{name}\"), host: Some(\"{host}\"), port: Some({port}), uri: Some(\"{uri}\"), weight: Some({weight}), api_token: Some(\"***********\"), api_token_file: Some(\"{api_token_file}\"), ssl: 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 {
|
||||||
|
name: Some(name),
|
||||||
|
host: Some(host),
|
||||||
|
port: Some(port),
|
||||||
|
uri: Some(uri),
|
||||||
|
weight: Some(weight),
|
||||||
|
api_token: Some(api_token),
|
||||||
|
api_token_file: Some(api_token_file),
|
||||||
|
ssl_cert_path: Some(ssl_cert_path),
|
||||||
|
ssl: Some(true),
|
||||||
|
custom_headers: Some(custom_headers),
|
||||||
|
monitored_storage_paths: Some(monitored_storage),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_str_eq!(format!("{servarr_config:?}"), expected_str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,43 @@
|
|||||||
use crate::app::key_binding::{KeyBinding, DEFAULT_KEYBINDINGS};
|
use crate::app::App;
|
||||||
|
use crate::app::key_binding::{DEFAULT_KEYBINDINGS, KeyBinding};
|
||||||
|
use crate::app::lidarr::lidarr_context_clues::LidarrContextClueProvider;
|
||||||
|
use crate::app::radarr::radarr_context_clues::RadarrContextClueProvider;
|
||||||
|
use crate::app::sonarr::sonarr_context_clues::SonarrContextClueProvider;
|
||||||
|
use crate::models::Route;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "context_clues_tests.rs"]
|
#[path = "context_clues_tests.rs"]
|
||||||
mod context_clues_tests;
|
mod context_clues_tests;
|
||||||
|
|
||||||
pub(in crate::app) type ContextClue = (KeyBinding, &'static str);
|
pub type ContextClue = (KeyBinding, &'static str);
|
||||||
|
|
||||||
pub fn build_context_clue_string(context_clues: &[(KeyBinding, &str)]) -> String {
|
pub trait ContextClueProvider {
|
||||||
context_clues
|
fn get_context_clues(_app: &mut App<'_>) -> Option<&'static [ContextClue]>;
|
||||||
.iter()
|
|
||||||
.map(|(key_binding, desc)| format!("{} {desc}", key_binding.key))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(" | ")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static SERVARR_CONTEXT_CLUES: [ContextClue; 3] = [
|
pub struct ServarrContextClueProvider;
|
||||||
|
|
||||||
|
impl ContextClueProvider for ServarrContextClueProvider {
|
||||||
|
fn get_context_clues(app: &mut App<'_>) -> Option<&'static [ContextClue]> {
|
||||||
|
match app.get_current_route() {
|
||||||
|
Route::Radarr(_, _) => RadarrContextClueProvider::get_context_clues(app),
|
||||||
|
Route::Sonarr(_, _) => SonarrContextClueProvider::get_context_clues(app),
|
||||||
|
Route::Lidarr(_, _) => LidarrContextClueProvider::get_context_clues(app),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static SERVARR_CONTEXT_CLUES: [ContextClue; 10] = [
|
||||||
|
(DEFAULT_KEYBINDINGS.up, "scroll up"),
|
||||||
|
(DEFAULT_KEYBINDINGS.down, "scroll down"),
|
||||||
|
(DEFAULT_KEYBINDINGS.left, "previous tab"),
|
||||||
|
(DEFAULT_KEYBINDINGS.right, "next tab"),
|
||||||
|
(DEFAULT_KEYBINDINGS.pg_up, DEFAULT_KEYBINDINGS.pg_up.desc),
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.pg_down,
|
||||||
|
DEFAULT_KEYBINDINGS.pg_down.desc,
|
||||||
|
),
|
||||||
(
|
(
|
||||||
DEFAULT_KEYBINDINGS.next_servarr,
|
DEFAULT_KEYBINDINGS.next_servarr,
|
||||||
DEFAULT_KEYBINDINGS.next_servarr.desc,
|
DEFAULT_KEYBINDINGS.next_servarr.desc,
|
||||||
@@ -24,6 +47,7 @@ pub static SERVARR_CONTEXT_CLUES: [ContextClue; 3] = [
|
|||||||
DEFAULT_KEYBINDINGS.previous_servarr.desc,
|
DEFAULT_KEYBINDINGS.previous_servarr.desc,
|
||||||
),
|
),
|
||||||
(DEFAULT_KEYBINDINGS.quit, DEFAULT_KEYBINDINGS.quit.desc),
|
(DEFAULT_KEYBINDINGS.quit, DEFAULT_KEYBINDINGS.quit.desc),
|
||||||
|
(DEFAULT_KEYBINDINGS.help, DEFAULT_KEYBINDINGS.help.desc),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub static BARE_POPUP_CONTEXT_CLUES: [ContextClue; 1] =
|
pub static BARE_POPUP_CONTEXT_CLUES: [ContextClue; 1] =
|
||||||
@@ -78,6 +102,18 @@ pub static INDEXERS_CONTEXT_CLUES: [ContextClue; 6] = [
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
pub static HISTORY_CONTEXT_CLUES: [ContextClue; 6] = [
|
||||||
|
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||||
|
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
||||||
|
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
||||||
|
(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc),
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||||
|
),
|
||||||
|
(DEFAULT_KEYBINDINGS.esc, "cancel filter"),
|
||||||
|
];
|
||||||
|
|
||||||
pub static SYSTEM_CONTEXT_CLUES: [ContextClue; 5] = [
|
pub static SYSTEM_CONTEXT_CLUES: [ContextClue; 5] = [
|
||||||
(DEFAULT_KEYBINDINGS.tasks, "open tasks"),
|
(DEFAULT_KEYBINDINGS.tasks, "open tasks"),
|
||||||
(DEFAULT_KEYBINDINGS.events, "open events"),
|
(DEFAULT_KEYBINDINGS.events, "open events"),
|
||||||
@@ -88,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),
|
||||||
|
];
|
||||||
|
|||||||
@@ -1,212 +1,315 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
|
||||||
|
|
||||||
use crate::app::context_clues::{
|
use crate::app::context_clues::{
|
||||||
BARE_POPUP_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES,
|
BARE_POPUP_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES,
|
||||||
DOWNLOADS_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES,
|
ContextClueProvider, DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
|
||||||
SERVARR_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
|
ROOT_FOLDERS_CONTEXT_CLUES, SERVARR_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
|
||||||
|
SYSTEM_TASKS_CONTEXT_CLUES, ServarrContextClueProvider,
|
||||||
};
|
};
|
||||||
use crate::app::{context_clues::build_context_clue_string, key_binding::DEFAULT_KEYBINDINGS};
|
use crate::app::{App, key_binding::DEFAULT_KEYBINDINGS};
|
||||||
|
use crate::models::servarr_data::ActiveKeybindingBlock;
|
||||||
#[test]
|
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||||
fn test_build_context_clue_string() {
|
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
|
||||||
let test_context_clues_array = [
|
|
||||||
(DEFAULT_KEYBINDINGS.add, "add"),
|
|
||||||
(DEFAULT_KEYBINDINGS.delete, "delete"),
|
|
||||||
];
|
|
||||||
|
|
||||||
assert_str_eq!(
|
|
||||||
build_context_clue_string(&test_context_clues_array),
|
|
||||||
"<a> add | <del> delete"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_servarr_context_clues() {
|
fn test_servarr_context_clues() {
|
||||||
let mut servarr_context_clues_iter = SERVARR_CONTEXT_CLUES.iter();
|
let mut servarr_context_clues_iter = SERVARR_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = servarr_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
servarr_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.next_servarr);
|
&(DEFAULT_KEYBINDINGS.up, "scroll up")
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.next_servarr.desc);
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
let (key_binding, description) = servarr_context_clues_iter.next().unwrap();
|
servarr_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.down, "scroll down")
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.previous_servarr);
|
);
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.previous_servarr.desc);
|
assert_some_eq_x!(
|
||||||
|
servarr_context_clues_iter.next(),
|
||||||
let (key_binding, description) = servarr_context_clues_iter.next().unwrap();
|
&(DEFAULT_KEYBINDINGS.left, "previous tab")
|
||||||
|
);
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.quit);
|
assert_some_eq_x!(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.quit.desc);
|
servarr_context_clues_iter.next(),
|
||||||
assert_eq!(servarr_context_clues_iter.next(), None);
|
&(DEFAULT_KEYBINDINGS.right, "next tab")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
servarr_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.pg_up, DEFAULT_KEYBINDINGS.pg_up.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
servarr_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.pg_down,
|
||||||
|
DEFAULT_KEYBINDINGS.pg_down.desc
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
servarr_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.next_servarr,
|
||||||
|
DEFAULT_KEYBINDINGS.next_servarr.desc
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
servarr_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.previous_servarr,
|
||||||
|
DEFAULT_KEYBINDINGS.previous_servarr.desc
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
servarr_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.quit, DEFAULT_KEYBINDINGS.quit.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
servarr_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.help, DEFAULT_KEYBINDINGS.help.desc)
|
||||||
|
);
|
||||||
|
assert_none!(servarr_context_clues_iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bare_popup_context_clues() {
|
fn test_bare_popup_context_clues() {
|
||||||
let mut bare_popup_context_clues_iter = BARE_POPUP_CONTEXT_CLUES.iter();
|
let mut bare_popup_context_clues_iter = BARE_POPUP_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = bare_popup_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
bare_popup_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
|
);
|
||||||
assert_eq!(bare_popup_context_clues_iter.next(), None);
|
assert_none!(bare_popup_context_clues_iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_downloads_context_clues() {
|
fn test_downloads_context_clues() {
|
||||||
let mut downloads_context_clues_iter = DOWNLOADS_CONTEXT_CLUES.iter();
|
let mut downloads_context_clues_iter = DOWNLOADS_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = downloads_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
downloads_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
&(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
let (key_binding, description) = downloads_context_clues_iter.next().unwrap();
|
)
|
||||||
|
);
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
|
assert_some_eq_x!(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
|
downloads_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc)
|
||||||
let (key_binding, description) = downloads_context_clues_iter.next().unwrap();
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update);
|
downloads_context_clues_iter.next(),
|
||||||
assert_str_eq!(*description, "update downloads");
|
&(DEFAULT_KEYBINDINGS.update, "update downloads")
|
||||||
assert_eq!(downloads_context_clues_iter.next(), None);
|
);
|
||||||
|
assert_none!(downloads_context_clues_iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_blocklist_context_clues() {
|
fn test_blocklist_context_clues() {
|
||||||
let mut blocklist_context_clues_iter = BLOCKLIST_CONTEXT_CLUES.iter();
|
let mut blocklist_context_clues_iter = BLOCKLIST_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
blocklist_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
&(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
|
)
|
||||||
|
);
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
|
assert_some_eq_x!(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
|
blocklist_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||||
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
blocklist_context_clues_iter.next(),
|
||||||
assert_str_eq!(*description, "details");
|
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||||
|
);
|
||||||
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
blocklist_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
|
&(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc)
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
let (key_binding, description) = blocklist_context_clues_iter.next().unwrap();
|
blocklist_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.clear, "clear blocklist")
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.clear);
|
);
|
||||||
assert_str_eq!(*description, "clear blocklist");
|
assert_none!(blocklist_context_clues_iter.next());
|
||||||
assert_eq!(blocklist_context_clues_iter.next(), None);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_confirmation_prompt_context_clues() {
|
fn test_confirmation_prompt_context_clues() {
|
||||||
let mut confirmation_prompt_context_clues_iter = CONFIRMATION_PROMPT_CONTEXT_CLUES.iter();
|
let mut confirmation_prompt_context_clues_iter = CONFIRMATION_PROMPT_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = confirmation_prompt_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
confirmation_prompt_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.confirm);
|
&(DEFAULT_KEYBINDINGS.confirm, "submit")
|
||||||
assert_str_eq!(*description, "submit");
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
let (key_binding, description) = confirmation_prompt_context_clues_iter.next().unwrap();
|
confirmation_prompt_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.esc, "cancel")
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
);
|
||||||
assert_str_eq!(*description, "cancel");
|
assert_none!(confirmation_prompt_context_clues_iter.next());
|
||||||
assert_eq!(confirmation_prompt_context_clues_iter.next(), None);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_root_folders_context_clues() {
|
fn test_root_folders_context_clues() {
|
||||||
let mut root_folders_context_clues_iter = ROOT_FOLDERS_CONTEXT_CLUES.iter();
|
let mut root_folders_context_clues_iter = ROOT_FOLDERS_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = root_folders_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
root_folders_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.add);
|
&(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc)
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.add.desc);
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
let (key_binding, description) = root_folders_context_clues_iter.next().unwrap();
|
root_folders_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc)
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
|
);
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
|
assert_some_eq_x!(
|
||||||
|
root_folders_context_clues_iter.next(),
|
||||||
let (key_binding, description) = root_folders_context_clues_iter.next().unwrap();
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
)
|
||||||
assert_eq!(root_folders_context_clues_iter.next(), None);
|
);
|
||||||
|
assert_none!(root_folders_context_clues_iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_indexers_context_clues() {
|
fn test_indexers_context_clues() {
|
||||||
let mut indexers_context_clues_iter = INDEXERS_CONTEXT_CLUES.iter();
|
let mut indexers_context_clues_iter = INDEXERS_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
indexers_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.submit, "edit indexer")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
indexers_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.settings,
|
||||||
|
DEFAULT_KEYBINDINGS.settings.desc
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
indexers_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
indexers_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.test, "test indexer")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
indexers_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.test_all, "test all indexers")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
indexers_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_none!(indexers_context_clues_iter.next());
|
||||||
|
}
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
#[test]
|
||||||
assert_str_eq!(*description, "edit indexer");
|
fn test_history_context_clues() {
|
||||||
|
let mut history_context_clues_iter = HISTORY_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
history_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.settings);
|
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.settings.desc);
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
|
history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
|
);
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
|
assert_some_eq_x!(
|
||||||
|
history_context_clues_iter.next(),
|
||||||
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
|
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
|
||||||
|
);
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.test);
|
assert_some_eq_x!(
|
||||||
assert_str_eq!(*description, "test indexer");
|
history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc)
|
||||||
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.test_all);
|
history_context_clues_iter.next(),
|
||||||
assert_str_eq!(*description, "test all indexers");
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
|
)
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
);
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
assert_some_eq_x!(
|
||||||
assert_eq!(indexers_context_clues_iter.next(), None);
|
history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.esc, "cancel filter")
|
||||||
|
);
|
||||||
|
assert_none!(history_context_clues_iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_system_context_clues() {
|
fn test_system_context_clues() {
|
||||||
let mut system_context_clues_iter = SYSTEM_CONTEXT_CLUES.iter();
|
let mut system_context_clues_iter = SYSTEM_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = system_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
system_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.tasks, "open tasks")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
system_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.events, "open events")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
system_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.logs, "open logs")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
system_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.update, "open updates")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
system_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_none!(system_context_clues_iter.next());
|
||||||
|
}
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.tasks);
|
#[test]
|
||||||
assert_str_eq!(*description, "open tasks");
|
fn test_system_tasks_context_clues() {
|
||||||
|
let mut system_tasks_context_clues_iter = SYSTEM_TASKS_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = system_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
system_tasks_context_clues_iter.next(),
|
||||||
|
&(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());
|
||||||
|
}
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.events);
|
#[test]
|
||||||
assert_str_eq!(*description, "open events");
|
fn test_servarr_context_clue_provider_delegates_to_radarr_provider() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveRadarrBlock::SystemTasks.into());
|
||||||
|
|
||||||
let (key_binding, description) = system_context_clues_iter.next().unwrap();
|
let context_clues = ServarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.logs);
|
assert_some_eq_x!(context_clues, &SYSTEM_TASKS_CONTEXT_CLUES,);
|
||||||
assert_str_eq!(*description, "open logs");
|
}
|
||||||
|
|
||||||
let (key_binding, description) = system_context_clues_iter.next().unwrap();
|
#[test]
|
||||||
|
fn test_servarr_context_clue_provider_delegates_to_sonarr_provider() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::SystemTasks.into());
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update);
|
let context_clues = ServarrContextClueProvider::get_context_clues(&mut app);
|
||||||
assert_str_eq!(*description, "open updates");
|
|
||||||
|
|
||||||
let (key_binding, description) = system_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(context_clues, &SYSTEM_TASKS_CONTEXT_CLUES,);
|
||||||
|
}
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
#[test]
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
fn test_servarr_context_clue_provider_unsupported_route_returns_none() {
|
||||||
assert_eq!(system_context_clues_iter.next(), None);
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveKeybindingBlock::Help.into());
|
||||||
|
|
||||||
|
let context_clues = ServarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_none!(context_clues);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ generate_keybindings! {
|
|||||||
down,
|
down,
|
||||||
left,
|
left,
|
||||||
right,
|
right,
|
||||||
|
pg_down,
|
||||||
|
pg_up,
|
||||||
backspace,
|
backspace,
|
||||||
next_servarr,
|
next_servarr,
|
||||||
previous_servarr,
|
previous_servarr,
|
||||||
@@ -21,6 +23,7 @@ generate_keybindings! {
|
|||||||
search,
|
search,
|
||||||
auto_search,
|
auto_search,
|
||||||
settings,
|
settings,
|
||||||
|
help,
|
||||||
filter,
|
filter,
|
||||||
sort,
|
sort,
|
||||||
edit,
|
edit,
|
||||||
@@ -44,128 +47,203 @@ generate_keybindings! {
|
|||||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||||
pub struct KeyBinding {
|
pub struct KeyBinding {
|
||||||
pub key: Key,
|
pub key: Key,
|
||||||
|
pub alt: Option<Key>,
|
||||||
pub desc: &'static str,
|
pub desc: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings {
|
pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings {
|
||||||
add: KeyBinding {
|
add: KeyBinding {
|
||||||
key: Key::Char('a'),
|
key: Key::Char('a'),
|
||||||
|
alt: None,
|
||||||
desc: "add",
|
desc: "add",
|
||||||
},
|
},
|
||||||
up: KeyBinding {
|
up: KeyBinding {
|
||||||
key: Key::Up,
|
key: Key::Up,
|
||||||
|
alt: Some(Key::Char('k')),
|
||||||
desc: "up",
|
desc: "up",
|
||||||
},
|
},
|
||||||
down: KeyBinding {
|
down: KeyBinding {
|
||||||
key: Key::Down,
|
key: Key::Down,
|
||||||
|
alt: Some(Key::Char('j')),
|
||||||
desc: "down",
|
desc: "down",
|
||||||
},
|
},
|
||||||
left: KeyBinding {
|
left: KeyBinding {
|
||||||
key: Key::Left,
|
key: Key::Left,
|
||||||
|
alt: Some(Key::Char('h')),
|
||||||
desc: "left",
|
desc: "left",
|
||||||
},
|
},
|
||||||
right: KeyBinding {
|
right: KeyBinding {
|
||||||
key: Key::Right,
|
key: Key::Right,
|
||||||
|
alt: Some(Key::Char('l')),
|
||||||
desc: "right",
|
desc: "right",
|
||||||
},
|
},
|
||||||
|
pg_down: KeyBinding {
|
||||||
|
key: Key::PgDown,
|
||||||
|
alt: Some(Key::Ctrl('d')),
|
||||||
|
desc: "page down",
|
||||||
|
},
|
||||||
|
pg_up: KeyBinding {
|
||||||
|
key: Key::PgUp,
|
||||||
|
alt: Some(Key::Ctrl('u')),
|
||||||
|
desc: "page up",
|
||||||
|
},
|
||||||
backspace: KeyBinding {
|
backspace: KeyBinding {
|
||||||
key: Key::Backspace,
|
key: Key::Backspace,
|
||||||
|
alt: Some(Key::Ctrl('h')),
|
||||||
desc: "backspace",
|
desc: "backspace",
|
||||||
},
|
},
|
||||||
next_servarr: KeyBinding {
|
next_servarr: KeyBinding {
|
||||||
key: Key::Tab,
|
key: Key::Tab,
|
||||||
|
alt: None,
|
||||||
desc: "next servarr",
|
desc: "next servarr",
|
||||||
},
|
},
|
||||||
previous_servarr: KeyBinding {
|
previous_servarr: KeyBinding {
|
||||||
key: Key::BackTab,
|
key: Key::BackTab,
|
||||||
|
alt: None,
|
||||||
desc: "previous servarr",
|
desc: "previous servarr",
|
||||||
},
|
},
|
||||||
clear: KeyBinding {
|
clear: KeyBinding {
|
||||||
key: Key::Char('c'),
|
key: Key::Char('c'),
|
||||||
|
alt: None,
|
||||||
desc: "clear",
|
desc: "clear",
|
||||||
},
|
},
|
||||||
auto_search: KeyBinding {
|
auto_search: KeyBinding {
|
||||||
key: Key::Char('S'),
|
key: Key::Char('S'),
|
||||||
|
alt: None,
|
||||||
desc: "auto search",
|
desc: "auto search",
|
||||||
},
|
},
|
||||||
search: KeyBinding {
|
search: KeyBinding {
|
||||||
key: Key::Char('s'),
|
key: Key::Char('s'),
|
||||||
|
alt: None,
|
||||||
desc: "search",
|
desc: "search",
|
||||||
},
|
},
|
||||||
settings: KeyBinding {
|
settings: KeyBinding {
|
||||||
key: Key::Char('S'),
|
key: Key::Char('S'),
|
||||||
|
alt: None,
|
||||||
desc: "settings",
|
desc: "settings",
|
||||||
},
|
},
|
||||||
|
help: KeyBinding {
|
||||||
|
key: Key::Char('?'),
|
||||||
|
alt: None,
|
||||||
|
desc: "show/hide keybindings",
|
||||||
|
},
|
||||||
filter: KeyBinding {
|
filter: KeyBinding {
|
||||||
key: Key::Char('f'),
|
key: Key::Char('f'),
|
||||||
|
alt: None,
|
||||||
desc: "filter",
|
desc: "filter",
|
||||||
},
|
},
|
||||||
sort: KeyBinding {
|
sort: KeyBinding {
|
||||||
key: Key::Char('o'),
|
key: Key::Char('o'),
|
||||||
|
alt: None,
|
||||||
desc: "sort",
|
desc: "sort",
|
||||||
},
|
},
|
||||||
edit: KeyBinding {
|
edit: KeyBinding {
|
||||||
key: Key::Char('e'),
|
key: Key::Char('e'),
|
||||||
|
alt: None,
|
||||||
desc: "edit",
|
desc: "edit",
|
||||||
},
|
},
|
||||||
events: KeyBinding {
|
events: KeyBinding {
|
||||||
key: Key::Char('e'),
|
key: Key::Char('e'),
|
||||||
|
alt: None,
|
||||||
desc: "events",
|
desc: "events",
|
||||||
},
|
},
|
||||||
logs: KeyBinding {
|
logs: KeyBinding {
|
||||||
key: Key::Char('l'),
|
key: Key::Char('L'),
|
||||||
|
alt: None,
|
||||||
desc: "logs",
|
desc: "logs",
|
||||||
},
|
},
|
||||||
tasks: KeyBinding {
|
tasks: KeyBinding {
|
||||||
key: Key::Char('t'),
|
key: Key::Char('t'),
|
||||||
|
alt: None,
|
||||||
desc: "tasks",
|
desc: "tasks",
|
||||||
},
|
},
|
||||||
test: KeyBinding {
|
test: KeyBinding {
|
||||||
key: Key::Char('t'),
|
key: Key::Char('t'),
|
||||||
|
alt: None,
|
||||||
desc: "test",
|
desc: "test",
|
||||||
},
|
},
|
||||||
test_all: KeyBinding {
|
test_all: KeyBinding {
|
||||||
key: Key::Char('T'),
|
key: Key::Char('T'),
|
||||||
|
alt: None,
|
||||||
desc: "test all",
|
desc: "test all",
|
||||||
},
|
},
|
||||||
toggle_monitoring: KeyBinding {
|
toggle_monitoring: KeyBinding {
|
||||||
key: Key::Char('m'),
|
key: Key::Char('m'),
|
||||||
|
alt: None,
|
||||||
desc: "toggle monitoring",
|
desc: "toggle monitoring",
|
||||||
},
|
},
|
||||||
refresh: KeyBinding {
|
refresh: KeyBinding {
|
||||||
key: Key::Ctrl('r'),
|
key: Key::Ctrl('r'),
|
||||||
|
alt: None,
|
||||||
desc: "refresh",
|
desc: "refresh",
|
||||||
},
|
},
|
||||||
update: KeyBinding {
|
update: KeyBinding {
|
||||||
key: Key::Char('u'),
|
key: Key::Char('u'),
|
||||||
|
alt: None,
|
||||||
desc: "update",
|
desc: "update",
|
||||||
},
|
},
|
||||||
home: KeyBinding {
|
home: KeyBinding {
|
||||||
key: Key::Home,
|
key: Key::Home,
|
||||||
|
alt: None,
|
||||||
desc: "home",
|
desc: "home",
|
||||||
},
|
},
|
||||||
end: KeyBinding {
|
end: KeyBinding {
|
||||||
key: Key::End,
|
key: Key::End,
|
||||||
|
alt: None,
|
||||||
desc: "end",
|
desc: "end",
|
||||||
},
|
},
|
||||||
delete: KeyBinding {
|
delete: KeyBinding {
|
||||||
key: Key::Delete,
|
key: Key::Delete,
|
||||||
|
alt: None,
|
||||||
desc: "delete",
|
desc: "delete",
|
||||||
},
|
},
|
||||||
submit: KeyBinding {
|
submit: KeyBinding {
|
||||||
key: Key::Enter,
|
key: Key::Enter,
|
||||||
|
alt: None,
|
||||||
desc: "submit",
|
desc: "submit",
|
||||||
},
|
},
|
||||||
confirm: KeyBinding {
|
confirm: KeyBinding {
|
||||||
key: Key::Ctrl('s'),
|
key: Key::Ctrl('s'),
|
||||||
|
alt: None,
|
||||||
desc: "submit",
|
desc: "submit",
|
||||||
},
|
},
|
||||||
quit: KeyBinding {
|
quit: KeyBinding {
|
||||||
key: Key::Char('q'),
|
key: Key::Char('q'),
|
||||||
|
alt: None,
|
||||||
desc: "quit",
|
desc: "quit",
|
||||||
},
|
},
|
||||||
esc: KeyBinding {
|
esc: KeyBinding {
|
||||||
key: Key::Esc,
|
key: Key::Esc,
|
||||||
|
alt: None,
|
||||||
desc: "close",
|
desc: "close",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! matches_key {
|
||||||
|
($binding:ident, $key:expr) => {
|
||||||
|
$crate::app::key_binding::DEFAULT_KEYBINDINGS.$binding.key == $key
|
||||||
|
|| ($crate::app::key_binding::DEFAULT_KEYBINDINGS
|
||||||
|
.$binding
|
||||||
|
.alt
|
||||||
|
.is_some()
|
||||||
|
&& $crate::app::key_binding::DEFAULT_KEYBINDINGS
|
||||||
|
.$binding
|
||||||
|
.alt
|
||||||
|
.unwrap()
|
||||||
|
== $key)
|
||||||
|
};
|
||||||
|
($binding:ident, $key:expr, $ignore_special_keys:expr) => {
|
||||||
|
$crate::app::key_binding::DEFAULT_KEYBINDINGS.$binding.key == $key
|
||||||
|
|| !$ignore_special_keys
|
||||||
|
&& ($crate::app::key_binding::DEFAULT_KEYBINDINGS
|
||||||
|
.$binding
|
||||||
|
.alt
|
||||||
|
.is_some()
|
||||||
|
&& $crate::app::key_binding::DEFAULT_KEYBINDINGS
|
||||||
|
.$binding
|
||||||
|
.alt
|
||||||
|
.unwrap()
|
||||||
|
== $key)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,46 +3,90 @@ mod test {
|
|||||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
|
|
||||||
use crate::app::key_binding::{KeyBinding, DEFAULT_KEYBINDINGS};
|
use crate::app::key_binding::{DEFAULT_KEYBINDINGS, KeyBinding};
|
||||||
use crate::event::Key;
|
use crate::event::Key;
|
||||||
|
use crate::matches_key;
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(DEFAULT_KEYBINDINGS.add, Key::Char('a'), "add")]
|
#[case(DEFAULT_KEYBINDINGS.add, Key::Char('a'), None, "add")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.up, Key::Up, "up")]
|
#[case(DEFAULT_KEYBINDINGS.up, Key::Up, Some(Key::Char('k')), "up")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.down, Key::Down, "down")]
|
#[case(DEFAULT_KEYBINDINGS.down, Key::Down, Some(Key::Char('j')), "down")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.left, Key::Left, "left")]
|
#[case(DEFAULT_KEYBINDINGS.left, Key::Left, Some(Key::Char('h')), "left")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.right, Key::Right, "right")]
|
#[case(DEFAULT_KEYBINDINGS.right, Key::Right, Some(Key::Char('l')), "right")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.backspace, Key::Backspace, "backspace")]
|
#[case(DEFAULT_KEYBINDINGS.pg_down, Key::PgDown, Some(Key::Ctrl('d')), "page down")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.next_servarr, Key::Tab, "next servarr")]
|
#[case(DEFAULT_KEYBINDINGS.pg_up, Key::PgUp, Some(Key::Ctrl('u')), "page up")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.previous_servarr, Key::BackTab, "previous servarr")]
|
#[case(DEFAULT_KEYBINDINGS.backspace, Key::Backspace, Some(Key::Ctrl('h')), "backspace")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.clear, Key::Char('c'), "clear")]
|
#[case(DEFAULT_KEYBINDINGS.next_servarr, Key::Tab, None, "next servarr")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.auto_search, Key::Char('S'), "auto search")]
|
#[case(DEFAULT_KEYBINDINGS.previous_servarr, Key::BackTab, None, "previous servarr")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.search, Key::Char('s'), "search")]
|
#[case(DEFAULT_KEYBINDINGS.clear, Key::Char('c'), None, "clear")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.settings, Key::Char('S'), "settings")]
|
#[case(DEFAULT_KEYBINDINGS.auto_search, Key::Char('S'), None, "auto search")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.filter, Key::Char('f'), "filter")]
|
#[case(DEFAULT_KEYBINDINGS.search, Key::Char('s'), None, "search")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.sort, Key::Char('o'), "sort")]
|
#[case(DEFAULT_KEYBINDINGS.settings, Key::Char('S'), None, "settings")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.edit, Key::Char('e'), "edit")]
|
#[case(DEFAULT_KEYBINDINGS.help, Key::Char('?'), None, "show/hide keybindings")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.events, Key::Char('e'), "events")]
|
#[case(DEFAULT_KEYBINDINGS.filter, Key::Char('f'), None, "filter")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.logs, Key::Char('l'), "logs")]
|
#[case(DEFAULT_KEYBINDINGS.sort, Key::Char('o'), None, "sort")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.tasks, Key::Char('t'), "tasks")]
|
#[case(DEFAULT_KEYBINDINGS.edit, Key::Char('e'), None, "edit")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.test, Key::Char('t'), "test")]
|
#[case(DEFAULT_KEYBINDINGS.events, Key::Char('e'), None, "events")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.test_all, Key::Char('T'), "test all")]
|
#[case(DEFAULT_KEYBINDINGS.logs, Key::Char('L'), None, "logs")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.toggle_monitoring, Key::Char('m'), "toggle monitoring")]
|
#[case(DEFAULT_KEYBINDINGS.tasks, Key::Char('t'), None, "tasks")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.refresh, Key::Ctrl('r'), "refresh")]
|
#[case(DEFAULT_KEYBINDINGS.test, Key::Char('t'), None, "test")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.update, Key::Char('u'), "update")]
|
#[case(DEFAULT_KEYBINDINGS.test_all, Key::Char('T'), None, "test all")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.home, Key::Home, "home")]
|
#[case(DEFAULT_KEYBINDINGS.toggle_monitoring, Key::Char('m'), None, "toggle monitoring")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.end, Key::End, "end")]
|
#[case(DEFAULT_KEYBINDINGS.refresh, Key::Ctrl('r'), None, "refresh")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.delete, Key::Delete, "delete")]
|
#[case(DEFAULT_KEYBINDINGS.update, Key::Char('u'), None, "update")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.submit, Key::Enter, "submit")]
|
#[case(DEFAULT_KEYBINDINGS.home, Key::Home, None, "home")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.confirm, Key::Ctrl('s'), "submit")]
|
#[case(DEFAULT_KEYBINDINGS.end, Key::End, None, "end")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.quit, Key::Char('q'), "quit")]
|
#[case(DEFAULT_KEYBINDINGS.delete, Key::Delete, None, "delete")]
|
||||||
#[case(DEFAULT_KEYBINDINGS.esc, Key::Esc, "close")]
|
#[case(DEFAULT_KEYBINDINGS.submit, Key::Enter, None, "submit")]
|
||||||
|
#[case(DEFAULT_KEYBINDINGS.confirm, Key::Ctrl('s'), None, "submit")]
|
||||||
|
#[case(DEFAULT_KEYBINDINGS.quit, Key::Char('q'), None, "quit")]
|
||||||
|
#[case(DEFAULT_KEYBINDINGS.esc, Key::Esc, None, "close")]
|
||||||
fn test_default_key_bindings_and_descriptions(
|
fn test_default_key_bindings_and_descriptions(
|
||||||
#[case] key_binding: KeyBinding,
|
#[case] key_binding: KeyBinding,
|
||||||
#[case] expected_key: Key,
|
#[case] expected_key: Key,
|
||||||
|
#[case] expected_alt_key: Option<Key>,
|
||||||
#[case] expected_desc: &str,
|
#[case] expected_desc: &str,
|
||||||
) {
|
) {
|
||||||
assert_eq!(key_binding.key, expected_key);
|
assert_eq!(key_binding.key, expected_key);
|
||||||
|
assert_eq!(key_binding.alt, expected_alt_key);
|
||||||
assert_str_eq!(key_binding.desc, expected_desc);
|
assert_str_eq!(key_binding.desc, expected_desc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_matches_key_macro() {
|
||||||
|
let key = Key::Char('t');
|
||||||
|
|
||||||
|
assert!(matches_key!(test, key));
|
||||||
|
assert!(!matches_key!(test, Key::Char('T')));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_matches_key_macro_with_alt_keybinding() {
|
||||||
|
let alt_key = Key::Char('k');
|
||||||
|
let key = Key::Up;
|
||||||
|
|
||||||
|
assert!(matches_key!(up, key));
|
||||||
|
assert!(matches_key!(up, alt_key));
|
||||||
|
assert!(!matches_key!(up, Key::Char('t')));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_matches_key_macro_with_alt_keybinding_uses_alt_key_when_ignore_special_keys_is_false() {
|
||||||
|
let alt_key = Key::Char('k');
|
||||||
|
let key = Key::Up;
|
||||||
|
|
||||||
|
assert!(matches_key!(up, key, false));
|
||||||
|
assert!(matches_key!(up, alt_key, false));
|
||||||
|
assert!(!matches_key!(up, Key::Char('t'), false));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_matches_key_macro_with_alt_keybinding_ignores_alt_key_when_ignore_special_keys_is_true() {
|
||||||
|
let alt_key = Key::Char('k');
|
||||||
|
let key = Key::Up;
|
||||||
|
|
||||||
|
assert!(matches_key!(up, key, true));
|
||||||
|
assert!(!matches_key!(up, alt_key, true));
|
||||||
|
assert!(!matches_key!(up, Key::Char('t'), true));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,225 @@
|
|||||||
|
use crate::app::App;
|
||||||
|
use crate::app::context_clues::{
|
||||||
|
BARE_POPUP_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, ContextClue, ContextClueProvider,
|
||||||
|
SYSTEM_TASKS_CONTEXT_CLUES,
|
||||||
|
};
|
||||||
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
|
use crate::models::Route;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
|
ADD_ARTIST_BLOCKS, ADD_ROOT_FOLDER_BLOCKS, ALBUM_DETAILS_BLOCKS, ARTIST_DETAILS_BLOCKS,
|
||||||
|
ActiveLidarrBlock, EDIT_ARTIST_BLOCKS, EDIT_INDEXER_BLOCKS, INDEXER_SETTINGS_BLOCKS,
|
||||||
|
TRACK_DETAILS_BLOCKS,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "lidarr_context_clues_tests.rs"]
|
||||||
|
mod lidarr_context_clues_tests;
|
||||||
|
|
||||||
|
pub static ARTISTS_CONTEXT_CLUES: [ContextClue; 10] = [
|
||||||
|
(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc),
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.toggle_monitoring,
|
||||||
|
DEFAULT_KEYBINDINGS.toggle_monitoring.desc,
|
||||||
|
),
|
||||||
|
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
||||||
|
(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc),
|
||||||
|
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
|
||||||
|
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
||||||
|
(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc),
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||||
|
),
|
||||||
|
(DEFAULT_KEYBINDINGS.update, "update all"),
|
||||||
|
(DEFAULT_KEYBINDINGS.esc, "cancel filter"),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub static ADD_ARTIST_SEARCH_RESULTS_CONTEXT_CLUES: [ContextClue; 2] = [
|
||||||
|
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||||
|
(DEFAULT_KEYBINDINGS.esc, "edit search"),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub static ARTIST_DETAILS_CONTEXT_CLUES: [ContextClue; 8] = [
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||||
|
),
|
||||||
|
(DEFAULT_KEYBINDINGS.edit, "edit artist"),
|
||||||
|
(DEFAULT_KEYBINDINGS.delete, "delete album"),
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.toggle_monitoring,
|
||||||
|
"toggle album monitoring",
|
||||||
|
),
|
||||||
|
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
||||||
|
(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc),
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search,
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||||
|
),
|
||||||
|
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub static ARTIST_HISTORY_CONTEXT_CLUES: [ContextClue; 9] = [
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||||
|
),
|
||||||
|
(DEFAULT_KEYBINDINGS.edit, "edit artist"),
|
||||||
|
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||||
|
(DEFAULT_KEYBINDINGS.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;
|
||||||
|
|
||||||
|
impl ContextClueProvider for LidarrContextClueProvider {
|
||||||
|
fn get_context_clues(app: &mut App<'_>) -> Option<&'static [ContextClue]> {
|
||||||
|
let Route::Lidarr(active_lidarr_block, _context_option) = app.get_current_route() else {
|
||||||
|
panic!("LidarrContextClueProvider::get_context_clues called with non-Lidarr route");
|
||||||
|
};
|
||||||
|
|
||||||
|
match active_lidarr_block {
|
||||||
|
_ if ARTIST_DETAILS_BLOCKS.contains(&active_lidarr_block) => app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.artist_info_tabs
|
||||||
|
.get_active_route_contextual_help(),
|
||||||
|
_ if ALBUM_DETAILS_BLOCKS.contains(&active_lidarr_block) => app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.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)
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AddArtistPrompt
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectMonitor
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectMonitorNewItems
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectQualityProfile
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectMetadataProfile
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectRootFolder
|
||||||
|
| ActiveLidarrBlock::AddArtistTagsInput
|
||||||
|
| ActiveLidarrBlock::AddArtistAlreadyInLibrary => Some(&CONFIRMATION_PROMPT_CONTEXT_CLUES),
|
||||||
|
_ if ADD_ARTIST_BLOCKS.contains(&active_lidarr_block) => {
|
||||||
|
Some(&ADD_ARTIST_SEARCH_RESULTS_CONTEXT_CLUES)
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::SystemTasks => Some(&SYSTEM_TASKS_CONTEXT_CLUES),
|
||||||
|
_ => app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.main_tabs
|
||||||
|
.get_active_route_contextual_help(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,589 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::app::context_clues::{
|
||||||
|
BARE_POPUP_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, ContextClue, ContextClueProvider,
|
||||||
|
SYSTEM_TASKS_CONTEXT_CLUES,
|
||||||
|
};
|
||||||
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
|
use crate::app::lidarr::lidarr_context_clues::{
|
||||||
|
ADD_ARTIST_SEARCH_RESULTS_CONTEXT_CLUES, ALBUM_DETAILS_CONTEXT_CLUES,
|
||||||
|
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::{
|
||||||
|
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 rstest::rstest;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artists_context_clues() {
|
||||||
|
let mut artists_context_clues_iter = ARTISTS_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artists_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artists_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.toggle_monitoring,
|
||||||
|
DEFAULT_KEYBINDINGS.toggle_monitoring.desc
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artists_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artists_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artists_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artists_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artists_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artists_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artists_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.update, "update all")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artists_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.esc, "cancel filter")
|
||||||
|
);
|
||||||
|
assert_none!(artists_context_clues_iter.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artist_details_context_clues() {
|
||||||
|
let mut artist_details_context_clues_iter = ARTIST_DETAILS_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artist_details_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artist_details_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.edit, "edit artist")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artist_details_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.delete, "delete album")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artist_details_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.toggle_monitoring,
|
||||||
|
"toggle album monitoring",
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artist_details_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artist_details_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artist_details_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search,
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artist_details_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||||
|
);
|
||||||
|
assert_none!(artist_details_context_clues_iter.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_artist_search_results_context_clues() {
|
||||||
|
let mut add_artist_search_results_context_clues_iter =
|
||||||
|
ADD_ARTIST_SEARCH_RESULTS_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
|
assert_some_eq_x!(
|
||||||
|
add_artist_search_results_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
add_artist_search_results_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.esc, "edit search")
|
||||||
|
);
|
||||||
|
assert_none!(add_artist_search_results_context_clues_iter.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(
|
||||||
|
expected = "LidarrContextClueProvider::get_context_clues called with non-Lidarr route"
|
||||||
|
)]
|
||||||
|
fn test_lidarr_context_clue_provider_get_context_clues_non_lidarr_route() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveRadarrBlock::default().into());
|
||||||
|
|
||||||
|
LidarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artist_history_context_clues() {
|
||||||
|
let mut artist_history_context_clues_iter = ARTIST_HISTORY_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artist_history_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artist_history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.edit, "edit artist")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artist_history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artist_history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.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]
|
||||||
|
#[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(
|
||||||
|
#[case] index: usize,
|
||||||
|
#[case] active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
#[case] expected_context_clues: &[ContextClue],
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data = LidarrData::default();
|
||||||
|
app.data.lidarr_data.artist_info_tabs.set_index(index);
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
|
||||||
|
let context_clues = LidarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, expected_context_clues);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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]
|
||||||
|
fn test_lidarr_context_clue_provider_artists_block() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
||||||
|
|
||||||
|
let context_clues = LidarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, &ARTISTS_CONTEXT_CLUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_context_clue_provider_artists_sort_prompt_block() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistsSortPrompt.into());
|
||||||
|
|
||||||
|
let context_clues = LidarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, &ARTISTS_CONTEXT_CLUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_context_clue_provider_search_artists_block() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::SearchArtists.into());
|
||||||
|
|
||||||
|
let context_clues = LidarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, &ARTISTS_CONTEXT_CLUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_context_clue_provider_filter_artists_block() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::FilterArtists.into());
|
||||||
|
|
||||||
|
let context_clues = LidarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, &ARTISTS_CONTEXT_CLUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_lidarr_context_clue_provider_bare_popup_context_clues(
|
||||||
|
#[values(
|
||||||
|
ActiveLidarrBlock::AddArtistSearchInput,
|
||||||
|
ActiveLidarrBlock::AddArtistEmptySearchResults,
|
||||||
|
ActiveLidarrBlock::TestAllIndexers
|
||||||
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
|
||||||
|
let context_clues = LidarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, &BARE_POPUP_CONTEXT_CLUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_context_clue_provider_confirmation_prompt_popup_clues_edit_indexer_blocks() {
|
||||||
|
let mut blocks = EDIT_ARTIST_BLOCKS.to_vec();
|
||||||
|
blocks.extend(ADD_ROOT_FOLDER_BLOCKS);
|
||||||
|
blocks.extend(INDEXER_SETTINGS_BLOCKS);
|
||||||
|
blocks.extend(EDIT_INDEXER_BLOCKS);
|
||||||
|
|
||||||
|
for active_lidarr_block in blocks {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
|
||||||
|
let context_clues = LidarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_context_clue_provider_add_artist_search_results_context_clues() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::AddArtistSearchResults.into());
|
||||||
|
|
||||||
|
let context_clues = LidarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, &ADD_ARTIST_SEARCH_RESULTS_CONTEXT_CLUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_lidarr_context_clue_provider_confirmation_prompt_context_clues_add_artist_blocks(
|
||||||
|
#[values(
|
||||||
|
ActiveLidarrBlock::AddArtistPrompt,
|
||||||
|
ActiveLidarrBlock::AddArtistSelectMonitor,
|
||||||
|
ActiveLidarrBlock::AddArtistSelectMonitorNewItems,
|
||||||
|
ActiveLidarrBlock::AddArtistSelectQualityProfile,
|
||||||
|
ActiveLidarrBlock::AddArtistSelectMetadataProfile,
|
||||||
|
ActiveLidarrBlock::AddArtistSelectRootFolder,
|
||||||
|
ActiveLidarrBlock::AddArtistTagsInput,
|
||||||
|
ActiveLidarrBlock::AddArtistAlreadyInLibrary
|
||||||
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
|
||||||
|
let context_clues = LidarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,783 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
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::modals::AlbumDetailsModal;
|
||||||
|
use crate::models::servarr_models::Indexer;
|
||||||
|
use crate::network::NetworkEvent;
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::{
|
||||||
|
album, artist, track,
|
||||||
|
};
|
||||||
|
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_lidarr_block_artists() {
|
||||||
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
app.network_tx = Some(tx);
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_lidarr_block(&ActiveLidarrBlock::Artists)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert_eq!(
|
||||||
|
rx.recv().await.unwrap(),
|
||||||
|
LidarrEvent::GetQualityProfiles.into()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
rx.recv().await.unwrap(),
|
||||||
|
LidarrEvent::GetMetadataProfiles.into()
|
||||||
|
);
|
||||||
|
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetTags.into());
|
||||||
|
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::ListArtists.into());
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_lidarr_block_artist_details() {
|
||||||
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
app.data.lidarr_data.artists.set_items(vec![artist()]);
|
||||||
|
app.network_tx = Some(tx);
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_lidarr_block(&ActiveLidarrBlock::ArtistDetails)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetAlbums(1).into());
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_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_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]
|
||||||
|
async fn test_dispatch_by_lidarr_block_add_artist_search_results() {
|
||||||
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
app.network_tx = Some(tx);
|
||||||
|
app.data.lidarr_data.add_artist_search = Some("test artist".into());
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_lidarr_block(&ActiveLidarrBlock::AddArtistSearchResults)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert_eq!(
|
||||||
|
rx.recv().await.unwrap(),
|
||||||
|
LidarrEvent::SearchNewArtist("test artist".to_owned()).into()
|
||||||
|
);
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_history_block() {
|
||||||
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
app.network_tx = Some(tx);
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_lidarr_block(&ActiveLidarrBlock::History)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert_eq!(
|
||||||
|
rx.recv().await.unwrap(),
|
||||||
|
LidarrEvent::GetHistory(500).into()
|
||||||
|
);
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_root_folders_block() {
|
||||||
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
app.network_tx = Some(tx);
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_lidarr_block(&ActiveLidarrBlock::RootFolders)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetRootFolders.into());
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_indexers_block() {
|
||||||
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
app.network_tx = Some(tx);
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_lidarr_block(&ActiveLidarrBlock::Indexers)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetTags.into());
|
||||||
|
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetIndexers.into());
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_all_indexer_settings_block() {
|
||||||
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
app.network_tx = Some(tx);
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_lidarr_block(&ActiveLidarrBlock::AllIndexerSettingsPrompt)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert_eq!(
|
||||||
|
rx.recv().await.unwrap(),
|
||||||
|
LidarrEvent::GetAllIndexerSettings.into()
|
||||||
|
);
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_test_indexer_block() {
|
||||||
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
app.network_tx = Some(tx);
|
||||||
|
app.data.lidarr_data.indexers.set_items(vec![Indexer {
|
||||||
|
id: 1,
|
||||||
|
..Indexer::default()
|
||||||
|
}]);
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_lidarr_block(&ActiveLidarrBlock::TestIndexer)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::TestIndexer(1).into());
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_test_all_indexers_block() {
|
||||||
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
app.network_tx = Some(tx);
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_lidarr_block(&ActiveLidarrBlock::TestAllIndexers)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_eq!(
|
||||||
|
rx.recv().await.unwrap(),
|
||||||
|
LidarrEvent::TestAllIndexers.into()
|
||||||
|
);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_system_block() {
|
||||||
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
app.network_tx = Some(tx);
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_lidarr_block(&ActiveLidarrBlock::System)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetTasks.into());
|
||||||
|
assert_eq!(
|
||||||
|
rx.recv().await.unwrap(),
|
||||||
|
LidarrEvent::GetQueuedEvents.into()
|
||||||
|
);
|
||||||
|
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetLogs(500).into());
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_system_updates_block() {
|
||||||
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
app.network_tx = Some(tx);
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_lidarr_block(&ActiveLidarrBlock::SystemUpdates)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetUpdates.into());
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_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]
|
||||||
|
async fn test_extract_add_new_artist_search_query() {
|
||||||
|
let app = App::test_default_fully_populated();
|
||||||
|
|
||||||
|
let query = app.extract_add_new_artist_search_query().await;
|
||||||
|
|
||||||
|
assert_str_eq!(query, "Test Artist");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[should_panic(expected = "Add artist search is empty")]
|
||||||
|
async fn test_extract_add_new_artist_search_query_panics_when_the_query_is_not_set() {
|
||||||
|
let app = App::test_default();
|
||||||
|
|
||||||
|
app.extract_add_new_artist_search_query().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_extract_artist_id() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.artists.set_items(vec![artist()]);
|
||||||
|
|
||||||
|
assert_eq!(app.extract_artist_id().await, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_extract_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,279 @@
|
|||||||
|
use super::App;
|
||||||
|
use crate::{
|
||||||
|
models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock,
|
||||||
|
network::lidarr_network::LidarrEvent,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod lidarr_context_clues;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "lidarr_tests.rs"]
|
||||||
|
mod lidarr_tests;
|
||||||
|
|
||||||
|
impl App<'_> {
|
||||||
|
pub(super) async fn dispatch_by_lidarr_block(&mut self, active_lidarr_block: &ActiveLidarrBlock) {
|
||||||
|
match active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::Artists => {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetQualityProfiles.into())
|
||||||
|
.await;
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetMetadataProfiles.into())
|
||||||
|
.await;
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetTags.into())
|
||||||
|
.await;
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::ListArtists.into())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::Blocklist => {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetBlocklist.into())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::Downloads => {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetDownloads(500).into())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::ArtistDetails => {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetAlbums(self.extract_artist_id().await).into())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::ArtistHistory => {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(
|
||||||
|
LidarrEvent::GetArtistHistory(self.extract_artist_id().await).into(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::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 => {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(
|
||||||
|
LidarrEvent::SearchNewArtist(self.extract_add_new_artist_search_query().await).into(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::History => {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetHistory(500).into())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::RootFolders => {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetRootFolders.into())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::Indexers => {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetTags.into())
|
||||||
|
.await;
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetIndexers.into())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AllIndexerSettingsPrompt => {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetAllIndexerSettings.into())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::TestIndexer => {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(
|
||||||
|
LidarrEvent::TestIndexer(self.extract_lidarr_indexer_id().await).into(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::TestAllIndexers => {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::TestAllIndexers.into())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::System => {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetTasks.into())
|
||||||
|
.await;
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetQueuedEvents.into())
|
||||||
|
.await;
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetLogs(500).into())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::SystemUpdates => {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetUpdates.into())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.check_for_lidarr_prompt_action().await;
|
||||||
|
self.reset_tick_count();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn extract_add_new_artist_search_query(&self) -> String {
|
||||||
|
self
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_search
|
||||||
|
.as_ref()
|
||||||
|
.expect("Add artist search is empty")
|
||||||
|
.text
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn extract_artist_id(&self) -> i64 {
|
||||||
|
self.data.lidarr_data.artists.current_selection().id
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn extract_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) {
|
||||||
|
if self.data.lidarr_data.prompt_confirm {
|
||||||
|
self.data.lidarr_data.prompt_confirm = false;
|
||||||
|
if let Some(lidarr_event) = self.data.lidarr_data.prompt_confirm_action.take() {
|
||||||
|
self.dispatch_network_event(lidarr_event.into()).await;
|
||||||
|
self.should_refresh = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn lidarr_on_tick(&mut self, active_lidarr_block: ActiveLidarrBlock) {
|
||||||
|
if self.is_first_render {
|
||||||
|
self.refresh_lidarr_metadata().await;
|
||||||
|
self.dispatch_by_lidarr_block(&active_lidarr_block).await;
|
||||||
|
self.is_first_render = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.should_refresh {
|
||||||
|
self.dispatch_by_lidarr_block(&active_lidarr_block).await;
|
||||||
|
self.refresh_lidarr_metadata().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.is_routing {
|
||||||
|
if !self.should_refresh {
|
||||||
|
self.cancellation_token.cancel();
|
||||||
|
} else {
|
||||||
|
self.dispatch_by_lidarr_block(&active_lidarr_block).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.tick_count.is_multiple_of(self.tick_until_poll) {
|
||||||
|
self.refresh_lidarr_metadata().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn refresh_lidarr_metadata(&mut self) {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetQualityProfiles.into())
|
||||||
|
.await;
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetMetadataProfiles.into())
|
||||||
|
.await;
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetTags.into())
|
||||||
|
.await;
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetRootFolders.into())
|
||||||
|
.await;
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetDownloads(500).into())
|
||||||
|
.await;
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetDiskSpace.into())
|
||||||
|
.await;
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetStatus.into())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,59 +1,151 @@
|
|||||||
use std::process;
|
use anyhow::{Error, Result, anyhow};
|
||||||
|
|
||||||
use anyhow::{anyhow, Error};
|
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
use itertools::Itertools;
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
|
use regex::Regex;
|
||||||
|
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::{fs, process};
|
||||||
use tokio::sync::mpsc::Sender;
|
use tokio::sync::mpsc::Sender;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
use veil::Redact;
|
||||||
|
|
||||||
use crate::app::context_clues::{build_context_clue_string, SERVARR_CONTEXT_CLUES};
|
|
||||||
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::radarr::radarr_data::{ActiveRadarrBlock, RadarrData};
|
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData};
|
||||||
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SonarrData};
|
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SonarrData};
|
||||||
|
use crate::models::servarr_models::KeybindingItem;
|
||||||
|
use crate::models::stateful_table::StatefulTable;
|
||||||
use crate::models::{HorizontallyScrollableText, Route, TabRoute, TabState};
|
use crate::models::{HorizontallyScrollableText, Route, TabRoute, TabState};
|
||||||
use crate::network::NetworkEvent;
|
use crate::network::NetworkEvent;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "app_tests.rs"]
|
|
||||||
mod app_tests;
|
mod app_tests;
|
||||||
pub mod context_clues;
|
pub mod context_clues;
|
||||||
pub mod key_binding;
|
pub mod key_binding;
|
||||||
mod key_binding_tests;
|
mod key_binding_tests;
|
||||||
|
pub mod lidarr;
|
||||||
pub mod radarr;
|
pub mod radarr;
|
||||||
pub mod sonarr;
|
pub mod sonarr;
|
||||||
|
|
||||||
const DEFAULT_ROUTE: Route = Route::Radarr(ActiveRadarrBlock::Movies, None);
|
|
||||||
|
|
||||||
pub struct App<'a> {
|
pub struct App<'a> {
|
||||||
navigation_stack: Vec<Route>,
|
navigation_stack: Vec<Route>,
|
||||||
network_tx: Option<Sender<NetworkEvent>>,
|
network_tx: Option<Sender<NetworkEvent>>,
|
||||||
cancellation_token: CancellationToken,
|
pub cancellation_token: CancellationToken,
|
||||||
pub is_first_render: bool,
|
pub is_first_render: bool,
|
||||||
pub server_tabs: TabState,
|
pub server_tabs: TabState,
|
||||||
|
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,
|
||||||
|
pub ui_scroll_tick_count: u64,
|
||||||
pub is_routing: bool,
|
pub is_routing: bool,
|
||||||
pub is_loading: bool,
|
pub is_loading: bool,
|
||||||
pub should_refresh: bool,
|
pub should_refresh: bool,
|
||||||
pub should_ignore_quit_key: bool,
|
pub ignore_special_keys_for_textbox_input: bool,
|
||||||
pub cli_mode: bool,
|
pub cli_mode: bool,
|
||||||
pub config: AppConfig,
|
|
||||||
pub data: Data<'a>,
|
pub data: Data<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> App<'a> {
|
impl App<'_> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
network_tx: Sender<NetworkEvent>,
|
network_tx: Sender<NetworkEvent>,
|
||||||
config: AppConfig,
|
config: AppConfig,
|
||||||
cancellation_token: CancellationToken,
|
cancellation_token: CancellationToken,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let mut server_tabs = Vec::new();
|
||||||
|
|
||||||
|
if let Some(radarr_configs) = config.radarr {
|
||||||
|
let mut unnamed_idx = 0;
|
||||||
|
let radarr_tabs = radarr_configs.into_iter().map(|radarr_config| {
|
||||||
|
let name = if let Some(name) = radarr_config.name.clone() {
|
||||||
|
name
|
||||||
|
} else {
|
||||||
|
unnamed_idx += 1;
|
||||||
|
format!("Radarr {unnamed_idx}")
|
||||||
|
};
|
||||||
|
|
||||||
|
TabRoute {
|
||||||
|
title: name,
|
||||||
|
route: ActiveRadarrBlock::Movies.into(),
|
||||||
|
contextual_help: None,
|
||||||
|
config: Some(radarr_config),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
server_tabs.extend(radarr_tabs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(sonarr_configs) = config.sonarr {
|
||||||
|
let mut unnamed_idx = 0;
|
||||||
|
let sonarr_tabs = sonarr_configs.into_iter().map(|sonarr_config| {
|
||||||
|
let name = if let Some(name) = sonarr_config.name.clone() {
|
||||||
|
name
|
||||||
|
} else {
|
||||||
|
unnamed_idx += 1;
|
||||||
|
format!("Sonarr {unnamed_idx}")
|
||||||
|
};
|
||||||
|
|
||||||
|
TabRoute {
|
||||||
|
title: name,
|
||||||
|
route: ActiveSonarrBlock::Series.into(),
|
||||||
|
contextual_help: None,
|
||||||
|
config: Some(sonarr_config),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
server_tabs.extend(sonarr_tabs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(lidarr_configs) = config.lidarr {
|
||||||
|
let mut unnamed_idx = 0;
|
||||||
|
let lidarr_tabs = lidarr_configs.into_iter().map(|lidarr_config| {
|
||||||
|
let name = if let Some(name) = lidarr_config.name.clone() {
|
||||||
|
name
|
||||||
|
} else {
|
||||||
|
unnamed_idx += 1;
|
||||||
|
format!("Lidarr {unnamed_idx}")
|
||||||
|
};
|
||||||
|
|
||||||
|
TabRoute {
|
||||||
|
title: name,
|
||||||
|
route: ActiveLidarrBlock::Artists.into(),
|
||||||
|
contextual_help: None,
|
||||||
|
config: Some(lidarr_config),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
server_tabs.extend(lidarr_tabs);
|
||||||
|
}
|
||||||
|
|
||||||
|
let weight_sorted_tabs = server_tabs
|
||||||
|
.into_iter()
|
||||||
|
.sorted_by(|tab1, tab2| {
|
||||||
|
Ord::cmp(
|
||||||
|
tab1
|
||||||
|
.config
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.weight
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&1000),
|
||||||
|
tab2
|
||||||
|
.config
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.weight
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&1000),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
App {
|
App {
|
||||||
network_tx: Some(network_tx),
|
network_tx: Some(network_tx),
|
||||||
config,
|
|
||||||
cancellation_token,
|
cancellation_token,
|
||||||
|
server_tabs: TabState::new(weight_sorted_tabs),
|
||||||
..App::default()
|
..App::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,12 +157,12 @@ impl<'a> App<'a> {
|
|||||||
self.is_loading = true;
|
self.is_loading = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(network_tx) = &self.network_tx {
|
if let Some(network_tx) = &self.network_tx
|
||||||
if let Err(e) = network_tx.send(action).await {
|
&& let Err(e) = network_tx.send(action).await
|
||||||
self.is_loading = false;
|
{
|
||||||
error!("Failed to send event. {e:?}");
|
self.is_loading = false;
|
||||||
self.handle_error(anyhow!(e));
|
error!("Failed to send event. {e:?}");
|
||||||
}
|
self.handle_error(anyhow!(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +170,14 @@ impl<'a> App<'a> {
|
|||||||
self.tick_count = 0;
|
self.tick_count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn on_ui_scroll_tick(&mut self) {
|
||||||
|
if self.ui_scroll_tick_count == self.ticks_until_scroll {
|
||||||
|
self.ui_scroll_tick_count = 0;
|
||||||
|
} else {
|
||||||
|
self.ui_scroll_tick_count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.reset_tick_count();
|
self.reset_tick_count();
|
||||||
@@ -93,10 +193,14 @@ impl<'a> App<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn on_tick(&mut self) {
|
pub async fn on_tick(&mut self) {
|
||||||
if self.tick_count % self.tick_until_poll == 0 || self.is_routing || self.should_refresh {
|
if self.tick_count.is_multiple_of(self.tick_until_poll)
|
||||||
|
|| self.is_routing
|
||||||
|
|| self.should_refresh
|
||||||
|
{
|
||||||
match self.get_current_route() {
|
match self.get_current_route() {
|
||||||
Route::Radarr(active_radarr_block, _) => self.radarr_on_tick(active_radarr_block).await,
|
Route::Radarr(active_radarr_block, _) => self.radarr_on_tick(active_radarr_block).await,
|
||||||
Route::Sonarr(active_sonarr_block, _) => self.sonarr_on_tick(active_sonarr_block).await,
|
Route::Sonarr(active_sonarr_block, _) => self.sonarr_on_tick(active_sonarr_block).await,
|
||||||
|
Route::Lidarr(active_lidarr_block, _) => self.lidarr_on_tick(active_lidarr_block).await,
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +218,7 @@ impl<'a> App<'a> {
|
|||||||
|
|
||||||
pub fn pop_navigation_stack(&mut self) {
|
pub fn pop_navigation_stack(&mut self) {
|
||||||
self.is_routing = true;
|
self.is_routing = true;
|
||||||
if self.navigation_stack.len() > 1 {
|
if !self.navigation_stack.is_empty() {
|
||||||
self.navigation_stack.pop();
|
self.navigation_stack.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,77 +237,143 @@ impl<'a> App<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_current_route(&self) -> Route {
|
pub fn get_current_route(&self) -> Route {
|
||||||
*self.navigation_stack.last().unwrap_or(&DEFAULT_ROUTE)
|
*self.navigation_stack.last().unwrap_or(
|
||||||
|
&self
|
||||||
|
.server_tabs
|
||||||
|
.tabs
|
||||||
|
.first()
|
||||||
|
.expect("At least one server tab must exist")
|
||||||
|
.route,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Default for App<'a> {
|
impl Default for App<'_> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
App {
|
App {
|
||||||
navigation_stack: vec![DEFAULT_ROUTE],
|
navigation_stack: Vec::new(),
|
||||||
network_tx: None,
|
network_tx: None,
|
||||||
cancellation_token: CancellationToken::new(),
|
cancellation_token: CancellationToken::new(),
|
||||||
|
keymapping_table: None,
|
||||||
error: HorizontallyScrollableText::default(),
|
error: HorizontallyScrollableText::default(),
|
||||||
|
notification: None,
|
||||||
is_first_render: true,
|
is_first_render: true,
|
||||||
server_tabs: TabState::new(vec![
|
server_tabs: TabState::new(Vec::new()),
|
||||||
TabRoute {
|
|
||||||
title: "Radarr",
|
|
||||||
route: ActiveRadarrBlock::Movies.into(),
|
|
||||||
help: format!(
|
|
||||||
"<↑↓> scroll | ←→ change tab | {} ",
|
|
||||||
build_context_clue_string(&SERVARR_CONTEXT_CLUES)
|
|
||||||
),
|
|
||||||
contextual_help: None,
|
|
||||||
},
|
|
||||||
TabRoute {
|
|
||||||
title: "Sonarr",
|
|
||||||
route: ActiveSonarrBlock::Series.into(),
|
|
||||||
help: format!("{} ", build_context_clue_string(&SERVARR_CONTEXT_CLUES)),
|
|
||||||
contextual_help: None,
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
tick_until_poll: 400,
|
tick_until_poll: 400,
|
||||||
ticks_until_scroll: 4,
|
ticks_until_scroll: 64,
|
||||||
tick_count: 0,
|
tick_count: 0,
|
||||||
|
ui_scroll_tick_count: 0,
|
||||||
is_loading: false,
|
is_loading: false,
|
||||||
is_routing: false,
|
is_routing: false,
|
||||||
should_refresh: false,
|
should_refresh: false,
|
||||||
should_ignore_quit_key: false,
|
ignore_special_keys_for_textbox_input: false,
|
||||||
cli_mode: false,
|
cli_mode: false,
|
||||||
config: AppConfig::default(),
|
|
||||||
data: Data::default(),
|
data: Data::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
impl App<'_> {
|
||||||
|
pub fn test_default() -> Self {
|
||||||
|
App {
|
||||||
|
server_tabs: TabState::new(vec![
|
||||||
|
TabRoute {
|
||||||
|
title: "Radarr".to_owned(),
|
||||||
|
route: ActiveRadarrBlock::Movies.into(),
|
||||||
|
contextual_help: None,
|
||||||
|
config: Some(ServarrConfig::default()),
|
||||||
|
},
|
||||||
|
TabRoute {
|
||||||
|
title: "Sonarr".to_owned(),
|
||||||
|
route: ActiveSonarrBlock::Series.into(),
|
||||||
|
contextual_help: None,
|
||||||
|
config: Some(ServarrConfig::default()),
|
||||||
|
},
|
||||||
|
TabRoute {
|
||||||
|
title: "Lidarr".to_owned(),
|
||||||
|
route: ActiveLidarrBlock::Artists.into(),
|
||||||
|
contextual_help: None,
|
||||||
|
config: Some(ServarrConfig::default()),
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
..App::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn test_default_fully_populated() -> Self {
|
||||||
|
App {
|
||||||
|
data: Data {
|
||||||
|
lidarr_data: LidarrData::test_default_fully_populated(),
|
||||||
|
radarr_data: RadarrData::test_default_fully_populated(),
|
||||||
|
sonarr_data: SonarrData::test_default_fully_populated(),
|
||||||
|
},
|
||||||
|
server_tabs: TabState::new(vec![
|
||||||
|
TabRoute {
|
||||||
|
title: "Radarr".to_owned(),
|
||||||
|
route: ActiveRadarrBlock::Movies.into(),
|
||||||
|
contextual_help: None,
|
||||||
|
config: Some(ServarrConfig::default()),
|
||||||
|
},
|
||||||
|
TabRoute {
|
||||||
|
title: "Sonarr".to_owned(),
|
||||||
|
route: ActiveSonarrBlock::Series.into(),
|
||||||
|
contextual_help: None,
|
||||||
|
config: Some(ServarrConfig::default()),
|
||||||
|
},
|
||||||
|
TabRoute {
|
||||||
|
title: "Lidarr".to_owned(),
|
||||||
|
route: ActiveLidarrBlock::Artists.into(),
|
||||||
|
contextual_help: None,
|
||||||
|
config: Some(ServarrConfig::default()),
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
..App::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Data<'a> {
|
pub struct Data<'a> {
|
||||||
|
pub lidarr_data: LidarrData<'a>,
|
||||||
pub radarr_data: RadarrData<'a>,
|
pub radarr_data: RadarrData<'a>,
|
||||||
pub sonarr_data: SonarrData<'a>,
|
pub sonarr_data: SonarrData<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
|
||||||
pub struct AppConfig {
|
pub struct AppConfig {
|
||||||
pub radarr: Option<ServarrConfig>,
|
pub theme: Option<String>,
|
||||||
pub sonarr: Option<ServarrConfig>,
|
pub lidarr: Option<Vec<ServarrConfig>>,
|
||||||
|
pub radarr: Option<Vec<ServarrConfig>>,
|
||||||
|
pub sonarr: Option<Vec<ServarrConfig>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppConfig {
|
impl AppConfig {
|
||||||
pub fn validate(&self) {
|
pub fn validate(&self, config_path: &str) {
|
||||||
if let Some(radarr_config) = &self.radarr {
|
if self.lidarr.is_none() && self.radarr.is_none() && self.sonarr.is_none() {
|
||||||
radarr_config.validate();
|
log_and_print_error(format!(
|
||||||
|
"No Servarrs are configured in the file: {config_path}"
|
||||||
|
));
|
||||||
|
process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(sonarr_config) = &self.sonarr {
|
if let Some(radarr_configs) = &self.radarr {
|
||||||
sonarr_config.validate();
|
radarr_configs.iter().for_each(|config| config.validate());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(sonarr_configs) = &self.sonarr {
|
||||||
|
sonarr_configs.iter().for_each(|config| config.validate());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(lidarr_configs) = &self.lidarr {
|
||||||
|
lidarr_configs.iter().for_each(|config| config.validate());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify_config_present_for_cli(&self, command: &Command) {
|
pub fn verify_config_present_for_cli(&self, command: &Command) {
|
||||||
let msg = |servarr: &str| {
|
let msg = |servarr: &str| {
|
||||||
log_and_print_error(format!(
|
log_and_print_error(format!(
|
||||||
"{} configuration missing; Unable to run any {} commands.",
|
"{servarr} configuration missing; Unable to run any {servarr} commands."
|
||||||
servarr, servarr
|
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
match command {
|
match command {
|
||||||
@@ -215,18 +385,64 @@ impl AppConfig {
|
|||||||
msg("Sonarr");
|
msg("Sonarr");
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
|
Command::Lidarr(_) if self.lidarr.is_none() => {
|
||||||
|
msg("Lidarr");
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn post_process_initialization(&mut self) {
|
||||||
|
if let Some(radarr_configs) = self.radarr.as_mut() {
|
||||||
|
for radarr_config in radarr_configs {
|
||||||
|
radarr_config.post_process_initialization();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(sonarr_configs) = self.sonarr.as_mut() {
|
||||||
|
for sonarr_config in sonarr_configs {
|
||||||
|
sonarr_config.post_process_initialization();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(lidarr_configs) = self.lidarr.as_mut() {
|
||||||
|
for lidarr_config in lidarr_configs {
|
||||||
|
lidarr_config.post_process_initialization();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
#[derive(Redact, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||||
pub struct ServarrConfig {
|
pub struct ServarrConfig {
|
||||||
|
#[serde(default, deserialize_with = "deserialize_optional_env_var")]
|
||||||
|
pub name: Option<String>,
|
||||||
|
#[serde(default, deserialize_with = "deserialize_optional_env_var")]
|
||||||
pub host: Option<String>,
|
pub host: Option<String>,
|
||||||
|
#[serde(default, deserialize_with = "deserialize_u16_env_var")]
|
||||||
pub port: Option<u16>,
|
pub port: Option<u16>,
|
||||||
|
#[serde(default, deserialize_with = "deserialize_optional_env_var")]
|
||||||
pub uri: Option<String>,
|
pub uri: Option<String>,
|
||||||
pub api_token: String,
|
#[serde(default, deserialize_with = "deserialize_u16_env_var")]
|
||||||
|
pub weight: Option<u16>,
|
||||||
|
#[serde(default, deserialize_with = "deserialize_optional_env_var")]
|
||||||
|
#[redact]
|
||||||
|
pub api_token: Option<String>,
|
||||||
|
#[serde(default, deserialize_with = "deserialize_optional_env_var")]
|
||||||
|
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")]
|
||||||
pub ssl_cert_path: Option<String>,
|
pub ssl_cert_path: Option<String>,
|
||||||
|
#[serde(
|
||||||
|
default,
|
||||||
|
deserialize_with = "deserialize_optional_env_var_header_map",
|
||||||
|
serialize_with = "serialize_header_map"
|
||||||
|
)]
|
||||||
|
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 {
|
||||||
@@ -235,22 +451,184 @@ impl ServarrConfig {
|
|||||||
log_and_print_error("'host' or 'uri' is required for configuration".to_owned());
|
log_and_print_error("'host' or 'uri' is required for configuration".to_owned());
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.api_token_file.is_none() && self.api_token.is_none() {
|
||||||
|
log_and_print_error(
|
||||||
|
"'api_token' or 'api_token_path' is required for configuration".to_owned(),
|
||||||
|
);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn post_process_initialization(&mut self) {
|
||||||
|
if let Some(api_token_file) = self.api_token_file.as_ref() {
|
||||||
|
if !PathBuf::from(api_token_file).exists() {
|
||||||
|
log_and_print_error(format!(
|
||||||
|
"The specified {api_token_file} API token file does not exist"
|
||||||
|
));
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let api_token = fs::read_to_string(api_token_file)
|
||||||
|
.map_err(|e| anyhow!(e))
|
||||||
|
.unwrap();
|
||||||
|
self.api_token = Some(api_token.trim().to_owned());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ServarrConfig {
|
impl Default for ServarrConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
ServarrConfig {
|
ServarrConfig {
|
||||||
|
name: None,
|
||||||
host: Some("localhost".to_string()),
|
host: Some("localhost".to_string()),
|
||||||
port: None,
|
port: None,
|
||||||
uri: None,
|
uri: None,
|
||||||
api_token: "".to_string(),
|
weight: None,
|
||||||
|
api_token: Some(String::new()),
|
||||||
|
api_token_file: None,
|
||||||
ssl_cert_path: None,
|
ssl_cert_path: None,
|
||||||
|
ssl: None,
|
||||||
|
custom_headers: None,
|
||||||
|
monitored_storage_paths: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn log_and_print_error(error: String) {
|
pub fn log_and_print_error(error: String) {
|
||||||
error!("{}", error);
|
error!("{error}");
|
||||||
eprintln!("error: {}", error.red());
|
eprintln!("error: {}", error.red());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn serialize_header_map<S>(headers: &Option<HeaderMap>, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
if let Some(headers) = headers {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
for (name, value) in headers.iter() {
|
||||||
|
let name_str = name.as_str().to_string();
|
||||||
|
let value_str = value
|
||||||
|
.to_str()
|
||||||
|
.map_err(serde::ser::Error::custom)?
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
map.insert(name_str, value_str);
|
||||||
|
}
|
||||||
|
map.serialize(serializer)
|
||||||
|
} else {
|
||||||
|
serializer.serialize_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_optional_env_var<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s: Option<String> = Option::deserialize(deserializer)?;
|
||||||
|
match s {
|
||||||
|
Some(value) => {
|
||||||
|
let interpolated = interpolate_env_vars(&value);
|
||||||
|
Ok(Some(interpolated))
|
||||||
|
}
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>(
|
||||||
|
deserializer: D,
|
||||||
|
) -> Result<Option<HeaderMap>, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let opt: Option<HashMap<String, String>> = Option::deserialize(deserializer)?;
|
||||||
|
match opt {
|
||||||
|
Some(map) => {
|
||||||
|
let mut header_map = HeaderMap::new();
|
||||||
|
for (k, v) in map.iter() {
|
||||||
|
let name = HeaderName::from_bytes(k.as_bytes()).map_err(serde::de::Error::custom)?;
|
||||||
|
let value_str = interpolate_env_vars(v);
|
||||||
|
let value = HeaderValue::from_str(&value_str).map_err(serde::de::Error::custom)?;
|
||||||
|
header_map.insert(name, value);
|
||||||
|
}
|
||||||
|
Ok(Some(header_map))
|
||||||
|
}
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s: Option<String> = Option::deserialize(deserializer)?;
|
||||||
|
match s {
|
||||||
|
Some(value) => {
|
||||||
|
let interpolated = interpolate_env_vars(&value);
|
||||||
|
interpolated
|
||||||
|
.parse::<u16>()
|
||||||
|
.map(Some)
|
||||||
|
.map_err(serde::de::Error::custom)
|
||||||
|
}
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interpolate_env_vars(s: &str) -> String {
|
||||||
|
let result = s.to_string();
|
||||||
|
let scrubbing_regex = Regex::new(r#"[\s{}!$^()\[\]\\|`'"]+"#).unwrap();
|
||||||
|
let var_regex = Regex::new(r"\$\{(.*?)}").unwrap();
|
||||||
|
|
||||||
|
var_regex
|
||||||
|
.replace_all(s, |caps: ®ex::Captures<'_>| {
|
||||||
|
if let Some(mat) = caps.get(1)
|
||||||
|
&& let Ok(value) = std::env::var(mat.as_str())
|
||||||
|
{
|
||||||
|
return scrubbing_regex.replace_all(&value, "").to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
scrubbing_regex.replace_all(&result, "").to_string()
|
||||||
|
})
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ pub mod radarr_context_clues;
|
|||||||
#[path = "radarr_tests.rs"]
|
#[path = "radarr_tests.rs"]
|
||||||
mod radarr_tests;
|
mod radarr_tests;
|
||||||
|
|
||||||
impl<'a> App<'a> {
|
impl App<'_> {
|
||||||
pub(super) async fn dispatch_by_radarr_block(&mut self, active_radarr_block: &ActiveRadarrBlock) {
|
pub(super) async fn dispatch_by_radarr_block(&mut self, active_radarr_block: &ActiveRadarrBlock) {
|
||||||
match active_radarr_block {
|
match active_radarr_block {
|
||||||
ActiveRadarrBlock::Blocklist => {
|
ActiveRadarrBlock::Blocklist => {
|
||||||
@@ -40,7 +40,7 @@ impl<'a> App<'a> {
|
|||||||
}
|
}
|
||||||
ActiveRadarrBlock::Downloads => {
|
ActiveRadarrBlock::Downloads => {
|
||||||
self
|
self
|
||||||
.dispatch_network_event(RadarrEvent::GetDownloads.into())
|
.dispatch_network_event(RadarrEvent::GetDownloads(500).into())
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
ActiveRadarrBlock::RootFolders => {
|
ActiveRadarrBlock::RootFolders => {
|
||||||
@@ -59,7 +59,12 @@ impl<'a> App<'a> {
|
|||||||
.dispatch_network_event(RadarrEvent::GetMovies.into())
|
.dispatch_network_event(RadarrEvent::GetMovies.into())
|
||||||
.await;
|
.await;
|
||||||
self
|
self
|
||||||
.dispatch_network_event(RadarrEvent::GetDownloads.into())
|
.dispatch_network_event(RadarrEvent::GetDownloads(500).into())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
ActiveRadarrBlock::History => {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(RadarrEvent::GetHistory(500).into())
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
ActiveRadarrBlock::Indexers => {
|
ActiveRadarrBlock::Indexers => {
|
||||||
@@ -77,7 +82,9 @@ impl<'a> App<'a> {
|
|||||||
}
|
}
|
||||||
ActiveRadarrBlock::TestIndexer => {
|
ActiveRadarrBlock::TestIndexer => {
|
||||||
self
|
self
|
||||||
.dispatch_network_event(RadarrEvent::TestIndexer(None).into())
|
.dispatch_network_event(
|
||||||
|
RadarrEvent::TestIndexer(self.extract_radarr_indexer_id().await).into(),
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
ActiveRadarrBlock::TestAllIndexers => {
|
ActiveRadarrBlock::TestAllIndexers => {
|
||||||
@@ -93,7 +100,7 @@ impl<'a> App<'a> {
|
|||||||
.dispatch_network_event(RadarrEvent::GetQueuedEvents.into())
|
.dispatch_network_event(RadarrEvent::GetQueuedEvents.into())
|
||||||
.await;
|
.await;
|
||||||
self
|
self
|
||||||
.dispatch_network_event(RadarrEvent::GetLogs(None).into())
|
.dispatch_network_event(RadarrEvent::GetLogs(500).into())
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
ActiveRadarrBlock::SystemUpdates => {
|
ActiveRadarrBlock::SystemUpdates => {
|
||||||
@@ -103,17 +110,23 @@ impl<'a> App<'a> {
|
|||||||
}
|
}
|
||||||
ActiveRadarrBlock::AddMovieSearchResults => {
|
ActiveRadarrBlock::AddMovieSearchResults => {
|
||||||
self
|
self
|
||||||
.dispatch_network_event(RadarrEvent::SearchNewMovie(None).into())
|
.dispatch_network_event(
|
||||||
|
RadarrEvent::SearchNewMovie(self.extract_movie_search_query().await).into(),
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
ActiveRadarrBlock::MovieDetails | ActiveRadarrBlock::FileInfo => {
|
ActiveRadarrBlock::MovieDetails | ActiveRadarrBlock::FileInfo => {
|
||||||
self
|
self
|
||||||
.dispatch_network_event(RadarrEvent::GetMovieDetails(None).into())
|
.dispatch_network_event(
|
||||||
|
RadarrEvent::GetMovieDetails(self.extract_movie_id().await).into(),
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
ActiveRadarrBlock::MovieHistory => {
|
ActiveRadarrBlock::MovieHistory => {
|
||||||
self
|
self
|
||||||
.dispatch_network_event(RadarrEvent::GetMovieHistory(None).into())
|
.dispatch_network_event(
|
||||||
|
RadarrEvent::GetMovieHistory(self.extract_movie_id().await).into(),
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
ActiveRadarrBlock::Cast | ActiveRadarrBlock::Crew => {
|
ActiveRadarrBlock::Cast | ActiveRadarrBlock::Crew => {
|
||||||
@@ -123,7 +136,9 @@ impl<'a> App<'a> {
|
|||||||
|| movie_details_modal.movie_crew.items.is_empty() =>
|
|| movie_details_modal.movie_crew.items.is_empty() =>
|
||||||
{
|
{
|
||||||
self
|
self
|
||||||
.dispatch_network_event(RadarrEvent::GetMovieCredits(None).into())
|
.dispatch_network_event(
|
||||||
|
RadarrEvent::GetMovieCredits(self.extract_movie_id().await).into(),
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
@@ -132,7 +147,7 @@ impl<'a> App<'a> {
|
|||||||
ActiveRadarrBlock::ManualSearch => match self.data.radarr_data.movie_details_modal.as_ref() {
|
ActiveRadarrBlock::ManualSearch => match self.data.radarr_data.movie_details_modal.as_ref() {
|
||||||
Some(movie_details_modal) if movie_details_modal.movie_releases.items.is_empty() => {
|
Some(movie_details_modal) if movie_details_modal.movie_releases.items.is_empty() => {
|
||||||
self
|
self
|
||||||
.dispatch_network_event(RadarrEvent::GetReleases(None).into())
|
.dispatch_network_event(RadarrEvent::GetReleases(self.extract_movie_id().await).into())
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
@@ -147,12 +162,9 @@ impl<'a> App<'a> {
|
|||||||
async fn check_for_radarr_prompt_action(&mut self) {
|
async fn check_for_radarr_prompt_action(&mut self) {
|
||||||
if self.data.radarr_data.prompt_confirm {
|
if self.data.radarr_data.prompt_confirm {
|
||||||
self.data.radarr_data.prompt_confirm = false;
|
self.data.radarr_data.prompt_confirm = false;
|
||||||
if let Some(radarr_event) = &self.data.radarr_data.prompt_confirm_action {
|
if let Some(radarr_event) = self.data.radarr_data.prompt_confirm_action.take() {
|
||||||
self
|
self.dispatch_network_event(radarr_event.into()).await;
|
||||||
.dispatch_network_event(radarr_event.clone().into())
|
|
||||||
.await;
|
|
||||||
self.should_refresh = true;
|
self.should_refresh = true;
|
||||||
self.data.radarr_data.prompt_confirm_action = None;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,7 +190,7 @@ impl<'a> App<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.tick_count % self.tick_until_poll == 0 {
|
if self.tick_count.is_multiple_of(self.tick_until_poll) {
|
||||||
self.refresh_radarr_metadata().await;
|
self.refresh_radarr_metadata().await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,7 +206,7 @@ impl<'a> App<'a> {
|
|||||||
.dispatch_network_event(RadarrEvent::GetRootFolders.into())
|
.dispatch_network_event(RadarrEvent::GetRootFolders.into())
|
||||||
.await;
|
.await;
|
||||||
self
|
self
|
||||||
.dispatch_network_event(RadarrEvent::GetDownloads.into())
|
.dispatch_network_event(RadarrEvent::GetDownloads(500).into())
|
||||||
.await;
|
.await;
|
||||||
self
|
self
|
||||||
.dispatch_network_event(RadarrEvent::GetDiskSpace.into())
|
.dispatch_network_event(RadarrEvent::GetDiskSpace.into())
|
||||||
@@ -219,4 +231,23 @@ impl<'a> App<'a> {
|
|||||||
.collection_movies
|
.collection_movies
|
||||||
.set_items(collection_movies);
|
.set_items(collection_movies);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn extract_movie_id(&self) -> i64 {
|
||||||
|
self.data.radarr_data.movies.current_selection().id
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn extract_movie_search_query(&self) -> String {
|
||||||
|
self
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.add_movie_search
|
||||||
|
.as_ref()
|
||||||
|
.expect("Add movie search is empty")
|
||||||
|
.text
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn extract_radarr_indexer_id(&self) -> i64 {
|
||||||
|
self.data.radarr_data.indexers.current_selection().id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,26 @@
|
|||||||
use crate::app::context_clues::ContextClue;
|
use crate::app::App;
|
||||||
|
use crate::app::context_clues::{
|
||||||
|
BARE_POPUP_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, ContextClue, ContextClueProvider,
|
||||||
|
SYSTEM_TASKS_CONTEXT_CLUES,
|
||||||
|
};
|
||||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
|
use crate::models::Route;
|
||||||
|
use crate::models::servarr_data::radarr::radarr_data::{
|
||||||
|
ADD_MOVIE_BLOCKS, ActiveRadarrBlock, EDIT_COLLECTION_BLOCKS, EDIT_INDEXER_BLOCKS,
|
||||||
|
EDIT_MOVIE_BLOCKS, INDEXER_SETTINGS_BLOCKS, MOVIE_DETAILS_BLOCKS,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "radarr_context_clues_tests.rs"]
|
#[path = "radarr_context_clues_tests.rs"]
|
||||||
mod radarr_context_clues_tests;
|
mod radarr_context_clues_tests;
|
||||||
|
|
||||||
pub static LIBRARY_CONTEXT_CLUES: [ContextClue; 10] = [
|
pub static LIBRARY_CONTEXT_CLUES: [ContextClue; 11] = [
|
||||||
(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc),
|
(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc),
|
||||||
(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc),
|
(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc),
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.toggle_monitoring,
|
||||||
|
DEFAULT_KEYBINDINGS.toggle_monitoring.desc,
|
||||||
|
),
|
||||||
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
||||||
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
|
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
|
||||||
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
||||||
@@ -49,7 +62,7 @@ pub static MOVIE_DETAILS_CONTEXT_CLUES: [ContextClue; 5] = [
|
|||||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub static MANUAL_MOVIE_SEARCH_CONTEXT_CLUES: [ContextClue; 6] = [
|
pub static MANUAL_MOVIE_SEARCH_CONTEXT_CLUES: [ContextClue; 7] = [
|
||||||
(
|
(
|
||||||
DEFAULT_KEYBINDINGS.refresh,
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||||
@@ -61,24 +74,68 @@ pub static MANUAL_MOVIE_SEARCH_CONTEXT_CLUES: [ContextClue; 6] = [
|
|||||||
DEFAULT_KEYBINDINGS.auto_search,
|
DEFAULT_KEYBINDINGS.auto_search,
|
||||||
DEFAULT_KEYBINDINGS.auto_search.desc,
|
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||||
),
|
),
|
||||||
|
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub static MANUAL_MOVIE_SEARCH_CONTEXTUAL_CONTEXT_CLUES: [ContextClue; 1] =
|
|
||||||
[(DEFAULT_KEYBINDINGS.submit, "details")];
|
|
||||||
|
|
||||||
pub static ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES: [ContextClue; 2] = [
|
pub static ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES: [ContextClue; 2] = [
|
||||||
(DEFAULT_KEYBINDINGS.submit, "details"),
|
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||||
(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"),
|
||||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
pub(in crate::app) struct RadarrContextClueProvider;
|
||||||
|
|
||||||
|
impl ContextClueProvider for RadarrContextClueProvider {
|
||||||
|
fn get_context_clues(app: &mut App<'_>) -> Option<&'static [ContextClue]> {
|
||||||
|
let Route::Radarr(active_radarr_block, context_option) = app.get_current_route() else {
|
||||||
|
panic!("RadarrContextClueProvider::get_context_clues called with non-Radarr route");
|
||||||
|
};
|
||||||
|
match active_radarr_block {
|
||||||
|
_ if MOVIE_DETAILS_BLOCKS.contains(&active_radarr_block) => app
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.movie_info_tabs
|
||||||
|
.get_active_route_contextual_help(),
|
||||||
|
ActiveRadarrBlock::TestAllIndexers
|
||||||
|
| ActiveRadarrBlock::AddMovieSearchInput
|
||||||
|
| ActiveRadarrBlock::AddMovieEmptySearchResults
|
||||||
|
| ActiveRadarrBlock::SystemLogs
|
||||||
|
| ActiveRadarrBlock::SystemUpdates => Some(&BARE_POPUP_CONTEXT_CLUES),
|
||||||
|
_ if context_option.unwrap_or(active_radarr_block)
|
||||||
|
== ActiveRadarrBlock::ViewMovieOverview =>
|
||||||
|
{
|
||||||
|
Some(&BARE_POPUP_CONTEXT_CLUES)
|
||||||
|
}
|
||||||
|
ActiveRadarrBlock::SystemTasks => Some(&SYSTEM_TASKS_CONTEXT_CLUES),
|
||||||
|
_ if EDIT_COLLECTION_BLOCKS.contains(&active_radarr_block)
|
||||||
|
|| EDIT_INDEXER_BLOCKS.contains(&active_radarr_block)
|
||||||
|
|| INDEXER_SETTINGS_BLOCKS.contains(&active_radarr_block)
|
||||||
|
|| EDIT_MOVIE_BLOCKS.contains(&active_radarr_block) =>
|
||||||
|
{
|
||||||
|
Some(&CONFIRMATION_PROMPT_CONTEXT_CLUES)
|
||||||
|
}
|
||||||
|
ActiveRadarrBlock::AddMoviePrompt
|
||||||
|
| ActiveRadarrBlock::AddMovieSelectMonitor
|
||||||
|
| ActiveRadarrBlock::AddMovieSelectMinimumAvailability
|
||||||
|
| ActiveRadarrBlock::AddMovieSelectQualityProfile
|
||||||
|
| ActiveRadarrBlock::AddMovieSelectRootFolder
|
||||||
|
| ActiveRadarrBlock::AddMovieTagsInput
|
||||||
|
| ActiveRadarrBlock::SystemTaskStartConfirmPrompt => Some(&CONFIRMATION_PROMPT_CONTEXT_CLUES),
|
||||||
|
_ if ADD_MOVIE_BLOCKS.contains(&active_radarr_block) => {
|
||||||
|
Some(&ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES)
|
||||||
|
}
|
||||||
|
ActiveRadarrBlock::CollectionDetails => Some(&COLLECTION_DETAILS_CONTEXT_CLUES),
|
||||||
|
_ => app
|
||||||
|
.data
|
||||||
|
.radarr_data
|
||||||
|
.main_tabs
|
||||||
|
.get_active_route_contextual_help(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,114 +1,119 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
use crate::app::App;
|
||||||
|
use crate::app::context_clues::{
|
||||||
|
BARE_POPUP_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES,
|
||||||
|
ContextClue, ContextClueProvider, DOWNLOADS_CONTEXT_CLUES, HISTORY_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, COLLECTIONS_CONTEXT_CLUES,
|
ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES, COLLECTION_DETAILS_CONTEXT_CLUES,
|
||||||
COLLECTION_DETAILS_CONTEXT_CLUES, LIBRARY_CONTEXT_CLUES,
|
COLLECTIONS_CONTEXT_CLUES, LIBRARY_CONTEXT_CLUES, MANUAL_MOVIE_SEARCH_CONTEXT_CLUES,
|
||||||
MANUAL_MOVIE_SEARCH_CONTEXTUAL_CONTEXT_CLUES, MANUAL_MOVIE_SEARCH_CONTEXT_CLUES,
|
MOVIE_DETAILS_CONTEXT_CLUES, RadarrContextClueProvider,
|
||||||
MOVIE_DETAILS_CONTEXT_CLUES, SYSTEM_TASKS_CONTEXT_CLUES,
|
|
||||||
};
|
};
|
||||||
|
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, RadarrData};
|
||||||
|
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_library_context_clues() {
|
fn test_library_context_clues() {
|
||||||
let mut library_context_clues_iter = LIBRARY_CONTEXT_CLUES.iter();
|
let mut library_context_clues_iter = LIBRARY_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = library_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
library_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.add);
|
&(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc)
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.add.desc);
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
let (key_binding, description) = library_context_clues_iter.next().unwrap();
|
library_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc)
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit);
|
);
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.edit.desc);
|
assert_some_eq_x!(
|
||||||
|
library_context_clues_iter.next(),
|
||||||
let (key_binding, description) = library_context_clues_iter.next().unwrap();
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.toggle_monitoring,
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
|
DEFAULT_KEYBINDINGS.toggle_monitoring.desc
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
|
)
|
||||||
|
);
|
||||||
let (key_binding, description) = library_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
library_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
|
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
let (key_binding, description) = library_context_clues_iter.next().unwrap();
|
library_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc)
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search);
|
);
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.search.desc);
|
assert_some_eq_x!(
|
||||||
|
library_context_clues_iter.next(),
|
||||||
let (key_binding, description) = library_context_clues_iter.next().unwrap();
|
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
|
||||||
|
);
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.filter);
|
assert_some_eq_x!(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.filter.desc);
|
library_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc)
|
||||||
let (key_binding, description) = library_context_clues_iter.next().unwrap();
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
library_context_clues_iter.next(),
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
let (key_binding, description) = library_context_clues_iter.next().unwrap();
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
|
)
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update);
|
);
|
||||||
assert_str_eq!(*description, "update all");
|
assert_some_eq_x!(
|
||||||
|
library_context_clues_iter.next(),
|
||||||
let (key_binding, description) = library_context_clues_iter.next().unwrap();
|
&(DEFAULT_KEYBINDINGS.update, "update all")
|
||||||
|
);
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
assert_some_eq_x!(
|
||||||
assert_str_eq!(*description, "details");
|
library_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||||
let (key_binding, description) = library_context_clues_iter.next().unwrap();
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
library_context_clues_iter.next(),
|
||||||
assert_str_eq!(*description, "cancel filter");
|
&(DEFAULT_KEYBINDINGS.esc, "cancel filter")
|
||||||
assert_eq!(library_context_clues_iter.next(), None);
|
);
|
||||||
|
assert_none!(library_context_clues_iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collections_context_clues() {
|
fn test_collections_context_clues() {
|
||||||
let mut collections_context_clues = COLLECTIONS_CONTEXT_CLUES.iter();
|
let mut collections_context_clues = COLLECTIONS_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = collections_context_clues.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
collections_context_clues.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search);
|
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.search.desc);
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
let (key_binding, description) = collections_context_clues.next().unwrap();
|
collections_context_clues.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc)
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit);
|
);
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.edit.desc);
|
assert_some_eq_x!(
|
||||||
|
collections_context_clues.next(),
|
||||||
let (key_binding, description) = collections_context_clues.next().unwrap();
|
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||||
|
);
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
|
assert_some_eq_x!(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
|
collections_context_clues.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc)
|
||||||
let (key_binding, description) = collections_context_clues.next().unwrap();
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.filter);
|
collections_context_clues.next(),
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.filter.desc);
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
let (key_binding, description) = collections_context_clues.next().unwrap();
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
|
)
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
);
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
assert_some_eq_x!(
|
||||||
|
collections_context_clues.next(),
|
||||||
let (key_binding, description) = collections_context_clues.next().unwrap();
|
&(DEFAULT_KEYBINDINGS.update, "update all")
|
||||||
|
);
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update);
|
assert_some_eq_x!(
|
||||||
assert_str_eq!(*description, "update all");
|
collections_context_clues.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||||
let (key_binding, description) = collections_context_clues.next().unwrap();
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
collections_context_clues.next(),
|
||||||
assert_str_eq!(*description, "details");
|
&(DEFAULT_KEYBINDINGS.esc, "cancel filter")
|
||||||
|
);
|
||||||
let (key_binding, description) = collections_context_clues.next().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
|
||||||
assert_str_eq!(*description, "cancel filter");
|
|
||||||
assert_eq!(collections_context_clues.next(), None);
|
assert_eq!(collections_context_clues.next(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,30 +121,32 @@ mod tests {
|
|||||||
fn test_movie_details_context_clues() {
|
fn test_movie_details_context_clues() {
|
||||||
let mut movie_details_context_clues_iter = MOVIE_DETAILS_CONTEXT_CLUES.iter();
|
let mut movie_details_context_clues_iter = MOVIE_DETAILS_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = movie_details_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
movie_details_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
&(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
let (key_binding, description) = movie_details_context_clues_iter.next().unwrap();
|
)
|
||||||
|
);
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update);
|
assert_some_eq_x!(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.update.desc);
|
movie_details_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc)
|
||||||
let (key_binding, description) = movie_details_context_clues_iter.next().unwrap();
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit);
|
movie_details_context_clues_iter.next(),
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.edit.desc);
|
&(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc)
|
||||||
|
);
|
||||||
let (key_binding, description) = movie_details_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
movie_details_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search);
|
&(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc);
|
DEFAULT_KEYBINDINGS.auto_search,
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search.desc
|
||||||
let (key_binding, description) = movie_details_context_clues_iter.next().unwrap();
|
)
|
||||||
|
);
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
assert_some_eq_x!(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
|
movie_details_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||||
|
);
|
||||||
assert_eq!(movie_details_context_clues_iter.next(), None);
|
assert_eq!(movie_details_context_clues_iter.next(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,53 +154,41 @@ mod tests {
|
|||||||
fn test_manual_movie_search_context_clues() {
|
fn test_manual_movie_search_context_clues() {
|
||||||
let mut manual_movie_search_context_clues_iter = MANUAL_MOVIE_SEARCH_CONTEXT_CLUES.iter();
|
let mut manual_movie_search_context_clues_iter = MANUAL_MOVIE_SEARCH_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = manual_movie_search_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
manual_movie_search_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
&(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
let (key_binding, description) = manual_movie_search_context_clues_iter.next().unwrap();
|
)
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update);
|
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.update.desc);
|
|
||||||
|
|
||||||
let (key_binding, description) = manual_movie_search_context_clues_iter.next().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit);
|
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.edit.desc);
|
|
||||||
|
|
||||||
let (key_binding, description) = manual_movie_search_context_clues_iter.next().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
|
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
|
|
||||||
|
|
||||||
let (key_binding, description) = manual_movie_search_context_clues_iter.next().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search);
|
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc);
|
|
||||||
|
|
||||||
let (key_binding, description) = manual_movie_search_context_clues_iter.next().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
|
|
||||||
assert_eq!(manual_movie_search_context_clues_iter.next(), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_manual_movie_search_contextual_context_clues() {
|
|
||||||
let mut manual_movie_search_contextual_context_clues_iter =
|
|
||||||
MANUAL_MOVIE_SEARCH_CONTEXTUAL_CONTEXT_CLUES.iter();
|
|
||||||
|
|
||||||
let (key_binding, description) = manual_movie_search_contextual_context_clues_iter
|
|
||||||
.next()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
|
||||||
assert_str_eq!(*description, "details");
|
|
||||||
assert_eq!(
|
|
||||||
manual_movie_search_contextual_context_clues_iter.next(),
|
|
||||||
None
|
|
||||||
);
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
manual_movie_search_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
manual_movie_search_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
manual_movie_search_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
manual_movie_search_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search,
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search.desc
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
manual_movie_search_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
manual_movie_search_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||||
|
);
|
||||||
|
assert_eq!(manual_movie_search_context_clues_iter.next(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -201,15 +196,14 @@ mod tests {
|
|||||||
let mut add_movie_search_results_context_clues_iter =
|
let mut add_movie_search_results_context_clues_iter =
|
||||||
ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES.iter();
|
ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = add_movie_search_results_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
add_movie_search_results_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||||
assert_str_eq!(*description, "details");
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
let (key_binding, description) = add_movie_search_results_context_clues_iter.next().unwrap();
|
add_movie_search_results_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.esc, "edit search")
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
);
|
||||||
assert_str_eq!(*description, "edit search");
|
|
||||||
assert_eq!(add_movie_search_results_context_clues_iter.next(), None);
|
assert_eq!(add_movie_search_results_context_clues_iter.next(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,15 +211,14 @@ mod tests {
|
|||||||
fn test_system_tasks_context_clues() {
|
fn test_system_tasks_context_clues() {
|
||||||
let mut system_tasks_context_clues_iter = SYSTEM_TASKS_CONTEXT_CLUES.iter();
|
let mut system_tasks_context_clues_iter = SYSTEM_TASKS_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = system_tasks_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
system_tasks_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
&(DEFAULT_KEYBINDINGS.submit, "start task")
|
||||||
assert_str_eq!(*description, "start task");
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
let (key_binding, description) = system_tasks_context_clues_iter.next().unwrap();
|
system_tasks_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
);
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
|
|
||||||
assert_eq!(system_tasks_context_clues_iter.next(), None);
|
assert_eq!(system_tasks_context_clues_iter.next(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,20 +226,256 @@ mod tests {
|
|||||||
fn test_collection_details_context_clues() {
|
fn test_collection_details_context_clues() {
|
||||||
let mut collection_details_context_clues_iter = COLLECTION_DETAILS_CONTEXT_CLUES.iter();
|
let mut collection_details_context_clues_iter = COLLECTION_DETAILS_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = collection_details_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
collection_details_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
&(DEFAULT_KEYBINDINGS.submit, "show overview/add movie")
|
||||||
assert_str_eq!(*description, "show overview/add movie");
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
let (key_binding, description) = collection_details_context_clues_iter.next().unwrap();
|
collection_details_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.edit, "edit collection")
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit);
|
);
|
||||||
assert_str_eq!(*description, "edit collection");
|
assert_some_eq_x!(
|
||||||
|
collection_details_context_clues_iter.next(),
|
||||||
let (key_binding, description) = collection_details_context_clues_iter.next().unwrap();
|
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||||
|
);
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
|
|
||||||
assert_eq!(collection_details_context_clues_iter.next(), None);
|
assert_eq!(collection_details_context_clues_iter.next(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(
|
||||||
|
expected = "RadarrContextClueProvider::get_context_clues called with non-Radarr route"
|
||||||
|
)]
|
||||||
|
fn test_radarr_context_clue_provider_get_context_clues_non_radarr_route() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::default().into());
|
||||||
|
|
||||||
|
// This should panic because the route is not a Radarr route
|
||||||
|
RadarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(ActiveRadarrBlock::TestAllIndexers, None)]
|
||||||
|
#[case(ActiveRadarrBlock::AddMovieSearchInput, None)]
|
||||||
|
#[case(ActiveRadarrBlock::AddMovieEmptySearchResults, None)]
|
||||||
|
#[case(ActiveRadarrBlock::SystemLogs, None)]
|
||||||
|
#[case(ActiveRadarrBlock::SystemUpdates, None)]
|
||||||
|
#[case(ActiveRadarrBlock::ViewMovieOverview, None)]
|
||||||
|
#[case(
|
||||||
|
ActiveRadarrBlock::CollectionDetails,
|
||||||
|
Some(ActiveRadarrBlock::ViewMovieOverview)
|
||||||
|
)]
|
||||||
|
fn test_radarr_context_clue_provider_bare_popup_context_clues(
|
||||||
|
#[case] active_radarr_block: ActiveRadarrBlock,
|
||||||
|
#[case] context_option: Option<ActiveRadarrBlock>,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack((active_radarr_block, context_option).into());
|
||||||
|
|
||||||
|
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, &BARE_POPUP_CONTEXT_CLUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(0, ActiveRadarrBlock::MovieDetails, &MOVIE_DETAILS_CONTEXT_CLUES)]
|
||||||
|
#[case(1, ActiveRadarrBlock::MovieHistory, &MOVIE_DETAILS_CONTEXT_CLUES)]
|
||||||
|
#[case(2, ActiveRadarrBlock::FileInfo, &MOVIE_DETAILS_CONTEXT_CLUES)]
|
||||||
|
#[case(3, ActiveRadarrBlock::Cast, &MOVIE_DETAILS_CONTEXT_CLUES)]
|
||||||
|
#[case(4, ActiveRadarrBlock::Crew, &MOVIE_DETAILS_CONTEXT_CLUES)]
|
||||||
|
#[case(5, ActiveRadarrBlock::ManualSearch, &MANUAL_MOVIE_SEARCH_CONTEXT_CLUES)]
|
||||||
|
fn test_radarr_context_clue_provider_movie_details_block_context_clues(
|
||||||
|
#[case] index: usize,
|
||||||
|
#[case] active_radarr_block: ActiveRadarrBlock,
|
||||||
|
#[case] expected_context_clues: &[ContextClue],
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.radarr_data = RadarrData::default();
|
||||||
|
app.data.radarr_data.movie_info_tabs.set_index(index);
|
||||||
|
app.push_navigation_stack(active_radarr_block.into());
|
||||||
|
|
||||||
|
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, expected_context_clues);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_radarr_context_clue_provider_confirmation_prompt_context_clues(
|
||||||
|
#[values(
|
||||||
|
ActiveRadarrBlock::AddMoviePrompt,
|
||||||
|
ActiveRadarrBlock::AddMovieSelectMonitor,
|
||||||
|
ActiveRadarrBlock::AddMovieSelectMinimumAvailability,
|
||||||
|
ActiveRadarrBlock::AddMovieSelectQualityProfile,
|
||||||
|
ActiveRadarrBlock::AddMovieSelectRootFolder,
|
||||||
|
ActiveRadarrBlock::AddMovieTagsInput,
|
||||||
|
ActiveRadarrBlock::SystemTaskStartConfirmPrompt
|
||||||
|
)]
|
||||||
|
active_radarr_block: ActiveRadarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.radarr_data = RadarrData::default();
|
||||||
|
app.push_navigation_stack(active_radarr_block.into());
|
||||||
|
|
||||||
|
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_radarr_context_clue_provider_confirmation_prompt_context_clues_edit_collection_blocks(
|
||||||
|
#[values(
|
||||||
|
ActiveRadarrBlock::EditCollectionPrompt,
|
||||||
|
ActiveRadarrBlock::EditCollectionConfirmPrompt,
|
||||||
|
ActiveRadarrBlock::EditCollectionRootFolderPathInput,
|
||||||
|
ActiveRadarrBlock::EditCollectionSelectMinimumAvailability,
|
||||||
|
ActiveRadarrBlock::EditCollectionSelectQualityProfile,
|
||||||
|
ActiveRadarrBlock::EditCollectionToggleSearchOnAdd,
|
||||||
|
ActiveRadarrBlock::EditCollectionToggleMonitored
|
||||||
|
)]
|
||||||
|
active_radarr_block: ActiveRadarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.radarr_data = RadarrData::default();
|
||||||
|
app.push_navigation_stack(active_radarr_block.into());
|
||||||
|
|
||||||
|
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_radarr_context_clue_provider_confirmation_prompt_context_clues_edit_indexer_blocks(
|
||||||
|
#[values(
|
||||||
|
ActiveRadarrBlock::EditIndexerPrompt,
|
||||||
|
ActiveRadarrBlock::EditIndexerConfirmPrompt,
|
||||||
|
ActiveRadarrBlock::EditIndexerApiKeyInput,
|
||||||
|
ActiveRadarrBlock::EditIndexerNameInput,
|
||||||
|
ActiveRadarrBlock::EditIndexerSeedRatioInput,
|
||||||
|
ActiveRadarrBlock::EditIndexerToggleEnableRss,
|
||||||
|
ActiveRadarrBlock::EditIndexerToggleEnableAutomaticSearch,
|
||||||
|
ActiveRadarrBlock::EditIndexerToggleEnableInteractiveSearch,
|
||||||
|
ActiveRadarrBlock::EditIndexerPriorityInput,
|
||||||
|
ActiveRadarrBlock::EditIndexerUrlInput,
|
||||||
|
ActiveRadarrBlock::EditIndexerTagsInput
|
||||||
|
)]
|
||||||
|
active_radarr_block: ActiveRadarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.radarr_data = RadarrData::default();
|
||||||
|
app.push_navigation_stack(active_radarr_block.into());
|
||||||
|
|
||||||
|
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_radarr_context_clue_provider_confirmation_prompt_context_clues_indexer_settings_blocks(
|
||||||
|
#[values(
|
||||||
|
ActiveRadarrBlock::AllIndexerSettingsPrompt,
|
||||||
|
ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput,
|
||||||
|
ActiveRadarrBlock::IndexerSettingsConfirmPrompt,
|
||||||
|
ActiveRadarrBlock::IndexerSettingsMaximumSizeInput,
|
||||||
|
ActiveRadarrBlock::IndexerSettingsMinimumAgeInput,
|
||||||
|
ActiveRadarrBlock::IndexerSettingsRetentionInput,
|
||||||
|
ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput,
|
||||||
|
ActiveRadarrBlock::IndexerSettingsToggleAllowHardcodedSubs,
|
||||||
|
ActiveRadarrBlock::IndexerSettingsTogglePreferIndexerFlags,
|
||||||
|
ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput
|
||||||
|
)]
|
||||||
|
active_radarr_block: ActiveRadarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.radarr_data = RadarrData::default();
|
||||||
|
app.push_navigation_stack(active_radarr_block.into());
|
||||||
|
|
||||||
|
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_radarr_context_clue_provider_confirmation_prompt_context_clues_edit_movie_blocks(
|
||||||
|
#[values(
|
||||||
|
ActiveRadarrBlock::EditMoviePrompt,
|
||||||
|
ActiveRadarrBlock::EditMovieConfirmPrompt,
|
||||||
|
ActiveRadarrBlock::EditMoviePathInput,
|
||||||
|
ActiveRadarrBlock::EditMovieSelectMinimumAvailability,
|
||||||
|
ActiveRadarrBlock::EditMovieSelectQualityProfile,
|
||||||
|
ActiveRadarrBlock::EditMovieTagsInput,
|
||||||
|
ActiveRadarrBlock::EditMovieToggleMonitored
|
||||||
|
)]
|
||||||
|
active_radarr_block: ActiveRadarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.radarr_data = RadarrData::default();
|
||||||
|
app.push_navigation_stack(active_radarr_block.into());
|
||||||
|
|
||||||
|
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_radarr_context_clue_provider_add_movie_search_results_context_clues(
|
||||||
|
#[values(
|
||||||
|
ActiveRadarrBlock::AddMovieSearchResults,
|
||||||
|
ActiveRadarrBlock::AddMovieAlreadyInLibrary
|
||||||
|
)]
|
||||||
|
active_radarr_block: ActiveRadarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.radarr_data = RadarrData::default();
|
||||||
|
app.push_navigation_stack(active_radarr_block.into());
|
||||||
|
|
||||||
|
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, &ADD_MOVIE_SEARCH_RESULTS_CONTEXT_CLUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_radarr_context_clue_provider_collection_details_context_clues() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.radarr_data = RadarrData::default();
|
||||||
|
app.push_navigation_stack(ActiveRadarrBlock::CollectionDetails.into());
|
||||||
|
|
||||||
|
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, &COLLECTION_DETAILS_CONTEXT_CLUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_radarr_context_clue_provider_system_tasks_context_clues() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.radarr_data = RadarrData::default();
|
||||||
|
|
||||||
|
app.push_navigation_stack(ActiveRadarrBlock::SystemTasks.into());
|
||||||
|
|
||||||
|
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, &SYSTEM_TASKS_CONTEXT_CLUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(0, ActiveRadarrBlock::Movies, &LIBRARY_CONTEXT_CLUES)]
|
||||||
|
#[case(1, ActiveRadarrBlock::Collections, &COLLECTIONS_CONTEXT_CLUES)]
|
||||||
|
#[case(2, ActiveRadarrBlock::Downloads, &DOWNLOADS_CONTEXT_CLUES)]
|
||||||
|
#[case(3, ActiveRadarrBlock::Blocklist, &BLOCKLIST_CONTEXT_CLUES)]
|
||||||
|
#[case(4, ActiveRadarrBlock::History, &HISTORY_CONTEXT_CLUES)]
|
||||||
|
#[case(5, ActiveRadarrBlock::RootFolders, &ROOT_FOLDERS_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(
|
||||||
|
#[case] index: usize,
|
||||||
|
#[case] active_radarr_block: ActiveRadarrBlock,
|
||||||
|
#[case] expected_context_clues: &[ContextClue],
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.radarr_data = RadarrData::default();
|
||||||
|
app.data.radarr_data.main_tabs.set_index(index);
|
||||||
|
app.push_navigation_stack(active_radarr_block.into());
|
||||||
|
|
||||||
|
let context_clues = RadarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, expected_context_clues);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ pub mod sonarr_context_clues;
|
|||||||
#[path = "sonarr_tests.rs"]
|
#[path = "sonarr_tests.rs"]
|
||||||
mod sonarr_tests;
|
mod sonarr_tests;
|
||||||
|
|
||||||
impl<'a> App<'a> {
|
impl App<'_> {
|
||||||
pub(super) async fn dispatch_by_sonarr_block(&mut self, active_sonarr_block: &ActiveSonarrBlock) {
|
pub(super) async fn dispatch_by_sonarr_block(&mut self, active_sonarr_block: &ActiveSonarrBlock) {
|
||||||
match active_sonarr_block {
|
match active_sonarr_block {
|
||||||
ActiveSonarrBlock::Series => {
|
ActiveSonarrBlock::Series => {
|
||||||
@@ -38,30 +38,40 @@ impl<'a> App<'a> {
|
|||||||
}
|
}
|
||||||
ActiveSonarrBlock::SeriesHistory => {
|
ActiveSonarrBlock::SeriesHistory => {
|
||||||
self
|
self
|
||||||
.dispatch_network_event(SonarrEvent::GetSeriesHistory(None).into())
|
.dispatch_network_event(
|
||||||
|
SonarrEvent::GetSeriesHistory(self.extract_series_id().await).into(),
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
ActiveSonarrBlock::SeasonDetails => {
|
ActiveSonarrBlock::SeasonDetails => {
|
||||||
self
|
self
|
||||||
.dispatch_network_event(SonarrEvent::GetEpisodes(None).into())
|
.dispatch_network_event(SonarrEvent::GetEpisodes(self.extract_series_id().await).into())
|
||||||
.await;
|
.await;
|
||||||
self
|
self
|
||||||
.dispatch_network_event(SonarrEvent::GetEpisodeFiles(None).into())
|
.dispatch_network_event(
|
||||||
|
SonarrEvent::GetEpisodeFiles(self.extract_series_id().await).into(),
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
self
|
self
|
||||||
.dispatch_network_event(SonarrEvent::GetDownloads.into())
|
.dispatch_network_event(SonarrEvent::GetDownloads(500).into())
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
ActiveSonarrBlock::SeasonHistory => {
|
ActiveSonarrBlock::SeasonHistory => {
|
||||||
self
|
if !self.data.sonarr_data.seasons.is_empty() {
|
||||||
.dispatch_network_event(SonarrEvent::GetSeasonHistory(None).into())
|
let (series_id, season_number) = self.extract_series_id_season_number_tuple().await;
|
||||||
.await;
|
self
|
||||||
|
.dispatch_network_event(SonarrEvent::GetSeasonHistory(series_id, season_number).into())
|
||||||
|
.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(SonarrEvent::GetSeasonReleases(None).into())
|
.dispatch_network_event(
|
||||||
|
SonarrEvent::GetSeasonReleases(series_id, season_number).into(),
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
@@ -69,28 +79,33 @@ impl<'a> App<'a> {
|
|||||||
}
|
}
|
||||||
ActiveSonarrBlock::EpisodeDetails | ActiveSonarrBlock::EpisodeFile => {
|
ActiveSonarrBlock::EpisodeDetails | ActiveSonarrBlock::EpisodeFile => {
|
||||||
self
|
self
|
||||||
.dispatch_network_event(SonarrEvent::GetEpisodeDetails(None).into())
|
.dispatch_network_event(
|
||||||
|
SonarrEvent::GetEpisodeDetails(self.extract_episode_id().await).into(),
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
ActiveSonarrBlock::EpisodeHistory => {
|
ActiveSonarrBlock::EpisodeHistory => {
|
||||||
self
|
self
|
||||||
.dispatch_network_event(SonarrEvent::GetEpisodeHistory(None).into())
|
.dispatch_network_event(
|
||||||
|
SonarrEvent::GetEpisodeHistory(self.extract_episode_id().await).into(),
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
ActiveSonarrBlock::ManualEpisodeSearch => {
|
ActiveSonarrBlock::ManualEpisodeSearch => {
|
||||||
if let Some(season_details_modal) = self.data.sonarr_data.season_details_modal.as_ref() {
|
if let Some(season_details_modal) = self.data.sonarr_data.season_details_modal.as_ref()
|
||||||
if let Some(episode_details_modal) = season_details_modal.episode_details_modal.as_ref() {
|
&& let Some(episode_details_modal) = season_details_modal.episode_details_modal.as_ref()
|
||||||
if episode_details_modal.episode_releases.is_empty() {
|
&& episode_details_modal.episode_releases.is_empty()
|
||||||
self
|
{
|
||||||
.dispatch_network_event(SonarrEvent::GetEpisodeReleases(None).into())
|
self
|
||||||
.await;
|
.dispatch_network_event(
|
||||||
}
|
SonarrEvent::GetEpisodeReleases(self.extract_episode_id().await).into(),
|
||||||
}
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ActiveSonarrBlock::Downloads => {
|
ActiveSonarrBlock::Downloads => {
|
||||||
self
|
self
|
||||||
.dispatch_network_event(SonarrEvent::GetDownloads.into())
|
.dispatch_network_event(SonarrEvent::GetDownloads(500).into())
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
ActiveSonarrBlock::Blocklist => {
|
ActiveSonarrBlock::Blocklist => {
|
||||||
@@ -103,7 +118,7 @@ impl<'a> App<'a> {
|
|||||||
}
|
}
|
||||||
ActiveSonarrBlock::History => {
|
ActiveSonarrBlock::History => {
|
||||||
self
|
self
|
||||||
.dispatch_network_event(SonarrEvent::GetHistory(None).into())
|
.dispatch_network_event(SonarrEvent::GetHistory(500).into())
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
ActiveSonarrBlock::RootFolders => {
|
ActiveSonarrBlock::RootFolders => {
|
||||||
@@ -126,7 +141,9 @@ impl<'a> App<'a> {
|
|||||||
}
|
}
|
||||||
ActiveSonarrBlock::TestIndexer => {
|
ActiveSonarrBlock::TestIndexer => {
|
||||||
self
|
self
|
||||||
.dispatch_network_event(SonarrEvent::TestIndexer(None).into())
|
.dispatch_network_event(
|
||||||
|
SonarrEvent::TestIndexer(self.extract_sonarr_indexer_id().await).into(),
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
ActiveSonarrBlock::TestAllIndexers => {
|
ActiveSonarrBlock::TestAllIndexers => {
|
||||||
@@ -142,12 +159,14 @@ impl<'a> App<'a> {
|
|||||||
.dispatch_network_event(SonarrEvent::GetQueuedEvents.into())
|
.dispatch_network_event(SonarrEvent::GetQueuedEvents.into())
|
||||||
.await;
|
.await;
|
||||||
self
|
self
|
||||||
.dispatch_network_event(SonarrEvent::GetLogs(None).into())
|
.dispatch_network_event(SonarrEvent::GetLogs(500).into())
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
ActiveSonarrBlock::AddSeriesSearchResults => {
|
ActiveSonarrBlock::AddSeriesSearchResults => {
|
||||||
self
|
self
|
||||||
.dispatch_network_event(SonarrEvent::SearchNewSeries(None).into())
|
.dispatch_network_event(
|
||||||
|
SonarrEvent::SearchNewSeries(self.extract_add_new_series_search_query().await).into(),
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
ActiveSonarrBlock::SystemUpdates => {
|
ActiveSonarrBlock::SystemUpdates => {
|
||||||
@@ -165,12 +184,9 @@ impl<'a> App<'a> {
|
|||||||
async fn check_for_sonarr_prompt_action(&mut self) {
|
async fn check_for_sonarr_prompt_action(&mut self) {
|
||||||
if self.data.sonarr_data.prompt_confirm {
|
if self.data.sonarr_data.prompt_confirm {
|
||||||
self.data.sonarr_data.prompt_confirm = false;
|
self.data.sonarr_data.prompt_confirm = false;
|
||||||
if let Some(sonarr_event) = &self.data.sonarr_data.prompt_confirm_action {
|
if let Some(sonarr_event) = self.data.sonarr_data.prompt_confirm_action.take() {
|
||||||
self
|
self.dispatch_network_event(sonarr_event.into()).await;
|
||||||
.dispatch_network_event(sonarr_event.clone().into())
|
|
||||||
.await;
|
|
||||||
self.should_refresh = true;
|
self.should_refresh = true;
|
||||||
self.data.sonarr_data.prompt_confirm_action = None;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,7 +212,7 @@ impl<'a> App<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.tick_count % self.tick_until_poll == 0 {
|
if self.tick_count.is_multiple_of(self.tick_until_poll) {
|
||||||
self.refresh_sonarr_metadata().await;
|
self.refresh_sonarr_metadata().await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,7 +231,7 @@ impl<'a> App<'a> {
|
|||||||
.dispatch_network_event(SonarrEvent::GetRootFolders.into())
|
.dispatch_network_event(SonarrEvent::GetRootFolders.into())
|
||||||
.await;
|
.await;
|
||||||
self
|
self
|
||||||
.dispatch_network_event(SonarrEvent::GetDownloads.into())
|
.dispatch_network_event(SonarrEvent::GetDownloads(500).into())
|
||||||
.await;
|
.await;
|
||||||
self
|
self
|
||||||
.dispatch_network_event(SonarrEvent::GetDiskSpace.into())
|
.dispatch_network_event(SonarrEvent::GetDiskSpace.into())
|
||||||
@@ -242,4 +258,46 @@ impl<'a> App<'a> {
|
|||||||
.collect();
|
.collect();
|
||||||
self.data.sonarr_data.seasons.set_items(seasons);
|
self.data.sonarr_data.seasons.set_items(seasons);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn extract_episode_id(&self) -> i64 {
|
||||||
|
self
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.season_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.expect("Season details have not been loaded")
|
||||||
|
.episodes
|
||||||
|
.current_selection()
|
||||||
|
.id
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn extract_series_id(&self) -> i64 {
|
||||||
|
self.data.sonarr_data.series.current_selection().id
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn extract_series_id_season_number_tuple(&self) -> (i64, i64) {
|
||||||
|
let series_id = self.data.sonarr_data.series.current_selection().id;
|
||||||
|
let season_number = self
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.seasons
|
||||||
|
.current_selection()
|
||||||
|
.season_number;
|
||||||
|
(series_id, season_number)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn extract_add_new_series_search_query(&self) -> String {
|
||||||
|
self
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.add_series_search
|
||||||
|
.as_ref()
|
||||||
|
.expect("Add series search is empty")
|
||||||
|
.text
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn extract_sonarr_indexer_id(&self) -> i64 {
|
||||||
|
self.data.sonarr_data.indexers.current_selection().id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
use crate::app::{context_clues::ContextClue, key_binding::DEFAULT_KEYBINDINGS};
|
use crate::app::context_clues::{
|
||||||
|
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::models::Route;
|
||||||
|
use crate::models::servarr_data::sonarr::sonarr_data::{
|
||||||
|
ADD_SERIES_BLOCKS, ActiveSonarrBlock, EDIT_INDEXER_BLOCKS, EDIT_SERIES_BLOCKS,
|
||||||
|
EPISODE_DETAILS_BLOCKS, INDEXER_SETTINGS_BLOCKS, SEASON_DETAILS_BLOCKS, SERIES_DETAILS_BLOCKS,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "sonarr_context_clues_tests.rs"]
|
#[path = "sonarr_context_clues_tests.rs"]
|
||||||
@@ -9,9 +18,13 @@ pub static ADD_SERIES_SEARCH_RESULTS_CONTEXT_CLUES: [ContextClue; 2] = [
|
|||||||
(DEFAULT_KEYBINDINGS.esc, "edit search"),
|
(DEFAULT_KEYBINDINGS.esc, "edit search"),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub static SERIES_CONTEXT_CLUES: [ContextClue; 10] = [
|
pub static SERIES_CONTEXT_CLUES: [ContextClue; 11] = [
|
||||||
(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc),
|
(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc),
|
||||||
(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc),
|
(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc),
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.toggle_monitoring,
|
||||||
|
DEFAULT_KEYBINDINGS.toggle_monitoring.desc,
|
||||||
|
),
|
||||||
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
||||||
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
|
(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc),
|
||||||
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
||||||
@@ -45,18 +58,6 @@ pub static SERIES_DETAILS_CONTEXT_CLUES: [ContextClue; 8] = [
|
|||||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub static HISTORY_CONTEXT_CLUES: [ContextClue; 6] = [
|
|
||||||
(DEFAULT_KEYBINDINGS.submit, "details"),
|
|
||||||
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
|
||||||
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
|
||||||
(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc),
|
|
||||||
(
|
|
||||||
DEFAULT_KEYBINDINGS.refresh,
|
|
||||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
|
||||||
),
|
|
||||||
(DEFAULT_KEYBINDINGS.esc, "cancel filter"),
|
|
||||||
];
|
|
||||||
|
|
||||||
pub static SERIES_HISTORY_CONTEXT_CLUES: [ContextClue; 9] = [
|
pub static SERIES_HISTORY_CONTEXT_CLUES: [ContextClue; 9] = [
|
||||||
(
|
(
|
||||||
DEFAULT_KEYBINDINGS.refresh,
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
@@ -75,12 +76,7 @@ pub static SERIES_HISTORY_CONTEXT_CLUES: [ContextClue; 9] = [
|
|||||||
(DEFAULT_KEYBINDINGS.esc, "cancel filter/close"),
|
(DEFAULT_KEYBINDINGS.esc, "cancel filter/close"),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub static SEASON_DETAILS_CONTEXTUAL_CONTEXT_CLUES: [ContextClue; 2] = [
|
pub static SEASON_DETAILS_CONTEXT_CLUES: [ContextClue; 7] = [
|
||||||
(DEFAULT_KEYBINDINGS.submit, "episode details"),
|
|
||||||
(DEFAULT_KEYBINDINGS.delete, "delete episode"),
|
|
||||||
];
|
|
||||||
|
|
||||||
pub static SEASON_DETAILS_CONTEXT_CLUES: [ContextClue; 5] = [
|
|
||||||
(
|
(
|
||||||
DEFAULT_KEYBINDINGS.refresh,
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||||
@@ -95,9 +91,11 @@ pub static SEASON_DETAILS_CONTEXT_CLUES: [ContextClue; 5] = [
|
|||||||
DEFAULT_KEYBINDINGS.auto_search.desc,
|
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||||
),
|
),
|
||||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||||
|
(DEFAULT_KEYBINDINGS.submit, "episode details"),
|
||||||
|
(DEFAULT_KEYBINDINGS.delete, "delete episode"),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub static SEASON_HISTORY_CONTEXT_CLUES: [ContextClue; 6] = [
|
pub static SEASON_HISTORY_CONTEXT_CLUES: [ContextClue; 7] = [
|
||||||
(
|
(
|
||||||
DEFAULT_KEYBINDINGS.refresh,
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||||
@@ -109,10 +107,11 @@ pub static SEASON_HISTORY_CONTEXT_CLUES: [ContextClue; 6] = [
|
|||||||
DEFAULT_KEYBINDINGS.auto_search,
|
DEFAULT_KEYBINDINGS.auto_search,
|
||||||
DEFAULT_KEYBINDINGS.auto_search.desc,
|
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||||
),
|
),
|
||||||
|
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||||
(DEFAULT_KEYBINDINGS.esc, "cancel filter/close"),
|
(DEFAULT_KEYBINDINGS.esc, "cancel filter/close"),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub static MANUAL_SEASON_SEARCH_CONTEXT_CLUES: [ContextClue; 4] = [
|
pub static MANUAL_SEASON_SEARCH_CONTEXT_CLUES: [ContextClue; 5] = [
|
||||||
(
|
(
|
||||||
DEFAULT_KEYBINDINGS.refresh,
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||||
@@ -122,10 +121,11 @@ pub static MANUAL_SEASON_SEARCH_CONTEXT_CLUES: [ContextClue; 4] = [
|
|||||||
DEFAULT_KEYBINDINGS.auto_search.desc,
|
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||||
),
|
),
|
||||||
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
||||||
|
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub static MANUAL_EPISODE_SEARCH_CONTEXT_CLUES: [ContextClue; 4] = [
|
pub static MANUAL_EPISODE_SEARCH_CONTEXT_CLUES: [ContextClue; 5] = [
|
||||||
(
|
(
|
||||||
DEFAULT_KEYBINDINGS.refresh,
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
DEFAULT_KEYBINDINGS.refresh.desc,
|
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||||
@@ -135,12 +135,10 @@ pub static MANUAL_EPISODE_SEARCH_CONTEXT_CLUES: [ContextClue; 4] = [
|
|||||||
DEFAULT_KEYBINDINGS.auto_search.desc,
|
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||||
),
|
),
|
||||||
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc),
|
||||||
|
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub static DETAILS_CONTEXTUAL_CONTEXT_CLUES: [ContextClue; 1] =
|
|
||||||
[(DEFAULT_KEYBINDINGS.submit, "details")];
|
|
||||||
|
|
||||||
pub static EPISODE_DETAILS_CONTEXT_CLUES: [ContextClue; 3] = [
|
pub static EPISODE_DETAILS_CONTEXT_CLUES: [ContextClue; 3] = [
|
||||||
(
|
(
|
||||||
DEFAULT_KEYBINDINGS.refresh,
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
@@ -153,7 +151,79 @@ pub static EPISODE_DETAILS_CONTEXT_CLUES: [ContextClue; 3] = [
|
|||||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub static SYSTEM_TASKS_CONTEXT_CLUES: [ContextClue; 2] = [
|
pub static SELECTABLE_EPISODE_DETAILS_CONTEXT_CLUES: [ContextClue; 4] = [
|
||||||
(DEFAULT_KEYBINDINGS.submit, "start task"),
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search,
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||||
|
),
|
||||||
|
(DEFAULT_KEYBINDINGS.submit, "details"),
|
||||||
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
pub(in crate::app) struct SonarrContextClueProvider;
|
||||||
|
|
||||||
|
impl ContextClueProvider for SonarrContextClueProvider {
|
||||||
|
fn get_context_clues(app: &mut App<'_>) -> Option<&'static [ContextClue]> {
|
||||||
|
let Route::Sonarr(active_sonarr_block, _) = app.get_current_route() else {
|
||||||
|
panic!("SonarrContextClueProvider::get_context_clues called with non-Sonarr route");
|
||||||
|
};
|
||||||
|
match active_sonarr_block {
|
||||||
|
_ if SERIES_DETAILS_BLOCKS.contains(&active_sonarr_block) => app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.series_info_tabs
|
||||||
|
.get_active_route_contextual_help(),
|
||||||
|
_ if SEASON_DETAILS_BLOCKS.contains(&active_sonarr_block) => app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.season_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.season_details_tabs
|
||||||
|
.get_active_route_contextual_help(),
|
||||||
|
_ if EPISODE_DETAILS_BLOCKS.contains(&active_sonarr_block) => app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.season_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.episode_details_modal
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.episode_details_tabs
|
||||||
|
.get_active_route_contextual_help(),
|
||||||
|
ActiveSonarrBlock::TestAllIndexers
|
||||||
|
| ActiveSonarrBlock::AddSeriesSearchInput
|
||||||
|
| ActiveSonarrBlock::AddSeriesEmptySearchResults
|
||||||
|
| ActiveSonarrBlock::SystemLogs
|
||||||
|
| ActiveSonarrBlock::SystemUpdates => Some(&BARE_POPUP_CONTEXT_CLUES),
|
||||||
|
_ if EDIT_INDEXER_BLOCKS.contains(&active_sonarr_block)
|
||||||
|
|| INDEXER_SETTINGS_BLOCKS.contains(&active_sonarr_block)
|
||||||
|
|| EDIT_SERIES_BLOCKS.contains(&active_sonarr_block) =>
|
||||||
|
{
|
||||||
|
Some(&CONFIRMATION_PROMPT_CONTEXT_CLUES)
|
||||||
|
}
|
||||||
|
ActiveSonarrBlock::AddSeriesPrompt
|
||||||
|
| ActiveSonarrBlock::AddSeriesSelectMonitor
|
||||||
|
| ActiveSonarrBlock::AddSeriesSelectSeriesType
|
||||||
|
| ActiveSonarrBlock::AddSeriesSelectQualityProfile
|
||||||
|
| ActiveSonarrBlock::AddSeriesSelectLanguageProfile
|
||||||
|
| ActiveSonarrBlock::AddSeriesSelectRootFolder
|
||||||
|
| ActiveSonarrBlock::AddSeriesTagsInput
|
||||||
|
| ActiveSonarrBlock::SystemTaskStartConfirmPrompt => Some(&CONFIRMATION_PROMPT_CONTEXT_CLUES),
|
||||||
|
_ if ADD_SERIES_BLOCKS.contains(&active_sonarr_block) => {
|
||||||
|
Some(&ADD_SERIES_SEARCH_RESULTS_CONTEXT_CLUES)
|
||||||
|
}
|
||||||
|
ActiveSonarrBlock::SystemTasks => Some(&SYSTEM_TASKS_CONTEXT_CLUES),
|
||||||
|
_ => app
|
||||||
|
.data
|
||||||
|
.sonarr_data
|
||||||
|
.main_tabs
|
||||||
|
.get_active_route_contextual_help(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,402 +1,654 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
use crate::app::context_clues::{
|
||||||
|
BARE_POPUP_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES,
|
||||||
|
ContextClue, ContextClueProvider, DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES,
|
||||||
|
INDEXERS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
|
||||||
|
SYSTEM_TASKS_CONTEXT_CLUES,
|
||||||
|
};
|
||||||
|
use crate::app::sonarr::sonarr_context_clues::{
|
||||||
|
SELECTABLE_EPISODE_DETAILS_CONTEXT_CLUES, SonarrContextClueProvider,
|
||||||
|
};
|
||||||
use crate::app::{
|
use crate::app::{
|
||||||
|
App,
|
||||||
key_binding::DEFAULT_KEYBINDINGS,
|
key_binding::DEFAULT_KEYBINDINGS,
|
||||||
sonarr::sonarr_context_clues::{
|
sonarr::sonarr_context_clues::{
|
||||||
ADD_SERIES_SEARCH_RESULTS_CONTEXT_CLUES, DETAILS_CONTEXTUAL_CONTEXT_CLUES,
|
ADD_SERIES_SEARCH_RESULTS_CONTEXT_CLUES, EPISODE_DETAILS_CONTEXT_CLUES,
|
||||||
EPISODE_DETAILS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES, MANUAL_EPISODE_SEARCH_CONTEXT_CLUES,
|
MANUAL_EPISODE_SEARCH_CONTEXT_CLUES, MANUAL_SEASON_SEARCH_CONTEXT_CLUES,
|
||||||
MANUAL_SEASON_SEARCH_CONTEXT_CLUES, SEASON_DETAILS_CONTEXTUAL_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::sonarr::modals::{EpisodeDetailsModal, SeasonDetailsModal};
|
||||||
|
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SonarrData};
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_series_search_results_context_clues() {
|
fn test_add_series_search_results_context_clues() {
|
||||||
let mut add_series_search_results_context_clues_iter =
|
let mut add_series_search_results_context_clues_iter =
|
||||||
ADD_SERIES_SEARCH_RESULTS_CONTEXT_CLUES.iter();
|
ADD_SERIES_SEARCH_RESULTS_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = add_series_search_results_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
add_series_search_results_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||||
assert_str_eq!(*description, "details");
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
let (key_binding, description) = add_series_search_results_context_clues_iter.next().unwrap();
|
add_series_search_results_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.esc, "edit search")
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
);
|
||||||
assert_str_eq!(*description, "edit search");
|
assert_none!(add_series_search_results_context_clues_iter.next());
|
||||||
assert_eq!(add_series_search_results_context_clues_iter.next(), None);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_series_context_clues() {
|
fn test_series_context_clues() {
|
||||||
let mut series_context_clues_iter = SERIES_CONTEXT_CLUES.iter();
|
let mut series_context_clues_iter = SERIES_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = series_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
series_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.add);
|
&(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc)
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.add.desc);
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
let (key_binding, description) = series_context_clues_iter.next().unwrap();
|
series_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc)
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit);
|
);
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.edit.desc);
|
assert_some_eq_x!(
|
||||||
|
series_context_clues_iter.next(),
|
||||||
let (key_binding, description) = series_context_clues_iter.next().unwrap();
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.toggle_monitoring,
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
|
DEFAULT_KEYBINDINGS.toggle_monitoring.desc
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
|
)
|
||||||
|
);
|
||||||
let (key_binding, description) = series_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
series_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
|
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.delete.desc);
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
let (key_binding, description) = series_context_clues_iter.next().unwrap();
|
series_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.delete, DEFAULT_KEYBINDINGS.delete.desc)
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search);
|
);
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.search.desc);
|
assert_some_eq_x!(
|
||||||
|
series_context_clues_iter.next(),
|
||||||
let (key_binding, description) = series_context_clues_iter.next().unwrap();
|
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
|
||||||
|
);
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.filter);
|
assert_some_eq_x!(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.filter.desc);
|
series_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc)
|
||||||
let (key_binding, description) = series_context_clues_iter.next().unwrap();
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
series_context_clues_iter.next(),
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
let (key_binding, description) = series_context_clues_iter.next().unwrap();
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
|
)
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update);
|
);
|
||||||
assert_str_eq!(*description, "update all");
|
assert_some_eq_x!(
|
||||||
|
series_context_clues_iter.next(),
|
||||||
let (key_binding, description) = series_context_clues_iter.next().unwrap();
|
&(DEFAULT_KEYBINDINGS.update, "update all")
|
||||||
|
);
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
assert_some_eq_x!(
|
||||||
assert_str_eq!(*description, "details");
|
series_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||||
let (key_binding, description) = series_context_clues_iter.next().unwrap();
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
series_context_clues_iter.next(),
|
||||||
assert_str_eq!(*description, "cancel filter");
|
&(DEFAULT_KEYBINDINGS.esc, "cancel filter")
|
||||||
assert_eq!(series_context_clues_iter.next(), None);
|
);
|
||||||
|
assert_none!(series_context_clues_iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_series_history_context_clues() {
|
fn test_series_history_context_clues() {
|
||||||
let mut series_history_context_clues_iter = SERIES_HISTORY_CONTEXT_CLUES.iter();
|
let mut series_history_context_clues_iter = SERIES_HISTORY_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
series_history_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
&(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
|
)
|
||||||
|
);
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit);
|
assert_some_eq_x!(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.edit.desc);
|
series_history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc)
|
||||||
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
series_history_context_clues_iter.next(),
|
||||||
assert_str_eq!(*description, "details");
|
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||||
|
);
|
||||||
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
series_history_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
|
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
|
series_history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search);
|
);
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.search.desc);
|
assert_some_eq_x!(
|
||||||
|
series_history_context_clues_iter.next(),
|
||||||
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
|
&(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc)
|
||||||
|
);
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.filter);
|
assert_some_eq_x!(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.filter.desc);
|
series_history_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
|
DEFAULT_KEYBINDINGS.auto_search,
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search.desc
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search);
|
)
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc);
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
|
series_history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc)
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update);
|
);
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.update.desc);
|
assert_some_eq_x!(
|
||||||
|
series_history_context_clues_iter.next(),
|
||||||
let (key_binding, description) = series_history_context_clues_iter.next().unwrap();
|
&(DEFAULT_KEYBINDINGS.esc, "cancel filter/close")
|
||||||
|
);
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
assert_none!(series_history_context_clues_iter.next());
|
||||||
assert_str_eq!(*description, "cancel filter/close");
|
|
||||||
assert_eq!(series_history_context_clues_iter.next(), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_history_context_clues() {
|
|
||||||
let mut history_context_clues_iter = HISTORY_CONTEXT_CLUES.iter();
|
|
||||||
|
|
||||||
let (key_binding, description) = history_context_clues_iter.next().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
|
||||||
assert_str_eq!(*description, "details");
|
|
||||||
|
|
||||||
let (key_binding, description) = history_context_clues_iter.next().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
|
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
|
|
||||||
|
|
||||||
let (key_binding, description) = history_context_clues_iter.next().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search);
|
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.search.desc);
|
|
||||||
|
|
||||||
let (key_binding, description) = history_context_clues_iter.next().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.filter);
|
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.filter.desc);
|
|
||||||
|
|
||||||
let (key_binding, description) = history_context_clues_iter.next().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
|
||||||
|
|
||||||
let (key_binding, description) = history_context_clues_iter.next().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
|
||||||
assert_str_eq!(*description, "cancel filter");
|
|
||||||
assert_eq!(history_context_clues_iter.next(), None);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_series_details_context_clues() {
|
fn test_series_details_context_clues() {
|
||||||
let mut series_details_context_clues_iter = SERIES_DETAILS_CONTEXT_CLUES.iter();
|
let mut series_details_context_clues_iter = SERIES_DETAILS_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = series_details_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
series_details_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
&(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
let (key_binding, description) = series_details_context_clues_iter.next().unwrap();
|
)
|
||||||
|
);
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit);
|
assert_some_eq_x!(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.edit.desc);
|
series_details_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc)
|
||||||
let (key_binding, description) = series_details_context_clues_iter.next().unwrap();
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.toggle_monitoring);
|
series_details_context_clues_iter.next(),
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.toggle_monitoring.desc);
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.toggle_monitoring,
|
||||||
let (key_binding, description) = series_details_context_clues_iter.next().unwrap();
|
DEFAULT_KEYBINDINGS.toggle_monitoring.desc
|
||||||
|
)
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
);
|
||||||
assert_str_eq!(*description, "season details");
|
assert_some_eq_x!(
|
||||||
|
series_details_context_clues_iter.next(),
|
||||||
let (key_binding, description) = series_details_context_clues_iter.next().unwrap();
|
&(DEFAULT_KEYBINDINGS.submit, "season details")
|
||||||
|
);
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search);
|
assert_some_eq_x!(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.search.desc);
|
series_details_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
|
||||||
let (key_binding, description) = series_details_context_clues_iter.next().unwrap();
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.update);
|
series_details_context_clues_iter.next(),
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.update.desc);
|
&(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc)
|
||||||
|
);
|
||||||
let (key_binding, description) = series_details_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
series_details_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search);
|
&(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc);
|
DEFAULT_KEYBINDINGS.auto_search,
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search.desc
|
||||||
let (key_binding, description) = series_details_context_clues_iter.next().unwrap();
|
)
|
||||||
|
);
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
assert_some_eq_x!(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
|
series_details_context_clues_iter.next(),
|
||||||
assert_eq!(series_details_context_clues_iter.next(), None);
|
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||||
|
);
|
||||||
|
assert_none!(series_details_context_clues_iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_season_details_context_clues() {
|
fn test_season_details_context_clues() {
|
||||||
let mut season_details_context_clues_iter = SEASON_DETAILS_CONTEXT_CLUES.iter();
|
let mut season_details_context_clues_iter = SEASON_DETAILS_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = season_details_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
season_details_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
&(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
let (key_binding, description) = season_details_context_clues_iter.next().unwrap();
|
)
|
||||||
|
);
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.toggle_monitoring);
|
assert_some_eq_x!(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.toggle_monitoring.desc);
|
season_details_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
let (key_binding, description) = season_details_context_clues_iter.next().unwrap();
|
DEFAULT_KEYBINDINGS.toggle_monitoring,
|
||||||
|
DEFAULT_KEYBINDINGS.toggle_monitoring.desc
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search);
|
)
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.search.desc);
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
let (key_binding, description) = season_details_context_clues_iter.next().unwrap();
|
season_details_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search);
|
);
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc);
|
assert_some_eq_x!(
|
||||||
|
season_details_context_clues_iter.next(),
|
||||||
let (key_binding, description) = season_details_context_clues_iter.next().unwrap();
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search,
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
DEFAULT_KEYBINDINGS.auto_search.desc
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
|
)
|
||||||
assert_eq!(season_details_context_clues_iter.next(), None);
|
);
|
||||||
}
|
assert_some_eq_x!(
|
||||||
|
season_details_context_clues_iter.next(),
|
||||||
#[test]
|
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||||
fn test_season_details_contextual_context_clues() {
|
);
|
||||||
let mut season_details_contextual_context_clues_iter =
|
assert_some_eq_x!(
|
||||||
SEASON_DETAILS_CONTEXTUAL_CONTEXT_CLUES.iter();
|
season_details_context_clues_iter.next(),
|
||||||
let (key_binding, description) = season_details_contextual_context_clues_iter.next().unwrap();
|
&(DEFAULT_KEYBINDINGS.submit, "episode details")
|
||||||
|
);
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
assert_some_eq_x!(
|
||||||
assert_str_eq!(*description, "episode details");
|
season_details_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.delete, "delete episode")
|
||||||
let (key_binding, description) = season_details_contextual_context_clues_iter.next().unwrap();
|
);
|
||||||
|
assert_none!(season_details_context_clues_iter.next());
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.delete);
|
|
||||||
assert_str_eq!(*description, "delete episode");
|
|
||||||
assert_eq!(season_details_contextual_context_clues_iter.next(), None);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_season_history_context_clues() {
|
fn test_season_history_context_clues() {
|
||||||
let mut season_history_context_clues_iter = SEASON_HISTORY_CONTEXT_CLUES.iter();
|
let mut season_history_context_clues_iter = SEASON_HISTORY_CONTEXT_CLUES.iter();
|
||||||
let (key_binding, description) = season_history_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
season_history_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
&(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
let (key_binding, description) = season_history_context_clues_iter.next().unwrap();
|
)
|
||||||
|
);
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
|
assert_some_eq_x!(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
|
season_history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||||
let (key_binding, description) = season_history_context_clues_iter.next().unwrap();
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.search);
|
season_history_context_clues_iter.next(),
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.search.desc);
|
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
|
||||||
|
);
|
||||||
let (key_binding, description) = season_history_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
season_history_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.filter);
|
&(DEFAULT_KEYBINDINGS.filter, DEFAULT_KEYBINDINGS.filter.desc)
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.filter.desc);
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
let (key_binding, description) = season_history_context_clues_iter.next().unwrap();
|
season_history_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search);
|
DEFAULT_KEYBINDINGS.auto_search,
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc);
|
DEFAULT_KEYBINDINGS.auto_search.desc
|
||||||
|
)
|
||||||
let (key_binding, description) = season_history_context_clues_iter.next().unwrap();
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
season_history_context_clues_iter.next(),
|
||||||
assert_str_eq!(*description, "cancel filter/close");
|
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||||
assert_eq!(season_history_context_clues_iter.next(), None);
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
season_history_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.esc, "cancel filter/close")
|
||||||
|
);
|
||||||
|
assert_none!(season_history_context_clues_iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_manual_season_search_context_clues() {
|
fn test_manual_season_search_context_clues() {
|
||||||
let mut manual_season_search_context_clues_iter = MANUAL_SEASON_SEARCH_CONTEXT_CLUES.iter();
|
let mut manual_season_search_context_clues_iter = MANUAL_SEASON_SEARCH_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = manual_season_search_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
manual_season_search_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
&(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
let (key_binding, description) = manual_season_search_context_clues_iter.next().unwrap();
|
)
|
||||||
|
);
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search);
|
assert_some_eq_x!(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc);
|
manual_season_search_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
let (key_binding, description) = manual_season_search_context_clues_iter.next().unwrap();
|
DEFAULT_KEYBINDINGS.auto_search,
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search.desc
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
|
)
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
let (key_binding, description) = manual_season_search_context_clues_iter.next().unwrap();
|
manual_season_search_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
);
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
|
assert_some_eq_x!(
|
||||||
assert_eq!(manual_season_search_context_clues_iter.next(), None);
|
manual_season_search_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
manual_season_search_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||||
|
);
|
||||||
|
assert_none!(manual_season_search_context_clues_iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_manual_episode_search_context_clues() {
|
fn test_manual_episode_search_context_clues() {
|
||||||
let mut manual_episode_search_context_clues_iter = MANUAL_EPISODE_SEARCH_CONTEXT_CLUES.iter();
|
let mut manual_episode_search_context_clues_iter = MANUAL_EPISODE_SEARCH_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = manual_episode_search_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
manual_episode_search_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
&(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
let (key_binding, description) = manual_episode_search_context_clues_iter.next().unwrap();
|
)
|
||||||
|
);
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search);
|
assert_some_eq_x!(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc);
|
manual_episode_search_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
let (key_binding, description) = manual_episode_search_context_clues_iter.next().unwrap();
|
DEFAULT_KEYBINDINGS.auto_search,
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search.desc
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.sort);
|
)
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.sort.desc);
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
let (key_binding, description) = manual_episode_search_context_clues_iter.next().unwrap();
|
manual_episode_search_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.sort, DEFAULT_KEYBINDINGS.sort.desc)
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
);
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
|
assert_some_eq_x!(
|
||||||
assert_eq!(manual_episode_search_context_clues_iter.next(), None);
|
manual_episode_search_context_clues_iter.next(),
|
||||||
}
|
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||||
|
);
|
||||||
#[test]
|
assert_some_eq_x!(
|
||||||
fn details_contextual_context_clues() {
|
manual_episode_search_context_clues_iter.next(),
|
||||||
let mut manual_search_contextual_context_clues_iter = DETAILS_CONTEXTUAL_CONTEXT_CLUES.iter();
|
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||||
let (key_binding, description) = manual_search_contextual_context_clues_iter.next().unwrap();
|
);
|
||||||
|
assert_none!(manual_episode_search_context_clues_iter.next());
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
|
||||||
assert_str_eq!(*description, "details");
|
|
||||||
assert_eq!(manual_search_contextual_context_clues_iter.next(), None);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_episode_details_context_clues() {
|
fn test_episode_details_context_clues() {
|
||||||
let mut episode_details_context_clues_iter = EPISODE_DETAILS_CONTEXT_CLUES.iter();
|
let mut episode_details_context_clues_iter = EPISODE_DETAILS_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = episode_details_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
episode_details_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
episode_details_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search,
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search.desc
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
episode_details_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||||
|
);
|
||||||
|
assert_none!(episode_details_context_clues_iter.next());
|
||||||
|
}
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.refresh);
|
#[test]
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.refresh.desc);
|
fn test_selectable_episode_details_context_clues() {
|
||||||
|
let mut episode_details_context_clues_iter = SELECTABLE_EPISODE_DETAILS_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = episode_details_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
episode_details_context_clues_iter.next(),
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.auto_search);
|
&(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.auto_search.desc);
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc
|
||||||
let (key_binding, description) = episode_details_context_clues_iter.next().unwrap();
|
)
|
||||||
|
);
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
assert_some_eq_x!(
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
|
episode_details_context_clues_iter.next(),
|
||||||
assert_eq!(episode_details_context_clues_iter.next(), None);
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search,
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search.desc
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
episode_details_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.submit, "details")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
episode_details_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||||
|
);
|
||||||
|
assert_none!(episode_details_context_clues_iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_system_tasks_context_clues() {
|
fn test_system_tasks_context_clues() {
|
||||||
let mut system_tasks_context_clues_iter = SYSTEM_TASKS_CONTEXT_CLUES.iter();
|
let mut system_tasks_context_clues_iter = SYSTEM_TASKS_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
let (key_binding, description) = system_tasks_context_clues_iter.next().unwrap();
|
assert_some_eq_x!(
|
||||||
|
system_tasks_context_clues_iter.next(),
|
||||||
|
&(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());
|
||||||
|
}
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
#[test]
|
||||||
assert_str_eq!(*description, "start task");
|
#[should_panic(
|
||||||
|
expected = "SonarrContextClueProvider::get_context_clues called with non-Sonarr route"
|
||||||
|
)]
|
||||||
|
fn test_sonarr_context_clue_provider_get_context_clues_non_sonarr_route() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveRadarrBlock::default().into());
|
||||||
|
|
||||||
let (key_binding, description) = system_tasks_context_clues_iter.next().unwrap();
|
SonarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
}
|
||||||
|
|
||||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.esc);
|
#[rstest]
|
||||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.esc.desc);
|
#[case(0, ActiveSonarrBlock::SeriesDetails, &SERIES_DETAILS_CONTEXT_CLUES)]
|
||||||
assert_eq!(system_tasks_context_clues_iter.next(), None);
|
#[case(1, ActiveSonarrBlock::SeriesHistory, &SERIES_HISTORY_CONTEXT_CLUES)]
|
||||||
|
fn test_sonarr_context_clue_provider_series_info_tabs(
|
||||||
|
#[case] index: usize,
|
||||||
|
#[case] active_sonarr_block: ActiveSonarrBlock,
|
||||||
|
#[case] expected_context_clues: &[ContextClue],
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.sonarr_data = SonarrData::default();
|
||||||
|
app.data.sonarr_data.series_info_tabs.set_index(index);
|
||||||
|
app.push_navigation_stack(active_sonarr_block.into());
|
||||||
|
|
||||||
|
let context_clues = SonarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, expected_context_clues);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(0, ActiveSonarrBlock::SeasonDetails, &SEASON_DETAILS_CONTEXT_CLUES)]
|
||||||
|
#[case(1, ActiveSonarrBlock::SeasonHistory, &SEASON_HISTORY_CONTEXT_CLUES)]
|
||||||
|
#[case(2, ActiveSonarrBlock::ManualSeasonSearch, &MANUAL_SEASON_SEARCH_CONTEXT_CLUES)]
|
||||||
|
fn test_sonarr_context_clue_provider_season_details_tabs(
|
||||||
|
#[case] index: usize,
|
||||||
|
#[case] active_sonarr_block: ActiveSonarrBlock,
|
||||||
|
#[case] expected_context_clues: &[ContextClue],
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
let mut season_details_modal = SeasonDetailsModal::default();
|
||||||
|
season_details_modal.season_details_tabs.set_index(index);
|
||||||
|
let sonarr_data = SonarrData {
|
||||||
|
season_details_modal: Some(season_details_modal),
|
||||||
|
..SonarrData::default()
|
||||||
|
};
|
||||||
|
app.data.sonarr_data = sonarr_data;
|
||||||
|
app.push_navigation_stack(active_sonarr_block.into());
|
||||||
|
|
||||||
|
let context_clues = SonarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, expected_context_clues);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(0, ActiveSonarrBlock::EpisodeDetails, &EPISODE_DETAILS_CONTEXT_CLUES)]
|
||||||
|
#[case(1, ActiveSonarrBlock::EpisodeHistory, &SELECTABLE_EPISODE_DETAILS_CONTEXT_CLUES)]
|
||||||
|
#[case(2, ActiveSonarrBlock::EpisodeFile, &EPISODE_DETAILS_CONTEXT_CLUES)]
|
||||||
|
#[case(3, ActiveSonarrBlock::ManualEpisodeSearch, &MANUAL_EPISODE_SEARCH_CONTEXT_CLUES)]
|
||||||
|
fn test_sonarr_context_clue_provider_episode_details_tabs(
|
||||||
|
#[case] index: usize,
|
||||||
|
#[case] active_sonarr_block: ActiveSonarrBlock,
|
||||||
|
#[case] expected_context_clues: &[ContextClue],
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
let mut episode_details_modal = EpisodeDetailsModal::default();
|
||||||
|
episode_details_modal.episode_details_tabs.set_index(index);
|
||||||
|
let sonarr_data = SonarrData {
|
||||||
|
season_details_modal: Some(SeasonDetailsModal {
|
||||||
|
episode_details_modal: Some(episode_details_modal),
|
||||||
|
..SeasonDetailsModal::default()
|
||||||
|
}),
|
||||||
|
..SonarrData::default()
|
||||||
|
};
|
||||||
|
app.data.sonarr_data = sonarr_data;
|
||||||
|
app.push_navigation_stack(active_sonarr_block.into());
|
||||||
|
|
||||||
|
let context_clues = SonarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, expected_context_clues);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_sonarr_context_clue_provider_bare_popup_context_clues(
|
||||||
|
#[values(
|
||||||
|
ActiveSonarrBlock::TestAllIndexers,
|
||||||
|
ActiveSonarrBlock::AddSeriesSearchInput,
|
||||||
|
ActiveSonarrBlock::AddSeriesEmptySearchResults,
|
||||||
|
ActiveSonarrBlock::SystemLogs,
|
||||||
|
ActiveSonarrBlock::SystemUpdates
|
||||||
|
)]
|
||||||
|
active_sonarr_block: ActiveSonarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(active_sonarr_block.into());
|
||||||
|
|
||||||
|
let context_clues = SonarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, &BARE_POPUP_CONTEXT_CLUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_sonarr_context_clue_provider_confirmation_prompt_context_clues(
|
||||||
|
#[values(
|
||||||
|
ActiveSonarrBlock::AddSeriesPrompt,
|
||||||
|
ActiveSonarrBlock::AddSeriesSelectMonitor,
|
||||||
|
ActiveSonarrBlock::AddSeriesSelectSeriesType,
|
||||||
|
ActiveSonarrBlock::AddSeriesSelectQualityProfile,
|
||||||
|
ActiveSonarrBlock::AddSeriesSelectLanguageProfile,
|
||||||
|
ActiveSonarrBlock::AddSeriesSelectRootFolder,
|
||||||
|
ActiveSonarrBlock::AddSeriesTagsInput,
|
||||||
|
ActiveSonarrBlock::SystemTaskStartConfirmPrompt
|
||||||
|
)]
|
||||||
|
active_sonarr_block: ActiveSonarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(active_sonarr_block.into());
|
||||||
|
|
||||||
|
let context_clues = SonarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_sonarr_context_clue_provider_confirmation_prompt_popup_clues_edit_indexer_blocks(
|
||||||
|
#[values(
|
||||||
|
ActiveSonarrBlock::EditIndexerPrompt,
|
||||||
|
ActiveSonarrBlock::EditIndexerConfirmPrompt,
|
||||||
|
ActiveSonarrBlock::EditIndexerApiKeyInput,
|
||||||
|
ActiveSonarrBlock::EditIndexerNameInput,
|
||||||
|
ActiveSonarrBlock::EditIndexerSeedRatioInput,
|
||||||
|
ActiveSonarrBlock::EditIndexerToggleEnableRss,
|
||||||
|
ActiveSonarrBlock::EditIndexerToggleEnableAutomaticSearch,
|
||||||
|
ActiveSonarrBlock::EditIndexerToggleEnableInteractiveSearch,
|
||||||
|
ActiveSonarrBlock::EditIndexerPriorityInput,
|
||||||
|
ActiveSonarrBlock::EditIndexerUrlInput,
|
||||||
|
ActiveSonarrBlock::EditIndexerTagsInput
|
||||||
|
)]
|
||||||
|
active_sonarr_block: ActiveSonarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(active_sonarr_block.into());
|
||||||
|
|
||||||
|
let context_clues = SonarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_sonarr_context_clue_provider_confirmation_prompt_popup_clues_indexer_settings_blocks(
|
||||||
|
#[values(
|
||||||
|
ActiveSonarrBlock::AllIndexerSettingsPrompt,
|
||||||
|
ActiveSonarrBlock::IndexerSettingsConfirmPrompt,
|
||||||
|
ActiveSonarrBlock::IndexerSettingsMaximumSizeInput,
|
||||||
|
ActiveSonarrBlock::IndexerSettingsMinimumAgeInput,
|
||||||
|
ActiveSonarrBlock::IndexerSettingsRetentionInput,
|
||||||
|
ActiveSonarrBlock::IndexerSettingsRssSyncIntervalInput
|
||||||
|
)]
|
||||||
|
active_sonarr_block: ActiveSonarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(active_sonarr_block.into());
|
||||||
|
|
||||||
|
let context_clues = SonarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_sonarr_context_clue_provider_confirmation_prompt_popup_clues_edit_series_blocks(
|
||||||
|
#[values(
|
||||||
|
ActiveSonarrBlock::EditSeriesPrompt,
|
||||||
|
ActiveSonarrBlock::EditSeriesConfirmPrompt,
|
||||||
|
ActiveSonarrBlock::EditSeriesPathInput,
|
||||||
|
ActiveSonarrBlock::EditSeriesSelectSeriesType,
|
||||||
|
ActiveSonarrBlock::EditSeriesSelectQualityProfile,
|
||||||
|
ActiveSonarrBlock::EditSeriesSelectLanguageProfile,
|
||||||
|
ActiveSonarrBlock::EditSeriesTagsInput,
|
||||||
|
ActiveSonarrBlock::EditSeriesToggleMonitored,
|
||||||
|
ActiveSonarrBlock::EditSeriesToggleSeasonFolder
|
||||||
|
)]
|
||||||
|
active_sonarr_block: ActiveSonarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(active_sonarr_block.into());
|
||||||
|
|
||||||
|
let context_clues = SonarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_sonarr_context_clue_provider_add_series_search_results_clues(
|
||||||
|
#[values(
|
||||||
|
ActiveSonarrBlock::AddSeriesAlreadyInLibrary,
|
||||||
|
ActiveSonarrBlock::AddSeriesSearchResults
|
||||||
|
)]
|
||||||
|
active_sonarr_block: ActiveSonarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(active_sonarr_block.into());
|
||||||
|
|
||||||
|
let context_clues = SonarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, &ADD_SERIES_SEARCH_RESULTS_CONTEXT_CLUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sonarr_context_clue_provider_system_tasks_clues() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
|
||||||
|
app.push_navigation_stack(ActiveSonarrBlock::SystemTasks.into());
|
||||||
|
let context_clues = SonarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, &SYSTEM_TASKS_CONTEXT_CLUES);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(0, ActiveSonarrBlock::Series, &SERIES_CONTEXT_CLUES)]
|
||||||
|
#[case(1, ActiveSonarrBlock::Downloads, &DOWNLOADS_CONTEXT_CLUES)]
|
||||||
|
#[case(2, ActiveSonarrBlock::Blocklist, &BLOCKLIST_CONTEXT_CLUES)]
|
||||||
|
#[case(3, ActiveSonarrBlock::History, &HISTORY_CONTEXT_CLUES)]
|
||||||
|
#[case(4, ActiveSonarrBlock::RootFolders, &ROOT_FOLDERS_CONTEXT_CLUES)]
|
||||||
|
#[case(5, ActiveSonarrBlock::Indexers, &INDEXERS_CONTEXT_CLUES)]
|
||||||
|
#[case(6, ActiveSonarrBlock::System, &SYSTEM_CONTEXT_CLUES)]
|
||||||
|
fn test_sonarr_context_clue_provider_sonarr_tabs(
|
||||||
|
#[case] index: usize,
|
||||||
|
#[case] active_sonarr_block: ActiveSonarrBlock,
|
||||||
|
#[case] expected_context_clues: &[ContextClue],
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.sonarr_data = SonarrData::default();
|
||||||
|
app.data.sonarr_data.main_tabs.set_index(index);
|
||||||
|
app.push_navigation_stack(active_sonarr_block.into());
|
||||||
|
|
||||||
|
let context_clues = SonarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, expected_context_clues);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ mod tests {
|
|||||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
use crate::models::servarr_data::sonarr::sonarr_data::sonarr_test_utils::utils::create_test_sonarr_data;
|
||||||
|
use crate::models::servarr_models::Indexer;
|
||||||
|
use crate::models::sonarr_models::Episode;
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
models::{
|
models::{
|
||||||
@@ -13,7 +16,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
sonarr_models::{Season, Series, SonarrRelease},
|
sonarr_models::{Season, Series, SonarrRelease},
|
||||||
},
|
},
|
||||||
network::{sonarr_network::SonarrEvent, NetworkEvent},
|
network::{NetworkEvent, sonarr_network::SonarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -40,6 +43,10 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_dispatch_by_series_history_block() {
|
async fn test_dispatch_by_series_history_block() {
|
||||||
let (mut app, mut sync_network_rx) = construct_app_unit();
|
let (mut app, mut sync_network_rx) = construct_app_unit();
|
||||||
|
app.data.sonarr_data.series.set_items(vec![Series {
|
||||||
|
id: 1,
|
||||||
|
..Series::default()
|
||||||
|
}]);
|
||||||
|
|
||||||
app
|
app
|
||||||
.dispatch_by_sonarr_block(&ActiveSonarrBlock::SeriesHistory)
|
.dispatch_by_sonarr_block(&ActiveSonarrBlock::SeriesHistory)
|
||||||
@@ -48,7 +55,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::GetSeriesHistory(None).into()
|
SonarrEvent::GetSeriesHistory(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);
|
||||||
@@ -80,6 +87,10 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_dispatch_by_season_details_block() {
|
async fn test_dispatch_by_season_details_block() {
|
||||||
let (mut app, mut sync_network_rx) = construct_app_unit();
|
let (mut app, mut sync_network_rx) = construct_app_unit();
|
||||||
|
app.data.sonarr_data.series.set_items(vec![Series {
|
||||||
|
id: 1,
|
||||||
|
..Series::default()
|
||||||
|
}]);
|
||||||
|
|
||||||
app
|
app
|
||||||
.dispatch_by_sonarr_block(&ActiveSonarrBlock::SeasonDetails)
|
.dispatch_by_sonarr_block(&ActiveSonarrBlock::SeasonDetails)
|
||||||
@@ -88,15 +99,15 @@ 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::GetEpisodes(None).into()
|
SonarrEvent::GetEpisodes(1).into()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sync_network_rx.recv().await.unwrap(),
|
sync_network_rx.recv().await.unwrap(),
|
||||||
SonarrEvent::GetEpisodeFiles(None).into()
|
SonarrEvent::GetEpisodeFiles(1).into()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sync_network_rx.recv().await.unwrap(),
|
sync_network_rx.recv().await.unwrap(),
|
||||||
SonarrEvent::GetDownloads.into()
|
SonarrEvent::GetDownloads(500).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);
|
||||||
@@ -105,6 +116,14 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_dispatch_by_season_history_block() {
|
async fn test_dispatch_by_season_history_block() {
|
||||||
let (mut app, mut sync_network_rx) = construct_app_unit();
|
let (mut app, mut sync_network_rx) = construct_app_unit();
|
||||||
|
app.data.sonarr_data.series.set_items(vec![Series {
|
||||||
|
id: 1,
|
||||||
|
..Series::default()
|
||||||
|
}]);
|
||||||
|
app.data.sonarr_data.seasons.set_items(vec![Season {
|
||||||
|
season_number: 1,
|
||||||
|
..Season::default()
|
||||||
|
}]);
|
||||||
|
|
||||||
app
|
app
|
||||||
.dispatch_by_sonarr_block(&ActiveSonarrBlock::SeasonHistory)
|
.dispatch_by_sonarr_block(&ActiveSonarrBlock::SeasonHistory)
|
||||||
@@ -113,16 +132,41 @@ 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(None).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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_season_history_block_no_op_when_seasons_table_is_empty() {
|
||||||
|
let (mut app, _) = construct_app_unit();
|
||||||
|
app.data.sonarr_data.series.set_items(vec![Series {
|
||||||
|
id: 1,
|
||||||
|
..Series::default()
|
||||||
|
}]);
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_sonarr_block(&ActiveSonarrBlock::SeasonHistory)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(!app.is_loading);
|
||||||
|
assert!(!app.data.sonarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_dispatch_by_manual_season_search_block() {
|
async fn test_dispatch_by_manual_season_search_block() {
|
||||||
let (mut app, mut sync_network_rx) = construct_app_unit();
|
let (mut app, mut sync_network_rx) = construct_app_unit();
|
||||||
app.data.sonarr_data.season_details_modal = Some(SeasonDetailsModal::default());
|
app.data.sonarr_data.season_details_modal = Some(SeasonDetailsModal::default());
|
||||||
|
app.data.sonarr_data.series.set_items(vec![Series {
|
||||||
|
id: 1,
|
||||||
|
..Series::default()
|
||||||
|
}]);
|
||||||
|
app.data.sonarr_data.seasons.set_items(vec![Season {
|
||||||
|
season_number: 1,
|
||||||
|
..Season::default()
|
||||||
|
}]);
|
||||||
|
|
||||||
app
|
app
|
||||||
.dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualSeasonSearch)
|
.dispatch_by_sonarr_block(&ActiveSonarrBlock::ManualSeasonSearch)
|
||||||
@@ -131,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(None).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);
|
||||||
@@ -141,7 +185,7 @@ mod tests {
|
|||||||
async fn test_dispatch_by_manual_season_search_block_is_loading() {
|
async fn test_dispatch_by_manual_season_search_block_is_loading() {
|
||||||
let mut app = App {
|
let mut app = App {
|
||||||
is_loading: true,
|
is_loading: true,
|
||||||
..App::default()
|
..App::test_default()
|
||||||
};
|
};
|
||||||
|
|
||||||
app
|
app
|
||||||
@@ -155,7 +199,7 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_dispatch_by_manual_season_search_block_season_releases_non_empty() {
|
async fn test_dispatch_by_manual_season_search_block_season_releases_non_empty() {
|
||||||
let mut app = App::default();
|
let mut app = App::test_default();
|
||||||
let mut season_details_modal = SeasonDetailsModal::default();
|
let mut season_details_modal = SeasonDetailsModal::default();
|
||||||
season_details_modal
|
season_details_modal
|
||||||
.season_releases
|
.season_releases
|
||||||
@@ -174,6 +218,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_dispatch_by_episode_details_block() {
|
async fn test_dispatch_by_episode_details_block() {
|
||||||
let (mut app, mut sync_network_rx) = construct_app_unit();
|
let (mut app, mut sync_network_rx) = construct_app_unit();
|
||||||
|
app.data.sonarr_data = create_test_sonarr_data();
|
||||||
|
|
||||||
app
|
app
|
||||||
.dispatch_by_sonarr_block(&ActiveSonarrBlock::EpisodeDetails)
|
.dispatch_by_sonarr_block(&ActiveSonarrBlock::EpisodeDetails)
|
||||||
@@ -182,7 +227,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::GetEpisodeDetails(None).into()
|
SonarrEvent::GetEpisodeDetails(0).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);
|
||||||
@@ -191,6 +236,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_dispatch_by_episode_file_block() {
|
async fn test_dispatch_by_episode_file_block() {
|
||||||
let (mut app, mut sync_network_rx) = construct_app_unit();
|
let (mut app, mut sync_network_rx) = construct_app_unit();
|
||||||
|
app.data.sonarr_data = create_test_sonarr_data();
|
||||||
|
|
||||||
app
|
app
|
||||||
.dispatch_by_sonarr_block(&ActiveSonarrBlock::EpisodeFile)
|
.dispatch_by_sonarr_block(&ActiveSonarrBlock::EpisodeFile)
|
||||||
@@ -199,7 +245,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::GetEpisodeDetails(None).into()
|
SonarrEvent::GetEpisodeDetails(0).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);
|
||||||
@@ -208,6 +254,12 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_dispatch_by_episode_history_block() {
|
async fn test_dispatch_by_episode_history_block() {
|
||||||
let (mut app, mut sync_network_rx) = construct_app_unit();
|
let (mut app, mut sync_network_rx) = construct_app_unit();
|
||||||
|
let mut season_details_modal = SeasonDetailsModal::default();
|
||||||
|
season_details_modal.episodes.set_items(vec![Episode {
|
||||||
|
id: 1,
|
||||||
|
..Episode::default()
|
||||||
|
}]);
|
||||||
|
app.data.sonarr_data.season_details_modal = Some(season_details_modal);
|
||||||
|
|
||||||
app
|
app
|
||||||
.dispatch_by_sonarr_block(&ActiveSonarrBlock::EpisodeHistory)
|
.dispatch_by_sonarr_block(&ActiveSonarrBlock::EpisodeHistory)
|
||||||
@@ -216,7 +268,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::GetEpisodeHistory(None).into()
|
SonarrEvent::GetEpisodeHistory(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);
|
||||||
@@ -225,10 +277,14 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_dispatch_by_manual_episode_search_block() {
|
async fn test_dispatch_by_manual_episode_search_block() {
|
||||||
let (mut app, mut sync_network_rx) = construct_app_unit();
|
let (mut app, mut sync_network_rx) = construct_app_unit();
|
||||||
let season_details_modal = SeasonDetailsModal {
|
let mut season_details_modal = SeasonDetailsModal {
|
||||||
episode_details_modal: Some(EpisodeDetailsModal::default()),
|
episode_details_modal: Some(EpisodeDetailsModal::default()),
|
||||||
..SeasonDetailsModal::default()
|
..SeasonDetailsModal::default()
|
||||||
};
|
};
|
||||||
|
season_details_modal.episodes.set_items(vec![Episode {
|
||||||
|
id: 1,
|
||||||
|
..Episode::default()
|
||||||
|
}]);
|
||||||
app.data.sonarr_data.season_details_modal = Some(season_details_modal);
|
app.data.sonarr_data.season_details_modal = Some(season_details_modal);
|
||||||
|
|
||||||
app
|
app
|
||||||
@@ -238,7 +294,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::GetEpisodeReleases(None).into()
|
SonarrEvent::GetEpisodeReleases(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);
|
||||||
@@ -248,7 +304,7 @@ mod tests {
|
|||||||
async fn test_dispatch_by_manual_episode_search_block_is_loading() {
|
async fn test_dispatch_by_manual_episode_search_block_is_loading() {
|
||||||
let mut app = App {
|
let mut app = App {
|
||||||
is_loading: true,
|
is_loading: true,
|
||||||
..App::default()
|
..App::test_default()
|
||||||
};
|
};
|
||||||
|
|
||||||
app
|
app
|
||||||
@@ -262,7 +318,7 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_dispatch_by_manual_episode_search_block_episode_releases_non_empty() {
|
async fn test_dispatch_by_manual_episode_search_block_episode_releases_non_empty() {
|
||||||
let mut app = App::default();
|
let mut app = App::test_default();
|
||||||
let mut episode_details_modal = EpisodeDetailsModal::default();
|
let mut episode_details_modal = EpisodeDetailsModal::default();
|
||||||
episode_details_modal
|
episode_details_modal
|
||||||
.episode_releases
|
.episode_releases
|
||||||
@@ -293,7 +349,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::GetHistory(None).into()
|
SonarrEvent::GetHistory(500).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);
|
||||||
@@ -310,7 +366,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::GetDownloads.into()
|
SonarrEvent::GetDownloads(500).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);
|
||||||
@@ -403,6 +459,10 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_dispatch_by_test_indexer_block() {
|
async fn test_dispatch_by_test_indexer_block() {
|
||||||
let (mut app, mut sync_network_rx) = construct_app_unit();
|
let (mut app, mut sync_network_rx) = construct_app_unit();
|
||||||
|
app.data.sonarr_data.indexers.set_items(vec![Indexer {
|
||||||
|
id: 1,
|
||||||
|
..Indexer::default()
|
||||||
|
}]);
|
||||||
|
|
||||||
app
|
app
|
||||||
.dispatch_by_sonarr_block(&ActiveSonarrBlock::TestIndexer)
|
.dispatch_by_sonarr_block(&ActiveSonarrBlock::TestIndexer)
|
||||||
@@ -411,7 +471,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::TestIndexer(None).into()
|
SonarrEvent::TestIndexer(1).into()
|
||||||
);
|
);
|
||||||
assert_eq!(app.tick_count, 0);
|
assert_eq!(app.tick_count, 0);
|
||||||
}
|
}
|
||||||
@@ -451,7 +511,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sync_network_rx.recv().await.unwrap(),
|
sync_network_rx.recv().await.unwrap(),
|
||||||
SonarrEvent::GetLogs(None).into()
|
SonarrEvent::GetLogs(500).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);
|
||||||
@@ -477,6 +537,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_dispatch_by_add_series_search_results_block() {
|
async fn test_dispatch_by_add_series_search_results_block() {
|
||||||
let (mut app, mut sync_network_rx) = construct_app_unit();
|
let (mut app, mut sync_network_rx) = construct_app_unit();
|
||||||
|
app.data.sonarr_data.add_series_search = Some("test search".into());
|
||||||
|
|
||||||
app
|
app
|
||||||
.dispatch_by_sonarr_block(&ActiveSonarrBlock::AddSeriesSearchResults)
|
.dispatch_by_sonarr_block(&ActiveSonarrBlock::AddSeriesSearchResults)
|
||||||
@@ -485,7 +546,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::SearchNewSeries(None).into()
|
SonarrEvent::SearchNewSeries("test search".into()).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);
|
||||||
@@ -493,7 +554,7 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_check_for_sonarr_prompt_action_no_prompt_confirm() {
|
async fn test_check_for_sonarr_prompt_action_no_prompt_confirm() {
|
||||||
let mut app = App::default();
|
let mut app = App::test_default();
|
||||||
app.data.sonarr_data.prompt_confirm = false;
|
app.data.sonarr_data.prompt_confirm = false;
|
||||||
|
|
||||||
app.check_for_sonarr_prompt_action().await;
|
app.check_for_sonarr_prompt_action().await;
|
||||||
@@ -543,7 +604,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sync_network_rx.recv().await.unwrap(),
|
sync_network_rx.recv().await.unwrap(),
|
||||||
SonarrEvent::GetDownloads.into()
|
SonarrEvent::GetDownloads(500).into()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sync_network_rx.recv().await.unwrap(),
|
sync_network_rx.recv().await.unwrap(),
|
||||||
@@ -581,7 +642,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sync_network_rx.recv().await.unwrap(),
|
sync_network_rx.recv().await.unwrap(),
|
||||||
SonarrEvent::GetDownloads.into()
|
SonarrEvent::GetDownloads(500).into()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sync_network_rx.recv().await.unwrap(),
|
sync_network_rx.recv().await.unwrap(),
|
||||||
@@ -606,7 +667,7 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sync_network_rx.recv().await.unwrap(),
|
sync_network_rx.recv().await.unwrap(),
|
||||||
SonarrEvent::GetDownloads.into()
|
SonarrEvent::GetDownloads(500).into()
|
||||||
);
|
);
|
||||||
assert!(!app.data.sonarr_data.prompt_confirm);
|
assert!(!app.data.sonarr_data.prompt_confirm);
|
||||||
}
|
}
|
||||||
@@ -631,7 +692,7 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sync_network_rx.recv().await.unwrap(),
|
sync_network_rx.recv().await.unwrap(),
|
||||||
SonarrEvent::GetDownloads.into()
|
SonarrEvent::GetDownloads(500).into()
|
||||||
);
|
);
|
||||||
assert!(app.should_refresh);
|
assert!(app.should_refresh);
|
||||||
assert!(!app.data.sonarr_data.prompt_confirm);
|
assert!(!app.data.sonarr_data.prompt_confirm);
|
||||||
@@ -648,7 +709,7 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sync_network_rx.recv().await.unwrap(),
|
sync_network_rx.recv().await.unwrap(),
|
||||||
SonarrEvent::GetDownloads.into()
|
SonarrEvent::GetDownloads(500).into()
|
||||||
);
|
);
|
||||||
assert!(app.is_loading);
|
assert!(app.is_loading);
|
||||||
assert!(app.should_refresh);
|
assert!(app.should_refresh);
|
||||||
@@ -682,14 +743,14 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sync_network_rx.recv().await.unwrap(),
|
sync_network_rx.recv().await.unwrap(),
|
||||||
SonarrEvent::GetDownloads.into()
|
SonarrEvent::GetDownloads(500).into()
|
||||||
);
|
);
|
||||||
assert!(app.is_loading);
|
assert!(app.is_loading);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_populate_seasons_table_unfiltered() {
|
async fn test_populate_seasons_table_unfiltered() {
|
||||||
let mut app = App::default();
|
let mut app = App::test_default();
|
||||||
app.data.sonarr_data.series.set_items(vec![Series {
|
app.data.sonarr_data.series.set_items(vec![Series {
|
||||||
seasons: Some(vec![Season::default()]),
|
seasons: Some(vec![Season::default()]),
|
||||||
..Series::default()
|
..Series::default()
|
||||||
@@ -709,7 +770,7 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_populate_seasons_table_filtered() {
|
async fn test_populate_seasons_table_filtered() {
|
||||||
let mut app = App::default();
|
let mut app = App::test_default();
|
||||||
app.data.sonarr_data.series.set_filtered_items(vec![Series {
|
app.data.sonarr_data.series.set_filtered_items(vec![Series {
|
||||||
seasons: Some(vec![Season::default()]),
|
seasons: Some(vec![Season::default()]),
|
||||||
..Series::default()
|
..Series::default()
|
||||||
@@ -727,13 +788,90 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_extract_episode_id() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
let mut season_details_modal = SeasonDetailsModal::default();
|
||||||
|
season_details_modal.episodes.set_items(vec![Episode {
|
||||||
|
id: 1,
|
||||||
|
..Episode::default()
|
||||||
|
}]);
|
||||||
|
app.data.sonarr_data.season_details_modal = Some(season_details_modal);
|
||||||
|
|
||||||
|
assert_eq!(app.extract_episode_id().await, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[should_panic(expected = "Season details have not been loaded")]
|
||||||
|
async fn test_extract_episode_id_requires_season_details_modal_to_be_some() {
|
||||||
|
let app = App::test_default();
|
||||||
|
|
||||||
|
assert_eq!(app.extract_episode_id().await, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_extract_series_id() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.sonarr_data.series.set_items(vec![Series {
|
||||||
|
id: 1,
|
||||||
|
..Series::default()
|
||||||
|
}]);
|
||||||
|
|
||||||
|
assert_eq!(app.extract_series_id().await, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_extract_series_id_season_number_tuple() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.sonarr_data.series.set_items(vec![Series {
|
||||||
|
id: 1,
|
||||||
|
..Series::default()
|
||||||
|
}]);
|
||||||
|
app.data.sonarr_data.seasons.set_items(vec![Season {
|
||||||
|
season_number: 1,
|
||||||
|
..Season::default()
|
||||||
|
}]);
|
||||||
|
|
||||||
|
assert_eq!(app.extract_series_id_season_number_tuple().await, (1, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_extract_add_new_series_search_query() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.sonarr_data.add_series_search = Some("test search".into());
|
||||||
|
|
||||||
|
assert_str_eq!(
|
||||||
|
app.extract_add_new_series_search_query().await,
|
||||||
|
"test search"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[should_panic(expected = "Add series search is empty")]
|
||||||
|
async fn test_extract_add_new_series_search_query_panics_when_the_query_is_not_set() {
|
||||||
|
let app = App::test_default();
|
||||||
|
|
||||||
|
app.extract_add_new_series_search_query().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_extract_sonarr_indexer_id() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.sonarr_data.indexers.set_items(vec![Indexer {
|
||||||
|
id: 1,
|
||||||
|
..Indexer::default()
|
||||||
|
}]);
|
||||||
|
|
||||||
|
assert_eq!(app.extract_sonarr_indexer_id().await, 1);
|
||||||
|
}
|
||||||
|
|
||||||
fn construct_app_unit<'a>() -> (App<'a>, mpsc::Receiver<NetworkEvent>) {
|
fn construct_app_unit<'a>() -> (App<'a>, mpsc::Receiver<NetworkEvent>) {
|
||||||
let (sync_network_tx, sync_network_rx) = mpsc::channel::<NetworkEvent>(500);
|
let (sync_network_tx, sync_network_rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
let mut app = App {
|
let mut app = App {
|
||||||
network_tx: Some(sync_network_tx),
|
network_tx: Some(sync_network_tx),
|
||||||
tick_count: 1,
|
tick_count: 1,
|
||||||
is_first_render: false,
|
is_first_render: false,
|
||||||
..App::default()
|
..App::test_default()
|
||||||
};
|
};
|
||||||
app.data.sonarr_data.prompt_confirm = true;
|
app.data.sonarr_data.prompt_confirm = true;
|
||||||
|
|
||||||
|
|||||||
@@ -2,16 +2,24 @@
|
|||||||
mod tests {
|
mod tests {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use clap::{error::ErrorKind, CommandFactory};
|
use clap::{CommandFactory, error::ErrorKind};
|
||||||
use mockall::predicate::eq;
|
use mockall::predicate::eq;
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::cli::lidarr::LidarrCommand;
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
Cli,
|
||||||
app::App,
|
app::App,
|
||||||
cli::{handle_command, mutex_flags_or_option, radarr::RadarrCommand, sonarr::SonarrCommand},
|
cli::{handle_command, mutex_flags_or_option, radarr::RadarrCommand, sonarr::SonarrCommand},
|
||||||
models::{
|
models::{
|
||||||
|
Serdeable,
|
||||||
|
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,
|
||||||
@@ -20,12 +28,10 @@ mod tests {
|
|||||||
BlocklistItem as SonarrBlocklistItem, BlocklistResponse as SonarrBlocklistResponse,
|
BlocklistItem as SonarrBlocklistItem, BlocklistResponse as SonarrBlocklistResponse,
|
||||||
SonarrSerdeable,
|
SonarrSerdeable,
|
||||||
},
|
},
|
||||||
Serdeable,
|
|
||||||
},
|
},
|
||||||
network::{
|
network::{
|
||||||
radarr_network::RadarrEvent, sonarr_network::SonarrEvent, MockNetworkTrait, NetworkEvent,
|
MockNetworkTrait, NetworkEvent, radarr_network::RadarrEvent, sonarr_network::SonarrEvent,
|
||||||
},
|
},
|
||||||
Cli,
|
|
||||||
};
|
};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
@@ -33,7 +39,7 @@ mod tests {
|
|||||||
fn test_servarr_subcommand_requires_subcommand(#[values("radarr", "sonarr")] subcommand: &str) {
|
fn test_servarr_subcommand_requires_subcommand(#[values("radarr", "sonarr")] subcommand: &str) {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", subcommand]);
|
let result = Cli::command().try_get_matches_from(["managarr", subcommand]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
|
ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
|
||||||
@@ -45,21 +51,28 @@ mod tests {
|
|||||||
let result =
|
let result =
|
||||||
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "all-indexer-settings"]);
|
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "all-indexer-settings"]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sonarr_subcommand_delegates_to_sonarr() {
|
fn test_sonarr_subcommand_delegates_to_sonarr() {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "series"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "list", "series"]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_subcommand_delegates_to_lidarr() {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "list", "artists"]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_completions_requires_argument() {
|
fn test_completions_requires_argument() {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "completions"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "completions"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
|
ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
|
||||||
@@ -70,7 +83,7 @@ mod tests {
|
|||||||
fn test_completions_invalid_argument() {
|
fn test_completions_invalid_argument() {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "completions", "test"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "completions", "test"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +91,7 @@ mod tests {
|
|||||||
fn test_completions_satisfied_with_argument() {
|
fn test_completions_satisfied_with_argument() {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "completions", "bash"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "completions", "bash"]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
@@ -136,12 +149,12 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let clear_blocklist_command = RadarrCommand::ClearBlocklist.into();
|
let clear_blocklist_command = RadarrCommand::ClearBlocklist.into();
|
||||||
|
|
||||||
let result = handle_command(&app_arc, clear_blocklist_command, &mut mock_network).await;
|
let result = handle_command(&app_arc, clear_blocklist_command, &mut mock_network).await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -167,11 +180,42 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let clear_blocklist_command = SonarrCommand::ClearBlocklist.into();
|
let clear_blocklist_command = SonarrCommand::ClearBlocklist.into();
|
||||||
|
|
||||||
let result = handle_command(&app_arc, clear_blocklist_command, &mut mock_network).await;
|
let result = handle_command(&app_arc, clear_blocklist_command, &mut mock_network).await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,223 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::{ArgAction, Subcommand};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use super::LidarrCommand;
|
||||||
|
use crate::{
|
||||||
|
app::App,
|
||||||
|
cli::{CliCommandHandler, Command},
|
||||||
|
models::lidarr_models::{
|
||||||
|
AddArtistBody, AddArtistOptions, AddLidarrRootFolderBody, MonitorType, NewItemMonitorType,
|
||||||
|
},
|
||||||
|
network::{NetworkTrait, lidarr_network::LidarrEvent},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "add_command_handler_tests.rs"]
|
||||||
|
mod add_command_handler_tests;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
||||||
|
pub enum LidarrAddCommand {
|
||||||
|
#[command(about = "Add a new artist to your Lidarr library")]
|
||||||
|
Artist {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The MusicBrainz foreign artist ID of the artist you wish to add to your library",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
foreign_artist_id: String,
|
||||||
|
#[arg(long, help = "The name of the artist", required = true)]
|
||||||
|
artist_name: String,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The root folder path where all artist data and metadata should live",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
root_folder_path: String,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The ID of the quality profile to use for this artist",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
quality_profile_id: i64,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The ID of the metadata profile to use for this artist",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
metadata_profile_id: i64,
|
||||||
|
#[arg(long, help = "Disable monitoring for this artist")]
|
||||||
|
disable_monitoring: bool,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "Tag IDs to tag the artist with",
|
||||||
|
value_parser,
|
||||||
|
action = ArgAction::Append
|
||||||
|
)]
|
||||||
|
tag: Vec<i64>,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "What Lidarr should monitor for this artist",
|
||||||
|
value_enum,
|
||||||
|
default_value_t = MonitorType::default()
|
||||||
|
)]
|
||||||
|
monitor: MonitorType,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "How Lidarr should monitor new items for this artist",
|
||||||
|
value_enum,
|
||||||
|
default_value_t = NewItemMonitorType::default()
|
||||||
|
)]
|
||||||
|
monitor_new_items: NewItemMonitorType,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "Tell Lidarr to not start a search for missing albums once the artist is added to your library"
|
||||||
|
)]
|
||||||
|
no_search_for_missing_albums: bool,
|
||||||
|
},
|
||||||
|
#[command(about = "Add a new root folder")]
|
||||||
|
RootFolder {
|
||||||
|
#[arg(long, help = "The name of the root folder", required = true)]
|
||||||
|
name: String,
|
||||||
|
#[arg(long, help = "The path of the new root folder", required = true)]
|
||||||
|
root_folder_path: String,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The ID of the default quality profile for artists in this root folder",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
quality_profile_id: i64,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The ID of the default metadata profile for artists in this root folder",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
metadata_profile_id: i64,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The default monitor option for artists in this root folder",
|
||||||
|
value_enum,
|
||||||
|
default_value_t = MonitorType::default()
|
||||||
|
)]
|
||||||
|
monitor: MonitorType,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The default monitor new items option for artists in this root folder",
|
||||||
|
value_enum,
|
||||||
|
default_value_t = NewItemMonitorType::default()
|
||||||
|
)]
|
||||||
|
monitor_new_items: NewItemMonitorType,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "Default tag IDs for artists in this root folder",
|
||||||
|
value_parser,
|
||||||
|
action = ArgAction::Append
|
||||||
|
)]
|
||||||
|
tag: Vec<i64>,
|
||||||
|
},
|
||||||
|
#[command(about = "Add new tag")]
|
||||||
|
Tag {
|
||||||
|
#[arg(long, help = "The name of the tag to be added", required = true)]
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LidarrAddCommand> for Command {
|
||||||
|
fn from(value: LidarrAddCommand) -> Self {
|
||||||
|
Command::Lidarr(LidarrCommand::Add(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct LidarrAddCommandHandler<'a, 'b> {
|
||||||
|
_app: &'a Arc<Mutex<App<'b>>>,
|
||||||
|
command: LidarrAddCommand,
|
||||||
|
network: &'a mut dyn NetworkTrait,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrAddCommand> for LidarrAddCommandHandler<'a, 'b> {
|
||||||
|
fn with(
|
||||||
|
app: &'a Arc<Mutex<App<'b>>>,
|
||||||
|
command: LidarrAddCommand,
|
||||||
|
network: &'a mut dyn NetworkTrait,
|
||||||
|
) -> Self {
|
||||||
|
LidarrAddCommandHandler {
|
||||||
|
_app: app,
|
||||||
|
command,
|
||||||
|
network,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle(self) -> Result<String> {
|
||||||
|
let result = match self.command {
|
||||||
|
LidarrAddCommand::Artist {
|
||||||
|
foreign_artist_id,
|
||||||
|
artist_name,
|
||||||
|
root_folder_path,
|
||||||
|
quality_profile_id,
|
||||||
|
metadata_profile_id,
|
||||||
|
disable_monitoring,
|
||||||
|
tag: tags,
|
||||||
|
monitor,
|
||||||
|
monitor_new_items,
|
||||||
|
no_search_for_missing_albums,
|
||||||
|
} => {
|
||||||
|
let body = AddArtistBody {
|
||||||
|
foreign_artist_id,
|
||||||
|
artist_name,
|
||||||
|
monitored: !disable_monitoring,
|
||||||
|
root_folder_path,
|
||||||
|
quality_profile_id,
|
||||||
|
metadata_profile_id,
|
||||||
|
tags,
|
||||||
|
tag_input_string: None,
|
||||||
|
add_options: AddArtistOptions {
|
||||||
|
monitor,
|
||||||
|
monitor_new_items,
|
||||||
|
search_for_missing_albums: !no_search_for_missing_albums,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::AddArtist(body).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrAddCommand::RootFolder {
|
||||||
|
name,
|
||||||
|
root_folder_path,
|
||||||
|
quality_profile_id,
|
||||||
|
metadata_profile_id,
|
||||||
|
monitor,
|
||||||
|
monitor_new_items,
|
||||||
|
tag: tags,
|
||||||
|
} => {
|
||||||
|
let add_root_folder_body = AddLidarrRootFolderBody {
|
||||||
|
name,
|
||||||
|
path: root_folder_path,
|
||||||
|
default_quality_profile_id: quality_profile_id,
|
||||||
|
default_metadata_profile_id: metadata_profile_id,
|
||||||
|
default_monitor_option: monitor,
|
||||||
|
default_new_item_monitor_option: monitor_new_items,
|
||||||
|
default_tags: tags,
|
||||||
|
tag_input_string: None,
|
||||||
|
};
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::AddRootFolder(add_root_folder_body).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrAddCommand::Tag { name } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::AddTag(name).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,561 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use clap::{CommandFactory, Parser, error::ErrorKind};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
Cli,
|
||||||
|
cli::{
|
||||||
|
Command,
|
||||||
|
lidarr::{LidarrCommand, add_command_handler::LidarrAddCommand},
|
||||||
|
},
|
||||||
|
models::lidarr_models::{MonitorType, NewItemMonitorType},
|
||||||
|
};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_add_command_from() {
|
||||||
|
let command = LidarrAddCommand::Tag {
|
||||||
|
name: String::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = Command::from(command.clone());
|
||||||
|
|
||||||
|
assert_eq!(result, Command::Lidarr(LidarrCommand::Add(command)));
|
||||||
|
}
|
||||||
|
|
||||||
|
mod cli {
|
||||||
|
use super::*;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_root_folder_requires_arguments() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "add", "root-folder"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_root_folder_success() {
|
||||||
|
let expected_args = LidarrAddCommand::RootFolder {
|
||||||
|
name: "Music".to_owned(),
|
||||||
|
root_folder_path: "/nfs/test".to_owned(),
|
||||||
|
quality_profile_id: 1,
|
||||||
|
metadata_profile_id: 1,
|
||||||
|
monitor: MonitorType::All,
|
||||||
|
monitor_new_items: NewItemMonitorType::All,
|
||||||
|
tag: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"add",
|
||||||
|
"root-folder",
|
||||||
|
"--name",
|
||||||
|
"Music",
|
||||||
|
"--root-folder-path",
|
||||||
|
"/nfs/test",
|
||||||
|
"--quality-profile-id",
|
||||||
|
"1",
|
||||||
|
"--metadata-profile-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(add_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_tag_requires_arguments() {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "add", "tag"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_tag_success() {
|
||||||
|
let expected_args = LidarrAddCommand::Tag {
|
||||||
|
name: "test".to_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = Cli::try_parse_from(["managarr", "lidarr", "add", "tag", "--name", "test"]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||||
|
panic!("Unexpected command type")
|
||||||
|
};
|
||||||
|
assert_eq!(add_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_artist_requires_arguments() {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "add", "artist"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_artist_requires_foreign_artist_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"add",
|
||||||
|
"artist",
|
||||||
|
"--artist-name",
|
||||||
|
"Test",
|
||||||
|
"--root-folder-path",
|
||||||
|
"/music",
|
||||||
|
"--quality-profile-id",
|
||||||
|
"1",
|
||||||
|
"--metadata-profile-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_artist_requires_artist_name() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"add",
|
||||||
|
"artist",
|
||||||
|
"--foreign-artist-id",
|
||||||
|
"test-id",
|
||||||
|
"--root-folder-path",
|
||||||
|
"/music",
|
||||||
|
"--quality-profile-id",
|
||||||
|
"1",
|
||||||
|
"--metadata-profile-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_artist_requires_root_folder_path() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"add",
|
||||||
|
"artist",
|
||||||
|
"--foreign-artist-id",
|
||||||
|
"test-id",
|
||||||
|
"--artist-name",
|
||||||
|
"Test",
|
||||||
|
"--quality-profile-id",
|
||||||
|
"1",
|
||||||
|
"--metadata-profile-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_artist_requires_quality_profile_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"add",
|
||||||
|
"artist",
|
||||||
|
"--foreign-artist-id",
|
||||||
|
"test-id",
|
||||||
|
"--artist-name",
|
||||||
|
"Test",
|
||||||
|
"--root-folder-path",
|
||||||
|
"/music",
|
||||||
|
"--metadata-profile-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_artist_requires_metadata_profile_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"add",
|
||||||
|
"artist",
|
||||||
|
"--foreign-artist-id",
|
||||||
|
"test-id",
|
||||||
|
"--artist-name",
|
||||||
|
"Test",
|
||||||
|
"--root-folder-path",
|
||||||
|
"/music",
|
||||||
|
"--quality-profile-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_artist_success_with_required_args_only() {
|
||||||
|
let expected_args = LidarrAddCommand::Artist {
|
||||||
|
foreign_artist_id: "test-id".to_owned(),
|
||||||
|
artist_name: "Test Artist".to_owned(),
|
||||||
|
root_folder_path: "/music".to_owned(),
|
||||||
|
quality_profile_id: 1,
|
||||||
|
metadata_profile_id: 1,
|
||||||
|
disable_monitoring: false,
|
||||||
|
tag: vec![],
|
||||||
|
monitor: MonitorType::default(),
|
||||||
|
monitor_new_items: NewItemMonitorType::default(),
|
||||||
|
no_search_for_missing_albums: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"add",
|
||||||
|
"artist",
|
||||||
|
"--foreign-artist-id",
|
||||||
|
"test-id",
|
||||||
|
"--artist-name",
|
||||||
|
"Test Artist",
|
||||||
|
"--root-folder-path",
|
||||||
|
"/music",
|
||||||
|
"--quality-profile-id",
|
||||||
|
"1",
|
||||||
|
"--metadata-profile-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||||
|
panic!("Unexpected command type")
|
||||||
|
};
|
||||||
|
assert_eq!(add_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_artist_success_with_all_args() {
|
||||||
|
let expected_args = LidarrAddCommand::Artist {
|
||||||
|
foreign_artist_id: "test-id".to_owned(),
|
||||||
|
artist_name: "Test Artist".to_owned(),
|
||||||
|
root_folder_path: "/music".to_owned(),
|
||||||
|
quality_profile_id: 1,
|
||||||
|
metadata_profile_id: 2,
|
||||||
|
disable_monitoring: true,
|
||||||
|
tag: vec![1, 2],
|
||||||
|
monitor: MonitorType::Future,
|
||||||
|
monitor_new_items: NewItemMonitorType::New,
|
||||||
|
no_search_for_missing_albums: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"add",
|
||||||
|
"artist",
|
||||||
|
"--foreign-artist-id",
|
||||||
|
"test-id",
|
||||||
|
"--artist-name",
|
||||||
|
"Test Artist",
|
||||||
|
"--root-folder-path",
|
||||||
|
"/music",
|
||||||
|
"--quality-profile-id",
|
||||||
|
"1",
|
||||||
|
"--metadata-profile-id",
|
||||||
|
"2",
|
||||||
|
"--disable-monitoring",
|
||||||
|
"--tag",
|
||||||
|
"1",
|
||||||
|
"--tag",
|
||||||
|
"2",
|
||||||
|
"--monitor",
|
||||||
|
"future",
|
||||||
|
"--monitor-new-items",
|
||||||
|
"new",
|
||||||
|
"--no-search-for-missing-albums",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||||
|
panic!("Unexpected command type")
|
||||||
|
};
|
||||||
|
assert_eq!(add_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_artist_monitor_type_validation() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"add",
|
||||||
|
"artist",
|
||||||
|
"--foreign-artist-id",
|
||||||
|
"test-id",
|
||||||
|
"--artist-name",
|
||||||
|
"Test Artist",
|
||||||
|
"--root-folder-path",
|
||||||
|
"/music",
|
||||||
|
"--quality-profile-id",
|
||||||
|
"1",
|
||||||
|
"--metadata-profile-id",
|
||||||
|
"2",
|
||||||
|
"--monitor",
|
||||||
|
"test",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_artist_new_item_monitor_type_validation() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"add",
|
||||||
|
"artist",
|
||||||
|
"--foreign-artist-id",
|
||||||
|
"test-id",
|
||||||
|
"--artist-name",
|
||||||
|
"Test Artist",
|
||||||
|
"--root-folder-path",
|
||||||
|
"/music",
|
||||||
|
"--quality-profile-id",
|
||||||
|
"1",
|
||||||
|
"--metadata-profile-id",
|
||||||
|
"2",
|
||||||
|
"--monitor-new-items",
|
||||||
|
"test",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_artist_tags_is_repeatable() {
|
||||||
|
let expected_args = LidarrAddCommand::Artist {
|
||||||
|
foreign_artist_id: "test-id".to_owned(),
|
||||||
|
artist_name: "Test Artist".to_owned(),
|
||||||
|
root_folder_path: "/music".to_owned(),
|
||||||
|
quality_profile_id: 1,
|
||||||
|
metadata_profile_id: 2,
|
||||||
|
disable_monitoring: false,
|
||||||
|
tag: vec![1, 2],
|
||||||
|
monitor: MonitorType::default(),
|
||||||
|
monitor_new_items: NewItemMonitorType::default(),
|
||||||
|
no_search_for_missing_albums: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"add",
|
||||||
|
"artist",
|
||||||
|
"--foreign-artist-id",
|
||||||
|
"test-id",
|
||||||
|
"--artist-name",
|
||||||
|
"Test Artist",
|
||||||
|
"--root-folder-path",
|
||||||
|
"/music",
|
||||||
|
"--quality-profile-id",
|
||||||
|
"1",
|
||||||
|
"--metadata-profile-id",
|
||||||
|
"2",
|
||||||
|
"--tag",
|
||||||
|
"1",
|
||||||
|
"--tag",
|
||||||
|
"2",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||||
|
panic!("Unexpected command type")
|
||||||
|
};
|
||||||
|
assert_eq!(add_command, expected_args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod handler {
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use mockall::predicate::eq;
|
||||||
|
use serde_json::json;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::cli::CliCommandHandler;
|
||||||
|
use crate::cli::lidarr::add_command_handler::{LidarrAddCommand, LidarrAddCommandHandler};
|
||||||
|
use crate::models::Serdeable;
|
||||||
|
use crate::models::lidarr_models::{
|
||||||
|
AddArtistBody, AddArtistOptions, AddLidarrRootFolderBody, LidarrSerdeable, MonitorType,
|
||||||
|
NewItemMonitorType,
|
||||||
|
};
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use crate::{
|
||||||
|
app::App,
|
||||||
|
network::{MockNetworkTrait, NetworkEvent},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_add_root_folder_command() {
|
||||||
|
let expected_root_folder_path = "/nfs/test".to_owned();
|
||||||
|
let expected_add_root_folder_body = AddLidarrRootFolderBody {
|
||||||
|
name: "Music".to_owned(),
|
||||||
|
path: expected_root_folder_path.clone(),
|
||||||
|
default_quality_profile_id: 1,
|
||||||
|
default_metadata_profile_id: 1,
|
||||||
|
default_monitor_option: MonitorType::All,
|
||||||
|
default_new_item_monitor_option: NewItemMonitorType::All,
|
||||||
|
default_tags: vec![1, 2],
|
||||||
|
tag_input_string: None,
|
||||||
|
};
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::AddRootFolder(expected_add_root_folder_body.clone()).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let add_root_folder_command = LidarrAddCommand::RootFolder {
|
||||||
|
name: "Music".to_owned(),
|
||||||
|
root_folder_path: expected_root_folder_path,
|
||||||
|
quality_profile_id: 1,
|
||||||
|
metadata_profile_id: 1,
|
||||||
|
monitor: MonitorType::All,
|
||||||
|
monitor_new_items: NewItemMonitorType::All,
|
||||||
|
tag: vec![1, 2],
|
||||||
|
};
|
||||||
|
|
||||||
|
let result =
|
||||||
|
LidarrAddCommandHandler::with(&app_arc, add_root_folder_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_add_tag_command() {
|
||||||
|
let expected_tag_name = "test".to_owned();
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::AddTag(expected_tag_name.clone()).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let add_tag_command = LidarrAddCommand::Tag {
|
||||||
|
name: expected_tag_name,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = LidarrAddCommandHandler::with(&app_arc, add_tag_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_add_artist_command() {
|
||||||
|
let expected_body = AddArtistBody {
|
||||||
|
foreign_artist_id: "test-id".to_owned(),
|
||||||
|
artist_name: "Test Artist".to_owned(),
|
||||||
|
monitored: false,
|
||||||
|
root_folder_path: "/music".to_owned(),
|
||||||
|
quality_profile_id: 1,
|
||||||
|
metadata_profile_id: 1,
|
||||||
|
tags: vec![1, 2],
|
||||||
|
tag_input_string: None,
|
||||||
|
add_options: AddArtistOptions {
|
||||||
|
monitor: MonitorType::All,
|
||||||
|
monitor_new_items: NewItemMonitorType::All,
|
||||||
|
search_for_missing_albums: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::AddArtist(expected_body).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let add_artist_command = LidarrAddCommand::Artist {
|
||||||
|
foreign_artist_id: "test-id".to_owned(),
|
||||||
|
artist_name: "Test Artist".to_owned(),
|
||||||
|
root_folder_path: "/music".to_owned(),
|
||||||
|
quality_profile_id: 1,
|
||||||
|
metadata_profile_id: 1,
|
||||||
|
disable_monitoring: true,
|
||||||
|
tag: vec![1, 2],
|
||||||
|
monitor: MonitorType::All,
|
||||||
|
monitor_new_items: NewItemMonitorType::All,
|
||||||
|
no_search_for_missing_albums: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = LidarrAddCommandHandler::with(&app_arc, add_artist_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::Subcommand;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
app::App,
|
||||||
|
cli::{CliCommandHandler, Command},
|
||||||
|
models::lidarr_models::DeleteParams,
|
||||||
|
network::{NetworkTrait, lidarr_network::LidarrEvent},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::LidarrCommand;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "delete_command_handler_tests.rs"]
|
||||||
|
mod delete_command_handler_tests;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
||||||
|
pub enum LidarrDeleteCommand {
|
||||||
|
#[command(about = "Delete an album from your Lidarr library")]
|
||||||
|
Album {
|
||||||
|
#[arg(long, help = "The ID of the album to delete", required = true)]
|
||||||
|
album_id: i64,
|
||||||
|
#[arg(long, help = "Delete the album files from disk as well")]
|
||||||
|
delete_files_from_disk: bool,
|
||||||
|
#[arg(long, help = "Add a list exclusion for this album")]
|
||||||
|
add_list_exclusion: bool,
|
||||||
|
},
|
||||||
|
#[command(about = "Delete 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")]
|
||||||
|
Artist {
|
||||||
|
#[arg(long, help = "The ID of the artist to delete", required = true)]
|
||||||
|
artist_id: i64,
|
||||||
|
#[arg(long, help = "Delete the artist files from disk as well")]
|
||||||
|
delete_files_from_disk: bool,
|
||||||
|
#[arg(long, help = "Add a list exclusion for this artist")]
|
||||||
|
add_list_exclusion: bool,
|
||||||
|
},
|
||||||
|
#[command(about = "Delete the specified download")]
|
||||||
|
Download {
|
||||||
|
#[arg(long, help = "The ID of the download to delete", required = true)]
|
||||||
|
download_id: i64,
|
||||||
|
},
|
||||||
|
#[command(about = "Delete the indexer with the given ID")]
|
||||||
|
Indexer {
|
||||||
|
#[arg(long, help = "The ID of the indexer to delete", required = true)]
|
||||||
|
indexer_id: i64,
|
||||||
|
},
|
||||||
|
#[command(about = "Delete the root folder with the given ID")]
|
||||||
|
RootFolder {
|
||||||
|
#[arg(long, help = "The ID of the root folder to delete", required = true)]
|
||||||
|
root_folder_id: i64,
|
||||||
|
},
|
||||||
|
#[command(about = "Delete the tag with the specified ID")]
|
||||||
|
Tag {
|
||||||
|
#[arg(long, help = "The ID of the tag to delete", required = true)]
|
||||||
|
tag_id: i64,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LidarrDeleteCommand> for Command {
|
||||||
|
fn from(value: LidarrDeleteCommand) -> Self {
|
||||||
|
Command::Lidarr(LidarrCommand::Delete(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct LidarrDeleteCommandHandler<'a, 'b> {
|
||||||
|
_app: &'a Arc<Mutex<App<'b>>>,
|
||||||
|
command: LidarrDeleteCommand,
|
||||||
|
network: &'a mut dyn NetworkTrait,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrDeleteCommand> for LidarrDeleteCommandHandler<'a, 'b> {
|
||||||
|
fn with(
|
||||||
|
_app: &'a Arc<Mutex<App<'b>>>,
|
||||||
|
command: LidarrDeleteCommand,
|
||||||
|
network: &'a mut dyn NetworkTrait,
|
||||||
|
) -> Self {
|
||||||
|
LidarrDeleteCommandHandler {
|
||||||
|
_app,
|
||||||
|
command,
|
||||||
|
network,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle(self) -> Result<String> {
|
||||||
|
let result = match self.command {
|
||||||
|
LidarrDeleteCommand::Album {
|
||||||
|
album_id,
|
||||||
|
delete_files_from_disk,
|
||||||
|
add_list_exclusion,
|
||||||
|
} => {
|
||||||
|
let delete_album_params = DeleteParams {
|
||||||
|
id: album_id,
|
||||||
|
delete_files: delete_files_from_disk,
|
||||||
|
add_import_list_exclusion: add_list_exclusion,
|
||||||
|
};
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::DeleteAlbum(delete_album_params).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrDeleteCommand::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 {
|
||||||
|
artist_id,
|
||||||
|
delete_files_from_disk,
|
||||||
|
add_list_exclusion,
|
||||||
|
} => {
|
||||||
|
let delete_artist_params = DeleteParams {
|
||||||
|
id: artist_id,
|
||||||
|
delete_files: delete_files_from_disk,
|
||||||
|
add_import_list_exclusion: add_list_exclusion,
|
||||||
|
};
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::DeleteArtist(delete_artist_params).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrDeleteCommand::Download { download_id } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::DeleteDownload(download_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrDeleteCommand::Indexer { indexer_id } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::DeleteIndexer(indexer_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrDeleteCommand::RootFolder { root_folder_id } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::DeleteRootFolder(root_folder_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrDeleteCommand::Tag { tag_id } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::DeleteTag(tag_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,595 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{
|
||||||
|
Cli,
|
||||||
|
cli::{
|
||||||
|
Command,
|
||||||
|
lidarr::{LidarrCommand, delete_command_handler::LidarrDeleteCommand},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use clap::{CommandFactory, Parser, error::ErrorKind};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_delete_command_from() {
|
||||||
|
let command = LidarrDeleteCommand::Artist {
|
||||||
|
artist_id: 1,
|
||||||
|
delete_files_from_disk: false,
|
||||||
|
add_list_exclusion: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = Command::from(command.clone());
|
||||||
|
|
||||||
|
assert_eq!(result, Command::Lidarr(LidarrCommand::Delete(command)));
|
||||||
|
}
|
||||||
|
|
||||||
|
mod cli {
|
||||||
|
use super::*;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_album_requires_arguments() {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "album"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_album_defaults() {
|
||||||
|
let expected_args = LidarrDeleteCommand::Album {
|
||||||
|
album_id: 1,
|
||||||
|
delete_files_from_disk: false,
|
||||||
|
add_list_exclusion: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result =
|
||||||
|
Cli::try_parse_from(["managarr", "lidarr", "delete", "album", "--album-id", "1"]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
|
else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(delete_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_album_all_args_defined() {
|
||||||
|
let expected_args = LidarrDeleteCommand::Album {
|
||||||
|
album_id: 1,
|
||||||
|
delete_files_from_disk: true,
|
||||||
|
add_list_exclusion: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"delete",
|
||||||
|
"album",
|
||||||
|
"--album-id",
|
||||||
|
"1",
|
||||||
|
"--delete-files-from-disk",
|
||||||
|
"--add-list-exclusion",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
|
else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(delete_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_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]
|
||||||
|
fn test_delete_artist_requires_arguments() {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "artist"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_artist_defaults() {
|
||||||
|
let expected_args = LidarrDeleteCommand::Artist {
|
||||||
|
artist_id: 1,
|
||||||
|
delete_files_from_disk: false,
|
||||||
|
add_list_exclusion: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result =
|
||||||
|
Cli::try_parse_from(["managarr", "lidarr", "delete", "artist", "--artist-id", "1"]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
|
else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(delete_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_artist_all_args_defined() {
|
||||||
|
let expected_args = LidarrDeleteCommand::Artist {
|
||||||
|
artist_id: 1,
|
||||||
|
delete_files_from_disk: true,
|
||||||
|
add_list_exclusion: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"delete",
|
||||||
|
"artist",
|
||||||
|
"--artist-id",
|
||||||
|
"1",
|
||||||
|
"--delete-files-from-disk",
|
||||||
|
"--add-list-exclusion",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
|
else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(delete_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_download_requires_arguments() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "download"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_download_success() {
|
||||||
|
let expected_args = LidarrDeleteCommand::Download { download_id: 1 };
|
||||||
|
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"delete",
|
||||||
|
"download",
|
||||||
|
"--download-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
|
else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(delete_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_indexer_requires_arguments() {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "indexer"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_indexer_success() {
|
||||||
|
let expected_args = LidarrDeleteCommand::Indexer { indexer_id: 1 };
|
||||||
|
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"delete",
|
||||||
|
"indexer",
|
||||||
|
"--indexer-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
|
else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(delete_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_root_folder_requires_arguments() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "root-folder"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_root_folder_success() {
|
||||||
|
let expected_args = LidarrDeleteCommand::RootFolder { root_folder_id: 1 };
|
||||||
|
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"delete",
|
||||||
|
"root-folder",
|
||||||
|
"--root-folder-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
|
else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(delete_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_tag_requires_arguments() {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "tag"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_tag_success() {
|
||||||
|
let expected_args = LidarrDeleteCommand::Tag { tag_id: 1 };
|
||||||
|
|
||||||
|
let result = Cli::try_parse_from(["managarr", "lidarr", "delete", "tag", "--tag-id", "1"]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
|
else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(delete_command, expected_args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod handler {
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use mockall::predicate::eq;
|
||||||
|
use serde_json::json;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
app::App,
|
||||||
|
cli::{
|
||||||
|
CliCommandHandler,
|
||||||
|
lidarr::delete_command_handler::{LidarrDeleteCommand, LidarrDeleteCommandHandler},
|
||||||
|
},
|
||||||
|
models::{
|
||||||
|
Serdeable,
|
||||||
|
lidarr_models::{DeleteParams, LidarrSerdeable},
|
||||||
|
},
|
||||||
|
network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_delete_album_command() {
|
||||||
|
let expected_delete_album_params = DeleteParams {
|
||||||
|
id: 1,
|
||||||
|
delete_files: true,
|
||||||
|
add_import_list_exclusion: true,
|
||||||
|
};
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::DeleteAlbum(expected_delete_album_params).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let delete_album_command = LidarrDeleteCommand::Album {
|
||||||
|
album_id: 1,
|
||||||
|
delete_files_from_disk: true,
|
||||||
|
add_list_exclusion: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result =
|
||||||
|
LidarrDeleteCommandHandler::with(&app_arc, delete_album_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_delete_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]
|
||||||
|
async fn test_handle_delete_artist_command() {
|
||||||
|
let expected_delete_artist_params = DeleteParams {
|
||||||
|
id: 1,
|
||||||
|
delete_files: true,
|
||||||
|
add_import_list_exclusion: true,
|
||||||
|
};
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::DeleteArtist(expected_delete_artist_params).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let delete_artist_command = LidarrDeleteCommand::Artist {
|
||||||
|
artist_id: 1,
|
||||||
|
delete_files_from_disk: true,
|
||||||
|
add_list_exclusion: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result =
|
||||||
|
LidarrDeleteCommandHandler::with(&app_arc, delete_artist_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_delete_download_command() {
|
||||||
|
let expected_download_id = 1;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::DeleteDownload(expected_download_id).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let delete_download_command = LidarrDeleteCommand::Download { download_id: 1 };
|
||||||
|
|
||||||
|
let result =
|
||||||
|
LidarrDeleteCommandHandler::with(&app_arc, delete_download_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_delete_indexer_command() {
|
||||||
|
let expected_indexer_id = 1;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::DeleteIndexer(expected_indexer_id).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let delete_indexer_command = LidarrDeleteCommand::Indexer { indexer_id: 1 };
|
||||||
|
|
||||||
|
let result =
|
||||||
|
LidarrDeleteCommandHandler::with(&app_arc, delete_indexer_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_delete_root_folder_command() {
|
||||||
|
let expected_root_folder_id = 1;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::DeleteRootFolder(expected_root_folder_id).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let delete_root_folder_command = LidarrDeleteCommand::RootFolder { root_folder_id: 1 };
|
||||||
|
|
||||||
|
let result =
|
||||||
|
LidarrDeleteCommandHandler::with(&app_arc, delete_root_folder_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_delete_tag_command() {
|
||||||
|
let expected_tag_id = 1;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::DeleteTag(expected_tag_id).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let delete_tag_command = LidarrDeleteCommand::Tag { tag_id: 1 };
|
||||||
|
|
||||||
|
let result =
|
||||||
|
LidarrDeleteCommandHandler::with(&app_arc, delete_tag_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,343 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::{ArgAction, ArgGroup, Subcommand};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use super::LidarrCommand;
|
||||||
|
use crate::models::Serdeable;
|
||||||
|
use crate::models::lidarr_models::LidarrSerdeable;
|
||||||
|
use crate::models::servarr_models::{EditIndexerParams, IndexerSettings};
|
||||||
|
use crate::{
|
||||||
|
app::App,
|
||||||
|
cli::{CliCommandHandler, Command, mutex_flags_or_option},
|
||||||
|
models::lidarr_models::{EditArtistParams, NewItemMonitorType},
|
||||||
|
network::{NetworkTrait, lidarr_network::LidarrEvent},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "edit_command_handler_tests.rs"]
|
||||||
|
mod edit_command_handler_tests;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
||||||
|
pub enum LidarrEditCommand {
|
||||||
|
#[command(
|
||||||
|
about = "Edit and indexer settings that apply to all indexers",
|
||||||
|
group(
|
||||||
|
ArgGroup::new("edit_settings")
|
||||||
|
.args([
|
||||||
|
"maximum_size",
|
||||||
|
"minimum_age",
|
||||||
|
"retention",
|
||||||
|
"rss_sync_interval",
|
||||||
|
]).required(true)
|
||||||
|
.multiple(true))
|
||||||
|
)]
|
||||||
|
AllIndexerSettings {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The maximum size for a release to be grabbed in MB. Set to zero to set to unlimited"
|
||||||
|
)]
|
||||||
|
maximum_size: Option<i64>,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "Usenet only: Minimum age in minutes of NZBs before they are grabbed. Use this to give new releases time to propagate to your usenet provider."
|
||||||
|
)]
|
||||||
|
minimum_age: Option<i64>,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "Usenet only: The retention time in days to retain releases. Set to zero to set for unlimited retention"
|
||||||
|
)]
|
||||||
|
retention: Option<i64>,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The RSS sync interval in minutes. Set to zero to disable (this will stop all automatic release grabbing)"
|
||||||
|
)]
|
||||||
|
rss_sync_interval: Option<i64>,
|
||||||
|
},
|
||||||
|
#[command(
|
||||||
|
about = "Edit preferences for the specified artist",
|
||||||
|
group(
|
||||||
|
ArgGroup::new("edit_artist")
|
||||||
|
.args([
|
||||||
|
"enable_monitoring",
|
||||||
|
"disable_monitoring",
|
||||||
|
"monitor_new_items",
|
||||||
|
"quality_profile_id",
|
||||||
|
"metadata_profile_id",
|
||||||
|
"root_folder_path",
|
||||||
|
"tag",
|
||||||
|
"clear_tags"
|
||||||
|
]).required(true)
|
||||||
|
.multiple(true))
|
||||||
|
)]
|
||||||
|
Artist {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The ID of the artist whose settings you want to edit",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
artist_id: i64,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "Enable monitoring of this artist in Lidarr so Lidarr will automatically download releases from this artist if they are available",
|
||||||
|
conflicts_with = "disable_monitoring"
|
||||||
|
)]
|
||||||
|
enable_monitoring: bool,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "Disable monitoring of this artist so Lidarr does not automatically download releases from this artist if they are available",
|
||||||
|
conflicts_with = "enable_monitoring"
|
||||||
|
)]
|
||||||
|
disable_monitoring: bool,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "How Lidarr should monitor new albums from this artist",
|
||||||
|
value_enum
|
||||||
|
)]
|
||||||
|
monitor_new_items: Option<NewItemMonitorType>,
|
||||||
|
#[arg(long, help = "The ID of the quality profile to use for this artist")]
|
||||||
|
quality_profile_id: Option<i64>,
|
||||||
|
#[arg(long, help = "The ID of the metadata profile to use for this artist")]
|
||||||
|
metadata_profile_id: Option<i64>,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The root folder path where all artist data and metadata should live"
|
||||||
|
)]
|
||||||
|
root_folder_path: Option<String>,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "Tag IDs to tag this artist with",
|
||||||
|
value_parser,
|
||||||
|
action = ArgAction::Append,
|
||||||
|
conflicts_with = "clear_tags"
|
||||||
|
)]
|
||||||
|
tag: Option<Vec<i64>>,
|
||||||
|
#[arg(long, help = "Clear all tags on this artist", conflicts_with = "tag")]
|
||||||
|
clear_tags: bool,
|
||||||
|
},
|
||||||
|
#[command(
|
||||||
|
about = "Edit preferences for the specified indexer",
|
||||||
|
group(
|
||||||
|
ArgGroup::new("edit_indexer")
|
||||||
|
.args([
|
||||||
|
"name",
|
||||||
|
"enable_rss",
|
||||||
|
"disable_rss",
|
||||||
|
"enable_automatic_search",
|
||||||
|
"disable_automatic_search",
|
||||||
|
"enable_interactive_search",
|
||||||
|
"disable_automatic_search",
|
||||||
|
"url",
|
||||||
|
"api_key",
|
||||||
|
"seed_ratio",
|
||||||
|
"tag",
|
||||||
|
"priority",
|
||||||
|
"clear_tags"
|
||||||
|
]).required(true)
|
||||||
|
.multiple(true))
|
||||||
|
)]
|
||||||
|
Indexer {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The ID of the indexer whose settings you wish to edit",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
indexer_id: i64,
|
||||||
|
#[arg(long, help = "The name of the indexer")]
|
||||||
|
name: Option<String>,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "Indicate to Lidarr that this indexer should be used when Lidarr periodically looks for releases via RSS Sync",
|
||||||
|
conflicts_with = "disable_rss"
|
||||||
|
)]
|
||||||
|
enable_rss: bool,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "Disable using this indexer when Lidarr periodically looks for releases via RSS Sync",
|
||||||
|
conflicts_with = "enable_rss"
|
||||||
|
)]
|
||||||
|
disable_rss: bool,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "Indicate to Lidarr that this indexer should be used when automatic searches are performed via the UI or by Lidarr",
|
||||||
|
conflicts_with = "disable_automatic_search"
|
||||||
|
)]
|
||||||
|
enable_automatic_search: bool,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "Disable using this indexer whenever automatic searches are performed via the UI or by Lidarr",
|
||||||
|
conflicts_with = "enable_automatic_search"
|
||||||
|
)]
|
||||||
|
disable_automatic_search: bool,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "Indicate to Lidarr that this indexer should be used when an interactive search is used",
|
||||||
|
conflicts_with = "disable_interactive_search"
|
||||||
|
)]
|
||||||
|
enable_interactive_search: bool,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "Disable using this indexer whenever an interactive search is performed",
|
||||||
|
conflicts_with = "enable_interactive_search"
|
||||||
|
)]
|
||||||
|
disable_interactive_search: bool,
|
||||||
|
#[arg(long, help = "The URL of the indexer")]
|
||||||
|
url: Option<String>,
|
||||||
|
#[arg(long, help = "The API key used to access the indexer's API")]
|
||||||
|
api_key: Option<String>,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The ratio a torrent should reach before stopping; Empty uses the download client's default. Ratio should be at least 1.0 and follow the indexer's rules"
|
||||||
|
)]
|
||||||
|
seed_ratio: Option<String>,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "Only use this indexer for series with at least one matching tag ID. Leave blank to use with all series.",
|
||||||
|
value_parser,
|
||||||
|
action = ArgAction::Append,
|
||||||
|
conflicts_with = "clear_tags"
|
||||||
|
)]
|
||||||
|
tag: Option<Vec<i64>>,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "Indexer Priority from 1 (Highest) to 50 (Lowest). Default: 25. Used when grabbing releases as a tiebreaker for otherwise equal releases, Lidarr will still use all enabled indexers for RSS Sync and Searching"
|
||||||
|
)]
|
||||||
|
priority: Option<i64>,
|
||||||
|
#[arg(long, help = "Clear all tags on this indexer", conflicts_with = "tag")]
|
||||||
|
clear_tags: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LidarrEditCommand> for Command {
|
||||||
|
fn from(value: LidarrEditCommand) -> Self {
|
||||||
|
Command::Lidarr(LidarrCommand::Edit(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct LidarrEditCommandHandler<'a, 'b> {
|
||||||
|
_app: &'a Arc<Mutex<App<'b>>>,
|
||||||
|
command: LidarrEditCommand,
|
||||||
|
network: &'a mut dyn NetworkTrait,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrEditCommand> for LidarrEditCommandHandler<'a, 'b> {
|
||||||
|
fn with(
|
||||||
|
_app: &'a Arc<Mutex<App<'b>>>,
|
||||||
|
command: LidarrEditCommand,
|
||||||
|
network: &'a mut dyn NetworkTrait,
|
||||||
|
) -> Self {
|
||||||
|
LidarrEditCommandHandler {
|
||||||
|
_app,
|
||||||
|
command,
|
||||||
|
network,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle(self) -> Result<String> {
|
||||||
|
let result = match self.command {
|
||||||
|
LidarrEditCommand::AllIndexerSettings {
|
||||||
|
maximum_size,
|
||||||
|
minimum_age,
|
||||||
|
retention,
|
||||||
|
rss_sync_interval,
|
||||||
|
} => {
|
||||||
|
if let Serdeable::Lidarr(LidarrSerdeable::IndexerSettings(previous_indexer_settings)) = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetAllIndexerSettings.into())
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
let params = IndexerSettings {
|
||||||
|
id: 1,
|
||||||
|
maximum_size: maximum_size.unwrap_or(previous_indexer_settings.maximum_size),
|
||||||
|
minimum_age: minimum_age.unwrap_or(previous_indexer_settings.minimum_age),
|
||||||
|
retention: retention.unwrap_or(previous_indexer_settings.retention),
|
||||||
|
rss_sync_interval: rss_sync_interval
|
||||||
|
.unwrap_or(previous_indexer_settings.rss_sync_interval),
|
||||||
|
};
|
||||||
|
self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::EditAllIndexerSettings(params).into())
|
||||||
|
.await?;
|
||||||
|
"All indexer settings updated".to_owned()
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LidarrEditCommand::Artist {
|
||||||
|
artist_id,
|
||||||
|
enable_monitoring,
|
||||||
|
disable_monitoring,
|
||||||
|
monitor_new_items,
|
||||||
|
quality_profile_id,
|
||||||
|
metadata_profile_id,
|
||||||
|
root_folder_path,
|
||||||
|
tag,
|
||||||
|
clear_tags,
|
||||||
|
} => {
|
||||||
|
let monitored_value = mutex_flags_or_option(enable_monitoring, disable_monitoring);
|
||||||
|
let edit_artist_params = EditArtistParams {
|
||||||
|
artist_id,
|
||||||
|
monitored: monitored_value,
|
||||||
|
monitor_new_items,
|
||||||
|
quality_profile_id,
|
||||||
|
metadata_profile_id,
|
||||||
|
root_folder_path,
|
||||||
|
tags: tag,
|
||||||
|
tag_input_string: None,
|
||||||
|
clear_tags,
|
||||||
|
};
|
||||||
|
|
||||||
|
self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::EditArtist(edit_artist_params).into())
|
||||||
|
.await?;
|
||||||
|
"Artist Updated".to_owned()
|
||||||
|
}
|
||||||
|
LidarrEditCommand::Indexer {
|
||||||
|
indexer_id,
|
||||||
|
name,
|
||||||
|
enable_rss,
|
||||||
|
disable_rss,
|
||||||
|
enable_automatic_search,
|
||||||
|
disable_automatic_search,
|
||||||
|
enable_interactive_search,
|
||||||
|
disable_interactive_search,
|
||||||
|
url,
|
||||||
|
api_key,
|
||||||
|
seed_ratio,
|
||||||
|
tag,
|
||||||
|
priority,
|
||||||
|
clear_tags,
|
||||||
|
} => {
|
||||||
|
let rss_value = mutex_flags_or_option(enable_rss, disable_rss);
|
||||||
|
let automatic_search_value =
|
||||||
|
mutex_flags_or_option(enable_automatic_search, disable_automatic_search);
|
||||||
|
let interactive_search_value =
|
||||||
|
mutex_flags_or_option(enable_interactive_search, disable_interactive_search);
|
||||||
|
let edit_indexer_params = EditIndexerParams {
|
||||||
|
indexer_id,
|
||||||
|
name,
|
||||||
|
enable_rss: rss_value,
|
||||||
|
enable_automatic_search: automatic_search_value,
|
||||||
|
enable_interactive_search: interactive_search_value,
|
||||||
|
url,
|
||||||
|
api_key,
|
||||||
|
seed_ratio,
|
||||||
|
tags: tag,
|
||||||
|
tag_input_string: None,
|
||||||
|
priority,
|
||||||
|
clear_tags,
|
||||||
|
};
|
||||||
|
|
||||||
|
self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::EditIndexer(edit_indexer_params).into())
|
||||||
|
.await?;
|
||||||
|
"Indexer updated".to_owned()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,858 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::cli::{
|
||||||
|
Command,
|
||||||
|
lidarr::{LidarrCommand, edit_command_handler::LidarrEditCommand},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_edit_command_from() {
|
||||||
|
let command = LidarrEditCommand::Artist {
|
||||||
|
artist_id: 1,
|
||||||
|
enable_monitoring: false,
|
||||||
|
disable_monitoring: false,
|
||||||
|
monitor_new_items: None,
|
||||||
|
quality_profile_id: None,
|
||||||
|
metadata_profile_id: None,
|
||||||
|
root_folder_path: None,
|
||||||
|
tag: None,
|
||||||
|
clear_tags: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = Command::from(command.clone());
|
||||||
|
|
||||||
|
assert_eq!(result, Command::Lidarr(LidarrCommand::Edit(command)));
|
||||||
|
}
|
||||||
|
|
||||||
|
mod cli {
|
||||||
|
use crate::{Cli, models::lidarr_models::NewItemMonitorType};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use clap::{CommandFactory, Parser, error::ErrorKind};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_edit_all_indexer_settings_requires_arguments() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "edit", "all-indexer-settings"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_edit_all_indexer_settings_assert_argument_flags_require_args(
|
||||||
|
#[values(
|
||||||
|
"--maximum-size",
|
||||||
|
"--minimum-age",
|
||||||
|
"--retention",
|
||||||
|
"--rss-sync-interval"
|
||||||
|
)]
|
||||||
|
flag: &str,
|
||||||
|
) {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"edit",
|
||||||
|
"all-indexer-settings",
|
||||||
|
flag,
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_edit_all_indexer_settings_only_requires_at_least_one_argument() {
|
||||||
|
let expected_args = LidarrEditCommand::AllIndexerSettings {
|
||||||
|
maximum_size: Some(1),
|
||||||
|
minimum_age: None,
|
||||||
|
retention: None,
|
||||||
|
rss_sync_interval: None,
|
||||||
|
};
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"edit",
|
||||||
|
"all-indexer-settings",
|
||||||
|
"--maximum-size",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(edit_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_edit_all_indexer_settings_all_arguments_defined() {
|
||||||
|
let expected_args = LidarrEditCommand::AllIndexerSettings {
|
||||||
|
maximum_size: Some(1),
|
||||||
|
minimum_age: Some(1),
|
||||||
|
retention: Some(1),
|
||||||
|
rss_sync_interval: Some(1),
|
||||||
|
};
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"edit",
|
||||||
|
"all-indexer-settings",
|
||||||
|
"--maximum-size",
|
||||||
|
"1",
|
||||||
|
"--minimum-age",
|
||||||
|
"1",
|
||||||
|
"--retention",
|
||||||
|
"1",
|
||||||
|
"--rss-sync-interval",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(edit_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_edit_artist_requires_arguments() {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "edit", "artist"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_edit_artist_with_artist_id_still_requires_arguments() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"edit",
|
||||||
|
"artist",
|
||||||
|
"--artist-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_edit_artist_monitoring_flags_conflict() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"edit",
|
||||||
|
"artist",
|
||||||
|
"--artist-id",
|
||||||
|
"1",
|
||||||
|
"--enable-monitoring",
|
||||||
|
"--disable-monitoring",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_edit_artist_tag_flags_conflict() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"edit",
|
||||||
|
"artist",
|
||||||
|
"--artist-id",
|
||||||
|
"1",
|
||||||
|
"--tag",
|
||||||
|
"1",
|
||||||
|
"--clear-tags",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_edit_artist_assert_argument_flags_require_args(
|
||||||
|
#[values(
|
||||||
|
"--monitor-new-items",
|
||||||
|
"--quality-profile-id",
|
||||||
|
"--metadata-profile-id",
|
||||||
|
"--root-folder-path",
|
||||||
|
"--tag"
|
||||||
|
)]
|
||||||
|
flag: &str,
|
||||||
|
) {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"edit",
|
||||||
|
"artist",
|
||||||
|
"--artist-id",
|
||||||
|
"1",
|
||||||
|
flag,
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_edit_artist_monitor_new_items_validation() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"edit",
|
||||||
|
"artist",
|
||||||
|
"--artist-id",
|
||||||
|
"1",
|
||||||
|
"--monitor-new-items",
|
||||||
|
"test",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_edit_artist_only_requires_at_least_one_argument_plus_artist_id() {
|
||||||
|
let expected_args = LidarrEditCommand::Artist {
|
||||||
|
artist_id: 1,
|
||||||
|
enable_monitoring: false,
|
||||||
|
disable_monitoring: false,
|
||||||
|
monitor_new_items: None,
|
||||||
|
quality_profile_id: None,
|
||||||
|
metadata_profile_id: None,
|
||||||
|
root_folder_path: Some("/nfs/test".to_owned()),
|
||||||
|
tag: None,
|
||||||
|
clear_tags: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"edit",
|
||||||
|
"artist",
|
||||||
|
"--artist-id",
|
||||||
|
"1",
|
||||||
|
"--root-folder-path",
|
||||||
|
"/nfs/test",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(edit_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_edit_artist_tag_argument_is_repeatable() {
|
||||||
|
let expected_args = LidarrEditCommand::Artist {
|
||||||
|
artist_id: 1,
|
||||||
|
enable_monitoring: false,
|
||||||
|
disable_monitoring: false,
|
||||||
|
monitor_new_items: None,
|
||||||
|
quality_profile_id: None,
|
||||||
|
metadata_profile_id: None,
|
||||||
|
root_folder_path: None,
|
||||||
|
tag: Some(vec![1, 2]),
|
||||||
|
clear_tags: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"edit",
|
||||||
|
"artist",
|
||||||
|
"--artist-id",
|
||||||
|
"1",
|
||||||
|
"--tag",
|
||||||
|
"1",
|
||||||
|
"--tag",
|
||||||
|
"2",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(edit_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_edit_artist_all_arguments_defined() {
|
||||||
|
let expected_args = LidarrEditCommand::Artist {
|
||||||
|
artist_id: 1,
|
||||||
|
enable_monitoring: true,
|
||||||
|
disable_monitoring: false,
|
||||||
|
monitor_new_items: Some(NewItemMonitorType::New),
|
||||||
|
quality_profile_id: Some(1),
|
||||||
|
metadata_profile_id: Some(1),
|
||||||
|
root_folder_path: Some("/nfs/test".to_owned()),
|
||||||
|
tag: Some(vec![1, 2]),
|
||||||
|
clear_tags: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"edit",
|
||||||
|
"artist",
|
||||||
|
"--artist-id",
|
||||||
|
"1",
|
||||||
|
"--enable-monitoring",
|
||||||
|
"--monitor-new-items",
|
||||||
|
"new",
|
||||||
|
"--quality-profile-id",
|
||||||
|
"1",
|
||||||
|
"--metadata-profile-id",
|
||||||
|
"1",
|
||||||
|
"--root-folder-path",
|
||||||
|
"/nfs/test",
|
||||||
|
"--tag",
|
||||||
|
"1",
|
||||||
|
"--tag",
|
||||||
|
"2",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(edit_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_edit_indexer_requires_arguments() {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "edit", "indexer"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_edit_indexer_with_indexer_id_still_requires_arguments() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"edit",
|
||||||
|
"indexer",
|
||||||
|
"--indexer-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_edit_indexer_rss_flags_conflict() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"edit",
|
||||||
|
"indexer",
|
||||||
|
"--indexer-id",
|
||||||
|
"1",
|
||||||
|
"--enable-rss",
|
||||||
|
"--disable-rss",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_edit_indexer_automatic_search_flags_conflict() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"edit",
|
||||||
|
"indexer",
|
||||||
|
"--indexer-id",
|
||||||
|
"1",
|
||||||
|
"--enable-automatic-search",
|
||||||
|
"--disable-automatic-search",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_edit_indexer_interactive_search_flags_conflict() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"edit",
|
||||||
|
"indexer",
|
||||||
|
"--indexer-id",
|
||||||
|
"1",
|
||||||
|
"--enable-interactive-search",
|
||||||
|
"--disable-interactive-search",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_edit_indexer_tag_flags_conflict() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"edit",
|
||||||
|
"indexer",
|
||||||
|
"--indexer-id",
|
||||||
|
"1",
|
||||||
|
"--tag",
|
||||||
|
"1",
|
||||||
|
"--clear-tags",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_edit_indexer_assert_argument_flags_require_args(
|
||||||
|
#[values("--name", "--url", "--api-key", "--seed-ratio", "--tag", "--priority")] flag: &str,
|
||||||
|
) {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"edit",
|
||||||
|
"indexer",
|
||||||
|
"--indexer-id",
|
||||||
|
"1",
|
||||||
|
flag,
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_edit_indexer_only_requires_at_least_one_argument_plus_indexer_id() {
|
||||||
|
let expected_args = LidarrEditCommand::Indexer {
|
||||||
|
indexer_id: 1,
|
||||||
|
name: Some("Test".to_owned()),
|
||||||
|
enable_rss: false,
|
||||||
|
disable_rss: false,
|
||||||
|
enable_automatic_search: false,
|
||||||
|
disable_automatic_search: false,
|
||||||
|
enable_interactive_search: false,
|
||||||
|
disable_interactive_search: false,
|
||||||
|
url: None,
|
||||||
|
api_key: None,
|
||||||
|
seed_ratio: None,
|
||||||
|
tag: None,
|
||||||
|
priority: None,
|
||||||
|
clear_tags: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"edit",
|
||||||
|
"indexer",
|
||||||
|
"--indexer-id",
|
||||||
|
"1",
|
||||||
|
"--name",
|
||||||
|
"Test",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(edit_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_edit_indexer_tag_argument_is_repeatable() {
|
||||||
|
let expected_args = LidarrEditCommand::Indexer {
|
||||||
|
indexer_id: 1,
|
||||||
|
name: None,
|
||||||
|
enable_rss: false,
|
||||||
|
disable_rss: false,
|
||||||
|
enable_automatic_search: false,
|
||||||
|
disable_automatic_search: false,
|
||||||
|
enable_interactive_search: false,
|
||||||
|
disable_interactive_search: false,
|
||||||
|
url: None,
|
||||||
|
api_key: None,
|
||||||
|
seed_ratio: None,
|
||||||
|
tag: Some(vec![1, 2]),
|
||||||
|
priority: None,
|
||||||
|
clear_tags: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"edit",
|
||||||
|
"indexer",
|
||||||
|
"--indexer-id",
|
||||||
|
"1",
|
||||||
|
"--tag",
|
||||||
|
"1",
|
||||||
|
"--tag",
|
||||||
|
"2",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(edit_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_edit_indexer_all_arguments_defined() {
|
||||||
|
let expected_args = LidarrEditCommand::Indexer {
|
||||||
|
indexer_id: 1,
|
||||||
|
name: Some("Test".to_owned()),
|
||||||
|
enable_rss: true,
|
||||||
|
disable_rss: false,
|
||||||
|
enable_automatic_search: true,
|
||||||
|
disable_automatic_search: false,
|
||||||
|
enable_interactive_search: true,
|
||||||
|
disable_interactive_search: false,
|
||||||
|
url: Some("http://test.com".to_owned()),
|
||||||
|
api_key: Some("testKey".to_owned()),
|
||||||
|
seed_ratio: Some("1.2".to_owned()),
|
||||||
|
tag: Some(vec![1, 2]),
|
||||||
|
priority: Some(25),
|
||||||
|
clear_tags: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"edit",
|
||||||
|
"indexer",
|
||||||
|
"--indexer-id",
|
||||||
|
"1",
|
||||||
|
"--name",
|
||||||
|
"Test",
|
||||||
|
"--enable-rss",
|
||||||
|
"--enable-automatic-search",
|
||||||
|
"--enable-interactive-search",
|
||||||
|
"--url",
|
||||||
|
"http://test.com",
|
||||||
|
"--api-key",
|
||||||
|
"testKey",
|
||||||
|
"--seed-ratio",
|
||||||
|
"1.2",
|
||||||
|
"--tag",
|
||||||
|
"1",
|
||||||
|
"--tag",
|
||||||
|
"2",
|
||||||
|
"--priority",
|
||||||
|
"25",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(edit_command, expected_args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod handler {
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use mockall::predicate::eq;
|
||||||
|
use serde_json::json;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::models::servarr_models::{EditIndexerParams, IndexerSettings};
|
||||||
|
use crate::{
|
||||||
|
app::App,
|
||||||
|
cli::{
|
||||||
|
CliCommandHandler,
|
||||||
|
lidarr::edit_command_handler::{LidarrEditCommand, LidarrEditCommandHandler},
|
||||||
|
},
|
||||||
|
models::{
|
||||||
|
Serdeable,
|
||||||
|
lidarr_models::{EditArtistParams, LidarrSerdeable, NewItemMonitorType},
|
||||||
|
},
|
||||||
|
network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_edit_all_indexer_settings_command() {
|
||||||
|
let expected_edit_all_indexer_settings = IndexerSettings {
|
||||||
|
id: 1,
|
||||||
|
maximum_size: 1,
|
||||||
|
minimum_age: 1,
|
||||||
|
retention: 1,
|
||||||
|
rss_sync_interval: 1,
|
||||||
|
};
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::GetAllIndexerSettings.into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::IndexerSettings(
|
||||||
|
IndexerSettings {
|
||||||
|
id: 1,
|
||||||
|
maximum_size: 2,
|
||||||
|
minimum_age: 2,
|
||||||
|
retention: 2,
|
||||||
|
rss_sync_interval: 2,
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let edit_all_indexer_settings_command = LidarrEditCommand::AllIndexerSettings {
|
||||||
|
maximum_size: Some(1),
|
||||||
|
minimum_age: Some(1),
|
||||||
|
retention: Some(1),
|
||||||
|
rss_sync_interval: Some(1),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = LidarrEditCommandHandler::with(
|
||||||
|
&app_arc,
|
||||||
|
edit_all_indexer_settings_command,
|
||||||
|
&mut mock_network,
|
||||||
|
)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_edit_artist_command() {
|
||||||
|
let expected_edit_artist_params = EditArtistParams {
|
||||||
|
artist_id: 1,
|
||||||
|
monitored: Some(true),
|
||||||
|
monitor_new_items: Some(NewItemMonitorType::New),
|
||||||
|
quality_profile_id: Some(1),
|
||||||
|
metadata_profile_id: Some(1),
|
||||||
|
root_folder_path: Some("/nfs/test".to_owned()),
|
||||||
|
tags: Some(vec![1, 2]),
|
||||||
|
tag_input_string: None,
|
||||||
|
clear_tags: false,
|
||||||
|
};
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::EditArtist(expected_edit_artist_params).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let edit_artist_command = LidarrEditCommand::Artist {
|
||||||
|
artist_id: 1,
|
||||||
|
enable_monitoring: true,
|
||||||
|
disable_monitoring: false,
|
||||||
|
monitor_new_items: Some(NewItemMonitorType::New),
|
||||||
|
quality_profile_id: Some(1),
|
||||||
|
metadata_profile_id: Some(1),
|
||||||
|
root_folder_path: Some("/nfs/test".to_owned()),
|
||||||
|
tag: Some(vec![1, 2]),
|
||||||
|
clear_tags: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = LidarrEditCommandHandler::with(&app_arc, edit_artist_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_edit_artist_command_handles_disable_monitoring_flag_properly() {
|
||||||
|
let expected_edit_artist_params = EditArtistParams {
|
||||||
|
artist_id: 1,
|
||||||
|
monitored: Some(false),
|
||||||
|
monitor_new_items: Some(NewItemMonitorType::None),
|
||||||
|
quality_profile_id: Some(1),
|
||||||
|
metadata_profile_id: Some(1),
|
||||||
|
root_folder_path: Some("/nfs/test".to_owned()),
|
||||||
|
tags: Some(vec![1, 2]),
|
||||||
|
tag_input_string: None,
|
||||||
|
clear_tags: false,
|
||||||
|
};
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::EditArtist(expected_edit_artist_params).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let edit_artist_command = LidarrEditCommand::Artist {
|
||||||
|
artist_id: 1,
|
||||||
|
enable_monitoring: false,
|
||||||
|
disable_monitoring: true,
|
||||||
|
monitor_new_items: Some(NewItemMonitorType::None),
|
||||||
|
quality_profile_id: Some(1),
|
||||||
|
metadata_profile_id: Some(1),
|
||||||
|
root_folder_path: Some("/nfs/test".to_owned()),
|
||||||
|
tag: Some(vec![1, 2]),
|
||||||
|
clear_tags: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = LidarrEditCommandHandler::with(&app_arc, edit_artist_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_edit_artist_command_no_monitoring_boolean_flags_returns_none_value() {
|
||||||
|
let expected_edit_artist_params = EditArtistParams {
|
||||||
|
artist_id: 1,
|
||||||
|
monitored: None,
|
||||||
|
monitor_new_items: Some(NewItemMonitorType::All),
|
||||||
|
quality_profile_id: Some(1),
|
||||||
|
metadata_profile_id: Some(1),
|
||||||
|
root_folder_path: Some("/nfs/test".to_owned()),
|
||||||
|
tags: Some(vec![1, 2]),
|
||||||
|
tag_input_string: None,
|
||||||
|
clear_tags: false,
|
||||||
|
};
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::EditArtist(expected_edit_artist_params).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let edit_artist_command = LidarrEditCommand::Artist {
|
||||||
|
artist_id: 1,
|
||||||
|
enable_monitoring: false,
|
||||||
|
disable_monitoring: false,
|
||||||
|
monitor_new_items: Some(NewItemMonitorType::All),
|
||||||
|
quality_profile_id: Some(1),
|
||||||
|
metadata_profile_id: Some(1),
|
||||||
|
root_folder_path: Some("/nfs/test".to_owned()),
|
||||||
|
tag: Some(vec![1, 2]),
|
||||||
|
clear_tags: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = LidarrEditCommandHandler::with(&app_arc, edit_artist_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_edit_indexer_command() {
|
||||||
|
let expected_edit_indexer_params = EditIndexerParams {
|
||||||
|
indexer_id: 1,
|
||||||
|
name: Some("Test".to_owned()),
|
||||||
|
enable_rss: Some(true),
|
||||||
|
enable_automatic_search: Some(true),
|
||||||
|
enable_interactive_search: Some(true),
|
||||||
|
url: Some("http://test.com".to_owned()),
|
||||||
|
api_key: Some("testKey".to_owned()),
|
||||||
|
seed_ratio: Some("1.2".to_owned()),
|
||||||
|
tags: Some(vec![1, 2]),
|
||||||
|
tag_input_string: None,
|
||||||
|
priority: Some(25),
|
||||||
|
clear_tags: false,
|
||||||
|
};
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::EditIndexer(expected_edit_indexer_params).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let edit_indexer_command = LidarrEditCommand::Indexer {
|
||||||
|
indexer_id: 1,
|
||||||
|
name: Some("Test".to_owned()),
|
||||||
|
enable_rss: true,
|
||||||
|
disable_rss: false,
|
||||||
|
enable_automatic_search: true,
|
||||||
|
disable_automatic_search: false,
|
||||||
|
enable_interactive_search: true,
|
||||||
|
disable_interactive_search: false,
|
||||||
|
url: Some("http://test.com".to_owned()),
|
||||||
|
api_key: Some("testKey".to_owned()),
|
||||||
|
seed_ratio: Some("1.2".to_owned()),
|
||||||
|
tag: Some(vec![1, 2]),
|
||||||
|
priority: Some(25),
|
||||||
|
clear_tags: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result =
|
||||||
|
LidarrEditCommandHandler::with(&app_arc, edit_indexer_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::Subcommand;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
app::App,
|
||||||
|
cli::{CliCommandHandler, Command},
|
||||||
|
network::{NetworkTrait, lidarr_network::LidarrEvent},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::LidarrCommand;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "get_command_handler_tests.rs"]
|
||||||
|
mod get_command_handler_tests;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
||||||
|
pub enum LidarrGetCommand {
|
||||||
|
#[command(about = "Get detailed information for the album with the given ID")]
|
||||||
|
AlbumDetails {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The Lidarr ID of the album whose details you wish to fetch",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
album_id: i64,
|
||||||
|
},
|
||||||
|
#[command(about = "Get the shared settings for all indexers")]
|
||||||
|
AllIndexerSettings,
|
||||||
|
#[command(about = "Get detailed information for the artist with the given ID")]
|
||||||
|
ArtistDetails {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The Lidarr ID of the artist whose details you wish to fetch",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
artist_id: i64,
|
||||||
|
},
|
||||||
|
#[command(about = "Fetch the host config for your Lidarr instance")]
|
||||||
|
HostConfig,
|
||||||
|
#[command(about = "Fetch the security config for your Lidarr instance")]
|
||||||
|
SecurityConfig,
|
||||||
|
#[command(about = "Get the system status")]
|
||||||
|
SystemStatus,
|
||||||
|
#[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 {
|
||||||
|
fn from(value: LidarrGetCommand) -> Self {
|
||||||
|
Command::Lidarr(LidarrCommand::Get(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct LidarrGetCommandHandler<'a, 'b> {
|
||||||
|
_app: &'a Arc<Mutex<App<'b>>>,
|
||||||
|
command: LidarrGetCommand,
|
||||||
|
network: &'a mut dyn NetworkTrait,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrGetCommand> for LidarrGetCommandHandler<'a, 'b> {
|
||||||
|
fn with(
|
||||||
|
_app: &'a Arc<Mutex<App<'b>>>,
|
||||||
|
command: LidarrGetCommand,
|
||||||
|
network: &'a mut dyn NetworkTrait,
|
||||||
|
) -> Self {
|
||||||
|
LidarrGetCommandHandler {
|
||||||
|
_app,
|
||||||
|
command,
|
||||||
|
network,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle(self) -> Result<String> {
|
||||||
|
let result = match self.command {
|
||||||
|
LidarrGetCommand::AlbumDetails { album_id } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetAlbumDetails(album_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrGetCommand::AllIndexerSettings => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetAllIndexerSettings.into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrGetCommand::ArtistDetails { artist_id } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetArtistDetails(artist_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrGetCommand::HostConfig => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetHostConfig.into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrGetCommand::SecurityConfig => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetSecurityConfig.into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrGetCommand::SystemStatus => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetStatus.into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,329 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::Cli;
|
||||||
|
use crate::cli::{
|
||||||
|
Command,
|
||||||
|
lidarr::{LidarrCommand, get_command_handler::LidarrGetCommand},
|
||||||
|
};
|
||||||
|
use clap::CommandFactory;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_get_command_from() {
|
||||||
|
let command = LidarrGetCommand::SystemStatus;
|
||||||
|
|
||||||
|
let result = Command::from(command.clone());
|
||||||
|
|
||||||
|
assert_eq!(result, Command::Lidarr(LidarrCommand::Get(command)));
|
||||||
|
}
|
||||||
|
|
||||||
|
mod cli {
|
||||||
|
use clap::error::ErrorKind;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_album_details_requires_album_id() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "get", "album-details"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_album_details_requirements_satisfied() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"get",
|
||||||
|
"album-details",
|
||||||
|
"--album-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_all_indexer_settings_has_no_arg_requirements() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "get", "all-indexer-settings"]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artist_details_requires_artist_id() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "get", "artist-details"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artist_details_requirements_satisfied() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"get",
|
||||||
|
"artist-details",
|
||||||
|
"--artist-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_host_config_has_no_arg_requirements() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "get", "host-config"]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_security_config_has_no_arg_requirements() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "get", "security-config"]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_system_status_has_no_arg_requirements() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "get", "system-status"]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 {
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use mockall::predicate::eq;
|
||||||
|
use serde_json::json;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
app::App,
|
||||||
|
cli::{
|
||||||
|
CliCommandHandler,
|
||||||
|
lidarr::get_command_handler::{LidarrGetCommand, LidarrGetCommandHandler},
|
||||||
|
},
|
||||||
|
models::{Serdeable, lidarr_models::LidarrSerdeable},
|
||||||
|
network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_get_album_details_command() {
|
||||||
|
let expected_album_id = 1;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::GetAlbumDetails(expected_album_id).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let get_album_details_command = LidarrGetCommand::AlbumDetails { album_id: 1 };
|
||||||
|
|
||||||
|
let result =
|
||||||
|
LidarrGetCommandHandler::with(&app_arc, get_album_details_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_get_all_indexer_settings_command() {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::GetAllIndexerSettings.into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let get_all_indexer_settings_command = LidarrGetCommand::AllIndexerSettings;
|
||||||
|
|
||||||
|
let result = LidarrGetCommandHandler::with(
|
||||||
|
&app_arc,
|
||||||
|
get_all_indexer_settings_command,
|
||||||
|
&mut mock_network,
|
||||||
|
)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_get_artist_details_command() {
|
||||||
|
let expected_artist_id = 1;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::GetArtistDetails(expected_artist_id).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let get_artist_details_command = LidarrGetCommand::ArtistDetails { artist_id: 1 };
|
||||||
|
|
||||||
|
let result =
|
||||||
|
LidarrGetCommandHandler::with(&app_arc, get_artist_details_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_get_host_config_command() {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(LidarrEvent::GetHostConfig.into()))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let get_host_config_command = LidarrGetCommand::HostConfig;
|
||||||
|
|
||||||
|
let result =
|
||||||
|
LidarrGetCommandHandler::with(&app_arc, get_host_config_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_get_security_config_command() {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(LidarrEvent::GetSecurityConfig.into()))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let get_security_config_command = LidarrGetCommand::SecurityConfig;
|
||||||
|
|
||||||
|
let result =
|
||||||
|
LidarrGetCommandHandler::with(&app_arc, get_security_config_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_get_system_status_command() {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(LidarrEvent::GetStatus.into()))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let get_system_status_command = LidarrGetCommand::SystemStatus;
|
||||||
|
|
||||||
|
let result =
|
||||||
|
LidarrGetCommandHandler::with(&app_arc, get_system_status_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,775 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::Cli;
|
||||||
|
use crate::cli::{
|
||||||
|
Command,
|
||||||
|
lidarr::{LidarrCommand, list_command_handler::LidarrListCommand},
|
||||||
|
};
|
||||||
|
use clap::CommandFactory;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_command_from() {
|
||||||
|
let command = LidarrCommand::List(LidarrListCommand::Artists);
|
||||||
|
|
||||||
|
let result = Command::from(command.clone());
|
||||||
|
|
||||||
|
assert_eq!(result, Command::Lidarr(command));
|
||||||
|
}
|
||||||
|
|
||||||
|
mod cli {
|
||||||
|
use super::*;
|
||||||
|
use clap::error::ErrorKind;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_commands_that_have_no_arg_requirements(
|
||||||
|
#[values("clear-blocklist", "test-all-indexers")] subcommand: &str,
|
||||||
|
) {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", subcommand]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_artists_has_no_arg_requirements() {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "list", "artists"]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_list_subcommand_requires_subcommand() {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "list"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_add_subcommand_requires_subcommand() {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "add"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_delete_subcommand_requires_subcommand() {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "delete"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_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]
|
||||||
|
fn test_toggle_artist_monitoring_requires_artist_id() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "toggle-artist-monitoring"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_toggle_artist_monitoring_requirements_satisfied() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"toggle-artist-monitoring",
|
||||||
|
"--artist-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_toggle_album_monitoring_requires_album_id() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "toggle-album-monitoring"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_toggle_album_monitoring_requirements_satisfied() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"toggle-album-monitoring",
|
||||||
|
"--album-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_search_new_artist_requires_query() {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "search-new-artist"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_search_new_artist_requirements_satisfied() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"search-new-artist",
|
||||||
|
"--query",
|
||||||
|
"test query",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_start_task_requires_task_name() {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "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]
|
||||||
|
fn test_mark_history_item_as_failed_requires_history_item_id() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "mark-history-item-as-failed"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mark_history_item_as_failed_requirements_satisfied() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"mark-history-item-as-failed",
|
||||||
|
"--history-item-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_test_indexer_requires_indexer_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "test-indexer"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_test_indexer_requirements_satisfied() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"test-indexer",
|
||||||
|
"--indexer-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod handler {
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use mockall::predicate::eq;
|
||||||
|
use serde_json::json;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::cli::lidarr::add_command_handler::LidarrAddCommand;
|
||||||
|
use crate::cli::lidarr::edit_command_handler::LidarrEditCommand;
|
||||||
|
use crate::cli::lidarr::get_command_handler::LidarrGetCommand;
|
||||||
|
use crate::cli::lidarr::manual_search_command_handler::LidarrManualSearchCommand;
|
||||||
|
use crate::cli::lidarr::refresh_command_handler::LidarrRefreshCommand;
|
||||||
|
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::{
|
||||||
|
app::App,
|
||||||
|
cli::{
|
||||||
|
CliCommandHandler,
|
||||||
|
lidarr::{
|
||||||
|
LidarrCliHandler, LidarrCommand, delete_command_handler::LidarrDeleteCommand,
|
||||||
|
list_command_handler::LidarrListCommand,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
models::{
|
||||||
|
Serdeable,
|
||||||
|
lidarr_models::{Artist, DeleteParams, LidarrSerdeable},
|
||||||
|
},
|
||||||
|
network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_lidarr_cli_handler_delegates_add_commands_to_the_add_command_handler() {
|
||||||
|
let expected_tag_name = "test".to_owned();
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::AddTag(expected_tag_name.clone()).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let add_tag_command = LidarrCommand::Add(LidarrAddCommand::Tag {
|
||||||
|
name: expected_tag_name,
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = LidarrCliHandler::with(&app_arc, add_tag_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_lidarr_cli_handler_delegates_get_commands_to_the_get_command_handler() {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(LidarrEvent::GetStatus.into()))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let get_system_status_command = LidarrCommand::Get(LidarrGetCommand::SystemStatus);
|
||||||
|
|
||||||
|
let result = LidarrCliHandler::with(&app_arc, get_system_status_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_lidarr_cli_handler_delegates_delete_commands_to_the_delete_command_handler() {
|
||||||
|
let expected_delete_artist_params = DeleteParams {
|
||||||
|
id: 1,
|
||||||
|
delete_files: true,
|
||||||
|
add_import_list_exclusion: true,
|
||||||
|
};
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::DeleteArtist(expected_delete_artist_params).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let delete_artist_command = LidarrCommand::Delete(LidarrDeleteCommand::Artist {
|
||||||
|
artist_id: 1,
|
||||||
|
delete_files_from_disk: true,
|
||||||
|
add_list_exclusion: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = LidarrCliHandler::with(&app_arc, delete_artist_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_lidarr_cli_handler_delegates_edit_commands_to_the_edit_command_handler() {
|
||||||
|
let expected_edit_all_indexer_settings = IndexerSettings {
|
||||||
|
id: 1,
|
||||||
|
maximum_size: 1,
|
||||||
|
minimum_age: 1,
|
||||||
|
retention: 1,
|
||||||
|
rss_sync_interval: 1,
|
||||||
|
};
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::GetAllIndexerSettings.into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::IndexerSettings(
|
||||||
|
IndexerSettings {
|
||||||
|
id: 1,
|
||||||
|
maximum_size: 2,
|
||||||
|
minimum_age: 2,
|
||||||
|
retention: 2,
|
||||||
|
rss_sync_interval: 2,
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let edit_all_indexer_settings_command =
|
||||||
|
LidarrCommand::Edit(LidarrEditCommand::AllIndexerSettings {
|
||||||
|
maximum_size: Some(1),
|
||||||
|
minimum_age: Some(1),
|
||||||
|
retention: Some(1),
|
||||||
|
rss_sync_interval: Some(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = LidarrCliHandler::with(
|
||||||
|
&app_arc,
|
||||||
|
edit_all_indexer_settings_command,
|
||||||
|
&mut mock_network,
|
||||||
|
)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_lidarr_cli_handler_delegates_list_commands_to_the_list_command_handler() {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(LidarrEvent::ListArtists.into()))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Artists(vec![
|
||||||
|
Artist::default(),
|
||||||
|
])))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let list_artists_command = LidarrCommand::List(LidarrListCommand::Artists);
|
||||||
|
|
||||||
|
let result = LidarrCliHandler::with(&app_arc, list_artists_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_lidarr_cli_handler_delegates_refresh_commands_to_the_refresh_command_handler() {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(LidarrEvent::UpdateAllArtists.into()))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let refresh_artist_command = LidarrCommand::Refresh(LidarrRefreshCommand::AllArtists);
|
||||||
|
|
||||||
|
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()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_toggle_artist_monitoring_command() {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::ToggleArtistMonitoring(1).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let toggle_artist_monitoring_command = LidarrCommand::ToggleArtistMonitoring { artist_id: 1 };
|
||||||
|
|
||||||
|
let result = LidarrCliHandler::with(
|
||||||
|
&app_arc,
|
||||||
|
toggle_artist_monitoring_command,
|
||||||
|
&mut mock_network,
|
||||||
|
)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_search_new_artist_command() {
|
||||||
|
let expected_query = "test artist".to_owned();
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::SearchNewArtist(expected_query.clone()).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let search_new_artist_command = LidarrCommand::SearchNewArtist {
|
||||||
|
query: expected_query,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = LidarrCliHandler::with(&app_arc, search_new_artist_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_start_task_command() {
|
||||||
|
let expected_task_name = LidarrTaskName::ApplicationUpdateCheck;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::StartTask(expected_task_name).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let start_task_command = LidarrCommand::StartTask {
|
||||||
|
task_name: LidarrTaskName::ApplicationUpdateCheck,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = LidarrCliHandler::with(&app_arc, start_task_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_test_indexer_command() {
|
||||||
|
let expected_indexer_id = 1;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::TestIndexer(expected_indexer_id).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let test_indexer_command = LidarrCommand::TestIndexer { indexer_id: 1 };
|
||||||
|
|
||||||
|
let result = LidarrCliHandler::with(&app_arc, test_indexer_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_test_all_indexers_command() {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(LidarrEvent::TestAllIndexers.into()))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let test_all_indexers_command = LidarrCommand::TestAllIndexers;
|
||||||
|
|
||||||
|
let result = LidarrCliHandler::with(&app_arc, test_all_indexers_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_mark_history_item_as_failed_command() {
|
||||||
|
let expected_history_item_id = 1i64;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::MarkHistoryItemAsFailed(expected_history_item_id).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let mark_history_item_as_failed_command = LidarrCommand::MarkHistoryItemAsFailed {
|
||||||
|
history_item_id: expected_history_item_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = LidarrCliHandler::with(
|
||||||
|
&app_arc,
|
||||||
|
mark_history_item_as_failed_command,
|
||||||
|
&mut mock_network,
|
||||||
|
)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,350 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::Subcommand;
|
||||||
|
use serde_json::json;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use super::LidarrCommand;
|
||||||
|
use crate::models::Serdeable;
|
||||||
|
use crate::models::lidarr_models::{LidarrHistoryItem, LidarrSerdeable};
|
||||||
|
use crate::{
|
||||||
|
app::App,
|
||||||
|
cli::{CliCommandHandler, Command},
|
||||||
|
network::{NetworkTrait, lidarr_network::LidarrEvent},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "list_command_handler_tests.rs"]
|
||||||
|
mod list_command_handler_tests;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
||||||
|
pub enum LidarrListCommand {
|
||||||
|
#[command(about = "List all albums for the artist with the given ID")]
|
||||||
|
Albums {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The Lidarr ID of the artist whose albums you want to list",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
artist_id: i64,
|
||||||
|
},
|
||||||
|
#[command(
|
||||||
|
about = "Fetch all history events for the 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")]
|
||||||
|
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")]
|
||||||
|
History {
|
||||||
|
#[arg(long, help = "How many history events to fetch", default_value_t = 500)]
|
||||||
|
events: u64,
|
||||||
|
},
|
||||||
|
#[command(about = "List all Lidarr indexers")]
|
||||||
|
Indexers,
|
||||||
|
#[command(about = "Fetch Lidarr logs")]
|
||||||
|
Logs {
|
||||||
|
#[arg(long, help = "How many log events to fetch", default_value_t = 500)]
|
||||||
|
events: u64,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "Output the logs in the same format as they appear in the log files"
|
||||||
|
)]
|
||||||
|
output_in_log_format: bool,
|
||||||
|
},
|
||||||
|
#[command(about = "List all Lidarr metadata profiles")]
|
||||||
|
MetadataProfiles,
|
||||||
|
#[command(about = "List all Lidarr quality profiles")]
|
||||||
|
QualityProfiles,
|
||||||
|
#[command(about = "List all queued events")]
|
||||||
|
QueuedEvents,
|
||||||
|
#[command(about = "List all root folders in Lidarr")]
|
||||||
|
RootFolders,
|
||||||
|
#[command(about = "List all Lidarr tags")]
|
||||||
|
Tags,
|
||||||
|
#[command(about = "List all Lidarr tasks")]
|
||||||
|
Tasks,
|
||||||
|
#[command(about = "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 {
|
||||||
|
fn from(value: LidarrListCommand) -> Self {
|
||||||
|
Command::Lidarr(LidarrCommand::List(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct LidarrListCommandHandler<'a, 'b> {
|
||||||
|
app: &'a Arc<Mutex<App<'b>>>,
|
||||||
|
command: LidarrListCommand,
|
||||||
|
network: &'a mut dyn NetworkTrait,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrListCommand> for LidarrListCommandHandler<'a, 'b> {
|
||||||
|
fn with(
|
||||||
|
app: &'a Arc<Mutex<App<'b>>>,
|
||||||
|
command: LidarrListCommand,
|
||||||
|
network: &'a mut dyn NetworkTrait,
|
||||||
|
) -> Self {
|
||||||
|
LidarrListCommandHandler {
|
||||||
|
app,
|
||||||
|
command,
|
||||||
|
network,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle(self) -> Result<String> {
|
||||||
|
let result = match self.command {
|
||||||
|
LidarrListCommand::Albums { artist_id } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetAlbums(artist_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrListCommand::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 => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::ListArtists.into())
|
||||||
|
.await?;
|
||||||
|
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 } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetHistory(items).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrListCommand::Indexers => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetIndexers.into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrListCommand::Logs {
|
||||||
|
events,
|
||||||
|
output_in_log_format,
|
||||||
|
} => {
|
||||||
|
let logs = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetLogs(events).into())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if output_in_log_format {
|
||||||
|
let log_lines = &self.app.lock().await.data.sonarr_data.logs.items;
|
||||||
|
|
||||||
|
serde_json::to_string_pretty(log_lines)?
|
||||||
|
} else {
|
||||||
|
serde_json::to_string_pretty(&logs)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LidarrListCommand::MetadataProfiles => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetMetadataProfiles.into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrListCommand::QualityProfiles => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetQualityProfiles.into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrListCommand::QueuedEvents => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetQueuedEvents.into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrListCommand::RootFolders => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetRootFolders.into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrListCommand::Tags => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetTags.into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrListCommand::Tasks => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetTasks.into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrListCommand::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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,728 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::Cli;
|
||||||
|
use crate::cli::{
|
||||||
|
Command,
|
||||||
|
lidarr::{LidarrCommand, list_command_handler::LidarrListCommand},
|
||||||
|
};
|
||||||
|
use clap::CommandFactory;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_list_command_from() {
|
||||||
|
let command = LidarrListCommand::Artists;
|
||||||
|
|
||||||
|
let result = Command::from(command.clone());
|
||||||
|
|
||||||
|
assert_eq!(result, Command::Lidarr(LidarrCommand::List(command)));
|
||||||
|
}
|
||||||
|
|
||||||
|
mod cli {
|
||||||
|
use super::*;
|
||||||
|
use clap::{Parser, error::ErrorKind};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_list_commands_have_no_arg_requirements(
|
||||||
|
#[values(
|
||||||
|
"artists",
|
||||||
|
"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]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_albums_requires_artist_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "list", "albums"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_albums_with_artist_id() {
|
||||||
|
let expected_args = LidarrListCommand::Albums { artist_id: 1 };
|
||||||
|
let result =
|
||||||
|
Cli::try_parse_from(["managarr", "lidarr", "list", "albums", "--artist-id", "1"]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::List(album_command))) = result.unwrap().command
|
||||||
|
else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(album_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_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]
|
||||||
|
fn test_list_history_events_flag_requires_arguments() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "list", "history", "--events"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_history_default_values() {
|
||||||
|
let expected_args = LidarrListCommand::History { events: 500 };
|
||||||
|
let result = Cli::try_parse_from(["managarr", "lidarr", "list", "history"]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::List(history_command))) = result.unwrap().command
|
||||||
|
else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(history_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_logs_events_flag_requires_arguments() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "list", "logs", "--events"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_logs_default_values() {
|
||||||
|
let expected_args = LidarrListCommand::Logs {
|
||||||
|
events: 500,
|
||||||
|
output_in_log_format: false,
|
||||||
|
};
|
||||||
|
let result = Cli::try_parse_from(["managarr", "lidarr", "list", "logs"]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::List(logs_command))) = result.unwrap().command else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(logs_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 {
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use mockall::predicate::eq;
|
||||||
|
use pretty_assertions::assert_str_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
use serde_json::json;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::cli::CliCommandHandler;
|
||||||
|
use crate::cli::lidarr::list_command_handler::{LidarrListCommand, LidarrListCommandHandler};
|
||||||
|
use crate::models::Serdeable;
|
||||||
|
use crate::models::lidarr_models::{LidarrHistoryItem, LidarrSerdeable};
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::lidarr_history_item;
|
||||||
|
use crate::{
|
||||||
|
app::App,
|
||||||
|
network::{MockNetworkTrait, NetworkEvent},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[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::QualityProfiles, LidarrEvent::GetQualityProfiles)]
|
||||||
|
#[case(LidarrListCommand::QueuedEvents, LidarrEvent::GetQueuedEvents)]
|
||||||
|
#[case(LidarrListCommand::RootFolders, LidarrEvent::GetRootFolders)]
|
||||||
|
#[case(LidarrListCommand::Tags, LidarrEvent::GetTags)]
|
||||||
|
#[case(LidarrListCommand::Tasks, LidarrEvent::GetTasks)]
|
||||||
|
#[case(LidarrListCommand::Updates, LidarrEvent::GetUpdates)]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_list_command(
|
||||||
|
#[case] list_command: LidarrListCommand,
|
||||||
|
#[case] expected_lidarr_event: LidarrEvent,
|
||||||
|
) {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(expected_lidarr_event.into()))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
|
||||||
|
let result = LidarrListCommandHandler::with(&app_arc, list_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_list_albums_command() {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(LidarrEvent::GetAlbums(1).into()))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let list_command = LidarrListCommand::Albums { artist_id: 1 };
|
||||||
|
|
||||||
|
let result = LidarrListCommandHandler::with(&app_arc, list_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_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]
|
||||||
|
async fn test_handle_list_history_command() {
|
||||||
|
let expected_events = 1000;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::GetHistory(expected_events).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let list_history_command = LidarrListCommand::History { events: 1000 };
|
||||||
|
|
||||||
|
let result =
|
||||||
|
LidarrListCommandHandler::with(&app_arc, list_history_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_list_logs_command() {
|
||||||
|
let expected_events = 1000;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::GetLogs(expected_events).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let list_logs_command = LidarrListCommand::Logs {
|
||||||
|
events: 1000,
|
||||||
|
output_in_log_format: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = LidarrListCommandHandler::with(&app_arc, list_logs_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,295 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use add_command_handler::{LidarrAddCommand, LidarrAddCommandHandler};
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::Subcommand;
|
||||||
|
use delete_command_handler::{LidarrDeleteCommand, LidarrDeleteCommandHandler};
|
||||||
|
use edit_command_handler::{LidarrEditCommand, LidarrEditCommandHandler};
|
||||||
|
use get_command_handler::{LidarrGetCommand, LidarrGetCommandHandler};
|
||||||
|
use list_command_handler::{LidarrListCommand, LidarrListCommandHandler};
|
||||||
|
use refresh_command_handler::{LidarrRefreshCommand, LidarrRefreshCommandHandler};
|
||||||
|
use serde_json::json;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
use trigger_automatic_search_command_handler::{
|
||||||
|
LidarrTriggerAutomaticSearchCommand, LidarrTriggerAutomaticSearchCommandHandler,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{CliCommandHandler, Command};
|
||||||
|
use crate::cli::lidarr::manual_search_command_handler::{
|
||||||
|
LidarrManualSearchCommand, LidarrManualSearchCommandHandler,
|
||||||
|
};
|
||||||
|
use crate::models::lidarr_models::{LidarrReleaseDownloadBody, LidarrTaskName};
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use crate::{app::App, network::NetworkTrait};
|
||||||
|
|
||||||
|
mod add_command_handler;
|
||||||
|
mod delete_command_handler;
|
||||||
|
mod edit_command_handler;
|
||||||
|
mod get_command_handler;
|
||||||
|
mod list_command_handler;
|
||||||
|
mod manual_search_command_handler;
|
||||||
|
mod refresh_command_handler;
|
||||||
|
mod trigger_automatic_search_command_handler;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "lidarr_command_tests.rs"]
|
||||||
|
mod lidarr_command_tests;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
||||||
|
pub enum LidarrCommand {
|
||||||
|
#[command(
|
||||||
|
subcommand,
|
||||||
|
about = "Commands to add or create new resources within your Lidarr instance"
|
||||||
|
)]
|
||||||
|
Add(LidarrAddCommand),
|
||||||
|
#[command(
|
||||||
|
subcommand,
|
||||||
|
about = "Commands to delete resources from your Lidarr instance"
|
||||||
|
)]
|
||||||
|
Delete(LidarrDeleteCommand),
|
||||||
|
#[command(
|
||||||
|
subcommand,
|
||||||
|
about = "Commands to edit resources in your Lidarr instance"
|
||||||
|
)]
|
||||||
|
Edit(LidarrEditCommand),
|
||||||
|
#[command(
|
||||||
|
subcommand,
|
||||||
|
about = "Commands to fetch details of the resources in your Lidarr instance"
|
||||||
|
)]
|
||||||
|
Get(LidarrGetCommand),
|
||||||
|
#[command(
|
||||||
|
subcommand,
|
||||||
|
about = "Commands to list attributes from your Lidarr instance"
|
||||||
|
)]
|
||||||
|
List(LidarrListCommand),
|
||||||
|
#[command(
|
||||||
|
subcommand,
|
||||||
|
about = "Commands to refresh the data in your Lidarr instance"
|
||||||
|
)]
|
||||||
|
Refresh(LidarrRefreshCommand),
|
||||||
|
#[command(subcommand, about = "Commands to manually search for releases")]
|
||||||
|
ManualSearch(LidarrManualSearchCommand),
|
||||||
|
#[command(
|
||||||
|
subcommand,
|
||||||
|
about = "Commands to trigger automatic searches for releases of different resources in your Lidarr instance"
|
||||||
|
)]
|
||||||
|
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'")]
|
||||||
|
MarkHistoryItemAsFailed {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The Lidarr ID of the history item you wish to mark as 'failed'",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
history_item_id: i64,
|
||||||
|
},
|
||||||
|
#[command(about = "Search for a new artist to add to Lidarr")]
|
||||||
|
SearchNewArtist {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The name of the artist you want to search for",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
query: String,
|
||||||
|
},
|
||||||
|
#[command(about = "Start the specified Lidarr task")]
|
||||||
|
StartTask {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The name of the task to trigger",
|
||||||
|
value_enum,
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
task_name: LidarrTaskName,
|
||||||
|
},
|
||||||
|
#[command(
|
||||||
|
about = "Test the indexer with the given ID. Note that a successful test returns an empty JSON body; i.e. '{}'"
|
||||||
|
)]
|
||||||
|
TestIndexer {
|
||||||
|
#[arg(long, help = "The ID of the indexer to test", required = true)]
|
||||||
|
indexer_id: i64,
|
||||||
|
},
|
||||||
|
#[command(about = "Test all Lidarr indexers")]
|
||||||
|
TestAllIndexers,
|
||||||
|
#[command(
|
||||||
|
about = "Toggle monitoring for the specified album corresponding to the given album ID"
|
||||||
|
)]
|
||||||
|
ToggleAlbumMonitoring {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The Lidarr ID of the album to toggle monitoring on",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
album_id: i64,
|
||||||
|
},
|
||||||
|
#[command(
|
||||||
|
about = "Toggle monitoring for the specified artist corresponding to the given artist ID"
|
||||||
|
)]
|
||||||
|
ToggleArtistMonitoring {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The Lidarr ID of the artist to toggle monitoring on",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
artist_id: i64,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LidarrCommand> for Command {
|
||||||
|
fn from(lidarr_command: LidarrCommand) -> Command {
|
||||||
|
Command::Lidarr(lidarr_command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct LidarrCliHandler<'a, 'b> {
|
||||||
|
app: &'a Arc<Mutex<App<'b>>>,
|
||||||
|
command: LidarrCommand,
|
||||||
|
network: &'a mut dyn NetworkTrait,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrCommand> for LidarrCliHandler<'a, 'b> {
|
||||||
|
fn with(
|
||||||
|
app: &'a Arc<Mutex<App<'b>>>,
|
||||||
|
command: LidarrCommand,
|
||||||
|
network: &'a mut dyn NetworkTrait,
|
||||||
|
) -> Self {
|
||||||
|
LidarrCliHandler {
|
||||||
|
app,
|
||||||
|
command,
|
||||||
|
network,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle(self) -> Result<String> {
|
||||||
|
let result = match self.command {
|
||||||
|
LidarrCommand::Add(add_command) => {
|
||||||
|
LidarrAddCommandHandler::with(self.app, add_command, self.network)
|
||||||
|
.handle()
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
LidarrCommand::Delete(delete_command) => {
|
||||||
|
LidarrDeleteCommandHandler::with(self.app, delete_command, self.network)
|
||||||
|
.handle()
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
LidarrCommand::Edit(edit_command) => {
|
||||||
|
LidarrEditCommandHandler::with(self.app, edit_command, self.network)
|
||||||
|
.handle()
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
LidarrCommand::Get(get_command) => {
|
||||||
|
LidarrGetCommandHandler::with(self.app, get_command, self.network)
|
||||||
|
.handle()
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
LidarrCommand::List(list_command) => {
|
||||||
|
LidarrListCommandHandler::with(self.app, list_command, self.network)
|
||||||
|
.handle()
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
LidarrCommand::Refresh(refresh_command) => {
|
||||||
|
LidarrRefreshCommandHandler::with(self.app, refresh_command, self.network)
|
||||||
|
.handle()
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
LidarrCommand::ManualSearch(manual_search_command) => {
|
||||||
|
LidarrManualSearchCommandHandler::with(self.app, manual_search_command, self.network)
|
||||||
|
.handle()
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
LidarrCommand::TriggerAutomaticSearch(trigger_automatic_search_command) => {
|
||||||
|
LidarrTriggerAutomaticSearchCommandHandler::with(
|
||||||
|
self.app,
|
||||||
|
trigger_automatic_search_command,
|
||||||
|
self.network,
|
||||||
|
)
|
||||||
|
.handle()
|
||||||
|
.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 } => {
|
||||||
|
let _ = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::MarkHistoryItemAsFailed(history_item_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&json!({"message": "Lidarr history item marked as 'failed'"}))?
|
||||||
|
}
|
||||||
|
LidarrCommand::SearchNewArtist { query } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::SearchNewArtist(query).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrCommand::StartTask { task_name } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::StartTask(task_name).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrCommand::TestIndexer { indexer_id } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::TestIndexer(indexer_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrCommand::TestAllIndexers => {
|
||||||
|
println!("Testing all Lidarr indexers. This may take a minute...");
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::TestAllIndexers.into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrCommand::ToggleAlbumMonitoring { album_id } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::ToggleAlbumMonitoring(album_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrCommand::ToggleArtistMonitoring { artist_id } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::ToggleArtistMonitoring(artist_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use clap::Subcommand;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
app::App,
|
||||||
|
cli::{CliCommandHandler, Command},
|
||||||
|
network::{NetworkTrait, lidarr_network::LidarrEvent},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::LidarrCommand;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "refresh_command_handler_tests.rs"]
|
||||||
|
mod refresh_command_handler_tests;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
||||||
|
pub enum LidarrRefreshCommand {
|
||||||
|
#[command(about = "Refresh all artist data for all artists in your Lidarr library")]
|
||||||
|
AllArtists,
|
||||||
|
#[command(about = "Refresh artist data and scan disk for the artist with the given ID")]
|
||||||
|
Artist {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The ID of the artist to refresh information on and to scan the disk for",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
artist_id: i64,
|
||||||
|
},
|
||||||
|
#[command(about = "Refresh all downloads in Lidarr")]
|
||||||
|
Downloads,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LidarrRefreshCommand> for Command {
|
||||||
|
fn from(value: LidarrRefreshCommand) -> Self {
|
||||||
|
Command::Lidarr(LidarrCommand::Refresh(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct LidarrRefreshCommandHandler<'a, 'b> {
|
||||||
|
_app: &'a Arc<Mutex<App<'b>>>,
|
||||||
|
command: LidarrRefreshCommand,
|
||||||
|
network: &'a mut dyn NetworkTrait,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrRefreshCommand>
|
||||||
|
for LidarrRefreshCommandHandler<'a, 'b>
|
||||||
|
{
|
||||||
|
fn with(
|
||||||
|
_app: &'a Arc<Mutex<App<'b>>>,
|
||||||
|
command: LidarrRefreshCommand,
|
||||||
|
network: &'a mut dyn NetworkTrait,
|
||||||
|
) -> Self {
|
||||||
|
LidarrRefreshCommandHandler {
|
||||||
|
_app,
|
||||||
|
command,
|
||||||
|
network,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle(self) -> anyhow::Result<String> {
|
||||||
|
let result = match self.command {
|
||||||
|
LidarrRefreshCommand::AllArtists => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::UpdateAllArtists.into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrRefreshCommand::Artist { artist_id } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::UpdateAndScanArtist(artist_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
LidarrRefreshCommand::Downloads => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::UpdateDownloads.into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
use crate::Cli;
|
||||||
|
use crate::cli::{
|
||||||
|
Command,
|
||||||
|
lidarr::{LidarrCommand, refresh_command_handler::LidarrRefreshCommand},
|
||||||
|
};
|
||||||
|
use clap::CommandFactory;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_refresh_command_from() {
|
||||||
|
let command = LidarrRefreshCommand::AllArtists;
|
||||||
|
|
||||||
|
let result = Command::from(command.clone());
|
||||||
|
|
||||||
|
assert_eq!(result, Command::Lidarr(LidarrCommand::Refresh(command)));
|
||||||
|
}
|
||||||
|
|
||||||
|
mod cli {
|
||||||
|
use super::*;
|
||||||
|
use clap::{Parser, error::ErrorKind};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_refresh_commands_have_no_arg_requirements(
|
||||||
|
#[values("all-artists", "downloads")] subcommand: &str,
|
||||||
|
) {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "refresh", subcommand]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_refresh_artist_requires_artist_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "refresh", "artist"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_refresh_artist_with_artist_id() {
|
||||||
|
let expected_args = LidarrRefreshCommand::Artist { artist_id: 1 };
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"refresh",
|
||||||
|
"artist",
|
||||||
|
"--artist-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Refresh(refresh_command))) = result.unwrap().command
|
||||||
|
else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(refresh_command, expected_args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod handler {
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use mockall::predicate::eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
use serde_json::json;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::{app::App, cli::lidarr::refresh_command_handler::LidarrRefreshCommandHandler};
|
||||||
|
use crate::{
|
||||||
|
cli::{CliCommandHandler, lidarr::refresh_command_handler::LidarrRefreshCommand},
|
||||||
|
network::lidarr_network::LidarrEvent,
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
models::{Serdeable, lidarr_models::LidarrSerdeable},
|
||||||
|
network::{MockNetworkTrait, NetworkEvent},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(LidarrRefreshCommand::AllArtists, LidarrEvent::UpdateAllArtists)]
|
||||||
|
#[case(LidarrRefreshCommand::Downloads, LidarrEvent::UpdateDownloads)]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_refresh_command(
|
||||||
|
#[case] refresh_command: LidarrRefreshCommand,
|
||||||
|
#[case] expected_sonarr_event: LidarrEvent,
|
||||||
|
) {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(expected_sonarr_event.into()))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
|
||||||
|
let result = LidarrRefreshCommandHandler::with(&app_arc, refresh_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_refresh_artist_command() {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::UpdateAndScanArtist(1).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let refresh_command = LidarrRefreshCommand::Artist { artist_id: 1 };
|
||||||
|
|
||||||
|
let result = LidarrRefreshCommandHandler::with(&app_arc, refresh_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::Subcommand;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
app::App,
|
||||||
|
cli::{CliCommandHandler, Command},
|
||||||
|
network::{NetworkTrait, lidarr_network::LidarrEvent},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::LidarrCommand;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "trigger_automatic_search_command_handler_tests.rs"]
|
||||||
|
mod trigger_automatic_search_command_handler_tests;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
||||||
|
pub enum LidarrTriggerAutomaticSearchCommand {
|
||||||
|
#[command(about = "Trigger an automatic search for the 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")]
|
||||||
|
Artist {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The ID of the artist you want to trigger an automatic search for",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
artist_id: i64,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LidarrTriggerAutomaticSearchCommand> for Command {
|
||||||
|
fn from(value: LidarrTriggerAutomaticSearchCommand) -> Self {
|
||||||
|
Command::Lidarr(LidarrCommand::TriggerAutomaticSearch(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct LidarrTriggerAutomaticSearchCommandHandler<'a, 'b> {
|
||||||
|
_app: &'a Arc<Mutex<App<'b>>>,
|
||||||
|
command: LidarrTriggerAutomaticSearchCommand,
|
||||||
|
network: &'a mut dyn NetworkTrait,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrTriggerAutomaticSearchCommand>
|
||||||
|
for LidarrTriggerAutomaticSearchCommandHandler<'a, 'b>
|
||||||
|
{
|
||||||
|
fn with(
|
||||||
|
_app: &'a Arc<Mutex<App<'b>>>,
|
||||||
|
command: LidarrTriggerAutomaticSearchCommand,
|
||||||
|
network: &'a mut dyn NetworkTrait,
|
||||||
|
) -> Self {
|
||||||
|
LidarrTriggerAutomaticSearchCommandHandler {
|
||||||
|
_app,
|
||||||
|
command,
|
||||||
|
network,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle(self) -> Result<String> {
|
||||||
|
let result = match self.command {
|
||||||
|
LidarrTriggerAutomaticSearchCommand::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 } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::TriggerAutomaticArtistSearch(artist_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
use crate::Cli;
|
||||||
|
use crate::cli::{
|
||||||
|
Command,
|
||||||
|
lidarr::{
|
||||||
|
LidarrCommand, trigger_automatic_search_command_handler::LidarrTriggerAutomaticSearchCommand,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use clap::CommandFactory;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_trigger_automatic_search_command_from() {
|
||||||
|
let command = LidarrTriggerAutomaticSearchCommand::Artist { artist_id: 1 };
|
||||||
|
|
||||||
|
let result = Command::from(command.clone());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
Command::Lidarr(LidarrCommand::TriggerAutomaticSearch(command))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
mod cli {
|
||||||
|
use super::*;
|
||||||
|
use clap::error::ErrorKind;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trigger_automatic_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]
|
||||||
|
fn test_trigger_automatic_artist_search_requires_artist_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"trigger-automatic-search",
|
||||||
|
"artist",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trigger_automatic_artist_search_with_artist_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"trigger-automatic-search",
|
||||||
|
"artist",
|
||||||
|
"--artist-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod handler {
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use mockall::predicate::eq;
|
||||||
|
use serde_json::json;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::cli::lidarr::trigger_automatic_search_command_handler::{
|
||||||
|
LidarrTriggerAutomaticSearchCommand, LidarrTriggerAutomaticSearchCommandHandler,
|
||||||
|
};
|
||||||
|
use crate::{app::App, cli::CliCommandHandler};
|
||||||
|
use crate::{
|
||||||
|
models::{Serdeable, lidarr_models::LidarrSerdeable},
|
||||||
|
network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_trigger_automatic_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]
|
||||||
|
async fn test_handle_trigger_automatic_artist_search_command() {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::TriggerAutomaticArtistSearch(1).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let trigger_automatic_search_command =
|
||||||
|
LidarrTriggerAutomaticSearchCommand::Artist { artist_id: 1 };
|
||||||
|
|
||||||
|
let result = LidarrTriggerAutomaticSearchCommandHandler::with(
|
||||||
|
&app_arc,
|
||||||
|
trigger_automatic_search_command,
|
||||||
|
&mut mock_network,
|
||||||
|
)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,17 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{command, Subcommand};
|
use clap::Subcommand;
|
||||||
use clap_complete::Shell;
|
use clap_complete::Shell;
|
||||||
|
use indoc::indoc;
|
||||||
|
use lidarr::{LidarrCliHandler, LidarrCommand};
|
||||||
use radarr::{RadarrCliHandler, RadarrCommand};
|
use radarr::{RadarrCliHandler, RadarrCommand};
|
||||||
use sonarr::{SonarrCliHandler, SonarrCommand};
|
use sonarr::{SonarrCliHandler, SonarrCommand};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use crate::{app::App, network::NetworkTrait};
|
use crate::{app::App, network::NetworkTrait};
|
||||||
|
|
||||||
|
pub mod lidarr;
|
||||||
pub mod radarr;
|
pub mod radarr;
|
||||||
pub mod sonarr;
|
pub mod sonarr;
|
||||||
|
|
||||||
@@ -24,6 +27,9 @@ pub enum Command {
|
|||||||
#[command(subcommand, about = "Commands for manging your Sonarr instance")]
|
#[command(subcommand, about = "Commands for manging your Sonarr instance")]
|
||||||
Sonarr(SonarrCommand),
|
Sonarr(SonarrCommand),
|
||||||
|
|
||||||
|
#[command(subcommand, about = "Commands for manging your Lidarr instance")]
|
||||||
|
Lidarr(LidarrCommand),
|
||||||
|
|
||||||
#[command(
|
#[command(
|
||||||
arg_required_else_help = true,
|
arg_required_else_help = true,
|
||||||
about = "Generate shell completions for the Managarr CLI"
|
about = "Generate shell completions for the Managarr CLI"
|
||||||
@@ -38,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>> {
|
||||||
@@ -61,6 +73,11 @@ pub(crate) async fn handle_command(
|
|||||||
.handle()
|
.handle()
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
Command::Lidarr(lidarr_command) => {
|
||||||
|
LidarrCliHandler::with(app, lidarr_command, network)
|
||||||
|
.handle()
|
||||||
|
.await?
|
||||||
|
}
|
||||||
_ => String::new(),
|
_ => String::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{arg, command, ArgAction, Subcommand};
|
use clap::{ArgAction, Subcommand};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use super::RadarrCommand;
|
||||||
|
use crate::models::servarr_models::AddRootFolderBody;
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{CliCommandHandler, Command},
|
cli::{CliCommandHandler, Command},
|
||||||
models::radarr_models::{AddMovieBody, AddMovieOptions, MinimumAvailability, MovieMonitor},
|
models::radarr_models::{AddMovieBody, AddMovieOptions, MinimumAvailability, MovieMonitor},
|
||||||
network::{radarr_network::RadarrEvent, NetworkTrait},
|
network::{NetworkTrait, radarr_network::RadarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::RadarrCommand;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "add_command_handler_tests.rs"]
|
#[path = "add_command_handler_tests.rs"]
|
||||||
mod add_command_handler_tests;
|
mod add_command_handler_tests;
|
||||||
@@ -122,24 +122,28 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrAddCommand> for RadarrAddCommandHan
|
|||||||
title: String::new(),
|
title: String::new(),
|
||||||
root_folder_path,
|
root_folder_path,
|
||||||
quality_profile_id,
|
quality_profile_id,
|
||||||
minimum_availability: minimum_availability.to_string(),
|
minimum_availability,
|
||||||
monitored: !disable_monitoring,
|
monitored: !disable_monitoring,
|
||||||
tags,
|
tags,
|
||||||
|
tag_input_string: None,
|
||||||
add_options: AddMovieOptions {
|
add_options: AddMovieOptions {
|
||||||
monitor: monitor.to_string(),
|
monitor,
|
||||||
search_for_movie: !no_search_for_movie,
|
search_for_movie: !no_search_for_movie,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(RadarrEvent::AddMovie(Some(body)).into())
|
.handle_network_event(RadarrEvent::AddMovie(body).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrAddCommand::RootFolder { root_folder_path } => {
|
RadarrAddCommand::RootFolder { root_folder_path } => {
|
||||||
|
let add_root_folder_body = AddRootFolderBody {
|
||||||
|
path: root_folder_path,
|
||||||
|
};
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(RadarrEvent::AddRootFolder(Some(root_folder_path)).into())
|
.handle_network_event(RadarrEvent::AddRootFolder(add_root_folder_body).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use clap::{error::ErrorKind, CommandFactory, Parser};
|
use clap::{CommandFactory, Parser, error::ErrorKind};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
Cli,
|
||||||
cli::{
|
cli::{
|
||||||
radarr::{add_command_handler::RadarrAddCommand, RadarrCommand},
|
|
||||||
Command,
|
Command,
|
||||||
|
radarr::{RadarrCommand, add_command_handler::RadarrAddCommand},
|
||||||
},
|
},
|
||||||
models::radarr_models::{MinimumAvailability, MovieMonitor},
|
models::radarr_models::{MinimumAvailability, MovieMonitor},
|
||||||
Cli,
|
|
||||||
};
|
};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ mod tests {
|
|||||||
fn test_add_movie_requires_arguments() {
|
fn test_add_movie_requires_arguments() {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "add", "movie"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "add", "movie"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -52,7 +52,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -72,7 +72,7 @@ mod tests {
|
|||||||
"/test",
|
"/test",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -92,7 +92,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -117,7 +117,7 @@ mod tests {
|
|||||||
flag,
|
flag,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -156,7 +156,7 @@ mod tests {
|
|||||||
"test",
|
"test",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +177,7 @@ mod tests {
|
|||||||
"test",
|
"test",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,10 +207,11 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
if let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command {
|
let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||||
assert_eq!(add_command, expected_args);
|
panic!("Unexpected command type")
|
||||||
}
|
};
|
||||||
|
assert_eq!(add_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -243,10 +244,11 @@ mod tests {
|
|||||||
"2",
|
"2",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
if let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command {
|
let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||||
assert_eq!(add_command, expected_args);
|
panic!("Unexpected command type")
|
||||||
}
|
};
|
||||||
|
assert_eq!(add_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -285,10 +287,11 @@ mod tests {
|
|||||||
"--no-search-for-movie",
|
"--no-search-for-movie",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
if let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command {
|
let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||||
assert_eq!(add_command, expected_args);
|
panic!("Unexpected command type")
|
||||||
}
|
};
|
||||||
|
assert_eq!(add_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -296,7 +299,7 @@ mod tests {
|
|||||||
let result =
|
let result =
|
||||||
Cli::command().try_get_matches_from(["managarr", "radarr", "add", "root-folder"]);
|
Cli::command().try_get_matches_from(["managarr", "radarr", "add", "root-folder"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -318,18 +321,19 @@ mod tests {
|
|||||||
"/nfs/test",
|
"/nfs/test",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command {
|
let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||||
assert_eq!(add_command, expected_args);
|
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", "radarr", "add", "tag"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "add", "tag"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -344,11 +348,12 @@ mod tests {
|
|||||||
|
|
||||||
let result = Cli::try_parse_from(["managarr", "radarr", "add", "tag", "--name", "test"]);
|
let result = Cli::try_parse_from(["managarr", "radarr", "add", "tag", "--name", "test"]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command {
|
let Some(Command::Radarr(RadarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||||
assert_eq!(add_command, expected_args);
|
panic!("Unexpected command type")
|
||||||
}
|
};
|
||||||
|
assert_eq!(add_command, expected_args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,17 +362,18 @@ mod tests {
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{radarr::add_command_handler::RadarrAddCommandHandler, CliCommandHandler},
|
cli::{CliCommandHandler, radarr::add_command_handler::RadarrAddCommandHandler},
|
||||||
models::{
|
models::{
|
||||||
radarr_models::{AddMovieBody, AddMovieOptions, RadarrSerdeable},
|
|
||||||
Serdeable,
|
Serdeable,
|
||||||
|
radarr_models::{AddMovieBody, AddMovieOptions, RadarrSerdeable},
|
||||||
},
|
},
|
||||||
network::{radarr_network::RadarrEvent, MockNetworkTrait, NetworkEvent},
|
network::{MockNetworkTrait, NetworkEvent, radarr_network::RadarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use mockall::predicate::eq;
|
use mockall::predicate::eq;
|
||||||
|
|
||||||
|
use crate::models::servarr_models::AddRootFolderBody;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
@@ -378,11 +384,12 @@ mod tests {
|
|||||||
title: String::new(),
|
title: String::new(),
|
||||||
root_folder_path: "/test".to_owned(),
|
root_folder_path: "/test".to_owned(),
|
||||||
quality_profile_id: 1,
|
quality_profile_id: 1,
|
||||||
minimum_availability: "released".to_owned(),
|
minimum_availability: MinimumAvailability::Released,
|
||||||
monitored: false,
|
monitored: false,
|
||||||
tags: vec![1, 2],
|
tags: vec![1, 2],
|
||||||
|
tag_input_string: None,
|
||||||
add_options: AddMovieOptions {
|
add_options: AddMovieOptions {
|
||||||
monitor: "movieAndCollection".to_owned(),
|
monitor: MovieMonitor::MovieAndCollection,
|
||||||
search_for_movie: false,
|
search_for_movie: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -390,7 +397,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::AddMovie(Some(expected_add_movie_body)).into(),
|
RadarrEvent::AddMovie(expected_add_movie_body).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -398,7 +405,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let add_movie_command = RadarrAddCommand::Movie {
|
let add_movie_command = RadarrAddCommand::Movie {
|
||||||
tmdb_id: 1,
|
tmdb_id: 1,
|
||||||
root_folder_path: "/test".to_owned(),
|
root_folder_path: "/test".to_owned(),
|
||||||
@@ -414,17 +421,20 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_handle_add_root_folder_command() {
|
async fn test_handle_add_root_folder_command() {
|
||||||
let expected_root_folder_path = "/nfs/test".to_owned();
|
let expected_root_folder_path = "/nfs/test".to_owned();
|
||||||
|
let expected_add_root_folder_body = AddRootFolderBody {
|
||||||
|
path: expected_root_folder_path.clone(),
|
||||||
|
};
|
||||||
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>(
|
||||||
RadarrEvent::AddRootFolder(Some(expected_root_folder_path.clone())).into(),
|
RadarrEvent::AddRootFolder(expected_add_root_folder_body).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -432,7 +442,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let add_root_folder_command = RadarrAddCommand::RootFolder {
|
let add_root_folder_command = RadarrAddCommand::RootFolder {
|
||||||
root_folder_path: expected_root_folder_path,
|
root_folder_path: expected_root_folder_path,
|
||||||
};
|
};
|
||||||
@@ -442,7 +452,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -460,7 +470,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let add_tag_command = RadarrAddCommand::Tag {
|
let add_tag_command = RadarrAddCommand::Tag {
|
||||||
name: expected_tag_name,
|
name: expected_tag_name,
|
||||||
};
|
};
|
||||||
@@ -469,7 +479,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use crate::{
|
|||||||
app::App,
|
app::App,
|
||||||
cli::{CliCommandHandler, Command},
|
cli::{CliCommandHandler, Command},
|
||||||
models::radarr_models::DeleteMovieParams,
|
models::radarr_models::DeleteMovieParams,
|
||||||
network::{radarr_network::RadarrEvent, NetworkTrait},
|
network::{NetworkTrait, radarr_network::RadarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::RadarrCommand;
|
use super::RadarrCommand;
|
||||||
@@ -89,21 +89,21 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrDeleteCommand> for RadarrDeleteComm
|
|||||||
RadarrDeleteCommand::BlocklistItem { blocklist_item_id } => {
|
RadarrDeleteCommand::BlocklistItem { blocklist_item_id } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(RadarrEvent::DeleteBlocklistItem(Some(blocklist_item_id)).into())
|
.handle_network_event(RadarrEvent::DeleteBlocklistItem(blocklist_item_id).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrDeleteCommand::Download { download_id } => {
|
RadarrDeleteCommand::Download { download_id } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(RadarrEvent::DeleteDownload(Some(download_id)).into())
|
.handle_network_event(RadarrEvent::DeleteDownload(download_id).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrDeleteCommand::Indexer { indexer_id } => {
|
RadarrDeleteCommand::Indexer { indexer_id } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(RadarrEvent::DeleteIndexer(Some(indexer_id)).into())
|
.handle_network_event(RadarrEvent::DeleteIndexer(indexer_id).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
@@ -119,14 +119,14 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrDeleteCommand> for RadarrDeleteComm
|
|||||||
};
|
};
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(RadarrEvent::DeleteMovie(Some(delete_movie_params)).into())
|
.handle_network_event(RadarrEvent::DeleteMovie(delete_movie_params).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrDeleteCommand::RootFolder { root_folder_id } => {
|
RadarrDeleteCommand::RootFolder { root_folder_id } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(RadarrEvent::DeleteRootFolder(Some(root_folder_id)).into())
|
.handle_network_event(RadarrEvent::DeleteRootFolder(root_folder_id).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
cli::{
|
|
||||||
radarr::{delete_command_handler::RadarrDeleteCommand, RadarrCommand},
|
|
||||||
Command,
|
|
||||||
},
|
|
||||||
Cli,
|
Cli,
|
||||||
|
cli::{
|
||||||
|
Command,
|
||||||
|
radarr::{RadarrCommand, delete_command_handler::RadarrDeleteCommand},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use clap::{error::ErrorKind, CommandFactory, Parser};
|
use clap::{CommandFactory, Parser, error::ErrorKind};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -30,7 +30,7 @@ mod tests {
|
|||||||
let result =
|
let result =
|
||||||
Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "blocklist-item"]);
|
Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "blocklist-item"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -52,12 +52,13 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
{
|
else {
|
||||||
assert_eq!(delete_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(delete_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -65,7 +66,7 @@ mod tests {
|
|||||||
let result =
|
let result =
|
||||||
Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "download"]);
|
Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "download"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -85,19 +86,20 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
{
|
else {
|
||||||
assert_eq!(delete_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(delete_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_delete_indexer_requires_arguments() {
|
fn test_delete_indexer_requires_arguments() {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "indexer"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "indexer"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -117,19 +119,20 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
{
|
else {
|
||||||
assert_eq!(delete_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(delete_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_delete_movie_requires_arguments() {
|
fn test_delete_movie_requires_arguments() {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "movie"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "movie"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -147,12 +150,13 @@ mod tests {
|
|||||||
let result =
|
let result =
|
||||||
Cli::try_parse_from(["managarr", "radarr", "delete", "movie", "--movie-id", "1"]);
|
Cli::try_parse_from(["managarr", "radarr", "delete", "movie", "--movie-id", "1"]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
{
|
else {
|
||||||
assert_eq!(delete_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(delete_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -174,12 +178,13 @@ mod tests {
|
|||||||
"--add-list-exclusion",
|
"--add-list-exclusion",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
{
|
else {
|
||||||
assert_eq!(delete_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(delete_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -187,7 +192,7 @@ mod tests {
|
|||||||
let result =
|
let result =
|
||||||
Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "root-folder"]);
|
Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "root-folder"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -207,19 +212,20 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
{
|
else {
|
||||||
assert_eq!(delete_command, expected_args);
|
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", "radarr", "delete", "tag"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "delete", "tag"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -232,12 +238,13 @@ mod tests {
|
|||||||
|
|
||||||
let result = Cli::try_parse_from(["managarr", "radarr", "delete", "tag", "--tag-id", "1"]);
|
let result = Cli::try_parse_from(["managarr", "radarr", "delete", "tag", "--tag-id", "1"]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
let Some(Command::Radarr(RadarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
{
|
else {
|
||||||
assert_eq!(delete_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(delete_command, expected_args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,14 +258,14 @@ mod tests {
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{
|
cli::{
|
||||||
radarr::delete_command_handler::{RadarrDeleteCommand, RadarrDeleteCommandHandler},
|
|
||||||
CliCommandHandler,
|
CliCommandHandler,
|
||||||
|
radarr::delete_command_handler::{RadarrDeleteCommand, RadarrDeleteCommandHandler},
|
||||||
},
|
},
|
||||||
models::{
|
models::{
|
||||||
radarr_models::{DeleteMovieParams, RadarrSerdeable},
|
|
||||||
Serdeable,
|
Serdeable,
|
||||||
|
radarr_models::{DeleteMovieParams, RadarrSerdeable},
|
||||||
},
|
},
|
||||||
network::{radarr_network::RadarrEvent, MockNetworkTrait, NetworkEvent},
|
network::{MockNetworkTrait, NetworkEvent, radarr_network::RadarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -268,7 +275,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::DeleteBlocklistItem(Some(expected_blocklist_item_id)).into(),
|
RadarrEvent::DeleteBlocklistItem(expected_blocklist_item_id).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -276,7 +283,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let delete_blocklist_item_command = RadarrDeleteCommand::BlocklistItem {
|
let delete_blocklist_item_command = RadarrDeleteCommand::BlocklistItem {
|
||||||
blocklist_item_id: 1,
|
blocklist_item_id: 1,
|
||||||
};
|
};
|
||||||
@@ -289,7 +296,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -299,7 +306,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::DeleteDownload(Some(expected_download_id)).into(),
|
RadarrEvent::DeleteDownload(expected_download_id).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -307,7 +314,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let delete_download_command = RadarrDeleteCommand::Download { download_id: 1 };
|
let delete_download_command = RadarrDeleteCommand::Download { download_id: 1 };
|
||||||
|
|
||||||
let result =
|
let result =
|
||||||
@@ -315,7 +322,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -325,7 +332,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::DeleteIndexer(Some(expected_indexer_id)).into(),
|
RadarrEvent::DeleteIndexer(expected_indexer_id).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -333,7 +340,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let delete_indexer_command = RadarrDeleteCommand::Indexer { indexer_id: 1 };
|
let delete_indexer_command = RadarrDeleteCommand::Indexer { indexer_id: 1 };
|
||||||
|
|
||||||
let result =
|
let result =
|
||||||
@@ -341,7 +348,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -355,7 +362,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::DeleteMovie(Some(expected_delete_movie_params)).into(),
|
RadarrEvent::DeleteMovie(expected_delete_movie_params).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -363,7 +370,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let delete_movie_command = RadarrDeleteCommand::Movie {
|
let delete_movie_command = RadarrDeleteCommand::Movie {
|
||||||
movie_id: 1,
|
movie_id: 1,
|
||||||
delete_files_from_disk: true,
|
delete_files_from_disk: true,
|
||||||
@@ -375,7 +382,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -385,7 +392,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::DeleteRootFolder(Some(expected_root_folder_id)).into(),
|
RadarrEvent::DeleteRootFolder(expected_root_folder_id).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -393,7 +400,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let delete_root_folder_command = RadarrDeleteCommand::RootFolder { root_folder_id: 1 };
|
let delete_root_folder_command = RadarrDeleteCommand::RootFolder { root_folder_id: 1 };
|
||||||
|
|
||||||
let result =
|
let result =
|
||||||
@@ -401,7 +408,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -419,7 +426,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let delete_tag_command = RadarrDeleteCommand::Tag { tag_id: 1 };
|
let delete_tag_command = RadarrDeleteCommand::Tag { tag_id: 1 };
|
||||||
|
|
||||||
let result =
|
let result =
|
||||||
@@ -427,7 +434,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ use tokio::sync::Mutex;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{mutex_flags_or_default, mutex_flags_or_option, CliCommandHandler, Command},
|
cli::{CliCommandHandler, Command, mutex_flags_or_default, mutex_flags_or_option},
|
||||||
models::{
|
models::{
|
||||||
|
Serdeable,
|
||||||
radarr_models::{
|
radarr_models::{
|
||||||
EditCollectionParams, EditMovieParams, IndexerSettings, MinimumAvailability, RadarrSerdeable,
|
EditCollectionParams, EditMovieParams, IndexerSettings, MinimumAvailability, RadarrSerdeable,
|
||||||
},
|
},
|
||||||
servarr_models::EditIndexerParams,
|
servarr_models::EditIndexerParams,
|
||||||
Serdeable,
|
|
||||||
},
|
},
|
||||||
network::{radarr_network::RadarrEvent, NetworkTrait},
|
network::{NetworkTrait, radarr_network::RadarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::RadarrCommand;
|
use super::RadarrCommand;
|
||||||
@@ -379,18 +379,12 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrEditCommand> for RadarrEditCommandH
|
|||||||
rss_sync_interval: rss_sync_interval
|
rss_sync_interval: rss_sync_interval
|
||||||
.unwrap_or(previous_indexer_settings.rss_sync_interval),
|
.unwrap_or(previous_indexer_settings.rss_sync_interval),
|
||||||
whitelisted_hardcoded_subs: whitelisted_subtitle_tags
|
whitelisted_hardcoded_subs: whitelisted_subtitle_tags
|
||||||
.clone()
|
.unwrap_or(previous_indexer_settings.whitelisted_hardcoded_subs.text)
|
||||||
.unwrap_or_else(|| {
|
|
||||||
previous_indexer_settings
|
|
||||||
.whitelisted_hardcoded_subs
|
|
||||||
.text
|
|
||||||
.clone()
|
|
||||||
})
|
|
||||||
.into(),
|
.into(),
|
||||||
};
|
};
|
||||||
self
|
self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(RadarrEvent::EditAllIndexerSettings(Some(params)).into())
|
.handle_network_event(RadarrEvent::EditAllIndexerSettings(params).into())
|
||||||
.await?;
|
.await?;
|
||||||
"All indexer settings updated".to_owned()
|
"All indexer settings updated".to_owned()
|
||||||
} else {
|
} else {
|
||||||
@@ -420,7 +414,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrEditCommand> for RadarrEditCommandH
|
|||||||
};
|
};
|
||||||
self
|
self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(RadarrEvent::EditCollection(Some(edit_collection_params)).into())
|
.handle_network_event(RadarrEvent::EditCollection(edit_collection_params).into())
|
||||||
.await?;
|
.await?;
|
||||||
"Collection updated".to_owned()
|
"Collection updated".to_owned()
|
||||||
}
|
}
|
||||||
@@ -455,13 +449,14 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrEditCommand> for RadarrEditCommandH
|
|||||||
api_key,
|
api_key,
|
||||||
seed_ratio,
|
seed_ratio,
|
||||||
tags: tag,
|
tags: tag,
|
||||||
|
tag_input_string: None,
|
||||||
priority,
|
priority,
|
||||||
clear_tags,
|
clear_tags,
|
||||||
};
|
};
|
||||||
|
|
||||||
self
|
self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(RadarrEvent::EditIndexer(Some(edit_indexer_params)).into())
|
.handle_network_event(RadarrEvent::EditIndexer(edit_indexer_params).into())
|
||||||
.await?;
|
.await?;
|
||||||
"Indexer updated".to_owned()
|
"Indexer updated".to_owned()
|
||||||
}
|
}
|
||||||
@@ -483,12 +478,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrEditCommand> for RadarrEditCommandH
|
|||||||
quality_profile_id,
|
quality_profile_id,
|
||||||
root_folder_path,
|
root_folder_path,
|
||||||
tags: tag,
|
tags: tag,
|
||||||
|
tag_input_string: None,
|
||||||
clear_tags,
|
clear_tags,
|
||||||
};
|
};
|
||||||
|
|
||||||
self
|
self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(RadarrEvent::EditMovie(Some(edit_movie_params)).into())
|
.handle_network_event(RadarrEvent::EditMovie(edit_movie_params).into())
|
||||||
.await?;
|
.await?;
|
||||||
"Movie Updated".to_owned()
|
"Movie Updated".to_owned()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
cli::{
|
|
||||||
radarr::{edit_command_handler::RadarrEditCommand, RadarrCommand},
|
|
||||||
Command,
|
|
||||||
},
|
|
||||||
Cli,
|
Cli,
|
||||||
|
cli::{
|
||||||
|
Command,
|
||||||
|
radarr::{RadarrCommand, edit_command_handler::RadarrEditCommand},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use clap::{error::ErrorKind, CommandFactory, Parser};
|
use clap::{CommandFactory, Parser, error::ErrorKind};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -42,7 +42,7 @@ mod tests {
|
|||||||
let result =
|
let result =
|
||||||
Cli::command().try_get_matches_from(["managarr", "radarr", "edit", "all-indexer-settings"]);
|
Cli::command().try_get_matches_from(["managarr", "radarr", "edit", "all-indexer-settings"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -60,7 +60,7 @@ mod tests {
|
|||||||
"--disable-allow-hardcoded-subs",
|
"--disable-allow-hardcoded-subs",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ mod tests {
|
|||||||
"--disable-prefer-indexer-flags",
|
"--disable-prefer-indexer-flags",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ mod tests {
|
|||||||
flag,
|
flag,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,11 +126,12 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command {
|
let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||||
assert_eq!(edit_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(edit_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -168,11 +169,12 @@ mod tests {
|
|||||||
"test",
|
"test",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command {
|
let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||||
assert_eq!(edit_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(edit_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -180,7 +182,7 @@ mod tests {
|
|||||||
let result =
|
let result =
|
||||||
Cli::command().try_get_matches_from(["managarr", "radarr", "edit", "collection"]);
|
Cli::command().try_get_matches_from(["managarr", "radarr", "edit", "collection"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -198,7 +200,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -218,7 +220,7 @@ mod tests {
|
|||||||
"--disable-monitoring",
|
"--disable-monitoring",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +237,7 @@ mod tests {
|
|||||||
"--disable-search-on-add",
|
"--disable-search-on-add",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,7 +254,7 @@ mod tests {
|
|||||||
"test",
|
"test",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,7 +272,7 @@ mod tests {
|
|||||||
flag,
|
flag,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,11 +300,12 @@ mod tests {
|
|||||||
"/test",
|
"/test",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command {
|
let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||||
assert_eq!(edit_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(edit_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -334,18 +337,19 @@ mod tests {
|
|||||||
"--search-on-add",
|
"--search-on-add",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command {
|
let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||||
assert_eq!(edit_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(edit_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edit_indexer_requires_arguments() {
|
fn test_edit_indexer_requires_arguments() {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "edit", "indexer"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "edit", "indexer"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -363,7 +367,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -383,7 +387,7 @@ mod tests {
|
|||||||
"--disable-rss",
|
"--disable-rss",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,7 +404,7 @@ mod tests {
|
|||||||
"--disable-automatic-search",
|
"--disable-automatic-search",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,7 +421,7 @@ mod tests {
|
|||||||
"--disable-interactive-search",
|
"--disable-interactive-search",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,7 +439,7 @@ mod tests {
|
|||||||
"--clear-tags",
|
"--clear-tags",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,7 +457,7 @@ mod tests {
|
|||||||
flag,
|
flag,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -487,11 +491,12 @@ mod tests {
|
|||||||
"Test",
|
"Test",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command {
|
let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||||
assert_eq!(edit_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(edit_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -526,11 +531,12 @@ mod tests {
|
|||||||
"2",
|
"2",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command {
|
let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||||
assert_eq!(edit_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(edit_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -578,18 +584,19 @@ mod tests {
|
|||||||
"25",
|
"25",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command {
|
let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||||
assert_eq!(edit_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(edit_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edit_movie_requires_arguments() {
|
fn test_edit_movie_requires_arguments() {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "edit", "movie"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "edit", "movie"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -607,7 +614,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -627,7 +634,7 @@ mod tests {
|
|||||||
"--disable-monitoring",
|
"--disable-monitoring",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -645,7 +652,7 @@ mod tests {
|
|||||||
"--clear-tags",
|
"--clear-tags",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -669,7 +676,7 @@ mod tests {
|
|||||||
flag,
|
flag,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -686,7 +693,7 @@ mod tests {
|
|||||||
"test",
|
"test",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -714,11 +721,12 @@ mod tests {
|
|||||||
"/nfs/test",
|
"/nfs/test",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command {
|
let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||||
assert_eq!(edit_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(edit_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -747,11 +755,12 @@ mod tests {
|
|||||||
"2",
|
"2",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command {
|
let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||||
assert_eq!(edit_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(edit_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -787,11 +796,12 @@ mod tests {
|
|||||||
"2",
|
"2",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command {
|
let Some(Command::Radarr(RadarrCommand::Edit(edit_command))) = result.unwrap().command else {
|
||||||
assert_eq!(edit_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(edit_command, expected_args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -805,18 +815,18 @@ mod tests {
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{
|
cli::{
|
||||||
radarr::edit_command_handler::{RadarrEditCommand, RadarrEditCommandHandler},
|
|
||||||
CliCommandHandler,
|
CliCommandHandler,
|
||||||
|
radarr::edit_command_handler::{RadarrEditCommand, RadarrEditCommandHandler},
|
||||||
},
|
},
|
||||||
models::{
|
models::{
|
||||||
|
Serdeable,
|
||||||
radarr_models::{
|
radarr_models::{
|
||||||
EditCollectionParams, EditMovieParams, IndexerSettings, MinimumAvailability,
|
EditCollectionParams, EditMovieParams, IndexerSettings, MinimumAvailability,
|
||||||
RadarrSerdeable,
|
RadarrSerdeable,
|
||||||
},
|
},
|
||||||
servarr_models::EditIndexerParams,
|
servarr_models::EditIndexerParams,
|
||||||
Serdeable,
|
|
||||||
},
|
},
|
||||||
network::{radarr_network::RadarrEvent, MockNetworkTrait, NetworkEvent},
|
network::{MockNetworkTrait, NetworkEvent, radarr_network::RadarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -857,7 +867,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::EditAllIndexerSettings(Some(expected_edit_all_indexer_settings)).into(),
|
RadarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -865,7 +875,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let edit_all_indexer_settings_command = RadarrEditCommand::AllIndexerSettings {
|
let edit_all_indexer_settings_command = RadarrEditCommand::AllIndexerSettings {
|
||||||
allow_hardcoded_subs: true,
|
allow_hardcoded_subs: true,
|
||||||
disable_allow_hardcoded_subs: false,
|
disable_allow_hardcoded_subs: false,
|
||||||
@@ -887,7 +897,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -928,7 +938,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::EditAllIndexerSettings(Some(expected_edit_all_indexer_settings)).into(),
|
RadarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -936,7 +946,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let edit_all_indexer_settings_command = RadarrEditCommand::AllIndexerSettings {
|
let edit_all_indexer_settings_command = RadarrEditCommand::AllIndexerSettings {
|
||||||
allow_hardcoded_subs: false,
|
allow_hardcoded_subs: false,
|
||||||
disable_allow_hardcoded_subs: true,
|
disable_allow_hardcoded_subs: true,
|
||||||
@@ -958,12 +968,12 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_handle_edit_all_indexer_settings_command_unprovided_values_default_to_previous_values(
|
async fn test_handle_edit_all_indexer_settings_command_unprovided_values_default_to_previous_values()
|
||||||
) {
|
{
|
||||||
let expected_edit_all_indexer_settings = IndexerSettings {
|
let expected_edit_all_indexer_settings = IndexerSettings {
|
||||||
allow_hardcoded_subs: true,
|
allow_hardcoded_subs: true,
|
||||||
availability_delay: 2,
|
availability_delay: 2,
|
||||||
@@ -1000,7 +1010,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::EditAllIndexerSettings(Some(expected_edit_all_indexer_settings)).into(),
|
RadarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -1008,7 +1018,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let edit_all_indexer_settings_command = RadarrEditCommand::AllIndexerSettings {
|
let edit_all_indexer_settings_command = RadarrEditCommand::AllIndexerSettings {
|
||||||
allow_hardcoded_subs: false,
|
allow_hardcoded_subs: false,
|
||||||
disable_allow_hardcoded_subs: false,
|
disable_allow_hardcoded_subs: false,
|
||||||
@@ -1030,7 +1040,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -1047,7 +1057,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::EditCollection(Some(expected_edit_collection_params)).into(),
|
RadarrEvent::EditCollection(expected_edit_collection_params).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -1055,7 +1065,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let edit_collection_command = RadarrEditCommand::Collection {
|
let edit_collection_command = RadarrEditCommand::Collection {
|
||||||
collection_id: 1,
|
collection_id: 1,
|
||||||
enable_monitoring: true,
|
enable_monitoring: true,
|
||||||
@@ -1072,7 +1082,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -1089,7 +1099,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::EditCollection(Some(expected_edit_collection_params)).into(),
|
RadarrEvent::EditCollection(expected_edit_collection_params).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -1097,7 +1107,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let edit_collection_command = RadarrEditCommand::Collection {
|
let edit_collection_command = RadarrEditCommand::Collection {
|
||||||
collection_id: 1,
|
collection_id: 1,
|
||||||
enable_monitoring: false,
|
enable_monitoring: false,
|
||||||
@@ -1114,7 +1124,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -1131,7 +1141,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::EditCollection(Some(expected_edit_collection_params)).into(),
|
RadarrEvent::EditCollection(expected_edit_collection_params).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -1139,7 +1149,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let edit_collection_command = RadarrEditCommand::Collection {
|
let edit_collection_command = RadarrEditCommand::Collection {
|
||||||
collection_id: 1,
|
collection_id: 1,
|
||||||
enable_monitoring: false,
|
enable_monitoring: false,
|
||||||
@@ -1156,7 +1166,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -1171,6 +1181,7 @@ mod tests {
|
|||||||
api_key: Some("testKey".to_owned()),
|
api_key: Some("testKey".to_owned()),
|
||||||
seed_ratio: Some("1.2".to_owned()),
|
seed_ratio: Some("1.2".to_owned()),
|
||||||
tags: Some(vec![1, 2]),
|
tags: Some(vec![1, 2]),
|
||||||
|
tag_input_string: None,
|
||||||
priority: Some(25),
|
priority: Some(25),
|
||||||
clear_tags: false,
|
clear_tags: false,
|
||||||
};
|
};
|
||||||
@@ -1178,7 +1189,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::EditIndexer(Some(expected_edit_indexer_params)).into(),
|
RadarrEvent::EditIndexer(expected_edit_indexer_params).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -1186,7 +1197,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let edit_indexer_command = RadarrEditCommand::Indexer {
|
let edit_indexer_command = RadarrEditCommand::Indexer {
|
||||||
indexer_id: 1,
|
indexer_id: 1,
|
||||||
name: Some("Test".to_owned()),
|
name: Some("Test".to_owned()),
|
||||||
@@ -1209,7 +1220,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -1224,6 +1235,7 @@ mod tests {
|
|||||||
api_key: Some("testKey".to_owned()),
|
api_key: Some("testKey".to_owned()),
|
||||||
seed_ratio: Some("1.2".to_owned()),
|
seed_ratio: Some("1.2".to_owned()),
|
||||||
tags: Some(vec![1, 2]),
|
tags: Some(vec![1, 2]),
|
||||||
|
tag_input_string: None,
|
||||||
priority: Some(25),
|
priority: Some(25),
|
||||||
clear_tags: false,
|
clear_tags: false,
|
||||||
};
|
};
|
||||||
@@ -1231,7 +1243,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::EditIndexer(Some(expected_edit_indexer_params)).into(),
|
RadarrEvent::EditIndexer(expected_edit_indexer_params).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -1239,7 +1251,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let edit_indexer_command = RadarrEditCommand::Indexer {
|
let edit_indexer_command = RadarrEditCommand::Indexer {
|
||||||
indexer_id: 1,
|
indexer_id: 1,
|
||||||
name: Some("Test".to_owned()),
|
name: Some("Test".to_owned()),
|
||||||
@@ -1262,7 +1274,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -1277,6 +1289,7 @@ mod tests {
|
|||||||
api_key: Some("testKey".to_owned()),
|
api_key: Some("testKey".to_owned()),
|
||||||
seed_ratio: Some("1.2".to_owned()),
|
seed_ratio: Some("1.2".to_owned()),
|
||||||
tags: Some(vec![1, 2]),
|
tags: Some(vec![1, 2]),
|
||||||
|
tag_input_string: None,
|
||||||
priority: Some(25),
|
priority: Some(25),
|
||||||
clear_tags: false,
|
clear_tags: false,
|
||||||
};
|
};
|
||||||
@@ -1284,7 +1297,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::EditIndexer(Some(expected_edit_indexer_params)).into(),
|
RadarrEvent::EditIndexer(expected_edit_indexer_params).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -1292,7 +1305,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let edit_indexer_command = RadarrEditCommand::Indexer {
|
let edit_indexer_command = RadarrEditCommand::Indexer {
|
||||||
indexer_id: 1,
|
indexer_id: 1,
|
||||||
name: Some("Test".to_owned()),
|
name: Some("Test".to_owned()),
|
||||||
@@ -1315,7 +1328,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -1327,13 +1340,14 @@ mod tests {
|
|||||||
quality_profile_id: Some(1),
|
quality_profile_id: Some(1),
|
||||||
root_folder_path: Some("/nfs/test".to_owned()),
|
root_folder_path: Some("/nfs/test".to_owned()),
|
||||||
tags: Some(vec![1, 2]),
|
tags: Some(vec![1, 2]),
|
||||||
|
tag_input_string: None,
|
||||||
clear_tags: false,
|
clear_tags: false,
|
||||||
};
|
};
|
||||||
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>(
|
||||||
RadarrEvent::EditMovie(Some(expected_edit_movie_params)).into(),
|
RadarrEvent::EditMovie(expected_edit_movie_params).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -1341,7 +1355,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let edit_movie_command = RadarrEditCommand::Movie {
|
let edit_movie_command = RadarrEditCommand::Movie {
|
||||||
movie_id: 1,
|
movie_id: 1,
|
||||||
enable_monitoring: true,
|
enable_monitoring: true,
|
||||||
@@ -1357,7 +1371,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -1369,13 +1383,14 @@ mod tests {
|
|||||||
quality_profile_id: Some(1),
|
quality_profile_id: Some(1),
|
||||||
root_folder_path: Some("/nfs/test".to_owned()),
|
root_folder_path: Some("/nfs/test".to_owned()),
|
||||||
tags: Some(vec![1, 2]),
|
tags: Some(vec![1, 2]),
|
||||||
|
tag_input_string: None,
|
||||||
clear_tags: false,
|
clear_tags: false,
|
||||||
};
|
};
|
||||||
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>(
|
||||||
RadarrEvent::EditMovie(Some(expected_edit_movie_params)).into(),
|
RadarrEvent::EditMovie(expected_edit_movie_params).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -1383,7 +1398,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let edit_movie_command = RadarrEditCommand::Movie {
|
let edit_movie_command = RadarrEditCommand::Movie {
|
||||||
movie_id: 1,
|
movie_id: 1,
|
||||||
enable_monitoring: false,
|
enable_monitoring: false,
|
||||||
@@ -1399,7 +1414,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -1411,13 +1426,14 @@ mod tests {
|
|||||||
quality_profile_id: Some(1),
|
quality_profile_id: Some(1),
|
||||||
root_folder_path: Some("/nfs/test".to_owned()),
|
root_folder_path: Some("/nfs/test".to_owned()),
|
||||||
tags: Some(vec![1, 2]),
|
tags: Some(vec![1, 2]),
|
||||||
|
tag_input_string: None,
|
||||||
clear_tags: false,
|
clear_tags: false,
|
||||||
};
|
};
|
||||||
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>(
|
||||||
RadarrEvent::EditMovie(Some(expected_edit_movie_params)).into(),
|
RadarrEvent::EditMovie(expected_edit_movie_params).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -1425,7 +1441,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let edit_movie_command = RadarrEditCommand::Movie {
|
let edit_movie_command = RadarrEditCommand::Movie {
|
||||||
movie_id: 1,
|
movie_id: 1,
|
||||||
enable_monitoring: false,
|
enable_monitoring: false,
|
||||||
@@ -1441,7 +1457,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{command, Subcommand};
|
use clap::Subcommand;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{CliCommandHandler, Command},
|
cli::{CliCommandHandler, Command},
|
||||||
network::{radarr_network::RadarrEvent, NetworkTrait},
|
network::{NetworkTrait, radarr_network::RadarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::RadarrCommand;
|
use super::RadarrCommand;
|
||||||
@@ -90,14 +90,14 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrGetCommand> for RadarrGetCommandHan
|
|||||||
RadarrGetCommand::MovieDetails { movie_id } => {
|
RadarrGetCommand::MovieDetails { movie_id } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(RadarrEvent::GetMovieDetails(Some(movie_id)).into())
|
.handle_network_event(RadarrEvent::GetMovieDetails(movie_id).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrGetCommand::MovieHistory { movie_id } => {
|
RadarrGetCommand::MovieHistory { movie_id } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(RadarrEvent::GetMovieHistory(Some(movie_id)).into())
|
.handle_network_event(RadarrEvent::GetMovieHistory(movie_id).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use clap::error::ErrorKind;
|
|
||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
|
use clap::error::ErrorKind;
|
||||||
|
|
||||||
use crate::cli::radarr::get_command_handler::RadarrGetCommand;
|
|
||||||
use crate::cli::radarr::RadarrCommand;
|
|
||||||
use crate::cli::Command;
|
|
||||||
use crate::Cli;
|
use crate::Cli;
|
||||||
|
use crate::cli::Command;
|
||||||
|
use crate::cli::radarr::RadarrCommand;
|
||||||
|
use crate::cli::radarr::get_command_handler::RadarrGetCommand;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -27,7 +27,7 @@ mod tests {
|
|||||||
let result =
|
let result =
|
||||||
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "all-indexer-settings"]);
|
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "all-indexer-settings"]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -35,7 +35,7 @@ mod tests {
|
|||||||
let result =
|
let result =
|
||||||
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "host-config"]);
|
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "host-config"]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -43,7 +43,7 @@ mod tests {
|
|||||||
let result =
|
let result =
|
||||||
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "movie-details"]);
|
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "movie-details"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -61,7 +61,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -69,7 +69,7 @@ mod tests {
|
|||||||
let result =
|
let result =
|
||||||
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "movie-history"]);
|
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "movie-history"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -87,7 +87,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -95,7 +95,7 @@ mod tests {
|
|||||||
let result =
|
let result =
|
||||||
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "security-config"]);
|
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "security-config"]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -103,7 +103,7 @@ mod tests {
|
|||||||
let result =
|
let result =
|
||||||
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "system-status"]);
|
Cli::command().try_get_matches_from(["managarr", "radarr", "get", "system-status"]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,11 +117,11 @@ mod tests {
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{
|
cli::{
|
||||||
radarr::get_command_handler::{RadarrGetCommand, RadarrGetCommandHandler},
|
|
||||||
CliCommandHandler,
|
CliCommandHandler,
|
||||||
|
radarr::get_command_handler::{RadarrGetCommand, RadarrGetCommandHandler},
|
||||||
},
|
},
|
||||||
models::{radarr_models::RadarrSerdeable, Serdeable},
|
models::{Serdeable, radarr_models::RadarrSerdeable},
|
||||||
network::{radarr_network::RadarrEvent, MockNetworkTrait, NetworkEvent},
|
network::{MockNetworkTrait, NetworkEvent, radarr_network::RadarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -138,7 +138,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let get_all_indexer_settings_command = RadarrGetCommand::AllIndexerSettings;
|
let get_all_indexer_settings_command = RadarrGetCommand::AllIndexerSettings;
|
||||||
|
|
||||||
let result = RadarrGetCommandHandler::with(
|
let result = RadarrGetCommandHandler::with(
|
||||||
@@ -149,7 +149,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -164,7 +164,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let get_host_config_command = RadarrGetCommand::HostConfig;
|
let get_host_config_command = RadarrGetCommand::HostConfig;
|
||||||
|
|
||||||
let result =
|
let result =
|
||||||
@@ -172,7 +172,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -182,7 +182,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::GetMovieDetails(Some(expected_movie_id)).into(),
|
RadarrEvent::GetMovieDetails(expected_movie_id).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -190,7 +190,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let get_movie_details_command = RadarrGetCommand::MovieDetails { movie_id: 1 };
|
let get_movie_details_command = RadarrGetCommand::MovieDetails { movie_id: 1 };
|
||||||
|
|
||||||
let result =
|
let result =
|
||||||
@@ -198,7 +198,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -208,7 +208,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::GetMovieHistory(Some(expected_movie_id)).into(),
|
RadarrEvent::GetMovieHistory(expected_movie_id).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -216,7 +216,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let get_movie_history_command = RadarrGetCommand::MovieHistory { movie_id: 1 };
|
let get_movie_history_command = RadarrGetCommand::MovieHistory { movie_id: 1 };
|
||||||
|
|
||||||
let result =
|
let result =
|
||||||
@@ -224,7 +224,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -239,7 +239,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let get_security_config_command = RadarrGetCommand::SecurityConfig;
|
let get_security_config_command = RadarrGetCommand::SecurityConfig;
|
||||||
|
|
||||||
let result =
|
let result =
|
||||||
@@ -247,7 +247,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -262,7 +262,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let get_system_status_command = RadarrGetCommand::SystemStatus;
|
let get_system_status_command = RadarrGetCommand::SystemStatus;
|
||||||
|
|
||||||
let result =
|
let result =
|
||||||
@@ -270,7 +270,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{command, Subcommand};
|
use clap::Subcommand;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{CliCommandHandler, Command},
|
cli::{CliCommandHandler, Command},
|
||||||
network::{radarr_network::RadarrEvent, NetworkTrait},
|
network::{NetworkTrait, radarr_network::RadarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::RadarrCommand;
|
use super::RadarrCommand;
|
||||||
@@ -23,9 +23,17 @@ pub enum RadarrListCommand {
|
|||||||
#[command(about = "List all Radarr collections")]
|
#[command(about = "List all Radarr collections")]
|
||||||
Collections,
|
Collections,
|
||||||
#[command(about = "List all active downloads in Radarr")]
|
#[command(about = "List all active downloads in Radarr")]
|
||||||
Downloads,
|
Downloads {
|
||||||
|
#[arg(long, help = "How many downloads to fetch", default_value_t = 500)]
|
||||||
|
count: u64,
|
||||||
|
},
|
||||||
#[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")]
|
||||||
@@ -104,10 +112,10 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrListCommand> for RadarrListCommandH
|
|||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrListCommand::Downloads => {
|
RadarrListCommand::Downloads { count } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(RadarrEvent::GetDownloads.into())
|
.handle_network_event(RadarrEvent::GetDownloads(count).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
@@ -118,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
|
||||||
@@ -131,13 +146,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrListCommand> for RadarrListCommandH
|
|||||||
} => {
|
} => {
|
||||||
let logs = self
|
let logs = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(RadarrEvent::GetLogs(Some(events)).into())
|
.handle_network_event(RadarrEvent::GetLogs(events).into())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if output_in_log_format {
|
if output_in_log_format {
|
||||||
let log_lines = self.app.lock().await.data.radarr_data.logs.items.clone();
|
let log_lines = &self.app.lock().await.data.radarr_data.logs.items;
|
||||||
|
|
||||||
serde_json::to_string_pretty(&log_lines)?
|
serde_json::to_string_pretty(log_lines)?
|
||||||
} else {
|
} else {
|
||||||
serde_json::to_string_pretty(&logs)?
|
serde_json::to_string_pretty(&logs)?
|
||||||
}
|
}
|
||||||
@@ -152,7 +167,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrListCommand> for RadarrListCommandH
|
|||||||
RadarrListCommand::MovieCredits { movie_id } => {
|
RadarrListCommand::MovieCredits { movie_id } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(RadarrEvent::GetMovieCredits(Some(movie_id)).into())
|
.handle_network_event(RadarrEvent::GetMovieCredits(movie_id).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use clap::error::ErrorKind;
|
|
||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
|
use clap::error::ErrorKind;
|
||||||
|
|
||||||
use crate::cli::radarr::list_command_handler::RadarrListCommand;
|
|
||||||
use crate::cli::radarr::RadarrCommand;
|
|
||||||
use crate::cli::Command;
|
|
||||||
use crate::Cli;
|
use crate::Cli;
|
||||||
|
use crate::cli::Command;
|
||||||
|
use crate::cli::radarr::RadarrCommand;
|
||||||
|
use crate::cli::radarr::list_command_handler::RadarrListCommand;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -29,7 +29,6 @@ mod tests {
|
|||||||
#[values(
|
#[values(
|
||||||
"blocklist",
|
"blocklist",
|
||||||
"collections",
|
"collections",
|
||||||
"downloads",
|
|
||||||
"disk-space",
|
"disk-space",
|
||||||
"indexers",
|
"indexers",
|
||||||
"movies",
|
"movies",
|
||||||
@@ -44,7 +43,7 @@ mod tests {
|
|||||||
) {
|
) {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "list", subcommand]);
|
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "list", subcommand]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -52,19 +51,28 @@ mod tests {
|
|||||||
let result =
|
let result =
|
||||||
Cli::command().try_get_matches_from(["managarr", "radarr", "list", "movie-credits"]);
|
Cli::command().try_get_matches_from(["managarr", "radarr", "list", "movie-credits"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_downloads_count_flag_requires_arguments() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "radarr", "list", "downloads", "--count"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_list_logs_events_flag_requires_arguments() {
|
fn test_list_logs_events_flag_requires_arguments() {
|
||||||
let result =
|
let result =
|
||||||
Cli::command().try_get_matches_from(["managarr", "radarr", "list", "logs", "--events"]);
|
Cli::command().try_get_matches_from(["managarr", "radarr", "list", "logs", "--events"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,11 +88,50 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Radarr(RadarrCommand::List(credits_command))) = result.unwrap().command {
|
let Some(Command::Radarr(RadarrCommand::List(credits_command))) = result.unwrap().command
|
||||||
assert_eq!(credits_command, expected_args);
|
else {
|
||||||
}
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(credits_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_downloads_default_values() {
|
||||||
|
let expected_args = RadarrListCommand::Downloads { count: 500 };
|
||||||
|
let result = Cli::try_parse_from(["managarr", "radarr", "list", "downloads"]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Radarr(RadarrCommand::List(refresh_command))) = result.unwrap().command
|
||||||
|
else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
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]
|
||||||
@@ -95,11 +142,13 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let result = Cli::try_parse_from(["managarr", "radarr", "list", "logs"]);
|
let result = Cli::try_parse_from(["managarr", "radarr", "list", "logs"]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Radarr(RadarrCommand::List(refresh_command))) = result.unwrap().command {
|
let Some(Command::Radarr(RadarrCommand::List(refresh_command))) = result.unwrap().command
|
||||||
assert_eq!(refresh_command, expected_args);
|
else {
|
||||||
}
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(refresh_command, expected_args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,14 +164,13 @@ mod tests {
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::radarr::list_command_handler::{RadarrListCommand, RadarrListCommandHandler},
|
cli::radarr::list_command_handler::{RadarrListCommand, RadarrListCommandHandler},
|
||||||
models::{radarr_models::RadarrSerdeable, Serdeable},
|
models::{Serdeable, radarr_models::RadarrSerdeable},
|
||||||
network::{radarr_network::RadarrEvent, MockNetworkTrait, NetworkEvent},
|
network::{MockNetworkTrait, NetworkEvent, radarr_network::RadarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(RadarrListCommand::Blocklist, RadarrEvent::GetBlocklist)]
|
#[case(RadarrListCommand::Blocklist, RadarrEvent::GetBlocklist)]
|
||||||
#[case(RadarrListCommand::Collections, RadarrEvent::GetCollections)]
|
#[case(RadarrListCommand::Collections, RadarrEvent::GetCollections)]
|
||||||
#[case(RadarrListCommand::Downloads, RadarrEvent::GetDownloads)]
|
|
||||||
#[case(RadarrListCommand::DiskSpace, RadarrEvent::GetDiskSpace)]
|
#[case(RadarrListCommand::DiskSpace, RadarrEvent::GetDiskSpace)]
|
||||||
#[case(RadarrListCommand::Indexers, RadarrEvent::GetIndexers)]
|
#[case(RadarrListCommand::Indexers, RadarrEvent::GetIndexers)]
|
||||||
#[case(RadarrListCommand::Movies, RadarrEvent::GetMovies)]
|
#[case(RadarrListCommand::Movies, RadarrEvent::GetMovies)]
|
||||||
@@ -147,13 +195,13 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
|
||||||
let result = RadarrListCommandHandler::with(&app_arc, list_command, &mut mock_network)
|
let result = RadarrListCommandHandler::with(&app_arc, list_command, &mut mock_network)
|
||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -163,7 +211,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::GetMovieCredits(Some(expected_movie_id)).into(),
|
RadarrEvent::GetMovieCredits(expected_movie_id).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -171,7 +219,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let list_movie_credits_command = RadarrListCommand::MovieCredits { movie_id: 1 };
|
let list_movie_credits_command = RadarrListCommand::MovieCredits { movie_id: 1 };
|
||||||
|
|
||||||
let result =
|
let result =
|
||||||
@@ -179,7 +227,59 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
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>(
|
||||||
|
RadarrEvent::GetDownloads(expected_count).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Radarr(RadarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let list_downloads_command = RadarrListCommand::Downloads { count: 1000 };
|
||||||
|
|
||||||
|
let result =
|
||||||
|
RadarrListCommandHandler::with(&app_arc, list_downloads_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_list_history_command() {
|
||||||
|
let expected_events = 1000;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
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]
|
||||||
@@ -189,7 +289,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::GetLogs(Some(expected_events)).into(),
|
RadarrEvent::GetLogs(expected_events).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -197,7 +297,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let list_logs_command = RadarrListCommand::Logs {
|
let list_logs_command = RadarrListCommand::Logs {
|
||||||
events: 1000,
|
events: 1000,
|
||||||
output_in_log_format: false,
|
output_in_log_format: false,
|
||||||
@@ -207,7 +307,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ use crate::app::App;
|
|||||||
|
|
||||||
use crate::cli::CliCommandHandler;
|
use crate::cli::CliCommandHandler;
|
||||||
use crate::models::radarr_models::{RadarrReleaseDownloadBody, RadarrTaskName};
|
use crate::models::radarr_models::{RadarrReleaseDownloadBody, RadarrTaskName};
|
||||||
use crate::network::radarr_network::RadarrEvent;
|
|
||||||
use crate::network::NetworkTrait;
|
use crate::network::NetworkTrait;
|
||||||
|
use crate::network::radarr_network::RadarrEvent;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use super::Command;
|
use super::Command;
|
||||||
@@ -64,6 +64,15 @@ pub enum RadarrCommand {
|
|||||||
Refresh(RadarrRefreshCommand),
|
Refresh(RadarrRefreshCommand),
|
||||||
#[command(about = "Clear the blocklist")]
|
#[command(about = "Clear the blocklist")]
|
||||||
ClearBlocklist,
|
ClearBlocklist,
|
||||||
|
#[command(about = "Mark the Radarr history item with the given ID as 'failed'")]
|
||||||
|
MarkHistoryItemAsFailed {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The Radarr ID of the history item you wish to mark as 'failed'",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
history_item_id: i64,
|
||||||
|
},
|
||||||
#[command(about = "Manually download the given release for the specified movie ID")]
|
#[command(about = "Manually download the given release for the specified movie ID")]
|
||||||
DownloadRelease {
|
DownloadRelease {
|
||||||
#[arg(long, help = "The GUID of the release to download", required = true)]
|
#[arg(long, help = "The GUID of the release to download", required = true)]
|
||||||
@@ -118,6 +127,17 @@ pub enum RadarrCommand {
|
|||||||
},
|
},
|
||||||
#[command(about = "Test all Radarr indexers")]
|
#[command(about = "Test all Radarr indexers")]
|
||||||
TestAllIndexers,
|
TestAllIndexers,
|
||||||
|
#[command(
|
||||||
|
about = "Toggle monitoring for the specified movie corresponding to the given movie ID"
|
||||||
|
)]
|
||||||
|
ToggleMovieMonitoring {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The Radarr ID of the movie to toggle monitoring on",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
movie_id: i64,
|
||||||
|
},
|
||||||
#[command(about = "Trigger an automatic search for the movie with the specified ID")]
|
#[command(about = "Trigger an automatic search for the movie with the specified ID")]
|
||||||
TriggerAutomaticSearch {
|
TriggerAutomaticSearch {
|
||||||
#[arg(
|
#[arg(
|
||||||
@@ -197,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,
|
||||||
@@ -209,7 +238,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrCommand> for RadarrCliHandler<'a, '
|
|||||||
};
|
};
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(RadarrEvent::DownloadRelease(Some(params)).into())
|
.handle_network_event(RadarrEvent::DownloadRelease(params).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
@@ -217,28 +246,28 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrCommand> for RadarrCliHandler<'a, '
|
|||||||
println!("Searching for releases. This may take a minute...");
|
println!("Searching for releases. This may take a minute...");
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(RadarrEvent::GetReleases(Some(movie_id)).into())
|
.handle_network_event(RadarrEvent::GetReleases(movie_id).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrCommand::SearchNewMovie { query } => {
|
RadarrCommand::SearchNewMovie { query } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(RadarrEvent::SearchNewMovie(Some(query)).into())
|
.handle_network_event(RadarrEvent::SearchNewMovie(query).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrCommand::StartTask { task_name } => {
|
RadarrCommand::StartTask { task_name } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(RadarrEvent::StartTask(Some(task_name)).into())
|
.handle_network_event(RadarrEvent::StartTask(task_name).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
RadarrCommand::TestIndexer { indexer_id } => {
|
RadarrCommand::TestIndexer { indexer_id } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(RadarrEvent::TestIndexer(Some(indexer_id)).into())
|
.handle_network_event(RadarrEvent::TestIndexer(indexer_id).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
@@ -250,10 +279,17 @@ 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::ToggleMovieMonitoring { movie_id } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(RadarrEvent::ToggleMovieMonitoring(movie_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
RadarrCommand::TriggerAutomaticSearch { movie_id } => {
|
RadarrCommand::TriggerAutomaticSearch { movie_id } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(RadarrEvent::TriggerAutomaticSearch(Some(movie_id)).into())
|
.handle_network_event(RadarrEvent::TriggerAutomaticSearch(movie_id).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use clap::error::ErrorKind;
|
|
||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
|
use clap::error::ErrorKind;
|
||||||
|
|
||||||
use crate::cli::radarr::RadarrCommand;
|
|
||||||
use crate::cli::Command;
|
|
||||||
use crate::Cli;
|
use crate::Cli;
|
||||||
|
use crate::cli::Command;
|
||||||
|
use crate::cli::radarr::RadarrCommand;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -28,7 +28,32 @@ mod tests {
|
|||||||
) {
|
) {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", subcommand]);
|
let result = Cli::command().try_get_matches_from(["managarr", "radarr", subcommand]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mark_history_item_as_failed_requires_history_item_id() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "radarr", "mark-history-item-as-failed"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mark_history_item_as_failed_requirements_satisfied() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"radarr",
|
||||||
|
"mark-history-item-as-failed",
|
||||||
|
"--history-item-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -43,7 +68,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -62,7 +87,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -81,7 +106,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -102,14 +127,14 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_manual_search_requires_movie_id() {
|
fn test_manual_search_requires_movie_id() {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "manual-search"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "manual-search"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -126,14 +151,14 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_search_new_movie_requires_query() {
|
fn test_search_new_movie_requires_query() {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "search-new-movie"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "search-new-movie"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -150,14 +175,14 @@ mod tests {
|
|||||||
"halo",
|
"halo",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_start_task_requires_task_name() {
|
fn test_start_task_requires_task_name() {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "start-task"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "start-task"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -174,7 +199,7 @@ mod tests {
|
|||||||
"test",
|
"test",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,14 +213,14 @@ mod tests {
|
|||||||
"application-check-update",
|
"application-check-update",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_test_indexer_requires_indexer_id() {
|
fn test_test_indexer_requires_indexer_id() {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "test-indexer"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "test-indexer"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -212,7 +237,32 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_toggle_movie_monitoring_requires_movie_id() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "radarr", "toggle-movie-monitoring"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_toggle_movie_monitoring_requirements_satisfied() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"radarr",
|
||||||
|
"toggle-movie-monitoring",
|
||||||
|
"--movie-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -220,7 +270,7 @@ mod tests {
|
|||||||
let result =
|
let result =
|
||||||
Cli::command().try_get_matches_from(["managarr", "radarr", "trigger-automatic-search"]);
|
Cli::command().try_get_matches_from(["managarr", "radarr", "trigger-automatic-search"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -237,7 +287,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,22 +301,22 @@ mod tests {
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{
|
cli::{
|
||||||
radarr::{
|
|
||||||
add_command_handler::RadarrAddCommand, delete_command_handler::RadarrDeleteCommand,
|
|
||||||
edit_command_handler::RadarrEditCommand, get_command_handler::RadarrGetCommand,
|
|
||||||
list_command_handler::RadarrListCommand, refresh_command_handler::RadarrRefreshCommand,
|
|
||||||
RadarrCliHandler, RadarrCommand,
|
|
||||||
},
|
|
||||||
CliCommandHandler,
|
CliCommandHandler,
|
||||||
|
radarr::{
|
||||||
|
RadarrCliHandler, RadarrCommand, add_command_handler::RadarrAddCommand,
|
||||||
|
delete_command_handler::RadarrDeleteCommand, edit_command_handler::RadarrEditCommand,
|
||||||
|
get_command_handler::RadarrGetCommand, list_command_handler::RadarrListCommand,
|
||||||
|
refresh_command_handler::RadarrRefreshCommand,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
models::{
|
models::{
|
||||||
|
Serdeable,
|
||||||
radarr_models::{
|
radarr_models::{
|
||||||
BlocklistItem, BlocklistResponse, IndexerSettings, RadarrReleaseDownloadBody,
|
BlocklistItem, BlocklistResponse, IndexerSettings, RadarrReleaseDownloadBody,
|
||||||
RadarrSerdeable, RadarrTaskName,
|
RadarrSerdeable, RadarrTaskName,
|
||||||
},
|
},
|
||||||
Serdeable,
|
|
||||||
},
|
},
|
||||||
network::{radarr_network::RadarrEvent, MockNetworkTrait, NetworkEvent},
|
network::{MockNetworkTrait, NetworkEvent, radarr_network::RadarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -292,14 +342,44 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let claer_blocklist_command = RadarrCommand::ClearBlocklist;
|
let clear_blocklist_command = RadarrCommand::ClearBlocklist;
|
||||||
|
|
||||||
let result = RadarrCliHandler::with(&app_arc, claer_blocklist_command, &mut mock_network)
|
let result = RadarrCliHandler::with(&app_arc, clear_blocklist_command, &mut mock_network)
|
||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_mark_history_item_as_failed_command() {
|
||||||
|
let expected_history_item_id = 1;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
RadarrEvent::MarkHistoryItemAsFailed(expected_history_item_id).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Radarr(RadarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let mark_history_item_as_failed_command =
|
||||||
|
RadarrCommand::MarkHistoryItemAsFailed { history_item_id: 1 };
|
||||||
|
|
||||||
|
let result = RadarrCliHandler::with(
|
||||||
|
&app_arc,
|
||||||
|
mark_history_item_as_failed_command,
|
||||||
|
&mut mock_network,
|
||||||
|
)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -313,7 +393,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::DownloadRelease(Some(expected_release_download_body)).into(),
|
RadarrEvent::DownloadRelease(expected_release_download_body).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -321,7 +401,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let download_release_command = RadarrCommand::DownloadRelease {
|
let download_release_command = RadarrCommand::DownloadRelease {
|
||||||
guid: "guid".to_owned(),
|
guid: "guid".to_owned(),
|
||||||
indexer_id: 1,
|
indexer_id: 1,
|
||||||
@@ -332,7 +412,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -342,7 +422,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::GetReleases(Some(expected_movie_id)).into(),
|
RadarrEvent::GetReleases(expected_movie_id).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -350,14 +430,14 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let manual_search_command = RadarrCommand::ManualSearch { movie_id: 1 };
|
let manual_search_command = RadarrCommand::ManualSearch { movie_id: 1 };
|
||||||
|
|
||||||
let result = RadarrCliHandler::with(&app_arc, manual_search_command, &mut mock_network)
|
let result = RadarrCliHandler::with(&app_arc, manual_search_command, &mut mock_network)
|
||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -367,7 +447,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::SearchNewMovie(Some(expected_search_query)).into(),
|
RadarrEvent::SearchNewMovie(expected_search_query).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -375,7 +455,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let search_new_movie_command = RadarrCommand::SearchNewMovie {
|
let search_new_movie_command = RadarrCommand::SearchNewMovie {
|
||||||
query: "halo".to_owned(),
|
query: "halo".to_owned(),
|
||||||
};
|
};
|
||||||
@@ -384,7 +464,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -394,7 +474,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::StartTask(Some(expected_task_name)).into(),
|
RadarrEvent::StartTask(expected_task_name).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -402,7 +482,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let start_task_command = RadarrCommand::StartTask {
|
let start_task_command = RadarrCommand::StartTask {
|
||||||
task_name: RadarrTaskName::ApplicationCheckUpdate,
|
task_name: RadarrTaskName::ApplicationCheckUpdate,
|
||||||
};
|
};
|
||||||
@@ -411,7 +491,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -421,7 +501,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::TestIndexer(Some(expected_indexer_id)).into(),
|
RadarrEvent::TestIndexer(expected_indexer_id).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -429,14 +509,14 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let test_indexer_command = RadarrCommand::TestIndexer { indexer_id: 1 };
|
let test_indexer_command = RadarrCommand::TestIndexer { indexer_id: 1 };
|
||||||
|
|
||||||
let result = RadarrCliHandler::with(&app_arc, test_indexer_command, &mut mock_network)
|
let result = RadarrCliHandler::with(&app_arc, test_indexer_command, &mut mock_network)
|
||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -451,14 +531,40 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let test_all_indexers_command = RadarrCommand::TestAllIndexers;
|
let test_all_indexers_command = RadarrCommand::TestAllIndexers;
|
||||||
|
|
||||||
let result = RadarrCliHandler::with(&app_arc, test_all_indexers_command, &mut mock_network)
|
let result = RadarrCliHandler::with(&app_arc, test_all_indexers_command, &mut mock_network)
|
||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_toggle_movie_monitoring_command() {
|
||||||
|
let expected_movie_id = 1;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
RadarrEvent::ToggleMovieMonitoring(expected_movie_id).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Radarr(RadarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let toggle_movie_monitoring_command = RadarrCommand::ToggleMovieMonitoring { movie_id: 1 };
|
||||||
|
|
||||||
|
let result =
|
||||||
|
RadarrCliHandler::with(&app_arc, toggle_movie_monitoring_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -468,7 +574,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::TriggerAutomaticSearch(Some(expected_movie_id)).into(),
|
RadarrEvent::TriggerAutomaticSearch(expected_movie_id).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -476,7 +582,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let trigger_automatic_search_command = RadarrCommand::TriggerAutomaticSearch { movie_id: 1 };
|
let trigger_automatic_search_command = RadarrCommand::TriggerAutomaticSearch { movie_id: 1 };
|
||||||
|
|
||||||
let result = RadarrCliHandler::with(
|
let result = RadarrCliHandler::with(
|
||||||
@@ -487,7 +593,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -505,7 +611,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let add_tag_command = RadarrCommand::Add(RadarrAddCommand::Tag {
|
let add_tag_command = RadarrCommand::Add(RadarrAddCommand::Tag {
|
||||||
name: expected_tag_name,
|
name: expected_tag_name,
|
||||||
});
|
});
|
||||||
@@ -514,7 +620,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -524,7 +630,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::DeleteBlocklistItem(Some(expected_blocklist_item_id)).into(),
|
RadarrEvent::DeleteBlocklistItem(expected_blocklist_item_id).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -532,7 +638,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let delete_blocklist_item_command =
|
let delete_blocklist_item_command =
|
||||||
RadarrCommand::Delete(RadarrDeleteCommand::BlocklistItem {
|
RadarrCommand::Delete(RadarrDeleteCommand::BlocklistItem {
|
||||||
blocklist_item_id: 1,
|
blocklist_item_id: 1,
|
||||||
@@ -543,7 +649,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -584,7 +690,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::EditAllIndexerSettings(Some(expected_edit_all_indexer_settings)).into(),
|
RadarrEvent::EditAllIndexerSettings(expected_edit_all_indexer_settings).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -592,7 +698,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let edit_all_indexer_settings_command =
|
let edit_all_indexer_settings_command =
|
||||||
RadarrCommand::Edit(RadarrEditCommand::AllIndexerSettings {
|
RadarrCommand::Edit(RadarrEditCommand::AllIndexerSettings {
|
||||||
allow_hardcoded_subs: true,
|
allow_hardcoded_subs: true,
|
||||||
@@ -615,7 +721,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -632,7 +738,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let get_all_indexer_settings_command =
|
let get_all_indexer_settings_command =
|
||||||
RadarrCommand::Get(RadarrGetCommand::AllIndexerSettings);
|
RadarrCommand::Get(RadarrGetCommand::AllIndexerSettings);
|
||||||
|
|
||||||
@@ -644,7 +750,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -654,7 +760,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::GetMovieCredits(Some(expected_movie_id)).into(),
|
RadarrEvent::GetMovieCredits(expected_movie_id).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -662,7 +768,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let list_movie_credits_command =
|
let list_movie_credits_command =
|
||||||
RadarrCommand::List(RadarrListCommand::MovieCredits { movie_id: 1 });
|
RadarrCommand::List(RadarrListCommand::MovieCredits { movie_id: 1 });
|
||||||
|
|
||||||
@@ -670,7 +776,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -680,7 +786,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::UpdateAndScan(Some(expected_movie_id)).into(),
|
RadarrEvent::UpdateAndScan(expected_movie_id).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -688,7 +794,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let refresh_movie_command =
|
let refresh_movie_command =
|
||||||
RadarrCommand::Refresh(RadarrRefreshCommand::Movie { movie_id: 1 });
|
RadarrCommand::Refresh(RadarrRefreshCommand::Movie { movie_id: 1 });
|
||||||
|
|
||||||
@@ -696,7 +802,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use tokio::sync::Mutex;
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{CliCommandHandler, Command},
|
cli::{CliCommandHandler, Command},
|
||||||
network::{radarr_network::RadarrEvent, NetworkTrait},
|
network::{NetworkTrait, radarr_network::RadarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::RadarrCommand;
|
use super::RadarrCommand;
|
||||||
@@ -88,7 +88,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrRefreshCommand>
|
|||||||
RadarrRefreshCommand::Movie { movie_id } => {
|
RadarrRefreshCommand::Movie { movie_id } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(RadarrEvent::UpdateAndScan(Some(movie_id)).into())
|
.handle_network_event(RadarrEvent::UpdateAndScan(movie_id).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use clap::error::ErrorKind;
|
|
||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
|
use clap::error::ErrorKind;
|
||||||
|
|
||||||
use crate::cli::radarr::refresh_command_handler::RadarrRefreshCommand;
|
|
||||||
use crate::cli::radarr::RadarrCommand;
|
|
||||||
use crate::cli::Command;
|
|
||||||
use crate::Cli;
|
use crate::Cli;
|
||||||
|
use crate::cli::Command;
|
||||||
|
use crate::cli::radarr::RadarrCommand;
|
||||||
|
use crate::cli::radarr::refresh_command_handler::RadarrRefreshCommand;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -31,14 +31,14 @@ mod tests {
|
|||||||
let result =
|
let result =
|
||||||
Cli::command().try_get_matches_from(["managarr", "radarr", "refresh", subcommand]);
|
Cli::command().try_get_matches_from(["managarr", "radarr", "refresh", subcommand]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_refresh_movie_requires_movie_id() {
|
fn test_refresh_movie_requires_movie_id() {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "refresh", "movie"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "radarr", "refresh", "movie"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -51,13 +51,13 @@ mod tests {
|
|||||||
let result =
|
let result =
|
||||||
Cli::try_parse_from(["managarr", "radarr", "refresh", "movie", "--movie-id", "1"]);
|
Cli::try_parse_from(["managarr", "radarr", "refresh", "movie", "--movie-id", "1"]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Radarr(RadarrCommand::Refresh(refresh_command))) =
|
let Some(Command::Radarr(RadarrCommand::Refresh(refresh_command))) = result.unwrap().command
|
||||||
result.unwrap().command
|
else {
|
||||||
{
|
panic!("Unexpected command type");
|
||||||
assert_eq!(refresh_command, expected_args);
|
};
|
||||||
}
|
assert_eq!(refresh_command, expected_args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,8 +73,8 @@ mod tests {
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::radarr::refresh_command_handler::{RadarrRefreshCommand, RadarrRefreshCommandHandler},
|
cli::radarr::refresh_command_handler::{RadarrRefreshCommand, RadarrRefreshCommandHandler},
|
||||||
models::{radarr_models::RadarrSerdeable, Serdeable},
|
models::{Serdeable, radarr_models::RadarrSerdeable},
|
||||||
network::{radarr_network::RadarrEvent, MockNetworkTrait, NetworkEvent},
|
network::{MockNetworkTrait, NetworkEvent, radarr_network::RadarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
@@ -96,13 +96,13 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
|
||||||
let result = RadarrRefreshCommandHandler::with(&app_arc, refresh_command, &mut mock_network)
|
let result = RadarrRefreshCommandHandler::with(&app_arc, refresh_command, &mut mock_network)
|
||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -112,7 +112,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
RadarrEvent::UpdateAndScan(Some(expected_movie_id)).into(),
|
RadarrEvent::UpdateAndScan(expected_movie_id).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -120,7 +120,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let refresh_movie_command = RadarrRefreshCommand::Movie { movie_id: 1 };
|
let refresh_movie_command = RadarrRefreshCommand::Movie { movie_id: 1 };
|
||||||
|
|
||||||
let result =
|
let result =
|
||||||
@@ -128,7 +128,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ use anyhow::Result;
|
|||||||
use clap::{ArgAction, Subcommand};
|
use clap::{ArgAction, Subcommand};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use super::SonarrCommand;
|
||||||
|
use crate::models::servarr_models::AddRootFolderBody;
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{CliCommandHandler, Command},
|
cli::{CliCommandHandler, Command},
|
||||||
models::sonarr_models::{AddSeriesBody, AddSeriesOptions, SeriesMonitor, SeriesType},
|
models::sonarr_models::{AddSeriesBody, AddSeriesOptions, SeriesMonitor, SeriesType},
|
||||||
network::{sonarr_network::SonarrEvent, NetworkTrait},
|
network::{NetworkTrait, sonarr_network::SonarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::SonarrCommand;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "add_command_handler_tests.rs"]
|
#[path = "add_command_handler_tests.rs"]
|
||||||
mod add_command_handler_tests;
|
mod add_command_handler_tests;
|
||||||
@@ -137,25 +137,29 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrAddCommand> for SonarrAddCommandHan
|
|||||||
root_folder_path,
|
root_folder_path,
|
||||||
quality_profile_id,
|
quality_profile_id,
|
||||||
language_profile_id,
|
language_profile_id,
|
||||||
series_type: series_type.to_string(),
|
series_type,
|
||||||
season_folder: !disable_season_folders,
|
season_folder: !disable_season_folders,
|
||||||
tags,
|
tags,
|
||||||
|
tag_input_string: None,
|
||||||
add_options: AddSeriesOptions {
|
add_options: AddSeriesOptions {
|
||||||
monitor: monitor.to_string(),
|
monitor,
|
||||||
search_for_cutoff_unmet_episodes: !no_search_for_series,
|
search_for_cutoff_unmet_episodes: !no_search_for_series,
|
||||||
search_for_missing_episodes: !no_search_for_series,
|
search_for_missing_episodes: !no_search_for_series,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(SonarrEvent::AddSeries(Some(body)).into())
|
.handle_network_event(SonarrEvent::AddSeries(body).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
SonarrAddCommand::RootFolder { root_folder_path } => {
|
SonarrAddCommand::RootFolder { root_folder_path } => {
|
||||||
|
let add_root_folder_body = AddRootFolderBody {
|
||||||
|
path: root_folder_path,
|
||||||
|
};
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(SonarrEvent::AddRootFolder(Some(root_folder_path)).into())
|
.handle_network_event(SonarrEvent::AddRootFolder(add_root_folder_body).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use clap::{error::ErrorKind, CommandFactory, Parser};
|
use clap::{CommandFactory, Parser, error::ErrorKind};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cli::{
|
|
||||||
sonarr::{add_command_handler::SonarrAddCommand, SonarrCommand},
|
|
||||||
Command,
|
|
||||||
},
|
|
||||||
Cli,
|
Cli,
|
||||||
|
cli::{
|
||||||
|
Command,
|
||||||
|
sonarr::{SonarrCommand, add_command_handler::SonarrAddCommand},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -34,7 +34,7 @@ mod tests {
|
|||||||
let result =
|
let result =
|
||||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "add", "root-folder"]);
|
Cli::command().try_get_matches_from(["managarr", "sonarr", "add", "root-folder"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -56,18 +56,19 @@ mod tests {
|
|||||||
"/nfs/test",
|
"/nfs/test",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command {
|
let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||||
assert_eq!(add_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(add_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_series_requires_arguments() {
|
fn test_add_series_requires_arguments() {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "add", "series"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "add", "series"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -91,7 +92,7 @@ mod tests {
|
|||||||
"test",
|
"test",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -115,7 +116,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -139,7 +140,7 @@ mod tests {
|
|||||||
"test",
|
"test",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -163,7 +164,7 @@ mod tests {
|
|||||||
"test",
|
"test",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -187,7 +188,7 @@ mod tests {
|
|||||||
"test",
|
"test",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -216,7 +217,7 @@ mod tests {
|
|||||||
flag,
|
flag,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,7 +240,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -263,7 +264,7 @@ mod tests {
|
|||||||
"test",
|
"test",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,7 +289,7 @@ mod tests {
|
|||||||
"test",
|
"test",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,10 +326,11 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
if let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command {
|
let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||||
assert_eq!(add_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(add_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -368,10 +370,11 @@ mod tests {
|
|||||||
"2",
|
"2",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
if let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command {
|
let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||||
assert_eq!(add_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(add_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -418,17 +421,18 @@ mod tests {
|
|||||||
"--no-search-for-series",
|
"--no-search-for-series",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
if let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command {
|
let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||||
assert_eq!(add_command, expected_args);
|
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", "sonarr", "add", "tag"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "add", "tag"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -443,11 +447,12 @@ mod tests {
|
|||||||
|
|
||||||
let result = Cli::try_parse_from(["managarr", "sonarr", "add", "tag", "--name", "test"]);
|
let result = Cli::try_parse_from(["managarr", "sonarr", "add", "tag", "--name", "test"]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command {
|
let Some(Command::Sonarr(SonarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||||
assert_eq!(add_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(add_command, expected_args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -456,30 +461,34 @@ mod tests {
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{sonarr::add_command_handler::SonarrAddCommandHandler, CliCommandHandler},
|
cli::{CliCommandHandler, sonarr::add_command_handler::SonarrAddCommandHandler},
|
||||||
models::{
|
models::{
|
||||||
|
Serdeable,
|
||||||
sonarr_models::{
|
sonarr_models::{
|
||||||
AddSeriesBody, AddSeriesOptions, SeriesMonitor, SeriesType, SonarrSerdeable,
|
AddSeriesBody, AddSeriesOptions, SeriesMonitor, SeriesType, SonarrSerdeable,
|
||||||
},
|
},
|
||||||
Serdeable,
|
|
||||||
},
|
},
|
||||||
network::{sonarr_network::SonarrEvent, MockNetworkTrait, NetworkEvent},
|
network::{MockNetworkTrait, NetworkEvent, sonarr_network::SonarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use mockall::predicate::eq;
|
use mockall::predicate::eq;
|
||||||
|
|
||||||
|
use crate::models::servarr_models::AddRootFolderBody;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_handle_add_root_folder_command() {
|
async fn test_handle_add_root_folder_command() {
|
||||||
let expected_root_folder_path = "/nfs/test".to_owned();
|
let expected_root_folder_path = "/nfs/test".to_owned();
|
||||||
|
let expected_add_root_folder_body = AddRootFolderBody {
|
||||||
|
path: expected_root_folder_path.clone(),
|
||||||
|
};
|
||||||
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::AddRootFolder(Some(expected_root_folder_path.clone())).into(),
|
SonarrEvent::AddRootFolder(expected_add_root_folder_body.clone()).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -487,7 +496,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let add_root_folder_command = SonarrAddCommand::RootFolder {
|
let add_root_folder_command = SonarrAddCommand::RootFolder {
|
||||||
root_folder_path: expected_root_folder_path,
|
root_folder_path: expected_root_folder_path,
|
||||||
};
|
};
|
||||||
@@ -497,7 +506,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -508,12 +517,13 @@ mod tests {
|
|||||||
root_folder_path: "/test".to_owned(),
|
root_folder_path: "/test".to_owned(),
|
||||||
quality_profile_id: 1,
|
quality_profile_id: 1,
|
||||||
language_profile_id: 1,
|
language_profile_id: 1,
|
||||||
series_type: "anime".to_owned(),
|
series_type: SeriesType::Anime,
|
||||||
monitored: false,
|
monitored: false,
|
||||||
tags: vec![1, 2],
|
tags: vec![1, 2],
|
||||||
|
tag_input_string: None,
|
||||||
season_folder: false,
|
season_folder: false,
|
||||||
add_options: AddSeriesOptions {
|
add_options: AddSeriesOptions {
|
||||||
monitor: "future".to_owned(),
|
monitor: SeriesMonitor::Future,
|
||||||
search_for_cutoff_unmet_episodes: false,
|
search_for_cutoff_unmet_episodes: false,
|
||||||
search_for_missing_episodes: false,
|
search_for_missing_episodes: false,
|
||||||
},
|
},
|
||||||
@@ -522,7 +532,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
SonarrEvent::AddSeries(Some(expected_add_series_body)).into(),
|
SonarrEvent::AddSeries(expected_add_series_body).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -530,7 +540,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let add_series_command = SonarrAddCommand::Series {
|
let add_series_command = SonarrAddCommand::Series {
|
||||||
tvdb_id: 1,
|
tvdb_id: 1,
|
||||||
title: "test".to_owned(),
|
title: "test".to_owned(),
|
||||||
@@ -549,7 +559,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -567,7 +577,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let add_tag_command = SonarrAddCommand::Tag {
|
let add_tag_command = SonarrAddCommand::Tag {
|
||||||
name: expected_tag_name,
|
name: expected_tag_name,
|
||||||
};
|
};
|
||||||
@@ -576,7 +586,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use crate::{
|
|||||||
app::App,
|
app::App,
|
||||||
cli::{CliCommandHandler, Command},
|
cli::{CliCommandHandler, Command},
|
||||||
models::sonarr_models::DeleteSeriesParams,
|
models::sonarr_models::DeleteSeriesParams,
|
||||||
network::{sonarr_network::SonarrEvent, NetworkTrait},
|
network::{NetworkTrait, sonarr_network::SonarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::SonarrCommand;
|
use super::SonarrCommand;
|
||||||
@@ -94,35 +94,35 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrDeleteCommand> for SonarrDeleteComm
|
|||||||
SonarrDeleteCommand::BlocklistItem { blocklist_item_id } => {
|
SonarrDeleteCommand::BlocklistItem { blocklist_item_id } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(SonarrEvent::DeleteBlocklistItem(Some(blocklist_item_id)).into())
|
.handle_network_event(SonarrEvent::DeleteBlocklistItem(blocklist_item_id).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
SonarrDeleteCommand::Download { download_id } => {
|
SonarrDeleteCommand::Download { download_id } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(SonarrEvent::DeleteDownload(Some(download_id)).into())
|
.handle_network_event(SonarrEvent::DeleteDownload(download_id).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
SonarrDeleteCommand::EpisodeFile { episode_file_id } => {
|
SonarrDeleteCommand::EpisodeFile { episode_file_id } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(SonarrEvent::DeleteEpisodeFile(Some(episode_file_id)).into())
|
.handle_network_event(SonarrEvent::DeleteEpisodeFile(episode_file_id).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
SonarrDeleteCommand::Indexer { indexer_id } => {
|
SonarrDeleteCommand::Indexer { indexer_id } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(SonarrEvent::DeleteIndexer(Some(indexer_id)).into())
|
.handle_network_event(SonarrEvent::DeleteIndexer(indexer_id).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
SonarrDeleteCommand::RootFolder { root_folder_id } => {
|
SonarrDeleteCommand::RootFolder { root_folder_id } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(SonarrEvent::DeleteRootFolder(Some(root_folder_id)).into())
|
.handle_network_event(SonarrEvent::DeleteRootFolder(root_folder_id).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
@@ -138,7 +138,7 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrDeleteCommand> for SonarrDeleteComm
|
|||||||
};
|
};
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
.handle_network_event(SonarrEvent::DeleteSeries(Some(delete_series_params)).into())
|
.handle_network_event(SonarrEvent::DeleteSeries(delete_series_params).into())
|
||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
cli::{
|
|
||||||
sonarr::{delete_command_handler::SonarrDeleteCommand, SonarrCommand},
|
|
||||||
Command,
|
|
||||||
},
|
|
||||||
Cli,
|
Cli,
|
||||||
|
cli::{
|
||||||
|
Command,
|
||||||
|
sonarr::{SonarrCommand, delete_command_handler::SonarrDeleteCommand},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use clap::{error::ErrorKind, CommandFactory, Parser};
|
use clap::{CommandFactory, Parser, error::ErrorKind};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -30,7 +30,7 @@ mod tests {
|
|||||||
let result =
|
let result =
|
||||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "blocklist-item"]);
|
Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "blocklist-item"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -52,12 +52,13 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
{
|
else {
|
||||||
assert_eq!(delete_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(delete_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -65,7 +66,7 @@ mod tests {
|
|||||||
let result =
|
let result =
|
||||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "download"]);
|
Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "download"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -85,12 +86,13 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
{
|
else {
|
||||||
assert_eq!(delete_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(delete_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -98,7 +100,7 @@ mod tests {
|
|||||||
let result =
|
let result =
|
||||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "episode-file"]);
|
Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "episode-file"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -118,19 +120,20 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
{
|
else {
|
||||||
assert_eq!(delete_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(delete_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_delete_indexer_requires_arguments() {
|
fn test_delete_indexer_requires_arguments() {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "indexer"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "indexer"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -150,12 +153,13 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
{
|
else {
|
||||||
assert_eq!(delete_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(delete_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -163,7 +167,7 @@ mod tests {
|
|||||||
let result =
|
let result =
|
||||||
Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "root-folder"]);
|
Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "root-folder"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -183,19 +187,20 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
{
|
else {
|
||||||
assert_eq!(delete_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(delete_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_delete_series_requires_arguments() {
|
fn test_delete_series_requires_arguments() {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "series"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "series"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -213,12 +218,13 @@ mod tests {
|
|||||||
let result =
|
let result =
|
||||||
Cli::try_parse_from(["managarr", "sonarr", "delete", "series", "--series-id", "1"]);
|
Cli::try_parse_from(["managarr", "sonarr", "delete", "series", "--series-id", "1"]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
{
|
else {
|
||||||
assert_eq!(delete_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(delete_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -240,19 +246,20 @@ mod tests {
|
|||||||
"--add-list-exclusion",
|
"--add-list-exclusion",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
{
|
else {
|
||||||
assert_eq!(delete_command, expected_args);
|
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", "sonarr", "delete", "tag"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "tag"]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -265,12 +272,13 @@ mod tests {
|
|||||||
|
|
||||||
let result = Cli::try_parse_from(["managarr", "sonarr", "delete", "tag", "--tag-id", "1"]);
|
let result = Cli::try_parse_from(["managarr", "sonarr", "delete", "tag", "--tag-id", "1"]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
|
||||||
if let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
{
|
else {
|
||||||
assert_eq!(delete_command, expected_args);
|
panic!("Unexpected command type");
|
||||||
}
|
};
|
||||||
|
assert_eq!(delete_command, expected_args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,14 +292,14 @@ mod tests {
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{
|
cli::{
|
||||||
sonarr::delete_command_handler::{SonarrDeleteCommand, SonarrDeleteCommandHandler},
|
|
||||||
CliCommandHandler,
|
CliCommandHandler,
|
||||||
|
sonarr::delete_command_handler::{SonarrDeleteCommand, SonarrDeleteCommandHandler},
|
||||||
},
|
},
|
||||||
models::{
|
models::{
|
||||||
sonarr_models::{DeleteSeriesParams, SonarrSerdeable},
|
|
||||||
Serdeable,
|
Serdeable,
|
||||||
|
sonarr_models::{DeleteSeriesParams, SonarrSerdeable},
|
||||||
},
|
},
|
||||||
network::{sonarr_network::SonarrEvent, MockNetworkTrait, NetworkEvent},
|
network::{MockNetworkTrait, NetworkEvent, sonarr_network::SonarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -301,7 +309,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
SonarrEvent::DeleteBlocklistItem(Some(expected_blocklist_item_id)).into(),
|
SonarrEvent::DeleteBlocklistItem(expected_blocklist_item_id).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -309,7 +317,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let delete_blocklist_item_command = SonarrDeleteCommand::BlocklistItem {
|
let delete_blocklist_item_command = SonarrDeleteCommand::BlocklistItem {
|
||||||
blocklist_item_id: 1,
|
blocklist_item_id: 1,
|
||||||
};
|
};
|
||||||
@@ -322,7 +330,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -332,7 +340,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
SonarrEvent::DeleteDownload(Some(expected_download_id)).into(),
|
SonarrEvent::DeleteDownload(expected_download_id).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -340,7 +348,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let delete_download_command = SonarrDeleteCommand::Download { download_id: 1 };
|
let delete_download_command = SonarrDeleteCommand::Download { download_id: 1 };
|
||||||
|
|
||||||
let result =
|
let result =
|
||||||
@@ -348,7 +356,33 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_delete_episode_file_command() {
|
||||||
|
let expected_episode_file_id = 1;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
SonarrEvent::DeleteEpisodeFile(expected_episode_file_id).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Sonarr(SonarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let delete_episode_file_command = SonarrDeleteCommand::EpisodeFile { episode_file_id: 1 };
|
||||||
|
|
||||||
|
let result =
|
||||||
|
SonarrDeleteCommandHandler::with(&app_arc, delete_episode_file_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -358,7 +392,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
SonarrEvent::DeleteIndexer(Some(expected_indexer_id)).into(),
|
SonarrEvent::DeleteIndexer(expected_indexer_id).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -366,7 +400,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let delete_indexer_command = SonarrDeleteCommand::Indexer { indexer_id: 1 };
|
let delete_indexer_command = SonarrDeleteCommand::Indexer { indexer_id: 1 };
|
||||||
|
|
||||||
let result =
|
let result =
|
||||||
@@ -374,7 +408,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -384,7 +418,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
SonarrEvent::DeleteRootFolder(Some(expected_root_folder_id)).into(),
|
SonarrEvent::DeleteRootFolder(expected_root_folder_id).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -392,7 +426,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let delete_root_folder_command = SonarrDeleteCommand::RootFolder { root_folder_id: 1 };
|
let delete_root_folder_command = SonarrDeleteCommand::RootFolder { root_folder_id: 1 };
|
||||||
|
|
||||||
let result =
|
let result =
|
||||||
@@ -400,7 +434,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -414,7 +448,7 @@ mod tests {
|
|||||||
mock_network
|
mock_network
|
||||||
.expect_handle_network_event()
|
.expect_handle_network_event()
|
||||||
.with(eq::<NetworkEvent>(
|
.with(eq::<NetworkEvent>(
|
||||||
SonarrEvent::DeleteSeries(Some(expected_delete_series_params)).into(),
|
SonarrEvent::DeleteSeries(expected_delete_series_params).into(),
|
||||||
))
|
))
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_| {
|
.returning(|_| {
|
||||||
@@ -422,7 +456,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let delete_series_command = SonarrDeleteCommand::Series {
|
let delete_series_command = SonarrDeleteCommand::Series {
|
||||||
series_id: 1,
|
series_id: 1,
|
||||||
delete_files_from_disk: true,
|
delete_files_from_disk: true,
|
||||||
@@ -434,7 +468,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -452,7 +486,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let delete_tag_command = SonarrDeleteCommand::Tag { tag_id: 1 };
|
let delete_tag_command = SonarrDeleteCommand::Tag { tag_id: 1 };
|
||||||
|
|
||||||
let result =
|
let result =
|
||||||
@@ -460,7 +494,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use crate::{
|
|||||||
app::App,
|
app::App,
|
||||||
cli::{CliCommandHandler, Command},
|
cli::{CliCommandHandler, Command},
|
||||||
models::sonarr_models::SonarrReleaseDownloadBody,
|
models::sonarr_models::SonarrReleaseDownloadBody,
|
||||||
network::{sonarr_network::SonarrEvent, NetworkTrait},
|
network::{NetworkTrait, sonarr_network::SonarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::SonarrCommand;
|
use super::SonarrCommand;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
cli::{
|
|
||||||
sonarr::{download_command_handler::SonarrDownloadCommand, SonarrCommand},
|
|
||||||
Command,
|
|
||||||
},
|
|
||||||
Cli,
|
Cli,
|
||||||
|
cli::{
|
||||||
|
Command,
|
||||||
|
sonarr::{SonarrCommand, download_command_handler::SonarrDownloadCommand},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
@@ -41,7 +41,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -61,7 +61,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -81,7 +81,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -103,7 +103,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -121,7 +121,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -143,7 +143,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -165,7 +165,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -187,7 +187,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -211,7 +211,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -227,7 +227,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -247,7 +247,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -267,7 +267,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_err!(&result);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().kind(),
|
result.unwrap_err().kind(),
|
||||||
ErrorKind::MissingRequiredArgument
|
ErrorKind::MissingRequiredArgument
|
||||||
@@ -289,7 +289,7 @@ mod tests {
|
|||||||
"1",
|
"1",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,14 +303,14 @@ mod tests {
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{
|
cli::{
|
||||||
sonarr::download_command_handler::{SonarrDownloadCommand, SonarrDownloadCommandHandler},
|
|
||||||
CliCommandHandler,
|
CliCommandHandler,
|
||||||
|
sonarr::download_command_handler::{SonarrDownloadCommand, SonarrDownloadCommandHandler},
|
||||||
},
|
},
|
||||||
models::{
|
models::{
|
||||||
sonarr_models::{SonarrReleaseDownloadBody, SonarrSerdeable},
|
|
||||||
Serdeable,
|
Serdeable,
|
||||||
|
sonarr_models::{SonarrReleaseDownloadBody, SonarrSerdeable},
|
||||||
},
|
},
|
||||||
network::{sonarr_network::SonarrEvent, MockNetworkTrait, NetworkEvent},
|
network::{MockNetworkTrait, NetworkEvent, sonarr_network::SonarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -333,7 +333,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let download_release_command = SonarrDownloadCommand::Series {
|
let download_release_command = SonarrDownloadCommand::Series {
|
||||||
guid: "guid".to_owned(),
|
guid: "guid".to_owned(),
|
||||||
indexer_id: 1,
|
indexer_id: 1,
|
||||||
@@ -345,7 +345,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -369,7 +369,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let download_release_command = SonarrDownloadCommand::Season {
|
let download_release_command = SonarrDownloadCommand::Season {
|
||||||
guid: "guid".to_owned(),
|
guid: "guid".to_owned(),
|
||||||
indexer_id: 1,
|
indexer_id: 1,
|
||||||
@@ -382,7 +382,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -405,7 +405,7 @@ mod tests {
|
|||||||
json!({"testResponse": "response"}),
|
json!({"testResponse": "response"}),
|
||||||
)))
|
)))
|
||||||
});
|
});
|
||||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
let download_release_command = SonarrDownloadCommand::Episode {
|
let download_release_command = SonarrDownloadCommand::Episode {
|
||||||
guid: "guid".to_owned(),
|
guid: "guid".to_owned(),
|
||||||
indexer_id: 1,
|
indexer_id: 1,
|
||||||
@@ -417,7 +417,7 @@ mod tests {
|
|||||||
.handle()
|
.handle()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||