Skip to content

feat: implement TorBoxSDK core — client hierarchy, models, all 107 API endpoints, unit tests#1

Merged
devRael1 merged 9 commits into
masterfrom
copilot/develop-sdk-based-on-todo-list
Apr 4, 2026
Merged

feat: implement TorBoxSDK core — client hierarchy, models, all 107 API endpoints, unit tests#1
devRael1 merged 9 commits into
masterfrom
copilot/develop-sdk-based-on-todo-list

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 4, 2026

Full SDK implementation covering Phases 1–6 of the development roadmap in docs/TODO.md. Ships the complete TorBox API surface (Main, Search, Relay) as a multi-target C# library (net6.0–net10.0).

Project Infrastructure

  • Multi-target net6.0;net7.0;net8.0;net9.0;net10.0 with Directory.Build.props, .editorconfig, NuGet metadata, SourceLink
  • TreatWarningsAsErrors enabled, nullable reference types enforced, GenerateDocumentationFile active

Client Architecture

  • TorBoxClientMain / Search / Relay facade hierarchy
  • MainApiClient exposes 11 resource clients: Torrents, Usenet, WebDownloads, User, General, Notifications, Rss, Stream, Integrations, Vendors, Queued
  • AuthHandler (DelegatingHandler) for ****** injection
  • TorBoxApiHelper internal helper for response envelope processing, error mapping, query building
  • AddTorBox() DI extension with IHttpClientFactory typed client registration and ValidateDataAnnotations() for fail-fast options validation
  • Base URLs use trailing slashes for correct relative URI resolution with HttpClient
services.AddTorBox(options =>
    options.ApiKey = Environment.GetEnvironmentVariable("TORBOX_API_KEY")!);

ITorBoxClient client = provider.GetRequiredService<ITorBoxClient>();
TorBoxResponse<IReadOnlyList<Torrent>> torrents = await client.Main.Torrents.GetMyTorrentListAsync();

Data Models (67 files)

  • sealed record types with [JsonPropertyName], init properties, IReadOnlyList<T> collections
  • TorBoxResponse<T> / TorBoxResponse envelope, TorBoxException, TorBoxErrorCode enum
  • SnakeCaseLower polyfill for net6.0/net7.0 via conditional compilation
  • User model renamed to UserProfile to avoid namespace/type collision
  • All enums include Unknown = 0 for forward compatibility (including SeedPreference)

API Endpoints (107 methods — full coverage)

  • Main API — 95 endpoints across 11 resource clients:
    • Torrents (14): create, async-create, control, queued, request download, list, check cached, export, magnet-to-file, torrent info, edit
    • Usenet (8): create, async-create, control, request download, list, check cached, edit
    • WebDownloads (9): create, async-create, control, request download, list, check cached, hosters, edit
    • User (16): auth (refresh, device code), profile, referrals, subscriptions, transactions, search engines, settings
    • General (4): status, stats, 30-day stats, speedtest
    • Notifications (8): RSS/JSON feeds, list, clear, test, intercom, changelogs
    • RSS (5): add, control, modify, get feeds/items
    • Stream (2): create stream, get stream data
    • Integrations (17): OAuth (redirect, callback, success, register, unregister, Discord roles), cloud storage jobs (Google Drive, Dropbox, OneDrive, Gofile, 1Fichier, Pixeldrain), job management
    • Vendors (8): register, account management, user management
    • Queued (2): get queued, control queued
  • Search API — 12 endpoints (torrent/usenet/meta search, tutorials, by-ID, Torznab, Newznab)
  • Relay API — 2 endpoints (status, inactive check)

Input Validation

  • Torrent and Usenet create methods validate that at least one of Magnet/File or Link/File is provided before sending the request
  • XML docs accurately document both ArgumentNullException (for null) and ArgumentException (for empty) on all interfaces using Guard.ThrowIfNullOrEmpty

Bug Fixes

  • Fixed critical base URL resolution bug: all 3 default base URLs (MainApiBaseUrl, SearchApiBaseUrl, RelayApiBaseUrl) now include trailing slashes, which is required for correct relative URI resolution with HttpClient.BaseAddress. Without the trailing slash, relative paths like "torrents/mylist" would resolve to "/v1/torrents/mylist" instead of "/v1/api/torrents/mylist".
  • Fixed GeneralClient.GetUpStatusAsync and RelayApiClient.GetStatusAsync which used absolute path "/" — this bypassed BaseAddress entirely. Now uses string.Empty to correctly resolve to the base address.
  • Fixed ParseErrorCode() normalization: input is now normalized once before matching, added explicit UNKNOWNERROR/UNKNOWN mapping to TorBoxErrorCode.UnknownError.

Unit Tests (52 tests, all passing)

  • MockHttpMessageHandler + ClientTestBase test infrastructure with per-client-type base address resolution (Search → search-api.torbox.app, Relay → relay.torbox.app)
  • Coverage: response deserialization, error code mapping, auth handler, API helper, TorrentsClient (9 tests), SearchApiClient, RelayApiClient, DI registration
  • CodeQL security scan: 0 alerts

