├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ ├── build-and-test.yml │ ├── codeql-analysis.yml │ └── publish.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Directory.Build.props ├── Directory.Packages.props ├── ISSUE_TEMPLATE.md ├── LICENSE ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── appveyor.yml ├── docker-compose.yml ├── global.json ├── logo-resharper.gif ├── logo.png ├── redmine-net-api.sln ├── redmine-net-api.snk ├── releasenotes.props ├── signing.props ├── src └── redmine-net-api │ ├── Authentication │ ├── IRedmineAuthentication.cs │ ├── RedmineApiKeyAuthentication.cs │ ├── RedmineAuthenticationType.cs │ ├── RedmineBasicAuthentication.cs │ └── RedmineNoAuthentication.cs │ ├── Common │ ├── AType.cs │ ├── ArgumentVerifier.cs │ ├── IValue.cs │ └── PagedResults.cs │ ├── Exceptions │ ├── RedmineApiException.cs │ ├── RedmineException.cs │ ├── RedmineForbiddenException.cs │ ├── RedmineNotAcceptableException.cs │ ├── RedmineNotFoundException.cs │ ├── RedmineOperationCanceledException.cs │ ├── RedmineSerializationException.cs │ ├── RedmineTimeoutException.cs │ ├── RedmineUnauthorizedException.cs │ └── RedmineUnprocessableEntityException.cs │ ├── Extensions │ ├── EnumExtensions.cs │ ├── IEnumerableExtensions.cs │ ├── IdentifiableNameExtensions.cs │ ├── IntExtensions.cs │ ├── ListExtensions.cs │ ├── RedmineManagerExtensions.cs │ ├── SemaphoreSlimExtensions.cs │ ├── StringExtensions.cs │ └── TaskExtensions.cs │ ├── Features │ ├── CallerArgumentExpressionAttribute.cs │ ├── IsExternalInit.cs │ └── NotNullAttribute.cs │ ├── Http │ ├── Clients │ │ ├── HttpClient │ │ │ ├── HttpClientProvider.cs │ │ │ ├── HttpContentExtensions.cs │ │ │ ├── HttpContentPolyfills.cs │ │ │ ├── HttpResponseHeadersExtensions.cs │ │ │ ├── IRedmineHttpClientOptions.cs │ │ │ ├── InternalRedmineApiHttpClient.Async.cs │ │ │ ├── InternalRedmineApiHttpClient.cs │ │ │ └── RedmineHttpClientOptions.cs │ │ └── WebClient │ │ │ ├── InternalRedmineApiWebClient.Async.cs │ │ │ ├── InternalRedmineApiWebClient.cs │ │ │ ├── InternalWebClient.cs │ │ │ ├── RedmineApiRequestContent.cs │ │ │ ├── RedmineWebClientOptions.cs │ │ │ ├── WebClientExtensions.cs │ │ │ └── WebClientProvider.cs │ ├── Constants │ │ └── HttpConstants.cs │ ├── Extensions │ │ ├── NameValueCollectionExtensions.cs │ │ └── RedmineApiResponseExtensions.cs │ ├── Helpers │ │ ├── ClientHelper.cs │ │ ├── RedmineExceptionHelper.cs │ │ └── RedmineHttpMethodHelper.cs │ ├── IRedmineApiClient.cs │ ├── IRedmineApiClientOptions.cs │ ├── Messages │ │ ├── RedmineApiRequest.cs │ │ └── RedmineApiResponse.cs │ ├── RedirectType.cs │ ├── RedmineApiClient.Async.cs │ ├── RedmineApiClient.cs │ ├── RedmineApiClientOptions.cs │ └── RequestOptions.cs │ ├── ICloneableOfT.cs │ ├── IRedmineManager.Async.cs │ ├── IRedmineManager.cs │ ├── Internals │ ├── ArgumentNullThrowHelper.cs │ ├── HashCodeHelper.cs │ ├── HostHelper.cs │ └── ParameterValidator.cs │ ├── Logging │ ├── IRedmineLogger.cs │ ├── LogLevel.cs │ ├── MicrosoftLoggerRedmineAdapter.cs │ ├── RedmineConsoleLogger.cs │ ├── RedmineLoggerExtensions.cs │ ├── RedmineLoggerFactory.cs │ ├── RedmineLoggerMicrosoftAdapter.cs │ ├── RedmineLoggingOptions.cs │ └── RedmineNullLogger.cs │ ├── Net │ └── Internal │ │ ├── RedmineApiUrls.cs │ │ └── RedmineApiUrlsExtensions.cs │ ├── Options │ ├── RedmineManagerOptions.cs │ └── RedmineManagerOptionsBuilder.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── RedmineConstants.cs │ ├── RedmineKeys.cs │ ├── RedmineManager.Async.cs │ ├── RedmineManager.cs │ ├── SearchFilterBuilder.cs │ ├── Serialization │ ├── IRedmineSerializer.cs │ ├── Json │ │ ├── Extensions │ │ │ ├── JsonReaderExtensions.cs │ │ │ └── JsonWriterExtensions.cs │ │ ├── IJsonSerializable.cs │ │ ├── JsonObject.cs │ │ └── JsonRedmineSerializer.cs │ ├── RedmineSerializerFactory.cs │ ├── SerializationHelper.cs │ ├── SerializationType.cs │ └── Xml │ │ ├── CacheKeyFactory.cs │ │ ├── Extensions │ │ ├── XmlReaderExtensions.cs │ │ └── XmlWriterExtensions.cs │ │ ├── IXmlSerializerCache.cs │ │ ├── XmlRedmineSerializer.cs │ │ ├── XmlSerializerCache.cs │ │ └── XmlTextReaderBuilder.cs │ ├── Types │ ├── Attachment.cs │ ├── Attachments.cs │ ├── ChangeSet.cs │ ├── CustomField.cs │ ├── CustomFieldPossibleValue.cs │ ├── CustomFieldRole.cs │ ├── CustomFieldValue.cs │ ├── Detail.cs │ ├── DocumentCategory.cs │ ├── Error.cs │ ├── File.cs │ ├── Group.cs │ ├── GroupUser.cs │ ├── Identifiable.cs │ ├── IdentifiableName.cs │ ├── Include.cs │ ├── Issue.cs │ ├── IssueAllowedStatus.cs │ ├── IssueCategory.cs │ ├── IssueChild.cs │ ├── IssueCustomField.cs │ ├── IssuePriority.cs │ ├── IssueRelation.cs │ ├── IssueRelationType.cs │ ├── IssueStatus.cs │ ├── Journal.cs │ ├── Membership.cs │ ├── MembershipRole.cs │ ├── MyAccount.cs │ ├── MyAccountCustomField.cs │ ├── News.cs │ ├── NewsComment.cs │ ├── Permission.cs │ ├── Project.cs │ ├── ProjectEnabledModule.cs │ ├── ProjectIssueCategory.cs │ ├── ProjectMembership.cs │ ├── ProjectStatus.cs │ ├── ProjectTimeEntryActivity.cs │ ├── ProjectTracker.cs │ ├── Query.cs │ ├── Role.cs │ ├── Search.cs │ ├── TimeEntry.cs │ ├── TimeEntryActivity.cs │ ├── Tracker.cs │ ├── TrackerCoreField.cs │ ├── TrackerCustomField.cs │ ├── Upload.cs │ ├── User.cs │ ├── UserGroup.cs │ ├── UserStatus.cs │ ├── Version.cs │ ├── VersionSharing.cs │ ├── VersionStatus.cs │ ├── Watcher.cs │ └── WikiPage.cs │ ├── _net20 │ ├── ExtensionAttribute.cs │ ├── Func.cs │ └── IProgress{T}.cs │ └── redmine-net-api.csproj ├── tests ├── redmine-net-api.Integration.Tests │ ├── Collections │ │ └── RedmineTestContainerCollection.cs │ ├── Fixtures │ │ └── RedmineTestContainerFixture.cs │ ├── Helpers │ │ ├── AssertHelpers.cs │ │ ├── FileGeneratorHelper.cs │ │ └── RandomHelper.cs │ ├── Infrastructure │ │ ├── ClientType.cs │ │ ├── ConfigurationHelper.cs │ │ ├── Constants.cs │ │ ├── Options │ │ │ ├── AuthenticationMode.cs │ │ │ ├── AuthenticationOptions.cs │ │ │ ├── BasicAuthenticationOptions.cs │ │ │ ├── PostgresOptions.cs │ │ │ ├── RedmineOptions.cs │ │ │ └── TestContainerOptions.cs │ │ ├── RedmineConfiguration.cs │ │ ├── SerializationType.cs │ │ └── TestContainerMode.cs │ ├── TestData │ │ └── init-redmine.sql │ ├── Tests │ │ ├── Common │ │ │ ├── EmailNotificationType.cs │ │ │ ├── IssueTestHelper.cs │ │ │ ├── TestConstants.cs │ │ │ └── TestEntityFactory.cs │ │ ├── Entities │ │ │ ├── Attachment │ │ │ │ ├── AttachmentTests.cs │ │ │ │ └── AttachmentTestsAsync.cs │ │ │ ├── CustomField │ │ │ │ ├── CustomFieldTests.cs │ │ │ │ └── CustomFieldTestsAsync.cs │ │ │ ├── Enumeration │ │ │ │ ├── EnumerationTests.cs │ │ │ │ └── EnumerationTestsAsync.cs │ │ │ ├── File │ │ │ │ ├── FileTests.cs │ │ │ │ └── FileTestsAsync.cs │ │ │ ├── Group │ │ │ │ ├── GroupTests.cs │ │ │ │ └── GroupTestsAsync.cs │ │ │ ├── Issue │ │ │ │ ├── IssueAttachmentUploadTests.cs │ │ │ │ ├── IssueAttachmentUploadTestsAsync.cs │ │ │ │ ├── IssueTests.cs │ │ │ │ ├── IssueTestsAsync.cs │ │ │ │ ├── IssueWatcherTests.cs │ │ │ │ └── IssueWatcherTestsAsync.cs │ │ │ ├── IssueCategory │ │ │ │ ├── IssueCategoryTests.cs │ │ │ │ └── IssueCategoryTestsAsync.cs │ │ │ ├── IssueRelation │ │ │ │ ├── IssueRelationTests.cs │ │ │ │ └── IssueRelationTestsAsync.cs │ │ │ ├── IssueStatus │ │ │ │ ├── IssueStatusTests.cs │ │ │ │ └── IssueStatusTestsAsync.cs │ │ │ ├── Journal │ │ │ │ ├── JournalTests.cs │ │ │ │ └── JournalTestsAsync.cs │ │ │ ├── News │ │ │ │ ├── NewsTests.cs │ │ │ │ └── NewsTestsAsync.cs │ │ │ ├── Project │ │ │ │ └── ProjectTestsAsync.cs │ │ │ ├── ProjectMembership │ │ │ │ ├── ProjectMembershipTests.cs │ │ │ │ └── ProjectMembershipTestsAsync.cs │ │ │ ├── Query │ │ │ │ ├── QueryTests.cs │ │ │ │ └── QueryTestsAsync.cs │ │ │ ├── Role │ │ │ │ ├── RoleTests.cs │ │ │ │ └── RoleTestsAsync.cs │ │ │ ├── Search │ │ │ │ ├── SearchTests.cs │ │ │ │ └── SearchTestsAsync.cs │ │ │ ├── TimeEntry │ │ │ │ ├── TimeEntryActivityTestsAsync.cs │ │ │ │ └── TimeEntryTestsAsync.cs │ │ │ ├── Tracker │ │ │ │ └── TrackerTestsAsync.cs │ │ │ ├── UploadTestsAsync.cs │ │ │ ├── User │ │ │ │ └── UserTestsAsync.cs │ │ │ ├── Version │ │ │ │ └── VersionTestsAsync.cs │ │ │ └── Wiki │ │ │ │ └── WikiTestsAsync.cs │ │ ├── Progress │ │ │ ├── ProgressTests.Async.cs │ │ │ └── ProgressTests.cs │ │ └── RedmineApiWebClientTests.cs │ ├── appsettings.json │ ├── appsettings.local.json │ └── redmine-net-api.Integration.Tests.csproj └── redmine-net-api.Tests │ ├── .editorconfig │ ├── Bugs │ ├── RedmineApi-229.cs │ └── RedmineApi-371.cs │ ├── Clone │ ├── AttachmentCloneTests.cs │ ├── IssueCloneTests.cs │ └── JournalCloneTests.cs │ ├── Equality │ ├── AttachmentEqualityTests.cs │ ├── BaseEqualityTests.cs │ ├── CustomFieldPossibleValueTests.cs │ ├── CustomFieldRoleTests.cs │ ├── CustomFieldTests.cs │ ├── DetailTests.cs │ ├── ErrorTests.cs │ ├── GroupTests.cs │ ├── GroupUserTests.cs │ ├── IssueCategoryTests.cs │ ├── IssueEqualityTests.cs │ ├── IssueStatusTests.cs │ ├── JournalEqualityTests.cs │ ├── MembershipTests.cs │ ├── MyAccountCustomFieldTests.cs │ ├── MyAccountTests.cs │ ├── NewsTests.cs │ ├── PermissionTests.cs │ ├── ProjectMembershipTests.cs │ ├── ProjectTests.cs │ ├── QueryTests.cs │ ├── RoleTests.cs │ ├── SearchTests.cs │ ├── TimeEntryActivityTests.cs │ ├── TimeEntryTests.cs │ ├── TrackerCoreFieldTests.cs │ ├── TrackerCustomFieldTests.cs │ ├── UploadTests.cs │ ├── UserGroupTests.cs │ ├── UserTests.cs │ ├── VersionTests.cs │ ├── WatcherTests.cs │ └── WikiPageTests.cs │ ├── Infrastructure │ ├── Collections │ │ ├── JsonRedmineSerializerCollection.cs │ │ └── XmlRedmineSerializerCollection.cs │ ├── Constants.cs │ ├── Fixtures │ │ ├── JsonSerializerFixture.cs │ │ ├── RedmineApiUrlsFixture.cs │ │ └── XmlSerializerFixture.cs │ └── Order │ │ ├── CaseOrder.cs │ │ ├── CollectionOrderer.cs │ │ └── OrderAttribute.cs │ ├── Properties │ └── launchSettings.json │ ├── Serialization │ ├── Json │ │ ├── AttachmentTests.cs │ │ ├── CustomFieldTests.cs │ │ ├── ErrorTests.cs │ │ ├── IssueCustomFieldsTests.cs │ │ ├── IssuesTests.cs │ │ ├── MyAccount.cs │ │ ├── RoleTests.cs │ │ └── UserTests.cs │ └── Xml │ │ ├── AttachmentTests.cs │ │ ├── CustomFieldTests.cs │ │ ├── EnumerationTests.cs │ │ ├── ErrorTests.cs │ │ ├── FileTests.cs │ │ ├── GroupTests.cs │ │ ├── IssueCategoryTests.cs │ │ ├── IssueStatusTests.cs │ │ ├── IssueTests.cs │ │ ├── MembershipTests.cs │ │ ├── MyAccountTests.cs │ │ ├── NewsTests.cs │ │ ├── ProjectTests.cs │ │ ├── QueryTests.cs │ │ ├── RelationTests.cs │ │ ├── RoleTests.cs │ │ ├── SearchTests.cs │ │ ├── TrackerTests.cs │ │ ├── UploadTests.cs │ │ ├── UserTests.cs │ │ ├── VersionTests.cs │ │ └── WikiTests.cs │ ├── Tests │ ├── HostTests.cs │ └── RedmineApiUrlsTests.cs │ └── redmine-net-api.Tests.csproj └── version.props /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | ### c# and c++ stuff 4 | *.cs text 5 | *.c text 6 | *.cpp text 7 | *.h text 8 | *.hpp text 9 | *.dsp text eol=crlf 10 | *.dsw text eol=crlf 11 | *.vcprops text eol=crlf 12 | *.vcproj text eol=crlf 13 | *.vcxproj text eol=crlf 14 | *.props text eol=crlf 15 | *.sln text eol=crlf 16 | *.vcxproj.filters text eol=crlf 17 | *.rc text=auto 18 | *.rc2 text=auto 19 | resource.h text=auto 20 | 21 | ### Other 22 | *.ps1 text eol=crlf 23 | *.bat text eol=crlf 24 | *.reg text 25 | Makefile text 26 | *.sh text 27 | *.txt text 28 | *.bmp binary 29 | *.png binary 30 | *.jpg binary 31 | *.gif binary 32 | *.xml text 33 | *.wsdl text 34 | *.html text 35 | *.htm text 36 | *.css text 37 | *.js text 38 | *.mib text 39 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | buy_me_a_coffee: vXCNnz9 4 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '34 7 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'csharp' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v4 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v3 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v3 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v3 71 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributions are really appreciated! 2 | 3 | A good way to get started (flow): 4 | 5 | 1. Fork the redmine-net-api repository. 6 | 2. Create a new branch in your current repos from the 'master' branch. 7 | 3. 'Check out' the code with *Git*, *GitHub Desktop* or *SourceTree*. 8 | 4. Push commits and create a Pull Request (PR) to redmine-net-api. 9 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 5 | strict 6 | true 7 | 8 | 9 | 10 | true 11 | true 12 | true 13 | true 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | |net20|net40|net45|net451|net452|net46| 4 | |net20|net40|net45|net451|net452|net46|net461| 5 | |net45|net451|net452|net46| 6 | |net45|net451|net452|net46|net461| 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | When creating a new issue, please make sure the following information is part of your issue description (if applicable). 2 | 3 | - Which Redmine server version are you using 4 | - Which Redmine.Net.Api version are you using 5 | - Which serialization type (xml or json) are you using 6 | - A list of steps or a gist or a github repository which can be easily used to reproduce your case. -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | We welcome contributions! 2 | 3 | Here's how you can help: 4 | 5 | 1. Fork the repository 6 | 2. Create a new branch (git checkout -b feature/my-feature) 7 | 3. Make your changes and commit (git commit -m 'Add some feature') 8 | 4. Push to your fork (git push origin feature/my-feature) 9 | 5. Open a Pull Request 10 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | redmine: 5 | ports: 6 | - '8089:3000' 7 | image: 'redmine:6.0.5-alpine' 8 | container_name: 'redmine-web605' 9 | depends_on: 10 | - db-postgres 11 | # healthcheck: 12 | # test: ["CMD", "curl", "-f", "http://localhost:8089"] 13 | # interval: 1m30s 14 | # timeout: 10s 15 | # retries: 3 16 | # start_period: 40s 17 | restart: unless-stopped 18 | environment: 19 | REDMINE_DB_POSTGRES: db-postgres 20 | REDMINE_DB_PORT: 5432 21 | REDMINE_DB_DATABASE: redmine 22 | REDMINE_DB_USERNAME: redmine-usr 23 | REDMINE_DB_PASSWORD: redmine-pswd 24 | networks: 25 | - redmine-network 26 | stop_grace_period: 30s 27 | volumes: 28 | - redmine-data:/usr/src/redmine/files 29 | 30 | db-postgres: 31 | environment: 32 | POSTGRES_DB: redmine 33 | POSTGRES_USER: redmine-usr 34 | POSTGRES_PASSWORD: redmine-pswd 35 | container_name: 'redmine-db175' 36 | image: 'postgres:17.5-alpine' 37 | healthcheck: 38 | test: ["CMD-SHELL", "pg_isready -U postgres"] 39 | interval: 20s 40 | timeout: 20s 41 | retries: 5 42 | restart: unless-stopped 43 | ports: 44 | - '5432:5432' 45 | volumes: 46 | - postgres-data:/var/lib/postgresql/data 47 | networks: 48 | - redmine-network 49 | stop_grace_period: 30s 50 | 51 | volumes: 52 | postgres-data: 53 | redmine-data: 54 | 55 | networks: 56 | redmine-network: 57 | driver: bridge -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "9.0.203", 4 | "allowPrerelease": false, 5 | "rollForward": "latestMajor" 6 | } 7 | } -------------------------------------------------------------------------------- /logo-resharper.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zapadi/redmine-net-api/caaefe444c02ceb355cf56476f179de1ca9fb816/logo-resharper.gif -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zapadi/redmine-net-api/caaefe444c02ceb355cf56476f179de1ca9fb816/logo.png -------------------------------------------------------------------------------- /redmine-net-api.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zapadi/redmine-net-api/caaefe444c02ceb355cf56476f179de1ca9fb816/redmine-net-api.snk -------------------------------------------------------------------------------- /releasenotes.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(PackageReleaseNotes) 4 | See $(PackageProjectUrl)/blob/master/CHANGELOG.md#v$(VersionPrefix.Replace('.','')) for more details. 5 | 6 | -------------------------------------------------------------------------------- /signing.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | ..\..\redmine-net-api.snk 5 | 6 | -------------------------------------------------------------------------------- /src/redmine-net-api/Authentication/IRedmineAuthentication.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System.Net; 18 | 19 | namespace Redmine.Net.Api.Authentication; 20 | 21 | /// 22 | /// 23 | /// 24 | public interface IRedmineAuthentication 25 | { 26 | /// 27 | /// 28 | /// 29 | string AuthenticationType { get; } 30 | 31 | /// 32 | /// 33 | /// 34 | string Token { get; } 35 | 36 | /// 37 | /// 38 | /// 39 | ICredentials Credentials { get; } 40 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Authentication/RedmineApiKeyAuthentication.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System.Net; 18 | using Redmine.Net.Api.Extensions; 19 | 20 | namespace Redmine.Net.Api.Authentication; 21 | 22 | /// 23 | /// 24 | /// 25 | public sealed class RedmineApiKeyAuthentication: IRedmineAuthentication 26 | { 27 | /// 28 | public string AuthenticationType { get; } = RedmineAuthenticationType.ApiKey.ToText(); 29 | 30 | /// 31 | public string Token { get; init; } 32 | 33 | /// 34 | public ICredentials Credentials { get; init; } 35 | 36 | /// 37 | /// 38 | /// 39 | /// 40 | public RedmineApiKeyAuthentication(string apiKey) 41 | { 42 | Token = apiKey; 43 | } 44 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Authentication/RedmineAuthenticationType.cs: -------------------------------------------------------------------------------- 1 | namespace Redmine.Net.Api.Authentication; 2 | 3 | internal enum RedmineAuthenticationType 4 | { 5 | NoAuthentication, 6 | Basic, 7 | ApiKey 8 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Authentication/RedmineBasicAuthentication.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System; 18 | using System.Net; 19 | using System.Text; 20 | using Redmine.Net.Api.Exceptions; 21 | using Redmine.Net.Api.Extensions; 22 | 23 | namespace Redmine.Net.Api.Authentication 24 | { 25 | /// 26 | /// 27 | /// 28 | public sealed class RedmineBasicAuthentication: IRedmineAuthentication 29 | { 30 | /// 31 | public string AuthenticationType { get; } = RedmineAuthenticationType.Basic.ToText(); 32 | 33 | /// 34 | public string Token { get; init; } 35 | 36 | /// 37 | public ICredentials Credentials { get; init; } 38 | 39 | /// 40 | /// 41 | /// 42 | /// 43 | /// 44 | public RedmineBasicAuthentication(string username, string password) 45 | { 46 | if (username == null) throw new RedmineException(nameof(username)); 47 | if (password == null) throw new RedmineException(nameof(password)); 48 | 49 | Token = $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"))}"; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Authentication/RedmineNoAuthentication.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System.Net; 18 | using Redmine.Net.Api.Extensions; 19 | 20 | namespace Redmine.Net.Api.Authentication; 21 | 22 | /// 23 | /// 24 | /// 25 | public sealed class RedmineNoAuthentication: IRedmineAuthentication 26 | { 27 | /// 28 | public string AuthenticationType { get; } = RedmineAuthenticationType.NoAuthentication.ToText(); 29 | 30 | /// 31 | public string Token { get; init; } 32 | 33 | /// 34 | public ICredentials Credentials { get; init; } 35 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Common/AType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Redmine.Net.Api.Common; 4 | 5 | internal readonly struct A{ 6 | public static A Is => default; 7 | #pragma warning disable CS0184 // 'is' expression's given expression is never of the provided type 8 | public static bool IsEqual() => Is is A; 9 | #pragma warning restore CS0184 // 'is' expression's given expression is never of the provided type 10 | public static Type Value => typeof(T); 11 | 12 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Common/ArgumentVerifier.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | namespace Redmine.Net.Api.Common; 5 | 6 | /// 7 | /// A utility class to perform argument validations. 8 | /// 9 | internal static class ArgumentVerifier 10 | { 11 | /// 12 | /// Throws ArgumentNullException if the argument is null. 13 | /// 14 | /// Argument value to check. 15 | /// Name of Argument. 16 | public static void ThrowIfNull([NotNull] object value, string name) 17 | { 18 | if (value == null) 19 | { 20 | throw new ArgumentNullException(name); 21 | } 22 | } 23 | 24 | /// 25 | /// Validates string and throws: 26 | /// ArgumentNullException if the argument is null. 27 | /// ArgumentException if the argument is empty. 28 | /// 29 | /// Argument value to check. 30 | /// Name of Argument. 31 | public static void ThrowIfNullOrEmpty([NotNull] string value, string name) 32 | { 33 | if (value == null) 34 | { 35 | throw new ArgumentNullException(name); 36 | } 37 | 38 | if (string.IsNullOrEmpty(value)) 39 | { 40 | throw new ArgumentException("The value cannot be null or empty", name); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Common/IValue.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | namespace Redmine.Net.Api.Common 18 | { 19 | /// 20 | /// 21 | /// 22 | public interface IValue 23 | { 24 | /// 25 | /// 26 | /// 27 | string Value{ get;} 28 | } 29 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Common/PagedResults.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System.Collections.Generic; 18 | 19 | namespace Redmine.Net.Api.Common 20 | { 21 | /// 22 | /// 23 | /// 24 | public sealed class PagedResults where TOut: class 25 | { 26 | /// 27 | /// 28 | /// 29 | /// 30 | /// 31 | /// 32 | /// 33 | public PagedResults(List items, int total, int offset, int pageSize) 34 | { 35 | Items = items; 36 | TotalItems = total; 37 | Offset = offset; 38 | PageSize = pageSize; 39 | 40 | if (pageSize <= 0 || total == 0) 41 | { 42 | return; 43 | } 44 | 45 | CurrentPage = offset / pageSize + 1; 46 | 47 | TotalPages = total / pageSize + 1; 48 | } 49 | 50 | /// 51 | /// 52 | /// 53 | public int PageSize { get; } 54 | 55 | /// 56 | /// 57 | /// 58 | public int Offset { get; } 59 | 60 | /// 61 | /// 62 | /// 63 | public int CurrentPage { get; } 64 | 65 | /// 66 | /// 67 | /// 68 | public int TotalItems { get; } 69 | 70 | /// 71 | /// 72 | /// 73 | public int TotalPages { get; } 74 | 75 | /// 76 | /// 77 | /// 78 | public List Items { get; } 79 | } 80 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Exceptions/RedmineNotAcceptableException.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System; 18 | 19 | namespace Redmine.Net.Api.Exceptions 20 | { 21 | /// 22 | /// 23 | /// 24 | [Serializable] 25 | public sealed class RedmineNotAcceptableException : RedmineApiException 26 | { 27 | /// 28 | public override string ErrorCode => "REDMINE-NOT-ACCEPTABLE-010"; 29 | 30 | /// 31 | /// Initializes a new instance of the class. 32 | /// 33 | public RedmineNotAcceptableException() 34 | { 35 | } 36 | 37 | /// 38 | /// Initializes a new instance of the class. 39 | /// 40 | /// 41 | public RedmineNotAcceptableException(string message) 42 | : base(message) 43 | { 44 | } 45 | 46 | /// 47 | /// Initializes a new instance of the class. 48 | /// 49 | /// 50 | /// 51 | public RedmineNotAcceptableException(string message, Exception innerException) 52 | : base(message, innerException) 53 | { 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Extensions/IntExtensions.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | namespace Redmine.Net.Api.Extensions; 18 | 19 | internal static class IntExtensions 20 | { 21 | public static bool Between(this int val, int from, int to) 22 | { 23 | return val >= from && val <= to; 24 | } 25 | 26 | public static bool Greater(this int val, int than) 27 | { 28 | return val > than; 29 | } 30 | 31 | public static bool GreaterOrEqual(this int val, int than) 32 | { 33 | return val >= than; 34 | } 35 | 36 | public static bool Lower(this int val, int than) 37 | { 38 | return val < than; 39 | } 40 | 41 | public static bool LowerOrEqual(this int val, int than) 42 | { 43 | return val <= than; 44 | } 45 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Extensions/SemaphoreSlimExtensions.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #if !(NET20) 18 | using System.Threading; 19 | using System.Threading.Tasks; 20 | 21 | namespace Redmine.Net.Api.Extensions; 22 | #if !(NET45_OR_GREATER || NETCOREAPP) 23 | internal static class SemaphoreSlimExtensions 24 | { 25 | 26 | public static Task WaitAsync(this SemaphoreSlim semaphore, CancellationToken cancellationToken = default) 27 | { 28 | return Task.Factory.StartNew(() => semaphore.Wait(cancellationToken) 29 | , CancellationToken.None 30 | , TaskCreationOptions.None 31 | , TaskScheduler.Default); 32 | } 33 | } 34 | #endif 35 | #endif 36 | -------------------------------------------------------------------------------- /src/redmine-net-api/Extensions/TaskExtensions.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #if !(NET20) 18 | using System; 19 | using System.Collections.Generic; 20 | using System.Linq; 21 | using System.Threading; 22 | using System.Threading.Tasks; 23 | 24 | namespace Redmine.Net.Api.Extensions; 25 | 26 | internal static class TaskExtensions 27 | { 28 | public static T GetAwaiterResult(this Task task) 29 | { 30 | return task.GetAwaiter().GetResult(); 31 | } 32 | 33 | public static TResult Synchronize(Func> function) 34 | { 35 | return Task.Factory.StartNew(function, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default) 36 | .Unwrap().GetAwaiter().GetResult(); 37 | } 38 | 39 | public static void Synchronize(Func function) 40 | { 41 | Task.Factory.StartNew(function, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default) 42 | .Unwrap().GetAwaiter().GetResult(); 43 | } 44 | 45 | #if !(NET45_OR_GREATER || NETCOREAPP) 46 | public static Task WhenAll(IEnumerable> tasks) 47 | { 48 | var clone = tasks.ToArray(); 49 | 50 | var x = Task.Factory.StartNew(() => 51 | { 52 | Task.WaitAll(clone); 53 | return clone.Select(t => t.Result).ToArray(); 54 | }, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); 55 | 56 | return default; 57 | } 58 | #endif 59 | } 60 | #endif -------------------------------------------------------------------------------- /src/redmine-net-api/Features/CallerArgumentExpressionAttribute.cs: -------------------------------------------------------------------------------- 1 | #if NET20_OR_GREATER 2 | #pragma warning disable 3 | #nullable enable annotations 4 | 5 | // Licensed to the .NET Foundation under one or more agreements. 6 | // The .NET Foundation licenses this file to you under the MIT license. 7 | // ReSharper disable once CheckNamespace 8 | namespace System.Runtime.CompilerServices 9 | { 10 | /// 11 | /// An attribute that allows parameters to receive the expression of other parameters. 12 | /// 13 | [global::System.AttributeUsage(global::System.AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] 14 | internal sealed class CallerArgumentExpressionAttribute : global::System.Attribute 15 | { 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The condition parameter value. 20 | public CallerArgumentExpressionAttribute(string parameterName) 21 | { 22 | ParameterName = parameterName; 23 | } 24 | 25 | /// 26 | /// Gets the parameter name the expression is retrieved from. 27 | /// 28 | public string ParameterName { get; } 29 | } 30 | } 31 | #endif -------------------------------------------------------------------------------- /src/redmine-net-api/Features/IsExternalInit.cs: -------------------------------------------------------------------------------- 1 | #if NET20_OR_GREATER 2 | 3 | // ReSharper disable once CheckNamespace 4 | namespace System.Runtime.CompilerServices 5 | { 6 | /// 7 | /// Reserved to be used by the compiler for tracking metadata. 8 | /// This class should not be used by developers in source code. 9 | /// 10 | [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] 11 | internal static class IsExternalInit 12 | { 13 | } 14 | } 15 | 16 | #endif 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/redmine-net-api/Features/NotNullAttribute.cs: -------------------------------------------------------------------------------- 1 | #if NET20_OR_GREATER 2 | // 3 | #pragma warning disable 4 | #nullable enable annotations 5 | 6 | // Licensed to the .NET Foundation under one or more agreements. 7 | // The .NET Foundation licenses this file to you under the MIT license. 8 | // ReSharper disable once CheckNamespace 9 | namespace System.Diagnostics.CodeAnalysis 10 | { 11 | /// 12 | /// Specifies that an output will not be null even if the corresponding type allows it. 13 | /// Specifies that an input argument was not null when the call returns. 14 | /// 15 | [global::System.AttributeUsage( 16 | global::System.AttributeTargets.Field | 17 | global::System.AttributeTargets.Parameter | 18 | global::System.AttributeTargets.Property | 19 | global::System.AttributeTargets.ReturnValue, 20 | Inherited = false)] 21 | internal sealed class NotNullAttribute : global::System.Attribute 22 | { 23 | } 24 | } 25 | #endif -------------------------------------------------------------------------------- /src/redmine-net-api/Http/Clients/HttpClient/HttpContentExtensions.cs: -------------------------------------------------------------------------------- 1 | #if !NET20 2 | 3 | using System.Net; 4 | 5 | namespace Redmine.Net.Api.Http.Clients.HttpClient; 6 | 7 | internal static class HttpContentExtensions 8 | { 9 | public static bool IsUnprocessableEntity(this HttpStatusCode statusCode) 10 | { 11 | return 12 | #if NET5_0_OR_GREATER 13 | statusCode == HttpStatusCode.UnprocessableEntity; 14 | #else 15 | (int)statusCode == 422; 16 | #endif 17 | } 18 | } 19 | 20 | #endif -------------------------------------------------------------------------------- /src/redmine-net-api/Http/Clients/HttpClient/HttpContentPolyfills.cs: -------------------------------------------------------------------------------- 1 | 2 | #if !(NET20 || NET5_0_OR_GREATER) 3 | 4 | using System.IO; 5 | using System.Net.Http; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace Redmine.Net.Api.Http.Clients.HttpClient; 10 | 11 | internal static class HttpContentPolyfills 12 | { 13 | internal static Task ReadAsStringAsync(this HttpContent httpContent, CancellationToken cancellationToken) 14 | => httpContent.ReadAsStringAsync( 15 | #if !NETFRAMEWORK 16 | cancellationToken 17 | #endif 18 | ); 19 | 20 | internal static Task ReadAsStreamAsync(this HttpContent httpContent, CancellationToken cancellationToken) 21 | => httpContent.ReadAsStreamAsync( 22 | #if !NETFRAMEWORK 23 | cancellationToken 24 | #endif 25 | ); 26 | 27 | internal static Task ReadAsByteArrayAsync(this HttpContent httpContent, CancellationToken cancellationToken) 28 | => httpContent.ReadAsByteArrayAsync( 29 | #if !NETFRAMEWORK 30 | cancellationToken 31 | #endif 32 | ); 33 | } 34 | 35 | #endif -------------------------------------------------------------------------------- /src/redmine-net-api/Http/Clients/HttpClient/HttpResponseHeadersExtensions.cs: -------------------------------------------------------------------------------- 1 | #if !NET20 2 | using System.Collections.Specialized; 3 | using System.Net.Http.Headers; 4 | 5 | namespace Redmine.Net.Api.Http.Clients.HttpClient; 6 | 7 | internal static class HttpResponseHeadersExtensions 8 | { 9 | public static NameValueCollection ToNameValueCollection(this HttpResponseHeaders headers) 10 | { 11 | if (headers == null) return null; 12 | 13 | var collection = new NameValueCollection(); 14 | foreach (var header in headers) 15 | { 16 | var combinedValue = string.Join(", ", header.Value); 17 | collection.Add(header.Key, combinedValue); 18 | } 19 | return collection; 20 | } 21 | } 22 | #endif -------------------------------------------------------------------------------- /src/redmine-net-api/Http/Clients/HttpClient/IRedmineHttpClientOptions.cs: -------------------------------------------------------------------------------- 1 | #if NET40_OR_GREATER || NET 2 | using System; 3 | using System.Net; 4 | using System.Net.Http; 5 | using System.Net.Security; 6 | using System.Security.Authentication; 7 | using System.Security.Cryptography.X509Certificates; 8 | #if NET8_0_OR_GREATER 9 | using System.Diagnostics.Metrics; 10 | #endif 11 | 12 | 13 | namespace Redmine.Net.Api.Http.Clients.HttpClient; 14 | 15 | /// 16 | /// 17 | /// 18 | public interface IRedmineHttpClientOptions : IRedmineApiClientOptions 19 | { 20 | /// 21 | /// 22 | /// 23 | ClientCertificateOption ClientCertificateOptions { get; set; } 24 | 25 | #if NET471_OR_GREATER || NET 26 | /// 27 | /// 28 | /// 29 | ICredentials DefaultProxyCredentials { get; set; } 30 | 31 | /// 32 | /// 33 | /// 34 | Func ServerCertificateCustomValidationCallback { get; set; } 35 | 36 | /// 37 | /// 38 | /// 39 | SslProtocols SslProtocols { get; set; } 40 | #endif 41 | 42 | /// 43 | /// 44 | /// 45 | public 46 | #if NET || NET471_OR_GREATER 47 | Func 48 | #else 49 | RemoteCertificateValidationCallback 50 | #endif 51 | ServerCertificateValidationCallback { get; set; } 52 | 53 | #if NET8_0_OR_GREATER 54 | /// 55 | /// 56 | /// 57 | public IMeterFactory MeterFactory { get; set; } 58 | #endif 59 | 60 | /// 61 | /// 62 | /// 63 | bool SupportsAutomaticDecompression { get; set; } 64 | 65 | /// 66 | /// 67 | /// 68 | bool SupportsProxy { get; set; } 69 | 70 | /// 71 | /// 72 | /// 73 | bool SupportsRedirectConfiguration { get; set; } 74 | 75 | /// 76 | /// 77 | /// 78 | Version DefaultRequestVersion { get; set; } 79 | 80 | #if NET 81 | /// 82 | /// 83 | /// 84 | HttpVersionPolicy? DefaultVersionPolicy { get; set; } 85 | #endif 86 | } 87 | 88 | #endif -------------------------------------------------------------------------------- /src/redmine-net-api/Http/Clients/HttpClient/RedmineHttpClientOptions.cs: -------------------------------------------------------------------------------- 1 | #if !NET20 2 | using System; 3 | using System.Net; 4 | using System.Net.Http; 5 | using System.Net.Security; 6 | using System.Security.Authentication; 7 | using System.Security.Cryptography.X509Certificates; 8 | #if NET8_0_OR_GREATER 9 | using System.Diagnostics.Metrics; 10 | #endif 11 | 12 | namespace Redmine.Net.Api.Http.Clients.HttpClient; 13 | 14 | /// 15 | /// 16 | /// 17 | public sealed class RedmineHttpClientOptions: RedmineApiClientOptions 18 | { 19 | #if NET8_0_OR_GREATER 20 | /// 21 | /// 22 | /// 23 | public IMeterFactory MeterFactory { get; set; } 24 | #endif 25 | 26 | /// 27 | /// 28 | /// 29 | public Version DefaultRequestVersion { get; set; } 30 | 31 | #if NET 32 | /// 33 | /// 34 | /// 35 | public HttpVersionPolicy? DefaultVersionPolicy { get; set; } 36 | #endif 37 | /// 38 | /// 39 | /// 40 | public ICredentials DefaultProxyCredentials { get; set; } 41 | 42 | /// 43 | /// 44 | /// 45 | public ClientCertificateOption ClientCertificateOptions { get; set; } 46 | 47 | 48 | 49 | #if NETFRAMEWORK 50 | /// 51 | /// 52 | /// 53 | public Func ServerCertificateCustomValidationCallback 54 | { 55 | get; 56 | set; 57 | } 58 | 59 | /// 60 | /// 61 | /// 62 | public SslProtocols SslProtocols { get; set; } 63 | #endif 64 | /// 65 | /// 66 | /// 67 | public 68 | #if NET || NET471_OR_GREATER 69 | Func 70 | #else 71 | RemoteCertificateValidationCallback 72 | #endif 73 | ServerCertificateValidationCallback { get; set; } 74 | } 75 | #endif -------------------------------------------------------------------------------- /src/redmine-net-api/Http/Clients/WebClient/RedmineWebClientOptions.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System.Net; 18 | #if (NET45_OR_GREATER || NET) 19 | using System.Net.Security; 20 | #endif 21 | 22 | namespace Redmine.Net.Api.Http.Clients.WebClient; 23 | /// 24 | /// 25 | /// 26 | public sealed class RedmineWebClientOptions: RedmineApiClientOptions 27 | { 28 | 29 | /// 30 | /// 31 | /// 32 | public bool? KeepAlive { get; set; } 33 | 34 | /// 35 | /// 36 | /// 37 | public bool? UnsafeAuthenticatedConnectionSharing { get; set; } 38 | 39 | /// 40 | /// 41 | /// 42 | public int? DefaultConnectionLimit { get; set; } 43 | 44 | /// 45 | /// 46 | /// 47 | public int? DnsRefreshTimeout { get; set; } 48 | 49 | /// 50 | /// 51 | /// 52 | public bool? EnableDnsRoundRobin { get; set; } 53 | 54 | /// 55 | /// 56 | /// 57 | public int? MaxServicePoints { get; set; } 58 | 59 | /// 60 | /// 61 | /// 62 | public int? MaxServicePointIdleTime { get; set; } 63 | 64 | #if(NET46_OR_GREATER || NET) 65 | /// 66 | /// 67 | /// 68 | public bool? ReusePort { get; set; } 69 | #endif 70 | 71 | /// 72 | /// 73 | /// 74 | public SecurityProtocolType? SecurityProtocolType { get; set; } 75 | 76 | #if (NET45_OR_GREATER || NET) 77 | /// 78 | /// 79 | /// 80 | public RemoteCertificateValidationCallback ServerCertificateValidationCallback { get; set; } 81 | #endif 82 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Http/Clients/WebClient/WebClientProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Redmine.Net.Api.Options; 3 | 4 | namespace Redmine.Net.Api.Http.Clients.WebClient; 5 | 6 | internal static class WebClientProvider 7 | { 8 | /// 9 | /// Creates a new WebClient instance with the specified options. 10 | /// 11 | /// The options for the Redmine manager. 12 | /// A new WebClient instance. 13 | public static System.Net.WebClient CreateWebClient(RedmineManagerOptions options) 14 | { 15 | var webClient = new InternalWebClient(options); 16 | 17 | if (options?.ApiClientOptions is RedmineWebClientOptions webClientOptions) 18 | { 19 | ConfigureWebClient(webClient, webClientOptions); 20 | } 21 | 22 | return webClient; 23 | } 24 | 25 | /// 26 | /// Configures a WebClient instance with the specified options. 27 | /// 28 | /// The WebClient instance to configure. 29 | /// The options to apply. 30 | private static void ConfigureWebClient(System.Net.WebClient webClient, RedmineWebClientOptions options) 31 | { 32 | if (options == null) return; 33 | 34 | webClient.Proxy = options.Proxy; 35 | webClient.Headers = null; 36 | webClient.BaseAddress = null; 37 | webClient.CachePolicy = null; 38 | webClient.Credentials = null; 39 | webClient.Encoding = Encoding.UTF8; 40 | webClient.UseDefaultCredentials = false; 41 | 42 | // if (options.Timeout.HasValue && options.Timeout.Value != TimeSpan.Zero) 43 | // { 44 | // webClient.Timeout = options.Timeout; 45 | // } 46 | // 47 | // if (options.KeepAlive.HasValue) 48 | // { 49 | // webClient.KeepAlive = options.KeepAlive.Value; 50 | // } 51 | // 52 | // if (options.UnsafeAuthenticatedConnectionSharing.HasValue) 53 | // { 54 | // webClient.UnsafeAuthenticatedConnectionSharing = options.UnsafeAuthenticatedConnectionSharing.Value; 55 | // } 56 | // 57 | // #if NET40_OR_GREATER || NET 58 | // if (options.ClientCertificates != null) 59 | // { 60 | // webClient.ClientCertificates = options.ClientCertificates; 61 | // } 62 | // #endif 63 | 64 | } 65 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Http/Helpers/ClientHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Redmine.Net.Api.Http.Helpers; 4 | 5 | internal static class ClientHelper 6 | { 7 | internal static void ReportProgress(IProgressprogress, long total, long bytesRead) 8 | { 9 | if (progress == null || total <= 0) 10 | { 11 | return; 12 | } 13 | var percent = (int)(bytesRead * 100L / total); 14 | progress.Report(percent); 15 | } 16 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Http/Messages/RedmineApiRequest.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System.Collections.Generic; 18 | using System.Collections.Specialized; 19 | using Redmine.Net.Api.Http.Clients.WebClient; 20 | using Redmine.Net.Api.Http.Constants; 21 | 22 | namespace Redmine.Net.Api.Http.Messages; 23 | 24 | internal sealed class RedmineApiRequest 25 | { 26 | /// 27 | /// 28 | /// 29 | public RedmineApiRequestContent Content { get; set; } 30 | 31 | /// 32 | /// 33 | /// 34 | public string Method { get; set; } = HttpConstants.HttpVerbs.GET; 35 | 36 | /// 37 | /// 38 | /// 39 | public string RequestUri { get; set; } 40 | 41 | /// 42 | /// 43 | /// 44 | public NameValueCollection QueryString { get; set; } 45 | /// 46 | /// 47 | /// 48 | public string ImpersonateUser { get; set; } 49 | 50 | /// 51 | /// 52 | /// 53 | public string ContentType { get; set; } 54 | 55 | /// 56 | /// 57 | /// 58 | public string Accept { get; set; } 59 | /// 60 | /// 61 | /// 62 | public string UserAgent { get; set; } 63 | 64 | /// 65 | /// 66 | /// 67 | public Dictionary Headers { get; set; } 68 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Http/Messages/RedmineApiResponse.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System.Collections.Specialized; 18 | using System.Net; 19 | 20 | namespace Redmine.Net.Api.Http.Messages; 21 | 22 | internal sealed class RedmineApiResponse 23 | { 24 | public NameValueCollection Headers { get; init; } 25 | public byte[] Content { get; init; } 26 | 27 | public HttpStatusCode StatusCode { get; init; } 28 | 29 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Http/RedirectType.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | namespace Redmine.Net.Api.Http 18 | { 19 | /// 20 | /// 21 | /// 22 | internal enum RedirectType 23 | { 24 | /// 25 | /// 26 | /// 27 | None, 28 | /// 29 | /// 30 | /// 31 | OnlyHost, 32 | /// 33 | /// 34 | /// 35 | All 36 | } 37 | } -------------------------------------------------------------------------------- /src/redmine-net-api/ICloneableOfT.cs: -------------------------------------------------------------------------------- 1 | namespace Redmine.Net.Api; 2 | 3 | /// 4 | /// 5 | /// 6 | /// 7 | public interface ICloneable 8 | { 9 | /// 10 | /// 11 | /// 12 | /// 13 | internal T Clone(bool resetId); 14 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Internals/ArgumentNullThrowHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Runtime.CompilerServices; 4 | 5 | #nullable enable 6 | 7 | namespace Redmine.Net.Api.Internals; 8 | 9 | internal static class ArgumentNullThrowHelper 10 | { 11 | public static void ThrowIfNull( 12 | #if INTERNAL_NULLABLE_ATTRIBUTES || NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER 13 | [NotNull] 14 | #endif 15 | object? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null) 16 | { 17 | #if !NET7_0_OR_GREATER || NETSTANDARD || NETFRAMEWORK 18 | if (argument is null) 19 | { 20 | Throw(paramName); 21 | } 22 | #else 23 | ArgumentNullException.ThrowIfNull(argument, paramName); 24 | #endif 25 | } 26 | 27 | #if !NET7_0_OR_GREATER || NETSTANDARD || NETFRAMEWORK 28 | #if INTERNAL_NULLABLE_ATTRIBUTES || NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER 29 | [DoesNotReturn] 30 | #endif 31 | internal static void Throw(string? paramName) => 32 | throw new ArgumentNullException(paramName); 33 | #endif 34 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Internals/ParameterValidator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Redmine.Net.Api.Internals; 4 | 5 | /// 6 | /// 7 | /// 8 | internal static class ParameterValidator 9 | { 10 | public static void ValidateNotNull(T parameter, string parameterName) 11 | where T : class 12 | { 13 | if (parameter is null) 14 | { 15 | throw new ArgumentNullException(parameterName); 16 | } 17 | } 18 | 19 | public static void ValidateNotNullOrEmpty(string parameter, string parameterName) 20 | { 21 | if (string.IsNullOrEmpty(parameter)) 22 | { 23 | throw new ArgumentException("Value cannot be null or empty", parameterName); 24 | } 25 | } 26 | 27 | public static void ValidateId(int id, string parameterName) 28 | { 29 | if (id <= 0) 30 | { 31 | throw new ArgumentException("Id must be greater than 0", parameterName); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Logging/IRedmineLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Redmine.Net.Api.Logging; 5 | 6 | /// 7 | /// Provides abstraction for logging operations 8 | /// 9 | public interface IRedmineLogger 10 | { 11 | /// 12 | /// Checks if the specified log level is enabled 13 | /// 14 | bool IsEnabled(LogLevel level); 15 | 16 | /// 17 | /// Logs a message with the specified level 18 | /// 19 | void Log(LogLevel level, string message, Exception exception = null); 20 | 21 | /// 22 | /// Creates a scoped logger with additional context 23 | /// 24 | IRedmineLogger CreateScope(string scopeName, IDictionary scopeProperties = null); 25 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Logging/LogLevel.cs: -------------------------------------------------------------------------------- 1 | namespace Redmine.Net.Api.Logging; 2 | 3 | /// 4 | /// Defines logging severity levels 5 | /// 6 | public enum LogLevel 7 | { 8 | /// 9 | /// 10 | /// 11 | Trace, 12 | /// 13 | /// 14 | /// 15 | Debug, 16 | /// 17 | /// 18 | /// 19 | Information, 20 | /// 21 | /// 22 | /// 23 | Warning, 24 | /// 25 | /// 26 | /// 27 | Error, 28 | /// 29 | /// 30 | /// 31 | Critical 32 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Logging/RedmineConsoleLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Redmine.Net.Api.Logging; 5 | 6 | /// 7 | /// 8 | /// 9 | /// 10 | /// 11 | /// 12 | /// 13 | /// 14 | public class RedmineConsoleLogger(string categoryName = "Redmine", LogLevel minLevel = LogLevel.Information) : IRedmineLogger 15 | { 16 | /// 17 | /// 18 | /// 19 | /// 20 | /// 21 | public bool IsEnabled(LogLevel level) => level >= minLevel; 22 | 23 | /// 24 | /// 25 | /// 26 | /// 27 | /// 28 | /// 29 | public void Log(LogLevel level, string message, Exception exception = null) 30 | { 31 | if (!IsEnabled(level)) 32 | { 33 | return; 34 | } 35 | 36 | // var originalColor = Console.ForegroundColor; 37 | // 38 | // Console.ForegroundColor = level switch 39 | // { 40 | // LogLevel.Trace => ConsoleColor.Gray, 41 | // LogLevel.Debug => ConsoleColor.Gray, 42 | // LogLevel.Information => ConsoleColor.White, 43 | // LogLevel.Warning => ConsoleColor.Yellow, 44 | // LogLevel.Error => ConsoleColor.Red, 45 | // LogLevel.Critical => ConsoleColor.Red, 46 | // _ => ConsoleColor.White 47 | // }; 48 | 49 | Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{level}] [{categoryName}] {message}"); 50 | 51 | if (exception != null) 52 | { 53 | Console.WriteLine($"Exception: {exception}"); 54 | } 55 | 56 | // Console.ForegroundColor = originalColor; 57 | } 58 | 59 | /// 60 | /// 61 | /// 62 | /// 63 | /// 64 | /// 65 | public IRedmineLogger CreateScope(string scopeName, IDictionary scopeProperties = null) 66 | { 67 | return new RedmineConsoleLogger($"{categoryName}.{scopeName}", minLevel); 68 | } 69 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Logging/RedmineLoggingOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Redmine.Net.Api.Logging; 2 | 3 | /// 4 | /// Options for configuring Redmine logging 5 | /// 6 | public sealed class RedmineLoggingOptions 7 | { 8 | /// 9 | /// Gets or sets the minimum log level. The default value is LogLevel.Information 10 | /// 11 | public LogLevel MinimumLevel { get; set; } = LogLevel.Information; 12 | 13 | /// 14 | /// Gets or sets whether to include HTTP request/response details in logs 15 | /// 16 | public bool IncludeHttpDetails { get; set; } 17 | 18 | /// 19 | /// Gets or sets whether performance metrics should be logged 20 | /// 21 | public bool LogPerformanceMetrics { get; set; } 22 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Logging/RedmineNullLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Redmine.Net.Api.Logging; 5 | 6 | /// 7 | /// 8 | /// 9 | public class RedmineNullLogger : IRedmineLogger 10 | { 11 | /// 12 | /// 13 | /// 14 | public static readonly RedmineNullLogger Instance = new RedmineNullLogger(); 15 | 16 | private RedmineNullLogger() { } 17 | 18 | /// 19 | /// 20 | /// 21 | /// 22 | /// 23 | public bool IsEnabled(LogLevel level) => false; 24 | 25 | /// 26 | /// 27 | /// 28 | /// 29 | /// 30 | /// 31 | public void Log(LogLevel level, string message, Exception exception = null) { } 32 | 33 | /// 34 | /// 35 | /// 36 | /// 37 | /// 38 | /// 39 | public IRedmineLogger CreateScope(string scopeName, IDictionary scopeProperties = null) => this; 40 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("redmine-net20-api")] 8 | [assembly: AssemblyDescription("redmine-net-api is a library for communicating with a Redmine project management application.")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("redmine-net20-api")] 12 | [assembly: AssemblyCopyright("Copyright © Adrian Popescu 2011 - 2019")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("be4f22a5-4a38-4907-a5dd-385e1c4aa95c")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.4")] 35 | [assembly: AssemblyFileVersion("1.0.4")] 36 | -------------------------------------------------------------------------------- /src/redmine-net-api/RedmineConstants.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | namespace Redmine.Net.Api 18 | { 19 | /// 20 | /// 21 | /// 22 | public static class RedmineConstants 23 | { 24 | /// 25 | /// 26 | /// 27 | internal const string OBSOLETE_TEXT = "In next major release, it will no longer be available."; 28 | /// 29 | /// 30 | /// 31 | public const int DEFAULT_PAGE_SIZE_VALUE = 25; 32 | 33 | /// 34 | /// 35 | /// 36 | public const string CONTENT_TYPE_APPLICATION_JSON = "application/json"; 37 | /// 38 | /// 39 | /// 40 | public const string CONTENT_TYPE_APPLICATION_XML = "application/xml"; 41 | /// 42 | /// 43 | /// 44 | public const string CONTENT_TYPE_APPLICATION_STREAM = "application/octet-stream"; 45 | 46 | /// 47 | /// 48 | /// 49 | public const string IMPERSONATE_HEADER_KEY = "X-Redmine-Switch-User"; 50 | 51 | /// 52 | /// 53 | /// 54 | public const string AUTHORIZATION_HEADER_KEY = "Authorization"; 55 | /// 56 | /// 57 | /// 58 | public const string API_KEY_AUTHORIZATION_HEADER_KEY = "X-Redmine-API-Key"; 59 | 60 | /// 61 | /// 62 | /// 63 | public const string XML = "xml"; 64 | 65 | /// 66 | /// 67 | /// 68 | public const string JSON = "json"; 69 | 70 | internal const string USER_AGENT_HEADER_KEY = "User-Agent"; 71 | internal const string CONTENT_TYPE_HEADER_KEY = "Content-Type"; 72 | } 73 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Serialization/IRedmineSerializer.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using Redmine.Net.Api.Common; 18 | 19 | namespace Redmine.Net.Api.Serialization 20 | { 21 | /// 22 | /// Serialization interface that supports serialize and deserialize methods. 23 | /// 24 | internal interface IRedmineSerializer 25 | { 26 | /// 27 | /// Gets the application format this serializer supports (e.g. "json", "xml"). 28 | /// 29 | string Format { get; } 30 | 31 | /// 32 | /// 33 | /// 34 | string ContentType { get; } 35 | 36 | /// 37 | /// Serializes the specified object into a string. 38 | /// 39 | string Serialize(T obj) where T : class; 40 | 41 | /// 42 | /// Deserializes the string into a PageResult of T object. 43 | /// 44 | PagedResults DeserializeToPagedResults(string response) where T : class, new(); 45 | 46 | /// 47 | /// Deserializes the string into an object. 48 | /// 49 | T Deserialize(string input) where T : new(); 50 | } 51 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Serialization/Json/IJsonSerializable.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using Newtonsoft.Json; 18 | 19 | namespace Redmine.Net.Api.Serialization.Json 20 | { 21 | /// 22 | /// 23 | /// 24 | public interface IJsonSerializable 25 | { 26 | /// 27 | /// 28 | /// 29 | /// 30 | void WriteJson(JsonWriter writer); 31 | /// 32 | /// 33 | /// 34 | /// 35 | void ReadJson(JsonReader reader); 36 | } 37 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Serialization/Json/JsonObject.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System; 18 | using Newtonsoft.Json; 19 | using Redmine.Net.Api.Extensions; 20 | 21 | namespace Redmine.Net.Api.Serialization.Json 22 | { 23 | /// 24 | /// 25 | /// 26 | public sealed class JsonObject : IDisposable 27 | { 28 | private readonly bool hasRoot; 29 | 30 | /// 31 | /// 32 | /// 33 | /// 34 | /// 35 | public JsonObject(JsonWriter writer, string root = null) 36 | { 37 | Writer = writer; 38 | Writer.WriteStartObject(); 39 | 40 | if (root.IsNullOrWhiteSpace()) 41 | { 42 | return; 43 | } 44 | 45 | hasRoot = true; 46 | Writer.WritePropertyName(root); 47 | Writer.WriteStartObject(); 48 | } 49 | 50 | private JsonWriter Writer { get; } 51 | 52 | /// 53 | /// 54 | /// 55 | public void Dispose() 56 | { 57 | Writer.WriteEndObject(); 58 | if (hasRoot) 59 | { 60 | Writer.WriteEndObject(); 61 | } 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Serialization/RedmineSerializerFactory.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System; 18 | using Redmine.Net.Api.Serialization.Json; 19 | using Redmine.Net.Api.Serialization.Xml; 20 | 21 | namespace Redmine.Net.Api.Serialization; 22 | 23 | /// 24 | /// Factory for creating RedmineSerializer instances 25 | /// 26 | internal static class RedmineSerializerFactory 27 | { 28 | /// 29 | /// Creates an instance of an IRedmineSerializer based on the specified serialization type. 30 | /// 31 | /// The type of serialization, either Xml or Json. 32 | /// 33 | /// An instance of a serializer that implements the IRedmineSerializer interface. 34 | /// 35 | /// 36 | /// Thrown when the specified serialization type is not supported. 37 | /// 38 | public static IRedmineSerializer CreateSerializer(SerializationType type) 39 | { 40 | return type switch 41 | { 42 | SerializationType.Xml => new XmlRedmineSerializer(), 43 | SerializationType.Json => new JsonRedmineSerializer(), 44 | _ => throw new NotImplementedException($"No serializer has been implemented for the serialization type: {type}") 45 | }; 46 | } 47 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Serialization/SerializationType.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | namespace Redmine.Net.Api.Serialization 18 | { 19 | /// 20 | /// Specifies the serialization types supported by the Redmine API. 21 | /// 22 | public enum SerializationType 23 | { 24 | /// 25 | /// The XML format. 26 | /// 27 | Xml, 28 | 29 | /// 30 | /// The JSON format. 31 | /// 32 | Json 33 | } 34 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Serialization/Xml/IXmlSerializerCache.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System; 18 | using System.Xml.Serialization; 19 | 20 | namespace Redmine.Net.Api.Serialization.Xml 21 | { 22 | internal interface IXmlSerializerCache 23 | { 24 | XmlSerializer GetSerializer(Type type, string defaultNamespace); 25 | 26 | XmlSerializer GetSerializer(Type type, XmlRootAttribute root); 27 | 28 | XmlSerializer GetSerializer(Type type, XmlAttributeOverrides overrides); 29 | 30 | XmlSerializer GetSerializer(Type type, Type[] types); 31 | 32 | XmlSerializer GetSerializer(Type type, XmlAttributeOverrides overrides, Type[] types, XmlRootAttribute root, string defaultNamespace); 33 | } 34 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Types/Attachments.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System.Collections.Generic; 18 | using Newtonsoft.Json; 19 | using Redmine.Net.Api.Extensions; 20 | using Redmine.Net.Api.Serialization.Json; 21 | 22 | namespace Redmine.Net.Api.Types 23 | { 24 | /// 25 | /// 26 | /// 27 | internal sealed class Attachments : Dictionary, IJsonSerializable 28 | { 29 | /// 30 | /// 31 | /// 32 | /// 33 | public void ReadJson(JsonReader reader) { } 34 | 35 | /// 36 | /// 37 | /// 38 | /// 39 | public void WriteJson(JsonWriter writer) 40 | { 41 | using (new JsonObject(writer, RedmineKeys.ATTACHMENTS)) 42 | { 43 | writer.WriteStartArray(); 44 | foreach (var item in this) 45 | { 46 | writer.WritePropertyName(item.Key.ToInvariantString()); 47 | item.Value.WriteJson(writer); 48 | } 49 | writer.WriteEndArray(); 50 | } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Types/CustomFieldRole.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System.Diagnostics; 18 | using System.Xml.Serialization; 19 | using Redmine.Net.Api.Extensions; 20 | 21 | namespace Redmine.Net.Api.Types 22 | { 23 | /// 24 | /// 25 | /// 26 | [DebuggerDisplay($"{{{nameof(DebuggerDisplay)},nq}}")] 27 | [XmlRoot(RedmineKeys.ROLE)] 28 | public sealed class CustomFieldRole : IdentifiableName 29 | { 30 | /// 31 | /// Initializes a new instance of the class. 32 | /// 33 | /// Serialization 34 | public CustomFieldRole() { } 35 | 36 | internal CustomFieldRole(int id, string name) 37 | : base(id, name) 38 | { 39 | } 40 | 41 | /// 42 | /// 43 | /// 44 | /// 45 | private string DebuggerDisplay => $"[CustomFieldRole: Id={Id.ToInvariantString()}, Name={Name}]"; 46 | 47 | } 48 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Types/GroupUser.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System.Diagnostics; 18 | using System.Xml.Serialization; 19 | using Redmine.Net.Api.Common; 20 | using Redmine.Net.Api.Extensions; 21 | 22 | namespace Redmine.Net.Api.Types 23 | { 24 | /// 25 | /// 26 | /// 27 | [DebuggerDisplay($"{{{nameof(DebuggerDisplay)},nq}}")] 28 | [XmlRoot(RedmineKeys.USER)] 29 | public sealed class GroupUser : IdentifiableName, IValue 30 | { 31 | #region Implementation of IValue 32 | /// 33 | /// 34 | /// 35 | public string Value => Id.ToInvariantString(); 36 | #endregion 37 | 38 | /// 39 | /// 40 | /// 41 | /// 42 | private string DebuggerDisplay => $"[GroupUser: Id={Id.ToInvariantString()}, Name={Name}]"; 43 | 44 | } 45 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Types/IssueRelationType.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System.Xml.Serialization; 18 | 19 | namespace Redmine.Net.Api.Types 20 | { 21 | /// 22 | /// 23 | /// 24 | public enum IssueRelationType 25 | { 26 | #pragma warning disable CS0618 // Use of internal enumeration value is allowed here to have a fallback 27 | /// 28 | /// Fallback value for deserialization purposes in case the deserialization fails. Do not use to create new relations! 29 | /// 30 | Undefined = 0, 31 | #pragma warning restore CS0618 32 | /// 33 | /// 34 | /// 35 | Relates = 1, 36 | 37 | /// 38 | /// 39 | /// 40 | Duplicates, 41 | 42 | /// 43 | /// 44 | /// 45 | Duplicated, 46 | 47 | /// 48 | /// 49 | /// 50 | Blocks, 51 | 52 | /// 53 | /// 54 | /// 55 | Blocked, 56 | 57 | /// 58 | /// 59 | /// 60 | Precedes, 61 | 62 | /// 63 | /// 64 | /// 65 | Follows, 66 | 67 | /// 68 | /// 69 | /// 70 | 71 | [XmlEnum(RedmineKeys.COPIED_TO)] 72 | CopiedTo, 73 | 74 | /// 75 | /// 76 | /// 77 | [XmlEnum(RedmineKeys.COPIED_FROM)] 78 | CopiedFrom 79 | } 80 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Types/ProjectEnabledModule.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System; 18 | using System.Diagnostics; 19 | using System.Xml.Serialization; 20 | using Redmine.Net.Api.Common; 21 | using Redmine.Net.Api.Extensions; 22 | 23 | namespace Redmine.Net.Api.Types 24 | { 25 | /// 26 | /// the module name: boards, calendar, documents, files, gant, issue_tracking, news, repository, time_tracking, wiki. 27 | /// 28 | [DebuggerDisplay($"{{{nameof(DebuggerDisplay)},nq}}")] 29 | [XmlRoot(RedmineKeys.ENABLED_MODULE)] 30 | public sealed class ProjectEnabledModule : IdentifiableName, IValue 31 | { 32 | #region Ctors 33 | /// 34 | /// 35 | /// 36 | public ProjectEnabledModule() { } 37 | 38 | /// 39 | /// 40 | /// 41 | /// 42 | public ProjectEnabledModule(string moduleName) 43 | { 44 | if (moduleName.IsNullOrWhiteSpace()) 45 | { 46 | throw new ArgumentException("The module name should be one of: boards, calendar, documents, files, gant, issue_tracking, news, repository, time_tracking, wiki.", nameof(moduleName)); 47 | } 48 | 49 | Name = moduleName; 50 | } 51 | 52 | #endregion 53 | 54 | #region Implementation of IValue 55 | /// 56 | /// 57 | /// 58 | public string Value => Name; 59 | 60 | #endregion 61 | 62 | /// 63 | /// 64 | /// 65 | /// 66 | private string DebuggerDisplay => $"[ProjectEnabledModule: Id={Id.ToInvariantString()}, Name={Name}]"; 67 | 68 | } 69 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Types/ProjectIssueCategory.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System.Diagnostics; 18 | using System.Xml.Serialization; 19 | using Redmine.Net.Api.Extensions; 20 | 21 | namespace Redmine.Net.Api.Types 22 | { 23 | /// 24 | /// 25 | /// 26 | [DebuggerDisplay($"{{{nameof(DebuggerDisplay)},nq}}")] 27 | [XmlRoot(RedmineKeys.ISSUE_CATEGORY)] 28 | public sealed class ProjectIssueCategory : IdentifiableName 29 | { 30 | /// 31 | /// 32 | /// 33 | public ProjectIssueCategory() { } 34 | 35 | internal ProjectIssueCategory(int id, string name) 36 | : base(id, name) 37 | { 38 | } 39 | 40 | /// 41 | /// 42 | /// 43 | /// 44 | private string DebuggerDisplay => $"[ProjectIssueCategory: Id={Id.ToInvariantString()}, Name={Name}]"; 45 | 46 | } 47 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Types/ProjectStatus.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | namespace Redmine.Net.Api.Types 18 | { 19 | /// 20 | /// 21 | /// 22 | public enum ProjectStatus 23 | { 24 | /// 25 | /// value of zero - Not set/unknown 26 | /// 27 | None, 28 | /// 29 | /// 30 | /// 31 | Active = 1, 32 | /// 33 | /// 34 | /// 35 | Closed = 5, 36 | /// 37 | /// 38 | /// 39 | Archived = 9 40 | } 41 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Types/ProjectTimeEntryActivity.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System.Diagnostics; 18 | using System.Xml.Serialization; 19 | using Redmine.Net.Api.Extensions; 20 | 21 | namespace Redmine.Net.Api.Types 22 | { 23 | /// 24 | /// 25 | /// 26 | [DebuggerDisplay($"{{{nameof(DebuggerDisplay)},nq}}")] 27 | [XmlRoot(RedmineKeys.TIME_ENTRY_ACTIVITY)] 28 | public sealed class ProjectTimeEntryActivity : IdentifiableName 29 | { 30 | /// 31 | /// 32 | /// 33 | public ProjectTimeEntryActivity() { } 34 | 35 | internal ProjectTimeEntryActivity(int id, string name) 36 | : base(id, name) 37 | { 38 | } 39 | 40 | /// 41 | /// 42 | /// 43 | /// 44 | private string DebuggerDisplay => $"[ProjectTimeEntryActivity: Id={Id.ToInvariantString()}, Name={Name}]"; 45 | 46 | } 47 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Types/ProjectTracker.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System.Diagnostics; 18 | using System.Xml.Serialization; 19 | using Redmine.Net.Api.Common; 20 | using Redmine.Net.Api.Extensions; 21 | 22 | namespace Redmine.Net.Api.Types 23 | { 24 | /// 25 | /// 26 | /// 27 | [DebuggerDisplay($"{{{nameof(DebuggerDisplay)},nq}}")] 28 | [XmlRoot(RedmineKeys.TRACKER)] 29 | public sealed class ProjectTracker : IdentifiableName, IValue 30 | { 31 | /// 32 | /// 33 | /// 34 | public ProjectTracker() { } 35 | 36 | /// 37 | /// 38 | /// 39 | /// the tracker id: 1 for Bug, etc. 40 | /// 41 | public ProjectTracker(int trackerId, string name) 42 | : base(trackerId, name) 43 | { 44 | } 45 | 46 | /// 47 | /// 48 | /// 49 | /// 50 | internal ProjectTracker(int trackerId) 51 | { 52 | Id = trackerId; 53 | } 54 | 55 | #region Implementation of IValue 56 | 57 | /// 58 | /// 59 | /// 60 | public string Value => Id.ToInvariantString(); 61 | 62 | #endregion 63 | 64 | /// 65 | /// 66 | /// 67 | /// 68 | private string DebuggerDisplay => $"[ProjectTracker: Id={Id.ToInvariantString()}, Name={Name}]"; 69 | 70 | } 71 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Types/UserGroup.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | using System.Diagnostics; 18 | using System.Xml.Serialization; 19 | using Redmine.Net.Api.Extensions; 20 | 21 | namespace Redmine.Net.Api.Types 22 | { 23 | /// 24 | /// 25 | /// 26 | [DebuggerDisplay($"{{{nameof(DebuggerDisplay)},nq}}")] 27 | [XmlRoot(RedmineKeys.GROUP)] 28 | public sealed class UserGroup : IdentifiableName 29 | { 30 | /// 31 | /// 32 | /// 33 | /// 34 | private string DebuggerDisplay => $"[UserGroup: Id={Id.ToInvariantString()}, Name={Name}]"; 35 | 36 | } 37 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Types/UserStatus.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | namespace Redmine.Net.Api.Types 18 | { 19 | /// 20 | /// 21 | /// 22 | public enum UserStatus 23 | { 24 | /// 25 | /// 26 | /// 27 | StatusActive = 1, 28 | /// 29 | /// 30 | /// 31 | StatusRegistered = 2, 32 | /// 33 | /// 34 | /// 35 | StatusLocked = 3 36 | } 37 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Types/VersionSharing.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | namespace Redmine.Net.Api.Types 18 | { 19 | /// 20 | /// 21 | /// 22 | public enum VersionSharing 23 | { 24 | /// 25 | /// 26 | /// 27 | Unknown = 0, 28 | /// 29 | /// 30 | /// 31 | None = 1, 32 | /// 33 | /// 34 | /// 35 | Descendants, 36 | /// 37 | /// 38 | /// 39 | Hierarchy, 40 | /// 41 | /// 42 | /// 43 | Tree, 44 | /// 45 | /// 46 | /// 47 | System 48 | } 49 | } -------------------------------------------------------------------------------- /src/redmine-net-api/Types/VersionStatus.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011 - 2025 Adrian Popescu 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | namespace Redmine.Net.Api.Types 18 | { 19 | /// 20 | /// 21 | /// 22 | public enum VersionStatus 23 | { 24 | /// 25 | /// value of zero - Not set/unknown 26 | /// 27 | None, 28 | /// 29 | /// 30 | /// 31 | Open = 1, 32 | /// 33 | /// 34 | /// 35 | Locked, 36 | /// 37 | /// 38 | /// 39 | Closed 40 | } 41 | } -------------------------------------------------------------------------------- /src/redmine-net-api/_net20/ExtensionAttribute.cs: -------------------------------------------------------------------------------- 1 | #if NET20 2 | /* 3 | Copyright 2011 - 2025 Adrian Popescu 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | namespace System.Runtime.CompilerServices 19 | { 20 | /// 21 | /// 22 | /// 23 | /// 24 | [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method, AllowMultiple=false, Inherited=false)] 25 | public sealed class ExtensionAttribute: Attribute 26 | { 27 | } 28 | } 29 | 30 | #endif 31 | 32 | -------------------------------------------------------------------------------- /src/redmine-net-api/_net20/IProgress{T}.cs: -------------------------------------------------------------------------------- 1 | #if NET20 2 | /* 3 | Copyright 2011 - 2025 Adrian Popescu 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | namespace System; 18 | 19 | /// Defines a provider for progress updates. 20 | /// The type of progress update value. 21 | public interface IProgress 22 | { 23 | /// Reports a progress update. 24 | /// The value of the updated progress. 25 | void Report(T value); 26 | } 27 | 28 | /// 29 | /// 30 | /// 31 | /// 32 | public sealed class Progress : IProgress 33 | { 34 | private readonly Action _handler; 35 | 36 | /// 37 | /// 38 | /// 39 | /// 40 | public Progress(Action handler) 41 | { 42 | _handler = handler; 43 | } 44 | 45 | /// 46 | /// 47 | /// 48 | /// 49 | public void Report(T value) 50 | { 51 | _handler(value); 52 | } 53 | } 54 | #endif -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Collections/RedmineTestContainerCollection.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 4 | 5 | [CollectionDefinition(Constants.RedmineTestContainerCollection)] 6 | public sealed class RedmineTestContainerCollection : ICollectionFixture { } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Helpers/AssertHelpers.cs: -------------------------------------------------------------------------------- 1 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Helpers; 2 | 3 | internal static class AssertHelpers 4 | { 5 | /// 6 | /// Asserts that two values are equal within the specified tolerance. 7 | /// 8 | public static void Equal(float expected, float actual, float tolerance = 1e-4f) 9 | => Assert.InRange(actual, expected - tolerance, expected + tolerance); 10 | 11 | /// 12 | /// Asserts that two values are equal within the specified tolerance. 13 | /// 14 | public static void Equal(decimal expected, decimal actual, decimal tolerance = 0.0001m) 15 | => Assert.InRange(actual, expected - tolerance, expected + tolerance); 16 | 17 | /// 18 | /// Asserts that two values are equal within the supplied tolerance. 19 | /// Kind is ignored – both values are first converted to UTC. 20 | /// 21 | public static void Equal(DateTime expected, DateTime actual, TimeSpan? tolerance = null) 22 | { 23 | tolerance ??= TimeSpan.FromSeconds(1); 24 | 25 | var expectedUtc = expected.ToUniversalTime(); 26 | var actualUtc = actual.ToUniversalTime(); 27 | 28 | Assert.InRange(actualUtc, expectedUtc - tolerance.Value, expectedUtc + tolerance.Value); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Infrastructure/ClientType.cs: -------------------------------------------------------------------------------- 1 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | 3 | public enum ClientType 4 | { 5 | Http, 6 | Web 7 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Infrastructure/ConfigurationHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure.Options; 3 | 4 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure 5 | { 6 | internal static class ConfigurationHelper 7 | { 8 | private static IConfigurationRoot GetIConfigurationRoot(string outputPath) 9 | { 10 | // var environment = Environment.GetEnvironmentVariable("Environment"); 11 | 12 | return new ConfigurationBuilder() 13 | .SetBasePath(outputPath) 14 | .AddJsonFile("appsettings.json", optional: true) 15 | // .AddJsonFile($"appsettings.{environment}.json", optional: true) 16 | .AddJsonFile($"appsettings.local.json", optional: true) 17 | // .AddUserSecrets("f8b9e946-b547-42f1-861c-f719dca00a84") 18 | .Build(); 19 | } 20 | 21 | public static TestContainerOptions GetConfiguration(string outputPath = "") 22 | { 23 | if (string.IsNullOrWhiteSpace(outputPath)) 24 | { 25 | outputPath = Directory.GetCurrentDirectory(); 26 | } 27 | 28 | var testContainerOptions = new TestContainerOptions(); 29 | 30 | var iConfig = GetIConfigurationRoot(outputPath); 31 | 32 | iConfig.GetSection("TestContainer") 33 | .Bind(testContainerOptions); 34 | 35 | return testContainerOptions; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Infrastructure/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 2 | 3 | public static class Constants 4 | { 5 | public const string RedmineTestContainerCollection = nameof(RedmineTestContainerCollection); 6 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Infrastructure/Options/AuthenticationMode.cs: -------------------------------------------------------------------------------- 1 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure.Options; 2 | 3 | public enum AuthenticationMode 4 | { 5 | None, 6 | ApiKey, 7 | Basic 8 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Infrastructure/Options/AuthenticationOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure.Options; 2 | 3 | public sealed class AuthenticationOptions 4 | { 5 | public string ApiKey { get; set; } 6 | 7 | public BasicAuthenticationOptions Basic { get; set; } 8 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Infrastructure/Options/BasicAuthenticationOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure.Options; 2 | 3 | public sealed class BasicAuthenticationOptions 4 | { 5 | public string Username { get; set; } 6 | public string Password { get; set; } 7 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Infrastructure/Options/PostgresOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure.Options; 2 | 3 | public sealed class PostgresOptions 4 | { 5 | public int Port { get; set; } 6 | public string Image { get; set; } = string.Empty; 7 | public string Database { get; set; } = string.Empty; 8 | public string User { get; set; } = string.Empty; 9 | public string Password { get; set; } = string.Empty; 10 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Infrastructure/Options/RedmineOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure.Options 2 | { 3 | public sealed class RedmineOptions 4 | { 5 | public string Url { get; set; } 6 | 7 | public AuthenticationMode AuthenticationMode { get; set; } 8 | 9 | public AuthenticationOptions Authentication { get; set; } 10 | 11 | public int Port { get; set; } 12 | public string Image { get; set; } = string.Empty; 13 | public string SqlFilePath { get; set; } = string.Empty; 14 | } 15 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Infrastructure/Options/TestContainerOptions.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure.Options; 4 | 5 | public sealed class TestContainerOptions 6 | { 7 | public RedmineOptions Redmine { get; set; } 8 | public PostgresOptions Postgres { get; set; } 9 | public TestContainerMode Mode { get; set; } 10 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Infrastructure/RedmineConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | 3 | public record RedmineConfiguration(SerializationType Serialization, ClientType Client); -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Infrastructure/SerializationType.cs: -------------------------------------------------------------------------------- 1 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | 3 | public enum SerializationType 4 | { 5 | Xml, 6 | Json 7 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Infrastructure/TestContainerMode.cs: -------------------------------------------------------------------------------- 1 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | 3 | /// 4 | /// Enum defining how containers should be managed 5 | /// 6 | public enum TestContainerMode 7 | { 8 | /// Use existing running containers at specified URL 9 | UseExisting, 10 | 11 | /// Create new containers with random ports (CI-friendly) 12 | CreateNewWithRandomPorts 13 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Common/EmailNotificationType.cs: -------------------------------------------------------------------------------- 1 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Common; 2 | 3 | public sealed record EmailNotificationType 4 | { 5 | public static readonly EmailNotificationType OnlyMyEvents = new EmailNotificationType(1, "only_my_events"); 6 | public static readonly EmailNotificationType OnlyAssigned = new EmailNotificationType(2, "only_assigned"); 7 | public static readonly EmailNotificationType OnlyOwner = new EmailNotificationType(3, "only_owner"); 8 | public static readonly EmailNotificationType None = new EmailNotificationType(0, ""); 9 | 10 | public int Id { get; } 11 | public string Name { get; } 12 | 13 | private EmailNotificationType(int id, string name) 14 | { 15 | Id = id; 16 | Name = name; 17 | } 18 | 19 | public static EmailNotificationType FromId(int id) 20 | { 21 | return id switch 22 | { 23 | 1 => OnlyMyEvents, 24 | 2 => OnlyAssigned, 25 | 3 => OnlyOwner, 26 | _ => None 27 | }; 28 | } 29 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Common/TestConstants.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Extensions; 2 | using Redmine.Net.Api.Types; 3 | 4 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Common; 5 | 6 | public static class TestConstants 7 | { 8 | public static class Projects 9 | { 10 | public const int DefaultProjectId = 1; 11 | public const string DefaultProjectIdentifier = "1"; 12 | public static readonly IdentifiableName DefaultProject = DefaultProject.ToIdentifiableName(); 13 | } 14 | 15 | public static class Users 16 | { 17 | public const string DefaultPassword = "password123"; 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Entities/Attachment/AttachmentTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Helpers; 3 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 4 | using Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Common; 5 | using Redmine.Net.Api; 6 | using Redmine.Net.Api.Http; 7 | 8 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Entities.Attachment; 9 | 10 | [Collection(Constants.RedmineTestContainerCollection)] 11 | public class AttachmentTests(RedmineTestContainerFixture fixture) 12 | { 13 | [Fact] 14 | public void Attachment_UploadToIssue_Should_Succeed() 15 | { 16 | // Arrange 17 | var upload = FileTestHelper.UploadRandom500KbFile(fixture.RedmineManager); 18 | Assert.NotNull(upload); 19 | Assert.NotEmpty(upload.Token); 20 | 21 | var (issue, _) = IssueTestHelper.CreateRandomIssue(fixture.RedmineManager,uploads: [upload]); 22 | Assert.NotNull(issue); 23 | 24 | // Act 25 | var retrievedIssue = fixture.RedmineManager.Get( 26 | issue.Id.ToString(), 27 | RequestOptions.Include(RedmineKeys.ATTACHMENTS)); 28 | 29 | var attachment = retrievedIssue.Attachments.FirstOrDefault(); 30 | Assert.NotNull(attachment); 31 | 32 | var downloadedAttachment = fixture.RedmineManager.Get(attachment.Id.ToString()); 33 | 34 | // Assert 35 | Assert.NotNull(downloadedAttachment); 36 | Assert.Equal(attachment.Id, downloadedAttachment.Id); 37 | Assert.Equal(attachment.FileName, downloadedAttachment.FileName); 38 | } 39 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Entities/CustomField/CustomFieldTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 3 | 4 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Entities.CustomField; 5 | 6 | [Collection(Constants.RedmineTestContainerCollection)] 7 | public class CustomFieldTests(RedmineTestContainerFixture fixture) 8 | { 9 | [Fact] 10 | public void GetAllCustomFields_Should_Return_Null() 11 | { 12 | // Act 13 | var customFields = fixture.RedmineManager.Get(); 14 | 15 | // Assert 16 | Assert.Null(customFields); 17 | } 18 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Entities/CustomField/CustomFieldTestsAsync.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 3 | 4 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Entities.CustomField; 5 | 6 | [Collection(Constants.RedmineTestContainerCollection)] 7 | public class CustomFieldTestsAsync(RedmineTestContainerFixture fixture) 8 | { 9 | [Fact] 10 | public async Task GetAllCustomFields_Should_Return_Null() 11 | { 12 | // Act 13 | var customFields = await fixture.RedmineManager.GetAsync(); 14 | 15 | // Assert 16 | Assert.Null(customFields); 17 | } 18 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Entities/Enumeration/EnumerationTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 3 | using Redmine.Net.Api.Types; 4 | 5 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Entities.Enumeration; 6 | 7 | [Collection(Constants.RedmineTestContainerCollection)] 8 | public class EnumerationTests(RedmineTestContainerFixture fixture) 9 | { 10 | [Fact] 11 | public void GetDocumentCategories_Should_Succeed() => Assert.NotNull(fixture.RedmineManager.Get()); 12 | 13 | [Fact] 14 | public void GetIssuePriorities_Should_Succeed() => Assert.NotNull(fixture.RedmineManager.Get()); 15 | 16 | [Fact] 17 | public void GetTimeEntryActivities_Should_Succeed() => Assert.NotNull(fixture.RedmineManager.Get()); 18 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Entities/Enumeration/EnumerationTestsAsync.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 3 | using Redmine.Net.Api.Types; 4 | 5 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Entities.Enumeration; 6 | 7 | [Collection(Constants.RedmineTestContainerCollection)] 8 | public class EnumerationTestsAsync(RedmineTestContainerFixture fixture) 9 | { 10 | [Fact] 11 | public async Task GetDocumentCategories_Should_Succeed() 12 | { 13 | // Act 14 | var categories = await fixture.RedmineManager.GetAsync(); 15 | 16 | // Assert 17 | Assert.NotNull(categories); 18 | } 19 | 20 | [Fact] 21 | public async Task GetIssuePriorities_Should_Succeed() 22 | { 23 | // Act 24 | var issuePriorities = await fixture.RedmineManager.GetAsync(); 25 | 26 | // Assert 27 | Assert.NotNull(issuePriorities); 28 | } 29 | 30 | [Fact] 31 | public async Task GetTimeEntryActivities_Should_Succeed() 32 | { 33 | // Act 34 | var activities = await fixture.RedmineManager.GetAsync(); 35 | 36 | // Assert 37 | Assert.NotNull(activities); 38 | } 39 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Entities/Issue/IssueAttachmentUploadTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Helpers; 3 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 4 | using Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Common; 5 | using Redmine.Net.Api; 6 | using Redmine.Net.Api.Http; 7 | 8 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Entities.Issue; 9 | 10 | [Collection(Constants.RedmineTestContainerCollection)] 11 | public class IssueAttachmentTests(RedmineTestContainerFixture fixture) 12 | { 13 | [Fact] 14 | public void UploadAttachmentAndAttachToIssue_Should_Succeed() 15 | { 16 | // Arrange 17 | var (issue, _) = IssueTestHelper.CreateRandomIssue(fixture.RedmineManager); 18 | 19 | var content = "Test attachment content"u8.ToArray(); 20 | var fileName = "test_attachment.txt"; 21 | var upload = fixture.RedmineManager.UploadFile(content, fileName); 22 | Assert.NotNull(upload); 23 | Assert.NotEmpty(upload.Token); 24 | 25 | // Act 26 | var updateIssue = new Redmine.Net.Api.Types.Issue 27 | { 28 | Subject = $"Test issue for attachment {RandomHelper.GenerateText(5)}", 29 | Uploads = [upload] 30 | }; 31 | fixture.RedmineManager.Update(issue.Id.ToString(), updateIssue); 32 | 33 | 34 | var retrievedIssue = fixture.RedmineManager.Get( 35 | issue.Id.ToString(), 36 | RequestOptions.Include(RedmineKeys.ATTACHMENTS)); 37 | 38 | // Assert 39 | Assert.NotNull(retrievedIssue); 40 | Assert.NotEmpty(retrievedIssue.Attachments); 41 | Assert.Contains(retrievedIssue.Attachments, a => a.FileName == fileName); 42 | } 43 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Entities/Issue/IssueAttachmentUploadTestsAsync.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Helpers; 3 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 4 | using Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Common; 5 | using Redmine.Net.Api; 6 | using Redmine.Net.Api.Http; 7 | 8 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Entities.Issue; 9 | 10 | [Collection(Constants.RedmineTestContainerCollection)] 11 | public class IssueAttachmentTestsAsync(RedmineTestContainerFixture fixture) 12 | { 13 | [Fact] 14 | public async Task UploadAttachmentAndAttachToIssue_Should_Succeed() 15 | { 16 | // Arrange 17 | var (issue, _) = await IssueTestHelper.CreateRandomIssueAsync(fixture.RedmineManager); 18 | 19 | var fileContent = "Test attachment content"u8.ToArray(); 20 | var filename = "test_attachment.txt"; 21 | var upload = await fixture.RedmineManager.UploadFileAsync(fileContent, filename); 22 | Assert.NotNull(upload); 23 | Assert.NotEmpty(upload.Token); 24 | 25 | // Prepare issue with attachment 26 | var updateIssue = new Redmine.Net.Api.Types.Issue 27 | { 28 | Subject = $"Test issue for attachment {RandomHelper.GenerateText(5)}", 29 | Uploads = [upload] 30 | }; 31 | 32 | // Act 33 | await fixture.RedmineManager.UpdateAsync(issue.Id.ToString(), updateIssue); 34 | 35 | var retrievedIssue = 36 | await fixture.RedmineManager.GetAsync(issue.Id.ToString(), RequestOptions.Include(RedmineKeys.ATTACHMENTS)); 37 | 38 | // Assert 39 | Assert.NotNull(retrievedIssue); 40 | Assert.NotNull(retrievedIssue.Attachments); 41 | Assert.NotEmpty(retrievedIssue.Attachments); 42 | Assert.Contains(retrievedIssue.Attachments, a => a.FileName == filename); 43 | } 44 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Entities/Issue/IssueWatcherTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 3 | using Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Common; 4 | using Redmine.Net.Api; 5 | using Redmine.Net.Api.Extensions; 6 | using Redmine.Net.Api.Http; 7 | 8 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Entities.Issue; 9 | 10 | [Collection(Constants.RedmineTestContainerCollection)] 11 | public class IssueWatcherTests(RedmineTestContainerFixture fixture) 12 | { 13 | [Fact] 14 | public void AddWatcher_Should_Succeed() 15 | { 16 | var (issue, _) = IssueTestHelper.CreateRandomIssue(fixture.RedmineManager); 17 | const int userId = 1; 18 | 19 | fixture.RedmineManager.AddWatcherToIssue(issue.Id, userId); 20 | 21 | var updated = fixture.RedmineManager.Get( 22 | issue.Id.ToString(), 23 | RequestOptions.Include(RedmineKeys.WATCHERS)); 24 | 25 | Assert.Contains(updated.Watchers, w => w.Id == userId); 26 | } 27 | 28 | [Fact] 29 | public void RemoveWatcher_Should_Succeed() 30 | { 31 | var (issue, _) = IssueTestHelper.CreateRandomIssue(fixture.RedmineManager); 32 | const int userId = 1; 33 | 34 | fixture.RedmineManager.AddWatcherToIssue(issue.Id, userId); 35 | fixture.RedmineManager.RemoveWatcherFromIssue(issue.Id, userId); 36 | 37 | var updated = fixture.RedmineManager.Get( 38 | issue.Id.ToString(), 39 | RequestOptions.Include(RedmineKeys.WATCHERS)); 40 | 41 | Assert.DoesNotContain(updated.Watchers ?? [], w => w.Id == userId); 42 | } 43 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Entities/IssueRelation/IssueRelationTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 3 | using Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Common; 4 | using Redmine.Net.Api; 5 | using Redmine.Net.Api.Http; 6 | 7 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Entities.IssueRelation; 8 | 9 | [Collection(Constants.RedmineTestContainerCollection)] 10 | public class IssueRelationTests(RedmineTestContainerFixture fixture) 11 | { 12 | [Fact] 13 | public void CreateIssueRelation_Should_Succeed() 14 | { 15 | var (relation, i1, i2) = IssueTestHelper.CreateRandomIssueRelation(fixture.RedmineManager); 16 | 17 | Assert.NotNull(relation); 18 | Assert.True(relation.Id > 0); 19 | Assert.Equal(i1.Id, relation.IssueId); 20 | Assert.Equal(i2.Id, relation.IssueToId); 21 | } 22 | 23 | [Fact] 24 | public void DeleteIssueRelation_Should_Succeed() 25 | { 26 | var (rel, _, _) = IssueTestHelper.CreateRandomIssueRelation(fixture.RedmineManager); 27 | fixture.RedmineManager.Delete(rel.Id.ToString()); 28 | 29 | var issue = fixture.RedmineManager.Get( 30 | rel.IssueId.ToString(), 31 | RequestOptions.Include(RedmineKeys.RELATIONS)); 32 | 33 | Assert.Null(issue.Relations?.FirstOrDefault(r => r.Id == rel.Id)); 34 | } 35 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Entities/IssueRelation/IssueRelationTestsAsync.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 3 | using Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Common; 4 | using Redmine.Net.Api; 5 | using Redmine.Net.Api.Http; 6 | using Redmine.Net.Api.Types; 7 | 8 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Entities.IssueRelation; 9 | 10 | [Collection(Constants.RedmineTestContainerCollection)] 11 | public class IssueRelationTestsAsync(RedmineTestContainerFixture fixture) 12 | { 13 | [Fact] 14 | public async Task CreateIssueRelation_Should_Succeed() 15 | { 16 | // Arrange 17 | var (issue1, issue2) = await IssueTestHelper.CreateRandomTwoIssuesAsync(fixture.RedmineManager); 18 | 19 | var relation = new Redmine.Net.Api.Types.IssueRelation 20 | { 21 | IssueId = issue1.Id, 22 | IssueToId = issue2.Id, 23 | Type = IssueRelationType.Relates 24 | }; 25 | 26 | // Act 27 | var createdRelation = await fixture.RedmineManager.CreateAsync(relation, issue1.Id.ToString()); 28 | 29 | // Assert 30 | Assert.NotNull(createdRelation); 31 | Assert.True(createdRelation.Id > 0); 32 | Assert.Equal(relation.IssueId, createdRelation.IssueId); 33 | Assert.Equal(relation.IssueToId, createdRelation.IssueToId); 34 | Assert.Equal(relation.Type, createdRelation.Type); 35 | } 36 | 37 | [Fact] 38 | public async Task DeleteIssueRelation_Should_Succeed() 39 | { 40 | // Arrange 41 | var (relation, _, _) = await IssueTestHelper.CreateRandomIssueRelationAsync(fixture.RedmineManager); 42 | Assert.NotNull(relation); 43 | 44 | // Act & Assert 45 | await fixture.RedmineManager.DeleteAsync(relation.Id.ToString()); 46 | 47 | var issue = await fixture.RedmineManager.GetAsync(relation.IssueId.ToString(), RequestOptions.Include(RedmineKeys.RELATIONS)); 48 | 49 | Assert.Null(issue.Relations?.FirstOrDefault(r => r.Id == relation.Id)); 50 | } 51 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Entities/IssueStatus/IssueStatusTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 3 | 4 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Entities.IssueStatus; 5 | 6 | [Collection(Constants.RedmineTestContainerCollection)] 7 | public class IssueStatusTests(RedmineTestContainerFixture fixture) 8 | { 9 | [Fact] 10 | public void GetAllIssueStatuses_Should_Succeed() 11 | { 12 | var statuses = fixture.RedmineManager.Get(); 13 | Assert.NotNull(statuses); 14 | Assert.NotEmpty(statuses); 15 | } 16 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Entities/IssueStatus/IssueStatusTestsAsync.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 3 | 4 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Entities.IssueStatus; 5 | 6 | [Collection(Constants.RedmineTestContainerCollection)] 7 | public class IssueStatusAsyncTests(RedmineTestContainerFixture fixture) 8 | { 9 | [Fact] 10 | public async Task GetAllIssueStatuses_Should_Succeed() 11 | { 12 | // Act 13 | var statuses = await fixture.RedmineManager.GetAsync(); 14 | 15 | // Assert 16 | Assert.NotNull(statuses); 17 | Assert.NotEmpty(statuses); 18 | } 19 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Entities/Journal/JournalTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 3 | using Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Common; 4 | using Redmine.Net.Api; 5 | using Redmine.Net.Api.Extensions; 6 | using Redmine.Net.Api.Http; 7 | 8 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Entities.Journal; 9 | 10 | [Collection(Constants.RedmineTestContainerCollection)] 11 | public class JournalTests(RedmineTestContainerFixture fixture) 12 | { 13 | private Redmine.Net.Api.Types.Issue CreateRandomIssue() 14 | { 15 | var issue = TestEntityFactory.CreateRandomIssuePayload(); 16 | return fixture.RedmineManager.Create(issue); 17 | } 18 | 19 | [Fact] 20 | public void Get_Issue_With_Journals_Should_Succeed() 21 | { 22 | // Arrange 23 | var testIssue = CreateRandomIssue(); 24 | Assert.NotNull(testIssue); 25 | 26 | testIssue.Notes = "This is a test note to create a journal entry."; 27 | fixture.RedmineManager.Update(testIssue.Id.ToInvariantString(), testIssue); 28 | 29 | // Act 30 | var issueWithJournals = fixture.RedmineManager.Get( 31 | testIssue.Id.ToInvariantString(), 32 | RequestOptions.Include(RedmineKeys.JOURNALS)); 33 | 34 | // Assert 35 | Assert.NotNull(issueWithJournals); 36 | Assert.NotNull(issueWithJournals.Journals); 37 | Assert.True(issueWithJournals.Journals.Count > 0); 38 | Assert.Contains(issueWithJournals.Journals, j => j.Notes == testIssue.Notes); 39 | } 40 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Entities/Journal/JournalTestsAsync.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 3 | using Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Common; 4 | using Redmine.Net.Api; 5 | using Redmine.Net.Api.Extensions; 6 | using Redmine.Net.Api.Http; 7 | 8 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Entities.Journal; 9 | 10 | [Collection(Constants.RedmineTestContainerCollection)] 11 | public class JournalTestsAsync(RedmineTestContainerFixture fixture) 12 | { 13 | private async Task CreateRandomIssueAsync() 14 | { 15 | var issuePayload = TestEntityFactory.CreateRandomIssuePayload(); 16 | return await fixture.RedmineManager.CreateAsync(issuePayload); 17 | } 18 | 19 | [Fact] 20 | public async Task Get_Issue_With_Journals_Should_Succeed() 21 | { 22 | //Arrange 23 | var testIssue = await CreateRandomIssueAsync(); 24 | Assert.NotNull(testIssue); 25 | 26 | var issueIdToTest = testIssue.Id.ToInvariantString(); 27 | 28 | testIssue.Notes = "This is a test note to create a journal entry."; 29 | await fixture.RedmineManager.UpdateAsync(issueIdToTest, testIssue); 30 | 31 | //Act 32 | var issueWithJournals = await fixture.RedmineManager.GetAsync( 33 | issueIdToTest, 34 | RequestOptions.Include(RedmineKeys.JOURNALS)); 35 | 36 | //Assert 37 | Assert.NotNull(issueWithJournals); 38 | Assert.NotNull(issueWithJournals.Journals); 39 | Assert.True(issueWithJournals.Journals.Count > 0, "Issue should have journal entries."); 40 | Assert.Contains(issueWithJournals.Journals, j => j.Notes == testIssue.Notes); 41 | } 42 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Entities/News/NewsTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 3 | using Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Common; 4 | using Redmine.Net.Api.Extensions; 5 | 6 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Entities.News; 7 | 8 | [Collection(Constants.RedmineTestContainerCollection)] 9 | public class NewsTests(RedmineTestContainerFixture fixture) 10 | { 11 | [Fact] 12 | public void GetAllNews_Should_Succeed() 13 | { 14 | _ = fixture.RedmineManager.AddProjectNews(TestConstants.Projects.DefaultProjectIdentifier, TestEntityFactory.CreateRandomNewsPayload()); 15 | 16 | _ = fixture.RedmineManager.AddProjectNews(TestConstants.Projects.DefaultProjectIdentifier, TestEntityFactory.CreateRandomNewsPayload()); 17 | 18 | var news = fixture.RedmineManager.Get(); 19 | 20 | Assert.NotNull(news); 21 | } 22 | 23 | [Fact] 24 | public void GetProjectNews_Should_Succeed() 25 | { 26 | _ = fixture.RedmineManager.AddProjectNews(TestConstants.Projects.DefaultProjectIdentifier, TestEntityFactory.CreateRandomNewsPayload()); 27 | 28 | var news = fixture.RedmineManager.GetProjectNews(TestConstants.Projects.DefaultProjectIdentifier); 29 | 30 | Assert.NotNull(news); 31 | } 32 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Entities/News/NewsTestsAsync.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 3 | using Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Common; 4 | using Redmine.Net.Api.Extensions; 5 | 6 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Entities.News; 7 | 8 | [Collection(Constants.RedmineTestContainerCollection)] 9 | public class NewsTestsAsync(RedmineTestContainerFixture fixture) 10 | { 11 | [Fact] 12 | public async Task GetAllNews_Should_Succeed() 13 | { 14 | // Arrange 15 | _ = await fixture.RedmineManager.AddProjectNewsAsync(TestConstants.Projects.DefaultProjectIdentifier, TestEntityFactory.CreateRandomNewsPayload()); 16 | 17 | _ = await fixture.RedmineManager.AddProjectNewsAsync(TestConstants.Projects.DefaultProjectIdentifier, TestEntityFactory.CreateRandomNewsPayload()); 18 | 19 | // Act 20 | var news = await fixture.RedmineManager.GetAsync(); 21 | 22 | // Assert 23 | Assert.NotNull(news); 24 | } 25 | 26 | [Fact] 27 | public async Task GetProjectNews_Should_Succeed() 28 | { 29 | // Arrange 30 | var newsCreated = await fixture.RedmineManager.AddProjectNewsAsync(TestConstants.Projects.DefaultProjectIdentifier, TestEntityFactory.CreateRandomNewsPayload()); 31 | 32 | // Act 33 | var news = await fixture.RedmineManager.GetProjectNewsAsync(TestConstants.Projects.DefaultProjectIdentifier); 34 | 35 | // Assert 36 | Assert.NotNull(news); 37 | } 38 | 39 | [Fact] 40 | public async Task News_AddWithUploads_Should_Succeed() 41 | { 42 | // Arrange 43 | var newsPayload = TestEntityFactory.CreateRandomNewsPayload(); 44 | var newsCreated = await fixture.RedmineManager.AddProjectNewsAsync(TestConstants.Projects.DefaultProjectIdentifier, newsPayload); 45 | 46 | // Act 47 | var news = await fixture.RedmineManager.GetProjectNewsAsync(TestConstants.Projects.DefaultProjectIdentifier); 48 | 49 | // Assert 50 | Assert.NotNull(news); 51 | } 52 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Entities/Query/QueryTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 3 | 4 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Entities.Query; 5 | 6 | [Collection(Constants.RedmineTestContainerCollection)] 7 | public class QueryTests(RedmineTestContainerFixture fixture) 8 | { 9 | [Fact] 10 | public void GetAllQueries_Should_Succeed() 11 | { 12 | // Act 13 | var queries = fixture.RedmineManager.Get(); 14 | 15 | // Assert 16 | Assert.NotNull(queries); 17 | } 18 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Entities/Query/QueryTestsAsync.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 3 | 4 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Entities.Query; 5 | 6 | [Collection(Constants.RedmineTestContainerCollection)] 7 | public class QueryTestsAsync(RedmineTestContainerFixture fixture) 8 | { 9 | [Fact] 10 | public async Task GetAllQueries_Should_Succeed() 11 | { 12 | // Act 13 | var queries = await fixture.RedmineManager.GetAsync(); 14 | 15 | // Assert 16 | Assert.NotNull(queries); 17 | } 18 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Entities/Role/RoleTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 3 | 4 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Entities.Role; 5 | 6 | [Collection(Constants.RedmineTestContainerCollection)] 7 | public class RoleTests(RedmineTestContainerFixture fixture) 8 | { 9 | [Fact] 10 | public void Get_All_Roles_Should_Succeed() 11 | { 12 | //Act 13 | var roles = fixture.RedmineManager.Get(); 14 | 15 | //Assert 16 | Assert.NotNull(roles); 17 | Assert.NotEmpty(roles); 18 | } 19 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Entities/Role/RoleTestsAsync.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 3 | 4 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Entities.Role; 5 | 6 | [Collection(Constants.RedmineTestContainerCollection)] 7 | public class RoleTestsAsync(RedmineTestContainerFixture fixture) 8 | { 9 | [Fact] 10 | public async Task Get_All_Roles_Should_Succeed() 11 | { 12 | //Act 13 | var roles = await fixture.RedmineManager.GetAsync(); 14 | 15 | //Assert 16 | Assert.NotNull(roles); 17 | Assert.NotEmpty(roles); 18 | } 19 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Entities/Search/SearchTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 3 | using Redmine.Net.Api; 4 | using Redmine.Net.Api.Extensions; 5 | 6 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Entities.Search; 7 | 8 | [Collection(Constants.RedmineTestContainerCollection)] 9 | public class SearchTests(RedmineTestContainerFixture fixture) 10 | { 11 | [Fact] 12 | public void Search_Should_Succeed() 13 | { 14 | // Arrange 15 | var searchBuilder = new SearchFilterBuilder 16 | { 17 | IncludeIssues = true, 18 | IncludeWikiPages = true 19 | }; 20 | 21 | // Act 22 | var results = fixture.RedmineManager.Search("query_string",100, searchFilter:searchBuilder); 23 | 24 | // Assert 25 | Assert.NotNull(results); 26 | Assert.Null(results.Items); 27 | } 28 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Entities/Search/SearchTestsAsync.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 3 | using Redmine.Net.Api; 4 | using Redmine.Net.Api.Extensions; 5 | 6 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Entities.Search; 7 | 8 | [Collection(Constants.RedmineTestContainerCollection)] 9 | public class SearchTestsAsync(RedmineTestContainerFixture fixture) 10 | { 11 | [Fact] 12 | public async Task Search_Should_Succeed() 13 | { 14 | // Arrange 15 | var searchBuilder = new SearchFilterBuilder 16 | { 17 | IncludeIssues = true, 18 | IncludeWikiPages = true 19 | }; 20 | 21 | // Act 22 | var results = await fixture.RedmineManager.SearchAsync("query_string",100, searchFilter:searchBuilder); 23 | 24 | // Assert 25 | Assert.NotNull(results); 26 | Assert.Null(results.Items); 27 | } 28 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Entities/TimeEntry/TimeEntryActivityTestsAsync.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 3 | using Redmine.Net.Api.Types; 4 | 5 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Entities.TimeEntry; 6 | 7 | [Collection(Constants.RedmineTestContainerCollection)] 8 | public class TimeEntryActivityTestsAsync(RedmineTestContainerFixture fixture) 9 | { 10 | [Fact] 11 | public async Task GetAllTimeEntryActivities_Should_Succeed() 12 | { 13 | // Act 14 | var activities = await fixture.RedmineManager.GetAsync(); 15 | 16 | // Assert 17 | Assert.NotNull(activities); 18 | Assert.NotEmpty(activities); 19 | } 20 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Entities/Tracker/TrackerTestsAsync.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 3 | 4 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Entities.Tracker; 5 | 6 | [Collection(Constants.RedmineTestContainerCollection)] 7 | public class TrackerTestsAsync(RedmineTestContainerFixture fixture) 8 | { 9 | [Fact] 10 | public async Task Get_All_Trackers_Should_Succeed() 11 | { 12 | //Act 13 | var trackers = await fixture.RedmineManager.GetAsync(); 14 | 15 | //Assert 16 | Assert.NotNull(trackers); 17 | Assert.NotEmpty(trackers); 18 | } 19 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Progress/ProgressTests.Async.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Exceptions; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Progress; 4 | 5 | public partial class ProgressTests 6 | { 7 | [Fact] 8 | public async Task DownloadFileAsync_WithValidUrl_ShouldReportProgress() 9 | { 10 | // Arrange 11 | var progressTracker = new ProgressTracker(); 12 | 13 | // Act 14 | var result = await fixture.RedmineManager.DownloadFileAsync( 15 | "",null, 16 | progressTracker, 17 | CancellationToken.None); 18 | 19 | // Assert 20 | Assert.NotNull(result); 21 | Assert.True(result.Length > 0, "Downloaded content should not be empty"); 22 | 23 | AssertProgressWasReported(progressTracker); 24 | } 25 | 26 | [Fact] 27 | public async Task DownloadFileAsync_WithCancellation_ShouldStopDownload() 28 | { 29 | // Arrange 30 | var progressTracker = new ProgressTracker(); 31 | var cts = new CancellationTokenSource(); 32 | 33 | try 34 | { 35 | progressTracker.OnProgressReported += (sender, args) => 36 | { 37 | if (args.Value > 0 && !cts.IsCancellationRequested) 38 | { 39 | cts.Cancel(); 40 | } 41 | }; 42 | 43 | // Act & Assert 44 | await Assert.ThrowsAnyAsync(async () => 45 | { 46 | await fixture.RedmineManager.DownloadFileAsync( 47 | "", 48 | null, 49 | progressTracker, 50 | cts.Token); 51 | }); 52 | 53 | Assert.True(progressTracker.ReportCount > 0, "Progress should have been reported at least once"); 54 | } 55 | finally 56 | { 57 | cts.Dispose(); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/Tests/Progress/ProgressTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Integration.Tests.Fixtures; 2 | using Padi.DotNet.RedmineAPI.Integration.Tests.Infrastructure; 3 | 4 | namespace Padi.DotNet.RedmineAPI.Integration.Tests.Tests.Progress; 5 | 6 | [Collection(Constants.RedmineTestContainerCollection)] 7 | public partial class ProgressTests(RedmineTestContainerFixture fixture) 8 | { 9 | [Fact] 10 | public void DownloadFile_WithValidUrl_ShouldReportProgress() 11 | { 12 | // Arrange 13 | var progressTracker = new ProgressTracker(); 14 | 15 | // Act 16 | var result = fixture.RedmineManager.DownloadFile("", progressTracker); 17 | 18 | // Assert 19 | Assert.NotNull(result); 20 | Assert.True(result.Length > 0, "Downloaded content should not be empty"); 21 | 22 | AssertProgressWasReported(progressTracker); 23 | } 24 | 25 | private static void AssertProgressWasReported(ProgressTracker tracker) 26 | { 27 | Assert.True(tracker.ReportCount > 0, "Progress should have been reported at least once"); 28 | 29 | Assert.Contains(100, tracker.ProgressValues); 30 | 31 | for (var i = 0; i < tracker.ProgressValues.Count - 1; i++) 32 | { 33 | Assert.True(tracker.ProgressValues[i] <= tracker.ProgressValues[i + 1], 34 | $"Progress should not decrease: {tracker.ProgressValues[i]} -> {tracker.ProgressValues[i + 1]}"); 35 | } 36 | } 37 | 38 | private sealed class ProgressTracker : IProgress 39 | { 40 | public List ProgressValues { get; } = []; 41 | public int ReportCount => ProgressValues.Count; 42 | 43 | public event EventHandler OnProgressReported; 44 | 45 | public void Report(int value) 46 | { 47 | ProgressValues.Add(value); 48 | OnProgressReported?.Invoke(this, new ProgressReportedEventArgs(value)); 49 | } 50 | 51 | public sealed class ProgressReportedEventArgs(int value) : EventArgs 52 | { 53 | public int Value { get; } = value; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "TestContainer": { 3 | "Mode": "CreateNewWithRandomPorts", 4 | "Redmine":{ 5 | "Url": "$Url", 6 | "Port": 3000, 7 | "Image": "redmine:6.0.5-alpine", 8 | "SqlFilePath": "TestData/init-redmine.sql", 9 | "AuthenticationMode": "ApiKey", 10 | "Authentication": { 11 | "Basic":{ 12 | "Username": "$Username", 13 | "Password": "$Password" 14 | }, 15 | "ApiKey": "$ApiKey" 16 | } 17 | }, 18 | "Postgres": { 19 | "Port": 5432, 20 | "Image": "postgres:17.4-alpine", 21 | "Database": "postgres", 22 | "User": "postgres", 23 | "Password": "postgres" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/redmine-net-api.Integration.Tests/appsettings.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "TestContainer": { 3 | "Mode": "UseExisting", 4 | "Redmine":{ 5 | "Url": "http://localhost:8089/", 6 | "AuthenticationMode": "ApiKey", 7 | "Authentication": { 8 | "ApiKey": "61d6fa45ca2c570372b08b8c54b921e5fc39335a" 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/.editorconfig: -------------------------------------------------------------------------------- 1 | # To learn more about .editorconfig see https://aka.ms/editorconfigdocs 2 | root = true 3 | 4 | # All files 5 | [*] 6 | indent_style = space 7 | 8 | # Xml files 9 | [*.xml] 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Bugs/RedmineApi-371.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Specialized; 2 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; 3 | using Redmine.Net.Api; 4 | using Redmine.Net.Api.Extensions; 5 | using Redmine.Net.Api.Http; 6 | using Redmine.Net.Api.Types; 7 | using Xunit; 8 | 9 | namespace Padi.DotNet.RedmineAPI.Tests.Bugs; 10 | 11 | public sealed class RedmineApi371 : IClassFixture 12 | { 13 | private readonly RedmineApiUrlsFixture _fixture; 14 | 15 | public RedmineApi371(RedmineApiUrlsFixture fixture) 16 | { 17 | _fixture = fixture; 18 | } 19 | 20 | [Fact] 21 | public void Should_Return_IssueCategories_For_Project_Url() 22 | { 23 | var projectIdAsString = 1.ToInvariantString(); 24 | var result = _fixture.Sut.GetListFragment( 25 | new RequestOptions 26 | { 27 | QueryString = new NameValueCollection{ { RedmineKeys.PROJECT_ID, projectIdAsString } } 28 | }); 29 | 30 | Assert.Equal($"projects/{projectIdAsString}/issue_categories.{_fixture.Format}", result); 31 | } 32 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Clone/JournalCloneTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | using Xunit; 3 | 4 | namespace Padi.DotNet.RedmineAPI.Tests.Clone; 5 | 6 | public sealed class JournalCloneTests 7 | { 8 | [Fact] 9 | public void Clone_WithPopulatedProperties_ReturnsDeepCopy() 10 | { 11 | // Arrange 12 | var journal = new Journal 13 | { 14 | Id = 1, 15 | User = new IdentifiableName(1, "John Doe"), 16 | Notes = "Test notes", 17 | CreatedOn = DateTime.Now, 18 | PrivateNotes = true, 19 | Details = (List) 20 | [ 21 | new Detail 22 | { 23 | Property = "status_id", 24 | Name = "Status", 25 | OldValue = "1", 26 | NewValue = "2" 27 | } 28 | ] 29 | }; 30 | 31 | // Act 32 | var clone = journal.Clone(false); 33 | 34 | // Assert 35 | Assert.NotNull(clone); 36 | Assert.NotSame(journal, clone); 37 | Assert.Equal(journal.Id, clone.Id); 38 | Assert.Equal(journal.Notes, clone.Notes); 39 | Assert.Equal(journal.CreatedOn, clone.CreatedOn); 40 | Assert.Equal(journal.PrivateNotes, clone.PrivateNotes); 41 | 42 | Assert.NotSame(journal.User, clone.User); 43 | Assert.Equal(journal.User.Id, clone.User.Id); 44 | Assert.Equal(journal.User.Name, clone.User.Name); 45 | 46 | Assert.NotNull(clone.Details); 47 | Assert.NotSame(journal.Details, clone.Details); 48 | Assert.Equal(journal.Details.Count, clone.Details.Count); 49 | 50 | var originalDetail = journal.Details[0]; 51 | var clonedDetail = clone.Details[0]; 52 | Assert.NotSame(originalDetail, clonedDetail); 53 | Assert.Equal(originalDetail.Property, clonedDetail.Property); 54 | Assert.Equal(originalDetail.Name, clonedDetail.Name); 55 | Assert.Equal(originalDetail.OldValue, clonedDetail.OldValue); 56 | Assert.Equal(originalDetail.NewValue, clonedDetail.NewValue); 57 | } 58 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/AttachmentEqualityTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | using Xunit; 3 | 4 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 5 | 6 | public sealed class AttachmentEqualityTests 7 | { 8 | [Fact] 9 | public void Equals_SameReference_ReturnsTrue() 10 | { 11 | var attachment = CreateSampleAttachment(); 12 | Assert.True(attachment.Equals(attachment)); 13 | } 14 | 15 | [Fact] 16 | public void Equals_Null_ReturnsFalse() 17 | { 18 | var attachment = CreateSampleAttachment(); 19 | Assert.False(attachment.Equals(null)); 20 | } 21 | 22 | [Theory] 23 | [MemberData(nameof(GetDifferentAttachments))] 24 | public void Equals_DifferentProperties_ReturnsFalse(Attachment attachment1, Attachment attachment2, string propertyName) 25 | { 26 | Assert.False(attachment1.Equals(attachment2), $"Attachments should not be equal when {propertyName} is different"); 27 | } 28 | 29 | public static IEnumerable GetDifferentAttachments() 30 | { 31 | var baseAttachment = CreateSampleAttachment(); 32 | 33 | // Different FileName 34 | var differentFileName = CreateSampleAttachment(); 35 | differentFileName.FileName = "different.txt"; 36 | yield return [baseAttachment, differentFileName, "FileName"]; 37 | 38 | // Different FileSize 39 | var differentFileSize = CreateSampleAttachment(); 40 | differentFileSize.FileSize = 2048; 41 | yield return [baseAttachment, differentFileSize, "FileSize"]; 42 | 43 | // Different Author 44 | var differentAuthor = CreateSampleAttachment(); 45 | differentAuthor.Author = new IdentifiableName { Id = 999, Name = "Different Author" }; 46 | yield return [baseAttachment, differentAuthor, "Author"]; 47 | } 48 | 49 | private static Attachment CreateSampleAttachment() 50 | { 51 | return new Attachment 52 | { 53 | Id = 1, 54 | FileName = "test.txt", 55 | FileSize = 1024, 56 | ContentType = "text/plain", 57 | Description = "Test file", 58 | ContentUrl = "https://example.com/test.txt", 59 | ThumbnailUrl = "https://example.com/thumb.txt", 60 | Author = new IdentifiableName { Id = 1, Name = "John Doe" }, 61 | CreatedOn = DateTime.Now 62 | }; 63 | } 64 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/BaseEqualityTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public abstract class BaseEqualityTests where T : class, IEquatable 6 | { 7 | protected abstract T CreateSampleInstance(); 8 | protected abstract T CreateDifferentInstance(); 9 | 10 | [Fact] 11 | public void Equals_SameReference_ReturnsTrue() 12 | { 13 | var instance = CreateSampleInstance(); 14 | Assert.True(instance.Equals(instance)); 15 | } 16 | 17 | [Fact] 18 | public void Equals_Null_ReturnsFalse() 19 | { 20 | var instance = CreateSampleInstance(); 21 | Assert.False(instance.Equals(null)); 22 | } 23 | 24 | [Fact] 25 | public void Equals_DifferentType_ReturnsFalse() 26 | { 27 | var instance = CreateSampleInstance(); 28 | var differentObject = new object(); 29 | Assert.False(instance.Equals(differentObject)); 30 | } 31 | 32 | [Fact] 33 | public void Equals_IdenticalProperties_ReturnsTrue() 34 | { 35 | var instance1 = CreateSampleInstance(); 36 | var instance2 = CreateSampleInstance(); 37 | Assert.True(instance1.Equals(instance2)); 38 | Assert.True(instance2.Equals(instance1)); 39 | } 40 | 41 | [Fact] 42 | public void Equals_DifferentProperties_ReturnsFalse() 43 | { 44 | var instance1 = CreateSampleInstance(); 45 | var instance2 = CreateDifferentInstance(); 46 | Assert.False(instance1.Equals(instance2)); 47 | Assert.False(instance2.Equals(instance1)); 48 | } 49 | 50 | [Fact] 51 | public void GetHashCode_SameProperties_ReturnsSameValue() 52 | { 53 | var instance1 = CreateSampleInstance(); 54 | var instance2 = CreateSampleInstance(); 55 | Assert.Equal(instance1.GetHashCode(), instance2.GetHashCode()); 56 | } 57 | 58 | [Fact] 59 | public void GetHashCode_DifferentProperties_ReturnsDifferentValues() 60 | { 61 | var instance1 = CreateSampleInstance(); 62 | var instance2 = CreateDifferentInstance(); 63 | Assert.NotEqual(instance1.GetHashCode(), instance2.GetHashCode()); 64 | } 65 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/CustomFieldPossibleValueTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class CustomFieldPossibleValueTests : BaseEqualityTests 6 | { 7 | protected override CustomFieldPossibleValue CreateSampleInstance() 8 | { 9 | return new CustomFieldPossibleValue 10 | { 11 | Value = "test-value", 12 | Label = "Test Label" 13 | }; 14 | } 15 | 16 | protected override CustomFieldPossibleValue CreateDifferentInstance() 17 | { 18 | return new CustomFieldPossibleValue 19 | { 20 | Value = "different-value", 21 | Label = "Different Label" 22 | }; 23 | } 24 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/CustomFieldRoleTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class CustomFieldRoleTests : BaseEqualityTests 6 | { 7 | protected override IdentifiableName CreateSampleInstance() 8 | { 9 | return new CustomFieldRole 10 | { 11 | Id = 1, 12 | Name = "Test Role" 13 | }; 14 | } 15 | 16 | protected override IdentifiableName CreateDifferentInstance() 17 | { 18 | return new CustomFieldRole 19 | { 20 | Id = 2, 21 | Name = "Different Role" 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/CustomFieldTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class CustomFieldTests : BaseEqualityTests 6 | { 7 | protected override CustomField CreateSampleInstance() 8 | { 9 | return new CustomField 10 | { 11 | Id = 1, 12 | Name = "Test Field", 13 | CustomizedType = "issue", 14 | FieldFormat = "string", 15 | Regexp = "", 16 | MinLength = 0, 17 | MaxLength = 100, 18 | IsRequired = false, 19 | IsFilter = true, 20 | Searchable = true, 21 | Multiple = false, 22 | DefaultValue = "default", 23 | Visible = true, 24 | PossibleValues = [new CustomFieldPossibleValue { Value = "value1", Label = "Label 1" }] 25 | }; 26 | } 27 | 28 | protected override CustomField CreateDifferentInstance() 29 | { 30 | var field = CreateSampleInstance(); 31 | field.Name = "Different Field"; 32 | return field; 33 | } 34 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/DetailTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class DetailTests : BaseEqualityTests 6 | { 7 | protected override Detail CreateSampleInstance() 8 | { 9 | return new Detail 10 | { 11 | Property = "status", 12 | Name = "Status", 13 | OldValue = "1", 14 | NewValue = "2" 15 | }; 16 | } 17 | 18 | protected override Detail CreateDifferentInstance() 19 | { 20 | return new Detail 21 | { 22 | Property = "priority", 23 | Name = "Priority", 24 | OldValue = "3", 25 | NewValue = "4" 26 | }; 27 | } 28 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/ErrorTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class ErrorTests : BaseEqualityTests 6 | { 7 | protected override Error CreateSampleInstance() 8 | { 9 | return new Error( "Test error" ); 10 | } 11 | 12 | protected override Error CreateDifferentInstance() 13 | { 14 | return new Error("Different error"); 15 | } 16 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/GroupTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class GroupTests : BaseEqualityTests 6 | { 7 | protected override Group CreateSampleInstance() 8 | { 9 | return new Group 10 | { 11 | Id = 1, 12 | Name = "Test Group", 13 | Users = [new GroupUser { Id = 1, Name = "User 1" }], 14 | CustomFields = [new IssueCustomField { Id = 1, Name = "Field 1" }], 15 | Memberships = [new Membership { Id = 1, Project = new IdentifiableName { Id = 1, Name = "Project 1" } }] 16 | }; 17 | } 18 | 19 | protected override Group CreateDifferentInstance() 20 | { 21 | var group = CreateSampleInstance(); 22 | group.Name = "Different Group"; 23 | group.Users = [new GroupUser { Id = 2, Name = "User 2" }]; 24 | return group; 25 | } 26 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/GroupUserTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class GroupUserTests : BaseEqualityTests 6 | { 7 | protected override IdentifiableName CreateSampleInstance() 8 | { 9 | return new GroupUser 10 | { 11 | Id = 1, 12 | Name = "Test User" 13 | }; 14 | } 15 | 16 | protected override IdentifiableName CreateDifferentInstance() 17 | { 18 | return new GroupUser 19 | { 20 | Id = 2, 21 | Name = "Different User" 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/IssueCategoryTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class IssueCategoryTests : BaseEqualityTests 6 | { 7 | protected override IssueCategory CreateSampleInstance() 8 | { 9 | return new IssueCategory 10 | { 11 | Id = 1, 12 | Name = "Test Category", 13 | Project = new IdentifiableName { Id = 1, Name = "Project 1" }, 14 | AssignTo = new IdentifiableName { Id = 1, Name = "User 1" } 15 | }; 16 | } 17 | 18 | protected override IssueCategory CreateDifferentInstance() 19 | { 20 | return new IssueCategory 21 | { 22 | Id = 2, 23 | Name = "Different Category", 24 | Project = new IdentifiableName { Id = 2, Name = "Project 2" }, 25 | AssignTo = new IdentifiableName { Id = 2, Name = "User 2" } 26 | }; 27 | } 28 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/IssueStatusTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class IssueStatusTests : BaseEqualityTests 6 | { 7 | protected override IssueStatus CreateSampleInstance() 8 | { 9 | return new IssueStatus 10 | { 11 | Id = 1, 12 | Name = "New", 13 | IsDefault = true, 14 | IsClosed = false 15 | }; 16 | } 17 | 18 | protected override IssueStatus CreateDifferentInstance() 19 | { 20 | return new IssueStatus 21 | { 22 | Id = 2, 23 | Name = "Closed", 24 | IsDefault = false, 25 | IsClosed = true 26 | }; 27 | } 28 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/JournalEqualityTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | using Xunit; 3 | 4 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 5 | 6 | public sealed class JournalEqualityTests 7 | { 8 | [Fact] 9 | public void Equals_SameReference_ReturnsTrue() 10 | { 11 | var journal = CreateSampleJournal(); 12 | Assert.True(journal.Equals(journal)); 13 | } 14 | 15 | [Fact] 16 | public void Equals_Null_ReturnsFalse() 17 | { 18 | var journal = CreateSampleJournal(); 19 | Assert.False(journal.Equals(null)); 20 | } 21 | 22 | [Theory] 23 | [MemberData(nameof(GetDifferentJournals))] 24 | public void Equals_DifferentProperties_ReturnsFalse(Journal journal1, Journal journal2, string propertyName) 25 | { 26 | Assert.False(journal1.Equals(journal2), $"Journals should not be equal when {propertyName} is different"); 27 | } 28 | 29 | public static IEnumerable GetDifferentJournals() 30 | { 31 | var baseJournal = CreateSampleJournal(); 32 | 33 | // Different Notes 34 | var differentNotes = CreateSampleJournal(); 35 | differentNotes.Notes = "Different notes"; 36 | yield return [baseJournal, differentNotes, "Notes"]; 37 | 38 | // Different User 39 | var differentUser = CreateSampleJournal(); 40 | differentUser.User = new IdentifiableName { Id = 999, Name = "Different User" }; 41 | yield return [baseJournal, differentUser, "User"]; 42 | 43 | // Different Details 44 | var differentDetails = CreateSampleJournal(); 45 | differentDetails.Details[0].NewValue = "Different value"; 46 | yield return [baseJournal, differentDetails, "Details"]; 47 | } 48 | 49 | private static Journal CreateSampleJournal() 50 | { 51 | return new Journal 52 | { 53 | Id = 1, 54 | User = new IdentifiableName { Id = 1, Name = "John Doe" }, 55 | Notes = "Test notes", 56 | CreatedOn = new DateTime(2025,02,14,14,04,00), 57 | PrivateNotes = true, 58 | Details = 59 | [ 60 | new Detail 61 | { 62 | Property = "status_id", 63 | Name = "Status", 64 | OldValue = "1", 65 | NewValue = "2" 66 | } 67 | ] 68 | }; 69 | } 70 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/MembershipTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class MembershipTests : BaseEqualityTests 6 | { 7 | protected override Membership CreateSampleInstance() 8 | { 9 | return new Membership 10 | { 11 | Id = 1, 12 | Project = new IdentifiableName { Id = 1, Name = "Project 1" }, 13 | User = new IdentifiableName { Id = 1, Name = "User 1" }, 14 | Roles = [new MembershipRole { Id = 1, Name = "Developer", Inherited = false }] 15 | }; 16 | } 17 | 18 | protected override Membership CreateDifferentInstance() 19 | { 20 | return new Membership 21 | { 22 | Id = 2, 23 | Project = new IdentifiableName { Id = 2, Name = "Project 2" }, 24 | User = new IdentifiableName { Id = 2, Name = "User 2" }, 25 | Roles = [new MembershipRole { Id = 2, Name = "Manager", Inherited = true }] 26 | }; 27 | } 28 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/MyAccountCustomFieldTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public class MyAccountCustomFieldTests : BaseEqualityTests 6 | { 7 | protected override MyAccountCustomField CreateSampleInstance() 8 | { 9 | return new MyAccountCustomField 10 | { 11 | Id = 1, 12 | Name = "Test Field", 13 | Value = "Test Value", 14 | }; 15 | } 16 | 17 | protected override MyAccountCustomField CreateDifferentInstance() 18 | { 19 | return new MyAccountCustomField 20 | { 21 | Id = 2, 22 | Name = "Different Field", 23 | Value = "Different Value", 24 | }; 25 | } 26 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/MyAccountTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class MyAccountTests : BaseEqualityTests 6 | { 7 | protected override MyAccount CreateSampleInstance() 8 | { 9 | return new MyAccount 10 | { 11 | Id = 1, 12 | Login = "testaccount", 13 | FirstName = "Test", 14 | LastName = "Account", 15 | Email = "test@example.com", 16 | CreatedOn = new DateTime(2023, 1, 1).Date, 17 | LastLoginOn = new DateTime(2023, 1, 1).Date, 18 | ApiKey = "abc123", 19 | CustomFields = [ 20 | new MyAccountCustomField() { Value = "Value 1" } 21 | ] 22 | }; 23 | } 24 | 25 | protected override MyAccount CreateDifferentInstance() 26 | { 27 | return new MyAccount 28 | { 29 | Id = 2, 30 | Login = "differentaccount", 31 | FirstName = "Different", 32 | LastName = "Account", 33 | Email = "different@example.com", 34 | CreatedOn = new DateTime(2023, 1, 2).Date, 35 | LastLoginOn = new DateTime(2023, 1, 2).Date, 36 | ApiKey = "xyz789" 37 | }; 38 | } 39 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/NewsTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class NewsTests : BaseEqualityTests 6 | { 7 | protected override News CreateSampleInstance() 8 | { 9 | return new News 10 | { 11 | Id = 1, 12 | Project = new IdentifiableName { Id = 1, Name = "Project 1" }, 13 | Author = new IdentifiableName { Id = 1, Name = "Author 1" }, 14 | Title = "Test News", 15 | Summary = "Test Summary", 16 | Description = "Test Description", 17 | CreatedOn = new DateTime(2023, 1, 1, 0, 0, 0).Date, 18 | Comments = [new NewsComment { Id = 1, Content = "Test Comment" }] 19 | }; 20 | } 21 | 22 | protected override News CreateDifferentInstance() 23 | { 24 | return new News 25 | { 26 | Id = 2, 27 | Project = new IdentifiableName { Id = 2, Name = "Project 2" }, 28 | Author = new IdentifiableName { Id = 2, Name = "Author 2" }, 29 | Title = "Different News", 30 | Summary = "Different Summary", 31 | Description = "Different Description", 32 | CreatedOn = new DateTime(2023, 1, 2).Date, 33 | Comments = [new NewsComment { Id = 2, Content = "Different Comment" }] 34 | }; 35 | } 36 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/PermissionTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class PermissionTests : BaseEqualityTests 6 | { 7 | protected override Permission CreateSampleInstance() 8 | { 9 | return new Permission 10 | { 11 | Info = "add_issues" 12 | }; 13 | } 14 | 15 | protected override Permission CreateDifferentInstance() 16 | { 17 | return new Permission 18 | { 19 | Info = "edit_issues" 20 | }; 21 | } 22 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/ProjectMembershipTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class ProjectMembershipTests : BaseEqualityTests 6 | { 7 | protected override ProjectMembership CreateSampleInstance() 8 | { 9 | return new ProjectMembership 10 | { 11 | Id = 1, 12 | Project = new IdentifiableName { Id = 1, Name = "Project 1" }, 13 | User = new IdentifiableName { Id = 1, Name = "User 1" }, 14 | Roles = [new MembershipRole { Id = 1, Name = "Developer" }] 15 | }; 16 | } 17 | 18 | protected override ProjectMembership CreateDifferentInstance() 19 | { 20 | return new ProjectMembership 21 | { 22 | Id = 2, 23 | Project = new IdentifiableName { Id = 2, Name = "Project 2" }, 24 | User = new IdentifiableName { Id = 2, Name = "User 2" }, 25 | Roles = [new MembershipRole { Id = 2, Name = "Manager" }] 26 | }; 27 | } 28 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/QueryTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class QueryTests : BaseEqualityTests 6 | { 7 | protected override Query CreateSampleInstance() 8 | { 9 | return new Query 10 | { 11 | Id = 1, 12 | Name = "Test Query", 13 | IsPublic = true, 14 | ProjectId = 1 15 | }; 16 | } 17 | 18 | protected override Query CreateDifferentInstance() 19 | { 20 | return new Query 21 | { 22 | Id = 2, 23 | Name = "Different Query", 24 | IsPublic = false, 25 | ProjectId = 2 26 | }; 27 | } 28 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/RoleTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class RoleTests : BaseEqualityTests 6 | { 7 | protected override Role CreateSampleInstance() 8 | { 9 | return new Role 10 | { 11 | Id = 1, 12 | Name = "Developer", 13 | Permissions = 14 | [ 15 | new Permission { Info = "add_issues" }, 16 | new Permission { Info = "edit_issues" } 17 | ], 18 | IsAssignable = true 19 | }; 20 | } 21 | 22 | protected override Role CreateDifferentInstance() 23 | { 24 | return new Role 25 | { 26 | Id = 2, 27 | Name = "Manager", 28 | Permissions = 29 | [ 30 | new Permission { Info = "manage_project" } 31 | ], 32 | IsAssignable = false 33 | }; 34 | } 35 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/SearchTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class SearchTests : BaseEqualityTests 6 | { 7 | protected override Search CreateSampleInstance() 8 | { 9 | return new Search 10 | { 11 | Id = 1, 12 | Title = "Test Search", 13 | Type = "issue", 14 | Url = "http://example.com/search", 15 | Description = "Test Description", 16 | DateTime = new DateTime(2023, 1, 1).Date 17 | }; 18 | } 19 | 20 | protected override Search CreateDifferentInstance() 21 | { 22 | return new Search 23 | { 24 | Id = 2, 25 | Title = "Different Search", 26 | Type = "wiki", 27 | Url = "http://example.com/different", 28 | Description = "Different Description", 29 | DateTime = new DateTime(2023, 1, 2).Date 30 | }; 31 | } 32 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/TimeEntryActivityTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class TimeEntryActivityTests : BaseEqualityTests 6 | { 7 | protected override TimeEntryActivity CreateSampleInstance() 8 | { 9 | return new TimeEntryActivity 10 | { 11 | Id = 1, 12 | Name = "Development", 13 | IsDefault = true, 14 | IsActive = true 15 | }; 16 | } 17 | 18 | protected override TimeEntryActivity CreateDifferentInstance() 19 | { 20 | return new TimeEntryActivity 21 | { 22 | Id = 2, 23 | Name = "Testing", 24 | IsDefault = false, 25 | IsActive = false 26 | }; 27 | } 28 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/TimeEntryTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class TimeEntryTests : BaseEqualityTests 6 | { 7 | protected override TimeEntry CreateSampleInstance() 8 | { 9 | return new TimeEntry 10 | { 11 | Id = 1, 12 | Project = new IdentifiableName { Id = 1, Name = "Project 1" }, 13 | Issue = new IdentifiableName { Id = 1, Name = "Issue 1" }, 14 | User = new IdentifiableName { Id = 1, Name = "User 1" }, 15 | Activity = new IdentifiableName { Id = 1, Name = "Development" }, 16 | Hours = (decimal)8.0, 17 | Comments = "Work done", 18 | SpentOn = new DateTime(2023, 1, 1).Date, 19 | CreatedOn = new DateTime(2023, 1, 1).Date, 20 | UpdatedOn = new DateTime(2023, 1, 1).Date, 21 | CustomFields = 22 | [ 23 | new IssueCustomField 24 | { 25 | Id = 1, 26 | Name = "Field 1", 27 | Values = 28 | [ 29 | new CustomFieldValue("value") 30 | ] 31 | } 32 | ] 33 | }; 34 | } 35 | 36 | protected override TimeEntry CreateDifferentInstance() 37 | { 38 | return new TimeEntry 39 | { 40 | Id = 2, 41 | Project = new IdentifiableName { Id = 2, Name = "Project 2" }, 42 | Issue = new IdentifiableName { Id = 2, Name = "Issue 2" }, 43 | User = new IdentifiableName { Id = 2, Name = "User 2" }, 44 | Activity = new IdentifiableName { Id = 2, Name = "Testing" }, 45 | Hours = (decimal)4.0, 46 | Comments = "Different work", 47 | SpentOn = new DateTime(2023, 1, 2).Date, 48 | CreatedOn = new DateTime(2023, 1, 2).Date, 49 | UpdatedOn = new DateTime(2023, 1, 2).Date 50 | }; 51 | } 52 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/TrackerCoreFieldTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class TrackerCoreFieldTests : BaseEqualityTests 6 | { 7 | protected override TrackerCoreField CreateSampleInstance() 8 | { 9 | return new TrackerCoreField("Developer"); 10 | } 11 | 12 | protected override TrackerCoreField CreateDifferentInstance() 13 | { 14 | return new TrackerCoreField("Admin"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/TrackerCustomFieldTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class TrackerCustomFieldTests : BaseEqualityTests 6 | { 7 | protected override IdentifiableName CreateSampleInstance() 8 | { 9 | return new TrackerCustomField 10 | { 11 | Id = 1, 12 | Name = "Test Field" 13 | }; 14 | } 15 | 16 | protected override IdentifiableName CreateDifferentInstance() 17 | { 18 | return new TrackerCustomField 19 | { 20 | Id = 2, 21 | Name = "Different Field" 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/UploadTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class UploadTests : BaseEqualityTests 6 | { 7 | protected override Upload CreateSampleInstance() 8 | { 9 | return new Upload 10 | { 11 | Token = "abc123", 12 | FileName = "test.pdf", 13 | ContentType = "application/pdf", 14 | Description = "Test Upload" 15 | }; 16 | } 17 | 18 | protected override Upload CreateDifferentInstance() 19 | { 20 | return new Upload 21 | { 22 | Token = "xyz789", 23 | FileName = "different.pdf", 24 | ContentType = "application/pdf", 25 | Description = "Different Upload" 26 | }; 27 | } 28 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/UserGroupTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class UserGroupTests : BaseEqualityTests 6 | { 7 | protected override IdentifiableName CreateSampleInstance() 8 | { 9 | return new UserGroup 10 | { 11 | Id = 1, 12 | Name = "Test Group" 13 | }; 14 | } 15 | 16 | protected override IdentifiableName CreateDifferentInstance() 17 | { 18 | return new UserGroup 19 | { 20 | Id = 2, 21 | Name = "Different Group" 22 | }; 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/UserTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class UserTests : BaseEqualityTests 6 | { 7 | protected override User CreateSampleInstance() 8 | { 9 | return new User 10 | { 11 | Id = 1, 12 | Login = "testuser", 13 | FirstName = "Test", 14 | LastName = "User", 15 | Email = "test@example.com", 16 | CreatedOn = new DateTime(2023, 1, 1).Date, 17 | LastLoginOn = new DateTime(2023, 1, 1).Date, 18 | ApiKey = "abc123", 19 | Status = UserStatus.StatusActive, 20 | IsAdmin = false, 21 | CustomFields = 22 | [ 23 | new IssueCustomField 24 | { 25 | Id = 1, 26 | Name = "Field 1", 27 | Values = 28 | [ 29 | new CustomFieldValue("Value 1") 30 | ] 31 | } 32 | ], 33 | Memberships = 34 | [ 35 | new Membership 36 | { 37 | Id = 1, 38 | Project = new IdentifiableName { Id = 1, Name = "Project 1" } 39 | } 40 | ], 41 | Groups = 42 | [ 43 | new UserGroup { Id = 1, Name = "Group 1" } 44 | ] 45 | }; 46 | } 47 | 48 | protected override User CreateDifferentInstance() 49 | { 50 | return new User 51 | { 52 | Id = 2, 53 | Login = "differentuser", 54 | FirstName = "Different", 55 | LastName = "User", 56 | Email = "different@example.com", 57 | CreatedOn = new DateTime(2023, 1, 2).Date, 58 | LastLoginOn = new DateTime(2023, 1, 2).Date, 59 | ApiKey = "xyz789", 60 | Status = UserStatus.StatusLocked, 61 | IsAdmin = true 62 | }; 63 | } 64 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/VersionTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | using Version = Redmine.Net.Api.Types.Version; 3 | 4 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 5 | 6 | public sealed class VersionTests : BaseEqualityTests 7 | { 8 | protected override Version CreateSampleInstance() 9 | { 10 | return new Version 11 | { 12 | Id = 1, 13 | Project = new IdentifiableName { Id = 1, Name = "Project 1" }, 14 | Name = "1.0.0", 15 | Description = "First Release", 16 | Status = VersionStatus.Open, 17 | DueDate = new DateTime(2023, 12, 31).Date, 18 | CreatedOn = new DateTime(2023, 1, 1).Date, 19 | UpdatedOn = new DateTime(2023, 1, 1).Date, 20 | Sharing = VersionSharing.None, 21 | CustomFields = 22 | [ 23 | new IssueCustomField 24 | { 25 | Id = 1, Name = "Field 1", Values = [new CustomFieldValue("Value 1")] 26 | } 27 | ] 28 | }; 29 | } 30 | 31 | protected override Version CreateDifferentInstance() 32 | { 33 | return new Version 34 | { 35 | Id = 2, 36 | Project = new IdentifiableName { Id = 2, Name = "Project 2" }, 37 | Name = "2.0.0", 38 | Description = "Second Release", 39 | Status = VersionStatus.Closed, 40 | DueDate = new DateTime(2024, 12, 31).Date, 41 | CreatedOn = new DateTime(2023, 1, 2).Date, 42 | UpdatedOn = new DateTime(2023, 1, 2).Date, 43 | Sharing = VersionSharing.System 44 | }; 45 | } 46 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/WatcherTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class WatcherTests : BaseEqualityTests 6 | { 7 | protected override Watcher CreateSampleInstance() 8 | { 9 | return new Watcher 10 | { 11 | Id = 1, 12 | }; 13 | } 14 | 15 | protected override Watcher CreateDifferentInstance() 16 | { 17 | return new Watcher 18 | { 19 | Id = 2, 20 | }; 21 | } 22 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Equality/WikiPageTests.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Types; 2 | 3 | namespace Padi.DotNet.RedmineAPI.Tests.Equality; 4 | 5 | public sealed class WikiPageTests : BaseEqualityTests 6 | { 7 | protected override WikiPage CreateSampleInstance() 8 | { 9 | return new WikiPage 10 | { 11 | Id = 1, 12 | Title = "Home Page", 13 | Text = "Welcome to the wiki", 14 | Version = 1, 15 | Author = new IdentifiableName { Id = 1, Name = "Author 1" }, 16 | Comments = "Initial version", 17 | CreatedOn = new DateTime(2023, 1, 1), 18 | UpdatedOn = new DateTime(2023, 1, 1), 19 | Attachments = 20 | [ 21 | new Attachment 22 | { 23 | Id = 1, 24 | FileName = "doc.pdf", 25 | FileSize = 1024, 26 | Author = new IdentifiableName { Id = 1, Name = "Author 1" } 27 | } 28 | ] 29 | }; 30 | } 31 | 32 | protected override WikiPage CreateDifferentInstance() 33 | { 34 | return new WikiPage 35 | { 36 | Id = 2, 37 | Title = "Different Page", 38 | Text = "Different content", 39 | Version = 2, 40 | Author = new IdentifiableName { Id = 2, Name = "Author 2" }, 41 | Comments = "Updated version", 42 | CreatedOn = new DateTime(2023, 1, 2), 43 | UpdatedOn = new DateTime(2023, 1, 2) 44 | }; 45 | } 46 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Infrastructure/Collections/JsonRedmineSerializerCollection.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; 2 | using Xunit; 3 | 4 | namespace Padi.DotNet.RedmineAPI.Tests.Infrastructure.Collections; 5 | 6 | [CollectionDefinition(Constants.JsonRedmineSerializerCollection)] 7 | public sealed class JsonRedmineSerializerCollection : ICollectionFixture { } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Infrastructure/Collections/XmlRedmineSerializerCollection.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; 2 | using Xunit; 3 | 4 | namespace Padi.DotNet.RedmineAPI.Tests.Infrastructure.Collections; 5 | 6 | [CollectionDefinition(Constants.XmlRedmineSerializerCollection)] 7 | public sealed class XmlRedmineSerializerCollection : ICollectionFixture { } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Infrastructure/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace Padi.DotNet.RedmineAPI.Tests.Infrastructure; 2 | 3 | public static class Constants 4 | { 5 | public const string XmlRedmineSerializerCollection = "XmlRedmineSerializerCollection"; 6 | public const string JsonRedmineSerializerCollection = "JsonRedmineSerializerCollection"; 7 | public const string RedmineCollection = "RedmineCollection"; 8 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Infrastructure/Fixtures/JsonSerializerFixture.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Serialization; 2 | using Redmine.Net.Api.Serialization.Json; 3 | 4 | namespace Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; 5 | 6 | public sealed class JsonSerializerFixture 7 | { 8 | internal IRedmineSerializer Serializer { get; private set; } = new JsonRedmineSerializer(); 9 | 10 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Infrastructure/Fixtures/RedmineApiUrlsFixture.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Redmine.Net.Api.Net.Internal; 3 | 4 | namespace Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; 5 | 6 | public sealed class RedmineApiUrlsFixture 7 | { 8 | internal string Format { get; private set; } 9 | 10 | public RedmineApiUrlsFixture() 11 | { 12 | SetMimeTypeJson(); 13 | SetMimeTypeXml(); 14 | 15 | Sut = new RedmineApiUrls(Format); 16 | } 17 | 18 | internal RedmineApiUrls Sut { get; } 19 | 20 | [Conditional("DEBUG_JSON")] 21 | private void SetMimeTypeJson() 22 | { 23 | Format = "json"; 24 | } 25 | 26 | [Conditional("DEBUG_XML")] 27 | private void SetMimeTypeXml() 28 | { 29 | Format = "xml"; 30 | } 31 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Infrastructure/Fixtures/XmlSerializerFixture.cs: -------------------------------------------------------------------------------- 1 | using Redmine.Net.Api.Serialization; 2 | using Redmine.Net.Api.Serialization.Xml; 3 | 4 | namespace Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; 5 | 6 | public sealed class XmlSerializerFixture 7 | { 8 | internal IRedmineSerializer Serializer { get; private set; } = new XmlRedmineSerializer(); 9 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Infrastructure/Order/CaseOrder.cs: -------------------------------------------------------------------------------- 1 | #if !(NET20 || NET40) 2 | using System.Collections.Concurrent; 3 | using System.Reflection; 4 | using Xunit.Abstractions; 5 | using Xunit.Sdk; 6 | 7 | namespace Padi.DotNet.RedmineAPI.Tests.Infrastructure.Order 8 | { 9 | /// 10 | /// Custom xUnit test case orderer that uses the OrderAttribute 11 | /// 12 | public sealed class CaseOrderer : ITestCaseOrderer 13 | { 14 | // public const string TYPE_NAME = "redmine.net.api.Tests.Infrastructure.CaseOrderer"; 15 | // public const string ASSEMBLY_NAME = "redmine-net-api.Tests"; 16 | 17 | private static readonly ConcurrentDictionary> QueuedTests = new ConcurrentDictionary>(); 18 | 19 | public IEnumerable OrderTestCases(IEnumerable testCases) 20 | where TTestCase : ITestCase 21 | { 22 | return testCases.OrderBy(GetOrder); 23 | } 24 | 25 | private static int GetOrder(TTestCase testCase) 26 | where TTestCase : ITestCase 27 | { 28 | // Enqueue the test name. 29 | QueuedTests 30 | .GetOrAdd(testCase.TestMethod.TestClass.Class.Name,key => new ConcurrentQueue()) 31 | .Enqueue(testCase.TestMethod.Method.Name); 32 | 33 | // Order the test based on the attribute. 34 | var attr = testCase.TestMethod.Method 35 | .ToRuntimeMethod() 36 | .GetCustomAttribute(); 37 | 38 | return attr?.Index ?? 0; 39 | } 40 | } 41 | } 42 | #endif -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Infrastructure/Order/CollectionOrderer.cs: -------------------------------------------------------------------------------- 1 | #if !(NET20 || NET40) 2 | 3 | using System.Reflection; 4 | using Xunit; 5 | using Xunit.Abstractions; 6 | 7 | namespace Padi.DotNet.RedmineAPI.Tests.Infrastructure.Order 8 | { 9 | /// 10 | /// Custom xUnit test collection orderer that uses the OrderAttribute 11 | /// 12 | public sealed class CollectionOrderer : ITestCollectionOrderer 13 | { 14 | // public const string TYPE_NAME = "redmine.net.api.Tests.Infrastructure.CollectionOrderer"; 15 | // public const string ASSEMBLY_NAME = "redmine-net-api.Tests"; 16 | 17 | public IEnumerable OrderTestCollections(IEnumerable testCollections) 18 | { 19 | return testCollections.OrderBy(GetOrder); 20 | } 21 | 22 | /// 23 | /// Test collections are not bound to a specific class, however they 24 | /// are named by default with the type name as a suffix. We try to 25 | /// get the class name from the DisplayName and then use reflection to 26 | /// find the class and OrderAttribute. 27 | /// 28 | private static int GetOrder(ITestCollection testCollection) 29 | { 30 | var index = testCollection.DisplayName.LastIndexOf(' '); 31 | if (index <= -1) 32 | { 33 | return 0; 34 | } 35 | 36 | var className = testCollection.DisplayName.Substring(index + 1); 37 | var type = Type.GetType(className); 38 | if (type == null) 39 | { 40 | return 0; 41 | } 42 | 43 | var attr = type.GetCustomAttribute(); 44 | return attr?.Index ?? 0; 45 | } 46 | } 47 | } 48 | #endif -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Infrastructure/Order/OrderAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Padi.DotNet.RedmineAPI.Tests.Infrastructure.Order 2 | { 3 | public sealed class OrderAttribute : Attribute 4 | { 5 | public OrderAttribute(int index) 6 | { 7 | Index = index; 8 | } 9 | 10 | public int Index { get; private set; } 11 | } 12 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "redmine-net-api.Tests": { 4 | "commandName": "Project", 5 | "environmentVariables": { 6 | "BitVault410": "bitVault410", 7 | "Local410": "local410" 8 | } 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Serialization/Json/AttachmentTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure; 2 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; 3 | using Xunit; 4 | 5 | namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Json; 6 | 7 | [Collection(Constants.JsonRedmineSerializerCollection)] 8 | public class AttachmentTests(JsonSerializerFixture fixture) 9 | { 10 | [Fact] 11 | public void Should_Deserialize_Attachment() 12 | { 13 | const string input = """ 14 | { 15 | "attachment": { 16 | "id": 6243, 17 | "filename": "test.txt", 18 | "filesize": 124, 19 | "content_type": "text/plain", 20 | "description": "This is an attachment", 21 | "content_url": "http://localhost:3000/attachments/download/6243/test.txt", 22 | "author": {"name": "Jean-Philippe Lang", "id": 1}, 23 | "created_on": "2011-07-18T22:58:40+02:00" 24 | } 25 | } 26 | """; 27 | 28 | var output = fixture.Serializer.Deserialize(input); 29 | 30 | Assert.NotNull(output); 31 | Assert.Equal(6243, output.Id); 32 | Assert.Equal("test.txt", output.FileName); 33 | Assert.Equal(124, output.FileSize); 34 | Assert.Equal("text/plain", output.ContentType); 35 | Assert.Equal("This is an attachment", output.Description); 36 | Assert.Equal("http://localhost:3000/attachments/download/6243/test.txt", output.ContentUrl); 37 | Assert.Equal("Jean-Philippe Lang", output.Author.Name); 38 | Assert.Equal(1, output.Author.Id); 39 | Assert.Equal(new DateTime(2011, 7, 18, 20, 58, 40, DateTimeKind.Utc).ToLocalTime(), output.CreatedOn); 40 | } 41 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Serialization/Json/CustomFieldTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure; 2 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; 3 | using Redmine.Net.Api.Types; 4 | using Xunit; 5 | 6 | namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Json; 7 | 8 | [Collection(Constants.JsonRedmineSerializerCollection)] 9 | public sealed class CustomFieldTests(JsonSerializerFixture fixture) 10 | { 11 | [Fact] 12 | public void Should_Deserialize_CustomFields() 13 | { 14 | const string input = """ 15 | { 16 | "custom_fields": [ 17 | { 18 | "id": 1, 19 | "name": "Affected version", 20 | "customized_type": "issue", 21 | "field_format": "list", 22 | "regexp": null, 23 | "min_length": null, 24 | "max_length": null, 25 | "is_required": true, 26 | "is_filter": true, 27 | "searchable": true, 28 | "multiple": true, 29 | "default_value": null, 30 | "visible": false, 31 | "possible_values": [ 32 | { 33 | "value": "0.5.x" 34 | }, 35 | { 36 | "value": "0.6.x" 37 | } 38 | ] 39 | } 40 | ], 41 | "total_count": 1 42 | } 43 | """; 44 | 45 | var output = fixture.Serializer.DeserializeToPagedResults(input); 46 | 47 | Assert.NotNull(output); 48 | Assert.Equal(1, output.TotalItems); 49 | 50 | var customFields = output.Items.ToList(); 51 | Assert.Equal(1, customFields[0].Id); 52 | Assert.Equal("Affected version", customFields[0].Name); 53 | Assert.Equal("issue", customFields[0].CustomizedType); 54 | Assert.Equal("list", customFields[0].FieldFormat); 55 | Assert.True(customFields[0].IsRequired); 56 | Assert.True(customFields[0].IsFilter); 57 | Assert.True(customFields[0].Searchable); 58 | Assert.True(customFields[0].Multiple); 59 | Assert.False(customFields[0].Visible); 60 | 61 | var possibleValues = customFields[0].PossibleValues.ToList(); 62 | Assert.Equal(2, possibleValues.Count); 63 | Assert.Equal("0.5.x", possibleValues[0].Value); 64 | Assert.Equal("0.6.x", possibleValues[1].Value); 65 | } 66 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Serialization/Json/ErrorTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure; 2 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; 3 | using Redmine.Net.Api.Types; 4 | using Xunit; 5 | 6 | namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Json; 7 | 8 | [Collection(Constants.JsonRedmineSerializerCollection)] 9 | public class ErrorTests(JsonSerializerFixture fixture) 10 | { 11 | [Fact] 12 | public void Should_Deserialize_Errors() 13 | { 14 | const string input = """ 15 | { 16 | "errors":[ 17 | "First name can't be blank", 18 | "Email is invalid" 19 | ], 20 | "total_count":2 21 | } 22 | """; 23 | 24 | var output = fixture.Serializer.DeserializeToPagedResults(input); 25 | 26 | Assert.NotNull(output); 27 | Assert.Equal(2, output.TotalItems); 28 | 29 | var errors = output.Items.ToList(); 30 | Assert.Equal("First name can't be blank", errors[0].Info); 31 | Assert.Equal("Email is invalid", errors[1].Info); 32 | } 33 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Serialization/Json/IssueCustomFieldsTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure; 2 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; 3 | using Redmine.Net.Api.Types; 4 | using Xunit; 5 | 6 | namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Json; 7 | 8 | [Collection(Constants.JsonRedmineSerializerCollection)] 9 | public class IssueCustomFieldsTests(JsonSerializerFixture fixture) 10 | { 11 | [Fact] 12 | public void Should_Deserialize_Issue_With_CustomFields_With_Multiple_Values() 13 | { 14 | const string input = """ 15 | { 16 | "custom_fields":[ 17 | {"value":["1.0.1","1.0.2"],"multiple":true,"name":"Affected version","id":1}, 18 | {"value":"Fixed","name":"Resolution","id":2} 19 | ], 20 | "total_count":2 21 | } 22 | """; 23 | 24 | var output = fixture.Serializer.DeserializeToPagedResults(input); 25 | 26 | Assert.NotNull(output); 27 | Assert.Equal(2, output.TotalItems); 28 | 29 | var customFields = output.Items.ToList(); 30 | 31 | Assert.Equal(1, customFields[0].Id); 32 | Assert.Equal("Affected version", customFields[0].Name); 33 | Assert.True(customFields[0].Multiple); 34 | Assert.Equal(2, customFields[0].Values.Count); 35 | Assert.Equal("1.0.1", customFields[0].Values[0].Info); 36 | Assert.Equal("1.0.2", customFields[0].Values[1].Info); 37 | 38 | Assert.Equal(2, customFields[1].Id); 39 | Assert.Equal("Resolution", customFields[1].Name); 40 | Assert.False(customFields[1].Multiple); 41 | Assert.Equal("Fixed", customFields[1].Values[0].Info); 42 | } 43 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Serialization/Json/RoleTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure; 2 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; 3 | using Redmine.Net.Api.Types; 4 | using Xunit; 5 | 6 | namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Json; 7 | 8 | [Collection(Constants.JsonRedmineSerializerCollection)] 9 | public sealed class RoleTests(JsonSerializerFixture fixture) 10 | { 11 | [Fact] 12 | public void Should_Deserialize_Role_And_Permissions() 13 | { 14 | const string input = """ 15 | { 16 | "role": { 17 | "id": 5, 18 | "name": "Reporter", 19 | "assignable": true, 20 | "issues_visibility": "default", 21 | "time_entries_visibility": "all", 22 | "users_visibility": "all", 23 | "permissions": [ 24 | "view_issues", 25 | "add_issues", 26 | "add_issue_notes", 27 | ] 28 | } 29 | } 30 | """; 31 | 32 | var role = fixture.Serializer.Deserialize(input); 33 | 34 | Assert.Equal(5, role.Id); 35 | Assert.Equal("Reporter", role.Name); 36 | Assert.True(role.IsAssignable); 37 | Assert.Equal("default", role.IssuesVisibility); 38 | Assert.Equal("all", role.TimeEntriesVisibility); 39 | Assert.Equal("all", role.UsersVisibility); 40 | Assert.Equal(3, role.Permissions.Count); 41 | Assert.Equal("view_issues", role.Permissions[0].Info); 42 | Assert.Equal("add_issues", role.Permissions[1].Info); 43 | Assert.Equal("add_issue_notes", role.Permissions[2].Info); 44 | } 45 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Serialization/Json/UserTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure; 2 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; 3 | using Redmine.Net.Api.Types; 4 | using Xunit; 5 | 6 | namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Json; 7 | 8 | [Collection(Constants.JsonRedmineSerializerCollection)] 9 | public class UserTests(JsonSerializerFixture fixture) 10 | { 11 | [Fact] 12 | public void Should_Deserialize_User() 13 | { 14 | const string input = """ 15 | { 16 | "user":{ 17 | "id": 3, 18 | "login":"jplang", 19 | "firstname": "Jean-Philippe", 20 | "lastname":"Lang", 21 | "mail":"jp_lang@yahoo.fr", 22 | "created_on": "2007-09-28T00:16:04+02:00", 23 | "updated_on":"2010-08-01T18:05:45+02:00", 24 | "last_login_on":"2011-08-01T18:05:45+02:00", 25 | "passwd_changed_on": "2011-08-01T18:05:45+02:00", 26 | "api_key": "ebc3f6b781a6fb3f2b0a83ce0ebb80e0d585189d", 27 | "avatar_url": "", 28 | "status": 1 29 | } 30 | } 31 | """; 32 | 33 | var output = fixture.Serializer.Deserialize(input); 34 | 35 | Assert.NotNull(output); 36 | Assert.Equal(3, output.Id); 37 | Assert.Equal("jplang", output.Login); 38 | Assert.Equal("Jean-Philippe", output.FirstName); 39 | Assert.Equal("Lang", output.LastName); 40 | Assert.Equal("jp_lang@yahoo.fr", output.Email); 41 | Assert.Equal(new DateTime(2007, 9, 28, 0, 16, 4, DateTimeKind.Local).AddHours(1), output.CreatedOn); 42 | Assert.Equal(new DateTime(2010, 8, 1, 18, 5, 45, DateTimeKind.Local).AddHours(1), output.UpdatedOn); 43 | Assert.Equal(new DateTime(2011, 8, 1, 18, 5, 45, DateTimeKind.Local).AddHours(1), output.LastLoginOn); 44 | Assert.Equal(new DateTime(2011, 8, 1, 18, 5, 45, DateTimeKind.Local).AddHours(1), output.PasswordChangedOn); 45 | Assert.Equal("ebc3f6b781a6fb3f2b0a83ce0ebb80e0d585189d", output.ApiKey); 46 | Assert.Empty(output.AvatarUrl); 47 | Assert.Equal(UserStatus.StatusActive, output.Status); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Serialization/Xml/AttachmentTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure; 2 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; 3 | using Xunit; 4 | 5 | namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; 6 | 7 | [Collection(Constants.XmlRedmineSerializerCollection)] 8 | public class AttachmentTests(XmlSerializerFixture fixture) 9 | { 10 | [Fact] 11 | public void Should_Deserialize_Attachment() 12 | { 13 | const string input = """ 14 | 15 | 16 | 6243 17 | test.txt 18 | 124 19 | text/plain 20 | This is an attachment 21 | http://localhost:3000/attachments/download/6243/test.txt 22 | 23 | 2011-07-18T22:58:40+02:00 24 | 25 | """; 26 | 27 | var output = fixture.Serializer.Deserialize(input); 28 | 29 | Assert.NotNull(output); 30 | Assert.Equal(6243, output.Id); 31 | Assert.Equal("test.txt", output.FileName); 32 | Assert.Equal(124, output.FileSize); 33 | Assert.Equal("text/plain", output.ContentType); 34 | Assert.Equal("This is an attachment", output.Description); 35 | Assert.Equal("http://localhost:3000/attachments/download/6243/test.txt", output.ContentUrl); 36 | Assert.Equal("Jean-Philippe Lang", output.Author.Name); 37 | Assert.Equal(1, output.Author.Id); 38 | Assert.Equal(new DateTime(2011, 7, 18, 20, 58, 40, DateTimeKind.Utc).ToLocalTime(), output.CreatedOn); 39 | 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Serialization/Xml/CustomFieldTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure; 2 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; 3 | using Redmine.Net.Api.Types; 4 | using Xunit; 5 | 6 | namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; 7 | 8 | [Collection(Constants.XmlRedmineSerializerCollection)] 9 | public sealed class CustomFieldTests(XmlSerializerFixture fixture) 10 | { 11 | [Fact] 12 | public void Should_Deserialize_CustomFields() 13 | { 14 | const string input = """ 15 | 16 | 17 | 18 | 1 19 | Affected version 20 | issue 21 | list 22 | 23 | 24 | 25 | true 26 | true 27 | true 28 | true 29 | 30 | false 31 | 32 | 33 | 0.5.x 34 | 35 | 36 | 0.6.x 37 | 38 | 39 | 40 | 41 | """; 42 | 43 | var output = fixture.Serializer.DeserializeToPagedResults(input); 44 | 45 | Assert.NotNull(output); 46 | Assert.Equal(1, output.TotalItems); 47 | 48 | var customFields = output.Items.ToList(); 49 | Assert.Equal(1, customFields[0].Id); 50 | Assert.Equal("Affected version", customFields[0].Name); 51 | Assert.Equal("issue", customFields[0].CustomizedType); 52 | Assert.Equal("list", customFields[0].FieldFormat); 53 | Assert.True(customFields[0].IsRequired); 54 | Assert.True(customFields[0].IsFilter); 55 | Assert.True(customFields[0].Searchable); 56 | Assert.True(customFields[0].Multiple); 57 | Assert.False(customFields[0].Visible); 58 | 59 | var possibleValues = customFields[0].PossibleValues.ToList(); 60 | Assert.Equal(2, possibleValues.Count); 61 | Assert.Equal("0.5.x", possibleValues[0].Value); 62 | Assert.Equal("0.6.x", possibleValues[1].Value); 63 | } 64 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Serialization/Xml/ErrorTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure; 2 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; 3 | using Redmine.Net.Api.Types; 4 | using Xunit; 5 | 6 | namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; 7 | 8 | [Collection(Constants.XmlRedmineSerializerCollection)] 9 | public sealed class ErrorTests(XmlSerializerFixture fixture) 10 | { 11 | [Fact] 12 | public void Should_Deserialize_Errors() 13 | { 14 | const string input = """ 15 | 16 | First name can't be blank 17 | Email is invalid 18 | 19 | """; 20 | 21 | var output = fixture.Serializer.DeserializeToPagedResults(input); 22 | 23 | Assert.NotNull(output); 24 | Assert.Equal(2, output.TotalItems); 25 | 26 | var errors = output.Items.ToList(); 27 | Assert.Equal("First name can't be blank", errors[0].Info); 28 | Assert.Equal("Email is invalid", errors[1].Info); 29 | 30 | } 31 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Serialization/Xml/GroupTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure; 2 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; 3 | using Redmine.Net.Api.Types; 4 | using Xunit; 5 | 6 | namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; 7 | 8 | [Collection(Constants.XmlRedmineSerializerCollection)] 9 | public class GroupTests(XmlSerializerFixture fixture) 10 | { 11 | [Fact] 12 | public void Should_Deserialize_Group() 13 | { 14 | const string input = """ 15 | 16 | 20 17 | Developers 18 | 19 | 20 | 21 | 22 | 23 | """; 24 | 25 | var output = fixture.Serializer.Deserialize(input); 26 | Assert.NotNull(output); 27 | Assert.Equal(20, output.Id); 28 | Assert.Equal("Developers", output.Name); 29 | Assert.NotNull(output.Users); 30 | Assert.Equal(2, output.Users.Count); 31 | Assert.Equal("John Smith", output.Users[0].Name); 32 | Assert.Equal("Dave Loper", output.Users[1].Name); 33 | Assert.Equal(5, output.Users[0].Id); 34 | Assert.Equal(8, output.Users[1].Id); 35 | } 36 | 37 | [Fact] 38 | public void Should_Deserialize_Groups() 39 | { 40 | const string input = """ 41 | 42 | 43 | 44 | 53 45 | Managers 46 | 47 | 48 | 55 49 | Developers 50 | 51 | 52 | """; 53 | 54 | var output = fixture.Serializer.DeserializeToPagedResults(input); 55 | Assert.NotNull(output); 56 | Assert.Equal(2, output.TotalItems); 57 | 58 | var groups = output.Items.ToList(); 59 | Assert.Equal(53, groups[0].Id); 60 | Assert.Equal("Managers", groups[0].Name); 61 | 62 | Assert.Equal(55, groups[1].Id); 63 | Assert.Equal("Developers", groups[1].Name); 64 | } 65 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Serialization/Xml/IssueStatusTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure; 2 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; 3 | using Xunit; 4 | 5 | namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; 6 | 7 | [Collection(Constants.XmlRedmineSerializerCollection)] 8 | public class IssueStatusTests(XmlSerializerFixture fixture) 9 | { 10 | [Fact] 11 | public void Should_Deserialize_Issue_Statuses() 12 | { 13 | const string input = """ 14 | 15 | 16 | 17 | 1 18 | New 19 | false 20 | 21 | 22 | 2 23 | Closed 24 | true 25 | 26 | 27 | """; 28 | 29 | var output = fixture.Serializer.DeserializeToPagedResults(input); 30 | 31 | Assert.NotNull(output); 32 | Assert.Equal(2, output.TotalItems); 33 | 34 | var issueStatuses = output.Items.ToList(); 35 | Assert.Equal(2, issueStatuses.Count); 36 | 37 | Assert.Equal(1, issueStatuses[0].Id); 38 | Assert.Equal("New", issueStatuses[0].Name); 39 | Assert.False(issueStatuses[0].IsClosed); 40 | 41 | Assert.Equal(2, issueStatuses[1].Id); 42 | Assert.Equal("Closed", issueStatuses[1].Name); 43 | Assert.True(issueStatuses[1].IsClosed); 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Serialization/Xml/QueryTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure; 2 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; 3 | using Xunit; 4 | 5 | namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; 6 | 7 | [Collection(Constants.XmlRedmineSerializerCollection)] 8 | public class QueryTests(XmlSerializerFixture fixture) 9 | { 10 | [Fact] 11 | public void Should_Deserialize_Version() 12 | { 13 | const string input = """ 14 | 15 | 16 | 17 | 84 18 | Documentation issues 19 | true 20 | 1 21 | 22 | 23 | 1 24 | Open defects 25 | true 26 | 1 27 | 28 | 29 | """; 30 | 31 | var output = fixture.Serializer.DeserializeToPagedResults(input); 32 | 33 | Assert.NotNull(output); 34 | Assert.Equal(5, output.TotalItems); 35 | 36 | var queries = output.Items.ToList(); 37 | Assert.Equal(2, queries.Count); 38 | 39 | Assert.Equal(84, queries[0].Id); 40 | Assert.Equal("Documentation issues", queries[0].Name); 41 | Assert.True(queries[0].IsPublic); 42 | Assert.Equal(1, queries[0].ProjectId); 43 | 44 | Assert.Equal(1, queries[1].Id); 45 | Assert.Equal("Open defects", queries[1].Name); 46 | Assert.True(queries[1].IsPublic); 47 | Assert.Equal(1, queries[1].ProjectId); 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Serialization/Xml/SearchTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure; 2 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; 3 | using Redmine.Net.Api.Types; 4 | using Xunit; 5 | 6 | namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; 7 | 8 | [Collection(Constants.XmlRedmineSerializerCollection)] 9 | public sealed class SearchTests(XmlSerializerFixture fixture) 10 | { 11 | [Fact] 12 | public void Should_Deserialize_Search_Result() 13 | { 14 | const string input = """ 15 | 16 | 17 | 5 18 | Wiki: Wiki_Page_Name 19 | wiki-page 20 | http://www.redmine.org/projects/new_crm_dev/wiki/Wiki_Page_Name 21 | h1. Wiki Page Name wiki_keyword 22 | 2016-03-25T05:23:35Z 23 | 24 | 25 | 10 26 | Issue #10 (Closed): Issue_Title 27 | issue closed 28 | http://www.redmin.org/issues/10 29 | issue_keyword 30 | 2016-03-24T05:18:59Z 31 | 32 | 33 | """; 34 | 35 | var output = fixture.Serializer.DeserializeToPagedResults(input); 36 | 37 | Assert.NotNull(output); 38 | Assert.Equal(2, output.TotalItems); 39 | Assert.Equal(25, output.PageSize); 40 | 41 | var results = output.Items.ToList(); 42 | Assert.Equal(5, results[0].Id); 43 | Assert.Equal("Wiki: Wiki_Page_Name", results[0].Title); 44 | Assert.Equal("wiki-page", results[0].Type); 45 | Assert.Equal("http://www.redmine.org/projects/new_crm_dev/wiki/Wiki_Page_Name", results[0].Url); 46 | Assert.Equal("h1. Wiki Page Name wiki_keyword", results[0].Description); 47 | Assert.Equal(new DateTime(2016, 3, 25, 5, 23, 35, DateTimeKind.Utc).ToLocalTime(), results[0].DateTime); 48 | 49 | Assert.Equal(10, results[1].Id); 50 | Assert.Equal("Issue #10 (Closed): Issue_Title", results[1].Title); 51 | Assert.Equal("issue closed", results[1].Type); 52 | Assert.Equal("http://www.redmin.org/issues/10", results[1].Url); 53 | Assert.Equal("issue_keyword", results[1].Description); 54 | Assert.Equal(new DateTime(2016, 3, 24, 5, 18, 59, DateTimeKind.Utc).ToLocalTime(), results[1].DateTime); 55 | 56 | } 57 | } -------------------------------------------------------------------------------- /tests/redmine-net-api.Tests/Serialization/Xml/UploadTests.cs: -------------------------------------------------------------------------------- 1 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure; 2 | using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; 3 | using Xunit; 4 | 5 | namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; 6 | 7 | [Collection(Constants.XmlRedmineSerializerCollection)] 8 | public class UploadTests(XmlSerializerFixture fixture) 9 | { 10 | [Fact] 11 | public void Should_Deserialize_Upload() 12 | { 13 | const string input = """ 14 | 15 | 16 | #{token1} 17 | test1.txt 18 | 19 | """; 20 | 21 | var output = fixture.Serializer.Deserialize(input); 22 | 23 | Assert.NotNull(output); 24 | Assert.Equal("#{token1}", output.Token); 25 | Assert.Equal("test1.txt", output.FileName); 26 | } 27 | 28 | [Fact] 29 | public void Should_Deserialize_Uploads() 30 | { 31 | const string input = """ 32 | 33 | 34 | 35 | #{token1} 36 | test1.txt 37 | 38 | 39 | #{token2} 40 | test1.txt 41 | 42 | 43 | """; 44 | 45 | var output = fixture.Serializer.DeserializeToPagedResults(input); 46 | 47 | Assert.NotNull(output); 48 | Assert.Equal(2, output.TotalItems); 49 | 50 | var uploads = output.Items.ToList(); 51 | Assert.Equal(2, uploads.Count); 52 | 53 | Assert.Equal("#{token1}", uploads[0].Token); 54 | Assert.Equal("test1.txt", uploads[0].FileName); 55 | 56 | Assert.Equal("#{token2}", uploads[1].Token); 57 | Assert.Equal("test1.txt", uploads[1].FileName); 58 | 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /version.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | --------------------------------------------------------------------------------