mirror of
https://github.com/Cleanuparr/Cleanuparr.git
synced 2026-10-28 02:38:55 -04:00
added lidarr to e2e arr stack
This commit is contained in:
1 parent
f270d81d9a
commit
52ee661d28
11 files changed
+167
-35
No files matched your search
+1
-1
@@ -13,7 +13,7 @@ APP_SERVICES = app nginx
|
||||
|
||||
CORE_STACK = keycloak $(WIREMOCKS)
|
||||
CLIENTS_STACK = $(CORE_STACK) tracker qbittorrent transmission deluge utorrent rutorrent sabnews sabnzbd
|
||||
ARR_STACK = $(CORE_STACK) wiremock-indexer tracker qbittorrent sonarr radarr whisparr
|
||||
ARR_STACK = $(CORE_STACK) wiremock-indexer tracker qbittorrent sonarr radarr whisparr lidarr
|
||||
LAZYLIBRARIAN_STACK = $(CORE_STACK) wiremock-indexer tracker qbittorrent sabnews sabnzbd lazylibrarian
|
||||
|
||||
setup:
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<Config>
|
||||
<BindAddress>*</BindAddress>
|
||||
<Port>8686</Port>
|
||||
<EnableSsl>False</EnableSsl>
|
||||
<LaunchBrowser>False</LaunchBrowser>
|
||||
<ApiKey>0000000000000000000000000000e2e7</ApiKey>
|
||||
<AuthenticationMethod>External</AuthenticationMethod>
|
||||
<AuthenticationRequired>DisabledForLocalAddresses</AuthenticationRequired>
|
||||
<Branch>master</Branch>
|
||||
<LogLevel>info</LogLevel>
|
||||
<UrlBase></UrlBase>
|
||||
<InstanceName>Lidarr</InstanceName>
|
||||
<UpdateMechanism>Docker</UpdateMechanism>
|
||||
<AnalyticsEnabled>False</AnalyticsEnabled>
|
||||
<AllowedHosts></AllowedHosts>
|
||||
<TrustedNetworks></TrustedNetworks>
|
||||
<SslPort>6868</SslPort>
|
||||
<SslCertPath></SslCertPath>
|
||||
<SslCertPassword></SslCertPassword>
|
||||
</Config>
|
||||
Binary file not shown.
@@ -213,7 +213,7 @@ services:
|
||||
retries: 12
|
||||
start_period: 30s
|
||||
|
||||
# Real Sonarr, Radarr and Whisparr, restored from e2e/arr-seed by setup-test-data.sh.
|
||||
# Real Sonarr, Radarr, Whisparr and Lidarr, restored from e2e/arr-seed by setup-test-data.sh.
|
||||
# Every run starts with the same library, API key, indexer and download client.
|
||||
# Regenerate the seed with `make seed-arr`.
|
||||
# The tags are pinned: a seed database is only valid for the version that wrote it.
|
||||
@@ -272,6 +272,24 @@ services:
|
||||
retries: 12
|
||||
start_period: 30s
|
||||
|
||||
lidarr:
|
||||
image: lscr.io/linuxserver/lidarr:3.1.6
|
||||
network_mode: host
|
||||
environment:
|
||||
PUID: "${E2E_PUID:-1000}"
|
||||
PGID: "${E2E_PGID:-1000}"
|
||||
TZ: "UTC"
|
||||
volumes:
|
||||
- ./test-data/lidarr-config:/config
|
||||
- ./test-data/lidarr-music:/music
|
||||
- ./test-data/downloads/qbittorrent:/downloads
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -fs -o /dev/null http://localhost:8686/ping || exit 1"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 12
|
||||
start_period: 30s
|
||||
|
||||
# Real LazyLibrarian, restored from e2e/lazylibrarian-seed by setup-test-data.sh.
|
||||
# LazyLibrarian publishes no versions, so the tag names the upstream commit.
|
||||
# The seed database is only valid for the commit that wrote it.
|
||||
|
||||
+47
-8
@@ -7,7 +7,7 @@
|
||||
# CI therefore needs no network and every run sees the same library.
|
||||
#
|
||||
# Needs internet: each arr reads its metadata provider.
|
||||
# Stops the sonarr, radarr and whisparr containers.
|
||||
# Stops the sonarr, radarr, whisparr and lidarr containers.
|
||||
#
|
||||
# Re-run it when the pinned image tags change, or when the library has to change.
|
||||
#
|
||||
@@ -21,9 +21,11 @@ COMPOSE="docker compose -f $HERE/docker-compose.e2e.yml"
|
||||
SONARR_URL="http://localhost:8989"
|
||||
RADARR_URL="http://localhost:7878"
|
||||
WHISPARR_URL="http://localhost:6970"
|
||||
LIDARR_URL="http://localhost:8686"
|
||||
SONARR_API_KEY="0000000000000000000000000000e2e1"
|
||||
RADARR_API_KEY="0000000000000000000000000000e2e2"
|
||||
WHISPARR_API_KEY="0000000000000000000000000000e2e6"
|
||||
LIDARR_API_KEY="0000000000000000000000000000e2e7"
|
||||
|
||||
# The mock Torznab indexer, and qBittorrent, both on the host network.
|
||||
INDEXER_URL="http://127.0.0.1:9500"
|
||||
@@ -43,6 +45,8 @@ RADARR_TMDB_ID=911430
|
||||
RADARR_SECOND_TMDB_ID=1184918
|
||||
# Package Holiday (2004): a plain title, since the seed database is public.
|
||||
WHISPARR_TMDB_ID=1253309
|
||||
# Jeff Buckley: one studio album and a plain name.
|
||||
LIDARR_MBID=e6e879c0-3d56-4f12-b3c5-3ce459661a8e
|
||||
|
||||
log() {
|
||||
echo "[seed-arr] $*"
|
||||
@@ -134,7 +138,7 @@ wait_for_items() {
|
||||
log "waiting for $name"
|
||||
for _ in $(seq 1 90); do
|
||||
local count
|
||||
count=$(arr_get "$url" "$api_key" "$path" | python3 -c 'import sys,json; print(len(json.load(sys.stdin)))' 2>/dev/null || echo 0)
|
||||
count=$(arr_get "$url" "$api_key" "$path" | python3 -c 'import sys,json; d = json.load(sys.stdin); print(len(d["records"]) if isinstance(d, dict) else len(d))' 2>/dev/null || echo 0)
|
||||
if [[ "$count" -gt 0 ]]; then
|
||||
log "$name ready ($count)"
|
||||
return 0
|
||||
@@ -149,9 +153,9 @@ wait_for_items() {
|
||||
# forceSave skips the field validation, but not the connection test.
|
||||
# The mock indexer has to be up and answering caps.
|
||||
add_indexer() {
|
||||
local url="$1" api_key="$2" categories="$3"
|
||||
local url="$1" api_key="$2" categories="$3" api_version="${4:-v3}"
|
||||
|
||||
arr_post "$url" "$api_key" '/api/v3/indexer?forceSave=true' "$(cat <<EOF
|
||||
arr_post "$url" "$api_key" "/api/$api_version/indexer?forceSave=true" "$(cat <<EOF
|
||||
{
|
||||
"name": "E2E Torznab",
|
||||
"implementation": "Torznab",
|
||||
@@ -179,9 +183,9 @@ EOF
|
||||
}
|
||||
|
||||
add_download_client() {
|
||||
local url="$1" api_key="$2" category_field="$3" category="$4"
|
||||
local url="$1" api_key="$2" category_field="$3" category="$4" api_version="${5:-v3}"
|
||||
|
||||
arr_post "$url" "$api_key" '/api/v3/downloadclient?forceSave=true' "$(cat <<EOF
|
||||
arr_post "$url" "$api_key" "/api/$api_version/downloadclient?forceSave=true" "$(cat <<EOF
|
||||
{
|
||||
"name": "qBittorrent",
|
||||
"implementation": "QBittorrent",
|
||||
@@ -271,6 +275,35 @@ seed_whisparr() {
|
||||
wait_for_items "$WHISPARR_URL" "$WHISPARR_API_KEY" '/api/v3/movie' 'whisparr movies'
|
||||
}
|
||||
|
||||
seed_lidarr() {
|
||||
log "configuring lidarr"
|
||||
arr_post "$LIDARR_URL" "$LIDARR_API_KEY" '/api/v1/rootfolder' \
|
||||
'{"name":"Music","path":"/music","defaultMetadataProfileId":1,"defaultQualityProfileId":1,"defaultMonitorOption":"all"}' > /dev/null
|
||||
add_indexer "$LIDARR_URL" "$LIDARR_API_KEY" '[3000]' 'v1'
|
||||
add_download_client "$LIDARR_URL" "$LIDARR_API_KEY" 'musicCategory' 'lidarr' 'v1'
|
||||
|
||||
log "adding the artist"
|
||||
local artist
|
||||
artist=$(arr_get "$LIDARR_URL" "$LIDARR_API_KEY" "/api/v1/artist/lookup?term=lidarr%3A$LIDARR_MBID" \
|
||||
| python3 -c '
|
||||
import json
|
||||
import sys
|
||||
|
||||
item = json.load(sys.stdin)[0]
|
||||
item.update({
|
||||
"qualityProfileId": 1,
|
||||
"metadataProfileId": 1,
|
||||
"rootFolderPath": "/music",
|
||||
"monitored": True,
|
||||
"addOptions": {"monitor": "all", "searchForMissingAlbums": False},
|
||||
})
|
||||
print(json.dumps(item))
|
||||
')
|
||||
arr_post "$LIDARR_URL" "$LIDARR_API_KEY" '/api/v1/artist' "$artist" > /dev/null
|
||||
|
||||
wait_for_items "$LIDARR_URL" "$LIDARR_API_KEY" '/api/v1/wanted/missing' 'lidarr missing albums'
|
||||
}
|
||||
|
||||
# Keeps the database and config.xml, drops everything the arr rebuilds on start.
|
||||
# Whisparr v3 names its database whisparr3.db.
|
||||
snapshot() {
|
||||
@@ -302,30 +335,36 @@ main() {
|
||||
write_config_xml "$TEST_DATA/sonarr-config" 8989 "$SONARR_API_KEY" 'Sonarr'
|
||||
write_config_xml "$TEST_DATA/radarr-config" 7878 "$RADARR_API_KEY" 'Radarr'
|
||||
write_config_xml "$TEST_DATA/whisparr-config" 6970 "$WHISPARR_API_KEY" 'Whisparr'
|
||||
write_config_xml "$TEST_DATA/lidarr-config" 8686 "$LIDARR_API_KEY" 'Lidarr'
|
||||
|
||||
# An arr tests the indexer and the download client before it saves them.
|
||||
# Both have to answer during seeding.
|
||||
log "starting the arrs"
|
||||
$COMPOSE up -d --force-recreate wiremock-indexer qbittorrent sonarr radarr whisparr
|
||||
$COMPOSE up -d --force-recreate wiremock-indexer qbittorrent sonarr radarr whisparr lidarr
|
||||
|
||||
wait_for_arr "$SONARR_URL" 'sonarr'
|
||||
wait_for_arr "$RADARR_URL" 'radarr'
|
||||
wait_for_arr "$WHISPARR_URL" 'whisparr'
|
||||
wait_for_arr "$LIDARR_URL" 'lidarr'
|
||||
|
||||
seed_sonarr
|
||||
seed_radarr
|
||||
seed_whisparr
|
||||
seed_lidarr
|
||||
|
||||
log "stopping the arrs"
|
||||
$COMPOSE stop wiremock-indexer qbittorrent sonarr radarr whisparr
|
||||
$COMPOSE stop wiremock-indexer qbittorrent sonarr radarr whisparr lidarr
|
||||
|
||||
snapshot sonarr
|
||||
snapshot radarr
|
||||
snapshot whisparr whisparr3
|
||||
snapshot lidarr
|
||||
|
||||
# The seed is public, so it keeps the title and drops the synopsis, posters and cast.
|
||||
sqlite3 "$ARR_SEED/whisparr/whisparr3.db" "UPDATE MovieMetadata SET Overview = '', Images = '[]', Credits = '[]'; VACUUM;"
|
||||
|
||||
sqlite3 "$ARR_SEED/lidarr/lidarr.db" "UPDATE ArtistMetadata SET Overview = '', Images = '[]', Links = '[]'; UPDATE Albums SET Overview = '', Images = '[]', Links = '[]'; VACUUM;"
|
||||
|
||||
log "seed written to $ARR_SEED"
|
||||
}
|
||||
|
||||
|
||||
@@ -16,13 +16,14 @@ ARR_SEED="$HERE/arr-seed"
|
||||
rm -rf "$TEST_DATA/torznab-src"
|
||||
|
||||
# Media an arr imports during a run, wiped so its library always starts empty.
|
||||
rm -rf "$TEST_DATA/sonarr-tv" "$TEST_DATA/radarr-movies" "$TEST_DATA/whisparr-movies" "$TEST_DATA/lazylibrarian-books"
|
||||
rm -rf "$TEST_DATA/sonarr-tv" "$TEST_DATA/radarr-movies" "$TEST_DATA/whisparr-movies" "$TEST_DATA/lidarr-music" "$TEST_DATA/lazylibrarian-books"
|
||||
|
||||
mkdir -p \
|
||||
"$TEST_DATA/torznab-src" \
|
||||
"$TEST_DATA/sonarr-tv" \
|
||||
"$TEST_DATA/radarr-movies" \
|
||||
"$TEST_DATA/whisparr-movies" \
|
||||
"$TEST_DATA/lidarr-music" \
|
||||
"$TEST_DATA/lazylibrarian-books" \
|
||||
"$TEST_DATA/downloads/qbittorrent" \
|
||||
"$TEST_DATA/downloads/transmission" \
|
||||
@@ -101,9 +102,9 @@ script = Default
|
||||
priority = 0
|
||||
EOF
|
||||
|
||||
# Restore Sonarr, Radarr and Whisparr from the committed seed.
|
||||
# Restore Sonarr, Radarr, Whisparr and Lidarr from the committed seed.
|
||||
# The arrs write to /config on every start, so the run gets a throwaway copy.
|
||||
for arr in sonarr radarr whisparr; do
|
||||
for arr in sonarr radarr whisparr lidarr; do
|
||||
if [[ -d "$ARR_SEED/$arr" ]]; then
|
||||
rm -rf "$TEST_DATA/$arr-config"
|
||||
cp -R "$ARR_SEED/$arr" "$TEST_DATA/$arr-config"
|
||||
|
||||
@@ -2,7 +2,7 @@ import { TEST_CONFIG } from './test-config';
|
||||
import { WireMockClient } from './mocks/wiremock-client';
|
||||
|
||||
/**
|
||||
* Direct access to the real Sonarr, Radarr and Whisparr v3 containers.
|
||||
* Direct access to the real Sonarr, Radarr, Whisparr v3 and Lidarr containers.
|
||||
*
|
||||
* The specs drive Cleanuparr through its own API, like every other spec.
|
||||
* This is the other side of the contract: what the arr itself ended up with.
|
||||
@@ -18,6 +18,8 @@ export interface LiveArrQueueRecord {
|
||||
seriesId?: number;
|
||||
seasonNumber?: number;
|
||||
movieId?: number;
|
||||
artistId?: number;
|
||||
albumId?: number;
|
||||
}
|
||||
|
||||
export interface LiveArrCommand {
|
||||
@@ -32,6 +34,7 @@ export class LiveArr {
|
||||
readonly url: string,
|
||||
private readonly apiKey: string,
|
||||
private readonly queueQuery: string,
|
||||
readonly apiVersion: string = 'v3',
|
||||
) {}
|
||||
|
||||
get<T>(path: string): Promise<T> {
|
||||
@@ -52,14 +55,14 @@ export class LiveArr {
|
||||
|
||||
/** Creates the tag if the arr does not have it yet, and returns its id. */
|
||||
async ensureTag(label: string): Promise<number> {
|
||||
const tags = await this.get<Array<{ id: number; label: string }>>('/api/v3/tag');
|
||||
const tags = await this.get<Array<{ id: number; label: string }>>(`/api/${this.apiVersion}/tag`);
|
||||
const existing = tags.find((tag) => tag.label === label);
|
||||
|
||||
if (existing) {
|
||||
return existing.id;
|
||||
}
|
||||
|
||||
return (await this.post<{ id: number }>('/api/v3/tag', { label })).id;
|
||||
return (await this.post<{ id: number }>(`/api/${this.apiVersion}/tag`, { label })).id;
|
||||
}
|
||||
|
||||
private async request(path: string, init?: RequestInit): Promise<unknown> {
|
||||
@@ -83,19 +86,19 @@ export class LiveArr {
|
||||
}
|
||||
|
||||
async queuePage(page = 1): Promise<{ records?: LiveArrQueueRecord[]; totalRecords: number }> {
|
||||
return (await this.request(`/api/v3/queue?page=${page}&pageSize=200&${this.queueQuery}`)) as {
|
||||
return (await this.request(`/api/${this.apiVersion}/queue?page=${page}&pageSize=200&${this.queueQuery}`)) as {
|
||||
records?: LiveArrQueueRecord[];
|
||||
totalRecords: number;
|
||||
};
|
||||
}
|
||||
|
||||
async commands(): Promise<LiveArrCommand[]> {
|
||||
return (await this.request('/api/v3/command')) as LiveArrCommand[];
|
||||
return (await this.request(`/api/${this.apiVersion}/command`)) as LiveArrCommand[];
|
||||
}
|
||||
|
||||
/** The arr only queues a grab on its own refresh, which runs once a minute. */
|
||||
async refreshMonitoredDownloads(): Promise<void> {
|
||||
await this.request('/api/v3/command', {
|
||||
await this.request(`/api/${this.apiVersion}/command`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ name: 'RefreshMonitoredDownloads' }),
|
||||
});
|
||||
@@ -105,7 +108,7 @@ export class LiveArr {
|
||||
async clearQueue(): Promise<void> {
|
||||
for (const record of await this.queue()) {
|
||||
await this.request(
|
||||
`/api/v3/queue/${record.id}?removeFromClient=true&blocklist=false&skipRedownload=true`,
|
||||
`/api/${this.apiVersion}/queue/${record.id}?removeFromClient=true&blocklist=false&skipRedownload=true`,
|
||||
{ method: 'DELETE' },
|
||||
);
|
||||
}
|
||||
@@ -147,5 +150,12 @@ export const liveWhisparr = new LiveArr(
|
||||
'includeUnknownMovieItems=true&includeMovie=true',
|
||||
);
|
||||
|
||||
export const liveLidarr = new LiveArr(
|
||||
TEST_CONFIG.liveArr.lidarrUrl,
|
||||
TEST_CONFIG.liveArr.lidarrApiKey,
|
||||
'includeUnknownArtistItems=true&includeArtist=true&includeAlbum=true',
|
||||
'v1',
|
||||
);
|
||||
|
||||
/** The fake Torznab indexer, kept out of MockServers: other suites lack its container. */
|
||||
export const indexerMock = new WireMockClient(TEST_CONFIG.mocks.indexerAdminUrl);
|
||||
@@ -10,13 +10,14 @@ import type { Mapping } from './wiremock-client';
|
||||
* A test adds the release it wants grabbed, plus the torrent that release points at.
|
||||
*/
|
||||
|
||||
/** Torznab search mode. Sonarr sends `tvsearch`, Radarr sends `movie`. */
|
||||
export type TorznabSearchMode = 'tvsearch' | 'movie' | 'search';
|
||||
/** Torznab search mode. Sonarr sends `tvsearch`, Radarr sends `movie`, Lidarr sends `music`. */
|
||||
export type TorznabSearchMode = 'tvsearch' | 'movie' | 'music' | 'search';
|
||||
|
||||
/** Torznab category. The seeded arrs are configured for the parent categories. */
|
||||
export const TORZNAB_TV_CATEGORY = 5040;
|
||||
export const TORZNAB_MOVIE_CATEGORY = 2040;
|
||||
export const TORZNAB_XXX_CATEGORY = 6040;
|
||||
export const TORZNAB_AUDIO_CATEGORY = 3040;
|
||||
|
||||
/**
|
||||
* The advertised release size.
|
||||
@@ -120,6 +121,11 @@ export interface GrabbableRelease {
|
||||
mappings: Mapping[];
|
||||
}
|
||||
|
||||
/** Keeps the link path free of characters the arr would percent-encode, which WireMock matches raw. */
|
||||
function torrentFile(title: string): string {
|
||||
return `${title.replace(/[^A-Za-z0-9.-]/g, '.')}.torrent`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a release the arr can grab: a torrent, a search hit, a download link.
|
||||
*
|
||||
@@ -131,7 +137,7 @@ export function grabbableRelease(
|
||||
title: string,
|
||||
category: number,
|
||||
): GrabbableRelease {
|
||||
const file = `${title}.torrent`;
|
||||
const file = torrentFile(title);
|
||||
const torrent = buildSingleFileTorrent(TORRENT_SOURCE_DIR, title, 32_768, 'http://127.0.0.1:6969/announce');
|
||||
|
||||
return {
|
||||
@@ -162,7 +168,7 @@ export function grabbableReleases(
|
||||
mappings: [
|
||||
torznabSearchStub(
|
||||
mode,
|
||||
built.map((entry) => ({ title: entry.title, category, file: `${entry.title}.torrent` })),
|
||||
built.map((entry) => ({ title: entry.title, category, file: torrentFile(entry.title) })),
|
||||
),
|
||||
// Index 1 of each release's mappings is its own torrent stub.
|
||||
...built.map((entry) => entry.release.mappings[1]),
|
||||
|
||||
@@ -6,9 +6,14 @@ import { buildSingleFileTorrent } from './torrent-fixtures';
|
||||
import type { CleanuparrApi } from './api';
|
||||
import { buildDownloadClientPayload } from './api/download-client';
|
||||
import type { ArrType } from './api/arr';
|
||||
import { LiveArr, indexerMock, liveRadarr, liveSonarr, liveWhisparr } from './live-arr';
|
||||
import { LiveArr, indexerMock, liveLidarr, liveRadarr, liveSonarr, liveWhisparr } from './live-arr';
|
||||
import { QBittorrentDriver } from './torrent-clients/qbittorrent';
|
||||
import { TORZNAB_MOVIE_CATEGORY, TORZNAB_TV_CATEGORY, TORZNAB_XXX_CATEGORY } from './mocks/torznab-stubs';
|
||||
import {
|
||||
TORZNAB_AUDIO_CATEGORY,
|
||||
TORZNAB_MOVIE_CATEGORY,
|
||||
TORZNAB_TV_CATEGORY,
|
||||
TORZNAB_XXX_CATEGORY,
|
||||
} from './mocks/torznab-stubs';
|
||||
import { mkdirShared, writeFileShared } from './shared-volume';
|
||||
|
||||
/**
|
||||
@@ -54,13 +59,13 @@ export interface SeededArr {
|
||||
url: string;
|
||||
apiKey: string;
|
||||
version: number;
|
||||
searchMode: 'tvsearch' | 'movie';
|
||||
searchMode: 'tvsearch' | 'movie' | 'music';
|
||||
category: number;
|
||||
release: string;
|
||||
/** Id of the seeded library item, which is always 1 in a freshly restored seed. */
|
||||
itemId: number;
|
||||
/** The arr endpoint holding that item. */
|
||||
itemPath: 'series' | 'movie';
|
||||
itemPath: 'series' | 'movie' | 'artist';
|
||||
/** Title the seed gave that item. */
|
||||
seededTitle: string;
|
||||
/** Download client category the arr grabs into, set by the seed. */
|
||||
@@ -112,6 +117,21 @@ export const WHISPARR: SeededArr = {
|
||||
downloadCategory: 'whisparr',
|
||||
};
|
||||
|
||||
export const LIDARR: SeededArr = {
|
||||
type: 'lidarr',
|
||||
arr: liveLidarr,
|
||||
url: TEST_CONFIG.liveArr.lidarrUrl,
|
||||
apiKey: TEST_CONFIG.liveArr.lidarrApiKey,
|
||||
version: 3,
|
||||
searchMode: 'music',
|
||||
category: TORZNAB_AUDIO_CATEGORY,
|
||||
release: `${TEST_CONFIG.liveArr.seededArtistName} - ${TEST_CONFIG.liveArr.seededAlbumTitle} (1994) [FLAC]-E2E${RUN_TAG}`,
|
||||
itemId: 1,
|
||||
itemPath: 'artist',
|
||||
seededTitle: TEST_CONFIG.liveArr.seededArtistName,
|
||||
downloadCategory: 'lidarr',
|
||||
};
|
||||
|
||||
export const qbittorrent = new QBittorrentDriver();
|
||||
|
||||
/** Outside the qBittorrent save path, so a decoy download never finds its data. */
|
||||
@@ -259,7 +279,7 @@ export async function teardownInstances(api: CleanuparrApi): Promise<void> {
|
||||
* A spec takes this in its setup and restores it in its teardown.
|
||||
*/
|
||||
export async function snapshotLibrary(target: SeededArr): Promise<Array<Record<string, unknown>>> {
|
||||
return target.arr.get<Array<Record<string, unknown>>>(`/api/v3/${target.itemPath}`);
|
||||
return target.arr.get<Array<Record<string, unknown>>>(`/api/${target.arr.apiVersion}/${target.itemPath}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,7 +291,7 @@ export async function snapshotLibrary(target: SeededArr): Promise<Array<Record<s
|
||||
*/
|
||||
export async function restoreLibrary(target: SeededArr, snapshot: Array<Record<string, unknown>>): Promise<void> {
|
||||
for (const item of snapshot) {
|
||||
await target.arr.put(`/api/v3/${target.itemPath}/${item.id}`, item);
|
||||
await target.arr.put(`/api/${target.arr.apiVersion}/${target.itemPath}/${item.id}`, item);
|
||||
}
|
||||
|
||||
const restored = await snapshotLibrary(target);
|
||||
@@ -311,8 +331,18 @@ export async function waitForArrQueue(arr: LiveArr, downloadId: string, timeoutM
|
||||
*/
|
||||
export async function resetDownloads(): Promise<void> {
|
||||
await qbittorrent.clearAllTorrents();
|
||||
await Promise.all([liveSonarr.clearQueue(), liveRadarr.clearQueue(), liveWhisparr.clearQueue()]);
|
||||
await Promise.all([waitForEmptyQueue(liveSonarr), waitForEmptyQueue(liveRadarr), waitForEmptyQueue(liveWhisparr)]);
|
||||
await Promise.all([
|
||||
liveSonarr.clearQueue(),
|
||||
liveRadarr.clearQueue(),
|
||||
liveWhisparr.clearQueue(),
|
||||
liveLidarr.clearQueue(),
|
||||
]);
|
||||
await Promise.all([
|
||||
waitForEmptyQueue(liveSonarr),
|
||||
waitForEmptyQueue(liveRadarr),
|
||||
waitForEmptyQueue(liveWhisparr),
|
||||
waitForEmptyQueue(liveLidarr),
|
||||
]);
|
||||
}
|
||||
|
||||
async function waitForEmptyQueue(arr: LiveArr, timeoutMs = 120_000): Promise<void> {
|
||||
|
||||
@@ -26,7 +26,7 @@ export const TEST_CONFIG = {
|
||||
indexerAdminUrl: 'http://localhost:9500/__admin',
|
||||
},
|
||||
|
||||
// The real Sonarr, Radarr and Whisparr v3 containers.
|
||||
// The real Sonarr, Radarr, Whisparr v3 and Lidarr containers.
|
||||
// Their api keys come from the committed seed in e2e/arr-seed.
|
||||
liveArr: {
|
||||
sonarrUrl: 'http://localhost:8989',
|
||||
@@ -38,6 +38,10 @@ export const TEST_CONFIG = {
|
||||
whisparrUrl: 'http://localhost:6970',
|
||||
whisparrApiKey: '0000000000000000000000000000e2e6',
|
||||
seededWhisparrTitle: 'Package Holiday',
|
||||
lidarrUrl: 'http://localhost:8686',
|
||||
lidarrApiKey: '0000000000000000000000000000e2e7',
|
||||
seededArtistName: 'Jeff Buckley',
|
||||
seededAlbumTitle: 'Grace',
|
||||
},
|
||||
|
||||
// Real qBittorrent container: the API key seeded into
|
||||
|
||||
@@ -6,14 +6,18 @@
|
||||
<search available="yes" supportedParams="q" />
|
||||
<tv-search available="yes" supportedParams="q,season,ep,tvdbid" />
|
||||
<movie-search available="yes" supportedParams="q,imdbid,tmdbid" />
|
||||
<music-search available="no" supportedParams="q" />
|
||||
<audio-search available="no" supportedParams="q" />
|
||||
<music-search available="yes" supportedParams="q,artist,album" />
|
||||
<audio-search available="yes" supportedParams="q,artist,album" />
|
||||
<book-search available="no" supportedParams="q" />
|
||||
</searching>
|
||||
<categories>
|
||||
<category id="2000" name="Movies">
|
||||
<subcat id="2040" name="Movies/HD" />
|
||||
</category>
|
||||
<category id="3000" name="Audio">
|
||||
<subcat id="3010" name="Audio/MP3" />
|
||||
<subcat id="3040" name="Audio/Lossless" />
|
||||
</category>
|
||||
<category id="5000" name="TV">
|
||||
<subcat id="5040" name="TV/HD" />
|
||||
</category>
|
||||
|
||||
Reference in new issue
Block a user