Copilot AI and others added 5 commits April 4, 2026 15:44
…archy, response models, DI, error handling

Agent-Logs-Url: https://github.com/devRael1/TorBoxSDK/sessions/5aba6cc8-4469-4c9f-b732-9797eb3d4604

Co-authored-by: devRael1 <91017912+devRael1@users.noreply.github.com>
…resource clients

Agent-Logs-Url: https://github.com/devRael1/TorBoxSDK/sessions/5aba6cc8-4469-4c9f-b732-9797eb3d4604

Co-authored-by: devRael1 <91017912+devRael1@users.noreply.github.com>
…y API — 2 endpoints)

Agent-Logs-Url: https://github.com/devRael1/TorBoxSDK/sessions/5aba6cc8-4469-4c9f-b732-9797eb3d4604

Co-authored-by: devRael1 <91017912+devRael1@users.noreply.github.com>
@devRael1
Copy link
Copy Markdown
Owner

devRael1 commented Apr 4, 2026

@copilot assure toi que la totalité des APIs pour TorBox est couverte. Si ce n'est pas le cas, poursuit le développement.

N'oublie pas de faire une review du code avant de le valider.

Copilot AI and others added 2 commits April 4, 2026 16:48
…th, torznab/newznab, magnet-to-file) and fix 2 test assertions

Agent-Logs-Url: https://github.com/devRael1/TorBoxSDK/sessions/89be6b24-94a8-4113-bf5a-4d949d8ef8b8

Co-authored-by: devRael1 <91017912+devRael1@users.noreply.github.com>
… code review feedback

Agent-Logs-Url: https://github.com/devRael1/TorBoxSDK/sessions/89be6b24-94a8-4113-bf5a-4d949d8ef8b8

Co-authored-by: devRael1 <91017912+devRael1@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 4, 2026

@copilot assure toi que la totalité des APIs pour TorBox est couverte. Si ce n'est pas le cas, poursuit le développement.

N'oublie pas de faire une review du code avant de le vali...

J'ai audité toute la couverture API par rapport à docs/TODO.md et identifié 18 endpoints manquants. Ils sont maintenant tous implémentés dans le commit 54c4b42 :

  • Torrents : +4 (AsyncCreateTorrent, GetQueuedTorrents, ControlQueuedTorrents, MagnetToFile)
  • Usenet : +1 (AsyncCreateUsenetDownload)
  • WebDownloads : +1 (AsyncCreateWebDownload)
  • General : +4 (nouveau client : GetUpStatus, GetStats, Get30DayStats, GetSpeedtestFiles)
  • Integrations : +6 (OAuth : Redirect, Callback, Success, Register, Unregister, LinkedDiscordRoles)
  • Search : +2 (SearchTorznab, SearchNewznab)

Total : 107 endpoints couvrant la totalité des APIs TorBox (Main ~95, Search 12, Relay 2). Code review passée (CodeQL : 0 alertes), build 0 warnings/0 errors, 52 tests unitaires OK. Aussi corrigé 2 assertions de tests qui échouaient (ArgumentExceptionArgumentNullException).

Copilot AI changed the title feat: implement TorBoxSDK core — client hierarchy, models, all API endpoints, unit tests feat: implement TorBoxSDK core — client hierarchy, models, all 107 API endpoints, unit tests Apr 4, 2026
Copilot AI requested a review from devRael1 April 4, 2026 16:52
@devRael1
Copy link
Copy Markdown
Owner

devRael1 commented Apr 4, 2026

@copilot recommence la review afin de déterminer si des choses sont manquantes ou bien s'il y a des erreurs.

…ution, fix root endpoint paths

Agent-Logs-Url: https://github.com/devRael1/TorBoxSDK/sessions/66dfa255-22e7-490d-a2d1-418216e1a536

Co-authored-by: devRael1 <91017912+devRael1@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 4, 2026

@copilot recommence la review afin de déterminer si des choses sont manquantes ou bien s'il y a des erreurs.

Review complète effectuée. Voici les résultats :

Couverture API : 107/~106 endpoints — ✅ complète (Torrents 14, Usenet 8, WebDownloads 9, User 16, General 4, Notifications 8, RSS 5, Stream 2, Integrations 17, Vendors 8, Queued 2, Search 12, Relay 2).

Bug critique corrigé dans e15a0d5 : les BaseAddress des 3 API (Main, Search, Relay) manquaient le slash final. Sans le / final, HttpClient résolvait les URI relatives incorrectement — par ex. "https://api.torbox.app/v1/api" + "torrents/mylist" donnait "https://api.torbox.app/v1/torrents/mylist" au lieu de "https://api.torbox.app/v1/api/torrents/mylist". Aussi corrigé GeneralClient.GetUpStatusAsync et RelayApiClient.GetStatusAsync qui utilisaient le chemin absolu "/" (qui ignore complètement le BaseAddress).

Qualité : Build 0 warnings/0 errors sur les 5 cibles, 52 tests unitaires OK, CodeQL 0 alertes.

