fix: add testing-torrents params

This commit is contained in:
master 2025-04-05 09:20:51 +08:00
parent ecb56013a5
commit 3dfcf2a536
10 changed files with 2617 additions and 1603 deletions

View File

@ -209,6 +209,7 @@ pub struct QBittorrentDownloaderCreation {
pub save_path: String, pub save_path: String,
pub subscriber_id: i32, pub subscriber_id: i32,
pub downloader_id: i32, pub downloader_id: i32,
pub wait_sync_timeout: Option<Duration>,
} }
pub type QBittorrentHashSelector = DownloadIdSelector<QBittorrentTask>; pub type QBittorrentHashSelector = DownloadIdSelector<QBittorrentTask>;
@ -354,7 +355,9 @@ impl QBittorrentDownloader {
endpoint_url, endpoint_url,
subscriber_id: creation.subscriber_id, subscriber_id: creation.subscriber_id,
save_path: creation.save_path.into(), save_path: creation.save_path.into(),
wait_sync_timeout: Duration::from_millis(10000), wait_sync_timeout: creation
.wait_sync_timeout
.unwrap_or(Duration::from_millis(10000)),
downloader_id: creation.downloader_id, downloader_id: creation.downloader_id,
sync_watch: watch::channel(Utc::now()).0, sync_watch: watch::channel(Utc::now()).0,
sync_data: Arc::new(RwLock::new(QBittorrentSyncData::default())), sync_data: Arc::new(RwLock::new(QBittorrentSyncData::default())),
@ -610,7 +613,7 @@ impl QBittorrentDownloader {
let mut receiver = self.sync_watch.subscribe(); let mut receiver = self.sync_watch.subscribe();
while let Ok(()) = receiver.changed().await { while let Ok(()) = receiver.changed().await {
let has_timeout = { let has_timeout = {
let sync_time = receiver.borrow().clone(); let sync_time = *receiver.borrow();
sync_time sync_time
.signed_duration_since(start_time) .signed_duration_since(start_time)
.num_milliseconds() .num_milliseconds()
@ -625,7 +628,7 @@ impl QBittorrentDownloader {
} }
{ {
let sync_data = &self.sync_data.read().await; let sync_data = &self.sync_data.read().await;
if stop_wait_fn(&sync_data) { if stop_wait_fn(sync_data) {
break; break;
} }
} }
@ -827,6 +830,8 @@ impl Debug for QBittorrentDownloader {
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use serde::{Deserialize, Serialize};
use super::*; use super::*;
use crate::{ use crate::{
downloader::core::DownloadIdSelectorTrait, downloader::core::DownloadIdSelectorTrait,
@ -842,27 +847,47 @@ pub mod tests {
} }
} }
#[derive(Serialize)]
struct MockFileItem {
path: String,
size: u64,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct MockRequest {
id: String,
file_list: Vec<MockFileItem>,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
#[allow(dead_code)]
pub struct MockResponse {
torrent_url: String,
magnet_url: String,
hash: String,
}
#[cfg(feature = "testcontainers")] #[cfg(feature = "testcontainers")]
pub async fn create_torrents_testcontainers() pub async fn create_torrents_testcontainers()
-> RResult<testcontainers::ContainerRequest<testcontainers::GenericImage>> { -> RResult<testcontainers::ContainerRequest<testcontainers::GenericImage>> {
use testcontainers::{ use testcontainers::{
GenericImage, GenericImage,
core::{ core::{ContainerPort, WaitFor},
ContainerPort,
// ReuseDirective,
WaitFor,
},
}; };
use testcontainers_modules::testcontainers::ImageExt; use testcontainers_modules::testcontainers::ImageExt;
use crate::test_utils::testcontainers::ContainerRequestEnhancedExt; use crate::test_utils::testcontainers::ContainerRequestEnhancedExt;
let container = GenericImage::new("ghcr.io/dumtruck/konobangu-testing-torrents", "latest") let container = GenericImage::new("ghcr.io/dumtruck/konobangu-testing-torrents", "latest")
.with_exposed_port() .with_wait_for(WaitFor::message_on_stdout("Listening on"))
.with_wait_for(WaitFor::message_on_stderr("Connection to localhost")) .with_mapped_port(6080, ContainerPort::Tcp(6080))
.with_mapped_port(6081, ContainerPort::Tcp(6081))
.with_mapped_port(6082, ContainerPort::Tcp(6082))
// .with_reuse(ReuseDirective::Always) // .with_reuse(ReuseDirective::Always)
.with_default_log_consumer() .with_default_log_consumer()
.with_prune_existed_label("qbit-downloader", true, true) .with_prune_existed_label("konobangu-testing-torrents", true, true)
.await?; .await?;
Ok(container) Ok(container)
@ -913,17 +938,40 @@ pub mod tests {
use tokio::io::AsyncReadExt; use tokio::io::AsyncReadExt;
tracing_subscriber::fmt() tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG) .with_max_level(tracing::Level::TRACE)
.with_test_writer() .with_test_writer()
.init(); .init();
let image = create_qbit_testcontainers().await?; let torrents_image = create_torrents_testcontainers().await?;
let torrents_container = torrents_image.start().await?;
let container = image.start().await?; let torrents_req = MockRequest {
id: "test".into(),
file_list: vec![MockFileItem {
path: "test.torrent".into(),
size: 1024,
}],
};
let torrent_res: MockResponse = reqwest::Client::builder()
.pool_max_idle_per_host(0)
.build()?
.post("http://127.0.0.1:6080/api/torrents/mock")
.json(&torrents_req)
.send()
.await?
.json()
.await?;
let qbit_image = create_qbit_testcontainers().await?;
let qbit_container = qbit_image.start().await?;
let mut logs = String::new(); let mut logs = String::new();
container.stdout(false).read_to_string(&mut logs).await?; qbit_container
.stdout(false)
.read_to_string(&mut logs)
.await?;
let username = logs let username = logs
.lines() .lines()
@ -940,7 +988,10 @@ pub mod tests {
let password = logs let password = logs
.lines() .lines()
.find_map(|line| { .find_map(|line| {
if line.contains("A temporary password is provided for this session") { if line.contains(
"A temporary password is provided for this
session",
) {
line.split_whitespace().last() line.split_whitespace().last()
} else { } else {
None None
@ -951,7 +1002,15 @@ pub mod tests {
tracing::info!(username, password); tracing::info!(username, password);
test_qbittorrent_downloader_impl(Some(username), Some(password)).await?; test_qbittorrent_downloader_impl(
torrent_res.torrent_url,
torrent_res.hash,
Some(username),
Some(password),
)
.await?;
torrents_container.stop().await?;
Ok(()) Ok(())
} }
@ -965,31 +1024,25 @@ pub mod tests {
let http_client = build_testing_http_client()?; let http_client = build_testing_http_client()?;
let base_save_path = Path::new(get_tmp_qbit_test_folder()); let base_save_path = Path::new(get_tmp_qbit_test_folder());
let hash = "47ee2d69e7f19af783ad896541a07b012676f858".to_string(); let downloader = QBittorrentDownloader::from_creation(QBittorrentDownloaderCreation {
let mut downloader = QBittorrentDownloader::from_creation(QBittorrentDownloaderCreation {
endpoint: "http://127.0.0.1:8080".to_string(), endpoint: "http://127.0.0.1:8080".to_string(),
password: password.unwrap_or_default().to_string(), password: password.unwrap_or_default().to_string(),
username: username.unwrap_or_default().to_string(), username: username.unwrap_or_default().to_string(),
subscriber_id: 0, subscriber_id: 0,
save_path: base_save_path.to_string(), save_path: base_save_path.to_string(),
downloader_id: 0, downloader_id: 0,
wait_sync_timeout: Some(Duration::from_secs(3)),
}) })
.await?; .await?;
downloader.wait_sync_timeout = Duration::from_secs(3);
downloader.check_connection().await?; downloader.check_connection().await?;
downloader downloader
.remove_torrents(vec![hash.clone()].into()) .remove_torrents(vec![torrent_hash.clone()].into())
.await?; .await?;
let torrent_source = HashTorrentSource::from_url_and_http_client( let torrent_source =
&http_client, HashTorrentSource::from_url_and_http_client(&http_client, torrent_url).await?;
format!("https://mikanani.me/Download/20240301/{}.torrent", &hash),
)
.await?;
let folder_name = format!("torrent_test_{}", Utc::now().timestamp()); let folder_name = format!("torrent_test_{}", Utc::now().timestamp());
let save_path = base_save_path.join(&folder_name); let save_path = base_save_path.join(&folder_name);
@ -1006,13 +1059,13 @@ pub mod tests {
let get_torrent = async || -> Result<QBittorrentTask, DownloaderError> { let get_torrent = async || -> Result<QBittorrentTask, DownloaderError> {
let torrent_infos = downloader let torrent_infos = downloader
.query_downloads(QBittorrentSelector::Hash(QBittorrentHashSelector::from_id( .query_downloads(QBittorrentSelector::Hash(QBittorrentHashSelector::from_id(
hash.clone(), torrent_hash.clone(),
))) )))
.await?; .await?;
let result = torrent_infos let result = torrent_infos
.into_iter() .into_iter()
.find(|t| t.hash_info() == hash) .find(|t| t.hash_info() == torrent_hash)
.whatever_context::<_, DownloaderError>("no bittorrent")?; .whatever_context::<_, DownloaderError>("no bittorrent")?;
Ok(result) Ok(result)
@ -1032,7 +1085,7 @@ pub mod tests {
let test_tag = format!("test_tag_{}", Utc::now().timestamp()); let test_tag = format!("test_tag_{}", Utc::now().timestamp());
downloader downloader
.add_torrent_tags(vec![hash.clone()], vec![test_tag.clone()]) .add_torrent_tags(vec![torrent_hash.clone()], vec![test_tag.clone()])
.await?; .await?;
let target_torrent = get_torrent().await?; let target_torrent = get_torrent().await?;
@ -1042,7 +1095,7 @@ pub mod tests {
let test_category = format!("test_category_{}", Utc::now().timestamp()); let test_category = format!("test_category_{}", Utc::now().timestamp());
downloader downloader
.set_torrents_category(vec![hash.clone()], &test_category) .set_torrents_category(vec![torrent_hash.clone()], &test_category)
.await?; .await?;
let target_torrent = get_torrent().await?; let target_torrent = get_torrent().await?;
@ -1055,7 +1108,7 @@ pub mod tests {
let moved_torrent_path = base_save_path.join(format!("moved_{}", Utc::now().timestamp())); let moved_torrent_path = base_save_path.join(format!("moved_{}", Utc::now().timestamp()));
downloader downloader
.move_torrents(vec![hash.clone()], moved_torrent_path.as_str()) .move_torrents(vec![torrent_hash.clone()], moved_torrent_path.as_str())
.await?; .await?;
let target_torrent = get_torrent().await?; let target_torrent = get_torrent().await?;
@ -1073,7 +1126,7 @@ pub mod tests {
); );
downloader downloader
.move_torrent_contents(&hash, |f| { .move_torrent_contents(&torrent_hash, |f| {
f.replace(&folder_name, &format!("moved_{}", &folder_name)) f.replace(&folder_name, &format!("moved_{}", &folder_name))
}) })
.await?; .await?;
@ -1096,7 +1149,7 @@ pub mod tests {
); );
downloader downloader
.remove_torrents(vec![hash.clone()].into()) .remove_torrents(vec![torrent_hash.clone()].into())
.await?; .await?;
let torrent_infos1 = downloader let torrent_infos1 = downloader

View File

@ -79,7 +79,9 @@ where
.await .await
.map_err(|error| TestcontainersError::Other(Box::new(error)))?; .map_err(|error| TestcontainersError::Other(Box::new(error)))?;
tracing::warn!(name = "stop running containers", result = ?remove_containers); if !remove_containers.is_empty() {
tracing::warn!(name = "stop running containers", result = ?remove_containers);
}
} }
let result = client let result = client
@ -87,7 +89,13 @@ where
.await .await
.map_err(|err| TestcontainersError::Other(Box::new(err)))?; .map_err(|err| TestcontainersError::Other(Box::new(err)))?;
tracing::warn!(name = "prune existed containers", result = ?result); if result
.containers_deleted
.as_ref()
.is_some_and(|c| !c.is_empty())
{
tracing::warn!(name = "prune existed containers", result = ?result);
}
} }
let result = self.with_labels([ let result = self.with_labels([

View File

@ -8,11 +8,14 @@ FROM nodebt AS deps
RUN mkdir -p /app/workspace RUN mkdir -p /app/workspace
WORKDIR /app WORKDIR /app
COPY package.json /app/ COPY package.json /app/
RUN pnpm approve-builds utf-8-validate node-datachannel utp-native
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --no-frozen-lockfile RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --no-frozen-lockfile
FROM deps AS app FROM deps AS app
COPY main.ts /app/ COPY main.ts /app/
EXPOSE 6080
EXPOSE 6081
EXPOSE 6082
CMD [ "npm", "start" ] CMD [ "npm", "start" ]

View File

@ -1,5 +1,11 @@
# Konobangu Testing Torrents Container # Konobangu Testing Torrents Container
## Development
```bash
pnpm install --ignore-workspace
```
## Build ## Build
```bash ```bash
@ -17,4 +23,4 @@ docker run --network_mode=host --name konobangu-testing-torrents konobangu-testi
```bash ```bash
docker tag konobangu-testing-torrents:latest ghcr.io/dumtruck/konobangu-testing-torrents:latest docker tag konobangu-testing-torrents:latest ghcr.io/dumtruck/konobangu-testing-torrents:latest
docker push ghcr.io/dumtruck/konobangu-testing-torrents:latest docker push ghcr.io/dumtruck/konobangu-testing-torrents:latest
``` ```

View File

@ -1,5 +1,8 @@
services: services:
konobangu-testing-torrents: konobangu-testing-torrents:
build: . build: .
network_mode: host ports:
container_name: konobangu-testing-torrents - 6080:6080
- 6081:6081
- 6082:6082
container_name: konobangu-testing-torrents

View File

@ -29,9 +29,9 @@ app.register(fastifyStatic, {
}); });
const tracker = new TrackerServer({ const tracker = new TrackerServer({
udp: true, // enable udp server? [default=true] udp: false, // enable udp server? [default=true]
http: true, // enable http server? [default=true] http: true, // enable http server? [default=true]
ws: true, // enable websocket server? [default=true] ws: false, // enable websocket server? [default=true]
stats: true, // enable web-based statistics? [default=true] stats: true, // enable web-based statistics? [default=true]
trustProxy: true, // enable trusting x-forwarded-for header for remote IP [default=false] trustProxy: true, // enable trusting x-forwarded-for header for remote IP [default=false]
}); });
@ -50,12 +50,13 @@ interface RequestSchema {
interface ResponseSchema { interface ResponseSchema {
torrentUrl: string; torrentUrl: string;
magnetUrl: string; magnetUrl: string;
hash: string;
} }
// Start local Tracker // Start local Tracker
async function startTracker(): Promise<void> { async function startTracker(): Promise<void> {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
tracker.listen(TRACKER_PORT, 'localhost', () => { tracker.listen(TRACKER_PORT, '0.0.0.0', () => {
console.log(`Tracker listening on port ${TRACKER_PORT}`); console.log(`Tracker listening on port ${TRACKER_PORT}`);
resolve(); resolve();
}); });
@ -85,6 +86,7 @@ async function generateMockFile(filePath: string, size: number) {
await fsp.mkdir(dir, { recursive: true }); await fsp.mkdir(dir, { recursive: true });
} }
await fsp.writeFile(filePath, Buffer.alloc(0));
await fsp.truncate(filePath, size); await fsp.truncate(filePath, size);
} }
@ -162,6 +164,7 @@ app.post<{ Body: RequestSchema }>('/api/torrents/mock', async (req, _reply) => {
return { return {
torrentUrl: `${API_BASE_URL}${id}.torrent`, torrentUrl: `${API_BASE_URL}${id}.torrent`,
magnetUrl, magnetUrl,
hash: torrent.infoHash,
} as ResponseSchema; } as ResponseSchema;
}); });
@ -169,7 +172,8 @@ app.post<{ Body: RequestSchema }>('/api/torrents/mock', async (req, _reply) => {
async function main() { async function main() {
try { try {
await startTracker(); await startTracker();
await app.listen({ port: API_PORT, host: LOCAL_IP }); const address = await app.listen({ port: API_PORT, host: '0.0.0.0' });
console.log('Listening on:', address);
} catch (err) { } catch (err) {
console.error('Startup error:', err); console.error('Startup error:', err);
webTorrent.destroy(); webTorrent.destroy();

View File

@ -18,5 +18,14 @@
"devDependencies": { "devDependencies": {
"@types/create-torrent": "^5.0.2", "@types/create-torrent": "^5.0.2",
"@types/webtorrent": "^0.110.0" "@types/webtorrent": "^0.110.0"
},
"pnpm": {
"onlyBuiltDependencies": [
"bufferutil",
"esbuild",
"node-datachannel",
"utf-8-validate",
"utp-native"
]
} }
} }

2461
packages/testing-torrents/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

1567
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,11 @@
packages: packages:
- packages/* - packages/*
- apps/* - apps/*
- '!packages/testing-torrents'
onlyBuiltDependencies:
- '@biomejs/biome'
- bufferutil
- core-js
- esbuild
- sharp
- utf-8-validate