├── .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