@devRael1 devRael1 marked this pull request as ready for review April 4, 2026 17:13
Copilot AI review requested due to automatic review settings April 4, 2026 17:13
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements the TorBoxSDK core as a multi-targeted C# library, including the client hierarchy (Main/Search/Relay), shared HTTP/auth infrastructure, response/error models, and accompanying unit-test scaffolding.

Changes:

  • Added multi-target TorBoxSDK package project with NuGet metadata and shared build settings.
  • Implemented core clients (Main aggregate + selected resource clients, Search, Relay), HTTP helper utilities, auth handler, and options/DI registration.
  • Added/expanded unit tests and test infrastructure; removed template test stubs.

Reviewed changes

Copilot reviewed 131 out of 131 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
tests/TorboxSDK.UnitTests/UnitTest1.cs Removed template test stub.
tests/TorboxSDK.UnitTests/TorboxSDK.UnitTests.csproj Added SDK project reference and DI/options packages for tests.
tests/TorboxSDK.UnitTests/Search/SearchApiClientTests.cs Added Search API unit tests.
tests/TorboxSDK.UnitTests/Relay/RelayApiClientTests.cs Added Relay API unit tests.
tests/TorboxSDK.UnitTests/Models/TorBoxResponseTests.cs Added serialization/deserialization tests for response envelopes.
tests/TorboxSDK.UnitTests/Models/TorBoxErrorCodeTests.cs Added enum definition tests for expected error codes.
tests/TorboxSDK.UnitTests/Http/TorBoxApiHelperTests.cs Added tests for query building and response handling/error mapping.
tests/TorboxSDK.UnitTests/Http/AuthHandlerTests.cs Added tests for auth header injection behavior.
tests/TorboxSDK.UnitTests/Helpers/MockHttpMessageHandler.cs Added mock HTTP handler for unit tests.
tests/TorboxSDK.UnitTests/Helpers/ClientTestBase.cs Added test client factory helper.
tests/TorboxSDK.UnitTests/DependencyInjection/TorBoxServiceCollectionExtensionsTests.cs Added DI registration tests for AddTorBox().
tests/TorBoxSDK.PerformanceTests/UnitTest1.cs Removed template test stub.
tests/TorBoxSDK.PerformanceTests/TorBoxSDK.PerformanceTests.csproj Added SDK project reference.
tests/TorBoxSDK.IntegrationTests/UnitTest1.cs Removed template test stub.
tests/TorBoxSDK.IntegrationTests/TorBoxSDK.IntegrationTests.csproj Added SDK project reference.
src/TorBoxSDK/TorBoxSDK.csproj Switched to multi-targeting and added NuGet/SourceLink packaging metadata.
src/TorBoxSDK/TorBoxClientOptions.cs Added configurable SDK options (API key, base URLs, timeout).
src/TorBoxSDK/TorBoxClient.cs Added root client facade aggregating Main/Search/Relay clients.
src/TorBoxSDK/Search/SearchApiClient.cs Implemented Search API client methods.
src/TorBoxSDK/Search/ISearchApiClient.cs Added Search API client interface and XML docs.
src/TorBoxSDK/Relay/RelayApiClient.cs Implemented Relay API client methods.
src/TorBoxSDK/Relay/IRelayApiClient.cs Added Relay API client interface and XML docs.
src/TorBoxSDK/Properties/AssemblyInfo.cs Exposed internals to unit test assembly.
src/TorBoxSDK/Models/WebDownloads/WebDownload.cs Added WebDownloads response model.
src/TorBoxSDK/Models/WebDownloads/RequestWebDownloadOptions.cs Added web download request options model.
src/TorBoxSDK/Models/WebDownloads/Hoster.cs Added hoster model.
src/TorBoxSDK/Models/WebDownloads/EditWebDownloadRequest.cs Added edit request model.
src/TorBoxSDK/Models/WebDownloads/CreateWebDownloadRequest.cs Added create request model.
src/TorBoxSDK/Models/WebDownloads/ControlWebDownloadRequest.cs Added control request model.
src/TorBoxSDK/Models/WebDownloads/CheckWebCachedRequest.cs Added cache-check request model.
src/TorBoxSDK/Models/Vendors/VendorAccount.cs Added vendor account model.
src/TorBoxSDK/Models/Vendors/UpdateVendorAccountRequest.cs Added vendor update request model.
src/TorBoxSDK/Models/Vendors/RemoveVendorUserRequest.cs Added vendor user removal request model.
src/TorBoxSDK/Models/Vendors/RegisterVendorUserRequest.cs Added vendor user registration request model.
src/TorBoxSDK/Models/Vendors/RegisterVendorRequest.cs Added vendor registration request model.
src/TorBoxSDK/Models/User/UserSettings.cs Added user settings model.
src/TorBoxSDK/Models/User/User.cs Added user profile model.
src/TorBoxSDK/Models/User/Transaction.cs Added transaction model.
src/TorBoxSDK/Models/User/Subscription.cs Added subscription model.
src/TorBoxSDK/Models/User/SearchEngine.cs Added search engine model.
src/TorBoxSDK/Models/User/RefreshTokenRequest.cs Added refresh token request model.
src/TorBoxSDK/Models/User/ReferralData.cs Added referral data model.
src/TorBoxSDK/Models/User/ModifySearchEnginesRequest.cs Added modify search engines request model.
src/TorBoxSDK/Models/User/EditSettingsRequest.cs Added edit settings request model.
src/TorBoxSDK/Models/User/DeviceTokenRequest.cs Added device token request model.
src/TorBoxSDK/Models/User/DeviceCodeResponse.cs Added device code response model.
src/TorBoxSDK/Models/User/DeleteAccountRequest.cs Added delete account request model.
src/TorBoxSDK/Models/User/ControlSearchEnginesRequest.cs Added control search engines request model.
src/TorBoxSDK/Models/User/AddSearchEnginesRequest.cs Added add search engines request model.
src/TorBoxSDK/Models/User/AddReferralRequest.cs Added add referral request model.
src/TorBoxSDK/Models/Usenet/UsenetDownload.cs Added Usenet download model.
src/TorBoxSDK/Models/Usenet/RequestUsenetDownloadOptions.cs Added Usenet request download options model.
src/TorBoxSDK/Models/Usenet/EditUsenetDownloadRequest.cs Added Usenet edit request model.
src/TorBoxSDK/Models/Usenet/CreateUsenetDownloadRequest.cs Added Usenet create request model.
src/TorBoxSDK/Models/Usenet/ControlUsenetDownloadRequest.cs Added Usenet control request model.
src/TorBoxSDK/Models/Usenet/CheckUsenetCachedRequest.cs Added Usenet cache-check request model.
src/TorBoxSDK/Models/Torrents/TorrentInfo.cs Added torrent info model.
src/TorBoxSDK/Models/Torrents/TorrentFile.cs Added torrent file model.
src/TorBoxSDK/Models/Torrents/Torrent.cs Added torrent model.
src/TorBoxSDK/Models/Torrents/SeedPreference.cs Added torrent seed preference enum.
src/TorBoxSDK/Models/Torrents/RequestDownloadOptions.cs Added torrent request download options model.
src/TorBoxSDK/Models/Torrents/MagnetToFileRequest.cs Added magnet-to-file request model.
src/TorBoxSDK/Models/Torrents/ExportDataOptions.cs Added export options model.
src/TorBoxSDK/Models/Torrents/EditTorrentRequest.cs Added edit torrent request model.
src/TorBoxSDK/Models/Torrents/CreateTorrentRequest.cs Added create torrent request model.
src/TorBoxSDK/Models/Torrents/ControlTorrentRequest.cs Added control torrent request model.
src/TorBoxSDK/Models/Torrents/CheckCachedRequest.cs Added torrent cache-check request model.
src/TorBoxSDK/Models/Search/UsenetSearchResult.cs Added Usenet search result model.
src/TorBoxSDK/Models/Search/TorrentSearchResult.cs Added torrent search result model.
src/TorBoxSDK/Models/Search/SearchType.cs Added search type enum.
src/TorBoxSDK/Models/Search/MetaSearchResult.cs Added meta search result model.
src/TorBoxSDK/Models/Search/MediaType.cs Added media type enum.
src/TorBoxSDK/Models/Rss/RssFeedItem.cs Added RSS feed item model.
src/TorBoxSDK/Models/Rss/RssFeed.cs Added RSS feed model.
src/TorBoxSDK/Models/Rss/ModifyRssRequest.cs Added RSS modify request model.
src/TorBoxSDK/Models/Rss/ControlRssRequest.cs Added RSS control request model.
src/TorBoxSDK/Models/Rss/AddRssRequest.cs Added RSS add request model.
src/TorBoxSDK/Models/Relay/RelayStatus.cs Added relay status model.
src/TorBoxSDK/Models/Relay/InactiveCheckResult.cs Added relay inactivity check result model.
src/TorBoxSDK/Models/Queued/QueuedDownload.cs Added queued download model.
src/TorBoxSDK/Models/Queued/ControlQueuedRequest.cs Added queued control request model.
src/TorBoxSDK/Models/Notifications/Stats.cs Added stats model.
src/TorBoxSDK/Models/Notifications/Notification.cs Added notification model.
src/TorBoxSDK/Models/Notifications/IntercomHash.cs Added intercom hash model.
src/TorBoxSDK/Models/Notifications/Changelog.cs Added changelog model.
src/TorBoxSDK/Models/Integrations/OAuthRegisterRequest.cs Added OAuth register request model.
src/TorBoxSDK/Models/Integrations/OAuthIntegration.cs Added OAuth integration model.
src/TorBoxSDK/Models/Integrations/IntegrationJob.cs Added integration job model.
src/TorBoxSDK/Models/Integrations/CreateIntegrationJobRequest.cs Added integration job creation request model.
src/TorBoxSDK/Models/General/SpeedtestOptions.cs Added speedtest options model.
src/TorBoxSDK/Models/Common/TorBoxResponse.cs Added standard response envelopes (generic + non-generic).
src/TorBoxSDK/Models/Common/TorBoxException.cs Added API exception type carrying error code/detail.
src/TorBoxSDK/Models/Common/TorBoxErrorCode.cs Added known API error code enum.
src/TorBoxSDK/Models/Common/DownloadType.cs Added download type enum.
src/TorBoxSDK/Models/Common/DownloadStatus.cs Added download status enum.
src/TorBoxSDK/Models/Common/DownloadFile.cs Added download file model.
src/TorBoxSDK/Models/Common/ControlOperation.cs Added control operation enum.
src/TorBoxSDK/Main/WebDownloads/WebDownloadsClient.cs Implemented WebDownloads resource client.
src/TorBoxSDK/Main/WebDownloads/IWebDownloadsClient.cs Added WebDownloads interface and XML docs.
src/TorBoxSDK/Main/Vendors/VendorsClient.cs Implemented Vendors resource client.
src/TorBoxSDK/Main/Vendors/IVendorsClient.cs Added Vendors interface and XML docs.
src/TorBoxSDK/Main/User/UserClient.cs Implemented User resource client.
src/TorBoxSDK/Main/User/IUserClient.cs Added User interface and XML docs.
src/TorBoxSDK/Main/Usenet/UsenetClient.cs Implemented Usenet resource client.
src/TorBoxSDK/Main/Usenet/IUsenetClient.cs Added Usenet interface and XML docs.
src/TorBoxSDK/Main/Torrents/ITorrentsClient.cs Added Torrents interface and XML docs.
src/TorBoxSDK/Main/Stream/StreamClient.cs Implemented Stream resource client.
src/TorBoxSDK/Main/Stream/IStreamClient.cs Added Stream interface and XML docs.
src/TorBoxSDK/Main/Rss/RssClient.cs Implemented RSS resource client.
src/TorBoxSDK/Main/Rss/IRssClient.cs Added RSS interface and XML docs.
src/TorBoxSDK/Main/Queued/QueuedClient.cs Implemented Queued resource client.
src/TorBoxSDK/Main/Queued/IQueuedClient.cs Added Queued interface and XML docs.
src/TorBoxSDK/Main/Notifications/NotificationsClient.cs Implemented Notifications resource client.
src/TorBoxSDK/Main/Notifications/INotificationsClient.cs Added Notifications interface and XML docs.
src/TorBoxSDK/Main/MainApiClient.cs Added Main API aggregate client.
src/TorBoxSDK/Main/IMainApiClient.cs Added Main API aggregate interface.
src/TorBoxSDK/Main/General/IGeneralClient.cs Added General resource interface.
src/TorBoxSDK/Main/General/GeneralClient.cs Implemented General resource client.
src/TorBoxSDK/ITorBoxClient.cs Added root client interface.
src/TorBoxSDK/Http/TorBoxJsonOptions.cs Added shared JSON options (snake_case policy + enum converter).
src/TorBoxSDK/Http/TorBoxApiHelper.cs Added HTTP helper for sending/deserializing envelope responses and query building.
src/TorBoxSDK/Http/Guard.cs Added internal guard utility for null/empty strings.
src/TorBoxSDK/Http/AuthHandler.cs Added delegating handler for Bearer token injection.
src/TorBoxSDK/DependencyInjection/TorBoxServiceCollectionExtensions.cs Added AddTorBox() DI registration for clients + handlers.
src/TorBoxSDK/Class1.cs Removed template placeholder class.
Directory.Build.props Added repo-wide build settings (nullable, implicit usings, warnings-as-errors, metadata).

Comment thread src/TorBoxSDK/Http/TorBoxApiHelper.cs Outdated
Comment on lines +129 to +151
return error.ToUpperInvariant().Replace(" ", "").Replace("_", "") switch
{
"DATABASE_ERROR" or "DATABASEERROR" => TorBoxErrorCode.DatabaseError,
"BAD_TOKEN" or "BADTOKEN" => TorBoxErrorCode.BadToken,
"NO_AUTH" or "NOAUTH" => TorBoxErrorCode.NoAuth,
"INVALID_OPTION" or "INVALIDOPTION" => TorBoxErrorCode.InvalidOption,
"PERMISSION_DENIED" or "PERMISSIONDENIED" => TorBoxErrorCode.PermissionDenied,
"PLAN_RESTRICTED_FEATURE" or "PLANRESTRICTEDFEATURE" => TorBoxErrorCode.PlanRestrictedFeature,
"DUPLICATE_ITEM" or "DUPLICATEITEM" => TorBoxErrorCode.DuplicateItem,
"BREACH_OF_TOS" or "BREACHOFTOS" => TorBoxErrorCode.BreachOfTos,
"ACTIVE_LIMIT" or "ACTIVELIMIT" => TorBoxErrorCode.ActiveLimit,
"SEEDING_LIMIT" or "SEEDINGLIMIT" => TorBoxErrorCode.SeedingLimit,
"BANNED_CONTENT_DETECTED" or "BANNEDCONTENTDETECTED" => TorBoxErrorCode.BannedContentDetected,
"COULD_NOT_PERFORM_ACTION" or "COULDNOTPERFORMACTION" => TorBoxErrorCode.CouldNotPerformAction,
"ITEM_NOT_FOUND" or "ITEMNOTFOUND" => TorBoxErrorCode.ItemNotFound,
"INVALID_DEVICE" or "INVALIDDEVICE" => TorBoxErrorCode.InvalidDevice,
"DEVICE_ALREADY_AUTHED" or "DEVICEALREADYAUTHED" => TorBoxErrorCode.DeviceAlreadyAuthed,
"TOO_MANY_REQUESTS" or "TOOMANYREQUESTS" => TorBoxErrorCode.TooManyRequests,
"DOWNLOAD_TOO_LARGE" or "DOWNLOADTOOLARGE" => TorBoxErrorCode.DownloadTooLarge,
"MISSING_REQUIRED_OPTION" or "MISSINGREQUIREDOPTION" => TorBoxErrorCode.MissingRequiredOption,
"BANNED_USER" or "BANNEDUSER" => TorBoxErrorCode.BannedUser,
"SEARCH_ERROR" or "SEARCHERROR" => TorBoxErrorCode.SearchError,
"SERVER_ERROR" or "SERVERERROR" => TorBoxErrorCode.ServerError,
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ParseErrorCode() never maps the API value for an unknown server error to TorBoxErrorCode.UnknownError even though the enum defines it. This will misclassify responses like UNKNOWN_ERROR (or similar) as Unknown, reducing the usefulness of TorBoxException.ErrorCode. Consider adding an explicit mapping for the API’s unknown-error code(s) and aligning the normalization logic/patterns so the switch cases match the normalized input consistently.

Suggested change
return error.ToUpperInvariant().Replace(" ", "").Replace("_", "") switch
{
"DATABASE_ERROR" or "DATABASEERROR" => TorBoxErrorCode.DatabaseError,
"BAD_TOKEN" or "BADTOKEN" => TorBoxErrorCode.BadToken,
"NO_AUTH" or "NOAUTH" => TorBoxErrorCode.NoAuth,
"INVALID_OPTION" or "INVALIDOPTION" => TorBoxErrorCode.InvalidOption,
"PERMISSION_DENIED" or "PERMISSIONDENIED" => TorBoxErrorCode.PermissionDenied,
"PLAN_RESTRICTED_FEATURE" or "PLANRESTRICTEDFEATURE" => TorBoxErrorCode.PlanRestrictedFeature,
"DUPLICATE_ITEM" or "DUPLICATEITEM" => TorBoxErrorCode.DuplicateItem,
"BREACH_OF_TOS" or "BREACHOFTOS" => TorBoxErrorCode.BreachOfTos,
"ACTIVE_LIMIT" or "ACTIVELIMIT" => TorBoxErrorCode.ActiveLimit,
"SEEDING_LIMIT" or "SEEDINGLIMIT" => TorBoxErrorCode.SeedingLimit,
"BANNED_CONTENT_DETECTED" or "BANNEDCONTENTDETECTED" => TorBoxErrorCode.BannedContentDetected,
"COULD_NOT_PERFORM_ACTION" or "COULDNOTPERFORMACTION" => TorBoxErrorCode.CouldNotPerformAction,
"ITEM_NOT_FOUND" or "ITEMNOTFOUND" => TorBoxErrorCode.ItemNotFound,
"INVALID_DEVICE" or "INVALIDDEVICE" => TorBoxErrorCode.InvalidDevice,
"DEVICE_ALREADY_AUTHED" or "DEVICEALREADYAUTHED" => TorBoxErrorCode.DeviceAlreadyAuthed,
"TOO_MANY_REQUESTS" or "TOOMANYREQUESTS" => TorBoxErrorCode.TooManyRequests,
"DOWNLOAD_TOO_LARGE" or "DOWNLOADTOOLARGE" => TorBoxErrorCode.DownloadTooLarge,
"MISSING_REQUIRED_OPTION" or "MISSINGREQUIREDOPTION" => TorBoxErrorCode.MissingRequiredOption,
"BANNED_USER" or "BANNEDUSER" => TorBoxErrorCode.BannedUser,
"SEARCH_ERROR" or "SEARCHERROR" => TorBoxErrorCode.SearchError,
"SERVER_ERROR" or "SERVERERROR" => TorBoxErrorCode.ServerError,
string normalizedError = error.ToUpperInvariant().Replace(" ", "").Replace("_", "");
return normalizedError switch
{
"UNKNOWNERROR" or "UNKNOWN" => TorBoxErrorCode.UnknownError,
"DATABASEERROR" => TorBoxErrorCode.DatabaseError,
"BADTOKEN" => TorBoxErrorCode.BadToken,
"NOAUTH" => TorBoxErrorCode.NoAuth,
"INVALIDOPTION" => TorBoxErrorCode.InvalidOption,
"PERMISSIONDENIED" => TorBoxErrorCode.PermissionDenied,
"PLANRESTRICTEDFEATURE" => TorBoxErrorCode.PlanRestrictedFeature,
"DUPLICATEITEM" => TorBoxErrorCode.DuplicateItem,
"BREACHOFTOS" => TorBoxErrorCode.BreachOfTos,
"ACTIVELIMIT" => TorBoxErrorCode.ActiveLimit,
"SEEDINGLIMIT" => TorBoxErrorCode.SeedingLimit,
"BANNEDCONTENTDETECTED" => TorBoxErrorCode.BannedContentDetected,
"COULDNOTPERFORMACTION" => TorBoxErrorCode.CouldNotPerformAction,
"ITEMNOTFOUND" => TorBoxErrorCode.ItemNotFound,
"INVALIDDEVICE" => TorBoxErrorCode.InvalidDevice,
"DEVICEALREADYAUTHED" => TorBoxErrorCode.DeviceAlreadyAuthed,
"TOOMANYREQUESTS" => TorBoxErrorCode.TooManyRequests,
"DOWNLOADTOOLARGE" => TorBoxErrorCode.DownloadTooLarge,
"MISSINGREQUIREDOPTION" => TorBoxErrorCode.MissingRequiredOption,
"BANNEDUSER" => TorBoxErrorCode.BannedUser,
"SEARCHERROR" => TorBoxErrorCode.SearchError,
"SERVERERROR" => TorBoxErrorCode.ServerError,

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +15
var handler = new MockHttpMessageHandler(json, statusCode);
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("https://api.torbox.app/v1/api/") };
TClient client = (TClient)Activator.CreateInstance(typeof(TClient), httpClient)!;
return (client, handler);
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ClientTestBase.CreateClient<TClient>() hardcodes the Main API base address (https://api.torbox.app/v1/api/), but it’s used to construct SearchApiClient and RelayApiClient in the tests. This can hide URI-composition bugs (Search/Relay use different base addresses and path shapes), and makes the URL assertions less meaningful. Consider taking a baseAddress parameter (or inferring per client type) so Search tests use https://search-api.torbox.app/ and Relay tests use https://relay.torbox.app/.

Copilot uses AI. Check for mistakes.
{
var handler = new MockHttpMessageHandler(json, statusCode);
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("https://api.torbox.app/v1/api/") };
TClient client = (TClient)Activator.CreateInstance(typeof(TClient), httpClient)!;
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The null-forgiving operator on Activator.CreateInstance(... )! suppresses possible null returns without justification, which can mask a failing test setup (e.g., missing HttpClient constructor). Prefer throwing a clearer exception if the instance can’t be created (or use a helper that guarantees non-null) instead of !.

Copilot uses AI. Check for mistakes.
Comment on lines +34 to +49
public async Task SendAsync_WithEmptyApiKey_SetsEmptyBearerToken()
{
// Arrange
var options = Options.Create(new TorBoxClientOptions { ApiKey = string.Empty });
var innerHandler = new MockHttpMessageHandler("""{"success":true,"error":null,"detail":"OK"}""");
var authHandler = new AuthHandler(options) { InnerHandler = innerHandler };
using var httpClient = new HttpClient(authHandler);
using var request = new HttpRequestMessage(HttpMethod.Get, "https://api.torbox.app/v1/api/torrents/mylist");

// Act
await httpClient.SendAsync(request);

// Assert
Assert.NotNull(innerHandler.LastRequest);
Assert.Null(innerHandler.LastRequest.Headers.Authorization);
}
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test name is misleading: when the API key is empty the handler does not set an Authorization header (asserted as null). Rename the test to reflect the behavior (e.g., “DoesNotAddAuthorizationHeader”) or adjust the behavior/assertion so the name matches.

Copilot uses AI. Check for mistakes.
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(configure);

services.Configure(configure);
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TorBoxClientOptions.ApiKey is marked [Required], but AddTorBox() doesn’t register any options validation, so missing/empty API keys won’t fail fast and the data annotation is effectively ignored. Consider adding services.AddOptions<TorBoxClientOptions>().ValidateDataAnnotations() (and optionally a custom validator for base URL formats / trailing slashes) or removing [Required] if you don’t intend to validate via DI.

Suggested change
services.Configure(configure);
services
.AddOptions<TorBoxClientOptions>()
.Configure(configure)
.ValidateDataAnnotations();

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +18
public enum SeedPreference
{
/// <summary>Automatically determine seeding behavior based on user settings.</summary>
Auto = 1,

/// <summary>Seed the torrent after download completion.</summary>
Seed = 2,

/// <summary>Do not seed the torrent after download completion.</summary>
NoSeed = 3,
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SeedPreference is missing an Unknown = 0 member. Other SDK enums include an Unknown value for forward compatibility, and the SDK’s enum design guideline expects a catch-all unknown member. Adding Unknown = 0 also prevents default(enum) from serializing to an unintended value.

Copilot uses AI. Check for mistakes.
Comment on lines +30 to +41
ArgumentNullException.ThrowIfNull(request);

var content = new MultipartFormDataContent();
if (request.Link is not null)
{
content.Add(new StringContent(request.Link), "link");
}

if (request.File is not null)
{
content.Add(new ByteArrayContent(request.File), "file", "download.nzb");
}
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateUsenetDownloadAsync does not enforce the request contract described by CreateUsenetDownloadRequest (“Either Link or File must be provided”). As written, it can send an empty multipart form body when both are null, producing a late API error. Add boundary validation to ensure exactly one (or at least one, depending on API rules) of Link/File is provided before sending the request.

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +31
/// <summary>Retrieves the authenticated user's profile.</summary>
/// <param name="settings">Whether to include account settings in the response.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>The user profile data.</returns>
/// <exception cref="TorBoxException">Thrown when the API returns an error.</exception>
Task<TorBoxResponse<Models.User.User>> GetMeAsync(bool? settings = null, CancellationToken ct = default);

Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using TorBoxResponse<Models.User.User> is a readability smell caused by the model type name colliding with the User namespace. Consider introducing a type alias (using UserProfile = TorBoxSDK.Models.User.User;) or renaming the model (e.g., UserProfile) to avoid requiring fully-qualified names in the public API surface.

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +24
/// <summary>Checks whether a torrent is inactive on the relay.</summary>
/// <param name="authId">The authentication identifier of the user.</param>
/// <param name="torrentId">The unique identifier of the torrent to check.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>The inactivity check result.</returns>
/// <exception cref="ArgumentException">Thrown when <paramref name="authId"/> is null or empty.</exception>
/// <exception cref="TorBoxException">Thrown when the API returns an error.</exception>
Task<TorBoxResponse<InactiveCheckResult>> CheckForInactiveAsync(string authId, long torrentId, CancellationToken ct = default);
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The interface documents ArgumentException for a null-or-empty authId, but the implementation uses Guard.ThrowIfNullOrEmpty(...), which throws ArgumentNullException when authId is null. Update the XML docs to include ArgumentNullException so consumers get an accurate exception contract.

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +18
/// <summary>Creates a stream for a download item.</summary>
/// <param name="id">The identifier of the download.</param>
/// <param name="fileId">The identifier of the file within the download.</param>
/// <param name="type">The type of download (e.g., torrent, usenet).</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>The stream URL or data as a string.</returns>
/// <exception cref="ArgumentException">Thrown when <paramref name="type"/> is null or empty.</exception>
/// <exception cref="TorBoxException">Thrown when the API returns an error.</exception>
Task<TorBoxResponse<string>> CreateStreamAsync(long id, long fileId, string type, CancellationToken ct = default);
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The interface documents ArgumentException for a null-or-empty type, but the implementation uses Guard.ThrowIfNullOrEmpty(...), which throws ArgumentNullException when type is null. Update the XML docs to include ArgumentNullException (or change the guard behavior) to keep the documented contract accurate.

Copilot uses AI. Check for mistakes.
@devRael1
Copy link
Copy Markdown
Owner

devRael1 commented Apr 4, 2026

@copilot apply changes based on the comments in this thread

…ntTestBase base address per client type, test rename, DI validation, XML docs ArgumentNullException, SeedPreference Unknown=0, create request validation, User→UserProfile rename

Agent-Logs-Url: https://github.com/devRael1/TorBoxSDK/sessions/0d3877b1-323d-4a52-a9fa-1becc13312be

Co-authored-by: devRael1 <91017912+devRael1@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 4, 2026

@copilot apply changes based on the comments in this thread

Les 11 commentaires de review ont été appliqués dans 940aa0b :

  1. ParseErrorCode — normalisé l'input avant le switch, ajouté le mapping UNKNOWNERROR/UNKNOWNTorBoxErrorCode.UnknownError
  2. ClientTestBase — base address auto-détectée par type client (Search → search-api.torbox.app, Relay → relay.torbox.app), suppression du ! null-forgiving
  3. AuthHandlerTests — renommé SetsEmptyBearerTokenDoesNotAddAuthorizationHeader
  4. DI ValidateDataAnnotations — ajouté aux 2 surcharges AddTorBox() + package Microsoft.Extensions.Options.DataAnnotations
  5. XML docs — ajouté ArgumentNullException dans toutes les interfaces utilisant Guard.ThrowIfNullOrEmpty (ISearchApiClient, IRelayApiClient, IStreamClient, IIntegrationsClient, ITorrentsClient, IVendorsClient)
  6. SeedPreference — ajouté Unknown = 0
  7. Validation create requests — ajouté vérification Link/File dans UsenetClient et Magnet/File dans TorrentsClient (4 méthodes)
  8. User → UserProfile — renommé le modèle pour éviter la collision namespace/type

Build 0 warnings/0 errors, 52 tests OK, CodeQL 0 alertes.

@devRael1 devRael1 merged commit 0fcdc7d into master Apr 4, 2026
@devRael1 devRael1 deleted the copilot/develop-sdk-based-on-todo-list branch April 4, 2026 18:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants